@rebasepro/server 0.23.0 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (181) hide show
  1. package/README.md +1 -1
  2. package/bin/rebase-server.js +4 -2
  3. package/dist/{GCSStorageController-CjrA4PMo.js → GCSStorageController-BSiP1c-f.js} +22 -8
  4. package/dist/GCSStorageController-BSiP1c-f.js.map +1 -0
  5. package/dist/{S3StorageController-B6pKDNVj.js → S3StorageController-CAwFRgjV.js} +19 -7
  6. package/dist/S3StorageController-CAwFRgjV.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 +44 -10
  13. package/dist/api/rest/write-validation.d.ts +2 -2
  14. package/dist/api/types.d.ts +17 -1
  15. package/dist/{ast-schema-editor-Mvr50v_S.js → ast-schema-editor-CWqS_sLJ.js} +309 -11
  16. package/dist/ast-schema-editor-CWqS_sLJ.js.map +1 -0
  17. package/dist/auth/access.d.ts +105 -0
  18. package/dist/auth/adapter-middleware.d.ts +2 -1
  19. package/dist/auth/address-ownership.d.ts +16 -1
  20. package/dist/auth/admin-roles-route.d.ts +4 -2
  21. package/dist/auth/admin-roles.d.ts +17 -20
  22. package/dist/auth/admin-users-route.d.ts +1 -0
  23. package/dist/auth/api-keys/api-key-middleware.d.ts +56 -55
  24. package/dist/auth/api-keys/api-key-routes.d.ts +41 -11
  25. package/dist/auth/api-keys/api-key-store.d.ts +31 -8
  26. package/dist/auth/api-keys/api-key-types.d.ts +14 -16
  27. package/dist/auth/api-keys/http-operation.d.ts +19 -0
  28. package/dist/auth/api-keys/index.d.ts +11 -11
  29. package/dist/auth/api-keys/key-grant.d.ts +41 -0
  30. package/dist/auth/api-keys/legacy-permissions.d.ts +33 -0
  31. package/dist/auth/auth-hooks.d.ts +46 -7
  32. package/dist/auth/builtin-auth-adapter.d.ts +8 -0
  33. package/dist/auth/cookie-utils.d.ts +7 -0
  34. package/dist/auth/deliverable-address.d.ts +6 -0
  35. package/dist/auth/email-change-routes.d.ts +41 -0
  36. package/dist/auth/expired-token-sweep.d.ts +67 -0
  37. package/dist/auth/impersonation.d.ts +110 -0
  38. package/dist/auth/index.d.ts +4 -2
  39. package/dist/auth/interfaces.d.ts +110 -59
  40. package/dist/auth/jwt.d.ts +49 -3
  41. package/dist/auth/magic-link-routes.d.ts +2 -6
  42. package/dist/auth/mfa-routes.d.ts +2 -9
  43. package/dist/auth/middleware.d.ts +17 -5
  44. package/dist/auth/otp-routes.d.ts +2 -6
  45. package/dist/auth/passwordless-signup.d.ts +27 -0
  46. package/dist/auth/platform-token.d.ts +122 -0
  47. package/dist/auth/rate-limiter.d.ts +41 -0
  48. package/dist/auth/routes.d.ts +45 -0
  49. package/dist/auth/scope-routes.d.ts +22 -0
  50. package/dist/auth/session-routes.d.ts +11 -6
  51. package/dist/auth/token-revocation.d.ts +50 -1
  52. package/dist/auth/verify-credential.d.ts +28 -0
  53. package/dist/{auth-B-GIMpDG.js → auth-DMLngxn_.js} +2159 -569
  54. package/dist/auth-DMLngxn_.js.map +1 -0
  55. package/dist/backend-DTAOsLQc.js.map +1 -1
  56. package/dist/backup/backup-common.d.ts +10 -0
  57. package/dist/backup/backup-routes.d.ts +24 -4
  58. package/dist/backup/backup-schedule.d.ts +33 -0
  59. package/dist/backup/backup-storage.d.ts +14 -0
  60. package/dist/backup/index.d.ts +2 -0
  61. package/dist/backup-CN0s50D2.js +444 -0
  62. package/dist/backup-CN0s50D2.js.map +1 -0
  63. package/dist/boot/bundle.d.ts +19 -0
  64. package/dist/boot/env.d.ts +49 -4
  65. package/dist/boot/security-headers.d.ts +26 -0
  66. package/dist/boot/static-routing.d.ts +56 -0
  67. package/dist/collection_patch-BRu-BvDv.js +472 -0
  68. package/dist/collection_patch-BRu-BvDv.js.map +1 -0
  69. package/dist/{contract-routes-CbFjuBwa.js → contract-routes-fz8i4pxs.js} +17 -4
  70. package/dist/contract-routes-fz8i4pxs.js.map +1 -0
  71. package/dist/cron/cron-scheduler.d.ts +25 -20
  72. package/dist/cron/cron-store.d.ts +6 -2
  73. package/dist/{cron-loader-DfTj2Hbi.js → cron-loader-CwaANlOG.js} +4 -4
  74. package/dist/cron-loader-CwaANlOG.js.map +1 -0
  75. package/dist/{cron-routes-eE8nif_b.js → cron-routes-Bc-SB0Se.js} +10 -7
  76. package/dist/cron-routes-Bc-SB0Se.js.map +1 -0
  77. package/dist/{cron-scheduler-B0pLfAix.js → cron-scheduler-CYQgco86.js} +52 -34
  78. package/dist/cron-scheduler-CYQgco86.js.map +1 -0
  79. package/dist/{cron-store-TcoGz-xS.js → cron-store-D2Q9-Aco.js} +10 -15
  80. package/dist/cron-store-D2Q9-Aco.js.map +1 -0
  81. package/dist/{ddl-bootstrap-C6mo0Kmz.js → ddl-bootstrap-BaqMSa4Y.js} +2 -2
  82. package/dist/{ddl-bootstrap-C6mo0Kmz.js.map → ddl-bootstrap-BaqMSa4Y.js.map} +1 -1
  83. package/dist/email/index.d.ts +2 -2
  84. package/dist/email/templates.d.ts +22 -0
  85. package/dist/email/types.d.ts +26 -0
  86. package/dist/env.d.ts +1 -2
  87. package/dist/{errors-DWsX4yTd.js → errors-D6_y86c5.js} +102 -8
  88. package/dist/errors-D6_y86c5.js.map +1 -0
  89. package/dist/{function-loader-xnbDAPfa.js → function-loader-D7o5Epjj.js} +2 -2
  90. package/dist/{function-loader-xnbDAPfa.js.map → function-loader-D7o5Epjj.js.map} +1 -1
  91. package/dist/{function-routes-Chet4-lB.js → function-routes-CaNG4waN.js} +24 -12
  92. package/dist/function-routes-CaNG4waN.js.map +1 -0
  93. package/dist/functions/context.d.ts +17 -6
  94. package/dist/functions/guards.d.ts +22 -5
  95. package/dist/functions/index.d.ts +2 -2
  96. package/dist/functions/index.js +90 -36
  97. package/dist/functions/index.js.map +1 -1
  98. package/dist/{history-recorder-B4MpJfJK.js → history-recorder-Nr8zLvoU.js} +4 -4
  99. package/dist/{history-recorder-B4MpJfJK.js.map → history-recorder-Nr8zLvoU.js.map} +1 -1
  100. package/dist/{history-store-BhxWOuz9.js → history-store-rcAm_xFR.js} +2 -2
  101. package/dist/{history-store-BhxWOuz9.js.map → history-store-rcAm_xFR.js.map} +1 -1
  102. package/dist/index.d.ts +8 -2
  103. package/dist/index.es.js +3084 -753
  104. package/dist/index.es.js.map +1 -1
  105. package/dist/init/health.d.ts +17 -2
  106. package/dist/init/shutdown.d.ts +10 -0
  107. package/dist/init.d.ts +54 -0
  108. package/dist/{jobs-CazMYhyy.js → jobs-DqYNfquG.js} +5 -5
  109. package/dist/{jobs-CazMYhyy.js.map → jobs-DqYNfquG.js.map} +1 -1
  110. package/dist/{jwt-DnQHNFCl.js → jwt-R6bSPMjk.js} +39 -15
  111. package/dist/{jwt-DnQHNFCl.js.map → jwt-R6bSPMjk.js.map} +1 -1
  112. package/dist/{keys-CogCQpxG.js → keys-GAVZqbqx.js} +3 -17
  113. package/dist/{keys-CogCQpxG.js.map → keys-GAVZqbqx.js.map} +1 -1
  114. package/dist/{logger-DO2PZc4i.js → logger-D-S-hO5e.js} +26 -3
  115. package/dist/logger-D-S-hO5e.js.map +1 -0
  116. package/dist/{logs-routes-Bj4TYYUl.js → logs-routes-DAdv37GI.js} +48 -8
  117. package/dist/logs-routes-DAdv37GI.js.map +1 -0
  118. package/dist/mcp/consent-page.d.ts +1 -1
  119. package/dist/mcp/mcp-routes.d.ts +7 -0
  120. package/dist/mcp/mcp-tools.d.ts +15 -9
  121. package/dist/mcp/oauth-metadata.d.ts +21 -16
  122. package/dist/mcp/oauth-routes.d.ts +7 -1
  123. package/dist/{openapi-generator-O_O24MAT.js → openapi-generator-DAq_XVDu.js} +104 -13
  124. package/dist/openapi-generator-DAq_XVDu.js.map +1 -0
  125. package/dist/{proxy-Czngl3p9.js → proxy-qRlqeUmO.js} +2 -2
  126. package/dist/{proxy-Czngl3p9.js.map → proxy-qRlqeUmO.js.map} +1 -1
  127. package/dist/{query-parser-DGRVFNM3.js → query-parser-BgiKJKvc.js} +6 -56
  128. package/dist/query-parser-BgiKJKvc.js.map +1 -0
  129. package/dist/{request-timeout-C_4C2BeR.js → request-timeout-DgH7j8qO.js} +3 -3
  130. package/dist/{request-timeout-C_4C2BeR.js.map → request-timeout-DgH7j8qO.js.map} +1 -1
  131. package/dist/schema-edit/apply-schema-change.d.ts +63 -3
  132. package/dist/schema-edit/project-root.d.ts +3 -2
  133. package/dist/schema-edit/remote-source.d.ts +9 -4
  134. package/dist/{schema-editor-routes-C5-lh_jO.js → schema-editor-routes-oIyuWl3L.js} +12 -7
  135. package/dist/schema-editor-routes-oIyuWl3L.js.map +1 -0
  136. package/dist/serve-spa.d.ts +58 -0
  137. package/dist/services/routed-realtime-service.d.ts +11 -0
  138. package/dist/soft-delete-params-BWPilMPF.js +59 -0
  139. package/dist/soft-delete-params-BWPilMPF.js.map +1 -0
  140. package/dist/{src-vkcwKXbT.js → src-CatHFUym.js} +439 -20
  141. package/dist/src-CatHFUym.js.map +1 -0
  142. package/dist/{src-pmvW7BFx.js → src-I3aG1PcY.js} +252 -70
  143. package/dist/src-I3aG1PcY.js.map +1 -0
  144. package/dist/storage/GCSStorageController.d.ts +2 -0
  145. package/dist/storage/LocalStorageController.d.ts +2 -0
  146. package/dist/storage/S3StorageController.d.ts +2 -0
  147. package/dist/storage/index.d.ts +2 -2
  148. package/dist/storage/property-limits.d.ts +41 -6
  149. package/dist/storage/request-keys.d.ts +15 -0
  150. package/dist/storage/requested-object.d.ts +74 -0
  151. package/dist/storage/routes.d.ts +36 -18
  152. package/dist/storage/tus-handler.d.ts +30 -5
  153. package/dist/storage/types.d.ts +19 -0
  154. package/dist/types-BfKcm9do.js.map +1 -1
  155. package/dist/utils/logger.d.ts +12 -0
  156. package/package.json +5 -5
  157. package/dist/GCSStorageController-CjrA4PMo.js.map +0 -1
  158. package/dist/S3StorageController-B6pKDNVj.js.map +0 -1
  159. package/dist/admin-roles-vYdp_Pil.js +0 -36
  160. package/dist/admin-roles-vYdp_Pil.js.map +0 -1
  161. package/dist/admin_block-DxKLmdiv.js +0 -206
  162. package/dist/admin_block-DxKLmdiv.js.map +0 -1
  163. package/dist/ast-schema-editor-Mvr50v_S.js.map +0 -1
  164. package/dist/auth/api-keys/api-key-permission-guard.d.ts +0 -65
  165. package/dist/auth-B-GIMpDG.js.map +0 -1
  166. package/dist/backup-D7YR94N3.js +0 -253
  167. package/dist/backup-D7YR94N3.js.map +0 -1
  168. package/dist/contract-routes-CbFjuBwa.js.map +0 -1
  169. package/dist/cron-loader-DfTj2Hbi.js.map +0 -1
  170. package/dist/cron-routes-eE8nif_b.js.map +0 -1
  171. package/dist/cron-scheduler-B0pLfAix.js.map +0 -1
  172. package/dist/cron-store-TcoGz-xS.js.map +0 -1
  173. package/dist/errors-DWsX4yTd.js.map +0 -1
  174. package/dist/function-routes-Chet4-lB.js.map +0 -1
  175. package/dist/logger-DO2PZc4i.js.map +0 -1
  176. package/dist/logs-routes-Bj4TYYUl.js.map +0 -1
  177. package/dist/openapi-generator-O_O24MAT.js.map +0 -1
  178. package/dist/query-parser-DGRVFNM3.js.map +0 -1
  179. package/dist/schema-editor-routes-C5-lh_jO.js.map +0 -1
  180. package/dist/src-pmvW7BFx.js.map +0 -1
  181. package/dist/src-vkcwKXbT.js.map +0 -1
