@rebasepro/server 0.23.0 → 0.24.1-canary.g56af4a4

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 (261) hide show
  1. package/README.md +1 -1
  2. package/bin/rebase-server.js +4 -2
  3. package/dist/GCSStorageController-DivE9bAx.js +402 -0
  4. package/dist/GCSStorageController-DivE9bAx.js.map +1 -0
  5. package/dist/S3StorageController-D_vKs4kP.js +480 -0
  6. package/dist/S3StorageController-D_vKs4kP.js.map +1 -0
  7. package/dist/api/ast-schema-editor.d.ts +92 -1
  8. package/dist/api/errors.d.ts +9 -0
  9. package/dist/api/live-schema-routes.d.ts +38 -8
  10. package/dist/api/logs-routes.d.ts +39 -1
  11. package/dist/api/openapi-generator.d.ts +17 -0
  12. package/dist/api/rest/api-generator.d.ts +97 -11
  13. package/dist/api/rest/auth-collection-writes.d.ts +28 -21
  14. package/dist/api/rest/field-access-query.d.ts +28 -8
  15. package/dist/api/rest/field-ops.d.ts +19 -7
  16. package/dist/api/rest/query-body.d.ts +10 -0
  17. package/dist/api/rest/write-validation.d.ts +9 -7
  18. package/dist/api/schema-editor-routes.d.ts +10 -1
  19. package/dist/api/sql-export-routes.d.ts +8 -0
  20. package/dist/api/types.d.ts +28 -1
  21. package/dist/{ast-schema-editor-Mvr50v_S.js → ast-schema-editor-CSdL0iCk.js} +310 -12
  22. package/dist/ast-schema-editor-CSdL0iCk.js.map +1 -0
  23. package/dist/auth/access.d.ts +121 -0
  24. package/dist/auth/adapter-middleware.d.ts +2 -1
  25. package/dist/auth/address-ownership.d.ts +16 -1
  26. package/dist/auth/admin-roles-route.d.ts +4 -2
  27. package/dist/auth/admin-roles.d.ts +17 -20
  28. package/dist/auth/admin-user-ops.d.ts +24 -2
  29. package/dist/auth/admin-users-route.d.ts +2 -0
  30. package/dist/auth/api-keys/api-key-middleware.d.ts +56 -55
  31. package/dist/auth/api-keys/api-key-routes.d.ts +41 -11
  32. package/dist/auth/api-keys/api-key-store.d.ts +31 -8
  33. package/dist/auth/api-keys/api-key-types.d.ts +14 -16
  34. package/dist/auth/api-keys/http-operation.d.ts +19 -0
  35. package/dist/auth/api-keys/index.d.ts +11 -11
  36. package/dist/auth/api-keys/key-grant.d.ts +41 -0
  37. package/dist/auth/api-keys/legacy-permissions.d.ts +33 -0
  38. package/dist/auth/auth-hooks.d.ts +46 -7
  39. package/dist/auth/builtin-auth-adapter.d.ts +8 -0
  40. package/dist/auth/cookie-utils.d.ts +7 -0
  41. package/dist/auth/deliverable-address.d.ts +6 -0
  42. package/dist/auth/email-change-routes.d.ts +41 -0
  43. package/dist/auth/expired-token-sweep.d.ts +71 -0
  44. package/dist/auth/impersonation.d.ts +110 -0
  45. package/dist/auth/index.d.ts +4 -2
  46. package/dist/auth/interfaces.d.ts +120 -59
  47. package/dist/auth/jwt-crypto.d.ts +7 -0
  48. package/dist/auth/jwt-keys.d.ts +4 -0
  49. package/dist/auth/jwt.d.ts +54 -3
  50. package/dist/auth/magic-link-routes.d.ts +2 -6
  51. package/dist/auth/mfa-routes.d.ts +2 -9
  52. package/dist/auth/middleware.d.ts +17 -5
  53. package/dist/auth/otp-routes.d.ts +2 -6
  54. package/dist/auth/passwordless-signup.d.ts +27 -0
  55. package/dist/auth/platform-token.d.ts +122 -0
  56. package/dist/auth/rate-limiter.d.ts +54 -1
  57. package/dist/auth/registration-policy.d.ts +17 -0
  58. package/dist/auth/routes.d.ts +45 -0
  59. package/dist/auth/scope-routes.d.ts +22 -0
  60. package/dist/auth/session-routes.d.ts +11 -6
  61. package/dist/auth/token-revocation.d.ts +50 -1
  62. package/dist/auth/user-picking.d.ts +35 -0
  63. package/dist/auth/verify-credential.d.ts +28 -0
  64. package/dist/{auth-B-GIMpDG.js → auth-OPq_FeMs.js} +3115 -638
  65. package/dist/auth-OPq_FeMs.js.map +1 -0
  66. package/dist/auth_adapter-CIItCTc8.js +21 -0
  67. package/dist/auth_adapter-CIItCTc8.js.map +1 -0
  68. package/dist/backend-DTAOsLQc.js.map +1 -1
  69. package/dist/backup/backup-common.d.ts +24 -3
  70. package/dist/backup/backup-routes.d.ts +46 -5
  71. package/dist/backup/backup-schedule.d.ts +33 -0
  72. package/dist/backup/backup-storage.d.ts +16 -0
  73. package/dist/backup/index.d.ts +5 -1
  74. package/dist/backup-5d7XhLX3.js +581 -0
  75. package/dist/backup-5d7XhLX3.js.map +1 -0
  76. package/dist/boot/bundle.d.ts +19 -0
  77. package/dist/boot/driver.d.ts +3 -0
  78. package/dist/boot/env.d.ts +56 -4
  79. package/dist/boot/http-server.d.ts +22 -0
  80. package/dist/boot/options.d.ts +16 -1
  81. package/dist/boot/runtime-cors.d.ts +17 -0
  82. package/dist/boot/security-headers.d.ts +26 -0
  83. package/dist/boot/static-routing.d.ts +65 -0
  84. package/dist/collection_patch-BRu-BvDv.js +472 -0
  85. package/dist/collection_patch-BRu-BvDv.js.map +1 -0
  86. package/dist/{contract-routes-CbFjuBwa.js → contract-routes-02lASyJI.js} +5 -8
  87. package/dist/contract-routes-02lASyJI.js.map +1 -0
  88. package/dist/cron/cron-routes.d.ts +1 -1
  89. package/dist/cron/cron-scheduler.d.ts +94 -23
  90. package/dist/cron/cron-store.d.ts +31 -4
  91. package/dist/cron/define-cron.d.ts +6 -2
  92. package/dist/cron/index.d.ts +1 -1
  93. package/dist/{cron-loader-DfTj2Hbi.js → cron-loader-D7OmleAb.js} +11 -7
  94. package/dist/cron-loader-D7OmleAb.js.map +1 -0
  95. package/dist/{cron-routes-eE8nif_b.js → cron-routes-CBfiaqEL.js} +34 -8
  96. package/dist/cron-routes-CBfiaqEL.js.map +1 -0
  97. package/dist/{cron-scheduler-B0pLfAix.js → cron-scheduler-C2vbETMo.js} +349 -65
  98. package/dist/cron-scheduler-C2vbETMo.js.map +1 -0
  99. package/dist/cron-store-B2Ih0gCN.js +540 -0
  100. package/dist/cron-store-B2Ih0gCN.js.map +1 -0
  101. package/dist/{ddl-bootstrap-C6mo0Kmz.js → ddl-bootstrap-BaqMSa4Y.js} +2 -2
  102. package/dist/{ddl-bootstrap-C6mo0Kmz.js.map → ddl-bootstrap-BaqMSa4Y.js.map} +1 -1
  103. package/dist/email/index.d.ts +2 -2
  104. package/dist/email/smtp-email-service.d.ts +23 -0
  105. package/dist/email/templates.d.ts +22 -0
  106. package/dist/email/types.d.ts +44 -0
  107. package/dist/env.d.ts +1 -39
  108. package/dist/{errors-DWsX4yTd.js → errors-D6_y86c5.js} +102 -8
  109. package/dist/errors-D6_y86c5.js.map +1 -0
  110. package/dist/filter-dialect-REl-TjZr.js +1628 -0
  111. package/dist/filter-dialect-REl-TjZr.js.map +1 -0
  112. package/dist/{function-loader-xnbDAPfa.js → function-loader-BDKKiBmy.js} +5 -4
  113. package/dist/function-loader-BDKKiBmy.js.map +1 -0
  114. package/dist/{function-routes-Chet4-lB.js → function-routes-DWB7BBsi.js} +25 -13
  115. package/dist/function-routes-DWB7BBsi.js.map +1 -0
  116. package/dist/functions/context.d.ts +26 -6
  117. package/dist/functions/define-function.d.ts +7 -4
  118. package/dist/functions/guards.d.ts +22 -5
  119. package/dist/functions/index.d.ts +2 -2
  120. package/dist/functions/index.js +114 -37
  121. package/dist/functions/index.js.map +1 -1
  122. package/dist/functions/request-timeout.d.ts +7 -5
  123. package/dist/history/history-routes.d.ts +9 -0
  124. package/dist/history/retention-sweep.d.ts +81 -0
  125. package/dist/{history-recorder-B4MpJfJK.js → history-recorder-DHenr3lm.js} +4 -4
  126. package/dist/{history-recorder-B4MpJfJK.js.map → history-recorder-DHenr3lm.js.map} +1 -1
  127. package/dist/{history-store-BhxWOuz9.js → history-store-DPofGVDp.js} +2 -2
  128. package/dist/{history-store-BhxWOuz9.js.map → history-store-DPofGVDp.js.map} +1 -1
  129. package/dist/index.d.ts +14 -7
  130. package/dist/index.es.js +23363 -17552
  131. package/dist/index.es.js.map +1 -1
  132. package/dist/init/health.d.ts +17 -2
  133. package/dist/init/shutdown.d.ts +20 -0
  134. package/dist/init.d.ts +70 -6
  135. package/dist/internal-tables-L2t39RdT.js +136 -0
  136. package/dist/internal-tables-L2t39RdT.js.map +1 -0
  137. package/dist/jobs/job-queue.d.ts +8 -0
  138. package/dist/jobs/job-store.d.ts +24 -0
  139. package/dist/jobs/types.d.ts +25 -4
  140. package/dist/{jobs-CazMYhyy.js → jobs-BpD4Q0qR.js} +235 -25
  141. package/dist/jobs-BpD4Q0qR.js.map +1 -0
  142. package/dist/{jwt-DnQHNFCl.js → jwt-Bqcu7cGt.js} +207 -77
  143. package/dist/jwt-Bqcu7cGt.js.map +1 -0
  144. package/dist/{keys-CogCQpxG.js → keys-GAVZqbqx.js} +3 -17
  145. package/dist/{keys-CogCQpxG.js.map → keys-GAVZqbqx.js.map} +1 -1
  146. package/dist/load-failure-UvzZgGXT.js +48 -0
  147. package/dist/load-failure-UvzZgGXT.js.map +1 -0
  148. package/dist/{logger-DO2PZc4i.js → logger-D-S-hO5e.js} +26 -3
  149. package/dist/logger-D-S-hO5e.js.map +1 -0
  150. package/dist/{logs-routes-Bj4TYYUl.js → logs-routes-DAdv37GI.js} +48 -8
  151. package/dist/logs-routes-DAdv37GI.js.map +1 -0
  152. package/dist/mcp/consent-page.d.ts +1 -1
  153. package/dist/mcp/mcp-routes.d.ts +7 -0
  154. package/dist/mcp/mcp-tools.d.ts +23 -9
  155. package/dist/mcp/oauth-metadata.d.ts +21 -16
  156. package/dist/mcp/oauth-routes.d.ts +7 -1
  157. package/dist/metrics/index.d.ts +18 -0
  158. package/dist/metrics/pool-wait.d.ts +50 -0
  159. package/dist/offline-8Ks3yp9n-CadBKm2D.js +3305 -0
  160. package/dist/offline-8Ks3yp9n-CadBKm2D.js.map +1 -0
  161. package/dist/offline-store-D9ytNNaa-BCr2vsFB.js +1903 -0
  162. package/dist/offline-store-D9ytNNaa-BCr2vsFB.js.map +1 -0
  163. package/dist/{openapi-generator-O_O24MAT.js → openapi-generator-BqoUh4Ij.js} +455 -80
  164. package/dist/openapi-generator-BqoUh4Ij.js.map +1 -0
  165. package/dist/policy-PdzX-Opd.js +121 -0
  166. package/dist/policy-PdzX-Opd.js.map +1 -0
  167. package/dist/project_manifest-R6y_KHms.js +59 -0
  168. package/dist/project_manifest-R6y_KHms.js.map +1 -0
  169. package/dist/{proxy-Czngl3p9.js → proxy-qRlqeUmO.js} +2 -2
  170. package/dist/{proxy-Czngl3p9.js.map → proxy-qRlqeUmO.js.map} +1 -1
  171. package/dist/{query-parser-DGRVFNM3.js → query-parser-DK9eMkb9.js} +497 -52
  172. package/dist/query-parser-DK9eMkb9.js.map +1 -0
  173. package/dist/{request-timeout-C_4C2BeR.js → request-timeout-Dnl2_OBc.js} +17 -10
  174. package/dist/request-timeout-Dnl2_OBc.js.map +1 -0
  175. package/dist/resumable-upload-threshold-CKv-Ry0l.js +24 -0
  176. package/dist/resumable-upload-threshold-CKv-Ry0l.js.map +1 -0
  177. package/dist/schema-edit/apply-schema-change.d.ts +73 -3
  178. package/dist/schema-edit/local-source.d.ts +29 -0
  179. package/dist/schema-edit/project-root.d.ts +3 -2
  180. package/dist/schema-edit/remote-source.d.ts +20 -5
  181. package/dist/schema-edit/source-editor.d.ts +28 -0
  182. package/dist/schema-editor-routes-BFf3i8jm.js +136 -0
  183. package/dist/schema-editor-routes-BFf3i8jm.js.map +1 -0
  184. package/dist/serve-spa.d.ts +58 -0
  185. package/dist/services/outbound-post.d.ts +50 -0
  186. package/dist/services/outbound-url-guard.d.ts +34 -5
  187. package/dist/services/routed-realtime-service.d.ts +16 -0
  188. package/dist/services/webhook-service.d.ts +66 -14
  189. package/dist/soft-delete-params-47yBGs3_.js +668 -0
  190. package/dist/soft-delete-params-47yBGs3_.js.map +1 -0
  191. package/dist/sql-export-routes-BNNuGGLP.js +240 -0
  192. package/dist/sql-export-routes-BNNuGGLP.js.map +1 -0
  193. package/dist/{src-vkcwKXbT.js → src-CatHFUym.js} +439 -20
  194. package/dist/src-CatHFUym.js.map +1 -0
  195. package/dist/storage/GCSStorageController.d.ts +48 -2
  196. package/dist/storage/LocalStorageController.d.ts +69 -2
  197. package/dist/storage/S3StorageController.d.ts +39 -3
  198. package/dist/storage/image-transform.d.ts +5 -0
  199. package/dist/storage/index.d.ts +2 -2
  200. package/dist/storage/maintenance-routes.d.ts +23 -0
  201. package/dist/storage/maintenance.d.ts +126 -0
  202. package/dist/storage/property-limits.d.ts +41 -6
  203. package/dist/storage/rendition-cache.d.ts +9 -11
  204. package/dist/storage/request-keys.d.ts +15 -0
  205. package/dist/storage/request-principals.d.ts +30 -0
  206. package/dist/storage/requested-object.d.ts +74 -0
  207. package/dist/storage/routes.d.ts +52 -14
  208. package/dist/storage/source-variables.d.ts +10 -0
  209. package/dist/storage/streams.d.ts +42 -0
  210. package/dist/storage/tus-handler.d.ts +76 -7
  211. package/dist/storage/types.d.ts +101 -7
  212. package/dist/storage/upload-validation.d.ts +13 -0
  213. package/dist/storage-resumable-VO-cMpse-C_YX71pO.js +125 -0
  214. package/dist/storage-resumable-VO-cMpse-C_YX71pO.js.map +1 -0
  215. package/dist/strings.es-BeZWAodn.js +256 -0
  216. package/dist/strings.es-BeZWAodn.js.map +1 -0
  217. package/dist/upload-validation-Dd-fnsbA.js +172 -0
  218. package/dist/upload-validation-Dd-fnsbA.js.map +1 -0
  219. package/dist/utils/byte-size.d.ts +10 -0
  220. package/dist/utils/compression.d.ts +6 -0
  221. package/dist/utils/load-failure.d.ts +14 -0
  222. package/dist/utils/logger.d.ts +12 -0
  223. package/package.json +5 -5
  224. package/dist/GCSStorageController-CjrA4PMo.js +0 -242
  225. package/dist/GCSStorageController-CjrA4PMo.js.map +0 -1
  226. package/dist/S3StorageController-B6pKDNVj.js +0 -264
  227. package/dist/S3StorageController-B6pKDNVj.js.map +0 -1
  228. package/dist/admin-roles-vYdp_Pil.js +0 -36
  229. package/dist/admin-roles-vYdp_Pil.js.map +0 -1
  230. package/dist/admin_block-DxKLmdiv.js +0 -206
  231. package/dist/admin_block-DxKLmdiv.js.map +0 -1
  232. package/dist/ast-schema-editor-Mvr50v_S.js.map +0 -1
  233. package/dist/auth/api-keys/api-key-permission-guard.d.ts +0 -65
  234. package/dist/auth-B-GIMpDG.js.map +0 -1
  235. package/dist/backup-D7YR94N3.js +0 -253
  236. package/dist/backup-D7YR94N3.js.map +0 -1
  237. package/dist/contract-routes-CbFjuBwa.js.map +0 -1
  238. package/dist/cron-loader-DfTj2Hbi.js.map +0 -1
  239. package/dist/cron-routes-eE8nif_b.js.map +0 -1
  240. package/dist/cron-scheduler-B0pLfAix.js.map +0 -1
  241. package/dist/cron-store-TcoGz-xS.js +0 -305
  242. package/dist/cron-store-TcoGz-xS.js.map +0 -1
  243. package/dist/dynamic-import-X40pTZUQ.js +0 -23
  244. package/dist/dynamic-import-X40pTZUQ.js.map +0 -1
  245. package/dist/errors-DWsX4yTd.js.map +0 -1
  246. package/dist/function-loader-xnbDAPfa.js.map +0 -1
  247. package/dist/function-routes-Chet4-lB.js.map +0 -1
  248. package/dist/jobs-CazMYhyy.js.map +0 -1
  249. package/dist/jwt-DnQHNFCl.js.map +0 -1
  250. package/dist/logger-DO2PZc4i.js.map +0 -1
  251. package/dist/logs-routes-Bj4TYYUl.js.map +0 -1
  252. package/dist/openapi-generator-O_O24MAT.js.map +0 -1
  253. package/dist/query-parser-DGRVFNM3.js.map +0 -1
  254. package/dist/request-timeout-C_4C2BeR.js.map +0 -1
  255. package/dist/schema-editor-routes-C5-lh_jO.js +0 -87
  256. package/dist/schema-editor-routes-C5-lh_jO.js.map +0 -1
  257. package/dist/src-pmvW7BFx.js +0 -6688
  258. package/dist/src-pmvW7BFx.js.map +0 -1
  259. package/dist/src-vkcwKXbT.js.map +0 -1
  260. package/dist/types-BfKcm9do.js +0 -39
  261. package/dist/types-BfKcm9do.js.map +0 -1
