@rebasepro/server 0.22.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 (211) hide show
  1. package/README.md +1 -1
  2. package/bin/rebase-server.js +4 -2
  3. package/dist/{GCSStorageController-CLIJXwGS.js → GCSStorageController-BSiP1c-f.js} +57 -29
  4. package/dist/GCSStorageController-BSiP1c-f.js.map +1 -0
  5. package/dist/{S3StorageController-Dcuf8lMA.js → S3StorageController-CAwFRgjV.js} +19 -7
  6. package/dist/S3StorageController-CAwFRgjV.js.map +1 -0
  7. package/dist/api/ast-schema-editor.d.ts +127 -1
  8. package/dist/api/errors.d.ts +9 -0
  9. package/dist/api/live-schema-routes.d.ts +52 -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 +158 -42
  13. package/dist/api/rest/auth-collection-writes.d.ts +85 -0
  14. package/dist/api/rest/field-access-query.d.ts +6 -2
  15. package/dist/api/rest/idempotency.d.ts +7 -1
  16. package/dist/api/rest/nested-write-access.d.ts +46 -0
  17. package/dist/api/rest/write-validation.d.ts +34 -2
  18. package/dist/api/types.d.ts +17 -1
  19. package/dist/{ast-schema-editor-CslO8Oje.js → ast-schema-editor-CWqS_sLJ.js} +411 -13
  20. package/dist/ast-schema-editor-CWqS_sLJ.js.map +1 -0
  21. package/dist/auth/access.d.ts +105 -0
  22. package/dist/auth/adapter-middleware.d.ts +2 -1
  23. package/dist/auth/address-ownership.d.ts +68 -0
  24. package/dist/auth/admin-roles-route.d.ts +4 -2
  25. package/dist/auth/admin-roles.d.ts +17 -20
  26. package/dist/auth/admin-user-ops.d.ts +35 -2
  27. package/dist/auth/admin-users-route.d.ts +1 -0
  28. package/dist/auth/api-keys/api-key-middleware.d.ts +56 -55
  29. package/dist/auth/api-keys/api-key-routes.d.ts +41 -11
  30. package/dist/auth/api-keys/api-key-store.d.ts +31 -8
  31. package/dist/auth/api-keys/api-key-types.d.ts +14 -16
  32. package/dist/auth/api-keys/http-operation.d.ts +19 -0
  33. package/dist/auth/api-keys/index.d.ts +11 -11
  34. package/dist/auth/api-keys/key-grant.d.ts +41 -0
  35. package/dist/auth/api-keys/legacy-permissions.d.ts +33 -0
  36. package/dist/auth/auth-hooks.d.ts +50 -7
  37. package/dist/auth/builtin-auth-adapter.d.ts +8 -0
  38. package/dist/auth/captcha.d.ts +5 -0
  39. package/dist/auth/cookie-utils.d.ts +7 -0
  40. package/dist/auth/deliverable-address.d.ts +6 -0
  41. package/dist/auth/email-change-routes.d.ts +41 -0
  42. package/dist/auth/expired-token-sweep.d.ts +67 -0
  43. package/dist/auth/impersonation.d.ts +110 -0
  44. package/dist/auth/index.d.ts +4 -2
  45. package/dist/auth/interfaces.d.ts +146 -65
  46. package/dist/auth/jwt.d.ts +66 -3
  47. package/dist/auth/magic-link-routes.d.ts +2 -6
  48. package/dist/auth/mfa-routes.d.ts +2 -9
  49. package/dist/auth/middleware.d.ts +17 -5
  50. package/dist/auth/oauth-signin-policy.d.ts +25 -8
  51. package/dist/auth/otp-routes.d.ts +2 -6
  52. package/dist/auth/passwordless-signup.d.ts +27 -0
  53. package/dist/auth/platform-token.d.ts +122 -0
  54. package/dist/auth/rate-limiter.d.ts +72 -1
  55. package/dist/auth/routes.d.ts +45 -0
  56. package/dist/auth/scope-routes.d.ts +22 -0
  57. package/dist/auth/session-routes.d.ts +18 -6
  58. package/dist/auth/token-revocation.d.ts +53 -1
  59. package/dist/auth/verify-credential.d.ts +28 -0
  60. package/dist/{auth-CCDpk2rn.js → auth-DMLngxn_.js} +2712 -711
  61. package/dist/auth-DMLngxn_.js.map +1 -0
  62. package/dist/backend-DTAOsLQc.js +30 -0
  63. package/dist/backend-DTAOsLQc.js.map +1 -0
  64. package/dist/backup/backup-common.d.ts +29 -0
  65. package/dist/backup/backup-routes.d.ts +24 -4
  66. package/dist/backup/backup-schedule.d.ts +33 -0
  67. package/dist/backup/backup-storage.d.ts +14 -0
  68. package/dist/backup/index.d.ts +2 -0
  69. package/dist/backup-CN0s50D2.js +444 -0
  70. package/dist/backup-CN0s50D2.js.map +1 -0
  71. package/dist/boot/bundle.d.ts +19 -0
  72. package/dist/boot/driver.d.ts +10 -0
  73. package/dist/boot/env.d.ts +51 -6
  74. package/dist/boot/fetch-bundle.d.ts +18 -1
  75. package/dist/boot/rls-audit-option.d.ts +26 -0
  76. package/dist/boot/security-headers.d.ts +26 -0
  77. package/dist/boot/sources.d.ts +1 -0
  78. package/dist/boot/static-routing.d.ts +56 -0
  79. package/dist/collection_patch-BRu-BvDv.js +472 -0
  80. package/dist/collection_patch-BRu-BvDv.js.map +1 -0
  81. package/dist/{contract-routes-eLxV0le1.js → contract-routes-fz8i4pxs.js} +17 -4
  82. package/dist/contract-routes-fz8i4pxs.js.map +1 -0
  83. package/dist/cron/cron-routes.d.ts +7 -2
  84. package/dist/cron/cron-scheduler.d.ts +146 -21
  85. package/dist/cron/cron-store.d.ts +76 -8
  86. package/dist/cron/index.d.ts +1 -1
  87. package/dist/{cron-loader-CQjvjpEw.js → cron-loader-CwaANlOG.js} +4 -4
  88. package/dist/cron-loader-CwaANlOG.js.map +1 -0
  89. package/dist/cron-routes-Bc-SB0Se.js +96 -0
  90. package/dist/cron-routes-Bc-SB0Se.js.map +1 -0
  91. package/dist/{cron-scheduler-COPQxlEq.js → cron-scheduler-CYQgco86.js} +427 -83
  92. package/dist/cron-scheduler-CYQgco86.js.map +1 -0
  93. package/dist/{cron-store-BYGZFNWk.js → cron-store-D2Q9-Aco.js} +139 -23
  94. package/dist/cron-store-D2Q9-Aco.js.map +1 -0
  95. package/dist/{ddl-bootstrap-CfNvxMuK.js → ddl-bootstrap-BaqMSa4Y.js} +3 -26
  96. package/dist/ddl-bootstrap-BaqMSa4Y.js.map +1 -0
  97. package/dist/email/index.d.ts +2 -2
  98. package/dist/email/link-base.d.ts +5 -4
  99. package/dist/email/smtp-email-service.d.ts +13 -1
  100. package/dist/email/templates.d.ts +31 -0
  101. package/dist/email/types.d.ts +29 -2
  102. package/dist/env.d.ts +25 -7
  103. package/dist/{errors-DWsX4yTd.js → errors-D6_y86c5.js} +102 -8
  104. package/dist/errors-D6_y86c5.js.map +1 -0
  105. package/dist/{function-loader-xnbDAPfa.js → function-loader-D7o5Epjj.js} +2 -2
  106. package/dist/{function-loader-xnbDAPfa.js.map → function-loader-D7o5Epjj.js.map} +1 -1
  107. package/dist/{function-routes-Chet4-lB.js → function-routes-CaNG4waN.js} +24 -12
  108. package/dist/function-routes-CaNG4waN.js.map +1 -0
  109. package/dist/functions/context.d.ts +17 -6
  110. package/dist/functions/guards.d.ts +22 -5
  111. package/dist/functions/index.d.ts +2 -2
  112. package/dist/functions/index.js +90 -36
  113. package/dist/functions/index.js.map +1 -1
  114. package/dist/{history-recorder-BQmB0P_j.js → history-recorder-Nr8zLvoU.js} +9 -7
  115. package/dist/history-recorder-Nr8zLvoU.js.map +1 -0
  116. package/dist/{history-store-CetkrBBD.js → history-store-rcAm_xFR.js} +2 -2
  117. package/dist/{history-store-CetkrBBD.js.map → history-store-rcAm_xFR.js.map} +1 -1
  118. package/dist/index.d.ts +14 -4
  119. package/dist/index.es.js +5733 -1551
  120. package/dist/index.es.js.map +1 -1
  121. package/dist/init/docs.d.ts +5 -2
  122. package/dist/init/health.d.ts +17 -2
  123. package/dist/init/shutdown.d.ts +18 -3
  124. package/dist/init.d.ts +54 -0
  125. package/dist/jobs/index.d.ts +2 -2
  126. package/dist/jobs/job-queue.d.ts +23 -2
  127. package/dist/jobs/job-store.d.ts +37 -5
  128. package/dist/jobs/types.d.ts +8 -6
  129. package/dist/{jobs-Bjr8DZAi.js → jobs-DqYNfquG.js} +306 -167
  130. package/dist/jobs-DqYNfquG.js.map +1 -0
  131. package/dist/{jwt-C4OW-DNq.js → jwt-R6bSPMjk.js} +114 -38
  132. package/dist/{jwt-C4OW-DNq.js.map → jwt-R6bSPMjk.js.map} +1 -1
  133. package/dist/{keys-Qfc4XieN.js → keys-GAVZqbqx.js} +18 -17
  134. package/dist/{keys-Qfc4XieN.js.map → keys-GAVZqbqx.js.map} +1 -1
  135. package/dist/{logger-DO2PZc4i.js → logger-D-S-hO5e.js} +26 -3
  136. package/dist/logger-D-S-hO5e.js.map +1 -0
  137. package/dist/{logs-routes-3EEzPjhl.js → logs-routes-DAdv37GI.js} +54 -11
  138. package/dist/logs-routes-DAdv37GI.js.map +1 -0
  139. package/dist/mcp/consent-page.d.ts +1 -1
  140. package/dist/mcp/mcp-routes.d.ts +45 -2
  141. package/dist/mcp/mcp-tools.d.ts +22 -10
  142. package/dist/mcp/oauth-metadata.d.ts +21 -16
  143. package/dist/mcp/oauth-routes.d.ts +34 -1
  144. package/dist/mcp/oauth-store.d.ts +29 -13
  145. package/dist/metrics/history-recorder.d.ts +1 -1
  146. package/dist/{openapi-generator-D8uFz-LW.js → openapi-generator-DAq_XVDu.js} +135 -22
  147. package/dist/openapi-generator-DAq_XVDu.js.map +1 -0
  148. package/dist/{proxy-Czngl3p9.js → proxy-qRlqeUmO.js} +2 -2
  149. package/dist/{proxy-Czngl3p9.js.map → proxy-qRlqeUmO.js.map} +1 -1
  150. package/dist/{query-parser-BleZmY18.js → query-parser-BgiKJKvc.js} +41 -82
  151. package/dist/query-parser-BgiKJKvc.js.map +1 -0
  152. package/dist/{request-timeout-C_4C2BeR.js → request-timeout-DgH7j8qO.js} +3 -3
  153. package/dist/{request-timeout-C_4C2BeR.js.map → request-timeout-DgH7j8qO.js.map} +1 -1
  154. package/dist/rls-audit/index.d.ts +4 -0
  155. package/dist/schema-edit/apply-schema-change.d.ts +63 -3
  156. package/dist/schema-edit/project-root.d.ts +3 -2
  157. package/dist/schema-edit/remote-source.d.ts +9 -4
  158. package/dist/{schema-editor-routes-DdLihzp0.js → schema-editor-routes-oIyuWl3L.js} +12 -7
  159. package/dist/schema-editor-routes-oIyuWl3L.js.map +1 -0
  160. package/dist/serve-spa.d.ts +58 -0
  161. package/dist/services/routed-realtime-service.d.ts +11 -0
  162. package/dist/soft-delete-params-BWPilMPF.js +59 -0
  163. package/dist/soft-delete-params-BWPilMPF.js.map +1 -0
  164. package/dist/{src-Br6ARbs6.js → src-CatHFUym.js} +439 -45
  165. package/dist/src-CatHFUym.js.map +1 -0
  166. package/dist/{src-1vL-I1Po.js → src-I3aG1PcY.js} +371 -81
  167. package/dist/src-I3aG1PcY.js.map +1 -0
  168. package/dist/storage/GCSStorageController.d.ts +13 -1
  169. package/dist/storage/LocalStorageController.d.ts +2 -0
  170. package/dist/storage/S3StorageController.d.ts +2 -0
  171. package/dist/storage/index.d.ts +2 -2
  172. package/dist/storage/keys.d.ts +12 -0
  173. package/dist/storage/property-limits.d.ts +41 -6
  174. package/dist/storage/rendition-cache.d.ts +11 -1
  175. package/dist/storage/request-keys.d.ts +82 -0
  176. package/dist/storage/requested-object.d.ts +74 -0
  177. package/dist/storage/routes.d.ts +36 -18
  178. package/dist/storage/tus-handler.d.ts +30 -5
  179. package/dist/storage/types.d.ts +36 -1
  180. package/dist/types-BfKcm9do.js.map +1 -1
  181. package/dist/utils/logger.d.ts +12 -0
  182. package/package.json +9 -9
  183. package/dist/GCSStorageController-CLIJXwGS.js.map +0 -1
  184. package/dist/S3StorageController-Dcuf8lMA.js.map +0 -1
  185. package/dist/admin-roles-vYdp_Pil.js +0 -36
  186. package/dist/admin-roles-vYdp_Pil.js.map +0 -1
  187. package/dist/admin_block-DxKLmdiv.js +0 -206
  188. package/dist/admin_block-DxKLmdiv.js.map +0 -1
  189. package/dist/ast-schema-editor-CslO8Oje.js.map +0 -1
  190. package/dist/auth/api-keys/api-key-permission-guard.d.ts +0 -65
  191. package/dist/auth-CCDpk2rn.js.map +0 -1
  192. package/dist/backup-DzI9jLwc.js +0 -192
  193. package/dist/backup-DzI9jLwc.js.map +0 -1
  194. package/dist/contract-routes-eLxV0le1.js.map +0 -1
  195. package/dist/cron-loader-CQjvjpEw.js.map +0 -1
  196. package/dist/cron-routes-B7CRGfiq.js +0 -72
  197. package/dist/cron-routes-B7CRGfiq.js.map +0 -1
  198. package/dist/cron-scheduler-COPQxlEq.js.map +0 -1
  199. package/dist/cron-store-BYGZFNWk.js.map +0 -1
  200. package/dist/ddl-bootstrap-CfNvxMuK.js.map +0 -1
  201. package/dist/errors-DWsX4yTd.js.map +0 -1
  202. package/dist/function-routes-Chet4-lB.js.map +0 -1
  203. package/dist/history-recorder-BQmB0P_j.js.map +0 -1
  204. package/dist/jobs-Bjr8DZAi.js.map +0 -1
  205. package/dist/logger-DO2PZc4i.js.map +0 -1
  206. package/dist/logs-routes-3EEzPjhl.js.map +0 -1
  207. package/dist/openapi-generator-D8uFz-LW.js.map +0 -1
  208. package/dist/query-parser-BleZmY18.js.map +0 -1
  209. package/dist/schema-editor-routes-DdLihzp0.js.map +0 -1
  210. package/dist/src-1vL-I1Po.js.map +0 -1
  211. package/dist/src-Br6ARbs6.js.map +0 -1