@@ -0,0 +1,26 @@
1
+ import type { MiddlewareHandler } from "hono";
2
+ /**
3
+ * The `Strict-Transport-Security` value this runtime sends.
4
+ *
5
+ * Without `includeSubDomains` unless the operator asks for it. The default
6
+ * `secureHeaders()` sends it, and on a self-hosted custom domain that tells
7
+ * every browser to refuse plain HTTP on every sibling subdomain for six months —
8
+ * a decision about hosts this process does not serve, and not one it can undo
9
+ * once a browser has cached it.
10
+ */
11
+ export declare function strictTransportSecurity(includeSubDomains: boolean): string;
12
+ /**
13
+ * The Content-Security-Policy every static app is served with.
14
+ *
15
+ * Conservative on purpose: it says who may frame the app (this origin only,
16
+ * like the `X-Frame-Options: SAMEORIGIN` already sent), forbids plugins, and
17
+ * pins `<base>` to this origin. It restricts no script, style, worker, font or
18
+ * connection source, because an app's own build decides those — the CMS admin
19
+ * runs inline scripts, module workers and third-party sign-in, and a policy
20
+ * that broke any of them would be one every project had to turn off.
21
+ */
22
+ export declare const STATIC_APP_CSP = "frame-ancestors 'self'; object-src 'none'; base-uri 'self'";
23
+ /** The security headers both boot paths — backend and static-only — install. */
24
+ export declare function runtimeSecureHeaders(options: {
25
+ hstsIncludeSubDomains: boolean;
26
+ }): MiddlewareHandler;
@@ -0,0 +1,56 @@
1
+ import { type StaticAppRequest } from "../serve-spa.js";
2
+ import { type LoadedStaticApp } from "./bundle.js";
3
+ /**
4
+ * A request's hostname in the form an app's `host` is written in: lowercase,
5
+ * with no port and no trailing dot.
6
+ *
7
+ * All three are ways of naming the same host that a browser, a proxy or a
8
+ * person with curl will send. `Admin.Example.com`, `admin.example.com:443` and
9
+ * the fully-qualified `admin.example.com.` are one site, and an app that
10
+ * answered only the first spelling would be down for whoever typed another.
11
+ *
12
+ * An IPv6 literal comes back as it arrived: its colons are not a port, and no
13
+ * app is ever given one, so all it has to do here is not be mangled into
14
+ * something that could match.
15
+ */
16
+ export declare function normalizeRequestHost(host: string): string;
17
+ /**
18
+ * Which static app answers a request: the one rule, written once.
19
+ *
20
+ * An app is a candidate when it names no hostname or names this one, and the
21
+ * path is under its own. Of the candidates, the longest path wins, and at an
22
+ * equal path the app with a hostname beats the one without — which is
23
+ * {@link compareStaticApps}, so the first candidate in that order is the winner.
24
+ * `undefined` when no app claims the request, and then no app answers it.
25
+ *
26
+ * The CLI and the control plane order apps by the same rule, so the app a
27
+ * deploy says is at an address is the app that answers there.
28
+ */
29
+ export declare function createStaticAppResolver(apps: readonly LoadedStaticApp[]): (request: StaticAppRequest) => LoadedStaticApp | undefined;
30
+ /** How one static app is mounted: what `serveSPA` needs beyond its own build. */
31
+ export interface StaticAppMount {
32
+ app: LoadedStaticApp;
33
+ /** Sibling paths the app's SPA fallback must decline — the path-only rule. */
34
+ siblingPaths: string[];
35
+ /** Present when apps are told apart by hostname; see `ServeSPAConfig.owns`. */
36
+ owns?: (request: StaticAppRequest) => boolean;
37
+ }
38
+ /**
39
+ * Decide how each static app is mounted, in the order given.
40
+ *
41
+ * Two rules, chosen by whether any app names a hostname.
42
+ *
43
+ * **None does:** every app declines the paths of the apps nested beneath it
44
+ * (`nestedAppPaths`), exactly as a bundle has always been served. Not the
45
+ * resolver, even though it would agree on every ordinary request: in the
46
+ * corner it disagrees — a file in the root app's build under a sibling's
47
+ * prefix — those bundles are already deployed. A bundle that never mentions
48
+ * a hostname gets byte-for-byte what it got before hostnames existed.
49
+ *
50
+ * **Any does:** a path cannot say which app a request belongs to any more.
51
+ * Two apps can both sit at "/", and a host-less site must still answer
52
+ * `/cms` on its own domain while an admin on another hostname owns `/cms`
53
+ * there — excluding "/cms" by path would 404 the site's. So every mount asks
54
+ * the one resolver instead, and declines whatever another app wins.
55
+ */
56
+ export declare function planStaticAppMounts(apps: readonly LoadedStaticApp[]): StaticAppMount[];
@@ -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,10 +3,23 @@ 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 { r as logger } from "./logger-D-S-hO5e.js";
8
8
  import { Hono } from "hono";