@@ -0,0 +1,472 @@
1
+ import { createRequire as __rebaseCreateRequire } from "module";
2
+ import __rebaseProcess from "process";
3
+ globalThis.process ??= __rebaseProcess;
4
+ __rebaseCreateRequire(import.meta.url);
5
+ //#region ../types/src/types/admin_block.ts
6
+ /**
7
+ * The keys of a collection's admin block, as data.
8
+ *
9
+ * There is no *type* for the block in this package any more, and that is the point:
10
+ * `admin` is not declared on `BaseCollectionConfig` or on any property here, so a
11
+ * BaaS install cannot even write one. `@rebasepro/cms-types` adds the field back by
12
+ * declaration merging, which is why installing it is what makes the admin surface
13
+ * appear.
14
+ *
15
+ * The *list* still has to live here, because three runtime consumers need it and two
16
+ * of them are core — see below.
17
+ */
18
+ /**
19
+ * Every key that belongs inside a collection's `admin` block, as data.
20
+ *
21
+ * The type that describes these fields is `AdminCollectionOptions` in
22
+ * `@rebasepro/cms-types`, and it is erased at build time — but three runtime
23
+ * consumers need the list, and two of them are core:
24
+ *
25
+ * - `serializeCollections`, to drop the block from the contract
26
+ * - the ts-morph schema editor in `@rebasepro/server`, which rewrites collection
27
+ * files on disk from the admin panel and has to know where each key goes. A key
28
+ * missing from this list gets written to the *top level* of the file, where the
29
+ * backend ignores it and the panel never finds it again.
30
+ * - the `collections-admin-block` codemod
31
+ *
32
+ * `@rebasepro/cms-types` re-exports this and asserts it names only real option
33
+ * keys; the count is pinned by a test there.
34
+ *
35
+ * @group Models
36
+ */
37
+ var ADMIN_COLLECTION_KEYS = [
38
+ "Actions",
39
+ "additionalFields",
40
+ "alwaysApplyDefaultValues",
41
+ "browserCallbacks",
42
+ "components",
43
+ "customViews",
44
+ "defaultEntityAction",
45
+ "defaultFilter",
46
+ "defaultSelectedView",
47
+ "defaultSize",
48
+ "defaultViewMode",
49
+ "disableDefaultActions",
50
+ "display",
51
+ "enabledViews",
52
+ "entityActions",
53
+ "entityViews",
54
+ "exportable",
55
+ "filterPresets",
56
+ "fixedFilter",
57
+ "form",
58
+ "formAutoSave",
59
+ "formView",
60
+ "group",
61
+ "hideFromEntityViews",
62
+ "hideFromNavigation",
63
+ "hideIdFromCollection",
64
+ "hideIdFromForm",
65
+ "icon",
66
+ "includeJsonView",
67
+ "inlineEditing",
68
+ "kanban",
69
+ "listProperties",
70
+ "localChangesBackup",
71
+ "openEntityMode",
72
+ "orderProperty",
73
+ "pagination",
74
+ "previewProperties",
75
+ "propertiesOrder",
76
+ "selectionController",
77
+ "selectionEnabled",
78
+ "sideDialogWidth",
79
+ "sort"
80
+ ];
81
+ /**
82
+ * Every key that belongs inside a *property's* `admin` block, as data.
83
+ *
84
+ * The union of `AdminPropertyOptions` and its per-type extensions
85
+ * (`AdminStringOptions`, `AdminArrayOptions`, …) in `@rebasepro/cms-types`.
86
+ * It lives here for the same reason {@link ADMIN_COLLECTION_KEYS} does: the
87
+ * runtime consumers are core packages that the BaaS guard forbids from
88
+ * importing `@rebasepro/cms-types`. Here it is the boot-time collection
89
+ * validator in `@rebasepro/server`, which has to tell "you left `readOnly` at
90
+ * the top of the property, where nothing reads it" apart from "you invented a
91
+ * key we have never heard of".
92
+ *
93
+ * `@rebasepro/cms-types` re-exports this and asserts it names only real
94
+ * option keys.
95
+ *
96
+ * @group Models
97
+ */
98
+ var ADMIN_PROPERTY_KEYS = [
99
+ "canAddElements",
100
+ "clearable",
101
+ "columnWidth",
102
+ "customProps",
103
+ "disabled",
104
+ "expanded",
105
+ "Field",
106
+ "filledByServer",
107
+ "Filter",
108
+ "filterOperators",
109
+ "fixedFilter",
110
+ "format",
111
+ "hideFromCollection",
112
+ "includeEntityLink",
113
+ "includeId",
114
+ "markdown",
115
+ "minimalistView",
116
+ "multiline",
117
+ "Preview",
118
+ "previewAsTag",
119
+ "previewProperties",
120
+ "readOnly",
121
+ "renderInForm",
122
+ "sortable",
123
+ "span",
124
+ "spreadChildren",
125
+ "urlPreview",
126
+ "widget"
127
+ ];
128
+ /**
129
+ * Move flattened admin keys back down into the `admin` block.
130
+ *
131
+ * The admin panel works with a *flat* view model — the block merged onto the
132
+ * collection — so what comes back from a form has `icon` and `defaultViewMode`
133
+ * at the top level while `admin` still holds whatever the file was loaded with.
134
+ * This is the way back.
135
+ *
136
+ * **The top-level value wins.** It is the one the form just wrote; the block is
137
+ * the copy the collection was loaded with, and preferring it resolves every edit
138
+ * in favour of the value the user changed away from.
139
+ *
140
+ * This lives here, next to the key lists, because it had two implementations —
141
+ * `toAdminCollectionConfig` in `@rebasepro/cms-types` and `nestAdminKeys` in
142
+ * `@rebasepro/server`'s schema editor — that agreed on everything except that
143
+ * precedence, which is the only part that decides whether a save is visible.
144
+ *
145
+ * @group Models
146
+ */
147
+ function nestAdminKeysOf(source, adminKeys) {
148
+ const keys = new Set(adminKeys);
149
+ const top = {};
150
+ const block = { ...source.admin ?? {} };
151
+ for (const [key, value] of Object.entries(source)) {
152
+ if (key === "admin") continue;
153
+ if (keys.has(key)) block[key] = value;
154
+ else top[key] = value;
155
+ }
156
+ if (Object.keys(block).length > 0) top.admin = block;
157
+ return top;
158
+ }
159
+ /**
160
+ * {@link nestAdminKeysOf} for a collection.
161
+ *
162
+ * @group Models
163
+ */
164
+ function nestAdminCollectionKeys(collection) {
165
+ return nestAdminKeysOf(collection, ADMIN_COLLECTION_KEYS);
166
+ }
167
+ /** A record of properties, keyed by name — a map's `properties`, or a `oneOf` block's. */
168
+ function nestEachProperty(properties) {
169
+ return Object.fromEntries(Object.entries(properties).map(([key, child]) => [key, isNestable(child) ? nestAdminPropertyKeys(child) : child]));
170
+ }
171
+ /** Anything the walk can descend into: a plain object, not an array. */
172
+ function isNestable(value) {
173
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
174
+ }
175
+ /**
176
+ * {@link nestAdminKeysOf} for a property, applied to its children too.
177
+ *
178
+ * A map property carries `properties`, an array property carries `of`, and an
179
+ * array of typed blocks carries `oneOf.properties` — a record of properties like
180
+ * a map's. All of them hold properties with `admin` blocks of their own. A flat
181
+ * `readOnly` left on a child is as dead — and as fatal at the next boot — as one
182
+ * left on the parent, so the walk goes all the way down.
183
+ *
184
+ * `oneOf` was the container this walk did not know about, and it is the one the
185
+ * block-based collection templates are built out of: every block inside them
186
+ * kept its flat `markdown`, and the collection they created would not boot.
187
+ *
188
+ * @group Models
189
+ */
190
+ function nestAdminPropertyKeys(property) {
191
+ const nested = nestAdminKeysOf(property, ADMIN_PROPERTY_KEYS);
192
+ const children = nested.properties;
193
+ if (isNestable(children)) nested.properties = nestEachProperty(children);
194
+ const oneOf = nested.oneOf;
195
+ if (isNestable(oneOf) && isNestable(oneOf.properties)) nested.oneOf = {
196
+ ...oneOf,
197
+ properties: nestEachProperty(oneOf.properties)
198
+ };
199
+ const of = nested.of;
200
+ if (Array.isArray(of)) nested.of = of.map((entry) => isNestable(entry) ? nestAdminPropertyKeys(entry) : entry);
201
+ else if (isNestable(of)) nested.of = nestAdminPropertyKeys(of);
202
+ return nested;
203
+ }
204
+ //#endregion
205
+ //#region ../types/src/types/collection_patch.ts
206
+ /**
207
+ * What changed about a collection, as opposed to what the collection is.
208
+ *
209
+ * ## Why a save is a patch
210
+ *
211
+ * The admin panel edits a collection as JSON — the only form a browser and a
212
+ * server can exchange it in — and JSON cannot carry most of what a collection
213
+ * file holds: an entity action's `onClick`, a computed field's `value`, a
214
+ * property shared from another module (`status: statusProperty`), an enum
215
+ * imported from elsewhere (`enum: LOCALE_ENUM`), the comments around all of
216
+ * them. A save that posted the whole collection and had it written back
217
+ * rewrote every key it carried and deleted everything JSON had dropped, so
218
+ * renaming a collection removed its actions' handlers, inlined its shared
219
+ * property without the custom `Field`, and froze a copy of the enum.
220
+ *
221
+ * So a save sends what the person changed: the difference between the JSON
222
+ * the editor loaded and the JSON it is saving. Anything JSON could not carry
223
+ * is identical on both sides of that difference — dropped from both — and
224
+ * cannot appear in it. The server applies the patch to the collection as it
225
+ * is (for the plan) and writes only the patched keys into the file.
226
+ *
227
+ * ## The shape
228
+ *
229
+ * Two operations on paths of object keys. Arrays are values: an array that
230
+ * changed is set whole. Positions in an array are not stable identities —
231
+ * element 2 of an enum is not "the third value" once a value is inserted —
232
+ * and the writer is the one place that can match an old element to its new
233
+ * self (by `key`/`id`/`name`) and keep what JSON dropped from it.
234
+ *
235
+ * Paths are in the AUTHORING shape — presentation keys under `admin`, the
236
+ * shape the file has — see {@link nestCollectionPatchPaths}.
237
+ */
238
+ /**
239
+ * Keys the runtime adds to a collection that must never be written into its
240
+ * source. `resolvedRelation` is the registry's resolved form of a relation; a
241
+ * file holding one does not type-check (`target` is missing from it).
242
+ */
243
+ var RUNTIME_ONLY_COLLECTION_KEYS = ["resolvedRelation"];
244
+ /** Path segments that reach an object's prototype rather than a key of it. */
245
+ var UNSAFE_SEGMENTS = /* @__PURE__ */ new Set([
246
+ "__proto__",
247
+ "constructor",
248
+ "prototype"
249
+ ]);
250
+ var isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
251
+ /** A JSON value as JSON would carry it: functions, `undefined` and symbols dropped. */
252
+ function toCollectionJson(value) {
253
+ if (value === void 0) return void 0;
254
+ const text = JSON.stringify(value);
255
+ return text === void 0 ? void 0 : JSON.parse(text);
256
+ }
257
+ function jsonEqual(a, b) {
258
+ if (a === b) return true;
259
+ if (Array.isArray(a) && Array.isArray(b)) return a.length === b.length && a.every((item, index) => jsonEqual(item, b[index]));
260
+ if (isPlainObject(a) && isPlainObject(b)) {
261
+ const keys = Object.keys(a);
262
+ if (keys.length !== Object.keys(b).length) return false;
263
+ return keys.every((key) => Object.prototype.hasOwnProperty.call(b, key) && jsonEqual(a[key], b[key]));
264
+ }
265
+ return false;
266
+ }
267
+ /**
268
+ * The operations that take `before` to `after`, both JSON values.
269
+ *
270
+ * Objects are compared key by key; anything else that differs — a primitive,
271
+ * an array, a change of kind — is set whole.
272
+ */
273
+ function diffJsonValues(before, after, path = []) {
274
+ if (jsonEqual(before, after)) return [];
275
+ if (isPlainObject(before) && isPlainObject(after)) {
276
+ const ops = [];
277
+ for (const key of Object.keys(after)) if (!Object.prototype.hasOwnProperty.call(before, key)) ops.push({
278
+ op: "set",
279
+ path: [...path, key],
280
+ value: after[key]
281
+ });
282
+ else ops.push(...diffJsonValues(before[key], after[key], [...path, key]));
283
+ for (const key of Object.keys(before)) if (!Object.prototype.hasOwnProperty.call(after, key)) ops.push({
284
+ op: "remove",
285
+ path: [...path, key]
286
+ });
287
+ return ops;
288
+ }
289
+ return [{
290
+ op: "set",
291
+ path,
292
+ value: after
293
+ }];
294
+ }
295
+ function step(position, segment) {
296
+ switch (position) {
297
+ case "collection": return segment === "properties" ? "properties" : "other";
298
+ case "properties": return "property";
299
+ case "property":
300
+ if (segment === "properties") return "properties";
301
+ if (segment === "of") return "property";
302
+ if (segment === "oneOf") return "oneOf";
303
+ return "other";
304
+ case "oneOf": return segment === "properties" ? "properties" : "other";
305
+ default: return "other";
306
+ }
307
+ }
308
+ function nestPath(path) {
309
+ const nested = [];
310
+ let position = "collection";
311
+ for (const segment of path) {
312
+ const moved = position === "collection" && ADMIN_COLLECTION_KEYS.includes(segment) || position === "property" && ADMIN_PROPERTY_KEYS.includes(segment);
313
+ if (moved) nested.push("admin");
314
+ nested.push(segment);
315
+ position = moved ? "other" : step(position, segment);
316
+ }
317
+ return {
318
+ path: nested,
319
+ position
320
+ };
321
+ }
322
+ function nestValue(position, value) {
323
+ if (position === "property") {
324
+ if (Array.isArray(value)) return value.map((entry) => isPlainObject(entry) ? nestAdminPropertyKeys(entry) : entry);
325
+ return isPlainObject(value) ? nestAdminPropertyKeys(value) : value;
326
+ }
327
+ if (position === "properties" && isPlainObject(value)) return Object.fromEntries(Object.entries(value).map(([key, property]) => [key, isPlainObject(property) ? nestAdminPropertyKeys(property) : property]));
328
+ if (position === "oneOf" && isPlainObject(value)) return isPlainObject(value.properties) ? {
329
+ ...value,
330
+ properties: nestValue("properties", value.properties)
331
+ } : value;
332
+ if (position === "collection" && isPlainObject(value)) {
333
+ const nested = nestAdminCollectionKeys(value);
334
+ return isPlainObject(nested.properties) ? {
335
+ ...nested,
336
+ properties: nestValue("properties", nested.properties)
337
+ } : nested;
338
+ }
339
+ return value;
340
+ }
341
+ /**
342
+ * The patch, with every flat presentation key moved into its `admin` block.
343
+ *
344
+ * The panel edits a flat view model — `icon` beside `name`, a property's
345
+ * `readOnly` beside its `type` — while the file keeps them under `admin`. The
346
+ * difference is taken on the flat model, because that is where a cleared field
347
+ * shows up as a removed key (the `admin` copy the model was loaded with still
348
+ * holds the old value), and moved here.
349
+ *
350
+ * The flat value wins over the `admin` copy, as in {@link nestAdminKeysOf}:
351
+ * operations that were already under `admin` are ordered first, so the flat
352
+ * one — what the form just wrote — is applied last.
353
+ */
354
+ function nestCollectionPatchPaths(patch) {
355
+ return patch.map((op, index) => {
356
+ const { path, position } = nestPath(op.path);
357
+ const wasNested = op.path.includes("admin");
358
+ return {
359
+ moved: op.op === "remove" ? {
360
+ op: "remove",
361
+ path
362
+ } : {
363
+ op: "set",
364
+ path,
365
+ value: nestValue(position, op.value)
366
+ },
367
+ wasNested,
368
+ index
369
+ };
370
+ }).sort((a, b) => a.wasNested === b.wasNested ? a.index - b.index : a.wasNested ? -1 : 1).map((entry) => entry.moved);
371
+ }
372
+ /**
373
+ * The patch that takes the collection the editor loaded to the one it is
374
+ * saving, in the authoring shape. Empty when nothing changed.
375
+ *
376
+ * Both sides go through JSON first, so whatever JSON cannot carry — a
377
+ * function, a value it drops — is equally absent from both and never appears
378
+ * as a change.
379
+ */
380
+ function diffCollections(loaded, saving) {
381
+ return nestCollectionPatchPaths(diffJsonValues(toCollectionJson(loaded), toCollectionJson(saving)));
382
+ }
383
+ /**
384
+ * Why a patch may not be applied, or an empty list.
385
+ *
386
+ * Checked by every door that accepts one: a path is a list of non-empty object
387
+ * keys that cannot reach a prototype, and no runtime-only key may be written.
388
+ */
389
+ function collectionPatchProblems(patch) {
390
+ if (!Array.isArray(patch)) return ["`patch` must be a list of operations."];
391
+ const problems = [];
392
+ patch.forEach((op, index) => {
393
+ const where = `patch[${index}]`;
394
+ if (!isPlainObject(op) || op.op !== "set" && op.op !== "remove") {
395
+ problems.push(`${where} must be { op: "set", path, value } or { op: "remove", path }.`);
396
+ return;
397
+ }
398
+ const path = op.path;
399
+ if (!Array.isArray(path) || path.length === 0 || !path.every((segment) => typeof segment === "string" && segment.length > 0)) {
400
+ problems.push(`${where}.path must be a non-empty list of object keys.`);
401
+ return;
402
+ }
403
+ const unsafe = path.find((segment) => UNSAFE_SEGMENTS.has(segment));
404
+ if (unsafe) problems.push(`${where}.path may not contain "${unsafe}".`);
405
+ const runtime = path.find((segment) => RUNTIME_ONLY_COLLECTION_KEYS.includes(segment));
406
+ if (runtime) problems.push(`${where} writes "${runtime}", which the runtime adds and a collection file must not hold.`);
407
+ if (op.op === "set") {
408
+ const key = keyIn(op.value, (key) => UNSAFE_SEGMENTS.has(key) || RUNTIME_ONLY_COLLECTION_KEYS.includes(key));
409
+ if (key) problems.push(`${where}.value holds "${key}", which a collection file must not hold.`);
410
+ }
411
+ });
412
+ return problems;
413
+ }
414
+ /**
415
+ * The first runtime-only key (see {@link RUNTIME_ONLY_COLLECTION_KEYS}) found
416
+ * anywhere in a value, or `undefined`. For the doors that take a whole
417
+ * collection rather than a patch.
418
+ */
419
+ function runtimeOnlyKeyIn(value) {
420
+ return keyIn(value, (key) => RUNTIME_ONLY_COLLECTION_KEYS.includes(key));
421
+ }
422
+ /** A patch every door may apply: {@link collectionPatchProblems} found nothing. */
423
+ function isCollectionPatch(patch) {
424
+ return collectionPatchProblems(patch).length === 0;
425
+ }
426
+ function keyIn(value, matches) {
427
+ if (Array.isArray(value)) {
428
+ for (const item of value) {
429
+ const found = keyIn(item, matches);
430
+ if (found) return found;
431
+ }
432
+ return;
433
+ }
434
+ if (!isPlainObject(value)) return void 0;
435
+ for (const key of Object.keys(value)) {
436
+ if (matches(key)) return key;
437
+ const found = keyIn(value[key], matches);
438
+ if (found) return found;
439
+ }
440
+ }
441
+ /**
442
+ * `target` with the patch applied, copying only what it changes.
443
+ *
444
+ * Everything the patch does not reach is shared with `target` by reference —
445
+ * functions, thunks and all — which is what lets a planner see a relation's
446
+ * `target()` on the patched collection exactly as it was. Intermediate objects
447
+ * a `set` needs are created; a `remove` of a key that is not there is nothing
448
+ * to do.
449
+ */
450
+ function applyCollectionPatch(target, patch) {
451
+ let root = target;
452
+ for (const op of patch) root = applyOp(root, op.path, op);
453
+ return root;
454
+ }
455
+ function applyOp(node, path, op) {
456
+ const [head, ...rest] = path;
457
+ if (UNSAFE_SEGMENTS.has(head)) throw new Error(`A collection patch may not reach "${head}".`);
458
+ const base = isPlainObject(node) ? { ...node } : {};
459
+ if (rest.length === 0) {
460
+ if (op.op === "remove") delete base[head];
461
+ else base[head] = op.value;
462
+ return base;
463
+ }
464
+ const child = Object.prototype.hasOwnProperty.call(base, head) ? base[head] : void 0;
465
+ if (op.op === "remove" && !isPlainObject(child)) return node;
466
+ base[head] = applyOp(child, rest, op);
467
+ return base;
468
+ }
469
+ //#endregion
470
+ export { nestCollectionPatchPaths as a, ADMIN_PROPERTY_KEYS as c, isCollectionPatch as i, nestAdminCollectionKeys as l, collectionPatchProblems as n, runtimeOnlyKeyIn as o, diffCollections as r, ADMIN_COLLECTION_KEYS as s, applyCollectionPatch as t, nestAdminPropertyKeys as u };
471
+
472
+ //# sourceMappingURL=collection_patch-BRu-BvDv.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collection_patch-BRu-BvDv.js","names":[],"sources":["../../types/src/types/admin_block.ts","../../types/src/types/collection_patch.ts"],"sourcesContent":["/**\n * The keys of a collection's admin block, as data.\n *\n * There is no *type* for the block in this package any more, and that is the point:\n * `admin` is not declared on `BaseCollectionConfig` or on any property here, so a\n * BaaS install cannot even write one. `@rebasepro/cms-types` adds the field back by\n * declaration merging, which is why installing it is what makes the admin surface\n * appear.\n *\n * The *list* still has to live here, because three runtime consumers need it and two\n * of them are core — see below.\n */\n\n/**\n * Every key that belongs inside a collection's `admin` block, as data.\n *\n * The type that describes these fields is `AdminCollectionOptions` in\n * `@rebasepro/cms-types`, and it is erased at build time — but three runtime\n * consumers need the list, and two of them are core:\n *\n * - `serializeCollections`, to drop the block from the contract\n * - the ts-morph schema editor in `@rebasepro/server`, which rewrites collection\n * files on disk from the admin panel and has to know where each key goes. A key\n * missing from this list gets written to the *top level* of the file, where the\n * backend ignores it and the panel never finds it again.\n * - the `collections-admin-block` codemod\n *\n * `@rebasepro/cms-types` re-exports this and asserts it names only real option\n * keys; the count is pinned by a test there.\n *\n * @group Models\n */\nexport const ADMIN_COLLECTION_KEYS = [\n \"Actions\",\n \"additionalFields\",\n \"alwaysApplyDefaultValues\",\n \"browserCallbacks\",\n \"components\",\n \"customViews\",\n \"defaultEntityAction\",\n \"defaultFilter\",\n \"defaultSelectedView\",\n \"defaultSize\",\n \"defaultViewMode\",\n \"disableDefaultActions\",\n \"display\",\n \"enabledViews\",\n \"entityActions\",\n \"entityViews\",\n \"exportable\",\n \"filterPresets\",\n \"fixedFilter\",\n \"form\",\n \"formAutoSave\",\n \"formView\",\n \"group\",\n \"hideFromEntityViews\",\n \"hideFromNavigation\",\n \"hideIdFromCollection\",\n \"hideIdFromForm\",\n \"icon\",\n \"includeJsonView\",\n \"inlineEditing\",\n \"kanban\",\n \"listProperties\",\n \"localChangesBackup\",\n \"openEntityMode\",\n \"orderProperty\",\n \"pagination\",\n \"previewProperties\",\n \"propertiesOrder\",\n \"selectionController\",\n \"selectionEnabled\",\n \"sideDialogWidth\",\n \"sort\"\n] as const;\n\n/** A key of a collection's `admin` block. @group Models */\nexport type AdminCollectionKey = typeof ADMIN_COLLECTION_KEYS[number];\n\n/**\n * Every key that belongs inside a *property's* `admin` block, as data.\n *\n * The union of `AdminPropertyOptions` and its per-type extensions\n * (`AdminStringOptions`, `AdminArrayOptions`, …) in `@rebasepro/cms-types`.\n * It lives here for the same reason {@link ADMIN_COLLECTION_KEYS} does: the\n * runtime consumers are core packages that the BaaS guard forbids from\n * importing `@rebasepro/cms-types`. Here it is the boot-time collection\n * validator in `@rebasepro/server`, which has to tell \"you left `readOnly` at\n * the top of the property, where nothing reads it\" apart from \"you invented a\n * key we have never heard of\".\n *\n * `@rebasepro/cms-types` re-exports this and asserts it names only real\n * option keys.\n *\n * @group Models\n */\nexport const ADMIN_PROPERTY_KEYS = [\n \"canAddElements\",\n \"clearable\",\n \"columnWidth\",\n \"customProps\",\n \"disabled\",\n \"expanded\",\n \"Field\",\n \"filledByServer\",\n \"Filter\",\n \"filterOperators\",\n \"fixedFilter\",\n \"format\",\n \"hideFromCollection\",\n \"includeEntityLink\",\n \"includeId\",\n \"markdown\",\n \"minimalistView\",\n \"multiline\",\n \"Preview\",\n \"previewAsTag\",\n \"previewProperties\",\n \"readOnly\",\n \"renderInForm\",\n \"sortable\",\n \"span\",\n \"spreadChildren\",\n \"urlPreview\",\n \"widget\",\n] as const;\n\n/** A key of a property's `admin` block. @group Models */\nexport type AdminPropertyKey = typeof ADMIN_PROPERTY_KEYS[number];\n\n/**\n * Move flattened admin keys back down into the `admin` block.\n *\n * The admin panel works with a *flat* view model — the block merged onto the\n * collection — so what comes back from a form has `icon` and `defaultViewMode`\n * at the top level while `admin` still holds whatever the file was loaded with.\n * This is the way back.\n *\n * **The top-level value wins.** It is the one the form just wrote; the block is\n * the copy the collection was loaded with, and preferring it resolves every edit\n * in favour of the value the user changed away from.\n *\n * This lives here, next to the key lists, because it had two implementations —\n * `toAdminCollectionConfig` in `@rebasepro/cms-types` and `nestAdminKeys` in\n * `@rebasepro/server`'s schema editor — that agreed on everything except that\n * precedence, which is the only part that decides whether a save is visible.\n *\n * @group Models\n */\nexport function nestAdminKeysOf(\n source: Record<string, unknown>,\n adminKeys: readonly string[]\n): Record<string, unknown> {\n const keys = new Set<string>(adminKeys);\n const top: Record<string, unknown> = {};\n const block: Record<string, unknown> = { ...((source.admin as Record<string, unknown> | undefined) ?? {}) };\n\n for (const [key, value] of Object.entries(source)) {\n if (key === \"admin\") continue;\n if (keys.has(key)) block[key] = value;\n else top[key] = value;\n }\n\n if (Object.keys(block).length > 0) top.admin = block;\n return top;\n}\n\n/**\n * {@link nestAdminKeysOf} for a collection.\n *\n * @group Models\n */\nexport function nestAdminCollectionKeys(collection: Record<string, unknown>): Record<string, unknown> {\n return nestAdminKeysOf(collection, ADMIN_COLLECTION_KEYS);\n}\n\n/** A record of properties, keyed by name — a map's `properties`, or a `oneOf` block's. */\nfunction nestEachProperty(properties: Record<string, unknown>): Record<string, unknown> {\n return Object.fromEntries(\n Object.entries(properties).map(([key, child]) => [\n key,\n isNestable(child) ? nestAdminPropertyKeys(child) : child\n ])\n );\n}\n\n/** Anything the walk can descend into: a plain object, not an array. */\nfunction isNestable(value: unknown): value is Record<string, unknown> {\n return Boolean(value) && typeof value === \"object\" && !Array.isArray(value);\n}\n\n/**\n * {@link nestAdminKeysOf} for a property, applied to its children too.\n *\n * A map property carries `properties`, an array property carries `of`, and an\n * array of typed blocks carries `oneOf.properties` — a record of properties like\n * a map's. All of them hold properties with `admin` blocks of their own. A flat\n * `readOnly` left on a child is as dead — and as fatal at the next boot — as one\n * left on the parent, so the walk goes all the way down.\n *\n * `oneOf` was the container this walk did not know about, and it is the one the\n * block-based collection templates are built out of: every block inside them\n * kept its flat `markdown`, and the collection they created would not boot.\n *\n * @group Models\n */\nexport function nestAdminPropertyKeys(property: Record<string, unknown>): Record<string, unknown> {\n const nested = nestAdminKeysOf(property, ADMIN_PROPERTY_KEYS);\n\n const children = nested.properties;\n if (isNestable(children)) {\n nested.properties = nestEachProperty(children);\n }\n\n // `oneOf` is not itself a property — it is a block holding `properties`\n // alongside `typeField`, `valueField` and `propertiesOrder`, none of which\n // may be walked as one.\n const oneOf = nested.oneOf;\n if (isNestable(oneOf) && isNestable(oneOf.properties)) {\n nested.oneOf = { ...oneOf, properties: nestEachProperty(oneOf.properties) };\n }\n\n const of = nested.of;\n if (Array.isArray(of)) {\n nested.of = of.map(entry => isNestable(entry) ? nestAdminPropertyKeys(entry) : entry);\n } else if (isNestable(of)) {\n nested.of = nestAdminPropertyKeys(of);\n }\n\n return nested;\n}\n","/**\n * What changed about a collection, as opposed to what the collection is.\n *\n * ## Why a save is a patch\n *\n * The admin panel edits a collection as JSON — the only form a browser and a\n * server can exchange it in — and JSON cannot carry most of what a collection\n * file holds: an entity action's `onClick`, a computed field's `value`, a\n * property shared from another module (`status: statusProperty`), an enum\n * imported from elsewhere (`enum: LOCALE_ENUM`), the comments around all of\n * them. A save that posted the whole collection and had it written back\n * rewrote every key it carried and deleted everything JSON had dropped, so\n * renaming a collection removed its actions' handlers, inlined its shared\n * property without the custom `Field`, and froze a copy of the enum.\n *\n * So a save sends what the person changed: the difference between the JSON\n * the editor loaded and the JSON it is saving. Anything JSON could not carry\n * is identical on both sides of that difference — dropped from both — and\n * cannot appear in it. The server applies the patch to the collection as it\n * is (for the plan) and writes only the patched keys into the file.\n *\n * ## The shape\n *\n * Two operations on paths of object keys. Arrays are values: an array that\n * changed is set whole. Positions in an array are not stable identities —\n * element 2 of an enum is not \"the third value\" once a value is inserted —\n * and the writer is the one place that can match an old element to its new\n * self (by `key`/`id`/`name`) and keep what JSON dropped from it.\n *\n * Paths are in the AUTHORING shape — presentation keys under `admin`, the\n * shape the file has — see {@link nestCollectionPatchPaths}.\n */\nimport { ADMIN_COLLECTION_KEYS, ADMIN_PROPERTY_KEYS, nestAdminCollectionKeys, nestAdminPropertyKeys } from \"./admin_block\";\n\n/** One change to a collection. `path` is a list of object keys. */\nexport type CollectionPatchOp =\n | { op: \"set\"; path: string[]; value: unknown }\n | { op: \"remove\"; path: string[] };\n\n/** @group Models */\nexport type CollectionPatch = CollectionPatchOp[];\n\n/**\n * Keys the runtime adds to a collection that must never be written into its\n * source. `resolvedRelation` is the registry's resolved form of a relation; a\n * file holding one does not type-check (`target` is missing from it).\n */\nexport const RUNTIME_ONLY_COLLECTION_KEYS: readonly string[] = [\"resolvedRelation\"];\n\n/** Path segments that reach an object's prototype rather than a key of it. */\nconst UNSAFE_SEGMENTS = new Set([\"__proto__\", \"constructor\", \"prototype\"]);\n\nconst isPlainObject = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\n/** A JSON value as JSON would carry it: functions, `undefined` and symbols dropped. */\nexport function toCollectionJson(value: unknown): unknown {\n if (value === undefined) return undefined;\n const text = JSON.stringify(value);\n return text === undefined ? undefined : JSON.parse(text);\n}\n\nfunction jsonEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (Array.isArray(a) && Array.isArray(b)) {\n return a.length === b.length && a.every((item, index) => jsonEqual(item, b[index]));\n }\n if (isPlainObject(a) && isPlainObject(b)) {\n const keys = Object.keys(a);\n if (keys.length !== Object.keys(b).length) return false;\n return keys.every(key => Object.prototype.hasOwnProperty.call(b, key) && jsonEqual(a[key], b[key]));\n }\n return false;\n}\n\n/**\n * The operations that take `before` to `after`, both JSON values.\n *\n * Objects are compared key by key; anything else that differs — a primitive,\n * an array, a change of kind — is set whole.\n */\nexport function diffJsonValues(before: unknown, after: unknown, path: string[] = []): CollectionPatch {\n if (jsonEqual(before, after)) return [];\n if (isPlainObject(before) && isPlainObject(after)) {\n const ops: CollectionPatch = [];\n for (const key of Object.keys(after)) {\n if (!Object.prototype.hasOwnProperty.call(before, key)) {\n ops.push({ op: \"set\", path: [...path, key], value: after[key] });\n } else {\n ops.push(...diffJsonValues(before[key], after[key], [...path, key]));\n }\n }\n for (const key of Object.keys(before)) {\n if (!Object.prototype.hasOwnProperty.call(after, key)) ops.push({ op: \"remove\", path: [...path, key] });\n }\n return ops;\n }\n return [{ op: \"set\", path, value: after }];\n}\n\n/**\n * Where a path lands in a collection, so a flat presentation key can be moved\n * into the `admin` block it belongs to.\n *\n * A collection holds `properties`, a map of properties; a property holds\n * `properties` (a map's children), `of` (an array's element) and\n * `oneOf.properties` (typed blocks). Every other key leaves the structure.\n */\ntype Position = \"collection\" | \"properties\" | \"property\" | \"oneOf\" | \"other\";\n\nfunction step(position: Position, segment: string): Position {\n switch (position) {\n case \"collection\":\n return segment === \"properties\" ? \"properties\" : \"other\";\n case \"properties\":\n return \"property\";\n case \"property\":\n if (segment === \"properties\") return \"properties\";\n if (segment === \"of\") return \"property\";\n if (segment === \"oneOf\") return \"oneOf\";\n return \"other\";\n case \"oneOf\":\n return segment === \"properties\" ? \"properties\" : \"other\";\n default:\n return \"other\";\n }\n}\n\nfunction nestPath(path: string[]): { path: string[]; position: Position } {\n const nested: string[] = [];\n let position: Position = \"collection\";\n for (const segment of path) {\n const moved: boolean = (position === \"collection\" && (ADMIN_COLLECTION_KEYS as readonly string[]).includes(segment))\n || (position === \"property\" && (ADMIN_PROPERTY_KEYS as readonly string[]).includes(segment));\n if (moved) nested.push(\"admin\");\n nested.push(segment);\n // Inside `admin` nothing is moved any further.\n position = moved ? \"other\" : step(position, segment);\n }\n return { path: nested, position };\n}\n\nfunction nestValue(position: Position, value: unknown): unknown {\n if (position === \"property\") {\n if (Array.isArray(value)) return value.map(entry => isPlainObject(entry) ? nestAdminPropertyKeys(entry) : entry);\n return isPlainObject(value) ? nestAdminPropertyKeys(value) : value;\n }\n if (position === \"properties\" && isPlainObject(value)) {\n return Object.fromEntries(Object.entries(value).map(([key, property]) =>\n [key, isPlainObject(property) ? nestAdminPropertyKeys(property) : property]));\n }\n if (position === \"oneOf\" && isPlainObject(value)) {\n return isPlainObject(value.properties)\n ? { ...value, properties: nestValue(\"properties\", value.properties) }\n : value;\n }\n if (position === \"collection\" && isPlainObject(value)) {\n const nested = nestAdminCollectionKeys(value);\n return isPlainObject(nested.properties)\n ? { ...nested, properties: nestValue(\"properties\", nested.properties) }\n : nested;\n }\n return value;\n}\n\n/**\n * The patch, with every flat presentation key moved into its `admin` block.\n *\n * The panel edits a flat view model — `icon` beside `name`, a property's\n * `readOnly` beside its `type` — while the file keeps them under `admin`. The\n * difference is taken on the flat model, because that is where a cleared field\n * shows up as a removed key (the `admin` copy the model was loaded with still\n * holds the old value), and moved here.\n *\n * The flat value wins over the `admin` copy, as in {@link nestAdminKeysOf}:\n * operations that were already under `admin` are ordered first, so the flat\n * one — what the form just wrote — is applied last.\n */\nexport function nestCollectionPatchPaths(patch: CollectionPatch): CollectionPatch {\n const nested = patch.map((op, index) => {\n const { path, position } = nestPath(op.path);\n const wasNested = op.path.includes(\"admin\");\n const moved: CollectionPatchOp = op.op === \"remove\"\n ? { op: \"remove\", path }\n : { op: \"set\", path, value: nestValue(position, op.value) };\n return { moved, wasNested, index };\n });\n return nested\n .sort((a, b) => (a.wasNested === b.wasNested ? a.index - b.index : a.wasNested ? -1 : 1))\n .map(entry => entry.moved);\n}\n\n/**\n * The patch that takes the collection the editor loaded to the one it is\n * saving, in the authoring shape. Empty when nothing changed.\n *\n * Both sides go through JSON first, so whatever JSON cannot carry — a\n * function, a value it drops — is equally absent from both and never appears\n * as a change.\n */\nexport function diffCollections(loaded: unknown, saving: unknown): CollectionPatch {\n return nestCollectionPatchPaths(diffJsonValues(toCollectionJson(loaded), toCollectionJson(saving)));\n}\n\n/**\n * Why a patch may not be applied, or an empty list.\n *\n * Checked by every door that accepts one: a path is a list of non-empty object\n * keys that cannot reach a prototype, and no runtime-only key may be written.\n */\nexport function collectionPatchProblems(patch: unknown): string[] {\n if (!Array.isArray(patch)) return [\"`patch` must be a list of operations.\"];\n const problems: string[] = [];\n patch.forEach((op: unknown, index) => {\n const where = `patch[${index}]`;\n if (!isPlainObject(op) || (op.op !== \"set\" && op.op !== \"remove\")) {\n problems.push(`${where} must be { op: \"set\", path, value } or { op: \"remove\", path }.`);\n return;\n }\n const path = op.path;\n if (!Array.isArray(path) || path.length === 0 || !path.every(segment => typeof segment === \"string\" && segment.length > 0)) {\n problems.push(`${where}.path must be a non-empty list of object keys.`);\n return;\n }\n const unsafe = path.find(segment => UNSAFE_SEGMENTS.has(segment as string));\n if (unsafe) problems.push(`${where}.path may not contain \"${unsafe}\".`);\n const runtime = path.find(segment => RUNTIME_ONLY_COLLECTION_KEYS.includes(segment as string));\n if (runtime) problems.push(`${where} writes \"${runtime}\", which the runtime adds and a collection file must not hold.`);\n if (op.op === \"set\") {\n const key = keyIn(op.value, key => UNSAFE_SEGMENTS.has(key) || RUNTIME_ONLY_COLLECTION_KEYS.includes(key));\n if (key) problems.push(`${where}.value holds \"${key}\", which a collection file must not hold.`);\n }\n });\n return problems;\n}\n\n/**\n * The first runtime-only key (see {@link RUNTIME_ONLY_COLLECTION_KEYS}) found\n * anywhere in a value, or `undefined`. For the doors that take a whole\n * collection rather than a patch.\n */\nexport function runtimeOnlyKeyIn(value: unknown): string | undefined {\n return keyIn(value, key => RUNTIME_ONLY_COLLECTION_KEYS.includes(key));\n}\n\n/** A patch every door may apply: {@link collectionPatchProblems} found nothing. */\nexport function isCollectionPatch(patch: unknown): patch is CollectionPatch {\n return collectionPatchProblems(patch).length === 0;\n}\n\nfunction keyIn(value: unknown, matches: (key: string) => boolean): string | undefined {\n if (Array.isArray(value)) {\n for (const item of value) {\n const found = keyIn(item, matches);\n if (found) return found;\n }\n return undefined;\n }\n if (!isPlainObject(value)) return undefined;\n for (const key of Object.keys(value)) {\n if (matches(key)) return key;\n const found = keyIn(value[key], matches);\n if (found) return found;\n }\n return undefined;\n}\n\n/**\n * `target` with the patch applied, copying only what it changes.\n *\n * Everything the patch does not reach is shared with `target` by reference —\n * functions, thunks and all — which is what lets a planner see a relation's\n * `target()` on the patched collection exactly as it was. Intermediate objects\n * a `set` needs are created; a `remove` of a key that is not there is nothing\n * to do.\n */\nexport function applyCollectionPatch<T>(target: T, patch: CollectionPatch): T {\n let root: unknown = target;\n for (const op of patch) {\n root = applyOp(root, op.path, op);\n }\n return root as T;\n}\n\nfunction applyOp(node: unknown, path: string[], op: CollectionPatchOp): unknown {\n const [head, ...rest] = path;\n if (UNSAFE_SEGMENTS.has(head)) throw new Error(`A collection patch may not reach \"${head}\".`);\n const base: Record<string, unknown> = isPlainObject(node) ? { ...node } : {};\n if (rest.length === 0) {\n if (op.op === \"remove\") delete base[head];\n else base[head] = op.value;\n return base;\n }\n const child = Object.prototype.hasOwnProperty.call(base, head) ? base[head] : undefined;\n if (op.op === \"remove\" && !isPlainObject(child)) return node;\n base[head] = applyOp(child, rest, op);\n return base;\n}\n"],"mappings":"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"}
@@ -3,13 +3,10 @@ import __rebaseProcess from "process";
3
3
  globalThis.process ??= __rebaseProcess;
