@rebasepro/server 0.23.0 → 0.24.1-canary.g19596ef

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 (287) hide show
  1. package/README.md +28 -3
  2. package/bin/rebase-server.js +4 -2
  3. package/dist/GCSStorageController-DEga1ULx.js +402 -0
  4. package/dist/GCSStorageController-DEga1ULx.js.map +1 -0
  5. package/dist/S3StorageController-Btx-6bJ5.js +480 -0
  6. package/dist/S3StorageController-Btx-6bJ5.js.map +1 -0
  7. package/dist/api/ast-schema-editor.d.ts +92 -1
  8. package/dist/api/errors.d.ts +41 -0
  9. package/dist/api/live-schema-routes.d.ts +64 -10
  10. package/dist/api/logs-routes.d.ts +39 -1
  11. package/dist/api/mount-path.d.ts +30 -0
  12. package/dist/api/mount.d.ts +6 -13
  13. package/dist/api/openapi-generator.d.ts +17 -0
  14. package/dist/api/rest/api-generator.d.ts +97 -11
  15. package/dist/api/rest/auth-collection-writes.d.ts +28 -21
  16. package/dist/api/rest/field-access-query.d.ts +28 -8
  17. package/dist/api/rest/field-ops.d.ts +19 -7
  18. package/dist/api/rest/query-body.d.ts +10 -0
  19. package/dist/api/rest/write-validation.d.ts +9 -7
  20. package/dist/api/schema-editor-routes.d.ts +10 -1
  21. package/dist/api/sql-export-routes.d.ts +8 -0
  22. package/dist/api/types.d.ts +52 -1
  23. package/dist/{ast-schema-editor-Mvr50v_S.js → ast-schema-editor-mCeSC03x.js} +310 -12
  24. package/dist/ast-schema-editor-mCeSC03x.js.map +1 -0
  25. package/dist/auth/access.d.ts +156 -0
  26. package/dist/auth/adapter-middleware.d.ts +2 -1
  27. package/dist/auth/address-ownership.d.ts +16 -1
  28. package/dist/auth/admin-roles-route.d.ts +4 -2
  29. package/dist/auth/admin-roles.d.ts +17 -20
  30. package/dist/auth/admin-user-ops.d.ts +24 -2
  31. package/dist/auth/admin-users-route.d.ts +2 -0
  32. package/dist/auth/api-keys/api-key-middleware.d.ts +64 -56
  33. package/dist/auth/api-keys/api-key-routes.d.ts +44 -11
  34. package/dist/auth/api-keys/api-key-store.d.ts +31 -8
  35. package/dist/auth/api-keys/api-key-types.d.ts +14 -16
  36. package/dist/auth/api-keys/http-operation.d.ts +19 -0
  37. package/dist/auth/api-keys/index.d.ts +11 -11
  38. package/dist/auth/api-keys/key-grant.d.ts +41 -0
  39. package/dist/auth/api-keys/legacy-permissions.d.ts +33 -0
  40. package/dist/auth/auth-hooks.d.ts +46 -7
  41. package/dist/auth/builtin-auth-adapter.d.ts +21 -0
  42. package/dist/auth/cookie-utils.d.ts +7 -0
  43. package/dist/auth/deliverable-address.d.ts +6 -0
  44. package/dist/auth/email-change-routes.d.ts +44 -0
  45. package/dist/auth/expired-token-sweep.d.ts +71 -0
  46. package/dist/auth/impersonation.d.ts +110 -0
  47. package/dist/auth/index.d.ts +4 -2
  48. package/dist/auth/interfaces.d.ts +120 -59
  49. package/dist/auth/jwks-routes.d.ts +5 -1
  50. package/dist/auth/jwt-crypto.d.ts +7 -0
  51. package/dist/auth/jwt-keys.d.ts +4 -0
  52. package/dist/auth/jwt.d.ts +319 -157
  53. package/dist/auth/magic-link-routes.d.ts +5 -6
  54. package/dist/auth/mfa-crypto.d.ts +9 -3
  55. package/dist/auth/mfa-gate.d.ts +4 -2
  56. package/dist/auth/mfa-routes.d.ts +8 -9
  57. package/dist/auth/middleware.d.ts +26 -7
  58. package/dist/auth/otp-routes.d.ts +5 -6
  59. package/dist/auth/passwordless-signup.d.ts +27 -0
  60. package/dist/auth/platform-token.d.ts +122 -0
  61. package/dist/auth/rate-limiter.d.ts +99 -31
  62. package/dist/auth/registration-policy.d.ts +17 -0
  63. package/dist/auth/require-auth.d.ts +1 -1
  64. package/dist/auth/routes.d.ts +61 -0
  65. package/dist/auth/scope-routes.d.ts +22 -0
  66. package/dist/auth/session-routes.d.ts +17 -6
  67. package/dist/auth/token-revocation.d.ts +56 -1
  68. package/dist/auth/user-picking.d.ts +37 -0
  69. package/dist/auth/verify-credential.d.ts +49 -0
  70. package/dist/{auth-B-GIMpDG.js → auth-CSBky1mt.js} +4564 -1929
  71. package/dist/auth-CSBky1mt.js.map +1 -0
  72. package/dist/auth_adapter-CIItCTc8.js +21 -0
  73. package/dist/auth_adapter-CIItCTc8.js.map +1 -0
  74. package/dist/backend/create-backend.d.ts +281 -0
  75. package/dist/backend/default-backend.d.ts +35 -0
  76. package/dist/backend/no-collections.d.ts +19 -0
  77. package/dist/backend/options.d.ts +912 -0
  78. package/dist/backend/scope.d.ts +23 -0
  79. package/dist/backend/services.d.ts +54 -0
  80. package/dist/backend-DTAOsLQc.js.map +1 -1
  81. package/dist/backup/backup-common.d.ts +24 -3
  82. package/dist/backup/backup-routes.d.ts +46 -5
  83. package/dist/backup/backup-schedule.d.ts +33 -0
  84. package/dist/backup/backup-storage.d.ts +16 -0
  85. package/dist/backup/index.d.ts +5 -1
  86. package/dist/backup-DBGRUVTS.js +585 -0
  87. package/dist/backup-DBGRUVTS.js.map +1 -0
  88. package/dist/boot/boot.d.ts +2 -2
  89. package/dist/boot/bundle.d.ts +19 -0
  90. package/dist/boot/driver.d.ts +3 -0
  91. package/dist/boot/env.d.ts +58 -6
  92. package/dist/boot/http-server.d.ts +22 -0
  93. package/dist/boot/options.d.ts +16 -1
  94. package/dist/boot/provision.d.ts +4 -2
  95. package/dist/boot/resource-adapters.d.ts +11 -14
  96. package/dist/boot/runtime-cors.d.ts +17 -0
  97. package/dist/boot/security-headers.d.ts +26 -0
  98. package/dist/boot/sources.d.ts +9 -0
  99. package/dist/boot/static-routing.d.ts +65 -0
  100. package/dist/collection_patch-BRu-BvDv.js +472 -0
  101. package/dist/collection_patch-BRu-BvDv.js.map +1 -0
  102. package/dist/{contract-routes-CbFjuBwa.js → contract-routes-DiFwiLg2.js} +5 -8
  103. package/dist/contract-routes-DiFwiLg2.js.map +1 -0
  104. package/dist/cron/cron-loader.d.ts +77 -18
  105. package/dist/cron/cron-routes.d.ts +1 -1
  106. package/dist/cron/cron-scheduler.d.ts +94 -23
  107. package/dist/cron/cron-store.d.ts +37 -5
  108. package/dist/cron/define-cron.d.ts +6 -2
  109. package/dist/cron/index.d.ts +3 -3
  110. package/dist/cron-loader-NHqRfKF0.js +202 -0
  111. package/dist/cron-loader-NHqRfKF0.js.map +1 -0
  112. package/dist/{cron-routes-eE8nif_b.js → cron-routes-DsH8Jwn5.js} +34 -8
  113. package/dist/cron-routes-DsH8Jwn5.js.map +1 -0
  114. package/dist/{cron-scheduler-B0pLfAix.js → cron-scheduler-D1DT0nw5.js} +349 -65
  115. package/dist/cron-scheduler-D1DT0nw5.js.map +1 -0
  116. package/dist/cron-store-DEpVstUU.js +577 -0
  117. package/dist/cron-store-DEpVstUU.js.map +1 -0
  118. package/dist/{ddl-bootstrap-C6mo0Kmz.js → ddl-bootstrap-BNqi2NZe.js} +2 -2
  119. package/dist/{ddl-bootstrap-C6mo0Kmz.js.map → ddl-bootstrap-BNqi2NZe.js.map} +1 -1
  120. package/dist/email/index.d.ts +2 -2
  121. package/dist/email/smtp-email-service.d.ts +23 -0
  122. package/dist/email/templates.d.ts +22 -0
  123. package/dist/email/types.d.ts +44 -0
  124. package/dist/env.d.ts +1 -39
  125. package/dist/{errors-DWsX4yTd.js → errors-Cqp86KnA.js} +263 -10
  126. package/dist/errors-Cqp86KnA.js.map +1 -0
  127. package/dist/filter-dialect-O1HK6Nvr.js +1628 -0
  128. package/dist/filter-dialect-O1HK6Nvr.js.map +1 -0
  129. package/dist/{function-loader-xnbDAPfa.js → function-loader-DbCSoNJj.js} +69 -39
  130. package/dist/function-loader-DbCSoNJj.js.map +1 -0
  131. package/dist/{function-routes-Chet4-lB.js → function-routes-BSTClQNa.js} +28 -15
  132. package/dist/function-routes-BSTClQNa.js.map +1 -0
  133. package/dist/functions/context.d.ts +26 -6
  134. package/dist/functions/define-function.d.ts +10 -6
  135. package/dist/functions/function-loader.d.ts +75 -18
  136. package/dist/functions/function-routes.d.ts +2 -1
  137. package/dist/functions/guards.d.ts +22 -5
  138. package/dist/functions/index.d.ts +6 -5
  139. package/dist/functions/index.js +148 -50
  140. package/dist/functions/index.js.map +1 -1
  141. package/dist/functions/internal.d.ts +2 -2
  142. package/dist/functions/proxy.d.ts +6 -2
  143. package/dist/functions/request-timeout.d.ts +7 -5
  144. package/dist/history/history-routes.d.ts +9 -0
  145. package/dist/history/retention-sweep.d.ts +81 -0
  146. package/dist/{history-recorder-B4MpJfJK.js → history-recorder-RZ1Ow4YY.js} +4 -4
  147. package/dist/{history-recorder-B4MpJfJK.js.map → history-recorder-RZ1Ow4YY.js.map} +1 -1
  148. package/dist/{history-store-BhxWOuz9.js → history-store-DPofGVDp.js} +2 -2
  149. package/dist/{history-store-BhxWOuz9.js.map → history-store-DPofGVDp.js.map} +1 -1
  150. package/dist/index.d.ts +32 -13
  151. package/dist/index.es.js +25329 -18393
  152. package/dist/index.es.js.map +1 -1
  153. package/dist/init/docs.d.ts +35 -10
  154. package/dist/init/health.d.ts +17 -2
  155. package/dist/init/process-safety.d.ts +7 -2
  156. package/dist/init/shutdown.d.ts +26 -1
  157. package/dist/init.d.ts +49 -811
  158. package/dist/internal-tables-L2t39RdT.js +136 -0
  159. package/dist/internal-tables-L2t39RdT.js.map +1 -0
  160. package/dist/jobs/job-queue.d.ts +8 -0
  161. package/dist/jobs/job-store.d.ts +24 -0
  162. package/dist/jobs/types.d.ts +25 -4
  163. package/dist/{jobs-CazMYhyy.js → jobs-8G-_agld.js} +235 -25
  164. package/dist/jobs-8G-_agld.js.map +1 -0
  165. package/dist/{jwt-DnQHNFCl.js → jwt-CBaFc34Y.js} +546 -447
  166. package/dist/jwt-CBaFc34Y.js.map +1 -0
  167. package/dist/{keys-CogCQpxG.js → keys-COzQ2aE8.js} +3 -17
  168. package/dist/{keys-CogCQpxG.js.map → keys-COzQ2aE8.js.map} +1 -1
  169. package/dist/load-failure-L5hvfDoG.js +80 -0
  170. package/dist/load-failure-L5hvfDoG.js.map +1 -0
  171. package/dist/{logger-DO2PZc4i.js → logger-Bt32sHFw.js} +41 -5
  172. package/dist/logger-Bt32sHFw.js.map +1 -0
  173. package/dist/{logs-routes-Bj4TYYUl.js → logs-routes-DQJhrf-b.js} +48 -8
  174. package/dist/logs-routes-DQJhrf-b.js.map +1 -0
  175. package/dist/mcp/consent-page.d.ts +1 -1
  176. package/dist/mcp/mcp-routes.d.ts +13 -0
  177. package/dist/mcp/mcp-tools.d.ts +23 -9
  178. package/dist/mcp/oauth-metadata.d.ts +21 -16
  179. package/dist/mcp/oauth-routes.d.ts +14 -1
  180. package/dist/metrics/index.d.ts +18 -0
  181. package/dist/metrics/pool-wait.d.ts +50 -0
  182. package/dist/offline-QNQgsPOq-B2yGeXkn.js +3305 -0
  183. package/dist/offline-QNQgsPOq-B2yGeXkn.js.map +1 -0
  184. package/dist/offline-store-Cm5sAX81-CALiO4Gn.js +1915 -0
  185. package/dist/offline-store-Cm5sAX81-CALiO4Gn.js.map +1 -0
  186. package/dist/{openapi-generator-O_O24MAT.js → openapi-generator-CeGW2dLC.js} +455 -80
  187. package/dist/openapi-generator-CeGW2dLC.js.map +1 -0
  188. package/dist/policy-PdzX-Opd.js +121 -0
  189. package/dist/policy-PdzX-Opd.js.map +1 -0
  190. package/dist/project_manifest-R6y_KHms.js +59 -0
  191. package/dist/project_manifest-R6y_KHms.js.map +1 -0
  192. package/dist/{proxy-Czngl3p9.js → proxy-BEKbsil6.js} +5 -4
  193. package/dist/proxy-BEKbsil6.js.map +1 -0
  194. package/dist/{query-parser-DGRVFNM3.js → query-parser-ECxP0nDw.js} +497 -52
  195. package/dist/query-parser-ECxP0nDw.js.map +1 -0
  196. package/dist/{request-timeout-C_4C2BeR.js → request-timeout-BiRelY-U.js} +17 -10
  197. package/dist/request-timeout-BiRelY-U.js.map +1 -0
  198. package/dist/resumable-upload-threshold-CKv-Ry0l.js +24 -0
  199. package/dist/resumable-upload-threshold-CKv-Ry0l.js.map +1 -0
  200. package/dist/schema-edit/apply-schema-change.d.ts +73 -3
  201. package/dist/schema-edit/local-source.d.ts +29 -0
  202. package/dist/schema-edit/project-root.d.ts +3 -2
  203. package/dist/schema-edit/remote-source.d.ts +20 -5
  204. package/dist/schema-edit/source-editor.d.ts +28 -0
  205. package/dist/schema-editor-routes-DRXBAbZy.js +136 -0
  206. package/dist/schema-editor-routes-DRXBAbZy.js.map +1 -0
  207. package/dist/serve-spa.d.ts +58 -0
  208. package/dist/services/outbound-post.d.ts +50 -0
  209. package/dist/services/outbound-url-guard.d.ts +34 -5
  210. package/dist/services/routed-realtime-service.d.ts +16 -0
  211. package/dist/services/webhook-service.d.ts +66 -14
  212. package/dist/singleton.d.ts +9 -5
  213. package/dist/soft-delete-params-CBryZXMq.js +668 -0
  214. package/dist/soft-delete-params-CBryZXMq.js.map +1 -0
  215. package/dist/sql-export-routes-D8ZaRvoR.js +240 -0
  216. package/dist/sql-export-routes-D8ZaRvoR.js.map +1 -0
  217. package/dist/{src-vkcwKXbT.js → src-D5Y3N-S4.js} +440 -20
  218. package/dist/src-D5Y3N-S4.js.map +1 -0
  219. package/dist/storage/GCSStorageController.d.ts +48 -2
  220. package/dist/storage/LocalStorageController.d.ts +69 -2
  221. package/dist/storage/S3StorageController.d.ts +39 -3
  222. package/dist/storage/image-transform.d.ts +15 -0
  223. package/dist/storage/index.d.ts +2 -2
  224. package/dist/storage/maintenance-routes.d.ts +23 -0
  225. package/dist/storage/maintenance.d.ts +126 -0
  226. package/dist/storage/property-limits.d.ts +41 -6
  227. package/dist/storage/rendition-cache.d.ts +9 -11
  228. package/dist/storage/request-keys.d.ts +15 -0
  229. package/dist/storage/request-principals.d.ts +30 -0
  230. package/dist/storage/requested-object.d.ts +74 -0
  231. package/dist/storage/routes.d.ts +52 -14
  232. package/dist/storage/source-variables.d.ts +10 -0
  233. package/dist/storage/streams.d.ts +42 -0
  234. package/dist/storage/tus-handler.d.ts +76 -7
  235. package/dist/storage/types.d.ts +101 -7
  236. package/dist/storage/upload-validation.d.ts +13 -0
  237. package/dist/storage-resumable-VO-cMpse-C_YX71pO.js +125 -0
  238. package/dist/storage-resumable-VO-cMpse-C_YX71pO.js.map +1 -0
  239. package/dist/strings.es-BeZWAodn.js +256 -0
  240. package/dist/strings.es-BeZWAodn.js.map +1 -0
  241. package/dist/upload-validation-CVobI5xO.js +172 -0
  242. package/dist/upload-validation-CVobI5xO.js.map +1 -0
  243. package/dist/utils/byte-size.d.ts +10 -0
  244. package/dist/utils/compression.d.ts +6 -0
  245. package/dist/utils/load-failure.d.ts +14 -0
  246. package/dist/utils/logger.d.ts +21 -0
  247. package/package.json +5 -5
  248. package/dist/GCSStorageController-CjrA4PMo.js +0 -242
  249. package/dist/GCSStorageController-CjrA4PMo.js.map +0 -1
  250. package/dist/S3StorageController-B6pKDNVj.js +0 -264
  251. package/dist/S3StorageController-B6pKDNVj.js.map +0 -1
  252. package/dist/admin-roles-vYdp_Pil.js +0 -36
  253. package/dist/admin-roles-vYdp_Pil.js.map +0 -1
  254. package/dist/admin_block-DxKLmdiv.js +0 -206
  255. package/dist/admin_block-DxKLmdiv.js.map +0 -1
  256. package/dist/ast-schema-editor-Mvr50v_S.js.map +0 -1
  257. package/dist/auth/api-keys/api-key-permission-guard.d.ts +0 -65
  258. package/dist/auth-B-GIMpDG.js.map +0 -1
  259. package/dist/backup-D7YR94N3.js +0 -253
  260. package/dist/backup-D7YR94N3.js.map +0 -1
  261. package/dist/contract-routes-CbFjuBwa.js.map +0 -1
  262. package/dist/cron-loader-DfTj2Hbi.js +0 -118
  263. package/dist/cron-loader-DfTj2Hbi.js.map +0 -1
  264. package/dist/cron-routes-eE8nif_b.js.map +0 -1
  265. package/dist/cron-scheduler-B0pLfAix.js.map +0 -1
  266. package/dist/cron-store-TcoGz-xS.js +0 -305
  267. package/dist/cron-store-TcoGz-xS.js.map +0 -1
  268. package/dist/dynamic-import-X40pTZUQ.js +0 -23
  269. package/dist/dynamic-import-X40pTZUQ.js.map +0 -1
  270. package/dist/errors-DWsX4yTd.js.map +0 -1
  271. package/dist/function-loader-xnbDAPfa.js.map +0 -1
  272. package/dist/function-routes-Chet4-lB.js.map +0 -1
  273. package/dist/jobs-CazMYhyy.js.map +0 -1
  274. package/dist/jwt-DnQHNFCl.js.map +0 -1
  275. package/dist/logger-DO2PZc4i.js.map +0 -1
  276. package/dist/logs-routes-Bj4TYYUl.js.map +0 -1
  277. package/dist/openapi-generator-O_O24MAT.js.map +0 -1
  278. package/dist/proxy-Czngl3p9.js.map +0 -1
  279. package/dist/query-parser-DGRVFNM3.js.map +0 -1
  280. package/dist/request-timeout-C_4C2BeR.js.map +0 -1
  281. package/dist/schema-editor-routes-C5-lh_jO.js +0 -87
  282. package/dist/schema-editor-routes-C5-lh_jO.js.map +0 -1
  283. package/dist/src-pmvW7BFx.js +0 -6688
  284. package/dist/src-pmvW7BFx.js.map +0 -1
  285. package/dist/src-vkcwKXbT.js.map +0 -1
  286. package/dist/types-BfKcm9do.js +0 -39
  287. package/dist/types-BfKcm9do.js.map +0 -1
