@rebasepro/server-core 0.5.0 → 0.6.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.
- package/dist/{server-core/src/api → api}/errors.d.ts +5 -2
- package/dist/{server-core/src/api → api}/rest/api-generator.d.ts +8 -2
- package/dist/{server-core/src/api → api}/types.d.ts +2 -0
- package/dist/auth/admin-user-ops.d.ts +79 -0
- package/dist/{server-core/src/auth → auth}/api-keys/index.d.ts +2 -2
- package/dist/{server-core/src/auth → auth}/auth-hooks.d.ts +37 -9
- package/dist/{server-core/src/auth → auth}/builtin-auth-adapter.d.ts +4 -4
- package/dist/{server-core/src/auth → auth}/index.d.ts +5 -2
- package/dist/auth/mfa-routes.d.ts +6 -0
- package/dist/auth/reset-password-admin.d.ts +29 -0
- package/dist/auth/session-routes.d.ts +25 -0
- package/dist/backend-CIxN4FVm.js +15 -0
- package/dist/backend-CIxN4FVm.js.map +1 -0
- package/dist/chunk-Dze3rakg.js +42 -0
- package/dist/{server-core/src/cron → cron}/cron-scheduler.d.ts +1 -1
- package/dist/dist-CZKP-Xz4.js +832 -0
- package/dist/dist-CZKP-Xz4.js.map +1 -0
- package/dist/env.d.ts +102 -0
- package/dist/from-VbwD7xRf.js +3849 -0
- package/dist/from-VbwD7xRf.js.map +1 -0
- package/dist/{server-core/src/index.d.ts → index.d.ts} +1 -0
- package/dist/index.es.js +60867 -50462
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +73151 -51298
- package/dist/index.umd.js.map +1 -1
- package/dist/init/docs.d.ts +4 -0
- package/dist/init/health.d.ts +2 -0
- package/dist/init/middlewares.d.ts +10 -0
- package/dist/init/shutdown.d.ts +11 -0
- package/dist/init/storage.d.ts +5 -0
- package/dist/{server-core/src/init.d.ts → init.d.ts} +4 -4
- package/dist/jwt-DHcQRGC3.js +4168 -0
- package/dist/jwt-DHcQRGC3.js.map +1 -0
- package/dist/logger-BYU66ENZ.js +94 -0
- package/dist/logger-BYU66ENZ.js.map +1 -0
- package/dist/ms-BeBSuOXN.js +125 -0
- package/dist/ms-BeBSuOXN.js.map +1 -0
- package/dist/multipart-parser-CedBDOeC.js +299 -0
- package/dist/multipart-parser-CedBDOeC.js.map +1 -0
- package/dist/src-COaj0G3P.js +1182 -0
- package/dist/src-COaj0G3P.js.map +1 -0
- package/dist/{server-core/src/storage → storage}/image-transform.d.ts +3 -1
- package/dist/utils/request-id.d.ts +4 -0
- package/package.json +29 -29
- package/src/api/collections_for_test/callbacks_test_collection.ts +7 -6
- package/src/api/errors.ts +75 -19
- package/src/api/graphql/graphql-schema-generator.ts +15 -10
- package/src/api/logs-routes.ts +5 -2
- package/src/api/rest/api-generator-count.test.ts +21 -10
- package/src/api/rest/api-generator.ts +83 -24
- package/src/api/rest/query-parser.ts +18 -12
- package/src/api/server.ts +12 -1
- package/src/api/types.ts +2 -0
- package/src/auth/adapter-middleware.ts +17 -10
- package/src/auth/admin-user-ops.ts +236 -0
- package/src/auth/api-keys/api-key-middleware.ts +15 -9
- package/src/auth/api-keys/api-key-permission-guard.ts +1 -1
- package/src/auth/api-keys/api-key-routes.ts +3 -3
- package/src/auth/api-keys/api-key-store.ts +14 -8
- package/src/auth/api-keys/index.ts +2 -2
- package/src/auth/apple-oauth.ts +4 -3
- package/src/auth/auth-hooks.ts +50 -10
- package/src/auth/bitbucket-oauth.ts +5 -4
- package/src/auth/builtin-auth-adapter.ts +60 -39
- package/src/auth/custom-auth-adapter.ts +4 -5
- package/src/auth/discord-oauth.ts +5 -4
- package/src/auth/facebook-oauth.ts +5 -4
- package/src/auth/github-oauth.ts +6 -5
- package/src/auth/gitlab-oauth.ts +5 -4
- package/src/auth/google-oauth.ts +2 -1
- package/src/auth/index.ts +6 -2
- package/src/auth/jwt.ts +5 -4
- package/src/auth/linkedin-oauth.ts +4 -3
- package/src/auth/mfa-routes.ts +299 -0
- package/src/auth/mfa.ts +2 -1
- package/src/auth/microsoft-oauth.ts +5 -4
- package/src/auth/middleware.ts +5 -4
- package/src/auth/rate-limiter.ts +1 -1
- package/src/auth/reset-password-admin.ts +144 -0
- package/src/auth/rls-scope.ts +1 -1
- package/src/auth/routes.ts +52 -596
- package/src/auth/session-routes.ts +341 -0
- package/src/auth/slack-oauth.ts +6 -5
- package/src/auth/spotify-oauth.ts +5 -4
- package/src/auth/twitter-oauth.ts +4 -3
- package/src/collections/loader.ts +5 -4
- package/src/cron/cron-scheduler.test.ts +23 -11
- package/src/cron/cron-scheduler.ts +14 -8
- package/src/cron/cron-store.ts +1 -1
- package/src/email/smtp-email-service.ts +3 -2
- package/src/env.ts +11 -10
- package/src/index.ts +1 -0
- package/src/init/docs.ts +47 -0
- package/src/init/health.ts +37 -0
- package/src/init/middlewares.ts +61 -0
- package/src/init/shutdown.ts +56 -0
- package/src/init/storage.ts +57 -0
- package/src/init.ts +85 -279
- package/src/serve-spa.ts +12 -5
- package/src/services/driver-registry.ts +4 -3
- package/src/storage/S3StorageController.ts +1 -2
- package/src/storage/image-transform.ts +25 -10
- package/src/storage/routes.ts +30 -11
- package/src/storage/storage-registry.ts +4 -3
- package/src/storage/tus-handler.ts +12 -12
- package/src/utils/request-id.ts +40 -0
- package/src/utils/request-logger.ts +6 -0
- package/test/api-generator.test.ts +90 -2
- package/test/api-key-permission-guard.test.ts +24 -12
- package/test/auth-routes.test.ts +5 -3
- package/test/backend-hooks-data.test.ts +99 -30
- package/test/custom-auth-adapter.test.ts +16 -15
- package/test/email-templates.test.ts +10 -5
- package/test/env.test.ts +10 -10
- package/test/function-loader.test.ts +7 -4
- package/test/graphql-schema-generator.test.ts +554 -0
- package/test/jwt-security.test.ts +1 -1
- package/test/query-parser.test.ts +0 -1
- package/test/reset-password-admin.test.ts +113 -0
- package/test/singleton.test.ts +5 -5
- package/test/smtp-email-service.test.ts +21 -21
- package/test/webhook-service.test.ts +22 -11
- package/tsconfig.json +2 -0
- package/tsconfig.prod.json +3 -0
- package/vite.config.ts +5 -5
- package/dist/common/src/collections/CollectionRegistry.d.ts +0 -56
- package/dist/common/src/collections/default-collections.d.ts +0 -9
- package/dist/common/src/collections/index.d.ts +0 -2
- package/dist/common/src/data/buildRebaseData.d.ts +0 -14
- package/dist/common/src/data/query_builder.d.ts +0 -55
- package/dist/common/src/index.d.ts +0 -4
- package/dist/common/src/util/builders.d.ts +0 -57
- package/dist/common/src/util/callbacks.d.ts +0 -6
- package/dist/common/src/util/collections.d.ts +0 -11
- package/dist/common/src/util/common.d.ts +0 -2
- package/dist/common/src/util/conditions.d.ts +0 -26
- package/dist/common/src/util/entities.d.ts +0 -58
- package/dist/common/src/util/enums.d.ts +0 -3
- package/dist/common/src/util/index.d.ts +0 -16
- package/dist/common/src/util/navigation_from_path.d.ts +0 -34
- package/dist/common/src/util/navigation_utils.d.ts +0 -20
- package/dist/common/src/util/parent_references_from_path.d.ts +0 -6
- package/dist/common/src/util/paths.d.ts +0 -14
- package/dist/common/src/util/permissions.d.ts +0 -14
- package/dist/common/src/util/references.d.ts +0 -2
- package/dist/common/src/util/relations.d.ts +0 -22
- package/dist/common/src/util/resolutions.d.ts +0 -72
- package/dist/common/src/util/storage.d.ts +0 -24
- package/dist/index-Cr1D21av.js +0 -49
- package/dist/index-Cr1D21av.js.map +0 -1
- package/dist/server-core/src/auth/admin-routes.d.ts +0 -27
- package/dist/server-core/src/env.d.ts +0 -137
- package/dist/types/src/controllers/analytics_controller.d.ts +0 -7
- package/dist/types/src/controllers/auth.d.ts +0 -104
- package/dist/types/src/controllers/client.d.ts +0 -168
- package/dist/types/src/controllers/collection_registry.d.ts +0 -46
- package/dist/types/src/controllers/customization_controller.d.ts +0 -60
- package/dist/types/src/controllers/data.d.ts +0 -207
- package/dist/types/src/controllers/data_driver.d.ts +0 -218
- package/dist/types/src/controllers/database_admin.d.ts +0 -11
- package/dist/types/src/controllers/dialogs_controller.d.ts +0 -36
- package/dist/types/src/controllers/effective_role.d.ts +0 -4
- package/dist/types/src/controllers/email.d.ts +0 -36
- package/dist/types/src/controllers/index.d.ts +0 -18
- package/dist/types/src/controllers/local_config_persistence.d.ts +0 -20
- package/dist/types/src/controllers/navigation.d.ts +0 -225
- package/dist/types/src/controllers/registry.d.ts +0 -63
- package/dist/types/src/controllers/side_dialogs_controller.d.ts +0 -67
- package/dist/types/src/controllers/side_entity_controller.d.ts +0 -97
- package/dist/types/src/controllers/snackbar.d.ts +0 -24
- package/dist/types/src/controllers/storage.d.ts +0 -171
- package/dist/types/src/index.d.ts +0 -4
- package/dist/types/src/rebase_context.d.ts +0 -122
- package/dist/types/src/types/auth_adapter.d.ts +0 -301
- package/dist/types/src/types/backend.d.ts +0 -571
- package/dist/types/src/types/backend_hooks.d.ts +0 -172
- package/dist/types/src/types/builders.d.ts +0 -15
- package/dist/types/src/types/chips.d.ts +0 -5
- package/dist/types/src/types/collections.d.ts +0 -961
- package/dist/types/src/types/component_ref.d.ts +0 -47
- package/dist/types/src/types/cron.d.ts +0 -102
- package/dist/types/src/types/data_source.d.ts +0 -64
- package/dist/types/src/types/database_adapter.d.ts +0 -94
- package/dist/types/src/types/entities.d.ts +0 -145
- package/dist/types/src/types/entity_actions.d.ts +0 -104
- package/dist/types/src/types/entity_callbacks.d.ts +0 -173
- package/dist/types/src/types/entity_link_builder.d.ts +0 -7
- package/dist/types/src/types/entity_overrides.d.ts +0 -10
- package/dist/types/src/types/entity_views.d.ts +0 -87
- package/dist/types/src/types/export_import.d.ts +0 -21
- package/dist/types/src/types/formex.d.ts +0 -40
- package/dist/types/src/types/index.d.ts +0 -28
- package/dist/types/src/types/locales.d.ts +0 -4
- package/dist/types/src/types/modify_collections.d.ts +0 -5
- package/dist/types/src/types/plugins.d.ts +0 -282
- package/dist/types/src/types/properties.d.ts +0 -1173
- package/dist/types/src/types/property_config.d.ts +0 -74
- package/dist/types/src/types/relations.d.ts +0 -336
- package/dist/types/src/types/slots.d.ts +0 -262
- package/dist/types/src/types/translations.d.ts +0 -900
- package/dist/types/src/types/user_management_delegate.d.ts +0 -86
- package/dist/types/src/types/websockets.d.ts +0 -78
- package/dist/types/src/users/index.d.ts +0 -1
- package/dist/types/src/users/user.d.ts +0 -50
- package/src/auth/admin-routes.ts +0 -534
- package/test/admin-routes.test.ts +0 -544
- package/test/backend-hooks-admin.test.ts +0 -388
- /package/dist/{server-core/src/api → api}/ast-schema-editor.d.ts +0 -0
- /package/dist/{server-core/src/api → api}/collections_for_test/callbacks_test_collection.d.ts +0 -0
- /package/dist/{server-core/src/api → api}/graphql/graphql-schema-generator.d.ts +0 -0
- /package/dist/{server-core/src/api → api}/graphql/index.d.ts +0 -0
- /package/dist/{server-core/src/api → api}/index.d.ts +0 -0
- /package/dist/{server-core/src/api → api}/logs-routes.d.ts +0 -0
- /package/dist/{server-core/src/api → api}/openapi-generator.d.ts +0 -0
- /package/dist/{server-core/src/api → api}/rest/index.d.ts +0 -0
- /package/dist/{server-core/src/api → api}/rest/query-parser.d.ts +0 -0
- /package/dist/{server-core/src/api → api}/schema-editor-routes.d.ts +0 -0
- /package/dist/{server-core/src/api → api}/server.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/adapter-middleware.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/api-keys/api-key-middleware.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/api-keys/api-key-permission-guard.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/api-keys/api-key-routes.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/api-keys/api-key-store.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/api-keys/api-key-types.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/apple-oauth.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/bitbucket-oauth.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/crypto-utils.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/custom-auth-adapter.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/discord-oauth.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/facebook-oauth.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/github-oauth.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/gitlab-oauth.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/google-oauth.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/interfaces.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/jwt.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/linkedin-oauth.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/mfa.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/microsoft-oauth.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/middleware.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/password.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/rate-limiter.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/rls-scope.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/routes.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/slack-oauth.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/spotify-oauth.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/twitter-oauth.d.ts +0 -0
- /package/dist/{server-core/src/collections → collections}/BackendCollectionRegistry.d.ts +0 -0
- /package/dist/{server-core/src/collections → collections}/loader.d.ts +0 -0
- /package/dist/{server-core/src/cron → cron}/cron-loader.d.ts +0 -0
- /package/dist/{server-core/src/cron → cron}/cron-routes.d.ts +0 -0
- /package/dist/{server-core/src/cron → cron}/cron-store.d.ts +0 -0
- /package/dist/{server-core/src/cron → cron}/index.d.ts +0 -0
- /package/dist/{server-core/src/db → db}/interfaces.d.ts +0 -0
- /package/dist/{server-core/src/email → email}/index.d.ts +0 -0
- /package/dist/{server-core/src/email → email}/smtp-email-service.d.ts +0 -0
- /package/dist/{server-core/src/email → email}/templates.d.ts +0 -0
- /package/dist/{server-core/src/email → email}/types.d.ts +0 -0
- /package/dist/{server-core/src/functions → functions}/function-loader.d.ts +0 -0
- /package/dist/{server-core/src/functions → functions}/function-routes.d.ts +0 -0
- /package/dist/{server-core/src/functions → functions}/index.d.ts +0 -0
- /package/dist/{server-core/src/history → history}/history-routes.d.ts +0 -0
- /package/dist/{server-core/src/history → history}/index.d.ts +0 -0
- /package/dist/{server-core/src/serve-spa.d.ts → serve-spa.d.ts} +0 -0
- /package/dist/{server-core/src/services → services}/driver-registry.d.ts +0 -0
- /package/dist/{server-core/src/services → services}/webhook-service.d.ts +0 -0
- /package/dist/{server-core/src/singleton.d.ts → singleton.d.ts} +0 -0
- /package/dist/{server-core/src/storage → storage}/LocalStorageController.d.ts +0 -0
- /package/dist/{server-core/src/storage → storage}/S3StorageController.d.ts +0 -0
- /package/dist/{server-core/src/storage → storage}/index.d.ts +0 -0
- /package/dist/{server-core/src/storage → storage}/routes.d.ts +0 -0
- /package/dist/{server-core/src/storage → storage}/storage-registry.d.ts +0 -0
- /package/dist/{server-core/src/storage → storage}/tus-handler.d.ts +0 -0
- /package/dist/{server-core/src/storage → storage}/types.d.ts +0 -0
- /package/dist/{server-core/src/types → types}/index.d.ts +0 -0
- /package/dist/{server-core/src/utils → utils}/dev-port.d.ts +0 -0
- /package/dist/{server-core/src/utils → utils}/logger.d.ts +0 -0
- /package/dist/{server-core/src/utils → utils}/logging.d.ts +0 -0
- /package/dist/{server-core/src/utils → utils}/request-logger.d.ts +0 -0
- /package/dist/{server-core/src/utils → utils}/sql.d.ts +0 -0
package/src/init.ts
CHANGED
|
@@ -25,18 +25,18 @@ import { createBuiltinAuthAdapter } from "./auth/builtin-auth-adapter";
|
|
|
25
25
|
import { errorHandler } from "./api/errors";
|
|
26
26
|
import { Hono } from "hono";
|
|
27
27
|
import { bodyLimit } from "hono/body-limit";
|
|
28
|
-
import { csrf } from "hono/csrf";
|
|
29
28
|
import { HonoEnv } from "./api/types";
|
|
30
29
|
import { configureLogLevel } from "./utils/logging";
|
|
31
30
|
import { logger } from "./utils/logger";
|
|
32
|
-
import {
|
|
31
|
+
import { configureMiddlewares } from "./init/middlewares";
|
|
32
|
+
import { initializeStorage } from "./init/storage";
|
|
33
|
+
import { mountOpenApiDocs } from "./init/docs";
|
|
34
|
+
import { createHealthCheck } from "./init/health";
|
|
35
|
+
import { createShutdown } from "./init/shutdown";
|
|
33
36
|
import { configureJwt, requireAdmin, requireAuth } from "./auth";
|
|
34
37
|
import {
|
|
35
38
|
BackendStorageConfig,
|
|
36
|
-
createStorageController,
|
|
37
39
|
createStorageRoutes,
|
|
38
|
-
DEFAULT_STORAGE_ID,
|
|
39
|
-
DefaultStorageRegistry,
|
|
40
40
|
StorageController,
|
|
41
41
|
StorageRegistry
|
|
42
42
|
} from "./storage";
|
|
@@ -82,7 +82,7 @@ export interface RebaseAuthConfig {
|
|
|
82
82
|
* granted admin-level access without JWT verification. This is the
|
|
83
83
|
* Rebase equivalent of a Firebase Service Account key.
|
|
84
84
|
*
|
|
85
|
-
* Generate with: `node -e "
|
|
85
|
+
* Generate with: `node -e "logger.info(require('crypto').randomBytes(48).toString('base64'))"`
|
|
86
86
|
*
|
|
87
87
|
* Set via `REBASE_SERVICE_KEY` in your `.env`.
|
|
88
88
|
* Must be at least 32 characters.
|
|
@@ -223,9 +223,9 @@ export interface RebaseBackendConfig {
|
|
|
223
223
|
origin: string | string[] | ((origin: string) => boolean);
|
|
224
224
|
};
|
|
225
225
|
/**
|
|
226
|
-
* Backend-level hooks for intercepting
|
|
227
|
-
* at the API boundary. These run server-side after database
|
|
228
|
-
* and before API responses are sent.
|
|
226
|
+
* Backend-level hooks for intercepting collection entity data
|
|
227
|
+
* at the REST API boundary. These run server-side after database
|
|
228
|
+
* operations and before API responses are sent.
|
|
229
229
|
*
|
|
230
230
|
* Complement the per-collection `EntityCallbacks` system which
|
|
231
231
|
* handles collection CRUD operations.
|
|
@@ -309,36 +309,8 @@ async function _initializeRebaseBackend(config: RebaseBackendConfig): Promise<Re
|
|
|
309
309
|
const basePath = config.basePath || "/api";
|
|
310
310
|
const isProduction = process.env.NODE_ENV === "production";
|
|
311
311
|
|
|
312
|
-
//
|
|
313
|
-
|
|
314
|
-
if (maxBodySize > 0) {
|
|
315
|
-
config.app.use(`${basePath}/*`, bodyLimit({
|
|
316
|
-
maxSize: maxBodySize,
|
|
317
|
-
onError: (c) => {
|
|
318
|
-
return c.json({
|
|
319
|
-
error: {
|
|
320
|
-
message: `Request body too large. Maximum size is ${Math.round(maxBodySize / 1024 / 1024)}MB.`,
|
|
321
|
-
code: "PAYLOAD_TOO_LARGE"
|
|
322
|
-
}
|
|
323
|
-
}, 413);
|
|
324
|
-
}
|
|
325
|
-
}));
|
|
326
|
-
logger.info("Request body limit configured", { maxSizeMB: Math.round(maxBodySize / 1024 / 1024) });
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
// ─── CSRF Protection (opt-in) ────────────────────────────────────────
|
|
330
|
-
// BaaS APIs are consumed by mobile apps, SPAs on different origins, and
|
|
331
|
-
// CLI/SDK tools. CSRF is only enabled when the developer explicitly
|
|
332
|
-
// configures it with allowed origins.
|
|
333
|
-
if (config.csrf?.origin) {
|
|
334
|
-
config.app.use(`${basePath}/*`, csrf({
|
|
335
|
-
origin: config.csrf.origin
|
|
336
|
-
}));
|
|
337
|
-
logger.info("CSRF protection enabled");
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
// ─── Request Logging ─────────────────────────────────────────────────
|
|
341
|
-
config.app.use(`${basePath}/*`, requestLogger());
|
|
312
|
+
// Configure Hono middlewares (Request ID, body limit, CSRF, CORS warning, logging)
|
|
313
|
+
configureMiddlewares(config.app, basePath, isProduction, config);
|
|
342
314
|
|
|
343
315
|
const collectionRegistry = new BackendCollectionRegistry();
|
|
344
316
|
let activeCollections = config.collections || [];
|
|
@@ -380,7 +352,7 @@ async function _initializeRebaseBackend(config: RebaseBackendConfig): Promise<Re
|
|
|
380
352
|
initializeHistory: dbAdapter.initializeHistory,
|
|
381
353
|
initializeWebsockets: dbAdapter.initializeWebsockets,
|
|
382
354
|
getAdmin: dbAdapter.getAdmin,
|
|
383
|
-
mountRoutes: dbAdapter.mountRoutes
|
|
355
|
+
mountRoutes: dbAdapter.mountRoutes
|
|
384
356
|
};
|
|
385
357
|
bootstrappers = [wrappedBootstrapper];
|
|
386
358
|
}
|
|
@@ -449,11 +421,27 @@ async function _initializeRebaseBackend(config: RebaseBackendConfig): Promise<Re
|
|
|
449
421
|
// Populate authConfigResult for backward compatibility
|
|
450
422
|
// (the return type still exposes `auth?: BootstrappedAuth`)
|
|
451
423
|
authConfigResult = {
|
|
452
|
-
userService: authAdapter.userManagement ?? {}
|
|
424
|
+
userService: authAdapter.userManagement ?? {}
|
|
453
425
|
};
|
|
454
426
|
} else {
|
|
455
427
|
// ── RebaseAuthConfig — wrap in built-in adapter ──
|
|
456
428
|
const safeAuthConfig = config.auth as RebaseAuthConfig;
|
|
429
|
+
|
|
430
|
+
// Auto-discover the auth collection from activeCollections if not explicitly set
|
|
431
|
+
if (!safeAuthConfig.collection) {
|
|
432
|
+
const foundAuthCollection = activeCollections.find(c => {
|
|
433
|
+
const isAuth = c.auth;
|
|
434
|
+
return isAuth === true || (isAuth && typeof isAuth === "object" && isAuth.enabled === true);
|
|
435
|
+
});
|
|
436
|
+
if (foundAuthCollection) {
|
|
437
|
+
safeAuthConfig.collection = foundAuthCollection;
|
|
438
|
+
logger.info("Auto-discovered auth collection from collection definitions", { slug: foundAuthCollection.slug });
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// Extract the collection-level auth config (if `auth` is an object, not just `true`)
|
|
443
|
+
const collectionAuth = safeAuthConfig.collection ? safeAuthConfig.collection.auth : undefined;
|
|
444
|
+
const collectionAuthConfig = (typeof collectionAuth === "object" && collectionAuth !== null) ? collectionAuth : undefined;
|
|
457
445
|
if (safeAuthConfig.jwtSecret) {
|
|
458
446
|
configureJwt({
|
|
459
447
|
secret: safeAuthConfig.jwtSecret,
|
|
@@ -467,7 +455,7 @@ async function _initializeRebaseBackend(config: RebaseBackendConfig): Promise<Re
|
|
|
467
455
|
if (safeAuthConfig.serviceKey.length < 32) {
|
|
468
456
|
throw new Error(
|
|
469
457
|
"REBASE_SERVICE_KEY is too short. Must be at least 32 characters. " +
|
|
470
|
-
"Generate one with: node -e \"
|
|
458
|
+
"Generate one with: node -e \"logger.info(require('crypto').randomBytes(48).toString('base64'))\""
|
|
471
459
|
);
|
|
472
460
|
}
|
|
473
461
|
serviceKey = safeAuthConfig.serviceKey;
|
|
@@ -478,23 +466,8 @@ async function _initializeRebaseBackend(config: RebaseBackendConfig): Promise<Re
|
|
|
478
466
|
logger.info("Bootstrapping authentication via driver protocol");
|
|
479
467
|
authConfigResult = await defaultBootstrapper.initializeAuth(config.auth, defaultDriverResult);
|
|
480
468
|
|
|
481
|
-
//
|
|
482
|
-
|
|
483
|
-
const oauthProviders: OAuthProvider<unknown>[] = [...(safeAuthConfig.providers || [])];
|
|
484
|
-
// OAuth providers are resolved later in route mounting,
|
|
485
|
-
// but we need them here for the adapter
|
|
486
|
-
authAdapter = createBuiltinAuthAdapter({
|
|
487
|
-
authRepository: authConfigResult.authRepository as import("./auth/interfaces").AuthRepository ?? authConfigResult.userService as import("./auth/interfaces").AuthRepository,
|
|
488
|
-
emailService: authConfigResult.emailService as import("./email").EmailService,
|
|
489
|
-
emailConfig: safeAuthConfig.email,
|
|
490
|
-
allowRegistration: safeAuthConfig.allowRegistration ?? false,
|
|
491
|
-
defaultRole: safeAuthConfig.defaultRole,
|
|
492
|
-
oauthProviders,
|
|
493
|
-
serviceKey,
|
|
494
|
-
hooks: config.hooks,
|
|
495
|
-
authHooks: safeAuthConfig.hooks,
|
|
496
|
-
});
|
|
497
|
-
}
|
|
469
|
+
// The built-in auth adapter is created after OAuth providers
|
|
470
|
+
// are resolved (below) so it only needs to be constructed once.
|
|
498
471
|
|
|
499
472
|
logger.info("Authentication initialized");
|
|
500
473
|
} else {
|
|
@@ -527,50 +500,12 @@ async function _initializeRebaseBackend(config: RebaseBackendConfig): Promise<Re
|
|
|
527
500
|
}
|
|
528
501
|
|
|
529
502
|
// 3. Initialize Storage
|
|
530
|
-
|
|
531
|
-
let storageController: StorageController | undefined;
|
|
532
|
-
|
|
533
|
-
if (config.storage) {
|
|
534
|
-
logger.info("Configuring storage");
|
|
535
|
-
const controllers: Record<string, StorageController> = {};
|
|
536
|
-
|
|
537
|
-
// Helper: resolve a single storage entry to a controller
|
|
538
|
-
const toController = (entry: BackendStorageConfig | StorageController, label: string): StorageController => {
|
|
539
|
-
// Duck-type: if it has putObject, it's already a controller instance
|
|
540
|
-
if (typeof (entry as StorageController).putObject === "function") {
|
|
541
|
-
return entry as StorageController;
|
|
542
|
-
}
|
|
543
|
-
// Otherwise it's a config object — use the built-in factory
|
|
544
|
-
const conf = entry as BackendStorageConfig;
|
|
545
|
-
if (isProduction && conf.type === "local") {
|
|
546
|
-
logger.warn(`Storage backend "${label}" uses local filesystem in production. ` +
|
|
547
|
-
"Files will be lost on container restart. " +
|
|
548
|
-
"Configure S3-compatible storage or a custom StorageController.");
|
|
549
|
-
}
|
|
550
|
-
return createStorageController(conf);
|
|
551
|
-
};
|
|
552
|
-
|
|
553
|
-
if (typeof config.storage === "object" && ("type" in config.storage || typeof (config.storage as StorageController).putObject === "function")) {
|
|
554
|
-
// Single storage config or controller
|
|
555
|
-
controllers[DEFAULT_STORAGE_ID] = toController(config.storage as BackendStorageConfig | StorageController, DEFAULT_STORAGE_ID);
|
|
556
|
-
} else {
|
|
557
|
-
// Multi-backend record
|
|
558
|
-
for (const [storageId, entry] of Object.entries(config.storage as Record<string, BackendStorageConfig | StorageController>)) {
|
|
559
|
-
controllers[storageId] = toController(entry, storageId);
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
if (Object.keys(controllers).length > 0) {
|
|
564
|
-
storageRegistry = DefaultStorageRegistry.create(controllers);
|
|
565
|
-
storageController = storageRegistry.getDefault();
|
|
566
|
-
logger.info("Initialized storage backends", { count: Object.keys(controllers).length });
|
|
567
|
-
}
|
|
568
|
-
}
|
|
503
|
+
const { storageRegistry, storageController } = initializeStorage(config.storage, isProduction);
|
|
569
504
|
|
|
570
505
|
// basePath already resolved above
|
|
571
506
|
|
|
572
507
|
// 4. Mount API Routes
|
|
573
|
-
if (config.auth
|
|
508
|
+
if (config.auth) {
|
|
574
509
|
// ── Auth Capabilities Endpoint ───────────────────────────────────
|
|
575
510
|
// Exposes adapter capabilities so the frontend knows what's available
|
|
576
511
|
// (login form vs external redirect, OAuth providers, etc.)
|
|
@@ -584,70 +519,39 @@ async function _initializeRebaseBackend(config: RebaseBackendConfig): Promise<Re
|
|
|
584
519
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
585
520
|
const oauthProviders: OAuthProvider<any>[] = [...(safeAuthConfig.providers || [])];
|
|
586
521
|
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
if (safeAuthConfig.facebook?.clientId && safeAuthConfig.facebook?.clientSecret) {
|
|
616
|
-
const { createFacebookProvider } = await import("./auth");
|
|
617
|
-
oauthProviders.push(createFacebookProvider(safeAuthConfig.facebook));
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
if (safeAuthConfig.twitter?.clientId && safeAuthConfig.twitter?.clientSecret) {
|
|
621
|
-
const { createTwitterProvider } = await import("./auth");
|
|
622
|
-
oauthProviders.push(createTwitterProvider(safeAuthConfig.twitter));
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
if (safeAuthConfig.discord?.clientId && safeAuthConfig.discord?.clientSecret) {
|
|
626
|
-
const { createDiscordProvider } = await import("./auth");
|
|
627
|
-
oauthProviders.push(createDiscordProvider(safeAuthConfig.discord));
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
if (safeAuthConfig.gitlab?.clientId && safeAuthConfig.gitlab?.clientSecret) {
|
|
631
|
-
const { createGitLabProvider } = await import("./auth");
|
|
632
|
-
oauthProviders.push(createGitLabProvider(safeAuthConfig.gitlab));
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
if (safeAuthConfig.bitbucket?.clientId && safeAuthConfig.bitbucket?.clientSecret) {
|
|
636
|
-
const { createBitbucketProvider } = await import("./auth");
|
|
637
|
-
oauthProviders.push(createBitbucketProvider(safeAuthConfig.bitbucket));
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
if (safeAuthConfig.slack?.clientId && safeAuthConfig.slack?.clientSecret) {
|
|
641
|
-
const { createSlackProvider } = await import("./auth");
|
|
642
|
-
oauthProviders.push(createSlackProvider(safeAuthConfig.slack));
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
if (safeAuthConfig.spotify?.clientId && safeAuthConfig.spotify?.clientSecret) {
|
|
646
|
-
const { createSpotifyProvider } = await import("./auth");
|
|
647
|
-
oauthProviders.push(createSpotifyProvider(safeAuthConfig.spotify));
|
|
522
|
+
// Resolve configured OAuth providers via data-driven registration.
|
|
523
|
+
// Each entry maps a config key to its factory function name and required fields.
|
|
524
|
+
const OAUTH_PROVIDERS: Array<{
|
|
525
|
+
key: keyof RebaseAuthConfig;
|
|
526
|
+
factory: string;
|
|
527
|
+
requiredFields: string[];
|
|
528
|
+
}> = [
|
|
529
|
+
{ key: "google", factory: "createGoogleProvider", requiredFields: ["clientId"] },
|
|
530
|
+
{ key: "linkedin", factory: "createLinkedinProvider", requiredFields: ["clientId", "clientSecret"] },
|
|
531
|
+
{ key: "github", factory: "createGitHubProvider", requiredFields: ["clientId", "clientSecret"] },
|
|
532
|
+
{ key: "microsoft", factory: "createMicrosoftProvider", requiredFields: ["clientId", "clientSecret"] },
|
|
533
|
+
{ key: "apple", factory: "createAppleProvider", requiredFields: ["clientId", "teamId", "keyId", "privateKey"] },
|
|
534
|
+
{ key: "facebook", factory: "createFacebookProvider", requiredFields: ["clientId", "clientSecret"] },
|
|
535
|
+
{ key: "twitter", factory: "createTwitterProvider", requiredFields: ["clientId", "clientSecret"] },
|
|
536
|
+
{ key: "discord", factory: "createDiscordProvider", requiredFields: ["clientId", "clientSecret"] },
|
|
537
|
+
{ key: "gitlab", factory: "createGitLabProvider", requiredFields: ["clientId", "clientSecret"] },
|
|
538
|
+
{ key: "bitbucket", factory: "createBitbucketProvider", requiredFields: ["clientId", "clientSecret"] },
|
|
539
|
+
{ key: "slack", factory: "createSlackProvider", requiredFields: ["clientId", "clientSecret"] },
|
|
540
|
+
{ key: "spotify", factory: "createSpotifyProvider", requiredFields: ["clientId", "clientSecret"] }
|
|
541
|
+
];
|
|
542
|
+
|
|
543
|
+
for (const { key, factory, requiredFields } of OAUTH_PROVIDERS) {
|
|
544
|
+
const providerConfig = safeAuthConfig[key] as Record<string, unknown> | undefined;
|
|
545
|
+
if (providerConfig && requiredFields.every(f => Boolean(providerConfig[f]))) {
|
|
546
|
+
const authModule = await import("./auth");
|
|
547
|
+
const createFn = (authModule as unknown as Record<string, (cfg: unknown) => OAuthProvider<unknown>>)[factory];
|
|
548
|
+
oauthProviders.push(createFn(providerConfig));
|
|
549
|
+
}
|
|
648
550
|
}
|
|
649
551
|
|
|
650
552
|
// Re-create the built-in adapter with all resolved OAuth providers
|
|
553
|
+
const reCollectionAuth = safeAuthConfig.collection ? safeAuthConfig.collection.auth : undefined;
|
|
554
|
+
const collectionAuthConfig = (typeof reCollectionAuth === "object" && reCollectionAuth !== null) ? reCollectionAuth : undefined;
|
|
651
555
|
authAdapter = createBuiltinAuthAdapter({
|
|
652
556
|
authRepository: authConfigResult!.authRepository as import("./auth/interfaces").AuthRepository ?? authConfigResult!.userService as import("./auth/interfaces").AuthRepository,
|
|
653
557
|
emailService: authConfigResult!.emailService as import("./email").EmailService,
|
|
@@ -656,13 +560,13 @@ async function _initializeRebaseBackend(config: RebaseBackendConfig): Promise<Re
|
|
|
656
560
|
defaultRole: safeAuthConfig.defaultRole,
|
|
657
561
|
oauthProviders,
|
|
658
562
|
serviceKey,
|
|
659
|
-
hooks: config.hooks,
|
|
660
563
|
authHooks: safeAuthConfig.hooks,
|
|
564
|
+
collectionAuthConfig
|
|
661
565
|
});
|
|
662
566
|
}
|
|
663
567
|
|
|
664
568
|
// ── Mount auth & admin routes via the adapter ────────────────────
|
|
665
|
-
if (authAdapter.createAuthRoutes) {
|
|
569
|
+
if (authAdapter && authAdapter.createAuthRoutes) {
|
|
666
570
|
const authRoutes = authAdapter.createAuthRoutes();
|
|
667
571
|
if (authRoutes) {
|
|
668
572
|
config.app.route(`${basePath}/auth`, authRoutes);
|
|
@@ -670,7 +574,7 @@ async function _initializeRebaseBackend(config: RebaseBackendConfig): Promise<Re
|
|
|
670
574
|
}
|
|
671
575
|
}
|
|
672
576
|
|
|
673
|
-
if (authAdapter.createAdminRoutes) {
|
|
577
|
+
if (authAdapter && authAdapter.createAdminRoutes) {
|
|
674
578
|
const adminRoutes = authAdapter.createAdminRoutes();
|
|
675
579
|
if (adminRoutes) {
|
|
676
580
|
config.app.route(`${basePath}/admin`, adminRoutes);
|
|
@@ -690,7 +594,7 @@ async function _initializeRebaseBackend(config: RebaseBackendConfig): Promise<Re
|
|
|
690
594
|
// Mount API key admin routes
|
|
691
595
|
const apiKeyRoutes = createApiKeyRoutes({
|
|
692
596
|
store: apiKeyStore,
|
|
693
|
-
serviceKey
|
|
597
|
+
serviceKey
|
|
694
598
|
});
|
|
695
599
|
config.app.route(`${basePath}/admin/api-keys`, apiKeyRoutes);
|
|
696
600
|
logger.info("API key admin routes mounted", { path: `${basePath}/admin/api-keys` });
|
|
@@ -771,14 +675,14 @@ async function _initializeRebaseBackend(config: RebaseBackendConfig): Promise<Re
|
|
|
771
675
|
adapter: authAdapter,
|
|
772
676
|
driver: defaultDriver,
|
|
773
677
|
requireAuth: dataRequireAuth,
|
|
774
|
-
apiKeyStore
|
|
678
|
+
apiKeyStore
|
|
775
679
|
}));
|
|
776
680
|
} else {
|
|
777
681
|
dataRouter.use("/*", createAuthMiddleware({
|
|
778
682
|
driver: defaultDriver,
|
|
779
683
|
requireAuth: dataRequireAuth,
|
|
780
684
|
serviceKey,
|
|
781
|
-
apiKeyStore
|
|
685
|
+
apiKeyStore
|
|
782
686
|
}));
|
|
783
687
|
}
|
|
784
688
|
|
|
@@ -798,45 +702,19 @@ async function _initializeRebaseBackend(config: RebaseBackendConfig): Promise<Re
|
|
|
798
702
|
dataRouter.route("/", historyRoutes);
|
|
799
703
|
}
|
|
800
704
|
|
|
801
|
-
const restGenerator = new RestApiGenerator(
|
|
705
|
+
const restGenerator = new RestApiGenerator(
|
|
706
|
+
activeCollections,
|
|
707
|
+
defaultDriver,
|
|
708
|
+
config.hooks?.data,
|
|
709
|
+
authAdapter
|
|
710
|
+
);
|
|
802
711
|
dataRouter.route("/", restGenerator.generateRoutes());
|
|
803
712
|
|
|
804
713
|
config.app.route(`${basePath}/data`, dataRouter);
|
|
805
714
|
}
|
|
806
715
|
|
|
807
716
|
// ── OpenAPI / Swagger ─────────────────────────────────────────────────
|
|
808
|
-
|
|
809
|
-
const { generateOpenApiSpec } = await import("./api/openapi-generator");
|
|
810
|
-
|
|
811
|
-
config.app.get(`${basePath}/docs`, (c) => {
|
|
812
|
-
const spec = generateOpenApiSpec(activeCollections, {
|
|
813
|
-
basePath,
|
|
814
|
-
requireAuth: resolveRequireAuth(config.auth)
|
|
815
|
-
});
|
|
816
|
-
return c.json(spec);
|
|
817
|
-
});
|
|
818
|
-
|
|
819
|
-
if (process.env.NODE_ENV !== "production") {
|
|
820
|
-
config.app.get(`${basePath}/swagger`, (c) => {
|
|
821
|
-
return c.html(`<!DOCTYPE html>
|
|
822
|
-
<html>
|
|
823
|
-
<head>
|
|
824
|
-
<title>Rebase API Documentation</title>
|
|
825
|
-
<meta charset="utf-8"/>
|
|
826
|
-
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
|
827
|
-
<link rel="stylesheet" type="text/css" href="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css"/>
|
|
828
|
-
<style>body{margin:0;padding:0;}</style>
|
|
829
|
-
</head>
|
|
830
|
-
<body>
|
|
831
|
-
<div id="swagger-ui"></div>
|
|
832
|
-
<script src="https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js"></script>
|
|
833
|
-
<script>SwaggerUIBundle({ url: '${basePath}/docs', dom_id: '#swagger-ui' });</script>
|
|
834
|
-
</body>
|
|
835
|
-
</html>`);
|
|
836
|
-
});
|
|
837
|
-
logger.info("Swagger UI available", { path: `${basePath}/swagger` });
|
|
838
|
-
}
|
|
839
|
-
}
|
|
717
|
+
await mountOpenApiDocs(config.app, basePath, config.enableSwagger, activeCollections, resolveRequireAuth(config.auth));
|
|
840
718
|
|
|
841
719
|
// ─── Server-side singleton ────────────────────────────────────────────
|
|
842
720
|
// Build the admin-level RebaseClient and expose it as the `rebase` singleton.
|
|
@@ -925,14 +803,14 @@ async function _initializeRebaseBackend(config: RebaseBackendConfig): Promise<Re
|
|
|
925
803
|
adapter: authAdapter,
|
|
926
804
|
driver: defaultDriver,
|
|
927
805
|
requireAuth: functionsRequireAuth,
|
|
928
|
-
apiKeyStore
|
|
806
|
+
apiKeyStore
|
|
929
807
|
}));
|
|
930
808
|
} else {
|
|
931
809
|
functionsRouter.use("/*", createAuthMiddleware({
|
|
932
810
|
driver: defaultDriver,
|
|
933
811
|
requireAuth: functionsRequireAuth,
|
|
934
812
|
serviceKey,
|
|
935
|
-
apiKeyStore
|
|
813
|
+
apiKeyStore
|
|
936
814
|
}));
|
|
937
815
|
}
|
|
938
816
|
|
|
@@ -1005,86 +883,14 @@ async function _initializeRebaseBackend(config: RebaseBackendConfig): Promise<Re
|
|
|
1005
883
|
logger.info("Rebase Backend Initialized");
|
|
1006
884
|
|
|
1007
885
|
// ── Deep Health Check ─────────────────────────────────────────────────
|
|
1008
|
-
const healthCheck =
|
|
1009
|
-
const start = performance.now();
|
|
1010
|
-
try {
|
|
1011
|
-
// Use admin.executeSql if available (Postgres), otherwise try fetchCollection as a probe
|
|
1012
|
-
const admin = defaultDriver.admin;
|
|
1013
|
-
if (isSQLAdmin(admin)) {
|
|
1014
|
-
await admin.executeSql("SELECT 1");
|
|
1015
|
-
} else {
|
|
1016
|
-
// Fallback: try a lightweight fetch to confirm driver is responsive
|
|
1017
|
-
await defaultDriver.fetchCollection({
|
|
1018
|
-
path: "__health_check_nonexistent__",
|
|
1019
|
-
limit: 1
|
|
1020
|
-
});
|
|
1021
|
-
}
|
|
1022
|
-
const latencyMs = Math.round(performance.now() - start);
|
|
1023
|
-
return {
|
|
1024
|
-
healthy: true,
|
|
1025
|
-
latencyMs
|
|
1026
|
-
};
|
|
1027
|
-
} catch (error: unknown) {
|
|
1028
|
-
const latencyMs = Math.round(performance.now() - start);
|
|
1029
|
-
logger.error("Health check failed", {
|
|
1030
|
-
error: error instanceof Error ? error : new Error(String(error)),
|
|
1031
|
-
latencyMs
|
|
1032
|
-
});
|
|
1033
|
-
return {
|
|
1034
|
-
healthy: false,
|
|
1035
|
-
latencyMs,
|
|
1036
|
-
details: {
|
|
1037
|
-
error: error instanceof Error ? error.message : String(error)
|
|
1038
|
-
}
|
|
1039
|
-
};
|
|
1040
|
-
}
|
|
1041
|
-
};
|
|
886
|
+
const healthCheck = createHealthCheck(defaultDriver);
|
|
1042
887
|
|
|
1043
888
|
// ── Graceful Shutdown ─────────────────────────────────────────────────
|
|
1044
|
-
const shutdown = (
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
// 1. Stop cron scheduler
|
|
1050
|
-
if (cronScheduler) {
|
|
1051
|
-
cronScheduler.stop();
|
|
1052
|
-
logger.info("Cron scheduler stopped");
|
|
1053
|
-
}
|
|
1054
|
-
|
|
1055
|
-
// 2. Tear down realtime services (LISTEN clients, debounce timers,
|
|
1056
|
-
// subscriptions). Must happen BEFORE pool.end() so that pending
|
|
1057
|
-
// timer callbacks don't fire against a closed pool.
|
|
1058
|
-
for (const [key, rt] of Object.entries(realtimeServices)) {
|
|
1059
|
-
try {
|
|
1060
|
-
if (typeof rt.destroy === "function") {
|
|
1061
|
-
await rt.destroy();
|
|
1062
|
-
logger.info(`Realtime service "${key}" destroyed`);
|
|
1063
|
-
} else if (typeof rt.stopListening === "function") {
|
|
1064
|
-
await rt.stopListening();
|
|
1065
|
-
logger.info(`Realtime service "${key}" LISTEN client stopped`);
|
|
1066
|
-
}
|
|
1067
|
-
} catch (err) {
|
|
1068
|
-
logger.warn(`Error destroying realtime service "${key}":`, { error: err });
|
|
1069
|
-
}
|
|
1070
|
-
}
|
|
1071
|
-
|
|
1072
|
-
// 3. Close the HTTP server (stop accepting, drain in-flight)
|
|
1073
|
-
config.server.close(() => {
|
|
1074
|
-
logger.info("HTTP server closed");
|
|
1075
|
-
resolve();
|
|
1076
|
-
});
|
|
1077
|
-
|
|
1078
|
-
// 4. Force-resolve after timeout (unless disabled with 0)
|
|
1079
|
-
if (timeoutMs > 0) {
|
|
1080
|
-
setTimeout(() => {
|
|
1081
|
-
logger.warn(`Forced shutdown after ${timeoutMs / 1000}s timeout`);
|
|
1082
|
-
resolve();
|
|
1083
|
-
}, timeoutMs).unref();
|
|
1084
|
-
}
|
|
1085
|
-
})();
|
|
1086
|
-
});
|
|
1087
|
-
};
|
|
889
|
+
const shutdown = createShutdown({
|
|
890
|
+
server: config.server,
|
|
891
|
+
cronScheduler,
|
|
892
|
+
realtimeServices
|
|
893
|
+
});
|
|
1088
894
|
|
|
1089
895
|
return {
|
|
1090
896
|
driverRegistry,
|
package/src/serve-spa.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { Hono } from "hono";
|
|
|
2
2
|
import { serveStatic } from "@hono/node-server/serve-static";
|
|
3
3
|
import * as path from "path";
|
|
4
4
|
import * as fs from "fs";
|
|
5
|
+
import fsp from "node:fs/promises";
|
|
5
6
|
import { logger } from "./utils/logger.js";
|
|
6
7
|
|
|
7
8
|
/**
|
|
@@ -59,6 +60,9 @@ export function serveSPA<E extends import("hono").Env>(app: Hono<E>, config: Ser
|
|
|
59
60
|
// Build list of paths to exclude from SPA handling
|
|
60
61
|
const allExcludePaths = [apiBasePath, ...excludePaths];
|
|
61
62
|
|
|
63
|
+
// Cache the index.html content to avoid re-reading from disk on every navigation request.
|
|
64
|
+
let cachedHtml: string | null = null;
|
|
65
|
+
|
|
62
66
|
// SPA fallback - serve index.html for all non-excluded routes
|
|
63
67
|
app.get("*", async (c, next) => {
|
|
64
68
|
// Skip excluded paths (API, health checks, etc.)
|
|
@@ -68,13 +72,16 @@ export function serveSPA<E extends import("hono").Env>(app: Hono<E>, config: Ser
|
|
|
68
72
|
|
|
69
73
|
const indexPath = path.join(frontendPath, indexFile);
|
|
70
74
|
|
|
71
|
-
if (!
|
|
72
|
-
|
|
73
|
-
|
|
75
|
+
if (!cachedHtml) {
|
|
76
|
+
try {
|
|
77
|
+
cachedHtml = await fsp.readFile(indexPath, "utf-8");
|
|
78
|
+
} catch {
|
|
79
|
+
logger.warn(`⚠️ Index file not found: ${indexPath}`);
|
|
80
|
+
return next();
|
|
81
|
+
}
|
|
74
82
|
}
|
|
75
83
|
|
|
76
|
-
|
|
77
|
-
return c.html(html);
|
|
84
|
+
return c.html(cachedHtml);
|
|
78
85
|
});
|
|
79
86
|
|
|
80
87
|
logger.info(`✅ SPA serving enabled from: ${frontendPath}`);
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import { DataDriver } from "@rebasepro/types";
|
|
15
|
+
import { logger } from "../utils/logger";
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* The default driver identifier used when:
|
|
@@ -95,7 +96,7 @@ export class DefaultDriverRegistry implements DriverRegistry {
|
|
|
95
96
|
if (!registry.has(DEFAULT_DRIVER_ID) && registry.size() > 0) {
|
|
96
97
|
// If no explicit "(default)", use the first one as default
|
|
97
98
|
const firstId = Object.keys(input)[0];
|
|
98
|
-
|
|
99
|
+
logger.warn(
|
|
99
100
|
`[DriverRegistry] No "${DEFAULT_DRIVER_ID}" driver provided. ` +
|
|
100
101
|
`Using "${firstId}" as the default.`
|
|
101
102
|
);
|
|
@@ -108,7 +109,7 @@ export class DefaultDriverRegistry implements DriverRegistry {
|
|
|
108
109
|
|
|
109
110
|
register(id: string, delegate: DataDriver): void {
|
|
110
111
|
if (this.delegates.has(id)) {
|
|
111
|
-
|
|
112
|
+
logger.warn(`[DriverRegistry] Overwriting driver with id "${id}"`);
|
|
112
113
|
}
|
|
113
114
|
this.delegates.set(id, delegate);
|
|
114
115
|
}
|
|
@@ -144,7 +145,7 @@ export class DefaultDriverRegistry implements DriverRegistry {
|
|
|
144
145
|
}
|
|
145
146
|
|
|
146
147
|
// Fallback to default with warning
|
|
147
|
-
|
|
148
|
+
logger.warn(
|
|
148
149
|
`[DriverRegistry] Driver "${id}" not found, falling back to "${DEFAULT_DRIVER_ID}"`
|
|
149
150
|
);
|
|
150
151
|
return this.getDefault();
|
|
@@ -213,8 +213,7 @@ export class S3StorageController implements StorageController {
|
|
|
213
213
|
|
|
214
214
|
// Convert stream to buffer
|
|
215
215
|
const chunks: Uint8Array[] = [];
|
|
216
|
-
|
|
217
|
-
for await (const chunk of response.Body) {
|
|
216
|
+
for await (const chunk of response.Body as AsyncIterable<Uint8Array>) {
|
|
218
217
|
chunks.push(chunk);
|
|
219
218
|
}
|
|
220
219
|
const buffer = Buffer.concat(chunks);
|