4
4
  __rebaseCreateRequire(import.meta.url);
5
5
  import { n as __exportAll } from "./rolldown-runtime-dW7B1o5h.js";
6
- import "./src-vkcwKXbT.js";
7
- import { r as logger } from "./logger-DO2PZc4i.js";
6
+ import "./src-CatHFUym.js";
7
+ import { t as SCHEMA_VERSION_HEADER } from "./project_manifest-R6y_KHms.js";
8
+ import { r as logger } from "./logger-D-S-hO5e.js";
8
9
  import { Hono } from "hono";
9
- //#region ../types/src/types/project_manifest.ts
10
- /** Header carrying the schema version an SDK was generated from. */
11
- var SCHEMA_VERSION_HEADER = "x-rebase-schema";
12
- //#endregion
13
10
  //#region ../types/src/types/collection_contract.ts
14
11
  /** Depth limit for the walk — deep enough for real configs, finite for cyclic ones. */
15
12
  var MAX_DEPTH = 64;
@@ -275,6 +272,6 @@ function createContractRoutes(config) {
275
272
  return router;
276
273
  }
277
274
  //#endregion
278
- export { SCHEMA_VERSION_HEADER as i, createContractRoutes as n, computeSchemaVersion as r, contract_routes_exports as t };
275
+ export { createContractRoutes as n, computeSchemaVersion as r, contract_routes_exports as t };
279
276
 