@@ -1,4 +1,4 @@
1
- import { CollectionConfig, FieldOperation, FIELD_OPERATORS, isFieldOperation } from "@rebasepro/types";
1
+ import { CollectionConfig, FieldOperation, FIELD_OPERATORS, isFieldOperation, Properties, Property } from "@rebasepro/types";
2
2
  /**
3
3
  * Writes expressed as a change to the column's current value.
4
4
  *
@@ -38,23 +38,32 @@ export interface ParsedFieldOp {
38
38
  * this package.
39
39
  */
40
40
  export declare const looksLikeFieldOp: typeof isFieldOperation;
41
- /** True when any value in the payload is (or is attempting to be) an operation. */
42
- export declare function hasFieldOps(values: Record<string, unknown> | undefined): boolean;
41
+ /**
42
+ * True when any value in the payload is (or is attempting to be) an operation.
43
+ * With `properties`, a `map` value is one only when it is `{ <operator>: … }`;
44
+ * see `isFieldOperation`.
45
+ */
46
+ export declare function hasFieldOps(values: Record<string, unknown> | undefined, properties?: Properties): boolean;
43
47
  /**
44
48
  * Parse one value as an operation, or report why it is not a legal one.
45
49
  *
46
50
  * Returns `undefined` for an ordinary value. Throws for a value that reads as
47
- * an operation and is malformed, so a typo cannot be written as data.
51
+ * an operation and is malformed, so a typo cannot be written as data. Given
52
+ * the field's `property`, a `map` value with `$` keys is data unless it is a
53
+ * single real operator.
48
54
  */
