@rebasepro/server 0.24.0 → 0.24.1-canary.gcb918fc

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 (224) hide show
  1. package/README.md +27 -2
  2. package/dist/GCSStorageController-fv6JAZ87.js +402 -0
  3. package/dist/GCSStorageController-fv6JAZ87.js.map +1 -0
  4. package/dist/S3StorageController-BNg-pXiY.js +480 -0
  5. package/dist/S3StorageController-BNg-pXiY.js.map +1 -0
  6. package/dist/api/mount-path.d.ts +30 -0
  7. package/dist/api/mount.d.ts +6 -13
  8. package/dist/api/rest/api-generator.d.ts +53 -1
  9. package/dist/api/rest/auth-collection-writes.d.ts +28 -21
  10. package/dist/api/rest/field-access-query.d.ts +28 -8
  11. package/dist/api/rest/field-ops.d.ts +19 -7
  12. package/dist/api/rest/query-body.d.ts +10 -0
  13. package/dist/api/rest/write-validation.d.ts +7 -5
  14. package/dist/api/schema-editor-routes.d.ts +10 -1
  15. package/dist/api/sql-export-routes.d.ts +8 -0
  16. package/dist/api/types.d.ts +35 -0
  17. package/dist/{ast-schema-editor-CWqS_sLJ.js → ast-schema-editor-mCeSC03x.js} +3 -3
  18. package/dist/{ast-schema-editor-CWqS_sLJ.js.map → ast-schema-editor-mCeSC03x.js.map} +1 -1
  19. package/dist/auth/access.d.ts +57 -8
  20. package/dist/auth/admin-user-ops.d.ts +24 -2
  21. package/dist/auth/admin-users-route.d.ts +1 -0
  22. package/dist/auth/api-keys/api-key-middleware.d.ts +10 -3
  23. package/dist/auth/api-keys/api-key-routes.d.ts +4 -1
  24. package/dist/auth/builtin-auth-adapter.d.ts +13 -0
  25. package/dist/auth/email-change-routes.d.ts +3 -0
  26. package/dist/auth/expired-token-sweep.d.ts +5 -1
  27. package/dist/auth/interfaces.d.ts +10 -0
  28. package/dist/auth/jwks-routes.d.ts +5 -1
  29. package/dist/auth/jwt-crypto.d.ts +7 -0
  30. package/dist/auth/jwt-keys.d.ts +4 -0
  31. package/dist/auth/jwt.d.ts +282 -166
  32. package/dist/auth/magic-link-routes.d.ts +3 -0
  33. package/dist/auth/mfa-crypto.d.ts +9 -3
  34. package/dist/auth/mfa-gate.d.ts +4 -2
  35. package/dist/auth/mfa-routes.d.ts +6 -0
  36. package/dist/auth/middleware.d.ts +9 -2
  37. package/dist/auth/otp-routes.d.ts +3 -0
  38. package/dist/auth/rate-limiter.d.ts +61 -34
  39. package/dist/auth/registration-policy.d.ts +17 -0
  40. package/dist/auth/require-auth.d.ts +1 -1
  41. package/dist/auth/routes.d.ts +16 -0
  42. package/dist/auth/session-routes.d.ts +6 -0
  43. package/dist/auth/user-picking.d.ts +37 -0
  44. package/dist/auth/verify-credential.d.ts +25 -4
  45. package/dist/{auth-DMLngxn_.js → auth-GnONzteu.js} +2869 -1871
  46. package/dist/auth-GnONzteu.js.map +1 -0
  47. package/dist/auth_adapter-CIItCTc8.js +21 -0
  48. package/dist/auth_adapter-CIItCTc8.js.map +1 -0
  49. package/dist/backend/create-backend.d.ts +281 -0
  50. package/dist/backend/default-backend.d.ts +35 -0
  51. package/dist/backend/options.d.ts +912 -0
  52. package/dist/backend/scope.d.ts +23 -0
  53. package/dist/backend/services.d.ts +54 -0
  54. package/dist/backend-DTAOsLQc.js.map +1 -1
  55. package/dist/backup/backup-common.d.ts +14 -3
  56. package/dist/backup/backup-routes.d.ts +23 -2
  57. package/dist/backup/backup-storage.d.ts +3 -1
  58. package/dist/backup/index.d.ts +3 -1
  59. package/dist/{backup-CN0s50D2.js → backup-BnvkSLHi.js} +176 -35
  60. package/dist/backup-BnvkSLHi.js.map +1 -0
  61. package/dist/boot/boot.d.ts +2 -2
  62. package/dist/boot/driver.d.ts +3 -0
  63. package/dist/boot/env.d.ts +9 -2
  64. package/dist/boot/http-server.d.ts +22 -0
  65. package/dist/boot/options.d.ts +16 -1
  66. package/dist/boot/provision.d.ts +4 -2
  67. package/dist/boot/runtime-cors.d.ts +17 -0
  68. package/dist/boot/sources.d.ts +2 -0
  69. package/dist/boot/static-routing.d.ts +9 -0
  70. package/dist/{contract-routes-fz8i4pxs.js → contract-routes-Ey6ftnKH.js} +4 -20
  71. package/dist/contract-routes-Ey6ftnKH.js.map +1 -0
  72. package/dist/cron/cron-loader.d.ts +77 -18
  73. package/dist/cron/cron-routes.d.ts +1 -1
  74. package/dist/cron/cron-scheduler.d.ts +69 -3
  75. package/dist/cron/cron-store.d.ts +33 -5
  76. package/dist/cron/define-cron.d.ts +6 -2
  77. package/dist/cron/index.d.ts +3 -3
  78. package/dist/cron-loader-B-u0snqq.js +202 -0
  79. package/dist/cron-loader-B-u0snqq.js.map +1 -0
  80. package/dist/{cron-routes-Bc-SB0Se.js → cron-routes-OD6BstUj.js} +27 -4
  81. package/dist/cron-routes-OD6BstUj.js.map +1 -0
  82. package/dist/{cron-scheduler-CYQgco86.js → cron-scheduler-idtLPoN-.js} +299 -33
  83. package/dist/cron-scheduler-idtLPoN-.js.map +1 -0
  84. package/dist/cron-store-CZ30ZBi-.js +577 -0
  85. package/dist/cron-store-CZ30ZBi-.js.map +1 -0
  86. package/dist/email/smtp-email-service.d.ts +23 -0
  87. package/dist/email/types.d.ts +18 -0
  88. package/dist/env.d.ts +0 -37
  89. package/dist/filter-dialect-O1HK6Nvr.js +1628 -0
  90. package/dist/filter-dialect-O1HK6Nvr.js.map +1 -0
  91. package/dist/{function-loader-D7o5Epjj.js → function-loader-iKhOZatS.js} +68 -38
  92. package/dist/function-loader-iKhOZatS.js.map +1 -0
  93. package/dist/{function-routes-CaNG4waN.js → function-routes-j9TwpvQe.js} +5 -4
  94. package/dist/function-routes-j9TwpvQe.js.map +1 -0
  95. package/dist/functions/context.d.ts +9 -0
  96. package/dist/functions/define-function.d.ts +10 -6
  97. package/dist/functions/function-loader.d.ts +75 -18
  98. package/dist/functions/function-routes.d.ts +2 -1
  99. package/dist/functions/index.d.ts +5 -4
  100. package/dist/functions/index.js +42 -13
  101. package/dist/functions/index.js.map +1 -1
  102. package/dist/functions/internal.d.ts +2 -2
  103. package/dist/functions/proxy.d.ts +6 -2
  104. package/dist/functions/request-timeout.d.ts +7 -5
  105. package/dist/history/history-routes.d.ts +9 -0
  106. package/dist/history/retention-sweep.d.ts +81 -0
  107. package/dist/{history-recorder-Nr8zLvoU.js → history-recorder-BfWRwmPy.js} +3 -3
  108. package/dist/{history-recorder-Nr8zLvoU.js.map → history-recorder-BfWRwmPy.js.map} +1 -1
  109. package/dist/{history-store-rcAm_xFR.js → history-store-DPofGVDp.js} +2 -2
  110. package/dist/{history-store-rcAm_xFR.js.map → history-store-DPofGVDp.js.map} +1 -1
  111. package/dist/index.d.ts +23 -10
  112. package/dist/index.es.js +24423 -20038
  113. package/dist/index.es.js.map +1 -1
  114. package/dist/init/docs.d.ts +27 -10
  115. package/dist/init/process-safety.d.ts +7 -2
  116. package/dist/init/shutdown.d.ts +18 -3
  117. package/dist/init.d.ts +49 -865
  118. package/dist/internal-tables-L2t39RdT.js +136 -0
  119. package/dist/internal-tables-L2t39RdT.js.map +1 -0
  120. package/dist/jobs/job-queue.d.ts +8 -0
  121. package/dist/jobs/job-store.d.ts +24 -0
  122. package/dist/jobs/types.d.ts +25 -4
  123. package/dist/{jobs-DqYNfquG.js → jobs-BCbflZj2.js} +234 -24
  124. package/dist/jobs-BCbflZj2.js.map +1 -0
  125. package/dist/{jwt-R6bSPMjk.js → jwt-BlvuKoR6.js} +529 -454
  126. package/dist/jwt-BlvuKoR6.js.map +1 -0
  127. package/dist/{keys-GAVZqbqx.js → keys-COzQ2aE8.js} +2 -2
  128. package/dist/{keys-GAVZqbqx.js.map → keys-COzQ2aE8.js.map} +1 -1
  129. package/dist/load-failure-L5hvfDoG.js +80 -0
  130. package/dist/load-failure-L5hvfDoG.js.map +1 -0
  131. package/dist/mcp/mcp-routes.d.ts +6 -0
  132. package/dist/mcp/mcp-tools.d.ts +8 -0
  133. package/dist/mcp/oauth-routes.d.ts +7 -0
  134. package/dist/metrics/index.d.ts +18 -0
  135. package/dist/metrics/pool-wait.d.ts +50 -0
  136. package/dist/offline-QNQgsPOq-B2yGeXkn.js +3305 -0
  137. package/dist/offline-QNQgsPOq-B2yGeXkn.js.map +1 -0
  138. package/dist/offline-store-Cm5sAX81-CALiO4Gn.js +1915 -0
  139. package/dist/offline-store-Cm5sAX81-CALiO4Gn.js.map +1 -0
  140. package/dist/{openapi-generator-DAq_XVDu.js → openapi-generator-D9zUQmEp.js} +366 -82
  141. package/dist/openapi-generator-D9zUQmEp.js.map +1 -0
  142. package/dist/policy-PdzX-Opd.js +121 -0
  143. package/dist/policy-PdzX-Opd.js.map +1 -0
  144. package/dist/project_manifest-R6y_KHms.js +59 -0
  145. package/dist/project_manifest-R6y_KHms.js.map +1 -0
  146. package/dist/{proxy-qRlqeUmO.js → proxy-CkTlMGmh.js} +4 -3
  147. package/dist/proxy-CkTlMGmh.js.map +1 -0
  148. package/dist/{query-parser-BgiKJKvc.js → query-parser-4WkMoA_K.js} +510 -15
  149. package/dist/query-parser-4WkMoA_K.js.map +1 -0
  150. package/dist/{request-timeout-DgH7j8qO.js → request-timeout-Dnl2_OBc.js} +15 -8
  151. package/dist/request-timeout-Dnl2_OBc.js.map +1 -0
  152. package/dist/resumable-upload-threshold-CKv-Ry0l.js +24 -0
  153. package/dist/resumable-upload-threshold-CKv-Ry0l.js.map +1 -0
  154. package/dist/schema-edit/apply-schema-change.d.ts +10 -0
  155. package/dist/schema-edit/local-source.d.ts +29 -0
  156. package/dist/schema-edit/remote-source.d.ts +13 -3
  157. package/dist/schema-edit/source-editor.d.ts +28 -0
  158. package/dist/{schema-editor-routes-oIyuWl3L.js → schema-editor-routes-C11Tumyd.js} +54 -10
  159. package/dist/schema-editor-routes-C11Tumyd.js.map +1 -0
  160. package/dist/services/outbound-post.d.ts +50 -0
  161. package/dist/services/outbound-url-guard.d.ts +34 -5
  162. package/dist/services/routed-realtime-service.d.ts +5 -0
  163. package/dist/services/webhook-service.d.ts +66 -14
  164. package/dist/singleton.d.ts +9 -5
  165. package/dist/soft-delete-params-DfV3vVN5.js +668 -0
  166. package/dist/soft-delete-params-DfV3vVN5.js.map +1 -0
  167. package/dist/sql-export-routes-ClRwzIsC.js +240 -0
  168. package/dist/sql-export-routes-ClRwzIsC.js.map +1 -0
  169. package/dist/{src-CatHFUym.js → src-D5Y3N-S4.js} +2 -1
  170. package/dist/{src-CatHFUym.js.map → src-D5Y3N-S4.js.map} +1 -1
  171. package/dist/storage/GCSStorageController.d.ts +46 -2
  172. package/dist/storage/LocalStorageController.d.ts +67 -2
  173. package/dist/storage/S3StorageController.d.ts +37 -3
  174. package/dist/storage/image-transform.d.ts +5 -0
  175. package/dist/storage/maintenance-routes.d.ts +23 -0
  176. package/dist/storage/maintenance.d.ts +126 -0
  177. package/dist/storage/rendition-cache.d.ts +9 -11
  178. package/dist/storage/request-principals.d.ts +30 -0
  179. package/dist/storage/routes.d.ts +20 -0
  180. package/dist/storage/source-variables.d.ts +10 -0
  181. package/dist/storage/streams.d.ts +42 -0
  182. package/dist/storage/tus-handler.d.ts +46 -2
  183. package/dist/storage/types.d.ts +82 -7
  184. package/dist/storage/upload-validation.d.ts +13 -0
  185. package/dist/storage-resumable-VO-cMpse-C_YX71pO.js +125 -0
  186. package/dist/storage-resumable-VO-cMpse-C_YX71pO.js.map +1 -0
  187. package/dist/strings.es-BeZWAodn.js +256 -0
  188. package/dist/strings.es-BeZWAodn.js.map +1 -0
  189. package/dist/upload-validation-Dd-fnsbA.js +172 -0
  190. package/dist/upload-validation-Dd-fnsbA.js.map +1 -0
  191. package/dist/utils/byte-size.d.ts +10 -0
  192. package/dist/utils/compression.d.ts +6 -0
  193. package/dist/utils/load-failure.d.ts +14 -0
  194. package/package.json +5 -5
  195. package/dist/GCSStorageController-BSiP1c-f.js +0 -256
  196. package/dist/GCSStorageController-BSiP1c-f.js.map +0 -1
  197. package/dist/S3StorageController-CAwFRgjV.js +0 -276
  198. package/dist/S3StorageController-CAwFRgjV.js.map +0 -1
  199. package/dist/auth-DMLngxn_.js.map +0 -1
  200. package/dist/backup-CN0s50D2.js.map +0 -1
  201. package/dist/contract-routes-fz8i4pxs.js.map +0 -1
  202. package/dist/cron-loader-CwaANlOG.js +0 -118
  203. package/dist/cron-loader-CwaANlOG.js.map +0 -1
  204. package/dist/cron-routes-Bc-SB0Se.js.map +0 -1
  205. package/dist/cron-scheduler-CYQgco86.js.map +0 -1
  206. package/dist/cron-store-D2Q9-Aco.js +0 -300
  207. package/dist/cron-store-D2Q9-Aco.js.map +0 -1
  208. package/dist/dynamic-import-X40pTZUQ.js +0 -23
  209. package/dist/dynamic-import-X40pTZUQ.js.map +0 -1
  210. package/dist/function-loader-D7o5Epjj.js.map +0 -1
  211. package/dist/function-routes-CaNG4waN.js.map +0 -1
  212. package/dist/jobs-DqYNfquG.js.map +0 -1
  213. package/dist/jwt-R6bSPMjk.js.map +0 -1
  214. package/dist/openapi-generator-DAq_XVDu.js.map +0 -1
  215. package/dist/proxy-qRlqeUmO.js.map +0 -1
  216. package/dist/query-parser-BgiKJKvc.js.map +0 -1
  217. package/dist/request-timeout-DgH7j8qO.js.map +0 -1
  218. package/dist/schema-editor-routes-oIyuWl3L.js.map +0 -1
  219. package/dist/soft-delete-params-BWPilMPF.js +0 -59
  220. package/dist/soft-delete-params-BWPilMPF.js.map +0 -1
  221. package/dist/src-I3aG1PcY.js +0 -6870
  222. package/dist/src-I3aG1PcY.js.map +0 -1
  223. package/dist/types-BfKcm9do.js +0 -39
  224. package/dist/types-BfKcm9do.js.map +0 -1