280
- //# sourceMappingURL=contract-routes-CbFjuBwa.js.map
277
+ //# sourceMappingURL=contract-routes-02lASyJI.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract-routes-02lASyJI.js","names":[],"sources":["../../types/src/types/collection_contract.ts","../../types/src/types/schema_version.ts","../src/api/contract-routes.ts"],"sourcesContent":["import type { CollectionConfig } from \"./collections\";\n\n/**\n * Serializing collections so they survive a network hop.\n *\n * A collection definition is not plain data. Relations point at their target\n * with a *function* (`target: () => usersCollection`) so two collections can\n * reference each other without an import cycle, and collections also carry\n * callbacks, custom views and component references. `JSON.stringify` silently\n * drops every one of those, which matters because the SDK generator *calls*\n * `relation.target()` to decide whether a foreign key is a string or a number.\n * Serialize naively and remote SDK generation produces subtly wrong types\n * instead of failing — the worst possible outcome.\n *\n * So relation targets are resolved to a slug reference on the way out and\n * rebuilt into functions on the way in. Everything else that cannot cross a wire\n * is dropped deliberately: an SDK is generated from the *shape* of the data, and\n * server-side behaviour is neither useful to a client nor safe to publish.\n */\n\n/** Marker replacing a relation's `target` function in serialized form. */\nexport interface SerializedCollectionRef {\n __collectionRef: string;\n}\n\nexport function isSerializedCollectionRef(value: unknown): value is SerializedCollectionRef {\n return typeof value === \"object\"\n && value !== null\n && typeof (value as SerializedCollectionRef).__collectionRef === \"string\";\n}\n\n/** Depth limit for the walk — deep enough for real configs, finite for cyclic ones. */\nconst MAX_DEPTH = 64;\n\n/**\n * Resolve whatever a `target` thunk returns down to a collection.\n *\n * A target may be the collection, a module namespace (when the authoring file\n * used `import * as`), or a default-export wrapper. All three appear in real\n * projects, and the SDK generator already unwraps them the same way.\n */\nfunction unwrapTarget(value: unknown): CollectionConfig | undefined {\n if (!value || typeof value !== \"object\") return undefined;\n const candidate = value as { default?: unknown; __esModule?: boolean; properties?: unknown };\n if (candidate.default || candidate.__esModule) {\n const inner = candidate.default;\n if (inner && typeof inner === \"object\") return inner as CollectionConfig;\n }\n if (candidate.properties) return value as CollectionConfig;\n return undefined;\n}\n\n/** The identity a serialized reference uses. Slug first — it is the routing key. */\nfunction refFor(collection: CollectionConfig | undefined): string | undefined {\n if (!collection) return undefined;\n const withPath = collection as CollectionConfig & { path?: string };\n return collection.slug || withPath.path || collection.name;\n}\n\n/**\n * Deep-copy a value into something JSON can carry.\n *\n * `target` keys are special-cased into refs. Other functions vanish, cycles are\n * cut, and everything else is copied structurally.\n */\n/** Shared walk state: the memo, plus a count of depth-cap hits. */\ninterface WalkState {\n memo: WeakMap<object, unknown>;\n /**\n * How many times the walk has truncated a subtree — by hitting the depth\n * cap, or by cutting a cycle.\n *\n * Either kind of truncation makes a result valid only at the *position* it\n * was produced at, so caching it and serving it elsewhere silently drops\n * content that would have been included. Comparing this counter before and\n * after a node's children tells us whether its result is position-\n * independent and therefore safe to memoize.\n *\n * The cycle case is the subtle one: with `a.b = b` and `b.a = a`, serializing\n * `{ first: b, second: a }` visits `a` beneath `b` — where the cycle back to\n * `b` is cut — and would then reuse that truncated `a` for `second`, where\n * nothing needed cutting.\n */\n truncations: number;\n}\n\nfunction toSerializable(\n value: unknown,\n seen: WeakSet<object>,\n depth: number,\n state: WalkState,\n key?: string\n): unknown {\n if (depth > MAX_DEPTH) {\n state.truncations++;\n return undefined;\n }\n\n if (typeof value === \"function\") {\n // Only a relation target carries information a client needs. Calling it\n // is safe here — this runs on the server, where the target module is\n // already loaded — and a throwing target simply yields no reference,\n // which degrades the generated FK type rather than failing the request.\n if (key === \"target\") {\n try {\n const resolved = unwrapTarget((value as () => unknown)());\n const ref = refFor(resolved);\n return ref ? { __collectionRef: ref } : undefined;\n } catch {\n return undefined;\n }\n }\n return undefined;\n }\n\n if (value === null || typeof value !== \"object\") {\n return value;\n }\n\n if (value instanceof Date) return value.toISOString();\n if (value instanceof RegExp) return value.source;\n\n if (seen.has(value as object)) {\n state.truncations++;\n return undefined;\n }\n\n // A shared (non-cyclic) subgraph is reachable by many paths, and `seen` is a\n // *path* set — released in the `finally` below so a node referenced twice in\n // different branches is emitted twice rather than dropped as a false cycle.\n // Without memoization that makes the walk exponential in depth: a diamond\n // graph 20 levels deep took ~400ms, and each further level doubled it. The\n // result is a plain data tree, so handing back the same converted object for\n // a repeat visit is indistinguishable after JSON.stringify.\n const cached = state.memo.get(value as object);\n if (cached !== undefined) return cached;\n\n seen.add(value as object);\n const truncationsBefore = state.truncations;\n const memoize = (result: unknown): unknown => {\n // Only cache a result that nothing was cut from.\n if (result !== undefined && state.truncations === truncationsBefore) {\n state.memo.set(value as object, result);\n }\n return result;\n };\n\n try {\n if (Array.isArray(value)) {\n const items = value\n .map(item => toSerializable(item, seen, depth + 1, state))\n .filter(item => item !== undefined);\n // A container that had content, none of which can be represented, is\n // itself unrepresentable — see the note below.\n return memoize(value.length > 0 && items.length === 0 ? undefined : items);\n }\n\n // A React element or component reference has no meaning to a client and\n // will not survive JSON anyway.\n if (\"$$typeof\" in (value as Record<string, unknown>)) return undefined;\n\n const entries = Object.entries(value as Record<string, unknown>);\n const out: Record<string, unknown> = {};\n for (const [k, v] of entries) {\n const converted = toSerializable(v, seen, depth + 1, state, k);\n if (converted !== undefined) out[k] = converted;\n }\n\n // Drop a container whose entire content was dropped.\n //\n // `callbacks: { beforeSave() {…} }` would otherwise serialize to\n // `callbacks: {}` — an empty husk that carries no information but is not\n // *nothing*, so it lands in the payload and, worse, in the schema hash.\n // Editing a hook would then change every client's schema version and\n // report perfectly current SDKs as stale.\n //\n // A container that started empty stays empty: `properties: {}` is a\n // deliberate statement, not a casualty.\n if (entries.length > 0 && Object.keys(out).length === 0) return undefined;\n\n return memoize(out);\n } finally {\n // Released so a collection referenced twice in different branches is\n // emitted twice rather than being dropped as a false cycle.\n seen.delete(value as object);\n }\n}\n\n/**\n * Serialize collections for transport over the contract endpoint.\n *\n * Sorted by slug so the output — and therefore the schema hash computed from it\n * — does not depend on filesystem ordering.\n */\nexport function serializeCollections(collections: CollectionConfig[]): unknown[] {\n return [...collections]\n .sort((a, b) => String(a.slug ?? \"\").localeCompare(String(b.slug ?? \"\")))\n .map(collection => toSerializable(withoutAdminBlock(collection), new WeakSet(), 0, {\n memo: new WeakMap(),\n truncations: 0\n }))\n .filter((c): c is Record<string, unknown> => c !== undefined);\n}\n\n/**\n * Drop the admin block before the walk.\n *\n * Nothing downstream of serialization is an admin panel. The contract endpoint\n * feeds remote SDK generation, and `rebase build` writes the result into a bundle\n * manifest that only the backend runtime reads. The block would survive the walk\n * as a husk anyway — its React elements and component functions are dropped\n * individually — and that husk has two costs worth avoiding: it puts every custom\n * component's *file path* on an endpoint whose job is to describe data shapes, and\n * it grows a payload that is fetched and cached per project.\n *\n * Removing it here rather than at each call site means one chokepoint, so a future\n * consumer of `serializeCollections` cannot forget.\n *\n * Child collections carry their own block, so this recurses — stripping only the\n * top level was the mistake `stripNonClientFields` in the contract routes already\n * had to fix once for security rules.\n */\nfunction withoutAdminBlock(collection: CollectionConfig): CollectionConfig {\n const { admin: _admin, ...rest } = collection as CollectionConfig & Record<string, unknown>;\n const nested = rest as Record<string, unknown>;\n if (Array.isArray(nested.subcollections)) {\n nested.subcollections = nested.subcollections.map(\n (child) => withoutAdminBlock(child as CollectionConfig)\n );\n }\n return rest as CollectionConfig;\n}\n\n/**\n * Rebuild collections received from a contract endpoint.\n *\n * Relation refs become real thunks resolving through the returned set, so\n * downstream consumers — the SDK generator above all — see exactly the shape\n * they would have seen had the collections been imported from source.\n *\n * A ref naming a collection that is not in the payload resolves to `undefined`\n * rather than throwing: the generator already tolerates an unresolvable target\n * by falling back to a permissive key type, and a partial contract should still\n * produce a usable SDK.\n */\nexport function deserializeCollections(payload: unknown[]): CollectionConfig[] {\n const collections = payload\n .filter((c): c is Record<string, unknown> => typeof c === \"object\" && c !== null)\n .map(c => ({ ...c })) as unknown as CollectionConfig[];\n\n const bySlug = new Map<string, CollectionConfig>();\n for (const collection of collections) {\n const ref = refFor(collection);\n if (ref) bySlug.set(ref, collection);\n }\n\n const rehydrate = (value: unknown, depth: number): void => {\n if (depth > MAX_DEPTH || !value || typeof value !== \"object\") return;\n\n if (Array.isArray(value)) {\n for (const item of value) rehydrate(item, depth + 1);\n return;\n }\n\n const record = value as Record<string, unknown>;\n for (const [key, child] of Object.entries(record)) {\n if (key === \"target\" && isSerializedCollectionRef(child)) {\n const slug = child.__collectionRef;\n record.target = () => bySlug.get(slug);\n continue;\n }\n rehydrate(child, depth + 1);\n }\n };\n\n for (const collection of collections) rehydrate(collection, 0);\n return collections;\n}\n","import type { CollectionConfig } from \"./collections\";\nimport { serializeCollections } from \"./collection_contract\";\n\n/**\n * The schema version stamp.\n *\n * One function, used in three places that must agree or the whole drift-detection\n * story is noise: `rebase build` writes it into a bundle manifest, the runtime\n * serves it from the contract endpoint, and a generated SDK records the value it\n * was built from. If any two of those computed it differently, every client would\n * look permanently out of date.\n *\n * It covers **collections only** — the client's contract is the shape of the\n * data, so editing a hook or a server function must not invalidate every SDK in\n * every repository. That is a deliberate narrowing, not an oversight.\n */\n\n/** Stable stringify: object keys sorted at every level, so key order cannot alter the hash. */\nfunction canonicalize(value: unknown): string {\n if (value === null || typeof value !== \"object\") {\n return JSON.stringify(value) ?? \"null\";\n }\n if (Array.isArray(value)) {\n return `[${value.map(canonicalize).join(\",\")}]`;\n }\n const entries = Object.entries(value as Record<string, unknown>)\n .filter(([, v]) => v !== undefined)\n .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));\n return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${canonicalize(v)}`).join(\",\")}}`;\n}\n\n/**\n * Reduce a collection to the parts a generated client is actually built from.\n *\n * The version answers one question — \"is this SDK stale?\" — so it must change\n * exactly when the generated types could change, and never otherwise. Hashing a\n * whole collection fails both halves of that:\n *\n * - Security rules, callbacks, icons, groups and UI settings do not appear in a\n * generated client, so including them reports perfectly current SDKs as stale.\n * - Worse, they are not stable *inputs*. The runtime applies default security\n * rules when it loads collections, so the same source hashed before and after\n * loading produced two different answers — a build-time stamp that could never\n * match the server that served it.\n *\n * Codegen reads the slug (for the `Database` key and type names), the properties,\n * and the relations. That is the projection.\n */\nfunction projectForCodegen(collection: CollectionConfig): Record<string, unknown> {\n const source = collection as CollectionConfig & {\n relations?: unknown;\n subcollections?: CollectionConfig[];\n path?: string;\n engine?: unknown;\n dataSource?: unknown;\n };\n\n return {\n slug: collection.slug ?? source.path,\n properties: collection.properties,\n relations: source.relations,\n // The engine decides whether relations are resolved at all: codegen asks\n // `getDataSourceCapabilities(collection.engine).supportsRelations`, and an\n // engine that answers no drops every foreign-key column from the\n // generated Row/Insert/Update types. Moving a collection to such an\n // engine is a real change to the generated types, so it has to move the\n // version. `dataSource` is what resolves to `engine`, so it counts too.\n engine: source.engine,\n dataSource: source.dataSource,\n subcollections: source.subcollections?.map(projectForCodegen)\n };\n}\n\n/**\n * Compute the canonical string a schema version hashes.\n *\n * Exposed separately so the hashing itself can differ by environment: Node has\n * `crypto`, and callers without it can still compare canonical forms directly.\n */\nexport function canonicalSchemaPayload(collections: CollectionConfig[]): string {\n const projected = serializeCollections(collections)\n .map(collection => projectForCodegen(collection as CollectionConfig));\n return canonicalize(projected);\n}\n\n/**\n * A short, non-cryptographic digest of the canonical payload.\n *\n * FNV-1a style, 64 bits, as two 32-bit halves. This is an identity, not a\n * security boundary: nothing trusts a schema version to prove anything, it only\n * answers \"is this the same schema as before\". A hand-rolled hash keeps this\n * module free of `node:crypto`, so the identical function runs in the browser,\n * in the CLI, and in the runtime — which is the property that actually matters.\n */\nexport function computeSchemaVersion(collections: CollectionConfig[]): string {\n const payload = canonicalSchemaPayload(collections);\n\n let h1 = 0x811c9dc5;\n let h2 = 0x01000193;\n\n for (let i = 0; i < payload.length; i++) {\n const code = payload.charCodeAt(i);\n h1 ^= code;\n // Multiply by the FNV prime using shifts to stay in 32-bit integer math.\n h1 = (h1 + ((h1 << 1) + (h1 << 4) + (h1 << 7) + (h1 << 8) + (h1 << 24))) >>> 0;\n h2 ^= code + i;\n h2 = (h2 + ((h2 << 1) + (h2 << 5) + (h2 << 9) + (h2 << 15) + (h2 << 24))) >>> 0;\n }\n\n const hex = (n: number): string => n.toString(16).padStart(8, \"0\");\n return `v1:${hex(h1)}${hex(h2)}`;\n}\n","import { Hono } from \"hono\";\nimport {\n RUNTIME_CONTRACT_VERSION,\n SCHEMA_VERSION_HEADER,\n computeSchemaVersion,\n serializeCollections,\n type CollectionConfig,\n type RebaseProjectContract\n} from \"@rebasepro/types\";\nimport type { HonoEnv } from \"./types\";\nimport { logger } from \"../utils/logger\";\n\n/**\n * The project contract endpoint.\n *\n * This is what makes a repository able to build against a project it does not\n * contain. Without it, a typed client can only be generated from local\n * collection *source*, which means every frontend must live in the same\n * repository as the backend. Serving the contract turns that around: an app\n * asks the project what its shape is, so a web app, a second web app and a\n * mobile app can each live wherever they like and none of them needs to know\n * about the others.\n *\n * Admin-gated. Collection definitions describe every table, column and relation\n * in the project, including ones no security rule would ever expose — that is a\n * map of the database, not public API documentation.\n */\n\nexport interface ContractRoutesConfig {\n collectionRegistry: { getRawCollections(): CollectionConfig[] };\n /**\n * The schema version recorded at build time.\n *\n * Preferred over recomputing, so that what a client is told matches exactly\n * what the bundle claims. It is recomputed only when a bundle did not record\n * one — a `baas`-mode project derives its collections from the live database\n * at boot, so there was nothing to hash when it was built.\n */\n schemaVersion?: string;\n /** Runtime package version, surfaced so a client can report what it built against. */\n runtimeVersion?: string;\n}\n\n/**\n * Strip everything a client does not need from a serialized collection.\n *\n * The generator reads the slug, the properties and the relations. It never reads\n * a security rule — but `securityRules` carries the raw SQL of every RLS\n * predicate guarding the project, which is a description of the authorization\n * model rather than of the data shape. Publishing it to anyone who can generate\n * an SDK gives away more than the endpoint is for, so it is removed here rather\n * than trusted not to matter.\n */\nfunction stripNonClientFields(collection: unknown): unknown {\n if (!collection || typeof collection !== \"object\") return collection;\n const {\n securityRules: _securityRules,\n callbacks: _callbacks,\n ...rest\n } = collection as Record<string, unknown>;\n\n // Subcollections are collections, and carry their own rules. Stripping only\n // the top level published every nested policy — the leak this exists to\n // prevent, just one level down.\n if (Array.isArray(rest.subcollections)) {\n rest.subcollections = rest.subcollections.map(stripNonClientFields);\n }\n\n return rest;\n}\n\nexport function createContractRoutes(config: ContractRoutesConfig): Hono<HonoEnv> {\n const router = new Hono<HonoEnv>();\n\n // Computing a version walks and canonicalizes every collection, and\n // `/schema-version` is deliberately unauthenticated and meant to be polled.\n // Recomputing per request would make a CI convenience into a CPU\n // amplification anyone could aim at the server. Collections do not change\n // after boot, so once is enough.\n let cachedVersion: string | undefined;\n const schemaVersionOf = (collections: CollectionConfig[]): string => {\n if (config.schemaVersion) return config.schemaVersion;\n if (cachedVersion === undefined) cachedVersion = computeSchemaVersion(collections);\n return cachedVersion;\n };\n\n router.get(\"/contract\", (c) => {\n const collections = config.collectionRegistry.getRawCollections();\n const serialized = serializeCollections(collections).map(stripNonClientFields);\n\n // In `baas` mode the collections are whatever introspection found at\n // boot, so the version has to be computed from them rather than taken\n // from a build that never saw them.\n const schemaVersion = schemaVersionOf(collections);\n\n const contract: RebaseProjectContract = {\n schemaVersion,\n runtime: {\n version: config.runtimeVersion ?? \"unknown\",\n contract: RUNTIME_CONTRACT_VERSION\n },\n collections: serialized,\n collectionSlugs: collections\n .map(collection => collection.slug)\n .filter((slug): slug is string => Boolean(slug))\n .sort(),\n generatedAt: new Date().toISOString()\n };\n\n c.header(SCHEMA_VERSION_HEADER, schemaVersion);\n return c.json(contract);\n });\n\n /**\n * Cheap drift check.\n *\n * Deliberately unauthenticated and deliberately tiny: two version stamps\n * and nothing else. A CI job that only wants to know whether its generated\n * SDK is stale should not need admin credentials, and a version stamp\n * reveals nothing about the schema it stands for.\n *\n * `runtime` is here as well as on `/contract` because the two answer\n * different questions and only one of them was reachable. Which runtime a\n * project is on decides whether a client's wire format is understood at\n * all, and it was published solely on the admin-gated route — so a CLI or\n * an SDK, the two callers that actually need to know, could not ask. That\n * is the same shape as the header this route echoes: a documented signal\n * with no reachable sender. `contract` is the number that matters for\n * compatibility; `version` names the release a human should quote.\n */\n router.get(\"/schema-version\", (c) => {\n const schemaVersion = schemaVersionOf(config.collectionRegistry.getRawCollections());\n c.header(SCHEMA_VERSION_HEADER, schemaVersion);\n return c.json({\n schemaVersion,\n runtime: {\n version: config.runtimeVersion ?? \"unknown\",\n contract: RUNTIME_CONTRACT_VERSION\n }\n });\n });\n\n logger.debug(\"Contract routes mounted\");\n return router;\n}\n"],"mappings":"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"}
@@ -7,7 +7,7 @@ import { type CronScheduler } from "./cron-scheduler.js";
7
7
  * Routes:
8
8
  * GET / → list all cron jobs
9
9
  * GET /:id → get a single job's status
10
- * POST /:id/trigger → manually trigger a job
10
+ * POST /:id/trigger → manually trigger a job (`?async=1`: 202 once it has started)
11
11
  * GET /:id/logs → get execution logs for a job
12
12
  * PUT /:id → update job (enable/disable, or `null` to follow the code)
13
13
  *