9
9
  //#region ../types/src/types/project_manifest.ts
10
+ /**
11
+ * Whether `host` is a public DNS name an app can be given: lowercase labels of
12
+ * letters, digits and hyphens, at least two of them, not an IP address and not
13
+ * `localhost`. Internationalised names arrive here already in punycode.
14
+ */
15
+ function isValidAppHost(host) {
16
+ if (host.length === 0 || host.length > 253 || host !== host.toLowerCase()) return false;
17
+ const labels = host.split(".");
18
+ if (labels.length < 2) return false;
19
+ const label = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
20
+ if (!labels.every((part) => label.test(part))) return false;
21
+ return !/^[0-9]+$/.test(labels[labels.length - 1]);
22
+ }
10
23
  /** Header carrying the schema version an SDK was generated from. */
11
24
  var SCHEMA_VERSION_HEADER = "x-rebase-schema";
12
25
  //#endregion
@@ -275,6 +288,6 @@ function createContractRoutes(config) {
275
288
  return router;
276
289
  }
277
290
  //#endregion
278
- export { SCHEMA_VERSION_HEADER as i, createContractRoutes as n, computeSchemaVersion as r, contract_routes_exports as t };
291
+ export { isValidAppHost as a, SCHEMA_VERSION_HEADER as i, createContractRoutes as n, computeSchemaVersion as r, contract_routes_exports as t };
279
292
 
280
- //# sourceMappingURL=contract-routes-CbFjuBwa.js.map
293
+ //# sourceMappingURL=contract-routes-fz8i4pxs.js.map