49
- export declare function parseFieldOp(key: string, value: unknown): ParsedFieldOp | undefined;
55
+ export declare function parseFieldOp(key: string, value: unknown, property?: Property): ParsedFieldOp | undefined;
50
56
  /**
51
57
  * Split a write payload into ordinary values and field operations.
52
58
  *
53
59
  * The two travel together in one body — `{ title: "x", views: { $inc: 1 } }` is
54
60
  * a single `PATCH` — and are applied in one statement, so a caller never has to
55
61
  * choose between setting a column and incrementing another.
62
+ *
63
+ * Pass the collection's `properties`: without them a `map` value carrying `$`
64
+ * keys reads as a (malformed) operation rather than as the document it is.
56
65
  */
57
- export declare function splitFieldOps(values: Record<string, unknown>): {
66
+ export declare function splitFieldOps(values: Record<string, unknown>, properties?: Properties): {
58
67
  values: Record<string, unknown>;
59
68
  fieldOps: Record<string, ParsedFieldOp>;
60
69
  };
@@ -79,8 +88,11 @@ export declare function assertFieldOpsValid(values: Record<string, unknown>, col
79
88
  * are meaningless on an insert — `views + 1` over a row that does not exist yet
80
89
  * is just `1`, and accepting it would make `$inc` mean two different things
81
90
  * depending on a race the caller cannot observe.
91
+ *
92
+ * `collection` decides how a `map` value is read (see {@link splitFieldOps});
93
+ * every request boundary has it and passes it.
82
94
  */
83
- export declare function assertNoFieldOpsOnCreate(values: Record<string, unknown>, context: string): void;
95
+ export declare function assertNoFieldOpsOnCreate(values: Record<string, unknown>, context: string, collection?: CollectionConfig): void;
84
96
  /** Re-exported so callers type against one definition of the wire shape. */
85
97
  export type { FieldOperation };
86
98
  export { FIELD_OPERATORS };
@@ -0,0 +1,10 @@
1
+ /**
2
+ * The body of a `POST /<collection>/query`, as the dictionary the `GET`
3
+ * listing reads off its URL.
4
+ *
5
+ * @param body the parsed JSON body — already known to be an object
6
+ * @param urlParams the names of any parameters on the request's own URL, which
7
+ * are refused: two places to put one parameter is two answers to "which one
8
+ * applied", and the listing would silently answer one of them
9
+ */
10
+ export declare function queryDictFromBody(body: Record<string, unknown>, urlParams?: readonly string[]): Record<string, string[]>;
@@ -1,4 +1,4 @@
1
- import { CollectionConfig } from "@rebasepro/types";
1
+ import { CollectionConfig, type IncludeSpec } from "@rebasepro/types";
2
2
  import { type FieldViewer } from "@rebasepro/common";
3
3
  /**
4
4
  * Refuse a write naming a field this caller may not set.
@@ -91,11 +91,13 @@ export interface WriteViolation {
91
91
  * from that spec believed the server was checking. Enum membership, `email` and
92
92
  * `url` were published the same way and enforced the same amount.
93
93
  *
94
- * `required` is checked only when `options.status` says this write creates a
95
- * row. A `PATCH` legitimately omits keys — this function sees one request's
96
- * values, not the row they land on — so demanding them there would refuse every
97
- * partial update. Callers that do not pass a status get the old behaviour and
98
- * leave `required` to the database's NOT NULL.
94
+ * A missing `required` key is checked only when `options.status` says this
95
+ * write creates a row. A `PATCH` legitimately omits keys — this function sees
96
+ * one request's values, not the row they land on — so demanding them there
97
+ * would refuse every partial update. Callers that do not pass a status get the
98
+ * old behaviour and leave a missing key to the database's NOT NULL. A required
99
+ * string sent as `""` is refused on every write, whatever the status: NOT NULL
100
+ * would store it.
99
101
  *
100
102
  * A value of the wrong *type* is not this function's business — that reaches
101
103
  * Postgres, which types every column and gives a 400 through the driver's
@@ -151,7 +153,7 @@ export declare function assertWriteValuesValid(values: Record<string, unknown>,
151
153
  * assuming one.
152
154
  */
153
155
  export declare function projectResponseFields<T extends Record<string, unknown>>(rows: T[], fields: readonly string[] | undefined, collection: CollectionConfig, options?: {
154
- include?: readonly string[];
156
+ include?: IncludeSpec;
155
157
  }): T[];
156
158
  /**
157
159
  * Both write checks, as one call, for a transport that is not the REST router.
@@ -1,3 +1,12 @@
1
1
  import { Hono } from "hono";
2
+ import { SchemaSourceEditor } from "../schema-edit/source-editor.js";
2
3
  import { HonoEnv } from "./types.js";
3
- export declare function createSchemaEditorRoutes(collectionsDir: string): Hono<HonoEnv>;
4
+ /**
5
+ * The editor for `collectionsDir`: one per process, shared by these routes
6
+ * and live schema editing's `/apply`. See {@link SchemaSourceEditor}.
7
+ *
8
+ * Here rather than beside the class because building it loads `ts-morph`, an
9
+ * optional peer dependency this module is imported to probe for.
10
+ */
11
+ export declare function createSchemaSourceEditor(collectionsDir: string): SchemaSourceEditor;
12
+ export declare function createSchemaEditorRoutes(source: SchemaSourceEditor): Hono<HonoEnv>;
@@ -0,0 +1,8 @@
1
+ import { Hono } from "hono";
2
+ import { type DatabaseAdmin } from "@rebasepro/types";
3
+ import type { HonoEnv } from "./types.js";
4
+ export interface SqlExportRoutesOptions {
5
+ /** The default data source's admin capabilities, read per request. */
6
+ getAdmin: () => DatabaseAdmin | undefined;
7
+ }
8
+ export declare function createSqlExportRoutes(options: SqlExportRoutesOptions): Hono<HonoEnv>;
@@ -2,6 +2,7 @@ import type { VectorSearchParams, IncludeSpec, LogicalCondition, FilterValues, N
2
2
  import type { DecodedCursor } from "@rebasepro/common";
3
3
  import type { AuthResult } from "../auth/middleware.js";
4
4
  import type { ApiKeyMasked } from "../auth/api-keys/api-key-types.js";
5
+ import type { BackendServices } from "../backend/services.js";
5
6
  /**
6
7
  * Hono Environment Variables
7
8
  * Passed to generic Hono<HonoEnv> to type `c.get()`
@@ -31,10 +32,32 @@ export type HonoEnv = {
31
32
  */
32
33
  recipientEmail?: string;
33
34
  driver?: DataDriver;
34
- /** Set when the request is authenticated via a Service API Key. */
35
+ /** Set when the request is authenticated with an API key (`rk_`). */
35
36
  apiKey?: ApiKeyMasked;
37
+ /**
38
+ * What the caller may do, when its credential is narrower than its
39
+ * person — an API key or an MCP token. Absent for a person's own
40
+ * session, who holds what their roles hold. Read it through
41
+ * `callerScopes` / `hasScope`, which fill in that second case.
42
+ */
43
+ scopes?: string[];
44
+ /**
45
+ * Who asked, when an administrator's request runs as another user
46
+ * (`x-rebase-impersonate`). `user` and `driver` are then the
47
+ * impersonated user's; this is the only place the administrator
48
+ * remains. See `auth/impersonation.ts`.
49
+ */
50
+ impersonator?: {
51
+ uid: string;
52
+ };
36
53
  /** Unique request correlation ID (generated or propagated from X-Request-ID header). */
37
54
  requestId?: string;
55
+ /**
56
+ * Aborted when a custom function's request outlasts its timeout and is
57
+ * answered 504. Read it with `getSignal(c)` from
58
+ * `@rebasepro/server/functions`. See `functions/request-timeout.ts`.
59
+ */
60
+ functionSignal?: AbortSignal;
38
61
  /**
39
62
  * The collection this request is about, when it is about one.
40
63
  *
@@ -81,6 +104,29 @@ export type HonoEnv = {
81
104
  client: string;
82
105
  server: string;
83
106
  };
107
+ /**
108
+ * Where the route group answering this request is mounted, e.g.
109
+ * `/api/data` or `/api/functions` — wherever the application put it.
110
+ *
111
+ * Set by the group's first middleware from the request itself, so a
112
+ * group mounted at a path of the caller's choosing still knows which
113
+ * collection a path names and which URL a function answers at. See
114
+ * `api/mount-path.ts`.
115
+ */
116
+ mountPath?: string;
117
+ /**
118
+ * The backend answering this request: its JWT keys, access model,
119
+ * API-key store and rate limiters.
120
+ *
121
+ * Set by the first middleware of every route group a backend builds,
122
+ * so a group verifies tokens and checks scopes against its own
123
+ * backend wherever it is mounted, and two backends mounted on one app
124
+ * stay two. Absent on a route mounted outside any backend, where the
125
+ * helpers that read it (`requireScope`, `callerScopes`, `requireAuth`,
126
+ * …) answer for the process default. Set it yourself to make such a
127
+ * route answer for a backend: `c.set("services", backend.services)`.
128
+ */
129
+ services?: BackendServices;
84
130
  };
85
131
  };
86
132
  /**
@@ -141,4 +187,9 @@ export interface QueryOptions {
141
187
  * the point of the feature.
142
188
  */
143
189
  withDeleted?: boolean | "only";
190
+ /**
191
+ * From `?count=exact|none` — whether a listing counts `meta.total`. See
192
+ * `FindParams.count`. Unset is `"exact"`.
193
+ */
194
+ count?: "exact" | "none";
144
195
  }
