@rebasepro/server 0.17.3 → 0.18.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/LICENSE +0 -1
- package/README.md +5 -1
- package/bin/rebase-server.js +16 -1
- package/dist/{GCSStorageController-Bl7nmhdv.js → GCSStorageController-ZXPoNqW3.js} +21 -10
- package/dist/GCSStorageController-ZXPoNqW3.js.map +1 -0
- package/dist/{S3StorageController-CWvvrRpF.js → S3StorageController-5pAXyv31.js} +24 -10
- package/dist/S3StorageController-5pAXyv31.js.map +1 -0
- package/dist/admin-roles-vYdp_Pil.js +36 -0
- package/dist/admin-roles-vYdp_Pil.js.map +1 -0
- package/dist/{admin_block-BX6YULvJ.js → admin_block-0Xu0r6eZ.js} +4 -1
- package/dist/admin_block-0Xu0r6eZ.js.map +1 -0
- package/dist/api/errors.d.ts +37 -0
- package/dist/api/live-schema-routes.d.ts +14 -0
- package/dist/api/logs-routes.d.ts +8 -0
- package/dist/api/rest/api-generator.d.ts +33 -0
- package/dist/api/root-error-handler.d.ts +62 -0
- package/dist/api/schema-drift.d.ts +39 -0
- package/dist/api/types.d.ts +55 -0
- package/dist/{ast-schema-editor-CvkYOvri.js → ast-schema-editor-CNgFJ3NF.js} +23 -7
- package/dist/ast-schema-editor-CNgFJ3NF.js.map +1 -0
- package/dist/auth/api-keys/api-key-routes.d.ts +12 -0
- package/dist/auth/auth-hooks.d.ts +13 -2
- package/dist/auth/exclude-auth-secrets.d.ts +14 -0
- package/dist/auth/jwt.d.ts +22 -1
- package/dist/auth/mfa-routes.d.ts +10 -1
- package/dist/auth/middleware.d.ts +22 -0
- package/dist/auth/rate-limiter.d.ts +50 -0
- package/dist/auth/registration-policy.d.ts +27 -0
- package/dist/auth/rls-scope.d.ts +9 -1
- package/dist/auth/routes.d.ts +8 -1
- package/dist/auth/seed-admin.d.ts +73 -0
- package/dist/auth/session-routes.d.ts +10 -0
- package/dist/{auth-DkbzMZUS.js → auth-BRiOuyq8.js} +283 -198
- package/dist/auth-BRiOuyq8.js.map +1 -0
- package/dist/{backup-CRdZkA6c.js → backup-C8P6Cl3G.js} +2 -2
- package/dist/{backup-CRdZkA6c.js.map → backup-C8P6Cl3G.js.map} +1 -1
- package/dist/boot/boot.d.ts +18 -0
- package/dist/boot/bundle.d.ts +29 -6
- package/dist/boot/ddl-bootstrap.d.ts +2 -0
- package/dist/boot/env.d.ts +30 -1
- package/dist/boot/fetch-bundle.d.ts +24 -1
- package/dist/boot/options.d.ts +0 -8
- package/dist/boot/provision.d.ts +58 -18
- package/dist/boot/resource-adapters.d.ts +11 -1
- package/dist/boot/resource-resolvers.d.ts +96 -0
- package/dist/boot/sources.d.ts +27 -20
- package/dist/collections/validate-config.d.ts +29 -0
- package/dist/{contract-routes-CHPVKTZW.js → contract-routes-CusnEB5h.js} +24 -9
- package/dist/{contract-routes-CHPVKTZW.js.map → contract-routes-CusnEB5h.js.map} +1 -1
- package/dist/cron/cron-loader.d.ts +7 -1
- package/dist/cron/cron-scheduler.d.ts +48 -2
- package/dist/{cron-loader-d9WMFENB.js → cron-loader-DnmIePn_.js} +23 -4
- package/dist/cron-loader-DnmIePn_.js.map +1 -0
- package/dist/{cron-routes-DSC-kZXI.js → cron-routes-D3x2ydMa.js} +9 -6
- package/dist/cron-routes-D3x2ydMa.js.map +1 -0
- package/dist/{cron-scheduler-BB82dWuU.js → cron-scheduler-BpPKpl4i.js} +148 -28
- package/dist/cron-scheduler-BpPKpl4i.js.map +1 -0
- package/dist/{cron-store-KeN8rN63.js → cron-store-CCQXwgVL.js} +5 -5
- package/dist/{cron-store-KeN8rN63.js.map → cron-store-CCQXwgVL.js.map} +1 -1
- package/dist/{ddl-bootstrap-5YZCZ8qk.js → ddl-bootstrap-CfNvxMuK.js} +26 -2
- package/dist/{ddl-bootstrap-5YZCZ8qk.js.map → ddl-bootstrap-CfNvxMuK.js.map} +1 -1
- package/dist/email/index.d.ts +1 -0
- package/dist/email/unconfigured.d.ts +20 -0
- package/dist/env.d.ts +25 -0
- package/dist/errors-HjfaPlvY.js +385 -0
- package/dist/errors-HjfaPlvY.js.map +1 -0
- package/dist/{function-loader-LLdmBFoL.js → function-loader-xnbDAPfa.js} +9 -3
- package/dist/{function-loader-LLdmBFoL.js.map → function-loader-xnbDAPfa.js.map} +1 -1
- package/dist/function-routes-gQ0EShVG.js +267 -0
- package/dist/function-routes-gQ0EShVG.js.map +1 -0
- package/dist/functions/define-function.d.ts +6 -3
- package/dist/functions/function-routes.d.ts +8 -4
- package/dist/functions/index.js +128 -23
- package/dist/functions/index.js.map +1 -1
- package/dist/{history-recorder-B2pL3X8b.js → history-recorder-B1FwXx9J.js} +3 -3
- package/dist/{history-recorder-B2pL3X8b.js.map → history-recorder-B1FwXx9J.js.map} +1 -1
- package/dist/{history-store-BUJ-37Sm.js → history-store-LHXaQywp.js} +2 -2
- package/dist/{history-store-BUJ-37Sm.js.map → history-store-LHXaQywp.js.map} +1 -1
- package/dist/index.d.ts +16 -4
- package/dist/index.es.js +16229 -7676
- package/dist/index.es.js.map +1 -1
- package/dist/init.d.ts +33 -13
- package/dist/{jobs-XXXcZrmj.js → jobs-DkkD9mPV.js} +5 -5
- package/dist/{jobs-XXXcZrmj.js.map → jobs-DkkD9mPV.js.map} +1 -1
- package/dist/{jwt-DNeOn39r.js → jwt-DkhXwMzR.js} +9 -314
- package/dist/jwt-DkhXwMzR.js.map +1 -0
- package/dist/keys-g8lbVC_o.js +230 -0
- package/dist/keys-g8lbVC_o.js.map +1 -0
- package/dist/{logger-DS03e908.js → logger-DO2PZc4i.js} +197 -18
- package/dist/logger-DO2PZc4i.js.map +1 -0
- package/dist/{logs-routes-DB72iQSr.js → logs-routes-CbsTpozn.js} +74 -6
- package/dist/logs-routes-CbsTpozn.js.map +1 -0
- package/dist/{openapi-generator-CpZp2Fai.js → openapi-generator-BIBbO1Tq.js} +221 -38
- package/dist/openapi-generator-BIBbO1Tq.js.map +1 -0
- package/dist/{proxy-CMymhnwG.js → proxy-Czngl3p9.js} +2 -2
- package/dist/{proxy-CMymhnwG.js.map → proxy-Czngl3p9.js.map} +1 -1
- package/dist/{query-parser-DqXNaBIH.js → query-parser-C68Q9EX4.js} +35 -6
- package/dist/{query-parser-DqXNaBIH.js.map → query-parser-C68Q9EX4.js.map} +1 -1
- package/dist/queues/runtime.d.ts +56 -0
- package/dist/{request-timeout-C8gkc-j7.js → request-timeout-DESvlfrS.js} +4 -6
- package/dist/request-timeout-DESvlfrS.js.map +1 -0
- package/dist/schema-edit/project-root.d.ts +4 -4
- package/dist/{schema-editor-routes-DNV_nBhW.js → schema-editor-routes-CcZKh50q.js} +4 -4
- package/dist/{schema-editor-routes-DNV_nBhW.js.map → schema-editor-routes-CcZKh50q.js.map} +1 -1
- package/dist/{schemas-DBxgjM9A.js → schemas-C3234HWE.js} +2746 -72
- package/dist/schemas-C3234HWE.js.map +1 -0
- package/dist/singleton.d.ts +6 -4
- package/dist/src-DHK4fHkw.js +1002 -0
- package/dist/src-DHK4fHkw.js.map +1 -0
- package/dist/{src-C9gKj23J.js → src-Dq-I3Ybx.js} +507 -54
- package/dist/src-Dq-I3Ybx.js.map +1 -0
- package/dist/storage/GCSStorageController.d.ts +5 -0
- package/dist/storage/LocalStorageController.d.ts +14 -0
- package/dist/storage/S3StorageController.d.ts +8 -0
- package/dist/storage/image-transform.d.ts +35 -0
- package/dist/storage/keys.d.ts +23 -0
- package/dist/storage/policies.d.ts +22 -0
- package/dist/storage/routes.d.ts +9 -0
- package/dist/storage/tus-handler.d.ts +43 -1
- package/dist/storage/types.d.ts +28 -0
- package/dist/types-BfKcm9do.js.map +1 -1
- package/dist/utils/dev-port.d.ts +18 -0
- package/dist/utils/logger.d.ts +60 -0
- package/dist/utils/request-logger.d.ts +2 -1
- package/package.json +30 -24
- package/dist/GCSStorageController-Bl7nmhdv.js.map +0 -1
- package/dist/S3StorageController-CWvvrRpF.js.map +0 -1
- package/dist/admin_block-BX6YULvJ.js.map +0 -1
- package/dist/ast-schema-editor-CvkYOvri.js.map +0 -1
- package/dist/auth-DkbzMZUS.js.map +0 -1
- package/dist/cron-loader-d9WMFENB.js.map +0 -1
- package/dist/cron-routes-DSC-kZXI.js.map +0 -1
- package/dist/cron-scheduler-BB82dWuU.js.map +0 -1
- package/dist/errors-DBwpj9N8.js +0 -231
- package/dist/errors-DBwpj9N8.js.map +0 -1
- package/dist/function-routes-ClT6UQpD.js +0 -41
- package/dist/function-routes-ClT6UQpD.js.map +0 -1
- package/dist/jwt-DNeOn39r.js.map +0 -1
- package/dist/logger-DS03e908.js.map +0 -1
- package/dist/logs-routes-DB72iQSr.js.map +0 -1
- package/dist/openapi-generator-CpZp2Fai.js.map +0 -1
- package/dist/request-timeout-C8gkc-j7.js.map +0 -1
- package/dist/schemas-DBxgjM9A.js.map +0 -1
- package/dist/src-C9gKj23J.js.map +0 -1
- package/dist/src-CcOl05Oq.js +0 -552
- package/dist/src-CcOl05Oq.js.map +0 -1
- package/dist/utils/logging.d.ts +0 -5
|
@@ -3,7 +3,7 @@ import __rebaseProcess from "process";
|
|
|
3
3
|
globalThis.process ??= __rebaseProcess;
|
|
4
4
|
__rebaseCreateRequire(import.meta.url);
|
|
5
5
|
import { n as __exportAll } from "./rolldown-runtime-dW7B1o5h.js";
|
|
6
|
-
import { n as errorHandler, t as ApiError } from "./errors-
|
|
6
|
+
import { n as errorHandler, t as ApiError } from "./errors-HjfaPlvY.js";
|
|
7
7
|
import fs from "fs";
|
|
8
8
|
import path from "path";
|
|
9
9
|
import { Hono } from "hono";
|
|
@@ -161,4 +161,4 @@ var backup_exports = /* @__PURE__ */ __exportAll({
|
|
|
161
161
|
//#endregion
|
|
162
162
|
export { parseBackupTimestamp as a, parseBackupDestination as i, createBackupRoutes as n, readBackupBytes as o, listBackupObjects as r, backup_exports as t };
|
|
163
163
|
|
|
164
|
-
//# sourceMappingURL=backup-
|
|
164
|
+
//# sourceMappingURL=backup-C8P6Cl3G.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"backup-CRdZkA6c.js","names":[],"sources":["../src/backup/backup-common.ts","../src/backup/backup-routes.ts","../src/backup/index.ts"],"sourcesContent":["/**\n * Storage-generic backup helpers used by the admin backup routes.\n *\n * These live in server (which owns {@link StorageController}) so the\n * admin API can list backups without depending on the Postgres driver —\n * `server` must not import from `server-postgres`. The `pg_dump`-side\n * copies of the tiny pure parsers live in `@rebasepro/server-postgres`;\n * keep the two in sync if the naming scheme ever changes.\n */\nimport fs from \"fs\";\nimport path from \"path\";\nimport type { BackupInfo, BackupDestinationKind } from \"@rebasepro/types\";\nimport type { StorageController } from \"../storage\";\n\nexport type BackupDestination =\n | { kind: \"local\"; path: string }\n | { kind: \"s3\"; bucket: string; prefix: string }\n | { kind: \"gcs\"; bucket: string; prefix: string };\n\n/** Parse a destination string (`s3://…`, `gs://…`, or a local path). */\nexport function parseBackupDestination(out: string): BackupDestination {\n const s3 = out.match(/^s3:\\/\\/([^/]+)\\/?(.*)$/);\n if (s3) return { kind: \"s3\", bucket: s3[1], prefix: s3[2].replace(/\\/+$/, \"\") };\n const gcs = out.match(/^gs:\\/\\/([^/]+)\\/?(.*)$/);\n if (gcs) return { kind: \"gcs\", bucket: gcs[1], prefix: gcs[2].replace(/\\/+$/, \"\") };\n return { kind: \"local\", path: out };\n}\n\n/**\n * Recover the UTC creation time encoded in a `rebase-<db>-<ts>.dump` name.\n * Returns `null` for names that don't match, so foreign objects are ignored.\n */\nexport function parseBackupTimestamp(fileName: string): Date | null {\n const base = fileName.split(\"/\").pop() ?? fileName;\n const match = base.match(/-(\\d{8})T(\\d{6})Z\\.dump$/);\n if (!match) return null;\n const [, ymd, hms] = match;\n const iso =\n `${ymd.slice(0, 4)}-${ymd.slice(4, 6)}-${ymd.slice(6, 8)}` +\n `T${hms.slice(0, 2)}:${hms.slice(2, 4)}:${hms.slice(4, 6)}Z`;\n const date = new Date(iso);\n return Number.isNaN(date.getTime()) ? null : date;\n}\n\n/**\n * List the backups at a destination as {@link BackupInfo}, newest first.\n * Only `.dump` files are considered.\n */\nexport async function listBackupObjects(\n dest: BackupDestination,\n storage?: StorageController\n): Promise<BackupInfo[]> {\n if (dest.kind === \"local\") {\n if (!fs.existsSync(dest.path)) return [];\n const stat = fs.statSync(dest.path);\n const dir = stat.isDirectory() ? dest.path : path.dirname(dest.path);\n if (!fs.existsSync(dir)) return [];\n return fs\n .readdirSync(dir)\n .filter((f) => f.endsWith(\".dump\"))\n .map((f): BackupInfo => {\n const full = path.join(dir, f);\n const st = fs.statSync(full);\n const createdAt = parseBackupTimestamp(f) ?? st.mtime;\n return {\n key: full,\n name: f,\n sizeBytes: st.size,\n createdAt: createdAt.toISOString(),\n destinationKind: \"local\"\n };\n })\n .sort(byNewest);\n }\n\n if (!storage) return [];\n const result = await storage.listObjects(dest.prefix ? `${dest.prefix}/` : \"\", {\n bucket: dest.bucket,\n maxResults: 1000\n });\n return result.items\n .map((item) => item.fullPath)\n .filter((key) => key.endsWith(\".dump\"))\n .map((key): BackupInfo => {\n const createdAt = parseBackupTimestamp(key);\n return {\n key,\n name: key.split(\"/\").pop() || key,\n createdAt: createdAt ? createdAt.toISOString() : undefined,\n destinationKind: dest.kind as BackupDestinationKind\n };\n })\n .sort(byNewest);\n}\n\nfunction byNewest(a: BackupInfo, b: BackupInfo): number {\n const ta = a.createdAt ? Date.parse(a.createdAt) : 0;\n const tb = b.createdAt ? Date.parse(b.createdAt) : 0;\n return tb - ta;\n}\n\n/** Read a single backup's bytes. Guards against path traversal for local. */\nexport async function readBackupBytes(\n dest: BackupDestination,\n key: string,\n storage?: StorageController\n): Promise<{ bytes: Uint8Array<ArrayBuffer>; name: string } | null> {\n if (dest.kind === \"local\") {\n const stat = fs.existsSync(dest.path) ? fs.statSync(dest.path) : null;\n const dir = stat?.isDirectory() ? dest.path : path.dirname(dest.path);\n const resolvedDir = path.resolve(dir);\n const resolved = path.resolve(key);\n // Only allow reads inside the backup directory, and only .dump files.\n if (!resolved.startsWith(resolvedDir + path.sep) || !resolved.endsWith(\".dump\")) {\n return null;\n }\n if (!fs.existsSync(resolved)) return null;\n return { bytes: new Uint8Array(fs.readFileSync(resolved)), name: path.basename(resolved) };\n }\n\n if (!storage) return null;\n if (!key.endsWith(\".dump\")) return null;\n const file = await storage.getObject(key, dest.bucket);\n if (!file) return null;\n return { bytes: new Uint8Array(await file.arrayBuffer()), name: key.split(\"/\").pop() || key };\n}\n","import { Hono } from \"hono\";\nimport type { HonoEnv } from \"../api/types\";\nimport { ApiError, errorHandler } from \"../api/errors\";\nimport type { StorageController } from \"../storage\";\nimport { BackupDestination, listBackupObjects, readBackupBytes } from \"./backup-common\";\n\nexport interface BackupRoutesConfig {\n /**\n * Resolve the current backup destination, or `null` when backups are not\n * configured (`BACKUP_DESTINATION` unset). Read lazily so a restart isn't\n * required to pick up config.\n */\n getDestination: () => BackupDestination | null;\n /** Storage controller for object-storage destinations. */\n storage?: StorageController;\n}\n\n/**\n * Admin REST routes for the Backups panel.\n *\n * Routes (mounted under `/admin/backups`, admin-guarded by the caller):\n * GET / → list available backups\n * GET /download → download a backup's bytes (?key=…)\n */\nexport function createBackupRoutes(config: BackupRoutesConfig): Hono<HonoEnv> {\n const router = new Hono<HonoEnv>();\n router.onError(errorHandler);\n\n router.get(\"/\", async (c) => {\n const dest = config.getDestination();\n if (!dest) {\n return c.json({ backups: [], destinationKind: \"local\" as const, configured: false });\n }\n const backups = await listBackupObjects(dest, config.storage);\n return c.json({ backups, destinationKind: dest.kind, configured: true });\n });\n\n router.get(\"/download\", async (c) => {\n const dest = config.getDestination();\n if (!dest) {\n throw ApiError.badRequest(\"Backups are not configured (set BACKUP_DESTINATION).\");\n }\n const key = c.req.query(\"key\");\n if (!key) {\n throw ApiError.badRequest(\"Missing 'key' query parameter.\");\n }\n const result = await readBackupBytes(dest, key, config.storage);\n if (!result) {\n throw ApiError.notFound(`Backup not found: ${key}`);\n }\n c.header(\"Content-Type\", \"application/octet-stream\");\n c.header(\"Content-Disposition\", `attachment; filename=\"${result.name}\"`);\n return c.body(result.bytes);\n });\n\n return router;\n}\n","/**\n * Storage-generic backup helpers and admin routes for the Backups panel.\n * The `pg_dump`/`pg_restore` machinery lives in `@rebasepro/server-postgres`.\n */\nexport * from \"./backup-common\";\nexport { createBackupRoutes } from \"./backup-routes\";\nexport type { BackupRoutesConfig } from \"./backup-routes\";\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"backup-C8P6Cl3G.js","names":[],"sources":["../src/backup/backup-common.ts","../src/backup/backup-routes.ts","../src/backup/index.ts"],"sourcesContent":["/**\n * Storage-generic backup helpers used by the admin backup routes.\n *\n * These live in server (which owns {@link StorageController}) so the\n * admin API can list backups without depending on the Postgres driver —\n * `server` must not import from `server-postgres`. The `pg_dump`-side\n * copies of the tiny pure parsers live in `@rebasepro/server-postgres`;\n * keep the two in sync if the naming scheme ever changes.\n */\nimport fs from \"fs\";\nimport path from \"path\";\nimport type { BackupInfo, BackupDestinationKind } from \"@rebasepro/types\";\nimport type { StorageController } from \"../storage\";\n\nexport type BackupDestination =\n | { kind: \"local\"; path: string }\n | { kind: \"s3\"; bucket: string; prefix: string }\n | { kind: \"gcs\"; bucket: string; prefix: string };\n\n/** Parse a destination string (`s3://…`, `gs://…`, or a local path). */\nexport function parseBackupDestination(out: string): BackupDestination {\n const s3 = out.match(/^s3:\\/\\/([^/]+)\\/?(.*)$/);\n if (s3) return { kind: \"s3\", bucket: s3[1], prefix: s3[2].replace(/\\/+$/, \"\") };\n const gcs = out.match(/^gs:\\/\\/([^/]+)\\/?(.*)$/);\n if (gcs) return { kind: \"gcs\", bucket: gcs[1], prefix: gcs[2].replace(/\\/+$/, \"\") };\n return { kind: \"local\", path: out };\n}\n\n/**\n * Recover the UTC creation time encoded in a `rebase-<db>-<ts>.dump` name.\n * Returns `null` for names that don't match, so foreign objects are ignored.\n */\nexport function parseBackupTimestamp(fileName: string): Date | null {\n const base = fileName.split(\"/\").pop() ?? fileName;\n const match = base.match(/-(\\d{8})T(\\d{6})Z\\.dump$/);\n if (!match) return null;\n const [, ymd, hms] = match;\n const iso =\n `${ymd.slice(0, 4)}-${ymd.slice(4, 6)}-${ymd.slice(6, 8)}` +\n `T${hms.slice(0, 2)}:${hms.slice(2, 4)}:${hms.slice(4, 6)}Z`;\n const date = new Date(iso);\n return Number.isNaN(date.getTime()) ? null : date;\n}\n\n/**\n * List the backups at a destination as {@link BackupInfo}, newest first.\n * Only `.dump` files are considered.\n */\nexport async function listBackupObjects(\n dest: BackupDestination,\n storage?: StorageController\n): Promise<BackupInfo[]> {\n if (dest.kind === \"local\") {\n if (!fs.existsSync(dest.path)) return [];\n const stat = fs.statSync(dest.path);\n const dir = stat.isDirectory() ? dest.path : path.dirname(dest.path);\n if (!fs.existsSync(dir)) return [];\n return fs\n .readdirSync(dir)\n .filter((f) => f.endsWith(\".dump\"))\n .map((f): BackupInfo => {\n const full = path.join(dir, f);\n const st = fs.statSync(full);\n const createdAt = parseBackupTimestamp(f) ?? st.mtime;\n return {\n key: full,\n name: f,\n sizeBytes: st.size,\n createdAt: createdAt.toISOString(),\n destinationKind: \"local\"\n };\n })\n .sort(byNewest);\n }\n\n if (!storage) return [];\n const result = await storage.listObjects(dest.prefix ? `${dest.prefix}/` : \"\", {\n bucket: dest.bucket,\n maxResults: 1000\n });\n return result.items\n .map((item) => item.fullPath)\n .filter((key) => key.endsWith(\".dump\"))\n .map((key): BackupInfo => {\n const createdAt = parseBackupTimestamp(key);\n return {\n key,\n name: key.split(\"/\").pop() || key,\n createdAt: createdAt ? createdAt.toISOString() : undefined,\n destinationKind: dest.kind as BackupDestinationKind\n };\n })\n .sort(byNewest);\n}\n\nfunction byNewest(a: BackupInfo, b: BackupInfo): number {\n const ta = a.createdAt ? Date.parse(a.createdAt) : 0;\n const tb = b.createdAt ? Date.parse(b.createdAt) : 0;\n return tb - ta;\n}\n\n/** Read a single backup's bytes. Guards against path traversal for local. */\nexport async function readBackupBytes(\n dest: BackupDestination,\n key: string,\n storage?: StorageController\n): Promise<{ bytes: Uint8Array<ArrayBuffer>; name: string } | null> {\n if (dest.kind === \"local\") {\n const stat = fs.existsSync(dest.path) ? fs.statSync(dest.path) : null;\n const dir = stat?.isDirectory() ? dest.path : path.dirname(dest.path);\n const resolvedDir = path.resolve(dir);\n const resolved = path.resolve(key);\n // Only allow reads inside the backup directory, and only .dump files.\n if (!resolved.startsWith(resolvedDir + path.sep) || !resolved.endsWith(\".dump\")) {\n return null;\n }\n if (!fs.existsSync(resolved)) return null;\n return { bytes: new Uint8Array(fs.readFileSync(resolved)), name: path.basename(resolved) };\n }\n\n if (!storage) return null;\n if (!key.endsWith(\".dump\")) return null;\n const file = await storage.getObject(key, dest.bucket);\n if (!file) return null;\n return { bytes: new Uint8Array(await file.arrayBuffer()), name: key.split(\"/\").pop() || key };\n}\n","import { Hono } from \"hono\";\nimport type { HonoEnv } from \"../api/types\";\nimport { ApiError, errorHandler } from \"../api/errors\";\nimport type { StorageController } from \"../storage\";\nimport { BackupDestination, listBackupObjects, readBackupBytes } from \"./backup-common\";\n\nexport interface BackupRoutesConfig {\n /**\n * Resolve the current backup destination, or `null` when backups are not\n * configured (`BACKUP_DESTINATION` unset). Read lazily so a restart isn't\n * required to pick up config.\n */\n getDestination: () => BackupDestination | null;\n /** Storage controller for object-storage destinations. */\n storage?: StorageController;\n}\n\n/**\n * Admin REST routes for the Backups panel.\n *\n * Routes (mounted under `/admin/backups`, admin-guarded by the caller):\n * GET / → list available backups\n * GET /download → download a backup's bytes (?key=…)\n */\nexport function createBackupRoutes(config: BackupRoutesConfig): Hono<HonoEnv> {\n const router = new Hono<HonoEnv>();\n router.onError(errorHandler);\n\n router.get(\"/\", async (c) => {\n const dest = config.getDestination();\n if (!dest) {\n return c.json({ backups: [], destinationKind: \"local\" as const, configured: false });\n }\n const backups = await listBackupObjects(dest, config.storage);\n return c.json({ backups, destinationKind: dest.kind, configured: true });\n });\n\n router.get(\"/download\", async (c) => {\n const dest = config.getDestination();\n if (!dest) {\n throw ApiError.badRequest(\"Backups are not configured (set BACKUP_DESTINATION).\");\n }\n const key = c.req.query(\"key\");\n if (!key) {\n throw ApiError.badRequest(\"Missing 'key' query parameter.\");\n }\n const result = await readBackupBytes(dest, key, config.storage);\n if (!result) {\n throw ApiError.notFound(`Backup not found: ${key}`);\n }\n c.header(\"Content-Type\", \"application/octet-stream\");\n c.header(\"Content-Disposition\", `attachment; filename=\"${result.name}\"`);\n return c.body(result.bytes);\n });\n\n return router;\n}\n","/**\n * Storage-generic backup helpers and admin routes for the Backups panel.\n * The `pg_dump`/`pg_restore` machinery lives in `@rebasepro/server-postgres`.\n */\nexport * from \"./backup-common\";\nexport { createBackupRoutes } from \"./backup-routes\";\nexport type { BackupRoutesConfig } from \"./backup-routes\";\n"],"mappings":"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"}
|
package/dist/boot/boot.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { type Server } from "http";
|
|
|
2
2
|
import { Hono } from "hono";
|
|
3
3
|
import { type RebaseBackendInstance } from "../init.js";
|
|
4
4
|
import type { HonoEnv } from "../api/types.js";
|
|
5
|
+
import { type Logger } from "../utils/logger.js";
|
|
5
6
|
import { type RebaseBootEnv } from "./env.js";
|
|
6
7
|
import { type LoadedBundle } from "./bundle.js";
|
|
7
8
|
import { type InitializedDataSource } from "./driver.js";
|
|
@@ -111,6 +112,23 @@ export declare function bootFromBundle(options?: BootOptions): Promise<BootedRun
|
|
|
111
112
|
*/
|
|
112
113
|
export declare function resolveProvisionOnly(env?: NodeJS.ProcessEnv): boolean;
|
|
113
114
|
export declare function runFromBundle(options?: BootOptions): Promise<BootedRuntime>;
|
|
115
|
+
/** The two logger methods {@link reportBootFailure} writes through. */
|
|
116
|
+
export type BootFailureLog = Pick<Logger, "error" | "debug">;
|
|
117
|
+
/**
|
|
118
|
+
* Say why the boot failed, in the form the reader can act on.
|
|
119
|
+
*
|
|
120
|
+
* Extracted from `runFromBundle` so it can be tested: the catch it came from
|
|
121
|
+
* ends in `process.exit(1)`, and what a dying process printed on its way out is
|
|
122
|
+
* exactly the thing nobody had a test for. `CollectionConfigError` spent a
|
|
123
|
+
* release on the wrong side of this branch, so a multi-line validator message —
|
|
124
|
+
* composed over several lines, naming both offending files — reached the author
|
|
125
|
+
* as `{"error":{"name":"Error","message":"1 problem(s) …\n\n…","stack":"… at
|
|
126
|
+
* assertCollectionConfigs (…/dist/index.es.js:727:8) …"}}`: the answer,
|
|
127
|
+
* `\n`-escaped, inside 3 KB of framework frames.
|
|
128
|
+
*/
|
|
129
|
+
export declare function reportBootFailure(err: unknown, log?: BootFailureLog, env?: {
|
|
130
|
+
NODE_ENV?: string;
|
|
131
|
+
}): void;
|
|
114
132
|
/**
|
|
115
133
|
* Say so, once per boot, when a data source's driver is older than this runtime.
|
|
116
134
|
*
|
package/dist/boot/bundle.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { StorageAuthorize } from "../storage/types.js";
|
|
|
3
3
|
/** Thrown when a bundle cannot be read, or claims a contract this runtime cannot honour. */
|
|
4
4
|
export declare class BundleError extends Error {
|
|
5
5
|
readonly hint?: string | undefined;
|
|
6
|
-
constructor(message: string, hint?: string | undefined);
|
|
6
|
+
constructor(message: string, hint?: string | undefined, options?: ErrorOptions);
|
|
7
7
|
}
|
|
8
8
|
/** A bundle that has been located and whose manifest has been validated. */
|
|
9
9
|
export interface LoadedBundle {
|
|
@@ -21,6 +21,17 @@ export interface LoadedBundle {
|
|
|
21
21
|
* warning, so a partially-built bundle still boots its API.
|
|
22
22
|
*/
|
|
23
23
|
staticApps: LoadedStaticApp[];
|
|
24
|
+
/**
|
|
25
|
+
* True when this "bundle" is a view over the project's own source tree —
|
|
26
|
+
* what `rebase dev` boots — rather than a built one.
|
|
27
|
+
*
|
|
28
|
+
* The difference matters to anything that would write a file back: the
|
|
29
|
+
* schema editor rewrites collection *source*, which exists on one of these
|
|
30
|
+
* paths and not the other. Read it from here rather than from
|
|
31
|
+
* `manifest.runtime.builtAgainst === "source"`: the manifest is what a
|
|
32
|
+
* build wrote down, and a bundle can carry any string a builder put there.
|
|
33
|
+
*/
|
|
34
|
+
isSource: boolean;
|
|
24
35
|
}
|
|
25
36
|
/** One built static app inside a loaded bundle, with an absolute directory. */
|
|
26
37
|
export interface LoadedStaticApp {
|
|
@@ -117,7 +128,14 @@ export declare function createSourceBundle(options: {
|
|
|
117
128
|
* is known from the bundle, without reading anyone's secrets.
|
|
118
129
|
*/
|
|
119
130
|
export interface BundleConfigExports {
|
|
131
|
+
/**
|
|
132
|
+
* Read only so that `assertNoReplacedResourceConfig` can refuse a bundle
|
|
133
|
+
* still carrying them. Databases and buckets are declared with `database()`
|
|
134
|
+
* and `bucket()` in `config/resources.ts`; nothing downstream consumes these
|
|
135
|
+
* two fields.
|
|
136
|
+
*/
|
|
120
137
|
dataSources?: DataSourceDefinition[];
|
|
138
|
+
/** See `dataSources` — present only so boot can refuse it by name. */
|
|
121
139
|
storageSources?: StorageSourceDefinition[];
|
|
122
140
|
/**
|
|
123
141
|
* Per-object storage access control.
|
|
@@ -137,11 +155,16 @@ export interface BundleConfigExports {
|
|
|
137
155
|
/**
|
|
138
156
|
* Read the config package's `index` for declarations.
|
|
139
157
|
*
|
|
140
|
-
* Absent
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
* rather than fatal
|
|
144
|
-
* server whose collections are fine.
|
|
158
|
+
* Absent or empty mean the same thing: a single default database and a single
|
|
159
|
+
* default bucket. That is the overwhelmingly common project, and it must not be
|
|
160
|
+
* required to say so. A malformed export — `dataSources` that is not an array —
|
|
161
|
+
* is reported and ignored rather than fatal: a typo in one optional declaration
|
|
162
|
+
* should not take down a server whose collections are fine.
|
|
163
|
+
*
|
|
164
|
+
* A file that is THERE and cannot be imported is a different thing, and it is
|
|
165
|
+
* fatal. Every declaration in it is gone, so the process comes up bound to a
|
|
166
|
+
* different set of databases and buckets than the project declares, announcing
|
|
167
|
+
* success. That is not a degraded start, it is a different deployment.
|
|
145
168
|
*/
|
|
146
169
|
export declare function loadBundleConfigExports(bundle: LoadedBundle): Promise<BundleConfigExports>;
|
|
147
170
|
/**
|
|
@@ -59,6 +59,8 @@ export declare function isConcurrentDdlRace(err: unknown): boolean;
|
|
|
59
59
|
* what a lost `CREATE TYPE`/`CREATE TABLE` race raises (`pg_type_typname_nsp_index`
|
|
60
60
|
* is the one seen in practice); a unique violation on user data names the user's
|
|
61
61
|
* own constraint and is left to the caller.
|
|
62
|
+
*
|
|
63
|
+
* With one exception, in {@link NOT_A_RACE_PG_INDEXES}.
|
|
62
64
|
*/
|
|
63
65
|
export declare function isDuplicateObjectRace(err: unknown): boolean;
|
|
64
66
|
export interface DdlBootstrapper {
|
package/dist/boot/env.d.ts
CHANGED
|
@@ -95,11 +95,36 @@ declare const bootEnvExtension: z.ZodObject<{
|
|
|
95
95
|
Lax: "Lax";
|
|
96
96
|
None: "None";
|
|
97
97
|
}>>;
|
|
98
|
+
AUTH_COOKIE_SECURE: z.ZodPipe<z.ZodDefault<z.ZodEnum<{
|
|
99
|
+
"": "";
|
|
100
|
+
true: "true";
|
|
101
|
+
false: "false";
|
|
102
|
+
}>>, z.ZodTransform<boolean, "" | "true" | "false">>;
|
|
98
103
|
AUTH_DEFAULT_ROLE: z.ZodOptional<z.ZodString>;
|
|
99
104
|
GITHUB_CLIENT_ID: z.ZodOptional<z.ZodString>;
|
|
100
105
|
GITHUB_CLIENT_SECRET: z.ZodOptional<z.ZodString>;
|
|
101
106
|
MICROSOFT_CLIENT_ID: z.ZodOptional<z.ZodString>;
|
|
102
107
|
MICROSOFT_CLIENT_SECRET: z.ZodOptional<z.ZodString>;
|
|
108
|
+
LINKEDIN_CLIENT_ID: z.ZodOptional<z.ZodString>;
|
|
109
|
+
LINKEDIN_CLIENT_SECRET: z.ZodOptional<z.ZodString>;
|
|
110
|
+
FACEBOOK_CLIENT_ID: z.ZodOptional<z.ZodString>;
|
|
111
|
+
FACEBOOK_CLIENT_SECRET: z.ZodOptional<z.ZodString>;
|
|
112
|
+
TWITTER_CLIENT_ID: z.ZodOptional<z.ZodString>;
|
|
113
|
+
TWITTER_CLIENT_SECRET: z.ZodOptional<z.ZodString>;
|
|
114
|
+
DISCORD_CLIENT_ID: z.ZodOptional<z.ZodString>;
|
|
115
|
+
DISCORD_CLIENT_SECRET: z.ZodOptional<z.ZodString>;
|
|
116
|
+
GITLAB_CLIENT_ID: z.ZodOptional<z.ZodString>;
|
|
117
|
+
GITLAB_CLIENT_SECRET: z.ZodOptional<z.ZodString>;
|
|
118
|
+
BITBUCKET_CLIENT_ID: z.ZodOptional<z.ZodString>;
|
|
119
|
+
BITBUCKET_CLIENT_SECRET: z.ZodOptional<z.ZodString>;
|
|
120
|
+
SLACK_CLIENT_ID: z.ZodOptional<z.ZodString>;
|
|
121
|
+
SLACK_CLIENT_SECRET: z.ZodOptional<z.ZodString>;
|
|
122
|
+
SPOTIFY_CLIENT_ID: z.ZodOptional<z.ZodString>;
|
|
123
|
+
SPOTIFY_CLIENT_SECRET: z.ZodOptional<z.ZodString>;
|
|
124
|
+
APPLE_CLIENT_ID: z.ZodOptional<z.ZodString>;
|
|
125
|
+
APPLE_TEAM_ID: z.ZodOptional<z.ZodString>;
|
|
126
|
+
APPLE_KEY_ID: z.ZodOptional<z.ZodString>;
|
|
127
|
+
APPLE_PRIVATE_KEY: z.ZodOptional<z.ZodString>;
|
|
103
128
|
REBASE_BASE_PATH: z.ZodDefault<z.ZodString>;
|
|
104
129
|
REBASE_ENABLE_SWAGGER: z.ZodPipe<z.ZodOptional<z.ZodEnum<{
|
|
105
130
|
"": "";
|
|
@@ -162,7 +187,6 @@ export declare function loadBootEnv(): RebaseBootEnv;
|
|
|
162
187
|
* server with the developer's session and read the responses.
|
|
163
188
|
*/
|
|
164
189
|
export declare function isLocalhostOrigin(origin: string): boolean;
|
|
165
|
-
/** A CORS origin resolver of the shape Hono's `cors()` middleware expects. */
|
|
166
190
|
/**
|
|
167
191
|
* Whether this process serves the OpenAPI docs.
|
|
168
192
|
*
|
|
@@ -177,6 +201,7 @@ export declare function isLocalhostOrigin(origin: string): boolean;
|
|
|
177
201
|
* that can disagree about the same route is the bug this replaces.
|
|
178
202
|
*/
|
|
179
203
|
export declare function resolveEnableSwagger(env: RebaseBootEnv): boolean | undefined;
|
|
204
|
+
/** A CORS origin resolver of the shape Hono's `cors()` middleware expects. */
|
|
180
205
|
export type CorsOriginResolver = (origin: string) => string | null;
|
|
181
206
|
/**
|
|
182
207
|
* Build the CORS origin policy.
|
|
@@ -186,6 +211,10 @@ export type CorsOriginResolver = (origin: string) => string | null;
|
|
|
186
211
|
* `FRONTEND_URL`, so an empty list here can only mean the values were blank
|
|
187
212
|
* strings — still worth failing on, because the alternative is an API that
|
|
188
213
|
* quietly rejects its own frontend.
|
|
214
|
+
*
|
|
215
|
+
* Development reflects localhost *and* whatever that same allow-list names. The
|
|
216
|
+
* list is the same one in both environments; the difference is that development
|
|
217
|
+
* adds localhost to it rather than replacing it.
|
|
189
218
|
*/
|
|
190
219
|
export declare function resolveCorsOrigin(env: RebaseBootEnv): CorsOriginResolver;
|
|
191
220
|
export {};
|
|
@@ -159,6 +159,7 @@ export declare function installBundleDependencies(bundleRoot: string, exec?: (cm
|
|
|
159
159
|
* nothing to do.
|
|
160
160
|
*/
|
|
161
161
|
export declare const RUNTIME_MODULES_ENV = "REBASE_RUNTIME_MODULES";
|
|
162
|
+
export declare function imageModulesDir(): string | undefined;
|
|
162
163
|
/**
|
|
163
164
|
* The packages the runtime image supplies to a fetched bundle.
|
|
164
165
|
*
|
|
@@ -174,8 +175,17 @@ export declare const RUNTIME_MODULES_ENV = "REBASE_RUNTIME_MODULES";
|
|
|
174
175
|
* the same four-package gap that broke the non-fetch path, reproduced on the
|
|
175
176
|
* fetch path because this list was written before that fix and no gate watched
|
|
176
177
|
* this file.
|
|
178
|
+
*
|
|
179
|
+
* `zod` is not a @rebasepro package and belongs here anyway. The runtime
|
|
180
|
+
* exports `loadEnv`, whose `extend` option takes a zod schema and calls
|
|
181
|
+
* `.parse()` on it. When the bundle carries its own zod that schema is built
|
|
182
|
+
* from a DIFFERENT zod than the one doing the parsing, and every field carrying
|
|
183
|
+
* a `.default()` is rejected — so a managed deploy came up, reported success,
|
|
184
|
+
* and ran zero crons. The image installs zod (see
|
|
185
|
+
* infra/docker/server.Dockerfile); naming it here is what makes the bundle's
|
|
186
|
+
* copy collapse onto it.
|
|
177
187
|
*/
|
|
178
|
-
export declare const RUNTIME_PROVIDED_PACKAGES: readonly ["@rebasepro/server", "@rebasepro/types", "@rebasepro/client", "@rebasepro/common", "@rebasepro/utils"];
|
|
188
|
+
export declare const RUNTIME_PROVIDED_PACKAGES: readonly ["@rebasepro/server", "@rebasepro/types", "@rebasepro/client", "@rebasepro/common", "@rebasepro/utils", "zod"];
|
|
179
189
|
/**
|
|
180
190
|
* Collapse a duplicate framework copy in the bundle onto the image's.
|
|
181
191
|
*
|
|
@@ -189,6 +199,19 @@ export declare const RUNTIME_PROVIDED_PACKAGES: readonly ["@rebasepro/server", "
|
|
|
189
199
|
* duplicate, and refusing to boot over it would turn a degraded start into no
|
|
190
200
|
* start at all.
|
|
191
201
|
*
|
|
202
|
+
* Called from `bootFromBundle` on EVERY boot, against whatever directory boot
|
|
203
|
+
* settled on — not from the install. Hanging it off the install made it
|
|
204
|
+
* unreachable for three of the four ways a bundle arrives: one that vendored
|
|
205
|
+
* `node_modules` at build time, a pod restarting onto a tree it installed
|
|
206
|
+
* earlier, and the fallback that serves an on-disk bundle after a failed fetch
|
|
207
|
+
* all skip the install and took the dedupe with them. Prospector hit the first:
|
|
208
|
+
* two copies of `@rebasepro/types` in one process, whose `registerResourceKind`
|
|
209
|
+
* calls for kind `database` disagreed, so importing the driver threw at boot and
|
|
210
|
+
* the pod crash-looped reporting a driver that was installed all along.
|
|
211
|
+
*
|
|
212
|
+
* Safe to repeat: an entry that is already a symlink is left alone, so the
|
|
213
|
+
* container entrypoint having done this first costs one `lstat` per package.
|
|
214
|
+
*
|
|
192
215
|
* @param imageModules the runtime image's own `node_modules`. Absent, there is
|
|
193
216
|
* nothing to dedupe against and this is a no-op — which is the case in every
|
|
194
217
|
* context except a container built from the runtime image.
|
package/dist/boot/options.d.ts
CHANGED
|
@@ -33,12 +33,4 @@ export declare function resolveEmailOptions(env: RebaseBootEnv): EmailConfig | u
|
|
|
33
33
|
* being silently absent while the config says otherwise.
|
|
34
34
|
*/
|
|
35
35
|
export declare function resolveCaptchaOptions(env: RebaseBootEnv): CaptchaConfig | undefined;
|
|
36
|
-
/**
|
|
37
|
-
* Build the auth configuration from the environment and the bundle's users
|
|
38
|
-
* collection.
|
|
39
|
-
*
|
|
40
|
-
* OAuth providers are included only when both halves of a credential pair are
|
|
41
|
-
* present. Google is the exception the template already made: a client id alone
|
|
42
|
-
* is enough, because the ID-token flow needs no secret.
|
|
43
|
-
*/
|
|
44
36
|
export declare function resolveAuthOptions(env: RebaseBootEnv, usersCollection: CollectionConfig | undefined): RebaseAuthConfig;
|
package/dist/boot/provision.d.ts
CHANGED
|
@@ -57,7 +57,11 @@ export interface ProvisionTarget {
|
|
|
57
57
|
engine: string;
|
|
58
58
|
driverVersion?: string;
|
|
59
59
|
runtimeVersion?: string;
|
|
60
|
-
bootstrapper: Pick<BackendBootstrapper, "ensureCollectionSchema" | "ensureCollectionPolicies" | "readCollectionsSchemaVersion" | "stampCollectionsSchemaVersion">;
|
|
60
|
+
bootstrapper: Pick<BackendBootstrapper, "verifyConnection" | "ensureCollectionSchema" | "ensureCollectionPolicies" | "ensureRlsRuntime" | "readCollectionsSchemaVersion" | "stampCollectionsSchemaVersion">;
|
|
61
|
+
/** The data-source key this target provisions for. `(default)` for the primary. */
|
|
62
|
+
key?: string;
|
|
63
|
+
/** Whether this is the default source — the one auth lives on. */
|
|
64
|
+
isDefault?: boolean;
|
|
61
65
|
/**
|
|
62
66
|
* The handle the hooks read (`internals.db`), or `undefined` to let the
|
|
63
67
|
* adapter fall back to the connection it was constructed with.
|
|
@@ -89,6 +93,22 @@ export interface ProvisionRunOptions {
|
|
|
89
93
|
export declare function provisioningDisabled(env?: {
|
|
90
94
|
REBASE_MIGRATE_ON_BOOT?: string;
|
|
91
95
|
}): boolean;
|
|
96
|
+
/**
|
|
97
|
+
* Ask the database whether it is there, before boot's first real query.
|
|
98
|
+
*
|
|
99
|
+
* Ordering is the whole point. Provisioning is the first thing in boot that
|
|
100
|
+
* talks to the database — earlier than `initializeDriver`, which is where the
|
|
101
|
+
* Postgres adapter's connection diagnosis lives — so a developer whose database
|
|
102
|
+
* was not running got Drizzle's wrapper and nothing else: `Failed query:
|
|
103
|
+
* [redacted]`, a stack through drizzle internals, no host, no port, no
|
|
104
|
+
* `ECONNREFUSED`, and no suggestion to start the database. The diagnosis existed
|
|
105
|
+
* the whole time; nothing reached it.
|
|
106
|
+
*
|
|
107
|
+
* A driver that cannot probe (no `verifyConnection`) is not an error: this is a
|
|
108
|
+
* better first message, not a new requirement. And a probe that throws is fatal
|
|
109
|
+
* for the same reason provisioning is — every path after this needs the database.
|
|
110
|
+
*/
|
|
111
|
+
export declare function verifyProvisioningConnection(target: ProvisionTarget): Promise<void>;
|
|
92
112
|
/**
|
|
93
113
|
* Create any collection tables the database is missing, before serving.
|
|
94
114
|
*
|
|
@@ -130,36 +150,46 @@ export declare function provisionCollectionTables(collections: CollectionConfig[
|
|
|
130
150
|
*/
|
|
131
151
|
export declare function provisionCollectionPolicies(collections: CollectionConfig[], target: ProvisionTarget, options?: ProvisionRunOptions): Promise<ProvisionOutcome>;
|
|
132
152
|
/**
|
|
133
|
-
* The collections
|
|
153
|
+
* The collections one data source is the store for.
|
|
134
154
|
*
|
|
135
155
|
* A project's collections directory holds *every* collection it declares,
|
|
136
|
-
* whatever
|
|
137
|
-
* is not is a list of tables to create
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
* the
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
156
|
+
* whatever source serves it — that is the point of `dataSource` routing. What
|
|
157
|
+
* it is not is a list of tables for the primary to create. This used to
|
|
158
|
+
* filter by *engine*, so a collection routed to a second Postgres database
|
|
159
|
+
* was handed to the default one: its table was created where nothing would
|
|
160
|
+
* ever read it, and the database it was routed to never got one. Every query
|
|
161
|
+
* against `database("analytics")` then failed on a missing relation behind a
|
|
162
|
+
* boot that had reported the schema up to date.
|
|
163
|
+
*
|
|
164
|
+
* So: a collection belongs to the source its `dataSource` names, or to the
|
|
165
|
+
* default when it names none. The engine check stays as a guard — a
|
|
166
|
+
* collection that declares an `engine` other than this source's is never its
|
|
167
|
+
* table, whatever key it names — and a source of a different engine still
|
|
168
|
+
* gets nothing that is not explicitly its own.
|
|
148
169
|
*
|
|
149
170
|
* Takes the shapes it actually reads rather than an `InitializedDataSource`, so
|
|
150
171
|
* the path that has data sources and the path that has only an adapter can share
|
|
151
172
|
* one implementation instead of growing a second one that drifts.
|
|
152
173
|
*/
|
|
153
|
-
export declare function collectionsStoredBy(collections: CollectionConfig[],
|
|
174
|
+
export declare function collectionsStoredBy(collections: CollectionConfig[], source: {
|
|
175
|
+
key: string;
|
|
154
176
|
engine: string;
|
|
155
177
|
}, dataSources: Array<{
|
|
156
178
|
key: string;
|
|
157
179
|
engine: string;
|
|
158
180
|
}>): CollectionConfig[];
|
|
159
|
-
/**
|
|
160
|
-
|
|
181
|
+
/**
|
|
182
|
+
* Say what no source provisions, so a missing table is never a silent one.
|
|
183
|
+
*
|
|
184
|
+
* Every collection should be stored by exactly one of the sources handed in.
|
|
185
|
+
* One that none claims — routed to a key no source has, or declaring an
|
|
186
|
+
* engine none of them runs — is logged here by name rather than left to fail
|
|
187
|
+
* on its first request.
|
|
188
|
+
*/
|
|
189
|
+
export declare function logUnprovisionedCollections(all: CollectionConfig[], storedBySome: Set<CollectionConfig>, sources: Array<{
|
|
190
|
+
key: string;
|
|
161
191
|
engine: string;
|
|
162
|
-
}): void;
|
|
192
|
+
}>): void;
|
|
163
193
|
/**
|
|
164
194
|
* Which bootstrapper provisions, and what to call it in a message.
|
|
165
195
|
*
|
|
@@ -178,5 +208,15 @@ export declare function logForeignCollections(all: CollectionConfig[], stored: C
|
|
|
178
208
|
export declare function provisionTargetFor(bootstrappers: BackendBootstrapper[], adapter?: {
|
|
179
209
|
type: string;
|
|
180
210
|
}, driverResult?: InitializedDriver): ProvisionTarget;
|
|
211
|
+
/**
|
|
212
|
+
* One provisioning target per data source, the default first.
|
|
213
|
+
*
|
|
214
|
+
* The default keeps the pre-init handle the bundle path supplies; every other
|
|
215
|
+
* source is provisioned through its own adapter's connection, which is the
|
|
216
|
+
* contract `ensureCollectionSchema` already honours when handed no result.
|
|
217
|
+
*/
|
|
218
|
+
export declare function provisionTargetsFor(bootstrappers: BackendBootstrapper[], adapter?: {
|
|
219
|
+
type: string;
|
|
220
|
+
}, driverResult?: InitializedDriver): ProvisionTarget[];
|
|
181
221
|
/** Load collections for provisioning when only a directory is known. */
|
|
182
222
|
export declare function collectionsForProvisioning(declared: CollectionConfig[], collectionsDir: string | undefined): Promise<CollectionConfig[]>;
|
|
@@ -13,12 +13,22 @@
|
|
|
13
13
|
* becomes a rewrite and then does not ship.
|
|
14
14
|
*/
|
|
15
15
|
import { type DataSourceDefinition, type ResourceGraph, type StorageSourceDefinition } from "@rebasepro/types";
|
|
16
|
-
/**
|
|
16
|
+
/**
|
|
17
|
+
* Databases in the graph, as the data layer's definitions.
|
|
18
|
+
*
|
|
19
|
+
* The per-field mapping lives in `@rebasepro/types` next to the constructors,
|
|
20
|
+
* so this and `declaredDataSources()` cannot answer differently. They used to:
|
|
21
|
+
* this side carried a bucket's `account` and the types side dropped it, which
|
|
22
|
+
* meant shared credentials resolved on the managed runtime and resolved nothing
|
|
23
|
+
* in an ejected backend.
|
|
24
|
+
*/
|
|
17
25
|
export declare function graphToDataSources(graph: ResourceGraph): DataSourceDefinition[];
|
|
18
26
|
/** Buckets in the graph, as the storage layer's definitions. */
|
|
19
27
|
export declare function graphToStorageSources(graph: ResourceGraph): StorageSourceDefinition[];
|
|
20
28
|
/** Topics in the graph, for the runtime to publish through and the worker to wire. */
|
|
21
29
|
export declare function graphTopics(graph: ResourceGraph): ResourceGraph["resources"];
|
|
30
|
+
/** Queues in the graph, for the runtime to enqueue on and the worker to wire. */
|
|
31
|
+
export declare function graphQueues(graph: ResourceGraph): ResourceGraph["resources"];
|
|
22
32
|
/**
|
|
23
33
|
* Refuse a config still carrying a replaced key.
|
|
24
34
|
*
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How each resource kind is bound, registered per kind.
|
|
3
|
+
*
|
|
4
|
+
* `@rebasepro/types` registers what a kind *is* — its engines, its options,
|
|
5
|
+
* the variable bases it binds from. This is the other half: what the runtime
|
|
6
|
+
* does with those variables. Keeping it a registry rather than a switch is
|
|
7
|
+
* what makes the next kind an afternoon: a `cache` arrives as a spec in
|
|
8
|
+
* `@rebasepro/types`, a resolver here, and a driver — and nothing else in the
|
|
9
|
+
* boot path, `rebase status` or the gate that holds the two halves together
|
|
10
|
+
* has to learn its name.
|
|
11
|
+
*
|
|
12
|
+
* Two readers, one answer. Boot calls a resolver to bind; `rebase status`
|
|
13
|
+
* calls the same resolver to say whether a binding would succeed. The first
|
|
14
|
+
* time those two disagreed, status would be reassuring somebody about a
|
|
15
|
+
* deployment that was about to refuse to start — which is why status has no
|
|
16
|
+
* resolver of its own.
|
|
17
|
+
*
|
|
18
|
+
* ## Every declared kind must be bindable
|
|
19
|
+
*
|
|
20
|
+
* A graph entry whose kind has no resolver used to be dropped: the adapters
|
|
21
|
+
* filtered by the kinds they knew and the rest went nowhere, so a bundle from
|
|
22
|
+
* a newer CLI declaring a kind this runtime had never heard of booted with the
|
|
23
|
+
* declaration ignored. {@link assertEveryKindBindable} refuses that at boot,
|
|
24
|
+
* by name, which is the same rule the CLI applies to an unknown engine.
|
|
25
|
+
*/
|
|
26
|
+
import { type ResourceDeclaration, type ResourceGraph } from "@rebasepro/types";
|
|
27
|
+
import { type EnvBag } from "./sources.js";
|
|
28
|
+
/** What a resolver knows about the process it resolves for. */
|
|
29
|
+
export interface ResolveContext {
|
|
30
|
+
/** Whether this is a production process. Decides whether a local stand-in is allowed. */
|
|
31
|
+
production: boolean;
|
|
32
|
+
/** Where a local storage source lives when nothing names a path. */
|
|
33
|
+
defaultBasePath: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* One resource's binding verdict.
|
|
37
|
+
*
|
|
38
|
+
* - `ready`: bound, or standing in — `standsIn` names the engine a local
|
|
39
|
+
* directory is standing in for in development.
|
|
40
|
+
* - `unbound`: declared and not configured. Not fatal: the resource is
|
|
41
|
+
* skipped, and whatever uses it answers 501.
|
|
42
|
+
* - `blocked`: boot refuses. The detail is the resolver's own message.
|
|
43
|
+
* - `code`: binds from nothing. A cron, a function, a topic: the declaration
|
|
44
|
+
* is the whole configuration.
|
|
45
|
+
*/
|
|
46
|
+
export type ResourceResolution = {
|
|
47
|
+
state: "ready";
|
|
48
|
+
detail?: string;
|
|
49
|
+
standsIn?: string;
|
|
50
|
+
} | {
|
|
51
|
+
state: "unbound";
|
|
52
|
+
detail: string;
|
|
53
|
+
} | {
|
|
54
|
+
state: "blocked";
|
|
55
|
+
detail: string;
|
|
56
|
+
} | {
|
|
57
|
+
state: "code";
|
|
58
|
+
detail?: string;
|
|
59
|
+
};
|
|
60
|
+
export interface ResourceResolver {
|
|
61
|
+
kind: string;
|
|
62
|
+
/**
|
|
63
|
+
* The variable bases this resolver reads, exactly. Held to the kind's
|
|
64
|
+
* `envBases` by `resource-env-bases.test.ts`, so a base one side names and
|
|
65
|
+
* the other never consults — a variable somebody sets and nothing reads —
|
|
66
|
+
* fails a build instead of reaching a developer.
|
|
67
|
+
*/
|
|
68
|
+
reads: readonly string[];
|
|
69
|
+
/** The subset of `reads` that falls back to `<BASE>__<ACCOUNT>`. */
|
|
70
|
+
accountScoped?: readonly string[];
|
|
71
|
+
resolve(declaration: ResourceDeclaration, env: EnvBag, context: ResolveContext): ResourceResolution;
|
|
72
|
+
}
|
|
73
|
+
/** Register a kind's resolver. Idempotent for the same object; a second, different one throws. */
|
|
74
|
+
export declare function registerResourceResolver(resolver: ResourceResolver): void;
|
|
75
|
+
/** The resolver for a kind, or undefined when this runtime cannot bind it. */
|
|
76
|
+
export declare function resourceResolver(kind: string): ResourceResolver | undefined;
|
|
77
|
+
/** Every registered resolver, for the gate and for `rebase doctor`. */
|
|
78
|
+
export declare function resourceResolvers(): ResourceResolver[];
|
|
79
|
+
/**
|
|
80
|
+
* Refuse a graph that declares a kind this runtime has no resolver for.
|
|
81
|
+
*
|
|
82
|
+
* Throws with every offending kind at once, because they arrive together — a
|
|
83
|
+
* newer CLI that added two kinds is the usual cause — and a message naming one
|
|
84
|
+
* of two sends somebody back through the same failure twice.
|
|
85
|
+
*/
|
|
86
|
+
export declare function assertEveryKindBindable(graph: ResourceGraph): void;
|
|
87
|
+
/**
|
|
88
|
+
* Every kind `@rebasepro/types` registers that this runtime cannot bind.
|
|
89
|
+
*
|
|
90
|
+
* Empty in a consistent build. Non-empty means the two packages disagree —
|
|
91
|
+
* a kind was added to one and not the other — which the gate reports and
|
|
92
|
+
* `rebase doctor` can show.
|
|
93
|
+
*/
|
|
94
|
+
export declare function unbindableKinds(): string[];
|
|
95
|
+
/** The default-keyed declaration of a kind, synthesised for kinds that have one implicitly. */
|
|
96
|
+
export declare function implicitDeclaration(kind: string): ResourceDeclaration | undefined;
|
package/dist/boot/sources.d.ts
CHANGED
|
@@ -25,6 +25,16 @@ import type { BackendStorageConfig } from "../storage/types.js";
|
|
|
25
25
|
*/
|
|
26
26
|
/** Environment lookup, injectable so tests need not mutate `process.env`. */
|
|
27
27
|
export type EnvBag = Record<string, string | undefined>;
|
|
28
|
+
/** What the resolver needs to know about the process it resolves for. */
|
|
29
|
+
export interface ResolveStorageOptions {
|
|
30
|
+
/**
|
|
31
|
+
* Whether this is a production process. `false` lets a declared object
|
|
32
|
+
* store nothing bound stand in as a local directory; `true` or unknown
|
|
33
|
+
* leaves it unbound. Unknown is treated as production on purpose — a
|
|
34
|
+
* process that has not said it is development must not be handed a disk.
|
|
35
|
+
*/
|
|
36
|
+
production?: boolean;
|
|
37
|
+
}
|
|
28
38
|
/**
|
|
29
39
|
* Convert a source key into the suffix used in environment variable names.
|
|
30
40
|
*
|
|
@@ -70,6 +80,21 @@ export interface ResolvedDataSourceConfig {
|
|
|
70
80
|
* the wrong place with a healthy-looking server in front of it.
|
|
71
81
|
*/
|
|
72
82
|
export declare function resolveDataSources(env: EnvBag, definitions: DataSourceDefinition[] | undefined): ResolvedDataSourceConfig[];
|
|
83
|
+
/**
|
|
84
|
+
* The storage bindings that describe the PROVIDER rather than the bucket.
|
|
85
|
+
*
|
|
86
|
+
* These are the ones a source's `account` lets it share: naming an account
|
|
87
|
+
* makes them fall back to `<BASE>__<ACCOUNT>` when no per-key value is set.
|
|
88
|
+
* `S3_BUCKET` and `GCS_BUCKET` are deliberately absent — the bucket name is
|
|
89
|
+
* what distinguishes one source from another and must never fall back — and so
|
|
90
|
+
* are `STORAGE_TYPE` and `STORAGE_PATH`, which describe this source alone.
|
|
91
|
+
*
|
|
92
|
+
* Exported because `rebase status` has to tell a developer *which* variable a
|
|
93
|
+
* bucket is actually waiting on, and deriving that list a second time is how it
|
|
94
|
+
* would come to disagree with the resolver below. `resource-env-bases.test.ts`
|
|
95
|
+
* holds it to the reader each base is passed to.
|
|
96
|
+
*/
|
|
97
|
+
export declare const ACCOUNT_SCOPED_STORAGE_BASES: readonly ["S3_ACCESS_KEY_ID", "S3_SECRET_ACCESS_KEY", "S3_REGION", "S3_ENDPOINT", "S3_FORCE_PATH_STYLE", "GCS_PROJECT_ID", "GCS_KEY_FILENAME"];
|
|
73
98
|
/**
|
|
74
99
|
* Build one storage configuration from the variables for a single source.
|
|
75
100
|
*
|
|
@@ -85,7 +110,7 @@ export declare function resolveStorageBackend(env: EnvBag, key: string, engineHi
|
|
|
85
110
|
* account-scoped bindings — the ones describing the provider rather than the
|
|
86
111
|
* bucket — consult it. See `StorageSourceDefinition.account`.
|
|
87
112
|
*/
|
|
88
|
-
accountHint?: string): BackendStorageConfig | undefined;
|
|
113
|
+
accountHint?: string, options?: ResolveStorageOptions): BackendStorageConfig | undefined;
|
|
89
114
|
/**
|
|
90
115
|
* Resolve every server-transport storage source into a controller config map.
|
|
91
116
|
*
|
|
@@ -93,22 +118,4 @@ accountHint?: string): BackendStorageConfig | undefined;
|
|
|
93
118
|
* so multiple buckets need nothing new downstream — they were always supported,
|
|
94
119
|
* they just had no way to be configured from the environment.
|
|
95
120
|
*/
|
|
96
|
-
export declare function resolveStorageSources(env: EnvBag, definitions: StorageSourceDefinition[] | undefined, defaultBasePath: string): Record<string, BackendStorageConfig> | undefined;
|
|
97
|
-
/**
|
|
98
|
-
* Read a project's declared storage sources from its `rebase.json`.
|
|
99
|
-
*
|
|
100
|
-
* A managed bundle carries its topology in `manifest.json`, resolved at build
|
|
101
|
-
* time. A **custom** runtime has no manifest — it builds its own image and its
|
|
102
|
-
* own entrypoint — so without this it would have to re-declare in code what
|
|
103
|
-
* `rebase.json` already says, and the two would drift. Since a custom image
|
|
104
|
-
* contains the repository anyway, reading the file it already ships is what
|
|
105
|
-
* keeps one declaration authoritative for both runtimes.
|
|
106
|
-
*
|
|
107
|
-
* Walks up from `startDir` because an entrypoint lives at `backend/src` in the
|
|
108
|
-
* scaffolded layout and somewhere else in a hand-rolled one. A missing,
|
|
109
|
-
* unreadable or malformed file means "declared nothing" — one default source —
|
|
110
|
-
* which is the correct reading of every project that predates this and must
|
|
111
|
-
* never be an error: a storage declaration is optional, and failing to boot a
|
|
112
|
-
* whole backend over an absent optional file would be the worse bug.
|
|
113
|
-
*/
|
|
114
|
-
export declare function loadDeclaredStorageSources(startDir: string, levels?: number): StorageSourceDefinition[];
|
|
121
|
+
export declare function resolveStorageSources(env: EnvBag, definitions: StorageSourceDefinition[] | undefined, defaultBasePath: string, options?: ResolveStorageOptions): Record<string, BackendStorageConfig> | undefined;
|