@rebasepro/server 0.25.1 → 0.26.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.
Files changed (157) hide show
  1. package/dist/{GCSStorageController-CRd9bFrc.js → GCSStorageController-40FyFeSx.js} +6 -2
  2. package/dist/GCSStorageController-40FyFeSx.js.map +1 -0
  3. package/dist/{S3StorageController-D00EFa5P.js → S3StorageController-Bl5shp94.js} +6 -2
  4. package/dist/S3StorageController-Bl5shp94.js.map +1 -0
  5. package/dist/access-BFAUvsSs.js +1897 -0
  6. package/dist/access-BFAUvsSs.js.map +1 -0
  7. package/dist/api/branch-routes.d.ts +25 -0
  8. package/dist/api/live-schema-routes.d.ts +10 -0
  9. package/dist/api/rest/api-generator.d.ts +9 -1
  10. package/dist/api/rest/idempotency.d.ts +15 -1
  11. package/dist/api/root-error-handler.d.ts +6 -5
  12. package/dist/api/types.d.ts +12 -0
  13. package/dist/{ast-schema-editor-mCeSC03x.js → ast-schema-editor-madTa9Hh.js} +2 -2
  14. package/dist/{ast-schema-editor-mCeSC03x.js.map → ast-schema-editor-madTa9Hh.js.map} +1 -1
  15. package/dist/auth/access.d.ts +10 -0
  16. package/dist/auth/adapter-middleware.d.ts +2 -1
  17. package/dist/auth/adapter-service-key.d.ts +24 -0
  18. package/dist/auth/api-keys/api-key-middleware.d.ts +21 -1
  19. package/dist/auth/api-keys/api-key-store.d.ts +8 -1
  20. package/dist/auth/api-keys/key-grant.d.ts +18 -1
  21. package/dist/auth/expired-token-sweep.d.ts +5 -0
  22. package/dist/auth/impersonation.d.ts +3 -1
  23. package/dist/auth/jwt.d.ts +8 -2
  24. package/dist/auth/oauth-signin-policy.d.ts +21 -35
  25. package/dist/auth/rls-scope.d.ts +17 -7
  26. package/dist/auth/rls-simulator-routes.d.ts +51 -0
  27. package/dist/auth/simulation-read-only.d.ts +28 -0
  28. package/dist/auth/simulation.d.ts +141 -0
  29. package/dist/auth/sql-rate-limit-store.d.ts +15 -1
  30. package/dist/{auth-Dkvo2tTJ.js → auth-izI7AEzC.js} +370 -1880
  31. package/dist/auth-izI7AEzC.js.map +1 -0
  32. package/dist/auth_adapter-B7RUMk6e.js +89 -0
  33. package/dist/auth_adapter-B7RUMk6e.js.map +1 -0
  34. package/dist/backend/create-backend.d.ts +6 -0
  35. package/dist/backend/options.d.ts +9 -4
  36. package/dist/{backend-DTAOsLQc.js → backend-CaG_Jyso.js} +9 -2
  37. package/dist/backend-CaG_Jyso.js.map +1 -0
  38. package/dist/{backup-B9m3cb3X.js → backup-BVbHoKo8.js} +6 -6
  39. package/dist/{backup-B9m3cb3X.js.map → backup-BVbHoKo8.js.map} +1 -1
  40. package/dist/boot/boot.d.ts +2 -15
  41. package/dist/boot/ddl-bootstrap.d.ts +34 -0
  42. package/dist/boot/env.d.ts +6 -0
  43. package/dist/boot/fetch-bundle.d.ts +5 -1
  44. package/dist/boot/provision-only.d.ts +23 -0
  45. package/dist/boot/provision.d.ts +53 -2
  46. package/dist/boot/role.d.ts +2 -0
  47. package/dist/boot/schema-change-recorder.d.ts +45 -0
  48. package/dist/boot/security-headers.d.ts +22 -0
  49. package/dist/branch-routes-B2vvEx53.js +84 -0
  50. package/dist/branch-routes-B2vvEx53.js.map +1 -0
  51. package/dist/{contract-routes-CsFXW7j4.js → contract-routes-CU2A3JnF.js} +2 -2
  52. package/dist/{contract-routes-CsFXW7j4.js.map → contract-routes-CU2A3JnF.js.map} +1 -1
  53. package/dist/cron/cron-store.d.ts +8 -1
  54. package/dist/{cron-loader-Dj6lA4HQ.js → cron-loader-mMhqbVsl.js} +2 -2
  55. package/dist/{cron-loader-Dj6lA4HQ.js.map → cron-loader-mMhqbVsl.js.map} +1 -1
  56. package/dist/{cron-routes-CwbLZguO.js → cron-routes-Cs33j_Kr.js} +3 -3
  57. package/dist/{cron-routes-CwbLZguO.js.map → cron-routes-Cs33j_Kr.js.map} +1 -1
  58. package/dist/{cron-scheduler-B04ACoUE.js → cron-scheduler-Be7CNghL.js} +2 -2
  59. package/dist/{cron-scheduler-B04ACoUE.js.map → cron-scheduler-Be7CNghL.js.map} +1 -1
  60. package/dist/{cron-store-DkyfuLOH.js → cron-store-DLn11M8H.js} +10 -9
  61. package/dist/cron-store-DLn11M8H.js.map +1 -0
  62. package/dist/{strings.es-BeZWAodn.js → data_driver-D0mg1Y_x.js} +2 -90
  63. package/dist/data_driver-D0mg1Y_x.js.map +1 -0
  64. package/dist/{ddl-bootstrap-Ck20GSIX.js → ddl-bootstrap-CjRrn12l.js} +91 -2
  65. package/dist/ddl-bootstrap-CjRrn12l.js.map +1 -0
  66. package/dist/deploy/pod-contract.d.ts +1 -1
  67. package/dist/email/dev-sink.d.ts +6 -0
  68. package/dist/email/index.d.ts +1 -1
  69. package/dist/{soft-delete-params-BmlVwecC.js → field-access-DYsvjI_P.js} +27 -57
  70. package/dist/field-access-DYsvjI_P.js.map +1 -0
  71. package/dist/{filter-dialect-O1HK6Nvr.js → filter-dialect-TrKFWLqT.js} +3 -3
  72. package/dist/{filter-dialect-O1HK6Nvr.js.map → filter-dialect-TrKFWLqT.js.map} +1 -1
  73. package/dist/{function-loader-KKDqTcIr.js → function-loader-B8YzXEsQ.js} +21 -14
  74. package/dist/function-loader-B8YzXEsQ.js.map +1 -0
  75. package/dist/functions/hono-like.d.ts +12 -0
  76. package/dist/functions/index.js +19 -1
  77. package/dist/functions/index.js.map +1 -1
  78. package/dist/functions/request-timeout.d.ts +3 -3
  79. package/dist/history/history-routes.d.ts +6 -0
  80. package/dist/history/retention-sweep.d.ts +5 -0
  81. package/dist/{history-recorder-B2rkuhuy.js → history-recorder-Bsau7h4X.js} +6 -6
  82. package/dist/history-recorder-Bsau7h4X.js.map +1 -0
  83. package/dist/{history-store-DPofGVDp.js → history-store-BQz5YCkW.js} +26 -2
  84. package/dist/history-store-BQz5YCkW.js.map +1 -0
  85. package/dist/index.d.ts +3 -1
  86. package/dist/index.es.js +2449 -543
  87. package/dist/index.es.js.map +1 -1
  88. package/dist/init/process-safety.d.ts +4 -3
  89. package/dist/init/shutdown.d.ts +8 -0
  90. package/dist/init.d.ts +6 -1
  91. package/dist/jobs/job-store.d.ts +8 -1
  92. package/dist/{jobs-h1WRq3cp.js → jobs-Co6Ni5HP.js} +6 -6
  93. package/dist/jobs-Co6Ni5HP.js.map +1 -0
  94. package/dist/{jwt-D3oFauZI.js → jwt-rDS0884_.js} +4 -3
  95. package/dist/{jwt-D3oFauZI.js.map → jwt-rDS0884_.js.map} +1 -1
  96. package/dist/{keys-COzQ2aE8.js → keys-BXcdRNhJ.js} +2 -2
  97. package/dist/{keys-COzQ2aE8.js.map → keys-BXcdRNhJ.js.map} +1 -1
  98. package/dist/mcp/consent-page.d.ts +38 -1
  99. package/dist/mcp/mcp-routes.d.ts +9 -0
  100. package/dist/mcp/mcp-tools.d.ts +7 -1
  101. package/dist/mcp/oauth-store.d.ts +12 -5
  102. package/dist/metrics/history-recorder.d.ts +4 -1
  103. package/dist/metrics/history-store.d.ts +8 -0
  104. package/dist/{offline-BrqCRgih-BtDB1ecy.js → offline-AvuEcg7u-DFHL98OT.js} +127 -40
  105. package/dist/offline-AvuEcg7u-DFHL98OT.js.map +1 -0
  106. package/dist/{offline-store-BhYXViha-BW5j1We_.js → offline-store-BhYXViha-YTmjBpo2.js} +5 -5
  107. package/dist/{offline-store-BhYXViha-BW5j1We_.js.map → offline-store-BhYXViha-YTmjBpo2.js.map} +1 -1
  108. package/dist/{openapi-generator-BjHr6upl.js → openapi-generator-CyxEtYEX.js} +61 -22
  109. package/dist/openapi-generator-CyxEtYEX.js.map +1 -0
  110. package/dist/{proxy-CH8Jsovg.js → proxy-BYir8tWK.js} +4 -6
  111. package/dist/proxy-BYir8tWK.js.map +1 -0
  112. package/dist/{query-parser-otFZqJsC.js → query-parser-CDXWf2hH.js} +6 -5
  113. package/dist/{query-parser-otFZqJsC.js.map → query-parser-CDXWf2hH.js.map} +1 -1
  114. package/dist/{request-timeout-59crS-9u.js → request-timeout-DVjo-c_8.js} +4 -4
  115. package/dist/request-timeout-DVjo-c_8.js.map +1 -0
  116. package/dist/rls-simulator-routes-bEY7-5-u.js +190 -0
  117. package/dist/rls-simulator-routes-bEY7-5-u.js.map +1 -0
  118. package/dist/schema-edit/deployed-schema-guard.d.ts +47 -0
  119. package/dist/{schema-editor-routes-Bsut0bCC.js → schema-editor-routes-Dc3DU_jm.js} +3 -3
  120. package/dist/{schema-editor-routes-Bsut0bCC.js.map → schema-editor-routes-Dc3DU_jm.js.map} +1 -1
  121. package/dist/soft-delete-params-BRONCOA2.js +59 -0
  122. package/dist/soft-delete-params-BRONCOA2.js.map +1 -0
  123. package/dist/{sql-export-routes-DmB_uZ4l.js → sql-export-routes-QycX8W8S.js} +38 -4
  124. package/dist/sql-export-routes-QycX8W8S.js.map +1 -0
  125. package/dist/{src-D5Y3N-S4.js → src-Bh9LRPb9.js} +21 -2
  126. package/dist/{src-D5Y3N-S4.js.map → src-Bh9LRPb9.js.map} +1 -1
  127. package/dist/storage/GCSStorageController.d.ts +2 -0
  128. package/dist/storage/LocalStorageController.d.ts +8 -3
  129. package/dist/storage/S3StorageController.d.ts +2 -0
  130. package/dist/storage/maintenance-keys.d.ts +17 -0
  131. package/dist/storage/maintenance-routes.d.ts +28 -5
  132. package/dist/storage/maintenance.d.ts +136 -4
  133. package/dist/storage/orphan-scans.d.ts +120 -0
  134. package/dist/storage/types.d.ts +11 -0
  135. package/dist/strings.es-B7QLnHh2.js +96 -0
  136. package/dist/strings.es-B7QLnHh2.js.map +1 -0
  137. package/dist/upload-validation-Bgo626uY.js.map +1 -1
  138. package/package.json +5 -5
  139. package/dist/GCSStorageController-CRd9bFrc.js.map +0 -1
  140. package/dist/S3StorageController-D00EFa5P.js.map +0 -1
  141. package/dist/auth-Dkvo2tTJ.js.map +0 -1
  142. package/dist/auth_adapter-CIItCTc8.js +0 -21
  143. package/dist/auth_adapter-CIItCTc8.js.map +0 -1
  144. package/dist/backend-DTAOsLQc.js.map +0 -1
  145. package/dist/cron-store-DkyfuLOH.js.map +0 -1
  146. package/dist/ddl-bootstrap-Ck20GSIX.js.map +0 -1
  147. package/dist/function-loader-KKDqTcIr.js.map +0 -1
  148. package/dist/history-recorder-B2rkuhuy.js.map +0 -1
  149. package/dist/history-store-DPofGVDp.js.map +0 -1
  150. package/dist/jobs-h1WRq3cp.js.map +0 -1
  151. package/dist/offline-BrqCRgih-BtDB1ecy.js.map +0 -1
  152. package/dist/openapi-generator-BjHr6upl.js.map +0 -1
  153. package/dist/proxy-CH8Jsovg.js.map +0 -1
  154. package/dist/request-timeout-59crS-9u.js.map +0 -1
  155. package/dist/soft-delete-params-BmlVwecC.js.map +0 -1
  156. package/dist/sql-export-routes-DmB_uZ4l.js.map +0 -1
  157. package/dist/strings.es-BeZWAodn.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"ast-schema-editor-mCeSC03x.js","names":[],"sources":["../src/api/ast-schema-editor.ts"],"sourcesContent":["import { Project, SyntaxKind, Node, ObjectLiteralExpression, ObjectLiteralElementLike, PropertyAssignment, SourceFile, IndentationText } from \"ts-morph\";\nimport {\n collectionPatchProblems,\n nestAdminCollectionKeys,\n nestAdminPropertyKeys,\n nestCollectionPatchPaths,\n runtimeOnlyKeyIn,\n type CollectionPatch,\n type CollectionPatchOp\n} from \"@rebasepro/types\";\nimport * as path from \"path\";\nimport * as fs from \"fs\";\n\n/**\n * The helpers a collection file may be wrapped in.\n *\n * `rebase init` scaffolds every collection as\n * `const postsCollection = defineCollection({ … })` — a call expression, not the\n * bare object literal `rebase introspect` emits. An editor that only understood\n * the bare form found nothing to patch in any stock project, and then rewrote the\n * file from the panel's JSON: no wrapper, no imports, no relation thunks.\n */\nconst COLLECTION_FACTORIES = new Set([\"defineCollection\"]);\n\n/**\n * The only relation-target expression this editor will write through verbatim.\n *\n * A relation's `target` is emitted as SOURCE, not as a string literal, because\n * it has to be `() => otherCollection`. The test for \"is this already a thunk?\"\n * used to be \"does it contain an arrow\", which `() => { require(\"child_process\")\n * .execSync(\"…\") }` also satisfies — and `rebase dev` re-imports the file the\n * moment it changes, so the payload ran without anyone deploying anything.\n *\n * An arrow returning a single identifier is the whole grammar. Anything else is\n * read as a collection name and turned into a thunk by `targetThunk`, which\n * resolves it against the collections directory and refuses a name with no file.\n */\nconst ARROW_TO_IDENTIFIER = /^\\(\\s*\\)\\s*=>\\s*[A-Za-z_$][A-Za-z0-9_$]*$/;\n\n/**\n * A value that must be emitted as source code rather than as JSON.\n *\n * Everything reaching the writer has been through `JSON.stringify` on the wire,\n * so a function-valued key arrives either missing or as a string. A relation's\n * `target` is a thunk in the file and a slug in the payload; this is how the\n * thunk gets written back.\n */\nclass RawExpression {\n constructor(public readonly text: string) {\n }\n}\n\n/**\n * Move presentation keys into the `admin` block.\n *\n * The rule itself lives in `@rebasepro/types`, next to `ADMIN_COLLECTION_KEYS`,\n * because `@rebasepro/cms-types` has to apply the identical one on the panel's\n * side and this package cannot import that one. Two copies used to exist and\n * they disagreed about precedence, which decided whether a presentation edit was\n * saved or silently reverted to the value the user had just changed away from.\n */\nexport function nestAdminKeys(collectionData: Record<string, unknown>): Record<string, unknown> {\n return nestAdminCollectionKeys(collectionData);\n}\n\n/**\n * {@link nestAdminKeys} for a whole collection — its properties included.\n *\n * `nestAdminKeys` only ever moved the COLLECTION's presentation keys. A\n * property's — `markdown`, `multiline`, `previewAsTag` — went to disk exactly as\n * the panel sent them, at the top level of the property, which the boot\n * validator treats as fatal. `saveProperty` nests them; `saveCollection` did\n * not, and *creating* a collection is a `saveCollection`.\n *\n * So a collection created from a template wrote a file that could not boot, and\n * took the whole project down with it — the loader imports every collection in\n * the directory, so one bad file stops `rebase dev` from starting at all. The\n * panel reported success.\n */\nexport function nestAdminKeysDeep(collectionData: Record<string, unknown>): Record<string, unknown> {\n const nested = nestAdminKeys(collectionData);\n const properties = nested.properties;\n if (properties && typeof properties === \"object\" && !Array.isArray(properties)) {\n nested.properties = Object.fromEntries(\n Object.entries(properties as Record<string, unknown>).map(([key, property]) => [\n key,\n property && typeof property === \"object\" && !Array.isArray(property)\n ? nestAdminPropertyKeys(property as Record<string, unknown>)\n : property\n ])\n );\n }\n return nested;\n}\n\nexport class AstSchemaEditor {\n private project: Project;\n private collectionsDir: string;\n\n constructor(collectionsDir: string) {\n this.project = new Project({\n manipulationSettings: {\n indentationText: IndentationText.FourSpaces\n }\n });\n if (fs.existsSync(collectionsDir)) {\n this.project.addSourceFilesAtPaths(`${collectionsDir}/**/*.ts`);\n }\n this.collectionsDir = path.resolve(collectionsDir);\n }\n\n /**\n * Sanitize collectionId to prevent path traversal attacks.\n * Only allows alphanumeric characters, underscores, and hyphens.\n */\n /**\n * The variable name the generated file binds the collection to.\n *\n * `sanitizeCollectionId` guards the FILENAME, and permits hyphens and a\n * leading digit — both legal in a filename and neither legal in a\n * JavaScript identifier. So a collection created from the admin panel as\n * `my-notes` (the documented slug shape) or `2024 Archive` (auto-slugged to\n * `2024_archive`) wrote:\n *\n * const my-notesCollection: CollectionConfig = … \"',' expected\"\n * const 2024_archiveCollection: CollectionConfig = … \"Numeric separators\n * are not allowed here\"\n *\n * The panel reported success, and the next boot failed for EVERY collection\n * in the directory — the loader imports all of them — while the editor could\n * no longer parse the file it had just written, so it could not fix itself.\n *\n * Separators camel-case rather than vanish, so `my-notes` and `my_notes`\n * stay distinct; a leading digit is prefixed rather than stripped, so\n * `2024_archive` stays distinct from `archive`. Byte-identical for every\n * slug that already produced a valid identifier.\n */\n private static collectionVarName(safeId: string): string {\n const camel = safeId.replace(/[-_]+([a-zA-Z0-9])/g, (_, char: string) => char.toUpperCase());\n const identifier = /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(camel) ? camel : `c${camel.replace(/^[0-9]/, (d) => d)}`;\n return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(identifier) ? identifier : `c${identifier.replace(/[^A-Za-z0-9_$]/g, \"\")}`;\n }\n\n private sanitizeCollectionId(collectionId: string): string {\n const sanitized = collectionId.replace(/[^a-zA-Z0-9_-]/g, \"\");\n if (!sanitized || sanitized !== collectionId) {\n throw new Error(`Invalid collection ID: \"${collectionId}\". Only alphanumeric characters, underscores, and hyphens are allowed.`);\n }\n return sanitized;\n }\n\n /**\n * Resolve a file path and ensure it falls within the collectionsDir.\n */\n private safePath(filename: string): string {\n const resolved = path.resolve(this.collectionsDir, filename);\n if (!resolved.startsWith(this.collectionsDir + path.sep) && resolved !== this.collectionsDir) {\n throw new Error(\"Path traversal detected: resolved path is outside the collections directory.\");\n }\n return resolved;\n }\n\n /**\n * Bring the in-memory project back in line with the disk.\n *\n * The source-only editor builds ONE of these at boot and serves every\n * request with it, and ts-morph reads a file once and then trusts its\n * copy. `project.save()` writes that copy back whole, so anything that\n * reached the disk in between — the live editor's committed change (it\n * builds a fresh editor per request), or the developer's own edit in the\n * IDE — was overwritten by the boot-time copy plus this edit. In `rebase\n * dev` the tsx-watch restart usually closed the window; outside watch mode\n * the loss was permanent.\n *\n * Called at the top of every public method, so each edit applies to what\n * is on disk now.\n */\n private syncWithDisk(): void {\n for (const file of this.project.getSourceFiles()) {\n if (!fs.existsSync(file.getFilePath())) {\n this.project.removeSourceFile(file);\n } else {\n file.refreshFromFileSystemSync();\n }\n }\n if (fs.existsSync(this.collectionsDir)) {\n this.project.addSourceFilesAtPaths(`${this.collectionsDir}/**/*.ts`);\n }\n }\n\n private getCollectionFile(collectionId: string) {\n const safeId = this.sanitizeCollectionId(collectionId);\n const filePath = this.safePath(`${safeId}.ts`);\n let file = this.project.getSourceFile(filePath);\n if (!file && fs.existsSync(filePath)) {\n this.project.addSourceFilesAtPaths(`${this.collectionsDir}/**/*.ts`);\n file = this.project.getSourceFile(filePath);\n }\n return file;\n }\n\n /**\n * Find the object literal a collection is declared with, through whatever\n * wraps it.\n *\n * `defineCollection({ … })` is the shape `rebase init` writes and the one the\n * docs recommend; `satisfies` / `as` / parentheses are the other ways an\n * author can dress the same literal. Returning `null` for any of them meant\n * the three callers below each failed differently, and the worst of them\n * overwrote the file.\n */\n private unwrapCollectionObject(node: Node | undefined): ObjectLiteralExpression | null {\n if (!node) return null;\n if (Node.isObjectLiteralExpression(node)) return node;\n if (Node.isParenthesizedExpression(node) ||\n Node.isAsExpression(node) ||\n Node.isSatisfiesExpression(node) ||\n Node.isTypeAssertion(node) ||\n Node.isNonNullExpression(node)) {\n return this.unwrapCollectionObject(node.getExpression());\n }\n if (Node.isCallExpression(node)) {\n // `defineCollection`, `admin.defineCollection`, `defineCollection<Post>`\n const callee = node.getExpression().getText().split(\".\").pop();\n if (callee && COLLECTION_FACTORIES.has(callee)) {\n return this.unwrapCollectionObject(node.getArguments()[0]);\n }\n }\n return null;\n }\n\n private getCollectionObject(collectionId: string): ObjectLiteralExpression | null {\n const file = this.getCollectionFile(collectionId);\n if (!file) return null;\n\n const defaultExport = file.getDefaultExportSymbol();\n if (defaultExport) {\n const declaration = defaultExport.getDeclarations()[0];\n if (declaration && declaration.getKind() === SyntaxKind.ExportAssignment) {\n const expr = declaration.asKind(SyntaxKind.ExportAssignment)?.getExpression();\n if (expr && expr.getKind() === SyntaxKind.Identifier) {\n const varName = expr.getText();\n const varDecl = file.getVariableDeclaration(varName);\n const unwrapped = this.unwrapCollectionObject(varDecl?.getInitializer());\n if (unwrapped) return unwrapped;\n } else {\n // `export default defineCollection({ … })`\n const unwrapped = this.unwrapCollectionObject(expr);\n if (unwrapped) return unwrapped;\n }\n }\n }\n // Fallback: the first VariableDeclaration that holds a collection literal\n for (const varDecl of file.getVariableDeclarations()) {\n const init = this.unwrapCollectionObject(varDecl.getInitializer());\n if (init) return init;\n }\n return null;\n }\n\n /**\n * The collection's object literal, or a refusal that says what to do.\n *\n * Every caller needs this to be all-or-nothing: a missing object literal used\n * to mean \"throw\", \"report success and do nothing\" and \"recreate the file\n * from scratch\" depending on which method you called.\n */\n private requireCollectionObject(collectionId: string): ObjectLiteralExpression {\n const file = this.getCollectionFile(collectionId);\n if (!file) {\n throw new Error(`Collection \"${collectionId}\" has no file at ${path.join(this.collectionsDir, `${collectionId}.ts`)}.`);\n }\n const collectionObj = this.getCollectionObject(collectionId);\n if (!collectionObj) {\n throw new Error(this.unreadableFileMessage(collectionId, file));\n }\n return collectionObj;\n }\n\n private unreadableFileMessage(collectionId: string, file: SourceFile): string {\n return `Could not find the collection object in ${file.getFilePath()}. ` +\n \"The schema editor can only edit a collection declared as `const x = defineCollection({ … })` \" +\n \"or `const x: CollectionConfig = { … }` and exported as the file's default. \" +\n `Edit \"${collectionId}\" in code instead.`;\n }\n\n /** Look a key up on an object literal, quoted or not. */\n private findProperty(obj: ObjectLiteralExpression, name: string): ObjectLiteralElementLike | undefined {\n return obj.getProperty((p: ObjectLiteralElementLike) =>\n \"getName\" in p &&\n typeof (p as PropertyAssignment).getName === \"function\" &&\n ((p as PropertyAssignment).getName() === name ||\n (p as PropertyAssignment).getName() === `\"${name}\"` ||\n (p as PropertyAssignment).getName() === `'${name}'`));\n }\n\n private static quoteKey(key: string): string {\n return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key) ? key : JSON.stringify(key);\n }\n\n private static isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value) && !(value instanceof RawExpression);\n }\n\n /**\n * The field that identifies each element of an array of objects, so an\n * old element can be found again in the new array.\n *\n * Arrays travel whole (see `collection_patch.ts`), and an element's\n * position is not its identity — adding an entity action at the front\n * shifts every other one. `key`, `id`, `relationName`, `name`: the first\n * one every new element carries, distinct, wins.\n */\n private static identityField(items: unknown[]): string | undefined {\n for (const field of [\"key\", \"id\", \"relationName\", \"name\", \"slug\"]) {\n const values = items.map(item => AstSchemaEditor.isPlainObject(item) ? item[field] : undefined);\n if (values.every(value => typeof value === \"string\" || typeof value === \"number\") &&\n new Set(values).size === values.length) {\n return field;\n }\n }\n return undefined;\n }\n\n /** The old array's object elements, keyed by `field`'s literal value. */\n private oldElementsBy(oldArray: Node, field: string): Map<string, ObjectLiteralExpression> {\n const byKey = new Map<string, ObjectLiteralExpression>();\n const array = oldArray.asKind(SyntaxKind.ArrayLiteralExpression);\n for (const element of array?.getElements() ?? []) {\n const object = element.asKind(SyntaxKind.ObjectLiteralExpression);\n const value = object ? this.initializerOf(object, field) : undefined;\n const literal = value?.asKind(SyntaxKind.StringLiteral)?.getLiteralValue()\n ?? value?.asKind(SyntaxKind.NoSubstitutionTemplateLiteral)?.getLiteralValue()\n ?? value?.asKind(SyntaxKind.NumericLiteral)?.getText();\n if (object && literal !== undefined) byKey.set(String(literal), object);\n }\n return byKey;\n }\n\n private convertJsonToAstString(obj: unknown, indentLevel = 0, oldAstNode?: Node): string {\n // Base TS-morph parses arrays as 2 levels deep from the property key:\n // PropertiesObject = level 1, PropertyConfig = level 2.\n // We calibrate the spacing multiples to keep the items flush with standard TS format.\n const indentStr = \" \";\n const indent = indentStr.repeat(indentLevel);\n const innerIndent = indentStr.repeat(indentLevel + 1);\n\n if (obj instanceof RawExpression) {\n return obj.text;\n }\n if (obj === null || obj === undefined) {\n return \"undefined\";\n }\n if (typeof obj === \"string\") {\n return JSON.stringify(obj);\n }\n if (typeof obj === \"number\" || typeof obj === \"boolean\") {\n return String(obj);\n }\n if (Array.isArray(obj)) {\n if (obj.length === 0) return \"[]\";\n // Each element written over its old self, so what JSON dropped\n // from it — an action's `onClick`, a field's `value` — is kept.\n const field = oldAstNode ? AstSchemaEditor.identityField(obj) : undefined;\n const old = oldAstNode && field ? this.oldElementsBy(oldAstNode, field) : undefined;\n const items = obj.map(item => this.convertJsonToAstString(\n item,\n indentLevel + 1,\n old && field && AstSchemaEditor.isPlainObject(item) ? old.get(String(item[field])) : undefined\n ));\n return `[\\n${innerIndent}${items.join(`,\\n${innerIndent}`)}\\n${indent}]`;\n }\n if (typeof obj === \"object\") {\n const record = obj as Record<string, unknown>;\n const keys = Object.keys(record);\n const oldObject = oldAstNode?.asKind(SyntaxKind.ObjectLiteralExpression);\n\n // Collect preserved AST properties\n const preservedProps: string[] = [];\n if (oldObject) {\n const oldProps = oldObject.getProperties();\n for (const oldProp of oldProps) {\n if (oldProp.isKind(SyntaxKind.PropertyAssignment)) {\n const nameNode = oldProp.getNameNode();\n let name = nameNode.getText();\n if (name.startsWith('\"') && name.endsWith('\"')) name = name.slice(1, -1);\n if (name.startsWith(\"'\") && name.endsWith(\"'\")) name = name.slice(1, -1);\n\n // If the JSON object doesn't have this key, check if we should preserve it\n if (!(name in record)) {\n const init = oldProp.getInitializer();\n if (init) {\n const kind = init.getKind();\n const isCode = kind === SyntaxKind.ArrowFunction ||\n kind === SyntaxKind.FunctionExpression ||\n kind === SyntaxKind.Identifier ||\n kind === SyntaxKind.CallExpression ||\n kind === SyntaxKind.JsxElement;\n\n if (isCode || name === \"target\" || name === \"callbacks\" || name === \"browserCallbacks\" || name === \"permissions\" || name === \"securityRules\") {\n // Preserve this property exactly as it was\n preservedProps.push(`${AstSchemaEditor.quoteKey(name)}: ${init.getText()}`);\n }\n }\n }\n }\n }\n }\n\n if (keys.length === 0 && preservedProps.length === 0) return \"{}\";\n\n const props = keys.map(key => {\n const keyStr = AstSchemaEditor.quoteKey(key);\n\n // If the value is an object or an array, pass the old AST node\n // to recurse\n let childAstNode: Node | undefined;\n if (oldObject && (AstSchemaEditor.isPlainObject(record[key]) || Array.isArray(record[key]))) {\n const oldProp = this.findProperty(oldObject, key);\n if (oldProp && oldProp.isKind(SyntaxKind.PropertyAssignment)) {\n childAstNode = oldProp.getInitializerIfKind(SyntaxKind.ObjectLiteralExpression)\n ?? oldProp.getInitializerIfKind(SyntaxKind.ArrayLiteralExpression);\n }\n }\n\n return `${keyStr}: ${this.convertJsonToAstString(record[key], indentLevel + 1, childAstNode)}`;\n });\n\n const allProps = [...props, ...preservedProps];\n return `{\\n${innerIndent}${allProps.join(`,\\n${innerIndent}`)}\\n${indent}}`;\n }\n return \"undefined\";\n }\n\n /**\n * Write only the keys the patch names, leaving every sibling alone.\n *\n * A patch says what changed, not what the collection is. The panel sends one\n * — `{ propertiesOrder }` is what adding a column posts — and rewriting the\n * `admin` block from it deleted the collection's icon, group, list columns\n * and kanban config in the same write.\n */\n private mergeIntoObjectLiteral(target: ObjectLiteralExpression, data: Record<string, unknown>, indentLevel: number): void {\n for (const [key, value] of Object.entries(data)) {\n const existing = this.findProperty(target, key);\n const existingObj = existing && existing.isKind(SyntaxKind.PropertyAssignment)\n ? existing.getInitializerIfKind(SyntaxKind.ObjectLiteralExpression)\n : undefined;\n\n if (existingObj && AstSchemaEditor.isPlainObject(value)) {\n this.mergeIntoObjectLiteral(existingObj, value, indentLevel + 1);\n continue;\n }\n\n const initializer = this.convertJsonToAstString(value, indentLevel, existingObj);\n if (existing && existing.isKind(SyntaxKind.PropertyAssignment)) {\n existing.setInitializer(initializer);\n } else {\n target.addPropertyAssignment({\n name: AstSchemaEditor.quoteKey(key),\n initializer\n });\n }\n }\n }\n\n /**\n * Drop whatever a refused edit left in memory.\n *\n * One editor serves every request, and `project.save()` writes every file\n * with unsaved changes. So an edit refused halfway through — a relation with\n * no target, found after the name was already rewritten and an import\n * added — reached the disk with the next save of some other collection, and\n * a file refused as it was being created appeared anyway.\n */\n private forgetUnsaved(collectionId: string): void {\n const file = this.project.getSourceFile(path.resolve(this.collectionsDir, `${collectionId}.ts`));\n if (!file || file.isSaved()) return;\n if (fs.existsSync(file.getFilePath())) {\n file.refreshFromFileSystemSync();\n } else {\n this.project.removeSourceFile(file);\n }\n }\n\n public async saveProperty(collectionId: string, propertyKey: string, propertyConfig: Record<string, unknown>) {\n this.syncWithDisk();\n try {\n await this.writeProperty(collectionId, propertyKey, propertyConfig);\n } catch (err) {\n this.forgetUnsaved(collectionId);\n throw err;\n }\n }\n\n private async writeProperty(collectionId: string, propertyKey: string, propertyConfig: Record<string, unknown>) {\n const collectionObj = this.requireCollectionObject(collectionId);\n\n // The panel's property forms bind to the flat names — `readOnly`,\n // `hideFromCollection` — while on disk they belong inside the property's\n // own `admin` block. Written flat they are not merely ignored: the boot\n // validator treats a moved key as fatal.\n const nestedConfig = nestAdminPropertyKeys(propertyConfig);\n\n let propertiesProp = collectionObj.getProperty(\"properties\") as PropertyAssignment;\n if (!propertiesProp) {\n propertiesProp = collectionObj.addPropertyAssignment({\n name: \"properties\",\n initializer: \"{}\"\n });\n }\n\n const propsObj = propertiesProp.getInitializerIfKind(SyntaxKind.ObjectLiteralExpression);\n if (propsObj) {\n const existingProp = this.findProperty(propsObj, propertyKey);\n\n let oldPropAstNode: ObjectLiteralExpression | undefined;\n if (existingProp && existingProp.isKind(SyntaxKind.PropertyAssignment)) {\n oldPropAstNode = existingProp.getInitializerIfKind(SyntaxKind.ObjectLiteralExpression);\n }\n\n const withTargets = this.relationTargetsAsSource(\n collectionId,\n this.getCollectionFile(collectionId)!,\n { [propertyKey]: nestedConfig },\n propsObj\n )[propertyKey];\n const newInitializer = this.convertJsonToAstString(withTargets, 2, oldPropAstNode);\n\n if (existingProp) {\n if (existingProp.isKind(SyntaxKind.PropertyAssignment)) {\n existingProp.setInitializer(newInitializer);\n }\n } else {\n propsObj.addPropertyAssignment({\n name: AstSchemaEditor.quoteKey(propertyKey),\n initializer: newInitializer\n });\n }\n\n const file = this.getCollectionFile(collectionId);\n if (file) {\n file.formatText();\n }\n await this.project.save();\n }\n }\n\n public async deleteProperty(collectionId: string, propertyKey: string) {\n this.syncWithDisk();\n const collectionObj = this.requireCollectionObject(collectionId);\n\n const propertiesProp = collectionObj.getProperty(\"properties\") as PropertyAssignment;\n if (propertiesProp) {\n const propsObj = propertiesProp.getInitializerIfKind(SyntaxKind.ObjectLiteralExpression);\n if (propsObj) {\n const existingProp = this.findProperty(propsObj, propertyKey);\n if (existingProp) {\n existingProp.remove();\n const file = this.getCollectionFile(collectionId);\n if (file) {\n file.formatText();\n }\n await this.project.save();\n }\n }\n }\n }\n\n /**\n * Write a collection back to its file.\n *\n * `partial` is the difference between \"this is the collection\" and \"this is\n * what changed about it\". The panel sends both — a full save from the editor\n * dialog, and a one-key patch whenever a property is added, deleted or\n * reordered — and they cannot be told apart by looking at the payload. Read\n * as a full save, a patch deletes everything it does not mention, including\n * `securityRules`; the loader then hands the collection the directory\n * default, which in the scaffold is `access: \"public\"`.\n */\n public async saveCollection(collectionId: string, collectionData: Record<string, unknown>, options: { partial?: boolean } = {}) {\n this.syncWithDisk();\n // The registry's resolved form of a relation, written into a file,\n // stops it type-checking. A collection that carries one is the view\n // the panel renders, not one somebody authored.\n const runtimeOnly = runtimeOnlyKeyIn(collectionData);\n if (runtimeOnly) {\n throw new Error(\n `Refusing to write \"${collectionId}\": it holds \"${runtimeOnly}\", which the runtime adds to a ` +\n \"collection it has loaded and a collection file must not hold. Save what changed instead.\"\n );\n }\n try {\n await this.writeCollection(collectionId, collectionData, options);\n } catch (err) {\n this.forgetUnsaved(collectionId);\n throw err;\n }\n }\n\n /**\n * Write what changed about a collection, and nothing else.\n *\n * The patch is the difference between the collection the panel loaded and\n * the one it is saving (`diffCollections` in `@rebasepro/types`), so it\n * names only keys the person changed. Each is written where it is; every\n * other byte of the file — imports, comments, formatting, handlers,\n * properties shared from another module — is left as it was.\n *\n * A key whose value in the file is code — `status: statusProperty`,\n * `enum: LOCALE_ENUM`, a spread — is not overwritten with the JSON the\n * panel saw of it, which is what the whole-collection save did. The edit\n * is refused, naming the code, so it can be made where that code lives.\n *\n * All or nothing: a refused operation leaves the file as it was, including\n * the operations before it.\n */\n public async applyPatch(collectionId: string, patch: CollectionPatch) {\n this.syncWithDisk();\n const problems = collectionPatchProblems(patch);\n if (problems.length > 0) {\n throw new Error(`Refusing to edit \"${collectionId}\": ${problems.join(\" \")}`);\n }\n try {\n await this.writePatch(collectionId, nestCollectionPatchPaths(patch));\n } catch (err) {\n this.forgetUnsaved(collectionId);\n throw err;\n }\n }\n\n private async writePatch(collectionId: string, patch: CollectionPatch) {\n this.requireCollectionObject(collectionId);\n const file = this.getCollectionFile(collectionId)!;\n for (const op of patch) {\n // From the root each time: formatting an edited node can forget\n // the nodes around it.\n this.applyPatchOp(collectionId, file, this.requireCollectionObject(collectionId), op);\n }\n await this.project.save();\n }\n\n private applyPatchOp(collectionId: string, file: SourceFile, collectionObj: ObjectLiteralExpression, op: CollectionPatchOp): void {\n const { path: keys } = op;\n const dotted = (end: number) => keys.slice(0, end).join(\".\");\n\n let target = collectionObj;\n for (let depth = 0; depth < keys.length - 1; depth++) {\n const key = keys[depth];\n const member = this.findMember(target, key);\n if (!member) {\n const spread = this.spreadIn(target);\n if (spread) throw this.codeRefusal(collectionId, file, dotted(depth + 1), spread, \"comes from\");\n // Nothing there to remove.\n if (op.op === \"remove\") return;\n target = target.addPropertyAssignment({ name: AstSchemaEditor.quoteKey(key), initializer: \"{}\" })\n .getInitializerIfKindOrThrow(SyntaxKind.ObjectLiteralExpression);\n continue;\n }\n const value = member.isKind(SyntaxKind.PropertyAssignment)\n ? AstSchemaEditor.unwrapExpression(member.getInitializer())\n : undefined;\n const next = value?.asKind(SyntaxKind.ObjectLiteralExpression);\n if (!next) throw this.codeRefusal(collectionId, file, dotted(depth + 1), value ?? member, \"is\");\n target = next;\n }\n\n const key = keys[keys.length - 1];\n const member = this.findMember(target, key);\n\n if (op.op === \"remove\") {\n if (member) {\n member.remove();\n return;\n }\n const spread = this.spreadIn(target);\n if (spread) throw this.codeRefusal(collectionId, file, dotted(keys.length), spread, \"comes from\");\n return;\n }\n\n // The collection's own `relations` pair each entry with its old self\n // by name, to keep the targets JSON cannot carry.\n if (keys.length === 1 && key === \"relations\") {\n this.writeRelations(collectionId, file, collectionObj, op.value);\n this.findMember(collectionObj, \"relations\")?.formatText();\n return;\n }\n\n const old = member?.isKind(SyntaxKind.PropertyAssignment) ? member.getInitializer() : undefined;\n if (member && (!old || !AstSchemaEditor.isLiteralValue(old))) {\n throw this.codeRefusal(collectionId, file, dotted(keys.length), old ?? member, \"is\");\n }\n if (old?.isKind(SyntaxKind.ArrayLiteralExpression)) {\n const code = old.getElements().find(element => !AstSchemaEditor.isLiteralValue(element));\n if (code) throw this.codeRefusal(collectionId, file, dotted(keys.length), code, \"holds\");\n }\n\n const value = this.patchValueAsSource(collectionId, file, keys, op.value, old);\n const text = this.convertJsonToAstString(value, 0, old);\n const written = member?.isKind(SyntaxKind.PropertyAssignment)\n ? member.setInitializer(text)\n : target.addPropertyAssignment({ name: AstSchemaEditor.quoteKey(key), initializer: text });\n // This node only: the rest of the file keeps its own formatting.\n written.formatText();\n }\n\n /**\n * A patched value with every relation target it carries turned back into\n * source — `() => xCollection` plus its import — according to where the\n * path lands: a relation's `target`, a relation, a property, or a map of\n * properties.\n */\n private patchValueAsSource(collectionId: string, file: SourceFile, keys: string[], value: unknown, old: Node | undefined): unknown {\n // Where the path lands, walked the way `collection_patch.ts` walks it.\n let position: \"collection\" | \"properties\" | \"property\" | \"relation\" | \"oneOf\" | \"other\" = \"collection\";\n for (const key of keys) {\n switch (position) {\n case \"collection\": position = key === \"properties\" ? \"properties\" : \"other\"; break;\n case \"properties\": position = \"property\"; break;\n case \"property\":\n position = key === \"properties\" ? \"properties\"\n : key === \"of\" ? \"property\"\n : key === \"oneOf\" ? \"oneOf\"\n : key === \"relation\" ? \"relation\"\n : \"other\";\n break;\n case \"oneOf\": position = key === \"properties\" ? \"properties\" : \"other\"; break;\n case \"relation\":\n if (key === \"target\" && typeof value === \"string\" && value.trim().length > 0) {\n const trimmed = value.trim();\n return new RawExpression(ARROW_TO_IDENTIFIER.test(trimmed) ? trimmed : this.targetThunk(file, trimmed));\n }\n position = \"other\";\n break;\n default: position = \"other\";\n }\n }\n const oldObject = old?.asKind(SyntaxKind.ObjectLiteralExpression);\n const where = keys.join(\".\");\n if (position === \"property\") {\n return this.propertyRelationTargetsAsSource(collectionId, file, value, oldObject, where);\n }\n if (position === \"properties\") {\n return this.relationTargetsAsSource(collectionId, file, value, oldObject, where);\n }\n if (position === \"relation\" && AstSchemaEditor.isPlainObject(value)) {\n // A relation on its own: wrap it as the property it belongs to.\n const wrapped = this.propertyRelationTargetsAsSource(\n collectionId, file, { type: \"relation\", relation: value },\n oldObject ? undefined : undefined, where);\n const relation = AstSchemaEditor.isPlainObject(wrapped) ? wrapped.relation : value;\n if (AstSchemaEditor.isPlainObject(relation) && !(\"target\" in relation) &&\n !(oldObject && this.findProperty(oldObject, \"target\"))) {\n throw new Error(`Relation \"${where}\" on collection \"${collectionId}\" has no target collection. Pick one before saving.`);\n }\n return relation;\n }\n return value;\n }\n\n /** A member of an object literal by name: an assignment, a shorthand, a method. */\n private findMember(obj: ObjectLiteralExpression, name: string): ObjectLiteralElementLike | undefined {\n return obj.getProperties().find(member => {\n if (member.isKind(SyntaxKind.SpreadAssignment)) return false;\n const memberName = (member as PropertyAssignment).getName();\n return memberName === name || memberName === JSON.stringify(name) || memberName === `'${name}'`;\n });\n }\n\n private spreadIn(obj: ObjectLiteralExpression): Node | undefined {\n return obj.getProperties().find(member => member.isKind(SyntaxKind.SpreadAssignment));\n }\n\n /** `x as const`, `x satisfies T`, `(x)` — the value inside. */\n private static unwrapExpression(node: Node | undefined): Node | undefined {\n if (!node) return undefined;\n if (Node.isAsExpression(node) || Node.isSatisfiesExpression(node) || Node.isParenthesizedExpression(node)) {\n return AstSchemaEditor.unwrapExpression(node.getExpression());\n }\n return node;\n }\n\n /**\n * Whether a value in the file is data the panel's JSON fully describes —\n * a literal — rather than code it saw only the result of.\n */\n private static isLiteralValue(node: Node): boolean {\n const value = AstSchemaEditor.unwrapExpression(node);\n if (!value) return false;\n switch (value.getKind()) {\n case SyntaxKind.ObjectLiteralExpression:\n case SyntaxKind.ArrayLiteralExpression:\n case SyntaxKind.StringLiteral:\n case SyntaxKind.NoSubstitutionTemplateLiteral:\n case SyntaxKind.NumericLiteral:\n case SyntaxKind.TrueKeyword:\n case SyntaxKind.FalseKeyword:\n case SyntaxKind.NullKeyword:\n return true;\n case SyntaxKind.PrefixUnaryExpression:\n return value.asKindOrThrow(SyntaxKind.PrefixUnaryExpression).getOperand().isKind(SyntaxKind.NumericLiteral);\n case SyntaxKind.Identifier:\n return value.getText() === \"undefined\";\n default:\n return false;\n }\n }\n\n /** \"`properties.status` is `statusProperty` in posts.ts…\" */\n private codeRefusal(collectionId: string, file: SourceFile, where: string, code: Node, verb: \"is\" | \"holds\" | \"comes from\"): Error {\n const text = code.getText();\n const shown = text.length > 60 ? `${text.slice(0, 57)}…` : text;\n return new Error(\n `\\`${where}\\` ${verb} \\`${shown}\\` in ${path.basename(file.getFilePath())}, which is code the ` +\n `editor cannot change without losing what it does. Change \"${collectionId}\" there instead.`\n );\n }\n\n private async writeCollection(collectionId: string, collectionData: Record<string, unknown>, options: { partial?: boolean }) {\n const partial = options.partial === true;\n let file = this.getCollectionFile(collectionId);\n const collectionObj = file ? this.getCollectionObject(collectionId) : null;\n\n if (file && !collectionObj) {\n // The file is there and we could not read it. Recreating it from the\n // panel's JSON would drop the imports, the callbacks and the relation\n // thunks that JSON cannot carry.\n throw new Error(this.unreadableFileMessage(collectionId, file));\n }\n\n if (!file || !collectionObj) {\n if (partial) {\n throw new Error(`Cannot apply a partial update to \"${collectionId}\": it has no collection file yet.`);\n }\n // Create a new file\n const safeId = this.sanitizeCollectionId(collectionId);\n const newFilePath = this.safePath(`${safeId}.ts`);\n if (fs.existsSync(newFilePath)) {\n throw new Error(`Refusing to overwrite ${newFilePath}: a file for \"${collectionId}\" already exists but could not be parsed.`);\n }\n const varName = `${AstSchemaEditor.collectionVarName(safeId)}Collection`;\n // Created empty and filled in after: a relation's target becomes an\n // import, and an import needs a file to be added to.\n file = this.project.createSourceFile(newFilePath, `import { CollectionConfig } from \"@rebasepro/types\";\\n\\nconst ${varName}: CollectionConfig = {};\\n\\nexport default ${varName};\\n`);\n const { relations, ...rest } = nestAdminKeysDeep(collectionData);\n const data = \"properties\" in rest\n ? { ...rest, properties: this.relationTargetsAsSource(collectionId, file, rest.properties, undefined) }\n : rest;\n file.getVariableDeclarationOrThrow(varName).setInitializer(this.convertJsonToAstString(data));\n if (relations !== undefined) {\n this.writeRelations(collectionId, file, this.requireCollectionObject(collectionId), relations);\n }\n } else {\n // Update root level properties gracefully\n\n if (!partial) {\n // Force delete securityRules if empty or undefined to handle Formex / serialization stripping\n if (!(\"securityRules\" in collectionData) || collectionData.securityRules === undefined || (Array.isArray(collectionData.securityRules) && collectionData.securityRules.length === 0)) {\n const srProp = collectionObj.getProperty(\"securityRules\");\n if (srProp) {\n srProp.remove();\n }\n\n // If it was in collectionData as an empty array, delete it so the loop below doesn't add it back as \"[]\"\n // Actually, if it's \"[]\", omitting it entirely from the TS file achieves the same logical effect (no RLS rules)\n // and correctly triggers \"unmapped policies\" if the DB still has them.\n delete collectionData[\"securityRules\"];\n }\n }\n\n // The panel works with a flat view model — presentation merged onto the\n // collection — so what arrives here has `icon` and `listProperties` at\n // the top level. On disk they belong inside `admin`. Writing them flat\n // would produce a file the backend loads and ignores and the panel\n // never reads back, which looks exactly like the edit not saving. A\n // property's presentation keys are worse than ignored: they are fatal\n // at the next boot, which is why this goes all the way down.\n collectionData = nestAdminKeysDeep(collectionData);\n\n for (const key of Object.keys(collectionData)) {\n if (key === \"relations\") {\n this.writeRelations(collectionId, file, collectionObj, collectionData[key]);\n continue;\n }\n\n const prop = collectionObj.getProperty(key) as PropertyAssignment;\n\n let oldAstNode: ObjectLiteralExpression | undefined;\n if (prop && prop.isKind(SyntaxKind.PropertyAssignment)) {\n oldAstNode = prop.getInitializerIfKind(SyntaxKind.ObjectLiteralExpression);\n }\n\n if (key === \"properties\") {\n collectionData[key] = this.relationTargetsAsSource(collectionId, file, collectionData[key], oldAstNode);\n }\n\n if (partial && oldAstNode && AstSchemaEditor.isPlainObject(collectionData[key])) {\n this.mergeIntoObjectLiteral(oldAstNode, collectionData[key] as Record<string, unknown>, 2);\n continue;\n }\n\n const newInit = this.convertJsonToAstString(collectionData[key], 1, oldAstNode);\n if (prop) {\n prop.setInitializer(newInit);\n } else {\n collectionObj.addPropertyAssignment({\n // `quoteKey`, like every other site that emits a key.\n // This one did not, and ts-morph writes a property name\n // out verbatim — so a top-level key of\n // `injected: (() => { … })(), tail` closed the property\n // and opened an expression, which `rebase dev` then\n // re-imported and ran. The rest of this file has always\n // quoted; this was the one that did not.\n name: AstSchemaEditor.quoteKey(key),\n initializer: newInit\n });\n }\n }\n }\n if (file) {\n file.formatText();\n }\n await this.project.save();\n }\n\n /**\n * Every property relation's `target`, as source.\n *\n * The collection-level `relations` array has {@link writeRelations}. A\n * relation declared on a property went through `convertJsonToAstString`\n * like any other value, so a target the property form had picked — a slug,\n * which is all JSON can carry — was written as the string literal\n * `target: \"authors\"`, and the next boot threw in `resolveRelation` for every\n * collection in the directory. The same rule applies here as there: a slug\n * becomes `() => xCollection` plus its import, an arrow returning one\n * identifier is written through, and a relation sent without a target keeps\n * the one already in the file — or is refused, when there is none.\n *\n * Walks into `map` properties, an array's `of` and a block's `oneOf`, which\n * the registry resolves relations in too. `oldProperties` is the same\n * properties object in the file, when there is one.\n */\n private relationTargetsAsSource(\n collectionId: string,\n file: SourceFile,\n properties: unknown,\n oldProperties: ObjectLiteralExpression | undefined,\n path = \"properties\"\n ): Record<string, unknown> {\n if (!AstSchemaEditor.isPlainObject(properties)) return {};\n return Object.fromEntries(Object.entries(properties).map(([key, property]) => [\n key,\n this.propertyRelationTargetsAsSource(\n collectionId,\n file,\n property,\n oldProperties ? this.objectAt(oldProperties, key) : undefined,\n `${path}.${key}`\n )\n ]));\n }\n\n private propertyRelationTargetsAsSource(\n collectionId: string,\n file: SourceFile,\n property: unknown,\n oldProperty: ObjectLiteralExpression | undefined,\n path: string\n ): unknown {\n if (!AstSchemaEditor.isPlainObject(property)) return property;\n const next: Record<string, unknown> = { ...property };\n\n if (property.type === \"relation\" && AstSchemaEditor.isPlainObject(property.relation)) {\n const relation: Record<string, unknown> = { ...property.relation };\n const rawTarget = relation.target;\n if (typeof rawTarget === \"string\" && rawTarget.trim().length > 0) {\n const trimmed = rawTarget.trim();\n // See `writeRelations` for why the arrow is matched this narrowly.\n relation.target = new RawExpression(ARROW_TO_IDENTIFIER.test(trimmed)\n ? trimmed\n : this.targetThunk(file, trimmed));\n } else {\n const oldRelation = oldProperty ? this.objectAt(oldProperty, \"relation\") : undefined;\n if (!oldRelation || !this.findProperty(oldRelation, \"target\")) {\n throw new Error(`Relation property \"${path}\" on collection \"${collectionId}\" has no target collection. Pick one before saving.`);\n }\n // Absent, so `convertJsonToAstString` keeps the thunk in the file.\n delete relation.target;\n }\n next.relation = relation;\n }\n\n if (AstSchemaEditor.isPlainObject(property.properties)) {\n next.properties = this.relationTargetsAsSource(\n collectionId, file, property.properties,\n oldProperty ? this.objectAt(oldProperty, \"properties\") : undefined, path);\n }\n if (Array.isArray(property.of)) {\n const oldOf = oldProperty ? this.initializerOf(oldProperty, \"of\")?.asKind(SyntaxKind.ArrayLiteralExpression) : undefined;\n next.of = property.of.map((item, index) => this.propertyRelationTargetsAsSource(\n collectionId, file, item,\n oldOf?.getElements()[index]?.asKind(SyntaxKind.ObjectLiteralExpression), `${path}.of[${index}]`));\n } else if (AstSchemaEditor.isPlainObject(property.of)) {\n next.of = this.propertyRelationTargetsAsSource(\n collectionId, file, property.of,\n oldProperty ? this.objectAt(oldProperty, \"of\") : undefined, `${path}.of`);\n }\n if (AstSchemaEditor.isPlainObject(property.oneOf) && AstSchemaEditor.isPlainObject(property.oneOf.properties)) {\n const oldOneOf = oldProperty ? this.objectAt(oldProperty, \"oneOf\") : undefined;\n next.oneOf = {\n ...property.oneOf,\n properties: this.relationTargetsAsSource(\n collectionId, file, property.oneOf.properties,\n oldOneOf ? this.objectAt(oldOneOf, \"properties\") : undefined, `${path}.oneOf`)\n };\n }\n return next;\n }\n\n /** The initializer of `obj[key]` in the file, when it is written as a property assignment. */\n private initializerOf(obj: ObjectLiteralExpression, key: string): Node | undefined {\n const prop = this.findProperty(obj, key);\n return prop && prop.isKind(SyntaxKind.PropertyAssignment) ? prop.getInitializer() : undefined;\n }\n\n /** `obj[key]` in the file, when it is an object literal. */\n private objectAt(obj: ObjectLiteralExpression, key: string): ObjectLiteralExpression | undefined {\n return this.initializerOf(obj, key)?.asKind(SyntaxKind.ObjectLiteralExpression);\n }\n\n /**\n * Write the collection-level `relations` array.\n *\n * Relations are the one key whose values are not all data: `target` is a\n * thunk in the file, and the panel sends the target collection's slug (or,\n * for a relation it did not touch, nothing at all — `JSON.stringify` drops\n * the function). So each entry's target is resolved in that order: a slug\n * becomes `() => xCollection` plus the import it needs, and anything else\n * falls back to the thunk already in the file.\n *\n * This used to be a `continue` with a comment saying relations were handled\n * elsewhere. Nothing handled them; every edit in the Relations tab was\n * dropped without a word.\n */\n private writeRelations(collectionId: string, file: SourceFile, collectionObj: ObjectLiteralExpression, value: unknown): void {\n if (!Array.isArray(value)) return;\n\n const existing = this.findProperty(collectionObj, \"relations\");\n const oldArray = existing && existing.isKind(SyntaxKind.PropertyAssignment)\n ? existing.getInitializerIfKind(SyntaxKind.ArrayLiteralExpression)\n : undefined;\n\n const oldElements = oldArray?.getElements() ?? [];\n const oldTargetsByName = new Map<string, string>();\n // Only for entries the file left unnamed — `relationName` is optional, and\n // those cannot be matched any other way. Positional matching is off the\n // moment the array's length changes, because then a position no longer\n // means the same relation.\n const anonymousTargetsByIndex: (string | undefined)[] = [];\n oldElements.forEach((element, index) => {\n const elementObj = element.asKind(SyntaxKind.ObjectLiteralExpression);\n if (!elementObj) return;\n const targetProp = this.findProperty(elementObj, \"target\");\n const targetInit = targetProp && targetProp.isKind(SyntaxKind.PropertyAssignment)\n ? targetProp.getInitializer()\n : undefined;\n if (!targetInit) return;\n\n const nameProp = this.findProperty(elementObj, \"relationName\");\n const nameInit = nameProp && nameProp.isKind(SyntaxKind.PropertyAssignment)\n ? nameProp.getInitializerIfKind(SyntaxKind.StringLiteral)\n : undefined;\n if (nameInit) oldTargetsByName.set(nameInit.getLiteralValue(), targetInit.getText());\n else anonymousTargetsByIndex[index] = targetInit.getText();\n });\n\n const items = value.map((entry, index) => {\n if (!AstSchemaEditor.isPlainObject(entry)) return entry;\n const relation: Record<string, unknown> = { ...entry };\n const relationName = typeof relation.relationName === \"string\" ? relation.relationName : undefined;\n const rawTarget = relation.target;\n\n let targetText: string | undefined;\n if (typeof rawTarget === \"string\" && rawTarget.trim().length > 0) {\n const trimmed = rawTarget.trim();\n // A target that already looks like a thunk is written into the\n // file as source, so \"contains an arrow\" is not a good enough\n // reason to trust it: `() => { require(\"child_process\")… }` also\n // contains one. Only the exact shape this emits is accepted —\n // an arrow returning one identifier — and anything else is\n // treated as a collection NAME and turned into a thunk here,\n // which is the path that was always safe.\n targetText = ARROW_TO_IDENTIFIER.test(trimmed)\n ? trimmed\n : this.targetThunk(file, trimmed);\n } else if (relationName && oldTargetsByName.has(relationName)) {\n targetText = oldTargetsByName.get(relationName);\n } else if (value.length === oldElements.length) {\n targetText = anonymousTargetsByIndex[index];\n }\n\n if (!targetText) {\n throw new Error(`Relation \"${relationName ?? `#${index}`}\" on collection \"${collectionId}\" has no target collection. Pick one before saving.`);\n }\n\n relation.target = new RawExpression(targetText);\n return relation;\n });\n\n const initializer = this.convertJsonToAstString(items, 1);\n if (existing && existing.isKind(SyntaxKind.PropertyAssignment)) {\n existing.setInitializer(initializer);\n } else {\n collectionObj.addPropertyAssignment({ name: \"relations\", initializer });\n }\n }\n\n /**\n * `() => targetCollection` for a target named by its slug, importing it if\n * the file does not already.\n */\n private targetThunk(file: SourceFile, targetSlug: string): string {\n const targetFile = this.getCollectionFile(targetSlug);\n if (!targetFile) {\n throw new Error(`Cannot link to collection \"${targetSlug}\": no file for it in ${this.collectionsDir}.`);\n }\n\n const identifier = this.getDefaultExportName(targetFile);\n if (!identifier) {\n throw new Error(`Cannot link to collection \"${targetSlug}\": ${targetFile.getFilePath()} has no default export to import.`);\n }\n\n if (targetFile.getFilePath() !== file.getFilePath() && !this.hasDefaultImport(file, identifier)) {\n const relative = path.relative(path.dirname(file.getFilePath()), targetFile.getFilePath())\n .split(path.sep)\n .join(\"/\")\n .replace(/\\.tsx?$/, \".js\");\n file.addImportDeclaration({\n defaultImport: identifier,\n moduleSpecifier: relative.startsWith(\".\") ? relative : `./${relative}`\n });\n }\n\n return `() => ${identifier}`;\n }\n\n private hasDefaultImport(file: SourceFile, identifier: string): boolean {\n return file.getImportDeclarations().some(decl => decl.getDefaultImport()?.getText() === identifier);\n }\n\n private getDefaultExportName(file: SourceFile): string | undefined {\n const declaration = file.getDefaultExportSymbol()?.getDeclarations()[0];\n const expr = declaration?.asKind(SyntaxKind.ExportAssignment)?.getExpression();\n if (expr && expr.getKind() === SyntaxKind.Identifier) return expr.getText();\n\n for (const varDecl of file.getVariableDeclarations()) {\n if (this.unwrapCollectionObject(varDecl.getInitializer())) return varDecl.getName();\n }\n return undefined;\n }\n\n /**\n * Delete a collection's file, and take it out of `index.ts`.\n *\n * This used to unlink `<slug>.ts` and stop. Every scaffold's `index.ts`\n * imports every collection, so the next boot failed for the whole project\n * (\"Cannot find module …/tags\"), and a collection another one linked to\n * took that one down with it. Now:\n *\n * - a file any other module imports is refused, naming the importers — a\n * relation to it has to go first, and that is a change the person makes;\n * - `index.ts`'s import of it is removed, and so is each place it is listed\n * in an array; any other use there is refused, naming the line;\n * - the file is deleted.\n *\n * Only the source. The table and its rows are not touched.\n *\n * Returns what changed, as absolute paths, so the caller can commit it.\n */\n public async deleteCollection(collectionId: string): Promise<{ deleted: string; changed: string[] }> {\n this.syncWithDisk();\n const target = this.getCollectionFile(collectionId);\n if (!target) {\n throw new Error(`Collection \"${collectionId}\" has no file at ${path.join(this.collectionsDir, `${collectionId}.ts`)}.`);\n }\n const targetPath = target.getFilePath();\n const indexPath = path.join(this.collectionsDir, \"index.ts\");\n\n const importers = this.project.getSourceFiles().filter(file =>\n file !== target && this.declarationsOf(file, targetPath).length > 0);\n const others = importers.filter(file => file.getFilePath() !== indexPath);\n if (others.length > 0) {\n throw new Error(\n `Cannot delete \"${collectionId}\": ${others.map(file => path.basename(file.getFilePath())).join(\", \")} ` +\n `${others.length === 1 ? \"imports\" : \"import\"} it — a relation to it, most likely. Remove that first; ` +\n \"deleting the file would stop every collection from loading.\"\n );\n }\n\n const changed: string[] = [];\n const index = importers.find(file => file.getFilePath() === indexPath);\n try {\n if (index) {\n for (const declaration of this.declarationsOf(index, targetPath)) {\n const names = declaration.isKind(SyntaxKind.ImportDeclaration)\n ? [\n declaration.getDefaultImport()?.getText(),\n ...declaration.getNamedImports().map(named => (named.getAliasNode() ?? named.getNameNode()).getText())\n ].filter((name): name is string => Boolean(name))\n : [];\n for (const name of names) {\n const uses = index.getDescendantsOfKind(SyntaxKind.Identifier).filter(identifier =>\n identifier.getText() === name &&\n !identifier.getFirstAncestorByKind(SyntaxKind.ImportDeclaration));\n for (const use of uses) {\n const array = use.getParentIfKind(SyntaxKind.ArrayLiteralExpression);\n if (!array) {\n throw new Error(\n `Cannot delete \"${collectionId}\": index.ts uses \\`${name}\\` at line ` +\n `${use.getStartLineNumber()} in a way the editor cannot remove. Remove it there first.`\n );\n }\n array.removeElement(use);\n }\n }\n declaration.remove();\n }\n changed.push(index.getFilePath());\n }\n target.delete();\n await this.project.save();\n } catch (err) {\n if (index && !index.isSaved()) index.refreshFromFileSystemSync();\n this.syncWithDisk();\n throw err;\n }\n return { deleted: targetPath, changed };\n }\n\n /** The import and export declarations in `file` whose relative specifier resolves to `targetPath`. */\n private declarationsOf(file: SourceFile, targetPath: string) {\n const stem = (absolute: string) => absolute.replace(/\\.(ts|tsx|js|mjs|cjs)$/, \"\");\n const target = stem(targetPath);\n return [...file.getImportDeclarations(), ...file.getExportDeclarations()].filter(declaration => {\n const specifier = declaration.getModuleSpecifierValue();\n if (!specifier || !specifier.startsWith(\".\")) return false;\n return stem(path.resolve(path.dirname(file.getFilePath()), specifier)) === target;\n });\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ast-schema-editor-madTa9Hh.js","names":[],"sources":["../src/api/ast-schema-editor.ts"],"sourcesContent":["import { Project, SyntaxKind, Node, ObjectLiteralExpression, ObjectLiteralElementLike, PropertyAssignment, SourceFile, IndentationText } from \"ts-morph\";\nimport {\n collectionPatchProblems,\n nestAdminCollectionKeys,\n nestAdminPropertyKeys,\n nestCollectionPatchPaths,\n runtimeOnlyKeyIn,\n type CollectionPatch,\n type CollectionPatchOp\n} from \"@rebasepro/types\";\nimport * as path from \"path\";\nimport * as fs from \"fs\";\n\n/**\n * The helpers a collection file may be wrapped in.\n *\n * `rebase init` scaffolds every collection as\n * `const postsCollection = defineCollection({ … })` — a call expression, not the\n * bare object literal `rebase introspect` emits. An editor that only understood\n * the bare form found nothing to patch in any stock project, and then rewrote the\n * file from the panel's JSON: no wrapper, no imports, no relation thunks.\n */\nconst COLLECTION_FACTORIES = new Set([\"defineCollection\"]);\n\n/**\n * The only relation-target expression this editor will write through verbatim.\n *\n * A relation's `target` is emitted as SOURCE, not as a string literal, because\n * it has to be `() => otherCollection`. The test for \"is this already a thunk?\"\n * used to be \"does it contain an arrow\", which `() => { require(\"child_process\")\n * .execSync(\"…\") }` also satisfies — and `rebase dev` re-imports the file the\n * moment it changes, so the payload ran without anyone deploying anything.\n *\n * An arrow returning a single identifier is the whole grammar. Anything else is\n * read as a collection name and turned into a thunk by `targetThunk`, which\n * resolves it against the collections directory and refuses a name with no file.\n */\nconst ARROW_TO_IDENTIFIER = /^\\(\\s*\\)\\s*=>\\s*[A-Za-z_$][A-Za-z0-9_$]*$/;\n\n/**\n * A value that must be emitted as source code rather than as JSON.\n *\n * Everything reaching the writer has been through `JSON.stringify` on the wire,\n * so a function-valued key arrives either missing or as a string. A relation's\n * `target` is a thunk in the file and a slug in the payload; this is how the\n * thunk gets written back.\n */\nclass RawExpression {\n constructor(public readonly text: string) {\n }\n}\n\n/**\n * Move presentation keys into the `admin` block.\n *\n * The rule itself lives in `@rebasepro/types`, next to `ADMIN_COLLECTION_KEYS`,\n * because `@rebasepro/cms-types` has to apply the identical one on the panel's\n * side and this package cannot import that one. Two copies used to exist and\n * they disagreed about precedence, which decided whether a presentation edit was\n * saved or silently reverted to the value the user had just changed away from.\n */\nexport function nestAdminKeys(collectionData: Record<string, unknown>): Record<string, unknown> {\n return nestAdminCollectionKeys(collectionData);\n}\n\n/**\n * {@link nestAdminKeys} for a whole collection — its properties included.\n *\n * `nestAdminKeys` only ever moved the COLLECTION's presentation keys. A\n * property's — `markdown`, `multiline`, `previewAsTag` — went to disk exactly as\n * the panel sent them, at the top level of the property, which the boot\n * validator treats as fatal. `saveProperty` nests them; `saveCollection` did\n * not, and *creating* a collection is a `saveCollection`.\n *\n * So a collection created from a template wrote a file that could not boot, and\n * took the whole project down with it — the loader imports every collection in\n * the directory, so one bad file stops `rebase dev` from starting at all. The\n * panel reported success.\n */\nexport function nestAdminKeysDeep(collectionData: Record<string, unknown>): Record<string, unknown> {\n const nested = nestAdminKeys(collectionData);\n const properties = nested.properties;\n if (properties && typeof properties === \"object\" && !Array.isArray(properties)) {\n nested.properties = Object.fromEntries(\n Object.entries(properties as Record<string, unknown>).map(([key, property]) => [\n key,\n property && typeof property === \"object\" && !Array.isArray(property)\n ? nestAdminPropertyKeys(property as Record<string, unknown>)\n : property\n ])\n );\n }\n return nested;\n}\n\nexport class AstSchemaEditor {\n private project: Project;\n private collectionsDir: string;\n\n constructor(collectionsDir: string) {\n this.project = new Project({\n manipulationSettings: {\n indentationText: IndentationText.FourSpaces\n }\n });\n if (fs.existsSync(collectionsDir)) {\n this.project.addSourceFilesAtPaths(`${collectionsDir}/**/*.ts`);\n }\n this.collectionsDir = path.resolve(collectionsDir);\n }\n\n /**\n * Sanitize collectionId to prevent path traversal attacks.\n * Only allows alphanumeric characters, underscores, and hyphens.\n */\n /**\n * The variable name the generated file binds the collection to.\n *\n * `sanitizeCollectionId` guards the FILENAME, and permits hyphens and a\n * leading digit — both legal in a filename and neither legal in a\n * JavaScript identifier. So a collection created from the admin panel as\n * `my-notes` (the documented slug shape) or `2024 Archive` (auto-slugged to\n * `2024_archive`) wrote:\n *\n * const my-notesCollection: CollectionConfig = … \"',' expected\"\n * const 2024_archiveCollection: CollectionConfig = … \"Numeric separators\n * are not allowed here\"\n *\n * The panel reported success, and the next boot failed for EVERY collection\n * in the directory — the loader imports all of them — while the editor could\n * no longer parse the file it had just written, so it could not fix itself.\n *\n * Separators camel-case rather than vanish, so `my-notes` and `my_notes`\n * stay distinct; a leading digit is prefixed rather than stripped, so\n * `2024_archive` stays distinct from `archive`. Byte-identical for every\n * slug that already produced a valid identifier.\n */\n private static collectionVarName(safeId: string): string {\n const camel = safeId.replace(/[-_]+([a-zA-Z0-9])/g, (_, char: string) => char.toUpperCase());\n const identifier = /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(camel) ? camel : `c${camel.replace(/^[0-9]/, (d) => d)}`;\n return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(identifier) ? identifier : `c${identifier.replace(/[^A-Za-z0-9_$]/g, \"\")}`;\n }\n\n private sanitizeCollectionId(collectionId: string): string {\n const sanitized = collectionId.replace(/[^a-zA-Z0-9_-]/g, \"\");\n if (!sanitized || sanitized !== collectionId) {\n throw new Error(`Invalid collection ID: \"${collectionId}\". Only alphanumeric characters, underscores, and hyphens are allowed.`);\n }\n return sanitized;\n }\n\n /**\n * Resolve a file path and ensure it falls within the collectionsDir.\n */\n private safePath(filename: string): string {\n const resolved = path.resolve(this.collectionsDir, filename);\n if (!resolved.startsWith(this.collectionsDir + path.sep) && resolved !== this.collectionsDir) {\n throw new Error(\"Path traversal detected: resolved path is outside the collections directory.\");\n }\n return resolved;\n }\n\n /**\n * Bring the in-memory project back in line with the disk.\n *\n * The source-only editor builds ONE of these at boot and serves every\n * request with it, and ts-morph reads a file once and then trusts its\n * copy. `project.save()` writes that copy back whole, so anything that\n * reached the disk in between — the live editor's committed change (it\n * builds a fresh editor per request), or the developer's own edit in the\n * IDE — was overwritten by the boot-time copy plus this edit. In `rebase\n * dev` the tsx-watch restart usually closed the window; outside watch mode\n * the loss was permanent.\n *\n * Called at the top of every public method, so each edit applies to what\n * is on disk now.\n */\n private syncWithDisk(): void {\n for (const file of this.project.getSourceFiles()) {\n if (!fs.existsSync(file.getFilePath())) {\n this.project.removeSourceFile(file);\n } else {\n file.refreshFromFileSystemSync();\n }\n }\n if (fs.existsSync(this.collectionsDir)) {\n this.project.addSourceFilesAtPaths(`${this.collectionsDir}/**/*.ts`);\n }\n }\n\n private getCollectionFile(collectionId: string) {\n const safeId = this.sanitizeCollectionId(collectionId);\n const filePath = this.safePath(`${safeId}.ts`);\n let file = this.project.getSourceFile(filePath);\n if (!file && fs.existsSync(filePath)) {\n this.project.addSourceFilesAtPaths(`${this.collectionsDir}/**/*.ts`);\n file = this.project.getSourceFile(filePath);\n }\n return file;\n }\n\n /**\n * Find the object literal a collection is declared with, through whatever\n * wraps it.\n *\n * `defineCollection({ … })` is the shape `rebase init` writes and the one the\n * docs recommend; `satisfies` / `as` / parentheses are the other ways an\n * author can dress the same literal. Returning `null` for any of them meant\n * the three callers below each failed differently, and the worst of them\n * overwrote the file.\n */\n private unwrapCollectionObject(node: Node | undefined): ObjectLiteralExpression | null {\n if (!node) return null;\n if (Node.isObjectLiteralExpression(node)) return node;\n if (Node.isParenthesizedExpression(node) ||\n Node.isAsExpression(node) ||\n Node.isSatisfiesExpression(node) ||\n Node.isTypeAssertion(node) ||\n Node.isNonNullExpression(node)) {\n return this.unwrapCollectionObject(node.getExpression());\n }\n if (Node.isCallExpression(node)) {\n // `defineCollection`, `admin.defineCollection`, `defineCollection<Post>`\n const callee = node.getExpression().getText().split(\".\").pop();\n if (callee && COLLECTION_FACTORIES.has(callee)) {\n return this.unwrapCollectionObject(node.getArguments()[0]);\n }\n }\n return null;\n }\n\n private getCollectionObject(collectionId: string): ObjectLiteralExpression | null {\n const file = this.getCollectionFile(collectionId);\n if (!file) return null;\n\n const defaultExport = file.getDefaultExportSymbol();\n if (defaultExport) {\n const declaration = defaultExport.getDeclarations()[0];\n if (declaration && declaration.getKind() === SyntaxKind.ExportAssignment) {\n const expr = declaration.asKind(SyntaxKind.ExportAssignment)?.getExpression();\n if (expr && expr.getKind() === SyntaxKind.Identifier) {\n const varName = expr.getText();\n const varDecl = file.getVariableDeclaration(varName);\n const unwrapped = this.unwrapCollectionObject(varDecl?.getInitializer());\n if (unwrapped) return unwrapped;\n } else {\n // `export default defineCollection({ … })`\n const unwrapped = this.unwrapCollectionObject(expr);\n if (unwrapped) return unwrapped;\n }\n }\n }\n // Fallback: the first VariableDeclaration that holds a collection literal\n for (const varDecl of file.getVariableDeclarations()) {\n const init = this.unwrapCollectionObject(varDecl.getInitializer());\n if (init) return init;\n }\n return null;\n }\n\n /**\n * The collection's object literal, or a refusal that says what to do.\n *\n * Every caller needs this to be all-or-nothing: a missing object literal used\n * to mean \"throw\", \"report success and do nothing\" and \"recreate the file\n * from scratch\" depending on which method you called.\n */\n private requireCollectionObject(collectionId: string): ObjectLiteralExpression {\n const file = this.getCollectionFile(collectionId);\n if (!file) {\n throw new Error(`Collection \"${collectionId}\" has no file at ${path.join(this.collectionsDir, `${collectionId}.ts`)}.`);\n }\n const collectionObj = this.getCollectionObject(collectionId);\n if (!collectionObj) {\n throw new Error(this.unreadableFileMessage(collectionId, file));\n }\n return collectionObj;\n }\n\n private unreadableFileMessage(collectionId: string, file: SourceFile): string {\n return `Could not find the collection object in ${file.getFilePath()}. ` +\n \"The schema editor can only edit a collection declared as `const x = defineCollection({ … })` \" +\n \"or `const x: CollectionConfig = { … }` and exported as the file's default. \" +\n `Edit \"${collectionId}\" in code instead.`;\n }\n\n /** Look a key up on an object literal, quoted or not. */\n private findProperty(obj: ObjectLiteralExpression, name: string): ObjectLiteralElementLike | undefined {\n return obj.getProperty((p: ObjectLiteralElementLike) =>\n \"getName\" in p &&\n typeof (p as PropertyAssignment).getName === \"function\" &&\n ((p as PropertyAssignment).getName() === name ||\n (p as PropertyAssignment).getName() === `\"${name}\"` ||\n (p as PropertyAssignment).getName() === `'${name}'`));\n }\n\n private static quoteKey(key: string): string {\n return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key) ? key : JSON.stringify(key);\n }\n\n private static isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value) && !(value instanceof RawExpression);\n }\n\n /**\n * The field that identifies each element of an array of objects, so an\n * old element can be found again in the new array.\n *\n * Arrays travel whole (see `collection_patch.ts`), and an element's\n * position is not its identity — adding an entity action at the front\n * shifts every other one. `key`, `id`, `relationName`, `name`: the first\n * one every new element carries, distinct, wins.\n */\n private static identityField(items: unknown[]): string | undefined {\n for (const field of [\"key\", \"id\", \"relationName\", \"name\", \"slug\"]) {\n const values = items.map(item => AstSchemaEditor.isPlainObject(item) ? item[field] : undefined);\n if (values.every(value => typeof value === \"string\" || typeof value === \"number\") &&\n new Set(values).size === values.length) {\n return field;\n }\n }\n return undefined;\n }\n\n /** The old array's object elements, keyed by `field`'s literal value. */\n private oldElementsBy(oldArray: Node, field: string): Map<string, ObjectLiteralExpression> {\n const byKey = new Map<string, ObjectLiteralExpression>();\n const array = oldArray.asKind(SyntaxKind.ArrayLiteralExpression);\n for (const element of array?.getElements() ?? []) {\n const object = element.asKind(SyntaxKind.ObjectLiteralExpression);\n const value = object ? this.initializerOf(object, field) : undefined;\n const literal = value?.asKind(SyntaxKind.StringLiteral)?.getLiteralValue()\n ?? value?.asKind(SyntaxKind.NoSubstitutionTemplateLiteral)?.getLiteralValue()\n ?? value?.asKind(SyntaxKind.NumericLiteral)?.getText();\n if (object && literal !== undefined) byKey.set(String(literal), object);\n }\n return byKey;\n }\n\n private convertJsonToAstString(obj: unknown, indentLevel = 0, oldAstNode?: Node): string {\n // Base TS-morph parses arrays as 2 levels deep from the property key:\n // PropertiesObject = level 1, PropertyConfig = level 2.\n // We calibrate the spacing multiples to keep the items flush with standard TS format.\n const indentStr = \" \";\n const indent = indentStr.repeat(indentLevel);\n const innerIndent = indentStr.repeat(indentLevel + 1);\n\n if (obj instanceof RawExpression) {\n return obj.text;\n }\n if (obj === null || obj === undefined) {\n return \"undefined\";\n }\n if (typeof obj === \"string\") {\n return JSON.stringify(obj);\n }\n if (typeof obj === \"number\" || typeof obj === \"boolean\") {\n return String(obj);\n }\n if (Array.isArray(obj)) {\n if (obj.length === 0) return \"[]\";\n // Each element written over its old self, so what JSON dropped\n // from it — an action's `onClick`, a field's `value` — is kept.\n const field = oldAstNode ? AstSchemaEditor.identityField(obj) : undefined;\n const old = oldAstNode && field ? this.oldElementsBy(oldAstNode, field) : undefined;\n const items = obj.map(item => this.convertJsonToAstString(\n item,\n indentLevel + 1,\n old && field && AstSchemaEditor.isPlainObject(item) ? old.get(String(item[field])) : undefined\n ));\n return `[\\n${innerIndent}${items.join(`,\\n${innerIndent}`)}\\n${indent}]`;\n }\n if (typeof obj === \"object\") {\n const record = obj as Record<string, unknown>;\n const keys = Object.keys(record);\n const oldObject = oldAstNode?.asKind(SyntaxKind.ObjectLiteralExpression);\n\n // Collect preserved AST properties\n const preservedProps: string[] = [];\n if (oldObject) {\n const oldProps = oldObject.getProperties();\n for (const oldProp of oldProps) {\n if (oldProp.isKind(SyntaxKind.PropertyAssignment)) {\n const nameNode = oldProp.getNameNode();\n let name = nameNode.getText();\n if (name.startsWith('\"') && name.endsWith('\"')) name = name.slice(1, -1);\n if (name.startsWith(\"'\") && name.endsWith(\"'\")) name = name.slice(1, -1);\n\n // If the JSON object doesn't have this key, check if we should preserve it\n if (!(name in record)) {\n const init = oldProp.getInitializer();\n if (init) {\n const kind = init.getKind();\n const isCode = kind === SyntaxKind.ArrowFunction ||\n kind === SyntaxKind.FunctionExpression ||\n kind === SyntaxKind.Identifier ||\n kind === SyntaxKind.CallExpression ||\n kind === SyntaxKind.JsxElement;\n\n if (isCode || name === \"target\" || name === \"callbacks\" || name === \"browserCallbacks\" || name === \"permissions\" || name === \"securityRules\") {\n // Preserve this property exactly as it was\n preservedProps.push(`${AstSchemaEditor.quoteKey(name)}: ${init.getText()}`);\n }\n }\n }\n }\n }\n }\n\n if (keys.length === 0 && preservedProps.length === 0) return \"{}\";\n\n const props = keys.map(key => {\n const keyStr = AstSchemaEditor.quoteKey(key);\n\n // If the value is an object or an array, pass the old AST node\n // to recurse\n let childAstNode: Node | undefined;\n if (oldObject && (AstSchemaEditor.isPlainObject(record[key]) || Array.isArray(record[key]))) {\n const oldProp = this.findProperty(oldObject, key);\n if (oldProp && oldProp.isKind(SyntaxKind.PropertyAssignment)) {\n childAstNode = oldProp.getInitializerIfKind(SyntaxKind.ObjectLiteralExpression)\n ?? oldProp.getInitializerIfKind(SyntaxKind.ArrayLiteralExpression);\n }\n }\n\n return `${keyStr}: ${this.convertJsonToAstString(record[key], indentLevel + 1, childAstNode)}`;\n });\n\n const allProps = [...props, ...preservedProps];\n return `{\\n${innerIndent}${allProps.join(`,\\n${innerIndent}`)}\\n${indent}}`;\n }\n return \"undefined\";\n }\n\n /**\n * Write only the keys the patch names, leaving every sibling alone.\n *\n * A patch says what changed, not what the collection is. The panel sends one\n * — `{ propertiesOrder }` is what adding a column posts — and rewriting the\n * `admin` block from it deleted the collection's icon, group, list columns\n * and kanban config in the same write.\n */\n private mergeIntoObjectLiteral(target: ObjectLiteralExpression, data: Record<string, unknown>, indentLevel: number): void {\n for (const [key, value] of Object.entries(data)) {\n const existing = this.findProperty(target, key);\n const existingObj = existing && existing.isKind(SyntaxKind.PropertyAssignment)\n ? existing.getInitializerIfKind(SyntaxKind.ObjectLiteralExpression)\n : undefined;\n\n if (existingObj && AstSchemaEditor.isPlainObject(value)) {\n this.mergeIntoObjectLiteral(existingObj, value, indentLevel + 1);\n continue;\n }\n\n const initializer = this.convertJsonToAstString(value, indentLevel, existingObj);\n if (existing && existing.isKind(SyntaxKind.PropertyAssignment)) {\n existing.setInitializer(initializer);\n } else {\n target.addPropertyAssignment({\n name: AstSchemaEditor.quoteKey(key),\n initializer\n });\n }\n }\n }\n\n /**\n * Drop whatever a refused edit left in memory.\n *\n * One editor serves every request, and `project.save()` writes every file\n * with unsaved changes. So an edit refused halfway through — a relation with\n * no target, found after the name was already rewritten and an import\n * added — reached the disk with the next save of some other collection, and\n * a file refused as it was being created appeared anyway.\n */\n private forgetUnsaved(collectionId: string): void {\n const file = this.project.getSourceFile(path.resolve(this.collectionsDir, `${collectionId}.ts`));\n if (!file || file.isSaved()) return;\n if (fs.existsSync(file.getFilePath())) {\n file.refreshFromFileSystemSync();\n } else {\n this.project.removeSourceFile(file);\n }\n }\n\n public async saveProperty(collectionId: string, propertyKey: string, propertyConfig: Record<string, unknown>) {\n this.syncWithDisk();\n try {\n await this.writeProperty(collectionId, propertyKey, propertyConfig);\n } catch (err) {\n this.forgetUnsaved(collectionId);\n throw err;\n }\n }\n\n private async writeProperty(collectionId: string, propertyKey: string, propertyConfig: Record<string, unknown>) {\n const collectionObj = this.requireCollectionObject(collectionId);\n\n // The panel's property forms bind to the flat names — `readOnly`,\n // `hideFromCollection` — while on disk they belong inside the property's\n // own `admin` block. Written flat they are not merely ignored: the boot\n // validator treats a moved key as fatal.\n const nestedConfig = nestAdminPropertyKeys(propertyConfig);\n\n let propertiesProp = collectionObj.getProperty(\"properties\") as PropertyAssignment;\n if (!propertiesProp) {\n propertiesProp = collectionObj.addPropertyAssignment({\n name: \"properties\",\n initializer: \"{}\"\n });\n }\n\n const propsObj = propertiesProp.getInitializerIfKind(SyntaxKind.ObjectLiteralExpression);\n if (propsObj) {\n const existingProp = this.findProperty(propsObj, propertyKey);\n\n let oldPropAstNode: ObjectLiteralExpression | undefined;\n if (existingProp && existingProp.isKind(SyntaxKind.PropertyAssignment)) {\n oldPropAstNode = existingProp.getInitializerIfKind(SyntaxKind.ObjectLiteralExpression);\n }\n\n const withTargets = this.relationTargetsAsSource(\n collectionId,\n this.getCollectionFile(collectionId)!,\n { [propertyKey]: nestedConfig },\n propsObj\n )[propertyKey];\n const newInitializer = this.convertJsonToAstString(withTargets, 2, oldPropAstNode);\n\n if (existingProp) {\n if (existingProp.isKind(SyntaxKind.PropertyAssignment)) {\n existingProp.setInitializer(newInitializer);\n }\n } else {\n propsObj.addPropertyAssignment({\n name: AstSchemaEditor.quoteKey(propertyKey),\n initializer: newInitializer\n });\n }\n\n const file = this.getCollectionFile(collectionId);\n if (file) {\n file.formatText();\n }\n await this.project.save();\n }\n }\n\n public async deleteProperty(collectionId: string, propertyKey: string) {\n this.syncWithDisk();\n const collectionObj = this.requireCollectionObject(collectionId);\n\n const propertiesProp = collectionObj.getProperty(\"properties\") as PropertyAssignment;\n if (propertiesProp) {\n const propsObj = propertiesProp.getInitializerIfKind(SyntaxKind.ObjectLiteralExpression);\n if (propsObj) {\n const existingProp = this.findProperty(propsObj, propertyKey);\n if (existingProp) {\n existingProp.remove();\n const file = this.getCollectionFile(collectionId);\n if (file) {\n file.formatText();\n }\n await this.project.save();\n }\n }\n }\n }\n\n /**\n * Write a collection back to its file.\n *\n * `partial` is the difference between \"this is the collection\" and \"this is\n * what changed about it\". The panel sends both — a full save from the editor\n * dialog, and a one-key patch whenever a property is added, deleted or\n * reordered — and they cannot be told apart by looking at the payload. Read\n * as a full save, a patch deletes everything it does not mention, including\n * `securityRules`; the loader then hands the collection the directory\n * default, which in the scaffold is `access: \"public\"`.\n */\n public async saveCollection(collectionId: string, collectionData: Record<string, unknown>, options: { partial?: boolean } = {}) {\n this.syncWithDisk();\n // The registry's resolved form of a relation, written into a file,\n // stops it type-checking. A collection that carries one is the view\n // the panel renders, not one somebody authored.\n const runtimeOnly = runtimeOnlyKeyIn(collectionData);\n if (runtimeOnly) {\n throw new Error(\n `Refusing to write \"${collectionId}\": it holds \"${runtimeOnly}\", which the runtime adds to a ` +\n \"collection it has loaded and a collection file must not hold. Save what changed instead.\"\n );\n }\n try {\n await this.writeCollection(collectionId, collectionData, options);\n } catch (err) {\n this.forgetUnsaved(collectionId);\n throw err;\n }\n }\n\n /**\n * Write what changed about a collection, and nothing else.\n *\n * The patch is the difference between the collection the panel loaded and\n * the one it is saving (`diffCollections` in `@rebasepro/types`), so it\n * names only keys the person changed. Each is written where it is; every\n * other byte of the file — imports, comments, formatting, handlers,\n * properties shared from another module — is left as it was.\n *\n * A key whose value in the file is code — `status: statusProperty`,\n * `enum: LOCALE_ENUM`, a spread — is not overwritten with the JSON the\n * panel saw of it, which is what the whole-collection save did. The edit\n * is refused, naming the code, so it can be made where that code lives.\n *\n * All or nothing: a refused operation leaves the file as it was, including\n * the operations before it.\n */\n public async applyPatch(collectionId: string, patch: CollectionPatch) {\n this.syncWithDisk();\n const problems = collectionPatchProblems(patch);\n if (problems.length > 0) {\n throw new Error(`Refusing to edit \"${collectionId}\": ${problems.join(\" \")}`);\n }\n try {\n await this.writePatch(collectionId, nestCollectionPatchPaths(patch));\n } catch (err) {\n this.forgetUnsaved(collectionId);\n throw err;\n }\n }\n\n private async writePatch(collectionId: string, patch: CollectionPatch) {\n this.requireCollectionObject(collectionId);\n const file = this.getCollectionFile(collectionId)!;\n for (const op of patch) {\n // From the root each time: formatting an edited node can forget\n // the nodes around it.\n this.applyPatchOp(collectionId, file, this.requireCollectionObject(collectionId), op);\n }\n await this.project.save();\n }\n\n private applyPatchOp(collectionId: string, file: SourceFile, collectionObj: ObjectLiteralExpression, op: CollectionPatchOp): void {\n const { path: keys } = op;\n const dotted = (end: number) => keys.slice(0, end).join(\".\");\n\n let target = collectionObj;\n for (let depth = 0; depth < keys.length - 1; depth++) {\n const key = keys[depth];\n const member = this.findMember(target, key);\n if (!member) {\n const spread = this.spreadIn(target);\n if (spread) throw this.codeRefusal(collectionId, file, dotted(depth + 1), spread, \"comes from\");\n // Nothing there to remove.\n if (op.op === \"remove\") return;\n target = target.addPropertyAssignment({ name: AstSchemaEditor.quoteKey(key), initializer: \"{}\" })\n .getInitializerIfKindOrThrow(SyntaxKind.ObjectLiteralExpression);\n continue;\n }\n const value = member.isKind(SyntaxKind.PropertyAssignment)\n ? AstSchemaEditor.unwrapExpression(member.getInitializer())\n : undefined;\n const next = value?.asKind(SyntaxKind.ObjectLiteralExpression);\n if (!next) throw this.codeRefusal(collectionId, file, dotted(depth + 1), value ?? member, \"is\");\n target = next;\n }\n\n const key = keys[keys.length - 1];\n const member = this.findMember(target, key);\n\n if (op.op === \"remove\") {\n if (member) {\n member.remove();\n return;\n }\n const spread = this.spreadIn(target);\n if (spread) throw this.codeRefusal(collectionId, file, dotted(keys.length), spread, \"comes from\");\n return;\n }\n\n // The collection's own `relations` pair each entry with its old self\n // by name, to keep the targets JSON cannot carry.\n if (keys.length === 1 && key === \"relations\") {\n this.writeRelations(collectionId, file, collectionObj, op.value);\n this.findMember(collectionObj, \"relations\")?.formatText();\n return;\n }\n\n const old = member?.isKind(SyntaxKind.PropertyAssignment) ? member.getInitializer() : undefined;\n if (member && (!old || !AstSchemaEditor.isLiteralValue(old))) {\n throw this.codeRefusal(collectionId, file, dotted(keys.length), old ?? member, \"is\");\n }\n if (old?.isKind(SyntaxKind.ArrayLiteralExpression)) {\n const code = old.getElements().find(element => !AstSchemaEditor.isLiteralValue(element));\n if (code) throw this.codeRefusal(collectionId, file, dotted(keys.length), code, \"holds\");\n }\n\n const value = this.patchValueAsSource(collectionId, file, keys, op.value, old);\n const text = this.convertJsonToAstString(value, 0, old);\n const written = member?.isKind(SyntaxKind.PropertyAssignment)\n ? member.setInitializer(text)\n : target.addPropertyAssignment({ name: AstSchemaEditor.quoteKey(key), initializer: text });\n // This node only: the rest of the file keeps its own formatting.\n written.formatText();\n }\n\n /**\n * A patched value with every relation target it carries turned back into\n * source — `() => xCollection` plus its import — according to where the\n * path lands: a relation's `target`, a relation, a property, or a map of\n * properties.\n */\n private patchValueAsSource(collectionId: string, file: SourceFile, keys: string[], value: unknown, old: Node | undefined): unknown {\n // Where the path lands, walked the way `collection_patch.ts` walks it.\n let position: \"collection\" | \"properties\" | \"property\" | \"relation\" | \"oneOf\" | \"other\" = \"collection\";\n for (const key of keys) {\n switch (position) {\n case \"collection\": position = key === \"properties\" ? \"properties\" : \"other\"; break;\n case \"properties\": position = \"property\"; break;\n case \"property\":\n position = key === \"properties\" ? \"properties\"\n : key === \"of\" ? \"property\"\n : key === \"oneOf\" ? \"oneOf\"\n : key === \"relation\" ? \"relation\"\n : \"other\";\n break;\n case \"oneOf\": position = key === \"properties\" ? \"properties\" : \"other\"; break;\n case \"relation\":\n if (key === \"target\" && typeof value === \"string\" && value.trim().length > 0) {\n const trimmed = value.trim();\n return new RawExpression(ARROW_TO_IDENTIFIER.test(trimmed) ? trimmed : this.targetThunk(file, trimmed));\n }\n position = \"other\";\n break;\n default: position = \"other\";\n }\n }\n const oldObject = old?.asKind(SyntaxKind.ObjectLiteralExpression);\n const where = keys.join(\".\");\n if (position === \"property\") {\n return this.propertyRelationTargetsAsSource(collectionId, file, value, oldObject, where);\n }\n if (position === \"properties\") {\n return this.relationTargetsAsSource(collectionId, file, value, oldObject, where);\n }\n if (position === \"relation\" && AstSchemaEditor.isPlainObject(value)) {\n // A relation on its own: wrap it as the property it belongs to.\n const wrapped = this.propertyRelationTargetsAsSource(\n collectionId, file, { type: \"relation\", relation: value },\n oldObject ? undefined : undefined, where);\n const relation = AstSchemaEditor.isPlainObject(wrapped) ? wrapped.relation : value;\n if (AstSchemaEditor.isPlainObject(relation) && !(\"target\" in relation) &&\n !(oldObject && this.findProperty(oldObject, \"target\"))) {\n throw new Error(`Relation \"${where}\" on collection \"${collectionId}\" has no target collection. Pick one before saving.`);\n }\n return relation;\n }\n return value;\n }\n\n /** A member of an object literal by name: an assignment, a shorthand, a method. */\n private findMember(obj: ObjectLiteralExpression, name: string): ObjectLiteralElementLike | undefined {\n return obj.getProperties().find(member => {\n if (member.isKind(SyntaxKind.SpreadAssignment)) return false;\n const memberName = (member as PropertyAssignment).getName();\n return memberName === name || memberName === JSON.stringify(name) || memberName === `'${name}'`;\n });\n }\n\n private spreadIn(obj: ObjectLiteralExpression): Node | undefined {\n return obj.getProperties().find(member => member.isKind(SyntaxKind.SpreadAssignment));\n }\n\n /** `x as const`, `x satisfies T`, `(x)` — the value inside. */\n private static unwrapExpression(node: Node | undefined): Node | undefined {\n if (!node) return undefined;\n if (Node.isAsExpression(node) || Node.isSatisfiesExpression(node) || Node.isParenthesizedExpression(node)) {\n return AstSchemaEditor.unwrapExpression(node.getExpression());\n }\n return node;\n }\n\n /**\n * Whether a value in the file is data the panel's JSON fully describes —\n * a literal — rather than code it saw only the result of.\n */\n private static isLiteralValue(node: Node): boolean {\n const value = AstSchemaEditor.unwrapExpression(node);\n if (!value) return false;\n switch (value.getKind()) {\n case SyntaxKind.ObjectLiteralExpression:\n case SyntaxKind.ArrayLiteralExpression:\n case SyntaxKind.StringLiteral:\n case SyntaxKind.NoSubstitutionTemplateLiteral:\n case SyntaxKind.NumericLiteral:\n case SyntaxKind.TrueKeyword:\n case SyntaxKind.FalseKeyword:\n case SyntaxKind.NullKeyword:\n return true;\n case SyntaxKind.PrefixUnaryExpression:\n return value.asKindOrThrow(SyntaxKind.PrefixUnaryExpression).getOperand().isKind(SyntaxKind.NumericLiteral);\n case SyntaxKind.Identifier:\n return value.getText() === \"undefined\";\n default:\n return false;\n }\n }\n\n /** \"`properties.status` is `statusProperty` in posts.ts…\" */\n private codeRefusal(collectionId: string, file: SourceFile, where: string, code: Node, verb: \"is\" | \"holds\" | \"comes from\"): Error {\n const text = code.getText();\n const shown = text.length > 60 ? `${text.slice(0, 57)}…` : text;\n return new Error(\n `\\`${where}\\` ${verb} \\`${shown}\\` in ${path.basename(file.getFilePath())}, which is code the ` +\n `editor cannot change without losing what it does. Change \"${collectionId}\" there instead.`\n );\n }\n\n private async writeCollection(collectionId: string, collectionData: Record<string, unknown>, options: { partial?: boolean }) {\n const partial = options.partial === true;\n let file = this.getCollectionFile(collectionId);\n const collectionObj = file ? this.getCollectionObject(collectionId) : null;\n\n if (file && !collectionObj) {\n // The file is there and we could not read it. Recreating it from the\n // panel's JSON would drop the imports, the callbacks and the relation\n // thunks that JSON cannot carry.\n throw new Error(this.unreadableFileMessage(collectionId, file));\n }\n\n if (!file || !collectionObj) {\n if (partial) {\n throw new Error(`Cannot apply a partial update to \"${collectionId}\": it has no collection file yet.`);\n }\n // Create a new file\n const safeId = this.sanitizeCollectionId(collectionId);\n const newFilePath = this.safePath(`${safeId}.ts`);\n if (fs.existsSync(newFilePath)) {\n throw new Error(`Refusing to overwrite ${newFilePath}: a file for \"${collectionId}\" already exists but could not be parsed.`);\n }\n const varName = `${AstSchemaEditor.collectionVarName(safeId)}Collection`;\n // Created empty and filled in after: a relation's target becomes an\n // import, and an import needs a file to be added to.\n file = this.project.createSourceFile(newFilePath, `import { CollectionConfig } from \"@rebasepro/types\";\\n\\nconst ${varName}: CollectionConfig = {};\\n\\nexport default ${varName};\\n`);\n const { relations, ...rest } = nestAdminKeysDeep(collectionData);\n const data = \"properties\" in rest\n ? { ...rest, properties: this.relationTargetsAsSource(collectionId, file, rest.properties, undefined) }\n : rest;\n file.getVariableDeclarationOrThrow(varName).setInitializer(this.convertJsonToAstString(data));\n if (relations !== undefined) {\n this.writeRelations(collectionId, file, this.requireCollectionObject(collectionId), relations);\n }\n } else {\n // Update root level properties gracefully\n\n if (!partial) {\n // Force delete securityRules if empty or undefined to handle Formex / serialization stripping\n if (!(\"securityRules\" in collectionData) || collectionData.securityRules === undefined || (Array.isArray(collectionData.securityRules) && collectionData.securityRules.length === 0)) {\n const srProp = collectionObj.getProperty(\"securityRules\");\n if (srProp) {\n srProp.remove();\n }\n\n // If it was in collectionData as an empty array, delete it so the loop below doesn't add it back as \"[]\"\n // Actually, if it's \"[]\", omitting it entirely from the TS file achieves the same logical effect (no RLS rules)\n // and correctly triggers \"unmapped policies\" if the DB still has them.\n delete collectionData[\"securityRules\"];\n }\n }\n\n // The panel works with a flat view model — presentation merged onto the\n // collection — so what arrives here has `icon` and `listProperties` at\n // the top level. On disk they belong inside `admin`. Writing them flat\n // would produce a file the backend loads and ignores and the panel\n // never reads back, which looks exactly like the edit not saving. A\n // property's presentation keys are worse than ignored: they are fatal\n // at the next boot, which is why this goes all the way down.\n collectionData = nestAdminKeysDeep(collectionData);\n\n for (const key of Object.keys(collectionData)) {\n if (key === \"relations\") {\n this.writeRelations(collectionId, file, collectionObj, collectionData[key]);\n continue;\n }\n\n const prop = collectionObj.getProperty(key) as PropertyAssignment;\n\n let oldAstNode: ObjectLiteralExpression | undefined;\n if (prop && prop.isKind(SyntaxKind.PropertyAssignment)) {\n oldAstNode = prop.getInitializerIfKind(SyntaxKind.ObjectLiteralExpression);\n }\n\n if (key === \"properties\") {\n collectionData[key] = this.relationTargetsAsSource(collectionId, file, collectionData[key], oldAstNode);\n }\n\n if (partial && oldAstNode && AstSchemaEditor.isPlainObject(collectionData[key])) {\n this.mergeIntoObjectLiteral(oldAstNode, collectionData[key] as Record<string, unknown>, 2);\n continue;\n }\n\n const newInit = this.convertJsonToAstString(collectionData[key], 1, oldAstNode);\n if (prop) {\n prop.setInitializer(newInit);\n } else {\n collectionObj.addPropertyAssignment({\n // `quoteKey`, like every other site that emits a key.\n // This one did not, and ts-morph writes a property name\n // out verbatim — so a top-level key of\n // `injected: (() => { … })(), tail` closed the property\n // and opened an expression, which `rebase dev` then\n // re-imported and ran. The rest of this file has always\n // quoted; this was the one that did not.\n name: AstSchemaEditor.quoteKey(key),\n initializer: newInit\n });\n }\n }\n }\n if (file) {\n file.formatText();\n }\n await this.project.save();\n }\n\n /**\n * Every property relation's `target`, as source.\n *\n * The collection-level `relations` array has {@link writeRelations}. A\n * relation declared on a property went through `convertJsonToAstString`\n * like any other value, so a target the property form had picked — a slug,\n * which is all JSON can carry — was written as the string literal\n * `target: \"authors\"`, and the next boot threw in `resolveRelation` for every\n * collection in the directory. The same rule applies here as there: a slug\n * becomes `() => xCollection` plus its import, an arrow returning one\n * identifier is written through, and a relation sent without a target keeps\n * the one already in the file — or is refused, when there is none.\n *\n * Walks into `map` properties, an array's `of` and a block's `oneOf`, which\n * the registry resolves relations in too. `oldProperties` is the same\n * properties object in the file, when there is one.\n */\n private relationTargetsAsSource(\n collectionId: string,\n file: SourceFile,\n properties: unknown,\n oldProperties: ObjectLiteralExpression | undefined,\n path = \"properties\"\n ): Record<string, unknown> {\n if (!AstSchemaEditor.isPlainObject(properties)) return {};\n return Object.fromEntries(Object.entries(properties).map(([key, property]) => [\n key,\n this.propertyRelationTargetsAsSource(\n collectionId,\n file,\n property,\n oldProperties ? this.objectAt(oldProperties, key) : undefined,\n `${path}.${key}`\n )\n ]));\n }\n\n private propertyRelationTargetsAsSource(\n collectionId: string,\n file: SourceFile,\n property: unknown,\n oldProperty: ObjectLiteralExpression | undefined,\n path: string\n ): unknown {\n if (!AstSchemaEditor.isPlainObject(property)) return property;\n const next: Record<string, unknown> = { ...property };\n\n if (property.type === \"relation\" && AstSchemaEditor.isPlainObject(property.relation)) {\n const relation: Record<string, unknown> = { ...property.relation };\n const rawTarget = relation.target;\n if (typeof rawTarget === \"string\" && rawTarget.trim().length > 0) {\n const trimmed = rawTarget.trim();\n // See `writeRelations` for why the arrow is matched this narrowly.\n relation.target = new RawExpression(ARROW_TO_IDENTIFIER.test(trimmed)\n ? trimmed\n : this.targetThunk(file, trimmed));\n } else {\n const oldRelation = oldProperty ? this.objectAt(oldProperty, \"relation\") : undefined;\n if (!oldRelation || !this.findProperty(oldRelation, \"target\")) {\n throw new Error(`Relation property \"${path}\" on collection \"${collectionId}\" has no target collection. Pick one before saving.`);\n }\n // Absent, so `convertJsonToAstString` keeps the thunk in the file.\n delete relation.target;\n }\n next.relation = relation;\n }\n\n if (AstSchemaEditor.isPlainObject(property.properties)) {\n next.properties = this.relationTargetsAsSource(\n collectionId, file, property.properties,\n oldProperty ? this.objectAt(oldProperty, \"properties\") : undefined, path);\n }\n if (Array.isArray(property.of)) {\n const oldOf = oldProperty ? this.initializerOf(oldProperty, \"of\")?.asKind(SyntaxKind.ArrayLiteralExpression) : undefined;\n next.of = property.of.map((item, index) => this.propertyRelationTargetsAsSource(\n collectionId, file, item,\n oldOf?.getElements()[index]?.asKind(SyntaxKind.ObjectLiteralExpression), `${path}.of[${index}]`));\n } else if (AstSchemaEditor.isPlainObject(property.of)) {\n next.of = this.propertyRelationTargetsAsSource(\n collectionId, file, property.of,\n oldProperty ? this.objectAt(oldProperty, \"of\") : undefined, `${path}.of`);\n }\n if (AstSchemaEditor.isPlainObject(property.oneOf) && AstSchemaEditor.isPlainObject(property.oneOf.properties)) {\n const oldOneOf = oldProperty ? this.objectAt(oldProperty, \"oneOf\") : undefined;\n next.oneOf = {\n ...property.oneOf,\n properties: this.relationTargetsAsSource(\n collectionId, file, property.oneOf.properties,\n oldOneOf ? this.objectAt(oldOneOf, \"properties\") : undefined, `${path}.oneOf`)\n };\n }\n return next;\n }\n\n /** The initializer of `obj[key]` in the file, when it is written as a property assignment. */\n private initializerOf(obj: ObjectLiteralExpression, key: string): Node | undefined {\n const prop = this.findProperty(obj, key);\n return prop && prop.isKind(SyntaxKind.PropertyAssignment) ? prop.getInitializer() : undefined;\n }\n\n /** `obj[key]` in the file, when it is an object literal. */\n private objectAt(obj: ObjectLiteralExpression, key: string): ObjectLiteralExpression | undefined {\n return this.initializerOf(obj, key)?.asKind(SyntaxKind.ObjectLiteralExpression);\n }\n\n /**\n * Write the collection-level `relations` array.\n *\n * Relations are the one key whose values are not all data: `target` is a\n * thunk in the file, and the panel sends the target collection's slug (or,\n * for a relation it did not touch, nothing at all — `JSON.stringify` drops\n * the function). So each entry's target is resolved in that order: a slug\n * becomes `() => xCollection` plus the import it needs, and anything else\n * falls back to the thunk already in the file.\n *\n * This used to be a `continue` with a comment saying relations were handled\n * elsewhere. Nothing handled them; every edit in the Relations tab was\n * dropped without a word.\n */\n private writeRelations(collectionId: string, file: SourceFile, collectionObj: ObjectLiteralExpression, value: unknown): void {\n if (!Array.isArray(value)) return;\n\n const existing = this.findProperty(collectionObj, \"relations\");\n const oldArray = existing && existing.isKind(SyntaxKind.PropertyAssignment)\n ? existing.getInitializerIfKind(SyntaxKind.ArrayLiteralExpression)\n : undefined;\n\n const oldElements = oldArray?.getElements() ?? [];\n const oldTargetsByName = new Map<string, string>();\n // Only for entries the file left unnamed — `relationName` is optional, and\n // those cannot be matched any other way. Positional matching is off the\n // moment the array's length changes, because then a position no longer\n // means the same relation.\n const anonymousTargetsByIndex: (string | undefined)[] = [];\n oldElements.forEach((element, index) => {\n const elementObj = element.asKind(SyntaxKind.ObjectLiteralExpression);\n if (!elementObj) return;\n const targetProp = this.findProperty(elementObj, \"target\");\n const targetInit = targetProp && targetProp.isKind(SyntaxKind.PropertyAssignment)\n ? targetProp.getInitializer()\n : undefined;\n if (!targetInit) return;\n\n const nameProp = this.findProperty(elementObj, \"relationName\");\n const nameInit = nameProp && nameProp.isKind(SyntaxKind.PropertyAssignment)\n ? nameProp.getInitializerIfKind(SyntaxKind.StringLiteral)\n : undefined;\n if (nameInit) oldTargetsByName.set(nameInit.getLiteralValue(), targetInit.getText());\n else anonymousTargetsByIndex[index] = targetInit.getText();\n });\n\n const items = value.map((entry, index) => {\n if (!AstSchemaEditor.isPlainObject(entry)) return entry;\n const relation: Record<string, unknown> = { ...entry };\n const relationName = typeof relation.relationName === \"string\" ? relation.relationName : undefined;\n const rawTarget = relation.target;\n\n let targetText: string | undefined;\n if (typeof rawTarget === \"string\" && rawTarget.trim().length > 0) {\n const trimmed = rawTarget.trim();\n // A target that already looks like a thunk is written into the\n // file as source, so \"contains an arrow\" is not a good enough\n // reason to trust it: `() => { require(\"child_process\")… }` also\n // contains one. Only the exact shape this emits is accepted —\n // an arrow returning one identifier — and anything else is\n // treated as a collection NAME and turned into a thunk here,\n // which is the path that was always safe.\n targetText = ARROW_TO_IDENTIFIER.test(trimmed)\n ? trimmed\n : this.targetThunk(file, trimmed);\n } else if (relationName && oldTargetsByName.has(relationName)) {\n targetText = oldTargetsByName.get(relationName);\n } else if (value.length === oldElements.length) {\n targetText = anonymousTargetsByIndex[index];\n }\n\n if (!targetText) {\n throw new Error(`Relation \"${relationName ?? `#${index}`}\" on collection \"${collectionId}\" has no target collection. Pick one before saving.`);\n }\n\n relation.target = new RawExpression(targetText);\n return relation;\n });\n\n const initializer = this.convertJsonToAstString(items, 1);\n if (existing && existing.isKind(SyntaxKind.PropertyAssignment)) {\n existing.setInitializer(initializer);\n } else {\n collectionObj.addPropertyAssignment({ name: \"relations\", initializer });\n }\n }\n\n /**\n * `() => targetCollection` for a target named by its slug, importing it if\n * the file does not already.\n */\n private targetThunk(file: SourceFile, targetSlug: string): string {\n const targetFile = this.getCollectionFile(targetSlug);\n if (!targetFile) {\n throw new Error(`Cannot link to collection \"${targetSlug}\": no file for it in ${this.collectionsDir}.`);\n }\n\n const identifier = this.getDefaultExportName(targetFile);\n if (!identifier) {\n throw new Error(`Cannot link to collection \"${targetSlug}\": ${targetFile.getFilePath()} has no default export to import.`);\n }\n\n if (targetFile.getFilePath() !== file.getFilePath() && !this.hasDefaultImport(file, identifier)) {\n const relative = path.relative(path.dirname(file.getFilePath()), targetFile.getFilePath())\n .split(path.sep)\n .join(\"/\")\n .replace(/\\.tsx?$/, \".js\");\n file.addImportDeclaration({\n defaultImport: identifier,\n moduleSpecifier: relative.startsWith(\".\") ? relative : `./${relative}`\n });\n }\n\n return `() => ${identifier}`;\n }\n\n private hasDefaultImport(file: SourceFile, identifier: string): boolean {\n return file.getImportDeclarations().some(decl => decl.getDefaultImport()?.getText() === identifier);\n }\n\n private getDefaultExportName(file: SourceFile): string | undefined {\n const declaration = file.getDefaultExportSymbol()?.getDeclarations()[0];\n const expr = declaration?.asKind(SyntaxKind.ExportAssignment)?.getExpression();\n if (expr && expr.getKind() === SyntaxKind.Identifier) return expr.getText();\n\n for (const varDecl of file.getVariableDeclarations()) {\n if (this.unwrapCollectionObject(varDecl.getInitializer())) return varDecl.getName();\n }\n return undefined;\n }\n\n /**\n * Delete a collection's file, and take it out of `index.ts`.\n *\n * This used to unlink `<slug>.ts` and stop. Every scaffold's `index.ts`\n * imports every collection, so the next boot failed for the whole project\n * (\"Cannot find module …/tags\"), and a collection another one linked to\n * took that one down with it. Now:\n *\n * - a file any other module imports is refused, naming the importers — a\n * relation to it has to go first, and that is a change the person makes;\n * - `index.ts`'s import of it is removed, and so is each place it is listed\n * in an array; any other use there is refused, naming the line;\n * - the file is deleted.\n *\n * Only the source. The table and its rows are not touched.\n *\n * Returns what changed, as absolute paths, so the caller can commit it.\n */\n public async deleteCollection(collectionId: string): Promise<{ deleted: string; changed: string[] }> {\n this.syncWithDisk();\n const target = this.getCollectionFile(collectionId);\n if (!target) {\n throw new Error(`Collection \"${collectionId}\" has no file at ${path.join(this.collectionsDir, `${collectionId}.ts`)}.`);\n }\n const targetPath = target.getFilePath();\n const indexPath = path.join(this.collectionsDir, \"index.ts\");\n\n const importers = this.project.getSourceFiles().filter(file =>\n file !== target && this.declarationsOf(file, targetPath).length > 0);\n const others = importers.filter(file => file.getFilePath() !== indexPath);\n if (others.length > 0) {\n throw new Error(\n `Cannot delete \"${collectionId}\": ${others.map(file => path.basename(file.getFilePath())).join(\", \")} ` +\n `${others.length === 1 ? \"imports\" : \"import\"} it — a relation to it, most likely. Remove that first; ` +\n \"deleting the file would stop every collection from loading.\"\n );\n }\n\n const changed: string[] = [];\n const index = importers.find(file => file.getFilePath() === indexPath);\n try {\n if (index) {\n for (const declaration of this.declarationsOf(index, targetPath)) {\n const names = declaration.isKind(SyntaxKind.ImportDeclaration)\n ? [\n declaration.getDefaultImport()?.getText(),\n ...declaration.getNamedImports().map(named => (named.getAliasNode() ?? named.getNameNode()).getText())\n ].filter((name): name is string => Boolean(name))\n : [];\n for (const name of names) {\n const uses = index.getDescendantsOfKind(SyntaxKind.Identifier).filter(identifier =>\n identifier.getText() === name &&\n !identifier.getFirstAncestorByKind(SyntaxKind.ImportDeclaration));\n for (const use of uses) {\n const array = use.getParentIfKind(SyntaxKind.ArrayLiteralExpression);\n if (!array) {\n throw new Error(\n `Cannot delete \"${collectionId}\": index.ts uses \\`${name}\\` at line ` +\n `${use.getStartLineNumber()} in a way the editor cannot remove. Remove it there first.`\n );\n }\n array.removeElement(use);\n }\n }\n declaration.remove();\n }\n changed.push(index.getFilePath());\n }\n target.delete();\n await this.project.save();\n } catch (err) {\n if (index && !index.isSaved()) index.refreshFromFileSystemSync();\n this.syncWithDisk();\n throw err;\n }\n return { deleted: targetPath, changed };\n }\n\n /** The import and export declarations in `file` whose relative specifier resolves to `targetPath`. */\n private declarationsOf(file: SourceFile, targetPath: string) {\n const stem = (absolute: string) => absolute.replace(/\\.(ts|tsx|js|mjs|cjs)$/, \"\");\n const target = stem(targetPath);\n return [...file.getImportDeclarations(), ...file.getExportDeclarations()].filter(declaration => {\n const specifier = declaration.getModuleSpecifierValue();\n if (!specifier || !specifier.startsWith(\".\")) return false;\n return stem(path.resolve(path.dirname(file.getFilePath()), specifier)) === target;\n });\n }\n}\n"],"mappings":"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"}
