@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 @@
1
+ {"version":3,"file":"filter-dialect-O1HK6Nvr.js","names":[],"sources":["../../types/src/types/entities.ts","../../types/src/controllers/data.ts","../../common/src/util/entities.ts","../../common/src/data/sort-dialect.ts","../../common/src/data/include-spec.ts","../../common/src/data/paginate.ts","../../common/src/data/filter-dialect.ts"],"sourcesContent":["import type { SearchMatch } from \"./search\";\n/**\n * New or existing status\n * @group Models\n */\nexport type EntityStatus = \"new\" | \"existing\" | \"copy\";\n\n/**\n * Representation of a entity fetched from the driver\n * @group Models\n */\nexport interface Entity<M extends Record<string, unknown> = Record<string, unknown>> {\n\n /**\n * ID of the entity\n */\n id: string | number;\n\n /**\n * A string representing the path of the referenced document (relative\n * to the root of the database).\n */\n path: string;\n\n /**\n * Current values\n */\n values: EntityValues<M>;\n\n /**\n * Why this entity is in a search result: which declared fields matched, and\n * the text around each hit.\n *\n * Present only on rows returned by a search that asked for it. A sibling of\n * `values` rather than a key inside it, because it describes the *query*,\n * not the record — nothing in the collection declares it, no form edits it,\n * and a record fetched by id never has one.\n */\n searchMatches?: SearchMatch[];\n\n /**\n * The version of the row this entity was read at: the `ETag` the read\n * reported, for a `findById`, a `PATCH` answer or a live row frame. A\n * form saves with it as `If-Match`, so a write made against a version\n * someone has since replaced is refused instead of reverting their change.\n *\n * Absent for a row from a list, from a cache, or from a server or data\n * source that does not report versions — a save then is unconditional,\n * as it always was.\n */\n etag?: string;\n\n /**\n * Which driver this entity belongs to (e.g., 'postgres', 'firestore').\n * If not specified, the default driver is assumed.\n */\n driver?: string;\n\n /**\n * Which database within the driver (e.g., for Firestore multi-database).\n * If not specified, the default database of the driver is used.\n */\n databaseId?: string;\n}\n\n/**\n * This type represents a record of key value pairs as described in an\n * entity collection.\n * @group Models\n */\nexport type EntityValues<M extends Record<string, unknown>> = M;\n\n/**\n * Props for creating a EntityReference\n */\nexport interface EntityReferenceProps {\n /** ID of the entity */\n id: string;\n /** Path of the collection (relative to the root of the database) */\n path: string;\n /** Which driver (e.g., 'postgres', 'firestore'). Defaults to \"(default)\" */\n driver?: string;\n /** Which database within the driver. Defaults to \"(default)\" */\n databaseId?: string;\n}\n\n/**\n * Class used to create a reference to a entity in a different path.\n *\n * @example\n * // Simple reference (most common case - single driver, single db)\n * new EntityReference({ id: \"123\", path: \"users\" })\n *\n * // Reference to a different driver (e.g., Firestore)\n * new EntityReference({ id: \"123\", path: \"analytics\", driver: \"firestore\" })\n *\n * // Reference to a specific database within a driver\n * new EntityReference({ id: \"123\", path: \"orders\", driver: \"postgres\", databaseId: \"orders_db\" })\n */\nexport class EntityReference {\n\n readonly __type = \"reference\";\n /**\n * ID of the entity\n */\n readonly id: string;\n /**\n * A string representing the path of the referenced document (relative\n * to the root of the database).\n */\n readonly path: string;\n\n /**\n * Which driver (e.g., 'postgres', 'firestore').\n * Defaults to \"(default)\" if not specified.\n */\n readonly driver?: string;\n\n /**\n * Which database within the driver.\n * Defaults to \"(default)\" if not specified.\n */\n readonly databaseId?: string;\n\n /**\n * Create a reference to a entity.\n *\n * @example\n * // Simple reference (most common case)\n * new EntityReference({ id: \"123\", path: \"users\" })\n *\n * // With driver\n * new EntityReference({ id: \"123\", path: \"analytics\", driver: \"firestore\" })\n */\n constructor(props: EntityReferenceProps) {\n this.id = props.id;\n this.path = props.path;\n this.driver = props.driver;\n this.databaseId = props.databaseId;\n }\n\n get pathWithId() {\n return `${this.path}/${this.id}`;\n }\n\n /**\n * Get the full path including driver and database prefixes if specified.\n * For the common case (single driver, single db), this just returns pathWithId.\n */\n get fullPath() {\n const parts: string[] = [];\n\n // Add driver prefix if not default\n if (this.driver && this.driver !== \"(default)\") {\n parts.push(this.driver);\n }\n\n // Add database prefix if specified\n if (this.databaseId && this.databaseId !== \"(default)\") {\n parts.push(this.databaseId);\n }\n\n if (parts.length > 0) {\n return `${parts.join(\":\")}:::${this.path}/${this.id}`;\n }\n return this.pathWithId;\n }\n\n isEntityReference() {\n return true;\n }\n}\n\n/**\n * Class used to create a reference to a entity in a different path\n */\nexport class EntityRelation {\n\n readonly __type = \"relation\";\n /**\n * ID of the entity\n */\n readonly id: string | number;\n /**\n * A string representing the path of the referenced document (relative\n * to the root of the database).\n */\n readonly path: string;\n\n /**\n * Pre-fetched data payload to eliminate N+1 queries.\n * When present, clients can use this directly instead of fetching.\n */\n readonly data?: Record<string, unknown>;\n\n constructor(id: string | number, path: string, data?: Record<string, unknown>) {\n this.id = id;\n this.path = path;\n this.data = data;\n }\n\n get pathWithId() {\n return `${this.path}/${this.id}`;\n }\n\n isEntityReference() {\n return false;\n }\n\n isEntityRelation() {\n return true;\n }\n}\n\nexport class GeoPoint {\n\n /**\n * The latitude of this GeoPoint instance.\n */\n readonly latitude: number;\n /**\n * The longitude of this GeoPoint instance.\n */\n readonly longitude: number;\n\n constructor(latitude: number, longitude: number) {\n this.latitude = latitude;\n this.longitude = longitude;\n }\n}\n\nexport class Vector {\n readonly value: number[];\n\n constructor(value: number[]) {\n this.value = value;\n }\n}\n","import type { VectorSearchParams } from \"./data_driver\";\nimport type { ComputedSortField, SearchMatch } from \"../types/search\";\nimport { Entity, EntityValues } from \"../types/entities\";\nimport { WhereFilterOp, FieldPath, NonColumnFieldPath, FilterValues, NullsPlacement, OrderBySpec, OrderByTuple, RelationAggregateSort } from \"../types/filter-operators\";\n\n/**\n * The element type of an array column, and the column's own type otherwise.\n *\n * A generated SDK emits an `array` property as `Array<X>` and a to-many\n * relation as `Array<TargetRow>`, so this is what `array-contains` compares\n * against on either.\n */\nexport type ElementOf<T> = T extends readonly (infer E)[] ? E : T;\n\n/**\n * The `id` of a row-shaped element, and `never` for anything else.\n *\n * A to-many relation is emitted as `Array<TargetRow>`, but the filter compilers\n * compare a relation by **id** — `buildRelationFilterPredicate` in\n * `@rebasepro/server-postgres` unwraps a relation value down to its id — so\n * `where(\"tags\", \"array-contains\", tagId)` is the call that works, and the\n * element type alone would refuse it.\n */\nexport type IdOf<E> = E extends { id: infer I } ? I : never;\n\n/**\n * One member of an array column: its element, or — when the element is a row —\n * that row's id, which is what a relation filter is actually compared against.\n */\nexport type WhereElementOf<T> = ElementOf<T> | IdOf<ElementOf<T>>;\n\n/**\n * The value a given operator takes on a column of type `T`.\n *\n * `WhereValue<T>` was one value type for all sixteen operators, which made\n * `array-contains` uncallable from a generated SDK — it is the one operator\n * whose value is an *element* of the column rather than the column's own type,\n * so on `tags: string[]` it wanted a `string[]` and the documented\n * `.where(\"tags\", \"array-contains\", \"featured\")` was a compile error. The\n * spelling that did compile, `[\"featured\"]`, builds `@> ARRAY[$1]` with the\n * whole array bound as the single element and matches nothing: the correct\n * query rejected, the accepted query silently wrong.\n *\n * The branches mirror `buildSingleFilterCondition` in `@rebasepro/server-postgres`:\n *\n * - `array-contains` → one element of the column (or a related row's id).\n * - `in` / `not-in` / `array-contains-any` → a list of elements; a bare element\n * is read as the one-element list, and `null` is a null check.\n * - `like` / `ilike` / `not-like` / `not-ilike` → a SQL pattern, always a\n * string. It matches a text column, or an enum by its label; on a number,\n * a date or any other column the Postgres driver refuses it with a 400\n * (`INVALID_FILTER_OPERATOR`).\n * - `is-null` / `is-not-null` → nothing; the value is ignored everywhere.\n * - everything else → the column's own type, or `null` for a null comparison.\n *\n * Distributes over `Op`, so a caller holding an unnarrowed `WhereFilterOp`\n * (a dynamic filter UI, say) gets the union of every branch and stays as\n * permissive as it was.\n */\nexport type WhereValueFor<Op extends WhereFilterOp, T> =\n Op extends \"array-contains\"\n ? WhereElementOf<T>\n : Op extends \"in\" | \"not-in\" | \"array-contains-any\"\n ? readonly WhereElementOf<T>[] | WhereElementOf<T> | null\n : Op extends \"like\" | \"ilike\" | \"not-like\" | \"not-ilike\"\n ? string\n : Op extends \"is-null\" | \"is-not-null\"\n ? null | undefined\n : T | null;\n\n/**\n * One `[operator, value]` condition on a column of type `T`, with the value\n * typed for the operator exactly as the fluent `where(column, op, value)` types\n * it — a union with one tuple per operator, so `[\"array-contains\", \"featured\"]`\n * on a `string[]` column compiles and `[\"array-contains\", [\"featured\"]]` (which\n * builds `@> ARRAY[$1]` and matches nothing) does not.\n */\nexport type WhereConditionFor<T> = { [Op in WhereFilterOp]: [Op, WhereValueFor<Op, T>] }[WhereFilterOp];\n\n/**\n * The object-form `where` for row type `M`: each column takes conditions typed\n * against that column, as the fluent form does.\n *\n * `FilterValues` typed the key and left the value `unknown`, so the very\n * shapes the fluent form was fixed to refuse — a wrong value type, an array\n * where `array-contains` wants one element — compiled in `find({ where })`,\n * the form `listen`, `observe`, `count` and `iterate` all take.\n *\n * A path that is not a column (`author.name`, `metadata->>tier`) has no type to\n * check against and stays `unknown`, as in {@link FieldPath}. An untyped `M`\n * (`Record<string, unknown>`, every key) collapses to plain `FilterValues`, so\n * an untyped client is exactly as permissive as before.\n */\nexport type WhereFor<M extends Record<string, unknown>> =\n string extends keyof M\n ? FilterValues<FieldPath<M>>\n : { [K in Extract<keyof M, string>]?: WhereConditionFor<M[K]> | WhereConditionFor<M[K]>[] }\n & FilterValues<NonColumnFieldPath>;\n\n/**\n * A group of conditions combined with `and`, `or`, or negated with `not`.\n *\n * ## `not`\n *\n * `not` negates the **conjunction** of its `conditions`: `not(a)` is `NOT a`,\n * and `not(a, b)` is `NOT (a AND b)`. One rule, stated here and applied\n * identically by the wire codec (`or(...)`/`and(...)`/`not(...)` in\n * `@rebasepro/common`), the REST `?not=` parameter and every driver compiler,\n * so a negation means the same thing whichever end writes it.\n *\n * Negation is not expressible by inverting the operators inside the group: SQL\n * three-valued logic makes `NOT (a AND b)` and `(NOT a) OR (NOT b)` differ the\n * moment a NULL is involved, and only one of them is what the caller wrote. It\n * compiles to a real `NOT (...)`.\n */\nexport interface LogicalCondition {\n type: \"and\" | \"or\" | \"not\";\n conditions: (FilterCondition | LogicalCondition)[];\n}\n\nexport interface FilterCondition {\n column: string;\n operator: WhereFilterOp;\n value: unknown;\n}\n\n/**\n * One {@link FilterCondition} on row type `M`: the column is one of `M`'s and\n * the value is typed for the operator against that column, exactly as the\n * fluent `where(column, op, value)` types it. A path that is not a column\n * (`author.name`, `metadata->>tier`) has nothing to check against and stays\n * `unknown`, as in {@link WhereFor}.\n *\n * An untyped `M` (`Record<string, unknown>`) collapses to plain\n * `FilterCondition`, so an untyped client is exactly as permissive as before.\n */\nexport type FilterConditionFor<M extends Record<string, unknown>> =\n | ColumnConditions<M>[keyof ColumnConditions<M>]\n | { column: NonColumnFieldPath; operator: WhereFilterOp; value: unknown };\n\n/**\n * Per column, the union of its typed conditions; an index-signature key (an\n * untyped row) maps to plain {@link FilterCondition}.\n *\n * A key-remapped mapped type rather than a conditional over `keyof M`, for the\n * reason {@link NumericKeyOf} gives: tsc measures the conditional form as\n * invariant in `M`, and a typed `SDKCollectionClient<Post>` stopped widening to\n * the wide `SDKCollectionClient`.\n */\ntype ColumnConditions<M extends Record<string, unknown>> = {\n [K in keyof M as K extends string ? K : never]: string extends K\n ? FilterCondition\n : { [Op in WhereFilterOp]: { column: K; operator: Op; value: WhereValueFor<Op, M[K]> } }[WhereFilterOp]\n};\n\n/**\n * A {@link LogicalCondition} whose every condition, at any depth, is a\n * {@link FilterConditionFor} row type `M`.\n *\n * `cond()` cannot know the row it will be used on, so it returns its column,\n * operator and value exactly as written; this is where they are checked.\n * `FindParams.logical` and the builders' `.where(group)` take it, so\n * `posts.where(or(cond(\"titel\", \"==\", 1)))` is a compile error on a typed\n * client rather than the server's 400.\n */\nexport interface LogicalConditionFor<M extends Record<string, unknown>> {\n type: \"and\" | \"or\" | \"not\";\n conditions: (FilterConditionFor<M> | LogicalConditionFor<M>)[];\n}\n\n/**\n * How one relation is loaded by {@link FindParams.include}.\n *\n * `true` loads the relation whole. The object form narrows it — the same four\n * knobs a top-level query has, applied to the rows *inside* one relation — and\n * `include` nests, so a query can ask for \"each post's five newest published\n * comments, each with its author\" in one request.\n *\n * ```ts\n * include: {\n * comments: {\n * limit: 5,\n * where: { published: [\"==\", true] },\n * orderBy: [\"created_at\", \"desc\"],\n * include: { author: true }\n * }\n * }\n * ```\n *\n * Nesting is bounded at {@link MAX_INCLUDE_DEPTH} hops. Each hop is another\n * batched query, and the bound is what stops one request from walking a\n * self-referencing relation forever.\n *\n * @group Data\n */\nexport interface IncludeOptions {\n /** Rows to load per parent row. Applied per parent, not across the page. */\n limit?: number;\n /** Filter the related rows, in the same dialect as {@link FindParams.where}. */\n where?: FilterValues<string>;\n /** An `and`/`or`/`not` group over the related rows. */\n logical?: LogicalCondition;\n /**\n * Sort the related rows — the tuple form, or the `field:direction[:nulls]`\n * shorthand the REST `?orderBy=` parameter uses.\n *\n * The string is accepted because this whole object travels over a query\n * string, where a tuple is three characters of JSON heavier for no gain.\n */\n orderBy?: OrderBySpec<string> | string;\n /** Columns of the *related* row to return. `id` is always included. */\n fields?: string[];\n /** Relations of the related row to load in turn. */\n include?: IncludeSpec;\n /**\n * Also serve how many related rows there are in all, per parent, under\n * the row's `_count` ({@link INCLUDE_COUNT_KEY}) — `{ tags: [...], _count:\n * { tags: 37 } }`. Counted by the same query that reads the rows, over the\n * same rows a read without `limit` would return (the include's `where`,\n * soft delete, row-level security), so `limit` caps what is sent and the\n * count says what was left out.\n *\n * To-many relations only: a to-one relation is a 400.\n *\n * @since 0.24\n */\n count?: boolean;\n}\n\n/**\n * The relations a read loads, as a list of (possibly dotted) names or as a\n * tree.\n *\n * - `[\"author\", \"comments.author\"]` — a dotted path is the same thing as the\n * nested object form, spelled flat. It is what the REST `?include=` parameter\n * carries, and the two forms compile to the same request.\n * - `[\"*\"]` — every relation the collection declares, one hop deep, those\n * declared only for a record's tabs included — so on a list it can embed\n * every row of a to-many relation no column shows. Name the relations the\n * view renders instead; the admin panel does, and sends `*` only when it\n * cannot resolve a collection's relations in the browser.\n * - `{ comments: { limit: 5, include: { author: true } } }` — the parametrised\n * form.\n *\n * A name that is not a relation of the collection is a **400\n * `UNKNOWN_RELATION`**, not a silent omission: a read that quietly drops an\n * `include` answers 200 with the field missing, which is indistinguishable from\n * a row that genuinely has no related row.\n *\n * @group Data\n */\nexport type IncludeSpec = string[] | Record<string, true | IncludeOptions>;\n\n/**\n * What a read of one record by id takes besides the id — `findById` and\n * `listenById`.\n *\n * @group Data\n */\nexport interface ByIdReadOptions<Inc = IncludeSpec> {\n /**\n * Relations to load into the row, as {@link FindParams.include} loads them\n * into each row of a list. A to-many relation not named here is absent\n * from the row — not empty.\n */\n include?: Inc;\n}\n\n/**\n * An include checked against one generated collection's relations: `R` is its\n * `Relations` map (relation name → the accessor it reaches) and `DB` the\n * generated `Database`, so a nested `include` is checked against the *target's*\n * relations in turn.\n *\n * A name, a dotted path starting with one, or `\"*\"`; or the tree, keyed by\n * relation name. A name that is not a relation is a 400 `UNKNOWN_RELATION`\n * from the server, and a compile error here.\n *\n * @group Data\n */\nexport type IncludeSpecFor<DB, R> =\n | readonly (Extract<keyof R, string> | \"*\" | `${Extract<keyof R, string>}.${string}`)[]\n | { [K in Extract<keyof R, string>]?: true | IncludeOptionsFor<DB, R[K]> };\n\n/**\n * {@link IncludeOptions} for a relation that reaches the generated collection\n * `T`: its nested `include` is checked against `T`'s relations.\n *\n * @group Data\n */\nexport interface IncludeOptionsFor<DB, T> extends Omit<IncludeOptions, \"include\"> {\n include?: T extends keyof DB ? IncludeOf<DB[T], DB> : IncludeSpec;\n}\n\n/**\n * The include a generated `Database` entry takes — {@link IncludeSpecFor} over\n * its `Relations` — or the unchecked {@link IncludeSpec} for anything that does\n * not declare its relations (a hand-written row type, an older generated file).\n *\n * @group Data\n */\nexport type IncludeOf<Entry, DB> = Entry extends { Relations: infer R } ? IncludeSpecFor<DB, R> : IncludeSpec;\n\n/**\n * One argument of the fluent `.include(...)`: an element of the include list, a\n * whole list, or the tree.\n *\n * @group Data\n */\nexport type IncludeArgOf<Inc> = Inc extends readonly (infer E)[] ? E | Inc : Inc;\n\n/**\n * Hops an {@link IncludeSpec} may nest. `comments.author` is two.\n *\n * @group Data\n */\nexport const MAX_INCLUDE_DEPTH = 3;\n\n/**\n * Parameters for querying a collection.\n *\n * ## How the filter parameters combine\n *\n * `where`, `logical`, and `searchString` are **independent** and, when more\n * than one is present, are combined with **AND** — every clause must match.\n * Concretely the backend builds:\n *\n * ```text\n * (where filters, AND-ed together)\n * AND (logical group)\n * AND (searchString matches, OR-ed across searchable columns)\n * ```\n *\n * So `where` does **not** conflict with or override `logical` — they stack.\n * If you need `where` fields OR-ed with each other, move them into `logical`\n * instead. There is no way to OR `where` against `logical`; express anything\n * that isn't a plain AND of the three groups inside a single `logical` tree.\n *\n * ## Pagination precedence\n *\n * `limit`/`offset` and `page` describe the same window two ways, so send one.\n * `page` is `offset = (page - 1) * (limit ?? DEFAULT_LIST_LIMIT)`. A read with\n * **both** is refused with `PAGE_WITH_OFFSET` — a 400 from the server, and a\n * `RebaseClientError` with that code from a client before any request — rather\n * than quietly applying one of them. `after` is refused beside either\n * (`CURSOR_WITH_OFFSET`).\n *\n * @group Data\n */\nexport interface FindParams<M extends Record<string, unknown> = Record<string, unknown>, Inc = IncludeSpec> {\n /**\n * Maximum number of items to return.\n *\n * Omit it and the backend applies {@link DEFAULT_LIST_LIMIT}, so a read is\n * never unbounded. Provide it and it must be a whole number between 1 and\n * {@link MAX_LIST_LIMIT}: the backend **rejects** anything else with a 400\n * rather than trimming it to fit, because a page quietly smaller than the\n * one you asked for is indistinguishable from having reached the end of the\n * collection. To read past the ceiling, page with `offset` — or let\n * {@link SDKCollectionClient.iterate} / {@link SDKCollectionClient.findAll}\n * do it for you.\n */\n limit?: number;\n /**\n * Number of items to skip. Not with {@link FindParams.page}: the pair is\n * refused with `PAGE_WITH_OFFSET`.\n */\n offset?: number;\n /**\n * Page number (1-indexed), alternative to {@link FindParams.offset}: it\n * skips `(page - 1) * (limit ?? DEFAULT_LIST_LIMIT)` rows. Sent with\n * `offset`, the read is refused with `PAGE_WITH_OFFSET`.\n */\n page?: number;\n /**\n * Filter conditions keyed by field name.\n * Each value is a `[WhereFilterOp, value]` tuple or an array of tuples\n * for multiple conditions on the same field. Multiple fields, and multiple\n * tuples on one field, are **AND-ed**; also AND-ed with `logical` and\n * `searchString` when present (see the interface docs).\n *\n * @example\n * { status: [\"==\", \"active\"] }\n * { age: [\">=\", 18] }\n * { role: [\"in\", [\"admin\", \"editor\"]] }\n * { age: [[\">=\", 18], [\"<\", 65]] }\n */\n where?: WhereFor<M>;\n /**\n * Logical grouping conditions (AND/OR). Use this for anything `where`\n * can't express — notably OR-ing conditions. AND-ed with `where` and\n * `searchString` when present (see the interface docs).\n */\n logical?: LogicalConditionFor<M>;\n /**\n * Sort order as a `[field, direction]` tuple, or a list of them applied in\n * order of significance — the second key breaks ties on the first, and so on.\n *\n * @example orderBy: [\"created_at\", \"desc\"]\n * @example orderBy: [[\"roles\", \"asc\"], [\"created_at\", \"desc\"]]\n */\n orderBy?: OrderBySpec<FieldPath<M> | ComputedSortField>;\n /**\n * Relations to load into the response — see {@link IncludeSpec}.\n *\n * Not checked against `M` here: a relation name comes from the collection's\n * `relations`, not from its columns, so nothing in a *hand-written* row type\n * can validate one. A **generated** `Database` narrows this to the\n * collection's actual relation keys, recursively — see\n * {@link IncludeSpecFor}. The row a read returns still types each relation\n * as optional; the generated `RowWith<A, I>` names the row with the\n * included ones required.\n *\n * An unknown name is a 400 `UNKNOWN_RELATION`. It used to be ignored.\n */\n include?: Inc;\n\n /**\n * Columns to return, instead of all of them.\n *\n * A real column projection: only these columns are read from the database,\n * so a query that needs two fields of a wide row does not pay for the rest.\n * `excludeFromApi` still applies — naming such a column here does not\n * un-hide it — and the primary key is always returned, because a row that\n * cannot be addressed cannot be updated, deleted or paged past.\n *\n * A relation named in {@link FindParams.include} is loaded regardless of\n * whether it appears here; use {@link IncludeOptions.fields} to narrow the\n * columns *within* an included relation.\n */\n fields?: string[];\n\n /**\n * Collapse rows that are identical over the columns being returned.\n *\n * `SELECT DISTINCT` over the projection — so it is only meaningful\n * alongside {@link FindParams.fields}, and with the primary key in the\n * projection (which it always is) every row is already distinct. Pair it\n * with `fields` naming the columns you actually want the distinct values of.\n *\n * A distinct read is not counted, whichever door it goes through: over\n * HTTP its `meta.total` is absent, and in-process it is the rows up to the\n * end of the page — as for {@link FindParams.count} `\"none\"`. `hasMore`\n * is exact either way.\n */\n distinct?: boolean;\n\n /**\n * Continue from where a previous page ended — keyset (\"seek\") pagination.\n *\n * The value is the opaque `meta.nextCursor` of the previous response. It\n * encodes the sort keys the query was ordered by and the last row's values\n * for them, so a page picks up strictly after the last row served rather\n * than at a row *count* that concurrent writes have already moved.\n *\n * It has to describe the same query: `after` alongside a different\n * `orderBy` is a 400 `CURSOR_ORDER_MISMATCH` rather than a page of rows\n * seeked in an order nobody asked for. Mutually exclusive with `offset` and\n * `page` for the same reason.\n *\n * Multi-key sorts and nullable keys both work — the comparison is built\n * over every key, in order, with the NULL placement the sort declared.\n */\n after?: string;\n /**\n * Text search string, AND-ed with `where`/`logical`. This is the value\n * behind the query builder's `.search()` method.\n *\n * What it compiles to depends on the collection. By default — matching\n * every collection that has not said otherwise — it is a case-insensitive\n * substring match OR-ed across the collection's top-level `string`\n * properties: it does not reach inside `map` or `array` properties, it does\n * not stem or rank, and it cannot use an index.\n *\n * A Postgres collection that declares a `search` block instead gets a\n * ranked full-text match over exactly the fields it named, and rows come\n * back with a {@link FindParams.orderBy}-able `_score`.\n */\n searchString?: string;\n\n /**\n * Nearest-neighbour search over a `vector` property.\n *\n * Postgres only, and only for a collection that declares a property of\n * type `vector`. Rows come back ordered by distance, closest first, each\n * carrying a `_distance`. Combines with `where` and `logical`, which are\n * applied as filters before the ordering — so this is \"the nearest rows\n * that also match\", not \"the nearest rows, then filtered\".\n *\n * Supplying the query vector is the caller's job: rebase stores and\n * searches embeddings, it does not compute them.\n */\n vectorSearch?: VectorSearchParams;\n\n /**\n * Ask each returned row to explain itself: which declared search fields\n * matched, with a highlighted snippet from each. Populates `_matches`.\n *\n * Off by default because it is not free — one `ts_headline` per declared\n * field per returned row, and `ts_headline` re-parses the document rather\n * than reading the index. Fine for a page of results, not for an export.\n *\n * Ignored unless the collection declares a `search` block and the query\n * carries a `searchString`; there is nothing to explain otherwise.\n */\n searchExplain?: boolean;\n\n /**\n * Whether to count the whole matching set for `meta.total`.\n *\n * `\"exact\"`, the default, runs a `COUNT(*)` over every row the query\n * matches beside the page — and on a large table that count, not the page,\n * is most of what a list read costs: it reads every matching row to report\n * one number. `\"none\"` skips it. `meta.hasMore` is still exact (the read\n * asks for one row past the page and reports whether it came back), so\n * nothing that pages — `iterate()`, `findAll()`, a \"load more\" button —\n * needs the count.\n *\n * Without the count there is no `total` to report: over HTTP `meta.total`\n * is absent, and in-process it is the rows up to the end of this page\n * (`offset` + the page), the figure a driver that cannot count reports.\n * Sent as `?count=none`.\n */\n count?: \"exact\" | \"none\";\n}\n\n/**\n * Paginated response from a collection query.\n * @group Data\n */\nexport interface FindResponse<M extends Record<string, unknown> = Record<string, unknown>> {\n /** Array of entities matching the query */\n data: Entity<M>[];\n /** Pagination metadata */\n meta: {\n total: number;\n limit: number;\n offset: number;\n hasMore: boolean;\n };\n}\n\n\n\n/**\n * Fluent query builder for the **admin panel** — resolves to `FindResponse<M>`\n * (Snapshot-wrapped rows).\n *\n * @internal App developers should use {@link SDKQueryBuilderInterface}\n * (flat rows, returned by `client.data.*` / `context.data.*`). This\n * Snapshot-flavored variant backs the admin panel internals only.\n *\n * @group Data\n */\nexport interface QueryBuilderInterface<M extends Record<string, unknown> = Record<string, unknown>> {\n where<K extends keyof M & string, Op extends WhereFilterOp>(column: K, operator: Op, value: WhereValueFor<Op, M[K]>): this;\n where(logicalCondition: LogicalConditionFor<M>): this;\n orderBy(column: (keyof M & string) | ComputedSortField, direction?: \"asc\" | \"desc\"): this;\n limit(count: number): this;\n offset(count: number): this;\n search(searchString: string, options?: { explain?: boolean }): this;\n\n /**\n * Order rows by nearest-neighbour distance to `vector`, closest first.\n *\n * Postgres only, over a property declared as `type: \"vector\"`. Each row\n * comes back with a `_distance`. Any `where` on the same query filters\n * before the ordering; distance decides the order.\n *\n * The query embedding is the caller's to produce.\n */\n vectorSearch(\n property: string,\n vector: number[],\n options?: { distance?: \"cosine\" | \"l2\" | \"inner_product\"; threshold?: number }\n ): this;\n include(...relations: string[]): this;\n find(): Promise<FindResponse<M>>;\n listen(onUpdate: (data: FindResponse<M>) => void, onError?: (error: Error) => void): () => void;\n}\n\n/**\n * A single collection's CRUD accessor for the **admin panel** — every method\n * resolves to `Snapshot`-wrapped rows (`FindResponse<M>` / `Snapshot<M>`).\n *\n * @internal App developers do **not** use this. The public, symmetric surface\n * is {@link SDKCollectionClient} (flat rows), exposed as `client.data.products`\n * in the SDK and `context.data.products` in framework callbacks. This\n * Snapshot-flavored accessor backs the admin panel view-model only.\n *\n * @group Data\n */\nexport interface CollectionAccessor<M extends Record<string, unknown> = Record<string, unknown>> {\n /**\n * Find multiple records with optional filtering, pagination, and sorting.\n */\n find(params?: FindParams<M>): Promise<FindResponse<M>>;\n\n /**\n * Find a single record by its ID.\n */\n findById(id: string | number, options?: ByIdReadOptions): Promise<Entity<M> | undefined>;\n\n /**\n * Create a new record.\n * @param data The entity data to create.\n * @param id Optional specific ID to use for the new record.\n * @returns The created entity\n */\n create(data: Partial<EntityValues<M>>, id?: string | number): Promise<Entity<M>>;\n\n /**\n * Create many records in a single transaction.\n *\n * See {@link SDKCollectionClient.createMany}. Optional: not every driver can\n * write in bulk, and callers should fall back to `create` per record.\n */\n createMany?(\n data: Partial<EntityValues<M>>[],\n options?: { upsert?: boolean; onConflict?: readonly string[] }\n ): Promise<Entity<M>[]>;\n\n /**\n * Update an existing record by ID.\n *\n * `options.ifMatch` is the version the edit was made against\n * ({@link Entity.etag}): the write is refused with `412\n * PRECONDITION_FAILED` when the row has changed since. A data source that\n * cannot check one ignores it, and the write is unconditional.\n * @returns The updated entity\n */\n update(id: string | number, data: Partial<EntityValues<M>>, options?: { ifMatch?: string }): Promise<Entity<M>>;\n\n /**\n * Update many records in a single transaction.\n *\n * See {@link SDKCollectionClient.updateMany}. Optional, as `createMany` is.\n */\n updateMany?(updates: { id: string | number; data: Partial<EntityValues<M>> }[]): Promise<Entity<M>[]>;\n\n /**\n * Delete many records in a single transaction.\n *\n * See {@link SDKCollectionClient.deleteMany}. Optional, as `createMany` is.\n */\n deleteMany?(ids: (string | number)[]): Promise<void>;\n\n /**\n * Delete a record by ID.\n */\n delete(id: string | number): Promise<void>;\n\n /**\n * Subscribe to a collection for real-time updates.\n * Optional method, may not be supported by all implementations (like stateless HTTP clients).\n */\n listen?(params: FindParams<M> | undefined, onUpdate: (response: FindResponse<M>) => void, onError?: (error: Error) => void): () => void;\n\n /**\n * Subscribe to a single record for real-time updates: every frame is the\n * record as `findById(id, options)` reads it.\n * Optional method.\n */\n listenById?(id: string | number, onUpdate: (entity: Entity<M> | undefined) => void, onError?: (error: Error) => void, options?: ByIdReadOptions): () => void;\n\n /**\n * {@link SDKCollectionClient.listenCount}: the matching rows' count, live.\n * Optional method; absent where the data source cannot subscribe.\n */\n listenCount?(params: FindParams<M> | undefined, onCount: (total: number) => void, onError?: (error: Error) => void): () => void;\n\n /**\n * The rows a soft delete stamped, and the delete that removes a row for\n * good: what {@link SDKCollectionClient.trash} is built from. A restore is\n * an ordinary {@link update}. Optional; absent where the data source\n * cannot read a collection's trash.\n */\n trash?: CollectionTrashAccessor<M>;\n\n /**\n * Count the number of records matching the given filter.\n *\n * Optional on this contract because a data source need not support it, and\n * required on `CollectionClient` — the HTTP implementation always has it.\n * So `client.data.posts.count()` compiles in the browser while the same\n * call through a `context.data` accessor needs `count?.()`, which is the\n * one place the two halves of this API are not interchangeable.\n */\n count?(params?: FindParams<M>): Promise<number>;\n\n /**\n * {@link SDKCollectionClient.aggregate}. Optional here for the same reason\n * `count` is: not every data source can compute one, and the SDK wraps an\n * absent implementation in a stub that says so rather than returning a\n * number nothing counted.\n */\n aggregate?(params: AggregateParams<M>): Promise<AggregateResult>;\n\n // Fluent Query Builder\n where<K extends keyof M & string, Op extends WhereFilterOp>(column: K, operator: Op, value: WhereValueFor<Op, M[K]>): QueryBuilderInterface<M>;\n where(logicalCondition: LogicalConditionFor<M>): QueryBuilderInterface<M>;\n orderBy(column: (keyof M & string) | ComputedSortField, direction?: \"asc\" | \"desc\"): QueryBuilderInterface<M>;\n limit(count: number): QueryBuilderInterface<M>;\n offset(count: number): QueryBuilderInterface<M>;\n search(searchString: string, options?: { explain?: boolean }): QueryBuilderInterface<M>;\n\n /**\n * Order rows by nearest-neighbour distance to `vector`, closest first.\n *\n * Postgres only, over a property declared as `type: \"vector\"`. Each row\n * comes back with a `_distance`. Any `where` on the same query filters\n * before the ordering; distance decides the order.\n *\n * The query embedding is the caller's to produce.\n */\n vectorSearch(\n property: string,\n vector: number[],\n options?: { distance?: \"cosine\" | \"l2\" | \"inner_product\"; threshold?: number }\n ): QueryBuilderInterface<M>;\n include(...relations: string[]): QueryBuilderInterface<M>;\n}\n\n// =============================================================================\n// SDK-facing types — flat rows, no Entity wrapper\n// =============================================================================\n\n/**\n * Pagination metadata returned with collection queries.\n * @group Data\n */\nexport interface PaginationMeta {\n /**\n * Every row the query matches, counted. Not counted — and absent from an\n * HTTP response — when the query asked for {@link FindParams.count}\n * `\"none\"`, and on a `distinct` read over HTTP.\n */\n total: number;\n limit: number;\n offset: number;\n hasMore: boolean;\n /**\n * The opaque cursor that continues this listing — pass it back as\n * {@link FindParams.after}.\n *\n * Present whenever there is a next page to describe (`hasMore` is true and\n * the page returned at least one row). Absent on the last page, and absent\n * on a query no cursor can describe (relevance ordering, whose scores are\n * computed per query and are not comparable between two of them).\n *\n * Opaque on purpose: it encodes the sort keys *and* the last row's values\n * for them, and a client that parsed it would be depending on an encoding\n * that exists to be changed.\n */\n nextCursor?: string;\n}\n\n/**\n * Paginated response from a collection query (SDK-facing).\n * Returns flat rows instead of Entity-wrapped objects.\n *\n * @example\n * const { data, meta } = await rebase.data.posts.find();\n * console.log(data[0].title); // direct access — no .values\n * console.log(meta.total);\n *\n * @group Data\n */\nexport interface FindResult<M extends Record<string, unknown> = Record<string, unknown>> {\n /**\n * Flat rows matching the query, each carrying whatever the query computed\n * for it — see {@link QueryComputedFields}.\n */\n data: (M & QueryComputedFields)[];\n /** Pagination metadata */\n meta: PaginationMeta;\n}\n\n/**\n * Values a query attaches to a row that are not columns of it.\n *\n * Both are absent unless the query asked for the thing that produces them, so\n * both are optional — and reading one on a query that did not ask returns\n * `undefined` rather than a wrong number.\n *\n * They live here rather than on the row type because a generated row type\n * describes a *table*, and neither of these is in one. Without this, a caller\n * who sorted by relevance could not then read the relevance.\n *\n * A `type` alias, deliberately, not an `interface`. TypeScript grants an\n * implicit index signature to a type alias and withholds it from an interface,\n * so `Row & QueryComputedFields` stops being assignable to\n * `Record<string, unknown>` the moment this becomes an interface. Seven casts\n * in one downstream app broke on exactly that.\n *\n * @group Data\n */\nexport type QueryComputedFields = {\n /**\n * Relevance, when the collection declares a {@link SearchConfig} and the\n * query carried a search string. Higher is better; the scale is not\n * comparable between two different search strings.\n */\n _score?: number;\n /**\n * Which declared fields matched, and the text around each hit. Present only\n * when the query asked for it — `.search(term, { explain: true })` — because\n * it costs a `ts_headline` per field per row.\n */\n _matches?: SearchMatch[];\n /**\n * Distance to the query vector, when the query used\n * {@link FindParams.vectorSearch}. Lower is closer, and the rows are\n * already ordered by it.\n */\n _distance?: number;\n};\n\n/**\n * One aggregate a query asks for.\n *\n * `count` alone counts rows; every other function names a column, and `count`\n * with a column counts its non-NULL values.\n *\n * The result key is derived rather than chosen: `sum(total)` comes back as\n * `sum_total` and a bare `count()` as `count`. Letting a caller name it would\n * mean checking their name is not also a `groupBy` field — a rule nobody would\n * guess, and a silently overwritten value if it went unchecked.\n *\n * @group Data\n */\nexport type AggregateSelect<M extends Record<string, unknown> = Record<string, unknown>> =\n | { fn: \"count\"; field?: Extract<keyof M, string> }\n | { fn: \"sum\" | \"avg\"; field: NumericKeyOf<M> }\n | { fn: \"min\" | \"max\"; field: Extract<keyof M, string> };\n\n/**\n * The columns of `M` that hold numbers — what `sum` and `avg` can add up. Any\n * column used to be accepted, and `sum` of a text column was a 400. An untyped\n * row (every key `string`) keeps every name.\n *\n * Written as the keys of a key-remapped mapped type, not as a conditional over\n * `keyof M`: tsc measures the conditional form as invariant in `M`, which made\n * a typed `SDKCollectionClient<Post>` unassignable to the wide\n * `SDKCollectionClient` that helpers taking any collection accept. A mapped\n * type is related structurally, so a typed client still widens.\n */\nexport type NumericKeyOf<M extends Record<string, unknown>> = Extract<keyof {\n [K in keyof M as string extends K ? K : NonNullable<M[K]> extends number ? K : never]: M[K]\n}, string>;\n\n/**\n * One row of an aggregate result: the `groupBy` columns, plus one key per\n * {@link AggregateSelect} under its derived alias.\n *\n * `count`, `sum` and `avg` arrive as numbers — Postgres returns bigint and\n * numeric as strings, and they are parsed once at the driver rather than by\n * every caller. `min`/`max` keep the column's own type.\n *\n * @group Data\n */\nexport type AggregateRow = Record<string, unknown>;\n\n/**\n * What {@link SDKCollectionClient.aggregate} takes: the same narrowing a\n * `find()` takes, minus the parts of it that describe a *page* of rows.\n *\n * `limit` and `offset` page through the **groups**, as they page a listing's\n * rows, because grouping by a high-cardinality column is a whole table's worth\n * of rows in one response. Over HTTP a grouped aggregate with no `limit` gets\n * the listing default (50 groups) — read {@link AggregateResult.meta} to know\n * when there were more. Without `groupBy` there is one row: `limit` does nothing\n * there and `offset` is refused.\n *\n * `include`, `after` and the rest are absent on purpose: an aggregate has no\n * relations to load and no row to continue after.\n *\n * @group Data\n */\nexport interface AggregateParams<M extends Record<string, unknown> = Record<string, unknown>> {\n /** The aggregates to compute. At least one. */\n select: AggregateSelect<M>[];\n /** Columns to group by. Omit for a single row over everything that matches. */\n groupBy?: Extract<keyof M, string>[];\n /** Filter conditions, as {@link FindParams.where}. */\n where?: WhereFor<M>;\n /** An `and`/`or`/`not` group, AND-ed with `where`. */\n logical?: LogicalConditionFor<M>;\n /** Text search, AND-ed with the filters. */\n searchString?: string;\n /** Most groups to return. Grouped aggregates only. */\n limit?: number;\n /** Groups to skip, to page past `limit`. Grouped aggregates only. */\n offset?: number;\n /**\n * Sort the groups — by a `groupBy` column or by an aggregate's alias\n * (`\"count\"`, `\"sum_total\"`, see {@link AggregateAlias}). One\n * `[key, \"asc\" | \"desc\"]` tuple, or several applied in order. Groups the\n * sort leaves tied keep their `groupBy` order. Grouped aggregates only:\n * without `groupBy` there is one row, and over HTTP an `orderBy` beside it is\n * refused with `INVALID_AGGREGATE_WINDOW`. A key that is neither a group\n * column nor a selected aggregate is refused with `INVALID_ORDER_BY`.\n *\n * ```ts\n * await client.data.orders.aggregate({\n * select: [{ fn: \"sum\", field: \"total\" }],\n * groupBy: [\"status\"],\n * orderBy: [\"sum_total\", \"desc\"]\n * });\n * ```\n */\n orderBy?: OrderByTuple<AggregateOrderKey<M>> | OrderByTuple<AggregateOrderKey<M>>[];\n}\n\n/**\n * The key an {@link AggregateSelect} comes back under: `count` for `count()`,\n * `<fn>_<field>` otherwise (`sum_total`, `max_created_at`).\n *\n * @group Data\n */\nexport type AggregateAlias<M extends Record<string, unknown> = Record<string, unknown>> =\n | \"count\"\n | `${\"count\" | \"min\" | \"max\"}_${Extract<keyof M, string>}`\n | `${\"sum\" | \"avg\"}_${NumericKeyOf<M>}`;\n\n/**\n * What {@link AggregateParams.orderBy} can sort the groups by: a column (to\n * group by) or an aggregate's alias.\n *\n * @group Data\n */\nexport type AggregateOrderKey<M extends Record<string, unknown> = Record<string, unknown>> =\n | Extract<keyof M, string>\n | AggregateAlias<M>;\n\n/**\n * Where a grouped aggregate's page of groups sits: the `limit` it was cut at,\n * the `offset` it started from, and whether more groups follow.\n *\n * @group Data\n */\nexport interface AggregateMeta {\n limit: number;\n offset: number;\n hasMore: boolean;\n}\n\n/**\n * What {@link SDKCollectionClient.aggregate} resolves to: the rows, as an\n * array, carrying the page they are as `meta`.\n *\n * `meta` is present on a grouped aggregate that was cut at a `limit` — which,\n * over HTTP, is every grouped one, since the route applies the listing default\n * when none is given. `hasMore: true` means the groups did not all fit: page on\n * with `offset`, or raise `limit`. It is a non-enumerable property, so a spread,\n * `JSON.stringify` or `Object.keys` of the result sees only the rows.\n *\n * @group Data\n */\nexport interface AggregateResult extends Array<AggregateRow> {\n readonly meta?: AggregateMeta;\n}\n\n/**\n * Which column an iteration sorts by when it is asked to seek, for keyset\n * (\"seek\") pagination.\n *\n * Either the column name on its own — sorted ascending — or the column plus an\n * explicit direction. It is used only when the query names no `orderBy`; see\n * {@link PageWalkOptions.cursor}.\n *\n * @group Data\n */\nexport type CursorSpec<M extends Record<string, unknown> = Record<string, unknown>> =\n | (Extract<keyof M, string>)\n | { field: Extract<keyof M, string>; direction?: \"asc\" | \"desc\" };\n\n/**\n * How {@link SDKCollectionClient.iterate} / {@link SDKCollectionClient.findAll}\n * walk a collection, layered on top of the normal `find()` parameters.\n *\n * @group Data\n */\nexport interface PageWalkOptions<M extends Record<string, unknown> = Record<string, unknown>> {\n /**\n * Rows fetched per request. Defaults to 200; values below 1 are clamped up.\n * This is the request size, not a result cap — the iteration keeps going\n * until the server says there is nothing left.\n */\n pageSize?: number;\n /**\n * **Require** seeking: fail rather than fall back to offset paging.\n *\n * Every walk already seeks when it can. After the first page it hands the\n * server's `meta.nextCursor` back as `after`, so each page asks for rows\n * strictly after the last one seen — concurrent writes before that row\n * cannot shift the window, and the database never skips rows by count.\n * Where the server issues no cursor (a relevance-ordered search, a vector\n * search, a `distinct` read, a server that predates cursors), the default\n * walk pages by offset instead, which a concurrent insert or delete can\n * make skip or repeat a row.\n *\n * Passing `cursor` turns that fallback into an error: a listing with no\n * cursor throws `cursor-missing`, and a cursor that stops advancing throws\n * `cursor-stalled`. It also names the column to sort by when the query has\n * no `orderBy`; an explicit `orderBy` wins, and the cursor follows whatever\n * the query is sorted by, multi-key sorts and nullable keys included. The\n * row id breaks the final tie, so the order is always total.\n *\n * @example\n * for await (const job of client.data.jobs.iterate({ cursor: \"id\" })) { … }\n */\n cursor?: CursorSpec<M>;\n /**\n * Hard ceiling on the number of requests one walk may make, so a server\n * that never stops saying `hasMore` cannot spin forever. Defaults to\n * 10 000 pages; hitting it throws.\n */\n maxPages?: number;\n}\n\n/**\n * Parameters accepted by {@link SDKCollectionClient.iterate} — everything\n * `find()` takes except the window itself (`limit`, `offset`, `page`), which\n * the iterator owns, plus the walk options.\n *\n * `count` is accepted and ignored: no page of a walk is counted, since nothing\n * a walk returns carries a total (see {@link FindParams.count}).\n *\n * @group Data\n */\nexport type IterateParams<M extends Record<string, unknown> = Record<string, unknown>, Inc = IncludeSpec> =\n Omit<FindParams<M, Inc>, \"limit\" | \"offset\" | \"page\"> & PageWalkOptions<M>;\n\n/**\n * Parameters accepted by {@link SDKCollectionClient.findAll}: the iteration\n * parameters plus the ceiling that keeps a whole collection from being pulled\n * into memory unnoticed.\n *\n * @group Data\n */\nexport type FindAllParams<M extends Record<string, unknown> = Record<string, unknown>, Inc = IncludeSpec> =\n IterateParams<M, Inc> & {\n /**\n * Most rows to materialise. Defaults to 10 000. Exceeding it **throws**\n * — a truncated array returned as if it were the whole answer is the\n * kind of quiet wrong that shows up months later in a report. Pass\n * `Infinity` to opt out deliberately, or use `iterate()` to stream.\n */\n maxRows?: number;\n };\n\n/**\n * Fluent Query Builder Interface for the SDK client.\n * Returns `FindResult<M>` (flat rows) instead of `FindResponse<M>` (Entity-wrapped).\n *\n * @group Data\n */\nexport interface SDKQueryBuilderInterface<M extends Record<string, unknown> = Record<string, unknown>, Inc = IncludeSpec> {\n where<K extends keyof M & string, Op extends WhereFilterOp>(column: K, operator: Op, value: WhereValueFor<Op, M[K]>): this;\n /**\n * Filter on a relation path (`author.name`) or a JSON path\n * (`metadata->>tier`).\n *\n * A separate overload because the value cannot be typed: neither addresses\n * a column of `M`, so there is nothing in a generated row type to check\n * against — the driver resolves the path and refuses what it cannot. The\n * key is still constrained to a *path*, so a mistyped column name does not\n * fall through to here and lose its check.\n *\n * `find({ where })` has accepted both all along ({@link FieldPath}); the\n * builder did not, so the documented relation-path filters were compile\n * errors on a typed client.\n */\n where(column: NonColumnFieldPath, operator: WhereFilterOp, value: unknown): this;\n where(logicalCondition: LogicalConditionFor<M>): this;\n /**\n * Sort by a column, a relation or JSON path, `_score`, or an aggregate over\n * a to-many relation — the same key set {@link FindParams.orderBy} takes.\n */\n orderBy(\n column: FieldPath<M> | ComputedSortField | RelationAggregateSort,\n direction?: \"asc\" | \"desc\",\n nulls?: NullsPlacement\n ): this;\n limit(count: number): this;\n offset(count: number): this;\n search(searchString: string, options?: { explain?: boolean }): this;\n\n /**\n * Order rows by nearest-neighbour distance to `vector`, closest first.\n *\n * Postgres only, over a property declared as `type: \"vector\"`. Each row\n * comes back with a `_distance`. Any `where` on the same query filters\n * before the ordering; distance decides the order.\n *\n * The query embedding is the caller's to produce.\n */\n vectorSearch(\n property: Extract<keyof M, string>,\n vector: number[],\n options?: { distance?: \"cosine\" | \"l2\" | \"inner_product\"; threshold?: number }\n ): this;\n /**\n * Load relations — names, dotted paths (`\"comments.author\"`), or the\n * parametrised tree. Repeated calls merge rather than replace.\n */\n include(...relations: IncludeArgOf<Inc>[]): this;\n /**\n * Return only these columns. A real projection: the columns are what is\n * read from the database, not what survives a trim of the response.\n */\n fields(...columns: Extract<keyof M, string>[]): this;\n /** `SELECT DISTINCT` over the projection — see {@link FindParams.distinct}. */\n distinct(enabled?: boolean): this;\n /** Continue after a previous page's `meta.nextCursor`. */\n after(cursor: string): this;\n find(): Promise<FindResult<M>>;\n /**\n * Aggregate the rows this query matches instead of returning them.\n *\n * The builder's `where`/`logical`/`search` narrow which rows are\n * aggregated; its `orderBy`, `include` and window do not apply and are\n * ignored, exactly as they are on the REST route. Page the groups with the\n * `limit`/`offset` passed here.\n */\n aggregate(params: Omit<AggregateParams<M>, \"where\" | \"logical\" | \"searchString\">): Promise<AggregateResult>;\n\n /**\n * Page through everything this query matches, one row at a time.\n *\n * The same walker {@link SDKCollectionClient.iterate} uses, so the ceiling\n * on `limit` is not a ceiling on what a query can read. `.limit()` set on\n * the builder becomes the **page size** here, not a total.\n */\n iterate(options?: PageWalkOptions<M>): AsyncIterableIterator<M>;\n\n /**\n * Collect everything this query matches into one array.\n *\n * {@link SDKCollectionClient.findAll}'s `maxRows` guard applies: an\n * unbounded collect is a memory hazard, so it stops and says so rather than\n * growing until the process dies.\n */\n findAll(options?: PageWalkOptions<M> & { maxRows?: number }): Promise<M[]>;\n\n count(): Promise<number>;\n listen(onUpdate: (data: FindResult<M>) => void, onError?: (error: Error) => void): () => void;\n}\n\n/**\n * SDK collection client — returns flat rows, no Entity wrapper.\n *\n * This is the public API surface for app developers using\n * `createRebaseClient()`. admin internals use `CollectionAccessor` instead.\n *\n * Type parameters:\n * - `M` — the **Row** shape returned by reads (`find`, `findById`, `listen`).\n * - `I` — the **Insert** shape accepted by {@link create}. Defaults to\n * `Partial<M>`; the generated SDK supplies a dedicated `Insert` type where\n * required columns are required and auto-generated / read-only columns are\n * omitted, so `create({})` on a table with required fields is a compile error.\n * - `U` — the **Update** shape accepted by {@link update}. Defaults to\n * `Partial<M>`; the generated SDK supplies a dedicated `Update` type.\n *\n * @example\n * const { data: posts } = await rebase.data.posts.find();\n * console.log(posts[0].title); // flat access\n * console.log(posts[0].id); // id at top level\n *\n * const post = await rebase.data.posts.findById(1);\n * console.log(post?.title); // no .values needed\n *\n * @group Data\n */\n/**\n * A change expressed as an operation on the column's current value, rather than\n * as the value to store.\n *\n * `{ views: 5 }` says what the number becomes; `{ views: { $inc: 1 } }` says\n * what happens to it. The difference is the read the caller no longer has to\n * make — and the race that read opens. Two requests that each read `4`, add one\n * and write `5` lose an increment between them; `SET views = views + 1` cannot,\n * because the arithmetic happens inside the statement holding the row lock.\n *\n * Exactly one operator per field. `{ views: { $inc: 1, $push: \"x\" } }` is\n * refused rather than applied in an order the caller cannot see.\n *\n * @group Data\n */\n/**\n * The operator names, as a value.\n *\n * A runtime list beside the type because three layers have to *recognise* an\n * operation, not just accept one: the REST validator, the driver that compiles\n * it, and the offline queue that must refuse to apply one locally. Three copies\n * of four strings is three chances for one of them to miss an operator added to\n * the other two, and the failure is silent in the worst direction — an\n * unrecognised marker is written to the column as a JSON document.\n *\n * @group Data\n */\nexport const FIELD_OPERATORS = [\"$inc\", \"$push\", \"$pull\", \"$merge\"] as const;\n\n/**\n * The key of a {@link BatchRef}. Declared here, beside the field operators,\n * because the two share one namespace: a `$`-prefixed key in a write payload is\n * a marker, and every reader of that namespace has to know all of it.\n *\n * @group Data\n */\nexport const BATCH_REF_KEY = \"$ref\";\n\n/**\n * Whether a value is *trying* to be a field operation — including a misspelled\n * one, which is the case worth catching.\n *\n * Any `$`-prefixed key counts, because `{ $increment: 1 }` written to a number\n * column as a JSON document is the failure this exists to prevent.\n *\n * Except on a `map` (pass `propertyType`): a map's value is a JSON document,\n * and one may legitimately carry `$` keys — a JSON Schema's `$schema`, a\n * MongoDB-style export. There, only a single-key object whose key is one of\n * {@link FIELD_OPERATORS} is an operation, and anything else is data. A caller\n * that does not know the property type gets the strict reading.\n *\n * The one exception is `{ $ref: … }`, the batch's backward reference. It stands\n * where a *value* goes and is resolved to one before the row is written, so it\n * is not an operation on a column — reading it as a misspelled operator refused\n * every `$ref` in a batch with \"unknown field operator '$ref'\".\n *\n * @group Data\n */\nexport function isFieldOperation(value: unknown, propertyType?: string): boolean {\n if (typeof value !== \"object\" || value === null || Array.isArray(value) || value instanceof Date) {\n return false;\n }\n const keys = Object.keys(value);\n if (keys.length === 1 && keys[0] === BATCH_REF_KEY) return false;\n if (propertyType === \"map\") {\n return keys.length === 1 && (FIELD_OPERATORS as readonly string[]).includes(keys[0]);\n }\n return keys.some((key) => key.startsWith(\"$\"));\n}\n\n/** True when any value in a write payload is (or is attempting to be) one. @group Data */\nexport function hasFieldOperation(values: Record<string, unknown> | undefined): boolean {\n return !!values && Object.values(values).some((value) => isFieldOperation(value));\n}\n\nexport type FieldOperation =\n /** Add to a `number` column; negative to subtract. `SET col = col + n`. */\n | { $inc: number }\n /** Append one value, or each of an array of values, to an `array` column. */\n | { $push: unknown }\n /** Remove every occurrence of a value from an `array` column. */\n | { $pull: unknown }\n /** Shallow-merge an object into a `map` column. `SET col = col || …::jsonb`. */\n | { $merge: Record<string, unknown> };\n\n/**\n * The payload {@link SDKCollectionClient.update} accepts: plain values, field\n * operations, or both in one body.\n *\n * @group Data\n */\nexport type UpdateValues<U> = { [K in keyof U]?: U[K] | FieldOperationFor<U[K]> };\n\n/**\n * The field operations a column of type `T` takes: `$inc` on a number, `$push`\n * / `$pull` on an array, `$merge` on a map. Any operation on any column used to\n * compile — `update(id, { title: { $inc: 1 } })` — and the server answered 400.\n * A column whose type says nothing (`unknown`, an untyped client) takes them all.\n */\nexport type FieldOperationFor<T> =\n unknown extends T\n ? FieldOperation\n : | (NonNullable<T> extends number ? { $inc: number } : never)\n | (NonNullable<T> extends readonly unknown[] ? { $push: unknown } | { $pull: unknown } : never)\n | (NonNullable<T> extends readonly unknown[] ? never : NonNullable<T> extends Record<string, unknown> ? { $merge: Record<string, unknown> } : never);\n\n/**\n * Where an upsert looks for the row it might be replacing.\n *\n * The columns must carry a uniqueness guarantee the database can use as an\n * `ON CONFLICT` target — the primary key, a property with\n * `validation.unique`, or the columns of a declared `unique` index. Anything\n * else is refused with a 400 rather than sent to Postgres, which would answer\n * `there is no unique or exclusion constraint matching the ON CONFLICT\n * specification` from inside a transaction that has already done work.\n *\n * @group Data\n */\nexport interface UpsertOptions extends WriteOptions {\n /** Column names forming the conflict target. Defaults to the primary key. */\n onConflict?: readonly string[];\n}\n\n/**\n * A placeholder standing for a value only the server will know: the id of a row\n * an earlier operation in the same batch creates.\n *\n * `{ \"$ref\": \"order.id\" }` reads the field `id` off the result of the operation\n * that named itself `ref: \"order\"`. Without it a batch cannot express the one\n * thing a cross-collection batch exists for — writing a parent and its children\n * together — because the child's foreign key is not knowable until the parent\n * has been inserted, and splitting the two into separate requests is exactly\n * the non-atomic sequence the batch replaces.\n *\n * Only backward references resolve. `ref` names must be unique within a batch,\n * and an operation may not reference itself or anything after it.\n *\n * @group Data\n */\nexport interface BatchRef {\n /** `<ref name>.<field>`, e.g. `order.id`. */\n $ref: string;\n}\n\n/**\n * A row's values in a batch: the collection's own columns, each of which may\n * instead be a {@link BatchRef} to a value an earlier operation produces.\n *\n * Homomorphic over the row type, so a column the row type requires is required\n * here too — a create in a batch is the same insert `create()` is. There used\n * to be an `& Record<string, unknown>` on the end, which let a misspelt column\n * compile and come back as a 400 `VALIDATION_UNKNOWN_FIELDS`.\n */\ntype BatchValues<T> = { [F in keyof T]: T[F] | BatchRef };\n\n/**\n * One entry of a batch request.\n *\n * `collection` is the collection's **accessor** on a typed client — the name\n * `client.data.<accessor>` takes, and the key of the generated `Database`\n * (`orderItems`). The client resolves it to the slug the route knows\n * (`order_items`) through its `collections` dictionary; a slug that is not an\n * accessor is sent as written.\n *\n * @group Data\n */\nexport type BatchOperation<DB = Record<string, unknown>> = {\n [K in Extract<keyof DB, string>]:\n | {\n op: \"create\";\n collection: K;\n values: BatchValues<InsertOf<DB[K]>>;\n /** Name this row so a later operation can reference its columns. */\n ref?: string;\n }\n | {\n op: \"upsert\";\n collection: K;\n values: BatchValues<InsertOf<DB[K]>>;\n /** See {@link UpsertOptions.onConflict}. Defaults to the primary key. */\n onConflict?: readonly string[];\n ref?: string;\n }\n | {\n op: \"update\";\n collection: K;\n id: string | number | BatchRef;\n values: { [F in keyof UpdateOf<DB[K]>]?: UpdateOf<DB[K]>[F] | FieldOperationFor<UpdateOf<DB[K]>[F]> | BatchRef };\n ref?: string;\n }\n | {\n op: \"delete\";\n collection: K;\n id: string | number | BatchRef;\n ref?: string;\n };\n}[Extract<keyof DB, string>];\n\n/**\n * What `POST /api/data/_batch` answers with.\n *\n * `data` is aligned to `operations`: the written row for a create, upsert or\n * update, and `null` for a delete — so an index into one is an index into the\n * other, whatever the batch mixed.\n *\n * @group Data\n */\nexport interface BatchResult<R = Record<string, unknown>> {\n data: (R | null)[];\n meta: { operations: number };\n}\n\n/**\n * Per-request options for a write.\n * @group Data\n */\nexport interface WriteOptions {\n /**\n * Names this write, so re-sending it is recognised instead of repeated.\n *\n * A client that does not see a response cannot know whether the write\n * committed. Retrying is therefore the only option, and without a key the\n * server has no way to tell a retry from a second, genuinely new write — so\n * it performs it again. On a table with a server-assigned id that is a\n * duplicate row, because the id the client chose was never used.\n *\n * A key names **one** request, not a job. It records the method, the path\n * and the body it was claimed for, so re-sending that exact request replays\n * its answer, while the same key on a different one is refused with\n * `IDEMPOTENCY_KEY_REUSED` (422) rather than silently answered with the\n * first request's result. Pass a fresh key — a uuid — per call; a reusable\n * business id shared by the create and the delete of one import means the\n * second of them never runs.\n *\n * Set by the offline queue on every replay. Honoured for 24 hours and scoped\n * to the authenticated user — an unauthenticated caller has no principal to\n * scope it to, so the key is ignored there. A retry sent while the first\n * attempt is still being answered gets `IDEMPOTENCY_KEY_IN_PROGRESS` (409)\n * and should be sent again. A server that cannot store keys ignores the\n * header rather than refusing the write.\n *\n * The in-process accessor (`context.data`, `rebase.data`) has no store to\n * keep keys in, and refuses one with `UNSUPPORTED_OPTION` rather than\n * writing as though it had been checked.\n */\n idempotencyKey?: string;\n\n /**\n * Whether the server should send the written row back.\n *\n * `false` sends `Prefer: return=minimal`, and the write answers `204 No\n * Content` — `200` carrying the ids only, for a batch. The row is the\n * default because it carries what the server decided: a serial id, an\n * `autoValue` timestamp, whatever `beforeSave` rewrote. A caller that\n * needs none of that is paying for a full row serialisation and, on\n * Postgres, a read-back per written row.\n *\n * Reach for it on imports and fire-and-forget writes. The method resolves\n * to `undefined` (or `[]`) when it is set, so a caller cannot accidentally\n * use a row the server never sent — and `create`, `update` and `upsert`\n * are typed `M | undefined` for a literal `returning: false`, so reading a\n * field off the result is a compile error rather than a `TypeError`.\n */\n returning?: boolean;\n\n /**\n * The version of the row this write was made against, so it is refused if\n * the row has moved on.\n *\n * The `ETag` from the read that produced the row — `etagOf(row)` on a row\n * from `findById`, or the `ETag` response header. A mismatch answers `412`\n * rather than writing, which is the difference between \"update the row I\n * read\" and \"overwrite whatever is there now\". Without it a read, an edit\n * and a write is last-writer-wins over everything the write did not send,\n * and the loser is told nothing.\n *\n * `\"*\"` asserts only that the row exists.\n *\n * Honoured on `update` and `delete` over HTTP. The in-process accessor\n * (`context.data`, `rebase.data`) cannot check a precondition yet, and\n * refuses one with `UNSUPPORTED_OPTION` rather than writing unconditionally.\n */\n ifMatch?: string;\n}\n\n/**\n * The entity-shaped half of {@link TrashClient}, on a {@link CollectionAccessor}.\n */\nexport interface CollectionTrashAccessor<M extends Record<string, unknown> = Record<string, unknown>> {\n /** The trashed rows matching `params`. */\n find(params?: FindParams<M>): Promise<FindResponse<M>>;\n /** How many trashed rows match `params`. */\n count(params?: FindParams<M>): Promise<number>;\n /** Delete a row for good, trashed or live. */\n purge(id: string | number): Promise<void>;\n /** {@link purge} for several rows, as one transaction, where the data source can. */\n purgeMany?(ids: (string | number)[]): Promise<void>;\n}\n\n/**\n * Options for {@link TrashClient.restore} and {@link TrashClient.restoreMany}.\n */\nexport interface RestoreOptions<M extends Record<string, unknown> = Record<string, unknown>> extends WriteOptions {\n /**\n * The collection's soft-delete field, as declared in `softDelete`.\n * Defaults to `deletedAt`, the field `softDelete: true` uses; pass the\n * name a `softDelete: { field: \"archivedAt\" }` collection declares.\n *\n * A restore is an ordinary update setting this field to `null`, which is\n * the one update the server lets reach a trashed row.\n */\n field?: keyof M & string;\n}\n\n/**\n * The trash of a collection that declares `softDelete`: the rows a delete\n * stamped rather than removed. `client.data.<slug>.trash` in the browser,\n * `context.data.<slug>.trash` and `rebase.data.<slug>.trash` on the server.\n *\n * For a collection that declares `softDelete`. On one that does not, `find`\n * and `count` answer for the live rows, `restore` is refused for a field the\n * collection does not have, and `purge` is an ordinary delete.\n *\n * Where the data source cannot read a trash, every method is a stub that\n * throws when called.\n */\nexport interface TrashClient<M extends Record<string, unknown> = Record<string, unknown>, Inc = IncludeSpec> {\n /** The trashed rows matching `params` — `GET /data/<slug>?deleted=only`. */\n find(params?: FindParams<M, Inc>): Promise<FindResult<M>>;\n /** How many trashed rows match `params` — `GET /data/<slug>/count?deleted=only`. */\n count(params?: FindParams<M, Inc>): Promise<number>;\n /**\n * Bring a trashed row back: the update setting the soft-delete field to\n * `null` (`PATCH /data/<slug>/<id>`). Resolves with the restored row.\n */\n restore(id: string | number, options?: RestoreOptions<M>): Promise<M>;\n /** {@link TrashClient.restore} for several rows, as one transaction (`PATCH /data/<slug>/bulk`). */\n restoreMany(ids: (string | number)[], options?: RestoreOptions<M>): Promise<M[]>;\n /**\n * Delete a row for good — `DELETE /data/<slug>/<id>?hard=true`. It cannot\n * be restored afterwards. Needs the same permission an ordinary delete\n * does, and works on a live row as well as a trashed one.\n */\n purge(id: string | number, options?: WriteOptions): Promise<void>;\n /** {@link TrashClient.purge} for several rows, as one transaction (`POST /data/<slug>/bulk/delete?hard=true`). */\n purgeMany(ids: (string | number)[], options?: WriteOptions): Promise<void>;\n}\n\nexport interface SDKCollectionClient<\n M extends Record<string, unknown> = Record<string, unknown>,\n I = Partial<M>,\n U = Partial<M>,\n /** The include this collection takes; {@link IncludeOf} for a generated `Database`. */\n Inc = IncludeSpec\n> {\n /**\n * Find multiple records with optional filtering, pagination, and sorting.\n *\n * ## What a list method returns\n *\n * Two shapes, and one rule that tells them apart: **a window is wrapped, a\n * whole answer is not.**\n *\n * - `find()` and `listen()` return {@link FindResult} — `{ data, meta }` —\n * because they hand back *one page*. `meta.total` and `meta.hasMore` are\n * the caller's only way to know there is more, so a bare array would lose\n * the answer to the question the call raises.\n * - `findAll()`, `createMany()` and `updateMany()` return a plain `M[]`,\n * because there is nothing left over to report: the walk finished, or the\n * batch is exactly the rows that were written. A `meta` there would be\n * `{ total: rows.length, hasMore: false }`, which says nothing.\n * - `iterate()` yields rows one at a time and never materialises a list at\n * all.\n *\n * So `data` is not a wrapper the SDK sometimes adds and sometimes forgets —\n * it is where the pagination metadata lives, and it is present exactly when\n * there is some.\n */\n find(params?: FindParams<M, Inc>): Promise<FindResult<M>>;\n\n /**\n * Walk every record matching a query, one row at a time, fetching pages as\n * the consumer consumes them.\n *\n * This is the pagination primitive: `find()` returns one window, `iterate()`\n * returns all of them without the caller hand-rolling the\n * `limit` / `offset += ` / \"am I done yet\" loop. Nothing is buffered — rows\n * are yielded as each page arrives, so a million-row walk costs one page of\n * memory. `break` stops the walk and no further requests are made.\n *\n * Termination is driven by the server's `meta.hasMore`, never by comparing\n * a page's length against the requested limit — a final page that happens\n * to be exactly full is indistinguishable that way, and a walk that stops\n * there drops rows. An empty page also ends the walk, and\n * {@link PageWalkOptions.maxPages} bounds a server that never stops saying\n * there is more.\n *\n * ## Consistency\n *\n * After the first page the walk **seeks**: it hands the server's\n * `meta.nextCursor` back as `after`, so each page asks for rows strictly\n * after the last one it saw, which concurrent writes cannot perturb. A\n * listing the server issues no cursor for — relevance, a vector search, a\n * `distinct` read — is paged by **offset** instead, which is only as stable\n * as the table is still: a row inserted or deleted ahead of the window\n * between two requests shifts every later one, so the walk can skip a row\n * or hand back the same one twice. Pass {@link PageWalkOptions.cursor} to\n * make seeking a requirement rather than a preference.\n *\n * @example\n * for await (const job of client.data.jobs.iterate({\n * where: { status: [\"==\", \"queued\"] },\n * cursor: \"id\",\n * pageSize: 500\n * })) {\n * await handle(job);\n * }\n */\n iterate(params?: IterateParams<M, Inc>): AsyncIterableIterator<M>;\n\n /**\n * {@link iterate}, collected into an array.\n *\n * Convenient when the result is known to be small and awkward to stream.\n * Because \"known to be small\" is an assumption and not a fact, the result is\n * capped — 10 000 rows by default — and going over the cap **throws**\n * rather than returning a short array that reads like a complete one. Raise\n * {@link FindAllParams.maxRows} when the data really is bigger, or switch to\n * `iterate()` and stream it.\n *\n * The consistency notes on {@link iterate} apply here too.\n *\n * @throws When more rows match than `maxRows` allows.\n *\n * @example\n * const overdue = await client.data.invoices.findAll({\n * where: { due_at: [\"<\", today] },\n * cursor: \"id\"\n * });\n */\n findAll(params?: FindAllParams<M, Inc>): Promise<M[]>;\n\n /**\n * Find a single record by its ID. `options.include` loads its relations,\n * as `find({ include })` does for each row of a list.\n */\n findById(id: string | number, options?: ByIdReadOptions<Inc>): Promise<M | undefined>;\n\n /**\n * Read one record by its ID, or throw if it is not there.\n *\n * The counterpart to {@link findById}, and the one most reads want. A row\n * fetched by an id that came from a link, a route parameter or another row\n * is expected to exist; when it does not, that is the error case, not a\n * value to thread through the rest of the function.\n *\n * `findById` returns `M | undefined`, so every caller had to prove the row\n * existed before touching a field:\n *\n * ```ts\n * const post = await rebase.data.posts.findById(id);\n * post.title; // TS18048: 'post' is possibly 'undefined'\n * const ok = (await rebase.data.posts.findById(id))!.title; // the `!` everyone reaches for\n * ```\n *\n * With `get`, the absent case is an exception with a code you can branch on,\n * and the happy path is typed as present:\n *\n * ```ts\n * const post = await rebase.data.posts.get(id); // M, not M | undefined\n * ```\n *\n * Same split as Prisma's `findUnique` / `findUniqueOrThrow`: two contracts,\n * both wanted, named so the choice is visible at the call site.\n *\n * @throws {RebaseApiError} `NOT_FOUND` (status 404) when no such row exists,\n * or is visible to the caller — row-level security makes a row the caller\n * may not read indistinguishable from one that is not there, deliberately.\n */\n get(id: string | number): Promise<M>;\n\n /**\n * Create a new record.\n * @param data The record data to create (the collection's `Insert` shape).\n * @param id Optional specific id, sent as an `id` column. This is for tables\n * whose key *is* `id`: the value goes in as that column. For a table keyed\n * on anything else (a `sku`, a composite key), there is no `id` column to\n * receive it — put the key in `data` instead, where it belongs among the\n * columns.\n * @returns The created row — or nothing, with `returning: false`.\n */\n create(data: I, id: string | number | undefined, options: WriteOptions & { returning: false }): Promise<M | undefined>;\n create(data: I, id?: string | number, options?: WriteOptions): Promise<M>;\n\n /**\n * Write many records in a single request and a single transaction.\n *\n * Built for imports and ETL, where one call per row means one HTTP round\n * trip and one transaction per row. Every record still runs the normal\n * pipeline — callbacks, relations, row-level security — and the batch is\n * all-or-nothing: if any record is rejected, none of them land and the\n * error names the offending index.\n *\n * A record carrying its primary key updates that row; one without inserts.\n * With `{ upsert: true }` each record is written as INSERT ... ON CONFLICT\n * DO UPDATE on the primary key instead, which is what makes a re-runnable\n * import idempotent.\n *\n * Batches are capped server-side (1000 rows by default) because one batch\n * holds its locks for the whole transaction — chunk larger jobs.\n *\n * Pass {@link WriteOptions.idempotencyKey} on anything that may be retried.\n * A client that never sees the response cannot know whether the batch\n * committed, and without a key the server cannot tell the retry from a\n * second genuine import — so it performs it again, duplicating every row in\n * the batch rather than just one.\n *\n * @returns The written rows, in the order given.\n *\n * @example\n * ```ts\n * for (const chunk of chunks(rows, 1000)) {\n * await client.data.products.createMany(chunk, { upsert: true });\n * }\n * ```\n */\n createMany(data: I[], options?: { upsert?: boolean; onConflict?: readonly string[] } & WriteOptions): Promise<M[]>;\n\n /**\n * Update an existing record by ID.\n * @param data The fields to update (the collection's `Update` shape).\n * @param options Per-request write options — notably `idempotencyKey`.\n * @returns The updated row.\n * @throws {RebaseApiError} with status 404 when the record does not exist.\n *\n * `create`, `createMany`, `updateMany`, `delete` and `deleteMany` all took\n * {@link WriteOptions}; this one did not, so the single-row update was the\n * one write on the surface that could not be made idempotent. A client that\n * never sees the response retries, and without a key the server cannot tell\n * that retry from a second deliberate edit — which on a `PATCH` that\n * increments or appends is a second edit applied.\n */\n update(id: string | number, data: U | UpdateValues<U>, options: WriteOptions & { returning: false }): Promise<M | undefined>;\n update(id: string | number, data: U | UpdateValues<U>, options?: WriteOptions): Promise<M>;\n\n /**\n * Insert the row, or replace the one already occupying its key.\n *\n * `INSERT ... ON CONFLICT DO UPDATE`, in one statement — so unlike a\n * `findById` followed by `create`-or-`update` it cannot lose the race\n * between the two, and unlike `create` it does not fail when the row is\n * already there. That is what makes a re-runnable import idempotent\n * without a key.\n *\n * The conflict target defaults to the primary key. Pass `onConflict` to\n * upsert on a natural key instead — `[\"email\"]`, `[\"tenant_id\", \"slug\"]` —\n * and the columns must carry a uniqueness guarantee the database can use:\n * a property with `validation.unique`, or the columns of a declared\n * `unique` index. Anything else is a 400 rather than a Postgres error\n * raised half-way through a transaction.\n *\n * Refused (400 `INVALID_CONFLICT_TARGET`) on the auth collection, whose\n * rows are the users: a create and an update of a user follow different\n * rules, and an upsert only learns which it is inside the write.\n *\n * The `on_create` timestamp of a row that already existed is left alone: a\n * conflict means the row's creation is a fact about the past, and a nightly\n * re-import that reset `createdAt` on everything it touched would take\n * every \"new this week\" query with it.\n *\n * @example\n * ```ts\n * await client.data.products.upsert(\n * { sku: \"LAMP-01\", name: \"Desk lamp\" },\n * { onConflict: [\"sku\"] }\n * );\n * ```\n */\n upsert(data: I, options: UpsertOptions & { returning: false }): Promise<M | undefined>;\n upsert(data: I, options?: UpsertOptions): Promise<M>;\n\n /**\n * Update many records in a single request and a single transaction.\n *\n * The counterpart to {@link createMany}, and the reason it exists is the\n * same: one call per row means one HTTP round trip and one transaction per\n * row. Every record still runs the normal pipeline — callbacks, relations,\n * row-level security — and the batch is all-or-nothing, so a rejected\n * record leaves none of them written and the error names the offending\n * index.\n *\n * Each entry is `{ id, data }` rather than a flat row carrying its own key.\n * That is deliberate: on a table keyed on something other than `id` — a\n * `sku`, a composite key — a flat row cannot say whether a column is the\n * address or a value to write. Naming the address separately mirrors\n * single-row `update(id, data)` exactly and leaves nothing to infer.\n *\n * An id that matches no row fails the batch with a 404 rather than being\n * skipped, for the same reason `update()` does: silently updating four of\n * five rows is worse than updating none.\n *\n * Batches share `createMany`'s server-side cap (1000 rows by default),\n * because one batch holds its locks for the whole transaction.\n *\n * Pass {@link WriteOptions.idempotencyKey} on anything that may be retried.\n * An update replayed in full is naturally idempotent, but one interleaved\n * with another writer's is not — the key is what stops a lost ACK from\n * re-applying a stale batch over newer data.\n *\n * @returns The updated rows, in the order given.\n *\n * @example\n * ```ts\n * await client.data.orders.updateMany([\n * { id: \"o-1\", data: { status: \"shipped\" } },\n * { id: \"o-2\", data: { status: \"shipped\" } }\n * ]);\n * ```\n */\n updateMany(updates: { id: string | number; data: U | UpdateValues<U> }[], options?: WriteOptions): Promise<M[]>;\n\n /**\n * Delete a record by ID.\n * @throws {RebaseApiError} with status 404 when the record does not exist.\n *\n * Takes {@link WriteOptions} like every other write. It did not, so the one\n * mutation that cannot be made safe by repeating it — a delete replayed\n * after the row is gone answers 404, which an offline queue reads as a\n * permanent failure — was also the one that could not carry an\n * `idempotencyKey`.\n */\n delete(id: string | number, options?: WriteOptions): Promise<void>;\n\n /**\n * Delete many records in a single request and a single transaction.\n *\n * Takes ids, not a filter. A filter-shaped bulk delete is a different and\n * far more dangerous operation — the failure mode is an omitted or\n * mistyped condition emptying a table, and it cannot be reviewed at the\n * call site the way an explicit list can. Read first, then pass the ids you\n * meant.\n *\n * `beforeDelete` and `afterDelete` fire per row, exactly as they do for\n * single deletes, and returning `false` from `beforeDelete` fails the batch\n * rather than quietly dropping one row from it. All-or-nothing, so an id\n * that matches no row 404s the whole call.\n *\n * Shares `createMany`'s row cap.\n *\n * @example\n * ```ts\n * const stale = await client.data.sessions.findAll({\n * where: { expires_at: [\"<\", cutoff] }\n * });\n * await client.data.sessions.deleteMany(stale.map(s => s.id as string));\n * ```\n */\n deleteMany(ids: (string | number)[], options?: WriteOptions): Promise<void>;\n\n /**\n * The low-level realtime subscription: raw server pushes, nothing else.\n *\n * **Prefer `observe()`** on a client from `@rebasepro/client`, which wraps\n * this one and is what a UI actually wants — it emits from the local\n * database first when offline is enabled, re-emits on local writes and\n * rollbacks, and de-duplicates emissions so a refresh that changes nothing\n * does not call back. `listen` does none of that; it forwards what the\n * socket sends.\n *\n * Always present. A client that cannot subscribe — one built with\n * `realtime: false`, or on a driver with no `listenCollection` — installs a\n * stub that throws a `RebaseClientError` naming the configuration that\n * would make it work. It used to be optional, which made every call site\n * either write `listen!(…)` or a null check the type system could not tell\n * apart from a real capability question; the answer to *that* question is\n * {@link isUnsupported}, and the answer for ordinary code is to just call\n * it.\n *\n * `observe()` degrades to a single fetch instead of throwing, which is the\n * other reason to reach for it instead.\n */\n listen(params: FindParams<M, Inc> | undefined, onUpdate: (response: FindResult<M>) => void, onError?: (error: Error) => void): () => void;\n\n /**\n * {@link listen} for a single row. Prefer `observeById()`. Every frame is\n * the row as `findById(id, options)` reads it, relations included.\n */\n listenById(id: string | number, onUpdate: (row: M | undefined) => void, onError?: (error: Error) => void, options?: ByIdReadOptions<Inc>): () => void;\n\n /**\n * The number of rows matching `params`, kept current: `onCount` is called\n * with the total once it is known, and again whenever a write changes it.\n * The live counterpart of {@link count} — a list's total, or a \"select all\n * N\" offer, that does not go stale while other people add and remove rows.\n * Only the filter (`where`, `logical`, `searchString`) counts; the window\n * and sort are ignored.\n *\n * Always present; on a client that cannot subscribe it is a stub that\n * throws, as {@link listen} is.\n */\n listenCount(params: FindParams<M, Inc> | undefined, onCount: (total: number) => void, onError?: (error: Error) => void): () => void;\n\n /**\n * The collection's trash: list, count, restore and purge the rows a soft\n * delete stamped. See {@link TrashClient}.\n */\n trash: TrashClient<M, Inc>;\n\n /**\n * Count the number of records matching the given filter.\n *\n * Always present; see {@link listen} for what a transport that cannot serve\n * it does instead.\n */\n count(params?: FindParams<M, Inc>): Promise<number>;\n\n /**\n * `count`/`sum`/`avg`/`min`/`max` over the matching rows, optionally\n * grouped — the SDK half of `GET /<collection>/aggregate`.\n *\n * The whole point is not to fetch rows in order to reduce them: \"revenue by\n * status\" over a million orders is one query and one row per status here,\n * and a `findAll()` plus a loop everywhere else — which is wrong under a\n * `limit` and unaffordable without one. It runs through the same\n * request-scoped handle as every other read, so RLS applies to the rows\n * being aggregated.\n *\n * ```ts\n * const rows = await rebase.data.orders.aggregate({\n * select: [{ fn: \"sum\", field: \"total\" }, { fn: \"count\" }],\n * groupBy: [\"status\"],\n * where: { created_at: [\">=\", startOfMonth] }\n * });\n * // [{ status: \"paid\", sum_total: 41822.5, count: 317 }, …]\n * ```\n *\n * Always present; a backend whose driver cannot aggregate answers 501\n * naming the capability rather than an empty result set, which would read\n * as \"nothing matched\".\n */\n aggregate(params: AggregateParams<M>): Promise<AggregateResult>;\n\n // Fluent Query Builder\n where<K extends keyof M & string, Op extends WhereFilterOp>(column: K, operator: Op, value: WhereValueFor<Op, M[K]>): SDKQueryBuilderInterface<M, Inc>;\n /** A relation path (`author.name`) or a JSON path (`metadata->>tier`). */\n where(column: NonColumnFieldPath, operator: WhereFilterOp, value: unknown): SDKQueryBuilderInterface<M, Inc>;\n where(logicalCondition: LogicalConditionFor<M>): SDKQueryBuilderInterface<M, Inc>;\n orderBy(\n column: FieldPath<M> | ComputedSortField | RelationAggregateSort,\n direction?: \"asc\" | \"desc\",\n nulls?: NullsPlacement\n ): SDKQueryBuilderInterface<M, Inc>;\n limit(count: number): SDKQueryBuilderInterface<M, Inc>;\n offset(count: number): SDKQueryBuilderInterface<M, Inc>;\n search(searchString: string, options?: { explain?: boolean }): SDKQueryBuilderInterface<M, Inc>;\n /**\n * Order rows by nearest-neighbour distance to `vector`, closest first.\n * Postgres only, over a `type: \"vector\"` property. See\n * {@link SDKQueryBuilderInterface.vectorSearch}.\n */\n vectorSearch(\n property: Extract<keyof M, string>,\n vector: number[],\n options?: { distance?: \"cosine\" | \"l2\" | \"inner_product\"; threshold?: number }\n ): SDKQueryBuilderInterface<M, Inc>;\n include(...relations: IncludeArgOf<Inc>[]): SDKQueryBuilderInterface<M, Inc>;\n /** {@link SDKQueryBuilderInterface.fields} */\n fields(...columns: Extract<keyof M, string>[]): SDKQueryBuilderInterface<M, Inc>;\n /** {@link SDKQueryBuilderInterface.distinct} */\n distinct(enabled?: boolean): SDKQueryBuilderInterface<M, Inc>;\n /** {@link SDKQueryBuilderInterface.after} */\n after(cursor: string): SDKQueryBuilderInterface<M, Inc>;\n}\n\n/**\n * The unified data access object for the **admin panel** (Entity-shaped).\n *\n * Access collections as dynamic properties: `data.products.find(...)`. Each\n * accessor returns `Entity`-wrapped records (`{ id, path, values }`) — the\n * view-model the admin renders. This is what `useData()` / the admin\n * `RebaseContext.data` are backed by.\n *\n * @internal App developers do **not** use this — they use\n * {@link RebaseSdkData} (flat rows), which is what the SDK client and backend\n * `context.data` expose. This Entity-shaped map backs the admin panel only.\n *\n * @group Data\n */\nexport type RebaseData<DB = unknown> = {\n /**\n * Get a collection accessor by slug.\n * Alternative to dynamic property access for cases where\n * the collection name is a variable.\n *\n * @example\n * const accessor = data.collection(\"products\");\n * await accessor.find({ limit: 10 });\n */\n collection<M extends Record<string, unknown> = Record<string, unknown>>(slug: string): CollectionAccessor<M>;\n} & (\n DB extends Record<string, unknown>\n ? { [K in keyof DB]: CollectionAccessor<DB[K] extends { Row: infer R extends Record<string, unknown> } ? R : Record<string, unknown>> }\n : {\n /**\n * Dynamic collection accessor.\n * Access any collection by its slug as a property.\n *\n * The index signature is `CollectionAccessor` alone, for the reason\n * spelled out on {@link RebaseSdkData}: unioning in the `collection`\n * method's own signature is unnecessary across an intersection, and it\n * costs `data.products.find()` — the access this `@example` documents.\n *\n * @example\n * data.products.find({ where: { status: [\"==\", \"published\"] } })\n */\n [collectionSlug: string]: CollectionAccessor;\n }\n);\n\n/**\n * The unified data access object for the **SDK** — flat rows, no Entity wrapper.\n *\n * This is the symmetric developer-facing data API, identical in shape on both\n * sides of the stack:\n * - The frontend SDK client (`client.data.products.find()`)\n * - Backend framework callbacks & scripts (`context.data.products.find()`)\n *\n * Every accessor returns flat rows (the table's columns) via\n * {@link SDKCollectionClient} — access fields directly (`row.title`), never\n * `row.values.title`. The admin uses {@link RebaseData} (Entity) instead.\n *\n * @example\n * // Frontend SDK\n * const { data: posts } = await client.data.posts.find();\n * console.log(posts[0].title); // flat — no .values\n *\n * // Backend callback — identical shape\n * callbacks: {\n * beforeSave: async ({ context }) => {\n * const product = await context.data.products.findById(id);\n * console.log(product?.price); // flat — no .values\n * }\n * }\n *\n * @group Data\n */\n/**\n * Extract the `Row` shape from a generated `Database[slug]` entry, falling\n * back to an open record when the entry is untyped.\n * @group Data\n */\nexport type RowOf<T> = T extends { Row: infer R extends Record<string, unknown> } ? R : Record<string, unknown>;\n\n/**\n * Extract the `Insert` shape from a generated `Database[slug]` entry (the\n * input accepted by `create`), falling back to `Partial<Row>`.\n * @group Data\n */\nexport type InsertOf<T> = T extends { Insert: infer I extends Record<string, unknown> } ? I : Partial<RowOf<T>>;\n\n/**\n * Extract the `Update` shape from a generated `Database[slug]` entry (the\n * input accepted by `update`), falling back to `Partial<Row>`.\n * @group Data\n */\nexport type UpdateOf<T> = T extends { Update: infer U extends Record<string, unknown> } ? U : Partial<RowOf<T>>;\n\n/**\n * The slugs a generated `Database` declares, from the `Slug` each entry\n * carries beside the accessor codegen chose for it.\n * @group Data\n */\nexport type SlugOf<DB> = {\n [K in keyof DB]: DB[K] extends { Slug: infer S extends string } ? S : never\n}[keyof DB];\n\n/** The generated `Database` entry whose `Slug` is `S`. */\ntype EntryOfSlug<DB, S extends string> = DB[{\n [K in keyof DB]: DB[K] extends { Slug: S } ? K : never\n}[keyof DB]];\n\n/**\n * `collection(slug)` for a slug the registered `Database` declares: the same\n * typed client as the slug's accessor (`collection(\"my-notes\")` is\n * `myNotes`). A slug it does not declare, or one computed at runtime, is\n * served by the untyped overload.\n */\ntype TypedSlugCollection<DB> = {\n collection<S extends SlugOf<DB>>(slug: S): SDKCollectionClient<\n RowOf<EntryOfSlug<DB, S>>,\n InsertOf<EntryOfSlug<DB, S>>,\n UpdateOf<EntryOfSlug<DB, S>>,\n IncludeOf<EntryOfSlug<DB, S>, DB>\n >;\n};\n\n/**\n * Note on the untyped branch below: its index signature is\n * `SDKCollectionClient`, NOT `SDKCollectionClient | ((slug: string) => …)`.\n *\n * The union looks like it is needed so `collection` — a method on this same\n * object — satisfies the index signature. It is not, because `collection` is\n * declared in a *separate* member of the intersection, and TypeScript only\n * requires named properties to be assignable to an index signature declared\n * alongside them. Including the function arm cost the documented accessor:\n *\n * rebase.dataAsAdmin.projects.find()\n * // ^ Property 'find' does not exist on type\n * // 'SDKCollectionClient | ((slug: string) => …)'\n *\n * Every project without a generated `Database` type lands on this branch, so\n * property-style access — the form used by the `@example` below, by the\n * scaffolded function template, and by the 0.13 migration note — did not\n * compile for any of them. Do not restore the arm; use `collection(slug)` if a\n * caller genuinely needs the by-slug function.\n */\nexport type RebaseSdkData<DB = unknown> = (\n // Typed first, so a literal slug the `Database` declares picks this\n // overload; any other string falls through to the untyped one below.\n unknown extends DB ? unknown : TypedSlugCollection<DB>\n) & {\n /**\n * Get a flat collection accessor by slug.\n *\n * @example\n * const accessor = data.collection(\"products\");\n * await accessor.find({ limit: 10 });\n */\n collection<M extends Record<string, unknown> = Record<string, unknown>>(slug: string): SDKCollectionClient<M>;\n} & (\n // `unknown extends DB` rather than `DB extends Record<string, unknown>`:\n // the generated `Database` is an `interface`, and an interface has no\n // implicit index signature, so it never satisfied `Record<string,\n // unknown>` and a registered `Database` silently fell to the untyped arm.\n unknown extends DB\n ? {\n /**\n * Dynamic flat collection accessor.\n * Access any collection by its slug as a property.\n *\n * @example\n * data.products.find({ where: { status: [\"==\", \"published\"] } })\n */\n [collectionSlug: string]: SDKCollectionClient;\n }\n : { [K in keyof DB]: SDKCollectionClient<RowOf<DB[K]>, InsertOf<DB[K]>, UpdateOf<DB[K]>, IncludeOf<DB[K], DB>> }\n);\n","import {\n DataType,\n Entity,\n EntityReference,\n EntityRelation,\n EntityStatus,\n EntityValues,\n Properties,\n Property\n} from \"@rebasepro/types\";\nimport { DEFAULT_ONE_OF_TYPE, DEFAULT_ONE_OF_VALUE } from \"./common\";\nimport { mergeDeep } from \"@rebasepro/utils\";\n\nexport function isPropertyBuilder(property?: Property) {\n return typeof property?.dynamicProps === \"function\";\n}\n\n/**\n * What a form opens with: a value for every property it can write.\n *\n * `excludeFromApi` columns are left out, and that is the whole of the rule —\n * they are not part of the API surface in either direction, so there is nothing\n * for a form to open showing and nothing it may send back. Including them was\n * not cosmetic: the baseline is what gets submitted, so a new record carried\n * `passwordHash: null` and `emailVerificationToken: null` into the create, and\n * the server refused the whole write with \"these columns are the server's to\n * set\" — the users collection could not be added to from the panel at all. The\n * fields were invisible on screen (`admin.disabled.hidden`), which is what made\n * the error read as being about the roles the operator *had* just edited.\n *\n * Server-side defaulting does not come through here: `applyDefaultValuesOnCreate`\n * asks each property for its own default, so an excluded column with a declared\n * `defaultValue` is still filled in on an in-process write.\n */\nexport function getDefaultValuesFor<M extends Record<string, unknown>>(properties: Properties): Partial<EntityValues<M>> {\n if (!properties) return {};\n return Object.entries(properties)\n .map(([key, property]) => {\n if (!property) return {};\n if ((property as Property).excludeFromApi) return {};\n const value = getDefaultValueFor(property);\n return value === undefined ? {} : { [key]: value };\n })\n .reduce((a, b) => ({ ...a,\n...b }), {}) as EntityValues<M>;\n}\n\nexport function getDefaultValueFor(property?: Property): unknown {\n if (!property) return undefined;\n if (isPropertyBuilder(property)) return undefined;\n // `defaultValue !== undefined`, not truthiness. The test used to be\n // `property.defaultValue || property.defaultValue === null`, which special-\n // cased exactly one falsy value and dropped the rest: `defaultValue: 0`\n // fell through to the per-type default and became `null`, `defaultValue: \"\"`\n // became `null`, and `defaultValue: false` survived only by coincidence\n // (the per-type default for a boolean is also `false`). A default of zero\n // is the most ordinary default a number column has.\n if (property.defaultValue !== undefined) {\n return property.defaultValue;\n } else if (property.type === \"map\" && property.properties) {\n const defaultValuesFor = getDefaultValuesFor(property.properties as Properties);\n if (Object.keys(defaultValuesFor).length === 0) return undefined;\n return defaultValuesFor;\n } else {\n return getDefaultValueFortype(property.type);\n }\n}\n\nexport function getDefaultValueFortype(type: DataType): unknown {\n if (type === \"string\") {\n return null;\n } else if (type === \"number\") {\n return null;\n } else if (type === \"boolean\") {\n return false;\n } else if (type === \"date\") {\n return null;\n } else if (type === \"array\") {\n return [];\n } else if (type === \"map\") {\n return {};\n } else if (type === \"vector\") {\n return null;\n } else if (type === \"binary\") {\n return null;\n } else {\n return null;\n }\n}\n\n/**\n * Update the automatic values in a entity before save\n * @group Driver\n */\nexport function updateDateAutoValues<M extends Record<string, unknown>>({\n inputValues,\n properties,\n status,\n timestampNowValue\n}:\n {\n inputValues: Partial<EntityValues<M>>,\n properties: Properties,\n status: EntityStatus,\n timestampNowValue: unknown\n }): EntityValues<M> {\n return traverseValuesProperties(\n inputValues,\n properties,\n (inputValue, property) => {\n if (property.type === \"date\") {\n if (status === \"existing\" && property.autoValue === \"on_update\") {\n return timestampNowValue;\n } else if ((status === \"new\" || status === \"copy\") &&\n (property.autoValue === \"on_update\" || property.autoValue === \"on_create\")) {\n return timestampNowValue;\n } else {\n return inputValue;\n }\n } else {\n return inputValue;\n }\n }\n ) ?? {} as M;\n}\n\n/**\n * Stamp the acting user's uid into the `user_on_create` / `user_on_update`\n * columns a collection declares.\n *\n * A deliberate sibling of {@link updateDateAutoValues} rather than another\n * branch inside it. The two share a shape and nothing else: one takes an\n * instant the server generates and the other takes an identity the request\n * carries, so overloading the timestamp function would have meant threading a\n * second, unrelated argument through every one of its callers and letting a\n * `date` property and a `string` property compete for the same `autoValue`\n * union. Called side by side in the driver.\n *\n * The stamped value overwrites whatever arrived in the body. A caller who can\n * set `createdBy` is a caller who can attribute their write to somebody else,\n * which is the one thing an audit column must not allow.\n *\n * `uid` is `undefined` for an anonymous request, a service token or an\n * in-process write; the column is set to an explicit `null` there. Explicit\n * matters on an update: leaving the key absent would keep whatever uid the\n * column already held, so an anonymous edit would be recorded as the previous\n * editor's. Refusing that write outright is `required`'s job, not this\n * function's — see `assertWriteValuesValid`.\n *\n * Top-level properties only, deliberately, unlike {@link updateDateAutoValues}.\n * `traverseValuesProperties` cannot express \"set this key to null\" — a `null`\n * from its operation means \"leave the key out\" — and an audit column nested\n * inside a `map` is not a column at all, so there is nothing down there to\n * stamp.\n *\n * @group Driver\n */\nexport function updateUserAutoValues<M extends Record<string, unknown>>({\n inputValues,\n properties,\n status,\n uid\n}:\n {\n inputValues: Partial<EntityValues<M>>,\n properties: Properties,\n status: EntityStatus,\n uid: string | undefined\n }): EntityValues<M> {\n const result = { ...(inputValues ?? {}) } as Record<string, unknown>;\n for (const [key, property] of Object.entries(properties ?? {})) {\n const prop = property as (Property & { autoValue?: string }) | undefined;\n if (!prop || prop.type !== \"string\") continue;\n const autoValue = prop.autoValue;\n if (autoValue !== \"user_on_create\" && autoValue !== \"user_on_update\") continue;\n // `user_on_create` says nothing about an update: the column holds the\n // creator's uid and this write is not rewriting it.\n if (status === \"existing\" && autoValue === \"user_on_create\") continue;\n // A copy is a new row and gets a new author, exactly as it gets a new\n // `created_on`.\n result[key] = uid ?? null;\n }\n return result as EntityValues<M>;\n}\n\n/**\n * Fill in the `defaultValue`s a create left unset.\n *\n * `defaultValue` was read by exactly one thing: the Studio's form, which uses it\n * to prefill inputs. Every other way into the same collection — the REST create,\n * the SDK, the socket, an import — stored whatever arrived and nothing where the\n * key was absent. So `active: { type: \"boolean\", defaultValue: true }` produced\n * rows with `active` unset through the API and `true` through the panel, from\n * one declaration that reads like a promise about the data.\n *\n * Only genuinely absent keys are filled. An explicit `null` is a caller saying\n * \"no value\", which is a different statement from not mentioning the field, and\n * overwriting it would make the default impossible to opt out of. The exception\n * is a `databaseDefault` column, whose `null` is taken out of the create so the\n * database's own DEFAULT fills it.\n *\n * `getDefaultValuesFor` also invents a per-type default for properties with no\n * `defaultValue` at all (`false` for a boolean, `[]` for an array, `null` for\n * the rest) — right for a form, which must render *something* in every input,\n * and wrong here, where an absent key must stay absent so the column's own\n * DEFAULT applies. Only declared defaults are taken.\n *\n * @param values the caller's payload\n * @param properties the collection's declared properties\n * @group Driver\n */\nexport function applyDefaultValuesOnCreate<M extends Record<string, unknown>>(\n values: Partial<EntityValues<M>> | undefined,\n properties: Properties\n): Partial<EntityValues<M>> {\n if (!properties) return values ?? {};\n const result = { ...(values ?? {}) } as Record<string, unknown>;\n\n for (const [key, property] of Object.entries(properties)) {\n if (!property) continue;\n // The database fills this column, and only when the INSERT leaves it\n // out: a `null` (what a form sends for a field nobody filled in) would\n // be stored as NULL, or refused by the column's NOT NULL.\n if (!isPropertyBuilder(property) && (property as Property).databaseDefault && result[key] === null) {\n delete result[key];\n continue;\n }\n const declared = declaresDefault(property as Property);\n if (!declared) continue;\n // Asked of the property, not read out of `getDefaultValuesFor`: that\n // one answers for a *form*, and leaves out the columns the API excludes.\n // A server-owned column with a declared default is still defaulted here.\n const defaultValue = getDefaultValueFor(property as Property);\n if (result[key] !== undefined) {\n // A map whose own sub-properties carry defaults is filled in\n // field by field, so `{ notify: false }` keeps `notify` and still\n // gains the siblings it did not mention.\n if ((property as Property).type === \"map\" &&\n (property as Property & { defaultValue?: unknown }).defaultValue === undefined &&\n isPlainObject(result[key])) {\n result[key] = {\n ...(defaultValue as Record<string, unknown> ?? {}),\n ...(result[key] as Record<string, unknown>)\n };\n }\n continue;\n }\n if (defaultValue !== undefined) result[key] = defaultValue;\n }\n return result as Partial<EntityValues<M>>;\n}\n\n/** Does this property, or something nested under it, state a `defaultValue`? */\nfunction declaresDefault(property: Property): boolean {\n if (isPropertyBuilder(property)) return false;\n if (property.defaultValue !== undefined) return true;\n if (property.type === \"map\" && property.properties) {\n return Object.values(property.properties as Properties)\n .some(child => child && declaresDefault(child as Property));\n }\n return false;\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/**\n * Add missing required fields, expected in the collection, to the values of a entity\n * @param values\n * @param properties\n * @group Driver\n */\nexport function sanitizeData<M extends Record<string, unknown>>\n (\n values: EntityValues<M>,\n properties: Properties\n ) {\n const result = values as Record<string, unknown>;\n Object.entries(properties)\n .forEach(([key, property]) => {\n if (values && values[key] !== undefined) result[key] = values[key];\n else if ((property as Property).validation?.required) result[key] = null;\n });\n return result;\n}\n\n/**\n * A reference to `entity`.\n *\n * @param path where the reference points, when that is not the path the\n * entity is addressed by. A Firestore or MongoDB collection may declare a\n * `path` for its store other than its slug, and a reference has to carry the\n * stored one, where `entity.path` is the path the admin addresses it by.\n */\nexport function getReferenceFrom<M extends Record<string, unknown>>(entity: Entity<M>, path: string = entity.path): EntityReference {\n if (typeof entity.id !== \"string\")\n throw new Error(\"Only string IDs are supported in references\");\n return new EntityReference({\n id: entity.id,\n path,\n driver: entity.driver,\n databaseId: entity.databaseId\n });\n}\n\nexport function getRelationFrom<M extends Record<string, unknown>>(entity: Entity<M>): EntityRelation {\n return new EntityRelation(entity.id, entity.path, entity as unknown as Record<string, unknown>);\n}\n\n/**\n * Normalize a value into a proper EntityRelation instance.\n * Handles EntityRelation class instances, and plain objects\n * with `__type === \"relation\"` or an `isEntityRelation()` method.\n *\n * When `propertyType` is `\"relation\"`, also accepts plain objects that\n * have `id` and `path` fields — these are relation-shaped objects from\n * edge cases in the data pipeline (REST fallback, stale cache, custom data source).\n *\n * When `targetPath` is given, also accepts a bare id. A relation column is a\n * foreign key, and the REST layer returns it as the scalar it is; only some\n * fetch paths hydrate it into an object. Which form a caller sees therefore\n * depends on how the row was loaded, and a caller that only accepted objects\n * reported half of its own data as a type error. The declared target is the\n * missing half: with it, an id is a relation that has not been fetched yet.\n *\n * Returns null if the value cannot be coerced.\n */\nexport function normalizeToEntityRelation(value: unknown, propertyType?: string, targetPath?: string): EntityRelation | null {\n if (value instanceof EntityRelation) return value;\n\n if (targetPath && (typeof value === \"string\" || typeof value === \"number\")) {\n // An empty string is an unset foreign key, not row \"\".\n if (value === \"\") return null;\n return new EntityRelation(value, targetPath);\n }\n\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return null;\n\n const obj = value as Record<string, unknown>;\n const isRelationLike =\n obj.__type === \"relation\" ||\n obj.__type === \"reference\" ||\n (typeof obj.isEntityRelation === \"function\" && (obj.isEntityRelation as () => boolean)()) ||\n (typeof obj.isEntityReference === \"function\" && (obj.isEntityReference as () => boolean)()) ||\n (propertyType === \"relation\" && typeof obj.id !== \"undefined\" && typeof obj.path === \"string\");\n\n if (!isRelationLike) return null;\n\n return new EntityRelation(\n obj.id as string | number,\n obj.path as string,\n obj.data as Record<string, unknown> | undefined\n );\n}\n\nexport function traverseValuesProperties<M extends Record<string, unknown>>(\n inputValues: Partial<EntityValues<M>>,\n properties: Properties,\n operation: (value: unknown, property: Property) => unknown\n): EntityValues<M> | undefined {\n // Handle null/undefined inputValues - use empty object as base for mergeDeep\n const safeInputValues = inputValues ?? {};\n\n const updatedValues = Object.entries(properties)\n .map(([key, property]) => {\n const inputValue = safeInputValues && (safeInputValues)[key];\n const updatedValue = traverseValueProperty(inputValue, property as Property, operation);\n if (updatedValue === null) return null;\n if (updatedValue === undefined) return undefined;\n return ({ [key]: updatedValue });\n })\n .reduce((a, b) => ({ ...a,\n...b }), {}) as EntityValues<M>;\n // Use mergeDeep to preserve class instances like EntityReference, GeoPoint\n const result = mergeDeep(safeInputValues, updatedValues);\n if (!result || Object.keys(result).length === 0) return undefined;\n return result;\n}\n\nexport function traverseValueProperty(inputValue: unknown,\n property: Property,\n operation: (value: unknown, property: Property) => unknown): unknown {\n\n let value;\n if (property.type === \"map\" && property.properties) {\n value = traverseValuesProperties(inputValue as Partial<Record<string, unknown>>, property.properties, operation);\n } else if (property.type === \"array\") {\n const of = property.of;\n if (of && Array.isArray(inputValue) && !Array.isArray(of)) {\n value = inputValue.map((e) => traverseValueProperty(e, of, operation));\n } else if (of && Array.isArray(inputValue) && Array.isArray(of)) {\n value = inputValue.map((e, i) => {\n if (i < of.length)\n return traverseValueProperty(e, of[i], operation);\n return null\n }).filter(Boolean);\n } else if (property.oneOf && Array.isArray(inputValue)) {\n const typeField = property.oneOf?.typeField ?? DEFAULT_ONE_OF_TYPE;\n const valueField = property.oneOf?.valueField ?? DEFAULT_ONE_OF_VALUE;\n value = inputValue.map((e) => {\n if (e === null) return null;\n if (typeof e !== \"object\") return e;\n const rec = e as Record<string, unknown>;\n const type = rec[typeField] as string;\n const childProperty = property.oneOf?.properties[type];\n if (!type || !childProperty) return e;\n return {\n [typeField]: type,\n [valueField]: traverseValueProperty(rec[valueField], childProperty, operation)\n };\n });\n } else {\n value = inputValue;\n }\n } else {\n value = operation(inputValue, property);\n }\n\n return value;\n}\n\n/**\n * Relation reference types used throughout the server layer.\n * These replace the 50+ manual `{ id, path, __type: \"relation\" }` constructions.\n */\nexport interface RelationRef {\n readonly id: string | number;\n readonly path: string;\n readonly __type: \"relation\";\n}\n\nexport interface RelationRefWithData extends RelationRef {\n readonly data: Entity;\n}\n\n/**\n * Create a lightweight relation stub for admin views.\n * Replaces inline `{ id, path, __type: \"relation\" }` object literals.\n */\nexport function createRelationRef(id: string | number, path: string): RelationRef {\n return { id,\npath,\n__type: \"relation\" };\n}\n\n/**\n * A row snapshot with every relation in it reduced to its reference — target\n * path and id, `__type` kept — and the related row's values dropped.\n *\n * What a history entry stores and serves. The history route is gated on\n * reading the row, not the rows it points at, so a snapshot that embedded the\n * admin's `{ __type: \"relation\", data: { values } }` envelope handed whoever\n * may read `posts` history the `authors` columns too. Entries are written as\n * the stored row now, which holds no envelope; this keeps it that way for any\n * path that hands one in, and reads entries recorded before.\n *\n * Walks maps and arrays, since a relation can sit inside either. Anything that\n * is not an envelope comes back as it was.\n */\nexport function withRelationReferencesOnly<T>(value: T): T {\n if (Array.isArray(value)) return value.map(item => withRelationReferencesOnly(item)) as T;\n if (value === null || typeof value !== \"object\" || value instanceof Date) return value;\n const obj = value as Record<string, unknown>;\n if (obj.__type === \"relation\" || obj.__type === \"reference\") {\n const { data: _related, ...reference } = obj;\n return reference as T;\n }\n if (Object.getPrototypeOf(obj) !== Object.prototype && Object.getPrototypeOf(obj) !== null) return value;\n return Object.fromEntries(\n Object.entries(obj).map(([key, item]) => [key, withRelationReferencesOnly(item)])\n ) as T;\n}\n\n/**\n * Create a hydrated relation reference that includes the full entity data.\n * Used when entity data has been pre-fetched (e.g., via batch loading or JOINs).\n */\nexport function createRelationRefWithData(id: string | number, path: string, data: Entity): RelationRefWithData {\n return { id,\npath,\n__type: \"relation\",\ndata };\n}\n","import type { NullsPlacement, OrderBySortTuple, OrderBySpec, OrderByTuple } from \"@rebasepro/types\";\nimport { isRelationAggregateSort, sortKeyToString } from \"@rebasepro/types\";\n\n/**\n * Sort-order wire codec.\n *\n * This is the ONLY module that knows about the colon-delimited wire format\n * (`\"field:direction\"`) used in HTTP query parameters, and about the JSON-array\n * form that carries a multi-column sort over the same parameter.\n * Everything else speaks {@link OrderByTuple} exclusively.\n *\n * Mirrors the filter architecture in `filter-dialect.ts`.\n *\n * @module\n */\n\n/**\n * Collapse the one-key and many-key spellings of a sort into the list form.\n *\n * `[\"a\", \"desc\"]` and `[[\"a\", \"desc\"]]` mean the same thing and normalize to\n * the same value; the two are told apart by whether the first element is\n * itself an array, which no field name ever is.\n *\n * This is also where a {@link RelationAggregateSort} object stops being an\n * object. Above this function a sort key may be either spelling; below it,\n * every key is a string — which is what `OrderByTuple`, the REST parameter, the\n * driver contract and the cursor all already were. Doing it here means the one\n * place that already collapses the two *shapes* of a sort also collapses the\n * two *spellings* of a key, rather than every consumer learning about both.\n *\n * @returns The keys in order of significance, or `undefined` for no sort. An\n * empty list also returns `undefined` — \"sort by nothing\" is no sort, and\n * letting `[]` through would have every layer below re-deciding what it meant.\n */\nexport function normalizeOrderBy(orderBy?: OrderBySpec): OrderByTuple[] | undefined {\n if (!orderBy || orderBy.length === 0) return undefined;\n // An aggregate key is an object, so the first element being an array still\n // tells the list form from the single-tuple one — no field name is an\n // array, and neither is an aggregate key.\n const list = Array.isArray(orderBy[0])\n ? orderBy as OrderBySortTuple[]\n : [orderBy as OrderBySortTuple];\n if (list.length === 0) return undefined;\n // Through `toStrictTuple`, not a destructure. `([key, direction]) => …` over\n // whatever it was handed is only safe for a caller the types checked, and\n // this is reached straight from `find({ orderBy })` — where the plausible\n // mistakes are an object (`{ title: \"asc\" }`, which is how every other\n // query API spells a sort) and a bare number. Both used to come back as\n // `TypeError: object is not iterable`, from a package the caller has never\n // heard of, with no `code` and no field name, while the same call's `where`\n // clause answers with a `RebaseClientError` naming the field and the fix.\n return list.map((entry, index) => toStrictTuple(entry, index));\n}\n\n/**\n * The most significant sort key, for a caller that can only express one —\n * a column header's arrow, a URL parameter, a driver that has not been taught\n * the list form.\n */\nexport function primaryOrderBy(orderBy?: OrderBySpec): OrderByTuple | undefined {\n return normalizeOrderBy(orderBy)?.[0];\n}\n\n/**\n * Collapse the driver-level `{orderBy, order}` pair into the list form.\n *\n * The driver contract spells a single-column sort as a field name plus a\n * separate direction, and a multi-column one as a list of tuples that leaves\n * `order` meaningless. Every driver reads both through here so neither\n * spelling has to be handled twice.\n *\n * An absent direction means ascending — the same thing a bare `?orderBy=name`\n * has always meant over HTTP. The Postgres driver used to read the same pair as\n * *descending* while Mongo read it as ascending, so one field name and no\n * direction described two different queries depending on which database was\n * underneath. Neither had a caller: every path in the workspace passes a\n * direction, which is why the disagreement went unnoticed rather than being\n * load-bearing.\n */\nexport function normalizeDriverOrderBy(\n orderBy?: string | OrderByTuple[],\n order?: \"asc\" | \"desc\"\n): OrderByTuple[] | undefined {\n if (!orderBy) return undefined;\n if (typeof orderBy === \"string\") return [[orderBy, order === \"desc\" ? \"desc\" : \"asc\"]];\n return orderBy.length > 0 ? orderBy : undefined;\n}\n\n/** A sort whose *shape* is unusable, as opposed to one naming a field that does not exist. */\nexport class OrderBySpecError extends Error {\n readonly code = \"INVALID_ORDER_BY\";\n constructor(detail: string) {\n super(\n `Invalid \\`orderBy\\`: ${detail}. Expected a field name, or a list of ` +\n \"[field, direction] pairs like [[\\\"roles\\\",\\\"asc\\\"],[\\\"created_at\\\",\\\"desc\\\"]]\"\n );\n this.name = \"OrderBySpecError\";\n }\n}\n\n/**\n * Validate an `orderBy` that arrived from outside this process — a WebSocket\n * subscribe frame, a driver call from untyped JavaScript — and return it in the\n * list form.\n *\n * Strict on purpose, in the same way the REST `parseOrderByParam` is: the\n * failure mode for a shape nobody checks is not a crash but a *silently\n * different query*. A malformed entry read as a field name resolves to no\n * column, and under the lenient unknown-field mode the sort is then dropped and\n * the rows come back in whatever order the database pleased — sorted, as far as\n * the subscriber can tell, by whatever they asked for.\n */\nexport function parseOrderBySpecStrict(raw: unknown, order?: \"asc\" | \"desc\"): OrderByTuple[] | undefined {\n if (raw === undefined || raw === null || raw === \"\") return undefined;\n // The string spelling is the driver contract's, so it takes its direction\n // from the same companion `order` — and defaults the same way it does.\n if (typeof raw === \"string\") return normalizeDriverOrderBy(raw, order);\n if (!Array.isArray(raw) || raw.length === 0) {\n throw new OrderBySpecError(`${typeof raw} is not a field name or a list of sort keys`);\n }\n\n // The single-tuple spelling, `[\"created_at\", \"desc\"]` — or the same shape\n // with an aggregate key in place of the field name.\n if (typeof raw[0] === \"string\" || isRelationAggregateSort(raw[0])) return [toStrictTuple(raw, 0)];\n\n return raw.map(toStrictTuple);\n}\n\n/** `first`/`last`, or a refusal naming the entry — see {@link NullsPlacement}. */\nfunction toStrictNulls(raw: unknown, index: number): NullsPlacement | undefined {\n if (raw === undefined || raw === null) return undefined;\n if (raw !== \"first\" && raw !== \"last\") {\n throw new OrderBySpecError(\n `entry ${index} has nulls '${String(raw)}' — expected \"first\" or \"last\"`\n );\n }\n return raw;\n}\n\nfunction toStrictTuple(raw: unknown, index: number): OrderByTuple {\n if (!Array.isArray(raw)) {\n throw new OrderBySpecError(`entry ${index} has no field name`);\n }\n // The object spelling of an aggregate key, from an untyped caller that did\n // not go through `normalizeOrderBy`. Encoded rather than refused: it is a\n // sort this understands, and rejecting the shape a typed caller writes\n // would be a distinction between the two spellings that nothing else makes.\n const key = isRelationAggregateSort(raw[0]) ? sortKeyToString(raw[0]) : raw[0];\n if (typeof key !== \"string\" || key.trim() === \"\") {\n throw new OrderBySpecError(`entry ${index} has no field name`);\n }\n const direction = raw[1];\n if (direction !== undefined && direction !== \"asc\" && direction !== \"desc\") {\n throw new OrderBySpecError(`entry ${index} has direction '${String(direction)}'`);\n }\n const nulls = toStrictNulls(raw[2], index);\n // Omitted rather than defaulted: absent means \"the direction's convention\",\n // and writing one in here would make an explicit `NULLS LAST` on a\n // descending key indistinguishable from having said nothing — which the\n // keyset comparison and the ORDER BY both have to agree about.\n return nulls ? [key, direction ?? \"asc\", nulls] : [key, direction ?? \"asc\"];\n}\n\n/**\n * Serialize a sort to the wire.\n *\n * A single key keeps the `\"field:direction\"` shorthand it has always used —\n * short, readable in a URL, and what every existing client and test expects.\n * Several keys are emitted as the canonical JSON array the server already\n * accepts, because the shorthand has no separator to spare: a comma-joined\n * `\"a:asc,b:desc\"` parses as one field named `a` with the direction\n * `\"asc,b:desc\"`, which the server refuses.\n *\n * **Runtime tolerance:** if the input is already a well-formed wire string\n * (from an untyped JS caller), it is returned unchanged.\n * This is undocumented tolerance, not public API — don't rely on it.\n *\n * @param orderBy - A canonical tuple or list of tuples, or at runtime\n * possibly a pre-serialized string (undocumented tolerance).\n * @returns The wire-format string, or `undefined` if the input is falsy.\n *\n * @remarks\n * Field names containing `:` are representable in the tuple form but\n * **not** in the single-key wire encoding — this is an inherent limitation of\n * the colon-delimited shorthand and is not resolved here.\n */\nexport function serializeOrderBy(orderBy?: OrderBySpec | string): string | undefined {\n if (!orderBy) return undefined;\n // Runtime tolerance: pass through a pre-serialized wire string unchanged.\n if (typeof orderBy === \"string\") return orderBy;\n // `normalizeOrderBy` has already encoded any aggregate key to its string\n // spelling, which is why the shorthand below can assume a string: neither\n // `min(applications.created_at)` nor `count(applications)` contains a `:`.\n const list = normalizeOrderBy(orderBy);\n if (!list) return undefined;\n // `field:direction:nulls` — the third segment appears only when the key\n // asked for a placement, so every sort written before nulls existed still\n // serializes to exactly the string it always did.\n if (list.length === 1) {\n const [field, direction, nulls] = list[0];\n return nulls ? `${field}:${direction}:${nulls}` : `${field}:${direction}`;\n }\n return JSON.stringify(list.map(([field, direction, nulls]) => (nulls\n ? { field, direction, nulls }\n : { field, direction })));\n}\n\n/**\n * Deserialize a wire-format `\"field:direction\"` string into an {@link OrderByTuple}.\n *\n * Lenient parsing:\n * - Bare field name (no colon): `\"name\"` → `[\"name\", \"asc\"]`\n * - Unknown direction: `\"name:foo\"` → `[\"name\", \"asc\"]`\n * - Empty / falsy input, or a blank field name: → `undefined`\n *\n * The leniency is this end's alone; the *server* refuses the same value. This\n * used to say \"matches existing server behaviour\", and it stopped being true\n * when `parseOrderByParam` grew a strict direction check: `?orderBy=name:foo`\n * now answers `400 INVALID_ORDER_BY` (\"entry 0 has direction 'foo'\"). The split\n * is deliberate — see {@link parseOrderBySpecStrict} — because a value this\n * function is handed was produced by {@link serializeOrderBy} a moment earlier,\n * and one that reaches the server came from a stranger.\n *\n * A blank field is `undefined` rather than `[\" \", \"asc\"]`: whitespace is not a\n * field name, and the tuple it used to produce could not be re-encoded — the\n * only value in this codec that survived a decode and failed the next encode.\n *\n * Reads the single-key shorthand only. For a value that may carry several keys,\n * use {@link deserializeOrderByList} — handed a JSON array this returns the\n * whole array as one nonsensical field name.\n *\n * @param raw - The wire-format string from an HTTP query parameter.\n * @returns The canonical tuple, or `undefined` if the input names no field.\n */\nexport function deserializeOrderBy(raw?: string): OrderByTuple | undefined {\n if (!raw) return undefined;\n const idx = raw.indexOf(\":\");\n if (idx === -1) return raw.trim() === \"\" ? undefined : [raw, \"asc\"];\n const field = raw.slice(0, idx);\n if (field.trim() === \"\") return undefined;\n const rest = raw.slice(idx + 1);\n // `field:direction:nulls`. The nulls segment is optional, and — leniently,\n // as everything else on this end of the codec is — anything that is not\n // \"first\"/\"last\" is read as \"unspecified\" rather than refused. The *server*\n // end (`parseOrderByParam`) refuses it, for the reason in the docblock.\n const nullsIdx = rest.indexOf(\":\");\n const dir = nullsIdx === -1 ? rest : rest.slice(0, nullsIdx);\n const nulls = nullsIdx === -1 ? undefined : rest.slice(nullsIdx + 1);\n const direction = dir === \"desc\" ? \"desc\" : \"asc\";\n return nulls === \"first\" || nulls === \"last\"\n ? [field, direction, nulls]\n : [field, direction];\n}\n\n/**\n * Deserialize either wire spelling — the single-key shorthand or the JSON\n * array — into the list form.\n *\n * Lenient in the same way {@link deserializeOrderBy} is: this is the client end\n * of the codec, where the value was produced by {@link serializeOrderBy} a\n * moment earlier. The *server* end parses the same shapes strictly, in\n * `parseOrderByParam`, because there the value came from a stranger and a\n * direction it cannot read has to be refused rather than quietly turned into\n * `\"asc\"`.\n */\nexport function deserializeOrderByList(raw?: string): OrderByTuple[] | undefined {\n if (!raw) return undefined;\n const trimmed = raw.trim();\n if (trimmed.startsWith(\"[\")) {\n try {\n const parsed = JSON.parse(trimmed);\n if (Array.isArray(parsed)) {\n const list = parsed\n .map((entry): OrderByTuple | undefined => {\n if (typeof entry === \"string\") return deserializeOrderBy(entry);\n if (entry && typeof entry === \"object\" && typeof entry.field === \"string\") {\n const direction = entry.direction === \"desc\" ? \"desc\" : \"asc\";\n return entry.nulls === \"first\" || entry.nulls === \"last\"\n ? [entry.field, direction, entry.nulls]\n : [entry.field, direction];\n }\n return undefined;\n })\n .filter((entry): entry is OrderByTuple => entry !== undefined);\n return list.length > 0 ? list : undefined;\n }\n } catch {\n // Not JSON after all — fall through to the shorthand, which is what\n // a field name that merely begins with \"[\" would be.\n }\n }\n const single = deserializeOrderBy(trimmed);\n return single ? [single] : undefined;\n}\n","import { MAX_INCLUDE_DEPTH } from \"@rebasepro/types\";\nimport type { FilterValues, IncludeOptions, IncludeSpec, LogicalCondition, OrderByTuple } from \"@rebasepro/types\";\nimport { deserializeOrderByList, normalizeOrderBy } from \"./sort-dialect\";\n\n/**\n * The `include` codec: one shape, whatever spelling it arrived in.\n *\n * `include` reaches the driver by four routes — the REST `?include=` parameter,\n * a WebSocket subscribe frame, the SDK's `include(...)`, and the admin panel's\n * \"all relations\" — and each used to hand the driver something slightly\n * different. This normalises all four to one tree, so the fetch pipeline has a\n * single thing to read and `find()`, `findById()` and `listen()` cannot disagree\n * about what \"include the author\" means.\n *\n * @module\n */\n\n/**\n * One relation to load, and how.\n *\n * `children` is the nesting: `comments.author` is a `comments` node with an\n * `author` child. Every other field narrows the rows *of this relation* — the\n * same knobs a top-level query has, which is the point.\n */\nexport interface IncludeNode {\n /** Rows to load per parent row. */\n limit?: number;\n /** Filter over the related rows. */\n where?: FilterValues<string>;\n /** An `and`/`or`/`not` group over the related rows. */\n logical?: LogicalCondition;\n /** Sort for the related rows. */\n orderBy?: OrderByTuple[];\n /** Columns of the related row to return. */\n fields?: string[];\n /** Serve the per-parent total under the row's `_count` too. */\n count?: boolean;\n /** Relations of the related row, loaded in turn. */\n children: Record<string, IncludeNode>;\n}\n\n/**\n * A whole `include` request: the tree, plus whether the caller asked for\n * *every* relation.\n *\n * The wildcard is kept as a flag rather than expanded into names here, because\n * expanding it needs the collection — which this package does not have. The\n * driver expands it against the relations it actually resolved.\n */\nexport interface NormalizedInclude {\n /** `include=*` — every relation of the collection, one hop deep. */\n wildcard: boolean;\n /** The named relations. Empty when `wildcard` is set alone. */\n tree: Record<string, IncludeNode>;\n}\n\n/** An `include` that cannot be read, as opposed to one naming a relation that does not exist. */\nexport class IncludeSpecError extends Error {\n readonly code: string;\n constructor(detail: string, code = \"INVALID_INCLUDE\") {\n super(`Invalid \\`include\\`: ${detail}`);\n this.name = \"IncludeSpecError\";\n this.code = code;\n Object.setPrototypeOf(this, IncludeSpecError.prototype);\n }\n}\n\nconst emptyNode = (): IncludeNode => ({ children: {} });\n\nfunction ensureNode(tree: Record<string, IncludeNode>, key: string): IncludeNode {\n return (tree[key] ??= emptyNode());\n}\n\n/**\n * Merge one dotted path (`\"comments.author\"`) into a tree.\n *\n * Merging rather than assigning is what makes `include=comments,comments.author`\n * mean the same thing as `include=comments.author`: the second path deepens the\n * node the first created instead of replacing it and losing its options.\n */\nfunction addPath(tree: Record<string, IncludeNode>, path: string): void {\n const segments = path.split(\".\").map(s => s.trim()).filter(Boolean);\n if (segments.length === 0) return;\n if (segments.length > MAX_INCLUDE_DEPTH) {\n throw new IncludeSpecError(\n `\"${path}\" nests ${segments.length} relations deep; the limit is ${MAX_INCLUDE_DEPTH}. ` +\n \"Each hop is another query, and an unbounded one walks a self-referencing relation forever.\",\n \"INCLUDE_TOO_DEEP\"\n );\n }\n let level = tree;\n for (const segment of segments) {\n level = ensureNode(level, segment).children;\n }\n}\n\nfunction normalizeOptions(key: string, options: IncludeOptions, depth: number): IncludeNode {\n if (depth > MAX_INCLUDE_DEPTH) {\n throw new IncludeSpecError(\n `\"${key}\" nests more than ${MAX_INCLUDE_DEPTH} relations deep.`,\n \"INCLUDE_TOO_DEEP\"\n );\n }\n if (options.limit !== undefined\n && (!Number.isInteger(options.limit) || options.limit < 1)) {\n throw new IncludeSpecError(\n `\"${key}\" has limit ${JSON.stringify(options.limit)} — expected a whole number of 1 or more.`\n );\n }\n if (options.count !== undefined && typeof options.count !== \"boolean\") {\n throw new IncludeSpecError(\n `\"${key}\" has count ${JSON.stringify(options.count)} — expected \\`true\\` or \\`false\\`.`\n );\n }\n const node: IncludeNode = { children: {} };\n if (options.limit !== undefined) node.limit = options.limit;\n if (options.count) node.count = true;\n if (options.where) node.where = options.where;\n if (options.logical) node.logical = options.logical;\n if (options.fields && options.fields.length > 0) node.fields = [...options.fields];\n // The same two spellings the top-level `?orderBy=` accepts: the\n // `field:direction[:nulls]` shorthand a caller writes into a query string,\n // and the tuple form a typed caller writes in code. Accepting only the\n // tuples made the JSON include form — the one that exists *because* it\n // travels over a query string — unable to express the shorthand beside it.\n const orderBy = typeof options.orderBy === \"string\"\n ? deserializeOrderByList(options.orderBy)\n : normalizeOrderBy(options.orderBy);\n if (orderBy) node.orderBy = orderBy;\n if (options.include) {\n const nested = normalizeIncludeAt(options.include, depth + 1);\n if (nested.wildcard) {\n // `*` inside a nested include has no bound: it would load every\n // relation of every related row, of every related row. The outer\n // wildcard is already the widest thing this API offers.\n throw new IncludeSpecError(\n `\"${key}\" asks for \\`*\\` inside a nested include. Name the relations you need.`\n );\n }\n node.children = nested.tree;\n }\n return node;\n}\n\nfunction normalizeIncludeAt(spec: IncludeSpec, depth: number): NormalizedInclude {\n if (Array.isArray(spec)) {\n const tree: Record<string, IncludeNode> = {};\n let wildcard = false;\n for (const raw of spec) {\n if (typeof raw !== \"string\") {\n throw new IncludeSpecError(`${typeof raw} is not a relation name`);\n }\n const name = raw.trim();\n if (!name) continue;\n if (name === \"*\") { wildcard = true; continue; }\n addPath(tree, name);\n }\n return { wildcard, tree };\n }\n if (typeof spec !== \"object\" || spec === null) {\n throw new IncludeSpecError(`${typeof spec} is not a list of relations or an include tree`);\n }\n\n const tree: Record<string, IncludeNode> = {};\n let wildcard = false;\n for (const [key, value] of Object.entries(spec)) {\n if (key === \"*\") {\n if (value) wildcard = true;\n continue;\n }\n if (value === true) { ensureNode(tree, key); continue; }\n // `false`/`null` are not in `IncludeSpec`, but this reads values that\n // arrived as JSON off a query string, where they are exactly what a\n // caller writes to turn one relation off in a tree they built by\n // spreading another. Skipping is what they mean.\n if ((value as unknown) === false || value === undefined || value === null) continue;\n if (typeof value !== \"object\" || Array.isArray(value)) {\n throw new IncludeSpecError(`\"${key}\" must be \\`true\\` or an options object`);\n }\n tree[key] = normalizeOptions(key, value as IncludeOptions, depth);\n }\n return { wildcard, tree };\n}\n\n/**\n * Collapse any {@link IncludeSpec} spelling into one tree.\n *\n * `[\"author\", \"comments.author\"]` and\n * `{ author: true, comments: { include: { author: true } } }` normalize to the\n * same value — which is the whole point: the REST parameter can only carry the\n * flat spelling, the SDK prefers the tree, and the driver should never learn\n * about either.\n *\n * @throws {IncludeSpecError} for a shape that is not an include at all, or one\n * that nests past {@link MAX_INCLUDE_DEPTH}.\n */\nexport function normalizeInclude(spec?: IncludeSpec): NormalizedInclude | undefined {\n if (spec === undefined || spec === null) return undefined;\n const normalized = normalizeIncludeAt(spec, 1);\n if (!normalized.wildcard && Object.keys(normalized.tree).length === 0) return undefined;\n return normalized;\n}\n\n/**\n * Every relation name a tree names, as dotted paths — `[\"comments\",\n * \"comments.author\"]`.\n *\n * Used to report which names an `include` asked for when one of them is not a\n * relation, and to serialize a tree that carries no per-relation options back\n * to the flat wire spelling.\n */\nexport function includePaths(tree: Record<string, IncludeNode>, prefix = \"\"): string[] {\n const out: string[] = [];\n for (const [key, node] of Object.entries(tree)) {\n const path = prefix ? `${prefix}.${key}` : key;\n out.push(path);\n out.push(...includePaths(node.children, path));\n }\n return out;\n}\n\n/**\n * The relation names an `include` asks for at the top level.\n *\n * `[\"author\", \"comments.author\"]` and `{author: true, comments: {...}}` both\n * answer `[\"author\", \"comments\"]` — a *hop*, not a path, because the only\n * consumer is `?fields=`, which names keys on the row being returned and a\n * nested relation is not one of those.\n *\n * Derived rather than passed: `include` has four spellings and three of them\n * are not a `string[]`, so every consumer that wants the plain names either\n * calls this or reimplements the flattening.\n */\nexport function topLevelIncludeNames(spec?: IncludeSpec): string[] {\n const normalized = normalizeInclude(spec);\n if (!normalized) return [];\n return Object.keys(normalized.tree);\n}\n\n/** Whether any node in the tree carries per-relation options. */\nfunction hasOptions(tree: Record<string, IncludeNode>): boolean {\n return Object.values(tree).some(node =>\n node.limit !== undefined || node.where !== undefined || node.logical !== undefined\n || node.orderBy !== undefined || node.fields !== undefined\n || node.count !== undefined\n || hasOptions(node.children));\n}\n\n/**\n * Serialize an {@link IncludeSpec} for the REST `?include=` parameter.\n *\n * Two spellings, and which one is used is decided by the request rather than\n * chosen:\n *\n * - **Comma-separated dotted paths** — `include=author,comments.author`. What a\n * plain include is, what a human types, and what every existing client sends.\n * - **JSON**, when any relation carries options — `include={\"comments\":{\"limit\":5,\n * \"include\":{\"author\":true}}}`. The flat spelling has nowhere to put a\n * `limit`, and inventing a punctuation for it (`comments(limit:5)`) would be a\n * third grammar to learn beside the two this API already has.\n *\n * The server accepts both on every list and get route, and tells them apart the\n * same way this does: a value starting with `{` is JSON.\n */\nexport function serializeInclude(spec?: IncludeSpec): string | undefined {\n const normalized = normalizeInclude(spec);\n if (!normalized) return undefined;\n if (normalized.wildcard && Object.keys(normalized.tree).length === 0) return \"*\";\n if (!hasOptions(normalized.tree)) {\n const paths = includePaths(normalized.tree);\n // Only the leaves: `comments.author` already implies `comments`, and\n // sending both is the same request twice.\n const leaves = paths.filter(path => !paths.some(other => other.startsWith(`${path}.`)));\n const all = normalized.wildcard ? [\"*\", ...leaves] : leaves;\n return all.length > 0 ? all.join(\",\") : undefined;\n }\n return JSON.stringify(toWireTree(normalized));\n}\n\n/**\n * A normalized tree, back in the {@link IncludeSpec} spelling a caller writes.\n *\n * The round trip is what lets a builder accumulate `include` calls: normalize\n * each, merge, and hand the result back as a spec the next layer can normalize\n * again. Idempotent, so doing it twice changes nothing.\n */\nexport function denormalizeInclude(normalized: NormalizedInclude): IncludeSpec {\n return toWireTree(normalized) as IncludeSpec;\n}\n\nfunction mergeTrees(\n into: Record<string, IncludeNode>,\n from: Record<string, IncludeNode>\n): Record<string, IncludeNode> {\n for (const [key, node] of Object.entries(from)) {\n const existing = into[key];\n if (!existing) { into[key] = node; continue; }\n // The later call wins on each option it names, and says nothing about\n // the ones it does not — so `.include(\"comments\")` after\n // `.include({comments:{limit:5}})` keeps the limit rather than erasing\n // it, which is the behaviour that makes accumulating calls safe.\n if (node.limit !== undefined) existing.limit = node.limit;\n if (node.where !== undefined) existing.where = node.where;\n if (node.logical !== undefined) existing.logical = node.logical;\n if (node.orderBy !== undefined) existing.orderBy = node.orderBy;\n if (node.fields !== undefined) existing.fields = node.fields;\n if (node.count !== undefined) existing.count = node.count;\n existing.children = mergeTrees(existing.children, node.children);\n }\n return into;\n}\n\n/**\n * Combine several `include` requests into one.\n *\n * Repeated `.include(...)` calls on a query builder are additive: each names\n * more of the graph to load, and a later one must not discard what an earlier\n * one asked for. Assigning instead of merging is why `.include(\"author\")\n * .include(\"tags\")` used to load only tags.\n */\nexport function mergeIncludeSpecs(\n existing: IncludeSpec | undefined,\n additions: (string | IncludeSpec)[]\n): IncludeSpec | undefined {\n const merged: NormalizedInclude = { wildcard: false, tree: {} };\n const absorb = (spec?: IncludeSpec) => {\n const normalized = normalizeInclude(spec);\n if (!normalized) return;\n merged.wildcard ||= normalized.wildcard;\n mergeTrees(merged.tree, normalized.tree);\n };\n absorb(existing);\n // A bare string is one relation name; anything else is a spec in its own\n // right. `.include(\"a\", \"b\")` and `.include([\"a\",\"b\"])` are the same call.\n const names = additions.filter((a): a is string => typeof a === \"string\");\n if (names.length > 0) absorb(names);\n for (const addition of additions) {\n if (typeof addition !== \"string\") absorb(addition);\n }\n if (!merged.wildcard && Object.keys(merged.tree).length === 0) return undefined;\n return denormalizeInclude(merged);\n}\n\nfunction toWireTree(normalized: NormalizedInclude): Record<string, unknown> {\n const emit = (tree: Record<string, IncludeNode>): Record<string, unknown> => {\n const out: Record<string, unknown> = {};\n for (const [key, node] of Object.entries(tree)) {\n const options: Record<string, unknown> = {};\n if (node.limit !== undefined) options.limit = node.limit;\n if (node.where) options.where = node.where;\n if (node.logical) options.logical = node.logical;\n if (node.orderBy) options.orderBy = node.orderBy;\n if (node.fields) options.fields = node.fields;\n if (node.count) options.count = true;\n const children = emit(node.children);\n if (Object.keys(children).length > 0) options.include = children;\n out[key] = Object.keys(options).length > 0 ? options : true;\n }\n return out;\n };\n const tree = emit(normalized.tree);\n if (normalized.wildcard) tree[\"*\"] = true;\n return tree;\n}\n\n/**\n * Read the REST `?include=` parameter, in either spelling.\n *\n * @throws {IncludeSpecError} for malformed JSON or a tree that nests too deep.\n */\nexport function deserializeInclude(raw?: string): IncludeSpec | undefined {\n if (raw === undefined || raw === null) return undefined;\n const text = raw.trim();\n if (!text) return undefined;\n if (text.startsWith(\"{\")) {\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch {\n throw new IncludeSpecError(\n \"the parametrised form must be a JSON object, e.g. \"\n + \"{\\\"comments\\\":{\\\"limit\\\":5,\\\"include\\\":{\\\"author\\\":true}}}\"\n );\n }\n if (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) {\n throw new IncludeSpecError(\"the parametrised form must be a JSON object\");\n }\n return parsed as IncludeSpec;\n }\n return text.split(\",\").map(s => s.trim()).filter(Boolean);\n}\n","import {\n DEFAULT_LIST_LIMIT,\n FindAllParams,\n FindParams,\n FindResult,\n IterateParams,\n RebaseClientError\n} from \"@rebasepro/types\";\nimport { normalizeOrderBy } from \"./sort-dialect\";\n\n/**\n * The pagination engine behind `iterate()` / `findAll()`.\n *\n * It lives here, above both transports, on purpose: the HTTP client and the\n * in-process accessor implement the same `SDKCollectionClient` contract, and a\n * helper written twice is a helper that drifts. Both call into this file, so\n * \"the SDK paginates like *this*\" has exactly one definition.\n *\n * Everything below is expressed in terms of a single `find(params)` function,\n * which is all either transport has to supply.\n */\n\n/** Rows requested per page when the caller does not say. */\nexport const DEFAULT_PAGE_SIZE = 200;\n\n/** Rows `findAll()` will materialise before it refuses to continue. */\nexport const DEFAULT_FIND_ALL_MAX_ROWS = 10_000;\n\n/**\n * Requests one walk may make before it gives up on the server ever saying\n * `hasMore: false`. At the default page size that is two million rows — far\n * past any legitimate walk, and short of running forever.\n */\nexport const DEFAULT_MAX_PAGES = 10_000;\n\n/** Why a pagination walk refused to continue. */\nexport type PaginationErrorCode =\n /** `findAll()` matched more rows than its ceiling allows. */\n | \"max-rows\"\n /** The walk made its maximum number of requests without the server finishing. */\n | \"max-pages\"\n /**\n * The server said there was another page but issued no cursor to reach it.\n *\n * A query whose ordering has no stored value to seek on — relevance — is the\n * case that produces this. Page it by offset instead.\n */\n | \"cursor-missing\"\n /** Two consecutive pages returned the same cursor, so the walk cannot advance. */\n | \"cursor-stalled\";\n\n/**\n * Thrown when a walk stops for a reason the caller needs to know about.\n *\n * Every one of these is a case where the alternative would be silent: a\n * truncated array that looks complete, or a loop that never returns. Check\n * {@link code} to tell them apart.\n */\nexport class RebasePaginationError extends Error {\n readonly code: PaginationErrorCode;\n\n constructor(code: PaginationErrorCode, message: string) {\n super(message);\n this.name = \"RebasePaginationError\";\n this.code = code;\n // Keeps `instanceof` working when this is compiled down for an older\n // target, where extending a builtin otherwise loses the prototype.\n Object.setPrototypeOf(this, RebasePaginationError.prototype);\n }\n}\n\n/**\n * What a read naming both `page` and `offset` is refused with — by the server\n * (400 `PAGE_WITH_OFFSET`) and, before any request, by every client.\n */\nexport const PAGE_WITH_OFFSET_MESSAGE = \"`page` and `offset` describe the same window; send one.\";\n\n/** The one thing a transport has to provide to be paginated. */\nexport type PageFinder<M extends Record<string, unknown> = Record<string, unknown>> =\n (params: FindParams<M>) => Promise<FindResult<M>>;\n\n/**\n * Resolve `limit`/`offset`/`page` into the window a read will actually use.\n *\n * Lives here, next to the walk, for the reason at the top of this file: every\n * transport has to mean the same thing by \"page two\". Four of them did not —\n * the REST layer strode by {@link DEFAULT_LIST_LIMIT}, the local-first\n * evaluator by {@link DEFAULT_PAGE_SIZE}, the in-process accessor by 20, and\n * the published type documented a fourth number. Pages that overlap or skip\n * rows are the mildest of those outcomes.\n *\n * `page` and `offset` together are refused with `PAGE_WITH_OFFSET`, as the\n * server refuses them: they name one window twice, and `page` used to win\n * silently over an `offset` the caller then never saw applied. `driverOffset`\n * is the value to hand a driver: it stays `undefined` when the caller named no\n * offset, because keyset pagination seeks with a `where` clause and must not\n * look like it is paging by offset.\n */\nexport function resolveFindWindow(\n params?: Pick<FindParams, \"limit\" | \"offset\" | \"page\">\n): { limit: number; offset: number; driverOffset: number | undefined } {\n if (params?.page != null && params.offset != null) {\n throw new RebaseClientError(PAGE_WITH_OFFSET_MESSAGE, { code: \"PAGE_WITH_OFFSET\" });\n }\n const limit = params?.limit ?? DEFAULT_LIST_LIMIT;\n const offset = params?.page != null\n ? Math.max(0, (params.page - 1) * limit)\n : (params?.offset ?? 0);\n return {\n limit,\n offset,\n driverOffset: params?.page != null ? offset : params?.offset\n };\n}\n\nfunction normalizePageSize(raw: number | undefined): number {\n if (raw === undefined || !Number.isFinite(raw)) return DEFAULT_PAGE_SIZE;\n return Math.max(1, Math.floor(raw));\n}\n\nfunction normalizeMaxPages(raw: number | undefined): number {\n if (raw === undefined) return DEFAULT_MAX_PAGES;\n if (raw === Number.POSITIVE_INFINITY) return raw;\n if (!Number.isFinite(raw)) return DEFAULT_MAX_PAGES;\n return Math.max(1, Math.floor(raw));\n}\n\nfunction normalizeMaxRows(raw: number | undefined): number {\n if (raw === undefined) return DEFAULT_FIND_ALL_MAX_ROWS;\n if (raw === Number.POSITIVE_INFINITY) return raw;\n if (!Number.isFinite(raw)) return DEFAULT_FIND_ALL_MAX_ROWS;\n return Math.max(0, Math.floor(raw));\n}\n\n/**\n * Walk every row a query matches, yielding one row at a time and fetching the\n * next page only when the consumer asks for it.\n *\n * See {@link SDKCollectionClient.iterate} for the caller-facing contract,\n * including the offset-drift caveat and the `cursor` alternative.\n *\n * @param find the transport's single-page read\n * @param params `find()` parameters minus the window, plus the walk options\n * @param label the collection name, so an error says which walk failed\n */\nexport async function* paginateFind<M extends Record<string, unknown> = Record<string, unknown>>(\n find: PageFinder<M>,\n params?: IterateParams<M>,\n label = \"collection\"\n): AsyncGenerator<M, void, undefined> {\n const {\n pageSize,\n cursor,\n maxPages,\n // The window is the walk's, but where it starts is the caller's. Left\n // in `rest`, these rode along on every page beside the walk's own: a\n // caller's `after` went out next to `offset=0`, which the server\n // refuses outright (CURSOR_WITH_OFFSET), and a caller's `offset` was\n // overwritten by that `0`, so the walk silently started over from the\n // top. `IterateParams` omits `offset` and `page`, but the query builder\n // hands its whole state to `iterate()`, `.offset(40)` included.\n after: startAfter,\n offset: startOffset,\n page: startPage,\n ...rest\n } = (params ?? {}) as IterateParams<M> & Pick<FindParams<M>, \"offset\" | \"page\">;\n\n // No page of a walk is counted. Nothing it returns carries a total —\n // `iterate()` yields rows, `findAll()` an array — and `hasMore`, the one\n // thing it reads off `meta`, does not need one: without a count the read\n // asks for one row past the page. The count is most of what a list read\n // costs on a large table (1.8 s of a 100k-row walk's SQL, 26 ms without).\n // A caller's own `count` is overridden for the same reason.\n const findParams = { ...rest, count: \"none\" } as FindParams<M>;\n const size = normalizePageSize(pageSize);\n const pageCap = normalizeMaxPages(maxPages);\n\n // ── Cursor (keyset) setup ────────────────────────────────────────────────\n //\n // The walk no longer builds a keyset of its own. It used to: a `>`/`<` on\n // one column, expressed as an extra `where`, which threw on any multi-key\n // sort (\"keyset pagination advances along a single column\") and dropped\n // every row whose sort value was NULL, because `> value` answers *unknown*\n // against NULL. The driver has had a NULL-correct multi-key comparison all\n // along and nothing over HTTP could reach it.\n //\n // So this is now a *request* for seeking, not an implementation of it: the\n // server issues `meta.nextCursor` and the walk hands it back as `after`.\n // Multi-key sorts and nullable keys work because the comparison is the\n // driver's, and there is one of it.\n //\n // A caller's own `after` is a request for seeking too: it is a cursor\n // issued for this query's sort, so the walk continues from it by the same\n // handoff.\n const seekRequested = (cursor !== undefined && cursor !== null) || !!startAfter;\n if (cursor !== undefined && cursor !== null) {\n // A named column still means \"sort by this and seek along it\", which is\n // what every existing caller wrote. It is an `orderBy` now rather than\n // a second pagination mode — the seeking itself needs no column named,\n // since the cursor carries whatever keys the sort used.\n const field = typeof cursor === \"string\" ? cursor : cursor.field;\n const requested = (typeof cursor === \"object\" && cursor !== null) ? cursor.direction : undefined;\n const explicit = normalizeOrderBy(findParams.orderBy);\n // An explicit `orderBy` wins and the named column is redundant, not\n // wrong: seeking follows whatever the query is sorted by, so there is\n // no longer a mismatch to refuse.\n if (!explicit) {\n findParams.orderBy = [field, requested ?? \"asc\"] as FindParams<M>[\"orderBy\"];\n }\n }\n\n // `page` counts in pages of this walk's size, by the one rule every read\n // resolves it with.\n let offset = resolveFindWindow({ limit: size, offset: startOffset, page: startPage }).offset;\n let pages = 0;\n let after: string | undefined = startAfter || undefined;\n\n // ── The default walk seeks too, whenever the server lets it ─────────────\n //\n // A walk nobody asked to seek still continues from `meta.nextCursor` when a\n // page carries one. Advancing `offset` instead made every request skip\n // `offset` rows in the database before reading the next page, so the walk\n // cost grew with the square of the collection: 4.5 s of SQL for 100k rows\n // where seeking is 1.8 s — and the order is the same, because the cursor\n // is the server's, built over the very sort the offset pages used.\n //\n // Where no cursor comes — relevance, a vector search, a distinct read, a\n // server that predates cursors — it pages by offset exactly as before, from\n // the position it has reached (`offset` keeps counting rows either way).\n // `after` and `offset` never go out together: the server refuses that\n // (`CURSOR_WITH_OFFSET`). Only an explicit `cursor`/`after` walk treats a\n // missing or stalled cursor as an error; this one never promised to seek.\n let seekingByServerCursor = false;\n\n for (;;) {\n if (pages >= pageCap) {\n throw new RebasePaginationError(\n \"max-pages\",\n `Iterating \"${label}\" made ${pages} requests without the server reporting the end of ` +\n `the collection. Stopping rather than looping forever — raise \\`maxPages\\` if the walk ` +\n `is genuinely this long, or check that the backend sets \\`meta.hasMore\\`.`\n );\n }\n\n const pageParams: FindParams<M> = { ...findParams, limit: size };\n if (seekRequested) {\n if (after) pageParams.after = after;\n } else if (seekingByServerCursor && after) {\n pageParams.after = after;\n } else {\n pageParams.offset = offset;\n }\n\n const page = await find(pageParams);\n pages += 1;\n\n const rows = page?.data ?? [];\n // A page with nothing on it always ends the walk, whatever the server\n // claims about `hasMore` — there is no cursor to advance and no offset\n // that would ever move past it.\n if (rows.length === 0) return;\n\n for (const row of rows) {\n yield row;\n }\n\n // The server is the only authority on whether more rows exist. Never\n // infer it from `rows.length >= size`: a last page that happens to be\n // exactly full is indistinguishable from a middle one, and guessing\n // there drops every row after it.\n if (page?.meta?.hasMore !== true) return;\n\n if (seekRequested) {\n const next = page.meta.nextCursor;\n if (!next) {\n throw new RebasePaginationError(\n \"cursor-missing\",\n `Cannot seek past the last row of \"${label}\": the server reported another page but ` +\n `issued no cursor for it. An ordering with no stored value to compare against — ` +\n `relevance (\\`_score\\`) — cannot key a cursor. Drop \\`cursor\\` to page by offset.`\n );\n }\n if (next === after) {\n throw new RebasePaginationError(\n \"cursor-stalled\",\n `Iterating \"${label}\" is stuck: two pages in a row ended on the same cursor, so the ` +\n `walk cannot advance. Continuing would loop forever. Page by offset instead, or ` +\n `report this — a cursor that does not move is a server-side bug.`\n );\n }\n after = next;\n } else {\n // Advance by what actually arrived, not by the page size: a server\n // free to return fewer rows than asked for would otherwise leave a\n // hole in the walk. Counted on seeked pages too, so a fallback to\n // offset resumes exactly where the walk has got to.\n offset += rows.length;\n const next = page.meta.nextCursor;\n // A cursor equal to the one just sent cannot advance the walk;\n // offset still can.\n seekingByServerCursor = !!next && next !== after;\n after = seekingByServerCursor ? next : undefined;\n }\n }\n}\n\n/**\n * {@link paginateFind}, collected into an array under a ceiling.\n *\n * See {@link SDKCollectionClient.findAll}.\n */\nexport async function collectAllPages<M extends Record<string, unknown> = Record<string, unknown>>(\n find: PageFinder<M>,\n params?: FindAllParams<M>,\n label = \"collection\"\n): Promise<M[]> {\n const { maxRows, ...rest } = (params ?? {}) as FindAllParams<M> & Record<string, unknown>;\n const cap = normalizeMaxRows(maxRows as number | undefined);\n\n const out: M[] = [];\n for await (const row of paginateFind<M>(find, rest as IterateParams<M>, label)) {\n out.push(row);\n if (out.length > cap) {\n throw new RebasePaginationError(\n \"max-rows\",\n `findAll(\"${label}\") matched more than ${cap} rows. Returning the first ${cap} would ` +\n `look like the whole answer and quietly not be one, so this throws instead. Raise ` +\n `\\`maxRows\\` if you meant to load them all, or stream with \\`iterate()\\`.`\n );\n }\n }\n return out;\n}\n\n/**\n * Build the `iterate` / `findAll` pair for one collection from its `find`.\n *\n * Both transports call this, which is what keeps the two implementations from\n * being two implementations.\n */\nexport function createPaginationHelpers<M extends Record<string, unknown> = Record<string, unknown>>(\n find: PageFinder<M>,\n label: string\n): {\n iterate: (params?: IterateParams<M>) => AsyncIterableIterator<M>;\n findAll: (params?: FindAllParams<M>) => Promise<M[]>;\n} {\n return {\n iterate: (params?: IterateParams<M>) => paginateFind<M>(find, params, label),\n findAll: (params?: FindAllParams<M>) => collectAllPages<M>(find, params, label)\n };\n}\n","/**\n * REST wire-format adapter for the unified filter system.\n *\n * This module is the ONLY code in the entire codebase that knows about\n * PostgREST-style dot-syntax strings (`eq.active`, `gt.18`, `in.(a,b)`).\n * Everything else speaks `FilterValues` exclusively.\n *\n * Wire-format values are always strings — the wire format carries no type\n * metadata, so type coercion is the responsibility of the server-side data\n * driver which has access to the collection schema.\n *\n * Structural characters inside a value are backslash-escaped: `,` → `\\,`,\n * `(` → `\\(`, `)` → `\\)`, and a literal backslash as `\\\\`. Decoding is\n * deliberately conservative — only those four sequences are decoded, so a\n * backslash that arrives unescaped from an older client survives intact.\n *\n * @module\n */\n\nimport {\n WhereFilterOp,\n FilterValues,\n ALL_WHERE_FILTER_OPS,\n CANONICAL_TO_REST,\n REST_TO_CANONICAL,\n RestFilterOp,\n toCanonicalOp,\n LogicalCondition,\n FilterCondition,\n LIST_OPS,\n NULL_OPS\n} from \"@rebasepro/types\";\nimport { normalizeToEntityRelation } from \"../util/entities\";\n\n// ---------------------------------------------------------------------------\n// Value stringification\n// ---------------------------------------------------------------------------\n\n/**\n * Serialize a JS value to its querystring representation.\n * `null` is serialized as the literal string `\"null\"`.\n * Relation values (`EntityRelation` instances or `{ __type: \"relation\", id, path }`\n * objects) are serialized as their raw id — the wire format only carries the\n * value to compare against the FK column.\n *\n * A `Date` is its ISO 8601 instant — what JSON makes of it on the socket and\n * in-process, so one filter names one instant through every door. `String()`\n * gave the viewer's local zone, no milliseconds and a parenthesised zone name\n * Postgres cannot parse. An invalid date keeps its \"Invalid Date\" spelling for\n * the server to refuse, rather than throwing a `RangeError` from here.\n */\nfunction stringifyValue(value: unknown): string {\n if (value === null) return \"null\";\n if (value instanceof Date && !Number.isNaN(value.getTime())) return value.toISOString();\n const relation = normalizeToEntityRelation(value);\n if (relation) return String(relation.id);\n return String(value);\n}\n\n// ---------------------------------------------------------------------------\n// Comma escaping for list values\n// ---------------------------------------------------------------------------\n\n/**\n * Characters that carry structure in the wire format and must therefore be\n * escaped inside a value: the separator, the group delimiters, and the escape\n * character itself.\n *\n * Parentheses are here because `and(...)`/`or(...)` groups are parsed by\n * tracking paren depth. A value containing one is not merely ambiguous, it\n * moves where the parser thinks the group ends.\n */\nconst WIRE_SPECIALS = /[\\\\,()]/g;\n\n/**\n * Escape a value for the wire format: `\\` → `\\\\`, `,` → `\\,`, `(` → `\\(`,\n * `)` → `\\)`.\n */\n/**\n * The wire spelling of an empty list.\n *\n * A lone backslash: unproducible by {@link escapeWireValue}, which doubles\n * every backslash it emits, so it cannot collide with any real item.\n */\nconst EMPTY_LIST_TOKEN = \"\\\\\";\n\nfunction escapeWireValue(value: string): string {\n return value.replace(WIRE_SPECIALS, ch => `\\\\${ch}`);\n}\n\n/**\n * Unescape a wire-format value.\n *\n * **Conservative**, and deliberately so: only the four sequences\n * {@link escapeWireValue} actually produces are decoded. A backslash followed\n * by anything else is left exactly as it is.\n *\n * This used to consume the backslash before *any* character, which is\n * indistinguishable for anything this codec emitted — it only ever emits those\n * four — but not for input arriving from elsewhere. A client on an older\n * release sends a Windows path or a LIKE pattern with a literal `C:\\x`\n * unescaped, and greedy unescaping silently turned it into `C:x`, changing\n * which rows matched. Decoding only what the encoder can produce makes the two\n * directions agree across versions.\n */\nfunction unescapeWireValue(value: string): string {\n let result = \"\";\n for (let i = 0; i < value.length; i++) {\n const next = value[i + 1];\n if (value[i] === \"\\\\\" && (next === \"\\\\\" || next === \",\" || next === \"(\" || next === \")\")) {\n result += next;\n i++;\n continue;\n }\n result += value[i];\n }\n return result;\n}\n\n/**\n * Whether a scalar, sent raw as a query parameter's operand, would be read\n * back as something other than itself.\n *\n * A query parameter owns its whole value, so its scalar goes out unescaped —\n * `eq.a,b`, `eq.C:\\x` — and the decoder returns it verbatim. Two shapes break\n * that. A parenthesised operand is a list for every operator (so that\n * `[\"==\", [\"a\",\"b\"]]` on an array column stays a list), so raw, the scalar\n * `\"(deleted)\"` would arrive as `[\"deleted\"]`: an `==` matching nothing, a\n * `!=` returning the very rows it excludes. Such a scalar is sent escaped,\n * `eq.\\(deleted\\)`. And that spelling has to be told apart from a raw value\n * that looks the same, so a scalar of *that* shape is escaped in turn.\n *\n * Everything else stays raw, so a value an older client sends decodes as it\n * always has — unless it is exactly the escaped spelling of such a scalar,\n * which decodes to the scalar it spells.\n */\nfunction scalarNeedsEscape(value: string): boolean {\n if (value.startsWith(\"(\") && value.endsWith(\")\")) return true;\n return decodeEscapedScalar(value) !== undefined;\n}\n\n/**\n * The scalar an operand is the escaped spelling of, when it is one.\n *\n * Exact on purpose: only an operand that {@link escapeWireValue} produces from\n * a scalar that {@link scalarNeedsEscape} is decoded. Any other operand —\n * including a raw backslash an older client sent, like `\\\\server\\share` — is\n * left verbatim. Terminates because an operand that matches contains an\n * escape pair, so the value is strictly shorter than it.\n */\nfunction decodeEscapedScalar(operand: string): string | undefined {\n if (!operand.startsWith(\"\\\\\")) return undefined;\n const value = unescapeWireValue(operand);\n return escapeWireValue(value) === operand && scalarNeedsEscape(value) ? value : undefined;\n}\n\n/**\n * Split a parenthesized list string on unescaped commas.\n * Input is the content between `(` and `)`.\n *\n * @example\n * splitListItems(\"admin,editor\") // [\"admin\", \"editor\"]\n * splitListItems(\"hello\\\\, world,foo\") // [\"hello, world\", \"foo\"]\n */\nfunction splitListItems(inner: string): string[] {\n const items: string[] = [];\n let current = \"\";\n for (let i = 0; i < inner.length; i++) {\n if (inner[i] === \"\\\\\" && i + 1 < inner.length) {\n // Escaped pair — consume both chars so the comma in `\\,` is not\n // read as a separator. Kept verbatim; decoding happens once, below.\n current += inner[i] + inner[i + 1];\n i++;\n } else if (inner[i] === \",\") {\n items.push(unescapeWireValue(current));\n current = \"\";\n } else {\n current += inner[i];\n }\n }\n items.push(unescapeWireValue(current));\n return items;\n}\n\n/**\n * Split a group body on commas at paren depth 0, honouring escapes.\n *\n * The escape-awareness is the point. The splitter used to track only paren\n * depth, so a comma inside a scalar value ended a condition:\n * `or(name.eq.Doe, John,age.gte.18)` parsed as *three* conditions, the middle\n * one a fabricated `\" John\" == true`. On an `or` that widens the result set,\n * and nothing anywhere reports an error — the query simply stops meaning what\n * the caller wrote.\n */\nfunction splitGroupItems(inner: string): string[] {\n const parts: string[] = [];\n let depth = 0;\n let start = 0;\n for (let i = 0; i < inner.length; i++) {\n const ch = inner[i];\n if (ch === \"\\\\\" && i + 1 < inner.length) { i++; continue; }\n if (ch === \"(\") depth++;\n else if (ch === \")\") depth--;\n else if (ch === \",\" && depth === 0) {\n parts.push(inner.slice(start, i));\n start = i + 1;\n }\n }\n parts.push(inner.slice(start));\n return parts;\n}\n\n// ---------------------------------------------------------------------------\n// Typed operator map lookups (no `as any`)\n// ---------------------------------------------------------------------------\n\n/**\n * Operator tables as `Map`s, because the key comes off the wire.\n *\n * Indexed as plain objects, every `Object.prototype` member answered: a query\n * string of `?f=valueOf.x` found a truthy \"operator\" — the inherited function —\n * and `deserializeTuple` returned it *as the operator*, so a function object\n * travelled on into the compilers in place of a `WhereFilterOp`. The guard one\n * line below (`if (!canonicalOp)`) reads as though it rejects anything unknown,\n * and does not: `Object.prototype` is not unknown to a plain object.\n *\n * Same shape as the prototype-key defects swept out of `setIn`, `getIn`,\n * `mergeDeep`, `unflattenObject` and `FOREIGN_CONVENTION_UIDS`.\n */\nconst REST_OP_LOOKUP = new Map<string, WhereFilterOp>(\n Object.entries(REST_TO_CANONICAL) as [string, WhereFilterOp][]\n);\nconst CANONICAL_OP_LOOKUP = new Map<string, RestFilterOp>(\n Object.entries(CANONICAL_TO_REST) as [string, RestFilterOp][]\n);\n\n// ---------------------------------------------------------------------------\n// Unknown operators\n// ---------------------------------------------------------------------------\n\n/** The operator spellings a rejection lists back to the caller. */\nconst VALID_OPERATOR_LIST = ALL_WHERE_FILTER_OPS.join(\", \");\n\n/**\n * A filter condition named an operator this dialect does not have.\n *\n * ## Why this throws, rather than returning a typed rejection\n *\n * `deserializeFilter` is the *shared* codec: the REST ingress\n * (`packages/server/src/api/rest/query-parser.ts`), the browser SDK and the\n * admin panel (`buildRebaseData.ts`) all decode through it. Two constraints\n * follow.\n *\n * - It cannot throw the server's `ApiError`. `@rebasepro/common` does not\n * depend on `@rebasepro/server` (the dependency runs the other way), and a\n * browser client has no error handler to render an `ApiError` with. So the\n * rejection is this plain `Error` subclass, whose `message` reads correctly\n * wherever it surfaces — a rejected promise in an app, a 400 body over HTTP.\n * - It cannot be a returned rejection *value*. Every caller assigns the result\n * straight into a query it is about to run; a sentinel that none of them\n * check would be ignored, which is exactly the silently-wrong-filter failure\n * this exists to stop. Throwing is also what this file already does for the\n * sibling cases — `serializeTuple` on an unknown canonical operator,\n * `deserializeLogicalCondition` past the nesting bound — and the REST parser\n * already converts the latter into a 400.\n *\n * `statusCode`, `code` and `details` are carried as fields because the server's\n * Hono error handler duck-types those off any thrown error: a decode path that\n * forgets to convert still answers 400 with the canonical envelope instead of a\n * 500 that says \"An unexpected error occurred\". `query-parser.ts` converts\n * explicitly all the same — that is the path the contract is stated on, and an\n * incidental 400 is not a contract.\n */\nexport class UnknownFilterOperatorError extends Error {\n /** The field the condition was written against. */\n public readonly field: string;\n /** The operator string as it arrived, verbatim. */\n public readonly operator: string;\n /** Every operator this dialect accepts, in canonical spelling. */\n public readonly validOperators: readonly WhereFilterOp[] = ALL_WHERE_FILTER_OPS;\n /** See the class docblock: read by the server's error handler. */\n public readonly statusCode = 400;\n public readonly code = \"UNKNOWN_FILTER_OPERATOR\";\n public readonly details: { field: string; operator: string; validOperators: readonly WhereFilterOp[] };\n\n constructor(field: string, operator: string) {\n super(\n `Unknown filter operator '${operator}' on field '${field}'. `\n + `Valid operators: ${VALID_OPERATOR_LIST}`\n );\n this.name = \"UnknownFilterOperatorError\";\n this.field = field;\n this.operator = operator;\n this.details = { field, operator, validOperators: ALL_WHERE_FILTER_OPS };\n }\n}\n\n/**\n * Two to three characters of ASCII punctuation and nothing else — the shape\n * every symbolic operator has (`==`, `>=`, `<>`, `~~`, `!!`, `>>`, `===`), and\n * one a column value effectively never has.\n *\n * Two characters minimum on purpose. A *single* punctuation character is a\n * perfectly ordinary value — `{ grade: [\"-\", \"+\"] }` is a two-item list, not a\n * condition — and the only single-character operator anyone actually mistypes\n * is `=`, which is named separately below. `<` and `>` need no special case:\n * they are real operators and resolve.\n */\nconst SYMBOLIC_OPERATOR = /^[^\\p{L}\\p{N}\\s]{2,3}$/u;\n\n/** Lowercase, strip everything that is not a letter or digit. */\nfunction normalizeOperatorName(op: string): string {\n return op.toLowerCase().replace(/[^a-z0-9]/g, \"\");\n}\n\n/**\n * Every real operator name with its case and separators removed, so a\n * respelling of one — `arrayContains`, `not_in`, `NOT-LIKE`, `isNull` — is\n * recognised as an attempt at an operator rather than read as a value.\n *\n * These are rejected rather than accepted: admitting a second spelling of an\n * operator would leave two wire spellings of one thing, and the rejection\n * message names the one that works.\n */\nconst RESPELLED_OPERATORS: ReadonlySet<string> = new Set(\n [...ALL_WHERE_FILTER_OPS, ...Object.keys(REST_TO_CANONICAL)].map(normalizeOperatorName)\n);\n\n/**\n * Operator names *other* query dialects use, which this one does not have.\n *\n * This list is curated, and deliberately so. For a word-shaped string there is\n * no rule that separates \"an operator the caller guessed\" from \"a value that\n * happens to be a word\": `{ tags: [\"a\", \"b\"] }` has to keep meaning a two-item\n * `in` list, so the codec cannot simply refuse every unrecognised word in\n * position 0. The line is therefore drawn by name, and only around names whose\n * use as an operator is far more likely than their use as one of two sibling\n * values. `contains` is the motivating case — the first thing a developer\n * reaches for, and until now it compiled to `title IN ('contains', 'Hell')`.\n *\n * Genuinely ambiguous single words (`any`, `all`, `exists`, `search`, `not`)\n * are left off: as operators they are rare, and as enum values they are common.\n * Everywhere else the tie goes to *rejecting*, because a 400 naming the\n * supported set costs the caller one round trip, and the alternative — which is\n * what every name on this list used to produce — is a query that runs, returns\n * rows, and is wrong.\n */\nconst NEAR_MISS_OPERATORS: ReadonlySet<string> = new Set([\n \"contains\", \"notcontains\", \"doesnotcontain\", \"doesnotcontains\",\n \"includes\", \"notincludes\",\n \"startswith\", \"notstartswith\", \"beginswith\", \"startingwith\",\n \"endswith\", \"notendswith\",\n \"matches\", \"notmatches\", \"regex\", \"regexp\",\n \"between\", \"notbetween\",\n \"equals\", \"notequals\", \"equalto\", \"isequalto\", \"isnotequalto\",\n \"greaterthan\", \"greaterthanorequal\", \"greaterthanorequalto\",\n \"lessthan\", \"lessthanorequal\", \"lessthanorequalto\",\n \"isempty\", \"isnotempty\",\n \"oneof\", \"noneof\", \"anyof\", \"allof\",\n \"null\", \"isnullorempty\"\n]);\n\n/**\n * Was this string *meant* as an operator?\n *\n * Only consulted after {@link toCanonicalOp} has already failed to resolve it,\n * so a `true` here is always a rejection.\n */\nfunction isOperatorShaped(op: string): boolean {\n if (op === \"=\") return true;\n if (SYMBOLIC_OPERATOR.test(op)) return true;\n const normalized = normalizeOperatorName(op);\n if (!normalized) return false;\n return RESPELLED_OPERATORS.has(normalized) || NEAR_MISS_OPERATORS.has(normalized);\n}\n\n/**\n * Read a `[op, value]` tuple, if that is what this is.\n *\n * Three outcomes, and the middle one is the defect this function exists for:\n *\n * - the operator resolves (canonical *or* REST spelling) → the canonical tuple;\n * - the operator does not resolve but was plainly meant as one → throw;\n * - it does not look like an operator at all → `undefined`, and the caller\n * falls back to reading the array as a list of values.\n *\n * The old test was `toCanonicalOp(raw[0]) === raw[0]`, i.e. canonical spelling\n * only, with *everything else* — including every REST short-code — dropping\n * through to `[\"in\", raw]`. So the operator string itself became a value in a\n * membership test: `[\"!!\", \"Hello\"]` compiled to `title IN ('!!','Hello')`,\n * which matches, and the caller got back rows their filter was written to\n * exclude. `[\"eq\", \"active\"]` had the same shape of failure.\n */\nfunction readTuple(field: string, raw: unknown): [WhereFilterOp, unknown] | undefined {\n if (!Array.isArray(raw) || raw.length !== 2) return undefined;\n const [op, value] = raw;\n if (typeof op !== \"string\") return undefined;\n\n const canonical = toCanonicalOp(op);\n if (canonical) return [canonical, value];\n\n // A dot means this is a *wire* string, not an operator token: two repeated\n // query params arrive as `[\"gte.18\", \"lt.65\"]`, which is a two-element array\n // of strings and therefore tuple-shaped. Deferred with exactly the test the\n // repeated-dot-string branch below uses, so the two cannot disagree.\n //\n // The property test found this: `[\"ilike\", \"\"]` serializes to `\"ilike.\"`,\n // whose normalized form is a real operator name, so a well-formed\n // round-trip was being rejected as a bad operator.\n if (op.includes(\".\")) return undefined;\n\n if (isOperatorShaped(op)) throw new UnknownFilterOperatorError(field, op);\n\n return undefined;\n}\n\n// ---------------------------------------------------------------------------\n// Reserved query-parameter names\n// ---------------------------------------------------------------------------\n\n/**\n * The query-parameter names a REST listing reads as something other than a\n * field filter: pagination and whether to count, sorting, includes,\n * projection, search, the logical groups, the JSON `where` dialect, an\n * aggregate's `select`/`groupBy`, and the soft-delete switches.\n *\n * One list for both ends. The server skips these names when it collects\n * `?<field>=<op>.<value>` filters, and the SDK sends a filter on a column with\n * one of these names inside `?where=` instead — a collection is free to have a\n * `page`, `select` or `hard` column, and a filter on it sent as its own\n * parameter was read as the parameter and never applied.\n */\nexport const RESERVED_QUERY_KEYS: ReadonlySet<string> = new Set([\n \"limit\", \"offset\", \"page\", \"after\", \"orderBy\", \"include\", \"fields\", \"distinct\", \"count\",\n \"searchString\", \"searchExplain\", \"vector_search\", \"vector\", \"vector_distance\", \"vector_threshold\",\n \"or\", \"and\", \"not\", \"where\", \"select\", \"groupBy\", \"deleted\", \"hard\"\n]);\n\n// ---------------------------------------------------------------------------\n// Serialize: FilterValues → REST querystring\n// ---------------------------------------------------------------------------\n\n/**\n * Encode the `<op>.<value>` half of a wire condition.\n *\n * This is the single leaf encoder. Both wire positions that carry a condition\n * — a top-level query parameter (`?status=eq.active`) and a leaf inside an\n * `and(...)`/`or(...)` group (`or(status.eq.active,…)`) — go through it, so a\n * rule expressed here holds in both. The group serializer used to carry its\n * own copy, and the copy had drifted on every rule that matters: `null` went\n * out as the four-character string, the empty list as `()`, and an operator\n * this dialect does not have was silently rewritten to `eq` — a filter that\n * ran, returned rows, and answered a different question than the one asked.\n *\n * `escapeScalar` is the one thing the two positions legitimately disagree\n * about. A scalar in a query parameter owns the whole value and needs no\n * escaping; a scalar inside a group sits between the same commas a list item\n * does, so a comma in it would end the condition early.\n */\nfunction serializeOperatorAndValue(\n op: WhereFilterOp,\n value: unknown,\n { escapeScalar, where }: { escapeScalar: boolean; where: string }\n): string {\n if (typeof op !== \"string\") {\n throw new TypeError(\n `${where}: operator must be a string, got ${typeof op}`\n );\n }\n\n // Canonical spellings only, on purpose: this codec parses liberally and\n // emits strictly. `deserializeFilter` accepts a REST short-code because one\n // arrives off the wire; a *caller* handing one to the serializer has a\n // condition object built by hand, and the spelling it wants is the one the\n // types name.\n //\n // The throw is the fix. `serializeLogicalCondition` used to end this lookup\n // with `?? \"eq\"`, so `{ operator: \"gte\" }` — the spelling the wire uses, and\n // therefore the one most often guessed — was sent as `age.eq.18`: a query\n // that ran, returned rows, and answered a different question.\n const restOp = CANONICAL_OP_LOOKUP.get(op);\n if (!restOp) {\n throw new TypeError(\n `${where}: unknown operator \"${op}\". Valid operators: ${Object.keys(CANONICAL_TO_REST).join(\", \")}`\n );\n }\n\n // `== null` and `!= null` go out as the null-testing operators.\n //\n // They used to serialize as `eq.null`, and `deserializeTuple` had no way to\n // tell that from a search for the four-character string \"null\" — so it\n // returned the string, and `.where(\"deleted_at\", \"==\", null)` compiled to\n // `deleted_at = 'null'` over HTTP. The typed builder allows it, the Postgres\n // compiler implements it as IS NULL, and only the wire trip broke it.\n //\n // These are the same query: SQL `= NULL` is never true, so `== null` can\n // only mean IS NULL. Emitting it as such is unambiguous in both directions\n // and leaves `eq.null` free to mean the literal string, which it now does.\n if (value === null && (op === \"==\" || op === \"!=\")) {\n return op === \"==\" ? \"isnull.null\" : \"notnull.null\";\n }\n\n // A null test has no operand. Whatever was parked in `value` is dropped\n // here rather than on the way back, so the encoding is stable: both\n // deserializers normalize `isnull.<anything>` to `null`, and re-encoding\n // that must land on the same string it came from.\n if (NULL_OPS.has(op)) return `${restOp}.null`;\n\n if (Array.isArray(value)) {\n // The empty list needs a spelling of its own.\n //\n // A comma-joined format has no way to write \"zero items\": `()` is the\n // empty string between the parens, which splits to `[\"\"]`. So\n // `.where(\"id\", \"in\", [])` — which matches nothing — used to arrive as\n // a search for the empty string: a 500 on a uuid column, silently the\n // wrong rows on a text one.\n //\n // `EMPTY_LIST_TOKEN` is a single unescaped backslash, which no real\n // value can produce: `escapeWireValue` doubles every backslash, so a\n // one-item list holding `\\` serializes as `(\\\\)`. That keeps both\n // directions exact — `[]` and `[\"\"]` stay distinct — rather than\n // trading one lossy reading for another.\n if (value.length === 0) return `${restOp}.(${EMPTY_LIST_TOKEN})`;\n const items = value.map(v => escapeWireValue(stringifyValue(v))).join(\",\");\n return `${restOp}.(${items})`;\n }\n\n const scalar = stringifyValue(value);\n // A list operator's operand is a list, so a scalar there has no spelling\n // to protect; see `scalarNeedsEscape` for the others.\n const escape = escapeScalar || (!LIST_OPS.has(op) && scalarNeedsEscape(scalar));\n return `${restOp}.${escape ? escapeWireValue(scalar) : scalar}`;\n}\n\n/**\n * Serialize a single canonical condition tuple to a PostgREST dot-string.\n *\n * Throws `TypeError` if the input is not a valid `[WhereFilterOp, unknown]` tuple.\n *\n * @example\n * serializeTuple([\"==\", \"active\"]) // \"eq.active\"\n * serializeTuple([\"in\", [\"admin\",\"editor\"]]) // \"in.(admin,editor)\"\n * serializeTuple([\">=\", 18]) // \"gte.18\"\n */\nfunction serializeTuple(tuple: [WhereFilterOp, unknown]): string {\n if (!Array.isArray(tuple) || tuple.length !== 2) {\n throw new TypeError(\n `serializeTuple: expected a [WhereFilterOp, value] tuple, got ${JSON.stringify(tuple)}`\n );\n }\n\n const [op, value] = tuple;\n return serializeOperatorAndValue(op, value, {\n escapeScalar: false,\n where: \"serializeTuple\"\n });\n}\n\n/**\n * Convert `FilterValues` (or `WireFilterValues`) to a PostgREST-style\n * querystring record.\n *\n * - Canonical `[WhereFilterOp, value]` tuples are serialized strictly.\n * - Pre-serialized PostgREST strings (e.g. `\"eq.published\"`) are passed through.\n * - Single conditions produce a string value.\n * - Multiple conditions on the same field produce a string array (repeated params).\n *\n * @example\n * serializeFilter({ status: [\"==\", \"active\"] })\n * // → { status: \"eq.active\" }\n *\n * serializeFilter({ age: [[\">=\", 18], [\"<\", 65]] })\n * // → { age: [\"gte.18\", \"lt.65\"] }\n *\n * // Pre-serialized strings pass through unchanged:\n * serializeFilter({ status: \"eq.published\" })\n * // → { status: \"eq.published\" }\n */\nexport function serializeFilter(\n filter: FilterValues<string> | Record<string, unknown>\n): Record<string, string | string[]> {\n const result: Record<string, string | string[]> = {};\n\n for (const [field, condition] of Object.entries(filter)) {\n if (condition === undefined) continue;\n\n // Pre-serialized PostgREST string — pass through unchanged.\n // This supports WireFilterValues where values may already be\n // serialized dot-strings like \"eq.active\" or raw strings like \"true\".\n if (typeof condition === \"string\") {\n result[field] = condition;\n continue;\n }\n\n // Multiple conditions on the same field: array of tuples\n // We detect this by checking if the first element is also an array.\n if (Array.isArray(condition) && condition.length > 0 && Array.isArray(condition[0])) {\n result[field] = (condition as [WhereFilterOp, unknown][]).map(serializeTuple);\n } else {\n // Single condition — must be a [WhereFilterOp, value] tuple\n result[field] = serializeTuple(condition as [WhereFilterOp, unknown]);\n }\n }\n\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// Deserialize: REST querystring → FilterValues\n// ---------------------------------------------------------------------------\n\n/**\n * The spellings a null-testing operator's operand may take.\n *\n * The serializer writes `isnull.null`; a hand-written `isnull.true` means the\n * same thing and has always been accepted. Anything else after the operator is\n * not an operand it has — `notnull.reason` is a *value* — see\n * {@link deserializeSingle}.\n */\nconst NULL_OPERANDS: ReadonlySet<string> = new Set([\"null\", \"true\", \"false\", \"\"]);\n\n/**\n * Parse a single PostgREST dot-string into a `[WhereFilterOp, unknown]` tuple.\n *\n * All values are returned as strings — the wire format carries no type\n * metadata, so coercion is the data driver's responsibility.\n *\n * ## When a leading segment is an operator, and when it is part of the value\n *\n * `?status=in.progress` and `?status=in.(a,b)` differ by one character and mean\n * entirely different things, and the reading here decides which. The rule, in\n * full:\n *\n * > A dot-string is read as `operator.operand` **only** when its first segment\n * > names a known REST operator **and** what follows is a well-formed operand\n * > *for that operator's arity*. Otherwise the whole string is the value.\n *\n * Arity, per operator family:\n *\n * - **List** operators (`in`, `nin`, `csa` — `LIST_OPS`) take a parenthesised\n * list and nothing else. `in.(draft,review)` is the operator; `in.progress`\n * is the *value* `\"in.progress\"`, because there is no list there and so no\n * `in` filter that could have been written. That case used to compile to\n * `status IN ('progress')` — a filter the caller never wrote, quietly\n * matching the wrong rows and, on a status field, hiding every row they were\n * looking for.\n * - **Null** operators (`isnull`, `notnull` — {@link NULL_OPS}) take no\n * operand: only {@link NULL_OPERANDS}. `notnull.reason` is the value\n * `\"notnull.reason\"`, not \"reason is not null\".\n * - **Everything else** takes one scalar, and any remainder is one — including\n * the empty string, so `eq.` really is \"equals the empty string\". A\n * parenthesised remainder is a list for these too (`eq.(a,b)` is array\n * equality); a scalar that is itself parenthesised arrives escaped,\n * `eq.\\(none\\)`, and is decoded here — see {@link scalarNeedsEscape}.\n *\n * ### The one ambiguity that remains, and how to write past it\n *\n * A scalar operator's operand is unconstrained, so `?status=like.that` is a\n * `LIKE 'that'` and no rule at this layer can tell it from the literal value\n * `\"like.that\"` — both are well-formed encodings, and picking either by guess\n * would break the other. Two spellings say \"value\" unambiguously, and both\n * round-trip:\n *\n * - `?status=eq.like.that` — name the operator. The *first* segment is consumed\n * as the operator and everything after it is the value, dots and all. This is\n * what `serializeFilter` emits, which is why the SDK never meets the\n * ambiguity at all.\n * - `?where={\"status\":[\"==\",\"like.that\"]}` — the JSON dialect's tuple form.\n *\n * Values that merely *contain* dots (`user@host.com`, `1.2.3`) were never\n * ambiguous: their first segment names no operator to begin with.\n */\nfunction deserializeSingle(raw: string): [WhereFilterOp, unknown] {\n const dotIndex = raw.indexOf(\".\");\n if (dotIndex === -1) {\n // No dot → equality on the raw value (kept as string)\n return [\"==\", raw];\n }\n\n const prefix = raw.substring(0, dotIndex);\n const rest = raw.substring(dotIndex + 1);\n\n // Check if the prefix is a known REST operator.\n // This is the key defense against values like \"eq.something\" or \"gt.foo\"\n // being misinterpreted — only known REST short-codes are treated as operators.\n const canonicalOp = REST_OP_LOOKUP.get(prefix);\n if (!canonicalOp) {\n // Not a known operator (e.g., email \"user@host.com\" or version \"1.2.3\")\n // Treat the entire string as an equality value\n return [\"==\", raw];\n }\n\n // Null-testing operators ignore their serialized value — normalize to null\n // so the tuple round-trips stably (`isnull.null` → [\"is-null\", null]).\n if (NULL_OPS.has(canonicalOp)) {\n // ...but only when what follows is an operand this operator has. See\n // the docblock: `notnull.reason` names no null test, so it is a value.\n if (!NULL_OPERANDS.has(rest)) return [\"==\", raw];\n return [canonicalOp, null];\n }\n\n // Parse list values: \"(admin,editor)\" → [\"admin\", \"editor\"]\n if (rest.startsWith(\"(\") && rest.endsWith(\")\")) {\n const inner = rest.slice(1, -1);\n // See EMPTY_LIST_TOKEN: `(\\)` is the empty list. `()` remains a list\n // holding one empty string, which is what splitting it yields anyway.\n const items = inner === EMPTY_LIST_TOKEN ? [] : splitListItems(inner);\n return [canonicalOp, items];\n }\n\n // A list operator with no list is not that operator — see the docblock.\n // `?status=in.progress` is the value \"in.progress\"; the `in` filter it used\n // to compile to was never written by anyone.\n if (LIST_OPS.has(canonicalOp)) return [\"==\", raw];\n\n // A scalar the serializer had to escape, because raw it would have read\n // as a list; see `scalarNeedsEscape`.\n return [canonicalOp, decodeEscapedScalar(rest) ?? rest];\n}\n\n/**\n * Convert a PostgREST-style querystring record to `FilterValues`.\n *\n * - String values are parsed as single conditions.\n * - String arrays (repeated query params) become multiple conditions on the same field.\n *\n * @example\n * deserializeFilter({ status: \"eq.active\" })\n * // → { status: [\"==\", \"active\"] }\n *\n * deserializeFilter({ age: [\"gte.18\", \"lt.65\"] })\n * // → { age: [[\">=\", \"18\"], [\"<\", \"65\"]] }\n *\n * @throws {UnknownFilterOperatorError} when a condition names an operator this\n * dialect does not have. See that class for why a rejection here is a throw.\n */\nexport function deserializeFilter(\n query: Record<string, unknown>\n): FilterValues<string> {\n const result: FilterValues<string> = {};\n\n for (const [field, raw] of Object.entries(query)) {\n if (raw === undefined) continue;\n\n // A single `[op, value]` condition.\n const tuple = readTuple(field, raw);\n if (tuple) {\n result[field] = tuple;\n continue;\n }\n\n if (Array.isArray(raw)) {\n if (raw.length === 0) continue;\n\n // An array of tuples: several conditions on the same field. Every\n // element is checked, not just the first — the old test read\n // `raw[0]` and cast the whole array, so one bad operator among\n // several travelled on untouched.\n if (Array.isArray(raw[0])) {\n const tuples = raw.map(item => readTuple(field, item));\n if (tuples.every((t): t is [WhereFilterOp, unknown] => t !== undefined)) {\n result[field] = tuples;\n continue;\n }\n }\n\n if (raw.length === 1) {\n result[field] = typeof raw[0] === \"string\" ? deserializeSingle(raw[0]) : [\"==\", raw[0]];\n } else {\n // If the elements are strings, they might be PostgREST dot-strings (repeated params)\n if (typeof raw[0] === \"string\" && raw[0].includes(\".\")) {\n result[field] = raw.map(r => typeof r === \"string\" ? deserializeSingle(r) : ([\"==\", r] as [WhereFilterOp, unknown])) as [WhereFilterOp, unknown][];\n } else {\n // Otherwise assume it's a list of values for an implicit\n // \"in\" — `{ tags: [\"a\",\"b\"] }`, and `?tags=a&tags=b`, which\n // arrives here identically.\n //\n // A two-element array reaches this line only after\n // `readTuple` has decided its first element was not meant\n // as an operator. Everything longer never had the\n // ambiguity: an operator tuple has exactly two slots.\n result[field] = [\"in\", raw];\n }\n }\n } else if (typeof raw === \"string\") {\n result[field] = deserializeSingle(raw);\n } else {\n result[field] = [\"==\", raw];\n }\n }\n\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// Logical conditions: serialize / deserialize\n// ---------------------------------------------------------------------------\n\n/**\n * Serialize a `LogicalCondition` or `FilterCondition` to its wire-format string.\n *\n * Leaf encoding is {@link serializeOperatorAndValue}, the same function\n * `serializeTuple` uses, so `null`, the empty list and an unknown operator\n * behave identically inside a group and in a query parameter.\n *\n * @throws {TypeError} when a leaf names an operator this dialect does not have.\n * It used to fall back to `eq`, which turned `age >= 18` into `age = 18` with\n * no diagnostic anywhere.\n *\n * @example\n * serializeLogicalCondition({ column: \"status\", operator: \"==\", value: \"active\" })\n * // → \"status.eq.active\"\n *\n * serializeLogicalCondition({ column: \"deleted_at\", operator: \"==\", value: null })\n * // → \"deleted_at.isnull.null\"\n *\n * serializeLogicalCondition({ type: \"or\", conditions: [...] })\n * // → \"or(status.eq.active,status.eq.pending)\"\n */\nexport function serializeLogicalCondition(\n cond: LogicalCondition | FilterCondition\n): string {\n if (\"type\" in cond) {\n // LogicalCondition (and/or)\n const inner = (cond.conditions ?? [])\n .map(serializeLogicalCondition)\n .join(\",\");\n return `${cond.type}(${inner})`;\n }\n\n // FilterCondition. The leaf goes through the shared encoder, so a group\n // condition and a query parameter agree on nulls, empty lists and unknown\n // operators — see `serializeOperatorAndValue`.\n //\n // The column is escaped like a value: it is not one, but it shares the\n // delimiters, and a comma or paren in it would move where the group parser\n // thinks the condition ends. Dots are deliberately *not* escaped — a\n // relation path is `author.name` on the wire, and the parser below finds\n // the operator rather than assuming it is the second segment.\n return `${escapeWireValue(cond.column)}.${serializeOperatorAndValue(cond.operator, cond.value, {\n escapeScalar: true,\n where: \"serializeLogicalCondition\"\n })}`;\n}\n\n/**\n * The positions of the dots that can separate a leaf's segments.\n *\n * Dots inside a list value (`in.(1.5,2.5)`) are not separators. The value\n * always follows the operator, so the search only needs the region before the\n * first unescaped paren.\n */\nfunction leafSeparators(str: string): number[] {\n let limit = str.length;\n for (let i = 0; i < str.length; i++) {\n if (str[i] === \"\\\\\") { i++; continue; }\n if (str[i] === \"(\") { limit = i; break; }\n }\n\n const dots: number[] = [];\n for (let i = 0; i < limit; i++) {\n if (str[i] === \"\\\\\") { i++; continue; }\n if (str[i] === \".\") dots.push(i);\n }\n return dots;\n}\n\n/**\n * Split a leaf condition into `column`, operator token and value.\n *\n * The naive reading — column is everything before the first dot, operator is\n * everything up to the second — cannot express a relation path. A filter on\n * `author.name` serializes to `author.name.eq.bob` and came back as the column\n * `author` with the operator `name`, which resolves to nothing, so the\n * fallback made it `author == \"eq.bob\"`: a condition that runs and matches\n * nothing, on a column the caller never named.\n *\n * So the operator is found rather than assumed: it is the first dot-separated\n * segment after the column that resolves to a real operator. Everything before\n * it is the column, everything after is the value. `version.eq.1.2.3` reads as\n * `version == \"1.2.3\"` because the scan stops at the first match — the `eq` at\n * offset 1, not a later segment — and `metadata->>x.eq.5` never had dots in the\n * column to begin with.\n *\n * Returns `undefined` when no segment resolves — `status.active`, an equality\n * written without an operator, which the caller handles.\n */\nfunction splitLeafCondition(str: string): { column: string; operator: WhereFilterOp; value: string } | undefined {\n const dots = leafSeparators(str);\n\n // Segment 0 is always the column, and an operator needs a value after it,\n // so a candidate is bounded on both sides by a dot.\n for (let i = 1; i < dots.length; i++) {\n const operator = toCanonicalOp(str.substring(dots[i - 1] + 1, dots[i]));\n if (!operator) continue;\n return {\n column: unescapeWireValue(str.substring(0, dots[i - 1])),\n operator,\n value: str.substring(dots[i] + 1)\n };\n }\n\n return undefined;\n}\n\n/**\n * Parse a logical condition wire-format string back into a\n * `LogicalCondition` or `FilterCondition`.\n *\n * @example\n * deserializeLogicalCondition(\"status.eq.active\")\n * // → { column: \"status\", operator: \"==\", value: \"active\" }\n *\n * deserializeLogicalCondition(\"or(status.eq.active,age.gte.18)\")\n * // → { type: \"or\", conditions: [...] }\n */\n/**\n * How deeply `or(...)`/`and(...)` groups may nest.\n *\n * This parser recurses once per level, on a value that arrives in a query\n * string. Unbounded, twenty thousand levels reached `RangeError: Maximum call\n * stack size exceeded`, which a caller sees as a 500 about the call stack\n * rather than a 400 about their filter. Node's 16 KB header cap keeps a GET\n * below that in practice, but \"the HTTP layer happens to stop it\" is not a\n * bound this parser should rely on.\n *\n * Thirty-two is far past anything a real filter expresses; the deepest in this\n * repository's own tests is three.\n */\nexport const MAX_LOGICAL_NESTING_DEPTH = 32;\n\nexport function deserializeLogicalCondition(\n str: string,\n // Not `depth`: the body already uses that name for paren tracking, inside a\n // block that shadows a parameter of the same name — so the recursion\n // counter silently became the paren counter and never grew.\n nesting = 0\n): LogicalCondition | FilterCondition {\n if (nesting > MAX_LOGICAL_NESTING_DEPTH) {\n throw new Error(\n `Filter groups nest more than ${MAX_LOGICAL_NESTING_DEPTH} levels deep. ` +\n \"Flatten the condition — `or(a,or(b,c))` is `or(a,b,c)`.\"\n );\n }\n // Check for logical group: \"and(...)\", \"or(...)\" or \"not(...)\"\n const logicalMatch = str.match(/^(and|or|not)\\((.+)\\)$/);\n if (logicalMatch) {\n const type = logicalMatch[1] as \"and\" | \"or\" | \"not\";\n const innerStr = logicalMatch[2];\n\n const conditions = splitGroupItems(innerStr)\n .map(part => deserializeLogicalCondition(part, nesting + 1));\n\n return { type, conditions };\n }\n\n // FilterCondition: \"column.op.value\"\n const leaf = splitLeafCondition(str);\n if (!leaf) {\n const firstDot = str.indexOf(\".\");\n if (firstDot === -1) {\n return { column: unescapeWireValue(str), operator: \"==\", value: true };\n }\n // \"column.value\" — no segment resolved as an operator, so this is an\n // equality written without one. The value keeps its dots.\n //\n // Unless a segment was plainly meant as an operator: `title.contains.x`\n // read as `title == \"contains.x\"` matches nothing, and inside `not(…)`\n // its negation matched every row — the widened read\n // `parseWhereParam` exists to avoid. Refused by the same test the\n // tuple reader applies, so the two spellings of one filter agree.\n const dots = leafSeparators(str);\n for (let i = 1; i < dots.length; i++) {\n const segment = str.substring(dots[i - 1] + 1, dots[i]);\n if (isOperatorShaped(segment)) {\n throw new UnknownFilterOperatorError(unescapeWireValue(str.substring(0, dots[i - 1])), segment);\n }\n }\n return {\n column: unescapeWireValue(str.substring(0, firstDot)),\n operator: \"==\",\n value: unescapeWireValue(str.substring(firstDot + 1))\n };\n }\n\n const { column, operator, value: valueStr } = leaf;\n\n // A null test has no operand: `isnull.null` is what the serializer writes,\n // but a hand-written `isnull.true` means the same thing. Normalizing here\n // is what makes the tuple stable through a re-encode, and it matches\n // `deserializeSingle`, which has done it for query parameters all along.\n if (NULL_OPS.has(operator)) {\n return { column, operator, value: null };\n }\n\n // Parse list values with escape-aware splitting. The wrapping parens are\n // written by the serializer *after* the items are escaped, so an escaped\n // paren inside an item can never be mistaken for them.\n if (valueStr.startsWith(\"(\") && valueStr.endsWith(\")\")) {\n const inner = valueStr.slice(1, -1);\n // See EMPTY_LIST_TOKEN: `(\\)` is the empty list, which is not the same\n // query as a search for the empty string.\n const items = inner === EMPTY_LIST_TOKEN ? [] : splitListItems(inner);\n return { column, operator, value: items };\n }\n\n return { column, operator, value: unescapeWireValue(valueStr) };\n}\n"],"mappings":"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"}