@rebasepro/server 0.17.3 → 0.18.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/LICENSE +0 -1
  2. package/README.md +5 -1
  3. package/bin/rebase-server.js +16 -1
  4. package/dist/{GCSStorageController-Bl7nmhdv.js → GCSStorageController-ZXPoNqW3.js} +21 -10
  5. package/dist/GCSStorageController-ZXPoNqW3.js.map +1 -0
  6. package/dist/{S3StorageController-CWvvrRpF.js → S3StorageController-5pAXyv31.js} +24 -10
  7. package/dist/S3StorageController-5pAXyv31.js.map +1 -0
  8. package/dist/admin-roles-vYdp_Pil.js +36 -0
  9. package/dist/admin-roles-vYdp_Pil.js.map +1 -0
  10. package/dist/{admin_block-BX6YULvJ.js → admin_block-0Xu0r6eZ.js} +4 -1
  11. package/dist/admin_block-0Xu0r6eZ.js.map +1 -0
  12. package/dist/api/errors.d.ts +37 -0
  13. package/dist/api/live-schema-routes.d.ts +14 -0
  14. package/dist/api/logs-routes.d.ts +8 -0
  15. package/dist/api/rest/api-generator.d.ts +33 -0
  16. package/dist/api/root-error-handler.d.ts +62 -0
  17. package/dist/api/schema-drift.d.ts +39 -0
  18. package/dist/api/types.d.ts +55 -0
  19. package/dist/{ast-schema-editor-CvkYOvri.js → ast-schema-editor-CNgFJ3NF.js} +23 -7
  20. package/dist/ast-schema-editor-CNgFJ3NF.js.map +1 -0
  21. package/dist/auth/api-keys/api-key-routes.d.ts +12 -0
  22. package/dist/auth/auth-hooks.d.ts +13 -2
  23. package/dist/auth/exclude-auth-secrets.d.ts +14 -0
  24. package/dist/auth/jwt.d.ts +22 -1
  25. package/dist/auth/mfa-routes.d.ts +10 -1
  26. package/dist/auth/middleware.d.ts +22 -0
  27. package/dist/auth/rate-limiter.d.ts +50 -0
  28. package/dist/auth/registration-policy.d.ts +27 -0
  29. package/dist/auth/rls-scope.d.ts +9 -1
  30. package/dist/auth/routes.d.ts +8 -1
  31. package/dist/auth/seed-admin.d.ts +73 -0
  32. package/dist/auth/session-routes.d.ts +10 -0
  33. package/dist/{auth-DkbzMZUS.js → auth-BRiOuyq8.js} +283 -198
  34. package/dist/auth-BRiOuyq8.js.map +1 -0
  35. package/dist/{backup-CRdZkA6c.js → backup-C8P6Cl3G.js} +2 -2
  36. package/dist/{backup-CRdZkA6c.js.map → backup-C8P6Cl3G.js.map} +1 -1
  37. package/dist/boot/boot.d.ts +18 -0
  38. package/dist/boot/bundle.d.ts +29 -6
  39. package/dist/boot/ddl-bootstrap.d.ts +2 -0
  40. package/dist/boot/env.d.ts +30 -1
  41. package/dist/boot/fetch-bundle.d.ts +24 -1
  42. package/dist/boot/options.d.ts +0 -8
  43. package/dist/boot/provision.d.ts +58 -18
  44. package/dist/boot/resource-adapters.d.ts +11 -1
  45. package/dist/boot/resource-resolvers.d.ts +96 -0
  46. package/dist/boot/sources.d.ts +27 -20
  47. package/dist/collections/validate-config.d.ts +29 -0
  48. package/dist/{contract-routes-CHPVKTZW.js → contract-routes-CusnEB5h.js} +24 -9
  49. package/dist/{contract-routes-CHPVKTZW.js.map → contract-routes-CusnEB5h.js.map} +1 -1
  50. package/dist/cron/cron-loader.d.ts +7 -1
  51. package/dist/cron/cron-scheduler.d.ts +48 -2
  52. package/dist/{cron-loader-d9WMFENB.js → cron-loader-DnmIePn_.js} +23 -4
  53. package/dist/cron-loader-DnmIePn_.js.map +1 -0
  54. package/dist/{cron-routes-DSC-kZXI.js → cron-routes-D3x2ydMa.js} +9 -6
  55. package/dist/cron-routes-D3x2ydMa.js.map +1 -0
  56. package/dist/{cron-scheduler-BB82dWuU.js → cron-scheduler-BpPKpl4i.js} +148 -28
  57. package/dist/cron-scheduler-BpPKpl4i.js.map +1 -0
  58. package/dist/{cron-store-KeN8rN63.js → cron-store-CCQXwgVL.js} +5 -5
  59. package/dist/{cron-store-KeN8rN63.js.map → cron-store-CCQXwgVL.js.map} +1 -1
  60. package/dist/{ddl-bootstrap-5YZCZ8qk.js → ddl-bootstrap-CfNvxMuK.js} +26 -2
  61. package/dist/{ddl-bootstrap-5YZCZ8qk.js.map → ddl-bootstrap-CfNvxMuK.js.map} +1 -1
  62. package/dist/email/index.d.ts +1 -0
  63. package/dist/email/unconfigured.d.ts +20 -0
  64. package/dist/env.d.ts +25 -0
  65. package/dist/errors-HjfaPlvY.js +385 -0
  66. package/dist/errors-HjfaPlvY.js.map +1 -0
  67. package/dist/{function-loader-LLdmBFoL.js → function-loader-xnbDAPfa.js} +9 -3
  68. package/dist/{function-loader-LLdmBFoL.js.map → function-loader-xnbDAPfa.js.map} +1 -1
  69. package/dist/function-routes-gQ0EShVG.js +267 -0
  70. package/dist/function-routes-gQ0EShVG.js.map +1 -0
  71. package/dist/functions/define-function.d.ts +6 -3
  72. package/dist/functions/function-routes.d.ts +8 -4
  73. package/dist/functions/index.js +128 -23
  74. package/dist/functions/index.js.map +1 -1
  75. package/dist/{history-recorder-B2pL3X8b.js → history-recorder-B1FwXx9J.js} +3 -3
  76. package/dist/{history-recorder-B2pL3X8b.js.map → history-recorder-B1FwXx9J.js.map} +1 -1
  77. package/dist/{history-store-BUJ-37Sm.js → history-store-LHXaQywp.js} +2 -2
  78. package/dist/{history-store-BUJ-37Sm.js.map → history-store-LHXaQywp.js.map} +1 -1
  79. package/dist/index.d.ts +16 -4
  80. package/dist/index.es.js +16229 -7676
  81. package/dist/index.es.js.map +1 -1
  82. package/dist/init.d.ts +33 -13
  83. package/dist/{jobs-XXXcZrmj.js → jobs-DkkD9mPV.js} +5 -5
  84. package/dist/{jobs-XXXcZrmj.js.map → jobs-DkkD9mPV.js.map} +1 -1
  85. package/dist/{jwt-DNeOn39r.js → jwt-DkhXwMzR.js} +9 -314
  86. package/dist/jwt-DkhXwMzR.js.map +1 -0
  87. package/dist/keys-g8lbVC_o.js +230 -0
  88. package/dist/keys-g8lbVC_o.js.map +1 -0
  89. package/dist/{logger-DS03e908.js → logger-DO2PZc4i.js} +197 -18
  90. package/dist/logger-DO2PZc4i.js.map +1 -0
  91. package/dist/{logs-routes-DB72iQSr.js → logs-routes-CbsTpozn.js} +74 -6
  92. package/dist/logs-routes-CbsTpozn.js.map +1 -0
  93. package/dist/{openapi-generator-CpZp2Fai.js → openapi-generator-BIBbO1Tq.js} +221 -38
  94. package/dist/openapi-generator-BIBbO1Tq.js.map +1 -0
  95. package/dist/{proxy-CMymhnwG.js → proxy-Czngl3p9.js} +2 -2
  96. package/dist/{proxy-CMymhnwG.js.map → proxy-Czngl3p9.js.map} +1 -1
  97. package/dist/{query-parser-DqXNaBIH.js → query-parser-C68Q9EX4.js} +35 -6
  98. package/dist/{query-parser-DqXNaBIH.js.map → query-parser-C68Q9EX4.js.map} +1 -1
  99. package/dist/queues/runtime.d.ts +56 -0
  100. package/dist/{request-timeout-C8gkc-j7.js → request-timeout-DESvlfrS.js} +4 -6
  101. package/dist/request-timeout-DESvlfrS.js.map +1 -0
  102. package/dist/schema-edit/project-root.d.ts +4 -4
  103. package/dist/{schema-editor-routes-DNV_nBhW.js → schema-editor-routes-CcZKh50q.js} +4 -4
  104. package/dist/{schema-editor-routes-DNV_nBhW.js.map → schema-editor-routes-CcZKh50q.js.map} +1 -1
  105. package/dist/{schemas-DBxgjM9A.js → schemas-C3234HWE.js} +2746 -72
  106. package/dist/schemas-C3234HWE.js.map +1 -0
  107. package/dist/singleton.d.ts +6 -4
  108. package/dist/src-DHK4fHkw.js +1002 -0
  109. package/dist/src-DHK4fHkw.js.map +1 -0
  110. package/dist/{src-C9gKj23J.js → src-Dq-I3Ybx.js} +507 -54
  111. package/dist/src-Dq-I3Ybx.js.map +1 -0
  112. package/dist/storage/GCSStorageController.d.ts +5 -0
  113. package/dist/storage/LocalStorageController.d.ts +14 -0
  114. package/dist/storage/S3StorageController.d.ts +8 -0
  115. package/dist/storage/image-transform.d.ts +35 -0
  116. package/dist/storage/keys.d.ts +23 -0
  117. package/dist/storage/policies.d.ts +22 -0
  118. package/dist/storage/routes.d.ts +9 -0
  119. package/dist/storage/tus-handler.d.ts +43 -1
  120. package/dist/storage/types.d.ts +28 -0
  121. package/dist/types-BfKcm9do.js.map +1 -1
  122. package/dist/utils/dev-port.d.ts +18 -0
  123. package/dist/utils/logger.d.ts +60 -0
  124. package/dist/utils/request-logger.d.ts +2 -1
  125. package/package.json +30 -24
  126. package/dist/GCSStorageController-Bl7nmhdv.js.map +0 -1
  127. package/dist/S3StorageController-CWvvrRpF.js.map +0 -1
  128. package/dist/admin_block-BX6YULvJ.js.map +0 -1
  129. package/dist/ast-schema-editor-CvkYOvri.js.map +0 -1
  130. package/dist/auth-DkbzMZUS.js.map +0 -1
  131. package/dist/cron-loader-d9WMFENB.js.map +0 -1
  132. package/dist/cron-routes-DSC-kZXI.js.map +0 -1
  133. package/dist/cron-scheduler-BB82dWuU.js.map +0 -1
  134. package/dist/errors-DBwpj9N8.js +0 -231
  135. package/dist/errors-DBwpj9N8.js.map +0 -1
  136. package/dist/function-routes-ClT6UQpD.js +0 -41
  137. package/dist/function-routes-ClT6UQpD.js.map +0 -1
  138. package/dist/jwt-DNeOn39r.js.map +0 -1
  139. package/dist/logger-DS03e908.js.map +0 -1
  140. package/dist/logs-routes-DB72iQSr.js.map +0 -1
  141. package/dist/openapi-generator-CpZp2Fai.js.map +0 -1
  142. package/dist/request-timeout-C8gkc-j7.js.map +0 -1
  143. package/dist/schemas-DBxgjM9A.js.map +0 -1
  144. package/dist/src-C9gKj23J.js.map +0 -1
  145. package/dist/src-CcOl05Oq.js +0 -552
  146. package/dist/src-CcOl05Oq.js.map +0 -1
  147. 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-DBwpj9N8.js";
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-CRdZkA6c.js.map
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":";;;;;;;;;;;;;;;;;;;;AAoBA,SAAgB,uBAAuB,KAAgC;CACnE,MAAM,KAAK,IAAI,MAAM,yBAAyB;CAC9C,IAAI,IAAI,OAAO;EAAE,MAAM;EAAM,QAAQ,GAAG;EAAI,QAAQ,GAAG,EAAE,CAAC,QAAQ,QAAQ,EAAE;CAAE;CAC9E,MAAM,MAAM,IAAI,MAAM,yBAAyB;CAC/C,IAAI,KAAK,OAAO;EAAE,MAAM;EAAO,QAAQ,IAAI;EAAI,QAAQ,IAAI,EAAE,CAAC,QAAQ,QAAQ,EAAE;CAAE;CAClF,OAAO;EAAE,MAAM;EAAS,MAAM;CAAI;AACtC;;;;;AAMA,SAAgB,qBAAqB,UAA+B;CAEhE,MAAM,SADO,SAAS,MAAM,GAAG,CAAC,CAAC,IAAI,KAAK,SAAA,CACvB,MAAM,0BAA0B;CACnD,IAAI,CAAC,OAAO,OAAO;CACnB,MAAM,GAAG,KAAK,OAAO;CACrB,MAAM,MACF,GAAG,IAAI,MAAM,GAAG,CAAC,EAAE,GAAG,IAAI,MAAM,GAAG,CAAC,EAAE,GAAG,IAAI,MAAM,GAAG,CAAC,EAAA,GACnD,IAAI,MAAM,GAAG,CAAC,EAAE,GAAG,IAAI,MAAM,GAAG,CAAC,EAAE,GAAG,IAAI,MAAM,GAAG,CAAC,EAAE;CAC9D,MAAM,OAAO,IAAI,KAAK,GAAG;CACzB,OAAO,OAAO,MAAM,KAAK,QAAQ,CAAC,IAAI,OAAO;AACjD;;;;;AAMA,eAAsB,kBAClB,MACA,SACqB;CACrB,IAAI,KAAK,SAAS,SAAS;EACvB,IAAI,CAAC,GAAG,WAAW,KAAK,IAAI,GAAG,OAAO,CAAC;EAEvC,MAAM,MADO,GAAG,SAAS,KAAK,IAClB,CAAA,CAAK,YAAY,IAAI,KAAK,OAAO,KAAK,QAAQ,KAAK,IAAI;EACnE,IAAI,CAAC,GAAG,WAAW,GAAG,GAAG,OAAO,CAAC;EACjC,OAAO,GACF,YAAY,GAAG,CAAC,CAChB,QAAQ,MAAM,EAAE,SAAS,OAAO,CAAC,CAAC,CAClC,KAAK,MAAkB;GACpB,MAAM,OAAO,KAAK,KAAK,KAAK,CAAC;GAC7B,MAAM,KAAK,GAAG,SAAS,IAAI;GAC3B,MAAM,YAAY,qBAAqB,CAAC,KAAK,GAAG;GAChD,OAAO;IACH,KAAK;IACL,MAAM;IACN,WAAW,GAAG;IACd,WAAW,UAAU,YAAY;IACjC,iBAAiB;GACrB;EACJ,CAAC,CAAC,CACD,KAAK,QAAQ;CACtB;CAEA,IAAI,CAAC,SAAS,OAAO,CAAC;CAKtB,QAAO,MAJc,QAAQ,YAAY,KAAK,SAAS,GAAG,KAAK,OAAO,KAAK,IAAI;EAC3E,QAAQ,KAAK;EACb,YAAY;CAChB,CAAC,EAAA,CACa,MACT,KAAK,SAAS,KAAK,QAAQ,CAAC,CAC5B,QAAQ,QAAQ,IAAI,SAAS,OAAO,CAAC,CAAC,CACtC,KAAK,QAAoB;EACtB,MAAM,YAAY,qBAAqB,GAAG;EAC1C,OAAO;GACH;GACA,MAAM,IAAI,MAAM,GAAG,CAAC,CAAC,IAAI,KAAK;GAC9B,WAAW,YAAY,UAAU,YAAY,IAAI,KAAA;GACjD,iBAAiB,KAAK;EAC1B;CACJ,CAAC,CAAC,CACD,KAAK,QAAQ;AACtB;AAEA,SAAS,SAAS,GAAe,GAAuB;CACpD,MAAM,KAAK,EAAE,YAAY,KAAK,MAAM,EAAE,SAAS,IAAI;CAEnD,QADW,EAAE,YAAY,KAAK,MAAM,EAAE,SAAS,IAAI,KACvC;AAChB;;AAGA,eAAsB,gBAClB,MACA,KACA,SACgE;CAChE,IAAI,KAAK,SAAS,SAAS;EAEvB,MAAM,OADO,GAAG,WAAW,KAAK,IAAI,IAAI,GAAG,SAAS,KAAK,IAAI,IAAI,KAAA,EAC/C,YAAY,IAAI,KAAK,OAAO,KAAK,QAAQ,KAAK,IAAI;EACpE,MAAM,cAAc,KAAK,QAAQ,GAAG;EACpC,MAAM,WAAW,KAAK,QAAQ,GAAG;EAEjC,IAAI,CAAC,SAAS,WAAW,cAAc,KAAK,GAAG,KAAK,CAAC,SAAS,SAAS,OAAO,GAC1E,OAAO;EAEX,IAAI,CAAC,GAAG,WAAW,QAAQ,GAAG,OAAO;EACrC,OAAO;GAAE,OAAO,IAAI,WAAW,GAAG,aAAa,QAAQ,CAAC;GAAG,MAAM,KAAK,SAAS,QAAQ;EAAE;CAC7F;CAEA,IAAI,CAAC,SAAS,OAAO;CACrB,IAAI,CAAC,IAAI,SAAS,OAAO,GAAG,OAAO;CACnC,MAAM,OAAO,MAAM,QAAQ,UAAU,KAAK,KAAK,MAAM;CACrD,IAAI,CAAC,MAAM,OAAO;CAClB,OAAO;EAAE,OAAO,IAAI,WAAW,MAAM,KAAK,YAAY,CAAC;EAAG,MAAM,IAAI,MAAM,GAAG,CAAC,CAAC,IAAI,KAAK;CAAI;AAChG;;;;;;;;;;ACrGA,SAAgB,mBAAmB,QAA2C;CAC1E,MAAM,SAAS,IAAI,KAAc;CACjC,OAAO,QAAQ,YAAY;CAE3B,OAAO,IAAI,KAAK,OAAO,MAAM;EACzB,MAAM,OAAO,OAAO,eAAe;EACnC,IAAI,CAAC,MACD,OAAO,EAAE,KAAK;GAAE,SAAS,CAAC;GAAG,iBAAiB;GAAkB,YAAY;EAAM,CAAC;EAEvF,MAAM,UAAU,MAAM,kBAAkB,MAAM,OAAO,OAAO;EAC5D,OAAO,EAAE,KAAK;GAAE;GAAS,iBAAiB,KAAK;GAAM,YAAY;EAAK,CAAC;CAC3E,CAAC;CAED,OAAO,IAAI,aAAa,OAAO,MAAM;EACjC,MAAM,OAAO,OAAO,eAAe;EACnC,IAAI,CAAC,MACD,MAAM,SAAS,WAAW,sDAAsD;EAEpF,MAAM,MAAM,EAAE,IAAI,MAAM,KAAK;EAC7B,IAAI,CAAC,KACD,MAAM,SAAS,WAAW,gCAAgC;EAE9D,MAAM,SAAS,MAAM,gBAAgB,MAAM,KAAK,OAAO,OAAO;EAC9D,IAAI,CAAC,QACD,MAAM,SAAS,SAAS,qBAAqB,KAAK;EAEtD,EAAE,OAAO,gBAAgB,0BAA0B;EACnD,EAAE,OAAO,uBAAuB,yBAAyB,OAAO,KAAK,EAAE;EACvE,OAAO,EAAE,KAAK,OAAO,KAAK;CAC9B,CAAC;CAED,OAAO;AACX"}
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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"}
@@ -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
  *