@@ -0,0 +1,1628 @@
1
+ import { createRequire as __rebaseCreateRequire } from "module";
2
+ import __rebaseProcess from "process";
3
+ globalThis.process ??= __rebaseProcess;
4
+ __rebaseCreateRequire(import.meta.url);
5
+ import { c as RebaseClientError } from "./strings.es-BeZWAodn.js";
6
+ import { J as CANONICAL_TO_REST, Q as isRelationAggregateSort, 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-D5Y3N-S4.js";
7
+ //#region ../types/src/types/entities.ts
8
+ /**
9
+ * Class used to create a reference to a entity in a different path.
10
+ *
11
+ * @example
12
+ * // Simple reference (most common case - single driver, single db)
13
+ * new EntityReference({ id: "123", path: "users" })
14
+ *
15
+ * // Reference to a different driver (e.g., Firestore)
16
+ * new EntityReference({ id: "123", path: "analytics", driver: "firestore" })
17
+ *
18
+ * // Reference to a specific database within a driver
19
+ * new EntityReference({ id: "123", path: "orders", driver: "postgres", databaseId: "orders_db" })
20
+ */
21
+ var EntityReference = class {
22
+ __type = "reference";
23
+ /**
24
+ * ID of the entity
25
+ */
26
+ id;
27
+ /**
28
+ * A string representing the path of the referenced document (relative
29
+ * to the root of the database).
30
+ */
31
+ path;
32
+ /**
33
+ * Which driver (e.g., 'postgres', 'firestore').
34
+ * Defaults to "(default)" if not specified.
35
+ */
36
+ driver;
37
+ /**
38
+ * Which database within the driver.
39
+ * Defaults to "(default)" if not specified.
40
+ */
41
+ databaseId;
42
+ /**
43
+ * Create a reference to a entity.
44
+ *
45
+ * @example
46
+ * // Simple reference (most common case)
47
+ * new EntityReference({ id: "123", path: "users" })
48
+ *
49
+ * // With driver
50
+ * new EntityReference({ id: "123", path: "analytics", driver: "firestore" })
51
+ */
52
+ constructor(props) {
53
+ this.id = props.id;
54
+ this.path = props.path;
55
+ this.driver = props.driver;
56
+ this.databaseId = props.databaseId;
57
+ }
58
+ get pathWithId() {
59
+ return `${this.path}/${this.id}`;
60
+ }
61
+ /**
62
+ * Get the full path including driver and database prefixes if specified.
63
+ * For the common case (single driver, single db), this just returns pathWithId.
64
+ */
65
+ get fullPath() {
66
+ const parts = [];
67
+ if (this.driver && this.driver !== "(default)") parts.push(this.driver);
68
+ if (this.databaseId && this.databaseId !== "(default)") parts.push(this.databaseId);
69
+ if (parts.length > 0) return `${parts.join(":")}:::${this.path}/${this.id}`;
70
+ return this.pathWithId;
71
+ }
72
+ isEntityReference() {
73
+ return true;
74
+ }
75
+ };
76
+ /**
77
+ * Class used to create a reference to a entity in a different path
78
+ */
79
+ var EntityRelation = class {
80
+ __type = "relation";
81
+ /**
82
+ * ID of the entity
83
+ */
84
+ id;
85
+ /**
86
+ * A string representing the path of the referenced document (relative
87
+ * to the root of the database).
88
+ */
89
+ path;
90
+ /**
91
+ * Pre-fetched data payload to eliminate N+1 queries.
92
+ * When present, clients can use this directly instead of fetching.
93
+ */
94
+ data;
95
+ constructor(id, path, data) {
96
+ this.id = id;
97
+ this.path = path;
98
+ this.data = data;
99
+ }
100
+ get pathWithId() {
101
+ return `${this.path}/${this.id}`;
102
+ }
103
+ isEntityReference() {
104
+ return false;
105
+ }
106
+ isEntityRelation() {
107
+ return true;
108
+ }
109
+ };
110
+ var GeoPoint = class {
111
+ /**
112
+ * The latitude of this GeoPoint instance.
113
+ */
114
+ latitude;
115
+ /**
116
+ * The longitude of this GeoPoint instance.
117
+ */
118
+ longitude;
119
+ constructor(latitude, longitude) {
120
+ this.latitude = latitude;
121
+ this.longitude = longitude;
122
+ }
123
+ };
124
+ var Vector = class {
125
+ value;
126
+ constructor(value) {
127
+ this.value = value;
128
+ }
129
+ };
130
+ //#endregion
131
+ //#region ../types/src/controllers/data.ts
132
+ /**
133
+ * SDK collection client — returns flat rows, no Entity wrapper.
134
+ *
135
+ * This is the public API surface for app developers using
136
+ * `createRebaseClient()`. admin internals use `CollectionAccessor` instead.
137
+ *
138
+ * Type parameters:
139
+ * - `M` — the **Row** shape returned by reads (`find`, `findById`, `listen`).
140
+ * - `I` — the **Insert** shape accepted by {@link create}. Defaults to
141
+ * `Partial<M>`; the generated SDK supplies a dedicated `Insert` type where
142
+ * required columns are required and auto-generated / read-only columns are
143
+ * omitted, so `create({})` on a table with required fields is a compile error.
144
+ * - `U` — the **Update** shape accepted by {@link update}. Defaults to
145
+ * `Partial<M>`; the generated SDK supplies a dedicated `Update` type.
146
+ *
147
+ * @example
148
+ * const { data: posts } = await rebase.data.posts.find();
149
+ * console.log(posts[0].title); // flat access
150
+ * console.log(posts[0].id); // id at top level
151
+ *
152
+ * const post = await rebase.data.posts.findById(1);
153
+ * console.log(post?.title); // no .values needed
154
+ *
155
+ * @group Data
156
+ */
157
+ /**
158
+ * A change expressed as an operation on the column's current value, rather than
159
+ * as the value to store.
160
+ *
161
+ * `{ views: 5 }` says what the number becomes; `{ views: { $inc: 1 } }` says
162
+ * what happens to it. The difference is the read the caller no longer has to
163
+ * make — and the race that read opens. Two requests that each read `4`, add one
164
+ * and write `5` lose an increment between them; `SET views = views + 1` cannot,
165
+ * because the arithmetic happens inside the statement holding the row lock.
166
+ *
167
+ * Exactly one operator per field. `{ views: { $inc: 1, $push: "x" } }` is
168
+ * refused rather than applied in an order the caller cannot see.
169
+ *
170
+ * @group Data
171
+ */
172
+ /**
173
+ * The operator names, as a value.
174
+ *
175
+ * A runtime list beside the type because three layers have to *recognise* an
176
+ * operation, not just accept one: the REST validator, the driver that compiles
177
+ * it, and the offline queue that must refuse to apply one locally. Three copies
178
+ * of four strings is three chances for one of them to miss an operator added to
179
+ * the other two, and the failure is silent in the worst direction — an
180
+ * unrecognised marker is written to the column as a JSON document.
181
+ *
182
+ * @group Data
183
+ */
184
+ var FIELD_OPERATORS = [
185
+ "$inc",
186
+ "$push",
187
+ "$pull",
188
+ "$merge"
189
+ ];
190
+ /**
191
+ * The key of a {@link BatchRef}. Declared here, beside the field operators,
192
+ * because the two share one namespace: a `$`-prefixed key in a write payload is
193
+ * a marker, and every reader of that namespace has to know all of it.
194
+ *
195
+ * @group Data
196
+ */
197
+ var BATCH_REF_KEY = "$ref";
198
+ /**
199
+ * Whether a value is *trying* to be a field operation — including a misspelled
200
+ * one, which is the case worth catching.
201
+ *
202
+ * Any `$`-prefixed key counts, because `{ $increment: 1 }` written to a number
203
+ * column as a JSON document is the failure this exists to prevent.
204
+ *
205
+ * Except on a `map` (pass `propertyType`): a map's value is a JSON document,
206
+ * and one may legitimately carry `$` keys — a JSON Schema's `$schema`, a
207
+ * MongoDB-style export. There, only a single-key object whose key is one of
208
+ * {@link FIELD_OPERATORS} is an operation, and anything else is data. A caller
209
+ * that does not know the property type gets the strict reading.
210
+ *
211
+ * The one exception is `{ $ref: … }`, the batch's backward reference. It stands
212
+ * where a *value* goes and is resolved to one before the row is written, so it
213
+ * is not an operation on a column — reading it as a misspelled operator refused
214
+ * every `$ref` in a batch with "unknown field operator '$ref'".
215
+ *
216
+ * @group Data
217
+ */
218
+ function isFieldOperation(value, propertyType) {
219
+ if (typeof value !== "object" || value === null || Array.isArray(value) || value instanceof Date) return false;
220
+ const keys = Object.keys(value);
221
+ if (keys.length === 1 && keys[0] === "$ref") return false;
222
+ if (propertyType === "map") return keys.length === 1 && FIELD_OPERATORS.includes(keys[0]);
223
+ return keys.some((key) => key.startsWith("$"));
224
+ }
225
+ /** True when any value in a write payload is (or is attempting to be) one. @group Data */
226
+ function hasFieldOperation(values) {
227
+ return !!values && Object.values(values).some((value) => isFieldOperation(value));
228
+ }
229
+ //#endregion
230
+ //#region ../common/src/util/entities.ts
231
+ /**
232
+ * Normalize a value into a proper EntityRelation instance.
233
+ * Handles EntityRelation class instances, and plain objects
234
+ * with `__type === "relation"` or an `isEntityRelation()` method.
235
+ *
236
+ * When `propertyType` is `"relation"`, also accepts plain objects that
237
+ * have `id` and `path` fields — these are relation-shaped objects from
238
+ * edge cases in the data pipeline (REST fallback, stale cache, custom data source).
239
+ *
240
+ * When `targetPath` is given, also accepts a bare id. A relation column is a
241
+ * foreign key, and the REST layer returns it as the scalar it is; only some
242
+ * fetch paths hydrate it into an object. Which form a caller sees therefore
243
+ * depends on how the row was loaded, and a caller that only accepted objects
244
+ * reported half of its own data as a type error. The declared target is the
245
+ * missing half: with it, an id is a relation that has not been fetched yet.
246
+ *
247
+ * Returns null if the value cannot be coerced.
248
+ */
249
+ function normalizeToEntityRelation(value, propertyType, targetPath) {
250
+ if (value instanceof EntityRelation) return value;
251
+ if (targetPath && (typeof value === "string" || typeof value === "number")) {
252
+ if (value === "") return null;
253
+ return new EntityRelation(value, targetPath);
254
+ }
255
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
256
+ const obj = value;
257
+ if (!(obj.__type === "relation" || obj.__type === "reference" || typeof obj.isEntityRelation === "function" && obj.isEntityRelation() || typeof obj.isEntityReference === "function" && obj.isEntityReference() || propertyType === "relation" && typeof obj.id !== "undefined" && typeof obj.path === "string")) return null;
258
+ return new EntityRelation(obj.id, obj.path, obj.data);
259
+ }
260
+ /**
261
+ * A row snapshot with every relation in it reduced to its reference — target
262
+ * path and id, `__type` kept — and the related row's values dropped.
263
+ *
264
+ * What a history entry stores and serves. The history route is gated on
265
+ * reading the row, not the rows it points at, so a snapshot that embedded the
266
+ * admin's `{ __type: "relation", data: { values } }` envelope handed whoever
267
+ * may read `posts` history the `authors` columns too. Entries are written as
268
+ * the stored row now, which holds no envelope; this keeps it that way for any
269
+ * path that hands one in, and reads entries recorded before.
270
+ *
271
+ * Walks maps and arrays, since a relation can sit inside either. Anything that
272
+ * is not an envelope comes back as it was.
273
+ */
274
+ function withRelationReferencesOnly(value) {
275
+ if (Array.isArray(value)) return value.map((item) => withRelationReferencesOnly(item));
276
+ if (value === null || typeof value !== "object" || value instanceof Date) return value;
277
+ const obj = value;
278
+ if (obj.__type === "relation" || obj.__type === "reference") {
279
+ const { data: _related, ...reference } = obj;
280
+ return reference;
281
+ }
282
+ if (Object.getPrototypeOf(obj) !== Object.prototype && Object.getPrototypeOf(obj) !== null) return value;
283
+ return Object.fromEntries(Object.entries(obj).map(([key, item]) => [key, withRelationReferencesOnly(item)]));
284
+ }
285
+ //#endregion
286
+ //#region ../common/src/data/sort-dialect.ts
287
+ /**
288
+ * Sort-order wire codec.
289
+ *
290
+ * This is the ONLY module that knows about the colon-delimited wire format
291
+ * (`"field:direction"`) used in HTTP query parameters, and about the JSON-array
292
+ * form that carries a multi-column sort over the same parameter.
293
+ * Everything else speaks {@link OrderByTuple} exclusively.
294
+ *
295
+ * Mirrors the filter architecture in `filter-dialect.ts`.
296
+ *
297
+ * @module
298
+ */
299
+ /**
300
+ * Collapse the one-key and many-key spellings of a sort into the list form.
301
+ *
302
+ * `["a", "desc"]` and `[["a", "desc"]]` mean the same thing and normalize to
303
+ * the same value; the two are told apart by whether the first element is
304
+ * itself an array, which no field name ever is.
305
+ *
306
+ * This is also where a {@link RelationAggregateSort} object stops being an
307
+ * object. Above this function a sort key may be either spelling; below it,
308
+ * every key is a string — which is what `OrderByTuple`, the REST parameter, the
309
+ * driver contract and the cursor all already were. Doing it here means the one
310
+ * place that already collapses the two *shapes* of a sort also collapses the
311
+ * two *spellings* of a key, rather than every consumer learning about both.
312
+ *
313
+ * @returns The keys in order of significance, or `undefined` for no sort. An
314
+ * empty list also returns `undefined` — "sort by nothing" is no sort, and
315
+ * letting `[]` through would have every layer below re-deciding what it meant.
316
+ */
317
+ function normalizeOrderBy(orderBy) {
318
+ if (!orderBy || orderBy.length === 0) return void 0;
319
+ const list = Array.isArray(orderBy[0]) ? orderBy : [orderBy];
320
+ if (list.length === 0) return void 0;
321
+ return list.map((entry, index) => toStrictTuple(entry, index));
322
+ }
323
+ /** A sort whose *shape* is unusable, as opposed to one naming a field that does not exist. */
324
+ var OrderBySpecError = class extends Error {
325
+ code = "INVALID_ORDER_BY";
326
+ constructor(detail) {
327
+ super(`Invalid \`orderBy\`: ${detail}. Expected a field name, or a list of [field, direction] pairs like [["roles","asc"],["created_at","desc"]]`);
328
+ this.name = "OrderBySpecError";
329
+ }
330
+ };
331
+ /** `first`/`last`, or a refusal naming the entry — see {@link NullsPlacement}. */
332
+ function toStrictNulls(raw, index) {
333
+ if (raw === void 0 || raw === null) return void 0;
334
+ if (raw !== "first" && raw !== "last") throw new OrderBySpecError(`entry ${index} has nulls '${String(raw)}' — expected "first" or "last"`);
335
+ return raw;
336
+ }
337
+ function toStrictTuple(raw, index) {
338
+ if (!Array.isArray(raw)) throw new OrderBySpecError(`entry ${index} has no field name`);
339
+ const key = isRelationAggregateSort(raw[0]) ? sortKeyToString(raw[0]) : raw[0];
340
+ if (typeof key !== "string" || key.trim() === "") throw new OrderBySpecError(`entry ${index} has no field name`);
341
+ const direction = raw[1];
342
+ if (direction !== void 0 && direction !== "asc" && direction !== "desc") throw new OrderBySpecError(`entry ${index} has direction '${String(direction)}'`);
343
+ const nulls = toStrictNulls(raw[2], index);
344
+ return nulls ? [
345
+ key,
346
+ direction ?? "asc",
347
+ nulls
348
+ ] : [key, direction ?? "asc"];
349
+ }
350
+ /**
351
+ * Serialize a sort to the wire.
352
+ *
353
+ * A single key keeps the `"field:direction"` shorthand it has always used —
354
+ * short, readable in a URL, and what every existing client and test expects.
355
+ * Several keys are emitted as the canonical JSON array the server already
356
+ * accepts, because the shorthand has no separator to spare: a comma-joined
357
+ * `"a:asc,b:desc"` parses as one field named `a` with the direction
358
+ * `"asc,b:desc"`, which the server refuses.
359
+ *
360
+ * **Runtime tolerance:** if the input is already a well-formed wire string
361
+ * (from an untyped JS caller), it is returned unchanged.
362
+ * This is undocumented tolerance, not public API — don't rely on it.
363
+ *
364
+ * @param orderBy - A canonical tuple or list of tuples, or at runtime
365
+ * possibly a pre-serialized string (undocumented tolerance).
366
+ * @returns The wire-format string, or `undefined` if the input is falsy.
367
+ *
368
+ * @remarks
369
+ * Field names containing `:` are representable in the tuple form but
370
+ * **not** in the single-key wire encoding — this is an inherent limitation of
371
+ * the colon-delimited shorthand and is not resolved here.
372
+ */
373
+ function serializeOrderBy(orderBy) {
374
+ if (!orderBy) return void 0;
375
+ if (typeof orderBy === "string") return orderBy;
376
+ const list = normalizeOrderBy(orderBy);
377
+ if (!list) return void 0;
378
+ if (list.length === 1) {
379
+ const [field, direction, nulls] = list[0];
380
+ return nulls ? `${field}:${direction}:${nulls}` : `${field}:${direction}`;
381
+ }
382
+ return JSON.stringify(list.map(([field, direction, nulls]) => nulls ? {
383
+ field,
384
+ direction,
385
+ nulls
386
+ } : {
387
+ field,
388
+ direction
389
+ }));
390
+ }
391
+ /**
392
+ * Deserialize a wire-format `"field:direction"` string into an {@link OrderByTuple}.
393
+ *
394
+ * Lenient parsing:
395
+ * - Bare field name (no colon): `"name"` → `["name", "asc"]`
396
+ * - Unknown direction: `"name:foo"` → `["name", "asc"]`
397
+ * - Empty / falsy input, or a blank field name: → `undefined`
398
+ *
399
+ * The leniency is this end's alone; the *server* refuses the same value. This
400
+ * used to say "matches existing server behaviour", and it stopped being true
401
+ * when `parseOrderByParam` grew a strict direction check: `?orderBy=name:foo`
402
+ * now answers `400 INVALID_ORDER_BY` ("entry 0 has direction 'foo'"). The split
403
+ * is deliberate — see {@link parseOrderBySpecStrict} — because a value this
404
+ * function is handed was produced by {@link serializeOrderBy} a moment earlier,
405
+ * and one that reaches the server came from a stranger.
406
+ *
407
+ * A blank field is `undefined` rather than `[" ", "asc"]`: whitespace is not a
408
+ * field name, and the tuple it used to produce could not be re-encoded — the
409
+ * only value in this codec that survived a decode and failed the next encode.
410
+ *
411
+ * Reads the single-key shorthand only. For a value that may carry several keys,
412
+ * use {@link deserializeOrderByList} — handed a JSON array this returns the
413
+ * whole array as one nonsensical field name.
414
+ *
415
+ * @param raw - The wire-format string from an HTTP query parameter.
416
+ * @returns The canonical tuple, or `undefined` if the input names no field.
417
+ */
418
+ function deserializeOrderBy(raw) {
419
+ if (!raw) return void 0;
420
+ const idx = raw.indexOf(":");
421
+ if (idx === -1) return raw.trim() === "" ? void 0 : [raw, "asc"];
422
+ const field = raw.slice(0, idx);
423
+ if (field.trim() === "") return void 0;
424
+ const rest = raw.slice(idx + 1);
425
+ const nullsIdx = rest.indexOf(":");
426
+ const dir = nullsIdx === -1 ? rest : rest.slice(0, nullsIdx);
427
+ const nulls = nullsIdx === -1 ? void 0 : rest.slice(nullsIdx + 1);
428
+ const direction = dir === "desc" ? "desc" : "asc";
429
+ return nulls === "first" || nulls === "last" ? [
430
+ field,
431
+ direction,
432
+ nulls
433
+ ] : [field, direction];
434
+ }
435
+ /**
436
+ * Deserialize either wire spelling — the single-key shorthand or the JSON
437
+ * array — into the list form.
438
+ *
439
+ * Lenient in the same way {@link deserializeOrderBy} is: this is the client end
440
+ * of the codec, where the value was produced by {@link serializeOrderBy} a
441
+ * moment earlier. The *server* end parses the same shapes strictly, in
442
+ * `parseOrderByParam`, because there the value came from a stranger and a
443
+ * direction it cannot read has to be refused rather than quietly turned into
444
+ * `"asc"`.
445
+ */
446
+ function deserializeOrderByList(raw) {
447
+ if (!raw) return void 0;
448
+ const trimmed = raw.trim();
449
+ if (trimmed.startsWith("[")) try {
450
+ const parsed = JSON.parse(trimmed);
451
+ if (Array.isArray(parsed)) {
452
+ const list = parsed.map((entry) => {
453
+ if (typeof entry === "string") return deserializeOrderBy(entry);
454
+ if (entry && typeof entry === "object" && typeof entry.field === "string") {
455
+ const direction = entry.direction === "desc" ? "desc" : "asc";
456
+ return entry.nulls === "first" || entry.nulls === "last" ? [
457
+ entry.field,
458
+ direction,
459
+ entry.nulls
460
+ ] : [entry.field, direction];
461
+ }
462
+ }).filter((entry) => entry !== void 0);
463
+ return list.length > 0 ? list : void 0;
464
+ }
465
+ } catch {}
466
+ const single = deserializeOrderBy(trimmed);
467
+ return single ? [single] : void 0;
468
+ }
469
+ //#endregion
470
+ //#region ../common/src/data/include-spec.ts
471
+ /** An `include` that cannot be read, as opposed to one naming a relation that does not exist. */
472
+ var IncludeSpecError = class IncludeSpecError extends Error {
473
+ code;
474
+ constructor(detail, code = "INVALID_INCLUDE") {
475
+ super(`Invalid \`include\`: ${detail}`);
476
+ this.name = "IncludeSpecError";
477
+ this.code = code;
478
+ Object.setPrototypeOf(this, IncludeSpecError.prototype);
479
+ }
480
+ };
481
+ var emptyNode = () => ({ children: {} });
482
+ function ensureNode(tree, key) {
483
+ return tree[key] ??= emptyNode();
484
+ }
485
+ /**
486
+ * Merge one dotted path (`"comments.author"`) into a tree.
487
+ *
488
+ * Merging rather than assigning is what makes `include=comments,comments.author`
489
+ * mean the same thing as `include=comments.author`: the second path deepens the
490
+ * node the first created instead of replacing it and losing its options.
491
+ */
492
+ function addPath(tree, path) {
493
+ const segments = path.split(".").map((s) => s.trim()).filter(Boolean);
494
+ if (segments.length === 0) return;
495
+ if (segments.length > 3) throw new IncludeSpecError(`"${path}" nests ${segments.length} relations deep; the limit is 3. Each hop is another query, and an unbounded one walks a self-referencing relation forever.`, "INCLUDE_TOO_DEEP");
496
+ let level = tree;
497
+ for (const segment of segments) level = ensureNode(level, segment).children;
498
+ }
499
+ function normalizeOptions(key, options, depth) {
500
+ if (depth > 3) throw new IncludeSpecError(`"${key}" nests more than 3 relations deep.`, "INCLUDE_TOO_DEEP");
501
+ if (options.limit !== void 0 && (!Number.isInteger(options.limit) || options.limit < 1)) throw new IncludeSpecError(`"${key}" has limit ${JSON.stringify(options.limit)} — expected a whole number of 1 or more.`);
502
+ if (options.count !== void 0 && typeof options.count !== "boolean") throw new IncludeSpecError(`"${key}" has count ${JSON.stringify(options.count)} — expected \`true\` or \`false\`.`);
503
+ const node = { children: {} };
504
+ if (options.limit !== void 0) node.limit = options.limit;
505
+ if (options.count) node.count = true;
506
+ if (options.where) node.where = options.where;
507
+ if (options.logical) node.logical = options.logical;
508
+ if (options.fields && options.fields.length > 0) node.fields = [...options.fields];
509
+ const orderBy = typeof options.orderBy === "string" ? deserializeOrderByList(options.orderBy) : normalizeOrderBy(options.orderBy);
510
+ if (orderBy) node.orderBy = orderBy;
511
+ if (options.include) {
512
+ const nested = normalizeIncludeAt(options.include, depth + 1);
513
+ if (nested.wildcard) throw new IncludeSpecError(`"${key}" asks for \`*\` inside a nested include. Name the relations you need.`);
514
+ node.children = nested.tree;
515
+ }
516
+ return node;
517
+ }
518
+ function normalizeIncludeAt(spec, depth) {
519
+ if (Array.isArray(spec)) {
520
+ const tree = {};
521
+ let wildcard = false;
522
+ for (const raw of spec) {
523
+ if (typeof raw !== "string") throw new IncludeSpecError(`${typeof raw} is not a relation name`);
524
+ const name = raw.trim();
525
+ if (!name) continue;
526
+ if (name === "*") {
527
+ wildcard = true;
528
+ continue;
529
+ }
530
+ addPath(tree, name);
531
+ }
532
+ return {
533
+ wildcard,
534
+ tree
535
+ };
536
+ }
537
+ if (typeof spec !== "object" || spec === null) throw new IncludeSpecError(`${typeof spec} is not a list of relations or an include tree`);
538
+ const tree = {};
539
+ let wildcard = false;
540
+ for (const [key, value] of Object.entries(spec)) {
541
+ if (key === "*") {
542
+ if (value) wildcard = true;
543
+ continue;
544
+ }
545
+ if (value === true) {
546
+ ensureNode(tree, key);
547
+ continue;
548
+ }
549
+ if (value === false || value === void 0 || value === null) continue;
550
+ if (typeof value !== "object" || Array.isArray(value)) throw new IncludeSpecError(`"${key}" must be \`true\` or an options object`);
551
+ tree[key] = normalizeOptions(key, value, depth);
552
+ }
553
+ return {
554
+ wildcard,
555
+ tree
556
+ };
557
+ }
558
+ /**
559
+ * Collapse any {@link IncludeSpec} spelling into one tree.
560
+ *
561
+ * `["author", "comments.author"]` and
562
+ * `{ author: true, comments: { include: { author: true } } }` normalize to the
563
+ * same value — which is the whole point: the REST parameter can only carry the
564
+ * flat spelling, the SDK prefers the tree, and the driver should never learn
565
+ * about either.
566
+ *
567
+ * @throws {IncludeSpecError} for a shape that is not an include at all, or one
568
+ * that nests past {@link MAX_INCLUDE_DEPTH}.
569
+ */
570
+ function normalizeInclude(spec) {
571
+ if (spec === void 0 || spec === null) return void 0;
572
+ const normalized = normalizeIncludeAt(spec, 1);
573
+ if (!normalized.wildcard && Object.keys(normalized.tree).length === 0) return void 0;
574
+ return normalized;
575
+ }
576
+ /**
577
+ * Every relation name a tree names, as dotted paths — `["comments",
578
+ * "comments.author"]`.
579
+ *
580
+ * Used to report which names an `include` asked for when one of them is not a
581
+ * relation, and to serialize a tree that carries no per-relation options back
582
+ * to the flat wire spelling.
583
+ */
584
+ function includePaths(tree, prefix = "") {
585
+ const out = [];
586
+ for (const [key, node] of Object.entries(tree)) {
587
+ const path = prefix ? `${prefix}.${key}` : key;
588
+ out.push(path);
589
+ out.push(...includePaths(node.children, path));
590
+ }
591
+ return out;
592
+ }
593
+ /** Whether any node in the tree carries per-relation options. */
594
+ function hasOptions(tree) {
595
+ 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 || node.count !== void 0 || hasOptions(node.children));
596
+ }
597
+ /**
598
+ * Serialize an {@link IncludeSpec} for the REST `?include=` parameter.
599
+ *
600
+ * Two spellings, and which one is used is decided by the request rather than
601
+ * chosen:
602
+ *
603
+ * - **Comma-separated dotted paths** — `include=author,comments.author`. What a
604
+ * plain include is, what a human types, and what every existing client sends.
605
+ * - **JSON**, when any relation carries options — `include={"comments":{"limit":5,
606
+ * "include":{"author":true}}}`. The flat spelling has nowhere to put a
607
+ * `limit`, and inventing a punctuation for it (`comments(limit:5)`) would be a
608
+ * third grammar to learn beside the two this API already has.
609
+ *
610
+ * The server accepts both on every list and get route, and tells them apart the
611
+ * same way this does: a value starting with `{` is JSON.
612
+ */
613
+ function serializeInclude(spec) {
614
+ const normalized = normalizeInclude(spec);
615
+ if (!normalized) return void 0;
616
+ if (normalized.wildcard && Object.keys(normalized.tree).length === 0) return "*";
617
+ if (!hasOptions(normalized.tree)) {
618
+ const paths = includePaths(normalized.tree);
619
+ const leaves = paths.filter((path) => !paths.some((other) => other.startsWith(`${path}.`)));
620
+ const all = normalized.wildcard ? ["*", ...leaves] : leaves;
621
+ return all.length > 0 ? all.join(",") : void 0;
622
+ }
623
+ return JSON.stringify(toWireTree(normalized));
624
+ }
625
+ /**
626
+ * A normalized tree, back in the {@link IncludeSpec} spelling a caller writes.
627
+ *
628
+ * The round trip is what lets a builder accumulate `include` calls: normalize
629
+ * each, merge, and hand the result back as a spec the next layer can normalize
630
+ * again. Idempotent, so doing it twice changes nothing.
631
+ */
632
+ function denormalizeInclude(normalized) {
633
+ return toWireTree(normalized);
634
+ }
635
+ function mergeTrees(into, from) {
636
+ for (const [key, node] of Object.entries(from)) {
637
+ const existing = into[key];
638
+ if (!existing) {
639
+ into[key] = node;
640
+ continue;
641
+ }
642
+ if (node.limit !== void 0) existing.limit = node.limit;
643
+ if (node.where !== void 0) existing.where = node.where;
644
+ if (node.logical !== void 0) existing.logical = node.logical;
645
+ if (node.orderBy !== void 0) existing.orderBy = node.orderBy;
646
+ if (node.fields !== void 0) existing.fields = node.fields;
647
+ if (node.count !== void 0) existing.count = node.count;
648
+ existing.children = mergeTrees(existing.children, node.children);
649
+ }
650
+ return into;
651
+ }
652
+ /**
653
+ * Combine several `include` requests into one.
654
+ *
655
+ * Repeated `.include(...)` calls on a query builder are additive: each names
656
+ * more of the graph to load, and a later one must not discard what an earlier
657
+ * one asked for. Assigning instead of merging is why `.include("author")
658
+ * .include("tags")` used to load only tags.
659
+ */
660
+ function mergeIncludeSpecs(existing, additions) {
661
+ const merged = {
662
+ wildcard: false,
663
+ tree: {}
664
+ };
665
+ const absorb = (spec) => {
666
+ const normalized = normalizeInclude(spec);
667
+ if (!normalized) return;
668
+ merged.wildcard ||= normalized.wildcard;
669
+ mergeTrees(merged.tree, normalized.tree);
670
+ };
671
+ absorb(existing);
672
+ const names = additions.filter((a) => typeof a === "string");
673
+ if (names.length > 0) absorb(names);
674
+ for (const addition of additions) if (typeof addition !== "string") absorb(addition);
675
+ if (!merged.wildcard && Object.keys(merged.tree).length === 0) return void 0;
676
+ return denormalizeInclude(merged);
677
+ }
678
+ function toWireTree(normalized) {
679
+ const emit = (tree) => {
680
+ const out = {};
681
+ for (const [key, node] of Object.entries(tree)) {
682
+ const options = {};
683
+ if (node.limit !== void 0) options.limit = node.limit;
684
+ if (node.where) options.where = node.where;
685
+ if (node.logical) options.logical = node.logical;
686
+ if (node.orderBy) options.orderBy = node.orderBy;
687
+ if (node.fields) options.fields = node.fields;
688
+ if (node.count) options.count = true;
689
+ const children = emit(node.children);
690
+ if (Object.keys(children).length > 0) options.include = children;
691
+ out[key] = Object.keys(options).length > 0 ? options : true;
692
+ }
693
+ return out;
694
+ };
695
+ const tree = emit(normalized.tree);
696
+ if (normalized.wildcard) tree["*"] = true;
697
+ return tree;
698
+ }
699
+ /**
700
+ * Read the REST `?include=` parameter, in either spelling.
701
+ *
702
+ * @throws {IncludeSpecError} for malformed JSON or a tree that nests too deep.
703
+ */
704
+ function deserializeInclude(raw) {
705
+ if (raw === void 0 || raw === null) return void 0;
706
+ const text = raw.trim();
707
+ if (!text) return void 0;
708
+ if (text.startsWith("{")) {
709
+ let parsed;
710
+ try {
711
+ parsed = JSON.parse(text);
712
+ } catch {
713
+ throw new IncludeSpecError("the parametrised form must be a JSON object, e.g. {\"comments\":{\"limit\":5,\"include\":{\"author\":true}}}");
714
+ }
715
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new IncludeSpecError("the parametrised form must be a JSON object");
716
+ return parsed;
717
+ }
718
+ return text.split(",").map((s) => s.trim()).filter(Boolean);
719
+ }
720
+ /** Rows `findAll()` will materialise before it refuses to continue. */
721
+ var DEFAULT_FIND_ALL_MAX_ROWS = 1e4;
722
+ /**
723
+ * Requests one walk may make before it gives up on the server ever saying
724
+ * `hasMore: false`. At the default page size that is two million rows — far
725
+ * past any legitimate walk, and short of running forever.
726
+ */
727
+ var DEFAULT_MAX_PAGES = 1e4;
728
+ /**
729
+ * Thrown when a walk stops for a reason the caller needs to know about.
730
+ *
731
+ * Every one of these is a case where the alternative would be silent: a
732
+ * truncated array that looks complete, or a loop that never returns. Check
733
+ * {@link code} to tell them apart.
734
+ */
735
+ var RebasePaginationError = class RebasePaginationError extends Error {
736
+ code;
737
+ constructor(code, message) {
738
+ super(message);
739
+ this.name = "RebasePaginationError";
740
+ this.code = code;
741
+ Object.setPrototypeOf(this, RebasePaginationError.prototype);
742
+ }
743
+ };
744
+ /**
745
+ * What a read naming both `page` and `offset` is refused with — by the server
746
+ * (400 `PAGE_WITH_OFFSET`) and, before any request, by every client.
747
+ */
748
+ var PAGE_WITH_OFFSET_MESSAGE = "`page` and `offset` describe the same window; send one.";
749
+ /**
750
+ * Resolve `limit`/`offset`/`page` into the window a read will actually use.
751
+ *
752
+ * Lives here, next to the walk, for the reason at the top of this file: every
753
+ * transport has to mean the same thing by "page two". Four of them did not —
754
+ * the REST layer strode by {@link DEFAULT_LIST_LIMIT}, the local-first
755
+ * evaluator by {@link DEFAULT_PAGE_SIZE}, the in-process accessor by 20, and
756
+ * the published type documented a fourth number. Pages that overlap or skip
757
+ * rows are the mildest of those outcomes.
758
+ *
759
+ * `page` and `offset` together are refused with `PAGE_WITH_OFFSET`, as the
760
+ * server refuses them: they name one window twice, and `page` used to win
761
+ * silently over an `offset` the caller then never saw applied. `driverOffset`
762
+ * is the value to hand a driver: it stays `undefined` when the caller named no
763
+ * offset, because keyset pagination seeks with a `where` clause and must not
764
+ * look like it is paging by offset.
765
+ */
766
+ function resolveFindWindow(params) {
767
+ if (params?.page != null && params.offset != null) throw new RebaseClientError(PAGE_WITH_OFFSET_MESSAGE, { code: "PAGE_WITH_OFFSET" });
768
+ const limit = params?.limit ?? 50;
769
+ const offset = params?.page != null ? Math.max(0, (params.page - 1) * limit) : params?.offset ?? 0;
770
+ return {
771
+ limit,
772
+ offset,
773
+ driverOffset: params?.page != null ? offset : params?.offset
774
+ };
775
+ }
776
+ function normalizePageSize(raw) {
777
+ if (raw === void 0 || !Number.isFinite(raw)) return 200;
778
+ return Math.max(1, Math.floor(raw));
779
+ }
780
+ function normalizeMaxPages(raw) {
781
+ if (raw === void 0) return DEFAULT_MAX_PAGES;
782
+ if (raw === Number.POSITIVE_INFINITY) return raw;
783
+ if (!Number.isFinite(raw)) return DEFAULT_MAX_PAGES;
784
+ return Math.max(1, Math.floor(raw));
785
+ }
786
+ function normalizeMaxRows(raw) {
787
+ if (raw === void 0) return DEFAULT_FIND_ALL_MAX_ROWS;
788
+ if (raw === Number.POSITIVE_INFINITY) return raw;
789
+ if (!Number.isFinite(raw)) return DEFAULT_FIND_ALL_MAX_ROWS;
790
+ return Math.max(0, Math.floor(raw));
791
+ }
792
+ /**
793
+ * Walk every row a query matches, yielding one row at a time and fetching the
794
+ * next page only when the consumer asks for it.
795
+ *
796
+ * See {@link SDKCollectionClient.iterate} for the caller-facing contract,
797
+ * including the offset-drift caveat and the `cursor` alternative.
798
+ *
799
+ * @param find the transport's single-page read
800
+ * @param params `find()` parameters minus the window, plus the walk options
801
+ * @param label the collection name, so an error says which walk failed
802
+ */
803
+ async function* paginateFind(find, params, label = "collection") {
804
+ const { pageSize, cursor, maxPages, after: startAfter, offset: startOffset, page: startPage, ...rest } = params ?? {};
805
+ const findParams = {
806
+ ...rest,
807
+ count: "none"
808
+ };
809
+ const size = normalizePageSize(pageSize);
810
+ const pageCap = normalizeMaxPages(maxPages);
811
+ const seekRequested = cursor !== void 0 && cursor !== null || !!startAfter;
812
+ if (cursor !== void 0 && cursor !== null) {
813
+ const field = typeof cursor === "string" ? cursor : cursor.field;
814
+ const requested = typeof cursor === "object" && cursor !== null ? cursor.direction : void 0;
815
+ if (!normalizeOrderBy(findParams.orderBy)) findParams.orderBy = [field, requested ?? "asc"];
816
+ }
817
+ let offset = resolveFindWindow({
818
+ limit: size,
819
+ offset: startOffset,
820
+ page: startPage
821
+ }).offset;
822
+ let pages = 0;
823
+ let after = startAfter || void 0;
824
+ let seekingByServerCursor = false;
825
+ for (;;) {
826
+ 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\`.`);
827
+ const pageParams = {
828
+ ...findParams,
829
+ limit: size
830
+ };
831
+ if (seekRequested) {
832
+ if (after) pageParams.after = after;
833
+ } else if (seekingByServerCursor && after) pageParams.after = after;
834
+ else pageParams.offset = offset;
835
+ const page = await find(pageParams);
836
+ pages += 1;
837
+ const rows = page?.data ?? [];
838
+ if (rows.length === 0) return;
839
+ for (const row of rows) yield row;
840
+ if (page?.meta?.hasMore !== true) return;
841
+ if (seekRequested) {
842
+ const next = page.meta.nextCursor;
843
+ if (!next) throw new RebasePaginationError("cursor-missing", `Cannot seek past the last row of "${label}": the server reported another page but issued no cursor for it. An ordering with no stored value to compare against — relevance (\`_score\`) — cannot key a cursor. Drop \`cursor\` to page by offset.`);
844
+ if (next === after) throw new RebasePaginationError("cursor-stalled", `Iterating "${label}" is stuck: two pages in a row ended on the same cursor, so the walk cannot advance. Continuing would loop forever. Page by offset instead, or report this — a cursor that does not move is a server-side bug.`);
845
+ after = next;
846
+ } else {
847
+ offset += rows.length;
848
+ const next = page.meta.nextCursor;
849
+ seekingByServerCursor = !!next && next !== after;
850
+ after = seekingByServerCursor ? next : void 0;
851
+ }
852
+ }
853
+ }
854
+ /**
855
+ * {@link paginateFind}, collected into an array under a ceiling.
856
+ *
857
+ * See {@link SDKCollectionClient.findAll}.
858
+ */
859
+ async function collectAllPages(find, params, label = "collection") {
860
+ const { maxRows, ...rest } = params ?? {};
861
+ const cap = normalizeMaxRows(maxRows);
862
+ const out = [];
863
+ for await (const row of paginateFind(find, rest, label)) {
864
+ out.push(row);
865
+ if (out.length > cap) throw new RebasePaginationError("max-rows", `findAll("${label}") matched more than ${cap} rows. Returning the first ${cap} would look like the whole answer and quietly not be one, so this throws instead. Raise \`maxRows\` if you meant to load them all, or stream with \`iterate()\`.`);
866
+ }
867
+ return out;
868
+ }
869
+ //#endregion
870
+ //#region ../common/src/data/filter-dialect.ts
871
+ /**
872
+ * REST wire-format adapter for the unified filter system.
873
+ *
874
+ * This module is the ONLY code in the entire codebase that knows about
875
+ * PostgREST-style dot-syntax strings (`eq.active`, `gt.18`, `in.(a,b)`).
876
+ * Everything else speaks `FilterValues` exclusively.
877
+ *
878
+ * Wire-format values are always strings — the wire format carries no type
879
+ * metadata, so type coercion is the responsibility of the server-side data
880
+ * driver which has access to the collection schema.
881
+ *
882
+ * Structural characters inside a value are backslash-escaped: `,` → `\,`,
883
+ * `(` → `\(`, `)` → `\)`, and a literal backslash as `\\`. Decoding is
884
+ * deliberately conservative — only those four sequences are decoded, so a
885
+ * backslash that arrives unescaped from an older client survives intact.
886
+ *
887
+ * @module
888
+ */
889
+ /**
890
+ * Serialize a JS value to its querystring representation.
891
+ * `null` is serialized as the literal string `"null"`.
892
+ * Relation values (`EntityRelation` instances or `{ __type: "relation", id, path }`
893
+ * objects) are serialized as their raw id — the wire format only carries the
894
+ * value to compare against the FK column.
895
+ *
896
+ * A `Date` is its ISO 8601 instant — what JSON makes of it on the socket and
897
+ * in-process, so one filter names one instant through every door. `String()`
898
+ * gave the viewer's local zone, no milliseconds and a parenthesised zone name
899
+ * Postgres cannot parse. An invalid date keeps its "Invalid Date" spelling for
900
+ * the server to refuse, rather than throwing a `RangeError` from here.
901
+ */
902
+ function stringifyValue(value) {
903
+ if (value === null) return "null";
904
+ if (value instanceof Date && !Number.isNaN(value.getTime())) return value.toISOString();
905
+ const relation = normalizeToEntityRelation(value);
906
+ if (relation) return String(relation.id);
907
+ return String(value);
908
+ }
909
+ /**
910
+ * Characters that carry structure in the wire format and must therefore be
911
+ * escaped inside a value: the separator, the group delimiters, and the escape
912
+ * character itself.
913
+ *
914
+ * Parentheses are here because `and(...)`/`or(...)` groups are parsed by
915
+ * tracking paren depth. A value containing one is not merely ambiguous, it
916
+ * moves where the parser thinks the group ends.
917
+ */
918
+ var WIRE_SPECIALS = /[\\,()]/g;
919
+ /**
920
+ * Escape a value for the wire format: `\` → `\\`, `,` → `\,`, `(` → `\(`,
921
+ * `)` → `\)`.
922
+ */
923
+ /**
924
+ * The wire spelling of an empty list.
925
+ *
926
+ * A lone backslash: unproducible by {@link escapeWireValue}, which doubles
927
+ * every backslash it emits, so it cannot collide with any real item.
928
+ */
929
+ var EMPTY_LIST_TOKEN = "\\";
930
+ function escapeWireValue(value) {
931
+ return value.replace(WIRE_SPECIALS, (ch) => `\\${ch}`);
932
+ }
933
+ /**
934
+ * Unescape a wire-format value.
935
+ *
936
+ * **Conservative**, and deliberately so: only the four sequences
937
+ * {@link escapeWireValue} actually produces are decoded. A backslash followed
938
+ * by anything else is left exactly as it is.
939
+ *
940
+ * This used to consume the backslash before *any* character, which is
941
+ * indistinguishable for anything this codec emitted — it only ever emits those
942
+ * four — but not for input arriving from elsewhere. A client on an older
943
+ * release sends a Windows path or a LIKE pattern with a literal `C:\x`
944
+ * unescaped, and greedy unescaping silently turned it into `C:x`, changing
945
+ * which rows matched. Decoding only what the encoder can produce makes the two
946
+ * directions agree across versions.
947
+ */
948
+ function unescapeWireValue(value) {
949
+ let result = "";
950
+ for (let i = 0; i < value.length; i++) {
951
+ const next = value[i + 1];
952
+ if (value[i] === "\\" && (next === "\\" || next === "," || next === "(" || next === ")")) {
953
+ result += next;
954
+ i++;
955
+ continue;
956
+ }
957
+ result += value[i];
958
+ }
959
+ return result;
960
+ }
961
+ /**
962
+ * Whether a scalar, sent raw as a query parameter's operand, would be read
963
+ * back as something other than itself.
964
+ *
965
+ * A query parameter owns its whole value, so its scalar goes out unescaped —
966
+ * `eq.a,b`, `eq.C:\x` — and the decoder returns it verbatim. Two shapes break
967
+ * that. A parenthesised operand is a list for every operator (so that
968
+ * `["==", ["a","b"]]` on an array column stays a list), so raw, the scalar
969
+ * `"(deleted)"` would arrive as `["deleted"]`: an `==` matching nothing, a
970
+ * `!=` returning the very rows it excludes. Such a scalar is sent escaped,
971
+ * `eq.\(deleted\)`. And that spelling has to be told apart from a raw value
972
+ * that looks the same, so a scalar of *that* shape is escaped in turn.
973
+ *
974
+ * Everything else stays raw, so a value an older client sends decodes as it
975
+ * always has — unless it is exactly the escaped spelling of such a scalar,
976
+ * which decodes to the scalar it spells.
977
+ */
978
+ function scalarNeedsEscape(value) {
979
+ if (value.startsWith("(") && value.endsWith(")")) return true;
980
+ return decodeEscapedScalar(value) !== void 0;
981
+ }
982
+ /**
983
+ * The scalar an operand is the escaped spelling of, when it is one.
984
+ *
985
+ * Exact on purpose: only an operand that {@link escapeWireValue} produces from
986
+ * a scalar that {@link scalarNeedsEscape} is decoded. Any other operand —
987
+ * including a raw backslash an older client sent, like `\\server\share` — is
988
+ * left verbatim. Terminates because an operand that matches contains an
989
+ * escape pair, so the value is strictly shorter than it.
990
+ */
991
+ function decodeEscapedScalar(operand) {
992
+ if (!operand.startsWith("\\")) return void 0;
993
+ const value = unescapeWireValue(operand);
994
+ return escapeWireValue(value) === operand && scalarNeedsEscape(value) ? value : void 0;
995
+ }
996
+ /**
997
+ * Split a parenthesized list string on unescaped commas.
998
+ * Input is the content between `(` and `)`.
999
+ *
1000
+ * @example
1001
+ * splitListItems("admin,editor") // ["admin", "editor"]
1002
+ * splitListItems("hello\\, world,foo") // ["hello, world", "foo"]
1003
+ */
1004
+ function splitListItems(inner) {
1005
+ const items = [];
1006
+ let current = "";
1007
+ for (let i = 0; i < inner.length; i++) if (inner[i] === "\\" && i + 1 < inner.length) {
1008
+ current += inner[i] + inner[i + 1];
1009
+ i++;
1010
+ } else if (inner[i] === ",") {
1011
+ items.push(unescapeWireValue(current));
1012
+ current = "";
1013
+ } else current += inner[i];
1014
+ items.push(unescapeWireValue(current));
1015
+ return items;
1016
+ }
1017
+ /**
1018
+ * Split a group body on commas at paren depth 0, honouring escapes.
1019
+ *
1020
+ * The escape-awareness is the point. The splitter used to track only paren
1021
+ * depth, so a comma inside a scalar value ended a condition:
1022
+ * `or(name.eq.Doe, John,age.gte.18)` parsed as *three* conditions, the middle
1023
+ * one a fabricated `" John" == true`. On an `or` that widens the result set,
1024
+ * and nothing anywhere reports an error — the query simply stops meaning what
1025
+ * the caller wrote.
1026
+ */
1027
+ function splitGroupItems(inner) {
1028
+ const parts = [];
1029
+ let depth = 0;
1030
+ let start = 0;
1031
+ for (let i = 0; i < inner.length; i++) {
1032
+ const ch = inner[i];
1033
+ if (ch === "\\" && i + 1 < inner.length) {
1034
+ i++;
1035
+ continue;
1036
+ }
1037
+ if (ch === "(") depth++;
1038
+ else if (ch === ")") depth--;
1039
+ else if (ch === "," && depth === 0) {
1040
+ parts.push(inner.slice(start, i));
1041
+ start = i + 1;
1042
+ }
1043
+ }
1044
+ parts.push(inner.slice(start));
1045
+ return parts;
1046
+ }
1047
+ /**
1048
+ * Operator tables as `Map`s, because the key comes off the wire.
1049
+ *
1050
+ * Indexed as plain objects, every `Object.prototype` member answered: a query
1051
+ * string of `?f=valueOf.x` found a truthy "operator" — the inherited function —
1052
+ * and `deserializeTuple` returned it *as the operator*, so a function object
1053
+ * travelled on into the compilers in place of a `WhereFilterOp`. The guard one
1054
+ * line below (`if (!canonicalOp)`) reads as though it rejects anything unknown,
1055
+ * and does not: `Object.prototype` is not unknown to a plain object.
1056
+ *
1057
+ * Same shape as the prototype-key defects swept out of `setIn`, `getIn`,
1058
+ * `mergeDeep`, `unflattenObject` and `FOREIGN_CONVENTION_UIDS`.
1059
+ */
1060
+ var REST_OP_LOOKUP = new Map(Object.entries(REST_TO_CANONICAL));
1061
+ var CANONICAL_OP_LOOKUP = new Map(Object.entries(CANONICAL_TO_REST));
1062
+ /** The operator spellings a rejection lists back to the caller. */
1063
+ var VALID_OPERATOR_LIST = ALL_WHERE_FILTER_OPS.join(", ");
1064
+ /**
1065
+ * A filter condition named an operator this dialect does not have.
1066
+ *
1067
+ * ## Why this throws, rather than returning a typed rejection
1068
+ *
1069
+ * `deserializeFilter` is the *shared* codec: the REST ingress
1070
+ * (`packages/server/src/api/rest/query-parser.ts`), the browser SDK and the
1071
+ * admin panel (`buildRebaseData.ts`) all decode through it. Two constraints
1072
+ * follow.
1073
+ *
1074
+ * - It cannot throw the server's `ApiError`. `@rebasepro/common` does not
1075
+ * depend on `@rebasepro/server` (the dependency runs the other way), and a
1076
+ * browser client has no error handler to render an `ApiError` with. So the
1077
+ * rejection is this plain `Error` subclass, whose `message` reads correctly
1078
+ * wherever it surfaces — a rejected promise in an app, a 400 body over HTTP.
1079
+ * - It cannot be a returned rejection *value*. Every caller assigns the result
1080
+ * straight into a query it is about to run; a sentinel that none of them
1081
+ * check would be ignored, which is exactly the silently-wrong-filter failure
1082
+ * this exists to stop. Throwing is also what this file already does for the
1083
+ * sibling cases — `serializeTuple` on an unknown canonical operator,
1084
+ * `deserializeLogicalCondition` past the nesting bound — and the REST parser
1085
+ * already converts the latter into a 400.
1086
+ *
1087
+ * `statusCode`, `code` and `details` are carried as fields because the server's
1088
+ * Hono error handler duck-types those off any thrown error: a decode path that
1089
+ * forgets to convert still answers 400 with the canonical envelope instead of a
1090
+ * 500 that says "An unexpected error occurred". `query-parser.ts` converts
1091
+ * explicitly all the same — that is the path the contract is stated on, and an
1092
+ * incidental 400 is not a contract.
1093
+ */
1094
+ var UnknownFilterOperatorError = class extends Error {
1095
+ /** The field the condition was written against. */
1096
+ field;
1097
+ /** The operator string as it arrived, verbatim. */
1098
+ operator;
1099
+ /** Every operator this dialect accepts, in canonical spelling. */
1100
+ validOperators = ALL_WHERE_FILTER_OPS;
1101
+ /** See the class docblock: read by the server's error handler. */
1102
+ statusCode = 400;
1103
+ code = "UNKNOWN_FILTER_OPERATOR";
1104
+ details;
1105
+ constructor(field, operator) {
1106
+ super(`Unknown filter operator '${operator}' on field '${field}'. Valid operators: ${VALID_OPERATOR_LIST}`);
1107
+ this.name = "UnknownFilterOperatorError";
1108
+ this.field = field;
1109
+ this.operator = operator;
1110
+ this.details = {
1111
+ field,
1112
+ operator,
1113
+ validOperators: ALL_WHERE_FILTER_OPS
1114
+ };
1115
+ }
1116
+ };
1117
+ /**
1118
+ * Two to three characters of ASCII punctuation and nothing else — the shape
1119
+ * every symbolic operator has (`==`, `>=`, `<>`, `~~`, `!!`, `>>`, `===`), and
1120
+ * one a column value effectively never has.
1121
+ *
1122
+ * Two characters minimum on purpose. A *single* punctuation character is a
1123
+ * perfectly ordinary value — `{ grade: ["-", "+"] }` is a two-item list, not a
1124
+ * condition — and the only single-character operator anyone actually mistypes
1125
+ * is `=`, which is named separately below. `<` and `>` need no special case:
1126
+ * they are real operators and resolve.
1127
+ */
1128
+ var SYMBOLIC_OPERATOR = /^[^\p{L}\p{N}\s]{2,3}$/u;
1129
+ /** Lowercase, strip everything that is not a letter or digit. */
1130
+ function normalizeOperatorName(op) {
1131
+ return op.toLowerCase().replace(/[^a-z0-9]/g, "");
1132
+ }
1133
+ /**
1134
+ * Every real operator name with its case and separators removed, so a
1135
+ * respelling of one — `arrayContains`, `not_in`, `NOT-LIKE`, `isNull` — is
1136
+ * recognised as an attempt at an operator rather than read as a value.
1137
+ *
1138
+ * These are rejected rather than accepted: admitting a second spelling of an
1139
+ * operator would leave two wire spellings of one thing, and the rejection
1140
+ * message names the one that works.
1141
+ */
1142
+ var RESPELLED_OPERATORS = new Set([...ALL_WHERE_FILTER_OPS, ...Object.keys(REST_TO_CANONICAL)].map(normalizeOperatorName));
1143
+ /**
1144
+ * Operator names *other* query dialects use, which this one does not have.
1145
+ *
1146
+ * This list is curated, and deliberately so. For a word-shaped string there is
1147
+ * no rule that separates "an operator the caller guessed" from "a value that
1148
+ * happens to be a word": `{ tags: ["a", "b"] }` has to keep meaning a two-item
1149
+ * `in` list, so the codec cannot simply refuse every unrecognised word in
1150
+ * position 0. The line is therefore drawn by name, and only around names whose
1151
+ * use as an operator is far more likely than their use as one of two sibling
1152
+ * values. `contains` is the motivating case — the first thing a developer
1153
+ * reaches for, and until now it compiled to `title IN ('contains', 'Hell')`.
1154
+ *
1155
+ * Genuinely ambiguous single words (`any`, `all`, `exists`, `search`, `not`)
1156
+ * are left off: as operators they are rare, and as enum values they are common.
1157
+ * Everywhere else the tie goes to *rejecting*, because a 400 naming the
1158
+ * supported set costs the caller one round trip, and the alternative — which is
1159
+ * what every name on this list used to produce — is a query that runs, returns
1160
+ * rows, and is wrong.
1161
+ */
1162
+ var NEAR_MISS_OPERATORS = /* @__PURE__ */ new Set([
1163
+ "contains",
1164
+ "notcontains",
1165
+ "doesnotcontain",
1166
+ "doesnotcontains",
1167
+ "includes",
1168
+ "notincludes",
1169
+ "startswith",
1170
+ "notstartswith",
1171
+ "beginswith",
1172
+ "startingwith",
1173
+ "endswith",
1174
+ "notendswith",
1175
+ "matches",
1176
+ "notmatches",
1177
+ "regex",
1178
+ "regexp",
1179
+ "between",
1180
+ "notbetween",
1181
+ "equals",
1182
+ "notequals",
1183
+ "equalto",
1184
+ "isequalto",
1185
+ "isnotequalto",
1186
+ "greaterthan",
1187
+ "greaterthanorequal",
1188
+ "greaterthanorequalto",
1189
+ "lessthan",
1190
+ "lessthanorequal",
1191
+ "lessthanorequalto",
1192
+ "isempty",
1193
+ "isnotempty",
1194
+ "oneof",
1195
+ "noneof",
1196
+ "anyof",
1197
+ "allof",
1198
+ "null",
1199
+ "isnullorempty"
1200
+ ]);
1201
+ /**
1202
+ * Was this string *meant* as an operator?
1203
+ *
1204
+ * Only consulted after {@link toCanonicalOp} has already failed to resolve it,
1205
+ * so a `true` here is always a rejection.
1206
+ */
1207
+ function isOperatorShaped(op) {
1208
+ if (op === "=") return true;
1209
+ if (SYMBOLIC_OPERATOR.test(op)) return true;
1210
+ const normalized = normalizeOperatorName(op);
1211
+ if (!normalized) return false;
1212
+ return RESPELLED_OPERATORS.has(normalized) || NEAR_MISS_OPERATORS.has(normalized);
1213
+ }
1214
+ /**
1215
+ * Read a `[op, value]` tuple, if that is what this is.
1216
+ *
1217
+ * Three outcomes, and the middle one is the defect this function exists for:
1218
+ *
1219
+ * - the operator resolves (canonical *or* REST spelling) → the canonical tuple;
1220
+ * - the operator does not resolve but was plainly meant as one → throw;
1221
+ * - it does not look like an operator at all → `undefined`, and the caller
1222
+ * falls back to reading the array as a list of values.
1223
+ *
1224
+ * The old test was `toCanonicalOp(raw[0]) === raw[0]`, i.e. canonical spelling
1225
+ * only, with *everything else* — including every REST short-code — dropping
1226
+ * through to `["in", raw]`. So the operator string itself became a value in a
1227
+ * membership test: `["!!", "Hello"]` compiled to `title IN ('!!','Hello')`,
1228
+ * which matches, and the caller got back rows their filter was written to
1229
+ * exclude. `["eq", "active"]` had the same shape of failure.
1230
+ */
1231
+ function readTuple(field, raw) {
1232
+ if (!Array.isArray(raw) || raw.length !== 2) return void 0;
1233
+ const [op, value] = raw;
1234
+ if (typeof op !== "string") return void 0;
1235
+ const canonical = toCanonicalOp(op);
1236
+ if (canonical) return [canonical, value];
1237
+ if (op.includes(".")) return void 0;
1238
+ if (isOperatorShaped(op)) throw new UnknownFilterOperatorError(field, op);
1239
+ }
1240
+ /**
1241
+ * The query-parameter names a REST listing reads as something other than a
1242
+ * field filter: pagination and whether to count, sorting, includes,
1243
+ * projection, search, the logical groups, the JSON `where` dialect, an
1244
+ * aggregate's `select`/`groupBy`, and the soft-delete switches.
1245
+ *
1246
+ * One list for both ends. The server skips these names when it collects
1247
+ * `?<field>=<op>.<value>` filters, and the SDK sends a filter on a column with
1248
+ * one of these names inside `?where=` instead — a collection is free to have a
1249
+ * `page`, `select` or `hard` column, and a filter on it sent as its own
1250
+ * parameter was read as the parameter and never applied.
1251
+ */
1252
+ var RESERVED_QUERY_KEYS = /* @__PURE__ */ new Set([
1253
+ "limit",
1254
+ "offset",
1255
+ "page",
1256
+ "after",
1257
+ "orderBy",
1258
+ "include",
1259
+ "fields",
1260
+ "distinct",
1261
+ "count",
1262
+ "searchString",
1263
+ "searchExplain",
1264
+ "vector_search",
1265
+ "vector",
1266
+ "vector_distance",
1267
+ "vector_threshold",
1268
+ "or",
1269
+ "and",
1270
+ "not",
1271
+ "where",
1272
+ "select",
1273
+ "groupBy",
1274
+ "deleted",
1275
+ "hard"
1276
+ ]);
1277
+ /**
1278
+ * Encode the `<op>.<value>` half of a wire condition.
1279
+ *
1280
+ * This is the single leaf encoder. Both wire positions that carry a condition
1281
+ * — a top-level query parameter (`?status=eq.active`) and a leaf inside an
1282
+ * `and(...)`/`or(...)` group (`or(status.eq.active,…)`) — go through it, so a
1283
+ * rule expressed here holds in both. The group serializer used to carry its
1284
+ * own copy, and the copy had drifted on every rule that matters: `null` went
1285
+ * out as the four-character string, the empty list as `()`, and an operator
1286
+ * this dialect does not have was silently rewritten to `eq` — a filter that
1287
+ * ran, returned rows, and answered a different question than the one asked.
1288
+ *
1289
+ * `escapeScalar` is the one thing the two positions legitimately disagree
1290
+ * about. A scalar in a query parameter owns the whole value and needs no
1291
+ * escaping; a scalar inside a group sits between the same commas a list item
1292
+ * does, so a comma in it would end the condition early.
1293
+ */
1294
+ function serializeOperatorAndValue(op, value, { escapeScalar, where }) {
1295
+ if (typeof op !== "string") throw new TypeError(`${where}: operator must be a string, got ${typeof op}`);
1296
+ const restOp = CANONICAL_OP_LOOKUP.get(op);
1297
+ if (!restOp) throw new TypeError(`${where}: unknown operator "${op}". Valid operators: ${Object.keys(CANONICAL_TO_REST).join(", ")}`);
1298
+ if (value === null && (op === "==" || op === "!=")) return op === "==" ? "isnull.null" : "notnull.null";
1299
+ if (NULL_OPS.has(op)) return `${restOp}.null`;
1300
+ if (Array.isArray(value)) {
1301
+ if (value.length === 0) return `${restOp}.(${EMPTY_LIST_TOKEN})`;
1302
+ return `${restOp}.(${value.map((v) => escapeWireValue(stringifyValue(v))).join(",")})`;
1303
+ }
1304
+ const scalar = stringifyValue(value);
1305
+ return `${restOp}.${escapeScalar || !LIST_OPS.has(op) && scalarNeedsEscape(scalar) ? escapeWireValue(scalar) : scalar}`;
1306
+ }
1307
+ /**
1308
+ * Serialize a single canonical condition tuple to a PostgREST dot-string.
1309
+ *
1310
+ * Throws `TypeError` if the input is not a valid `[WhereFilterOp, unknown]` tuple.
1311
+ *
1312
+ * @example
1313
+ * serializeTuple(["==", "active"]) // "eq.active"
1314
+ * serializeTuple(["in", ["admin","editor"]]) // "in.(admin,editor)"
1315
+ * serializeTuple([">=", 18]) // "gte.18"
1316
+ */
1317
+ function serializeTuple(tuple) {
1318
+ if (!Array.isArray(tuple) || tuple.length !== 2) throw new TypeError(`serializeTuple: expected a [WhereFilterOp, value] tuple, got ${JSON.stringify(tuple)}`);
1319
+ const [op, value] = tuple;
1320
+ return serializeOperatorAndValue(op, value, {
1321
+ escapeScalar: false,
1322
+ where: "serializeTuple"
1323
+ });
1324
+ }
1325
+ /**
1326
+ * Convert `FilterValues` (or `WireFilterValues`) to a PostgREST-style
1327
+ * querystring record.
1328
+ *
1329
+ * - Canonical `[WhereFilterOp, value]` tuples are serialized strictly.
1330
+ * - Pre-serialized PostgREST strings (e.g. `"eq.published"`) are passed through.
1331
+ * - Single conditions produce a string value.
1332
+ * - Multiple conditions on the same field produce a string array (repeated params).
1333
+ *
1334
+ * @example
1335
+ * serializeFilter({ status: ["==", "active"] })
1336
+ * // → { status: "eq.active" }
1337
+ *
1338
+ * serializeFilter({ age: [[">=", 18], ["<", 65]] })
1339
+ * // → { age: ["gte.18", "lt.65"] }
1340
+ *
1341
+ * // Pre-serialized strings pass through unchanged:
1342
+ * serializeFilter({ status: "eq.published" })
1343
+ * // → { status: "eq.published" }
1344
+ */
1345
+ function serializeFilter(filter) {
1346
+ const result = {};
1347
+ for (const [field, condition] of Object.entries(filter)) {
1348
+ if (condition === void 0) continue;
1349
+ if (typeof condition === "string") {
1350
+ result[field] = condition;
1351
+ continue;
1352
+ }
1353
+ if (Array.isArray(condition) && condition.length > 0 && Array.isArray(condition[0])) result[field] = condition.map(serializeTuple);
1354
+ else result[field] = serializeTuple(condition);
1355
+ }
1356
+ return result;
1357
+ }
1358
+ /**
1359
+ * The spellings a null-testing operator's operand may take.
1360
+ *
1361
+ * The serializer writes `isnull.null`; a hand-written `isnull.true` means the
1362
+ * same thing and has always been accepted. Anything else after the operator is
1363
+ * not an operand it has — `notnull.reason` is a *value* — see
1364
+ * {@link deserializeSingle}.
1365
+ */
1366
+ var NULL_OPERANDS = /* @__PURE__ */ new Set([
1367
+ "null",
1368
+ "true",
1369
+ "false",
1370
+ ""
1371
+ ]);
1372
+ /**
1373
+ * Parse a single PostgREST dot-string into a `[WhereFilterOp, unknown]` tuple.
1374
+ *
1375
+ * All values are returned as strings — the wire format carries no type
1376
+ * metadata, so coercion is the data driver's responsibility.
1377
+ *
1378
+ * ## When a leading segment is an operator, and when it is part of the value
1379
+ *
1380
+ * `?status=in.progress` and `?status=in.(a,b)` differ by one character and mean
1381
+ * entirely different things, and the reading here decides which. The rule, in
1382
+ * full:
1383
+ *
1384
+ * > A dot-string is read as `operator.operand` **only** when its first segment
1385
+ * > names a known REST operator **and** what follows is a well-formed operand
1386
+ * > *for that operator's arity*. Otherwise the whole string is the value.
1387
+ *
1388
+ * Arity, per operator family:
1389
+ *
1390
+ * - **List** operators (`in`, `nin`, `csa` — `LIST_OPS`) take a parenthesised
1391
+ * list and nothing else. `in.(draft,review)` is the operator; `in.progress`
1392
+ * is the *value* `"in.progress"`, because there is no list there and so no
1393
+ * `in` filter that could have been written. That case used to compile to
1394
+ * `status IN ('progress')` — a filter the caller never wrote, quietly
1395
+ * matching the wrong rows and, on a status field, hiding every row they were
1396
+ * looking for.
1397
+ * - **Null** operators (`isnull`, `notnull` — {@link NULL_OPS}) take no
1398
+ * operand: only {@link NULL_OPERANDS}. `notnull.reason` is the value
1399
+ * `"notnull.reason"`, not "reason is not null".
1400
+ * - **Everything else** takes one scalar, and any remainder is one — including
1401
+ * the empty string, so `eq.` really is "equals the empty string". A
1402
+ * parenthesised remainder is a list for these too (`eq.(a,b)` is array
1403
+ * equality); a scalar that is itself parenthesised arrives escaped,
1404
+ * `eq.\(none\)`, and is decoded here — see {@link scalarNeedsEscape}.
1405
+ *
1406
+ * ### The one ambiguity that remains, and how to write past it
1407
+ *
1408
+ * A scalar operator's operand is unconstrained, so `?status=like.that` is a
1409
+ * `LIKE 'that'` and no rule at this layer can tell it from the literal value
1410
+ * `"like.that"` — both are well-formed encodings, and picking either by guess
1411
+ * would break the other. Two spellings say "value" unambiguously, and both
1412
+ * round-trip:
1413
+ *
1414
+ * - `?status=eq.like.that` — name the operator. The *first* segment is consumed
1415
+ * as the operator and everything after it is the value, dots and all. This is
1416
+ * what `serializeFilter` emits, which is why the SDK never meets the
1417
+ * ambiguity at all.
1418
+ * - `?where={"status":["==","like.that"]}` — the JSON dialect's tuple form.
1419
+ *
1420
+ * Values that merely *contain* dots (`user@host.com`, `1.2.3`) were never
1421
+ * ambiguous: their first segment names no operator to begin with.
1422
+ */
1423
+ function deserializeSingle(raw) {
1424
+ const dotIndex = raw.indexOf(".");
1425
+ if (dotIndex === -1) return ["==", raw];
1426
+ const prefix = raw.substring(0, dotIndex);
1427
+ const rest = raw.substring(dotIndex + 1);
1428
+ const canonicalOp = REST_OP_LOOKUP.get(prefix);
1429
+ if (!canonicalOp) return ["==", raw];
1430
+ if (NULL_OPS.has(canonicalOp)) {
1431
+ if (!NULL_OPERANDS.has(rest)) return ["==", raw];
1432
+ return [canonicalOp, null];
1433
+ }
1434
+ if (rest.startsWith("(") && rest.endsWith(")")) {
1435
+ const inner = rest.slice(1, -1);
1436
+ return [canonicalOp, inner === EMPTY_LIST_TOKEN ? [] : splitListItems(inner)];
1437
+ }
1438
+ if (LIST_OPS.has(canonicalOp)) return ["==", raw];
1439
+ return [canonicalOp, decodeEscapedScalar(rest) ?? rest];
1440
+ }
1441
+ /**
1442
+ * Convert a PostgREST-style querystring record to `FilterValues`.
1443
+ *
1444
+ * - String values are parsed as single conditions.
1445
+ * - String arrays (repeated query params) become multiple conditions on the same field.
1446
+ *
1447
+ * @example
1448
+ * deserializeFilter({ status: "eq.active" })
1449
+ * // → { status: ["==", "active"] }
1450
+ *
1451
+ * deserializeFilter({ age: ["gte.18", "lt.65"] })
1452
+ * // → { age: [[">=", "18"], ["<", "65"]] }
1453
+ *
1454
+ * @throws {UnknownFilterOperatorError} when a condition names an operator this
1455
+ * dialect does not have. See that class for why a rejection here is a throw.
1456
+ */
1457
+ function deserializeFilter(query) {
1458
+ const result = {};
1459
+ for (const [field, raw] of Object.entries(query)) {
1460
+ if (raw === void 0) continue;
1461
+ const tuple = readTuple(field, raw);
1462
+ if (tuple) {
1463
+ result[field] = tuple;
1464
+ continue;
1465
+ }
1466
+ if (Array.isArray(raw)) {
1467
+ if (raw.length === 0) continue;
1468
+ if (Array.isArray(raw[0])) {
1469
+ const tuples = raw.map((item) => readTuple(field, item));
1470
+ if (tuples.every((t) => t !== void 0)) {
1471
+ result[field] = tuples;
1472
+ continue;
1473
+ }
1474
+ }
1475
+ if (raw.length === 1) result[field] = typeof raw[0] === "string" ? deserializeSingle(raw[0]) : ["==", raw[0]];
1476
+ else if (typeof raw[0] === "string" && raw[0].includes(".")) result[field] = raw.map((r) => typeof r === "string" ? deserializeSingle(r) : ["==", r]);
1477
+ else result[field] = ["in", raw];
1478
+ } else if (typeof raw === "string") result[field] = deserializeSingle(raw);
1479
+ else result[field] = ["==", raw];
1480
+ }
1481
+ return result;
1482
+ }
1483
+ /**
1484
+ * Serialize a `LogicalCondition` or `FilterCondition` to its wire-format string.
1485
+ *
1486
+ * Leaf encoding is {@link serializeOperatorAndValue}, the same function
1487
+ * `serializeTuple` uses, so `null`, the empty list and an unknown operator
1488
+ * behave identically inside a group and in a query parameter.
1489
+ *
1490
+ * @throws {TypeError} when a leaf names an operator this dialect does not have.
1491
+ * It used to fall back to `eq`, which turned `age >= 18` into `age = 18` with
1492
+ * no diagnostic anywhere.
1493
+ *
1494
+ * @example
1495
+ * serializeLogicalCondition({ column: "status", operator: "==", value: "active" })
1496
+ * // → "status.eq.active"
1497
+ *
1498
+ * serializeLogicalCondition({ column: "deleted_at", operator: "==", value: null })
1499
+ * // → "deleted_at.isnull.null"
1500
+ *
1501
+ * serializeLogicalCondition({ type: "or", conditions: [...] })
1502
+ * // → "or(status.eq.active,status.eq.pending)"
1503
+ */
1504
+ function serializeLogicalCondition(cond) {
1505
+ if ("type" in cond) {
1506
+ const inner = (cond.conditions ?? []).map(serializeLogicalCondition).join(",");
1507
+ return `${cond.type}(${inner})`;
1508
+ }
1509
+ return `${escapeWireValue(cond.column)}.${serializeOperatorAndValue(cond.operator, cond.value, {
1510
+ escapeScalar: true,
1511
+ where: "serializeLogicalCondition"
1512
+ })}`;
1513
+ }
1514
+ /**
1515
+ * The positions of the dots that can separate a leaf's segments.
1516
+ *
1517
+ * Dots inside a list value (`in.(1.5,2.5)`) are not separators. The value
1518
+ * always follows the operator, so the search only needs the region before the
1519
+ * first unescaped paren.
1520
+ */
1521
+ function leafSeparators(str) {
1522
+ let limit = str.length;
1523
+ for (let i = 0; i < str.length; i++) {
1524
+ if (str[i] === "\\") {
1525
+ i++;
1526
+ continue;
1527
+ }
1528
+ if (str[i] === "(") {
1529
+ limit = i;
1530
+ break;
1531
+ }
1532
+ }
1533
+ const dots = [];
1534
+ for (let i = 0; i < limit; i++) {
1535
+ if (str[i] === "\\") {
1536
+ i++;
1537
+ continue;
1538
+ }
1539
+ if (str[i] === ".") dots.push(i);
1540
+ }
1541
+ return dots;
1542
+ }
1543
+ /**
1544
+ * Split a leaf condition into `column`, operator token and value.
1545
+ *
1546
+ * The naive reading — column is everything before the first dot, operator is
1547
+ * everything up to the second — cannot express a relation path. A filter on
1548
+ * `author.name` serializes to `author.name.eq.bob` and came back as the column
1549
+ * `author` with the operator `name`, which resolves to nothing, so the
1550
+ * fallback made it `author == "eq.bob"`: a condition that runs and matches
1551
+ * nothing, on a column the caller never named.
1552
+ *
1553
+ * So the operator is found rather than assumed: it is the first dot-separated
1554
+ * segment after the column that resolves to a real operator. Everything before
1555
+ * it is the column, everything after is the value. `version.eq.1.2.3` reads as
1556
+ * `version == "1.2.3"` because the scan stops at the first match — the `eq` at
1557
+ * offset 1, not a later segment — and `metadata->>x.eq.5` never had dots in the
1558
+ * column to begin with.
1559
+ *
1560
+ * Returns `undefined` when no segment resolves — `status.active`, an equality
1561
+ * written without an operator, which the caller handles.
1562
+ */
1563
+ function splitLeafCondition(str) {
1564
+ const dots = leafSeparators(str);
1565
+ for (let i = 1; i < dots.length; i++) {
1566
+ const operator = toCanonicalOp(str.substring(dots[i - 1] + 1, dots[i]));
1567
+ if (!operator) continue;
1568
+ return {
1569
+ column: unescapeWireValue(str.substring(0, dots[i - 1])),
1570
+ operator,
1571
+ value: str.substring(dots[i] + 1)
1572
+ };
1573
+ }
1574
+ }
1575
+ function deserializeLogicalCondition(str, nesting = 0) {
1576
+ if (nesting > 32) throw new Error(`Filter groups nest more than 32 levels deep. Flatten the condition — \`or(a,or(b,c))\` is \`or(a,b,c)\`.`);
1577
+ const logicalMatch = str.match(/^(and|or|not)\((.+)\)$/);
1578
+ if (logicalMatch) {
1579
+ const type = logicalMatch[1];
1580
+ const innerStr = logicalMatch[2];
1581
+ return {
1582
+ type,
1583
+ conditions: splitGroupItems(innerStr).map((part) => deserializeLogicalCondition(part, nesting + 1))
1584
+ };
1585
+ }
1586
+ const leaf = splitLeafCondition(str);
1587
+ if (!leaf) {
1588
+ const firstDot = str.indexOf(".");
1589
+ if (firstDot === -1) return {
1590
+ column: unescapeWireValue(str),
1591
+ operator: "==",
1592
+ value: true
1593
+ };
1594
+ const dots = leafSeparators(str);
1595
+ for (let i = 1; i < dots.length; i++) {
1596
+ const segment = str.substring(dots[i - 1] + 1, dots[i]);
1597
+ if (isOperatorShaped(segment)) throw new UnknownFilterOperatorError(unescapeWireValue(str.substring(0, dots[i - 1])), segment);
1598
+ }
1599
+ return {
1600
+ column: unescapeWireValue(str.substring(0, firstDot)),
1601
+ operator: "==",
1602
+ value: unescapeWireValue(str.substring(firstDot + 1))
1603
+ };
1604
+ }
1605
+ const { column, operator, value: valueStr } = leaf;
1606
+ if (NULL_OPS.has(operator)) return {
1607
+ column,
1608
+ operator,
1609
+ value: null
1610
+ };
1611
+ if (valueStr.startsWith("(") && valueStr.endsWith(")")) {
1612
+ const inner = valueStr.slice(1, -1);
1613
+ return {
1614
+ column,
1615
+ operator,
1616
+ value: inner === EMPTY_LIST_TOKEN ? [] : splitListItems(inner)
1617
+ };
1618
+ }
1619
+ return {
1620
+ column,
1621
+ operator,
1622
+ value: unescapeWireValue(valueStr)
1623
+ };
1624
+ }
1625
+ //#endregion
1626
+ export { isFieldOperation as C, Vector as D, GeoPoint as E, hasFieldOperation as S, EntityRelation as T, normalizeOrderBy as _, serializeFilter as a, BATCH_REF_KEY as b, collectAllPages as c, IncludeSpecError as d, deserializeInclude as f, OrderBySpecError as g, serializeInclude as h, deserializeLogicalCondition as i, paginateFind as l, normalizeInclude as m, UnknownFilterOperatorError as n, serializeLogicalCondition as o, mergeIncludeSpecs as p, deserializeFilter as r, PAGE_WITH_OFFSET_MESSAGE as s, RESERVED_QUERY_KEYS as t, resolveFindWindow as u, serializeOrderBy as v, EntityReference as w, FIELD_OPERATORS as x, withRelationReferencesOnly as y };
1627
+
1628
+ //# sourceMappingURL=filter-dialect-O1HK6Nvr.js.map