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