@@ -3,7 +3,7 @@ import __rebaseProcess from "process";
3
3
  globalThis.process ??= __rebaseProcess;
4
4
  __rebaseCreateRequire(import.meta.url);
5
5
  import { r as __require, t as __commonJSMin } from "./rolldown-runtime-dW7B1o5h.js";
6
- import { A as LIST_OPS, C as getDeclaredSubcollections, D as getDataSourceCapabilities, F as sortKeyToString, I as toCanonicalOp, M as REST_TO_CANONICAL, N as isRelationAggregateSort, O as ALL_WHERE_FILTER_OPS, T as isRelationalCollectionConfig, j as NULL_OPS, k as CANONICAL_TO_REST, p as resolveResourceRefs, w as isPostgresCollectionConfig, x as rewriteLegacyRlsFunctions } from "./src-Br6ARbs6.js";
6
+ import { B as rewriteLegacyRlsFunctions, H as getDeclaredSubcollections, J as CANONICAL_TO_REST, K as getDataSourceCapabilities, M as resolveResourceRefs, Q as isRelationAggregateSort, U as isPostgresCollectionConfig, V as getCollectionDataPath, W as isRelationalCollectionConfig, X as NULL_OPS, Y as LIST_OPS, Z as REST_TO_CANONICAL, et as sortKeyToString, q as ALL_WHERE_FILTER_OPS, tt as toCanonicalOp } from "./src-CatHFUym.js";
7
7
  //#region ../types/src/errors.ts
