@rebasepro/server-core 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (300) hide show
  1. package/README.md +2 -2
  2. package/dist/api/ast-schema-editor.d.ts +21 -0
  3. package/dist/api/collections_for_test/callbacks_test_collection.d.ts +2 -0
  4. package/dist/api/errors.d.ts +53 -0
  5. package/dist/api/index.d.ts +7 -0
  6. package/dist/api/logs-routes.d.ts +37 -0
  7. package/dist/api/openapi-generator.d.ts +16 -0
  8. package/dist/api/rest/api-generator.d.ts +68 -0
  9. package/{src/api/rest/index.ts → dist/api/rest/index.d.ts} +0 -1
  10. package/dist/api/rest/query-parser.d.ts +6 -0
  11. package/dist/api/schema-editor-routes.d.ts +3 -0
  12. package/dist/api/types.d.ts +56 -0
  13. package/dist/auth/adapter-middleware.d.ts +43 -0
  14. package/dist/auth/admin-roles-route.d.ts +18 -0
  15. package/dist/auth/admin-user-ops.d.ts +79 -0
  16. package/dist/auth/admin-users-route.d.ts +28 -0
  17. package/dist/auth/api-keys/api-key-middleware.d.ts +39 -0
  18. package/{src/auth/api-keys/api-key-permission-guard.ts → dist/auth/api-keys/api-key-permission-guard.d.ts} +2 -34
  19. package/dist/auth/api-keys/api-key-routes.d.ts +20 -0
  20. package/dist/auth/api-keys/api-key-store.d.ts +35 -0
  21. package/{src/auth/api-keys/api-key-types.ts → dist/auth/api-keys/api-key-types.d.ts} +0 -6
  22. package/{src/auth/api-keys/index.ts → dist/auth/api-keys/index.d.ts} +2 -22
  23. package/dist/auth/apple-oauth.d.ts +30 -0
  24. package/{src/auth/auth-hooks.ts → dist/auth/auth-hooks.d.ts} +15 -73
  25. package/dist/auth/bitbucket-oauth.d.ts +11 -0
  26. package/dist/auth/builtin-auth-adapter.d.ts +55 -0
  27. package/dist/auth/cookie-utils.d.ts +32 -0
  28. package/{src/auth/crypto-utils.ts → dist/auth/crypto-utils.d.ts} +1 -16
  29. package/dist/auth/custom-auth-adapter.d.ts +39 -0
  30. package/dist/auth/discord-oauth.d.ts +14 -0
  31. package/dist/auth/facebook-oauth.d.ts +14 -0
  32. package/dist/auth/github-oauth.d.ts +15 -0
  33. package/dist/auth/gitlab-oauth.d.ts +13 -0
  34. package/dist/auth/google-oauth.d.ts +47 -0
  35. package/{src/auth/index.ts → dist/auth/index.d.ts} +4 -22
  36. package/{src/auth/interfaces.ts → dist/auth/interfaces.d.ts} +20 -89
  37. package/dist/auth/jwt.d.ts +67 -0
  38. package/dist/auth/linkedin-oauth.d.ts +18 -0
  39. package/dist/auth/magic-link-routes.d.ts +32 -0
  40. package/dist/auth/mfa-crypto.d.ts +23 -0
  41. package/dist/auth/mfa-routes.d.ts +7 -0
  42. package/dist/auth/mfa.d.ts +49 -0
  43. package/dist/auth/microsoft-oauth.d.ts +16 -0
  44. package/dist/auth/middleware.d.ts +158 -0
  45. package/dist/auth/password.d.ts +22 -0
  46. package/dist/auth/rate-limiter.d.ts +50 -0
  47. package/dist/auth/reset-password-admin.d.ts +29 -0
  48. package/dist/auth/rls-scope.d.ts +31 -0
  49. package/dist/auth/routes.d.ts +66 -0
  50. package/dist/auth/session-routes.d.ts +29 -0
  51. package/dist/auth/slack-oauth.d.ts +12 -0
  52. package/dist/auth/spotify-oauth.d.ts +12 -0
  53. package/dist/auth/twitter-oauth.d.ts +18 -0
  54. package/dist/collections/BackendCollectionRegistry.d.ts +13 -0
  55. package/dist/collections/loader.d.ts +5 -0
  56. package/dist/cron/cron-loader.d.ts +18 -0
  57. package/dist/cron/cron-routes.d.ts +14 -0
  58. package/dist/cron/cron-scheduler.d.ts +106 -0
  59. package/dist/cron/cron-store.d.ts +32 -0
  60. package/dist/cron/define-cron.d.ts +28 -0
  61. package/{src/cron/index.ts → dist/cron/index.d.ts} +1 -0
  62. package/dist/db/interfaces.d.ts +18 -0
  63. package/dist/dynamic-import-Dvh-K5fl.js +22 -0
  64. package/dist/dynamic-import-Dvh-K5fl.js.map +1 -0
  65. package/dist/email/index.d.ts +6 -0
  66. package/dist/email/smtp-email-service.d.ts +30 -0
  67. package/dist/email/templates.d.ts +50 -0
  68. package/{src/email/types.ts → dist/email/types.d.ts} +40 -36
  69. package/dist/env.d.ts +102 -0
  70. package/dist/functions/define-function.d.ts +55 -0
  71. package/dist/functions/function-loader.d.ts +18 -0
  72. package/dist/functions/function-routes.d.ts +10 -0
  73. package/{src/functions/index.ts → dist/functions/index.d.ts} +2 -0
  74. package/dist/history/history-routes.d.ts +23 -0
  75. package/{src/history/index.ts → dist/history/index.d.ts} +0 -1
  76. package/dist/index.d.ts +35 -0
  77. package/dist/index.es.js +18184 -0
  78. package/dist/index.es.js.map +1 -0
  79. package/dist/index.umd.js +48629 -0
  80. package/dist/index.umd.js.map +1 -0
  81. package/dist/init/docs.d.ts +4 -0
  82. package/dist/init/health.d.ts +2 -0
  83. package/dist/init/middlewares.d.ts +10 -0
  84. package/dist/init/shutdown.d.ts +65 -0
  85. package/dist/init/storage.d.ts +5 -0
  86. package/dist/init.d.ts +344 -0
  87. package/dist/jwt-BwIn8xmk.js +4200 -0
  88. package/dist/jwt-BwIn8xmk.js.map +1 -0
  89. package/dist/logger-BYU66ENZ.js +94 -0
  90. package/dist/logger-BYU66ENZ.js.map +1 -0
  91. package/dist/ms-DnYXB-Wd.js +162 -0
  92. package/dist/ms-DnYXB-Wd.js.map +1 -0
  93. package/dist/openapi-generator-Z9oYWLf_.js +586 -0
  94. package/dist/openapi-generator-Z9oYWLf_.js.map +1 -0
  95. package/dist/schema-editor-routes-D3ef8zu1.js +219 -0
  96. package/dist/schema-editor-routes-D3ef8zu1.js.map +1 -0
  97. package/dist/serve-spa.d.ts +36 -0
  98. package/dist/services/driver-registry.d.ts +78 -0
  99. package/dist/services/routed-realtime-service.d.ts +43 -0
  100. package/dist/services/webhook-service.d.ts +29 -0
  101. package/dist/singleton.d.ts +51 -0
  102. package/dist/src-B4OLmNVa.js +437 -0
  103. package/dist/src-B4OLmNVa.js.map +1 -0
  104. package/dist/src-B5WkP1Rv.js +24778 -0
  105. package/dist/src-B5WkP1Rv.js.map +1 -0
  106. package/dist/storage/GCSStorageController.d.ts +43 -0
  107. package/dist/storage/LocalStorageController.d.ts +46 -0
  108. package/dist/storage/S3StorageController.d.ts +40 -0
  109. package/dist/storage/image-transform.d.ts +50 -0
  110. package/{src/storage/index.ts → dist/storage/index.d.ts} +6 -21
  111. package/dist/storage/routes.d.ts +70 -0
  112. package/dist/storage/storage-registry.d.ts +78 -0
  113. package/dist/storage/tus-handler.d.ts +53 -0
  114. package/{src/storage/types.ts → dist/storage/types.d.ts} +32 -38
  115. package/dist/types/index.d.ts +11 -0
  116. package/dist/utils/dev-port.d.ts +45 -0
  117. package/dist/utils/dynamic-import.d.ts +25 -0
  118. package/dist/utils/logger.d.ts +31 -0
  119. package/dist/utils/logging.d.ts +9 -0
  120. package/dist/utils/request-id.d.ts +4 -0
  121. package/dist/utils/request-logger.d.ts +19 -0
  122. package/{src/utils/sql.ts → dist/utils/sql.d.ts} +5 -16
  123. package/package.json +49 -22
  124. package/coverage/clover.xml +0 -3739
  125. package/coverage/coverage-final.json +0 -31
  126. package/coverage/lcov-report/base.css +0 -224
  127. package/coverage/lcov-report/block-navigation.js +0 -87
  128. package/coverage/lcov-report/favicon.png +0 -0
  129. package/coverage/lcov-report/index.html +0 -266
  130. package/coverage/lcov-report/prettify.css +0 -1
  131. package/coverage/lcov-report/prettify.js +0 -2
  132. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  133. package/coverage/lcov-report/sorter.js +0 -210
  134. package/coverage/lcov-report/src/api/ast-schema-editor.ts.html +0 -952
  135. package/coverage/lcov-report/src/api/errors.ts.html +0 -472
  136. package/coverage/lcov-report/src/api/graphql/graphql-schema-generator.ts.html +0 -1069
  137. package/coverage/lcov-report/src/api/graphql/index.html +0 -116
  138. package/coverage/lcov-report/src/api/index.html +0 -176
  139. package/coverage/lcov-report/src/api/openapi-generator.ts.html +0 -565
  140. package/coverage/lcov-report/src/api/rest/api-generator.ts.html +0 -994
  141. package/coverage/lcov-report/src/api/rest/index.html +0 -131
  142. package/coverage/lcov-report/src/api/rest/query-parser.ts.html +0 -550
  143. package/coverage/lcov-report/src/api/schema-editor-routes.ts.html +0 -202
  144. package/coverage/lcov-report/src/api/server.ts.html +0 -823
  145. package/coverage/lcov-report/src/auth/admin-routes.ts.html +0 -973
  146. package/coverage/lcov-report/src/auth/index.html +0 -176
  147. package/coverage/lcov-report/src/auth/jwt.ts.html +0 -574
  148. package/coverage/lcov-report/src/auth/middleware.ts.html +0 -745
  149. package/coverage/lcov-report/src/auth/password.ts.html +0 -310
  150. package/coverage/lcov-report/src/auth/services.ts.html +0 -2074
  151. package/coverage/lcov-report/src/collections/index.html +0 -116
  152. package/coverage/lcov-report/src/collections/loader.ts.html +0 -232
  153. package/coverage/lcov-report/src/db/auth-schema.ts.html +0 -523
  154. package/coverage/lcov-report/src/db/data-transformer.ts.html +0 -1753
  155. package/coverage/lcov-report/src/db/entityService.ts.html +0 -700
  156. package/coverage/lcov-report/src/db/index.html +0 -146
  157. package/coverage/lcov-report/src/db/services/EntityFetchService.ts.html +0 -4048
  158. package/coverage/lcov-report/src/db/services/EntityPersistService.ts.html +0 -883
  159. package/coverage/lcov-report/src/db/services/RelationService.ts.html +0 -3121
  160. package/coverage/lcov-report/src/db/services/entity-helpers.ts.html +0 -442
  161. package/coverage/lcov-report/src/db/services/index.html +0 -176
  162. package/coverage/lcov-report/src/db/services/index.ts.html +0 -124
  163. package/coverage/lcov-report/src/generate-drizzle-schema-logic.ts.html +0 -1960
  164. package/coverage/lcov-report/src/index.html +0 -116
  165. package/coverage/lcov-report/src/services/driver-registry.ts.html +0 -631
  166. package/coverage/lcov-report/src/services/index.html +0 -131
  167. package/coverage/lcov-report/src/services/postgresDataDriver.ts.html +0 -3025
  168. package/coverage/lcov-report/src/storage/LocalStorageController.ts.html +0 -1189
  169. package/coverage/lcov-report/src/storage/S3StorageController.ts.html +0 -970
  170. package/coverage/lcov-report/src/storage/index.html +0 -161
  171. package/coverage/lcov-report/src/storage/storage-registry.ts.html +0 -646
  172. package/coverage/lcov-report/src/storage/types.ts.html +0 -451
  173. package/coverage/lcov-report/src/utils/drizzle-conditions.ts.html +0 -3082
  174. package/coverage/lcov-report/src/utils/index.html +0 -116
  175. package/coverage/lcov.info +0 -7179
  176. package/jest.config.cjs +0 -19
  177. package/src/api/ast-schema-editor.ts +0 -289
  178. package/src/api/collections_for_test/callbacks_test_collection.ts +0 -61
  179. package/src/api/errors.ts +0 -278
  180. package/src/api/graphql/graphql-schema-generator.ts +0 -383
  181. package/src/api/graphql/index.ts +0 -2
  182. package/src/api/index.ts +0 -11
  183. package/src/api/logs-routes.ts +0 -132
  184. package/src/api/openapi-generator.ts +0 -727
  185. package/src/api/rest/api-generator-count.test.ts +0 -126
  186. package/src/api/rest/api-generator.ts +0 -747
  187. package/src/api/rest/query-parser.ts +0 -300
  188. package/src/api/schema-editor-routes.ts +0 -41
  189. package/src/api/server.ts +0 -271
  190. package/src/api/types.ts +0 -104
  191. package/src/auth/adapter-middleware.ts +0 -114
  192. package/src/auth/admin-roles-route.ts +0 -36
  193. package/src/auth/admin-user-ops.ts +0 -236
  194. package/src/auth/admin-users-route.ts +0 -302
  195. package/src/auth/api-keys/api-key-middleware.ts +0 -133
  196. package/src/auth/api-keys/api-key-routes.ts +0 -193
  197. package/src/auth/api-keys/api-key-store.ts +0 -340
  198. package/src/auth/apple-oauth.ts +0 -122
  199. package/src/auth/bitbucket-oauth.ts +0 -84
  200. package/src/auth/builtin-auth-adapter.ts +0 -380
  201. package/src/auth/custom-auth-adapter.ts +0 -86
  202. package/src/auth/discord-oauth.ts +0 -85
  203. package/src/auth/facebook-oauth.ts +0 -74
  204. package/src/auth/github-oauth.ts +0 -112
  205. package/src/auth/gitlab-oauth.ts +0 -72
  206. package/src/auth/google-oauth.ts +0 -223
  207. package/src/auth/jwt.ts +0 -208
  208. package/src/auth/linkedin-oauth.ts +0 -83
  209. package/src/auth/magic-link-routes.ts +0 -167
  210. package/src/auth/mfa-crypto.ts +0 -91
  211. package/src/auth/mfa-routes.ts +0 -323
  212. package/src/auth/mfa.ts +0 -161
  213. package/src/auth/microsoft-oauth.ts +0 -90
  214. package/src/auth/middleware.ts +0 -369
  215. package/src/auth/password.ts +0 -77
  216. package/src/auth/rate-limiter.ts +0 -225
  217. package/src/auth/reset-password-admin.ts +0 -160
  218. package/src/auth/rls-scope.ts +0 -58
  219. package/src/auth/routes.ts +0 -802
  220. package/src/auth/session-routes.ts +0 -353
  221. package/src/auth/slack-oauth.ts +0 -73
  222. package/src/auth/spotify-oauth.ts +0 -69
  223. package/src/auth/twitter-oauth.ts +0 -128
  224. package/src/collections/BackendCollectionRegistry.ts +0 -20
  225. package/src/collections/loader.ts +0 -50
  226. package/src/cron/cron-loader.ts +0 -84
  227. package/src/cron/cron-routes.test.ts +0 -265
  228. package/src/cron/cron-routes.ts +0 -85
  229. package/src/cron/cron-scheduler.test.ts +0 -559
  230. package/src/cron/cron-scheduler.ts +0 -581
  231. package/src/cron/cron-store.ts +0 -166
  232. package/src/db/interfaces.ts +0 -60
  233. package/src/email/index.ts +0 -19
  234. package/src/email/smtp-email-service.ts +0 -123
  235. package/src/email/templates.ts +0 -470
  236. package/src/env.ts +0 -227
  237. package/src/functions/function-loader.ts +0 -116
  238. package/src/functions/function-routes.ts +0 -31
  239. package/src/history/history-routes.ts +0 -129
  240. package/src/index.ts +0 -70
  241. package/src/init/docs.ts +0 -47
  242. package/src/init/health.ts +0 -37
  243. package/src/init/middlewares.ts +0 -61
  244. package/src/init/shutdown.ts +0 -56
  245. package/src/init/storage.ts +0 -57
  246. package/src/init.ts +0 -1041
  247. package/src/serve-spa.ts +0 -89
  248. package/src/services/driver-registry.ts +0 -183
  249. package/src/services/routed-realtime-service.ts +0 -113
  250. package/src/services/webhook-service.ts +0 -155
  251. package/src/singleton.ts +0 -70
  252. package/src/storage/LocalStorageController.ts +0 -389
  253. package/src/storage/S3StorageController.ts +0 -300
  254. package/src/storage/image-transform.ts +0 -218
  255. package/src/storage/routes.ts +0 -368
  256. package/src/storage/storage-registry.ts +0 -188
  257. package/src/storage/tus-handler.ts +0 -315
  258. package/src/types/index.ts +0 -27
  259. package/src/utils/dev-port.ts +0 -196
  260. package/src/utils/logger.ts +0 -143
  261. package/src/utils/logging.ts +0 -40
  262. package/src/utils/request-id.ts +0 -40
  263. package/src/utils/request-logger.ts +0 -72
  264. package/test/api-generator.test.ts +0 -588
  265. package/test/api-key-permission-guard.test.ts +0 -144
  266. package/test/ast-schema-editor.test.ts +0 -89
  267. package/test/auth-middleware-hono.test.ts +0 -556
  268. package/test/auth-routes.test.ts +0 -1098
  269. package/test/backend-hooks-data.test.ts +0 -477
  270. package/test/custom-auth-adapter.test.ts +0 -189
  271. package/test/driver-registry.test.ts +0 -282
  272. package/test/email-templates.test.ts +0 -174
  273. package/test/env.test.ts +0 -128
  274. package/test/error-propagation.test.ts +0 -226
  275. package/test/errors-hono.test.ts +0 -133
  276. package/test/errors.test.ts +0 -155
  277. package/test/function-loader.test.ts +0 -127
  278. package/test/graphql-schema-generator.test.ts +0 -554
  279. package/test/jwt-security.test.ts +0 -182
  280. package/test/jwt.test.ts +0 -326
  281. package/test/mfa.test.ts +0 -197
  282. package/test/middleware.test.ts +0 -305
  283. package/test/multi-datasource-routing.test.ts +0 -113
  284. package/test/password.test.ts +0 -165
  285. package/test/query-parser.test.ts +0 -233
  286. package/test/rate-limiter.test.ts +0 -102
  287. package/test/reset-password-admin.test.ts +0 -113
  288. package/test/routed-realtime-service.test.ts +0 -86
  289. package/test/safe-compare.test.ts +0 -66
  290. package/test/singleton.test.ts +0 -59
  291. package/test/smtp-email-service.test.ts +0 -169
  292. package/test/storage-local.test.ts +0 -271
  293. package/test/storage-registry.test.ts +0 -282
  294. package/test/storage-routes.test.ts +0 -222
  295. package/test/storage-s3.test.ts +0 -304
  296. package/test/transform-auth-response.test.ts +0 -305
  297. package/test/webhook-service.test.ts +0 -260
  298. package/tsconfig.json +0 -54
  299. package/tsconfig.prod.json +0 -23
  300. package/vite.config.ts +0 -86