@@ -2,8 +2,8 @@ import { createRequire as __rebaseCreateRequire } from "module";
2
2
  import __rebaseProcess from "process";
3
3
  globalThis.process ??= __rebaseProcess;
4
4
  __rebaseCreateRequire(import.meta.url);
5
- import "./src-vkcwKXbT.js";
6
- import { i as nestAdminPropertyKeys, r as nestAdminCollectionKeys } from "./admin_block-DxKLmdiv.js";
5
+ import "./src-D5Y3N-S4.js";
6
+ import { a as nestCollectionPatchPaths, l as nestAdminCollectionKeys, n as collectionPatchProblems, o as runtimeOnlyKeyIn, u as nestAdminPropertyKeys } from "./collection_patch-BRu-BvDv.js";
7
7
  import * as fs$1 from "fs";
8
8
  import * as path$1 from "path";
9
9
  import { IndentationText, Node, Project, SyntaxKind } from "ts-morph";
@@ -130,6 +130,26 @@ var AstSchemaEditor = class AstSchemaEditor {
130
130
  if (!resolved.startsWith(this.collectionsDir + path$1.sep) && resolved !== this.collectionsDir) throw new Error("Path traversal detected: resolved path is outside the collections directory.");
131
131
  return resolved;
132
132
  }
133
+ /**
134
+ * Bring the in-memory project back in line with the disk.
135
+ *
136
+ * The source-only editor builds ONE of these at boot and serves every
137
+ * request with it, and ts-morph reads a file once and then trusts its
138
+ * copy. `project.save()` writes that copy back whole, so anything that
139
+ * reached the disk in between — the live editor's committed change (it
140
+ * builds a fresh editor per request), or the developer's own edit in the
141
+ * IDE — was overwritten by the boot-time copy plus this edit. In `rebase
142
+ * dev` the tsx-watch restart usually closed the window; outside watch mode
143
+ * the loss was permanent.
144
+ *
145
+ * Called at the top of every public method, so each edit applies to what
146
+ * is on disk now.
147
+ */
148
+ syncWithDisk() {
149
+ for (const file of this.project.getSourceFiles()) if (!fs$1.existsSync(file.getFilePath())) this.project.removeSourceFile(file);
150
+ else file.refreshFromFileSystemSync();
151
+ if (fs$1.existsSync(this.collectionsDir)) this.project.addSourceFilesAtPaths(`${this.collectionsDir}/**/*.ts`);
152
+ }
133
153
  getCollectionFile(collectionId) {
134
154
  const safeId = this.sanitizeCollectionId(collectionId);
135
155
  const filePath = this.safePath(`${safeId}.ts`);
@@ -212,6 +232,39 @@ var AstSchemaEditor = class AstSchemaEditor {
212
232
  static isPlainObject(value) {
213
233
  return typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof RawExpression);
214
234
  }
235
+ /**
236
+ * The field that identifies each element of an array of objects, so an
237
+ * old element can be found again in the new array.
238
+ *
239
+ * Arrays travel whole (see `collection_patch.ts`), and an element's
240
+ * position is not its identity — adding an entity action at the front
241
+ * shifts every other one. `key`, `id`, `relationName`, `name`: the first
242
+ * one every new element carries, distinct, wins.
243
+ */
244
+ static identityField(items) {
245
+ for (const field of [
246
+ "key",
247
+ "id",
248
+ "relationName",
249
+ "name",
250
+ "slug"
251
+ ]) {
252
+ const values = items.map((item) => AstSchemaEditor.isPlainObject(item) ? item[field] : void 0);
253
+ if (values.every((value) => typeof value === "string" || typeof value === "number") && new Set(values).size === values.length) return field;
254
+ }
255
+ }
256
+ /** The old array's object elements, keyed by `field`'s literal value. */
257
+ oldElementsBy(oldArray, field) {
258
+ const byKey = /* @__PURE__ */ new Map();
259
+ const array = oldArray.asKind(SyntaxKind.ArrayLiteralExpression);
260
+ for (const element of array?.getElements() ?? []) {
261
+ const object = element.asKind(SyntaxKind.ObjectLiteralExpression);
262
+ const value = object ? this.initializerOf(object, field) : void 0;
263
+ const literal = value?.asKind(SyntaxKind.StringLiteral)?.getLiteralValue() ?? value?.asKind(SyntaxKind.NoSubstitutionTemplateLiteral)?.getLiteralValue() ?? value?.asKind(SyntaxKind.NumericLiteral)?.getText();
264
+ if (object && literal !== void 0) byKey.set(String(literal), object);
265
+ }
266
+ return byKey;
267
+ }
215
268
  convertJsonToAstString(obj, indentLevel = 0, oldAstNode) {
216
269
  const indentStr = " ";
217
270
  const indent = indentStr.repeat(indentLevel);
@@ -222,14 +275,17 @@ var AstSchemaEditor = class AstSchemaEditor {
222
275
  if (typeof obj === "number" || typeof obj === "boolean") return String(obj);
223
276
  if (Array.isArray(obj)) {
224
277
  if (obj.length === 0) return "[]";
225
- return `[\n${innerIndent}${obj.map((item) => this.convertJsonToAstString(item, indentLevel + 1)).join(`,\n${innerIndent}`)}\n${indent}]`;
278
+ const field = oldAstNode ? AstSchemaEditor.identityField(obj) : void 0;
279
+ const old = oldAstNode && field ? this.oldElementsBy(oldAstNode, field) : void 0;
280
+ return `[\n${innerIndent}${obj.map((item) => this.convertJsonToAstString(item, indentLevel + 1, old && field && AstSchemaEditor.isPlainObject(item) ? old.get(String(item[field])) : void 0)).join(`,\n${innerIndent}`)}\n${indent}]`;
226
281
  }
227
282
  if (typeof obj === "object") {
228
283
  const record = obj;
229
284
  const keys = Object.keys(record);
285
+ const oldObject = oldAstNode?.asKind(SyntaxKind.ObjectLiteralExpression);
230
286
  const preservedProps = [];
231
- if (oldAstNode) {
232
- const oldProps = oldAstNode.getProperties();
287
+ if (oldObject) {
288
+ const oldProps = oldObject.getProperties();
233
289
  for (const oldProp of oldProps) if (oldProp.isKind(SyntaxKind.PropertyAssignment)) {
234
290
  let name = oldProp.getNameNode().getText();
235
291
  if (name.startsWith("\"") && name.endsWith("\"")) name = name.slice(1, -1);
@@ -247,9 +303,9 @@ var AstSchemaEditor = class AstSchemaEditor {
247
303
  return `{\n${innerIndent}${[...keys.map((key) => {
248
304
  const keyStr = AstSchemaEditor.quoteKey(key);
249
305
  let childAstNode;
250
- if (oldAstNode && AstSchemaEditor.isPlainObject(record[key])) {
251
- const oldProp = this.findProperty(oldAstNode, key);
252
- if (oldProp && oldProp.isKind(SyntaxKind.PropertyAssignment)) childAstNode = oldProp.getInitializerIfKind(SyntaxKind.ObjectLiteralExpression);
306
+ if (oldObject && (AstSchemaEditor.isPlainObject(record[key]) || Array.isArray(record[key]))) {
307
+ const oldProp = this.findProperty(oldObject, key);
308
+ if (oldProp && oldProp.isKind(SyntaxKind.PropertyAssignment)) childAstNode = oldProp.getInitializerIfKind(SyntaxKind.ObjectLiteralExpression) ?? oldProp.getInitializerIfKind(SyntaxKind.ArrayLiteralExpression);
253
309
  }
254
310
  return `${keyStr}: ${this.convertJsonToAstString(record[key], indentLevel + 1, childAstNode)}`;
255
311
  }), ...preservedProps].join(`,\n${innerIndent}`)}\n${indent}}`;
@@ -296,6 +352,7 @@ var AstSchemaEditor = class AstSchemaEditor {
296
352
  else this.project.removeSourceFile(file);
297
353
  }
298
354
  async saveProperty(collectionId, propertyKey, propertyConfig) {
355
+ this.syncWithDisk();
299
356
  try {
300
357
  await this.writeProperty(collectionId, propertyKey, propertyConfig);
301
358
  } catch (err) {
@@ -330,6 +387,7 @@ var AstSchemaEditor = class AstSchemaEditor {
330
387
  }
331
388
  }
332
389
  async deleteProperty(collectionId, propertyKey) {
390
+ this.syncWithDisk();
333
391
  const propertiesProp = this.requireCollectionObject(collectionId).getProperty("properties");
334
392
  if (propertiesProp) {
335
393
  const propsObj = propertiesProp.getInitializerIfKind(SyntaxKind.ObjectLiteralExpression);
@@ -356,6 +414,9 @@ var AstSchemaEditor = class AstSchemaEditor {
356
414
  * default, which in the scaffold is `access: "public"`.
357
415
  */
358
416
  async saveCollection(collectionId, collectionData, options = {}) {
417
+ this.syncWithDisk();
418
+ const runtimeOnly = runtimeOnlyKeyIn(collectionData);
419
+ if (runtimeOnly) throw new Error(`Refusing to write "${collectionId}": it holds "${runtimeOnly}", which the runtime adds to a collection it has loaded and a collection file must not hold. Save what changed instead.`);
359
420
  try {
360
421
  await this.writeCollection(collectionId, collectionData, options);
361
422
  } catch (err) {
@@ -363,6 +424,180 @@ var AstSchemaEditor = class AstSchemaEditor {
363
424
  throw err;
364
425
  }
365
426
  }
427
+ /**
428
+ * Write what changed about a collection, and nothing else.
429
+ *
430
+ * The patch is the difference between the collection the panel loaded and
431
+ * the one it is saving (`diffCollections` in `@rebasepro/types`), so it
432
+ * names only keys the person changed. Each is written where it is; every
433
+ * other byte of the file — imports, comments, formatting, handlers,
434
+ * properties shared from another module — is left as it was.
435
+ *
436
+ * A key whose value in the file is code — `status: statusProperty`,
437
+ * `enum: LOCALE_ENUM`, a spread — is not overwritten with the JSON the
438
+ * panel saw of it, which is what the whole-collection save did. The edit
439
+ * is refused, naming the code, so it can be made where that code lives.
440
+ *
441
+ * All or nothing: a refused operation leaves the file as it was, including
442
+ * the operations before it.
443
+ */
444
+ async applyPatch(collectionId, patch) {
445
+ this.syncWithDisk();
446
+ const problems = collectionPatchProblems(patch);
447
+ if (problems.length > 0) throw new Error(`Refusing to edit "${collectionId}": ${problems.join(" ")}`);
448
+ try {
449
+ await this.writePatch(collectionId, nestCollectionPatchPaths(patch));
450
+ } catch (err) {
451
+ this.forgetUnsaved(collectionId);
452
+ throw err;
453
+ }
454
+ }
455
+ async writePatch(collectionId, patch) {
456
+ this.requireCollectionObject(collectionId);
457
+ const file = this.getCollectionFile(collectionId);
458
+ for (const op of patch) this.applyPatchOp(collectionId, file, this.requireCollectionObject(collectionId), op);
459
+ await this.project.save();
460
+ }
461
+ applyPatchOp(collectionId, file, collectionObj, op) {
462
+ const { path: keys } = op;
463
+ const dotted = (end) => keys.slice(0, end).join(".");
464
+ let target = collectionObj;
465
+ for (let depth = 0; depth < keys.length - 1; depth++) {
466
+ const key = keys[depth];
467
+ const member = this.findMember(target, key);
468
+ if (!member) {
469
+ const spread = this.spreadIn(target);
470
+ if (spread) throw this.codeRefusal(collectionId, file, dotted(depth + 1), spread, "comes from");
471
+ if (op.op === "remove") return;
472
+ target = target.addPropertyAssignment({
473
+ name: AstSchemaEditor.quoteKey(key),
474
+ initializer: "{}"
475
+ }).getInitializerIfKindOrThrow(SyntaxKind.ObjectLiteralExpression);
476
+ continue;
477
+ }
478
+ const value = member.isKind(SyntaxKind.PropertyAssignment) ? AstSchemaEditor.unwrapExpression(member.getInitializer()) : void 0;
479
+ const next = value?.asKind(SyntaxKind.ObjectLiteralExpression);
480
+ if (!next) throw this.codeRefusal(collectionId, file, dotted(depth + 1), value ?? member, "is");
481
+ target = next;
482
+ }
483
+ const key = keys[keys.length - 1];
484
+ const member = this.findMember(target, key);
485
+ if (op.op === "remove") {
486
+ if (member) {
487
+ member.remove();
488
+ return;
489
+ }
490
+ const spread = this.spreadIn(target);
491
+ if (spread) throw this.codeRefusal(collectionId, file, dotted(keys.length), spread, "comes from");
492
+ return;
493
+ }
494
+ if (keys.length === 1 && key === "relations") {
495
+ this.writeRelations(collectionId, file, collectionObj, op.value);
496
+ this.findMember(collectionObj, "relations")?.formatText();
497
+ return;
498
+ }
499
+ const old = member?.isKind(SyntaxKind.PropertyAssignment) ? member.getInitializer() : void 0;
500
+ if (member && (!old || !AstSchemaEditor.isLiteralValue(old))) throw this.codeRefusal(collectionId, file, dotted(keys.length), old ?? member, "is");
501
+ if (old?.isKind(SyntaxKind.ArrayLiteralExpression)) {
502
+ const code = old.getElements().find((element) => !AstSchemaEditor.isLiteralValue(element));
503
+ if (code) throw this.codeRefusal(collectionId, file, dotted(keys.length), code, "holds");
504
+ }
505
+ const value = this.patchValueAsSource(collectionId, file, keys, op.value, old);
506
+ const text = this.convertJsonToAstString(value, 0, old);
507
+ (member?.isKind(SyntaxKind.PropertyAssignment) ? member.setInitializer(text) : target.addPropertyAssignment({
508
+ name: AstSchemaEditor.quoteKey(key),
509
+ initializer: text
510
+ })).formatText();
511
+ }
512
+ /**
513
+ * A patched value with every relation target it carries turned back into
514
+ * source — `() => xCollection` plus its import — according to where the
515
+ * path lands: a relation's `target`, a relation, a property, or a map of
516
+ * properties.
517
+ */
518
+ patchValueAsSource(collectionId, file, keys, value, old) {
519
+ let position = "collection";
520
+ for (const key of keys) switch (position) {
521
+ case "collection":
522
+ position = key === "properties" ? "properties" : "other";
523
+ break;
524
+ case "properties":
525
+ position = "property";
526
+ break;
527
+ case "property":
528
+ position = key === "properties" ? "properties" : key === "of" ? "property" : key === "oneOf" ? "oneOf" : key === "relation" ? "relation" : "other";
529
+ break;
530
+ case "oneOf":
531
+ position = key === "properties" ? "properties" : "other";
532
+ break;
533
+ case "relation":
534
+ if (key === "target" && typeof value === "string" && value.trim().length > 0) {
535
+ const trimmed = value.trim();
536
+ return new RawExpression(ARROW_TO_IDENTIFIER.test(trimmed) ? trimmed : this.targetThunk(file, trimmed));
537
+ }
538
+ position = "other";
539
+ break;
540
+ default: position = "other";
541
+ }
542
+ const oldObject = old?.asKind(SyntaxKind.ObjectLiteralExpression);
543
+ const where = keys.join(".");
544
+ if (position === "property") return this.propertyRelationTargetsAsSource(collectionId, file, value, oldObject, where);
545
+ if (position === "properties") return this.relationTargetsAsSource(collectionId, file, value, oldObject, where);
546
+ if (position === "relation" && AstSchemaEditor.isPlainObject(value)) {
547
+ const wrapped = this.propertyRelationTargetsAsSource(collectionId, file, {
548
+ type: "relation",
549
+ relation: value
550
+ }, oldObject ? void 0 : void 0, where);
551
+ const relation = AstSchemaEditor.isPlainObject(wrapped) ? wrapped.relation : value;
552
+ if (AstSchemaEditor.isPlainObject(relation) && !("target" in relation) && !(oldObject && this.findProperty(oldObject, "target"))) throw new Error(`Relation "${where}" on collection "${collectionId}" has no target collection. Pick one before saving.`);
553
+ return relation;
554
+ }
555
+ return value;
556
+ }
557
+ /** A member of an object literal by name: an assignment, a shorthand, a method. */
558
+ findMember(obj, name) {
559
+ return obj.getProperties().find((member) => {
560
+ if (member.isKind(SyntaxKind.SpreadAssignment)) return false;
561
+ const memberName = member.getName();
562
+ return memberName === name || memberName === JSON.stringify(name) || memberName === `'${name}'`;
563
+ });
564
+ }
565
+ spreadIn(obj) {
566
+ return obj.getProperties().find((member) => member.isKind(SyntaxKind.SpreadAssignment));
567
+ }
568
+ /** `x as const`, `x satisfies T`, `(x)` — the value inside. */
569
+ static unwrapExpression(node) {
570
+ if (!node) return void 0;
571
+ if (Node.isAsExpression(node) || Node.isSatisfiesExpression(node) || Node.isParenthesizedExpression(node)) return AstSchemaEditor.unwrapExpression(node.getExpression());
572
+ return node;
573
+ }
574
+ /**
575
+ * Whether a value in the file is data the panel's JSON fully describes —
576
+ * a literal — rather than code it saw only the result of.
577
+ */
578
+ static isLiteralValue(node) {
579
+ const value = AstSchemaEditor.unwrapExpression(node);
580
+ if (!value) return false;
581
+ switch (value.getKind()) {
582
+ case SyntaxKind.ObjectLiteralExpression:
583
+ case SyntaxKind.ArrayLiteralExpression:
584
+ case SyntaxKind.StringLiteral:
585
+ case SyntaxKind.NoSubstitutionTemplateLiteral:
586
+ case SyntaxKind.NumericLiteral:
587
+ case SyntaxKind.TrueKeyword:
588
+ case SyntaxKind.FalseKeyword:
589
+ case SyntaxKind.NullKeyword: return true;
590
+ case SyntaxKind.PrefixUnaryExpression: return value.asKindOrThrow(SyntaxKind.PrefixUnaryExpression).getOperand().isKind(SyntaxKind.NumericLiteral);
591
+ case SyntaxKind.Identifier: return value.getText() === "undefined";
592
+ default: return false;
593
+ }
594
+ }
595
+ /** "`properties.status` is `statusProperty` in posts.ts…" */
596
+ codeRefusal(collectionId, file, where, code, verb) {
597
+ const text = code.getText();
598
+ const shown = text.length > 60 ? `${text.slice(0, 57)}…` : text;
599
+ return /* @__PURE__ */ new Error(`\`${where}\` ${verb} \`${shown}\` in ${path$1.basename(file.getFilePath())}, which is code the editor cannot change without losing what it does. Change "${collectionId}" there instead.`);
600
+ }
366
601
  async writeCollection(collectionId, collectionData, options) {
367
602
  const partial = options.partial === true;
368
603
  let file = this.getCollectionFile(collectionId);
@@ -554,12 +789,75 @@ var AstSchemaEditor = class AstSchemaEditor {
554
789
  if (expr && expr.getKind() === SyntaxKind.Identifier) return expr.getText();
555
790
  for (const varDecl of file.getVariableDeclarations()) if (this.unwrapCollectionObject(varDecl.getInitializer())) return varDecl.getName();
556
791
  }
792
+ /**
793
+ * Delete a collection's file, and take it out of `index.ts`.
794
+ *
795
+ * This used to unlink `<slug>.ts` and stop. Every scaffold's `index.ts`
796
+ * imports every collection, so the next boot failed for the whole project
797
+ * ("Cannot find module …/tags"), and a collection another one linked to
798
+ * took that one down with it. Now:
799
+ *
800
+ * - a file any other module imports is refused, naming the importers — a
801
+ * relation to it has to go first, and that is a change the person makes;
802
+ * - `index.ts`'s import of it is removed, and so is each place it is listed
803
+ * in an array; any other use there is refused, naming the line;
804
+ * - the file is deleted.
805
+ *
806
+ * Only the source. The table and its rows are not touched.
807
+ *
808
+ * Returns what changed, as absolute paths, so the caller can commit it.
809
+ */
557
810
  async deleteCollection(collectionId) {
558
- const file = this.getCollectionFile(collectionId);
559
- if (file) file.deleteImmediatelySync();
811
+ this.syncWithDisk();
812
+ const target = this.getCollectionFile(collectionId);
813
+ if (!target) throw new Error(`Collection "${collectionId}" has no file at ${path$1.join(this.collectionsDir, `${collectionId}.ts`)}.`);
814
+ const targetPath = target.getFilePath();
815
+ const indexPath = path$1.join(this.collectionsDir, "index.ts");
816
+ const importers = this.project.getSourceFiles().filter((file) => file !== target && this.declarationsOf(file, targetPath).length > 0);
817
+ const others = importers.filter((file) => file.getFilePath() !== indexPath);
818
+ if (others.length > 0) throw new Error(`Cannot delete "${collectionId}": ${others.map((file) => path$1.basename(file.getFilePath())).join(", ")} ${others.length === 1 ? "imports" : "import"} it — a relation to it, most likely. Remove that first; deleting the file would stop every collection from loading.`);
819
+ const changed = [];
820
+ const index = importers.find((file) => file.getFilePath() === indexPath);
821
+ try {
822
+ if (index) {
823
+ for (const declaration of this.declarationsOf(index, targetPath)) {
824
+ const names = declaration.isKind(SyntaxKind.ImportDeclaration) ? [declaration.getDefaultImport()?.getText(), ...declaration.getNamedImports().map((named) => (named.getAliasNode() ?? named.getNameNode()).getText())].filter((name) => Boolean(name)) : [];
825
+ for (const name of names) {
826
+ const uses = index.getDescendantsOfKind(SyntaxKind.Identifier).filter((identifier) => identifier.getText() === name && !identifier.getFirstAncestorByKind(SyntaxKind.ImportDeclaration));
827
+ for (const use of uses) {
828
+ const array = use.getParentIfKind(SyntaxKind.ArrayLiteralExpression);
829
+ if (!array) throw new Error(`Cannot delete "${collectionId}": index.ts uses \`${name}\` at line ${use.getStartLineNumber()} in a way the editor cannot remove. Remove it there first.`);
830
+ array.removeElement(use);
831
+ }
832
+ }
833
+ declaration.remove();
834
+ }
835
+ changed.push(index.getFilePath());
836
+ }
837
+ target.delete();
838
+ await this.project.save();
839
+ } catch (err) {
840
+ if (index && !index.isSaved()) index.refreshFromFileSystemSync();
841
+ this.syncWithDisk();
842
+ throw err;
843
+ }
844
+ return {
845
+ deleted: targetPath,
846
+ changed
847
+ };
848
+ }
849
+ /** The import and export declarations in `file` whose relative specifier resolves to `targetPath`. */
850
+ declarationsOf(file, targetPath) {
851
+ const stem = (absolute) => absolute.replace(/\.(ts|tsx|js|mjs|cjs)$/, "");
852
+ const target = stem(targetPath);
853
+ return [...file.getImportDeclarations(), ...file.getExportDeclarations()].filter((declaration) => {
854
+ const specifier = declaration.getModuleSpecifierValue();
855
+ if (!specifier || !specifier.startsWith(".")) return false;
856
+ return stem(path$1.resolve(path$1.dirname(file.getFilePath()), specifier)) === target;
857
+ });
560
858
  }
561
859
  };
562
860
  //#endregion
563
- export { AstSchemaEditor };
861
+ export { AstSchemaEditor, nestAdminKeys, nestAdminKeysDeep };
564
862
 
565
- //# sourceMappingURL=ast-schema-editor-Mvr50v_S.js.map
863
+ //# sourceMappingURL=ast-schema-editor-mCeSC03x.js.map