8
8
  /**
9
9
  * The single error type thrown across the entire Rebase client surface —
@@ -261,6 +261,10 @@ var Vector = class {
261
261
  */
262
262
  var JUNCTION_PIVOT_KEY = "_pivot";
263
263
  /** @group Models */
264
+ function hasForeignKeyOnTarget(relation) {
265
+ return relation.kind === "hasOne" || relation.kind === "hasMany";
266
+ }
267
+ /** @group Models */
264
268
  function isManyToMany(relation) {
265
269
  return relation.kind === "manyToMany";
266
270
  }
@@ -3266,6 +3270,7 @@ var REBASE_INTERNAL_TABLES = [
3266
3270
  "api_keys",
3267
3271
  "cron_logs",
3268
3272
  "cron_claims",
3273
+ "cron_job_state",
3269
3274
  "jobs",
3270
3275
  "rate_limit_hits",
3271
3276
  "idempotency_keys",
@@ -4013,6 +4018,13 @@ var CollectionRegistry = class {
4013
4018
  }
4014
4019
  collectionsByTableName = /* @__PURE__ */ new Map();
4015
4020
  collectionsBySlug = /* @__PURE__ */ new Map();
4021
+ /**
4022
+ * Root collections whose driver stores them under a `path` other than
4023
+ * their slug, by that path. A list, because the path is only unique within
4024
+ * a data source: two collections in different databases may both be stored
4025
+ * at `customer`.
4026
+ */
4027
+ collectionsByDataPath = /* @__PURE__ */ new Map();
4016
4028
  rootCollections = [];
4017
4029
  cachedCollectionsList = null;
4018
4030
  rawCollectionsByTableName = /* @__PURE__ */ new Map();
@@ -4037,6 +4049,7 @@ var CollectionRegistry = class {
4037
4049
  reset() {
4038
4050
  this.collectionsByTableName.clear();
4039
4051
  this.collectionsBySlug.clear();
4052
+ this.collectionsByDataPath.clear();
4040
4053
  this.rootCollections = [];
4041
4054
  this.cachedCollectionsList = null;
4042
4055
  this.rawCollectionsByTableName.clear();
@@ -4069,6 +4082,7 @@ var CollectionRegistry = class {
4069
4082
  this.collectionsByTableName.set(getTableName(normalized), normalized);
4070
4083
  this.rawCollectionsByTableName.set(getTableName(raw), raw);
4071
4084
  if (normalized.slug) this.collectionsBySlug.set(normalized.slug, normalized);
4085
+ this.indexDataPath(normalized);
4072
4086
  if (raw.slug) this.rawCollectionsBySlug.set(raw.slug, raw);
4073
4087
  });
4074
4088
  normalizedCollections.forEach((c) => {
@@ -4086,6 +4100,19 @@ var CollectionRegistry = class {
4086
4100
  this.rootCollections.push(collection);
4087
4101
  this.rawRootCollections.push(raw);
4088
4102
  this._registerRecursively(collection, raw);
4103
+ const registered = this.collectionsByTableName.get(getTableName(collection));
4104
+ if (registered) this.indexDataPath(registered);
4105
+ }
4106
+ /**
4107
+ * Index a root collection by the path its driver stores it under, when
4108
+ * that is not its slug. Only roots: a subcollection's `path` is relative to
4109
+ * the record it hangs off, so it names nothing on its own.
4110
+ */
4111
+ indexDataPath(collection) {
4112
+ const dataPath = getCollectionDataPath(collection);
4113
+ if (!dataPath || dataPath === collection.slug) return;
4114
+ const indexed = this.collectionsByDataPath.get(dataPath) ?? [];
4115
+ if (!indexed.includes(collection)) this.collectionsByDataPath.set(dataPath, [...indexed, collection]);
4089
4116
  }
4090
4117
  _registerRecursively(collection, rawCollection) {
4091
4118
  if (this.collectionsByTableName.has(getTableName(collection))) return;
@@ -4139,7 +4166,24 @@ var CollectionRegistry = class {
4139
4166
  }
4140
4167
  return newProperty;
4141
4168
  }
4142
- get(path) {
4169
+ /**
4170
+ * The collection registered under `path`: by slug, by slug with hyphens
4171
+ * for underscores, by table name, and last by the `path` a root Firestore
4172
+ * or MongoDB collection declares for its driver.
4173
+ *
4174
+ * One string can name two collections. Declaring `slug: "fs_diagnosis",
4175
+ * path: "diagnosis"` is how a Firestore collection sits beside a Postgres
4176
+ * collection whose slug is `diagnosis` — and a reference read back from
4177
+ * Firestore carries `diagnosis`. The order above settles it in favour of the
4178
+ * slug unless `preferredDriver` is given: then the first collection whose
4179
+ * data source or engine is `preferredDriver` wins. Pass a reference's
4180
+ * `driver`, or the data source of the record the path was read from.
4181
+ */
4182
+ get(path, preferredDriver) {
4183
+ if (preferredDriver) {
4184
+ const preferred = this.candidatesFor(path).find((collection) => collection.dataSource === preferredDriver || collection.engine === preferredDriver);
4185
+ if (preferred) return preferred;
4186
+ }
4143
4187
  const bySlug = this.collectionsBySlug.get(path);
4144
4188
  if (bySlug) return bySlug;
4145
4189
  if (path.includes("-")) {
@@ -4147,7 +4191,84 @@ var CollectionRegistry = class {
4147
4191
  const byNormalized = this.collectionsBySlug.get(normalized);
4148
4192
  if (byNormalized) return byNormalized;
4149
4193
  }
4150
- return this.collectionsByTableName.get(path);
4194
+ const byTableName = this.collectionsByTableName.get(path);
4195
+ if (byTableName) return byTableName;
4196
+ return this.collectionsByDataPath.get(path)?.[0];
4197
+ }
4198
+ /** Every collection {@link get} could answer `path` with, in its order. */
4199
+ candidatesFor(path) {
4200
+ const candidates = [
4201
+ this.collectionsBySlug.get(path),
4202
+ path.includes("-") ? this.collectionsBySlug.get(path.replace(/-/g, "_")) : void 0,
4203
+ this.collectionsByTableName.get(path),
4204
+ ...this.collectionsByDataPath.get(path) ?? []
4205
+ ];
4206
+ return candidates.filter((collection, index) => collection !== void 0 && candidates.indexOf(collection) === index);
4207
+ }
4208
+ /**
4209
+ * The path a collection's rows are stored under, for the path the admin
4210
+ * addresses it by.
4211
+ *
4212
+ * The admin addresses a collection by its slug — in routes, in
4213
+ * `data.collection(...)`, in the `path` of every entity it lists — because
4214
+ * the slug is what is unique. A Firestore or MongoDB collection may declare
4215
+ * a `path` of its own ({@link getCollectionDataPath}), and its driver has to
4216
+ * be handed that one, under every record and subcollection too:
4217
+ * `fs_diagnosis/abc/locales` is stored at `diagnosis/abc/locales`.
4218
+ *
4219
+ * A path that names no registered collection, or runs only through
4220
+ * collections stored under their slugs, comes back unchanged.
4221
+ */
4222
+ resolveDataPath(path) {
4223
+ let resolved;
4224
+ const exact = this.get(path);
4225
+ if (exact) resolved = {
4226
+ collections: [exact],
4227
+ entityIds: []
4228
+ };
4229
+ else try {
4230
+ resolved = this.resolvePathToCollections(path);
4231
+ } catch {
4232
+ return path;
4233
+ }
4234
+ const { collections, entityIds } = resolved;
4235
+ if (collections.every((collection) => getCollectionDataPath(collection) === collection.slug)) return path;
4236
+ return collections.map((collection, index) => index < entityIds.length ? `${getCollectionDataPath(collection)}/${entityIds[index]}` : getCollectionDataPath(collection)).join("/");
4237
+ }
4238
+ /**
4239
+ * The path the admin addresses a collection by, for a path its rows are
4240
+ * stored under: the inverse of {@link resolveDataPath}, for a reference
4241
+ * read back from its driver.
4242
+ *
4243
+ * The root is the longest leading run of segments naming a collection, and
4244
+ * becomes its slug when the run is where that collection is stored. Each
4245
+ * subcollection after a record id is matched by where it is stored too. A
4246
+ * path that already names its collections by slug comes back unchanged.
4247
+ *
4248
+ * `preferredDriver` decides between a collection stored at the path and one
4249
+ * whose slug it is, as in {@link get}.
4250
+ */
4251
+ resolveCollectionPath(path, preferredDriver) {
4252
+ const segments = path.split("/").filter(Boolean);
4253
+ for (let length = segments.length; length > 0; length--) {
4254
+ const prefix = segments.slice(0, length).join("/");
4255
+ const root = this.get(prefix, preferredDriver);
4256
+ if (!root) continue;
4257
+ const translated = [getCollectionDataPath(root) === prefix ? root.slug : prefix];
4258
+ let current = root;
4259
+ let rest = segments.slice(length);
4260
+ while (rest.length >= 2) {
4261
+ const [entityId, storedAt, ...after] = rest;
4262
+ const child = getSubcollections(current).find((subcollection) => getCollectionDataPath(subcollection) === storedAt);
4263
+ if (!child) break;
4264
+ translated.push(entityId, child.slug);
4265
+ current = child;
4266
+ rest = after;
4267
+ }
4268
+ const collectionPath = [...translated, ...rest].join("/");
4269
+ return collectionPath === segments.join("/") ? path : collectionPath;
4270
+ }
4271
+ return path;
4151
4272
  }
4152
4273
  /**
4153
4274
  * Gets the pristine, un-normalized collection exactly as it was provided.
@@ -4195,29 +4316,41 @@ var CollectionRegistry = class {
4195
4316
  }
4196
4317
  /**
4197
4318
  * Resolves a multi-segment path like "products/123/locales" and returns
4198
- * information about the collections and entity IDs along the path
4319
+ * information about the collections and entity IDs along the path.
4320
+ *
4321
+ * A slug may contain slashes (`content/de-DE/podcasts`), so the root is
4322
+ * the longest leading run of segments naming a registered collection, and
4323
+ * each subcollection is matched the same way among its parent's.
4324
+ *
4325
+ * @param options.allowRecordPath accept a path that ends at a record
4326
+ * (`…/{id}`) and resolve it to the collection holding that record, with the
4327
+ * id last in `entityIds`. Without it such a path is an error. Only the
4328
+ * registry can tell the two shapes apart, because only it knows where each
4329
+ * slug ends.
4199
4330
  */
4200
- resolvePathToCollections(path) {
4331
+ resolvePathToCollections(path, options) {
4201
4332
  const pathSegments = path.split("/").filter((p) => p);
4202
4333
  if (pathSegments.length === 0) throw new Error(`Invalid path: ${path}`);
4203
- if (pathSegments.length % 2 !== 1) throw new Error(`Invalid collection path: ${path}. It must have an odd number of segments.`);
4204
- const collections = [];
4334
+ const root = splitAtLeadingCollection(pathSegments, (candidate) => this.get(candidate));
4335
+ if (!root) throw new Error(`Unknown collection path or slug: ${path}`);
4336
+ let currentCollection = root.collection;
4337
+ const collections = [currentCollection];
4205
4338
  const entityIds = [];
4206
- let currentCollection = this.get(pathSegments[0]);
4207
- if (!currentCollection) throw new Error(`Unknown collection path or slug: ${pathSegments[0]}`);
4208
- collections.push(currentCollection);
4209
- for (let i = 1; i < pathSegments.length; i += 2) {
4210
- const entityId = pathSegments[i];
4339
+ let rest = root.rest;
4340
+ while (rest.length > 0) {
4341
+ const [entityId, ...afterEntity] = rest;
4211
4342
  entityIds.push(entityId);
4212
- if (i + 1 < pathSegments.length) {
4213
- const subcollectionSlug = pathSegments[i + 1];
4214
- const subcollections = getSubcollections(currentCollection);
4215
- if (!subcollections || subcollections.length === 0) throw new Error(`No subcollections found for ${currentCollection.slug} in path: ${path}`);
4216
- const subcollection = subcollections.find((c) => c.slug === subcollectionSlug);
4217
- if (!subcollection) throw new Error(`Subcollection '${subcollectionSlug}' not found in ${currentCollection.slug}`);
4218
- currentCollection = this.normalizeCollection(subcollection);
4219
- collections.push(currentCollection);
4343
+ if (afterEntity.length === 0) {
4344
+ if (options?.allowRecordPath) break;
4345
+ throw new Error(`Invalid collection path: ${path}. It ends at the record '${entityId}' of '${currentCollection.slug}', not at a collection.`);
4220
4346
  }
4347
+ const subcollections = getSubcollections(currentCollection);
4348
+ if (!subcollections || subcollections.length === 0) throw new Error(`No subcollections found for ${currentCollection.slug} in path: ${path}`);
4349
+ const child = splitAtLeadingCollection(afterEntity, (candidate) => subcollections.find((c) => c.slug === candidate));
4350
+ if (!child) throw new Error(`Subcollection '${afterEntity[0]}' not found in ${currentCollection.slug}`);
4351
+ currentCollection = this.normalizeCollection(child.collection);
4352
+ collections.push(currentCollection);
4353
+ rest = child.rest;
4221
4354
  }
4222
4355
  return {
4223
4356
  collections,
@@ -4226,6 +4359,26 @@ var CollectionRegistry = class {
4226
4359
  };
4227
4360
  }
4228
4361
  };
4362
+ /**
4363
+ * Split path segments at the collection they start with.
4364
+ *
4365
+ * A slug may contain slashes, and several drivers need it to: a Firestore
4366
+ * collection partitioned by locale is declared as `content/de-DE/podcasts`,
4367
+ * and that string is its *name*. So the collection a path starts with is not
4368
+ * its first segment but the longest run of whole leading segments that names
4369
+ * one, and what follows it is `entityId/subcollection/…`.
4370
+ *
4371
+ * Whole segments only, so `content` never matches the start of `contents/1`.
4372
+ */
4373
+ function splitAtLeadingCollection(segments, find) {
4374
+ for (let length = segments.length; length > 0; length--) {
4375
+ const collection = find(segments.slice(0, length).join("/"));
4376
+ if (collection) return {
4377
+ collection,
4378
+ rest: segments.slice(length)
4379
+ };
4380
+ }
4381
+ }
4229
4382
  //#endregion
4230
4383
  //#region ../common/src/collections/engine-support.ts
4231
4384
  /**
@@ -4317,18 +4470,36 @@ function canWriteField(property, viewer) {
4317
4470
  * who knows the table can send `password_hash` as readily as `passwordHash`, and
4318
4471
  * a rule that only knew the wire name would be one rename away from useless.
4319
4472
  *
4473
+ * A restricted to-one relation takes its foreign key with it. `bandId: 7` names
4474
+ * the band exactly as `band: { id: 7 }` does, and the row carries the column
4475
+ * beside the relation — so a rule on the relation that left the key alone
4476
+ * withheld the value from the row and still let `?bandId=7` ask for it.
4477
+ *
4320
4478
  * `kind` picks which half of the rule is read; nothing else differs.
4321
4479
  */
4322
4480
  function restrictedFieldNames(collection, viewer, kind) {
4323
4481
  const declared = [];
4324
4482
  const refused = /* @__PURE__ */ new Set();
4325
4483
  const allowed = kind === "read" ? canReadField : canWriteField;
4326
- for (const [name, property] of Object.entries(collection.properties ?? {})) {
4327
- if (allowed(property, viewer)) continue;
4328
- declared.push(name);
4484
+ const properties = collection.properties ?? {};
4485
+ let relations;
4486
+ const restrict = (name) => {
4487
+ if (name in properties && !declared.includes(name)) declared.push(name);
4329
4488
  refused.add(name);
4489
+ };
4490
+ for (const [name, property] of Object.entries(properties)) {
4491
+ if (allowed(property, viewer)) continue;
4492
+ restrict(name);
4330
4493
  const columnName = property.columnName;
4331
4494
  if (columnName) refused.add(columnName);
4495
+ if (property.type === "relation") {
4496
+ relations ??= resolveCollectionRelations(collection);
4497
+ const relation = findRelation(relations, name);
4498
+ if (relation?.kind === "belongsTo") {
4499
+ restrict(fieldKeyForColumn(collection, relation.localKey));
4500
+ refused.add(relation.localKey);
4501
+ }
4502
+ }
4332
4503
  }
4333
4504
  return {
4334
4505
  declared,
@@ -4762,23 +4933,6 @@ function includePaths(tree, prefix = "") {
4762
4933
  }
4763
4934
  return out;
4764
4935
  }
4765
- /**
4766
- * The relation names an `include` asks for at the top level.
4767
- *
4768
- * `["author", "comments.author"]` and `{author: true, comments: {...}}` both
4769
- * answer `["author", "comments"]` — a *hop*, not a path, because the only
4770
- * consumer is `?fields=`, which names keys on the row being returned and a
4771
- * nested relation is not one of those.
4772
- *
4773
- * Derived rather than passed: `include` has four spellings and three of them
4774
- * are not a `string[]`, so every consumer that wants the plain names either
4775
- * calls this or reimplements the flattening.
4776
- */
4777
- function topLevelIncludeNames(spec) {
4778
- const normalized = normalizeInclude(spec);
4779
- if (!normalized) return [];
4780
- return Object.keys(normalized.tree);
4781
- }
4782
4936
  /** Whether any node in the tree carries per-relation options. */
4783
4937
  function hasOptions(tree) {
4784
4938
  return Object.values(tree).some((node) => node.limit !== void 0 || node.where !== void 0 || node.logical !== void 0 || node.orderBy !== void 0 || node.fields !== void 0 || hasOptions(node.children));
@@ -5094,19 +5248,23 @@ function normalizeMaxRows(raw) {
5094
5248
  * @param label the collection name, so an error says which walk failed
5095
5249
  */
5096
5250
  async function* paginateFind(find, params, label = "collection") {
5097
- const { pageSize, cursor, maxPages, ...rest } = params ?? {};
5251
+ const { pageSize, cursor, maxPages, after: startAfter, offset: startOffset, page: startPage, ...rest } = params ?? {};
5098
5252
  const findParams = { ...rest };
5099
5253
  const size = normalizePageSize(pageSize);
5100
5254
  const pageCap = normalizeMaxPages(maxPages);
5101
- const seekRequested = cursor !== void 0 && cursor !== null;
5102
- if (seekRequested) {
5255
+ const seekRequested = cursor !== void 0 && cursor !== null || !!startAfter;
5256
+ if (cursor !== void 0 && cursor !== null) {
5103
5257
  const field = typeof cursor === "string" ? cursor : cursor.field;
5104
5258
  const requested = typeof cursor === "object" && cursor !== null ? cursor.direction : void 0;
5105
5259
  if (!normalizeOrderBy(findParams.orderBy)) findParams.orderBy = [field, requested ?? "asc"];
5106
5260
  }
5107
- let offset = 0;
5261
+ let offset = resolveFindWindow({
5262
+ limit: size,
5263
+ offset: startOffset,
5264
+ page: startPage
5265
+ }).offset;
5108
5266
  let pages = 0;
5109
- let after;
5267
+ let after = startAfter || void 0;
5110
5268
  for (;;) {
5111
5269
  if (pages >= pageCap) throw new RebasePaginationError("max-pages", `Iterating "${label}" made ${pages} requests without the server reporting the end of the collection. Stopping rather than looping forever — raise \`maxPages\` if the walk is genuinely this long, or check that the backend sets \`meta.hasMore\`.`);
5112
5270
  const pageParams = {
@@ -5171,9 +5329,16 @@ async function collectAllPages(find, params, label = "collection") {
5171
5329
  * Relation values (`EntityRelation` instances or `{ __type: "relation", id, path }`
5172
5330
  * objects) are serialized as their raw id — the wire format only carries the
5173
5331
  * value to compare against the FK column.
5332
+ *
5333
+ * A `Date` is its ISO 8601 instant — what JSON makes of it on the socket and
5334
+ * in-process, so one filter names one instant through every door. `String()`
5335
+ * gave the viewer's local zone, no milliseconds and a parenthesised zone name
5336
+ * Postgres cannot parse. An invalid date keeps its "Invalid Date" spelling for
5337
+ * the server to refuse, rather than throwing a `RangeError` from here.
5174
5338
  */
5175
5339
  function stringifyValue(value) {
5176
5340
  if (value === null) return "null";
5341
+ if (value instanceof Date && !Number.isNaN(value.getTime())) return value.toISOString();
5177
5342
  const relation = normalizeToEntityRelation(value);
5178
5343
  if (relation) return String(relation.id);
5179
5344
  return String(value);
@@ -5231,6 +5396,41 @@ function unescapeWireValue(value) {
5231
5396
  return result;
5232
5397
  }
5233
5398
  /**
5399
+ * Whether a scalar, sent raw as a query parameter's operand, would be read
5400
+ * back as something other than itself.
5401
+ *
5402
+ * A query parameter owns its whole value, so its scalar goes out unescaped —
5403
+ * `eq.a,b`, `eq.C:\x` — and the decoder returns it verbatim. Two shapes break
5404
+ * that. A parenthesised operand is a list for every operator (so that
5405
+ * `["==", ["a","b"]]` on an array column stays a list), so raw, the scalar
5406
+ * `"(deleted)"` would arrive as `["deleted"]`: an `==` matching nothing, a
5407
+ * `!=` returning the very rows it excludes. Such a scalar is sent escaped,
5408
+ * `eq.\(deleted\)`. And that spelling has to be told apart from a raw value
5409
+ * that looks the same, so a scalar of *that* shape is escaped in turn.
5410
+ *
5411
+ * Everything else stays raw, so a value an older client sends decodes as it
5412
+ * always has — unless it is exactly the escaped spelling of such a scalar,
5413
+ * which decodes to the scalar it spells.
5414
+ */
5415
+ function scalarNeedsEscape(value) {
5416
+ if (value.startsWith("(") && value.endsWith(")")) return true;
5417
+ return decodeEscapedScalar(value) !== void 0;
5418
+ }
5419
+ /**
5420
+ * The scalar an operand is the escaped spelling of, when it is one.
5421
+ *
5422
+ * Exact on purpose: only an operand that {@link escapeWireValue} produces from
5423
+ * a scalar that {@link scalarNeedsEscape} is decoded. Any other operand —
5424
+ * including a raw backslash an older client sent, like `\\server\share` — is
5425
+ * left verbatim. Terminates because an operand that matches contains an
5426
+ * escape pair, so the value is strictly shorter than it.
5427
+ */
5428
+ function decodeEscapedScalar(operand) {
5429
+ if (!operand.startsWith("\\")) return void 0;
5430
+ const value = unescapeWireValue(operand);
5431
+ return escapeWireValue(value) === operand && scalarNeedsEscape(value) ? value : void 0;
5432
+ }
5433
+ /**
5234
5434
  * Split a parenthesized list string on unescaped commas.
5235
5435
  * Input is the content between `(` and `)`.
5236
5436
  *
@@ -5475,6 +5675,42 @@ function readTuple(field, raw) {
5475
5675
  if (isOperatorShaped(op)) throw new UnknownFilterOperatorError(field, op);
5476
5676
  }
5477
5677
  /**
5678
+ * The query-parameter names a REST listing reads as something other than a
5679
+ * field filter: pagination, sorting, includes, projection, search, the logical
5680
+ * groups, the JSON `where` dialect, an aggregate's `select`/`groupBy`, and the
5681
+ * soft-delete switches.
5682
+ *
5683
+ * One list for both ends. The server skips these names when it collects
5684
+ * `?<field>=<op>.<value>` filters, and the SDK sends a filter on a column with
5685
+ * one of these names inside `?where=` instead — a collection is free to have a
5686
+ * `page`, `select` or `hard` column, and a filter on it sent as its own
5687
+ * parameter was read as the parameter and never applied.
5688
+ */
5689
+ var RESERVED_QUERY_KEYS = /* @__PURE__ */ new Set([
5690
+ "limit",
5691
+ "offset",
5692
+ "page",
5693
+ "after",
5694
+ "orderBy",
5695
+ "include",
5696
+ "fields",
5697
+ "distinct",
5698
+ "searchString",
5699
+ "searchExplain",
5700
+ "vector_search",
5701
+ "vector",
5702
+ "vector_distance",
5703
+ "vector_threshold",
5704
+ "or",
5705
+ "and",
5706
+ "not",
5707
+ "where",
5708
+ "select",
5709
+ "groupBy",
5710
+ "deleted",
5711
+ "hard"
5712
+ ]);
5713
+ /**
5478
5714
  * Encode the `<op>.<value>` half of a wire condition.
5479
5715
  *
5480
5716
  * This is the single leaf encoder. Both wire positions that carry a condition
@@ -5502,7 +5738,7 @@ function serializeOperatorAndValue(op, value, { escapeScalar, where }) {
5502
5738
  return `${restOp}.(${value.map((v) => escapeWireValue(stringifyValue(v))).join(",")})`;
5503
5739
  }
5504
5740
  const scalar = stringifyValue(value);
5505
- return `${restOp}.${escapeScalar ? escapeWireValue(scalar) : scalar}`;
5741
+ return `${restOp}.${escapeScalar || !LIST_OPS.has(op) && scalarNeedsEscape(scalar) ? escapeWireValue(scalar) : scalar}`;
5506
5742
  }
5507
5743
  /**
5508
5744
  * Serialize a single canonical condition tuple to a PostgREST dot-string.
@@ -5598,7 +5834,10 @@ var NULL_OPERANDS = /* @__PURE__ */ new Set([
5598
5834
  * operand: only {@link NULL_OPERANDS}. `notnull.reason` is the value
5599
5835
  * `"notnull.reason"`, not "reason is not null".
5600
5836
  * - **Everything else** takes one scalar, and any remainder is one — including
5601
- * the empty string, so `eq.` really is "equals the empty string".
5837
+ * the empty string, so `eq.` really is "equals the empty string". A
5838
+ * parenthesised remainder is a list for these too (`eq.(a,b)` is array
5839
+ * equality); a scalar that is itself parenthesised arrives escaped,
5840
+ * `eq.\(none\)`, and is decoded here — see {@link scalarNeedsEscape}.
5602
5841
  *
5603
5842
  * ### The one ambiguity that remains, and how to write past it
5604
5843
  *
@@ -5633,7 +5872,7 @@ function deserializeSingle(raw) {
5633
5872
  return [canonicalOp, inner === EMPTY_LIST_TOKEN ? [] : splitListItems(inner)];
5634
5873
  }
5635
5874
  if (LIST_OPS.has(canonicalOp)) return ["==", raw];
5636
- return [canonicalOp, rest];
5875
+ return [canonicalOp, decodeEscapedScalar(rest) ?? rest];
5637
5876
  }
5638
5877
  /**
5639
5878
  * Convert a PostgREST-style querystring record to `FilterValues`.
@@ -6002,7 +6241,7 @@ function inlineRelationRefs(row) {
6002
6241
  }
6003
6242
  return out ?? row;
6004
6243
  }
6005
- function createDriverAccessor(driver, slug, getPks = () => [], toViewModel) {
6244
+ function createDriverAccessor(driver, slug, getPks = () => [], toViewModel, storedPath = () => slug) {
6006
6245
  const accessor = {
6007
6246
  async find(params) {
6008
6247
  const filter = params?.where ? deserializeFilter(params.where) : void 0;
@@ -6012,7 +6251,7 @@ function createDriverAccessor(driver, slug, getPks = () => [], toViewModel) {
6012
6251
  const startAfter = cursor ? cursorToStartAfter(cursor) : void 0;
6013
6252
  const probeLimit = startAfter ? limit + 1 : limit;
6014
6253
  const fetchService = driver.restFetchService;
6015
- const fetched = fetchService ? await fetchService.fetchCollectionForRest(slug, {
6254
+ const fetched = fetchService ? await fetchService.fetchCollectionForRest(storedPath(), {
6016
6255
  filter,
6017
6256
  logical: params?.logical,
6018
6257
  limit: probeLimit,
@@ -6023,7 +6262,7 @@ function createDriverAccessor(driver, slug, getPks = () => [], toViewModel) {
6023
6262
  fields: params?.fields,
6024
6263
  distinct: params?.distinct
6025
6264
  }, params?.include) : await driver.fetchCollection({
6026
- path: slug,
6265
+ path: storedPath(),
6027
6266
  limit: probeLimit,
6028
6267
  offset: startAfter ? void 0 : driverOffset,
6029
6268
  startAfter,
@@ -6041,7 +6280,7 @@ function createDriverAccessor(driver, slug, getPks = () => [], toViewModel) {
6041
6280
  let hasMore = seeking ? fetched.length > limit : rows.length >= limit;
6042
6281
  if (driver.count) {
6043
6282
  total = await driver.count({
6044
- path: slug,
6283
+ path: storedPath(),
6045
6284
  filter,
6046
6285
  logical: params?.logical,
6047
6286
  searchString: params?.searchString
@@ -6049,7 +6288,7 @@ function createDriverAccessor(driver, slug, getPks = () => [], toViewModel) {
6049
6288
  if (!seeking) hasMore = offset + rows.length < total;
6050
6289
  }
6051
6290
  const last = rows[rows.length - 1];
6052
- const nextCursor = hasMore && last && driver.restFetchService?.cursorFor ? driver.restFetchService.cursorFor(slug, last, orderBy) : void 0;
6291
+ const nextCursor = hasMore && last && driver.restFetchService?.cursorFor ? driver.restFetchService.cursorFor(storedPath(), last, orderBy) : void 0;
6053
6292
  return {
6054
6293
  data: rows.map((row) => rowToEntity(row, slug, getPks(), toViewModel)),
6055
6294
  meta: {
@@ -6063,23 +6302,39 @@ function createDriverAccessor(driver, slug, getPks = () => [], toViewModel) {
6063
6302
  },
6064
6303
  async findById(id) {
6065
6304
  const fetchService = driver.restFetchService;
6066
- const row = fetchService ? await fetchService.fetchOneForRest(slug, id) : await driver.fetchOne({
6067
- path: slug,
6305
+ const row = fetchService ? await fetchService.fetchOneForRest(storedPath(), id) : await driver.fetchOne({
6306
+ path: storedPath(),
6068
6307
  id
6069
6308
  });
6070
6309
  return row ? rowToEntity(row, slug, getPks(), toViewModel) : void 0;
6071
6310
  },
6072
- aggregate: driver.restFetchService?.aggregate ? async (params) => driver.restFetchService.aggregate(slug, {
6073
- aggregates: params.select.map(toDriverAggregate),
6074
- groupBy: params.groupBy,
6075
- filter: params.where ? deserializeFilter(params.where) : void 0,
6076
- logical: params.logical,
6077
- searchString: params.searchString,
6078
- limit: params.limit
6079
- }) : void 0,
6311
+ aggregate: driver.restFetchService?.aggregate ? async (params) => {
6312
+ const limit = (params.groupBy?.length ?? 0) > 0 ? params.limit : void 0;
6313
+ const rows = await driver.restFetchService.aggregate(storedPath(), {
6314
+ aggregates: params.select.map(toDriverAggregate),
6315
+ groupBy: params.groupBy,
6316
+ filter: params.where ? deserializeFilter(params.where) : void 0,
6317
+ logical: params.logical,
6318
+ searchString: params.searchString,
6319
+ limit: limit !== void 0 ? limit + 1 : params.limit,
6320
+ offset: params.offset
6321
+ });
6322
+ if (limit === void 0) return rows;
6323
+ const page = rows.slice(0, limit);
6324
+ const meta = {
6325
+ limit,
6326
+ offset: params.offset ?? 0,
6327
+ hasMore: rows.length > limit
6328
+ };
6329
+ Object.defineProperty(page, "meta", {
6330
+ value: meta,
6331
+ enumerable: false
6332
+ });
6333
+ return page;
6334
+ } : void 0,
6080
6335
  async create(data, id) {
6081
6336
  return rowToEntity(await driver.save({
6082
- path: slug,
6337
+ path: storedPath(),
6083
6338
  values: data,
6084
6339
  id,
6085
6340
  status: "new"
@@ -6087,7 +6342,7 @@ function createDriverAccessor(driver, slug, getPks = () => [], toViewModel) {
6087
6342
  },
6088
6343
  createMany: driver.saveMany ? async (data, options) => {
6089
6344
  return (await driver.saveMany({
6090
- path: slug,
6345
+ path: storedPath(),
6091
6346
  rows: data,
6092
6347
  upsert: options?.upsert,
6093
6348
  onConflict: options?.onConflict
@@ -6095,7 +6350,7 @@ function createDriverAccessor(driver, slug, getPks = () => [], toViewModel) {
6095
6350
  } : void 0,
6096
6351
  async update(id, data) {
6097
6352
  return rowToEntity(await driver.save({
6098
- path: slug,
6353
+ path: storedPath(),
6099
6354
  values: data,
6100
6355
  id,
6101
6356
  status: "existing"
@@ -6104,12 +6359,12 @@ function createDriverAccessor(driver, slug, getPks = () => [], toViewModel) {
6104
6359
  async delete(id) {
6105
6360
  return driver.delete({ row: {
6106
6361
  id,
6107
- path: slug
6362
+ path: storedPath()
6108
6363
  } });
6109
6364
  },
6110
6365
  updateMany: driver.updateMany ? async (updates) => {
6111
6366
  return (await driver.updateMany({
6112
- path: slug,
6367
+ path: storedPath(),
6113
6368
  updates: updates.map((u) => ({
6114
6369
  id: u.id,
6115
6370
  values: u.data
@@ -6118,14 +6373,14 @@ function createDriverAccessor(driver, slug, getPks = () => [], toViewModel) {
6118
6373
  } : void 0,
6119
6374
  deleteMany: driver.deleteMany ? async (ids) => {
6120
6375
  await driver.deleteMany({
6121
- path: slug,
6376
+ path: storedPath(),
6122
6377
  ids
6123
6378
  });
6124
6379
  } : void 0,
6125
6380
  count: driver.count ? async (params) => {
6126
6381
  const filter = params?.where ? deserializeFilter(params.where) : void 0;
6127
6382
  return driver.count({
6128
- path: slug,
6383
+ path: storedPath(),
6129
6384
  filter,
6130
6385
  logical: params?.logical,
6131
6386
  searchString: params?.searchString
@@ -6135,7 +6390,7 @@ function createDriverAccessor(driver, slug, getPks = () => [], toViewModel) {
6135
6390
  const { limit, offset, driverOffset } = resolveFindWindow(params);
6136
6391
  const normalize = driver.restFetchService ? inlineRelationRefs : (row) => row;
6137
6392
  return driver.listenCollection({
6138
- path: slug,
6393
+ path: storedPath(),
6139
6394
  limit,
6140
6395
  offset: driverOffset,
6141
6396
  filter: params?.where,
@@ -6161,7 +6416,7 @@ function createDriverAccessor(driver, slug, getPks = () => [], toViewModel) {
6161
6416
  listenById: driver.listenOne ? (id, onUpdate, onError) => {
6162
6417
  const normalize = driver.restFetchService ? inlineRelationRefs : (row) => row;
6163
6418
  return driver.listenOne({
6164
- path: slug,
6419
+ path: storedPath(),
6165
6420
  id,
6166
6421
  onUpdate: (entity) => onUpdate(entity ? rowToEntity(normalize(entity), slug, getPks(), toViewModel) : void 0),
6167
6422
  onError
@@ -6230,7 +6485,7 @@ function buildRebaseData(driver, options) {
6230
6485
  function getAccessor(slug) {
6231
6486
  let accessor = cache.get(slug);
6232
6487
  if (!accessor) {
6233
- accessor = createDriverAccessor(driver, slug, () => primaryKeysFor(slug), viewModelFor(slug));
6488
+ accessor = createDriverAccessor(driver, slug, () => primaryKeysFor(slug), viewModelFor(slug), () => options?.resolveDataPath?.(slug) ?? slug);
6234
6489
  cache.set(slug, accessor);
6235
6490
  }
6236
6491
  return accessor;
@@ -6387,6 +6642,31 @@ var SdkQueryBuilder = class {
6387
6642
  }
6388
6643
  };
6389
6644
  /**
6645
+ * Refuse a write option this door cannot honour, by name, before anything is
6646
+ * written.
6647
+ *
6648
+ * The in-process accessor implements the same `SDKCollectionClient` as the HTTP
6649
+ * client, whose `ifMatch` is "Honoured on `update` and `delete`" — and its
6650
+ * writes took no options at all, so `rebase.data.posts.update(id, data, {
6651
+ * ifMatch: staleEtag })` overwrote the row and resolved. A precondition the
6652
+ * caller wrote, dropped without a word, is the worst thing a door can do with
6653
+ * it. The driver's `save` has no precondition and no idempotency store to hand
6654
+ * these to, so they are refused rather than ignored. An `undefined` value is no
6655
+ * option, as it is over HTTP: `etagOf(row)` is `undefined` for a row that
6656
+ * carries no version, and that means "write unconditionally".
6657
+ */
6658
+ function refuseUnhonouredWriteOptions(method, slug, options) {
6659
+ const unsupported = ["ifMatch", "idempotencyKey"].filter((key) => options?.[key]);
6660
+ if (unsupported.length === 0) return;
6661
+ throw new RebaseClientError(`${method}() on "${slug}" in-process (context.data, rebase.data) cannot honour ${unsupported.join(" or ")}: this door has no ${unsupported.includes("ifMatch") ? "precondition check" : "idempotency store"}, and ignoring the option would write as though it had been checked. Drop it here, or send the write through the HTTP client, which honours it.`, {
6662
+ code: "UNSUPPORTED_OPTION",
6663
+ details: {
6664
+ method,
6665
+ options: unsupported
6666
+ }
6667
+ });
6668
+ }
6669
+ /**
6390
6670
  * Wrap a Entity-shaped {@link CollectionAccessor} into a flat
6391
6671
  * {@link SDKCollectionClient}. Every returned record is unwrapped to a flat row
6392
6672
  * so the backend SDK is byte-for-byte the same shape as the frontend client.
@@ -6418,14 +6698,19 @@ function toSdkCollectionClient(snap, slug = "collection") {
6418
6698
  });
6419
6699
  return entityToRow(s);
6420
6700
  },
6421
- async create(data, id) {
6701
+ async create(data, id, options) {
6702
+ refuseUnhonouredWriteOptions("create", slug, options);
6422
6703
  return entityToRow(await snap.create(data, id));
6423
6704
  },
6424
6705
  async createMany(data, options) {
6706
+ refuseUnhonouredWriteOptions("createMany", slug, options);
6425
6707
  if (!Array.isArray(data)) throw new TypeError("createMany expects an array of records.");
6426
6708
  if (data.length === 0) return [];
6427
6709
  if (!snap.createMany) throw new Error("Bulk writes are not supported by this collection's data source. Fall back to create() per record.");
6428
- return (await snap.createMany(data, options)).map(entityToRow);
6710
+ return (await snap.createMany(data, {
6711
+ upsert: options?.upsert,
6712
+ onConflict: options?.onConflict
6713
+ })).map(entityToRow);
6429
6714
  },
6430
6715
  /**
6431
6716
  * One row through the bulk path, because the bulk path is where the
@@ -6437,6 +6722,7 @@ function toSdkCollectionClient(snap, slug = "collection") {
6437
6722
  * one is exactly an upsert of one.
6438
6723
  */
6439
6724
  async upsert(data, options) {
6725
+ refuseUnhonouredWriteOptions("upsert", slug, options);
6440
6726
  if (!snap.createMany) throw new Error("Upsert is not supported by this collection's data source: it needs a bulk write, which this driver does not implement. Fall back to create() or update().");
6441
6727
  const row = (await snap.createMany([data], {
6442
6728
  upsert: true,
@@ -6445,10 +6731,12 @@ function toSdkCollectionClient(snap, slug = "collection") {
6445
6731
  if (!row) throw new Error(`Upsert into "${slug}" returned no row.`);
6446
6732
  return entityToRow(row);
6447
6733
  },
6448
- async update(id, data) {
6734
+ async update(id, data, options) {
6735
+ refuseUnhonouredWriteOptions("update", slug, options);
6449
6736
  return entityToRow(await snap.update(id, data));
6450
6737
  },
6451
- async updateMany(updates) {
6738
+ async updateMany(updates, options) {
6739
+ refuseUnhonouredWriteOptions("updateMany", slug, options);
6452
6740
  if (!Array.isArray(updates)) throw new TypeError("updateMany expects an array of { id, data } entries.");
6453
6741
  if (updates.length === 0) return [];
6454
6742
  if (!snap.updateMany) throw new Error("Bulk updates are not supported by this collection's data source. Fall back to update() per record.");
@@ -6457,10 +6745,12 @@ function toSdkCollectionClient(snap, slug = "collection") {
6457
6745
  data: u.data
6458
6746
  })))).map(entityToRow);
6459
6747
  },
6460
- delete(id) {
6748
+ async delete(id, options) {
6749
+ refuseUnhonouredWriteOptions("delete", slug, options);
6461
6750
  return snap.delete(id);
6462
6751
  },
6463
- async deleteMany(ids) {
6752
+ async deleteMany(ids, options) {
6753
+ refuseUnhonouredWriteOptions("deleteMany", slug, options);
6464
6754
  if (!Array.isArray(ids)) throw new TypeError("deleteMany expects an array of ids.");
6465
6755
  if (ids.length === 0) return;
6466
6756
  if (!snap.deleteMany) throw new Error("Bulk deletes are not supported by this collection's data source. Fall back to delete() per record.");
@@ -6575,6 +6865,6 @@ function buildRoutedRebaseData({ defaultData, sources, resolveKey }) {
6575
6865
  }
6576
6866
  new Set(REBASE_INTERNAL_TABLES);
6577
6867
  //#endregion
6578
- export { ListLimitError as $, createDataSourceRegistry as A, fieldKeyForColumn as B, decodeCursor as C, restrictedFieldNames as D, effectiveAccess as E, revokeInternalTableSql as F, enumToObjectEntries as G, getTableName as H, getJunctionConfigForRelation as I, hydrateRegExp as J, buildCompositeId as K, getEffectiveSecurityRules as L, resolveDataSource as M, authSecretsMissingExclusion as N, assertBeforeQueryIsPostgresOnly as O, REBASE_USER_ROLE as P, unref as Q, getTenantConfig as R, cursorToStartAfter as S, canWriteField as T, isRelationRequired as U, findRelation as V, resolveCollectionRelations as W, suggestNearMiss as X, splitSearchTerms as Y, toSnakeCase as Z, OrderBySpecError as _, isUnsupported as _t, deserializeLogicalCondition as a, isFieldOperation as at, CursorError as b, collectAllPages as c, JUNCTION_PIVOT_KEY as ct, IncludeSpecError as d, EntityReference as dt, MAX_LIST_LIMIT as et, deserializeInclude as f, EntityRelation as ft, topLevelIncludeNames as g, RebaseClientError as gt, serializeInclude as h, RebaseApiError as ht, deserializeFilter as i, hasFieldOperation as it, isRelationalCollection as j, CollectionRegistry as k, paginateFind as l, isManyToMany as lt, normalizeInclude as m, Vector as mt, buildSdkData as n, BATCH_REF_KEY as nt, serializeFilter as o, ANONYMOUS_USER_ID as ot, mergeIncludeSpecs as p, GeoPoint as pt, resolvePrimaryKeys as q, UnknownFilterOperatorError as r, FIELD_OPERATORS as rt, serializeLogicalCondition as s, isAnonymousUid as st, buildRoutedRebaseData as t, resolveClientListLimit as tt, resolveFindWindow as u, isToMany as ut, normalizeOrderBy as v, unsupportedMethod as vt, reconcileCursorOrder as w, CursorMismatchError as x, serializeOrderBy as y, securityRuleToConditions as z };
6868
+ export { unref as $, isRelationalCollection as A, findRelation as B, decodeCursor as C, assertBeforeQueryIsPostgresOnly as D, restrictedFieldNames as E, getJunctionConfigForRelation as F, enumToObjectEntries as G, isRelationRequired as H, getEffectiveSecurityRules as I, hydrateRegExp as J, buildCompositeId as K, getTenantConfig as L, authSecretsMissingExclusion as M, REBASE_USER_ROLE as N, CollectionRegistry as O, revokeInternalTableSql as P, toSnakeCase as Q, securityRuleToConditions as R, cursorToStartAfter as S, effectiveAccess as T, relationDeclaringProperty as U, getTableName as V, resolveCollectionRelations as W, splitSearchTerms as X, isObject as Y, suggestNearMiss as Z, OrderBySpecError as _, RebaseApiError as _t, deserializeFilter as a, hasFieldOperation as at, CursorError as b, unsupportedMethod as bt, serializeLogicalCondition as c, isAnonymousUid as ct, resolveFindWindow as d, isManyToMany as dt, ListLimitError as et, IncludeSpecError as f, isToMany as ft, serializeInclude as g, Vector as gt, normalizeInclude as h, GeoPoint as ht, UnknownFilterOperatorError as i, FIELD_OPERATORS as it, resolveDataSource as j, createDataSourceRegistry as k, collectAllPages as l, JUNCTION_PIVOT_KEY as lt, mergeIncludeSpecs as m, EntityRelation as mt, buildSdkData as n, resolveClientListLimit as nt, deserializeLogicalCondition as o, isFieldOperation as ot, deserializeInclude as p, EntityReference as pt, resolvePrimaryKeys as q, RESERVED_QUERY_KEYS as r, BATCH_REF_KEY as rt, serializeFilter as s, ANONYMOUS_USER_ID as st, buildRoutedRebaseData as t, MAX_LIST_LIMIT as tt, paginateFind as u, hasForeignKeyOnTarget as ut, normalizeOrderBy as v, RebaseClientError as vt, reconcileCursorOrder as w, CursorMismatchError as x, serializeOrderBy as y, isUnsupported as yt, fieldKeyForColumn as z };
6579
6869
 
6580
- //# sourceMappingURL=src-1vL-I1Po.js.map
6870
+ //# sourceMappingURL=src-I3aG1PcY.js.map