@@ -158,5 +158,15 @@ export declare function requireScopeByMethod(scopes: {
158
158
  * and sign in as them.
159
159
  */
160
160
  export declare function assertMayManageAccount(c: Context<HonoEnv>, targetRoles: readonly string[]): void;
161
+ /**
162
+ * Read a `roles` body field as the exact list that is then judged and written.
163
+ *
164
+ * Every entry must be a string `roleNameProblem` accepts. Filtering out the bad
165
+ * entries instead is how the ceiling once judged one list and the write stored
166
+ * another: `["support", ["admin"]]` was judged as `["support"]` and written as
167
+ * `{support,admin}`, and `"support,admin"` was one undeclared role to the check
168
+ * and two roles to Postgres.
169
+ */
170
+ export declare function readRoleNames(value: unknown): string[];
161
171
  /** Refuse to let the caller grant roles that hold anything the caller does not. */
162
172
  export declare function assertMayGrantRoles(c: Context<HonoEnv>, roles: readonly string[]): void;
@@ -11,7 +11,8 @@
11
11
  * 3. Scopes the DataDriver via `withAuth()` for RLS
12
12
  * 4. Rejects a presented-but-unverifiable token with 401, whatever
13
13
  * `requireAuth` says
14
- * 5. Honours or refuses `x-rebase-impersonate` (`applyImpersonation`)
14
+ * 5. Honours or refuses the RLS simulator's `x-rebase-simulate-*` headers
15
+ * (`applySimulation`) and `x-rebase-impersonate` (`applyImpersonation`)
15
16
  * 6. Enforces auth (401) when `requireAuth` is true and no user is found
16
17
  *
17
18
  * The behavior is identical to `createAuthMiddleware()` — only the
@@ -0,0 +1,24 @@
1
+ /**
2
+ * One backend's view of an auth adapter the caller built.
3
+ *
4
+ * A backend whose adapter has no `serviceKey` mints a per-boot internal key and
5
+ * has to tell the adapter about it, so the adapter and the socket recognise the
6
+ * in-process client's control-plane calls. It used to write the key onto the
7
+ * adapter instance. That instance is the caller's, and a caller who builds two
8
+ * backends from one adapter got one key for both: the second backend's write
9
+ * replaced nothing (the first had already set it) and adopted the first one's
10
+ * key, so the credential that opens one backend opened the other.
11
+ *
12
+ * This answers `serviceKey` with the backend's own key and leaves the instance
13
+ * untouched. Everything else is the instance's.
14
+ *
15
+ * A method's `this` depends on what the adapter is. An object literal — what
16
+ * `createCustomAuthAdapter` returns, and every documented example — cannot hold
17
+ * private fields, so its methods run with `this` as the view and read the key as
18
+ * they read it from the instance before. A class instance may hold `#private`
19
+ * fields, which only the instance itself can read, so its methods run with
20
+ * `this` as the instance; a method of one that reads `this.serviceKey` sees the
21
+ * value the caller configured on it.
22
+ */
23
+ import type { AuthAdapter } from "@rebasepro/types";
24
+ export declare function withBackendServiceKey(adapter: AuthAdapter, serviceKey: string): AuthAdapter;
@@ -19,7 +19,7 @@
19
19
  * @module
20
20
  */
21
21
  import type { Context, MiddlewareHandler } from "hono";
22
- import { type DataDriver } from "@rebasepro/types";
22
+ import { type CallerCredential, type DataDriver } from "@rebasepro/types";
23
23
  import type { HonoEnv } from "../../api/types.js";
24
24
  import type { ApiKeyStore } from "./api-key-store.js";
25
25
  import type { ApiKeyMasked } from "./api-key-types.js";
@@ -48,6 +48,22 @@ export interface ApiKeyRefusal {
48
48
  * when omitted.
49
49
  */
50
50
  export declare function resolveApiKey(store: ApiKeyStore, token: string, access?: AccessControl): Promise<ApiKeyIdentity | ApiKeyRefusal>;
51
+ /**
52
+ * What a collection callback is told about the key a request authenticated
53
+ * with (`context.credential`): its kind, id and name, never the secret.
54
+ */
55
+ export declare function credentialOf(apiKey: Pick<ApiKeyMasked, "id" | "name" | "kind">): CallerCredential;
56
+ /**
57
+ * Who a stored key acts as and what it holds — the half of
58
+ * {@link resolveApiKey} that does not depend on how the key was found. The RLS
59
+ * simulator asks it of a key named by id, so "view as this key" and a request
60
+ * presenting the key cannot drift apart.
61
+ */
62
+ export declare function identityOfKey(apiKey: Pick<ApiKeyMasked, "id" | "kind" | "owner_uid" | "scopes" | "roles" | "revoked_at" | "expires_at">, access?: AccessControl): Promise<{
63
+ uid: string;
64
+ roles: string[];
65
+ scopes: string[];
66
+ } | ApiKeyRefusal>;
51
67
  /**
52
68
  * Options for the API key authentication handler.
53
69
  */
@@ -89,6 +105,10 @@ export declare function createTusScopeGuard(): MiddlewareHandler<HonoEnv>;
89
105
  * name from the request path. Omitted, it is the
90
106
  * `mountPath` the functions group recorded for this
91
107
  * request — wherever the application mounted it.
108
+ *
109
+ * It also keeps "Simulate access" read-only here: a function called with a
110
+ * simulation header by any verb but GET, HEAD or OPTIONS is refused
111
+ * (`SIMULATION_READ_ONLY`), since it would run as the simulated identity.
92
112
  */
93
113
  export declare function createFunctionScopeGuard(mountPrefix?: string): MiddlewareHandler<HonoEnv>;
94
114
  /**
@@ -55,4 +55,11 @@ export interface ApiKeyStore {
55
55
  *
56
56
  * Returns `undefined` if the driver does not support `executeSql`.
57
57
  */
58
- export declare function createApiKeyStore(driver: DataDriver): ApiKeyStore | undefined;
58
+ export declare function createApiKeyStore(driver: DataDriver, options?: {
59
+ /**
60
+ * Default `true`. `false` is a backend that changes nothing in its
61
+ * database: `ensureTable` reads the table, creates, alters and revokes
62
+ * nothing, and leaves a pre-scopes table's rows as they are.
63
+ */
64
+ provision?: boolean;
65
+ }): ApiKeyStore | undefined;
@@ -21,7 +21,11 @@ export interface KeyTargets {
21
21
  }
22
22
  /** Read a `scopes` body field: a non-empty array of strings, deduplicated. */
23
23
  export declare function readScopesField(value: unknown): string[];
24
- /** Read a `roles` body field: an array of non-empty strings, deduplicated, without `service`. */
24
+ /**
25
+ * Read a `roles` body field: an array of role names, trimmed, deduplicated,
26
+ * without `service`. A key's roles reach RLS joined by `,` in `app.user_roles`,
27
+ * so a name `roleNameProblem` refuses — `"editor,admin"` — is refused here too.
28
+ */
25
29
  export declare function readRolesField(value: unknown): string[];
26
30
  /**
27
31
  * Refuse scopes that are malformed, unknown, aimed at a target that does not
@@ -33,6 +37,19 @@ export declare function assertScopesGrantable(requested: readonly string[], mint
33
37
  * already read every row — and anyone else only their own.
34
38
  */
35
39
  export declare function assertRolesGrantable(requested: readonly string[], minterRoles: readonly string[]): void;
40
+ /**
41
+ * Refuse changing or revoking a key that holds more than the caller — a role
42
+ * they do not hold, or a scope beyond theirs. An admin manages any key.
43
+ *
44
+ * The same ceiling as minting, applied to the key as it already is: a
45
+ * `keys:write` role could otherwise widen an admin-role key it could never
46
+ * have minted to every data scope, or revoke the key an admin's integration
47
+ * runs on. `assertMayManageAccount` is the same rule for accounts.
48
+ */
49
+ export declare function assertMayManageKey(existing: {
50
+ roles: readonly string[];
51
+ scopes: readonly string[];
52
+ }, managerRoles: readonly string[], managerScopes: readonly string[]): void;
36
53
  /** Read an optional `expires_at`: absent, null, or a future ISO-8601 instant. */
37
54
  export declare function readExpiresAt(value: unknown, requireFuture: boolean): string | null | undefined;
38
55
  /** Read an optional `rate_limit`: absent, null, or a positive integer. */
@@ -59,6 +59,11 @@ export interface ExpiredTokenSweepWiring {
59
59
  ownsTimers: boolean;
60
60
  /** `cronPersistence: false` keeps the claims table out of the database, here too. */
61
61
  persistClaims?: boolean;
62
+ /**
63
+ * `false`: the backend changes nothing in its database, so the claims
64
+ * table is used if it is there and never created by the first tick.
65
+ */
66
+ provision?: boolean;
62
67
  }
63
68
  /**
64
69
  * Start the sweep where it belongs, or answer `undefined` where it does not:
@@ -94,7 +94,9 @@ export interface ImpersonationOptions {
94
94
  */
95
95
  export declare function applyImpersonation(c: Context<HonoEnv>, { credential, driver, resolveUser }: ImpersonationOptions): Promise<Response | undefined>;
96
96
  /**
97
- * Refuse {@link IMPERSONATE_HEADER} on every route that does not honour it.
97
+ * Refuse {@link IMPERSONATE_HEADER} — and the RLS simulator's
98
+ * `x-rebase-simulate-*` headers (`auth/simulation.ts`) — on every route that
99
+ * does not honour it.
98
100
  *
99
101
  * Only the data API and custom functions run a request as another user; the
100
102
  * rest — storage, auth, the admin surfaces — authenticate the caller and
@@ -318,7 +318,7 @@ export interface JwtService {
318
318
  * it ever needs to verify one, and an asymmetric key here would be a
319
319
  * second key surface bought for nobody.
320
320
  */
321
- generateMcpAccessToken(payload: Omit<McpAccessTokenPayload, "purpose">, expiresInSeconds: number): Promise<string>;
321
+ generateMcpAccessToken(payload: Omit<McpAccessTokenPayload, "purpose" | "iat">, expiresInSeconds: number): Promise<string>;
322
322
  /**
323
323
  * Verify an MCP access token **for a specific resource**.
324
324
  *
@@ -484,9 +484,15 @@ export interface McpAccessTokenPayload {
484
484
  /** The resource this token is bound to (RFC 8707). */
485
485
  aud: string;
486
486
  iss: string;
487
+ /**
488
+ * When it was signed, in whole seconds. Set on a verified token only; the
489
+ * signer stamps it itself. `/mcp` places it against the account's
490
+ * revocation watermark, as every other door does a session token.
491
+ */
492
+ iat?: number;
487
493
  }
488
494
  /** See {@link JwtService.generateMcpAccessToken}. Answers for the process default. */
489
- export declare function generateMcpAccessToken(payload: Omit<McpAccessTokenPayload, "purpose">, expiresInSeconds: number): Promise<string>;
495
+ export declare function generateMcpAccessToken(payload: Omit<McpAccessTokenPayload, "purpose" | "iat">, expiresInSeconds: number): Promise<string>;
490
496
  /** See {@link JwtService.verifyMcpAccessToken}. Answers for the process default. */
491
497
  export declare function verifyMcpAccessToken(token: string, expectedAudience: string): Promise<McpAccessTokenPayload | null>;
492
498
  /**