@rebasepro/server-core 0.0.1-canary.000dc36

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 (305) hide show
  1. package/LICENSE +6 -0
  2. package/README.md +40 -0
  3. package/build-errors.txt +52 -0
  4. package/coverage/clover.xml +3739 -0
  5. package/coverage/coverage-final.json +31 -0
  6. package/coverage/lcov-report/base.css +224 -0
  7. package/coverage/lcov-report/block-navigation.js +87 -0
  8. package/coverage/lcov-report/favicon.png +0 -0
  9. package/coverage/lcov-report/index.html +266 -0
  10. package/coverage/lcov-report/prettify.css +1 -0
  11. package/coverage/lcov-report/prettify.js +2 -0
  12. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  13. package/coverage/lcov-report/sorter.js +210 -0
  14. package/coverage/lcov-report/src/api/ast-schema-editor.ts.html +952 -0
  15. package/coverage/lcov-report/src/api/errors.ts.html +472 -0
  16. package/coverage/lcov-report/src/api/graphql/graphql-schema-generator.ts.html +1069 -0
  17. package/coverage/lcov-report/src/api/graphql/index.html +116 -0
  18. package/coverage/lcov-report/src/api/index.html +176 -0
  19. package/coverage/lcov-report/src/api/openapi-generator.ts.html +565 -0
  20. package/coverage/lcov-report/src/api/rest/api-generator.ts.html +994 -0
  21. package/coverage/lcov-report/src/api/rest/index.html +131 -0
  22. package/coverage/lcov-report/src/api/rest/query-parser.ts.html +550 -0
  23. package/coverage/lcov-report/src/api/schema-editor-routes.ts.html +202 -0
  24. package/coverage/lcov-report/src/api/server.ts.html +823 -0
  25. package/coverage/lcov-report/src/auth/admin-routes.ts.html +973 -0
  26. package/coverage/lcov-report/src/auth/index.html +176 -0
  27. package/coverage/lcov-report/src/auth/jwt.ts.html +574 -0
  28. package/coverage/lcov-report/src/auth/middleware.ts.html +745 -0
  29. package/coverage/lcov-report/src/auth/password.ts.html +310 -0
  30. package/coverage/lcov-report/src/auth/services.ts.html +2074 -0
  31. package/coverage/lcov-report/src/collections/index.html +116 -0
  32. package/coverage/lcov-report/src/collections/loader.ts.html +232 -0
  33. package/coverage/lcov-report/src/db/auth-schema.ts.html +523 -0
  34. package/coverage/lcov-report/src/db/data-transformer.ts.html +1753 -0
  35. package/coverage/lcov-report/src/db/entityService.ts.html +700 -0
  36. package/coverage/lcov-report/src/db/index.html +146 -0
  37. package/coverage/lcov-report/src/db/services/EntityFetchService.ts.html +4048 -0
  38. package/coverage/lcov-report/src/db/services/EntityPersistService.ts.html +883 -0
  39. package/coverage/lcov-report/src/db/services/RelationService.ts.html +3121 -0
  40. package/coverage/lcov-report/src/db/services/entity-helpers.ts.html +442 -0
  41. package/coverage/lcov-report/src/db/services/index.html +176 -0
  42. package/coverage/lcov-report/src/db/services/index.ts.html +124 -0
  43. package/coverage/lcov-report/src/generate-drizzle-schema-logic.ts.html +1960 -0
  44. package/coverage/lcov-report/src/index.html +116 -0
  45. package/coverage/lcov-report/src/services/driver-registry.ts.html +631 -0
  46. package/coverage/lcov-report/src/services/index.html +131 -0
  47. package/coverage/lcov-report/src/services/postgresDataDriver.ts.html +3025 -0
  48. package/coverage/lcov-report/src/storage/LocalStorageController.ts.html +1189 -0
  49. package/coverage/lcov-report/src/storage/S3StorageController.ts.html +970 -0
  50. package/coverage/lcov-report/src/storage/index.html +161 -0
  51. package/coverage/lcov-report/src/storage/storage-registry.ts.html +646 -0
  52. package/coverage/lcov-report/src/storage/types.ts.html +451 -0
  53. package/coverage/lcov-report/src/utils/drizzle-conditions.ts.html +3082 -0
  54. package/coverage/lcov-report/src/utils/index.html +116 -0
  55. package/coverage/lcov.info +7179 -0
  56. package/dist/common/src/collections/CollectionRegistry.d.ts +56 -0
  57. package/dist/common/src/collections/index.d.ts +1 -0
  58. package/dist/common/src/data/buildRebaseData.d.ts +14 -0
  59. package/dist/common/src/index.d.ts +3 -0
  60. package/dist/common/src/util/builders.d.ts +57 -0
  61. package/dist/common/src/util/callbacks.d.ts +6 -0
  62. package/dist/common/src/util/collections.d.ts +11 -0
  63. package/dist/common/src/util/common.d.ts +2 -0
  64. package/dist/common/src/util/conditions.d.ts +26 -0
  65. package/dist/common/src/util/entities.d.ts +58 -0
  66. package/dist/common/src/util/enums.d.ts +3 -0
  67. package/dist/common/src/util/index.d.ts +16 -0
  68. package/dist/common/src/util/navigation_from_path.d.ts +34 -0
  69. package/dist/common/src/util/navigation_utils.d.ts +20 -0
  70. package/dist/common/src/util/parent_references_from_path.d.ts +6 -0
  71. package/dist/common/src/util/paths.d.ts +14 -0
  72. package/dist/common/src/util/permissions.d.ts +5 -0
  73. package/dist/common/src/util/references.d.ts +2 -0
  74. package/dist/common/src/util/relations.d.ts +22 -0
  75. package/dist/common/src/util/resolutions.d.ts +72 -0
  76. package/dist/common/src/util/storage.d.ts +24 -0
  77. package/dist/index-DXVBFp5V.js +37 -0
  78. package/dist/index-DXVBFp5V.js.map +1 -0
  79. package/dist/index.es.js +49249 -0
  80. package/dist/index.es.js.map +1 -0
  81. package/dist/index.umd.js +49283 -0
  82. package/dist/index.umd.js.map +1 -0
  83. package/dist/server-core/src/api/ast-schema-editor.d.ts +21 -0
  84. package/dist/server-core/src/api/collections_for_test/callbacks_test_collection.d.ts +2 -0
  85. package/dist/server-core/src/api/errors.d.ts +35 -0
  86. package/dist/server-core/src/api/graphql/graphql-schema-generator.d.ts +35 -0
  87. package/dist/server-core/src/api/graphql/index.d.ts +1 -0
  88. package/dist/server-core/src/api/index.d.ts +9 -0
  89. package/dist/server-core/src/api/openapi-generator.d.ts +16 -0
  90. package/dist/server-core/src/api/rest/api-generator.d.ts +76 -0
  91. package/dist/server-core/src/api/rest/index.d.ts +1 -0
  92. package/dist/server-core/src/api/rest/query-parser.d.ts +9 -0
  93. package/dist/server-core/src/api/schema-editor-routes.d.ts +3 -0
  94. package/dist/server-core/src/api/server.d.ts +40 -0
  95. package/dist/server-core/src/api/types.d.ts +90 -0
  96. package/dist/server-core/src/auth/admin-routes.d.ts +21 -0
  97. package/dist/server-core/src/auth/apple-oauth.d.ts +30 -0
  98. package/dist/server-core/src/auth/bitbucket-oauth.d.ts +11 -0
  99. package/dist/server-core/src/auth/discord-oauth.d.ts +14 -0
  100. package/dist/server-core/src/auth/facebook-oauth.d.ts +14 -0
  101. package/dist/server-core/src/auth/github-oauth.d.ts +15 -0
  102. package/dist/server-core/src/auth/gitlab-oauth.d.ts +13 -0
  103. package/dist/server-core/src/auth/google-oauth.d.ts +14 -0
  104. package/dist/server-core/src/auth/index.d.ts +23 -0
  105. package/dist/server-core/src/auth/interfaces.d.ts +309 -0
  106. package/dist/server-core/src/auth/jwt.d.ts +43 -0
  107. package/dist/server-core/src/auth/linkedin-oauth.d.ts +18 -0
  108. package/dist/server-core/src/auth/microsoft-oauth.d.ts +16 -0
  109. package/dist/server-core/src/auth/middleware.d.ts +81 -0
  110. package/dist/server-core/src/auth/password.d.ts +22 -0
  111. package/dist/server-core/src/auth/rate-limiter.d.ts +31 -0
  112. package/dist/server-core/src/auth/routes.d.ts +27 -0
  113. package/dist/server-core/src/auth/slack-oauth.d.ts +12 -0
  114. package/dist/server-core/src/auth/spotify-oauth.d.ts +12 -0
  115. package/dist/server-core/src/auth/twitter-oauth.d.ts +18 -0
  116. package/dist/server-core/src/bootstrappers/index.d.ts +0 -0
  117. package/dist/server-core/src/collections/BackendCollectionRegistry.d.ts +13 -0
  118. package/dist/server-core/src/collections/loader.d.ts +5 -0
  119. package/dist/server-core/src/cron/cron-loader.d.ts +17 -0
  120. package/dist/server-core/src/cron/cron-routes.d.ts +14 -0
  121. package/dist/server-core/src/cron/cron-scheduler.d.ts +106 -0
  122. package/dist/server-core/src/cron/cron-store.d.ts +32 -0
  123. package/dist/server-core/src/cron/index.d.ts +6 -0
  124. package/dist/server-core/src/db/interfaces.d.ts +18 -0
  125. package/dist/server-core/src/email/index.d.ts +6 -0
  126. package/dist/server-core/src/email/smtp-email-service.d.ts +25 -0
  127. package/dist/server-core/src/email/templates.d.ts +42 -0
  128. package/dist/server-core/src/email/types.d.ts +107 -0
  129. package/dist/server-core/src/functions/function-loader.d.ts +17 -0
  130. package/dist/server-core/src/functions/function-routes.d.ts +10 -0
  131. package/dist/server-core/src/functions/index.d.ts +3 -0
  132. package/dist/server-core/src/history/history-routes.d.ts +23 -0
  133. package/dist/server-core/src/history/index.d.ts +1 -0
  134. package/dist/server-core/src/index.d.ts +29 -0
  135. package/dist/server-core/src/init.d.ts +168 -0
  136. package/dist/server-core/src/serve-spa.d.ts +30 -0
  137. package/dist/server-core/src/services/driver-registry.d.ts +78 -0
  138. package/dist/server-core/src/singleton.d.ts +35 -0
  139. package/dist/server-core/src/storage/LocalStorageController.d.ts +46 -0
  140. package/dist/server-core/src/storage/S3StorageController.d.ts +36 -0
  141. package/dist/server-core/src/storage/index.d.ts +25 -0
  142. package/dist/server-core/src/storage/routes.d.ts +38 -0
  143. package/dist/server-core/src/storage/storage-registry.d.ts +78 -0
  144. package/dist/server-core/src/storage/types.d.ts +103 -0
  145. package/dist/server-core/src/types/index.d.ts +11 -0
  146. package/dist/server-core/src/utils/dev-port.d.ts +35 -0
  147. package/dist/server-core/src/utils/logger.d.ts +31 -0
  148. package/dist/server-core/src/utils/logging.d.ts +9 -0
  149. package/dist/server-core/src/utils/request-logger.d.ts +19 -0
  150. package/dist/server-core/src/utils/sql.d.ts +27 -0
  151. package/dist/types/src/controllers/analytics_controller.d.ts +7 -0
  152. package/dist/types/src/controllers/auth.d.ts +119 -0
  153. package/dist/types/src/controllers/client.d.ts +170 -0
  154. package/dist/types/src/controllers/collection_registry.d.ts +46 -0
  155. package/dist/types/src/controllers/customization_controller.d.ts +60 -0
  156. package/dist/types/src/controllers/data.d.ts +168 -0
  157. package/dist/types/src/controllers/data_driver.d.ts +195 -0
  158. package/dist/types/src/controllers/database_admin.d.ts +11 -0
  159. package/dist/types/src/controllers/dialogs_controller.d.ts +36 -0
  160. package/dist/types/src/controllers/effective_role.d.ts +4 -0
  161. package/dist/types/src/controllers/email.d.ts +34 -0
  162. package/dist/types/src/controllers/index.d.ts +18 -0
  163. package/dist/types/src/controllers/local_config_persistence.d.ts +20 -0
  164. package/dist/types/src/controllers/navigation.d.ts +213 -0
  165. package/dist/types/src/controllers/registry.d.ts +54 -0
  166. package/dist/types/src/controllers/side_dialogs_controller.d.ts +67 -0
  167. package/dist/types/src/controllers/side_entity_controller.d.ts +90 -0
  168. package/dist/types/src/controllers/snackbar.d.ts +24 -0
  169. package/dist/types/src/controllers/storage.d.ts +171 -0
  170. package/dist/types/src/index.d.ts +4 -0
  171. package/dist/types/src/rebase_context.d.ts +105 -0
  172. package/dist/types/src/types/backend.d.ts +536 -0
  173. package/dist/types/src/types/backend_hooks.d.ts +187 -0
  174. package/dist/types/src/types/builders.d.ts +15 -0
  175. package/dist/types/src/types/chips.d.ts +5 -0
  176. package/dist/types/src/types/collections.d.ts +857 -0
  177. package/dist/types/src/types/cron.d.ts +102 -0
  178. package/dist/types/src/types/data_source.d.ts +64 -0
  179. package/dist/types/src/types/entities.d.ts +145 -0
  180. package/dist/types/src/types/entity_actions.d.ts +98 -0
  181. package/dist/types/src/types/entity_callbacks.d.ts +173 -0
  182. package/dist/types/src/types/entity_link_builder.d.ts +7 -0
  183. package/dist/types/src/types/entity_overrides.d.ts +10 -0
  184. package/dist/types/src/types/entity_views.d.ts +59 -0
  185. package/dist/types/src/types/export_import.d.ts +21 -0
  186. package/dist/types/src/types/formex.d.ts +40 -0
  187. package/dist/types/src/types/index.d.ts +25 -0
  188. package/dist/types/src/types/locales.d.ts +4 -0
  189. package/dist/types/src/types/modify_collections.d.ts +5 -0
  190. package/dist/types/src/types/plugins.d.ts +282 -0
  191. package/dist/types/src/types/properties.d.ts +1148 -0
  192. package/dist/types/src/types/property_config.d.ts +70 -0
  193. package/dist/types/src/types/relations.d.ts +336 -0
  194. package/dist/types/src/types/slots.d.ts +262 -0
  195. package/dist/types/src/types/translations.d.ts +874 -0
  196. package/dist/types/src/types/user_management_delegate.d.ts +121 -0
  197. package/dist/types/src/types/websockets.d.ts +78 -0
  198. package/dist/types/src/users/index.d.ts +2 -0
  199. package/dist/types/src/users/roles.d.ts +22 -0
  200. package/dist/types/src/users/user.d.ts +46 -0
  201. package/history_diff.log +385 -0
  202. package/jest.config.cjs +16 -0
  203. package/package.json +86 -0
  204. package/scratch.ts +9 -0
  205. package/src/api/ast-schema-editor.ts +289 -0
  206. package/src/api/collections_for_test/callbacks_test_collection.ts +60 -0
  207. package/src/api/errors.ts +179 -0
  208. package/src/api/graphql/graphql-schema-generator.ts +336 -0
  209. package/src/api/graphql/index.ts +2 -0
  210. package/src/api/index.ts +11 -0
  211. package/src/api/openapi-generator.ts +715 -0
  212. package/src/api/rest/api-generator-count.test.ts +113 -0
  213. package/src/api/rest/api-generator.ts +573 -0
  214. package/src/api/rest/index.ts +2 -0
  215. package/src/api/rest/query-parser.ts +155 -0
  216. package/src/api/schema-editor-routes.ts +41 -0
  217. package/src/api/server.ts +249 -0
  218. package/src/api/types.ts +90 -0
  219. package/src/auth/admin-routes.ts +605 -0
  220. package/src/auth/apple-oauth.ts +120 -0
  221. package/src/auth/bitbucket-oauth.ts +82 -0
  222. package/src/auth/discord-oauth.ts +83 -0
  223. package/src/auth/facebook-oauth.ts +72 -0
  224. package/src/auth/github-oauth.ts +110 -0
  225. package/src/auth/gitlab-oauth.ts +70 -0
  226. package/src/auth/google-oauth.ts +48 -0
  227. package/src/auth/index.ts +34 -0
  228. package/src/auth/interfaces.ts +363 -0
  229. package/src/auth/jwt.ts +181 -0
  230. package/src/auth/linkedin-oauth.ts +81 -0
  231. package/src/auth/microsoft-oauth.ts +88 -0
  232. package/src/auth/middleware.ts +384 -0
  233. package/src/auth/password.ts +77 -0
  234. package/src/auth/rate-limiter.ts +133 -0
  235. package/src/auth/routes.ts +788 -0
  236. package/src/auth/slack-oauth.ts +71 -0
  237. package/src/auth/spotify-oauth.ts +67 -0
  238. package/src/auth/twitter-oauth.ts +120 -0
  239. package/src/bootstrappers/index.ts +1 -0
  240. package/src/collections/BackendCollectionRegistry.ts +20 -0
  241. package/src/collections/loader.ts +49 -0
  242. package/src/cron/cron-loader.ts +89 -0
  243. package/src/cron/cron-routes.test.ts +265 -0
  244. package/src/cron/cron-routes.ts +85 -0
  245. package/src/cron/cron-scheduler.test.ts +547 -0
  246. package/src/cron/cron-scheduler.ts +576 -0
  247. package/src/cron/cron-store.ts +163 -0
  248. package/src/cron/index.ts +6 -0
  249. package/src/db/interfaces.ts +60 -0
  250. package/src/email/index.ts +18 -0
  251. package/src/email/smtp-email-service.ts +91 -0
  252. package/src/email/templates.ts +388 -0
  253. package/src/email/types.ts +105 -0
  254. package/src/functions/function-loader.ts +119 -0
  255. package/src/functions/function-routes.ts +31 -0
  256. package/src/functions/index.ts +3 -0
  257. package/src/history/history-routes.ts +129 -0
  258. package/src/history/index.ts +2 -0
  259. package/src/index.ts +66 -0
  260. package/src/init.ts +737 -0
  261. package/src/serve-spa.ts +81 -0
  262. package/src/services/driver-registry.ts +182 -0
  263. package/src/singleton.test.ts +28 -0
  264. package/src/singleton.ts +70 -0
  265. package/src/storage/LocalStorageController.ts +365 -0
  266. package/src/storage/S3StorageController.ts +298 -0
  267. package/src/storage/index.ts +43 -0
  268. package/src/storage/routes.ts +264 -0
  269. package/src/storage/storage-registry.ts +187 -0
  270. package/src/storage/types.ts +134 -0
  271. package/src/types/index.ts +27 -0
  272. package/src/utils/dev-port.ts +176 -0
  273. package/src/utils/logger.ts +143 -0
  274. package/src/utils/logging.ts +38 -0
  275. package/src/utils/request-logger.ts +66 -0
  276. package/src/utils/sql.ts +38 -0
  277. package/test/admin-routes.test.ts +640 -0
  278. package/test/api-generator.test.ts +501 -0
  279. package/test/ast-schema-editor.test.ts +63 -0
  280. package/test/auth-middleware-hono.test.ts +556 -0
  281. package/test/auth-routes.test.ts +1047 -0
  282. package/test/backend-hooks-admin.test.ts +394 -0
  283. package/test/backend-hooks-data.test.ts +408 -0
  284. package/test/driver-registry.test.ts +282 -0
  285. package/test/error-propagation.test.ts +226 -0
  286. package/test/errors-hono.test.ts +133 -0
  287. package/test/errors.test.ts +155 -0
  288. package/test/jwt-security.test.ts +182 -0
  289. package/test/jwt.test.ts +324 -0
  290. package/test/middleware.test.ts +300 -0
  291. package/test/password.test.ts +165 -0
  292. package/test/query-parser.test.ts +263 -0
  293. package/test/rate-limiter.test.ts +102 -0
  294. package/test/safe-compare.test.ts +66 -0
  295. package/test/singleton.test.ts +59 -0
  296. package/test/storage-local.test.ts +271 -0
  297. package/test/storage-registry.test.ts +282 -0
  298. package/test/storage-routes.test.ts +222 -0
  299. package/test/storage-s3.test.ts +304 -0
  300. package/test-ast.ts +28 -0
  301. package/test.ts +6 -0
  302. package/test_output.txt +1133 -0
  303. package/tsconfig.json +49 -0
  304. package/tsconfig.prod.json +20 -0
  305. package/vite.config.ts +80 -0
