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