package/src/init.ts DELETED
@@ -1,1041 +0,0 @@
1
- import {
2
- AuthAdapter,
3
- BackendBootstrapper,
4
- BackendHooks,
5
- BootstrappedAuth,
6
- DatabaseAdapter,
7
- DataDriver,
8
- DataSourceDefinition,
9
- EntityCollection,
10
- HealthCheckResult,
11
- InitializedDriver,
12
- isPostgresCollection,
13
- isSQLAdmin,
14
- RealtimeProvider,
15
- SecurityRule
16
- } from "@rebasepro/types";
17
- import { createDataSourceRegistry, resolveDataSource } from "@rebasepro/common";
18
- import { BackendCollectionRegistry } from "./collections/BackendCollectionRegistry";
19
- import { loadCollectionsFromDirectory } from "./collections/loader";
20
- import { DEFAULT_DRIVER_ID, DefaultDriverRegistry, DriverRegistry } from "./services/driver-registry";
21
- import { createRoutedRealtimeService } from "./services/routed-realtime-service";
22
- import { Server } from "http";
23
-
24
- import { RestApiGenerator } from "./api/rest/api-generator";
25
- import { createAuthMiddleware } from "./auth/middleware";
26
- import { createAdapterAuthMiddleware } from "./auth/adapter-middleware";
27
- import { createBuiltinAuthAdapter } from "./auth/builtin-auth-adapter";
28
- import { errorHandler } from "./api/errors";
29
- import { Hono } from "hono";
30
- import { bodyLimit } from "hono/body-limit";
31
- import { HonoEnv } from "./api/types";
32
- import { configureLogLevel } from "./utils/logging";
33
- import { logger } from "./utils/logger";
34
- import { configureMiddlewares } from "./init/middlewares";
35
- import { initializeStorage } from "./init/storage";
36
- import { mountOpenApiDocs } from "./init/docs";
37
- import { createHealthCheck } from "./init/health";
38
- import { createShutdown } from "./init/shutdown";
39
- import { configureJwt, requireAdmin, requireAuth } from "./auth";
40
- import {
41
- BackendStorageConfig,
42
- createStorageRoutes,
43
- StorageController,
44
- StorageRegistry
45
- } from "./storage";
46
- import type { ApiKeyStore } from "./auth/api-keys/api-key-store";
47
- import { createApiKeyStore } from "./auth/api-keys/api-key-store";
48
- import { createApiKeyRoutes } from "./auth/api-keys/api-key-routes";
49
- import { createApiKeyRateLimiter } from "./auth/rate-limiter";
50
- import { createRebaseClient } from "@rebasepro/client";
51
-
52
- import { createHistoryRoutes } from "./history";
53
- import type { EmailService } from "./email";
54
- import { createEmailService, EmailConfig } from "./email";
55
- import type { OAuthProvider } from "./auth/interfaces";
56
- import type { AuthHooks } from "./auth/auth-hooks";
57
- import { _initRebase } from "./singleton";
58
-
59
- export interface RebaseAuthConfig {
60
- /**
61
- * The collection that represents auth users.
62
- *
63
- * When provided, this collection's underlying database table is used
64
- * for all auth operations (login, registration, password reset, etc.).
65
- *
66
- * Import the built-in default:
67
- * ```ts
68
- * import { defaultUsersCollection } from "@rebasepro/common";
69
- * auth: { collection: defaultUsersCollection, jwtSecret: "..." }
70
- * ```
71
- *
72
- * Or pass your own collection with the required auth fields
73
- * (email, passwordHash, displayName, etc.).
74
- */
75
- collection?: EntityCollection;
76
- jwtSecret?: string;
77
- accessExpiresIn?: string;
78
- refreshExpiresIn?: string;
79
- requireAuth?: boolean;
80
- allowRegistration?: boolean;
81
- /**
82
- * A static secret key for server-to-server / script authentication.
83
- *
84
- * When a request includes `Authorization: Bearer <serviceKey>`, it is
85
- * granted admin-level access without JWT verification. This is the
86
- * Rebase equivalent of a Firebase Service Account key.
87
- *
88
- * Generate with: `node -e "logger.info(require('crypto').randomBytes(48).toString('base64'))"`
89
- *
90
- * Set via `REBASE_SERVICE_KEY` in your `.env`.
91
- * Must be at least 32 characters.
92
- */
93
- serviceKey?: string;
94
- email?: EmailConfig;
95
- google?: { clientId: string; clientSecret?: string };
96
- linkedin?: { clientId: string; clientSecret: string };
97
- github?: { clientId: string; clientSecret: string };
98
- microsoft?: { clientId: string; clientSecret: string; tenantId?: string };
99
- apple?: { clientId: string; teamId: string; keyId: string; privateKey: string };
100
- facebook?: { clientId: string; clientSecret: string };
101
- twitter?: { clientId: string; clientSecret: string };
102
- discord?: { clientId: string; clientSecret: string };
103
- gitlab?: { clientId: string; clientSecret: string; baseUrl?: string };
104
- bitbucket?: { clientId: string; clientSecret: string };
105
- slack?: { clientId: string; clientSecret: string };
106
- spotify?: { clientId: string; clientSecret: string };
107
- defaultRole?: string;
108
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
109
- providers?: OAuthProvider<any>[];
110
- /**
111
- * Override specific parts of the built-in auth implementation.
112
- *
113
- * Each override replaces one piece of the default behavior while
114
- * keeping everything else intact. Unset overrides fall through
115
- * to the built-in defaults (scrypt passwords, standard validation, etc.).
116
- *
117
- * @example bcrypt passwords with a custom hash
118
- * ```ts
119
- * import bcrypt from "bcrypt";
120
- *
121
- * hooks: {
122
- * hashPassword: (pw) => bcrypt.hash(pw, 12),
123
- * verifyPassword: (pw, hash) => bcrypt.compare(pw, hash),
124
- * }
125
- * ```
126
- */
127
- hooks?: AuthHooks;
128
-
129
- /**
130
- * Enable magic link (passwordless email) authentication.
131
- * Requires email to be configured.
132
- */
133
- magicLink?: boolean;
134
-
135
- [key: string]: unknown;
136
- }
137
-
138
- export interface RebaseBackendConfig {
139
- collections?: EntityCollection[];
140
- collectionsDir?: string;
141
- server: Server;
142
- app: Hono<HonoEnv>;
143
- basePath?: string;
144
-
145
- /**
146
- * Declared data sources, shared with the frontend `<Rebase dataSources>`.
147
- *
148
- * Used to resolve each collection's engine (capabilities) and transport.
149
- * Collections on a `direct`/`custom` transport are client-only: the backend
150
- * still owns their schema/registry but does **not** generate server data
151
- * routes for them. Server-mediated sources (the default) need no entry.
152
- */
153
- dataSources?: DataSourceDefinition[];
154
-
155
- /**
156
- * Database bootstrappers.
157
- */
158
- bootstrappers?: BackendBootstrapper[];
159
- /**
160
- * Database adapter.
161
- *
162
- * When set, this takes precedence over `bootstrappers`.
163
- *
164
- * @example
165
- * ```ts
166
- * import { createPostgresAdapter } from "@rebasepro/server-postgresql";
167
- * database: createPostgresAdapter({ connection: db, schema }),
168
- * ```
169
- */
170
- database?: DatabaseAdapter;
171
-
172
- logging?: {
173
- level?: "error" | "warn" | "info" | "debug";
174
- };
175
-
176
- /**
177
- * Authentication configuration.
178
- *
179
- * Accepts **either**:
180
- * - `RebaseAuthConfig` — built-in configuration
181
- * - `AuthAdapter` — pluggable adapter for external auth (Clerk, Auth0, etc.)
182
- *
183
- * When a plain config object is provided, the built-in adapter is created
184
- * automatically from the bootstrapper's `initializeAuth()` result.
185
- */
186
- auth?: RebaseAuthConfig | AuthAdapter;
187
-
188
- /**
189
- * Storage configuration. Accepts:
190
- *
191
- * - A `BackendStorageConfig` object (`{ type: 'local' | 's3', ... }`)
192
- * - A `StorageController` instance (for custom providers like GCS, Azure, etc.)
193
- * - A `Record<string, ...>` of either, for multi-backend setups
194
- */
195
- storage?: BackendStorageConfig | StorageController | Record<string, BackendStorageConfig | StorageController>;
196
- history?: unknown;
197
- /**
198
- * Default security rules applied to any collection that does not define
199
- * its own `securityRules`. Opt-in — if not set, collections without
200
- * explicit rules remain unrestricted (beyond `requireAuth`).
201
- *
202
- * @example
203
- * ```ts
204
- * defaultSecurityRules: [
205
- * { operation: "select", access: "public" },
206
- * { operations: ["insert", "update", "delete"], roles: ["admin"] }
207
- * ]
208
- * ```
209
- */
210
- defaultSecurityRules?: SecurityRule[];
211
- enableSwagger?: boolean;
212
- functionsDir?: string;
213
- cronsDir?: string;
214
- /**
215
- * Enable/disable database persistence for cron job execution logs.
216
- * When set to false, cron jobs will run but logs will not be persisted to the database.
217
- * Default: true.
218
- */
219
- cronPersistence?: boolean;
220
- /**
221
- * Maximum request body size in bytes for API routes (default: 10MB).
222
- * Set to 0 to disable the global limit entirely.
223
- *
224
- * Note: Storage upload routes use their own limit from the storage config's
225
- * `maxFileSize` property (default: 50MB), which takes precedence over this.
226
- */
227
- maxBodySize?: number;
228
- /**
229
- * CSRF protection configuration. **Opt-in** — disabled by default.
230
- *
231
- * BaaS APIs are consumed by mobile apps, SPAs on different domains,
232
- * and CLI tools, so CSRF is intentionally not enabled unless you
233
- * explicitly configure it with allowed origins.
234
- *
235
- * @example
236
- * ```ts
237
- * csrf: { origin: ["https://myapp.com", "https://admin.myapp.com"] }
238
- * ```
239
- */
240
- csrf?: {
241
- /** Allowed origins for CSRF validation. */
242
- origin: string | string[] | ((origin: string) => boolean);
243
- };
244
- /**
245
- * Backend-level hooks for intercepting collection entity data
246
- * at the REST API boundary. These run server-side after database
247
- * operations and before API responses are sent.
248
- *
249
- * Complement the per-collection `EntityCallbacks` system which
250
- * handles collection CRUD operations.
251
- */
252
- hooks?: BackendHooks;
253
- }
254
-
255
- /**
256
- * Type guard to detect whether the `auth` config is an `AuthAdapter`
257
- * (has a `verifyRequest` method) vs a plain `RebaseAuthConfig` (plain object).
258
- */
259
- export function isAuthAdapter(auth: RebaseAuthConfig | AuthAdapter): auth is AuthAdapter {
260
- return typeof auth === "object" && auth !== null && "verifyRequest" in auth && typeof (auth as AuthAdapter).verifyRequest === "function";
261
- }
262
-
263
- /**
264
- * Type guard to detect whether `database` is a `DatabaseAdapter`.
265
- */
266
- export function isDatabaseAdapter(db: unknown): db is DatabaseAdapter {
267
- return typeof db === "object" && db !== null && "initializeDriver" in db && "type" in db && !("initializeAuth" in db);
268
- }
269
-
270
- /**
271
- * Resolve the `requireAuth` flag from either a `RebaseAuthConfig` or an `AuthAdapter`.
272
- *
273
- * - `RebaseAuthConfig` has an explicit `requireAuth` boolean
274
- * - `AuthAdapter` always implies auth is required (secure by default)
275
- * - If no auth config is provided at all, default to `true`
276
- */
277
- function resolveRequireAuth(auth?: RebaseAuthConfig | AuthAdapter): boolean {
278
- if (!auth) return true;
279
- if (isAuthAdapter(auth)) return true; // AuthAdapters are always secure-by-default
280
- return (auth as RebaseAuthConfig).requireAuth !== false;
281
- }
282
-
283
- export interface RebaseBackendInstance {
284
- driverRegistry: DriverRegistry;
285
- driver: DataDriver;
286
- realtimeServices: Record<string, RealtimeProvider>;
287
- realtimeService: RealtimeProvider;
288
- auth?: BootstrappedAuth;
289
- history?: unknown;
290
- storageRegistry?: StorageRegistry;
291
- storageController?: StorageController;
292
- collectionRegistry: BackendCollectionRegistry;
293
- cronScheduler?: import("./cron").CronScheduler;
294
-
295
- /**
296
- * Deep health check that verifies database connectivity.
297
- * Returns latency and component status.
298
- */
299
- healthCheck(): Promise<HealthCheckResult>;
300
-
301
- /**
302
- * Graceful shutdown helper for the BaaS instance.
303
- * Stops the cron scheduler and closes the HTTP server, allowing
304
- * in-flight requests to drain within the given timeout.
305
- *
306
- * @param timeoutMs - Maximum time (ms) to wait for drain before force-exit (default: 15000).
307
- * Pass 0 to skip the force-exit timer (useful in tests).
308
- */
309
- shutdown(timeoutMs?: number): Promise<void>;
310
- }
311
-
312
- export async function initializeRebaseBackend(config: RebaseBackendConfig): Promise<RebaseBackendInstance> {
313
- // No try/catch: let init errors propagate to the caller.
314
- // The app entry point (e.g. startServer()) should catch and process.exit(1).
315
- // Returning a fake instance hides critical failures and leads to silent data loss.
316
- return await _initializeRebaseBackend(config);
317
- }
318
-
319
- async function _initializeRebaseBackend(config: RebaseBackendConfig): Promise<RebaseBackendInstance> {
320
- if (config.logging?.level) {
321
- configureLogLevel(config.logging.level);
322
- } else {
323
- configureLogLevel();
324
- }
325
-
326
- logger.info("Initializing Rebase Backend");
327
-
328
- const basePath = config.basePath || "/api";
329
- const isProduction = process.env.NODE_ENV === "production";
330
-
331
- // Configure Hono middlewares (Request ID, body limit, CSRF, CORS warning, logging)
332
- configureMiddlewares(config.app, basePath, isProduction, config);
333
-
334
- const collectionRegistry = new BackendCollectionRegistry();
335
- // Declared data sources — drives engine resolution (capabilities) and the
336
- // server-vs-direct transport distinction. Set before collections register
337
- // so normalization can resolve each collection's engine.
338
- const dataSourceRegistry = createDataSourceRegistry(config.dataSources);
339
- collectionRegistry.setDataSources(dataSourceRegistry);
340
- let activeCollections = config.collections || [];
341
- if (config.collectionsDir && activeCollections.length === 0) {
342
- activeCollections = await loadCollectionsFromDirectory(config.collectionsDir);
343
- logger.info("Auto-discovered collections", {
344
- count: activeCollections.length,
345
- dir: config.collectionsDir
346
- });
347
- }
348
-
349
- // Apply default security rules to collections that don't define their own
350
- if (config.defaultSecurityRules?.length) {
351
- for (const collection of activeCollections) {
352
- if (isPostgresCollection(collection) && (!collection.securityRules || collection.securityRules.length === 0)) {
353
- collection.securityRules = config.defaultSecurityRules;
354
- }
355
- }
356
- logger.info("Default security rules applied to collections without explicit rules");
357
- }
358
-
359
- const realtimeServices: Record<string, RealtimeProvider> = {};
360
- const delegates: Record<string, DataDriver> = {};
361
-
362
- // ─── Resolve bootstrappers ───────────────────────────────────────────
363
- let bootstrappers: BackendBootstrapper[] = config.bootstrappers || [];
364
- if (config.database) {
365
- const dbAdapter = config.database;
366
- logger.info("Using DatabaseAdapter", { type: dbAdapter.type });
367
- const wrappedBootstrapper: BackendBootstrapper = {
368
- type: dbAdapter.type,
369
- initializeDriver: (initConfig: unknown) =>
370
- dbAdapter.initializeDriver(initConfig as import("@rebasepro/types").DatabaseAdapterInitConfig),
371
- initializeRealtime: dbAdapter.initializeRealtime
372
- ? (_config: unknown, driverResult: InitializedDriver) =>
373
- dbAdapter.initializeRealtime!(driverResult)
374
- : undefined,
375
- initializeAuth: dbAdapter.initializeAuth,
376
- initializeHistory: dbAdapter.initializeHistory,
377
- initializeWebsockets: dbAdapter.initializeWebsockets,
378
- getAdmin: dbAdapter.getAdmin,
379
- mountRoutes: dbAdapter.mountRoutes
380
- };
381
- bootstrappers = [wrappedBootstrapper];
382
- }
383
-
384
- if (bootstrappers.length === 0) {
385
- throw new Error("No bootstrappers or database adapter provided. Cannot initialize database drivers.");
386
- }
387
-
388
- let defaultDriverId = DEFAULT_DRIVER_ID;
389
-
390
- let defaultDriverResult: InitializedDriver | undefined = undefined;
391
-
392
- // 1. Initialize all drivers
393
- for (const bootstrapper of bootstrappers) {
394
- const b = bootstrapper;
395
- logger.info("Running bootstrapper for driver", { driverId: b.id || bootstrapper.type });
396
- if (b.isDefault) {
397
- defaultDriverId = b.id || bootstrapper.type;
398
- }
399
-
400
- const driverResult = await bootstrapper.initializeDriver({
401
- collections: activeCollections,
402
- collectionRegistry
403
- });
404
- delegates[b.id || bootstrapper.type] = driverResult.driver;
405
-
406
- if ((b.id || bootstrapper.type) === defaultDriverId || !defaultDriverResult) {
407
- defaultDriverResult = driverResult;
408
- }
409
-
410
- if (bootstrapper.initializeRealtime) {
411
- const realtime = await bootstrapper.initializeRealtime({}, driverResult);
412
- if (realtime) {
413
- realtimeServices[b.id || bootstrapper.type] = realtime;
414
- }
415
- }
416
- }
417
-
418
- const driverRegistry = DefaultDriverRegistry.create(delegates);
419
- activeCollections.forEach(collection => collectionRegistry.register(collection));
420
-
421
- const defaultDriver = driverRegistry.getOrDefault(defaultDriverId);
422
- if (!defaultDriver || !defaultDriverResult) {
423
- throw new Error("Default driver not initialized by bootstrappers");
424
- }
425
- const defaultBootstrapper = bootstrappers.find(b => b.id === defaultDriverId || b.type === defaultDriverId) || bootstrappers[0];
426
- const defaultRealtimeService = defaultDriverResult.realtimeProvider;
427
-
428
- // Resolve a collection path (e.g. "products", "authors/1/posts") to its
429
- // data-source key — shared by the data-driver router and the realtime
430
- // router. Falls back to the default key for unknown paths.
431
- const keyForCollectionPath = (collectionPath: string): string => {
432
- const slug = collectionPath.replace(/^\/+/, "").split("/")[0]?.split("?")[0];
433
- if (!slug) return DEFAULT_DRIVER_ID;
434
- const collection = collectionRegistry.get(slug) ?? collectionRegistry.getCollectionByPath(slug);
435
- if (!collection) return DEFAULT_DRIVER_ID;
436
- return resolveDataSource(collection, dataSourceRegistry).key;
437
- };
438
-
439
- // ── Data-source misconfiguration check ────────────────────────────────
440
- // A server-transport collection whose resolved data-source key has no
441
- // registered driver delegate would silently fall back to the default
442
- // driver — i.e. land in the wrong database. Warn loudly so this surfaces
443
- // at boot rather than as mysterious data going to the wrong engine.
444
- {
445
- const unresolved = new Map<string, string[]>();
446
- const nonRlsEngines = new Set<string>();
447
- for (const collection of activeCollections) {
448
- const ds = resolveDataSource(collection, dataSourceRegistry);
449
- if (ds.transport !== "server") continue; // direct/custom are client-only
450
- // Server engines without row-level security enforce authorization
451
- // only at the application layer — surface this so it isn't a
452
- // silent assumption. (The default Postgres engine supports RLS.)
453
- if (!ds.capabilities.supportsRLS) nonRlsEngines.add(ds.engine);
454
- if (ds.key === DEFAULT_DRIVER_ID) continue; // always maps to the default
455
- if (!driverRegistry.has(ds.key)) {
456
- const slugs = unresolved.get(ds.key) ?? [];
457
- slugs.push(collection.slug ?? collection.name ?? "?");
458
- unresolved.set(ds.key, slugs);
459
- }
460
- }
461
- for (const [key, slugs] of unresolved) {
462
- logger.warn(
463
- `[DataSource] No driver registered for data source "${key}" ` +
464
- `(used by: ${slugs.join(", ")}). These collections will fall back to the ` +
465
- `default driver "${defaultDriverId}" — register a bootstrapper with this id, ` +
466
- `or mark the data source as a direct/custom transport in \`dataSources\`.`
467
- );
468
- }
469
- for (const engine of nonRlsEngines) {
470
- logger.warn(
471
- `[DataSource] Engine "${engine}" does not support row-level security; ` +
472
- `authorization for its collections is enforced only at the application layer ` +
473
- `(authentication still applies). Ensure app-level checks or engine-native rules are in place.`
474
- );
475
- }
476
- }
477
-
478
- // 2. Initialize Auth & History via the default driver's bootstrapper
479
- let authConfigResult: BootstrappedAuth | undefined = undefined;
480
- let serviceKey: string | undefined;
481
- let authAdapter: AuthAdapter | undefined;
482
-
483
- if (config.auth) {
484
- if (isAuthAdapter(config.auth)) {
485
- // ── New path: User provided an AuthAdapter directly ──────────
486
- authAdapter = config.auth;
487
- serviceKey = authAdapter.serviceKey;
488
-
489
- if (authAdapter.initialize) {
490
- await authAdapter.initialize();
491
- }
492
-
493
- logger.info("Using AuthAdapter", { id: authAdapter.id });
494
-
495
- // Populate authConfigResult for backward compatibility
496
- // (the return type still exposes `auth?: BootstrappedAuth`)
497
- authConfigResult = {
498
- userService: authAdapter.userManagement ?? {}
499
- };
500
- } else {
501
- // ── RebaseAuthConfig — wrap in built-in adapter ──
502
- const safeAuthConfig = config.auth as RebaseAuthConfig;
503
-
504
- // Auto-discover the auth collection from activeCollections if not explicitly set
505
- if (!safeAuthConfig.collection) {
506
- const foundAuthCollection = activeCollections.find(c => {
507
- const isAuth = c.auth;
508
- return isAuth === true || (isAuth && typeof isAuth === "object" && isAuth.enabled === true);
509
- });
510
- if (foundAuthCollection) {
511
- safeAuthConfig.collection = foundAuthCollection;
512
- logger.info("Auto-discovered auth collection from collection definitions", { slug: foundAuthCollection.slug });
513
- }
514
- }
515
-
516
- // The built-in auth subsystem (users, sessions, repository) is
517
- // bootstrapped on the DEFAULT driver. If the auth collection is
518
- // routed to a non-default data source, login would read/write the
519
- // default engine while the collection's data views hit another —
520
- // a split-brain user store. Warn loudly.
521
- if (safeAuthConfig.collection) {
522
- const authDs = resolveDataSource(safeAuthConfig.collection, dataSourceRegistry);
523
- if (authDs.key !== DEFAULT_DRIVER_ID) {
524
- logger.warn(
525
- `[Auth] The auth collection "${safeAuthConfig.collection.slug}" is on data source ` +
526
- `"${authDs.key}", but the built-in auth system always uses the default data source. ` +
527
- `Move the auth collection to the default data source, or replace auth with an AuthAdapter ` +
528
- `that manages users in "${authDs.key}".`
529
- );
530
- }
531
- }
532
-
533
- // Extract the collection-level auth config (if `auth` is an object, not just `true`)
534
- const collectionAuth = safeAuthConfig.collection ? safeAuthConfig.collection.auth : undefined;
535
- const collectionAuthConfig = (typeof collectionAuth === "object" && collectionAuth !== null) ? collectionAuth : undefined;
536
- if (safeAuthConfig.jwtSecret) {
537
- configureJwt({
538
- secret: safeAuthConfig.jwtSecret,
539
- accessExpiresIn: safeAuthConfig.accessExpiresIn || "1h",
540
- refreshExpiresIn: safeAuthConfig.refreshExpiresIn || "30d"
541
- });
542
- }
543
-
544
- // ── Service Key Validation ───────────────────────────────────
545
- if (safeAuthConfig.serviceKey) {
546
- if (safeAuthConfig.serviceKey.length < 32) {
547
- throw new Error(
548
- "REBASE_SERVICE_KEY is too short. Must be at least 32 characters. " +
549
- "Generate one with: node -e \"logger.info(require('crypto').randomBytes(48).toString('base64'))\""
550
- );
551
- }
552
- serviceKey = safeAuthConfig.serviceKey;
553
- logger.info("Service key configured for script/server-to-server authentication");
554
- }
555
-
556
- if (defaultBootstrapper.initializeAuth) {
557
- logger.info("Bootstrapping authentication via driver protocol");
558
- authConfigResult = await defaultBootstrapper.initializeAuth(config.auth, defaultDriverResult);
559
-
560
- // The built-in auth adapter is created after OAuth providers
561
- // are resolved (below) so it only needs to be constructed once.
562
-
563
- logger.info("Authentication initialized");
564
- } else {
565
- logger.warn("Auth requested but default bootstrapper does not support initializeAuth");
566
- }
567
- }
568
- }
569
-
570
- let historyConfigResult: Record<string, unknown> | undefined = undefined;
571
- if (config.history) {
572
- if (defaultBootstrapper.initializeHistory) {
573
- logger.info("Bootstrapping entity history via driver protocol");
574
- historyConfigResult = await defaultBootstrapper.initializeHistory(config.history, defaultDriverResult);
575
-
576
- // Inject the historyService into the driver so saveEntity/deleteEntity can record history.
577
- // The driver was created during initializeDriver() (before history was initialized),
578
- // so we must set it retroactively here.
579
- if (historyConfigResult?.historyService && defaultDriverResult.internals) {
580
- const internals = defaultDriverResult.internals as Record<string, unknown>;
581
- const driver = internals.driver as Record<string, unknown> | undefined;
582
- if (driver && "historyService" in driver) {
583
- driver.historyService = historyConfigResult.historyService;
584
- }
585
- }
586
-
587
- logger.info("Entity history initialized");
588
- } else {
589
- logger.warn("History requested but default bootstrapper does not support initializeHistory");
590
- }
591
- }
592
-
593
- // 3. Initialize Storage
594
- const { storageRegistry, storageController } = initializeStorage(config.storage, isProduction);
595
-
596
- // basePath already resolved above
597
-
598
- // 4. Mount API Routes
599
- if (config.auth) {
600
- // ── Auth Capabilities Endpoint ───────────────────────────────────
601
- // Exposes adapter capabilities so the frontend knows what's available
602
- // (login form vs external redirect, OAuth providers, etc.)
603
- config.app.get(`${basePath}/auth/config`, async (c) => {
604
- const capabilities = await authAdapter!.getCapabilities();
605
- return c.json(capabilities);
606
- });
607
-
608
- if (!isAuthAdapter(config.auth)) {
609
- const safeAuthConfig = config.auth as RebaseAuthConfig;
610
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
611
- const oauthProviders: OAuthProvider<any>[] = [...(safeAuthConfig.providers || [])];
612
-
613
- // Resolve configured OAuth providers via data-driven registration.
614
- // Each entry maps a config key to its factory function name and required fields.
615
- const OAUTH_PROVIDERS: Array<{
616
- key: keyof RebaseAuthConfig;
617
- factory: string;
618
- requiredFields: string[];
619
- }> = [
620
- { key: "google", factory: "createGoogleProvider", requiredFields: ["clientId"] },
621
- { key: "linkedin", factory: "createLinkedinProvider", requiredFields: ["clientId", "clientSecret"] },
622
- { key: "github", factory: "createGitHubProvider", requiredFields: ["clientId", "clientSecret"] },
623
- { key: "microsoft", factory: "createMicrosoftProvider", requiredFields: ["clientId", "clientSecret"] },
624
- { key: "apple", factory: "createAppleProvider", requiredFields: ["clientId", "teamId", "keyId", "privateKey"] },
625
- { key: "facebook", factory: "createFacebookProvider", requiredFields: ["clientId", "clientSecret"] },
626
- { key: "twitter", factory: "createTwitterProvider", requiredFields: ["clientId", "clientSecret"] },
627
- { key: "discord", factory: "createDiscordProvider", requiredFields: ["clientId", "clientSecret"] },
628
- { key: "gitlab", factory: "createGitLabProvider", requiredFields: ["clientId", "clientSecret"] },
629
- { key: "bitbucket", factory: "createBitbucketProvider", requiredFields: ["clientId", "clientSecret"] },
630
- { key: "slack", factory: "createSlackProvider", requiredFields: ["clientId", "clientSecret"] },
631
- { key: "spotify", factory: "createSpotifyProvider", requiredFields: ["clientId", "clientSecret"] }
632
- ];
633
-
634
- for (const { key, factory, requiredFields } of OAUTH_PROVIDERS) {
635
- const providerConfig = safeAuthConfig[key] as Record<string, unknown> | undefined;
636
- if (providerConfig && requiredFields.every(f => Boolean(providerConfig[f]))) {
637
- const authModule = await import("./auth");
638
- const createFn = (authModule as unknown as Record<string, (cfg: unknown) => OAuthProvider<unknown>>)[factory];
639
- oauthProviders.push(createFn(providerConfig));
640
- }
641
- }
642
-
643
- // Re-create the built-in adapter with all resolved OAuth providers
644
- const reCollectionAuth = safeAuthConfig.collection ? safeAuthConfig.collection.auth : undefined;
645
- const collectionAuthConfig = (typeof reCollectionAuth === "object" && reCollectionAuth !== null) ? reCollectionAuth : undefined;
646
- authAdapter = createBuiltinAuthAdapter({
647
- authRepository: authConfigResult!.authRepository as import("./auth/interfaces").AuthRepository ?? authConfigResult!.userService as import("./auth/interfaces").AuthRepository,
648
- emailService: authConfigResult!.emailService as import("./email").EmailService,
649
- emailConfig: safeAuthConfig.email,
650
- allowRegistration: safeAuthConfig.allowRegistration ?? false,
651
- defaultRole: safeAuthConfig.defaultRole,
652
- oauthProviders,
653
- serviceKey,
654
- authHooks: safeAuthConfig.hooks,
655
- collectionAuthConfig,
656
- enableMagicLink: safeAuthConfig.magicLink ?? false
657
- });
658
- }
659
-
660
- // ── Mount auth & admin routes via the adapter ────────────────────
661
- if (authAdapter && authAdapter.createAuthRoutes) {
662
- const authRoutes = authAdapter.createAuthRoutes();
663
- if (authRoutes) {
664
- config.app.route(`${basePath}/auth`, authRoutes);
665
- logger.info("Auth routes mounted via adapter", { adapter: authAdapter.id });
666
- }
667
- }
668
-
669
- if (authAdapter && authAdapter.createAdminRoutes) {
670
- const adminRoutes = authAdapter.createAdminRoutes();
671
- if (adminRoutes) {
672
- config.app.route(`${basePath}/admin`, adminRoutes);
673
- logger.info("Admin routes mounted via adapter", { adapter: authAdapter.id });
674
- }
675
- }
676
- }
677
-
678
- // ─── API Key Store Bootstrap ──────────────────────────────────────────
679
- let apiKeyStore: ApiKeyStore | undefined;
680
- const apiKeyStoreResult = createApiKeyStore(defaultDriver);
681
- if (apiKeyStoreResult) {
682
- apiKeyStore = apiKeyStoreResult;
683
- await apiKeyStore.ensureTable();
684
- logger.info("Service API Keys initialized");
685
-
686
- // Mount API key admin routes
687
- const apiKeyRoutes = createApiKeyRoutes({
688
- store: apiKeyStore,
689
- serviceKey
690
- });
691
- config.app.route(`${basePath}/admin/api-keys`, apiKeyRoutes);
692
- logger.info("API key admin routes mounted", { path: `${basePath}/admin/api-keys` });
693
- }
694
-
695
- if (config.collectionsDir) {
696
- if (process.env.NODE_ENV !== "production") {
697
- const { createSchemaEditorRoutes } = await import("./api/schema-editor-routes");
698
- const schemaEditorRoutes = createSchemaEditorRoutes(config.collectionsDir);
699
-
700
- if (authAdapter && !isAuthAdapter(config.auth!)) {
701
- const safeAuth = config.auth as RebaseAuthConfig;
702
- if (safeAuth.requireAuth !== false && !!safeAuth.jwtSecret) {
703
- schemaEditorRoutes.use("/*", requireAuth, requireAdmin);
704
- }
705
- } else if (authAdapter) {
706
- // External auth adapter — still protect schema editor
707
- schemaEditorRoutes.use("/*", requireAuth, requireAdmin);
708
- }
709
-
710
- config.app.route(`${basePath}/schema-editor`, schemaEditorRoutes);
711
- logger.info("Schema Editor mounted", { path: `${basePath}/schema-editor` });
712
- }
713
- }
714
-
715
- if (storageController) {
716
- // Storage uploads get their own body limit, derived from the storage config's
717
- // maxFileSize (default 50MB), which overrides the global API body limit.
718
- const storageMaxSize = (
719
- config.storage && typeof config.storage === "object" && "type" in config.storage
720
- ? (config.storage as BackendStorageConfig).maxFileSize
721
- : undefined
722
- ) ?? 50 * 1024 * 1024;
723
-
724
- const storageRoutes = createStorageRoutes({
725
- controller: storageController,
726
- requireAuth: resolveRequireAuth(config.auth)
727
- });
728
-
729
- // Apply a permissive body limit specifically for the upload endpoint
730
- storageRoutes.use("/upload", bodyLimit({
731
- maxSize: storageMaxSize,
732
- onError: (c) => {
733
- return c.json({
734
- error: {
735
- message: `File too large. Maximum upload size is ${Math.round(storageMaxSize / 1024 / 1024)}MB.`,
736
- code: "PAYLOAD_TOO_LARGE"
737
- }
738
- }, 413);
739
- }
740
- }));
741
-
742
- config.app.route(`${basePath}/storage`, storageRoutes);
743
- }
744
-
745
- if (activeCollections.length > 0) {
746
- const dataRouter = new Hono<HonoEnv>();
747
- dataRouter.onError(errorHandler);
748
-
749
- // Secure by default: require auth when auth is configured.
750
- // Developers who intentionally want public data access (relying
751
- // entirely on Postgres RLS) must explicitly set `auth.requireAuth: false`.
752
- const dataRequireAuth = resolveRequireAuth(config.auth);
753
-
754
- if (!dataRequireAuth) {
755
- logger.warn(
756
- "Data routes running WITHOUT authentication enforcement. " +
757
- "Access control is fully delegated to Postgres RLS policies. " +
758
- "If no RLS policies exist, data is publicly accessible. " +
759
- "Set auth.requireAuth to true (or remove it) to require authentication."
760
- );
761
- }
762
-
763
- // Multi-data-source routing: when more than one database engine is
764
- // registered (e.g. Postgres + MongoDB in one instance), resolve the
765
- // delegate per request from the request's collection data source. The
766
- // auth middleware then scopes that delegate (RLS for Postgres, no-op
767
- // for engines without `withAuth()`) into the request context. For a
768
- // single-engine backend this is omitted — behaviour is unchanged.
769
- const dataPathMarker = `${basePath}/data/`;
770
- const resolveRequestDriver = (reqPath: string): DataDriver => {
771
- const i = reqPath.indexOf(dataPathMarker);
772
- const collectionPath = i >= 0 ? reqPath.slice(i + dataPathMarker.length) : reqPath;
773
- const key = keyForCollectionPath(collectionPath);
774
- // Use the authoritative default for the default key; otherwise the
775
- // named delegate, falling back to default if it isn't registered.
776
- if (!key || key === DEFAULT_DRIVER_ID) return defaultDriver;
777
- return driverRegistry.get(key) ?? defaultDriver;
778
- };
779
- const multiEngine = bootstrappers.length > 1;
780
- const resolveDriver = multiEngine ? ((c: { req: { path: string } }) => resolveRequestDriver(c.req.path)) : undefined;
781
-
782
- // Use adapter middleware when an AuthAdapter is available,
783
- // falling back to the built-in JWT middleware otherwise.
784
- if (authAdapter) {
785
- dataRouter.use("/*", createAdapterAuthMiddleware({
786
- adapter: authAdapter,
787
- driver: defaultDriver,
788
- resolveDriver,
789
- requireAuth: dataRequireAuth,
790
- apiKeyStore
791
- }));
792
- } else {
793
- dataRouter.use("/*", createAuthMiddleware({
794
- driver: defaultDriver,
795
- resolveDriver,
796
- requireAuth: dataRequireAuth,
797
- serviceKey,
798
- apiKeyStore
799
- }));
800
- }
801
-
802
- // Per-API-key rate limiting (no-op for non-API-key requests)
803
- if (apiKeyStore) {
804
- dataRouter.use("/*", createApiKeyRateLimiter());
805
- }
806
-
807
- // Mount history routes BEFORE the REST API subcollection catch-all so
808
- // that /:slug/:entityId/history is matched by the dedicated handler first.
809
- if (historyConfigResult && historyConfigResult.historyService) {
810
- const historyRoutes = createHistoryRoutes({
811
- historyService: historyConfigResult.historyService as import("./history/history-routes").HistoryService,
812
- registry: collectionRegistry,
813
- driver: defaultDriver
814
- });
815
- dataRouter.route("/", historyRoutes);
816
- }
817
-
818
- // Only generate server data routes for server-mediated collections.
819
- // Collections on a direct/custom transport are client-only — the
820
- // backend must not expose a (mis-engined) endpoint for them.
821
- const serverCollections = activeCollections.filter(
822
- (collection) => resolveDataSource(collection, dataSourceRegistry).transport === "server"
823
- );
824
-
825
- const restGenerator = new RestApiGenerator(
826
- serverCollections,
827
- defaultDriver,
828
- config.hooks?.data,
829
- authAdapter
830
- );
831
- dataRouter.route("/", restGenerator.generateRoutes());
832
-
833
- config.app.route(`${basePath}/data`, dataRouter);
834
- }
835
-
836
- // ── OpenAPI / Swagger ─────────────────────────────────────────────────
837
- await mountOpenApiDocs(config.app, basePath, config.enableSwagger, activeCollections, resolveRequireAuth(config.auth));
838
-
839
- // ─── Server-side singleton ────────────────────────────────────────────
840
- // Build the admin-level RebaseClient and expose it as the `rebase` singleton.
841
- // This client bypasses the network and uses Hono's internal request handler.
842
- // websocketUrl is explicitly empty to prevent opening a WebSocket connection.
843
- const serverClient = createRebaseClient({
844
- baseUrl: "http://localhost",
845
- apiPath: basePath,
846
- websocketUrl: "",
847
- token: serviceKey,
848
- fetch: async (input: RequestInfo | URL, init?: RequestInit) => {
849
- return await config.app.request(input as string | Request | URL, init);
850
- }
851
- });
852
-
853
- // Attach email service to the server client when configured.
854
- // The email service may come from the auth bootstrapper or from the auth config directly.
855
- let emailService: EmailService | undefined;
856
- if (authConfigResult?.emailService) {
857
- emailService = authConfigResult.emailService as EmailService;
858
- } else if (config.auth && !isAuthAdapter(config.auth) && (config.auth as RebaseAuthConfig).email) {
859
- emailService = createEmailService((config.auth as RebaseAuthConfig).email!);
860
- }
861
-
862
- if (emailService) {
863
- Object.assign(serverClient, { email: emailService });
864
- logger.info("Email service attached to singleton", { configured: emailService.isConfigured() });
865
-
866
- if (emailService.isConfigured() && typeof emailService.verifyConnection === "function") {
867
- emailService.verifyConnection().then((success) => {
868
- if (!success) {
869
- logger.warn("Warning: SMTP connection verification failed. Email delivery may fail.");
870
- } else {
871
- logger.info("SMTP connection verified successfully.");
872
- }
873
- }).catch((err) => {
874
- logger.warn("Warning: SMTP connection verification failed. Email delivery may fail.", { error: err });
875
- });
876
- }
877
- }
878
-
879
- // Attach raw SQL capability when the driver supports it (Postgres, MySQL).
880
- // Document databases (MongoDB, Firestore) won't have this.
881
- const driverAdmin = defaultBootstrapper.getAdmin?.(defaultDriverResult);
882
- if (isSQLAdmin(driverAdmin)) {
883
- Object.assign(serverClient, {
884
- sql: (query: string, options?: { database?: string; role?: string }) =>
885
- driverAdmin.executeSql(query, options)
886
- });
887
- logger.info("SQL capability attached to singleton");
888
- }
889
-
890
- _initRebase(serverClient);
891
- logger.info("Rebase singleton initialized");
892
-
893
- // Retroactively inject the server client into the driver so that
894
- // entity callbacks receive `context.client` at runtime.
895
- // The driver is created before the client (which depends on the mounted
896
- // Hono app), so we set it here, mirroring the historyService injection above.
897
- if (defaultDriverResult.internals) {
898
- const internals = defaultDriverResult.internals as Record<string, unknown>;
899
- const driver = internals.driver as Record<string, unknown> | undefined;
900
- if (driver && "client" in driver) {
901
- driver.client = serverClient;
902
- }
903
- }
904
-
905
- // 5. Mount Custom Functions
906
- if (config.functionsDir) {
907
- const { loadFunctionsFromDirectory } = await import("./functions/function-loader");
908
- const { createFunctionRoutes } = await import("./functions/function-routes");
909
-
910
- const loadedFunctions = await loadFunctionsFromDirectory(config.functionsDir);
911
-
912
- if (loadedFunctions.length > 0) {
913
- const functionsRouter = new Hono<HonoEnv>();
914
- functionsRouter.onError(errorHandler);
915
-
916
- // Custom functions follow the same auth policy as data routes.
917
- // Per-route auth can be further refined inside individual functions.
918
- const functionsRequireAuth = resolveRequireAuth(config.auth);
919
-
920
- // Use adapter middleware when available, fallback to built-in
921
- if (authAdapter) {
922
- functionsRouter.use("/*", createAdapterAuthMiddleware({
923
- adapter: authAdapter,
924
- driver: defaultDriver,
925
- requireAuth: functionsRequireAuth,
926
- apiKeyStore
927
- }));
928
- } else {
929
- functionsRouter.use("/*", createAuthMiddleware({
930
- driver: defaultDriver,
931
- requireAuth: functionsRequireAuth,
932
- serviceKey,
933
- apiKeyStore
934
- }));
935
- }
936
-
937
- const fnRoutes = createFunctionRoutes(loadedFunctions);
938
- functionsRouter.route("/", fnRoutes);
939
- config.app.route(`${basePath}/functions`, functionsRouter);
940
- logger.info("Mounted custom functions", {
941
- count: loadedFunctions.length,
942
- path: `${basePath}/functions`
943
- });
944
- }
945
- }
946
-
947
- // 6. Mount Cron Jobs
948
- let cronScheduler: import("./cron").CronScheduler | undefined;
949
- if (config.cronsDir) {
950
- const { loadCronJobsFromDirectory } = await import("./cron/cron-loader");
951
- const { CronScheduler } = await import("./cron/cron-scheduler");
952
- const { createCronRoutes } = await import("./cron/cron-routes");
953
- const { createCronStore } = await import("./cron/cron-store");
954
-
955
- const loadedCronJobs = await loadCronJobsFromDirectory(config.cronsDir);
956
-
957
- if (loadedCronJobs.length > 0) {
958
- cronScheduler = new CronScheduler();
959
-
960
- // The cron scheduler uses the same serverClient as the singleton.
961
- // ctx.client inside cron handlers IS the same `rebase` instance.
962
- cronScheduler.setClient(serverClient);
963
-
964
- cronScheduler.registerJobs(loadedCronJobs);
965
-
966
- // Attach database persistence if the driver supports SQL and persistence is enabled
967
- const admin = defaultBootstrapper.getAdmin?.(defaultDriverResult);
968
- const store = (admin && config.cronPersistence !== false) ? createCronStore(defaultDriver) : undefined;
969
- if (store) {
970
- await store.ensureTable();
971
- cronScheduler.setStore(store);
972
- }
973
-
974
- const cronRouter = new Hono<HonoEnv>();
975
-
976
- // Cron admin routes require authentication + admin role
977
- if (authAdapter && !isAuthAdapter(config.auth!)) {
978
- const safeAuth = config.auth as RebaseAuthConfig;
979
- if (safeAuth.requireAuth !== false && !!safeAuth.jwtSecret) {
980
- cronRouter.use("/*", requireAuth, requireAdmin);
981
- }
982
- } else if (authAdapter) {
983
- cronRouter.use("/*", requireAuth, requireAdmin);
984
- }
985
-
986
- cronRouter.route("/", createCronRoutes(cronScheduler));
987
- config.app.route(`${basePath}/cron`, cronRouter);
988
-
989
- // Start the scheduler
990
- cronScheduler.start();
991
-
992
- logger.info("Mounted cron jobs", {
993
- count: loadedCronJobs.length,
994
- path: `${basePath}/cron`
995
- });
996
- }
997
- }
998
-
999
- // With multiple realtime-capable engines, route subscriptions to the
1000
- // provider owning each collection (the realtime counterpart of the data
1001
- // router). The single WebSocket server is driven by this composite.
1002
- // Single-engine setups use the default provider unchanged.
1003
- const effectiveRealtimeService: RealtimeProvider = Object.keys(realtimeServices).length > 1
1004
- ? createRoutedRealtimeService({
1005
- providers: realtimeServices,
1006
- defaultKey: defaultDriverId,
1007
- resolveKey: keyForCollectionPath
1008
- })
1009
- : defaultRealtimeService as RealtimeProvider;
1010
-
1011
- if (defaultBootstrapper.initializeWebsockets && effectiveRealtimeService) {
1012
- await defaultBootstrapper.initializeWebsockets(config.server, effectiveRealtimeService, defaultDriver, config.auth, authAdapter);
1013
- }
1014
-
1015
- logger.info("Rebase Backend Initialized");
1016
-
1017
- // ── Deep Health Check ─────────────────────────────────────────────────
1018
- const healthCheck = createHealthCheck(defaultDriver);
1019
-
1020
- // ── Graceful Shutdown ─────────────────────────────────────────────────
1021
- const shutdown = createShutdown({
1022
- server: config.server,
1023
- cronScheduler,
1024
- realtimeServices
1025
- });
1026
-
1027
- return {
1028
- driverRegistry,
1029
- driver: defaultDriver,
1030
- realtimeServices,
1031
- realtimeService: effectiveRealtimeService,
1032
- auth: authConfigResult,
1033
- history: historyConfigResult,
1034
- storageRegistry,
1035
- storageController,
1036
- collectionRegistry,
1037
- cronScheduler,
1038
- healthCheck,
1039
- shutdown
1040
- };
1041
- }