package/src/init.ts ADDED
@@ -0,0 +1,737 @@
1
+ import { DataDriver, EntityCollection, BackendBootstrapper, BootstrappedAuth, RealtimeProvider, HealthCheckResult, InitializedDriver, isSQLAdmin, BackendHooks } from "@rebasepro/types";
2
+ import { BackendCollectionRegistry } from "./collections/BackendCollectionRegistry";
3
+ import { loadCollectionsFromDirectory } from "./collections/loader";
4
+ import { DriverRegistry, DEFAULT_DRIVER_ID, DefaultDriverRegistry } from "./services/driver-registry";
5
+ import { Server } from "http";
6
+
7
+ import { RestApiGenerator } from "./api/rest/api-generator";
8
+ import { createAuthMiddleware } from "./auth/middleware";
9
+ import { errorHandler } from "./api/errors";
10
+ import { Hono } from "hono";
11
+ import { bodyLimit } from "hono/body-limit";
12
+ import { csrf } from "hono/csrf";
13
+ import { HonoEnv } from "./api/types";
14
+ import { configureLogLevel } from "./utils/logging";
15
+ import { logger } from "./utils/logger";
16
+ import { requestLogger } from "./utils/request-logger";
17
+ import { createAdminRoutes, createAuthRoutes, requireAuth, requireAdmin, configureJwt } from "./auth";
18
+ import { createStorageController, createStorageRoutes, DEFAULT_STORAGE_ID, DefaultStorageRegistry, BackendStorageConfig, StorageController, StorageRegistry } from "./storage";
19
+ import { createRebaseClient } from "@rebasepro/client";
20
+ import { createHistoryRoutes } from "./history";
21
+ import { EmailConfig, createEmailService } from "./email";
22
+ import type { EmailService } from "./email";
23
+ import type { OAuthProvider } from "./auth/interfaces";
24
+ import { _initRebase } from "./singleton";
25
+
26
+ export interface RebaseAuthConfig {
27
+ jwtSecret?: string;
28
+ accessExpiresIn?: string;
29
+ refreshExpiresIn?: string;
30
+ requireAuth?: boolean;
31
+ allowRegistration?: boolean;
32
+ /**
33
+ * A static secret key for server-to-server / script authentication.
34
+ *
35
+ * When a request includes `Authorization: Bearer <serviceKey>`, it is
36
+ * granted admin-level access without JWT verification. This is the
37
+ * Rebase equivalent of a Firebase Service Account key.
38
+ *
39
+ * Generate with: `node -e "console.log(require('crypto').randomBytes(48).toString('base64'))"`
40
+ *
41
+ * Set via `REBASE_SERVICE_KEY` in your `.env`.
42
+ * Must be at least 32 characters.
43
+ */
44
+ serviceKey?: string;
45
+ email?: EmailConfig;
46
+ google?: { clientId: string };
47
+ linkedin?: { clientId: string; clientSecret: string };
48
+ github?: { clientId: string; clientSecret: string };
49
+ microsoft?: { clientId: string; clientSecret: string; tenantId?: string };
50
+ apple?: { clientId: string; teamId: string; keyId: string; privateKey: string };
51
+ facebook?: { clientId: string; clientSecret: string };
52
+ twitter?: { clientId: string; clientSecret: string };
53
+ discord?: { clientId: string; clientSecret: string };
54
+ gitlab?: { clientId: string; clientSecret: string; baseUrl?: string };
55
+ bitbucket?: { clientId: string; clientSecret: string };
56
+ slack?: { clientId: string; clientSecret: string };
57
+ spotify?: { clientId: string; clientSecret: string };
58
+ defaultRole?: string;
59
+ providers?: OAuthProvider<any>[];
60
+ [key: string]: unknown;
61
+ }
62
+
63
+ export interface RebaseBackendConfig {
64
+ collections?: EntityCollection[];
65
+ collectionsDir?: string;
66
+ server: Server;
67
+ app: Hono<HonoEnv>;
68
+ basePath?: string;
69
+ bootstrappers: BackendBootstrapper[];
70
+ logging?: {
71
+ level?: "error" | "warn" | "info" | "debug";
72
+ };
73
+ auth?: RebaseAuthConfig;
74
+ /**
75
+ * Storage configuration. Accepts:
76
+ *
77
+ * - A `BackendStorageConfig` object (`{ type: 'local' | 's3', ... }`)
78
+ * - A `StorageController` instance (for custom providers like GCS, Azure, etc.)
79
+ * - A `Record<string, ...>` of either, for multi-backend setups
80
+ */
81
+ storage?: BackendStorageConfig | StorageController | Record<string, BackendStorageConfig | StorageController>;
82
+ history?: unknown;
83
+ enableSwagger?: boolean;
84
+ functionsDir?: string;
85
+ cronsDir?: string;
86
+ /**
87
+ * Maximum request body size in bytes for API routes (default: 10MB).
88
+ * Set to 0 to disable the global limit entirely.
89
+ *
90
+ * Note: Storage upload routes use their own limit from the storage config's
91
+ * `maxFileSize` property (default: 50MB), which takes precedence over this.
92
+ */
93
+ maxBodySize?: number;
94
+ /**
95
+ * CSRF protection configuration. **Opt-in** — disabled by default.
96
+ *
97
+ * BaaS APIs are consumed by mobile apps, SPAs on different domains,
98
+ * and CLI tools, so CSRF is intentionally not enabled unless you
99
+ * explicitly configure it with allowed origins.
100
+ *
101
+ * @example
102
+ * ```ts
103
+ * csrf: { origin: ["https://myapp.com", "https://admin.myapp.com"] }
104
+ * ```
105
+ */
106
+ csrf?: {
107
+ /** Allowed origins for CSRF validation. */
108
+ origin: string | string[] | ((origin: string) => boolean);
109
+ };
110
+ /**
111
+ * Backend-level hooks for intercepting admin data (users, roles)
112
+ * at the API boundary. These run server-side after database reads
113
+ * and before API responses are sent.
114
+ *
115
+ * Complement the per-collection `EntityCallbacks` system which
116
+ * handles collection CRUD operations.
117
+ */
118
+ hooks?: BackendHooks;
119
+ }
120
+
121
+ export interface RebaseBackendInstance {
122
+ driverRegistry: DriverRegistry;
123
+ driver: DataDriver;
124
+ realtimeServices: Record<string, RealtimeProvider>;
125
+ realtimeService: RealtimeProvider;
126
+ auth?: BootstrappedAuth;
127
+ history?: unknown;
128
+ storageRegistry?: StorageRegistry;
129
+ storageController?: StorageController;
130
+ collectionRegistry: BackendCollectionRegistry;
131
+ cronScheduler?: import("./cron").CronScheduler;
132
+ /**
133
+ * Deep health check that verifies database connectivity.
134
+ * Returns latency and component status.
135
+ */
136
+ healthCheck(): Promise<HealthCheckResult>;
137
+ /**
138
+ * Graceful shutdown helper for the BaaS instance.
139
+ * Stops the cron scheduler and closes the HTTP server, allowing
140
+ * in-flight requests to drain within the given timeout.
141
+ *
142
+ * @param timeoutMs - Maximum time (ms) to wait for drain before force-exit (default: 15000).
143
+ * Pass 0 to skip the force-exit timer (useful in tests).
144
+ */
145
+ shutdown(timeoutMs?: number): Promise<void>;
146
+ }
147
+
148
+ export async function initializeRebaseBackend(config: RebaseBackendConfig): Promise<RebaseBackendInstance> {
149
+ // No try/catch: let init errors propagate to the caller.
150
+ // The app entry point (e.g. startServer()) should catch and process.exit(1).
151
+ // Returning a fake instance hides critical failures and leads to silent data loss.
152
+ return await _initializeRebaseBackend(config);
153
+ }
154
+
155
+ async function _initializeRebaseBackend(config: RebaseBackendConfig): Promise<RebaseBackendInstance> {
156
+ if (config.logging?.level) {
157
+ configureLogLevel(config.logging.level);
158
+ } else {
159
+ configureLogLevel();
160
+ }
161
+
162
+ logger.info("Initializing Rebase Backend (Bootstrapper Protocol V2)");
163
+
164
+ const basePath = config.basePath || "/api";
165
+ const isProduction = process.env.NODE_ENV === "production";
166
+
167
+ // ─── Request Body Size Limit ─────────────────────────────────────────
168
+ const maxBodySize = config.maxBodySize ?? 10 * 1024 * 1024; // 10MB default
169
+ if (maxBodySize > 0) {
170
+ config.app.use(`${basePath}/*`, bodyLimit({
171
+ maxSize: maxBodySize,
172
+ onError: (c) => {
173
+ return c.json({
174
+ error: {
175
+ message: `Request body too large. Maximum size is ${Math.round(maxBodySize / 1024 / 1024)}MB.`,
176
+ code: "PAYLOAD_TOO_LARGE"
177
+ }
178
+ }, 413);
179
+ }
180
+ }));
181
+ logger.info("Request body limit configured", { maxSizeMB: Math.round(maxBodySize / 1024 / 1024) });
182
+ }
183
+
184
+ // ─── CSRF Protection (opt-in) ────────────────────────────────────────
185
+ // BaaS APIs are consumed by mobile apps, SPAs on different origins, and
186
+ // CLI/SDK tools. CSRF is only enabled when the developer explicitly
187
+ // configures it with allowed origins.
188
+ if (config.csrf?.origin) {
189
+ config.app.use(`${basePath}/*`, csrf({
190
+ origin: config.csrf.origin
191
+ }));
192
+ logger.info("CSRF protection enabled");
193
+ }
194
+
195
+ // ─── Request Logging ─────────────────────────────────────────────────
196
+ config.app.use(`${basePath}/*`, requestLogger());
197
+
198
+ const collectionRegistry = new BackendCollectionRegistry();
199
+ let activeCollections = config.collections || [];
200
+ if (config.collectionsDir && activeCollections.length === 0) {
201
+ activeCollections = await loadCollectionsFromDirectory(config.collectionsDir);
202
+ logger.info("Auto-discovered collections", { count: activeCollections.length,
203
+ dir: config.collectionsDir });
204
+ }
205
+
206
+ const realtimeServices: Record<string, RealtimeProvider> = {};
207
+ const delegates: Record<string, DataDriver> = {};
208
+ const bootstrappers = config.bootstrappers || [];
209
+
210
+ if (bootstrappers.length === 0) {
211
+ throw new Error("No bootstrappers provided. Cannot initialize database drivers.");
212
+ }
213
+
214
+ let defaultDriverId = DEFAULT_DRIVER_ID;
215
+
216
+ let defaultDriverResult: InitializedDriver | undefined = undefined;
217
+
218
+ // 1. Initialize all drivers
219
+ for (const bootstrapper of bootstrappers) {
220
+ const b = bootstrapper as BackendBootstrapper & { id?: string; isDefault?: boolean };
221
+ logger.info("Running bootstrapper for driver", { driverId: b.id || bootstrapper.type });
222
+ if (b.isDefault) {
223
+ defaultDriverId = b.id || bootstrapper.type;
224
+ }
225
+
226
+ const driverResult = await bootstrapper.initializeDriver({ collections: activeCollections,
227
+ collectionRegistry });
228
+ delegates[b.id || bootstrapper.type] = driverResult.driver;
229
+
230
+ if ((b.id || bootstrapper.type) === defaultDriverId || !defaultDriverResult) {
231
+ defaultDriverResult = driverResult;
232
+ }
233
+
234
+ if (bootstrapper.initializeRealtime) {
235
+ const realtime = await bootstrapper.initializeRealtime({}, driverResult);
236
+ realtimeServices[b.id || bootstrapper.type] = realtime as RealtimeProvider;
237
+ }
238
+ }
239
+
240
+ const driverRegistry = DefaultDriverRegistry.create(delegates);
241
+ activeCollections.forEach(collection => collectionRegistry.register(collection));
242
+
243
+ const defaultDriver = driverRegistry.getOrDefault(defaultDriverId);
244
+ if (!defaultDriver || !defaultDriverResult) {
245
+ throw new Error("Default driver not initialized by bootstrappers");
246
+ }
247
+ const defaultBootstrapper = bootstrappers.find(b => (b as BackendBootstrapper & { id?: string }).id === defaultDriverId || b.type === defaultDriverId) || bootstrappers[0];
248
+ const defaultRealtimeService = defaultDriverResult.realtimeProvider;
249
+
250
+ // 2. Initialize Auth & History via the default driver's bootstrapper
251
+ let authConfigResult: BootstrappedAuth | undefined = undefined;
252
+ let serviceKey: string | undefined;
253
+
254
+ if (config.auth) {
255
+ // Secure JWT setup proactively within core package memory to eliminate dual-package hazards
256
+ const safeAuthConfig = config.auth;
257
+ if (safeAuthConfig.jwtSecret) {
258
+ configureJwt({
259
+ secret: safeAuthConfig.jwtSecret,
260
+ accessExpiresIn: safeAuthConfig.accessExpiresIn || "1h",
261
+ refreshExpiresIn: safeAuthConfig.refreshExpiresIn || "30d"
262
+ });
263
+ }
264
+
265
+ // ── Service Key Validation ───────────────────────────────────────
266
+ if (safeAuthConfig.serviceKey) {
267
+ if (safeAuthConfig.serviceKey.length < 32) {
268
+ throw new Error(
269
+ "REBASE_SERVICE_KEY is too short. Must be at least 32 characters. " +
270
+ "Generate one with: node -e \"console.log(require('crypto').randomBytes(48).toString('base64'))\""
271
+ );
272
+ }
273
+ serviceKey = safeAuthConfig.serviceKey;
274
+ logger.info("Service key configured for script/server-to-server authentication");
275
+ }
276
+
277
+ if (defaultBootstrapper.initializeAuth) {
278
+ logger.info("Bootstrapping authentication via driver protocol");
279
+ authConfigResult = await defaultBootstrapper.initializeAuth(config.auth, defaultDriverResult);
280
+ logger.info("Authentication initialized");
281
+ } else {
282
+ logger.warn("Auth requested but default bootstrapper does not support initializeAuth");
283
+ }
284
+ }
285
+
286
+ let historyConfigResult: Record<string, unknown> | undefined = undefined;
287
+ if (config.history) {
288
+ if (defaultBootstrapper.initializeHistory) {
289
+ logger.info("Bootstrapping entity history via driver protocol");
290
+ historyConfigResult = await defaultBootstrapper.initializeHistory(config.history, defaultDriverResult);
291
+
292
+ // Inject the historyService into the driver so saveEntity/deleteEntity can record history.
293
+ // The driver was created during initializeDriver() (before history was initialized),
294
+ // so we must set it retroactively here.
295
+ if (historyConfigResult?.historyService && defaultDriverResult.internals) {
296
+ const internals = defaultDriverResult.internals as Record<string, unknown>;
297
+ const driver = internals.driver as Record<string, unknown> | undefined;
298
+ if (driver && "historyService" in driver) {
299
+ driver.historyService = historyConfigResult.historyService;
300
+ }
301
+ }
302
+
303
+ logger.info("Entity history initialized");
304
+ } else {
305
+ logger.warn("History requested but default bootstrapper does not support initializeHistory");
306
+ }
307
+ }
308
+
309
+ // 3. Initialize Storage
310
+ let storageRegistry: StorageRegistry | undefined;
311
+ let storageController: StorageController | undefined;
312
+
313
+ if (config.storage) {
314
+ logger.info("Configuring storage");
315
+ const controllers: Record<string, StorageController> = {};
316
+
317
+ // Helper: resolve a single storage entry to a controller
318
+ const toController = (entry: BackendStorageConfig | StorageController, label: string): StorageController => {
319
+ // Duck-type: if it has putObject, it's already a controller instance
320
+ if (typeof (entry as StorageController).putObject === "function") {
321
+ return entry as StorageController;
322
+ }
323
+ // Otherwise it's a config object — use the built-in factory
324
+ const conf = entry as BackendStorageConfig;
325
+ if (isProduction && conf.type === "local") {
326
+ logger.warn(`Storage backend "${label}" uses local filesystem in production. ` +
327
+ "Files will be lost on container restart. " +
328
+ "Configure S3-compatible storage or a custom StorageController.");
329
+ }
330
+ return createStorageController(conf);
331
+ };
332
+
333
+ if (typeof config.storage === "object" && ("type" in config.storage || typeof (config.storage as StorageController).putObject === "function")) {
334
+ // Single storage config or controller
335
+ controllers[DEFAULT_STORAGE_ID] = toController(config.storage as BackendStorageConfig | StorageController, DEFAULT_STORAGE_ID);
336
+ } else {
337
+ // Multi-backend record
338
+ for (const [storageId, entry] of Object.entries(config.storage as Record<string, BackendStorageConfig | StorageController>)) {
339
+ controllers[storageId] = toController(entry, storageId);
340
+ }
341
+ }
342
+
343
+ if (Object.keys(controllers).length > 0) {
344
+ storageRegistry = DefaultStorageRegistry.create(controllers);
345
+ storageController = storageRegistry.getDefault();
346
+ logger.info("Initialized storage backends", { count: Object.keys(controllers).length });
347
+ }
348
+ }
349
+
350
+ // basePath already resolved above
351
+
352
+ // 4. Mount API Routes
353
+ if (config.auth && authConfigResult) {
354
+ const oauthProviders: OAuthProvider<any>[] = [...(config.auth.providers || [])];
355
+
356
+ if (config.auth.google?.clientId) {
357
+ const { createGoogleProvider } = await import("./auth");
358
+ oauthProviders.push(createGoogleProvider(config.auth.google.clientId));
359
+ }
360
+
361
+ if (config.auth.linkedin?.clientId && config.auth.linkedin?.clientSecret) {
362
+ const { createLinkedinProvider } = await import("./auth");
363
+ oauthProviders.push(createLinkedinProvider(config.auth.linkedin as { clientId: string; clientSecret: string }));
364
+ }
365
+
366
+ if (config.auth.github?.clientId && config.auth.github?.clientSecret) {
367
+ const { createGitHubProvider } = await import("./auth");
368
+ oauthProviders.push(createGitHubProvider(config.auth.github));
369
+ }
370
+
371
+ if (config.auth.microsoft?.clientId && config.auth.microsoft?.clientSecret) {
372
+ const { createMicrosoftProvider } = await import("./auth");
373
+ oauthProviders.push(createMicrosoftProvider(config.auth.microsoft));
374
+ }
375
+
376
+ if (config.auth.apple?.clientId && config.auth.apple?.teamId && config.auth.apple?.keyId && config.auth.apple?.privateKey) {
377
+ const { createAppleProvider } = await import("./auth");
378
+ oauthProviders.push(createAppleProvider(config.auth.apple));
379
+ }
380
+
381
+ if (config.auth.facebook?.clientId && config.auth.facebook?.clientSecret) {
382
+ const { createFacebookProvider } = await import("./auth");
383
+ oauthProviders.push(createFacebookProvider(config.auth.facebook));
384
+ }
385
+
386
+ if (config.auth.twitter?.clientId && config.auth.twitter?.clientSecret) {
387
+ const { createTwitterProvider } = await import("./auth");
388
+ oauthProviders.push(createTwitterProvider(config.auth.twitter));
389
+ }
390
+
391
+ if (config.auth.discord?.clientId && config.auth.discord?.clientSecret) {
392
+ const { createDiscordProvider } = await import("./auth");
393
+ oauthProviders.push(createDiscordProvider(config.auth.discord));
394
+ }
395
+
396
+ if (config.auth.gitlab?.clientId && config.auth.gitlab?.clientSecret) {
397
+ const { createGitLabProvider } = await import("./auth");
398
+ oauthProviders.push(createGitLabProvider(config.auth.gitlab));
399
+ }
400
+
401
+ if (config.auth.bitbucket?.clientId && config.auth.bitbucket?.clientSecret) {
402
+ const { createBitbucketProvider } = await import("./auth");
403
+ oauthProviders.push(createBitbucketProvider(config.auth.bitbucket));
404
+ }
405
+
406
+ if (config.auth.slack?.clientId && config.auth.slack?.clientSecret) {
407
+ const { createSlackProvider } = await import("./auth");
408
+ oauthProviders.push(createSlackProvider(config.auth.slack));
409
+ }
410
+
411
+ if (config.auth.spotify?.clientId && config.auth.spotify?.clientSecret) {
412
+ const { createSpotifyProvider } = await import("./auth");
413
+ oauthProviders.push(createSpotifyProvider(config.auth.spotify));
414
+ }
415
+
416
+ const authRoutes = createAuthRoutes({
417
+ authRepo: authConfigResult.authRepository as import("./auth/interfaces").AuthRepository ?? authConfigResult.userService as import("./auth/interfaces").AuthRepository,
418
+ emailService: authConfigResult.emailService as import("./email").EmailService,
419
+ emailConfig: config.auth.email,
420
+ allowRegistration: config.auth.allowRegistration ?? false,
421
+ defaultRole: config.auth.defaultRole,
422
+ oauthProviders
423
+ });
424
+ config.app.route(`${basePath}/auth`, authRoutes);
425
+
426
+ const adminRoutes = createAdminRoutes({
427
+ authRepo: authConfigResult.authRepository as import("./auth/interfaces").AuthRepository ?? authConfigResult.userService as import("./auth/interfaces").AuthRepository,
428
+ emailService: authConfigResult.emailService as import("./email").EmailService,
429
+ emailConfig: config.auth.email,
430
+ serviceKey,
431
+ hooks: config.hooks
432
+ });
433
+ config.app.route(`${basePath}/admin`, adminRoutes);
434
+ }
435
+
436
+ if (config.collectionsDir) {
437
+ if (process.env.NODE_ENV !== "production") {
438
+ const { createSchemaEditorRoutes } = await import("./api/schema-editor-routes");
439
+ const schemaEditorRoutes = createSchemaEditorRoutes(config.collectionsDir);
440
+
441
+ if (config.auth?.requireAuth !== false && !!config.auth?.jwtSecret) {
442
+ schemaEditorRoutes.use("/*", requireAuth, requireAdmin);
443
+ }
444
+
445
+ config.app.route(`${basePath}/schema-editor`, schemaEditorRoutes);
446
+ logger.info("Schema Editor mounted", { path: `${basePath}/schema-editor` });
447
+ }
448
+ }
449
+
450
+ if (storageController) {
451
+ // Storage uploads get their own body limit, derived from the storage config's
452
+ // maxFileSize (default 50MB), which overrides the global API body limit.
453
+ const storageMaxSize = (
454
+ config.storage && typeof config.storage === "object" && "type" in config.storage
455
+ ? (config.storage as BackendStorageConfig & { maxFileSize?: number }).maxFileSize
456
+ : undefined
457
+ ) ?? 50 * 1024 * 1024;
458
+
459
+ const storageRoutes = createStorageRoutes({
460
+ controller: storageController,
461
+ requireAuth: config.auth?.requireAuth ?? true
462
+ });
463
+
464
+ // Apply a permissive body limit specifically for the upload endpoint
465
+ storageRoutes.use("/upload", bodyLimit({
466
+ maxSize: storageMaxSize,
467
+ onError: (c) => {
468
+ return c.json({
469
+ error: {
470
+ message: `File too large. Maximum upload size is ${Math.round(storageMaxSize / 1024 / 1024)}MB.`,
471
+ code: "PAYLOAD_TOO_LARGE"
472
+ }
473
+ }, 413);
474
+ }
475
+ }));
476
+
477
+ config.app.route(`${basePath}/storage`, storageRoutes);
478
+ }
479
+
480
+ if (activeCollections.length > 0) {
481
+ const dataRouter = new Hono<HonoEnv>();
482
+ dataRouter.onError(errorHandler);
483
+
484
+ // Secure by default: require auth when auth is configured.
485
+ // Developers who intentionally want public data access (relying
486
+ // entirely on Postgres RLS) must explicitly set `auth.requireAuth: false`.
487
+ const dataRequireAuth = config.auth?.requireAuth !== false;
488
+
489
+ if (!dataRequireAuth) {
490
+ logger.warn(
491
+ "Data routes running WITHOUT authentication enforcement. " +
492
+ "Access control is fully delegated to Postgres RLS policies. " +
493
+ "If no RLS policies exist, data is publicly accessible. " +
494
+ "Set auth.requireAuth to true (or remove it) to require authentication."
495
+ );
496
+ }
497
+
498
+ dataRouter.use("/*", createAuthMiddleware({
499
+ driver: defaultDriver,
500
+ requireAuth: dataRequireAuth,
501
+ serviceKey
502
+ }));
503
+
504
+ // Mount history routes BEFORE the REST API subcollection catch-all so
505
+ // that /:slug/:entityId/history is matched by the dedicated handler first.
506
+ if (historyConfigResult && historyConfigResult.historyService) {
507
+ const historyRoutes = createHistoryRoutes({
508
+ historyService: historyConfigResult.historyService as import("./history/history-routes").HistoryService,
509
+ registry: collectionRegistry,
510
+ driver: defaultDriver
511
+ });
512
+ dataRouter.route("/", historyRoutes);
513
+ }
514
+
515
+ const restGenerator = new RestApiGenerator(activeCollections, defaultDriver, config.hooks?.data);
516
+ dataRouter.route("/", restGenerator.generateRoutes());
517
+
518
+ config.app.route(`${basePath}/data`, dataRouter);
519
+ }
520
+
521
+ // ── OpenAPI / Swagger ─────────────────────────────────────────────────
522
+ if (config.enableSwagger !== false && activeCollections.length > 0) {
523
+ const { generateOpenApiSpec } = await import("./api/openapi-generator");
524
+
525
+ config.app.get(`${basePath}/docs`, (c) => {
526
+ const spec = generateOpenApiSpec(activeCollections, {
527
+ basePath,
528
+ requireAuth: config.auth?.requireAuth !== false
529
+ });
530
+ return c.json(spec);
531
+ });
532
+
533
+ if (process.env.NODE_ENV !== "production") {
534
+ config.app.get(`${basePath}/swagger`, (c) => {
535
+ return c.html(`<!DOCTYPE html>
536
+ <html>
537
+ <head>
538
+ <title>Rebase API Documentation</title>
539
+ <meta charset="utf-8"/>
540
+ <meta name="viewport" content="width=device-width, initial-scale=1"/>
541
+ <link rel="stylesheet" type="text/css" href="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css"/>
542
+ <style>body{margin:0;padding:0;}</style>
543
+ </head>
544
+ <body>
545
+ <div id="swagger-ui"></div>
546
+ <script src="https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js"></script>
547
+ <script>SwaggerUIBundle({ url: '${basePath}/docs', dom_id: '#swagger-ui' });</script>
548
+ </body>
549
+ </html>`);
550
+ });
551
+ logger.info("Swagger UI available", { path: `${basePath}/swagger` });
552
+ }
553
+ }
554
+
555
+ // ─── Server-side singleton ────────────────────────────────────────────
556
+ // Build the admin-level RebaseClient and expose it as the `rebase` singleton.
557
+ // This client bypasses the network and uses Hono's internal request handler.
558
+ // websocketUrl is explicitly empty to prevent opening a WebSocket connection.
559
+ const serverClient = createRebaseClient({
560
+ baseUrl: "http://localhost",
561
+ apiPath: basePath,
562
+ websocketUrl: "",
563
+ fetch: async (input: RequestInfo | URL, init?: RequestInit) => {
564
+ return await config.app.request(input as string | Request | URL, init);
565
+ }
566
+ });
567
+
568
+ // Attach email service to the server client when configured.
569
+ // The email service may come from the auth bootstrapper or from the auth config directly.
570
+ let emailService: EmailService | undefined;
571
+ if (authConfigResult?.emailService) {
572
+ emailService = authConfigResult.emailService as EmailService;
573
+ } else if (config.auth?.email) {
574
+ emailService = createEmailService(config.auth.email);
575
+ }
576
+
577
+ if (emailService) {
578
+ Object.assign(serverClient, { email: emailService });
579
+ logger.info("Email service attached to singleton", { configured: emailService.isConfigured() });
580
+ }
581
+
582
+ _initRebase(serverClient);
583
+ logger.info("Rebase singleton initialized");
584
+
585
+ // 5. Mount Custom Functions
586
+ if (config.functionsDir) {
587
+ const { loadFunctionsFromDirectory } = await import("./functions/function-loader");
588
+ const { createFunctionRoutes } = await import("./functions/function-routes");
589
+
590
+ const loadedFunctions = await loadFunctionsFromDirectory(config.functionsDir);
591
+
592
+ if (loadedFunctions.length > 0) {
593
+ const functionsRouter = new Hono<HonoEnv>();
594
+ functionsRouter.onError(errorHandler);
595
+
596
+ // Custom functions follow the same auth policy as data routes.
597
+ // Per-route auth can be further refined inside individual functions.
598
+ const functionsRequireAuth = config.auth?.requireAuth !== false;
599
+
600
+ functionsRouter.use("/*", createAuthMiddleware({
601
+ driver: defaultDriver,
602
+ requireAuth: functionsRequireAuth,
603
+ serviceKey
604
+ }));
605
+
606
+ const fnRoutes = createFunctionRoutes(loadedFunctions);
607
+ functionsRouter.route("/", fnRoutes);
608
+ config.app.route(`${basePath}/functions`, functionsRouter);
609
+ logger.info("Mounted custom functions", { count: loadedFunctions.length,
610
+ path: `${basePath}/functions` });
611
+ }
612
+ }
613
+
614
+ // 6. Mount Cron Jobs
615
+ let cronScheduler: import("./cron").CronScheduler | undefined;
616
+ if (config.cronsDir) {
617
+ const { loadCronJobsFromDirectory } = await import("./cron/cron-loader");
618
+ const { CronScheduler } = await import("./cron/cron-scheduler");
619
+ const { createCronRoutes } = await import("./cron/cron-routes");
620
+ const { createCronStore } = await import("./cron/cron-store");
621
+
622
+ const loadedCronJobs = await loadCronJobsFromDirectory(config.cronsDir);
623
+
624
+ if (loadedCronJobs.length > 0) {
625
+ cronScheduler = new CronScheduler();
626
+
627
+ // The cron scheduler uses the same serverClient as the singleton.
628
+ // ctx.client inside cron handlers IS the same `rebase` instance.
629
+ cronScheduler.setClient(serverClient);
630
+
631
+ cronScheduler.registerJobs(loadedCronJobs);
632
+
633
+ // Attach database persistence if the driver supports SQL
634
+ const store = createCronStore(defaultDriver);
635
+ if (store) {
636
+ await store.ensureTable();
637
+ cronScheduler.setStore(store);
638
+ }
639
+
640
+ const cronRouter = new Hono<HonoEnv>();
641
+
642
+ // Cron admin routes require authentication + admin role
643
+ if (config.auth?.requireAuth !== false && !!config.auth?.jwtSecret) {
644
+ cronRouter.use("/*", requireAuth, requireAdmin);
645
+ }
646
+
647
+ cronRouter.route("/", createCronRoutes(cronScheduler));
648
+ config.app.route(`${basePath}/cron`, cronRouter);
649
+
650
+ // Start the scheduler
651
+ cronScheduler.start();
652
+
653
+ logger.info("Mounted cron jobs", { count: loadedCronJobs.length,
654
+ path: `${basePath}/cron` });
655
+ }
656
+ }
657
+
658
+ if ((defaultBootstrapper as BackendBootstrapper & { initializeWebsockets?: (...args: unknown[]) => unknown }).initializeWebsockets) {
659
+ await (defaultBootstrapper as BackendBootstrapper & { initializeWebsockets: (...args: unknown[]) => unknown }).initializeWebsockets(config.server, defaultRealtimeService, defaultDriver, config.auth);
660
+ }
661
+
662
+ logger.info("Rebase Backend Initialized");
663
+
664
+ // ── Deep Health Check ─────────────────────────────────────────────────
665
+ const healthCheck = async (): Promise<HealthCheckResult> => {
666
+ const start = performance.now();
667
+ try {
668
+ // Use admin.executeSql if available (Postgres), otherwise try fetchCollection as a probe
669
+ const admin = defaultDriver.admin;
670
+ if (isSQLAdmin(admin)) {
671
+ await admin.executeSql("SELECT 1");
672
+ } else {
673
+ // Fallback: try a lightweight fetch to confirm driver is responsive
674
+ await defaultDriver.fetchCollection({ path: "__health_check_nonexistent__",
675
+ limit: 1 });
676
+ }
677
+ const latencyMs = Math.round(performance.now() - start);
678
+ return { healthy: true,
679
+ latencyMs };
680
+ } catch (error: unknown) {
681
+ const latencyMs = Math.round(performance.now() - start);
682
+ logger.error("Health check failed", {
683
+ error: error instanceof Error ? error : new Error(String(error)),
684
+ latencyMs
685
+ });
686
+ return {
687
+ healthy: false,
688
+ latencyMs,
689
+ details: {
690
+ error: error instanceof Error ? error.message : String(error)
691
+ }
692
+ };
693
+ }
694
+ };
695
+
696
+ // ── Graceful Shutdown ─────────────────────────────────────────────────
697
+ const shutdown = (timeoutMs = 15_000): Promise<void> => {
698
+ return new Promise<void>((resolve) => {
699
+ logger.info("Shutting down Rebase Backend...");
700
+
701
+ // 1. Stop cron scheduler
702
+ if (cronScheduler) {
703
+ cronScheduler.stop();
704
+ logger.info("Cron scheduler stopped");
705
+ }
706
+
707
+ // 2. Close the HTTP server (stop accepting, drain in-flight)
708
+ config.server.close(() => {
709
+ logger.info("HTTP server closed");
710
+ resolve();
711
+ });
712
+
713
+ // 3. Force-resolve after timeout (unless disabled with 0)
714
+ if (timeoutMs > 0) {
715
+ setTimeout(() => {
716
+ logger.warn(`Forced shutdown after ${timeoutMs / 1000}s timeout`);
717
+ resolve();
718
+ }, timeoutMs).unref();
719
+ }
720
+ });
721
+ };
722
+
723
+ return {
724
+ driverRegistry,
725
+ driver: defaultDriver,
726
+ realtimeServices,
727
+ realtimeService: defaultRealtimeService as RealtimeProvider,
728
+ auth: authConfigResult,
729
+ history: historyConfigResult,
730
+ storageRegistry,
731
+ storageController,
732
+ collectionRegistry,
733
+ cronScheduler,
734
+ healthCheck,
735
+ shutdown
736
+ };
737
+ }