@@ -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, empty or unreadable all mean the same thing: a single default database
141
- * and a single default bucket. That is the overwhelmingly common project, and it
142
- * must not be required to say so. A malformed export is reported and ignored
143
- * rather than fatal a typo in an optional declaration should not take down a
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 {
@@ -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.
@@ -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;
@@ -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 a data source's engine is the store for.
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 engine serves it — that is the point of `dataSource` routing. What it
137
- * is not is a list of tables to create: handing the whole directory to the
138
- * primary source's bootstrapper made a Firestore collection declared alongside
139
- * the Postgres ones arrive as an empty Postgres table, with RLS policies, while
140
- * the app went on reading its documents from Firestore.
141
- *
142
- * Excluding is deliberately conservative. A collection that names neither an
143
- * `engine` nor a `dataSource` belongs to whichever source is primary — the
144
- * "postgres" that `resolveDataSource` falls back to there is a default, not a
145
- * declaration, and must not exclude anything on its own. Only a collection that
146
- * explicitly routes to a *different* engine is dropped, so a project running two
147
- * sources on the same engine is unaffected.
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[], primary: {
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
- /** Say what was routed elsewhere, so a missing table is never a silent one. */
160
- export declare function logForeignCollections(all: CollectionConfig[], stored: CollectionConfig[], primary: {
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
- /** Databases in the graph, as the data layer's definitions. */
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;
@@ -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;