@rebasepro/server-postgres 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 (172) hide show
  1. package/README.md +16 -0
  2. package/dist/{BranchService-BRt78gfa.js → BranchService-Bsn2wfaj.js} +3 -2
  3. package/dist/{BranchService-BRt78gfa.js.map → BranchService-Bsn2wfaj.js.map} +1 -1
  4. package/dist/PostgresBackendDriver.d.ts +91 -2
  5. package/dist/PostgresBootstrapper.d.ts +101 -2
  6. package/dist/auth/ensure-tables.d.ts +12 -0
  7. package/dist/auth/services.d.ts +6 -0
  8. package/dist/backup/backup-cli.d.ts +14 -0
  9. package/dist/backup/backup-service.d.ts +2 -2
  10. package/dist/backup/pg-binary.d.ts +5 -0
  11. package/dist/{backup-cli-DW5p9_zv.js → backup-cli-DnpJi6I7.js} +37 -29
  12. package/dist/backup-cli-DnpJi6I7.js.map +1 -0
  13. package/dist/{backup-service-EwcDVG-8.js → backup-service-BTBB7RXn.js} +55 -21
  14. package/dist/backup-service-BTBB7RXn.js.map +1 -0
  15. package/dist/boot-ddl-DVpsFN6r.js +105 -0
  16. package/dist/boot-ddl-DVpsFN6r.js.map +1 -0
  17. package/dist/cli-console-Ca6ZlAVU.js +154 -0
  18. package/dist/cli-console-Ca6ZlAVU.js.map +1 -0
  19. package/dist/cli-console.d.ts +39 -0
  20. package/dist/cli-errors-CaI4ntyD.js +1919 -0
  21. package/dist/cli-errors-CaI4ntyD.js.map +1 -0
  22. package/dist/cli-errors.d.ts +51 -5
  23. package/dist/{cli-flags-BglvpjHv.js → cli-flags-JNI-pwSf.js} +6 -3
  24. package/dist/cli-flags-JNI-pwSf.js.map +1 -0
  25. package/dist/cli-helpers.d.ts +40 -0
  26. package/dist/cli-output.d.ts +16 -28
  27. package/dist/cli-push.d.ts +87 -0
  28. package/dist/cli.js +566 -182
  29. package/dist/cli.js.map +1 -1
  30. package/dist/{column-plan-helpers-1-LQD0yI.js → column-plan-helpers-drZIsSNf.js} +10 -4
  31. package/dist/{column-plan-helpers-1-LQD0yI.js.map → column-plan-helpers-drZIsSNf.js.map} +1 -1
  32. package/dist/{databasePoolManager-Bj5FbeAs.js → connection-C9mT8bgf.js} +24 -112
  33. package/dist/connection-C9mT8bgf.js.map +1 -0
  34. package/dist/connection.d.ts +13 -2
  35. package/dist/databasePoolManager-BIuewidb.js +119 -0
  36. package/dist/databasePoolManager-BIuewidb.js.map +1 -0
  37. package/dist/{ensure-collection-tables-kkkHk8oo.js → deferred-index-build-DP1_SNFo.js} +484 -346
  38. package/dist/deferred-index-build-DP1_SNFo.js.map +1 -0
  39. package/dist/{doctor-C-sYWbmt.js → doctor-C4WiftHm.js} +63 -52
  40. package/dist/doctor-C4WiftHm.js.map +1 -0
  41. package/dist/ensure-collection-policies-CXpbeNag.js +541 -0
  42. package/dist/ensure-collection-policies-CXpbeNag.js.map +1 -0
  43. package/dist/ensure-collection-tables-BhsU6qUE.js +532 -0
  44. package/dist/ensure-collection-tables-BhsU6qUE.js.map +1 -0
  45. package/dist/{ensure-tables-BmI_tRxc.js → ensure-tables-CVm_yvzb.js} +173 -77
  46. package/dist/ensure-tables-CVm_yvzb.js.map +1 -0
  47. package/dist/enum-labels-kept-D8fI1MoX.js +56 -0
  48. package/dist/enum-labels-kept-D8fI1MoX.js.map +1 -0
  49. package/dist/generate-drizzle-schema-Bz52jH2h.js +57 -0
  50. package/dist/generate-drizzle-schema-Bz52jH2h.js.map +1 -0
  51. package/dist/{generate-drizzle-schema-logic-sSFDp6LR.js → generate-drizzle-schema-logic-B74WN37S.js} +5 -5
  52. package/dist/generate-drizzle-schema-logic-B74WN37S.js.map +1 -0
  53. package/dist/generate-files-CdEckX6c.js +199 -0
  54. package/dist/generate-files-CdEckX6c.js.map +1 -0
  55. package/dist/{generate-postgres-ddl-logic-BJsLaVNX.js → generate-postgres-ddl-logic-B64O2oZF.js} +3 -3
  56. package/dist/{generate-postgres-ddl-logic-BJsLaVNX.js.map → generate-postgres-ddl-logic-B64O2oZF.js.map} +1 -1
  57. package/dist/generated-sql.d.ts +8 -2
  58. package/dist/history/HistoryService.d.ts +64 -5
  59. package/dist/history/ensure-history-table.d.ts +63 -4
  60. package/dist/index.d.ts +1 -0
  61. package/dist/index.es.js +139 -19236
  62. package/dist/index.es.js.map +1 -1
  63. package/dist/{introspect-db-logic-kCETE8TY.js → introspect-db-logic-C-Di8dA9.js} +44 -6
  64. package/dist/introspect-db-logic-C-Di8dA9.js.map +1 -0
  65. package/dist/{introspect-db-queries-C_Q5VgQw.js → introspect-db-queries-D6kAqReI.js} +23 -1
  66. package/dist/introspect-db-queries-D6kAqReI.js.map +1 -0
  67. package/dist/open-rebase-CbOur0TU.js +20858 -0
  68. package/dist/open-rebase-CbOur0TU.js.map +1 -0
  69. package/dist/open-rebase.d.ts +86 -0
  70. package/dist/pg-error-utils-vJezlXdI.js +388 -0
  71. package/dist/pg-error-utils-vJezlXdI.js.map +1 -0
  72. package/dist/{plan-schema-DU9exq6C.js → plan-schema-B1YF_tYq.js} +151 -41
  73. package/dist/plan-schema-B1YF_tYq.js.map +1 -0
  74. package/dist/{policy-drift-B-J2hhm0.js → policy-drift-Dm8i-s1J.js} +23 -6
  75. package/dist/policy-drift-Dm8i-s1J.js.map +1 -0
  76. package/dist/push-index-prebuild-Db4AQF4i.js +45 -0
  77. package/dist/push-index-prebuild-Db4AQF4i.js.map +1 -0
  78. package/dist/push-legacy-renames-BK6ilQq2.js +34 -0
  79. package/dist/push-legacy-renames-BK6ilQq2.js.map +1 -0
  80. package/dist/{render-ddl-Ds2t_d9V.js → render-ddl-CtC3y9Ol.js} +8 -4
  81. package/dist/render-ddl-CtC3y9Ol.js.map +1 -0
  82. package/dist/{rls-bootstrap-sql-_KNnjanK.js → rls-bootstrap-sql-RQUljnJ1.js} +125 -1270
  83. package/dist/rls-bootstrap-sql-RQUljnJ1.js.map +1 -0
  84. package/dist/{rls-enforcement-CfXOJJaW.js → rls-enforcement-DvzqlaL1.js} +129 -17
  85. package/dist/rls-enforcement-DvzqlaL1.js.map +1 -0
  86. package/dist/schema/auth-schema.d.ts +2 -2
  87. package/dist/schema/boot-ddl.d.ts +47 -0
  88. package/dist/schema/collection-index.d.ts +45 -0
  89. package/dist/schema/column-plan-helpers.d.ts +5 -0
  90. package/dist/schema/deferred-index-build.d.ts +131 -0
  91. package/dist/schema/destructive-sql.d.ts +7 -0
  92. package/dist/schema/doctor-cli.js +110 -6
  93. package/dist/schema/doctor-cli.js.map +1 -1
  94. package/dist/schema/doctor-search-scale.d.ts +45 -0
  95. package/dist/schema/doctor.d.ts +20 -1
  96. package/dist/schema/ensure-collection-policies.d.ts +73 -35
  97. package/dist/schema/ensure-collection-tables.d.ts +56 -3
  98. package/dist/schema/enum-labels-kept.d.ts +26 -0
  99. package/dist/schema/generate-drizzle-schema.js +1 -1
  100. package/dist/schema/generate-files.d.ts +44 -0
  101. package/dist/schema/generate-next-step.d.ts +0 -13
  102. package/dist/schema/generate-postgres-ddl.js +10 -81
  103. package/dist/schema/generate-postgres-ddl.js.map +1 -1
  104. package/dist/schema/introspect-db-logic.d.ts +12 -0
  105. package/dist/schema/introspect-db-queries.d.ts +11 -1
  106. package/dist/schema/introspect-db-structure.d.ts +10 -0
  107. package/dist/schema/introspect-db.js +4 -5
  108. package/dist/schema/introspect-db.js.map +1 -1
  109. package/dist/schema/migration-post-process.d.ts +34 -0
  110. package/dist/schema/plan/diff-plan.d.ts +126 -0
  111. package/dist/schema/plan/types.d.ts +8 -0
  112. package/dist/schema/push-index-prebuild.d.ts +25 -0
  113. package/dist/schema/push-legacy-renames.d.ts +20 -0
  114. package/dist/schema/rls-bootstrap-sql.d.ts +15 -0
  115. package/dist/security/rls-enforcement.d.ts +32 -4
  116. package/dist/services/FetchService.d.ts +58 -8
  117. package/dist/services/PersistService.d.ts +15 -0
  118. package/dist/services/RelationService.d.ts +88 -3
  119. package/dist/services/cdc/CdcListener.d.ts +17 -6
  120. package/dist/services/cdc/trigger-cdc.d.ts +151 -27
  121. package/dist/services/channel-bus/index.d.ts +1 -1
  122. package/dist/services/channel-history.d.ts +15 -4
  123. package/dist/services/collection-helpers.d.ts +15 -0
  124. package/dist/services/field-viewer.d.ts +15 -1
  125. package/dist/services/pg-notify-listener.d.ts +7 -0
  126. package/dist/services/realtimeService.d.ts +314 -14
  127. package/dist/services/row-pipeline.d.ts +16 -9
  128. package/dist/services/socket-liveness.d.ts +42 -2
  129. package/dist/services/sql-script.d.ts +207 -3
  130. package/dist/services/vector-iterative-scan.d.ts +40 -0
  131. package/dist/services/write-depth.d.ts +6 -0
  132. package/dist/services/write-transaction-scope.d.ts +9 -0
  133. package/dist/socket-opt-ins.d.ts +64 -0
  134. package/dist/source-DBDbBTL4.js +811 -0
  135. package/dist/source-DBDbBTL4.js.map +1 -0
  136. package/dist/tsx-loader.d.ts +14 -0
  137. package/dist/utils/drizzle-conditions.d.ts +21 -1
  138. package/dist/utils/error-cause.d.ts +7 -0
  139. package/dist/utils/pg-error-utils.d.ts +2 -4
  140. package/dist/utils/result-rows.d.ts +10 -0
  141. package/dist/websocket-BLGc5yhs.js +1604 -0
  142. package/dist/websocket-BLGc5yhs.js.map +1 -0
  143. package/dist/websocket.d.ts +13 -0
  144. package/package.json +7 -7
  145. package/dist/backup-cli-DW5p9_zv.js.map +0 -1
  146. package/dist/backup-service-EwcDVG-8.js.map +0 -1
  147. package/dist/cli-errors-DsA-K9uP.js +0 -893
  148. package/dist/cli-errors-DsA-K9uP.js.map +0 -1
  149. package/dist/cli-flags-BglvpjHv.js.map +0 -1
  150. package/dist/cli-output-CNdMql-L.js +0 -47
  151. package/dist/cli-output-CNdMql-L.js.map +0 -1
  152. package/dist/collection-index-DdnsxiJ_.js +0 -286
  153. package/dist/collection-index-DdnsxiJ_.js.map +0 -1
  154. package/dist/databasePoolManager-Bj5FbeAs.js.map +0 -1
  155. package/dist/doctor-C-sYWbmt.js.map +0 -1
  156. package/dist/ensure-collection-policies-B1ureSIV.js +0 -160
  157. package/dist/ensure-collection-policies-B1ureSIV.js.map +0 -1
  158. package/dist/ensure-collection-tables-kkkHk8oo.js.map +0 -1
  159. package/dist/ensure-tables-BmI_tRxc.js.map +0 -1
  160. package/dist/generate-drizzle-schema-93M0lUxK.js +0 -123
  161. package/dist/generate-drizzle-schema-93M0lUxK.js.map +0 -1
  162. package/dist/generate-drizzle-schema-logic-sSFDp6LR.js.map +0 -1
  163. package/dist/introspect-db-logic-kCETE8TY.js.map +0 -1
  164. package/dist/introspect-db-queries-C_Q5VgQw.js.map +0 -1
  165. package/dist/plan-schema-DU9exq6C.js.map +0 -1
  166. package/dist/policy-drift-B-J2hhm0.js.map +0 -1
  167. package/dist/render-ddl-Ds2t_d9V.js.map +0 -1
  168. package/dist/rls-bootstrap-sql-_KNnjanK.js.map +0 -1
  169. package/dist/rls-enforcement-CfXOJJaW.js.map +0 -1
  170. package/dist/source-Br7L7GOI.js +0 -410
  171. package/dist/source-Br7L7GOI.js.map +0 -1
  172. package/dist/utils/sql-redaction.d.ts +0 -21
@@ -1 +1 @@
1
- {"version":3,"file":"index.es.js","names":[],"sources":["../src/services/field-viewer.ts","../src/services/collection-helpers.ts","../src/utils/drizzle-conditions.ts","../src/services/row-pipeline.ts","../src/services/write-denial.ts","../src/services/junction-writes.ts","../src/services/soft-delete.ts","../src/services/read-scope.ts","../src/services/RelationService.ts","../src/data-transformer.ts","../src/services/nested-path.ts","../src/services/FetchService.ts","../src/services/RelationWriteService.ts","../src/services/field-op-sql.ts","../src/services/PersistService.ts","../src/services/dataService.ts","../src/services/write-transaction-scope.ts","../src/services/write-depth.ts","../src/schema/classify-change.ts","../src/schema/generate-schema-commit.ts","../src/services/sql-script.ts","../src/PostgresBackendDriver.ts","../src/schema/auth-schema.ts","../src/services/cdc/junction-tables.ts","../src/services/cdc/identity-columns.ts","../src/services/read-field-access.ts","../src/services/cdc/trigger-cdc.ts","../src/services/pg-notify-listener.ts","../src/services/cdc/CdcListener.ts","../src/services/socket-liveness.ts","../src/schema/drizzle-ddl.ts","../src/services/channel-history.ts","../src/services/channel-presence.ts","../src/services/channel-bus/ChannelBus.ts","../src/services/channel-bus/PostgresChannelBus.ts","../src/services/channel-bus/index.ts","../src/services/realtimeService.ts","../src/utils/sql-redaction.ts","../src/websocket.ts","../src/collections/PostgresCollectionRegistry.ts","../src/backup/backup-cron.ts","../src/types.ts","../src/collections/validate-relations.ts","../src/collections/buildRegistry.ts","../src/auth/services.ts","../src/history/ensure-history-table.ts","../src/history/HistoryService.ts","../src/utils/pg-array-null-patch.ts","../src/utils/pg-numeric-number-patch.ts","../src/schema/introspect-runtime.ts","../src/schema/dynamic-tables.ts","../src/schema/config-relations.ts","../src/schema/catalogue-schema.ts","../src/schema/generated-schema-diff.ts","../src/PostgresBootstrapper.ts","../src/PostgresAdapter.ts"],"sourcesContent":["import { AsyncLocalStorage } from \"node:async_hooks\";\nimport type { FieldViewer } from \"@rebasepro/common\";\n\n/**\n * Who the current read is *for*, available to the row walk without threading it\n * through every read signature.\n *\n * Per-field `access.read` has to be applied wherever a row leaves this driver,\n * and that is not one place: a REST list, a single get, a relation target\n * inlined into a parent, the same target rendered as an admin ref, a realtime\n * frame, a history entry. The row pipeline is the one function they all share —\n * and it is reached through `FetchService`, `RelationService` and `DataService`,\n * none of which carry a user. Threading one through would be a parameter added\n * to a dozen signatures and forgotten on the thirteenth, which is precisely how\n * `stripExcluded` came to be missing from the relation-ref branch for a year.\n *\n * So the viewer is ambient, established at the two places that already\n * establish the *database's* auth context for a read — `applyAuthContext` is\n * called on the same line in both:\n *\n * - `AuthenticatedPostgresBackendDriver.withTransaction`, which every REST,\n * SDK and WebSocket read runs inside;\n * - `RealtimeService`'s RLS-bound refetches, which build the frames a\n * subscriber receives.\n *\n * Deliberately `run()` and not `enterWith()`. `enterWith` persists past the call\n * that made it, so a `rebase.dataAsAdmin` read inside a user's request — a\n * `beforeSave` hook, an `afterRead` hook — would leave the admin viewer in place\n * for the rest of that request and strip nothing from what the user is served.\n *\n * @module\n */\n\n/**\n * `undefined` inside the store is meaningful and is not the same as no store at\n * all: {@link FieldViewer} reads absence as the trusted server plane. Nothing\n * currently stores `undefined`, but the store's type says the difference is\n * carried rather than flattened.\n */\nconst storage = new AsyncLocalStorage<FieldViewer | undefined>();\n\n/**\n * Run `fn` with `viewer` as the caller every row strip judges against.\n *\n * Nested calls shadow: a `dataAsAdmin` read inside a user request runs under the\n * service identity for its duration and the user's viewer is restored after it,\n * which is the behaviour both halves need and the reason this is not a variable.\n */\nexport function withFieldViewer<T>(viewer: FieldViewer | undefined, fn: () => T): T {\n return storage.run(viewer, fn);\n}\n\n/**\n * The caller the current read is for, or `undefined` on the trusted server plane\n * — an in-process `rebase.data` call on the base driver, a migration, the auth\n * adapter reading a password hash to verify it.\n *\n * Absence cannot be reached from outside: every API ingress scopes its driver\n * with `withAuth`, including an unauthenticated one (`roles: [\"anon\"]`), and\n * that is what establishes the viewer.\n */\nexport function currentFieldViewer(): FieldViewer | undefined {\n return storage.getStore();\n}\n","import { PgTable, AnyPgColumn, getTableConfig } from \"drizzle-orm/pg-core\";\nimport { and, eq, getTableColumns, type SQL } from \"drizzle-orm\";\nimport { CollectionConfig, Property, ResolvedHasMany, ResolvedHasOne } from \"@rebasepro/types\";\nimport { PostgresCollectionRegistry } from \"../collections/PostgresCollectionRegistry\";\nimport { fieldKeyForColumn, getTableName, resolveCollectionRelations } from \"@rebasepro/common\";\nimport { ApiError, logger } from \"@rebasepro/server\";\n\n// Row identity is derived on both sides of the wire — the driver parses an\n// incoming address into key columns, the admin derives one from a served row —\n// so the implementation lives in `common` and both agree by construction.\nexport { buildCompositeId, parseIdValues, isAddressableId, COMPOSITE_ID_SEPARATOR } from \"@rebasepro/common\";\nexport type { PrimaryKeyInfo } from \"@rebasepro/common\";\nimport { buildCompositeId, COMPOSITE_ID_SEPARATOR, getDeclaredPrimaryKeys, isAddressableId, parseIdValues } from \"@rebasepro/common\";\nimport type { PrimaryKeyInfo } from \"@rebasepro/common\";\n\n/**\n * Shared helper functions for row operations.\n * These are used by FetchService, PersistService, and RelationService.\n *\n * All functions that need collection/table lookups require an explicit\n * `PostgresCollectionRegistry` instance — there is no global singleton.\n */\n\n/**\n * Interface for Drizzle column metadata introspection.\n * Replaces unsafe `as Record<string, unknown>` double-cast chains.\n */\nexport interface DrizzleColumnMeta {\n columnType?: string;\n dataType?: string;\n primary?: boolean;\n}\n\n/** Safely extract Drizzle column metadata from a column object. */\nexport function getColumnMeta(col: AnyPgColumn): DrizzleColumnMeta {\n // `columnType`, `dataType` and `primary` are declared members of drizzle's\n // `Column`, so there is nothing to convert. The `typeof` checks stay: this\n // is called on columns from runtime-introspected tables as well as declared\n // ones, and the generic parameters those carry are not always resolved.\n return {\n columnType: typeof col.columnType === \"string\" ? col.columnType : undefined,\n dataType: typeof col.dataType === \"string\" ? col.dataType : undefined,\n primary: typeof col.primary === \"boolean\" ? col.primary : undefined\n };\n}\n\n/**\n * Whether an address could name a row in this table, judged by the columns.\n *\n * {@link getPrimaryKeys} lets a config's `isId: \"uuid\"` win over the schema, so\n * its `isUUID` is a claim rather than a fact — right for deriving addresses,\n * wrong for refusing a query. Here the Drizzle column type decides, because it\n * is what Postgres will enforce: a `uuid` column meets `/c/products/new` with\n * `22P02`, which aborts the surrounding transaction and turns every later\n * statement into an unrelated-looking `25P02`.\n */\nexport function idCanAddressTable(\n id: string | number,\n table: PgTable,\n idInfoArray: PrimaryKeyInfo[]\n): boolean {\n const columnBacked = idInfoArray.map(info => {\n const col = table[info.fieldName as keyof typeof table] as AnyPgColumn | undefined;\n const meta = col ? getColumnMeta(col) : undefined;\n // No column to ask (a key the schema does not carry) leaves the id\n // addressable: refusing on a guess would 404 rows that do exist.\n if (!meta?.columnType) return info;\n return { ...info,\n isUUID: meta.columnType === \"PgUUID\" };\n });\n return isAddressableId(id, columnBacked);\n}\n\n/**\n * The condition that names one row: every key column equal to its part of the\n * address.\n *\n * Every key column, not the first. A composite-key read that matched only the\n * leading column answered `p1:::bob` with whichever `p1` row Postgres reached\n * first, and a delete then judged that row's `beforeDelete` while deleting bob —\n * the writes have always matched the whole key, so the reads must too.\n */\nexport function rowIdentityCondition(\n table: PgTable,\n idInfoArray: PrimaryKeyInfo[],\n id: string | number,\n collectionPath: string\n): SQL {\n // Throws on a collection with no key: no condition at all is a WHERE that\n // matches every row, which is the opposite of naming one.\n const columns = primaryKeyColumns(table, idInfoArray, collectionPath);\n const parsed = parseIdValues(id, idInfoArray);\n const conditions = columns.map((column, i) => eq(column, parsed[idInfoArray[i].fieldName]));\n return conditions.length === 1 ? conditions[0] : and(...conditions) as SQL;\n}\n\n/**\n * The columns of a table's composite primary-key constraint, or `[]`.\n *\n * Fails open for anything that is not a real Drizzle table — a test double, a\n * hand-built registry entry — because \"no key\" is a legitimate answer here and\n * throwing would take out every caller that only wanted to ask.\n */\nfunction compositePrimaryKeyColumns(table: PgTable): AnyPgColumn[] {\n try {\n const config = getTableConfig(table);\n return (config.primaryKeys?.[0]?.columns ?? []) as AnyPgColumn[];\n } catch {\n return [];\n }\n}\n\n/**\n * The Drizzle key a column is registered under, falling back to its name.\n *\n * Matched on the SQL name, which is unique within a table, as well as on object\n * identity: the columns a `primaryKey({ columns })` constraint holds are the\n * copies drizzle builds for a table's extra config, never the objects\n * `getTableColumns` returns, so identity alone never finds them. The fallback\n * is not the field either on a table keyed by wire name — `payment_id` is\n * served as `paymentId` — and a key resolved to a field the table does not\n * have is a key no read or write can build a condition on.\n */\nfunction columnFieldName(table: PgTable, column: AnyPgColumn): string {\n for (const [key, candidate] of Object.entries(getTableColumns(table))) {\n if (candidate === column || candidate.name === column.name) return key;\n }\n return column.name;\n}\n\nexport function getCollectionByPath(collectionPath: string, registry: PostgresCollectionRegistry): CollectionConfig {\n const collection = registry.getCollectionByPath(collectionPath);\n if (!collection) {\n const registered = registry.getCollections().map(c => c.slug).join(\", \");\n throw new Error(`Collection not found: ${collectionPath}. Registered collections: [${registered}]`);\n }\n return collection;\n}\n\n/**\n * Reject a write naming something that is not a column of the table.\n *\n * \"The table\" is the one read back from `information_schema` at boot — see\n * `catalogue-schema.ts` — so this asks the database, not a file. It used to ask\n * the generated module, which made the commonest cause of this error a stale\n * `schema.generated.ts`: a property added to a collection was created in the\n * database by boot-ensure and still answered `400` on its first save, because\n * the module had not been regenerated. That cause no longer exists, and the\n * remedy below no longer names it.\n *\n * Drizzle builds INSERT from `Object.entries(table[Symbol.Columns])` and UPDATE\n * from `Object.keys(tableColumns)`, so a key the table does not carry is not\n * rejected by anything — it is *left out of the statement*. The insert answers\n * 201 having stored nothing under that name; the update, if the key was the\n * only one, builds `update \"posts\" set where …` and Postgres raises a syntax\n * error (SQLSTATE 42601), which is neither class 22 nor 23 and so surfaces as a\n * 500 for what is a caller's typo.\n *\n * That makes this the last honest place to check, and the only one every write\n * passes through. `assertKnownWriteFields` in the REST layer checks the same\n * thing against the *config* and is skipped on four paths — `strictWrites:\n * false`, a collection declaring no properties, an auth adapter that owns the\n * body's shape, and a nested route whose target cannot be walked — and it never\n * sees an in-process `rebase.dataAsAdmin` write at all.\n *\n * It also gives `strictWrites: false` a truthful implementation. The flag is\n * documented for \"a column that really does exist which the config never\n * declared\", and skipping the config check alone could not deliver that: the\n * value was dropped a layer later regardless. Skipping the config check and\n * keeping this one does exactly what the flag says — the column must exist,\n * the property need not.\n */\nexport function assertWritableColumns(\n values: Record<string, unknown>,\n table: PgTable,\n collectionPath: string\n): void {\n // Only a real Drizzle table carries a column list. A stand-in that does not\n // (a test double, a registry entry built by hand) has nothing to check\n // against, and inventing an answer from its own keys would reject writes\n // over the shape of the double rather than the shape of the table.\n const columns = getTableColumns(table) as Record<string, unknown> | undefined;\n if (!columns || Object.keys(columns).length === 0) return;\n\n const unknown = Object.keys(values).filter(key => !(key in columns));\n if (unknown.length === 0) return;\n\n // One key is worth naming precisely: the *column* of a field whose wire name\n // differs from it. A table is keyed by the wire name — `authorId` for\n // `author_id` — so writing the column reaches here as \"no such column\",\n // which is the least helpful true statement available about it. Says the\n // name to use instead.\n const byColumnName = new Map<string, string>();\n for (const [key, column] of Object.entries(columns)) {\n const name = (column as { name?: unknown } | undefined)?.name;\n if (typeof name === \"string\" && name !== key) byColumnName.set(name, key);\n }\n const misspelled = unknown.filter(key => byColumnName.has(key));\n if (misspelled.length === unknown.length) {\n throw ApiError.badRequest(\n `'${collectionPath}' has no field ${misspelled.map(key => `'${key}'`).join(\", \")} — ` +\n `${misspelled.length > 1 ? \"those are column names\" : \"that is a column name\"}. ` +\n `Write ${misspelled.map(key => `'${byColumnName.get(key)}'`).join(\", \")} instead: a field is ` +\n \"addressed by its property key, and the column behind it is an implementation detail.\",\n \"VALIDATION_UNKNOWN_FIELDS\"\n );\n }\n\n // The offending keys, and deliberately not the list of real ones: this\n // error is reachable on paths where the REST field check was skipped, and\n // an `excludeFromApi` column is documented as never being served to a\n // caller. Naming what was sent is the actionable half anyway.\n //\n // The remedy names the schema, not the schema *file*: the columns here were\n // read from the database this request is about to write to, so a key that is\n // not among them is a key no column exists for — either a typo, or a\n // property whose column was never created because nothing has applied the\n // collection schema to this database yet.\n throw ApiError.badRequest(\n `'${collectionPath}' has no column${unknown.length > 1 ? \"s\" : \"\"} ` +\n `${unknown.map(key => `'${key}'`).join(\", \")}, so the value${unknown.length > 1 ? \"s\" : \"\"} ` +\n \"would have been dropped before the statement was built. \" +\n \"If you just added the propert\" + (unknown.length > 1 ? \"ies\" : \"y\") +\n \", the database does not have the column yet: apply the schema \" +\n \"(`rebase db push`, or redeploy so boot creates it).\",\n \"VALIDATION_UNKNOWN_FIELDS\"\n );\n}\n\n/**\n * A relation whose names do not resolve against the registered schema.\n *\n * Every one of these used to be a `logger.warn` followed by `continue`, so a\n * save reported success for a relation it had not written and a read answered\n * `[]` for one it could not resolve. `assertRelationsResolve` (validate-relations)\n * fails boot on the same defects, which is where they belong — a server that\n * refuses to start is recoverable in a minute. This is the second line, for the\n * paths that assemble a registry by hand, and it exists so that \"cannot resolve\"\n * is never again reported as \"done\".\n *\n * @param label `<collection>.<relation>`\n * @param detail what does not resolve, in terms of the schema\n */\nexport function relationMisconfigured(label: string, detail: string): ApiError {\n return ApiError.internal(\n `Relation '${label}' does not resolve against the registered schema: ${detail}. ` +\n \"The operation was refused rather than skipped — silently dropping it would report \" +\n \"success for a write that never happened, or emptiness for rows that exist. Run \" +\n \"`rebase schema generate` if the generated schema is older than the database.\",\n \"RELATION_MISCONFIGURED\"\n );\n}\n\nexport function getTableForCollection(collection: CollectionConfig, registry: PostgresCollectionRegistry): PgTable<any> {\n const tableName = getTableName(collection);\n const table = registry.getTable(tableName);\n if (!table) {\n throw new Error(`Table not found for collection '${collection.slug}' (table: ${tableName})`);\n }\n return table;\n}\n\n/**\n * The key columns a collection's rows are addressed by.\n *\n * Three tiers, in order: properties marked `isId`, the primary keys of the\n * registered table, and finally a column literally named `id`. Only the first is\n * visible to the browser, which is why a key known only to the table is reported\n * at boot — see {@link warnOnKeysTheAdminCannotResolve}.\n *\n * The second tier reads the table the registry holds, which is built from\n * `information_schema` — the database's own primary key, not a generated file's\n * claim about it. Composite keys are declared as a table constraint rather than\n * on each column (that is the only legal way to say it), so both shapes are\n * asked for.\n *\n * Returns `[]` when nothing resolves, rather than throwing. It used to open by\n * resolving the table, which throws when there is none — so the `isId` tier,\n * which needs no table at all, was unreachable for exactly the collections\n * most likely to have no table registered. Every caller that wanted \"no keys\"\n * to mean \"no keys\" had to spell that out in a try/catch.\n *\n * Callers that cannot proceed without a key must say so themselves, naming the\n * collection: an empty array here means \"this collection has no address\", which\n * is a different answer in a notification (broadcast a wildcard) than in a save\n * (fail).\n */\nexport function getPrimaryKeys(collection: CollectionConfig, registry: PostgresCollectionRegistry): PrimaryKeyInfo[] {\n // Explicitly declared `isId` properties win, and need no table.\n if (collection.properties) {\n const idProps = Object.entries(collection.properties)\n .filter(([_, prop]) => \"isId\" in (prop as object) && Boolean((prop as { isId?: unknown }).isId))\n .map(([key, prop]) => ({\n fieldName: key,\n type: prop.type === \"number\" ? \"number\" as const : \"string\" as const,\n isUUID: (prop as { isId?: unknown }).isId === \"uuid\"\n }));\n\n if (idProps.length > 0) {\n return idProps;\n }\n }\n\n // The remaining tiers read the drizzle schema, so they need the table. A\n // collection without one — another engine's, or simply unregistered — has\n // nothing more to offer.\n const table = registry.getTable(getTableName(collection));\n if (!table) return [];\n\n // Otherwise infer from Drizzle schema\n const keys: PrimaryKeyInfo[] = [];\n for (const [key, colRaw] of Object.entries(table)) {\n const col = colRaw as AnyPgColumn;\n if (col && typeof col === \"object\" && \"primary\" in col && col.primary) {\n const meta = getColumnMeta(col);\n const type = col.dataType === \"number\" || meta.columnType === \"PgSerial\" || meta.columnType === \"PgInteger\" ? \"number\" : \"string\";\n const isUUID = meta.columnType === \"PgUUID\";\n keys.push({ fieldName: key,\ntype,\nisUUID });\n }\n }\n\n // A composite key is not a flag on each column — Postgres has one\n // constraint naming several, and that is how the table declares it too. The\n // generated module happened to mark each member `primaryKey()` individually,\n // which is not valid DDL but did leave `col.primary` set; reading the\n // constraint is what keeps a two-column key resolvable now that the table\n // describes it honestly.\n if (keys.length === 0) {\n for (const column of compositePrimaryKeyColumns(table)) {\n const meta = getColumnMeta(column);\n keys.push({\n fieldName: columnFieldName(table, column),\n type: column.dataType === \"number\" || meta.columnType === \"PgSerial\" || meta.columnType === \"PgInteger\" ? \"number\" : \"string\",\n isUUID: meta.columnType === \"PgUUID\"\n });\n }\n }\n\n // A collection that declares no primary key gets the implicit `id` column,\n // which is what the schema generators emit for it — so read it back the\n // same way rather than reporting the table as keyless.\n if (keys.length === 0 && \"id\" in table) {\n const idCol = table[\"id\" as keyof typeof table] as AnyPgColumn;\n const idMeta = getColumnMeta(idCol);\n const type = idCol.dataType === \"number\" || idMeta.columnType === \"PgSerial\" || idMeta.columnType === \"PgInteger\" ? \"number\" : \"string\";\n const isUUID = idMeta.columnType === \"PgUUID\";\n keys.push({ fieldName: \"id\",\ntype,\nisUUID });\n }\n\n return keys;\n}\n\n/**\n * The key columns, for callers that cannot do their job without one.\n *\n * {@link getPrimaryKeys} answers \"what keys, if any\" and returns `[]` for a\n * collection with no address. Most of this driver, though, is building a WHERE\n * clause and has no meaning without a key — for those, an empty array is not an\n * answer, and indexing `[0]` into it produces `Cannot read properties of\n * undefined` three frames from where the real problem is. This says what is\n * wrong and which collection it is wrong about.\n */\nexport function requirePrimaryKeys(collection: CollectionConfig, registry: PostgresCollectionRegistry): PrimaryKeyInfo[] {\n const keys = getPrimaryKeys(collection, registry);\n if (keys.length === 0) {\n throw new Error(\n `Collection '${collection.slug}' has no primary key, so its rows cannot be addressed. ` +\n `Mark the key property with \\`isId\\` in its config, or register a table whose schema declares one.`\n );\n }\n return keys;\n}\n\n/**\n * The table's key columns, in key order.\n *\n * Throws when there are none, or when the table has no column for one of them:\n * a WHERE built from fewer columns than the key matches every row that shares\n * the columns it did find, so there is no partial answer worth returning.\n *\n * @param collectionPath named in the error.\n */\nexport function primaryKeyColumns(\n table: PgTable,\n primaryKeys: PrimaryKeyInfo[],\n collectionPath: string\n): AnyPgColumn[] {\n if (primaryKeys.length === 0) {\n throw new Error(`Collection '${collectionPath}' has no primary key, so its rows cannot be addressed.`);\n }\n return primaryKeys.map(info => {\n const column = table[info.fieldName as keyof typeof table] as AnyPgColumn | undefined;\n if (!column) {\n throw new Error(`ID field '${info.fieldName}' not found in table for collection '${collectionPath}'`);\n }\n return column;\n });\n}\n\n/**\n * The column on the *source* table that a `hasOne`/`hasMany` link points at.\n *\n * `sourceKey` is authored when the two sides join on a natural key — an\n * external identity id, a SKU — and left off when they join on the row id,\n * which is the overwhelming majority. That makes `undefined` the only optional\n * field on a resolved relation, so it gets exactly one reader: this function.\n * Every consumer that needs the column asks here, and none of them re-derives\n * \"or else the primary key\" for itself. That is the whole point — the fallback\n * chains this codebase removed from relation resolution were dangerous because\n * they were *duplicated* and could disagree, not because they existed.\n */\nexport function sourceKeyField(\n relation: ResolvedHasOne | ResolvedHasMany,\n sourceCollection: CollectionConfig,\n registry: PostgresCollectionRegistry\n): string {\n // A **field**, as the name says: callers index the Drizzle table and the\n // rows it returns with what comes back from here, and both are keyed by the\n // wire name. `sourceKey` is authored in *column* terms, like every other\n // link on a relation, so it is translated here rather than at each of the\n // call sites — where it used to be `tenant_id` looked up on a table whose\n // key is `tenantId`, finding nothing.\n if (relation.sourceKey) return fieldKeyForColumn(sourceCollection, relation.sourceKey);\n return requirePrimaryKeys(sourceCollection, registry)[0].fieldName;\n}\n\n/**\n * Whether this link joins on something other than the source's primary key.\n *\n * Callers that hold a parent *id* — which is most of them, since an id is what\n * a URL carries — must translate it to the source key's value before it can be\n * compared with the target's foreign key. Those that hold the parent *row*, or\n * that build a correlated subquery over the source table, can read the column\n * directly and skip the lookup.\n */\nexport function joinsOnNaturalKey(\n relation: ResolvedHasOne | ResolvedHasMany,\n sourceCollection: CollectionConfig,\n registry: PostgresCollectionRegistry\n): boolean {\n if (!relation.sourceKey) return false;\n // Compared as fields, because the right-hand side is one.\n return fieldKeyForColumn(sourceCollection, relation.sourceKey)\n !== requirePrimaryKeys(sourceCollection, registry)[0].fieldName;\n}\n\n/**\n * Collections whose key the *browser* cannot resolve, and what it will do\n * instead.\n *\n * The two sides resolve keys from different evidence. This driver reads, in\n * order: properties marked `isId`, the primary keys of the Drizzle schema, then\n * a column literally named `id`. The admin shares the `CollectionConfig` — it\n * compiles the same collection files into its bundle — but never the Drizzle\n * schema, so the middle tier is invisible to it.\n *\n * Nothing can normalize this at runtime: the server does not serve the admin\n * its collections, so a key resolved here cannot be handed over there. The\n * config files are the only thing both sides read, so the fix is an edit to\n * them, and the most this can do is say exactly which edit.\n *\n * Two shapes, and the second is the dangerous one:\n *\n * - No `isId`, no `id` property → the admin resolves no address, warns in the\n * console, and rows cannot be opened or linked.\n * - No `isId`, but an `id` property that is *not* the key → the admin addresses\n * rows by `id` while this driver reads the address as the real key. Nothing\n * errors: the addresses look right and route wrong.\n */\nexport function findUnresolvableKeyCollections(\n collections: CollectionConfig[],\n registry: PostgresCollectionRegistry\n): { collection: CollectionConfig; keys: PrimaryKeyInfo[]; shadowedByIdProperty: boolean }[] {\n const findings: { collection: CollectionConfig; keys: PrimaryKeyInfo[]; shadowedByIdProperty: boolean }[] = [];\n\n for (const collection of collections) {\n // Declared `isId` is the tier both sides share: if it is there, they agree.\n if (getDeclaredPrimaryKeys(collection).length > 0) continue;\n\n // No registered table resolves to no keys, and a collection this cannot\n // resolve a key for is one it has nothing to say about.\n const keys = getPrimaryKeys(collection, registry);\n if (keys.length === 0) continue;\n\n // A single key named `id` is the last tier on both sides: they agree\n // without anything being declared.\n if (keys.length === 1 && keys[0].fieldName === \"id\") continue;\n\n findings.push({\n collection,\n keys,\n shadowedByIdProperty: Boolean(collection.properties?.id)\n });\n }\n\n return findings;\n}\n\n/**\n * The `isId` to suggest for one key column, written as it goes in the config.\n *\n * `true` means the application supplies the value; `\"increment\"` and `\"uuid\"`\n * mean the database generates it, so the admin withholds the field when a row\n * is created. Suggesting a generating strategy for a column the database does\n * not generate is therefore worse than suggesting none: the form sends no\n * value, and the insert fails on a NULL key.\n *\n * The tables this server reads carry each column's type and whether it is part\n * of the key, not its default, so a generating strategy is suggested only where\n * the key's shape makes it near-certain — a lone `uuid` or integer column that\n * points at nothing — and `true` everywhere else:\n *\n * - a column of a **composite** key holds part of the row's identity, which is\n * the caller's to give (`company_translation (id, locale)` is the id of a\n * company and a locale, neither of which this table invents);\n * - a **foreign-key** column holds its parent's key;\n * - a `text` or `varchar` column has no generating strategy at all.\n */\nfunction suggestedIdStrategy(key: PrimaryKeyInfo, keys: PrimaryKeyInfo[], collection: CollectionConfig): string {\n if (keys.length > 1) return \"true\";\n\n const foreignKeyFields = new Set<string>();\n for (const relation of Object.values(resolveCollectionRelations(collection))) {\n if (relation.kind === \"belongsTo\") foreignKeyFields.add(fieldKeyForColumn(collection, relation.localKey));\n }\n for (const [name, property] of Object.entries(collection.properties ?? {})) {\n if ((property as Property).type === \"reference\") foreignKeyFields.add(name);\n }\n if (foreignKeyFields.has(key.fieldName)) return \"true\";\n\n if (key.isUUID) return \"\\\"uuid\\\"\";\n if (key.type === \"number\") return \"\\\"increment\\\"\";\n return \"true\";\n}\n\n/**\n * Report the collections from {@link findUnresolvableKeyCollections} at boot,\n * with the edit that fixes each one.\n *\n * Grouped by failure, not by collection: the shadowed case is a routing bug and\n * the silent case is a missing feature, and they deserve different urgency.\n */\nexport function warnOnKeysTheAdminCannotResolve(\n collections: CollectionConfig[],\n registry: PostgresCollectionRegistry\n): void {\n const findings = findUnresolvableKeyCollections(collections, registry);\n if (findings.length === 0) return;\n\n const edit = (f: { collection: CollectionConfig; keys: PrimaryKeyInfo[] }): string => {\n // `isId` is declared on string, number and reference properties only.\n // A key column declared as anything else cannot be marked as it is, and\n // marking the other columns alone would make them the whole key.\n const unmarkable = f.keys.flatMap(key => {\n const type = (f.collection.properties?.[key.fieldName] as Property | undefined)?.type;\n return type !== undefined && type !== \"string\" && type !== \"number\" && type !== \"reference\"\n ? [`\\`${key.fieldName}\\` (a \\`${type}\\` property)`]\n : [];\n });\n if (unmarkable.length > 0) {\n return `${f.collection.slug}: its key includes ${unmarkable.join(\" and \")}, which cannot carry ` +\n \"`isId`. Declare that column as a `string` property, then mark every key column \" +\n `(${f.keys.map(key => `\\`${key.fieldName}\\``).join(\", \")}) with \\`isId: true\\``;\n }\n const marks = f.keys.map(key => ({\n field: `\\`${key.fieldName}\\``,\n strategy: `\\`isId: ${suggestedIdStrategy(key, f.keys, f.collection)}\\``\n }));\n const same = marks.every(mark => mark.strategy === marks[0].strategy);\n const what = same\n ? `${marks.map(mark => mark.field).join(\" and \")} with ${marks[0].strategy}`\n : marks.map(mark => `${mark.field} with ${mark.strategy}`).join(\" and \");\n // Several columns are one key, and the admin addresses a row by all of\n // them — saying how is what makes a link like `/1:::en_US` readable.\n const composite = f.keys.length > 1\n ? ` — one composite key, whose rows are addressed as \\`${f.keys.map(key => `<${key.fieldName}>`).join(COMPOSITE_ID_SEPARATOR)}\\``\n : \"\";\n return `${f.collection.slug}: mark ${what}${composite}`;\n };\n\n const shadowed = findings.filter(f => f.shadowedByIdProperty);\n const silent = findings.filter(f => !f.shadowedByIdProperty);\n\n if (shadowed.length > 0) {\n logger.warn(\n `⚠️ These collections declare no \\`isId\\`, and their key is only in the drizzle schema — but they ` +\n `do have a property called \\`id\\`. The admin has no way to know \\`id\\` is not the key, so it will ` +\n `address rows by it while this server reads the address as the real key: the links look right and ` +\n `route wrong. Nothing will error.\\n\\n` +\n shadowed.map(f => ` • ${edit(f)}`).join(\"\\n\") + \"\\n\"\n );\n }\n\n if (silent.length > 0) {\n logger.warn(\n `⚠️ These collections declare no \\`isId\\`, and their key is only in the drizzle schema, which the ` +\n `admin never sees. It will resolve no address for their rows, so detail links, caching and ` +\n `relations will not work for them:\\n\\n` +\n silent.map(f => ` • ${edit(f)}`).join(\"\\n\") + \"\\n\"\n );\n }\n}\n\n/**\n * The address of a row: derived from the collection's primary keys, because a\n * row does not carry one — it is exactly its columns.\n *\n * Falls back to a literal `id` column, for a row that reached us from somewhere\n * other than this driver. Returns `\"\"` when there is no key and no `id` —\n * callers decide what that means, since \"unaddressable\" is a different answer\n * in a notification (broadcast a wildcard) than in a save (fail).\n */\nexport function deriveRowAddress(\n row: Record<string, unknown>,\n collection: CollectionConfig,\n registry: PostgresCollectionRegistry\n): string {\n const composite = buildCompositeId(row, getPrimaryKeys(collection, registry));\n // An all-empty composite is indistinguishable from a missing key, and\n // `buildCompositeId` returns \"\" for no keys at all.\n if (composite && composite.split(COMPOSITE_ID_SEPARATOR).some(part => part !== \"\")) {\n return composite;\n }\n if (row.id !== undefined && row.id !== null) return String(row.id);\n return \"\";\n}\n\n","import { and, eq, not, or, sql, SQL, ilike, inArray, getTableColumns, getTableName as drizzleTableName } from \"drizzle-orm\";\nimport { AnyPgColumn, PgTable } from \"drizzle-orm/pg-core\";\nimport {\n ALL_WHERE_FILTER_OPS,\n CollectionConfig, FilterValues, WhereFilterOp, JoinStep, LogicalCondition, FilterCondition,\n ResolvedRelation, ResolvedBelongsTo, ResolvedHasOne, ResolvedHasMany,\n ResolvedForeignKeyOnTarget, ResolvedManyToMany, hasForeignKeyOnTarget, isManyToMany,\n encodeRelationAggregateSort, type Property, type RelationAggregateFn, type RelationAggregateSort\n} from \"@rebasepro/types\";\nimport {\n canReadField,\n fieldKeyForColumn, getColumnName, getTableName, normalizeToEntityRelation, resolveCollectionRelations, toFilterTuples\n} from \"@rebasepro/common\";\nimport { currentFieldViewer } from \"../services/field-viewer\";\nimport { generateForeignKeyName, splitSearchTerms, toWireKey } from \"@rebasepro/utils\";\n/**\n * Postgres's own default for `pg_trgm.word_similarity_threshold`. Named here\n * because the fuzzy predicate has to know when the index-backed operator agrees\n * with the collection's declared threshold and when it would narrow too far.\n */\nconst PG_TRGM_WORD_SIMILARITY_DEFAULT = 0.6;\n/**\n * What a substring-only hit contributes to `_score` under\n * {@link SearchMode} `\"hybrid\"`.\n *\n * Below the smallest `ts_rank` a real lexeme match can produce (a single\n * D-weighted hit is 0.1 × 0.0607927 ≈ 0.006), so a row found by the indexed\n * half always outranks one found only by substring.\n */\nconst HYBRID_SUBSTRING_SCORE = 0.001;\nimport { buildSearchColumnSpec, SEARCH_UNACCENT_FN, type SearchColumnSpec } from \"../schema/search-column\";\nimport { PostgresCollectionRegistry } from \"../collections/PostgresCollectionRegistry\";\nimport { ConditionBuilderStatic } from \"../interfaces\";\nimport { ApiError, logger } from \"@rebasepro/server\";\nimport { getColumnMeta } from \"../services/collection-helpers\";\n\n/**\n * What to do with a filter field that resolves to no column at all.\n *\n * - `\"error\"` (default) — reject the request. A filter that cannot be\n * compiled is *dropped*, and dropping a condition can only ever widen the\n * result set. On a data plane where row-level security is the last line of\n * defence, a typo'd or renamed filter key therefore runs the query without\n * that condition and returns everything RLS happens to allow.\n * - `\"warn\"` — the historical behaviour: log and silently drop the condition.\n * Only for a deployment that knowingly sends filter keys the table does not\n * have and has satisfied itself that widening is safe there.\n */\nexport type UnknownFilterFieldsMode = \"error\" | \"warn\";\n\n/**\n * A user's search term, made safe to drop inside a `%…%` LIKE pattern.\n *\n * The term is already a bind parameter, so this is not about injection. It is\n * about the two things a LIKE metacharacter does when it arrives from a search\n * box:\n *\n * 1. **It changes the query.** `%` and `_` are wildcards, so searching for\n * `50%` returned every row and `a_c` matched `abc`. Nothing the caller\n * could type would find a literal `%`.\n * 2. **It is a cost the caller chooses.** Postgres matches LIKE by\n * backtracking: each `%` re-tries every remaining offset, so\n * `?searchString=a%a%a%a%a%a%a%b` is polynomial with an attacker-chosen\n * exponent — evaluated per row, OR-ed across every string property of the\n * collection, on a sequential scan (a leading `%` cannot use an index), and\n * the page limit does not bound it because the scan happens first.\n *\n * This is the server-side half of the pattern `like-pattern-redos.test.ts`\n * hardened the offline evaluator against; that test's own note (\"the same\n * translation in the Mongo driver hands the expression to the database, where\n * it occupies a server thread instead\") describes this call site.\n *\n * Backslash is the default `ESCAPE` character for LIKE, and the pattern is\n * bound rather than interpolated, so a single backslash here reaches the\n * matcher as one. Escaping the escape character first is what keeps a term\n * ending in `\\` from swallowing the closing `%`.\n *\n * Note this is a *substring search*, not the `like` filter operator: a caller\n * who wants wildcards has `?title=like.foo%` for that, where the pattern is the\n * documented input.\n */\nexport const escapeLikePattern = (value: string): string =>\n value.replace(/[\\\\%_]/g, ch => `\\\\${ch}`);\n\n/**\n * The Drizzle column a relation's column name addresses on a table.\n *\n * A relation names its link in *column* terms — `localKey: \"author_id\"`,\n * `foreignKeyOnTarget: \"author_id\"` — because that is what the database and\n * every FK constraint call it. A Drizzle table is keyed by the *wire* name,\n * `authorId`. Indexing the table with the column, which is what every one of\n * these call sites used to do, therefore finds nothing the moment the two\n * differ: for a `columnName`-carrying property that was already true, and it is\n * now true of every derived foreign key.\n *\n * `undefined` rather than a throw: each caller already has a message naming the\n * relation it was resolving, which is worth more than a generic one here.\n */\n/**\n * The junction table a `manyToMany` relation names is not in the registry.\n *\n * Six sites raised `Junction table not found: <name>`, which is a fact and not\n * a diagnosis. The name in the message is the one the relation *declares*, and\n * the two things it can mean are opposite: the table exists in the database and\n * the generated Drizzle schema predates it, or the table was never created and\n * the relation's `through.table` is a typo. Both are one command away from\n * being settled, and neither was named.\n */\nfunction junctionTableMissing(junctionName: string): Error {\n return new Error(\n `The relation declares a junction table \"${junctionName}\", and no such table is registered. ` +\n \"Either the generated Drizzle schema predates it — run `rebase schema generate` — or the \" +\n \"table does not exist yet: `rebase db push` creates the junctions a relation declares. \" +\n \"If neither helps, check the `through.table` name on the relation.\"\n );\n}\n\nconst relationColumn = (\n table: PgTable<any>,\n collection: CollectionConfig | undefined,\n column: string\n): AnyPgColumn | undefined => {\n const key = fieldKeyForColumn(collection, column);\n return (key in table ? table[key as keyof typeof table] as AnyPgColumn : undefined) || undefined;\n};\n\n/**\n * A table's column by the name a `joinPath` step gives it — the SQL column — or\n * by its Drizzle property key.\n *\n * A column has two names. A generated schema keys a collection table's columns\n * by property (`orderId: uuid(\"order_id\")`) while a step names the column\n * (`from: \"order_id\"`), so a lookup by key alone found nothing on a real app,\n * and the hand-written fixtures — keyed by column name — never showed it.\n * Junction tables are generated keyed by column name, which is why a\n * many-to-many was unaffected.\n */\nexport function joinColumn(table: PgTable<any>, name: string): AnyPgColumn | undefined {\n const columns = getTableColumns(table) as Record<string, AnyPgColumn>;\n const column = getColumnName(name);\n return columns[column] ?? Object.values(columns).find(candidate => candidate.name === column);\n}\n\n/** The target collection of a relation, or `undefined` if its thunk cannot resolve. */\nconst targetOf = (relation: ResolvedRelation): CollectionConfig | undefined => {\n try {\n return relation.target();\n } catch {\n return undefined;\n }\n};\n\n/**\n * The SQL each aggregate sort function compiles to.\n *\n * A lookup rather than interpolation, and `sql.raw` applied to five constants\n * written out here rather than to anything derived from a request. The key is\n * already validated by `parseRelationAggregateSort` — this is the second lock\n * on the same door, so that a future caller reaching the builder directly\n * cannot put a string from the wire where a function name goes.\n */\nconst AGGREGATE_SQL: Record<RelationAggregateFn, SQL> = {\n min: sql.raw(\"min\"),\n max: sql.raw(\"max\"),\n count: sql.raw(\"count\"),\n sum: sql.raw(\"sum\"),\n avg: sql.raw(\"avg\")\n};\n\n/** Column types `ILIKE '%…%'` is defined on. */\nconst ILIKE_SQL_TYPES = /^(text|varchar|character varying|char|character|bpchar|citext)\\b/;\n\n/**\n * Can this column be matched with `ILIKE`?\n *\n * Asked of the column's *declared SQL type*, never with `instanceof`. The\n * previous version tested `column instanceof PgVarchar || … PgText || … PgChar`,\n * and `instanceof` compares class identity: it is only true when the column was\n * constructed by the very same copy of `drizzle-orm` that this module imported.\n *\n * An application's generated schema builds its tables with the app's own\n * `drizzle-orm`, and this driver declares its own dependency on one. When the\n * two ranges do not overlap — an app scaffolded against `^0.44` with a driver\n * asking for `^0.45` — a strict installer gives the driver a second copy, every\n * check returns false, no condition is produced, and the caller compiles that\n * into an impossible `WHERE`. The result is a 200 with an empty page for every\n * search on every collection without a `search` block: the failure looks\n * exactly like \"nothing matched\". Observed in production, not theorised.\n *\n * `getSQLType()` is a value the column reports about itself, so it crosses\n * module instances the way a class identity cannot. It also happens to fix\n * `citext`, which the `instanceof` list never covered.\n */\nconst supportsILike = (column: AnyPgColumn): boolean => {\n const sqlType = typeof column?.getSQLType === \"function\" ? column.getSQLType().toLowerCase() : \"\";\n return ILIKE_SQL_TYPES.test(sqlType);\n};\n\n/**\n * Process-wide default, set once when the driver is constructed.\n *\n * The condition builder is a set of *static* methods reached from a dozen\n * `FetchService` call sites, none of which carry the driver's config — the\n * service is built from `(db, registry)` alone. Threading an option from\n * `createPostgresAdapter` down to each of them would mean touching every\n * intermediate signature to plumb a value that is a single deployment-wide\n * switch. A module-level default set at adapter construction, plus an explicit\n * per-call override for callers that have one (tests, mainly), buys the same\n * control for none of the churn. It is safe by default, so the only reason to\n * set it at all is to opt *out*.\n */\nlet defaultUnknownFilterFieldsMode: UnknownFilterFieldsMode = \"error\";\n\n/** Set the process-wide behaviour for unresolvable filter fields. */\nexport function configureUnknownFilterFields(mode: UnknownFilterFieldsMode): void {\n defaultUnknownFilterFieldsMode = mode;\n}\n\n/** The process-wide behaviour for unresolvable filter fields. */\nexport function getUnknownFilterFieldsMode(): UnknownFilterFieldsMode {\n return defaultUnknownFilterFieldsMode;\n}\n\n/** Per-call context for compiling a filter into SQL. */\nexport interface FilterCompilationOptions {\n /**\n * Overrides the process-wide {@link UnknownFilterFieldsMode} for this call.\n */\n unknownFields?: UnknownFilterFieldsMode;\n /**\n * The collection the filter is written against. Its resolved relations are\n * what turn an owning-relation filter key into the foreign-key column it\n * actually lives in; without it only the default key shapes can be guessed.\n */\n collection?: CollectionConfig;\n /**\n * The driver's registry, for relations whose link is not on this row at\n * all. A `manyToMany` compiles to an `EXISTS` over its junction and a\n * `hasMany`/`hasOne` to one over the target table — neither of which this\n * builder can reach from the collection alone.\n */\n registry?: PostgresCollectionRegistry;\n /**\n * The key column of the table being filtered — what those `EXISTS`\n * subqueries correlate back to.\n *\n * It has to be the Drizzle column object rather than a name: a column\n * renders qualified with its own table, which is what binds it to the\n * *outer* row instead of to the junction or target aliased inside the\n * subquery. See {@link DrizzleConditionBuilder.buildRelationFilterCondition}.\n */\n sourceIdColumn?: AnyPgColumn;\n}\n\n/**\n * What a filter field turns out to name.\n *\n * A field naming a column compiles to a comparison on it. A field naming a\n * relation that owns no column here compiles to a whole `EXISTS` condition\n * instead, so there is no column to hand back — which is why resolution\n * answers with a discriminated result rather than a column. The caller cannot\n * tell the two apart from the field name, and the difference is not cosmetic:\n * one is `column <op> value`, the other is a correlated subquery.\n */\ntype FilterTarget =\n | { kind: \"column\"; column: AnyPgColumn }\n | {\n /** A path *inside* a json/jsonb column — `metadata->>country`. */\n kind: \"json\";\n column: AnyPgColumn;\n /** The keys to walk, outermost first. Always at least one. */\n path: string[];\n }\n | {\n kind: \"relation\";\n relation: ResolvedForeignKeyOnTarget | ResolvedManyToMany;\n /** Bound here so the compile step cannot be reached without them. */\n registry: PostgresCollectionRegistry;\n sourceIdColumn: AnyPgColumn;\n }\n | {\n /**\n * A *column of the related row* — `applications.status`. Same `EXISTS`\n * as `relation`, with the predicate moved off the target's id and onto\n * one of its columns.\n */\n kind: \"relation-field\";\n /**\n * `via` is not among them: it is refused at resolution, and leaving it\n * in the type would let a later edit reach the compile step with a\n * relation there is no correlation for.\n */\n relation: ResolvedBelongsTo | ResolvedForeignKeyOnTarget | ResolvedManyToMany;\n registry: PostgresCollectionRegistry;\n /** The column on *this* table the subquery correlates back to. */\n sourceIdColumn: AnyPgColumn;\n /** The table the predicate is asked of, already resolved. */\n targetTable: PgTable<any>;\n /** The column on {@link targetTable} the predicate compares. */\n targetColumn: AnyPgColumn;\n };\n\n/**\n * Split `metadata->address->>city` into its column and its path.\n *\n * The arrows are PostgREST's spelling and Postgres's own, so the filter reads\n * the same as the SQL it becomes — and, more usefully, the same as what someone\n * would have written by hand in the SQL console while working out what to ask\n * for. A field with no arrow is not a JSON path and returns `undefined`, which\n * leaves every existing filter on exactly the path it took before.\n *\n * Both arrows are accepted and mean the same thing here: the extraction is\n * always compiled to `->>` (text) at the leaf, because that is the only form a\n * comparison can be made against. `->` is allowed because people write it out\n * of habit, and refusing it would be pedantry about a distinction this layer\n * erases anyway.\n */\n/**\n * Split `applications.status` into the relation and the column it addresses.\n *\n * One dot, and only the first one: a relation name cannot contain a dot, and\n * everything after it is handed to the target as a single column key. A second\n * dot would be a second hop — `talents.applications.job.title` — which is a\n * different feature (it needs a chain of subqueries, and a chain has to decide\n * what \"some\" means at every level), so it is refused rather than silently read\n * as a column named `job.title` that no table has.\n *\n * Returns `undefined` for a field with no dot, which leaves every existing\n * filter on exactly the path it took before.\n */\nfunction parseRelationFieldPath(field: string): { relationKey: string; fieldKey: string } | undefined {\n const dot = field.indexOf(\".\");\n if (dot <= 0 || dot === field.length - 1) return undefined;\n return { relationKey: field.slice(0, dot), fieldKey: field.slice(dot + 1) };\n}\n\nfunction parseJsonFieldPath(field: string): { columnKey: string; path: string[] } | undefined {\n if (!field.includes(\"->\")) return undefined;\n\n const segments = field.split(/->>?/).map(s => s.trim()).filter(Boolean);\n if (segments.length < 2) return undefined;\n\n const [columnKey, ...path] = segments;\n return { columnKey, path };\n}\n\n/**\n * Filter values may arrive as relation wire objects — `EntityRelation`\n * instances or their JSON form `{ __type: \"relation\", id, path }` — e.g. when\n * the admin filters a relation column. SQL comparisons need the raw id, so\n * unwrap them here (element-wise for list operators like `in` / `not-in`).\n */\nfunction unwrapRelationFilterValue(value: unknown): unknown {\n if (Array.isArray(value)) return value.map(unwrapRelationFilterValue);\n const relation = normalizeToEntityRelation(value);\n return relation ? relation.id : value;\n}\n\n/**\n * The operand of `in`/`not-in`, as a list.\n *\n * A scalar is the one-element list, because that is what it means and because\n * the wire produces one: `?filter=id.in.5` parses to the string `\"5\"`, not to\n * `[\"5\"]` — the REST dialect only builds an array when the value is\n * parenthesised. Treating that as malformed and dropping the condition turned\n * a perfectly ordinary query into an unfiltered read.\n *\n * The empty list stays empty. Callers must decide what \"no candidates\" means\n * for their operator — it is `FALSE` for `in` and `TRUE` for `not-in` — and\n * neither of those is \"no condition at all\".\n */\nfunction toMembershipList(value: unknown): unknown[] {\n return Array.isArray(value) ? value : [value];\n}\n\n/** Drizzle dynamic query builder — accepts innerJoin + where chaining */\n\nexport interface DrizzleDynamicQuery {\n innerJoin(table: PgTable<any>, condition: SQL): this;\n where(condition: SQL | undefined): this;\n limit(limit: number): this;\n}\n\n/**\n * Unified condition builder for Drizzle/PostgreSQL queries.\n *\n * This class uses static methods and satisfies the ConditionBuilderStatic<SQL> type.\n * It translates Rebase filter conditions to Drizzle SQL conditions.\n *\n * @example\n * const builder: ConditionBuilderStatic<SQL> = DrizzleConditionBuilder;\n */\nexport class DrizzleConditionBuilder {\n\n /**\n * Express \"reachable from this parent through this relation\" as a plain\n * `WHERE` condition on the target table.\n *\n * This is the primitive that lets a relation be a *filter* rather than an\n * addressing scheme. A nested listing used to be served by its own query\n * builder — `fetchEntitiesUsingJoins`, which grew joins the root pipeline\n * did not have and lost the options the root pipeline did have (offset,\n * filter, orderBy, include). Reduced to a condition, the same listing runs\n * through the ordinary collection query, so it inherits all of them and\n * there is one read path instead of two.\n *\n * The shapes:\n * - inverse FK → `target.<fk> = :parentId`, a column comparison.\n * - `through` → `EXISTS (SELECT 1 FROM junction …)`, correlated on the\n * target's key, so the junction never multiplies rows the\n * way an `INNER JOIN` would.\n * - `joinPath` → the same `EXISTS`, with the path's steps joined inside\n * it and the final step correlating to the outer row.\n */\n static buildRelationScopeCondition(\n relation: ResolvedRelation,\n /**\n * Lazy: `via`, `belongsTo`, and a foreign key that points at a\n * `sourceKey` need the parent's own table. A junction and a plain\n * foreign key are expressible from the parent's *id* alone, and\n * requiring the table for them would make a child listing fail on a\n * parent whose table isn't registered.\n *\n * `key` is the parent row's key, every column with its value: those\n * three shapes find the parent row itself, and a lookup on the first\n * column of a composite key finds every row that shares it.\n */\n parent: () => { table: PgTable<any>; key: { column: AnyPgColumn; value: string | number }[] },\n /**\n * The value a plain foreign key or a junction column holds for this\n * parent. One column, so it reaches a single-column key only —\n * `findRelationDefects` refuses those links into a composite one.\n */\n parentId: string | number,\n targetTable: PgTable<any>,\n targetIdColumn: AnyPgColumn,\n registry: PostgresCollectionRegistry\n ): SQL {\n // The parent row, inside a subquery that names its table unaliased.\n const parentRow = (key: { column: AnyPgColumn; value: string | number }[]): SQL =>\n sql.join(key.map(({ column, value }) => sql`${column} = ${value}`), sql` AND `);\n\n switch (relation.kind) {\n case \"via\": {\n const { table, key } = parent();\n return this.buildJoinPathScopeCondition(\n relation.joinPath, table, key, targetTable, registry\n );\n }\n\n case \"manyToMany\": {\n const { table: junctionName, sourceColumn, targetColumn } = relation.through;\n const junctionTable = registry.getTable(junctionName);\n if (!junctionTable) {\n throw junctionTableMissing(junctionName);\n }\n const sourceCol = junctionTable[sourceColumn as keyof typeof junctionTable] as AnyPgColumn;\n const targetCol = junctionTable[targetColumn as keyof typeof junctionTable] as AnyPgColumn;\n if (!sourceCol || !targetCol) {\n throw new Error(\n `Junction columns '${sourceColumn}'/'${targetColumn}' not found in '${junctionName}'`\n );\n }\n // Correlated, not joined: a join through a junction multiplies\n // the target rows by the number of matching links and silently\n // breaks `limit`/`offset`.\n //\n // The junction is aliased and referenced by identifier, never as a\n // Drizzle column. A column object carries no table qualifier of its\n // own — it is rendered against whatever the surrounding builder\n // thinks the current table is — so inside `db.query.findMany`, which\n // aliases the root table, `${sourceCol}` came out qualified with the\n // *target's* alias: `podcast.podcast_id`, a column that does not\n // exist. That aborts the transaction, and the fallback read then\n // fails on the poisoned transaction rather than on anything to do\n // with the relation. Only `targetIdColumn` stays a column object,\n // because that one *must* bind to the outer row to correlate.\n //\n // Aliasing also disambiguates a self-referential many-to-many, where\n // the junction and the target are the same table.\n const junctionAlias = \"__rel_m2m\";\n const junctionRef = (column: AnyPgColumn) =>\n sql`${sql.identifier(junctionAlias)}.${sql.identifier(column.name)}`;\n return sql`EXISTS (SELECT 1 FROM ${junctionTable} AS ${sql.identifier(junctionAlias)} WHERE ${junctionRef(targetCol)} = ${targetIdColumn} AND ${junctionRef(sourceCol)} = ${parentId})`;\n }\n\n case \"hasOne\":\n case \"hasMany\": {\n const fkColumn = relationColumn(targetTable, targetOf(relation), relation.foreignKeyOnTarget);\n if (!fkColumn) {\n throw new Error(\n `Foreign key column '${relation.foreignKeyOnTarget}' not found in the target table of ` +\n `relation '${relation.relationName}'.`\n );\n }\n if (!relation.sourceKey) return eq(fkColumn, parentId);\n\n // A link on a natural key: the foreign key holds a column of the\n // parent row, not its id, so the parent has to be read. As a\n // subquery rather than a prior SELECT, for the same reason\n // `belongsTo` below is — one statement sees one snapshot, and a\n // scope condition that read the key separately could be built\n // from a value the very next statement no longer agrees with.\n const { table, key } = parent();\n return sql`${fkColumn} = (SELECT ${sql.identifier(relation.sourceKey)} FROM ${table} WHERE ${parentRow(key)})`;\n }\n\n case \"belongsTo\": {\n // The single target row the parent's foreign key points at.\n const { table, key } = parent();\n return sql`${targetIdColumn} = (SELECT ${sql.identifier(relation.localKey)} FROM ${table} WHERE ${parentRow(key)})`;\n }\n\n default: {\n // Exhaustive. There is no \"declares nothing\" case to fall\n // through to any more — every kind names its own link.\n const exhaustive: never = relation;\n throw new Error(`Unknown relation kind: ${JSON.stringify(exhaustive)}`);\n }\n }\n }\n\n /**\n * `EXISTS` for an explicit `joinPath`.\n *\n * The path is declared source → target. The subquery replays every step but\n * the last from inside, and turns the last one into the correlation with the\n * outer target row — so the target table is never named twice and needs no\n * alias. Each intermediate table is aliased positionally, which keeps a path\n * that revisits a table (a self-referencing many-to-many) unambiguous.\n */\n private static buildJoinPathScopeCondition(\n joinPath: JoinStep[],\n parentTable: PgTable<any>,\n /** The parent row's key, every column with its value. */\n parentKey: { column: AnyPgColumn; value: string | number }[],\n targetTable: PgTable<any>,\n registry: PostgresCollectionRegistry\n ): SQL {\n const sourceAlias = \"__rel_src\";\n const aliasFor = (index: number) => `__rel_j${index}`;\n\n // Column reference against the previous hop: the aliased source for the\n // first step, the previous aliased join table after that.\n const fromRef = (stepIndex: number, column: string) =>\n sql`${sql.identifier(stepIndex === 0 ? sourceAlias : aliasFor(stepIndex - 1))}.${sql.identifier(getColumnName(column))}`;\n\n const pairs = (step: JoinStep): { from: string; to: string }[] => {\n const from = Array.isArray(step.on.from) ? step.on.from : [step.on.from];\n const to = Array.isArray(step.on.to) ? step.on.to : [step.on.to];\n if (from.length !== to.length) {\n throw new Error(`Join step on '${step.table}' has ${from.length} \\`from\\` columns and ${to.length} \\`to\\` columns`);\n }\n return from.map((f, i) => ({ from: f, to: to[i] }));\n };\n\n const inner = joinPath.slice(0, -1);\n const last = joinPath[joinPath.length - 1];\n\n const joins: SQL[] = inner.map((step, index) => {\n const table = registry.getTable(step.table);\n if (!table) throw new Error(`Join table not found: ${step.table}`);\n const on = pairs(step).map(({ from, to }) =>\n sql`${fromRef(index, from)} = ${sql.identifier(aliasFor(index))}.${sql.identifier(getColumnName(to))}`\n );\n return sql`JOIN ${table} AS ${sql.identifier(aliasFor(index))} ON ${sql.join(on, sql` AND `)}`;\n });\n\n // The last step correlates to the outer row instead of joining the\n // target table into the subquery.\n //\n // On the step's own `to` column, which is not always the target's\n // primary key: a one-step path like `{ table: \"posts\", on: { from:\n // \"id\", to: \"author_id\" } }` correlates through a foreign key, and\n // matching `authors.id = posts.id` there compares two unrelated\n // identifiers — which returns nothing, quietly. Referencing the Drizzle\n // column rather than a bare name also keeps it qualified, so it binds\n // to the outer target and not to a table joined inside the EXISTS.\n const targetColumn = (name: string): AnyPgColumn => {\n const column = joinColumn(targetTable, name);\n if (!column) {\n throw new Error(`Join step column '${name}' not found in the target table of this joinPath`);\n }\n return column;\n };\n\n const correlation = sql.join(\n pairs(last).map(({ from, to }) => sql`${fromRef(inner.length, from)} = ${targetColumn(to)}`),\n sql` AND `\n );\n\n const joinsSql = joins.length > 0 ? sql` ${sql.join(joins, sql` `)}` : sql``;\n\n const parentRow = sql.join(\n parentKey.map(({ column, value }) => sql`${sql.identifier(sourceAlias)}.${sql.identifier(column.name)} = ${value}`),\n sql` AND `\n );\n\n return sql`EXISTS (SELECT 1 FROM ${parentTable} AS ${sql.identifier(sourceAlias)}${joinsSql} WHERE ${parentRow} AND ${correlation})`;\n }\n\n /**\n * What a filter field names, or `undefined` if it names nothing.\n *\n * Three ways a field resolves. It may address its column directly; it may\n * be an owning relation, whose foreign key is a column here; or it may be\n * a relation whose link lives on another table entirely, which compiles to\n * a subquery instead of a column. Only a field that resolves to *none* of\n * them is an error, and by default it is one: see\n * {@link UnknownFilterFieldsMode} for why silently dropping it is a\n * data-exposure primitive rather than a convenience.\n *\n * For an owning relation the relation's own `localKey` is the authority,\n * not `<field>_id`. The default local key is `generateForeignKeyName`,\n * which snake-cases *and singularises* — `userProfile` → `user_profile_id`,\n * `users` → `user_id` — and it can be overridden outright. Guessing\n * `<field>_id` therefore misses perfectly ordinary owning relations, and\n * with this resolution failing closed that miss is a 400 on a filter that\n * has nothing wrong with it. The guesses stay, last, for callers that hand\n * over no collection to resolve against.\n *\n * The subquery kinds need a registry and the source table's key column on\n * top of the collection. A caller that supplies neither gets the behaviour\n * it had before they were compilable — unresolvable, and so fail-closed —\n * rather than a half-built condition.\n */\n private static resolveFilterTarget(\n table: PgTable<any>,\n field: string,\n collectionPath: string,\n mode: UnknownFilterFieldsMode,\n options: FilterCompilationOptions\n ): FilterTarget | undefined {\n const { collection, registry, sourceIdColumn } = options;\n\n const columnAt = (key: string): AnyPgColumn | undefined =>\n (key in table ? table[key as keyof typeof table] as AnyPgColumn : undefined) || undefined;\n\n const direct = columnAt(field);\n if (direct) return { kind: \"column\", column: direct };\n\n // Checked after the direct lookup, so a column literally named with an\n // arrow — which Postgres permits, if someone quoted it — still wins.\n const jsonPath = parseJsonFieldPath(field);\n if (jsonPath) {\n const base = columnAt(jsonPath.columnKey);\n if (base) {\n const meta = getColumnMeta(base);\n // Refused rather than compiled: `->>` on a text column is a\n // Postgres error at execution time, which surfaces as a 500 on\n // a request whose only fault is a typo'd column name.\n if (meta.dataType !== \"json\" && meta.columnType !== \"PgJsonb\" && meta.columnType !== \"PgJson\") {\n throw ApiError.badRequest(\n `Cannot filter inside \"${jsonPath.columnKey}\" — it is not a json or jsonb column.`,\n \"INVALID_FILTER_FIELD\"\n );\n }\n return { kind: \"json\", column: base, path: jsonPath.path };\n }\n }\n\n if (collection) {\n const relation = resolveCollectionRelations(collection)[field];\n\n // Owning relation, resolved: the relation names its own local key.\n if (relation?.kind === \"belongsTo\") {\n const foreignKey = relationColumn(table, collection, relation.localKey);\n if (foreignKey) return { kind: \"column\", column: foreignKey };\n }\n\n // The link is on the target table or in a junction. `via` is left\n // out: its join path is authored source → target with no stated\n // inverse, so reversing it into a filter is a different problem\n // from the two shapes below rather than a third case of them.\n if (relation && (hasForeignKeyOnTarget(relation) || isManyToMany(relation)) && registry && sourceIdColumn) {\n // The `EXISTS` correlates the target's foreign key with a column\n // on *this* table, and that is the primary key only when the\n // link joins on it. A `sourceKey` names a different one, and\n // correlating on the id anyway silently matches nothing —\n // \"filter by this relation\" would quietly return zero rows.\n const correlationColumn = hasForeignKeyOnTarget(relation) && relation.sourceKey\n ? relationColumn(table, collection, relation.sourceKey)\n : sourceIdColumn;\n if (!correlationColumn) {\n throw new Error(\n `\\`sourceKey: \"${(relation as ResolvedForeignKeyOnTarget).sourceKey}\"\\` on relation ` +\n `'${relation.relationName}' is not a column on '${collectionPath}', so a filter on ` +\n \"that relation has nothing to correlate against.\"\n );\n }\n return { kind: \"relation\", relation, registry, sourceIdColumn: correlationColumn };\n }\n\n // `applications.status` — a column of the related row rather than\n // its id. Resolved last of the relation shapes, so a relation\n // literally named with a dot still wins above.\n const relationField = parseRelationFieldPath(field);\n if (relationField && registry && sourceIdColumn) {\n const target = this.resolveRelationFieldTarget(\n table, relationField, collection, registry, sourceIdColumn, field, collectionPath\n );\n if (target) return target;\n }\n }\n\n // No collection in hand — the shapes an owning relation's key takes by\n // default (e.g. `project` → `projectId`, `userProfile` →\n // `userProfileId`). The snake forms stay in the list because a project\n // may have authored the property under its column name, which is still\n // its wire name.\n for (const guess of [\n `${field}Id`,\n toWireKey(generateForeignKeyName(field)),\n `${field}_id`,\n generateForeignKeyName(field)\n ]) {\n const foreignKey = columnAt(guess);\n if (foreignKey) return { kind: \"column\", column: foreignKey };\n }\n\n if (mode === \"warn\") {\n logger.warn(`Filtering by field '${field}', but it does not exist in table for collection '${collectionPath}'`);\n return undefined;\n }\n\n let validFields: string[] = [];\n try {\n validFields = Object.keys(getTableColumns(table)).sort();\n } catch {\n // A table stand-in without Drizzle's column symbols — the message\n // is worth less without the list, but not worth failing over.\n }\n\n // Not `expected`: unlike an anonymous token refresh, this is never a\n // routine outcome. It means a filter key and the schema have drifted\n // apart, which used to widen results silently — exactly the thing an\n // operator wants in the log at warn.\n throw ApiError.badRequest(\n `Unknown filter field '${field}' on collection '${collectionPath}'` +\n (validFields.length > 0 ? `. Valid fields: ${validFields.join(\", \")}` : \"\"),\n \"UNKNOWN_FILTER_FIELD\",\n { field, collection: collectionPath, ...(validFields.length > 0 && { validFields }) }\n );\n }\n\n /**\n * `applications.status` — the relation, and the column of the target it\n * addresses.\n *\n * `undefined` when the first segment names no relation: the field simply is\n * not a relation path, and resolution carries on to the guesses and then to\n * the unknown-field answer, which is where a typo belongs. A segment that\n * *does* name a relation is a different matter — the author plainly meant\n * this shape — so everything after that point throws rather than returning,\n * naming what went wrong. Falling through would report \"unknown filter\n * field 'applications.status'\" and list the columns of the wrong table.\n *\n * `via` is refused for the reason it is absent from\n * `filterableRelationKinds`: its join path is authored source → target with\n * no stated inverse, so there is nothing to correlate a subquery back to.\n */\n private static resolveRelationFieldTarget(\n table: PgTable<any>,\n path: { relationKey: string; fieldKey: string },\n collection: CollectionConfig,\n registry: PostgresCollectionRegistry,\n sourceIdColumn: AnyPgColumn,\n field: string,\n collectionPath: string\n ): FilterTarget | undefined {\n const relation = resolveCollectionRelations(collection)[path.relationKey];\n if (!relation) return undefined;\n\n if (relation.kind === \"via\") {\n throw ApiError.badRequest(\n `Cannot filter by '${field}' on collection '${collectionPath}': '${path.relationKey}' is a ` +\n \"`via` relation, whose join path is authored one way only, so there is nothing to correlate \" +\n \"a subquery back to.\",\n \"UNSUPPORTED_RELATION_FILTER\",\n { field, collection: collectionPath, relation: path.relationKey, kind: relation.kind }\n );\n }\n\n const targetCollection = targetOf(relation);\n const targetTable = targetCollection && registry.getTable(getTableName(targetCollection));\n if (!targetCollection || !targetTable) {\n throw new Error(\n `Table not found for the target of relation '${relation.relationName}' on '${collectionPath}', ` +\n `so '${field}' has nothing to filter against.`\n );\n }\n\n const targetColumn = relationColumn(targetTable, targetCollection, path.fieldKey)\n ?? (path.fieldKey in targetTable\n ? targetTable[path.fieldKey as keyof typeof targetTable] as AnyPgColumn\n : undefined);\n if (!targetColumn) {\n let validFields: string[] = [];\n try {\n validFields = Object.keys(getTableColumns(targetTable)).sort();\n } catch {\n // Same tolerance as the column path: a table stand-in without\n // Drizzle's column symbols still gets the error, just no list.\n }\n throw ApiError.badRequest(\n `Unknown field '${path.fieldKey}' on '${targetCollection.slug}', the target of relation ` +\n `'${path.relationKey}' on collection '${collectionPath}'` +\n (validFields.length > 0 ? `. Valid fields: ${validFields.join(\", \")}` : \"\"),\n \"UNKNOWN_FILTER_FIELD\",\n {\n field,\n collection: collectionPath,\n relation: path.relationKey,\n targetCollection: targetCollection.slug,\n ...(validFields.length > 0 && { validFields })\n }\n );\n }\n\n // The column on *this* table the subquery correlates back to. Only\n // `hasMany`/`hasOne` can name a different one; `belongsTo` correlates\n // from its own foreign key, and a many-to-many from the primary key the\n // junction was built against.\n const correlationColumn = relation.kind === \"belongsTo\"\n ? relationColumn(table, collection, relation.localKey)\n : hasForeignKeyOnTarget(relation) && relation.sourceKey\n ? relationColumn(table, collection, relation.sourceKey)\n : sourceIdColumn;\n if (!correlationColumn) {\n throw new Error(\n `Relation '${relation.relationName}' on '${collectionPath}' names a key that is not a column ` +\n `there, so '${field}' has nothing to correlate against.`\n );\n }\n\n return {\n kind: \"relation-field\",\n relation,\n registry,\n sourceIdColumn: correlationColumn,\n targetTable,\n targetColumn\n };\n }\n\n /**\n * Build filter conditions from FilterValues\n */\n static buildFilterConditions<M extends Record<string, unknown>>(\n filter: FilterValues<Extract<keyof M, string>>,\n table: PgTable<any>,\n collectionPath: string,\n options: FilterCompilationOptions = {}\n ): SQL[] {\n const mode = options.unknownFields ?? defaultUnknownFilterFieldsMode;\n const conditions: SQL[] = [];\n\n for (const [field, filterParam] of Object.entries(filter)) {\n if (!filterParam) continue;\n\n const target = this.resolveFilterTarget(table, field, collectionPath, mode, options);\n if (!target) continue;\n\n // One tuple or an array of them — the grammar, read the same way by\n // every compiler. See `toFilterTuples`.\n for (const [op, value] of toFilterTuples(filterParam)) {\n const condition = this.compileFilterTarget(target, op, value, field, collectionPath);\n if (condition) {\n conditions.push(condition);\n }\n }\n }\n\n return conditions;\n }\n\n /**\n * Build logical conditions recursively from LogicalCondition or FilterCondition\n */\n static buildLogicalConditions(\n cond: LogicalCondition | FilterCondition,\n table: PgTable<any>,\n collectionPath: string,\n options: FilterCompilationOptions = {}\n ): SQL | null {\n if (\"type\" in cond) {\n const subSQLs = cond.conditions\n .map(c => this.buildLogicalConditions(c, table, collectionPath, options))\n .filter((sql): sql is SQL => sql !== null);\n if (subSQLs.length === 0) return null;\n if (cond.type === \"or\") return or(...subSQLs) ?? null;\n const conjunction = and(...subSQLs) ?? null;\n // `not` negates the *conjunction* of its conditions — the rule\n // stated on `LogicalCondition`, applied here as a real `NOT (...)`.\n //\n // Not by inverting the operators underneath: SQL is three-valued,\n // so `NOT (a AND b)` and `(NOT a) OR (NOT b)` stop agreeing the\n // moment a NULL is involved, and only one of them is the query the\n // caller wrote. `NOT (status = 'draft')` also excludes rows whose\n // status is NULL, which is what `NOT` means and what a caller\n // reaching for it is asking for.\n if (cond.type === \"not\") return conjunction ? not(conjunction) : null;\n return conjunction;\n } else {\n // A dropped leaf is worse here than in a flat filter: inside an\n // `or(...)` the disjunction loses a branch, so the surviving\n // branches match on their own and the result set widens by\n // everything the dropped leaf would have excluded.\n const target = this.resolveFilterTarget(\n table,\n cond.column,\n collectionPath,\n options.unknownFields ?? defaultUnknownFilterFieldsMode,\n options\n );\n if (!target) return null;\n return this.compileFilterTarget(\n target, cond.operator as WhereFilterOp, cond.value, cond.column, collectionPath\n );\n }\n }\n\n /** Dispatch a resolved filter field onto the shape it actually compiles to. */\n private static compileFilterTarget(\n target: FilterTarget,\n op: WhereFilterOp,\n value: unknown,\n field: string,\n collectionPath: string\n ): SQL | null {\n if (target.kind === \"column\") {\n return this.buildSingleFilterCondition(target.column, op, value);\n }\n if (target.kind === \"json\") {\n return this.buildJsonPathCondition(target.column, target.path, op, value);\n }\n if (target.kind === \"relation-field\") {\n return this.buildRelationFieldCondition(target, op, value, field, collectionPath);\n }\n return this.buildRelationFilterCondition(\n target.relation, op, value, target.sourceIdColumn, target.registry, field, collectionPath\n );\n }\n\n /**\n * A comparison against a value extracted from a json/jsonb column.\n *\n * The path is walked with `->` and the leaf taken with `->>`, so what comes\n * out is always **text**. That is the whole of the type story, and it is\n * the part worth being explicit about, because the alternatives are all\n * worse:\n *\n * - text comparison alone makes `[\"<\", 100]` compare lexically, where\n * `\"9\"` is greater than `\"100\"`;\n * - casting unconditionally makes every filter on a non-numeric value a\n * runtime `invalid input syntax for type numeric` — a 500 on a row whose\n * JSON simply holds a string.\n *\n * So the *filter value* decides. A number on an ordering comparison casts\n * both sides to numeric; everything else compares as text, with booleans\n * rendered the way `->>` renders them (`\"true\"` / `\"false\"`). A row whose\n * JSON holds a non-numeric value at a path being compared numerically is\n * excluded rather than fatal, which is what `IS NOT NULL`-style filtering\n * means everywhere else in this file.\n *\n * The path segments are bound as parameters, never interpolated: they come\n * from a query string, and `->>` takes a text parameter perfectly well.\n */\n private static buildJsonPathCondition(\n column: AnyPgColumn,\n path: string[],\n op: WhereFilterOp,\n value: unknown\n ): SQL | null {\n // Every segment but the last with `->` (staying in json), the last\n // with `->>` (leaving as text).\n let expr: SQL = sql`${column}`;\n for (const key of path.slice(0, -1)) {\n expr = sql`${expr} -> ${key}`;\n }\n const leaf = sql`${expr} ->> ${path[path.length - 1]}`;\n\n const numericComparison = typeof value === \"number\" &&\n (op === \">\" || op === \">=\" || op === \"<\" || op === \"<=\");\n\n if (numericComparison) {\n // The guard is what keeps this from being a 500: rows whose value\n // at this path is not a number are excluded, not fatal.\n const numeric = sql`CASE WHEN ${leaf} ~ '^-?[0-9]+(\\\\.[0-9]+)?$' THEN (${leaf})::numeric END`;\n switch (op) {\n case \">\": return sql`${numeric} > ${value}`;\n case \">=\": return sql`${numeric} >= ${value}`;\n case \"<\": return sql`${numeric} < ${value}`;\n case \"<=\": return sql`${numeric} <= ${value}`;\n }\n }\n\n const asText = (v: unknown): string => typeof v === \"boolean\" ? String(v) : String(v);\n\n switch (op) {\n case \"==\":\n return value === null || value === undefined ? sql`${leaf} IS NULL` : sql`${leaf} = ${asText(value)}`;\n case \"!=\":\n return value === null || value === undefined ? sql`${leaf} IS NOT NULL` : sql`${leaf} != ${asText(value)}`;\n case \">\": return sql`${leaf} > ${asText(value)}`;\n case \">=\": return sql`${leaf} >= ${asText(value)}`;\n case \"<\": return sql`${leaf} < ${asText(value)}`;\n case \"<=\": return sql`${leaf} <= ${asText(value)}`;\n case \"like\": return sql`${leaf} LIKE ${asText(value)}`;\n case \"ilike\": return sql`${leaf} ILIKE ${asText(value)}`;\n case \"not-like\": return sql`${leaf} NOT LIKE ${asText(value)}`;\n case \"not-ilike\": return sql`${leaf} NOT ILIKE ${asText(value)}`;\n case \"is-null\": return sql`${leaf} IS NULL`;\n case \"is-not-null\": return sql`${leaf} IS NOT NULL`;\n case \"in\":\n case \"not-in\": {\n if (value === null || value === undefined) {\n return op === \"in\" ? sql`${leaf} IS NULL` : sql`${leaf} IS NOT NULL`;\n }\n const values = toMembershipList(value).map(asText);\n // Same inversion guard as the column path: an empty list\n // matches nothing, and dropping the condition would match\n // everything.\n if (values.length === 0) return op === \"in\" ? sql`FALSE` : sql`TRUE`;\n const list = sql.join(values.map(v => sql`${v}`), sql`, `);\n return op === \"in\" ? sql`${leaf} IN (${list})` : sql`${leaf} NOT IN (${list})`;\n }\n default:\n // `array-contains` and friends are about the column, not a\n // scalar inside it — `metadata @> '{\"tags\":[\"x\"]}'` is the\n // question, and it is asked of the column directly.\n throw ApiError.badRequest(\n `Operator \"${op}\" is not supported on a JSON path. Use it on the column itself.`,\n \"INVALID_FILTER_OPERATOR\"\n );\n }\n }\n\n /**\n * A filter on a relation that owns no column on this row — `EXISTS` over\n * the rows it reaches.\n *\n * `posts` filtered by `tags == <tagId>` is not a comparison on `posts`; it\n * is a question about the junction:\n *\n * EXISTS (SELECT 1 FROM posts_tags AS j\n * WHERE j.post_id = posts.id AND j.tag_id = <tagId>)\n *\n * which is {@link buildRelationScopeCondition}'s many-to-many shape with\n * source and target swapped — there the junction's *target* column\n * correlates and the source is pinned; here the *source* column correlates\n * and the target is what the filter constrains.\n *\n * `hasMany`/`hasOne` are the same shape one table over: the target row\n * carries the foreign key, so the correlation is on that key and the\n * compared column is the target's own id.\n *\n * `EXISTS` and not a join, for the reason the scope condition gives: a join\n * through a junction multiplies the outer rows by the number of matching\n * links, which duplicates results and silently breaks `limit`/`offset`.\n *\n * Everything inside the subquery is referenced by identifier against a\n * local alias, and only `sourceIdColumn` stays a Drizzle column object —\n * again see {@link buildRelationScopeCondition}, which explains why a\n * column object renders against whatever table the surrounding builder\n * thinks is current and so cannot be used for the inner references. The\n * alias is also what keeps a self-referential relation unambiguous\n * (`categories.children`, or a many-to-many whose junction and target are\n * the same table), where the subquery's table and the outer one coincide.\n */\n static buildRelationFilterCondition(\n relation: ResolvedForeignKeyOnTarget | ResolvedManyToMany,\n op: WhereFilterOp,\n value: unknown,\n sourceIdColumn: AnyPgColumn,\n registry: PostgresCollectionRegistry,\n field: string,\n collectionPath: string\n ): SQL {\n const alias = \"__rel_filter\";\n const ref = (column: AnyPgColumn) =>\n sql`${sql.identifier(alias)}.${sql.identifier(column.name)}`;\n\n let scanTable: PgTable<any>;\n let correlation: SQL;\n let comparedColumn: AnyPgColumn;\n\n if (relation.kind === \"manyToMany\") {\n const { table: junctionName, sourceColumn, targetColumn } = relation.through;\n const junctionTable = registry.getTable(junctionName);\n if (!junctionTable) {\n throw junctionTableMissing(junctionName);\n }\n const sourceCol = junctionTable[sourceColumn as keyof typeof junctionTable] as AnyPgColumn;\n const targetCol = junctionTable[targetColumn as keyof typeof junctionTable] as AnyPgColumn;\n if (!sourceCol || !targetCol) {\n throw new Error(\n `Junction columns '${sourceColumn}'/'${targetColumn}' not found in '${junctionName}'`\n );\n }\n scanTable = junctionTable;\n correlation = sql`${ref(sourceCol)} = ${sourceIdColumn}`;\n comparedColumn = targetCol;\n } else {\n const targetCollection = relation.target();\n const targetTable = registry.getTable(getTableName(targetCollection));\n if (!targetTable) {\n throw new Error(\n `Table not found for the target of relation '${relation.relationName}' ` +\n `(collection '${targetCollection.slug}')`\n );\n }\n const fkColumn = relationColumn(targetTable, targetCollection, relation.foreignKeyOnTarget);\n if (!fkColumn) {\n throw new Error(\n `Foreign key column '${relation.foreignKeyOnTarget}' not found in the target table of ` +\n `relation '${relation.relationName}'.`\n );\n }\n // The filter value is a target row's id, so that is what the\n // subquery compares — the foreign key is spent on the correlation.\n const targetIdColumn = this.primaryKeyColumn(targetTable);\n if (!targetIdColumn) {\n throw new Error(\n `No primary key or \"id\" column in the target table of relation '${relation.relationName}', ` +\n `so a filter on it has nothing to match against.`\n );\n }\n scanTable = targetTable;\n correlation = sql`${ref(fkColumn)} = ${sourceIdColumn}`;\n comparedColumn = targetIdColumn;\n }\n\n const { predicate, negate } = this.buildRelationFilterPredicate(\n ref(comparedColumn), op, value, field, collectionPath\n );\n\n const where = predicate ? sql`${correlation} AND ${predicate}` : correlation;\n const exists = sql`EXISTS (SELECT 1 FROM ${scanTable} AS ${sql.identifier(alias)} WHERE ${where})`;\n return negate ? sql`NOT ${exists}` : exists;\n }\n\n /**\n * The inner predicate of a relation filter, and whether the `EXISTS`\n * wrapping it is negated.\n *\n * Negation is `NOT EXISTS` of the *positive* predicate, never `EXISTS` of a\n * negated one. On a many-valued relation the two are different questions:\n * `EXISTS (… AND tag_id != X)` asks \"does some tag differ from X\", which is\n * true of nearly every post with more than one tag and answers nothing\n * anybody asked. `NOT EXISTS (… AND tag_id = X)` asks \"is X absent\", which\n * is what unticking a value in a filter control means — and it makes `==`\n * and `!=` partition the rows, the way a filter implies they do.\n *\n * `is-null`/`is-not-null` drop the predicate entirely: with nothing but the\n * correlation left, they become \"has no related row at all\" and \"has at\n * least one\", which is the only reading of null a link can have.\n *\n * Under RLS, \"no related row\" means *no row this reader can see*. A junction\n * with row-level security but no `SELECT` policy for `rebase_user` is opaque\n * to it, so every row comes back looking unlinked and `is-null` matches all\n * of them. That is not a leak — the outer table's own policies still decide\n * which rows exist at all, and the positive direction correctly returns\n * nothing — but it over-reports, and the cause is a missing junction policy\n * rather than anything here. Rebase derives one for a declared many-to-many;\n * a hand-written schema has to supply it.\n *\n * `in`/`not-in` against a *null value* mean the same thing, rather than\n * membership of an empty list. Membership against null is not a membership\n * question, and the admin's \"filter for null values\" control emits the\n * operator that happens to be selected — on a to-many relation that is\n * always `in` or `not-in`, because those are the only ones the multi-select\n * can produce. Reading `[\"in\", null]` as an empty list would answer \"posts\n * with no tags\" with no posts at all.\n *\n * An empty `in` list compiles to `FALSE` rather than being dropped. Dropped\n * is what the column path does, and dropping a condition widens the result\n * — the whole reason this resolution fails closed. `in []` matches nothing\n * and `not-in []` matches everything, and `NOT EXISTS (… AND FALSE)` gives\n * the second for free.\n *\n * Anything else is rejected. Returning `null` for an operator this cannot\n * express would drop the condition, and the operators the admin offers for\n * a relation are exactly the six below.\n */\n private static buildRelationFilterPredicate(\n ref: SQL,\n op: WhereFilterOp,\n value: unknown,\n field: string,\n collectionPath: string\n ): { predicate?: SQL; negate: boolean } {\n value = unwrapRelationFilterValue(value);\n const isNullish = value === null || value === undefined;\n\n const equals = () => sql`${ref} = ${value}`;\n const inList = () => {\n // Same reading as the column path: a scalar is the one-element\n // list, an empty list is `FALSE`. Here `FALSE` also gives\n // `not-in []` its answer for free — `NOT EXISTS (… AND FALSE)`\n // is true of every row, which is what excluding nothing means.\n const values = toMembershipList(value);\n return values.length === 0\n ? sql`FALSE`\n : sql`${ref} IN (${sql.join(values.map(v => sql`${v}`), sql`, `)})`;\n };\n\n switch (op) {\n case \"==\":\n return isNullish ? { negate: true } : { predicate: equals(), negate: false };\n case \"!=\":\n return isNullish ? { negate: false } : { predicate: equals(), negate: true };\n case \"in\":\n return isNullish ? { negate: true } : { predicate: inList(), negate: false };\n case \"not-in\":\n return isNullish ? { negate: false } : { predicate: inList(), negate: true };\n case \"is-null\":\n return { negate: true };\n case \"is-not-null\":\n return { negate: false };\n // A to-many relation *is* the list, so the array operators ask the\n // same two questions under different names: \"contains X\" is \"some\n // related row is X\", and \"contains any of [X, Y]\" is `in`. They\n // reach here because the admin offers them for a property that is\n // an *array of* relations, and rejecting a question the shape\n // answers perfectly well would put a 400 behind a working control.\n case \"array-contains\":\n return isNullish ? { negate: true } : { predicate: equals(), negate: false };\n case \"array-contains-any\":\n return isNullish ? { negate: true } : { predicate: inList(), negate: false };\n default:\n throw ApiError.badRequest(\n `Operator '${op}' cannot be applied to relation field '${field}' on collection ` +\n `'${collectionPath}'. A relation with no column on this row is filtered by ` +\n \"membership: ==, !=, in, not-in, array-contains, array-contains-any, is-null, \" +\n \"is-not-null.\",\n \"UNSUPPORTED_RELATION_FILTER_OPERATOR\",\n { field, collection: collectionPath, operator: op }\n );\n }\n }\n\n /**\n * A filter on a *column of the related row* — `applications.status`.\n *\n * The same `EXISTS` {@link buildRelationFilterCondition} builds, with the\n * predicate moved off the target's id and onto one of its columns:\n *\n * EXISTS (SELECT 1 FROM talent_applications AS t\n * WHERE t.talent_id = talents.id\n * AND t.status IN ('applied', 'reviewing', 'interview'))\n *\n * which is the shape every \"who is waiting\" queue is written in. Without\n * it the only way to ask is to fetch every row and filter in the browser,\n * and a filter the client applies after paging is not a filter — the page\n * was already chosen without it.\n *\n * A many-to-many needs one more table than the id filter does. That one\n * stops at the junction, because the junction already holds the value it\n * compares; a column of the target is a table further out, so the subquery\n * joins the target to the junction and correlates from the junction. The\n * join is inside `EXISTS`, so it cannot multiply the outer rows the way a\n * top-level join through a junction would.\n *\n * `belongsTo` is included even though its foreign key is a column here:\n * `author.name` is a column of another table either way, and refusing the\n * one relation kind that reads most naturally would be a rule about\n * implementation rather than about meaning.\n *\n * Under RLS the subquery runs as the reader, so it sees the target rows\n * that reader's policies allow and no others. On the positive direction\n * that is exactly right. On the negative — `!=`, `not-in`, and any\n * `NOT EXISTS` — \"no related row satisfies this\" and \"no related row this\n * reader can see satisfies this\" are the same sentence, so a target table\n * with row-level security and no `SELECT` policy for `rebase_user` makes\n * every row look unmatched and the negative filter over-reports. Nothing is\n * leaked: the outer table's own policies still decide which rows exist. The\n * cause is a missing policy on the target rather than anything here, and it\n * is the same caveat the id-filter path carries.\n */\n static buildRelationFieldCondition(\n target: Extract<FilterTarget, { kind: \"relation-field\" }>,\n op: WhereFilterOp,\n value: unknown,\n field: string,\n collectionPath: string\n ): SQL {\n const alias = \"__rel_field\";\n const { relation, targetTable, targetColumn } = target;\n const ref = sql`${sql.identifier(alias)}.${sql.identifier(targetColumn.name)}`;\n\n const { predicate, negate } = this.buildRelationColumnPredicate(\n ref, targetColumn, op, value, field, collectionPath\n );\n\n // Everything inside the subquery is referenced by identifier against a\n // local alias — see `buildRelationScopeCondition` for why a Drizzle\n // column object cannot be used there. Only `sourceIdColumn` stays a\n // column object, which is what binds it to the *outer* row.\n let from: SQL;\n let correlation: SQL;\n\n if (relation.kind === \"manyToMany\") {\n const { table: junctionName, sourceColumn, targetColumn: junctionTargetColumn } = relation.through;\n const junctionTable = target.registry.getTable(junctionName);\n if (!junctionTable) {\n throw junctionTableMissing(junctionName);\n }\n const sourceCol = junctionTable[sourceColumn as keyof typeof junctionTable] as AnyPgColumn;\n const targetCol = junctionTable[junctionTargetColumn as keyof typeof junctionTable] as AnyPgColumn;\n if (!sourceCol || !targetCol) {\n throw new Error(\n `Junction columns '${sourceColumn}'/'${junctionTargetColumn}' not found in '${junctionName}'`\n );\n }\n const targetKey = this.primaryKeyColumn(targetTable);\n if (!targetKey) {\n throw new Error(\n `No primary key or \"id\" column in the target table of relation '${relation.relationName}', ` +\n `so '${field}' has nothing to join the junction against.`\n );\n }\n const junctionAlias = \"__rel_field_junction\";\n from = sql`${targetTable} AS ${sql.identifier(alias)} INNER JOIN ${junctionTable} AS ${sql.identifier(junctionAlias)} ON ${sql.identifier(junctionAlias)}.${sql.identifier(targetCol.name)} = ${sql.identifier(alias)}.${sql.identifier(targetKey.name)}`;\n correlation = sql`${sql.identifier(junctionAlias)}.${sql.identifier(sourceCol.name)} = ${target.sourceIdColumn}`;\n } else if (relation.kind === \"belongsTo\") {\n const targetKey = this.primaryKeyColumn(targetTable);\n if (!targetKey) {\n throw new Error(\n `No primary key or \"id\" column in the target table of relation '${relation.relationName}', ` +\n `so '${field}' has nothing to correlate against.`\n );\n }\n from = sql`${targetTable} AS ${sql.identifier(alias)}`;\n correlation = sql`${sql.identifier(alias)}.${sql.identifier(targetKey.name)} = ${target.sourceIdColumn}`;\n } else {\n const foreignKey = relationColumn(targetTable, targetOf(relation), relation.foreignKeyOnTarget);\n if (!foreignKey) {\n throw new Error(\n `Foreign key column '${relation.foreignKeyOnTarget}' not found in the target table of ` +\n `relation '${relation.relationName}'.`\n );\n }\n from = sql`${targetTable} AS ${sql.identifier(alias)}`;\n correlation = sql`${sql.identifier(alias)}.${sql.identifier(foreignKey.name)} = ${target.sourceIdColumn}`;\n }\n\n const where = predicate ? sql`${correlation} AND ${predicate}` : correlation;\n const exists = sql`EXISTS (SELECT 1 FROM ${from} WHERE ${where})`;\n return negate ? sql`NOT ${exists}` : exists;\n }\n\n /**\n * The inner predicate of a relation *column* filter, and whether the\n * `EXISTS` wrapping it is negated.\n *\n * The negation rule is the one {@link buildRelationFilterPredicate} states\n * and holds for exactly the same reason, one column over. A negative\n * operator is `NOT EXISTS` of the **positive** predicate, never `EXISTS` of\n * a negated one: `EXISTS (… AND status != 'hired')` asks \"does some\n * application differ from hired\", which is true of nearly every candidate\n * with more than one application and answers nothing anybody asked.\n * `NOT EXISTS (… AND status = 'hired')` asks \"is there no hired\n * application\", which is what unticking a value means — and it makes `==`\n * and `!=` partition the rows, the way a filter implies they do.\n *\n * `is-null` and `is-not-null` are the exception, and deliberately not a\n * complementary pair here. On a column they compile to `EXISTS (… AND col\n * IS NULL)` and `EXISTS (… AND col IS NOT NULL)` — \"has a related row whose\n * column is unset\" and \"has one where it is set\" — which is the plain\n * reading of `applications.status is-not-null` and the useful one. They are\n * both true of a candidate with two applications, one of each. Making\n * `is-not-null` the negation instead would make it \"no application has an\n * unset status\", which is true of a candidate with no applications at all\n * and so answers a queue with the very rows the queue exists to exclude.\n *\n * Unlike the id path, every operator is available: the compared value is an\n * ordinary column, so `>=` on a date and `ilike` on a name mean here what\n * they mean anywhere else. Only an operator that does not exist is refused,\n * and it throws rather than returning `null` — a dropped condition widens\n * the read, which is the whole reason this file fails closed.\n */\n private static buildRelationColumnPredicate(\n ref: SQL,\n column: AnyPgColumn,\n op: WhereFilterOp,\n value: unknown,\n field: string,\n collectionPath: string\n ): { predicate?: SQL; negate: boolean } {\n value = unwrapRelationFilterValue(value);\n const isNullish = value === null || value === undefined;\n\n const equals = () => sql`${ref} = ${value}`;\n const inList = (): SQL => {\n const values = toMembershipList(value);\n // An empty list matches nothing, and `NOT EXISTS (… AND FALSE)`\n // gives `not-in []` — which excludes nothing — for free. Dropping\n // the condition instead would match everything.\n return values.length === 0\n ? sql`FALSE`\n : sql`${ref} IN (${sql.join(values.map(v => sql`${v}`), sql`, `)})`;\n };\n const isNull = () => sql`${ref} IS NULL`;\n const contains = (): SQL => {\n const meta = getColumnMeta(column);\n const isNativeArray = meta.dataType === \"array\" || meta.columnType === \"PgArray\";\n if (op === \"array-contains-any\") {\n if (Array.isArray(value) && value.length === 0) return sql`FALSE`;\n if (Array.isArray(value) && value.length > 0) {\n return isNativeArray\n ? sql`${ref} && ARRAY[${sql.join(value.map(v => sql`${v}`), sql`, `)}]`\n : sql`${ref} ?| array[${sql.join(value.map(v => sql`${String(v)}`), sql`, `)}]`;\n }\n }\n return isNativeArray\n ? sql`${ref} @> ARRAY[${value}]`\n : sql`${ref} @> ${JSON.stringify([value])}`;\n };\n\n switch (op) {\n case \"==\":\n return { predicate: isNullish ? isNull() : equals(), negate: false };\n case \"!=\":\n return { predicate: isNullish ? isNull() : equals(), negate: true };\n case \">\":\n return { predicate: sql`${ref} > ${value}`, negate: false };\n case \">=\":\n return { predicate: sql`${ref} >= ${value}`, negate: false };\n case \"<\":\n return { predicate: sql`${ref} < ${value}`, negate: false };\n case \"<=\":\n return { predicate: sql`${ref} <= ${value}`, negate: false };\n case \"in\":\n return { predicate: isNullish ? isNull() : inList(), negate: false };\n case \"not-in\":\n return { predicate: isNullish ? isNull() : inList(), negate: true };\n case \"like\":\n return { predicate: sql`${ref} LIKE ${String(value)}`, negate: false };\n case \"not-like\":\n return { predicate: sql`${ref} LIKE ${String(value)}`, negate: true };\n case \"ilike\":\n case \"not-ilike\": {\n // `ILIKE` is only defined on the text family. Asking it of a\n // date or an integer is a Postgres error at execution time — a\n // 500 on a request whose only fault is an operator the admin\n // offered for the wrong column.\n if (!supportsILike(column)) {\n throw ApiError.badRequest(\n `Operator '${op}' cannot be applied to '${field}' on collection '${collectionPath}': ` +\n `'${column.name}' is ${column.getSQLType?.() ?? \"not a text column\"}, and case-insensitive ` +\n \"matching is only defined on text.\",\n \"UNSUPPORTED_RELATION_FILTER_OPERATOR\",\n { field, collection: collectionPath, operator: op }\n );\n }\n return { predicate: sql`${ref} ILIKE ${String(value)}`, negate: op === \"not-ilike\" };\n }\n case \"is-null\":\n return { predicate: isNull(), negate: false };\n case \"is-not-null\":\n return { predicate: sql`${ref} IS NOT NULL`, negate: false };\n case \"array-contains\":\n case \"array-contains-any\":\n return { predicate: contains(), negate: false };\n default:\n throw ApiError.badRequest(\n `Unknown filter operator '${op}'. Valid operators: ${ALL_WHERE_FILTER_OPS.join(\", \")}.`,\n \"UNKNOWN_FILTER_OPERATOR\",\n { operator: op, validOperators: ALL_WHERE_FILTER_OPS }\n );\n }\n }\n\n /**\n * An aggregate over the rows a relation reaches, as a scalar expression —\n * what `orderBy: [{ relation: \"applications\", field: \"created_at\", agg:\n * \"min\" }, \"asc\"]` compiles to.\n *\n * (SELECT min(t.created_at) FROM talent_applications AS t\n * WHERE t.talent_id = talents.id)\n *\n * A correlated scalar subquery rather than a `LEFT JOIN LATERAL`: the join\n * would have to be threaded into a query the relational query builder\n * assembles, while a scalar expression drops straight into `ORDER BY` and\n * into the keyset comparison behind cursor paging — which has to be the\n * *same* expression, or paging and ordering disagree and rows are skipped.\n *\n * `correlateTo` is what the subquery is pinned against. Left out, it is the\n * outer row's key column and the expression is correlated in the ordinary\n * way. Given a literal — the cursor row's id — the subquery stops being\n * correlated at all, so Postgres evaluates it once for the whole statement\n * rather than per row. That is how a cursor pages over an aggregate it has\n * no stored value for: the value is recomputed from the id it does have.\n *\n * Over zero related rows `count` is 0 and every other function is NULL,\n * which is what puts \"nobody waiting\" at a defined end of the order rather\n * than wherever a missing value would land. See `buildOrderExpressions` for\n * where that end is pinned.\n *\n * Under RLS the subquery runs as the reader, so a related row the reader\n * cannot see does not contribute — an aggregate is over the rows that\n * reader can see, which is the only total it could honestly report.\n */\n static buildRelationAggregateExpression(\n spec: RelationAggregateSort,\n table: PgTable<any>,\n collection: CollectionConfig,\n registry: PostgresCollectionRegistry,\n sourceIdColumn: AnyPgColumn,\n collectionPath: string,\n correlateTo?: unknown\n ): SQL {\n const relation = resolveCollectionRelations(collection)[spec.relation];\n if (!relation) {\n throw ApiError.badRequest(\n `Cannot sort by '${encodeRelationAggregateSort(spec)}' on collection '${collectionPath}': ` +\n `'${spec.relation}' is not a relation there.`,\n \"UNKNOWN_ORDER_BY_FIELD\",\n { field: encodeRelationAggregateSort(spec), collection: collectionPath, relation: spec.relation }\n );\n }\n if (relation.kind === \"via\") {\n throw ApiError.badRequest(\n `Cannot sort by '${encodeRelationAggregateSort(spec)}' on collection '${collectionPath}': ` +\n \"`via` relations are authored one way only, so there is nothing to correlate a subquery back to.\",\n \"ORDER_BY_FIELD_NOT_SORTABLE\",\n { field: encodeRelationAggregateSort(spec), collection: collectionPath, kind: relation.kind }\n );\n }\n\n const targetCollection = targetOf(relation);\n const targetTable = targetCollection && registry.getTable(getTableName(targetCollection));\n if (!targetCollection || !targetTable) {\n throw new Error(\n `Table not found for the target of relation '${relation.relationName}' on '${collectionPath}', ` +\n \"so there is nothing to aggregate.\"\n );\n }\n\n const alias = \"__rel_agg\";\n // `count` with no field counts the related rows themselves. Every other\n // function needs something to aggregate, and a key that named no column\n // never got this far — `parseRelationAggregateSort` refuses it.\n let aggregand: SQL = sql`*`;\n if (spec.field) {\n const column = relationColumn(targetTable, targetCollection, spec.field)\n ?? (spec.field in targetTable\n ? targetTable[spec.field as keyof typeof targetTable] as AnyPgColumn\n : undefined);\n if (!column) {\n let validFields: string[] = [];\n try {\n validFields = Object.keys(getTableColumns(targetTable)).sort();\n } catch {\n // A table stand-in without Drizzle's column symbols.\n }\n throw ApiError.badRequest(\n `Unknown field '${spec.field}' on '${targetCollection.slug}', the target of relation ` +\n `'${spec.relation}' on collection '${collectionPath}'` +\n (validFields.length > 0 ? `. Valid fields: ${validFields.join(\", \")}` : \"\"),\n \"UNKNOWN_ORDER_BY_FIELD\",\n {\n field: encodeRelationAggregateSort(spec),\n collection: collectionPath,\n relation: spec.relation,\n targetCollection: targetCollection.slug,\n ...(validFields.length > 0 && { validFields })\n }\n );\n }\n aggregand = sql`${sql.identifier(alias)}.${sql.identifier(column.name)}`;\n } else if (spec.agg !== \"count\") {\n throw ApiError.badRequest(\n `'${spec.agg}' needs a field to aggregate — only 'count' means something on its own.`,\n \"ORDER_BY_FIELD_NOT_SORTABLE\",\n { field: encodeRelationAggregateSort(spec), collection: collectionPath }\n );\n }\n\n // The literal pins the subquery to one row; the column object binds it\n // to the outer row, because a Drizzle column renders qualified with its\n // own table. See `buildRelationScopeCondition`.\n const source: SQL = correlateTo === undefined ? sql`${sourceIdColumn}` : sql`${correlateTo}`;\n\n let from: SQL;\n let correlation: SQL;\n\n if (relation.kind === \"manyToMany\") {\n const { table: junctionName, sourceColumn, targetColumn } = relation.through;\n const junctionTable = registry.getTable(junctionName);\n if (!junctionTable) {\n throw junctionTableMissing(junctionName);\n }\n const sourceCol = junctionTable[sourceColumn as keyof typeof junctionTable] as AnyPgColumn;\n const targetCol = junctionTable[targetColumn as keyof typeof junctionTable] as AnyPgColumn;\n if (!sourceCol || !targetCol) {\n throw new Error(\n `Junction columns '${sourceColumn}'/'${targetColumn}' not found in '${junctionName}'`\n );\n }\n const targetKey = this.primaryKeyColumn(targetTable);\n if (!targetKey) {\n throw new Error(\n `No primary key or \"id\" column in the target table of relation '${relation.relationName}'.`\n );\n }\n const junctionAlias = \"__rel_agg_junction\";\n from = sql`${targetTable} AS ${sql.identifier(alias)} INNER JOIN ${junctionTable} AS ${sql.identifier(junctionAlias)} ON ${sql.identifier(junctionAlias)}.${sql.identifier(targetCol.name)} = ${sql.identifier(alias)}.${sql.identifier(targetKey.name)}`;\n correlation = sql`${sql.identifier(junctionAlias)}.${sql.identifier(sourceCol.name)} = ${source}`;\n } else if (relation.kind === \"belongsTo\") {\n const targetKey = this.primaryKeyColumn(targetTable);\n const localKey = relationColumn(table, collection, relation.localKey);\n if (!targetKey || !localKey) {\n throw new Error(\n `Relation '${relation.relationName}' on '${collectionPath}' names a key that is not a column, ` +\n \"so there is nothing to aggregate against.\"\n );\n }\n from = sql`${targetTable} AS ${sql.identifier(alias)}`;\n // A to-one reaches one row, so the aggregate is that row's value.\n // Pinning by a literal cursor id means looking the key up on the\n // cursor row rather than reading it off the outer one.\n const owner: SQL = correlateTo === undefined\n ? sql`${localKey}`\n : sql`(SELECT ${sql.identifier(localKey.name)} FROM ${table} WHERE ${sourceIdColumn} = ${correlateTo})`;\n correlation = sql`${sql.identifier(alias)}.${sql.identifier(targetKey.name)} = ${owner}`;\n } else {\n const foreignKey = relationColumn(targetTable, targetCollection, relation.foreignKeyOnTarget);\n if (!foreignKey) {\n throw new Error(\n `Foreign key column '${relation.foreignKeyOnTarget}' not found in the target table of ` +\n `relation '${relation.relationName}'.`\n );\n }\n // `sourceKey` names a column other than the primary key when the\n // link joins on one; correlating on the id anyway aggregates over\n // nothing and every row sorts as though it had no related rows.\n const sourceKeyColumn = relation.sourceKey\n ? relationColumn(table, collection, relation.sourceKey)\n : sourceIdColumn;\n if (!sourceKeyColumn) {\n throw new Error(\n `\\`sourceKey: \"${relation.sourceKey}\"\\` on relation '${relation.relationName}' is not a ` +\n `column on '${collectionPath}'.`\n );\n }\n const pinned: SQL = correlateTo === undefined\n ? sql`${sourceKeyColumn}`\n : relation.sourceKey\n ? sql`(SELECT ${sql.identifier(sourceKeyColumn.name)} FROM ${table} WHERE ${sourceIdColumn} = ${correlateTo})`\n : sql`${correlateTo}`;\n from = sql`${targetTable} AS ${sql.identifier(alias)}`;\n correlation = sql`${sql.identifier(alias)}.${sql.identifier(foreignKey.name)} = ${pinned}`;\n }\n\n // The function name is not interpolated from input: it comes off a\n // five-member union the parser validated, and is written out here so\n // nothing string-shaped reaches the statement.\n const aggregate = AGGREGATE_SQL[spec.agg];\n return sql`(SELECT ${aggregate}(${aggregand}) FROM ${from} WHERE ${correlation})`;\n }\n\n /** The column a table's rows are keyed by: its primary key, else `id`. */\n private static primaryKeyColumn(table: PgTable<any>): AnyPgColumn | undefined {\n return (Object.values(table).find((col: Record<string, unknown>) => col.primary)\n ?? Object.values(table).find((col: Record<string, unknown>) => col.name === \"id\")) as AnyPgColumn | undefined;\n }\n\n /**\n * Build a single filter condition for a specific operator and value\n */\n static buildSingleFilterCondition(\n column: AnyPgColumn,\n op: WhereFilterOp,\n value: unknown\n ): SQL | null {\n value = unwrapRelationFilterValue(value);\n switch (op) {\n case \"==\":\n if (value === null || value === undefined) {\n return sql`${column} IS NULL`;\n }\n return eq(column, value);\n case \"!=\":\n if (value === null || value === undefined) {\n return sql`${column} IS NOT NULL`;\n }\n return sql`${column} != ${value}`;\n case \">\":\n return sql`${column} > ${value}`;\n case \">=\":\n return sql`${column} >= ${value}`;\n case \"<\":\n return sql`${column} < ${value}`;\n case \"<=\":\n return sql`${column} <= ${value}`;\n case \"in\": {\n // Membership against a null *value* is a null check, not an\n // empty list — the admin's \"filter for null values\" control\n // emits whichever operator is selected, so `[\"in\", null]` is\n // how it asks for a null foreign key when the user picked\n // `in`. Reading it as an empty list dropped the condition\n // outright, which widened the read to every row.\n if (value === null || value === undefined) {\n return sql`${column} IS NULL`;\n }\n const values = toMembershipList(value);\n // An empty list matches nothing. Returning no condition — what\n // this did — matches *everything*, which is the same inversion\n // one layer down from the one `UnknownFilterFieldsMode` exists\n // for. It is the dangerous shape too: `filter: { id: [\"in\",\n // teamIds] }` with no teams is how a caller asks for nothing,\n // and it answered with the whole table.\n return values.length === 0 ? sql`FALSE` : inArray(column, values);\n }\n case \"array-contains\": {\n const meta = getColumnMeta(column);\n if (meta.dataType === \"array\" || meta.columnType === \"PgArray\") {\n return sql`${column} @> ARRAY[${value}]`;\n }\n // For JSONB arrays: checks if the column contains the given value\n return sql`${column} @> ${JSON.stringify([value])}`;\n }\n case \"array-contains-any\": {\n const meta = getColumnMeta(column);\n const isNativeArray = meta.dataType === \"array\" || meta.columnType === \"PgArray\";\n // \"Overlaps nothing\" is false, not a licence to skip the\n // condition. The single-value fallback below is for a *scalar*\n // operand; an empty array fell into it and built\n // `@> ARRAY[$1]` around an empty binding.\n if (Array.isArray(value) && value.length === 0) {\n return sql`FALSE`;\n }\n if (Array.isArray(value) && value.length > 0) {\n if (isNativeArray) {\n return sql`${column} && ARRAY[${sql.join(value.map(v => sql`${v}`), sql`, `)}]`;\n } else {\n // Use the ?| operator for JSONB overlap with text array\n const textValues = value.map(v => String(v));\n return sql`${column} ?| array[${sql.join(textValues.map(v => sql`${v}`), sql`, `)}]`;\n }\n }\n // Single value fallback: treat as array-contains\n if (isNativeArray) {\n return sql`${column} @> ARRAY[${value}]`;\n }\n return sql`${column} @> ${JSON.stringify([value])}`;\n }\n case \"not-in\": {\n // The mirror of `in` above, including the empty list — which\n // excludes nothing, and so matches every row. Same condition\n // the old code produced by accident, now on purpose and for\n // the empty list only.\n if (value === null || value === undefined) {\n return sql`${column} IS NOT NULL`;\n }\n const values = toMembershipList(value);\n if (values.length === 0) return sql`TRUE`;\n return sql`${column} NOT IN (${sql.join(values.map(v => sql`${v}`), sql`, `)})`;\n }\n case \"like\":\n return sql`${column} LIKE ${String(value)}`;\n case \"ilike\":\n return sql`${column} ILIKE ${String(value)}`;\n case \"not-like\":\n return sql`${column} NOT LIKE ${String(value)}`;\n case \"not-ilike\":\n return sql`${column} NOT ILIKE ${String(value)}`;\n case \"is-null\":\n return sql`${column} IS NULL`;\n case \"is-not-null\":\n return sql`${column} IS NOT NULL`;\n default:\n // The relation path five hundred lines up already refuses this,\n // and says why: \"returning `null` for an operator this cannot\n // express would drop the condition\", and a dropped condition\n // widens the result. The column path is its twin and kept the\n // warning — so `{ status: [\"contains\", \"x\"] }` filtered on\n // nothing and answered 200 with every row, which reads as data\n // that matched.\n //\n // The wire layer rejects operator-shaped unknowns before they\n // arrive (`UnknownFilterOperatorError`, 400). What reaches here\n // came from in-process `rebase.dataAsAdmin`, a stored filter preset or\n // a config — none of them typechecked at the call site, all of\n // them able to name an operator that no longer exists.\n throw ApiError.badRequest(\n `Unknown filter operator '${op}'. Valid operators: ${ALL_WHERE_FILTER_OPS.join(\", \")}.`,\n \"UNKNOWN_FILTER_OPERATOR\",\n { operator: op, validOperators: ALL_WHERE_FILTER_OPS }\n );\n }\n }\n\n /**\n * Build relation-based conditions for different relation types\n */\n /**\n * Joins and where-conditions that reach a relation's target rows.\n *\n * One case per kind. This used to be a chain of six `else if`s over\n * `cardinality`/`direction`/`through`, ending in\n * `findCorrespondingJunctionTable` — a search through the *target's* own\n * relations to work out whether an \"inverse many\" was a one-to-many or the\n * far side of a junction. That search is gone: the kind says which it is.\n *\n * The owning/inverse split for junctions is gone too. Both variants built\n * the identical condition — `through` is always written from the declaring\n * side's point of view — so the second was a distinction without a\n * difference and one of the places the two could drift apart.\n */\n static buildRelationConditions(\n relation: ResolvedRelation,\n parentId: string | number | (string | number)[],\n targetTable: PgTable<any>,\n parentTable: PgTable<any>,\n parentIdColumn: AnyPgColumn,\n targetIdColumn: AnyPgColumn,\n registry: PostgresCollectionRegistry\n ): {\n joinConditions: { table: PgTable<any>; condition: SQL }[];\n whereConditions: SQL[];\n } {\n const joinConditions: { table: PgTable<any>; condition: SQL }[] = [];\n const whereConditions: SQL[] = [];\n\n switch (relation.kind) {\n case \"via\": {\n const { joins, finalCondition } = this.buildJoinPathConditions(\n relation.joinPath, targetTable, parentTable, parentIdColumn, parentId, registry\n );\n joinConditions.push(...joins);\n whereConditions.push(finalCondition);\n break;\n }\n\n case \"manyToMany\": {\n const junctionResult = this.buildJunctionTableConditions(\n relation.through, targetIdColumn, parentId, registry\n );\n joinConditions.push(junctionResult.join);\n whereConditions.push(junctionResult.condition);\n break;\n }\n\n case \"hasOne\":\n case \"hasMany\":\n case \"belongsTo\":\n whereConditions.push(\n this.buildSimpleRelationCondition(relation, targetTable, parentTable, parentId)\n );\n break;\n\n default: {\n const exhaustive: never = relation;\n throw new Error(`Unknown relation kind: ${JSON.stringify(exhaustive)}`);\n }\n }\n\n return { joinConditions,\nwhereConditions };\n }\n\n /**\n * Build conditions for join path relations\n */\n private static buildJoinPathConditions(\n joinPath: JoinStep[],\n targetTable: PgTable<any>,\n parentTable: PgTable<any>,\n parentIdColumn: AnyPgColumn,\n parentId: string | number | (string | number)[],\n registry: PostgresCollectionRegistry\n ): {\n joins: { table: PgTable<any>; condition: SQL }[];\n finalCondition: SQL;\n } {\n const joins: { table: PgTable<any>; condition: SQL }[] = [];\n let currentTable = targetTable;\n const sourceTableName = drizzleTableName(parentTable);\n\n // Process join steps in reverse order to build path back to parent\n for (let index = joinPath.length - 1; index >= 0; index--) {\n const joinStep = joinPath[index];\n const { fromTableName, toTableName } = this.joinStepTableNames(joinPath, index, sourceTableName);\n const fromColName = this.getColumnNamesFromColumns(joinStep.on.from)[0];\n const toColName = this.getColumnNamesFromColumns(joinStep.on.to)[0];\n\n const fromTable = registry.getTable(fromTableName);\n const toTable = registry.getTable(toTableName);\n\n if (!fromTable || !toTable) {\n throw new Error(`Join tables not found for step: from ${fromTableName} to ${toTableName}`);\n }\n\n const {\n joinTable,\n condition,\n additionalJoins\n } = this.buildSingleJoinCondition(\n currentTable,\n fromTable,\n toTable,\n fromColName,\n toColName,\n fromTableName,\n toTableName,\n registry\n );\n\n joins.push({\n table: joinTable,\n condition\n });\n currentTable = joinTable;\n\n // Add any additional joins needed for many-to-many relationships\n if (additionalJoins && additionalJoins.length > 0) {\n joins.push(...additionalJoins);\n }\n }\n\n // Ensure we've connected back to the parent table\n // For junction tables, we might end up at the junction table instead of the parent table\n if (currentTable !== parentTable) {\n // `getTableName` is Drizzle's own accessor, and both of these are\n // declared `PgTable<any>`, so there is nothing to assert.\n //\n // What stood here reached for the name four ways — the\n // `Symbol.for(\"drizzle:Name\")` slot, then `._.name`, `.tableName`\n // and `.name` — through eight `as unknown as` casts and a\n // `\"unknown\"` sentinel for when all four missed. `getTableName`\n // reads the first of those and nothing else, because on a real\n // `PgTable` the other three are all `undefined`; they were dead\n // branches, and the casts were what kept anyone from seeing that\n // (each `as string` on a `||` chain asserts a type for a value the\n // compiler was told nothing about).\n const currentTableName = drizzleTableName(currentTable);\n const parentTableName = drizzleTableName(parentTable);\n\n // Junction tables are named for the two ends they join, so an\n // underscore is the signal that this walk plausibly stopped on one\n // rather than going wrong. The two `=== \"unknown\"` arms that used\n // to sit beside it went with the sentinel: a name that could not be\n // read was never a case that could arise here, only one the\n // fallback chain made it look like it could.\n if (!currentTableName.includes(\"_\")) {\n throw new Error(`Join path did not result in connecting to parent table. Current: ${currentTableName}, Parent: ${parentTableName}`);\n }\n }\n\n // Handle both single ID and array of IDs\n const finalCondition = Array.isArray(parentId)\n ? inArray(parentIdColumn, parentId)\n : eq(parentIdColumn, parentId);\n\n return {\n joins,\n finalCondition\n };\n }\n\n /**\n * Build a single join condition between tables\n */\n private static buildSingleJoinCondition(\n currentTable: PgTable<any>,\n fromTable: PgTable<any>,\n toTable: PgTable<any>,\n fromColName: string,\n toColName: string,\n fromTableName: string,\n toTableName: string,\n registry?: PostgresCollectionRegistry\n ): { joinTable: PgTable<any>; condition: SQL; additionalJoins?: { table: PgTable<any>; condition: SQL }[] } {\n let joinTable: PgTable<any>;\n let condition: SQL;\n const additionalJoins: { table: PgTable<any>; condition: SQL }[] = [];\n\n if (currentTable === toTable) {\n // current -> toTable, so join the fromTable\n // `getTableColumns` is Drizzle's own accessor and is already how the\n // rest of this file reads a column map. Indexing the table object\n // directly — which is what the two casts here were for — also\n // reaches its methods and symbols, so a relation whose column is\n // named `name` or `enableRLS` got one of those back and passed the\n // `!left || !right` guard below with something that is not a column.\n const left = joinColumn(fromTable, fromColName);\n const right = joinColumn(currentTable, toColName);\n\n if (!left || !right) {\n // Check if this might be a many-to-many relationship requiring a junction table\n if (registry) {\n const junctionResult = this.tryBuildJunctionJoin(\n currentTable,\n fromTable,\n fromColName,\n toColName,\n fromTableName,\n toTableName,\n registry\n );\n if (junctionResult) {\n return junctionResult;\n }\n }\n throw new Error(`Join columns not found: ${fromTableName}.${fromColName} = ${toTableName}.${toColName}`);\n }\n\n joinTable = fromTable;\n condition = eq(left, right);\n } else if (currentTable === fromTable) {\n // current -> fromTable, so join the toTable\n const left = joinColumn(toTable, toColName);\n const right = joinColumn(currentTable, fromColName);\n\n if (!left || !right) {\n // Check if this might be a many-to-many relationship requiring a junction table\n if (registry) {\n const junctionResult = this.tryBuildJunctionJoin(\n currentTable,\n toTable,\n fromColName,\n toColName,\n fromTableName,\n toTableName,\n registry\n );\n if (junctionResult) {\n return junctionResult;\n }\n }\n throw new Error(`Join columns not found: ${toTableName}.${toColName} = ${fromTableName}.${fromColName}`);\n }\n\n joinTable = toTable;\n condition = eq(left, right);\n } else {\n throw new Error(`Join step does not match current table. Current table does not match from: ${fromTableName} or to: ${toTableName}`);\n }\n\n return {\n joinTable,\n condition,\n additionalJoins\n };\n }\n\n /**\n * Try to build a junction table join when direct foreign key relationship is not found\n */\n private static tryBuildJunctionJoin(\n currentTable: PgTable<any>,\n targetTable: PgTable<any>,\n fromColName: string,\n toColName: string,\n fromTableName: string,\n toTableName: string,\n registry: PostgresCollectionRegistry\n ): { joinTable: PgTable<any>; condition: SQL; additionalJoins: { table: PgTable<any>; condition: SQL }[] } | null {\n // Try to find a junction table that connects these two tables\n // Common naming patterns: table1_table2, table1Table2, etc.\n const possibleJunctionNames = [\n `${fromTableName}_${toTableName}`,\n `${toTableName}_${fromTableName}`,\n `${fromTableName}${toTableName.charAt(0).toUpperCase() + toTableName.slice(1)}`,\n `${toTableName}${fromTableName.charAt(0).toUpperCase() + fromTableName.slice(1)}`\n ];\n\n for (const junctionName of possibleJunctionNames) {\n const junctionTable = registry.getTable(junctionName);\n if (junctionTable) {\n // Try to find the appropriate columns in the junction table\n const sourceColName = `${fromTableName.slice(0, -1)}_id`; // Remove 's' and add '_id'\n const targetColName = `${toTableName.slice(0, -1)}_id`;\n\n const junctionSourceCol = junctionTable[sourceColName as keyof typeof junctionTable] as AnyPgColumn;\n const junctionTargetCol = junctionTable[targetColName as keyof typeof junctionTable] as AnyPgColumn;\n\n if (junctionSourceCol && junctionTargetCol) {\n // Found a valid junction table setup\n const currentTableIdCol = Object.values(currentTable).find((col: Record<string, unknown>) => col.primary) as AnyPgColumn;\n const targetTableIdCol = Object.values(targetTable).find((col: Record<string, unknown>) => col.primary) as AnyPgColumn;\n\n if (!currentTableIdCol || !targetTableIdCol) {\n continue; // Skip if we can't find primary keys\n }\n\n // Determine which direction to join\n if (currentTable === targetTable) {\n // We're joining through junction to reach the other table\n return {\n joinTable: targetTable,\n condition: eq(targetTableIdCol, junctionTargetCol),\n additionalJoins: [\n {\n table: junctionTable,\n condition: eq(currentTableIdCol, junctionSourceCol)\n }\n ]\n };\n } else {\n // Standard junction join\n return {\n joinTable: junctionTable,\n condition: eq(currentTableIdCol, junctionSourceCol),\n additionalJoins: [\n {\n table: targetTable,\n condition: eq(targetTableIdCol, junctionTargetCol)\n }\n ]\n };\n }\n }\n }\n }\n\n return null; // No junction table found\n }\n\n /**\n * Build conditions for junction table (many-to-many) relations\n */\n private static buildJunctionTableConditions(\n through: { table: string; sourceColumn: string; targetColumn: string },\n targetIdColumn: AnyPgColumn,\n parentId: string | number | (string | number)[],\n registry: PostgresCollectionRegistry\n ): { join: { table: PgTable<any>; condition: SQL }; condition: SQL } {\n const junctionTable = registry.getTable(through.table);\n if (!junctionTable) {\n throw junctionTableMissing(through.table);\n }\n\n const junctionSourceCol = junctionTable[through.sourceColumn as keyof typeof junctionTable] as AnyPgColumn;\n const junctionTargetCol = junctionTable[through.targetColumn as keyof typeof junctionTable] as AnyPgColumn;\n\n if (!junctionSourceCol) {\n throw new Error(`Source column '${through.sourceColumn}' not found in junction table '${through.table}'`);\n }\n if (!junctionTargetCol) {\n throw new Error(`Target column '${through.targetColumn}' not found in junction table '${through.table}'`);\n }\n\n // Handle both single ID and array of IDs\n const condition = Array.isArray(parentId)\n ? inArray(junctionSourceCol, parentId)\n : eq(junctionSourceCol, parentId);\n\n return {\n join: {\n table: junctionTable,\n condition: eq(targetIdColumn, junctionTargetCol)\n },\n condition\n };\n }\n\n\n /**\n * The condition for a relation whose link is a single column.\n *\n * Two cases. It had five: two of them existed only to throw (\"should not be\n * called directly\", \"lacks proper configuration\"), and one guessed a column\n * name by appending `_id` to `inverseRelationName` when no foreign key had\n * been resolved. All three were reachable only because the old type let a\n * relation arrive here under-specified. It cannot now.\n */\n private static buildSimpleRelationCondition(\n relation: ResolvedBelongsTo | ResolvedHasOne | ResolvedHasMany,\n targetTable: PgTable<any>,\n parentTable: PgTable<any>,\n parentId: string | number | (string | number)[]\n ): SQL {\n const match = (column: AnyPgColumn) =>\n Array.isArray(parentId) ? inArray(column, parentId) : eq(column, parentId);\n\n if (relation.kind === \"belongsTo\") {\n // `parentId` is the foreign key's value, matched against the\n // target's own key.\n const targetIdCol = this.primaryKeyColumn(targetTable);\n if (!targetIdCol) {\n throw new Error(\n `No primary key or \"id\" column in the target table of relation '${relation.relationName}'.`\n );\n }\n return match(targetIdCol);\n }\n\n const foreignKeyCol = relationColumn(targetTable, targetOf(relation), relation.foreignKeyOnTarget);\n if (!foreignKeyCol) {\n throw new Error(\n `Foreign key column '${relation.foreignKeyOnTarget}' not found in the target table of relation ` +\n `'${relation.relationName}'. A link through a junction is \\`kind: \"manyToMany\"\\`.`\n );\n }\n return match(foreignKeyCol);\n }\n\n /**\n * Combine multiple conditions with AND operator\n */\n static combineConditionsWithAnd(conditions: SQL[]): SQL | undefined {\n if (conditions.length === 0) return undefined;\n if (conditions.length === 1) return conditions[0];\n return and(...conditions);\n }\n\n /**\n * Combine multiple conditions with OR operator\n */\n static combineConditionsWithOr(conditions: SQL[]): SQL | undefined {\n if (conditions.length === 0) return undefined;\n if (conditions.length === 1) return conditions[0];\n return or(...conditions);\n }\n\n /**\n * Build search conditions for text fields.\n *\n * Two shapes, chosen by whether the collection declared a `search` block:\n *\n * - **Declared** — one `@@ websearch_to_tsquery` against the generated\n * `tsvector` column. Stems, drops stopwords, AND-es the terms, reaches\n * inside JSONB and arrays, and uses the GIN index.\n * - **Not declared** — `ILIKE '%term%'` per term, OR-ed across top-level\n * string properties and AND-ed across the terms, with each term escaped\n * (see {@link escapeLikePattern}) so it is matched as the literal text\n * the user typed.\n *\n * The second is the default and stays the default, and it AND-es terms for\n * the same reason `websearch_to_tsquery` does: a person typing two words\n * into a search box means both, and a name lives in two columns. Matching\n * the whole string per column instead — which is what this did — cannot\n * find `sebastian melendez` on a row with `first_name`/`last_name`, and\n * could not find `sebastian ` at all.\n *\n * Returns at most one condition. Every caller ORs what it gets back, which\n * is right across columns and wrong across terms, so the AND is built here.\n *\n * `collection` is optional so that the callers which genuinely have no\n * collection in hand — nested paths, derived views — keep working; without\n * one there is no `search` block to read and the ILIKE path is correct.\n */\n static buildSearchConditions(\n searchString: string,\n properties: Record<string, unknown>,\n table: PgTable<any>,\n collection?: CollectionConfig\n ): SQL[] {\n const searchConditions: SQL[] = [];\n\n const ftsCondition = collection\n ? DrizzleConditionBuilder.buildFullTextCondition(searchString, table, collection)\n : undefined;\n if (ftsCondition) return [ftsCondition];\n\n let declaredStringProperties = 0;\n const viewer = currentFieldViewer();\n const searchableColumns: AnyPgColumn[] = [];\n\n for (const [key, prop] of Object.entries(properties)) {\n const p = prop as Record<string, unknown>;\n // A field this caller cannot read is not one they may search. The\n // strip keeps the value out of the response; without this the same\n // value is still recoverable a substring at a time by watching which\n // searches return the row, which is the disclosure the read rule\n // exists to prevent. `excludeFromApi` expands to the same rule, so a\n // password hash stops being ILIKE-matchable here too.\n if (!canReadField(prop as Property, viewer)) continue;\n // Only include string properties that don't have enum defined\n // PostgreSQL enum and uuid columns don't support ILIKE, so we skip them\n if (p.type === \"string\" && !p.enum && p.isId !== \"uuid\") {\n declaredStringProperties++;\n const fieldColumn = table[key as keyof typeof table] as AnyPgColumn;\n if (fieldColumn && supportsILike(fieldColumn)) {\n searchableColumns.push(fieldColumn);\n }\n }\n }\n\n // Every string property was rejected, so the caller is about to turn an\n // empty condition list into \"match nothing\" — a 200 with an empty page,\n // which reads as \"no such row\" rather than as the breakage it is. Say so\n // once per query: this is how the `instanceof` version of\n // {@link supportsILike} failed silently in the field for months.\n if (declaredStringProperties > 0 && searchableColumns.length === 0) {\n logger.warn(\n `[search] \"${collection?.slug ?? \"collection\"}\" declares ${declaredStringProperties} string ` +\n \"property(ies) but none compiled to a searchable column, so this search can only return nothing. \" +\n \"Check that the generated schema's column types are text/varchar/char.\"\n );\n }\n if (searchableColumns.length === 0) return [];\n\n // Every term has to match *something*, but not the same something:\n // \"sebastian melendez\" is a first name and a last name, and matching the\n // whole string per column finds neither. See {@link splitSearchTerms}.\n //\n // A string with no terms at all is only whitespace. It stays the single\n // literal pattern it has always been rather than becoming \"no rows\": an\n // empty condition list means \"match nothing\" to every caller here, and a\n // stray space is not a reason to answer that.\n const terms = splitSearchTerms(searchString);\n const perTerm = (terms.length > 0 ? terms : [searchString]).map(term => {\n const pattern = `%${escapeLikePattern(term)}%`;\n return DrizzleConditionBuilder.combineConditionsWithOr(\n searchableColumns.map(column => ilike(column, pattern))\n ) as SQL;\n });\n\n // One condition, already AND-ed across terms, because every caller ORs\n // what it gets back — which is right for \"this column or that one\" and\n // wrong for \"this term and that one\". A single-term search compiles to\n // exactly the SQL it compiled to before this existed.\n searchConditions.push(DrizzleConditionBuilder.combineConditionsWithAnd(perTerm) as SQL);\n\n return searchConditions;\n }\n\n /**\n * The `@@` predicate for a collection that declared a `search` block, or\n * undefined for one that did not.\n *\n * The query is normalized exactly as the indexed content was — same text\n * search configuration, same accent folding. Skipping that on the query\n * side is the subtle way to get a search that matches nothing: the column\n * would hold `gestion` while the query asked for `gestión`.\n *\n * `websearch_to_tsquery` rather than `plainto_tsquery` because it is the\n * one that behaves the way a search box looks like it should — quoted\n * phrases, `or`, and a leading `-` to exclude — and because it never throws\n * on user input, which `to_tsquery` does on so much as a stray parenthesis.\n */\n static buildFullTextCondition(\n searchString: string,\n table: PgTable<any>,\n collection: CollectionConfig\n ): SQL | undefined {\n let spec: SearchColumnSpec | undefined;\n try {\n spec = buildSearchColumnSpec(collection);\n } catch {\n // Reported at boot. Falling back to ILIKE here keeps reads serving.\n return undefined;\n }\n if (!spec) return undefined;\n\n const column = table[spec.column as keyof typeof table] as AnyPgColumn | undefined;\n if (!column) {\n // The block is declared but the column is not on the table yet —\n // a database that has not been migrated. ILIKE still answers.\n return undefined;\n }\n\n const query = DrizzleConditionBuilder.normalizedTsQuery(searchString, spec);\n const exact = sql`${column} @@ ${query}`;\n\n // `hybrid`: the indexed whole-lexeme predicate OR a substring match over\n // the same declared fields. One collection then answers both halves of\n // what a search box is, which neither branch does on its own —\n // measured, on the same five rows, in `search-mode-matrix.test.ts`:\n //\n // query | fts + unaccent | ILIKE default | hybrid\n // munoz | Muñoz, Munoz | Munoz | Muñoz, Munoz\n // seb | — | Sebastian, Sebastián| Sebastian, Sebastián\n // audit | — | Lead Auditor | Lead Auditor\n //\n // OR-ed rather than replacing the `@@`: the index still serves the half\n // it can serve, and the exact half is what carries the ranking.\n const substring = spec.mode === \"hybrid\"\n ? DrizzleConditionBuilder.buildFoldedSubstringCondition(searchString, spec)\n : undefined;\n const matched = substring ? sql`(${exact} OR ${substring})` : exact;\n\n if (!spec.fuzzy) return matched;\n\n const fuzzyColumn = table[spec.fuzzy.column as keyof typeof table] as AnyPgColumn | undefined;\n if (!fuzzyColumn) return matched;\n\n const needle = spec.unaccent\n ? sql`${sql.raw(SEARCH_UNACCENT_FN)}(${searchString})`\n : sql`${searchString}`;\n\n // `word_similarity(query, document)`, not `similarity`. `similarity`\n // scores two strings as wholes, so a short query against a whole row's\n // text scores near zero however well it matches part of it — measured:\n // \"iso 14001 auditor\" against one candidate's concatenated fields\n // scores 0.228 by `similarity` and 0.783 by `word_similarity`. The\n // first is below any usable threshold, which would have made `fuzzy`\n // a setting that quietly did nothing.\n //\n // Argument order matters: the first operand is the needle, and the\n // score is its similarity to the best-matching extent of the second.\n //\n // Both the function and the operator are schema-qualified: pg_trgm is\n // installed into `public`, and an unqualified reference resolves\n // through `search_path`, which does not necessarily reach it.\n const similar = sql`public.word_similarity(${needle}, ${fuzzyColumn}) >= ${spec.fuzzy.threshold}`;\n // `<%` is the index-backed form, but it tests against the session's\n // `pg_trgm.word_similarity_threshold` (0.6), not ours. Above that\n // default the operator narrows using the trigram index and the explicit\n // score refines; at or below it, the operator would exclude rows the\n // declared threshold admits, so the score stands alone and the planner\n // scans — correct either way, and only the faster path is conditional.\n const fuzzy = spec.fuzzy.threshold > PG_TRGM_WORD_SIMILARITY_DEFAULT\n ? sql`(${needle} OPERATOR(public.<%) ${fuzzyColumn} AND ${similar})`\n : similar;\n\n return sql`(${matched} OR ${fuzzy})`;\n }\n\n /**\n * The substring half of {@link SearchMode} `\"hybrid\"`: every term found\n * somewhere in the declared fields, with accents folded on both sides.\n *\n * Terms are AND-ed and fields OR-ed, exactly as the ILIKE default does and\n * for the same reason — a person typing two words means both, and a name\n * lives in two columns (`splitSearchTerms`). What it does *not* share with\n * the default is its reach: these are the declared `search.fields`, so it\n * folds accents, joins `text[]` and walks into JSONB through the same\n * IMMUTABLE helpers the generated column uses.\n *\n * The needle is folded with the same function as the document, so\n * `munoz` and `Muñoz` meet in the middle rather than one side being\n * normalized and the other not — the subtle way to build a search that\n * matches nothing.\n *\n * Interpolated as raw SQL because the field expressions are generated (they\n * name columns and helper functions, which are identifiers, not values);\n * the *term* is a bind parameter, escaped for LIKE first — see\n * {@link escapeLikePattern} for what that escape is for.\n */\n static buildFoldedSubstringCondition(\n searchString: string,\n spec: SearchColumnSpec\n ): SQL | undefined {\n if (spec.fields.length === 0) return undefined;\n\n const perTerm = DrizzleConditionBuilder.foldedNeedles(searchString).map(needle =>\n DrizzleConditionBuilder.combineConditionsWithOr(\n spec.fields.map(f => sql`${sql.raw(f.foldedTextSql)} ILIKE ${needle}`)\n ) as SQL\n );\n\n return DrizzleConditionBuilder.combineConditionsWithAnd(perTerm);\n }\n\n /**\n * Whether one text expression holds **any** term of the search string.\n *\n * The per-field question, where {@link buildFoldedSubstringCondition} asks\n * the per-row one: `OR` across terms rather than `AND`, because a two-word\n * query satisfied across two fields has each field carrying one term. Used\n * by `_matches` to name the fields that caused a substring hit.\n */\n static foldedSubstringOf(searchString: string, text: SQL): SQL {\n return DrizzleConditionBuilder.combineConditionsWithOr(\n DrizzleConditionBuilder.foldedNeedles(searchString)\n .map(needle => sql`${text} ILIKE ${needle}`)\n ) as SQL;\n }\n\n /**\n * One accent-folded `%term%` bind parameter per search term.\n *\n * The needle is folded with the same function as the document, so `munoz`\n * and `Muñoz` meet in the middle instead of one side being normalized and\n * the other not — the subtle way to build a search that matches nothing.\n *\n * A string with no terms at all is only whitespace, and stays the single\n * literal pattern it is on the ILIKE path rather than becoming \"no rows\",\n * so both paths answer a stray space the same way.\n */\n private static foldedNeedles(searchString: string): SQL[] {\n const terms = splitSearchTerms(searchString);\n const effective = terms.length > 0 ? terms : [searchString];\n return effective.map(term =>\n sql`${sql.raw(SEARCH_UNACCENT_FN)}(${`%${escapeLikePattern(term)}%`})`\n );\n }\n\n /**\n * `websearch_to_tsquery(<config>, <normalized search string>)`.\n *\n * Split out because the ranking expression needs the identical query — a\n * row ranked against a different tsquery than it was matched against is a\n * ranking of something else.\n */\n static normalizedTsQuery(searchString: string, spec: SearchColumnSpec): SQL {\n const normalized = spec.unaccent\n ? sql`${sql.raw(SEARCH_UNACCENT_FN)}(${searchString})`\n : sql`${searchString}`;\n return sql`websearch_to_tsquery(${spec.language}, ${normalized})`;\n }\n\n /**\n * A JSONB array of `{ field, snippet }` naming which declared fields matched\n * and showing the text around each hit — what backs `_matches`.\n *\n * A ranked list answers \"which rows\", never \"why this row\". For a talent\n * pool that difference is the product: a candidate surfacing for\n * \"iso 14001\" on a *certification* is a different candidate from one whose\n * bio happens to mention the standard, and the score cannot tell them apart.\n *\n * Built as a correlated subquery over a `VALUES` list of the declared\n * fields, rather than one `CASE` per field, so the shape does not change\n * with the number of fields and the empty result is a plain `[]`.\n *\n * `ts_headline` runs over the same normalized text that was indexed. Over\n * the *original* text it would find nothing to mark whenever `unaccent` is\n * on — the query's lexemes are folded and the document's are not — and\n * would return the text silently unhighlighted. Folded-but-marked beats\n * pretty-but-inert.\n *\n * Undefined when the collection has not opted in, when the column is not on\n * the table yet, or when the caller did not ask: this costs a `ts_headline`\n * per field per row and `ts_headline` re-parses the document.\n */\n static buildSearchMatchesExpression(\n searchString: string,\n table: PgTable<any>,\n collection: CollectionConfig\n ): SQL | undefined {\n let spec: SearchColumnSpec | undefined;\n try {\n spec = buildSearchColumnSpec(collection);\n } catch {\n return undefined;\n }\n if (!spec || spec.fields.length === 0) return undefined;\n if (!table[spec.column as keyof typeof table]) return undefined;\n\n const query = DrizzleConditionBuilder.normalizedTsQuery(searchString, spec);\n const config = sql`${spec.language}`;\n\n // `ord` keeps the author's declared field order in the output, so the\n // most important field they named reads first rather than whichever\n // Postgres aggregated first.\n //\n // Two text columns per field under `hybrid`: `txt` is what the tsvector\n // was built from and what `ts_headline` reads, `folded` is what the\n // substring half matched. They differ whenever `unaccent` is off, and a\n // single column would make one of the two halves report the wrong\n // fields.\n const rows = spec.fields.map((f, i) =>\n sql`(${i}, ${f.path}, ${sql.raw(f.textSql)}, ${sql.raw(f.foldedTextSql)})`\n );\n\n // Which fields a row reports. Under `hybrid` a field that matched only\n // by substring is reported too — it is the field that caused the hit,\n // and leaving it out would answer \"matched nothing\" for the row the\n // mode exists to find. Its snippet comes back with nothing marked:\n // `ts_headline` marks lexemes, and half a word is not one.\n const matchedField = spec.mode === \"hybrid\"\n ? sql`(to_tsvector(${config}::regconfig, f.txt) @@ ${query} OR ${DrizzleConditionBuilder.foldedSubstringOf(searchString, sql`f.folded`)})`\n : sql`to_tsvector(${config}::regconfig, f.txt) @@ ${query}`;\n\n return sql`(\n SELECT coalesce(jsonb_agg(s.m ORDER BY f.ord), '[]'::jsonb)\n FROM (VALUES ${sql.join(rows, sql`, `)}) AS f(ord, path, txt, folded)\n CROSS JOIN LATERAL (\n SELECT jsonb_build_object(\n 'field', f.path,\n 'snippet', ts_headline(${config}::regconfig, f.txt, ${query},\n 'StartSel=<mark>,StopSel=</mark>,MaxWords=14,MinWords=1,MaxFragments=1,FragmentDelimiter= … ')\n ) AS m\n WHERE ${matchedField}\n ) s\n )`;\n }\n\n /**\n * `ts_rank(<column>, <query>)` for the collection, or undefined when it has\n * not opted in. This is what backs `orderBy: [\"_score\", \"desc\"]`.\n */\n static buildSearchRankExpression(\n searchString: string,\n table: PgTable<any>,\n collection: CollectionConfig\n ): SQL | undefined {\n let spec: SearchColumnSpec | undefined;\n try {\n spec = buildSearchColumnSpec(collection);\n } catch {\n return undefined;\n }\n if (!spec) return undefined;\n const column = table[spec.column as keyof typeof table] as AnyPgColumn | undefined;\n if (!column) return undefined;\n\n const base = sql`ts_rank(${column}, ${DrizzleConditionBuilder.normalizedTsQuery(searchString, spec)})`;\n\n // Under `hybrid`, `ts_rank` alone is not a ranking for the same reason\n // it is not one under `fuzzy`: it is **zero** for every row the\n // substring half matched and the `@@` half did not, which is the whole\n // population of a prefix query like `seb`. Ordering by it would return\n // those rows in whatever order the table felt like.\n //\n // A constant, not a score, because a substring match carries no\n // measurable strength — `audit` is inside `Auditor` and that is all\n // there is to know. It sits below any real `ts_rank`: the smallest a\n // matching `ts_rank` can be is a single D-weighted hit, 0.1 × 0.0607927\n // ≈ 0.006, so `0.001` keeps every lexeme match above every\n // substring-only one and leaves the substring-only rows to the\n // deterministic id tiebreaker the order builder already appends.\n const rank = spec.mode === \"hybrid\"\n ? (() => {\n const substring = DrizzleConditionBuilder.buildFoldedSubstringCondition(searchString, spec);\n // A literal, cast, rather than a bind parameter: Postgres infers\n // a `CASE` branch's type from its siblings, so a bound `0.001`\n // beside `ELSE 0` is read as an integer and refused\n // (\"invalid input syntax for type integer\").\n return substring\n ? sql`(${base} + CASE WHEN ${substring} THEN ${sql.raw(`${HYBRID_SUBSTRING_SCORE}::real`)} ELSE 0 END)`\n : base;\n })()\n : base;\n\n if (!spec.fuzzy) return rank;\n\n const fuzzyColumn = table[spec.fuzzy.column as keyof typeof table] as AnyPgColumn | undefined;\n if (!fuzzyColumn) return rank;\n\n // With `fuzzy` on, `ts_rank` alone is not a ranking — it is zero for\n // every row the trigram path matched and the exact path did not, which\n // is the whole population of a typo'd query. Measured on the real\n // sustentalent pool: \"auditor de iso14000\" matches four candidates,\n // every one of them at ts_rank 0, so ordering by rank alone returned\n // the best match in whatever order the table felt like.\n //\n // Summed rather than blended with tuned constants: a row that matched\n // exactly contributes both terms, so it outranks a fuzzy-only row of\n // equal similarity without needing a coefficient to say so. Both terms\n // are non-negative and monotonic, which is all the ordering needs.\n const needle = spec.unaccent\n ? sql`${sql.raw(SEARCH_UNACCENT_FN)}(${searchString})`\n : sql`${searchString}`;\n return sql`(${rank} + public.word_similarity(${needle}, ${fuzzyColumn}))`;\n }\n\n /**\n * Build a unique field check condition: rows holding `value`, other than\n * the one `excludeRow` names.\n *\n * `excludeRow` is the whole identity of the row being saved — every key\n * column — so excluding one member of a composite-key table does not\n * exclude every row sharing its first key part.\n */\n static buildUniqueFieldCondition(\n fieldColumn: AnyPgColumn,\n value: unknown,\n excludeRow?: SQL\n ): SQL[] {\n const conditions: SQL[] = [eq(fieldColumn, value)];\n\n if (excludeRow) {\n conditions.push(not(excludeRow));\n }\n\n return conditions;\n }\n\n /**\n * Build relation-based query with joins and conditions\n */\n static buildRelationQuery<T extends DrizzleDynamicQuery>(\n baseQuery: T,\n relation: ResolvedRelation,\n parentId: string | number | (string | number)[],\n targetTable: PgTable<any>,\n parentTable: PgTable<any>,\n parentIdColumn: AnyPgColumn,\n targetIdColumn: AnyPgColumn,\n registry: PostgresCollectionRegistry,\n additionalFilters?: SQL[]\n ): T {\n const { joinConditions, whereConditions } = this.buildRelationConditions(\n relation,\n parentId,\n targetTable,\n parentTable,\n parentIdColumn,\n targetIdColumn,\n registry\n );\n\n let query = baseQuery;\n\n // Apply joins\n for (const { table, condition } of joinConditions) {\n query = query.innerJoin(table, condition);\n }\n\n // Combine all conditions\n const allConditions = [...whereConditions];\n if (additionalFilters) {\n allConditions.push(...additionalFilters);\n }\n\n // Apply where conditions\n if (allConditions.length > 0) {\n query = query.where(and(...allConditions));\n }\n\n return query;\n }\n\n /**\n * A count over a relation's target rows.\n *\n * The junction case counts `distinct` because the caller's query joins the\n * junction; the owning/inverse pair that used to sit here built the same\n * query twice.\n */\n static buildRelationCountQuery<T extends DrizzleDynamicQuery>(\n baseCountQuery: T,\n relation: ResolvedRelation,\n parentId: string | number,\n targetTable: PgTable<any>,\n parentTable: PgTable<any>,\n parentIdColumn: AnyPgColumn,\n targetIdColumn: AnyPgColumn,\n registry: PostgresCollectionRegistry,\n additionalFilters?: SQL[]\n ): T {\n switch (relation.kind) {\n case \"via\":\n return this.buildJoinPathCountQuery(\n baseCountQuery, relation.joinPath, targetTable, parentTable,\n parentIdColumn, parentId, registry, additionalFilters\n );\n\n case \"manyToMany\":\n return this.buildJunctionCountQuery(\n baseCountQuery, relation.through, targetIdColumn, parentId, registry, additionalFilters\n );\n\n case \"belongsTo\":\n case \"hasOne\":\n case \"hasMany\": {\n const allConditions = [\n this.buildSimpleRelationCondition(relation, targetTable, parentTable, parentId),\n ...(additionalFilters ?? [])\n ];\n return baseCountQuery.where(and(...allConditions));\n }\n\n default: {\n const exhaustive: never = relation;\n throw new Error(`Unknown relation kind: ${JSON.stringify(exhaustive)}`);\n }\n }\n }\n\n /**\n * Build join path conditions for count queries\n */\n private static buildJoinPathCountQuery<T extends DrizzleDynamicQuery>(\n baseCountQuery: T,\n joinPath: JoinStep[],\n targetTable: PgTable<any>,\n parentTable: PgTable<any>,\n parentIdColumn: AnyPgColumn,\n parentId: string | number,\n registry: PostgresCollectionRegistry,\n additionalFilters?: SQL[]\n ): T {\n let query = baseCountQuery;\n let currentTable = targetTable;\n const sourceTableName = drizzleTableName(parentTable);\n\n // Process join steps in reverse order\n for (let index = joinPath.length - 1; index >= 0; index--) {\n const joinStep = joinPath[index];\n const { fromTableName, toTableName } = this.joinStepTableNames(joinPath, index, sourceTableName);\n const fromColName = this.getColumnNamesFromColumns(joinStep.on.from)[0];\n const toColName = this.getColumnNamesFromColumns(joinStep.on.to)[0];\n\n const fromTable = registry.getTable(fromTableName);\n const toTable = registry.getTable(toTableName);\n\n if (!fromTable || !toTable) {\n throw new Error(`Join tables not found for step: from ${fromTableName} to ${toTableName}`);\n }\n\n const { joinTable, condition } = this.buildSingleJoinCondition(\n currentTable,\n fromTable,\n toTable,\n fromColName,\n toColName,\n fromTableName,\n toTableName\n );\n\n query = query.innerJoin(joinTable, condition);\n currentTable = joinTable;\n }\n\n if (currentTable !== parentTable) {\n throw new Error(\"Join path did not result in connecting to parent table\");\n }\n\n const allConditions = [eq(parentIdColumn, parentId)];\n if (additionalFilters) {\n allConditions.push(...additionalFilters);\n }\n\n return query.where(and(...allConditions));\n }\n\n /**\n * Build junction table conditions for count queries\n */\n private static buildJunctionCountQuery<T extends DrizzleDynamicQuery>(\n baseCountQuery: T,\n through: { table: string; sourceColumn: string; targetColumn: string },\n targetIdColumn: AnyPgColumn,\n parentId: string | number,\n registry: PostgresCollectionRegistry,\n additionalFilters?: SQL[]\n ): T {\n const junctionTable = registry.getTable(through.table);\n if (!junctionTable) {\n throw junctionTableMissing(through.table);\n }\n\n const junctionSourceCol = junctionTable[through.sourceColumn as keyof typeof junctionTable] as AnyPgColumn;\n const junctionTargetCol = junctionTable[through.targetColumn as keyof typeof junctionTable] as AnyPgColumn;\n\n if (!junctionSourceCol) {\n throw new Error(`Source column '${through.sourceColumn}' not found in junction table '${through.table}'`);\n }\n if (!junctionTargetCol) {\n throw new Error(`Target column '${through.targetColumn}' not found in junction table '${through.table}'`);\n }\n\n const baseConditions = [eq(junctionSourceCol, parentId)];\n if (additionalFilters && additionalFilters.length > 0) {\n baseConditions.push(...additionalFilters);\n }\n\n return baseCountQuery\n .innerJoin(junctionTable, eq(targetIdColumn, junctionTargetCol))\n .where(and(...baseConditions));\n }\n\n\n /**\n * Helper method to extract table names from columns\n */\n /**\n * The two tables one step of a join path joins, by the rule `JoinStep`\n * documents: `on.from` is a column of the previous table — the source\n * collection's for the first step — and `on.to` is a column of\n * `step.table`. A `table.column` spelling names its table outright.\n *\n * The tables used to come from that spelling alone, so a step written the\n * documented way, `{ from: \"id\", to: \"product_id\" }`, named no table at\n * all: every count and include over such a `via` threw \"Join tables not\n * found for step: from to \", and since a record read at a nested address\n * is gated on that count, opening an order from a product's Orders tab\n * failed. The tab's own listing worked — it walks the path forward from\n * the parent, by position — so only the record behind it broke.\n */\n private static joinStepTableNames(\n joinPath: JoinStep[],\n index: number,\n sourceTableName: string\n ): { fromTableName: string; toTableName: string } {\n const step = joinPath[index];\n return {\n fromTableName: this.getTableNamesFromColumns(step.on.from)[0]\n || (index === 0 ? sourceTableName : joinPath[index - 1].table),\n toTableName: this.getTableNamesFromColumns(step.on.to)[0] || step.table\n };\n }\n\n static getTableNamesFromColumns(columns: string | string[]): string[] {\n if (Array.isArray(columns)) {\n return columns.map(col => col.includes(\".\") ? col.split(\".\")[0] : \"\");\n }\n return [columns.includes(\".\") ? columns.split(\".\")[0] : \"\"];\n }\n\n /**\n * Helper method to extract column names from columns\n */\n static getColumnNamesFromColumns(columns: string | string[]): string[] {\n if (Array.isArray(columns)) {\n return columns.map(col => getColumnName(col));\n }\n return [getColumnName(columns)];\n }\n\n\n /**\n * Build vector similarity search expressions for pgvector.\n *\n * Returns:\n * - `orderBy`: SQL expression to ORDER BY distance (ascending = closest first)\n * - `filter`: optional WHERE clause for distance threshold\n * - `distanceSelect`: SQL expression for selecting the distance as `_distance`\n *\n * `property` is `?vector_search=` off the querystring, so it is an untrusted\n * *name*, and it used to be looked up straight in the drizzle table object.\n * Two ways that went wrong, both answering 500 to a malformed request:\n * `?vector_search=title` built `\"title\" <=> '[1,2]'::vector`, which the\n * database rejects with \"operator does not exist\"; and a table object also\n * carries non-column keys (`_`, methods), which passed the `if (!column)`\n * guard and compiled to nonsense. The name is resolved against the table's\n * actual columns and required to be a `vector` — anything else is the\n * caller's mistake and gets a 400 that says so.\n */\n static buildVectorSearchConditions(\n table: PgTable<any>,\n vectorSearch: {\n property: string;\n vector: number[];\n distance?: \"cosine\" | \"l2\" | \"inner_product\";\n threshold?: number;\n }\n ): { orderBy: SQL; filter?: SQL; distanceSelect: SQL } {\n const column = DrizzleConditionBuilder.resolveVectorColumn(table, vectorSearch.property);\n\n // The vector is interpolated as a raw SQL literal below (pgvector has no\n // bind form for the `::vector` cast), so every element must be a finite\n // number. The REST query parser already enforces this, but this builder\n // is a shared entry point — validate here too so no future caller can\n // turn an unchecked value into SQL injection.\n if (\n !Array.isArray(vectorSearch.vector) ||\n vectorSearch.vector.length === 0 ||\n !vectorSearch.vector.every((n) => typeof n === \"number\" && Number.isFinite(n))\n ) {\n throw new Error(\"Vector search requires a non-empty array of finite numbers\");\n }\n\n const vectorLiteral = `'[${vectorSearch.vector.join(\",\")}]'::vector`;\n const distanceFn = vectorSearch.distance || \"cosine\";\n\n let operator: string;\n switch (distanceFn) {\n case \"cosine\":\n operator = \"<=>\";\n break;\n case \"l2\":\n operator = \"<->\";\n break;\n case \"inner_product\":\n operator = \"<#>\";\n break;\n }\n\n const distanceExpr = sql`${column} ${sql.raw(operator)} ${sql.raw(vectorLiteral)}`;\n\n return {\n orderBy: distanceExpr,\n filter: vectorSearch.threshold != null\n ? sql`(${column} ${sql.raw(operator)} ${sql.raw(vectorLiteral)}) < ${vectorSearch.threshold}`\n : undefined,\n distanceSelect: sql`(${column} ${sql.raw(operator)} ${sql.raw(vectorLiteral)})`\n };\n }\n\n /**\n * The `vector` column a request named, or a 400 explaining what it named.\n *\n * `getTableColumns` rather than a key lookup: it returns only the columns,\n * so `_`, `getSQL` and every other property of a drizzle table stop looking\n * like candidates. The type check is on the *physical* column\n * (`vector(1536)`) rather than on the declared property, so it holds for an\n * introspected collection too, where the property carries no Rebase type.\n */\n private static resolveVectorColumn(table: PgTable<any>, property: string): AnyPgColumn {\n const columns = getTableColumns(table) as Record<string, AnyPgColumn> | undefined;\n const column = columns?.[property];\n if (!column) {\n const known = Object.entries(columns ?? {})\n .filter(([, c]) => isVectorColumn(c))\n .map(([name]) => name);\n throw ApiError.badRequest(\n `Unknown vector property \"${property}\". ` +\n (known.length > 0\n ? `This collection's vector properties are: ${known.join(\", \")}.`\n : \"This collection declares no `vector` property to search.\"),\n \"UNKNOWN_VECTOR_PROPERTY\"\n );\n }\n if (!isVectorColumn(column)) {\n throw ApiError.badRequest(\n `Property \"${property}\" is not a vector column (it is \\`${columnSqlType(column) || \"unknown\"}\\`), ` +\n \"so it has no distance operator. Name the property declared as `{ type: \\\"vector\\\" }`.\",\n \"UNKNOWN_VECTOR_PROPERTY\"\n );\n }\n return column;\n }\n}\n\n/** The column's SQL type, for a value that may not be a drizzle column at all. */\nconst columnSqlType = (column: unknown): string => {\n const getSQLType = (column as { getSQLType?: () => string })?.getSQLType;\n return typeof getSQLType === \"function\" ? getSQLType.call(column).toLowerCase() : \"\";\n};\n\n/** True for `vector(1536)` and its pgvector siblings, whatever the width. */\nconst isVectorColumn = (column: unknown): boolean =>\n /^(vector|halfvec|sparsevec)\\b/.test(columnSqlType(column));\n\n/**\n * Alias for DrizzleConditionBuilder for consistent naming with other database implementations.\n * This allows code to use PostgresConditionBuilder alongside future MongoConditionBuilder, etc.\n */\nexport const PostgresConditionBuilder = DrizzleConditionBuilder;\n\n","import { CollectionConfig, JUNCTION_PIVOT_KEY, Property, ResolvedRelation, isManyToMany, type ResolvedVia } from \"@rebasepro/types\";\nimport { canReadField, resolveCollectionRelations, findRelation, createRelationRefWithData, fieldKeyForColumn } from \"@rebasepro/common\";\nimport { currentFieldViewer } from \"./field-viewer\";\nimport { normalizeDbValues } from \"../data-transformer\";\nimport { deriveRowAddress } from \"./collection-helpers\";\nimport { PostgresCollectionRegistry } from \"../collections/PostgresCollectionRegistry\";\n\n/**\n * Turning a drizzle result into a row we serve.\n *\n * There are two shapes, and they are the same walk. Both take a row whose\n * relation fields hold nested objects, both unwrap junction rows to reach the\n * target behind them, and both leave every other column alone. They differ only\n * in what they put where the relation was:\n *\n * - `\"ref\"` — a `{ id, path, __type: \"relation\" }` reference carrying the\n * target's values. This is what the admin renders.\n * - `\"inline\"` — the target's own columns, flat. This is what REST serves, and\n * — since the in-process SDK reads through the same pipeline — what\n * `rebase.dataAsAdmin` / `context.data` serve too. A developer never sees a ref.\n *\n * They used to be two functions that happened to agree, and the agreement was\n * not enforced by anything: the row-identity bug had to be fixed five times\n * across differently-shaped copies of this walk, and one of the copies was\n * dead code nobody had noticed. Whatever the next cross-cutting change is, it\n * is one edit here.\n */\nexport type RelationStyle = \"ref\" | \"inline\";\n\n/**\n * Whether a many-relation reaches its target through a junction table.\n *\n * Also used to build the drizzle `with` config, which is why it is exported:\n * the query has to nest one level deeper for a junction, and the row walk has\n * to unwrap that same level back out.\n */\nexport function isJunctionRelation(relation: ResolvedRelation): boolean {\n // An explicit `through` says so outright.\n if (isManyToMany(relation)) return true;\n // A multi-hop join path is the same thing spelled longhand.\n return relation.kind === \"via\" && relation.joinPath.length > 1;\n}\n\n/**\n * Reach the target row inside a junction row.\n *\n * A junction row looks like `{ post_id: 1, tag_id: { id: 5, name: \"ts\" } }` —\n * the foreign keys, and the target nested under one of them. The target is the\n * only object among them, so that is how it is found. A junction row that has\n * not been nested (no `with` on the join) has no object and is returned as-is.\n *\n * \"The only object\" stopped being true when a junction gained\n * `through.properties`: a payload `map` is an object too, and it sits in the\n * row beside the target with no guaranteed order between them — so this would\n * have unwrapped to the payload and served it as the tag. The payload keys are\n * known from the relation, so they are skipped by name rather than by shape.\n */\nfunction unwrapJunctionRow(\n item: Record<string, unknown>,\n relation: ResolvedRelation\n): Record<string, unknown> {\n const payload = isManyToMany(relation) ? relation.through.properties : undefined;\n const nestedKey = Object.keys(item).find(\n key => key !== JUNCTION_PIVOT_KEY\n && !(payload && key in payload)\n && typeof item[key] === \"object\" && item[key] !== null && !Array.isArray(item[key])\n );\n return nestedKey ? item[nestedKey] as Record<string, unknown> : item;\n}\n\n/**\n * Give back the number a `number` property was declared to be.\n *\n * Postgres returns NUMERIC as a string and drizzle keeps it that way, since a\n * numeric can hold more precision than a double. But the collection declared\n * this property `number` and the OpenAPI spec this server publishes says\n * `type: number`, so serving `\"9.99\"` breaks its own contract — and breaks it\n * asymmetrically, because a create answers with the number and the read that\n * follows answers with the string. Any client that multiplies a price works\n * until the first refresh.\n *\n * Declaring a property `number` already accepts double precision — that is what\n * the admin has always parsed it to. Columns REST serves that no property\n * declares are left exactly as the database returned them.\n */\nfunction coerceDeclaredNumber(value: unknown, property: Property | undefined): unknown {\n if (property?.type !== \"number\" || typeof value !== \"string\") return value;\n const parsed = parseFloat(value);\n return isNaN(parsed) ? null : parsed;\n}\n\n/**\n * Serve a `date` column as the timestamp this API says it serves.\n *\n * The OpenAPI document this server publishes types a `date` property as\n * `string, format: date-time` — RFC 3339 — and node-postgres hands over the\n * Postgres literal: `\"2026-08-24 01:37:57.647+02\"`, with a space where the `T`\n * belongs and a two-digit offset. V8 parses that by accident; Safari's\n * `new Date()` does not have to, and the spec never promised it. A column whose\n * documented type only parses in some browsers is not a contract.\n *\n * A date-only column (`mode: \"date\"`, `format: \"date\"` in the spec) is already\n * RFC 3339 as `YYYY-MM-DD` and is left alone — widening it to a timestamp would\n * invent a time of day and a timezone the column does not have.\n */\nfunction toRestDate(value: unknown): unknown {\n if (value instanceof Date) return isNaN(value.getTime()) ? null : value.toISOString();\n // An upstream walk may already have tagged it for the admin's view model.\n if (value && typeof value === \"object\" && (value as { __type?: unknown }).__type === \"date\") {\n return (value as { value?: unknown }).value ?? null;\n }\n if (typeof value !== \"string\" && typeof value !== \"number\") return value;\n if (typeof value === \"string\" && /^\\d{4}-\\d{2}-\\d{2}$/.test(value)) return value;\n const date = new Date(value);\n // Unparseable is left exactly as it arrived: inventing `null` would erase a\n // value the database holds, and this is a rendering, not a validator.\n return isNaN(date.getTime()) ? value : date.toISOString();\n}\n\n/**\n * One scalar, as REST serves it: declared numbers as numbers, declared dates as\n * RFC 3339, everything else exactly as the database returned it.\n */\nfunction toRestScalar(value: unknown, property: Property | undefined): unknown {\n if (property?.type === \"date\") return toRestDate(value);\n return coerceDeclaredNumber(value, property);\n}\n\n/**\n * Apply {@link toRestScalar} across a row, leaving undeclared columns alone.\n *\n * Exported because the include loader attaches related rows itself, and a\n * target rendered differently from its parent is the shape bug this whole file\n * exists to prevent — a date was a string at the top level and a\n * `{ __type: \"date\" }` envelope one level down, in the same response.\n */\nexport function toRestValues(\n row: Record<string, unknown>,\n collection: CollectionConfig\n): Record<string, unknown> {\n const properties = collection.properties;\n if (!properties) return row;\n\n const out: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(row)) {\n out[key] = toRestScalar(value, properties[key] as Property | undefined);\n }\n return out;\n}\n\n/** Render one target row in the requested style. */\n/**\n * Drop every column this caller may not read.\n *\n * Password hashes and verification tokens have to be readable server-side but\n * must never reach a client — and \"never\" has to mean every exit from this\n * pipeline, including relation targets, or a secret leaks through whichever\n * path was overlooked. Keyed by both the property name and its column name,\n * since a row can arrive keyed either way depending on the caller.\n *\n * `excludeFromApi` is the case with no roles in it: `effectiveAccess` expands\n * the flag to `read: []`, which no caller satisfies, so the flag needs no branch\n * of its own here and no longer has one. `access.read: [\"hr\"]` is the same walk\n * asking a different question of {@link currentFieldViewer}.\n *\n * The field is **deleted**, never nulled. A withheld value that arrives as\n * `null` is indistinguishable from a stored `null`, which turns a permission\n * boundary into a question a client can answer by counting nulls — and it makes\n * an `update` that echoes the row back overwrite the real value with the null it\n * was handed.\n *\n * A withheld to-one relation takes its foreign key with it. `bandId: 7` names\n * the salary band exactly as `band: { id: 7 }` does, and the row carries the\n * column beside the relation — so withholding only the relation withheld\n * nothing.\n *\n * `_matches` is filtered rather than deleted: it is the list of fields a text\n * search hit, and a field the caller cannot read must not appear in it even\n * though the array itself is theirs to see.\n */\nexport function stripUnreadable(\n row: Record<string, unknown>,\n collection: CollectionConfig\n): Record<string, unknown> {\n const properties = collection.properties as Record<string, Property> | undefined;\n if (!properties) return row;\n\n const viewer = currentFieldViewer();\n const hidden = new Set<string>();\n const hide = (name: string) => {\n hidden.add(name);\n delete row[name];\n };\n // Resolved only when a withheld property is a relation, which is rare: this\n // runs on every row of every read.\n let relations: ReturnType<typeof resolveCollectionRelations> | undefined;\n\n for (const [key, property] of Object.entries(properties)) {\n if (canReadField(property, viewer)) continue;\n hide(key);\n if (property.columnName) hide(property.columnName);\n if (property.type === \"relation\") {\n relations ??= resolveCollectionRelations(collection);\n const relation = findRelation(relations, key);\n if (relation?.kind === \"belongsTo\") {\n hide(fieldKeyForColumn(collection, relation.localKey));\n hide(relation.localKey);\n }\n }\n }\n\n if (hidden.size > 0 && Array.isArray(row._matches)) {\n row._matches = (row._matches as unknown[]).filter(\n match => !hidden.has(typeof match === \"string\" ? match : String((match as { field?: unknown })?.field))\n );\n }\n return row;\n}\n\nfunction renderTarget(\n targetRow: Record<string, unknown>,\n targetCollection: CollectionConfig,\n style: RelationStyle,\n registry: PostgresCollectionRegistry\n): unknown {\n if (style === \"inline\") {\n // The target's columns, and only those: its address is the consumer's\n // to derive, and merging one in overwrites a real `id` column.\n return stripUnreadable(toRestValues({ ...targetRow }, targetCollection), targetCollection);\n }\n\n const address = relationTargetAddress(targetRow, targetCollection, registry);\n const path = targetCollection.slug;\n return createRelationRefWithData(address, path, {\n id: address,\n path,\n // Stripped here as well as in the inline branch above. The two branches\n // are the same data in two renderings — REST inlines the target's\n // columns, the admin and every realtime frame carry a ref with those\n // columns attached — and only the first one was filtered. So a password\n // hash that REST correctly withheld rode out on every `.listen()` frame\n // and every WebSocket fetch of anything with a relation to users.\n values: stripUnreadable(\n normalizeDbValues(targetRow, targetCollection) as Record<string, unknown>,\n targetCollection\n )\n });\n}\n\n/**\n * The address a relation ref points at.\n *\n * The whole key, not its first column: a composite-keyed target addressed by\n * `tenant_id` alone points at every row that shares it. A target whose key\n * cannot be resolved at all used to throw here — reading `[0]` of an empty\n * array — taking down the parent's fetch over a relation it may not even have\n * asked for. The first column is a guess, but a ref that resolves to nothing\n * beats no rows at all.\n */\nexport function relationTargetAddress(\n targetRow: Record<string, unknown>,\n targetCollection: CollectionConfig,\n registry: PostgresCollectionRegistry\n): string {\n const address = deriveRowAddress(targetRow, targetCollection, registry);\n if (address) return address;\n return String(targetRow[Object.keys(targetRow)[0]] ?? \"\");\n}\n\n/**\n * The row the admin renders: every column, with relations as references.\n *\n * Values are normalized (dates, numbers, NaN) because the admin's view-model\n * expects real types. The row's own address is *not* among the columns — it is\n * derived by the consumer from the collection's primary keys.\n */\nexport function toFlatRow(\n row: Record<string, unknown>,\n collection: CollectionConfig,\n registry: PostgresCollectionRegistry\n): Record<string, unknown> {\n const resolvedRelations = resolveCollectionRelations(collection);\n const normalized = normalizeDbValues(row, collection) as Record<string, unknown>;\n\n // Keyed by relation, reading the drizzle relation name off the raw row: the\n // relation's property key and the name drizzle nested it under are not\n // always the same, and the value is written back under the property key.\n for (const [key, relation] of Object.entries(resolvedRelations)) {\n const relData = row[relation.relationName || key];\n if (relData === undefined || relData === null) continue;\n\n if (relation.cardinality === \"many\" && Array.isArray(relData)) {\n const targetCollection = relation.target();\n normalized[key] = relData.map((item: Record<string, unknown>) =>\n renderTarget(\n isJunctionRelation(relation) ? unwrapJunctionRow(item, relation) : item,\n targetCollection,\n \"ref\",\n registry\n ));\n } else if (relation.cardinality === \"one\" && typeof relData === \"object\" && !Array.isArray(relData)) {\n normalized[key] = renderTarget(relData as Record<string, unknown>, relation.target(), \"ref\", registry);\n }\n }\n\n return stripUnreadable(normalized, collection);\n}\n\n/**\n * The row REST serves: every column under its own name, with the value Postgres\n * returned, and relations inlined as the target's columns.\n *\n * Values are the ones the database returned, except where that contradicts the\n * declared type: a `number` property is served as a number (see\n * {@link coerceDeclaredNumber}) and a `date` as RFC 3339, which is what this\n * server's own OpenAPI document says a date column is (see\n * {@link toRestDate}).\n *\n * Keyed by the row rather than by the relation list — a REST fetch only loads\n * the relations `include` asked for, so the row is the authority on which are\n * actually there.\n */\nexport function toRestRow(\n row: Record<string, unknown>,\n collection: CollectionConfig,\n registry: PostgresCollectionRegistry\n): Record<string, unknown> {\n const resolvedRelations = resolveCollectionRelations(collection);\n const flat: Record<string, unknown> = {};\n\n for (const [key, value] of Object.entries(row)) {\n const relation = findRelation(resolvedRelations, key);\n\n if (relation && Array.isArray(value)) {\n flat[key] = value.map((item: Record<string, unknown>) =>\n renderTarget(\n isJunctionRelation(relation) ? unwrapJunctionRow(item, relation) : item,\n relation.target(),\n \"inline\",\n registry\n ));\n } else if (relation && typeof value === \"object\" && value !== null) {\n flat[key] = renderTarget(value as Record<string, unknown>, relation.target(), \"inline\", registry);\n } else {\n flat[key] = toRestScalar(value, collection.properties?.[key] as Property | undefined);\n }\n }\n\n return stripUnreadable(flat, collection);\n}\n","import { and, sql, SQL } from \"drizzle-orm\";\nimport { PgTable } from \"drizzle-orm/pg-core\";\nimport { ApiError } from \"@rebasepro/server\";\nimport { DrizzleClient } from \"../interfaces\";\n\n/**\n * Explain a write that matched no rows.\n *\n * Row-level security filters UPDATE and DELETE through the policy's USING\n * clause instead of raising: a denied write is reported by Postgres exactly\n * like a successful one that happened to match nothing. Left unchecked, a\n * caller cannot tell \"denied\" from \"done\" — the write returns 200/204 and the\n * row is untouched. An agent handed a key with `orders:delete` and no delete\n * policy is the case that makes it concrete: it deletes nothing, forever, and\n * is told it worked every time.\n *\n * Re-reading the target over the *same* RLS-scoped handle separates the two\n * cases. A visible row means the policy rejected the write (403); an invisible\n * one means there is nothing there to write for this caller (404, matching what\n * a GET would say). The re-read is bound by the caller's own policies, so it\n * discloses nothing a plain read wouldn't.\n *\n * Only reached when zero rows matched, so the happy path pays nothing.\n *\n * It lives here, rather than beside its first caller, because every zero-row\n * write has to answer the same question and answer it identically: the rule\n * that a readable-but-unwritable target is a 403 is the contract, and a second\n * copy of it is a second chance to get it wrong.\n *\n * @param handle The RLS-scoped connection the write ran on — not a fresh\n * one, or the re-read would answer for a different caller.\n * @param table The table the write targeted (the junction, for a link).\n * @param conditions The write's own WHERE terms, reused verbatim.\n * @param denied Message for the 403: the target is there and was refused.\n * @param missing Message for the 404: there is nothing there for this caller.\n */\nexport async function explainZeroRowWrite(\n handle: DrizzleClient,\n table: PgTable,\n conditions: SQL[],\n denied: string,\n missing: string\n): Promise<ApiError> {\n const visible = await handle\n .select({ present: sql<number>`1` })\n .from(table)\n .where(and(...conditions))\n .limit(1);\n\n if (visible.length > 0) {\n return ApiError.forbidden(denied, \"WRITE_DENIED\");\n }\n\n return ApiError.notFound(missing);\n}\n","import { and, eq, inArray, sql, type SQL } from \"drizzle-orm\";\nimport { AnyPgColumn, PgTable } from \"drizzle-orm/pg-core\";\nimport { ApiError, logger } from \"@rebasepro/server\";\nimport type { Properties, ResolvedVia } from \"@rebasepro/types\";\nimport { DrizzleClient } from \"../interfaces\";\nimport { PostgresCollectionRegistry } from \"../collections/PostgresCollectionRegistry\";\nimport { relationMisconfigured } from \"./collection-helpers\";\nimport { explainZeroRowWrite } from \"./write-denial\";\n\n/**\n * Writing a many-to-many means writing rows in a junction table, and doing that\n * needs three things: the table, the column naming the row being written from,\n * and the column naming the far side.\n *\n * Those three used to be re-derived at each of the four call sites — twice for\n * `through`, twice for `joinPath`, in the owning and the inverse direction —\n * and the two `joinPath` derivations did not agree. The inverse one keyed off\n * `step.table`; the owning one asked `getTableNamesFromColumns` which table a\n * step's columns belonged to, and that answers `\"\"` for an unqualified column\n * name. So for `{ table: \"posts_tags\", on: { from: \"id\", to: \"tag_id\" } }` —\n * the form the docs and every fixture use — no branch matched, both columns\n * stayed null, and the write was skipped with a warning nobody reads. Writing\n * a to-many `via` relation did nothing at all.\n *\n * Hence one binder, keyed on `step.table` (always present) and accepting the\n * qualified `table.column` form where it is used.\n */\nexport interface JunctionBinding {\n table: PgTable;\n /** The junction column holding the id of the row being written from. */\n parentColumn: AnyPgColumn;\n /** The junction column holding the id of the row on the far side. */\n targetColumn: AnyPgColumn;\n /** `<collection>.<relation>`, for error messages. */\n label: string;\n /**\n * The link's own columns — `through.properties` — as authored. Empty for a\n * junction that carries nothing but its two keys, and for every `via`\n * relation (a join chain has no declared payload).\n */\n payload: Properties;\n}\n\n/**\n * One element of a membership write: a target id, and optionally what the link\n * to it should hold.\n *\n * The pivot is kept apart from the id rather than merged into a row shape,\n * because the two are answered by different collections: the id is parsed\n * against the *target's* primary key, the payload validated against the\n * junction's own properties.\n */\nexport interface JunctionLinkWrite {\n id: unknown;\n /** Payload values keyed by property key, already serialized for the driver. */\n pivot?: Record<string, unknown>;\n}\n\n/**\n * The target rows a membership write can see, when that is not all of them —\n * the target's `beforeQuery` scope and its soft delete.\n *\n * The diff reads \"what is linked now\" through it, so a link to a row the caller\n * was never shown is in neither list and is left alone rather than unlinked.\n */\nexport interface VisibleTargets {\n table: PgTable;\n /** The target column the junction's `targetColumn` holds values of. */\n idColumn: AnyPgColumn;\n condition: SQL;\n}\n\n/** A junction that cannot be resolved is a broken relation, not a no-op. */\nconst misconfigured = (label: string, detail: string): ApiError =>\n relationMisconfigured(label, detail);\n\nfunction column(table: PgTable, name: string | undefined, label: string, role: string): AnyPgColumn {\n const col = name ? table[name as keyof typeof table] as AnyPgColumn : undefined;\n if (!col) {\n throw misconfigured(label, `no ${role} column '${name ?? \"?\"}' on the junction table`);\n }\n return col;\n}\n\n/** The junction a `manyToMany` names outright. */\nexport function bindThroughJunction(\n registry: PostgresCollectionRegistry,\n through: { table: string; sourceColumn: string; targetColumn: string; properties?: Properties },\n label: string\n): JunctionBinding {\n const table = registry.getTable(through.table);\n if (!table) {\n throw misconfigured(label, `no table '${through.table}' in the registry`);\n }\n return {\n table,\n parentColumn: column(table, through.sourceColumn, label, \"source\"),\n targetColumn: column(table, through.targetColumn, label, \"target\"),\n label,\n payload: through.properties ?? {}\n };\n}\n\n/** The bare column name, whether written as `col` or `table.col`. */\nconst columnPart = (spec: string | string[]): string => {\n const one = Array.isArray(spec) ? spec[0] : spec;\n return one.includes(\".\") ? one.split(\".\")[1] : one;\n};\n\n/** The table a column spec names, or undefined when it is unqualified. */\nconst tablePart = (spec: string | string[]): string | undefined => {\n const one = Array.isArray(spec) ? spec[0] : spec;\n return one.includes(\".\") ? one.split(\".\")[0] : undefined;\n};\n\n/**\n * The junction a `via` relation reaches through, from either end.\n *\n * A step is `{ table: T, on: { from, to } }` where `from` names a column on\n * whatever the walk was standing on and `to` names one on `T`. That positional\n * meaning is what makes the unqualified form work at all, so the walk carries\n * the previous table rather than asking the column names where they live.\n */\nexport function bindJoinPathJunction(\n registry: PostgresCollectionRegistry,\n joinPath: ResolvedVia[\"joinPath\"],\n parentTableName: string,\n targetTableName: string,\n label: string\n): JunctionBinding {\n const junctionName = joinPath\n .map(step => step.table)\n .find(table => table !== parentTableName && table !== targetTableName);\n\n if (!junctionName) {\n throw misconfigured(label, \"its joinPath has no table between the two ends to hold the links\");\n }\n\n const table = registry.getTable(junctionName);\n if (!table) {\n throw misconfigured(label, `no table '${junctionName}' in the registry`);\n }\n\n let parentColumnName: string | undefined;\n let targetColumnName: string | undefined;\n let previousTable = parentTableName;\n\n for (const step of joinPath) {\n const fromTable = tablePart(step.on.from) ?? previousTable;\n const toTable = tablePart(step.on.to) ?? step.table;\n\n // Take the column on the junction's own side of the step, and let the\n // other side say which end of the relation this step reached.\n if (toTable === junctionName && fromTable === parentTableName) {\n parentColumnName = columnPart(step.on.to);\n } else if (fromTable === junctionName && toTable === parentTableName) {\n parentColumnName = columnPart(step.on.from);\n } else if (toTable === junctionName && fromTable === targetTableName) {\n targetColumnName = columnPart(step.on.to);\n } else if (fromTable === junctionName && toTable === targetTableName) {\n targetColumnName = columnPart(step.on.from);\n }\n\n previousTable = step.table;\n }\n\n if (!parentColumnName || !targetColumnName) {\n throw misconfigured(\n label,\n `its joinPath does not connect '${parentTableName}' and '${targetTableName}' through '${junctionName}'`\n );\n }\n\n return {\n table,\n parentColumn: column(table, parentColumnName, label, \"source\"),\n targetColumn: column(table, targetColumnName, label, \"target\"),\n label,\n // A `via` reaches through a table it did not declare, so there is no\n // `through.properties` to read and nothing to write beyond the keys.\n payload: {}\n };\n}\n\n/**\n * Remove one link, leaving the row on the far side alone.\n *\n * This is what `DELETE authors/1/tags/5` has to mean for a many-to-many: the\n * target is shared, so deleting the row would remove the tag from every other\n * post that uses it.\n */\nexport async function removeJunctionLink(\n tx: DrizzleClient,\n binding: JunctionBinding,\n parentId: unknown,\n targetId: unknown,\n subject: { parent: string; relation: string }\n): Promise<void> {\n const conditions = [\n eq(binding.parentColumn, parentId),\n eq(binding.targetColumn, targetId)\n ];\n const result = await tx.delete(binding.table).where(and(...conditions));\n\n // The link is the row here, so the junction's own policies decide. Callers\n // establish membership first, over this same handle, which is what makes a\n // zero-row delete meaningful rather than ambiguous.\n if ((result.rowCount ?? 0) === 0) {\n throw await explainZeroRowWrite(\n tx,\n binding.table,\n conditions,\n `Not allowed to unlink \"${targetId}\" from \"${subject.parent}\" \"${parentId}\": ` +\n \"a row-level security policy rejected the write.\",\n `No \"${subject.relation}\" link between \"${subject.parent}\" \"${parentId}\" ` +\n `and \"${targetId}\" to remove.`\n );\n }\n\n logger.info(`Unlinked '${subject.relation}' ${targetId} from ${subject.parent} ${parentId}`);\n}\n\n/**\n * Make the junction say that `parentId` is linked to exactly `targetIds`, by\n * diffing against what is linked now rather than replacing the set.\n *\n * A save of the parent used to delete every junction row for it and re-insert\n * the ids the browser sent — a list the browser assembled from a read it did\n * earlier. Three things followed, all data loss rather than display:\n *\n * - **Lost update.** Two editors with post 7 open: A adds tag X and saves, B\n * saves any field from a form that predates it, and X is gone with nothing\n * reported to either of them.\n * - **A partially-read set is a partially-deleted set.** The read that fills\n * the form runs under RLS, so a user who may edit the parent but cannot see\n * some of the linked rows gets a shorter list — and writing it back deleted\n * the links they were never shown. The select that drives the diff runs in\n * this same transaction under the same policies, and through\n * `visibleTargets` — the target's `beforeQuery` scope and soft delete, which\n * RLS knows nothing of — so a link the caller cannot read is in neither list\n * and survives the save.\n * - **Junction payload columns.** A junction carrying its own columns\n * (`position`, `role`, `created_at`) lost them on every save, because every\n * row was re-inserted with only the two keys. Untouched links are left alone.\n *\n * The insert is `ON CONFLICT DO NOTHING`, so two sessions adding the same link\n * concurrently is a no-op rather than a unique violation.\n *\n * ## The payload\n *\n * An element may carry `pivot` — the link's own columns, from a `_pivot` on the\n * wire. The array still *sets membership*: what is not in it is unlinked, and\n * what is in it is linked. The payload is what the link should hold once it\n * exists, so a new link is inserted with it (columns the caller left out take\n * their DEFAULT) and an existing link named with a payload is updated to match.\n *\n * The alternative — ignoring the payload on a link that already exists — makes\n * `PATCH { members: [{ id: 7, _pivot: { role: \"admin\" } }] }` a no-op that\n * answers 200, which is the one outcome a caller cannot tell from success. An\n * element with no `pivot` at all leaves the existing link exactly as it was;\n * that is what keeps a plain `[1, 2, 3]` membership write from wiping the\n * `role`s off the links it did not mention.\n */\nexport async function applyJunctionMembership(\n tx: DrizzleClient,\n binding: JunctionBinding,\n parentId: unknown,\n links: JunctionLinkWrite[],\n visibleTargets?: VisibleTargets\n): Promise<void> {\n const existingRows = visibleTargets\n ? await tx\n .select({ targetId: binding.targetColumn })\n .from(binding.table)\n .innerJoin(visibleTargets.table, eq(binding.targetColumn, visibleTargets.idColumn))\n .where(and(eq(binding.parentColumn, parentId), visibleTargets.condition))\n : await tx\n .select({ targetId: binding.targetColumn })\n .from(binding.table)\n .where(eq(binding.parentColumn, parentId));\n\n // Keyed by `String(...)` because a junction key can come back from the\n // driver as a string where the parsed value is a number, and a diff that\n // missed that would delete and re-insert every link on every save.\n const existingById = new Map<string, unknown>();\n for (const row of existingRows as Array<{ targetId: unknown }>) {\n if (row.targetId === null || row.targetId === undefined) continue;\n existingById.set(String(row.targetId), row.targetId);\n }\n\n const wantedById = new Map<string, JunctionLinkWrite>();\n for (const link of links) {\n if (link.id === null || link.id === undefined) continue;\n wantedById.set(String(link.id), link);\n }\n\n const removed = [...existingById.entries()]\n .filter(([key]) => !wantedById.has(key))\n .map(([, value]) => value);\n const added = [...wantedById.entries()]\n .filter(([key]) => !existingById.has(key))\n .map(([, link]) => link);\n // Already linked AND named with a payload: the caller is stating what the\n // link holds, not re-stating that it exists.\n const repayloaded = [...wantedById.entries()]\n .filter(([key, link]) => existingById.has(key) && link.pivot && Object.keys(link.pivot).length > 0)\n .map(([, link]) => link);\n\n if (removed.length > 0) {\n await removeLinks(tx, binding, parentId, removed);\n }\n\n if (added.length > 0) {\n await tx.insert(binding.table)\n .values(added.map(link => ({\n [binding.parentColumn.name]: parentId,\n [binding.targetColumn.name]: link.id,\n ...(link.pivot ?? {})\n })))\n .onConflictDoNothing();\n }\n\n for (const link of repayloaded) {\n await tx.update(binding.table)\n .set(link.pivot!)\n .where(and(\n eq(binding.parentColumn, parentId),\n eq(binding.targetColumn, link.id)\n ));\n }\n}\n\n/**\n * Set the columns of ONE existing link, without touching the membership.\n *\n * The membership array cannot express this on its own: sending one element\n * would unlink everything else, and sending the whole set to change one link's\n * `role` is the lost-update race {@link applyJunctionMembership}'s diff exists\n * to avoid, reintroduced by the caller. So there is a route for it —\n * `PATCH /api/data/:slug/:id/:relation/:targetId` with a `_pivot` body.\n *\n * A link that is not there is a **404**, not a silent insert: \"update the role\n * on this membership\" and \"create this membership\" are different requests, and\n * a caller who mistyped an id should hear about it rather than get a new link\n * with a partial payload. Zero rows is also how a policy refusal arrives on\n * Postgres, which is why the distinction is drawn by\n * {@link explainZeroRowWrite} rather than by this function guessing.\n */\nexport async function updateJunctionPivot(\n tx: DrizzleClient,\n binding: JunctionBinding,\n parentId: unknown,\n targetId: unknown,\n values: Record<string, unknown>,\n subject: { parent: string; relation: string }\n): Promise<void> {\n const conditions = [\n eq(binding.parentColumn, parentId),\n eq(binding.targetColumn, targetId)\n ];\n const result = await tx.update(binding.table).set(values).where(and(...conditions));\n\n if ((result.rowCount ?? 0) === 0) {\n throw await explainZeroRowWrite(\n tx,\n binding.table,\n conditions,\n `Not allowed to update the link between \"${subject.parent}\" \"${parentId}\" and ` +\n `\"${targetId}\": a row-level security policy rejected the write.`,\n `No \"${subject.relation}\" link between \"${subject.parent}\" \"${parentId}\" ` +\n `and \"${targetId}\" to update.`\n );\n }\n\n logger.info(`Updated '${subject.relation}' link ${targetId} on ${subject.parent} ${parentId}`);\n}\n\n/**\n * Drop the named links, and refuse to call it done if the database kept any.\n *\n * Every id here came out of the select in {@link applyJunctionMembership}, on\n * this same handle, so all of them were visible. Fewer deletions than that means\n * something refused them — and a save that reports success while the membership\n * it stored is not the membership it was given has told the caller something\n * untrue about the database.\n */\nasync function removeLinks(\n tx: DrizzleClient,\n binding: JunctionBinding,\n parentId: unknown,\n removed: unknown[]\n): Promise<void> {\n const conditions = [\n eq(binding.parentColumn, parentId),\n inArray(binding.targetColumn, removed)\n ];\n const result = await tx.delete(binding.table).where(and(...conditions));\n const deleted = result.rowCount ?? 0;\n\n if (deleted >= removed.length) return;\n\n const survivors = await tx\n .select({ present: sql<number>`1` })\n .from(binding.table)\n .where(and(...conditions))\n .limit(1);\n\n // Nothing survived: a concurrent session removed the rest between the\n // select and the delete. The membership is what the caller asked for,\n // which is all this promised.\n if (survivors.length === 0) return;\n\n throw ApiError.forbidden(\n `Not allowed to remove ${removed.length - deleted} of ${removed.length} link(s) ` +\n `for relation '${binding.label}': a row-level security policy rejected the write.`,\n \"WRITE_DENIED\"\n );\n}\n","import { and, isNotNull, isNull, SQL } from \"drizzle-orm\";\nimport type { AnyPgColumn, PgTable } from \"drizzle-orm/pg-core\";\nimport { getTableColumns } from \"drizzle-orm\";\nimport type { CollectionConfig } from \"@rebasepro/types\";\nimport { softDeleteFieldOf } from \"@rebasepro/common\";\nimport { ApiError } from \"@rebasepro/server\";\nimport { toSnakeCase } from \"@rebasepro/utils\";\n\n/**\n * Soft delete, in one place.\n *\n * A collection with `softDelete` records a deletion as a timestamp instead of\n * removing the row, which means two things have to hold everywhere and not\n * almost everywhere:\n *\n * 1. a delete writes the stamp rather than issuing `DELETE`;\n * 2. **every** read hides stamped rows unless asked otherwise.\n *\n * The second is the one that goes wrong. A listing that filters and a `count`\n * that does not is a page saying \"1 of 4 results\"; a relation load that does not\n * is a deleted comment reappearing under its post. So the condition is built by\n * exactly one function, here, and the read paths call it — rather than each\n * path growing its own `isNull(...)`, which is how these features rot.\n *\n * The field is a `date` property the collection declares itself. This module\n * resolves it and nothing more: it does not add the column, and a config that\n * turns the flag on without the property is refused at boot\n * (`assertCollectionConfigs`), where the answer is a config change, rather than\n * at the first delete, where it would be a 500 for whoever pressed the button.\n */\n\n/** How a caller asks about deleted rows. See `FetchCollectionProps.withDeleted`. */\nexport type WithDeleted = boolean | \"only\" | undefined;\n\nexport interface SoftDeleteField {\n /** The property key — the wire name, what a `PATCH` sets to `null`. */\n field: string;\n /** The physical column, `columnName ?? snake_case(field)`. */\n columnName: string;\n}\n\n/**\n * The soft-delete field this collection uses, or `undefined` if it has none.\n *\n * Returns the field even when the property is missing, because the *boot* check\n * is what refuses that config; making this return `undefined` for it would turn\n * a misconfiguration into \"soft delete quietly off\", which is the failure mode\n * worth avoiding — deletes would silently start removing rows.\n */\nexport function resolveSoftDelete(collection: CollectionConfig | undefined): SoftDeleteField | undefined {\n const field = softDeleteFieldOf(collection);\n if (!field) return undefined;\n const property = (collection?.properties as Record<string, { columnName?: string }> | undefined)?.[field];\n return {\n field,\n columnName: property?.columnName ?? toSnakeCase(field)\n };\n}\n\n/**\n * The drizzle column behind the soft-delete field, if the table has one.\n *\n * Matched on the column's own `name` rather than on the key, because a\n * generated schema keys columns by the property name while an introspected one\n * may key them by the column — and this has to work for both.\n */\nexport function softDeleteColumn(\n collection: CollectionConfig | undefined,\n table: PgTable\n): AnyPgColumn | undefined {\n const resolved = resolveSoftDelete(collection);\n if (!resolved) return undefined;\n // A registry can hand back something that is not a drizzle table (a stub in\n // a test, a collection whose schema has not been generated). No columns is\n // \"no soft-delete column\", not a crash on a read.\n const columns = (table ? getTableColumns(table) : undefined) as Record<string, AnyPgColumn> | undefined;\n if (!columns) return undefined;\n const direct = columns[resolved.field] ?? columns[resolved.columnName];\n if (direct) return direct;\n for (const column of Object.values(columns)) {\n if ((column as { name?: string }).name === resolved.columnName) return column;\n }\n return undefined;\n}\n\n/**\n * `deleted_at IS NULL` — or nothing, when the caller asked for the deleted rows\n * or the collection has no soft delete at all.\n *\n * The one place the rule is written. Call it from every read; `undefined` means\n * \"add no condition\", which composes with `and(...)` unchanged.\n */\nexport function softDeleteCondition(\n collection: CollectionConfig | undefined,\n table: PgTable,\n withDeleted?: WithDeleted\n): SQL | undefined {\n if (withDeleted === true) return undefined;\n const column = softDeleteColumn(collection, table);\n if (!column) return undefined;\n return withDeleted === \"only\" ? isNotNull(column) : isNull(column);\n}\n\n/**\n * Fold the soft-delete condition into a list of conditions that is being built.\n *\n * A convenience for the call sites that already collect `SQL[]`, so adding soft\n * delete to one of them is a single line rather than three.\n */\nexport function withSoftDelete(\n conditions: SQL[],\n collection: CollectionConfig | undefined,\n table: PgTable,\n withDeleted?: WithDeleted\n): SQL[] {\n const condition = softDeleteCondition(collection, table, withDeleted);\n if (condition) conditions.push(condition);\n return conditions;\n}\n\n/**\n * Combine an existing `where` with the soft-delete condition.\n *\n * For the call sites that hold one composed condition rather than a list.\n */\nexport function andSoftDelete(\n where: SQL | undefined,\n collection: CollectionConfig | undefined,\n table: PgTable,\n withDeleted?: WithDeleted\n): SQL | undefined {\n const condition = softDeleteCondition(collection, table, withDeleted);\n if (!condition) return where;\n return where ? and(where, condition) : condition;\n}\n\n/**\n * The refusal of an upsert whose key belongs to a row in the trash.\n *\n * The key is taken — the stamped row still holds it — so the write cannot be a\n * create, and an upsert is not the operation that brings a deleted row back:\n * that is the restore, an update setting the field to `null`, which goes\n * through the update gate and is what history records as one. Left to the\n * statement, `INSERT … ON CONFLICT DO UPDATE` wrote the new values into the\n * hidden row and the caller was told \"created\", with the row still in the trash.\n */\nexport function rowInTrashError(path: string, key: string, softDelete: SoftDeleteField): ApiError {\n return ApiError.conflict(\n `\"${key}\" in \"${path}\" is in the trash, so an upsert cannot write it. Restore the row first `\n + `(an update setting '${softDelete.field}' to null), or purge it (a delete with ?hard=true) and upsert again.`,\n \"ROW_IN_TRASH\"\n );\n}\n","/**\n * `beforeQuery`: the one place a collection callback can change *which* rows a\n * read asks for.\n *\n * `afterRead` runs over rows that have already been fetched, so until this\n * existed an application developer who needed the read itself narrowed — a\n * tenant scope, a visibility window, a per-role row filter — had two options:\n * patch this driver, or `rebase eject`.\n *\n * Two properties are load-bearing, and both belong to the *shape* of the thing\n * rather than to how carefully each call site was written.\n *\n * ## It can only narrow\n *\n * A hook returns a {@link QueryNarrowing} — a declarative filter, optionally a\n * logical group — which is AND-ed into the query the caller sent. `AND(q, c)`\n * is a subset of `q` for every `c`, so there is no `c` a hook can return that\n * widens a read. The obvious alternative shape, `(query) => query`, can: a\n * hook that drops a condition on the way through runs the query without it,\n * and on an RLS data plane a dropped condition returns everything the policies\n * happen to allow. That is the same reasoning behind\n * `UnknownFilterFieldsMode` defaulting to `\"error\"`, and it is why that mode is\n * **forced** to `\"error\"` for a hook's own filter here whatever the\n * process-wide setting is: a scope condition naming a renamed column has to\n * refuse the request, never be dropped from it.\n *\n * ## It cannot be skipped\n *\n * The hooks are resolved *by the read path itself*, out of the registry that\n * read already holds — not handed to it by a caller who might forget. So a\n * read that compiles a WHERE compiles this one too, or it throws.\n *\n * The one thing a read path cannot derive is the identity the hook runs as:\n * `FetchService`, `RelationService` and `DataService` carry no user. That\n * arrives as a {@link ReadCallContextProvider}, threaded from the driver that\n * constructed them — the driver is the only object that knows both the caller\n * and the connection, and it constructs a service per transaction anyway, so\n * there is exactly one place per instance to pass it.\n *\n * Absence is the interesting case, and it fails closed: a service built with no\n * provider refuses any read of a collection that declares `beforeQuery`, with\n * a message naming the collection. So a future construction site that forgets\n * the provider breaks loudly on the first scoped collection instead of quietly\n * serving every row — which is the failure mode this whole module exists to\n * rule out. A deployment with no `beforeQuery` anywhere never reaches any of\n * this: the check is one map lookup and the compiled SQL is byte-identical.\n *\n * @module\n */\nimport { and, SQL } from \"drizzle-orm\";\nimport { PgTable } from \"drizzle-orm/pg-core\";\nimport type {\n BeforeQueryProps,\n CollectionConfig,\n CollectionCallbacks,\n QueryNarrowing,\n ReadOperation,\n ReadQuery,\n RebaseCallContext\n} from \"@rebasepro/types\";\nimport type { PostgresCollectionRegistry } from \"../collections/PostgresCollectionRegistry\";\nimport {\n DrizzleConditionBuilder,\n type FilterCompilationOptions,\n type UnknownFilterFieldsMode\n} from \"../utils/drizzle-conditions\";\n\n/**\n * How a read path reaches the context its `beforeQuery` hooks run with.\n *\n * A function rather than the context itself, because the driver's transaction\n * handle and its `data` plane are established *after* it constructs the\n * services that read through them — and because building a context costs an\n * object that a collection with no hooks should never pay for.\n */\nexport type ReadCallContextProvider = () => RebaseCallContext;\n\n/**\n * The `beforeQuery` hooks that apply to one collection, in the order they run.\n *\n * Global first, then the collection's own — the same order `afterRead` and\n * `beforeSave` use. The order changes nothing about the outcome here (both are\n * AND-ed in, so neither can relax the other) and is kept because a hook that\n * logs or measures should see the same sequence everywhere.\n *\n * There is no property tier: a property callback shapes one value on a row, and\n * a row filter is not a value.\n */\nfunction resolveBeforeQuery(\n registry: PostgresCollectionRegistry | undefined,\n collection: CollectionConfig | undefined\n): NonNullable<CollectionCallbacks[\"beforeQuery\"]>[] {\n const hooks: NonNullable<CollectionCallbacks[\"beforeQuery\"]>[] = [];\n const global = registry?.getGlobalCallbacks();\n if (global?.beforeQuery) hooks.push(global.beforeQuery);\n const own = collection?.callbacks;\n if (own?.beforeQuery) hooks.push(own.beforeQuery);\n return hooks;\n}\n\n/** Whether anything at all would run for this collection. */\nexport function hasBeforeQuery(\n registry: PostgresCollectionRegistry | undefined,\n collection: CollectionConfig | undefined\n): boolean {\n return resolveBeforeQuery(registry, collection).length > 0;\n}\n\n/**\n * What a read path is about to compile, in the form a hook is shown it.\n *\n * Built by the caller rather than derived here, because only the caller knows\n * which of its options are part of the query and which are transport — a\n * `databaseId`, an `include` tree, a `withDeleted` flag are not \"which rows\".\n */\nexport interface ReadQueryDescription {\n operation: ReadOperation;\n query: ReadQuery;\n}\n\n/** Everything `beforeQueryConditions` needs that is not about this one read. */\nexport interface BeforeQueryEnv {\n registry: PostgresCollectionRegistry | undefined;\n /**\n * The context provider the driver handed down. `undefined` means no driver\n * built this service, which is a refusal rather than a bypass — see the\n * module comment.\n */\n callContext: ReadCallContextProvider | undefined;\n}\n\n/**\n * The conditions every `beforeQuery` hook for this collection asks to add.\n *\n * Returned as a list rather than pre-combined so a caller pushes them onto the\n * same `allConditions` array its own filter goes into. Every caller AND-es\n * that array, which is what makes these additive — the hook is not trusted to\n * narrow, it is only ever given a way to.\n */\nexport async function beforeQueryConditions(\n env: BeforeQueryEnv,\n collection: CollectionConfig | undefined,\n path: string,\n table: PgTable<never>,\n description: ReadQueryDescription,\n filterContext: Omit<FilterCompilationOptions, \"unknownFields\">\n): Promise<SQL[]> {\n const hooks = resolveBeforeQuery(env.registry, collection);\n if (hooks.length === 0) return [];\n\n if (!collection) {\n // A hook reaching here with no collection can only be a global one, and\n // a global hook has no properties to compile its filter against. It\n // cannot be honoured — so it is refused, not skipped.\n throw new Error(\n `[beforeQuery] \"${path}\" resolves to no registered collection, so a global \\`beforeQuery\\` cannot ` +\n \"be compiled against it. Register the collection, or narrow the hook to the collections it applies to.\"\n );\n }\n\n if (!env.callContext) {\n throw new Error(\n `[beforeQuery] \"${collection.slug}\" declares a \\`beforeQuery\\` hook, and this read was compiled by a ` +\n \"service that was built without a call-context provider, so the hook could not run. Serving the read \" +\n \"unnarrowed is not an option — a hook that does not run widens the result set. Construct the \" +\n \"service through the driver (which passes one), or pass a provider explicitly.\"\n );\n }\n const context = env.callContext();\n\n // Always fatal, whatever `configureUnknownFilterFields` was set to. The\n // process-wide `\"warn\"` mode exists for a deployment that knowingly sends\n // filter keys its tables do not have; a scope condition is not that, and\n // dropping one is precisely the widening that mode's own doc comment is\n // about.\n const unknownFields: UnknownFilterFieldsMode = \"error\";\n const options: FilterCompilationOptions = { ...filterContext, unknownFields };\n\n const conditions: SQL[] = [];\n for (const hook of hooks) {\n const props: BeforeQueryProps = {\n collection,\n path,\n operation: description.operation,\n query: description.query,\n context\n };\n const narrowing: QueryNarrowing | void = await hook(props);\n\n if (!narrowing) continue;\n\n // A hook that returned an object but expressed nothing in it is almost\n // certainly a branch that meant to build a filter and did not, and the\n // failure is invisible: the read is served whole. Refusing is the loud\n // half, and it comes before the compile so the message is about the\n // hook rather than about an empty condition list.\n if (!narrowing.filter && !narrowing.logical) {\n throw new Error(\n `[beforeQuery] \"${collection.slug}\" returned an object with neither \\`filter\\` nor \\`logical\\`. ` +\n \"Return nothing (or `undefined`) to add no conditions; an empty object reads as a scope that was \" +\n \"meant to be built and was not.\"\n );\n }\n\n if (narrowing.filter) {\n conditions.push(...DrizzleConditionBuilder.buildFilterConditions(\n narrowing.filter, table, path, options\n ));\n }\n if (narrowing.logical) {\n const logical = DrizzleConditionBuilder.buildLogicalConditions(\n narrowing.logical, table, path, options\n );\n if (logical) conditions.push(logical);\n }\n }\n return conditions;\n}\n\n/**\n * {@link beforeQueryConditions}, pre-combined, for the read paths that hold a\n * single `SQL` rather than a list of them — a single get, a relation loader.\n */\nexport async function beforeQueryCondition(\n env: BeforeQueryEnv,\n collection: CollectionConfig | undefined,\n path: string,\n table: PgTable<never>,\n description: ReadQueryDescription,\n filterContext: Omit<FilterCompilationOptions, \"unknownFields\">\n): Promise<SQL | undefined> {\n const conditions = await beforeQueryConditions(env, collection, path, table, description, filterContext);\n if (conditions.length === 0) return undefined;\n if (conditions.length === 1) return conditions[0];\n return and(...conditions);\n}\n","import { and, eq, inArray, notInArray, or, sql, SQL, getTableName as drizzleTableName } from \"drizzle-orm\";\nimport { AnyPgColumn, PgTable } from \"drizzle-orm/pg-core\";\nimport { DrizzleClient } from \"../interfaces\";\nimport { CollectionConfig, FilterValues, OrderByTuple, ResolvedRelation, ResolvedManyToMany, ResolvedHasMany, ResolvedHasOne } from \"@rebasepro/types\";\nimport { getTableName, getJunctionConfigForRelation, resolveCollectionRelations, findRelation, fieldKeyForColumn } from \"@rebasepro/common\";\nimport { hasForeignKeyOnTarget, isManyToMany, type ResolvedVia } from \"@rebasepro/types\";\nimport { DrizzleConditionBuilder, joinColumn } from \"../utils/drizzle-conditions\";\nimport {\n getCollectionByPath,\n getTableForCollection,\n relationMisconfigured,\n requirePrimaryKeys,\n parseIdValues,\n buildCompositeId,\n joinsOnNaturalKey,\n rowIdentityCondition,\n primaryKeyColumns,\n sourceKeyField,\n type PrimaryKeyInfo\n} from \"./collection-helpers\";\nimport { parseDataFromServer } from \"../data-transformer\";\nimport { stripUnreadable } from \"./row-pipeline\";\nimport { PostgresCollectionRegistry } from \"../collections/PostgresCollectionRegistry\";\nimport { ApiError, logger } from \"@rebasepro/server\";\nimport type { NestedPathHop } from \"./nested-path\";\nimport { bindThroughJunction } from \"./junction-writes\";\n// A soft-deleted row must not come back through a relation either: a deleted\n// comment reappearing under its post is the same bug as one reappearing in the\n// listing. See `soft-delete.ts`.\nimport { andSoftDelete, softDeleteCondition } from \"./soft-delete\";\nimport { beforeQueryCondition, type ReadCallContextProvider } from \"./read-scope\";\n\n/**\n * The ids in a to-many relation write, whatever shape the caller sent.\n *\n * A membership list is written as either the related rows (`[{ id: 1 }]`, what\n * the admin UI sends back after reading them) or as bare keys (`[1]`, `[\"t-1\"]`,\n * what anyone writing the API by hand sends). Only the first was read, via a\n * blind `.map(rel => rel.id)`, and a bare key therefore became `undefined`:\n * on a numeric-keyed target that surfaced as `Invalid numeric ID: undefined`,\n * and on a string-keyed one it did not surface at all — `String(undefined)`\n * wrote a junction row pointing at the literal `\"undefined\"`, which no read\n * would ever match. Both shapes are accepted here, in one place, because both\n * call sites had the same assumption.\n *\n * An element that carries no key is refused rather than skipped: dropping it\n * would silently write a shorter membership list than the caller asked for.\n */\nfunction relationTargetIds(value: unknown, relationName: string, collectionSlug: string): (string | number)[] {\n if (!Array.isArray(value)) return [];\n\n return value.map((element, index) => {\n if (typeof element === \"string\" || typeof element === \"number\") return element;\n if (element && typeof element === \"object\") {\n const id = (element as { id?: unknown }).id;\n if (typeof id === \"string\" || typeof id === \"number\") return id;\n }\n throw new Error(\n `Cannot write relation \"${relationName}\" on \"${collectionSlug}\": element ${index} carries no id. ` +\n \"Pass either the related rows (`[{ id: … }]`) or their keys (`[1, 2]`), not \" +\n `${element === null ? \"null\" : typeof element}.`\n );\n });\n}\n\n/**\n * Typed wrapper for Drizzle dynamic query innerJoin.\n * Drizzle's `$dynamic()` queries lose the `innerJoin` method from\n * their static type, but it exists at runtime. This helper bridges\n * the gap with a single confined cast.\n *\n * Exported so `data-transformer` uses this one rather than restating the same\n * fact — it wrote `query.innerJoin(...) as unknown as typeof query`, which is\n * the same claim about the same drizzle behaviour in a second spelling.\n */\nexport function applyDynamicJoin<T>(query: T, joinTable: PgTable, condition: SQL): T {\n return (query as unknown as { innerJoin(t: PgTable, c: SQL): T }).innerJoin(joinTable, condition) as T;\n}\n\n/**\n * The ON condition of one `joinPath` step: every `from` column equal to its\n * `to` column, or `undefined` naming the first column either table lacks.\n *\n * `on.from`/`on.to` are a column or a tuple, and a tuple is how a step joins on\n * a composite key. Comparing the first pair alone joined each row to every row\n * sharing that column — every locale of a translation, not the one addressed.\n */\nexport function joinStepCondition(\n currentTable: PgTable,\n joinTable: PgTable,\n step: { on: { from: string | string[]; to: string | string[] } }\n): { condition: SQL } | { missing: string } {\n const from = Array.isArray(step.on.from) ? step.on.from : [step.on.from];\n const to = Array.isArray(step.on.to) ? step.on.to : [step.on.to];\n const pairs: SQL[] = [];\n for (let i = 0; i < Math.max(from.length, to.length); i++) {\n const fromColumn = from[i] ?? \"(none)\";\n const toColumn = to[i] ?? \"(none)\";\n // By SQL name or property key: a generated table keys `order_id` as\n // `orderId`, and a step names the column. See `joinColumn`.\n const fromCol = joinColumn(currentTable, fromColumn);\n const toCol = joinColumn(joinTable, toColumn);\n if (!fromCol || !toCol) return { missing: `${fromColumn} -> ${toColumn}` };\n pairs.push(eq(fromCol, toCol));\n }\n return { condition: pairs.length === 1 ? pairs[0] : and(...pairs)! };\n}\n\n/**\n * Typed wrapper for DrizzleConditionBuilder.buildRelationQuery on dynamic queries.\n * The method returns a widened generic that doesn't reassign cleanly;\n * this helper confines the cast.\n */\nfunction applyDynamicRelationQuery<T>(\n query: T,\n ...args: Parameters<typeof DrizzleConditionBuilder.buildRelationQuery>\n): T {\n return DrizzleConditionBuilder.buildRelationQuery(...args) as T;\n}\n\n/**\n * The relation's own matching condition, AND-ed with an include's `where`.\n *\n * Written once because the batch loaders reach the same decision from six\n * places — one per relation kind, twice over for the to-one and to-many\n * variants — and a `where` dropped from one of them is a filter the caller\n * asked for that silently did not apply.\n */\nfunction narrowed(base: SQL, narrow?: SQL): SQL {\n return narrow ? and(base, narrow)! : base;\n}\n\n/**\n * Two optional conditions, AND-ed: either, both, or neither.\n *\n * The include's own `where` and the target collection's `beforeQuery` arrive\n * separately and are both optional, and they have to reach the loaders as one\n * condition because that is the shape every relation kind threads through.\n */\nfunction narrowBoth(a?: SQL, b?: SQL): SQL | undefined {\n if (a && b) return and(a, b)!;\n return a ?? b;\n}\n\n/**\n * The registry has no table for a collection a relation just asked about.\n *\n * \"Parent table not found\" was the whole message, four times over, and it names\n * neither the collection nor the table nor anything to do next. The state it\n * reports is a real one with two distinct causes worth telling apart, so the\n * message names both: the collection was never registered (its file is not\n * exported from `config/collections/index.ts`, so the driver never saw it), or\n * the generated schema has no such table (the collection is declared and\n * `schema.generated.ts` predates it).\n */\nfunction unregisteredTable(collectionSlug: string, table: string, role: string): Error {\n return new Error(\n `No registered table \"${table}\" for the ${role} collection \"${collectionSlug}\". ` +\n \"Either the collection is not exported from config/collections/index.ts, so this driver \" +\n \"never registered it, or the generated Drizzle schema predates it — run \" +\n \"`rebase schema generate` and restart.\"\n );\n}\n\n/**\n * Service for handling all relation-related operations.\n * Handles fetching, updating, and managing row relations.\n */\n/**\n * A related record resolved by {@link RelationService}: the target row's\n * values plus the identity (`id`) and originating collection (`path`)\n * needed to build relation references. Internal to the postgres driver —\n * flattened to plain rows at the fetch-service boundary.\n */\nexport interface RelatedRow<M extends Record<string, unknown> = Record<string, unknown>> {\n id: string | number;\n path: string;\n values: M;\n /**\n * The junction row's own columns, for a `manyToMany` whose `through`\n * declares `properties`. Absent for every other relation kind and for a\n * junction that carries nothing but its two keys.\n *\n * Kept beside `values` rather than merged into them, because it is not the\n * target's data: two posts sharing a tag see the same tag row and different\n * links. The include pipeline serves it under\n * {@link JUNCTION_PIVOT_KEY}.\n */\n pivot?: Record<string, unknown>;\n}\n\nexport class RelationService {\n constructor(\n private db: DrizzleClient,\n private registry: PostgresCollectionRegistry,\n /**\n * How this service's reads reach the identity their `beforeQuery` hooks\n * run as. Passed by the driver that constructed it; absent only in a\n * test, where a collection declaring the hook is refused rather than\n * read unnarrowed. See `read-scope.ts`.\n */\n private callContext?: ReadCallContextProvider\n ) { }\n\n /**\n * The **target** collection's `beforeQuery` narrowing, for rows reached\n * through a relation.\n *\n * The target's, not the parent's, because these are the target's rows: a\n * scope on `comments` has to hold whether they are listed at\n * `/comments` or loaded as `post.comments`. Without this an `include` is a\n * way around every row filter a collection declares — the exact hole\n * `stripUnreadable` was once missing from the relation-ref branch.\n *\n * Folded into the `narrow` condition the loaders already thread through\n * every relation kind, so a `via` join, a junction, a foreign key and the\n * dynamic relation builder all apply it or none of them do.\n */\n private narrowTargetRead(\n parentCollection: CollectionConfig,\n relation: ResolvedRelation,\n parentId?: string | number\n ): Promise<SQL | undefined> {\n const targetCollection = relation.target();\n const targetTable = getTableForCollection(targetCollection, this.registry);\n return beforeQueryCondition(\n { registry: this.registry, callContext: this.callContext },\n targetCollection,\n targetCollection.slug,\n targetTable as PgTable<never>,\n {\n operation: \"relation\",\n query: {\n relatedTo: {\n parentSlug: parentCollection.slug,\n parentId,\n relationName: relation.relationName\n }\n }\n },\n { collection: targetCollection, registry: this.registry }\n );\n }\n\n /**\n * The target rows a caller may see through `relation`: the target's\n * `beforeQuery` scope and its soft delete, as one condition on the target\n * table, or `undefined` when neither applies.\n *\n * What every loader here narrows a read by, exposed for the membership\n * writes. A write that diffs a new link set against \"what is linked now\"\n * has to diff against what the caller could have read — otherwise saving\n * back the list they were shown unlinks every row they were not.\n */\n async visibleTargetCondition(\n parentCollection: CollectionConfig,\n relation: ResolvedRelation,\n parentId?: string | number\n ): Promise<SQL | undefined> {\n const targetCollection = relation.target();\n const targetTable = getTableForCollection(targetCollection, this.registry);\n return andSoftDelete(\n await this.narrowTargetRead(parentCollection, relation, parentId),\n targetCollection,\n targetTable\n );\n }\n\n /**\n * One target row, as the {@link RelatedRow} everything here returns.\n *\n * Eight sites built this by hand, which is how the address came to be the\n * target's first key column in all eight — one edit, eight places to miss.\n *\n * `resolveNested` is the one thing they did not agree on, and the\n * disagreement was invisible: the single-parent fetches pass `db` and\n * `registry` to `parseDataFromServer`, so the target's *own* relations get\n * resolved too, while the batch paths deliberately do not — a query per\n * target row is the N+1 the batching exists to avoid. Naming the parameter\n * makes that a decision rather than a difference between two call sites\n * nobody was comparing.\n */\n private async toRelatedRow<M extends Record<string, unknown>>(\n targetRow: Record<string, unknown>,\n targetCollection: CollectionConfig,\n targetPks: PrimaryKeyInfo[],\n options?: { resolveNested?: boolean }\n ): Promise<RelatedRow<M>> {\n const values = options?.resolveNested\n ? await parseDataFromServer(targetRow, targetCollection, this.db, this.registry)\n : await parseDataFromServer(targetRow, targetCollection);\n\n // The target's OWN exclusions, applied to the target's own columns.\n // This is the second rendering of a relation target (row-pipeline's\n // `renderTarget` is the first) and it reached the same clients by a\n // different route: WebSocket fetches and every realtime frame come\n // through here. REST was clean and `.listen()` was not, which is the\n // worst version of this to have — the leak is invisible from the surface\n // people test.\n stripUnreadable(values as Record<string, unknown>, targetCollection);\n\n return {\n // The whole key: a composite target addressed by its first column\n // names every row that shares it.\n id: buildCompositeId(targetRow, targetPks),\n path: targetCollection.slug,\n values: values as M\n };\n }\n\n /**\n * The junction row's own columns, as the caller may see them.\n *\n * Read through the same two steps the target's columns go through:\n * `parseDataFromServer` gives the declared type (a `date` is a Date, a\n * NUMERIC is a number) and `stripUnreadable` drops what this caller may not\n * read. The second is the point — `access.read: [\"hr\"]` on a payload\n * property has to mean the same thing it means on a collection's, and the\n * synthetic junction config carries the property verbatim, so the same\n * `effectiveAccess` answers both.\n *\n * Only the payload keys: the two foreign keys are the link's identity, and\n * the caller already has both — one is the row they asked from, the other\n * is the target's own id.\n */\n private async readPivot(\n junctionRow: Record<string, unknown>,\n junctionConfig: CollectionConfig,\n payloadKeys: string[]\n ): Promise<Record<string, unknown>> {\n const raw: Record<string, unknown> = {};\n for (const key of payloadKeys) {\n if (key in junctionRow) raw[key] = junctionRow[key];\n }\n const parsed = await parseDataFromServer(raw, junctionConfig) as Record<string, unknown>;\n return stripUnreadable(parsed, junctionConfig);\n }\n\n /**\n * A WHERE matching any of `parentIds`, by the whole key.\n *\n * A single key is an `IN (…)`. A composite one cannot be: matching\n * `tenant_id IN (1, 1)` collects every row of tenant 1, so two parents that\n * share their first column each receive the other's relations. It becomes\n * an OR of ANDs — one exact address per parent — which Postgres indexes the\n * same way it would a multi-column key lookup.\n */\n private parentKeyCondition(\n parentTable: PgTable,\n parentPks: PrimaryKeyInfo[],\n parentIds: (string | number)[]\n ): SQL {\n const columnFor = (fieldName: string) => {\n const col = parentTable[fieldName as keyof typeof parentTable] as AnyPgColumn;\n if (!col) throw new Error(`Key column '${fieldName}' not found in parent table`);\n return col;\n };\n\n if (parentPks.length === 1) {\n const values = parentIds.map(id => parseIdValues(id, parentPks)[parentPks[0].fieldName]);\n return inArray(columnFor(parentPks[0].fieldName), values);\n }\n\n const perParent = parentIds.map(id => {\n const values = parseIdValues(id, parentPks);\n return and(...parentPks.map(pk => eq(columnFor(pk.fieldName), values[pk.fieldName])));\n });\n return or(...perParent) as SQL;\n }\n\n /**\n * Reject a relation that cannot express a composite-keyed parent.\n *\n * `localKey` and `foreignKeyOnTarget` are single column names: one column\n * cannot reference a two-column key, so such a relation has no correct\n * reading. Left alone it would silently match on the first key column and\n * hand a tenant's rows to its neighbour — say so instead.\n */\n private assertSingleKeyAddressable(\n parentCollection: CollectionConfig,\n parentPks: PrimaryKeyInfo[],\n via: string,\n relation?: ResolvedRelation\n ): void {\n // A `sourceKey` names the single column the link actually points at, so\n // the parent's key can be as wide as it likes — nothing here references\n // it. That is the one way out of this error, and the message below now\n // has to say so.\n if (relation && hasForeignKeyOnTarget(relation) && relation.sourceKey) return;\n\n if (parentPks.length > 1) {\n throw new Error(\n `Relation on '${parentCollection.slug}' uses '${via}', a single foreign-key column, but ` +\n `'${parentCollection.slug}' is keyed on ${parentPks.map(k => `'${k.fieldName}'`).join(\" + \")}. ` +\n `One column cannot reference a composite key — give the relation a \\`sourceKey\\` naming ` +\n `a single unique column to point at, or express it with \\`joinPath\\`, whose ` +\n `\\`on.from\\`/\\`on.to\\` take every key column.`\n );\n }\n }\n\n /**\n * What the target's foreign key holds, for each of these parent rows.\n *\n * Ordinarily the parent's id, and then this is free. When the relation\n * declares a `sourceKey` the two are different values, and the mapping\n * between them lives in the source table — so it costs one SELECT, issued\n * once for the whole batch rather than per parent.\n *\n * Both directions come back because both are needed and deriving one from\n * the other by hand is how a batch loader ends up attributing a child to the\n * wrong parent: reads translate id → key to build the WHERE, and then\n * translate key → id to attribute each row that comes back.\n */\n /**\n * The value a related row's foreign key must hold to belong to this parent.\n *\n * `undefined` when the parent's source key is null — which is not an error\n * here, only in the callers that were about to write it. Exposed for\n * {@link PersistService}, which stamps this onto a child created under a\n * nested path and would otherwise write the id and lose the row.\n */\n async parentKeyValue(\n parentCollection: CollectionConfig,\n relation: ResolvedHasOne | ResolvedHasMany,\n parentId: string | number,\n db: DrizzleClient = this.db\n ): Promise<string | number | undefined> {\n const { keyByParentId } = await this.resolveSourceKeys(parentCollection, relation, [parentId], db);\n return keyByParentId.get(String(parentId));\n }\n\n /**\n * Shared with {@link RelationWriteService}: a write needs the same source\n * key a read does, and resolving it twice is how the two would disagree.\n */\n async resolveSourceKeys(\n parentCollection: CollectionConfig,\n relation: ResolvedHasOne | ResolvedHasMany,\n parentIds: (string | number)[],\n // Writes pass their transaction: reading the source key on the pool\n // while the same transaction is holding an uncommitted change to it\n // would translate the id against a stale value.\n db: DrizzleClient = this.db\n ): Promise<{ keyByParentId: Map<string, string | number>; parentIdByKey: Map<string, string | number> }> {\n const keyByParentId = new Map<string, string | number>();\n const parentIdByKey = new Map<string, string | number>();\n\n const parentPks = requirePrimaryKeys(parentCollection, this.registry);\n\n if (!joinsOnNaturalKey(relation, parentCollection, this.registry)) {\n // The ordinary case, and it must stay free of a round-trip: the\n // key IS the id. Parsed, not passed through — an id arrives as the\n // string from a URL, and comparing \"7\" against an integer column is\n // how this returns nothing at all.\n for (const id of parentIds) {\n const value = parseIdValues(id, parentPks)[parentPks[0].fieldName];\n keyByParentId.set(String(id), value);\n parentIdByKey.set(String(value), id);\n }\n return { keyByParentId, parentIdByKey };\n }\n\n const field = sourceKeyField(relation, parentCollection, this.registry);\n const parentTable = getTableForCollection(parentCollection, this.registry);\n const keyColumn = parentTable[field as keyof typeof parentTable] as AnyPgColumn;\n if (!keyColumn) {\n throw new Error(\n `\\`sourceKey: \"${field}\"\\` on relation '${relation.relationName}' is not a column on ` +\n `'${parentCollection.slug}'. It names a column on the source table, not on the target.`\n );\n }\n\n const rows = await db\n .select()\n .from(parentTable)\n .where(this.parentKeyCondition(parentTable, parentPks, parentIds));\n\n for (const row of rows as Array<Record<string, unknown>>) {\n const keyValue = row[field] as string | number | null;\n if (keyValue === null || keyValue === undefined) continue;\n const parentId = buildCompositeId(row, parentPks);\n keyByParentId.set(String(parentId), keyValue);\n // A duplicate here means the source key is not unique, which makes\n // \"which parent does this child belong to\" unanswerable. Refuse\n // rather than pick the last one seen.\n const existing = parentIdByKey.get(String(keyValue));\n if (existing !== undefined && String(existing) !== String(parentId)) {\n throw new Error(\n `\\`sourceKey: \"${field}\"\\` on relation '${relation.relationName}' is not unique on ` +\n `'${parentCollection.slug}': rows '${existing}' and '${parentId}' both hold ` +\n `'${keyValue}'. Add a unique constraint — a link that addresses more than one source ` +\n `row cannot say which one a related row belongs to.`\n );\n }\n parentIdByKey.set(String(keyValue), parentId);\n }\n\n return { keyByParentId, parentIdByKey };\n }\n\n /**\n * Fetch rows related to a parent row through a specific relation\n */\n async fetchRelatedEntities<M extends Record<string, unknown>>(\n parentCollectionPath: string,\n parentId: string | number,\n relationKey: string,\n options: {\n filter?: FilterValues<Extract<keyof M, string>>;\n orderBy?: string | OrderByTuple[];\n order?: \"desc\" | \"asc\";\n limit?: number;\n startAfter?: Record<string, unknown>;\n searchString?: string;\n databaseId?: string;\n } = {}\n ): Promise<RelatedRow<M>[]> {\n const parentCollection = getCollectionByPath(parentCollectionPath, this.registry);\n const resolvedRelations = resolveCollectionRelations(parentCollection);\n const relation = findRelation(resolvedRelations, relationKey);\n\n if (!relation) {\n const available = Object.keys(resolvedRelations).join(\", \") || \"(none)\";\n // A path segment naming a relation the collection does not declare\n // is a bad URL, not a server fault. It answered 500 INTERNAL_ERROR,\n // which tells the caller to retry or report an outage over a\n // request that will never succeed — and buries a message that\n // already says exactly what is wrong.\n //\n // 404, like an unknown collection: the URL names nothing. The\n // available relations stay in the message because reaching this\n // point means the caller could already read the parent collection,\n // and `?include=` would list the same names.\n throw ApiError.notFound(\n `Relation '${relationKey}' not found in collection '${parentCollectionPath}'. Available relations: [${available}]`,\n \"UNKNOWN_RELATION\"\n );\n }\n\n return this.fetchEntitiesUsingJoins<M>(parentCollection, parentId, relation, options);\n }\n\n /**\n * Fetch rows using join paths for complex relations\n */\n async fetchEntitiesUsingJoins<M extends Record<string, unknown>>(\n parentCollection: CollectionConfig,\n parentId: string | number,\n relation: ResolvedRelation,\n options: {\n filter?: FilterValues<Extract<keyof M, string>>;\n orderBy?: string | OrderByTuple[];\n order?: \"desc\" | \"asc\";\n limit?: number;\n startAfter?: Record<string, unknown>;\n searchString?: string;\n databaseId?: string;\n } = {}\n ): Promise<RelatedRow<M>[]> {\n const targetCollection = relation.target();\n const targetTable = getTableForCollection(targetCollection, this.registry);\n const idInfo = requirePrimaryKeys(targetCollection, this.registry);\n const idField = targetTable[idInfo[0].fieldName as keyof typeof targetTable] as AnyPgColumn;\n\n const parentPks = requirePrimaryKeys(parentCollection, this.registry);\n const parentIdInfo = parentPks[0];\n const parsedParentIdObj = parseIdValues(parentId, parentPks);\n const parsedParentId = parsedParentIdObj[parentIdInfo.fieldName];\n const parentTableName = getTableName(parentCollection);\n const parentTable = this.registry.getTable(parentTableName);\n if (!parentTable) throw unregisteredTable(parentCollection.slug, parentTableName, \"parent\");\n const parentIdCol = parentTable[parentIdInfo.fieldName as keyof typeof parentTable] as AnyPgColumn;\n\n // The target collection's `beforeQuery`, resolved before either branch\n // builds a WHERE. `via` used to be the branch that carried none of the\n // conditions the other one does, so a scope applied there and not here\n // would be a read-only relation kind that answers with every row.\n const relatedNarrowing = await this.narrowTargetRead(parentCollection, relation, parentId);\n\n // Handle join path relations\n if (relation.kind === \"via\") {\n let query = this.db.select().from(parentTable).$dynamic();\n let currentTable = parentTable;\n\n // Apply each join in the path\n for (const join of relation.joinPath) {\n const joinTable = this.registry.getTable(join.table);\n if (!joinTable) {\n throw new Error(`Join table not found: ${join.table}`);\n }\n\n const step = joinStepCondition(currentTable, joinTable, join);\n if (\"missing\" in step) {\n throw new Error(`Join columns not found: ${step.missing}`);\n }\n\n query = applyDynamicJoin(query, joinTable, step.condition);\n currentTable = joinTable;\n }\n\n // The parent row by every key column — the join starts at the\n // parent's own table, so a condition on the first column alone\n // reads the related rows of every parent that shares it — and the\n // target's own soft delete beside its scope: the other branch below\n // carries both, and a join path is only another way to reach the\n // same rows.\n query = query.where(narrowed(\n rowIdentityCondition(parentTable, parentPks, parentId, parentCollection.slug),\n andSoftDelete(relatedNarrowing, targetCollection, targetTable)\n ));\n\n if (options.limit) {\n query = query.limit(options.limit);\n }\n\n const results = await query;\n const targetTableName = relation.joinPath[relation.joinPath.length - 1].table;\n\n // Process results\n const rows: RelatedRow<M>[] = [];\n for (const row of results as Array<Record<string, unknown>>) {\n const targetRow = (row[targetTableName] as Record<string, unknown>) || row;\n rows.push(await this.toRelatedRow<M>(targetRow, targetCollection, idInfo, { resolveNested: true }));\n }\n\n return rows;\n }\n\n // Handle other relation types.\n //\n // The query builder compares the target's foreign key against a value,\n // and for a link on a natural key that value is not the id in the URL.\n // Resolved first, before anything is built, so a parent that reaches\n // nothing costs one statement rather than two.\n const matchValue = hasForeignKeyOnTarget(relation)\n ? (await this.resolveSourceKeys(parentCollection, relation, [parentId]))\n .keyByParentId.get(String(parentId))\n : parsedParentId;\n\n // A parent whose source key is null reaches nothing: NULL never equals\n // a foreign key. Say so with an empty list rather than an `= NULL`.\n if (matchValue === undefined) return [];\n\n let query = this.db.select().from(targetTable).$dynamic();\n\n // Build additional filter conditions\n const additionalFilters: SQL[] = [];\n\n // Soft delete on the *target*: the rows being loaded belong to the\n // target collection, so it is that collection's flag that decides.\n const relatedSoftDelete = softDeleteCondition(targetCollection, targetTable);\n if (relatedSoftDelete) additionalFilters.push(relatedSoftDelete);\n\n // And the target's `beforeQuery`, for the same reason.\n if (relatedNarrowing) additionalFilters.push(relatedNarrowing);\n\n // Handle search conditions if searchString is provided\n if (options.searchString) {\n const searchConditions = DrizzleConditionBuilder.buildSearchConditions(\n options.searchString,\n targetCollection.properties,\n targetTable,\n targetCollection\n );\n\n if (searchConditions.length === 0) {\n // No searchable fields found, return empty results\n return [];\n }\n\n const searchCombined = DrizzleConditionBuilder.combineConditionsWithOr(searchConditions);\n if (searchCombined) {\n additionalFilters.push(searchCombined);\n }\n }\n\n // Use unified relation query builder\n query = applyDynamicRelationQuery(\n query,\n query,\n relation,\n matchValue,\n targetTable,\n parentTable,\n parentIdCol,\n idField,\n this.registry,\n additionalFilters\n );\n\n if (options.limit) {\n query = query.limit(options.limit);\n }\n\n const results = await query;\n\n // Process results - ensure results is iterable\n if (!results || !Array.isArray(results)) {\n return [];\n }\n\n const rows: RelatedRow<M>[] = [];\n for (const row of results) {\n const targetRow = row[getTableName(targetCollection)] || row;\n rows.push(await this.toRelatedRow<M>(targetRow as Record<string, unknown>, targetCollection, idInfo, { resolveNested: true }));\n }\n\n return rows;\n }\n\n /**\n * Count related rows for a parent row\n */\n async countRelatedEntities<M extends Record<string, unknown>>(\n parentCollectionPath: string,\n parentId: string | number,\n relationKey: string,\n options: { filter?: FilterValues<Extract<keyof M, string>>; databaseId?: string } = {}\n ): Promise<number> {\n const parentCollection = getCollectionByPath(parentCollectionPath, this.registry);\n const resolvedRelations = resolveCollectionRelations(parentCollection);\n const relation = findRelation(resolvedRelations, relationKey);\n if (!relation) {\n const available = Object.keys(resolvedRelations).join(\", \") || \"(none)\";\n // A path segment naming a relation the collection does not declare\n // is a bad URL, not a server fault. It answered 500 INTERNAL_ERROR,\n // which tells the caller to retry or report an outage over a\n // request that will never succeed — and buries a message that\n // already says exactly what is wrong.\n //\n // 404, like an unknown collection: the URL names nothing. The\n // available relations stay in the message because reaching this\n // point means the caller could already read the parent collection,\n // and `?include=` would list the same names.\n throw ApiError.notFound(\n `Relation '${relationKey}' not found in collection '${parentCollectionPath}'. Available relations: [${available}]`,\n \"UNKNOWN_RELATION\"\n );\n }\n\n return this.countRelatedRows(parentCollection, parentId, relation, []);\n }\n\n /**\n * Count the target rows a parent reaches through `relation`, narrowed by\n * `additionalFilters` (conditions on the target table).\n *\n * Shared by the public count and by {@link isRelated}, so \"how many children\n * does this parent have\" and \"is this row one of them\" are answered by the\n * same join — a membership test that reconstructed the join separately would\n * be free to disagree with the listing it is supposed to gate.\n */\n private async countRelatedRows(\n parentCollection: CollectionConfig,\n parentId: string | number,\n relation: ResolvedRelation,\n additionalFilters: SQL[]\n ): Promise<number> {\n const targetCollection = relation.target();\n const targetTable = getTableForCollection(targetCollection, this.registry);\n const targetPks = requirePrimaryKeys(targetCollection, this.registry);\n const targetIdInfo = targetPks[0];\n const targetIdField = targetTable[targetIdInfo.fieldName as keyof typeof targetTable] as AnyPgColumn;\n\n const parentPks = requirePrimaryKeys(parentCollection, this.registry);\n const parentIdInfo = parentPks[0];\n const parsedParentIdObj = parseIdValues(parentId, parentPks);\n const parsedParentId = parsedParentIdObj[parentIdInfo.fieldName];\n const parentTableName = getTableName(parentCollection);\n const parentTable = this.registry.getTable(parentTableName);\n if (!parentTable) throw unregisteredTable(parentCollection.slug, parentTableName, \"parent\");\n const parentIdCol = parentTable[parentIdInfo.fieldName as keyof typeof parentTable] as AnyPgColumn;\n\n // Same translation the listing does, and it has to be: `isRelated`\n // gates writes on this count, so a count built from a different value\n // than the read would authorise rows the read never returned.\n const matchValue = hasForeignKeyOnTarget(relation)\n ? (await this.resolveSourceKeys(parentCollection, relation, [parentId]))\n .keyByParentId.get(String(parentId))\n : parsedParentId;\n\n if (matchValue === undefined) return 0;\n\n // Soft delete, so a relation's count agrees with what loading it\n // returns — `isRelated` gates writes on this number.\n const countSoftDelete = softDeleteCondition(targetCollection, targetTable);\n if (countSoftDelete) additionalFilters = [...additionalFilters, countSoftDelete];\n\n // The target's `beforeQuery`, so the count and the listing describe the\n // same rows. `isRelated` gates reads, updates and deletes at a nested\n // address on this number, so a count that saw rows the listing does not\n // would authorise a write to a row the caller cannot read.\n const countNarrowing = await this.narrowTargetRead(parentCollection, relation, parentId);\n if (countNarrowing) additionalFilters = [...additionalFilters, countNarrowing];\n\n // Start count with distinct to avoid duplicates from junction tables\n let query = this.db.select({ count: sql<number>`count(distinct ${targetIdField})` }).from(targetTable).$dynamic();\n\n // Every `via` is counted through the scope condition a nested listing\n // filters by, so the count and the listing are one condition rather\n // than two that have to agree.\n //\n // The join-path count `buildRelationCountQuery` builds is not that\n // condition. It joins each step on its first column pair and matches\n // the parent on its first key column, so for a parent keyed on several\n // columns it counted the rows every parent sharing that column reaches\n // — `isRelated` would authorise a write through `1:::en_US` to a row\n // only `1:::de_DE` reaches. And it looks a step's columns up by the\n // Drizzle property key, where a step names the SQL column: on a\n // generated schema (`orderId: uuid(\"order_id\")`) every count threw\n // \"Join columns not found\", and since `isRelated` gates every read at a\n // nested address, an order opened from a product's Orders tab failed to\n // load while the tab listed it fine.\n if (relation.kind === \"via\") {\n const scope = DrizzleConditionBuilder.buildRelationScopeCondition(\n relation,\n () => ({\n table: parentTable,\n key: primaryKeyColumns(parentTable, parentPks, parentCollection.slug)\n .map((column, i) => ({ column, value: parsedParentIdObj[parentPks[i].fieldName] }))\n }),\n parentId,\n targetTable,\n targetIdField,\n this.registry\n );\n const result = await query.where(and(scope, ...additionalFilters));\n return Number(result[0]?.count || 0);\n }\n\n // Use unified count query builder from DrizzleConditionBuilder\n query = DrizzleConditionBuilder.buildRelationCountQuery(\n query,\n relation,\n matchValue,\n targetTable,\n parentTable,\n parentIdCol,\n targetIdField,\n this.registry,\n additionalFilters\n );\n\n const result = await query;\n return Number(result[0]?.count || 0);\n }\n\n /**\n * Whether `targetId` is actually reachable from the parent named in `hop`.\n *\n * A nested address like `authors/1/posts/43` used to resolve to the target\n * collection and then match on the primary key alone, so the parent segment\n * decided nothing: the row came back, and was updated or deleted, whoever it\n * belonged to. Reads, updates and deletes now all gate on this.\n */\n async isRelated(hop: NestedPathHop, targetId: string | number): Promise<boolean> {\n const targetTable = getTableForCollection(hop.targetCollection, this.registry);\n const targetPks = requirePrimaryKeys(hop.targetCollection, this.registry);\n const parsedTargetId = parseIdValues(targetId, targetPks);\n\n const identity: SQL[] = targetPks.map(pk => {\n const column = targetTable[pk.fieldName as keyof typeof targetTable] as AnyPgColumn;\n if (!column) {\n throw new Error(`ID field '${pk.fieldName}' not found in table for collection '${hop.targetCollection.slug}'`);\n }\n return eq(column, parsedTargetId[pk.fieldName]);\n });\n\n return (await this.countRelatedRows(hop.parentCollection, hop.parentId, hop.relation, identity)) > 0;\n }\n\n /**\n * Batch fetch related rows for multiple parent rows to avoid N+1 queries.\n *\n * `narrow` is an extra condition on the *target* table — what a per-include\n * `where` compiles to. It is pushed into the query rather than applied to\n * the rows that come back, because filtering afterwards reads every related\n * row of every parent in order to discard most of them.\n */\n async batchFetchRelatedEntities(\n parentCollectionPath: string,\n parentIds: (string | number)[],\n _relationKey: string,\n relation: ResolvedRelation,\n narrow?: SQL\n ): Promise<Map<string, RelatedRow<Record<string, unknown>>>> {\n if (parentIds.length === 0) return new Map();\n\n const parentCollection = getCollectionByPath(parentCollectionPath, this.registry);\n const targetCollection = relation.target();\n const targetTable = getTableForCollection(targetCollection, this.registry);\n const targetPks = requirePrimaryKeys(targetCollection, this.registry);\n const targetIdInfo = targetPks[0];\n const targetIdField = targetTable[targetIdInfo.fieldName as keyof typeof targetTable] as AnyPgColumn;\n\n const parentPks = requirePrimaryKeys(parentCollection, this.registry);\n const parentIdInfo = parentPks[0];\n const parentTableName = getTableName(parentCollection);\n const parentTable = this.registry.getTable(parentTableName);\n if (!parentTable) throw unregisteredTable(parentCollection.slug, parentTableName, \"parent\");\n const parentIdCol = parentTable[parentIdInfo.fieldName as keyof typeof parentTable] as AnyPgColumn;\n\n // Parse all parent IDs once\n const parsedParentIds = parentIds.map(id => parseIdValues(id, parentPks)[parentIdInfo.fieldName]);\n\n // Soft delete, once, for every branch below.\n //\n // These loaders are the only path an `include` takes now, and they\n // reach the target four different ways — a `via` join, a junction, a\n // foreign key, the dynamic relation builder. A `deleted_at IS NULL`\n // written into one of them is a deleted comment that stays hidden under\n // its post on one relation kind and reappears on another, which is\n // exactly the rot `soft-delete.ts` exists to prevent. Folded into\n // `narrow` here, it composes with the include's own `where` and every\n // branch below applies both or neither.\n // The target collection's own `beforeQuery`, folded in beside the\n // soft-delete stamp for the same reason and at the same point.\n const narrowTarget = andSoftDelete(\n narrowBoth(narrow, await this.narrowTargetRead(parentCollection, relation)),\n targetCollection, targetTable\n );\n\n // Handle join path relations with batching\n if (relation.kind === \"via\") {\n let query = this.db.select().from(parentTable).$dynamic();\n let currentTable = parentTable;\n\n // Apply each join in the path\n for (const join of relation.joinPath) {\n const joinTable = this.registry.getTable(join.table);\n if (!joinTable) {\n throw new Error(`Join table not found: ${join.table}`);\n }\n\n const step = joinStepCondition(currentTable, joinTable, join);\n if (\"missing\" in step) {\n throw new Error(`Join columns not found: ${step.missing}`);\n }\n\n query = applyDynamicJoin(query, joinTable, step.condition);\n currentTable = joinTable;\n }\n\n // Match every parent at once, each by its whole key.\n query = query.where(narrowed(this.parentKeyCondition(parentTable, parentPks, parentIds), narrowTarget));\n\n const results = await query;\n const targetTableName = relation.joinPath[relation.joinPath.length - 1].table;\n const resultMap = new Map<string, RelatedRow<Record<string, unknown>>>();\n\n // Group by the parent's address — the same token the caller looks\n // results up by, derived the same way on both sides.\n for (const row of results as Array<Record<string, unknown>>) {\n const parentRow = (row[getTableName(parentCollection)] || row) as Record<string, unknown>;\n const targetRow = (row[targetTableName] || row) as Record<string, unknown>;\n\n resultMap.set(\n buildCompositeId(parentRow, parentPks),\n await this.toRelatedRow(targetRow, targetCollection, targetPks)\n );\n }\n\n return resultMap;\n }\n\n // Handle owning relations with proper FK-based batching.\n // For owning relations, parent rows hold the FK (e.g. posts.author_id).\n // We need to:\n // 1. Fetch FK values from the parent table in a single query\n // 2. Query the target table with unique FK values\n // 3. Map results back to parent rows via their FK values\n if (relation.kind === \"belongsTo\") {\n this.assertSingleKeyAddressable(parentCollection, parentPks, relation.localKey);\n // `localKey` is the column; the generated table is keyed by the wire\n // name, so `author_id` finds nothing where the key is `authorId`.\n const localKeyField = fieldKeyForColumn(parentCollection, relation.localKey);\n const localKeyCol = parentTable[localKeyField as keyof typeof parentTable] as AnyPgColumn;\n if (!localKeyCol) {\n throw new Error(`Local key column '${relation.localKey}' not found in parent table`);\n }\n\n // Step 1: Fetch all FK values from parent table in ONE query\n const fkRows = await this.db\n .select({\n parentId: parentIdCol,\n fkValue: localKeyCol\n })\n .from(parentTable)\n .where(inArray(parentIdCol, parsedParentIds));\n\n // Build parentId → fkValue mapping and collect unique FK values\n const parentToFk = new Map<string, string | number>();\n const uniqueFkValues: (string | number)[] = [];\n const seenFks = new Set<string>();\n\n for (const row of fkRows as Array<{ parentId: string | number; fkValue: string | number | null }>) {\n if (row.fkValue == null) continue;\n parentToFk.set(String(row.parentId), row.fkValue);\n const fkStr = String(row.fkValue);\n if (!seenFks.has(fkStr)) {\n seenFks.add(fkStr);\n uniqueFkValues.push(row.fkValue);\n }\n }\n\n if (uniqueFkValues.length === 0) return new Map();\n\n // Step 2: Fetch all target rows in ONE query\n const targetResults = await this.db\n .select()\n .from(targetTable)\n .where(narrowed(inArray(targetIdField, uniqueFkValues), narrowTarget));\n\n // Index target rows by their ID\n const targetById = new Map<string, Record<string, unknown>>();\n for (const row of targetResults as Array<Record<string, unknown>>) {\n const tid = String(row[targetIdInfo.fieldName]);\n targetById.set(tid, row);\n }\n\n // Step 3: Map back to parent rows\n const resultMap = new Map<string, RelatedRow<Record<string, unknown>>>();\n for (const [parentIdStr, fkValue] of parentToFk) {\n const targetRow = targetById.get(String(fkValue));\n if (targetRow) {\n resultMap.set(parentIdStr, await this.toRelatedRow(targetRow, targetCollection, targetPks));\n }\n }\n\n return resultMap;\n }\n\n // Handle inverse relation types with batching. The parent is named by a\n // single FK column on the target, so a composite-keyed parent has no\n // correct reading here either.\n this.assertSingleKeyAddressable(\n parentCollection,\n parentPks,\n hasForeignKeyOnTarget(relation) ? relation.foreignKeyOnTarget : relation.relationName,\n relation\n );\n\n // One lookup for the whole batch, both directions: the WHERE is built\n // from the source-key values, and each row that comes back carries one\n // of those values rather than a parent id.\n const { keyByParentId, parentIdByKey } = hasForeignKeyOnTarget(relation)\n ? await this.resolveSourceKeys(parentCollection, relation, parentIds)\n : { keyByParentId: new Map<string, string | number>(), parentIdByKey: new Map<string, string | number>() };\n\n const matchValues = hasForeignKeyOnTarget(relation)\n ? [...keyByParentId.values()]\n : parsedParentIds;\n if (matchValues.length === 0) return new Map();\n\n let query = this.db.select().from(targetTable).$dynamic();\n\n // Build the relation query with ALL parent IDs\n query = applyDynamicRelationQuery(\n query,\n query,\n relation,\n matchValues, // Pass array instead of single ID\n targetTable,\n parentTable,\n parentIdCol,\n targetIdField,\n this.registry,\n // Where a per-include `where` lands on this relation kind: the\n // builder already AND-s `additionalFilters` into the relation's own\n // conditions, so there is nothing to invent here.\n narrowTarget ? [narrowTarget] : []\n );\n\n const results = await query;\n const resultMap = new Map<string, RelatedRow<Record<string, unknown>>>();\n\n // Map results back to parent rows\n for (const row of results as Array<Record<string, unknown>>) {\n const targetRow = (row[getTableName(targetCollection)] || row) as Record<string, unknown>;\n\n // The parent's key is on the target row, in the relation's own\n // column. There used to be a second branch here that guessed the\n // column by appending `_id` to `inverseRelationName`, reached only\n // when the foreign key had not been resolved — which cannot happen\n // now that resolution fills it in.\n if (!hasForeignKeyOnTarget(relation)) continue;\n\n // Keyed by string throughout: Drizzle returns a numeric column as a\n // number or a string depending on the column type and the driver.\n const foreignKeyValue = targetRow[\n fieldKeyForColumn(targetCollection, relation.foreignKeyOnTarget)\n ] as string | number | undefined;\n if (foreignKeyValue === undefined || foreignKeyValue === null) continue;\n\n const parentId = parentIdByKey.get(String(foreignKeyValue));\n if (parentId !== undefined) {\n resultMap.set(String(parentId), await this.toRelatedRow(targetRow, targetCollection, targetPks));\n }\n }\n\n return resultMap;\n }\n\n /**\n * Batch fetch many-cardinality related rows for multiple parent rows.\n * Returns a Map<parentId, RelatedRow[]> instead of Map<parentId, RelatedRow>.\n * Uses a single SQL query with IN clause to avoid N+1.\n */\n async batchFetchRelatedEntitiesMany(\n parentCollectionPath: string,\n parentIds: (string | number)[],\n _relationKey: string,\n relation: ResolvedRelation,\n /** An extra condition on the target — see {@link batchFetchRelatedEntities}. */\n narrow?: SQL\n ): Promise<Map<string, RelatedRow<Record<string, unknown>>[]>> {\n if (parentIds.length === 0) return new Map();\n\n const parentCollection = getCollectionByPath(parentCollectionPath, this.registry);\n const targetCollection = relation.target();\n const targetTable = getTableForCollection(targetCollection, this.registry);\n const targetPks = requirePrimaryKeys(targetCollection, this.registry);\n const targetIdInfo = targetPks[0];\n const targetIdField = targetTable[targetIdInfo.fieldName as keyof typeof targetTable] as AnyPgColumn;\n\n const parentPks = requirePrimaryKeys(parentCollection, this.registry);\n const parentIdInfo = parentPks[0];\n const parentTableName = getTableName(parentCollection);\n const parentTable = this.registry.getTable(parentTableName);\n if (!parentTable) throw unregisteredTable(parentCollection.slug, parentTableName, \"parent\");\n const parentIdCol = parentTable[parentIdInfo.fieldName as keyof typeof parentTable] as AnyPgColumn;\n\n const parsedParentIds = parentIds.map(id => parseIdValues(id, parentPks)[parentIdInfo.fieldName]);\n\n // Soft delete, once, for every branch below.\n //\n // These loaders are the only path an `include` takes now, and they\n // reach the target four different ways — a `via` join, a junction, a\n // foreign key, the dynamic relation builder. A `deleted_at IS NULL`\n // written into one of them is a deleted comment that stays hidden under\n // its post on one relation kind and reappears on another, which is\n // exactly the rot `soft-delete.ts` exists to prevent. Folded into\n // `narrow` here, it composes with the include's own `where` and every\n // branch below applies both or neither.\n // The target collection's own `beforeQuery`, folded in beside the\n // soft-delete stamp for the same reason and at the same point.\n const narrowTarget = andSoftDelete(\n narrowBoth(narrow, await this.narrowTargetRead(parentCollection, relation)),\n targetCollection, targetTable\n );\n\n // Handle join path relations (many-to-many through junction tables)\n if (relation.kind === \"via\") {\n let query = this.db.select().from(parentTable).$dynamic();\n let currentTable = parentTable;\n\n for (const join of relation.joinPath) {\n const joinTable = this.registry.getTable(join.table);\n if (!joinTable) throw new Error(`Join table not found: ${join.table}`);\n\n const step = joinStepCondition(currentTable, joinTable, join);\n if (\"missing\" in step) throw new Error(`Join columns not found: ${step.missing}`);\n\n query = applyDynamicJoin(query, joinTable, step.condition);\n currentTable = joinTable;\n }\n\n query = query.where(narrowed(this.parentKeyCondition(parentTable, parentPks, parentIds), narrowTarget));\n\n const results = await query;\n const targetTableName = relation.joinPath[relation.joinPath.length - 1].table;\n const resultMap = new Map<string, RelatedRow<Record<string, unknown>>[]>();\n\n for (const row of results as Array<Record<string, unknown>>) {\n const parentRow = (row[getTableName(parentCollection)] || row) as Record<string, unknown>;\n const targetRow = (row[targetTableName] || row) as Record<string, unknown>;\n const parentId = buildCompositeId(parentRow, parentPks);\n const arr = resultMap.get(parentId) || [];\n arr.push(await this.toRelatedRow(targetRow, targetCollection, targetPks));\n resultMap.set(parentId, arr);\n }\n\n return resultMap;\n }\n\n // Handle many-to-many owning relations with junction table (relation.through)\n // This is the standard path for posts→tags style relations where\n // sanitizeRelation populated the `through` config.\n if (relation.kind === \"manyToMany\") {\n // The junction names its parent with one column, so the same\n // single-key limit applies as for a direct foreign key.\n this.assertSingleKeyAddressable(parentCollection, parentPks, `${relation.through.table}.${relation.through.sourceColumn}`);\n\n // An empty map here is a lie a caller cannot see through: `posts/1/tags`\n // answering `[]` is exactly what a post with no tags looks like. That\n // is the failure `assertRelationsResolve` was written to make\n // impossible at boot; this says the same thing if one gets past it.\n const { table: junctionTable, parentColumn: sourceJunctionCol, targetColumn: targetJunctionCol } =\n bindThroughJunction(\n this.registry,\n relation.through,\n `${parentCollection.slug}.${relation.relationName}`\n );\n\n // SELECT target.*, junction.sourceColumn FROM junction\n // INNER JOIN target ON junction.targetColumn = target.id\n // WHERE junction.sourceColumn IN (parentIds)\n // The target is joined in, so its soft-delete stamp is a condition\n // this query can carry.\n const query = this.db\n .select()\n .from(junctionTable)\n .innerJoin(targetTable, eq(targetJunctionCol, targetIdField))\n .where(narrowed(inArray(sourceJunctionCol, parsedParentIds), narrowTarget));\n\n const results = await query;\n const resultMap = new Map<string, RelatedRow<Record<string, unknown>>[]>();\n const targetTableName = getTableName(targetCollection);\n // The junction's own columns, when it declares any. Built once per\n // batch rather than per row: it is derived from the relation, and\n // the relation does not change between rows.\n const payloadKeys = Object.keys(relation.through.properties);\n const junctionConfig = payloadKeys.length > 0\n ? getJunctionConfigForRelation(relation.through)\n : undefined;\n\n for (const row of results as Array<Record<string, unknown>>) {\n // The junction table data is namespaced under its table name\n const junctionData = (row[relation.through.table] || row) as Record<string, unknown>;\n const targetData = (row[targetTableName] || row) as Record<string, unknown>;\n\n const parentId = String(junctionData[relation.through.sourceColumn]);\n const arr = resultMap.get(parentId) || [];\n const related = await this.toRelatedRow(targetData, targetCollection, targetPks);\n if (junctionConfig) {\n related.pivot = await this.readPivot(junctionData, junctionConfig, payloadKeys);\n }\n arr.push(related);\n resultMap.set(parentId, arr);\n }\n\n return resultMap;\n }\n\n // Handle FK-based relations (one-to-many inverse). One column on the\n // target names the parent, so a composite-keyed parent cannot be named.\n this.assertSingleKeyAddressable(\n parentCollection,\n parentPks,\n hasForeignKeyOnTarget(relation) ? relation.foreignKeyOnTarget : relation.relationName,\n relation\n );\n\n const { keyByParentId, parentIdByKey } = hasForeignKeyOnTarget(relation)\n ? await this.resolveSourceKeys(parentCollection, relation, parentIds)\n : { keyByParentId: new Map<string, string | number>(), parentIdByKey: new Map<string, string | number>() };\n\n const matchValues = hasForeignKeyOnTarget(relation)\n ? [...keyByParentId.values()]\n : parsedParentIds;\n if (matchValues.length === 0) return new Map();\n\n let query = this.db.select().from(targetTable).$dynamic();\n\n query = applyDynamicRelationQuery(\n query,\n query,\n relation,\n matchValues,\n targetTable,\n parentTable,\n parentIdCol,\n targetIdField,\n this.registry,\n narrowTarget ? [narrowTarget] : []\n );\n\n const results = await query;\n const resultMap = new Map<string, RelatedRow<Record<string, unknown>>[]>();\n\n for (const row of results as Array<Record<string, unknown>>) {\n const targetRow = (row[getTableName(targetCollection)] || row) as Record<string, unknown>;\n\n // Junction-backed relations returned earlier in this method, so\n // what reaches here names the parent with a column on the target.\n if (!hasForeignKeyOnTarget(relation)) continue;\n\n const foreignKeyValue = targetRow[\n fieldKeyForColumn(targetCollection, relation.foreignKeyOnTarget)\n ] as string | number | undefined;\n if (foreignKeyValue === undefined || foreignKeyValue === null) continue;\n\n const parentId = parentIdByKey.get(String(foreignKeyValue));\n if (parentId !== undefined) {\n const key = String(parentId);\n const arr = resultMap.get(key) || [];\n arr.push(await this.toRelatedRow(targetRow, targetCollection, targetPks));\n resultMap.set(key, arr);\n }\n }\n\n return resultMap;\n }\n}\n","import { eq } from \"drizzle-orm\";\nimport { AnyPgColumn } from \"drizzle-orm/pg-core\";\nimport { NodePgDatabase } from \"drizzle-orm/node-postgres\";\nimport { CollectionConfig, Properties, Property, ResolvedRelation, RelationProperty, Vector, BinaryProperty, hasForeignKeyOnTarget, type ResolvedBelongsTo, type ResolvedForeignKeyOnTarget, type ResolvedVia } from \"@rebasepro/types\";\nimport { getTableName, resolveCollectionRelations, findRelation, fieldKeyForColumn, createRelationRef, DEFAULT_ONE_OF_TYPE, DEFAULT_ONE_OF_VALUE } from \"@rebasepro/common\";\nimport { isPrototypePollutingKey } from \"@rebasepro/utils\";\nimport { PostgresCollectionRegistry } from \"./collections/PostgresCollectionRegistry\";\nimport { getPrimaryKeys, buildCompositeId, rowIdentityCondition } from \"./services/collection-helpers\";\nimport { applyDynamicJoin, joinStepCondition } from \"./services/RelationService\";\nimport { ApiError, logger } from \"@rebasepro/server\";\n\n/**\n * Data transformation utilities for converting between frontend and database formats.\n */\n\n/**\n * Typed result from `serializeDataToServer`.\n * Replaces the hidden `__inverseRelationUpdates` / `__joinPathRelationUpdates`\n * dunder-property mutation pattern with explicit, typed state management.\n */\nexport interface SerializedEntityData {\n /** Scalar column values ready for INSERT/UPDATE. */\n scalarData: Record<string, unknown>;\n /**\n * Inverse relation updates that must be applied to target tables.\n *\n * No address here: the row being written does not know its own. These are\n * applied by `PersistService`, which addresses them with the id it holds —\n * the one it was given for an update, or the one the INSERT returned — and\n * that is the authority. This item used to carry a `currentId` derived from\n * the *input* values, which nothing ever read.\n */\n inverseRelationUpdates: Array<{\n relationKey: string;\n relation: ResolvedRelation;\n newValue: unknown;\n }>;\n /** JoinPath relation updates that require multi-hop writes. */\n joinPathRelationUpdates: Array<{\n relationKey: string;\n relation: ResolvedVia;\n newTargetId: string | number | null;\n }>;\n}\n/**\n * Helper function to sanitize and convert dates to ISO strings\n */\nexport function sanitizeAndConvertDates(obj: unknown): unknown {\n if (obj === null || obj === undefined) {\n return null;\n }\n\n if (typeof obj === \"number\" && isNaN(obj)) {\n return null;\n }\n\n if (typeof obj === \"string\" && obj.toLowerCase() === \"nan\") {\n return null;\n }\n\n if (Array.isArray(obj)) {\n return obj.map(v => sanitizeAndConvertDates(v));\n }\n\n if (obj instanceof Date) {\n return obj.toISOString();\n }\n\n if (typeof obj === \"object\") {\n const newObj: Record<string, unknown> = {};\n for (const key in obj) {\n if (!Object.prototype.hasOwnProperty.call(obj, key)) continue;\n // `JSON.parse` creates `__proto__` as an *own* property, so it gets\n // past the check above — and `newObj[key] = …` then invokes the\n // prototype setter instead of creating a property. The row's\n // prototype becomes whatever the request body supplied, so\n // `row.isAdmin` answers `true` while `Object.keys(row)` shows\n // nothing of the sort. No column can be reached by these names.\n if (isPrototypePollutingKey(key)) continue;\n newObj[key] = sanitizeAndConvertDates((obj as Record<string, unknown>)[key]);\n }\n return newObj;\n }\n\n if (typeof obj === \"string\") {\n const isoDateRegex = /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?Z$/;\n const jsDateRegex = /^\\w{3} \\w{3} \\d{2} \\d{4} \\d{2}:\\d{2}:\\d{2} GMT[+-]\\d{4} \\(.+\\)$/;\n if (isoDateRegex.test(obj) || jsDateRegex.test(obj)) {\n const date = new Date(obj);\n if (!isNaN(date.getTime())) {\n return date.toISOString();\n }\n }\n }\n\n return obj;\n}\n\n/**\n * Transform relations for database storage (relation objects to IDs)\n */\nexport function serializeDataToServer<M extends Record<string, unknown>>(\n row: M,\n properties: Properties,\n collection?: CollectionConfig,\n registry?: PostgresCollectionRegistry\n): SerializedEntityData {\n if (!row || !properties) return { scalarData: row ?? {},\ninverseRelationUpdates: [],\njoinPathRelationUpdates: [] };\n\n const result: Record<string, unknown> = {};\n\n // Get normalized relations if collection is provided\n const resolvedRelations = collection ? resolveCollectionRelations(collection) : {};\n\n // Track inverse relations that need to be handled separately\n const inverseRelationUpdates: Array<{\n relationKey: string;\n relation: ResolvedRelation;\n newValue: unknown;\n }> = [];\n const joinPathRelationUpdates: Array<{\n relationKey: string;\n relation: ResolvedVia;\n newTargetId: string | number | null;\n }> = [];\n\n // Pre-calculate all local keys used as foreign keys.\n //\n // Keyed by the *wire* name: the payload arrives from a client, which knows\n // the field as `authorId`, never as the `author_id` column behind it.\n const foreignKeys = new Set<string>();\n Object.values(resolvedRelations).forEach(relation => {\n if (relation.kind === \"belongsTo\") foreignKeys.add(fieldKeyForColumn(collection, relation.localKey));\n });\n\n for (const [key, value] of Object.entries(row)) {\n // Same reasoning as `sanitizeAndConvertDates`: these keys come from the\n // request body, and no column answers to them.\n if (isPrototypePollutingKey(key)) continue;\n\n // `{ subtitle: undefined }` means \"I have no value for this\", not \"set\n // this column to NULL\" — it is what spreading an optional field\n // produces, and what Drizzle itself skips. The key used to survive with\n // `undefined` and `sanitizeAndConvertDates` then turned it into `null`,\n // so `update(id, { title, subtitle: payload.subtitle })` wiped\n // `subtitle` whenever the payload happened not to carry one. Unreachable\n // over HTTP (JSON has no `undefined`); the in-process data API passes\n // the caller's object straight through, so it is reachable there.\n if (value === undefined) continue;\n\n const property = properties[key as keyof M] as Property;\n\n // Coerce empty strings to null for any field that acts as a foreign key\n const effectiveValue = (foreignKeys.has(key) && value === \"\") ? null : value;\n\n if (!property) {\n result[key] = effectiveValue;\n continue;\n }\n\n\n // Handle relation properties specially\n if (property.type === \"relation\" && collection) {\n const relation = findRelation(resolvedRelations, key);\n if (relation) {\n if (relation.kind === \"belongsTo\") {\n // Owning relation: Map relation object to FK column on current table\n const serializedValue = serializePropertyToServer(effectiveValue, property, key);\n if (serializedValue !== undefined) {\n // The Drizzle key, not the column: Drizzle maps it back\n // to `author_id` when it builds the statement.\n result[fieldKeyForColumn(collection, relation.localKey)] = serializedValue;\n }\n // Don't add the original relation property to the result\n continue;\n } else if (hasForeignKeyOnTarget(relation)) {\n // Inverse relation: Need to update the target table's FK\n const serializedValue = serializePropertyToServer(effectiveValue, property, key);\n inverseRelationUpdates.push({\n relationKey: key,\n relation,\n newValue: serializedValue\n });\n // Don't add the original relation property to the result\n continue;\n } else if (relation.kind === \"via\") {\n // A join chain is written through its own machinery, one\n // row at a time for a to-one and as a set for a to-many.\n // There used to be two arms here — \"owning\" and \"inverse\"\n // joinPath — but a join chain has no owning side, so they\n // only ever differed by which list they pushed to.\n const serializedValue = serializePropertyToServer(effectiveValue, property, key);\n if (relation.cardinality === \"one\") {\n // Ordering matters: PersistService applies these BEFORE\n // the main UPDATE, so the mapping reads the pre-update\n // foreign key rather than a value the same save changed.\n joinPathRelationUpdates.push({\n relationKey: key,\n relation,\n newTargetId: serializedValue as string | number | null\n });\n } else {\n inverseRelationUpdates.push({\n relationKey: key,\n relation,\n newValue: serializedValue\n });\n }\n continue;\n }\n }\n }\n\n result[key] = serializePropertyToServer(effectiveValue, property, key);\n }\n\n return {\n scalarData: result,\n inverseRelationUpdates,\n joinPathRelationUpdates\n };\n}\n\n/**\n * How to name a rejected value in an error, without quoting it back.\n *\n * The value may be anything the caller sent, including a secret in the wrong\n * field, so the message describes its shape rather than echoing it — an echoed\n * value ends up in logs and in error-reporting services.\n */\nfunction describeValue(value: unknown): string {\n if (value === null) return \"null\";\n if (Array.isArray(value)) return \"an array\";\n if (value instanceof Date) return \"a date\";\n const type = typeof value;\n return type === \"object\" ? \"an object\" : `a ${type}`;\n}\n\n/**\n * Serialize a single property value for database storage.\n *\n * `propertyKey` is only ever used to phrase errors and warnings. Without it the\n * one trace a bad value left was `Expected array value for array property, got\n * string` — no collection, no property, no value, which in the log of a\n * thousand-row import names nothing at all.\n */\n/** `HH:MM[:SS[.fff]]`, the text Postgres reads and writes for a `time` column. */\nconst TIME_OF_DAY = /^(\\d{2}):(\\d{2})(?::(\\d{2})(\\.\\d+)?)?$/;\n\n/**\n * A `time` column's text as the date it is served as: that time of day, UTC,\n * on 1970-01-01. `undefined` for text that is not a time of day.\n */\nfunction timeOfDayToDate(text: string): Date | undefined {\n const match = TIME_OF_DAY.exec(text.trim());\n if (!match) return undefined;\n const [, hh, mm, ss = \"00\", fraction = \"\"] = match;\n const date = new Date(`1970-01-01T${hh}:${mm}:${ss}${fraction ? fraction.slice(0, 4) : \"\"}Z`);\n return isNaN(date.getTime()) ? undefined : date;\n}\n\n/**\n * What a `time` column is written: the UTC time of day of a date, so neither\n * the server's zone nor the caller's moves it. Text already shaped as a time of\n * day is kept as written.\n */\nfunction dateToTimeOfDay(value: unknown): unknown {\n if (typeof value === \"string\" && TIME_OF_DAY.test(value.trim())) return value.trim();\n const date = value instanceof Date ? value : typeof value === \"string\" ? new Date(value) : undefined;\n if (!date || isNaN(date.getTime())) return value;\n return date.toISOString().slice(11, 23);\n}\n\nexport function serializePropertyToServer(value: unknown, property: Property, propertyKey?: string): unknown {\n if (value === null || value === undefined) {\n return value;\n }\n\n const fieldLabel = propertyKey ? `'${propertyKey}'` : `a '${property.type}' field`;\n\n const propertyType = property.type;\n\n switch (propertyType) {\n case \"date\":\n return property.columnType === \"time\" ? dateToTimeOfDay(value) : value;\n\n case \"relation\":\n if (Array.isArray(value)) {\n return value.map(v => serializePropertyToServer(v, property, propertyKey));\n } else if (typeof value === \"object\" && value !== null && \"id\" in value) {\n return (value as Record<string, unknown>).id;\n }\n if (value === \"\") return null;\n return value;\n\n case \"array\":\n if (Array.isArray(value)) {\n if (property.of) {\n return value.map(item => serializePropertyToServer(item, property.of as Property, propertyKey));\n } else if (property.oneOf) {\n const typeField = property.oneOf.typeField ?? DEFAULT_ONE_OF_TYPE;\n const valueField = property.oneOf.valueField ?? DEFAULT_ONE_OF_VALUE;\n return value.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]: serializePropertyToServer(rec[valueField], childProperty, propertyKey)\n };\n });\n }\n return value;\n }\n // A non-array value used to become `[]` here — the caller's value\n // destroyed, answered 200/201, and the row reading back as an empty\n // list on every later fetch. `POST /posts {\"tags\":\"news\"}` from a\n // client that sent a single tag as a scalar, or a CSV import that\n // did not split a column, was unrecoverable data loss on a `text[]`\n // column. The stated reason for coercing — \"avoid .map() crashes\n // downstream\" — is better served by refusing the value at the\n // boundary, which is what a 400 does.\n //\n // The read-side twin (`parsePropertyFromServer`) still coerces, and\n // should: a row already in the database is not this caller's fault.\n throw ApiError.badRequest(\n `${fieldLabel} expects an array, but received ${describeValue(value)}.`,\n \"VALIDATION_INVALID_VALUE\"\n );\n\n case \"geopoint\": {\n // Stored as `jsonb`, in the `{ latitude, longitude }` shape the\n // OpenAPI schema and the generated TS type both promise. Nothing\n // used to handle `geopoint` on either side, which is why the type\n // was documented, code-generated, admin-editable — and dropped.\n if (typeof value !== \"object\" || Array.isArray(value)) {\n throw ApiError.badRequest(\n `${fieldLabel} expects a geopoint object with \\`latitude\\` and \\`longitude\\`, ` +\n `but received ${describeValue(value)}.`,\n \"VALIDATION_INVALID_VALUE\"\n );\n }\n const point = value as Record<string, unknown>;\n if (typeof point.latitude !== \"number\" || typeof point.longitude !== \"number\") {\n throw ApiError.badRequest(\n `${fieldLabel} expects a geopoint object with numeric \\`latitude\\` and \\`longitude\\`.`,\n \"VALIDATION_INVALID_VALUE\"\n );\n }\n return { latitude: point.latitude,\nlongitude: point.longitude };\n }\n\n\n case \"map\":\n if (typeof value === \"object\" && property.properties) {\n const result: Record<string, unknown> = {};\n for (const [subKey, subValue] of Object.entries(value)) {\n const subProperty = (property.properties as Properties)[subKey];\n if (subProperty) {\n result[subKey] = serializePropertyToServer(subValue, subProperty, propertyKey ? `${propertyKey}.${subKey}` : subKey);\n } else {\n result[subKey] = subValue;\n }\n }\n return result;\n }\n return value;\n case \"vector\": {\n if (value instanceof Vector) {\n return value.value;\n }\n if (value && typeof value === \"object\" && \"value\" in value && Array.isArray((value as { value: unknown }).value)) {\n return (value as { value: unknown[] }).value.map(Number);\n }\n if (Array.isArray(value)) {\n return value.map(Number);\n }\n return value;\n }\n\n case \"binary\": {\n const decoded = tryDecodeBase64DataUrl(value);\n if (decoded) return decoded;\n if (Buffer.isBuffer(value)) return value;\n return value;\n }\n\n case \"string\":\n default: {\n const decoded = tryDecodeBase64DataUrl(value);\n if (decoded) return decoded;\n return value;\n }\n }\n}\n\n/**\n * Transform IDs back to relation objects for frontend\n */\nexport async function parseDataFromServer<M extends Record<string, unknown>>(\n data: M,\n collection: CollectionConfig,\n db?: NodePgDatabase<Record<string, unknown>>,\n registry?: PostgresCollectionRegistry\n): Promise<M> {\n const properties = collection.properties;\n if (!data || !properties) return data;\n\n // Get the normalized relations once\n const resolvedRelations = resolveCollectionRelations(collection);\n\n // Shared scalar + relation value normalization\n const result = normalizeScalarValues(data, properties, collection, resolvedRelations, { skipRelations: false });\n\n // Add relation properties that should be populated from FK values or inverse queries\n for (const [propKey, property] of Object.entries(properties)) {\n if (property.type === \"relation\" && !(propKey in result)) {\n // Find the normalized relation for this property\n const relation = findRelation(resolvedRelations, propKey);\n if (relation) {\n const localField = relation.kind === \"belongsTo\"\n ? fieldKeyForColumn(collection, relation.localKey)\n : \"\";\n if (relation.kind === \"belongsTo\" && localField in data) {\n // Owning relation: FK is in current table, under its wire name\n const fkValue = data[localField as keyof M];\n if (fkValue !== null && fkValue !== undefined) {\n try {\n const targetCollection = relation.target();\n result[propKey] = createRelationRef(fkValue.toString(), targetCollection.slug);\n } catch (e) {\n logger.warn(`Could not resolve target collection for relation property: ${propKey}`, { error: e });\n }\n }\n } else if (hasForeignKeyOnTarget(relation) && db && registry) {\n // Inverse relation: FK is in target table, need to query for it\n try {\n const targetCollection = relation.target();\n const targetTable = registry.getTable(getTableName(targetCollection));\n const pks = getPrimaryKeys(collection, registry!);\n // What the target's foreign key holds. Ordinarily this\n // row's id; for a link on a natural key, the value of\n // the column it points at — which is on the row already,\n // so this costs nothing extra.\n const currentId = relation.sourceKey\n ? (data as Record<string, unknown>)[relation.sourceKey] as string | number | undefined\n : buildCompositeId(data, pks);\n\n if (targetTable && currentId !== undefined && currentId !== null && currentId !== \"\") {\n const fkFieldKey = fieldKeyForColumn(targetCollection, relation.foreignKeyOnTarget);\n const foreignKeyColumn = targetTable[fkFieldKey as keyof typeof targetTable] as AnyPgColumn;\n if (foreignKeyColumn) {\n // Query the target table to find row that references this row\n const relatedRows = await db\n .select()\n .from(targetTable)\n .where(eq(foreignKeyColumn, currentId))\n .limit(relation.cardinality === \"one\" ? 1 : 100); // Limit for one-to-one vs one-to-many\n\n if (relatedRows.length > 0) {\n if (relation.cardinality === \"one\") {\n // One-to-one: return single relation object\n const targetPks = getPrimaryKeys(targetCollection, registry!);\n const relatedRow = relatedRows[0] as Record<string, unknown>;\n result[propKey] = createRelationRef(buildCompositeId(relatedRow, targetPks), targetCollection.slug);\n } else {\n // One-to-many: return array of relation objects\n const targetPks = getPrimaryKeys(targetCollection, registry!);\n result[propKey] = relatedRows.map((row: Record<string, unknown>) =>\n createRelationRef(buildCompositeId(row, targetPks), targetCollection.slug)\n );\n }\n }\n }\n }\n } catch (e) {\n logger.warn(`Could not resolve inverse relation property: ${propKey}`, { error: e });\n }\n } else if (relation.kind === \"via\" && db && registry) {\n // Join path relation: Multi-hop relation using joins\n try {\n const targetCollection = relation.target();\n const pks = getPrimaryKeys(collection, registry!);\n const currentId = buildCompositeId(data, pks);\n\n if (currentId) {\n // Build the join query following the join path\n const sourceTable = registry.getTable(getTableName(collection));\n if (!sourceTable) {\n logger.warn(`Source table not found for collection: ${collection.slug}`);\n continue;\n }\n\n let query = db.select().from(sourceTable);\n let currentTable = sourceTable;\n\n // Apply each join in the path\n for (const join of relation.joinPath) {\n const joinTable = registry.getTable(join.table);\n if (!joinTable) {\n logger.warn(`Join table not found: ${join.table}`);\n break;\n }\n\n // Every column pair of the step, for a step that joins on a tuple.\n const step = joinStepCondition(currentTable, joinTable, join);\n if (\"missing\" in step) {\n logger.warn(`Join columns not found: ${step.missing}`);\n break;\n }\n\n query = applyDynamicJoin(query, joinTable, step.condition);\n currentTable = joinTable;\n }\n\n // The current row, by every key column. Without a\n // condition on each of them the join reads the\n // related rows of every row sharing the first one —\n // or, with none at all, of the whole table.\n const currentRow = rowIdentityCondition(sourceTable, pks, currentId, collection.slug);\n\n // Execute the query\n const joinResults = await query.where(currentRow).limit(relation.cardinality === \"one\" ? 1 : 100);\n\n if (joinResults.length > 0) {\n const targetPks = getPrimaryKeys(targetCollection, registry!);\n const targetTableName = relation.joinPath[relation.joinPath.length - 1].table;\n\n if (relation.cardinality === \"one\") {\n // One-to-one: return single relation object\n const joinResult = joinResults[0] as Record<string, unknown>;\n const targetRow = (joinResult[targetTableName] || joinResult) as Record<string, unknown>;\n result[propKey] = createRelationRef(buildCompositeId(targetRow, targetPks), targetCollection.slug);\n } else {\n // One-to-many: return array of relation objects\n result[propKey] = joinResults.map((joinResult: Record<string, unknown>) => {\n const targetRow = (joinResult[targetTableName] || joinResult) as Record<string, unknown>;\n return createRelationRef(buildCompositeId(targetRow, targetPks), targetCollection.slug);\n });\n }\n }\n }\n } catch (e) {\n logger.warn(`Could not resolve join path relation property: ${propKey}`, { error: e });\n }\n }\n }\n }\n }\n\n return result as M;\n}\n\n/**\n * Try to decode a `data:application/octet-stream;base64,...` data URL string\n * into a Buffer. Returns null if the value is not a matching data URL.\n */\nfunction tryDecodeBase64DataUrl(value: unknown): Buffer | null {\n if (typeof value !== \"string\") return null;\n if (!value.startsWith(\"data:application/octet-stream;base64,\")) return null;\n const base64Data = value.split(\",\")[1];\n return base64Data ? Buffer.from(base64Data, \"base64\") : null;\n}\n\n/**\n * Try to resolve an unknown value into a Buffer.\n * Handles native Buffers and `{ type: \"Buffer\", data: number[] }` objects (from JSON deserialization).\n * Returns null if the value is not a buffer.\n */\nfunction tryResolveBuffer(value: unknown): Buffer | null {\n if (Buffer.isBuffer(value)) return value;\n if (typeof value === \"object\" && value !== null) {\n const rawVal = value as Record<string, unknown>;\n if (rawVal.type === \"Buffer\" && Array.isArray(rawVal.data)) {\n return Buffer.from(rawVal.data as number[]);\n }\n }\n return null;\n}\n\n/**\n * Convert a Buffer to a UTF-8 string if all bytes are printable ASCII,\n * otherwise return a base64 data URL.\n */\nfunction bufferToStringOrBase64(buf: Buffer): string {\n for (let i = 0; i < buf.length; i++) {\n const b = buf[i];\n // Allow standard printable ASCII + common whitespace (\\r, \\n, \\t)\n if ((b < 32 || b > 126) && b !== 9 && b !== 10 && b !== 13) {\n return `data:application/octet-stream;base64,${buf.toString(\"base64\")}`;\n }\n }\n return buf.toString(\"utf8\");\n}\n\nexport function parsePropertyFromServer(value: unknown, property: Property, collection: CollectionConfig, propertyKey?: string): unknown {\n if (value === null || value === undefined) return value;\n\n switch (property.type) {\n case \"binary\": {\n const buf = tryResolveBuffer(value);\n if (buf) {\n return `data:application/octet-stream;base64,${buf.toString(\"base64\")}`;\n }\n return value;\n }\n\n case \"string\": {\n if (typeof value === \"string\") return value;\n\n // Handle Buffer objects (e.g. from PostgreSQL bytea columns)\n const buf = tryResolveBuffer(value);\n if (buf) {\n return bufferToStringOrBase64(buf);\n }\n\n if (typeof value === \"object\" && value !== null) {\n try {\n return JSON.stringify(value);\n } catch {\n return String(value);\n }\n }\n return String(value);\n }\n\n case \"relation\":\n // Transform ID back to relation object with type information\n if (typeof value === \"string\" || typeof value === \"number\") {\n // Normalization stamps the resolved relation onto the property;\n // fall back to the collection's map for a property that never\n // went through the registry.\n let relationDef: ResolvedRelation | undefined = (property as RelationProperty).resolvedRelation;\n if (!relationDef && propertyKey) {\n relationDef = findRelation(resolveCollectionRelations(collection), propertyKey);\n }\n\n if (!relationDef) {\n logger.warn(`ResolvedRelation not defined in property for key: ${propertyKey || \"unknown\"}`);\n return value;\n }\n\n try {\n const targetCollection = relationDef.target();\n return createRelationRef(value.toString(), targetCollection.slug);\n } catch (e) {\n logger.warn(`Could not resolve target collection for relation property: ${propertyKey || \"unknown\"}`, { error: e });\n return value;\n }\n }\n return value;\n\n case \"array\":\n if (Array.isArray(value)) {\n if (property.of) {\n return value.map(item => parsePropertyFromServer(item, property.of as Property, collection));\n } else if (property.oneOf) {\n const typeField = property.oneOf.typeField ?? DEFAULT_ONE_OF_TYPE;\n const valueField = property.oneOf.valueField ?? DEFAULT_ONE_OF_VALUE;\n return value.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]: parsePropertyFromServer(rec[valueField], childProperty, collection)\n };\n });\n }\n } else {\n // Non-array value from DB for an array property — coerce to avoid .map() crashes\n logger.warn(`Expected array value from DB for array property, got ${typeof value}. Coercing to array.`);\n return typeof value === \"string\" ? [value] : [];\n }\n return value;\n\n case \"map\":\n if (typeof value === \"object\" && property.properties) {\n const result: Record<string, unknown> = {};\n for (const [subKey, subValue] of Object.entries(value)) {\n const subProperty = (property.properties as Properties)[subKey];\n if (subProperty) {\n result[subKey] = parsePropertyFromServer(subValue, subProperty, collection);\n } else {\n result[subKey] = subValue;\n }\n }\n return result;\n }\n return value;\n\n case \"number\":\n if (typeof value === \"string\") {\n const parsed = parseFloat(value);\n return isNaN(parsed) ? null : parsed;\n }\n return value;\n\n case \"geopoint\":\n // A `jsonb` column, so node-postgres hands back the object already.\n // Named explicitly rather than left to `default:`, which would run\n // it past the Buffer probe — and because a type with a write\n // serializer and no read counterpart is how this one went missing.\n return value;\n\n case \"vector\": {\n let nums: number[] = [];\n if (typeof value === \"string\") {\n nums = value.slice(1, -1).split(\",\").map(Number);\n } else if (Array.isArray(value)) {\n nums = value.map(Number);\n } else if (value instanceof Vector) {\n nums = value.value;\n } else if (typeof value === \"object\" && value !== null && \"value\" in value) {\n const valObj = value as { value: unknown };\n if (Array.isArray(valObj.value)) {\n nums = valObj.value.map(Number);\n }\n }\n return {\n __type: \"Vector\",\n value: nums\n };\n }\n\n case \"date\": {\n let date: Date | undefined;\n if (property.columnType === \"time\" && typeof value === \"string\") {\n // A time of day, served like a date-only column: on a fixed\n // day, in UTC. `new Date(\"09:30:00\")` is Invalid, which read\n // every time column as null.\n date = timeOfDayToDate(value);\n } else if (value instanceof Date) {\n date = value;\n } else if (typeof value === \"string\" || typeof value === \"number\") {\n const parsedDate = new Date(value);\n if (!isNaN(parsedDate.getTime())) {\n date = parsedDate;\n }\n }\n if (date) {\n return {\n __type: \"date\",\n value: date.toISOString()\n };\n }\n return null;\n }\n\n default: {\n // Fallback for buffers in case they are mapped to something other than string\n const buf = tryResolveBuffer(value);\n if (buf) {\n return bufferToStringOrBase64(buf);\n }\n return value;\n }\n }\n}\n\n/**\n * Shared internal helper: normalizes scalar column values from a DB row\n * into frontend format. Handles FK-column preservation, type coercion\n * (dates, numbers, NaN), and property filtering.\n *\n * @param skipRelations When `true`, relation-typed properties are omitted\n * from the result (used by `normalizeDbValues` where\n * Drizzle's relational API already hydrates them).\n */\n/**\n * Keys a query computes and attaches to a row, rather than reads from a column.\n *\n * An explicit set rather than a `_` prefix rule: a user's column may perfectly\n * well be called `_internal`, and passing it through here would put a value on\n * the row that no property describes and nothing downstream knows how to type.\n */\nconst QUERY_METADATA_KEYS = new Set([\"_score\", \"_distance\", \"_matches\"]);\n\nfunction normalizeScalarValues<M extends Record<string, unknown>>(\n data: M,\n properties: Properties,\n collection: CollectionConfig,\n resolvedRelations: Record<string, ResolvedRelation>,\n options: { skipRelations: boolean }\n): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n\n // Identify FK columns used only for relations and not exposed as properties\n const internalFKColumns = new Set<string>();\n Object.values(resolvedRelations).forEach(relation => {\n if (relation.kind !== \"belongsTo\") return;\n // The key a row carries this foreign key under, which is the wire name\n // — `authorId`, not the `author_id` column.\n const localField = fieldKeyForColumn(collection, relation.localKey);\n if (!properties[localField]) internalFKColumns.add(localField);\n });\n\n for (const [key, value] of Object.entries(data)) {\n // Keep internal FK columns as primitives\n if (internalFKColumns.has(key)) {\n result[key] = value === null ? null : (typeof value === \"number\" ? value : String(value));\n continue;\n }\n\n // Query-computed metadata, which by definition is not a column and so\n // has no property to be found. Dropped here until now — which meant\n // `_distance` was computed for every vector search on this path and\n // then discarded before the caller ever saw it.\n if (QUERY_METADATA_KEYS.has(key)) {\n result[key] = value;\n continue;\n }\n\n const property = properties[key as keyof M] as Property;\n if (!property) continue; // Skip DB columns not defined in properties\n\n if (options.skipRelations && property.type === \"relation\") continue;\n\n result[key] = parsePropertyFromServer(value, property, collection, key);\n }\n\n return result;\n}\n\n/**\n * Lightweight value normalization for db.query results.\n * Only handles type coercion (dates, numbers, NaN) and property filtering.\n * Does NOT query the database for relations — those are already resolved\n * by Drizzle's relational query API.\n *\n * Use this instead of `parseDataFromServer` when processing results from\n * `db.query.findFirst/findMany` which return pre-hydrated relation data.\n */\nexport function normalizeDbValues<M extends Record<string, unknown>>(\n data: M,\n collection: CollectionConfig\n): M {\n const properties = collection.properties;\n if (!data || !properties) return data;\n\n const resolvedRelations = resolveCollectionRelations(collection);\n return normalizeScalarValues(data, properties, collection, resolvedRelations, { skipRelations: true }) as M;\n}\n","import { CollectionConfig, ResolvedRelation } from \"@rebasepro/types\";\nimport { findRelation, resolveCollectionRelations } from \"@rebasepro/common\";\nimport { ApiError } from \"@rebasepro/server\";\n\nimport { getCollectionByPath } from \"./collection-helpers\";\nimport { PostgresCollectionRegistry } from \"../collections/PostgresCollectionRegistry\";\n\n/**\n * The last hop of a nested collection path, e.g. `authors/1/posts`.\n *\n * The walk that produces this was written out four separate times — in\n * `FetchService.fetchCollectionFromPath`, `FetchService.countEntitiesFromPath`,\n * `PersistService.save` and `CollectionRegistry.getCollectionByPath` — and had\n * drifted, so the read path and the write path did not agree on which relation\n * a path named. It lives here once now.\n */\nexport interface NestedPathHop {\n /** The collection the final relation is declared on (e.g. `authors`). */\n parentCollection: CollectionConfig;\n /** The parent's id as it appeared in the path, unparsed. */\n parentId: string;\n /** The path segment that named the relation (e.g. `posts`). */\n relationKey: string;\n relation: ResolvedRelation;\n /** `relation.target()`, resolved once. */\n targetCollection: CollectionConfig;\n}\n\n/**\n * True when `path` addresses rows through a relation rather than a root\n * collection.\n *\n * Any separator at all counts — a root collection slug never contains one — so\n * a malformed path like `collection/id` is a *broken* nested path and gets\n * reported as one by {@link resolveNestedPath}, rather than being looked up as\n * a root collection whose slug happens to contain a slash.\n */\nexport function isNestedPath(path: string): boolean {\n return path.includes(\"/\");\n}\n\nexport function splitPathSegments(path: string): string[] {\n return path.split(\"/\").filter(s => s && s !== \"undefined\");\n}\n\n/**\n * Walk a nested collection path down to the relation it ends in.\n *\n * Returns `undefined` for a plain root-collection path so callers can keep the\n * root case on its existing code path. Throws when the path is malformed, or\n * when a segment names a relation that does not exist — the same errors the\n * individual walks used to raise, with the available names attached.\n */\nexport function resolveNestedPath(\n path: string,\n registry: PostgresCollectionRegistry\n): NestedPathHop | undefined {\n if (!isNestedPath(path)) return undefined;\n\n const segments = splitPathSegments(path);\n if (segments.length < 3 || segments.length % 2 === 0) {\n throw new Error(`Invalid relation path: ${path}. Expected format: collection/id/relation`);\n }\n\n let parentCollection = getCollectionByPath(segments[0], registry);\n let parentId = segments[1];\n\n for (let i = 2; i < segments.length; i += 2) {\n const relationKey = segments[i];\n const resolvedRelations = resolveCollectionRelations(parentCollection);\n const relation = findRelation(resolvedRelations, relationKey);\n\n if (!relation) {\n const available = Object.keys(resolvedRelations).join(\", \") || \"(none)\";\n // A path segment naming a relation the collection does not declare\n // is a bad URL, not a server fault. It answered 500 INTERNAL_ERROR,\n // which tells the caller to retry or report an outage over a\n // request that will never succeed — and buries a message that\n // already says exactly what is wrong.\n //\n // 404, like an unknown collection: the URL names nothing. The\n // available relations stay in the message because reaching this\n // point means the caller could already read the parent collection,\n // and `?include=` would list the same names.\n throw ApiError.notFound(\n `Relation '${relationKey}' not found in collection '${parentCollection.slug}'. Available relations: [${available}]`,\n \"UNKNOWN_RELATION\"\n );\n }\n\n const targetCollection = relation.target();\n\n if (i === segments.length - 1) {\n return {\n parentCollection,\n parentId,\n relationKey,\n relation,\n targetCollection\n };\n }\n\n parentCollection = targetCollection;\n parentId = segments[i + 1];\n }\n\n // Unreachable: the loop returns on the final segment, and the odd-length\n // check above guarantees there is one.\n throw new Error(`Unable to resolve path: ${path}`);\n}\n\n/**\n * A relation reached through a junction table — many-to-many, or a multi-hop\n * `joinPath`. The target row is shared with other parents, so writing \"through\"\n * such a path addresses the *link*, not the row.\n */\nexport function isJunctionBackedRelation(relation: ResolvedRelation): boolean {\n return relation.shared;\n}\n\n/**\n * Reject a nested write whose final segment is a to-one relation.\n *\n * There is no column on the target row that records a to-one parent — the\n * foreign key lives on the *parent* table. The write path used to fall through\n * to `relation.localKey` here and stamp the parent's own FK column onto the\n * target row, which either raised an opaque \"column does not exist\" or, when a\n * column of that name happened to exist on the target, silently wrote the wrong\n * one.\n */\nexport function assertWritableThrough(hop: NestedPathHop, path: string): void {\n const { relation } = hop;\n\n // Read the flag rather than re-deriving the rule. This guard used to test\n // `cardinality !== \"many\"`, which let a to-many `via` through even though\n // its type declares `writable: false` — a second expression of one rule,\n // free to disagree with the first, which is the shape of defect this union\n // exists to remove.\n if (relation.writable && relation.cardinality === \"many\") return;\n\n if (!relation.writable) {\n throw ApiError.badRequest(\n `\"${path}\" ends in '${hop.relationKey}', a \\`via\\` relation. Rebase will not infer how to write ` +\n `through an arbitrary join chain — write the row at \"${hop.targetCollection.slug}\" directly.`,\n \"RELATION_NOT_WRITABLE\"\n );\n }\n\n throw ApiError.badRequest(\n `\"${path}\" ends in the to-one relation '${hop.relationKey}', which cannot be written through: ` +\n `the foreign key for a to-one relation lives on '${hop.parentCollection.slug}', not on ` +\n `'${hop.targetCollection.slug}'. Write the target row at \"${hop.targetCollection.slug}\" and set ` +\n `'${hop.relationKey}' on the parent instead.`,\n \"RELATION_NOT_WRITABLE\"\n );\n}\n","import { and, asc, count, desc, eq, getTableColumns, getTableName, gt, is, isNotNull, isNull, lt, or, sql, SQL, TableRelationalConfig, TablesRelationalConfig } from \"drizzle-orm\";\nimport { AnyPgColumn, PgTable, PgTimestamp, PgTimestampString } from \"drizzle-orm/pg-core\";\nimport { CollectionConfig, FilterValues, JUNCTION_PIVOT_KEY, MAX_INCLUDE_DEPTH, OrderByTuple, ResolvedRelation, LogicalCondition, parseRelationAggregateSort } from \"@rebasepro/types\";\nimport type { IncludeSpec, NullsPlacement, ReadOperation, ReadQuery, VectorSearchParams } from \"@rebasepro/types\";\nimport { resolveCollectionRelations, findRelation, fieldKeyForColumn, createRelationRefWithData, normalizeDriverOrderBy, normalizeInclude, encodeCursor, type IncludeNode, type NormalizedInclude } from \"@rebasepro/common\";\nimport { generateForeignKeyName, toWireKey } from \"@rebasepro/utils\";\nimport { DrizzleConditionBuilder, getUnknownFilterFieldsMode, type FilterCompilationOptions } from \"../utils/drizzle-conditions\";\nimport {\n getCollectionByPath,\n getTableForCollection,\n getPrimaryKeys,\n requirePrimaryKeys,\n deriveRowAddress,\n parseIdValues,\n idCanAddressTable,\n rowIdentityCondition,\n primaryKeyColumns,\n buildCompositeId,\n COMPOSITE_ID_SEPARATOR,\n type PrimaryKeyInfo\n} from \"./collection-helpers\";\nimport { parseDataFromServer } from \"../data-transformer\";\nimport { RelationService } from \"./RelationService\";\nimport { RelationalQueryBuilder } from \"drizzle-orm/pg-core/query-builders/query\";\nimport { DrizzleClient } from \"../interfaces\";\nimport { PostgresCollectionRegistry } from \"../collections/PostgresCollectionRegistry\";\nimport { toFlatRow, toRestRow, toRestValues, stripUnreadable } from \"./row-pipeline\";\nimport { visibleColumnProjection, hiddenColumnsOption } from \"../schema/search-column\";\nimport { isNestedPath, resolveNestedPath, type NestedPathHop } from \"./nested-path\";\n// One rule, one place. See `soft-delete.ts` for why every read has to ask.\nimport { andSoftDelete, withSoftDelete, type WithDeleted } from \"./soft-delete\";\nimport { beforeQueryCondition, beforeQueryConditions, type ReadCallContextProvider } from \"./read-scope\";\nimport { ApiError, logger } from \"@rebasepro/server\";\nimport { reachedDatabase } from \"../utils/pg-error-utils\";\n\n/** Type-safe accessor for Drizzle's relational query API via dynamic table name */\ntype DbQueryAccessor = Record<string, RelationalQueryBuilder<any, any>> | undefined;\n\n/**\n * One sort key, with the column or expression it was resolved to.\n *\n * `cursorTarget` is the same expression pinned to the cursor row instead of the\n * outer one, and is set only for a key that has no stored value to compare a\n * later page against — an aggregate over a relation. Where it is present the\n * keyset comparison recomputes the cursor row's value in SQL rather than\n * reading it off the cursor; see {@link FetchService.buildKeysetComparison}.\n */\ntype ResolvedOrderKey = {\n field: string;\n direction: \"asc\" | \"desc\";\n /**\n * Where this key's NULLs go. Absent means the direction's own convention —\n * see {@link FetchService.nullsLast}, which is the one place the two\n * functions that have to agree about it both read.\n */\n nulls?: NullsPlacement;\n target: AnyPgColumn | SQL;\n cursorTarget?: SQL;\n};\n\n/**\n * Ascending comparison for two values of one column, with NULLs placed.\n *\n * Used only to order the rows *inside* one included relation, where the sort\n * happens after a batched load rather than in SQL — see\n * {@link FetchService.shapeRelatedRows}. The NULL placement is the same\n * question the `ORDER BY` answers, and it is answered the same way here so a\n * nested `orderBy` does not put its empty values at the opposite end from the\n * identical top-level one.\n */\nfunction compareForSort(a: unknown, b: unknown, nullsLast: boolean): number {\n const aNull = a === null || a === undefined;\n const bNull = b === null || b === undefined;\n if (aNull && bNull) return 0;\n if (aNull) return nullsLast ? 1 : -1;\n if (bNull) return nullsLast ? -1 : 1;\n // Dates compare as numbers; everything else falls back to a string\n // comparison, which is what a mixed or unknown column has.\n const av = a instanceof Date ? a.getTime() : a;\n const bv = b instanceof Date ? b.getTime() : b;\n if (typeof av === \"number\" && typeof bv === \"number\") return av - bv;\n if (typeof av === \"boolean\" && typeof bv === \"boolean\") return (av ? 1 : 0) - (bv ? 1 : 0);\n return String(av).localeCompare(String(bv));\n}\n\n/**\n * Service for handling all row read operations.\n * Handles fetching, searching, counting, and filtering rows.\n */\n/**\n * Which aggregate aliases hold a number that Postgres handed back as a string.\n *\n * `count`, `sum` and `avg` always do: bigint and numeric are returned as text\n * because they do not fit a JS number in general. `min` and `max` are uncast,\n * so they do it too — but only over a numeric column, and those are the two\n * functions that also apply to text and dates.\n *\n * Decided by the column's DECLARED type, not by trying `Number()` on the value.\n * `?select=avg(price),max(price)` used to answer `{avg_price: 5,\n * max_price: \"8.5\"}` — one column, two functions, two JSON types, and\n * arithmetic on the second one silently concatenates. Parsing whatever looks\n * numeric would fix that and break something worse: a `min(sku)` of `\"00123\"`\n * would come back as `123`, a different value, and no NaN check would catch it.\n *\n * Exported to be tested: the parsing itself needs a database, this decision\n * does not.\n */\nexport function numericAggregateAliases(\n aggregates: { fn: \"count\" | \"sum\" | \"avg\" | \"min\" | \"max\"; field?: string; alias: string }[],\n properties: Record<string, { type?: string }>\n): Set<string> {\n const isNumberField = (field?: string): boolean =>\n Boolean(field) && properties[field as string]?.type === \"number\";\n return new Set(\n aggregates\n .filter(a =>\n a.fn === \"count\" || a.fn === \"sum\" || a.fn === \"avg\"\n || ((a.fn === \"min\" || a.fn === \"max\") && isNumberField(a.field)))\n .map(a => a.alias)\n );\n}\n\nexport class FetchService {\n private relationService: RelationService;\n\n constructor(\n private db: DrizzleClient,\n private registry: PostgresCollectionRegistry,\n /**\n * How this service's reads reach the identity their `beforeQuery` hooks\n * run as. Passed by the driver that constructed it; absent only in a\n * test, where a collection declaring the hook is refused rather than\n * read unnarrowed. See `read-scope.ts`.\n */\n private callContext?: ReadCallContextProvider\n ) {\n this.relationService = new RelationService(db, registry, callContext);\n }\n\n /**\n * Get the relational query builder for a given table name.\n * Safely narrows the DrizzleClient union type to access db.query[tableName].\n */\n private getQueryBuilder(tableName: string): RelationalQueryBuilder<TablesRelationalConfig, TableRelationalConfig> | undefined {\n const query = (this.db as { query?: DbQueryAccessor }).query;\n return query?.[tableName] as RelationalQueryBuilder<TablesRelationalConfig, TableRelationalConfig> | undefined;\n }\n\n /**\n * The context the condition builder needs to compile a filter key that is\n * not a column name outright.\n *\n * Two such keys. An owning relation's key resolves through the collection's\n * relations to its foreign-key column; a relation whose link lives on the\n * target table or in a junction resolves to a correlated `EXISTS`, which\n * needs the registry to reach that other table and this table's key column\n * to correlate back.\n *\n * Looked up rather than passed: every read path already has the path, only\n * some have the collection, and a path that names no registered collection\n * (a nested/derived one) is not an error here — the builder simply falls\n * back to guessing the default key shapes, and a relation filter it cannot\n * compile stays unresolvable and so fails closed.\n */\n private filterContext(collectionPath: string, table: PgTable<any>): FilterCompilationOptions {\n const collection = this.registry.getCollectionByPath(collectionPath) ?? undefined;\n return {\n collection,\n registry: this.registry,\n sourceIdColumn: collection ? this.resolveIdColumn(collection, table) : undefined\n };\n }\n\n /**\n * The conditions this collection's `beforeQuery` hooks ask to add, for a\n * read that is about to be compiled.\n *\n * Resolved here, from the registry this service already holds, rather than\n * handed in by a caller — so a read path that builds a WHERE cannot serve\n * one of these collections without the hook. `[]` when nothing is declared,\n * which leaves the compiled SQL byte-identical.\n *\n * Every caller pushes the result onto the same `allConditions` array its\n * own filter goes into, and every one of those arrays is AND-ed. That is\n * what keeps a hook additive: it is not trusted to narrow, it is only ever\n * given a way to.\n */\n private narrowRead(\n collectionPath: string,\n collection: CollectionConfig,\n table: PgTable<any>,\n operation: ReadOperation,\n query: ReadQuery\n ): Promise<SQL[]> {\n return beforeQueryConditions(\n { registry: this.registry, callContext: this.callContext },\n collection, collectionPath, table as PgTable<never>,\n { operation, query }, this.filterContext(collectionPath, table)\n );\n }\n\n /** {@link narrowRead}, pre-combined, for the reads that hold one `SQL`. */\n private narrowReadCondition(\n collectionPath: string,\n collection: CollectionConfig,\n table: PgTable<any>,\n operation: ReadOperation,\n query: ReadQuery\n ): Promise<SQL | undefined> {\n return beforeQueryCondition(\n { registry: this.registry, callContext: this.callContext },\n collection, collectionPath, table as PgTable<never>,\n { operation, query }, this.filterContext(collectionPath, table)\n );\n }\n\n /**\n * The read the hooks are shown, from the options a read path was given.\n *\n * Transport is left out on purpose — a `databaseId`, an `include` tree, a\n * `withDeleted` flag are not part of \"which rows\". What is in is what a\n * scope might reasonably branch on.\n */\n private static describeRead(options: {\n filter?: ReadQuery[\"filter\"];\n logical?: LogicalCondition;\n searchString?: string;\n limit?: number;\n offset?: number;\n orderBy?: string | OrderByTuple[];\n order?: \"desc\" | \"asc\";\n fields?: string[];\n relatedTo?: NestedPathHop;\n }): ReadQuery {\n const orderBy = normalizeDriverOrderBy(options.orderBy, options.order);\n return {\n filter: options.filter,\n logical: options.logical,\n searchString: options.searchString,\n limit: options.limit,\n offset: options.offset,\n orderBy: orderBy && orderBy.length > 0 ? orderBy : undefined,\n fields: options.fields,\n relatedTo: options.relatedTo\n ? {\n parentSlug: options.relatedTo.parentCollection.slug,\n parentId: options.relatedTo.parentId,\n relationName: options.relatedTo.relation.relationName\n }\n : undefined\n };\n }\n\n /**\n * The table column this collection's rows are keyed by, or `undefined`.\n *\n * `getPrimaryKeys` rather than `requirePrimaryKeys`: a collection with no\n * resolvable key is not an error on the filter path — it only means the\n * relation filters that would correlate on it cannot be compiled, which\n * the builder already handles by failing that field closed.\n */\n private resolveIdColumn(collection: CollectionConfig, table: PgTable<any>): AnyPgColumn | undefined {\n const [idInfo] = getPrimaryKeys(collection, this.registry);\n if (!idInfo) return undefined;\n return table[idInfo.fieldName as keyof typeof table] as AnyPgColumn | undefined;\n }\n\n /**\n * Build filter conditions from FilterValues\n * Delegates to DrizzleConditionBuilder.buildFilterConditions\n */\n buildFilterConditions<M extends Record<string, unknown>>(\n filter: FilterValues<Extract<keyof M, string>>,\n table: PgTable<any>,\n collectionPath: string\n ): SQL[] {\n return DrizzleConditionBuilder.buildFilterConditions(\n filter, table, collectionPath, this.filterContext(collectionPath, table)\n );\n }\n\n // =============================================================\n // DRIZZLE QUERY HELPERS\n // =============================================================\n\n /**\n * Resolves the correct Drizzle column for sorting.\n * Automatically maps owning relation property keys to their underlying foreign key column.\n *\n * The relation's own `localKey` is the authority for that foreign key, not\n * `<field>_id`. The default local key comes from `generateForeignKeyName`,\n * which snake-cases *and singularises* — `userProfile` → `user_profile_id`,\n * `users` → `user_id` — and an author can override it outright. A wrong\n * guess resolves to nothing, the caller drops the `ORDER BY`, and the rows\n * come back in whatever order Postgres pleases: paging over that repeats\n * and skips rows rather than erroring. The guesses stay, last, for a\n * caller that hands over no collection to resolve against.\n */\n /**\n * The ORDER BY target, which may be relevance rather than a column.\n *\n * `_score` is only meaningful for a collection that declared a `search`\n * block *and* for a request that carried a search string — ranking rows\n * against no query ranks them all at zero. Outside those two conditions it\n * is an unknown field and gets the same 400 as any other typo, which is the\n * behaviour that matters: a sort that is silently dropped returns 200 with\n * rows in arbitrary order, and paging over that repeats and skips rows.\n */\n static readonly SCORE_FIELD = \"_score\";\n\n /**\n * The distance a vector search attaches to every row it serves, and to no\n * other. Like `_score` it is computed per query and stored nowhere.\n */\n static readonly DISTANCE_FIELD = \"_distance\";\n\n private resolveOrderTarget(\n table: PgTable<any>,\n orderBy: string,\n collection?: CollectionConfig,\n searchString?: string\n ): AnyPgColumn | SQL | undefined {\n if (orderBy === FetchService.SCORE_FIELD && collection && searchString) {\n const rank = DrizzleConditionBuilder.buildSearchRankExpression(searchString, table, collection);\n if (rank) return rank;\n }\n return this.resolveOrderByField(table, orderBy, collection);\n }\n\n /**\n * The aggregate a sort key names, as an expression, or `undefined` if the\n * key is not one.\n *\n * `cursorId` builds the same expression pinned to the cursor row — see\n * {@link DrizzleConditionBuilder.buildRelationAggregateExpression}.\n *\n * A key that *parses* as an aggregate but names no relation, or a column\n * the target does not have, throws rather than falling through to the\n * column path. Falling through would report `min(applications.created_at)`\n * as an unknown column and list the columns of the wrong table.\n */\n private resolveAggregateOrderTarget(\n table: PgTable<any>,\n orderBy: string,\n collection: CollectionConfig | undefined,\n collectionPath: string | undefined,\n cursorId?: unknown\n ): SQL | undefined {\n const spec = parseRelationAggregateSort(orderBy);\n if (!spec) return undefined;\n if (!collection || !collectionPath) {\n throw ApiError.badRequest(\n `Cannot sort by '${orderBy}': an aggregate sort needs the collection it is written against, ` +\n \"and this query carries none.\",\n \"ORDER_BY_FIELD_NOT_SORTABLE\",\n { field: orderBy }\n );\n }\n const primaryKeys = getPrimaryKeys(collection, this.registry);\n const idColumn = primaryKeys.length === 1\n ? table[primaryKeys[0].fieldName as keyof typeof table] as AnyPgColumn\n : undefined;\n if (!idColumn) {\n throw ApiError.badRequest(\n `Cannot sort by '${orderBy}' on collection '${collectionPath}': the subquery correlates on a ` +\n \"single key column, and this collection has none or has a composite one.\",\n \"ORDER_BY_FIELD_NOT_SORTABLE\",\n { field: orderBy, collection: collectionPath }\n );\n }\n return DrizzleConditionBuilder.buildRelationAggregateExpression(\n spec, table, collection, this.registry, idColumn, collectionPath, cursorId\n );\n }\n\n /**\n * Resolve every sort key to the expression it orders by, in order of\n * significance.\n *\n * A key that resolves to nothing is dropped rather than skipping the rest:\n * `resolveOrderByField` only *returns* undefined under the lenient\n * unknown-field mode, where dropping is the configured answer, and dropping\n * one key of several still honours the ones that did resolve.\n */\n private resolveOrderKeys(\n table: PgTable<any>,\n keys: OrderByTuple[],\n collection?: CollectionConfig,\n searchString?: string,\n collectionPath?: string,\n cursorId?: unknown\n ): ResolvedOrderKey[] {\n const resolved: ResolvedOrderKey[] = [];\n for (const [field, direction, nulls] of keys) {\n // Checked before the column path: an aggregate key is not a column\n // name and would otherwise be reported as a typo'd one.\n const aggregate = this.resolveAggregateOrderTarget(table, field, collection, collectionPath);\n if (aggregate) {\n resolved.push({\n field,\n direction,\n nulls,\n target: aggregate,\n // Built only when a cursor is in play: it is a second\n // subquery, and a listing with no `startAfter` has nothing\n // to compare against.\n ...(cursorId !== undefined && {\n cursorTarget: this.resolveAggregateOrderTarget(\n table, field, collection, collectionPath, cursorId\n )\n })\n });\n continue;\n }\n const target = this.resolveOrderTarget(table, field, collection, searchString);\n if (target) resolved.push({ field,\ndirection,\nnulls,\ntarget });\n }\n return resolved;\n }\n\n /**\n * Whether this key's NULLs sort *after* its real values.\n *\n * The default is Postgres's own — `NULLS LAST` ascending, `NULLS FIRST`\n * descending — and it was previously hardcoded in two places that had to\n * agree: the `ORDER BY` and the keyset comparison behind cursor paging. A\n * key that states a placement overrides it, in both, because they read the\n * answer from here.\n *\n * The default is also the thing a \"newest first\" list gets wrong: every row\n * with no date sorts to the very top, ahead of everything real, and the only\n * way out used to be an `is-not-null` filter that dropped those rows\n * entirely. `orderBy: [[\"published_at\", \"desc\", \"last\"]]` is the fix.\n */\n private static nullsLast(key: Pick<ResolvedOrderKey, \"direction\" | \"nulls\">): boolean {\n if (key.nulls) return key.nulls === \"last\";\n return key.direction === \"asc\";\n }\n\n /**\n * The full `ORDER BY`: the caller's keys, then the primary key.\n *\n * The key is always last and always descending, every column of it. It is\n * not decoration — it is what makes the ordering *total*, and a cursor over\n * a non-total order repeats and skips rows among the ties. Every keyset\n * comparison built by {@link buildCursorConditions} ends on the same key\n * `DESC`, and the two have to agree: they did not, and an ascending sort\n * paged with `id >` against an `ORDER BY … , id DESC`, so rows sharing a\n * sort value were dropped from every page after the first. Nor is the\n * first column of a composite key enough: it ties every row that shares\n * it, which is exactly the rows a cursor then skipped.\n *\n * Where the NULLs go is written out rather than inherited. Postgres already\n * defaults to `NULLS LAST` ascending and `NULLS FIRST` descending, so this\n * changes no query — but {@link buildKeysetComparison} encodes that exact\n * placement, and an invariant two functions depend on should be stated in\n * both rather than assumed in one. It matters most for the keys that are\n * *always* nullable: an aggregate over a relation is NULL for every row the\n * relation reaches nothing from, which is precisely the \"nobody waiting\"\n * end of a queue.\n */\n private buildOrderExpressions(\n keys: ResolvedOrderKey[],\n /**\n * The primary key columns, appended as the final tie-breaker.\n *\n * Empty for a `SELECT DISTINCT`, which does not select the key: Postgres\n * requires every ORDER BY expression of a distinct read to be in the\n * select list and answers a bare `42P10` otherwise. There is also\n * nothing for it to do there — a distinct read returns a set of values,\n * not rows, so there are no ties between rows to break.\n */\n keyColumns: AnyPgColumn[]\n ): SQL[] {\n // Four literal branches rather than a nested `sql` fragment for the\n // placement: a nested fragment renders as a child SQL node, which is\n // correct in the statement and invisible to anything reading the\n // expression's own chunks — including the test that asserts a\n // descending sort really does say `NULLS LAST` when it was asked to.\n const expressions = keys.map((key) => {\n const nullsLast = FetchService.nullsLast(key);\n if (key.direction === \"asc\") {\n return nullsLast ? sql`${key.target} ASC NULLS LAST` : sql`${key.target} ASC NULLS FIRST`;\n }\n return nullsLast ? sql`${key.target} DESC NULLS LAST` : sql`${key.target} DESC NULLS FIRST`;\n });\n for (const column of keyColumns) expressions.push(desc(column));\n return expressions as SQL[];\n }\n\n private resolveOrderByField(\n table: PgTable<any>,\n orderBy: string,\n collection?: CollectionConfig\n ): AnyPgColumn | undefined {\n const columnAt = (key: string): AnyPgColumn | undefined =>\n (key in table ? table[key as keyof typeof table] as AnyPgColumn : undefined) || undefined;\n\n const direct = columnAt(orderBy);\n if (direct) return direct;\n\n // Owning relation, resolved: the relation names its own local key.\n const declaredRelation = collection ? resolveCollectionRelations(collection)[orderBy] : undefined;\n if (declaredRelation?.kind === \"belongsTo\") {\n // `localKey` is the column; the table is keyed by the wire name.\n const foreignKey = columnAt(fieldKeyForColumn(collection, declaredRelation.localKey));\n if (foreignKey) return foreignKey;\n }\n\n // No collection in hand — the shapes an owning relation's key takes by\n // default (e.g. `project` → `projectId`, `userProfile` →\n // `userProfileId`), then the snake forms for a project that authored the\n // property under its column name.\n for (const guess of [\n `${orderBy}Id`,\n toWireKey(generateForeignKeyName(orderBy)),\n `${orderBy}_id`,\n generateForeignKeyName(orderBy)\n ]) {\n const foreignKey = columnAt(guess);\n if (foreignKey) return foreignKey;\n }\n\n // Nothing resolved. Returning `undefined` is exactly what the docblock\n // above describes: the caller drops the ORDER BY and hands back rows in\n // whatever order Postgres pleases, while the requester believes they\n // are sorted. `?orderBy=titel` answered 200 with unsorted data and no\n // hint that the sort had been ignored.\n //\n // A *filter* naming a field that does not exist is already refused for\n // precisely this reason — it \"used to widen results silently\". An\n // unresolvable sort field is the same drift between a query and the\n // schema, so it answers the same way and honours the same switch: one\n // knob, because a deployment that wants the lenient behaviour wants it\n // for both.\n const collectionName = collection?.slug ?? collection?.name;\n const onCollection = collectionName ? ` on collection '${collectionName}'` : \"\";\n\n // A declared to-many relation is a different mistake from a typo, and\n // saying \"unknown field\" about a field the collection plainly declares\n // sends the reader looking for a spelling error that is not there.\n // There is simply no single value per row to order by — `posts.tags` is\n // a set — so no ORDER BY exists to write, with or without a typo.\n if (declaredRelation && declaredRelation.kind !== \"belongsTo\") {\n throw ApiError.badRequest(\n `Cannot sort by '${orderBy}'${onCollection}: it is a to-many relation ` +\n `(${declaredRelation.kind}), which has no single value per row to order by.`,\n \"ORDER_BY_FIELD_NOT_SORTABLE\",\n { field: orderBy, kind: declaredRelation.kind, ...(collectionName && { collection: collectionName }) }\n );\n }\n\n if (getUnknownFilterFieldsMode() === \"warn\") {\n logger.warn(\n `Sorting by field '${orderBy}'${onCollection}, but it does not exist in the table — ` +\n \"the ORDER BY was dropped and these rows are unsorted.\"\n );\n return undefined;\n }\n\n let validFields: string[] = [];\n try {\n validFields = Object.keys(getTableColumns(table)).sort();\n } catch {\n // A table stand-in without Drizzle's column symbols — the message\n // is worth less without the list, but not worth failing over.\n }\n\n throw ApiError.badRequest(\n `Unknown orderBy field '${orderBy}'${onCollection}` +\n (validFields.length > 0 ? `. Valid fields: ${validFields.join(\", \")}` : \"\"),\n \"UNKNOWN_ORDER_BY_FIELD\",\n {\n field: orderBy,\n ...(collectionName && { collection: collectionName }),\n ...(validFields.length > 0 && { validFields })\n }\n );\n }\n\n // =============================================================\n // THE INCLUDE PIPELINE\n //\n // One implementation, reached by every read: the REST list, the REST\n // get-by-id, the in-process accessor and the realtime refetch. It used to\n // be four. `db.query.findMany({ with })` served REST — the lateral-join\n // path this file's own comments call \"catastrophically slow… 7s+ for 350\n // rows\" — while the admin fetch batched through `RelationService` and the\n // realtime refetch loaded EVERY relation because it passed no `include` at\n // all. So `find()` and `listen()` answered the same query with different\n // rows, and the fast path was the one nobody could reach.\n //\n // What runs now is the batched one, for all of them: one query per relation\n // per level, never per row.\n // =============================================================\n\n /**\n * The relations one level of an include tree names, resolved.\n *\n * A name that is not a relation is a **400 `UNKNOWN_RELATION`**. It used to\n * be dropped silently, which answers 200 with the field missing — and a\n * missing relation field is indistinguishable from a row that genuinely has\n * no related row, so a typo in an `include` looked like empty data.\n */\n private resolveIncludeLevel(\n collection: CollectionConfig,\n tree: Record<string, IncludeNode>,\n wildcard: boolean\n ): { key: string; node: IncludeNode; relation: ResolvedRelation }[] {\n const resolvedRelations = resolveCollectionRelations(collection);\n const out: { key: string; node: IncludeNode; relation: ResolvedRelation }[] = [];\n\n if (wildcard) {\n // `*` is \"every relation, one hop\" — the admin panel's shape. A\n // relation also named explicitly keeps its options.\n for (const [key, relation] of Object.entries(resolvedRelations)) {\n out.push({ key, node: tree[key] ?? { children: {} }, relation });\n }\n }\n\n for (const [key, node] of Object.entries(tree)) {\n if (wildcard && key in resolvedRelations) continue;\n const relation = resolvedRelations[key];\n if (!relation) {\n const known = Object.keys(resolvedRelations).sort();\n throw ApiError.badRequest(\n `Unknown relation '${key}' on collection '${collection.slug ?? collection.name}'`\n + (known.length > 0\n ? `. Its relations are: ${known.join(\", \")}`\n : \". It declares no relations.\"),\n \"UNKNOWN_RELATION\",\n { relation: key, collection: collection.slug ?? collection.name, ...(known.length > 0 && { validRelations: known }) }\n );\n }\n out.push({ key, node, relation });\n }\n\n return out;\n }\n\n /**\n * The extra `WHERE` an include's own `where`/`logical` puts on the target.\n *\n * Pushed into the batch query rather than applied to the rows it returns:\n * filtering afterwards reads every related row of every parent to throw\n * most of them away, and on a to-many relation that is the whole table.\n */\n private includeNarrowing(node: IncludeNode, targetCollection: CollectionConfig): SQL | undefined {\n if (!node.where && !node.logical) return undefined;\n const targetPath = targetCollection.slug;\n const targetTable = getTableForCollection(targetCollection, this.registry);\n const conditions: SQL[] = [];\n if (node.where) {\n conditions.push(...this.buildFilterConditions(node.where, targetTable, targetPath));\n }\n if (node.logical) {\n const logical = DrizzleConditionBuilder.buildLogicalConditions(\n node.logical, targetTable, targetPath, this.filterContext(targetPath, targetTable)\n );\n if (logical) conditions.push(logical);\n }\n return conditions.length > 0\n ? DrizzleConditionBuilder.combineConditionsWithAnd(conditions)\n : undefined;\n }\n\n /**\n * Sort, cap and project the rows of ONE to-many relation, per parent.\n *\n * `orderBy` and `limit` are applied here rather than in SQL because a limit\n * on a batched relation load is *per parent*, and expressing that needs a\n * LATERAL or a window function per relation kind — four of them, each with\n * its own join shape. The rows were already narrowed by\n * {@link includeNarrowing} in SQL, so what this sorts and cuts is the set\n * the caller asked for and not the table.\n */\n private shapeRelatedRows(\n rows: Record<string, unknown>[],\n node: IncludeNode,\n targetCollection: CollectionConfig\n ): Record<string, unknown>[] {\n let out = rows;\n if (node.orderBy && node.orderBy.length > 0) {\n const keys = node.orderBy;\n out = [...out].sort((a, b) => {\n for (const [field, direction, nulls] of keys) {\n const cmp = compareForSort(a[field], b[field], FetchService.nullsLast({ direction, nulls }));\n if (cmp !== 0) return direction === \"desc\" ? -cmp : cmp;\n }\n return 0;\n });\n }\n if (node.limit !== undefined) out = out.slice(0, node.limit);\n if (node.fields && node.fields.length > 0) {\n const keep = new Set(node.fields);\n // The key always survives: a related row nobody can address cannot\n // be followed, updated or deduplicated by the caller.\n for (const pk of getPrimaryKeys(targetCollection, this.registry)) keep.add(pk.fieldName);\n // So does the link's own data. `fields` narrows the TARGET's\n // columns — it is keyed off the target's collection and validated\n // against it — and `_pivot` is not one of them: dropping it would\n // make `include=members&fields=id` silently lose the `role` that is\n // the reason the relation carries a payload at all.\n keep.add(JUNCTION_PIVOT_KEY);\n out = out.map(row => {\n const projected: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(row)) {\n if (keep.has(key)) projected[key] = value;\n }\n return projected;\n });\n }\n return out;\n }\n\n /**\n * Load an include tree onto `rows`, in place, one batched query per\n * relation per level.\n *\n * Recurses into nested includes with the rows it just loaded, so\n * `comments.author` is two queries for a page of posts rather than one per\n * comment. The depth bound lives on the normalizer\n * ({@link MAX_INCLUDE_DEPTH}); this asserts it again because the tree can\n * also be built in-process, where nothing normalized it.\n */\n private async loadIncludes(\n rows: Record<string, unknown>[],\n collection: CollectionConfig,\n collectionPath: string,\n include: NormalizedInclude,\n depth = 1\n ): Promise<void> {\n if (rows.length === 0 || depth > MAX_INCLUDE_DEPTH) return;\n\n const idInfoArray = getPrimaryKeys(collection, this.registry);\n if (idInfoArray.length === 0) return;\n\n // The address the batch groups its results by. Both sides derive it the\n // same way, so they agree by construction rather than by both happening\n // to pick column zero.\n const addressOf = (row: Record<string, unknown>): string | undefined => {\n const address = buildCompositeId(row, idInfoArray);\n return address && address.split(COMPOSITE_ID_SEPARATOR).some(part => part !== \"\")\n ? address\n : undefined;\n };\n const addressable = rows.filter(row => addressOf(row) !== undefined);\n if (addressable.length === 0) return;\n const rowIds = addressable.map(row => addressOf(row) as string);\n\n const levels = this.resolveIncludeLevel(collection, include.tree, include.wildcard);\n\n for (const { key, node, relation } of levels) {\n const targetCollection = relation.target();\n // Deliberately unguarded: a `where` inside an include that names a\n // column the target does not have is the caller's mistake, and it\n // is already an ApiError. Anything else is not this loop's to\n // swallow either, so both travel to the caller untouched.\n const narrow: SQL | undefined = this.includeNarrowing(node, targetCollection);\n\n // Rows loaded at this level, to recurse into. Collected as the\n // objects actually attached to the parents, so a nested include\n // mutates what the caller will see rather than a copy of it.\n const loaded: Record<string, unknown>[] = [];\n\n try {\n if (relation.cardinality === \"one\") {\n const results = await this.relationService.batchFetchRelatedEntities(\n collectionPath, rowIds, key, relation, narrow\n );\n for (const row of addressable) {\n const related = results.get(String(addressOf(row)));\n // `null`, not \"absent\": a to-one relation that reaches\n // nothing is a fact about the row, and leaving the key\n // off makes it indistinguishable from not having asked.\n if (!related) { row[key] = null; continue; }\n // Rendered exactly as the parent is: the include loader\n // attaches these itself, and a target left in the walk's\n // own types is how a date came back as a string at the\n // top level and a `{ __type: \"date\" }` envelope one\n // level down, in one response.\n const [shaped] = this.shapeRelatedRows(\n [toRestValues({ ...related.values }, targetCollection)], node, targetCollection\n );\n row[key] = shaped;\n loaded.push(shaped);\n }\n } else {\n const results = await this.relationService.batchFetchRelatedEntitiesMany(\n collectionPath, rowIds, key, relation, narrow\n );\n for (const row of addressable) {\n const related = results.get(String(addressOf(row))) ?? [];\n const shaped = this.shapeRelatedRows(\n // The junction's own columns ride along under\n // `_pivot` — the link's data, not the target's, so\n // it is nested rather than merged: two posts sharing\n // a tag see one tag row and two different links, and\n // a flattened `role` would collide with a column of\n // the target's own. It stays outside `toRestValues`\n // for the same reason: the target's collection does\n // not describe the link's columns.\n related.map(e => (e.pivot\n ? { ...toRestValues({ ...e.values }, targetCollection), [JUNCTION_PIVOT_KEY]: e.pivot }\n : toRestValues({ ...e.values }, targetCollection))),\n node, targetCollection\n );\n row[key] = shaped;\n loaded.push(...shaped);\n }\n }\n } catch (e) {\n // An `include` that failed is not an `include` that matched\n // nothing. A Postgres error also poisons the surrounding\n // transaction, so swallowing one loses every later relation in\n // the same request too — one failure, several missing fields.\n if (e instanceof ApiError) throw e;\n if (reachedDatabase(e)) throw e;\n logger.warn(`[include] Failed to load relation '${key}' on ${collectionPath}`, { error: e });\n continue;\n }\n\n const children = Object.keys(node.children).length > 0\n ? { wildcard: false, tree: node.children }\n : undefined;\n if (children && loaded.length > 0) {\n await this.loadIncludes(\n loaded, targetCollection, targetCollection.slug, children, depth + 1\n );\n }\n }\n }\n\n /**\n * The SELECT list for a read: the visible columns, narrowed to `fields`.\n *\n * `?fields=` used to be a trim of the *response* — every column read out of\n * the database and most of them thrown away in JavaScript. It is a\n * projection now, so asking for two columns of a wide row reads two\n * columns; that is also what makes `distinct` mean anything.\n *\n * Two things survive a narrowing regardless:\n *\n * - the **primary key**, because a row nobody can address cannot be\n * updated, deleted, or paged past — and `meta.nextCursor` is derived from\n * it, so a projection without it would silently disable seeking. The one\n * exception is `distinct`, below;\n * - the exclusions. `excludeFromApi` and the generated search columns are\n * removed *after* the narrowing, so naming one in `fields` does not\n * un-hide it. That was the shape of the `?searchString=` leak: a path\n * that skipped the projection returned a password hash the plain read\n * correctly withheld.\n *\n * An unknown column name is a 400 rather than a silent omission: a caller\n * who mistypes `?fields=titel` otherwise gets rows without titles and no\n * hint why.\n *\n * **`distinct` drops the primary key.** Keeping it is what the refusal of\n * `distinct` beside a search or vector query already describes: a value\n * that differs on every row makes the whole row distinct by construction,\n * so the query \"would answer 200 having done nothing\". A surrogate key does\n * that more reliably than any score — `?fields=status&distinct=true` came\n * back with one row per row, every one carrying its `id`. So a distinct\n * read is a read of the named columns and nothing else. It addresses no\n * rows, which is the honest shape for one: `cursorFor` finds no key on the\n * row and issues no cursor, and the result is a set of values rather than a\n * set of rows to update or delete.\n */\n private columnProjection(\n table: PgTable<any>,\n collection: CollectionConfig,\n fields: string[] | undefined,\n idInfoArray: { fieldName: string; type: \"string\" | \"number\" }[],\n distinct?: boolean\n ): Record<string, unknown> | undefined {\n const visible = visibleColumnProjection(getTableColumns(table), collection);\n if (!fields || fields.length === 0) return visible;\n\n let tableColumns: Record<string, unknown>;\n try {\n tableColumns = getTableColumns(table) as Record<string, unknown>;\n } catch {\n // Not a real drizzle table — a stub in a test, a derived path. No\n // projection is the right answer, as it is for the exclusions.\n return visible;\n }\n const available = visible ?? tableColumns;\n\n const keep = new Set(fields);\n if (!distinct) for (const pk of idInfoArray) keep.add(pk.fieldName);\n const relations = resolveCollectionRelations(collection);\n\n const projection: Record<string, unknown> = {};\n for (const name of keep) {\n const column = available[name];\n if (column !== undefined) { projection[name] = column; continue; }\n // Present on the table but not in `available` means it was excluded\n // — hidden, and staying hidden. Absent from both is a typo.\n if (tableColumns[name] !== undefined) continue;\n // A relation is not a column: `include` loads it, and naming it in\n // `fields` too is how a caller says to return it. Read as a typo,\n // `fields=title,author&include=author` was a 400 on every door.\n if (name in relations) continue;\n throw ApiError.badRequest(\n `Unknown field '${name}' in \\`fields\\` on collection `\n + `'${collection.slug ?? collection.name}'. Valid fields: `\n + Object.keys(available).sort().join(\", \"),\n \"UNKNOWN_FIELD\",\n { field: name, collection: collection.slug ?? collection.name }\n );\n }\n return projection;\n }\n\n /**\n * The opaque cursor that continues a listing after `row`. See\n * {@link RestFetchService.cursorFor}.\n *\n * Here rather than at the route because deriving it needs the collection's\n * primary key, which may be named anything and span several columns — the\n * driver's knowledge, not the HTTP layer's.\n */\n cursorFor(\n collectionPath: string,\n row: Record<string, unknown>,\n orderBy?: OrderByTuple[]\n ): string | undefined {\n // Relevance is computed per query rather than stored, so there is no\n // value on this row to compare a later page against — and two requests\n // with different search strings produce scores that are not on the same\n // scale at all. `buildCursorConditions` refuses such a cursor; issuing\n // one here would be handing out a string whose only use is a 400.\n if (orderBy?.some(([field]) => field === FetchService.SCORE_FIELD)) return undefined;\n // A vector read orders by distance whatever `orderBy` says, so the same\n // holds for its rows — which are the ones carrying a distance.\n if (FetchService.DISTANCE_FIELD in row) return undefined;\n try {\n const collection = getCollectionByPath(collectionPath, this.registry);\n const pks = getPrimaryKeys(collection, this.registry);\n if (pks.length === 0) return undefined;\n const address = buildCompositeId(row, pks);\n if (!address || !address.split(COMPOSITE_ID_SEPARATOR).some(part => part !== \"\")) return undefined;\n // A single key's own value, so it compares against the column as\n // it came; a composite key's whole address, because the keyset\n // comparison ends on every key column and the first alone ties\n // every row that shares it.\n return encodeCursor(orderBy, row, pks.length === 1 ? row[pks[0].fieldName] : address);\n } catch {\n // A path with no registered collection — a nested or derived one.\n // No cursor is the honest answer; the listing pages by offset.\n return undefined;\n }\n }\n\n /**\n * Every relation of one row, as the refs the admin renders, attached in\n * place.\n *\n * Loaded by the batched loaders an `include` takes, so a target reached from\n * this row is read as the caller may see it: its `beforeQuery` scope and its\n * soft delete applied, its unreadable fields stripped. Populated through\n * drizzle's relational `with`, the single get knew neither, and a\n * tenant-scoped user fetching an owner was handed another tenant's docs —\n * and the trashed ones — under a relation `?include=` answered correctly.\n *\n * A to-many relation that reaches nothing is `[]`; a to-one is left off. A\n * relation that fails for a reason other than the database is logged and\n * left off, as on the include path, rather than failing the whole row.\n */\n private async loadRelationRefs(\n row: Record<string, unknown>,\n address: string,\n collection: CollectionConfig,\n collectionPath: string\n ): Promise<void> {\n if (!address) return;\n const toRef = (related: { id: string | number; path: string; values: Record<string, unknown> }) =>\n createRelationRefWithData(related.id, related.path, {\n id: related.id,\n path: related.path,\n values: related.values\n });\n\n for (const [key, relation] of Object.entries(resolveCollectionRelations(collection))) {\n try {\n if (relation.cardinality === \"one\") {\n const related = (await this.relationService.batchFetchRelatedEntities(\n collectionPath, [address], key, relation\n )).get(address);\n if (related) row[key] = toRef(related);\n } else {\n const related = (await this.relationService.batchFetchRelatedEntitiesMany(\n collectionPath, [address], key, relation\n )).get(address) ?? [];\n row[key] = related.map(toRef);\n }\n } catch (e) {\n if (e instanceof ApiError) throw e;\n if (reachedDatabase(e)) throw e;\n logger.warn(`[FetchService] Could not load relation '${key}' on ${collectionPath}`, { error: e });\n }\n }\n }\n\n /**\n * Extract cursor pagination conditions from startAfter options.\n *\n * \"Every row that sorts after this one\", written out as a comparison over\n * the same keys the `ORDER BY` uses and ending on the same primary key,\n * descending. With one sort key that is the familiar\n * `k > v OR (k = v AND id < cursorId)`; with several it nests, each key's\n * tie handing the decision to the next.\n */\n private buildCursorConditions(\n table: PgTable<any>,\n idInfoArray: PrimaryKeyInfo[],\n options: { orderBy?: string | OrderByTuple[]; order?: \"desc\" | \"asc\"; startAfter?: Record<string, unknown> },\n collectionPath: string\n ): SQL[] {\n if (!options.startAfter) return [];\n const cursor = options.startAfter;\n const keys = normalizeDriverOrderBy(options.orderBy, options.order);\n const keyColumns = FetchService.keyColumns(table, idInfoArray, collectionPath);\n const cursorKey = FetchService.cursorKey(cursor, idInfoArray, collectionPath);\n\n if (keys) {\n // Relevance is computed per query, not stored, so there is no value\n // on the cursor row to compare a later page against — and two\n // requests with different search strings would produce scores that\n // are not on the same scale at all. Refusing is the only honest\n // answer: a dropped cursor condition silently repeats and skips\n // rows, which is precisely what paging exists to prevent.\n if (keys.some(([field]) => field === FetchService.SCORE_FIELD)) {\n throw ApiError.badRequest(\n \"Cursor pagination (`startAfter`) cannot be combined with `orderBy: \\\"_score\\\"`. \" +\n \"Relevance is computed per query rather than stored, so it cannot key a cursor. \" +\n \"Use `limit`/`offset` for relevance-ordered pages, or order by a column.\",\n \"SCORE_CURSOR_UNSUPPORTED\",\n { field: FetchService.SCORE_FIELD }\n );\n }\n const collection = getCollectionByPath(collectionPath, this.registry);\n const resolved = this.resolveOrderKeys(\n table, keys, collection, undefined, collectionPath,\n // An aggregate key correlates on a single key column — a\n // composite-key collection is refused one — so only a single\n // key's value can pin its subquery to the cursor row.\n cursorKey?.length === 1 ? cursorKey[0] : undefined\n );\n\n if (resolved.length > 0 && cursorKey) {\n const cursorValues = cursor.values as Record<string, unknown> | undefined;\n // `in`, not `??`: a cursor row whose sort value is genuinely\n // NULL is a row this has to be able to page past, and `??`\n // read it as \"the cursor did not carry this key\" and dropped\n // the whole condition.\n const values = resolved.map(({ field, cursorTarget }) => cursorTarget\n // An aggregate is not stored on the row, so the cursor\n // never carried it and never could. Its value is recomputed\n // from the cursor id instead, in SQL, by `cursorTarget` —\n // there is nothing for this list to supply.\n ? null\n : (cursorValues && field in cursorValues) ? cursorValues[field] : cursor[field]);\n // Every key needs a value from the cursor row. A missing one\n // cannot be guessed, and a comparison built from the keys that\n // happen to be present is not the same comparison — so this\n // falls through to no cursor condition, which is what a single\n // missing sort value has always done here.\n if (values.every((value, i) => resolved[i].cursorTarget || value !== undefined)) {\n const seekValues = values.map((value, i) =>\n this.preciseCursorValue(table, resolved[i].target, value, keyColumns, cursorKey));\n return [this.buildKeysetComparison(resolved, seekValues, keyColumns, cursorKey)];\n }\n }\n } else if (cursorKey) {\n return [FetchService.keyAfter(keyColumns, cursorKey)];\n }\n\n return [];\n }\n\n /** The table's primary key columns, in key order. */\n private static keyColumns(table: PgTable<any>, idInfoArray: PrimaryKeyInfo[], collectionPath: string): AnyPgColumn[] {\n return idInfoArray.map(info => {\n const column = table[info.fieldName as keyof typeof table] as AnyPgColumn | undefined;\n if (!column) {\n throw new Error(`ID field '${info.fieldName}' not found in table for collection '${collectionPath}'`);\n }\n return column;\n });\n }\n\n /**\n * The cursor row's key, one value per key column — or `undefined` for a\n * cursor that names no row.\n *\n * A cursor carries the row's address, which for a composite key is every\n * part joined (see {@link cursorFor}). One that carries fewer parts than\n * the key has columns — the first column's value alone, say — cannot say\n * which row it stopped at, and is refused rather than read as a guess.\n */\n private static cursorKey(\n cursor: Record<string, unknown>,\n idInfoArray: PrimaryKeyInfo[],\n collectionPath: string\n ): unknown[] | undefined {\n const address = cursor.id;\n if (idInfoArray.length === 1) {\n const value = address ?? cursor[idInfoArray[0].fieldName];\n return value === undefined || value === null ? undefined : [value];\n }\n if (address === undefined || address === null) {\n const parts = idInfoArray.map(info => cursor[info.fieldName]);\n return parts.some(part => part === undefined || part === null) ? undefined : parts;\n }\n let parsed: Record<string, string | number>;\n try {\n parsed = parseIdValues(String(address), idInfoArray);\n } catch {\n throw ApiError.badRequest(\n `Invalid cursor: its row id does not name every key column of '${collectionPath}' ` +\n `(${idInfoArray.map(info => info.fieldName).join(\", \")}). Pass back the \\`nextCursor\\` ` +\n \"from the previous page unchanged — it is opaque and must not be built by hand.\",\n \"INVALID_CURSOR\"\n );\n }\n return idInfoArray.map(info => parsed[info.fieldName]);\n }\n\n /**\n * \"Sorts after the cursor row\" on the key alone: the key descending, as the\n * `ORDER BY` ends.\n *\n * Every column runs the same direction, so for a composite key the\n * row-value comparison is exactly the lexicographic order the `ORDER BY`\n * produces.\n */\n private static keyAfter(keyColumns: AnyPgColumn[], cursorKey: unknown[]): SQL {\n if (keyColumns.length === 1) return lt(keyColumns[0], cursorKey[0]);\n const values = keyColumns.map((column, i) => sql.param(cursorKey[i], column));\n return sql`(${sql.join(keyColumns, sql`, `)}) < (${sql.join(values, sql`, `)})`;\n }\n\n /**\n * The cursor row's value for a timestamp key, to the microsecond.\n *\n * A cursor is built from the row as served, and a timestamp is served to\n * the millisecond while Postgres stores it to the microsecond. Seeking past\n * the served value compares the column against something up to 999µs short\n * of the cursor row's own, so every row in that sliver lands on the wrong\n * side of it: paging `createdAt desc` over rows written in one transaction\n * stopped after the first page, and ascending repeated the cursor row\n * forever.\n *\n * So the exact value is read back from the cursor row by its key — but only\n * while that row still holds the millisecond the cursor carries. A row\n * deleted or re-stamped since the page was served falls back to the stored\n * value, which is where the listing was when the cursor was issued.\n *\n * Any other key, and a NULL, is returned as it came.\n */\n private preciseCursorValue(\n table: PgTable<any>,\n target: AnyPgColumn | SQL,\n value: unknown,\n keyColumns: AnyPgColumn[],\n cursorKey: unknown[]\n ): unknown {\n if (value === null || value === undefined) return value;\n if (!is(target, PgTimestamp) && !is(target, PgTimestampString)) return value;\n // A column that keeps no more than milliseconds is served whole.\n if (target.precision !== undefined && target.precision <= 3) return value;\n\n const stored = sql`CAST(${value instanceof Date ? value.toISOString() : String(value)} AS ${sql.raw(target.getSQLType())})`;\n const alias = sql.identifier(\"__cursor_row\");\n const column = sql`${alias}.${sql.identifier(target.name)}`;\n const cursorRow = sql.join(keyColumns.map((key, i) =>\n sql`${alias}.${sql.identifier(key.name)} = ${sql.param(cursorKey[i], key)}`), sql` AND `);\n return sql`COALESCE((SELECT ${column} FROM ${table} AS ${alias} WHERE ${cursorRow} AND ${column} >= ${stored} AND ${column} < ${stored} + INTERVAL '1 millisecond' LIMIT 1), ${stored})`;\n }\n\n /**\n * \"Sorts strictly after the cursor row\", over `keys` and then the primary key.\n *\n * Built by recursion rather than as a row-value comparison — `(a, b) > (x, y)`\n * would be shorter, but it is only correct when every key runs the same\n * direction, and `roles ASC, created_at DESC` is exactly the case this\n * exists to serve.\n *\n * NULLs are compared by the rule Postgres sorts them under (last ascending,\n * first descending) rather than by `>`/`<`, which answer *unknown* against\n * NULL and therefore match nothing. Ordering by a nullable column and paging\n * used to drop every row whose sort value was NULL from page two onward.\n */\n private buildKeysetComparison(\n keys: ResolvedOrderKey[],\n values: unknown[],\n keyColumns: AnyPgColumn[],\n cursorKey: unknown[],\n index = 0\n ): SQL {\n // Past the last key, the primary key settles it. It is ordered `DESC`,\n // so \"after\" the cursor row means a smaller key.\n if (index >= keys.length) return FetchService.keyAfter(keyColumns, cursorKey);\n\n const { direction, cursorTarget } = keys[index];\n // Which end the NULLs sort at — the direction's convention unless the\n // key stated one. Read from the same place `buildOrderExpressions`\n // reads it, because a comparison that disagrees with the ORDER BY it\n // pages over repeats and skips rows and reports nothing wrong.\n const nullsLast = FetchService.nullsLast(keys[index]);\n // A column or an expression. `_score` is an expression too, but a\n // cursor over relevance is refused before this is reached; an aggregate\n // over a relation is the one that gets here. Drizzle's comparison\n // helpers are typed per operand kind, so the union has to be resolved\n // here rather than at the call site.\n const target = keys[index].target as AnyPgColumn;\n const value = values[index];\n const rest = this.buildKeysetComparison(keys, values, keyColumns, cursorKey, index + 1);\n\n // No stored value to compare against — the cursor row's is recomputed\n // by an expression instead, and whether it is NULL is a question only\n // SQL can answer. So both branches of the null test below have to exist\n // in the statement rather than being chosen here.\n //\n // `cursorTarget` references only the cursor id, never the outer row, so\n // Postgres evaluates it once for the whole statement rather than per\n // row — repeating it across the branches costs nothing.\n // \"Sorts after\" for two non-null values is decided by the *direction*;\n // where the NULLs sit is decided by `nullsLast`. The two used to be the\n // same test, which was correct only while the placement was the\n // direction's default and silently wrong the moment a key named one.\n const after = (a: AnyPgColumn | SQL, b: unknown) =>\n direction === \"asc\" ? gt(a as AnyPgColumn, b) : lt(a as AnyPgColumn, b);\n const afterSql = (a: AnyPgColumn | SQL, b: SQL) =>\n direction === \"asc\" ? sql`${a} > ${b}` : sql`${a} < ${b}`;\n\n if (cursorTarget) {\n return nullsLast\n // NULLS LAST. A cursor row among the NULLs has only later NULLs\n // after it; otherwise everything that sorts later, then the\n // NULLs, then the ties.\n ? or(\n and(isNull(cursorTarget), isNull(target), rest),\n and(\n isNotNull(cursorTarget),\n or(\n afterSql(target, cursorTarget),\n isNull(target),\n and(sql`${target} = ${cursorTarget}`, rest)\n )\n )\n )!\n // NULLS FIRST. A cursor row among the NULLs still has every\n // non-null row after it; a non-null cursor row has the NULLs\n // already behind it.\n : or(\n and(isNull(cursorTarget), or(isNotNull(target), and(isNull(target), rest))),\n and(\n isNotNull(cursorTarget),\n or(\n afterSql(target, cursorTarget),\n and(sql`${target} = ${cursorTarget}`, rest)\n )\n )\n )!;\n }\n\n if (value === null) {\n // The cursor row sorts among the NULLs.\n return nullsLast\n // NULLS LAST: nothing non-null is left, so only later NULLs.\n ? and(isNull(target), rest)!\n // NULLS FIRST: every non-null row is still ahead, plus later NULLs.\n : or(isNotNull(target), and(isNull(target), rest))!;\n }\n\n return nullsLast\n // NULLS LAST, so the NULLs are still ahead of a non-null cursor row.\n ? or(after(target, value), isNull(target), and(eq(target, value), rest))!\n // NULLS FIRST, so they are behind it and nothing has to admit them.\n : or(after(target, value), and(eq(target, value), rest))!;\n }\n\n /**\n * Compile \"rows reachable from this parent\" into a `WHERE` condition on the\n * target table, so a nested listing can run as an ordinary collection query.\n */\n private buildRelationScope(hop: NestedPathHop): SQL {\n const parentPks = requirePrimaryKeys(hop.parentCollection, this.registry);\n const parsedParent = parseIdValues(hop.parentId, parentPks);\n // What a single-column link compares against. A link from a composite\n // key has no such value, and `findRelationDefects` refuses one at boot.\n const parsedParentId = parsedParent[parentPks[0].fieldName];\n\n // The parent row by every key column, for the shapes that read it.\n const parent = () => {\n const table = getTableForCollection(hop.parentCollection, this.registry);\n const columns = primaryKeyColumns(table, parentPks, hop.parentCollection.slug);\n return {\n table,\n key: columns.map((column, i) => ({ column, value: parsedParent[parentPks[i].fieldName] }))\n };\n };\n\n const targetTable = getTableForCollection(hop.targetCollection, this.registry);\n const targetPks = requirePrimaryKeys(hop.targetCollection, this.registry);\n const targetIdColumn = targetTable[targetPks[0].fieldName as keyof typeof targetTable] as AnyPgColumn;\n if (!targetIdColumn) {\n throw new Error(`ID field '${targetPks[0].fieldName}' not found in table for collection '${hop.targetCollection.slug}'`);\n }\n\n return DrizzleConditionBuilder.buildRelationScopeCondition(\n hop.relation,\n parent,\n parsedParentId as string | number,\n targetTable,\n targetIdColumn,\n this.registry\n );\n }\n\n /**\n * Whether `id` is actually reachable at `collectionPath`.\n *\n * Trivially true for a root path. For a nested one it is a real question:\n * the path resolves to the target collection, and matching on the primary\n * key alone made the parent segment decorative — `authors/1/posts/43`\n * returned post 43 whoever wrote it, and the REST layer's delete then\n * deleted it. A row that is not under this parent is reported as absent,\n * which is what a caller addressing it through the parent should see.\n */\n private async isAddressableUnder(collectionPath: string, id: string | number): Promise<boolean> {\n if (!isNestedPath(collectionPath)) return true;\n const hop = resolveNestedPath(collectionPath, this.registry);\n if (!hop) return true;\n return this.relationService.isRelated(hop, id);\n }\n\n /**\n * Fetch a single row by ID\n */\n async fetchOne<M extends Record<string, unknown>>(\n collectionPath: string,\n id: string | number,\n databaseId?: string,\n withDeleted?: WithDeleted\n ): Promise<Record<string, unknown> | undefined> {\n if (!await this.isAddressableUnder(collectionPath, id)) return undefined;\n\n const collection = getCollectionByPath(collectionPath, this.registry);\n const table = getTableForCollection(collection, this.registry);\n const idInfoArray = requirePrimaryKeys(collection, this.registry);\n\n // An address the key columns cannot hold names no row — the same answer\n // as a well-formed id nobody has. Asking Postgres instead raises 22P02\n // and aborts the transaction around this read.\n if (!idCanAddressTable(id, table, idInfoArray)) return undefined;\n\n const addressed = rowIdentityCondition(table, idInfoArray, id, collectionPath);\n\n // Part of the identity, so a row the hook excludes is absent here\n // exactly as it is from the listing.\n const narrowing = await this.narrowReadCondition(\n collectionPath, collection, table, \"get\", {}\n );\n const identity = narrowing\n ? and(addressed, narrowing) as SQL\n : addressed;\n\n // Primary path: db.query.findFirst for the row, then its relations\n const tableName = getTableName(table);\n\n const qb = this.getQueryBuilder(tableName);\n if (qb) {\n // `null` until the query answers: `undefined` is its answer for a\n // row that is not there.\n let row: Record<string, unknown> | undefined | null = null;\n try {\n const hidden = hiddenColumnsOption(getTableColumns(table), collection);\n\n row = await qb.findFirst({\n // Soft delete: a stamped row answers 404 like any other\n // absent one, so `findById` and `find` agree about which\n // rows exist.\n where: andSoftDelete(identity, collection, table, withDeleted),\n ...(hidden ? { columns: hidden } : {})\n } as Parameters<NonNullable<typeof qb>[\"findFirst\"]>[0]);\n } catch (e) {\n if (reachedDatabase(e)) throw e;\n logger.warn(`[FetchService] db.query.findFirst failed for ${collectionPath}, falling back to db.select`, { error: e });\n }\n\n if (row === undefined) return undefined;\n if (row !== null) {\n const flatRow = toFlatRow(row, collection, this.registry);\n await this.loadRelationRefs(flatRow, buildCompositeId(row, idInfoArray), collection, collectionPath);\n\n // Again, now the relations are on the row: a relation property\n // the caller may not read is withheld like any other field.\n return stripUnreadable(flatRow, collection);\n }\n }\n\n // Fallback: db.select, then the same relation loader\n const visibleOne = visibleColumnProjection(getTableColumns(table), collection);\n const result = await this.db\n .select(visibleOne as never)\n .from(table)\n .where(andSoftDelete(identity, collection, table, withDeleted))\n .limit(1);\n\n if (result.length === 0) return undefined;\n\n const raw = result[0] as M;\n // Without `db`/`registry`: the parse only types the columns and turns an\n // owned foreign key into a ref. Given them, it queried each inverse\n // relation itself, with neither the target's scope nor its soft delete.\n const values = await parseDataFromServer(raw, collection) as Record<string, unknown>;\n await this.loadRelationRefs(values, buildCompositeId(raw, idInfoArray), collection, collectionPath);\n\n // The primary path strips inside `toFlatRow`; this one parses the row\n // itself, so it strips itself. Without it a fetch that fell back served\n // every declared column, `excludeFromApi` ones included.\n stripUnreadable(values, collection);\n\n return {\n ...values,\n id: id.toString()\n };\n }\n\n /**\n * Unified method to fetch rows with optional search functionality\n */\n async fetchRowsWithConditions<M extends Record<string, unknown>>(\n collectionPath: string,\n options: {\n filter?: FilterValues<Extract<keyof M, string>>;\n orderBy?: string | OrderByTuple[];\n order?: \"desc\" | \"asc\";\n limit?: number;\n offset?: number;\n startAfter?: Record<string, unknown>;\n searchString?: string;\n searchExplain?: boolean;\n databaseId?: string;\n vectorSearch?: VectorSearchParams;\n logical?: LogicalCondition;\n /** Narrow to the rows reachable from a parent through a relation. */\n relatedTo?: NestedPathHop;\n /** See `FetchCollectionProps.withDeleted`. */\n withDeleted?: WithDeleted;\n /**\n * Relations to load. **Absent means none** — the same as it means\n * over REST.\n *\n * It used to mean *all of them*, unconditionally, and this is the\n * method the realtime refetch goes through. So a subscription was\n * pushed rows carrying every relation the collection declares while\n * the identical `find()` returned rows carrying none, and a client\n * that rendered both saw the row change shape when a write landed.\n */\n include?: IncludeSpec;\n /** Columns to read — a projection pushed into the SELECT. */\n fields?: string[];\n /** `SELECT DISTINCT` over the projection. */\n distinct?: boolean;\n } = {}\n ): Promise<Record<string, unknown>[]> {\n const collection = getCollectionByPath(collectionPath, this.registry);\n const table = getTableForCollection(collection, this.registry);\n const idInfoArray = requirePrimaryKeys(collection, this.registry);\n const idInfo = idInfoArray[0];\n const idField = table[idInfo.fieldName as keyof typeof table] as AnyPgColumn;\n\n if (!idField) {\n throw new Error(`ID field '${idInfo.fieldName}' not found in table for collection '${collectionPath}'`);\n }\n\n // ONE read, then relations.\n //\n // This method used to hold a `db.query.findMany` branch and a full\n // second copy of the `db.select` builder below it — the same\n // conditions, ordering, vector and rank handling as\n // `fetchRowsWithConditionsRaw`, written out twice. Two copies of a\n // query builder is two answers to every question asked of it, and they\n // had already drifted: only one of them read `startAfter`, so cursor\n // paging worked on one path and silently did nothing on the other.\n const results = await this.fetchRowsWithConditionsRaw<M>(collectionPath, options);\n\n return this.processRowResults<M>(\n results, collection, collectionPath, idInfo, options.databaseId,\n normalizeInclude(options.include), idInfoArray\n );\n }\n\n /**\n * Fallback path used when db.query is unavailable.\n *\n * The primary path runs the results through `toFlatRow`, which maps\n * relations from what drizzle already nested — no query per row. This one\n * has no nesting to read, so it resolves relations itself, in batches.\n *\n * Process raw database results into flat rows with relations.\n */\n private async processRowResults<M extends Record<string, unknown>>(\n results: Record<string, unknown>[],\n collection: CollectionConfig,\n collectionPath: string,\n idInfo: { fieldName: string; type: \"string\" | \"number\" },\n _databaseId?: string,\n /**\n * The relations to load. **Absent means none.**\n *\n * It was a `skipRelations` boolean defaulting to *load everything*, and\n * that default is what made the realtime refetch return rows of a\n * different shape from the REST list serving the identical query. The\n * parameter now says which relations rather than whether, so the two\n * callers state the same thing and neither can quietly mean \"all\".\n */\n include?: NormalizedInclude,\n _idInfoArray?: { fieldName: string; type: \"string\" | \"number\" }[]\n ): Promise<Record<string, unknown>[]> {\n if (results.length === 0) return [];\n\n // First pass: parse all rows WITHOUT per-row relation queries.\n // We deliberately omit db/registry so parseDataFromServer only does type\n // coercion (dates, numbers, FK→relation stubs for owning relations) and\n // does NOT issue individual SQL queries for inverse relations. The\n // include pass below batch-loads what the caller asked for, one query\n // per relation per level, avoiding the N+1 that plagued the old path.\n void idInfo;\n const values = await Promise.all(results.map(async (rawRow: Record<string, unknown>) =>\n await parseDataFromServer(rawRow as M, collection) as Record<string, unknown>));\n\n // Second pass: the relations the caller named, through the same batched\n // loader every other read uses — so a row from here and a row from the\n // REST list carry the same relations, loaded the same way.\n if (include) {\n await this.loadIncludes(values, collection, collectionPath, include);\n }\n\n // Last, once the includes have read the key columns they group by: the\n // fields this caller may not read. The REST list strips in `toRestRow`\n // and the single get in `toFlatRow`; this parse is the third exit from\n // the pipeline and it is what the socket's `FETCH_COLLECTION`, a text\n // search and the MCP tools are served from. Without it every one of\n // them handed a plain user each row's password hash.\n for (const row of values) stripUnreadable(row, collection);\n\n // Columns only — the address is the consumer's to derive.\n return values;\n }\n\n /**\n * Fetch a collection of rows\n */\n async fetchCollection<M extends Record<string, unknown>>(\n collectionPath: string,\n options: {\n filter?: FilterValues<Extract<keyof M, string>>;\n /**\n * An `or(...)`/`and(...)` group, applied alongside `filter`.\n *\n * `fetchRowsWithConditions` below has always applied this; it was\n * simply absent from this signature, so the only callers that could\n * pass one were the ones that went around this method. Realtime\n * came through here, which is why a subscription filtered by a\n * logical group was pushed every row in the table.\n */\n logical?: LogicalCondition;\n orderBy?: string | OrderByTuple[];\n order?: \"desc\" | \"asc\";\n limit?: number;\n offset?: number;\n startAfter?: Record<string, unknown>;\n searchString?: string;\n searchExplain?: boolean;\n databaseId?: string;\n vectorSearch?: VectorSearchParams;\n /** Relations to load. Absent means none — see `fetchRowsWithConditions`. */\n include?: IncludeSpec;\n /** Columns to read — a projection pushed into the SELECT. */\n fields?: string[];\n /** `SELECT DISTINCT` over the projection. */\n distinct?: boolean;\n /** See `FetchCollectionProps.withDeleted`. */\n withDeleted?: WithDeleted;\n } = {}\n ): Promise<Record<string, unknown>[]> {\n // A nested path is the target collection narrowed by a relation — the\n // same query, one condition heavier. It used to be a separate builder\n // that honoured `limit` and nothing else.\n //\n // `relatedTo` is always set, to `undefined` for a root path: these\n // options arrive from a socket frame spread whole, and the scope is\n // this method's to derive from the path, never the caller's to send.\n const hop = isNestedPath(collectionPath) ? resolveNestedPath(collectionPath, this.registry) : undefined;\n return this.fetchRowsWithConditions<M>(\n hop ? hop.targetCollection.slug : collectionPath,\n { ...options, relatedTo: hop }\n );\n }\n\n /**\n * Search rows by text\n */\n async searchRows<M extends Record<string, unknown>>(\n collectionPath: string,\n searchString: string,\n options: {\n filter?: FilterValues<Extract<keyof M, string>>;\n /**\n * An `or(...)`/`and(...)` group, applied alongside `filter`.\n *\n * `fetchRowsWithConditions` has always applied one; it was missing\n * from this signature, so a realtime search subscription carrying a\n * group could not pass it on and served every row matching the text\n * that RLS allowed.\n */\n logical?: LogicalCondition;\n orderBy?: string | OrderByTuple[];\n order?: \"desc\" | \"asc\";\n limit?: number;\n databaseId?: string;\n /** Ask each row which declared search field matched. */\n searchExplain?: boolean;\n } = {}\n ): Promise<Record<string, unknown>[]> {\n return this.fetchRowsWithConditions<M>(collectionPath, {\n ...options,\n searchString\n });\n }\n\n /**\n * Count rows in a collection\n */\n async count<M extends Record<string, unknown>>(\n collectionPath: string,\n options: {\n filter?: FilterValues<Extract<keyof M, string>>;\n logical?: LogicalCondition;\n searchString?: string;\n databaseId?: string;\n /**\n * Only the `threshold` half of a vector search narrows a count: the\n * distance ordering and the `_distance` column change which rows\n * come back first, not how many there are. Omitting it here left\n * `meta.total` counting rows the threshold had excluded, so a\n * request that was served three rows was told there were nine.\n */\n vectorSearch?: VectorSearchParams;\n /** See `FetchCollectionProps.withDeleted`. */\n withDeleted?: WithDeleted;\n } = {}\n ): Promise<number> {\n // Same narrowing as the listing — and, unlike the count it replaces,\n // the same `filter` and `searchString` too, so `total` describes the\n // rows that were actually served.\n const hop = isNestedPath(collectionPath) ? resolveNestedPath(collectionPath, this.registry) : undefined;\n const effectivePath = hop ? hop.targetCollection.slug : collectionPath;\n\n const collection = getCollectionByPath(effectivePath, this.registry);\n const table = getTableForCollection(collection, this.registry);\n\n let query = this.db.select({ count: count() }).from(table).$dynamic();\n const allConditions: SQL[] = [];\n\n if (hop) allConditions.push(this.buildRelationScope(hop));\n\n // Soft delete. A listing that filters and a count that does not is a\n // page saying \"1 of 4 results\".\n withSoftDelete(allConditions, collection, table, options.withDeleted);\n\n if (options.searchString) {\n const searchConditions = DrizzleConditionBuilder.buildSearchConditions(\n options.searchString, collection.properties, table, collection\n );\n if (searchConditions.length === 0) return 0;\n allConditions.push(DrizzleConditionBuilder.combineConditionsWithOr(searchConditions)!);\n }\n\n if (options.filter) {\n const filterConditions = this.buildFilterConditions(options.filter, table, effectivePath);\n if (filterConditions.length > 0) allConditions.push(...filterConditions);\n }\n\n if (options.logical) {\n const logicalCondition = DrizzleConditionBuilder.buildLogicalConditions(\n options.logical, table, effectivePath, this.filterContext(effectivePath, table)\n );\n if (logicalCondition) allConditions.push(logicalCondition);\n }\n\n // The same narrowing the listing applies. A `beforeQuery` honoured by\n // one and not the other is a page reading \"1 of 4 results\" — the same\n // failure mode as the soft-delete condition above, which is why they sit\n // together.\n allConditions.push(...await this.narrowRead(\n effectivePath, collection, table, \"count\",\n FetchService.describeRead({ ...options, relatedTo: hop })\n ));\n\n // A `threshold` genuinely narrows the row set on the fetch path, and\n // this count did not apply it — so a similarity-filtered listing\n // reported the size of the *unfiltered* set, and `hasMore` stayed true\n // over pages that were already empty. Only the threshold narrows it:\n // the ORDER BY and the `_distance` projection change which rows come\n // first and what rides along with them, not how many there are.\n if (options.vectorSearch) {\n // Built for any vector search rather than only a thresholded one,\n // because this is also where an unknown or non-vector\n // `vector_search` property is refused with a 400. Without a\n // threshold it contributes no filter, so the count is unchanged and\n // what is gained is that `/count` refuses the request the listing\n // refuses instead of answering it with a number.\n const vectorMeta = DrizzleConditionBuilder.buildVectorSearchConditions(table, options.vectorSearch);\n if (vectorMeta.filter) allConditions.push(vectorMeta.filter);\n }\n\n if (allConditions.length > 0) {\n const finalCondition = DrizzleConditionBuilder.combineConditionsWithAnd(allConditions);\n if (finalCondition) query = query.where(finalCondition);\n }\n\n const result = await query;\n return Number(result[0]?.count || 0);\n }\n\n /**\n * `count`/`sum`/`avg`/`min`/`max`, optionally grouped.\n *\n * The gap this fills is narrow and constant: every dashboard wants \"revenue\n * by status\" and \"orders per day\", and without it the options were a custom\n * function holding hand-written SQL, or fetching every row and reducing in\n * JavaScript — which is wrong at any size that matters, and silently wrong\n * under a `limit`.\n *\n * It runs through the same request-scoped handle as every other read, so\n * **RLS applies to the rows being aggregated**. That is the property worth\n * protecting here: an aggregate is an effective way to read data you cannot\n * select, and `count(*)` over a table whose policies would return nothing\n * has to be zero.\n */\n async aggregate<M extends Record<string, unknown>>(\n collectionPath: string,\n options: {\n aggregates: { fn: \"count\" | \"sum\" | \"avg\" | \"min\" | \"max\"; field?: string; alias: string }[];\n groupBy?: string[];\n filter?: FilterValues<Extract<keyof M, string>>;\n logical?: LogicalCondition;\n searchString?: string;\n limit?: number;\n /** Groups to skip. Applied only to a grouped aggregate, like `limit`. */\n offset?: number;\n /**\n * Sort for the groups: by a `groupBy` field or an aggregate's alias.\n * Applied only to a grouped aggregate; see {@link aggregateOrdering}.\n */\n orderBy?: OrderByTuple[];\n /** See `FetchCollectionProps.withDeleted`. */\n withDeleted?: WithDeleted;\n }\n ): Promise<Record<string, unknown>[]> {\n const collection = getCollectionByPath(collectionPath, this.registry);\n const table = getTableForCollection(collection, this.registry);\n const columns = getTableColumns(table);\n\n const columnFor = (field: string, forWhat: string): AnyPgColumn => {\n const column = columns[field as keyof typeof columns] as AnyPgColumn | undefined;\n if (!column) {\n throw ApiError.badRequest(\n `Unknown field '${field}' in ${forWhat}. Valid fields: ${Object.keys(columns).sort().join(\", \")}`,\n \"UNKNOWN_AGGREGATE_FIELD\"\n );\n }\n return column;\n };\n\n const selection: Record<string, SQL> = {};\n\n for (const aggregate of options.aggregates) {\n if (aggregate.fn === \"count\" && !aggregate.field) {\n selection[aggregate.alias] = sql`count(*)`;\n continue;\n }\n const column = columnFor(aggregate.field as string, `${aggregate.fn}()`);\n switch (aggregate.fn) {\n case \"count\": selection[aggregate.alias] = sql`count(${column})`; break;\n // Cast through numeric so what comes back is a string this\n // method parses, rather than a float whose precision depends on\n // the column type — `avg` over an integer column is otherwise\n // one shape here and another there.\n case \"sum\": selection[aggregate.alias] = sql`sum(${column})::numeric`; break;\n case \"avg\": selection[aggregate.alias] = sql`avg(${column})::numeric`; break;\n case \"min\": selection[aggregate.alias] = sql`min(${column})`; break;\n case \"max\": selection[aggregate.alias] = sql`max(${column})`; break;\n }\n }\n\n const groupColumns = (options.groupBy ?? []).map(field => ({\n field,\n column: columnFor(field, \"groupBy\")\n }));\n for (const group of groupColumns) {\n selection[group.field] = sql`${group.column}`;\n }\n\n let query = this.db.select(selection).from(table).$dynamic();\n\n const conditions: SQL[] = [];\n // Soft delete. A dashboard that sums deleted orders is reporting\n // revenue that was withdrawn.\n withSoftDelete(conditions, collection, table, options.withDeleted);\n if (options.searchString) {\n const searchConditions = DrizzleConditionBuilder.buildSearchConditions(\n options.searchString, collection.properties, table, collection\n );\n // No searchable field means no row matches — the same impossible\n // WHERE the listing uses, rather than an unfiltered aggregate.\n if (searchConditions.length === 0) return [];\n conditions.push(DrizzleConditionBuilder.combineConditionsWithOr(searchConditions) as SQL);\n }\n if (options.filter) {\n conditions.push(...this.buildFilterConditions(options.filter, table, collectionPath));\n }\n if (options.logical) {\n const logicalCondition = DrizzleConditionBuilder.buildLogicalConditions(\n options.logical, table, collectionPath, this.filterContext(collectionPath, table)\n );\n if (logicalCondition) conditions.push(logicalCondition);\n }\n // An aggregate is an efficient way to learn about rows you cannot\n // select, so it is narrowed by the same hook as the listing. Without\n // this, `count(*)` over a scoped collection reports the unscoped total.\n conditions.push(...await this.narrowRead(\n collectionPath, collection, table, \"aggregate\", FetchService.describeRead(options)\n ));\n if (conditions.length > 0) {\n const finalCondition = DrizzleConditionBuilder.combineConditionsWithAnd(conditions);\n if (finalCondition) query = query.where(finalCondition);\n }\n\n if (groupColumns.length > 0) {\n query = query.groupBy(...groupColumns.map(g => g.column));\n query = query.orderBy(...this.aggregateOrdering(options.orderBy, groupColumns, options.aggregates, selection));\n // Bounded for the same reason a listing is: grouping by a\n // high-cardinality column is a whole table's worth of rows in one\n // response.\n if (options.limit) query = query.limit(options.limit);\n if (options.offset) query = query.offset(options.offset);\n }\n\n const rows = await query as Record<string, unknown>[];\n\n // `count`, `sum` and `avg` arrive as strings: Postgres returns bigint\n // and numeric that way because they do not fit a JS number in general.\n // They do fit for every aggregate anyone puts on a dashboard, and a\n // caller handed `\"12\"` where they expected `12` has to find that out\n // for themselves. Parsed once, here.\n //\n // `min` and `max` over a NUMBER column too, which they were not. Those\n // are uncast, so a `numeric` column returns them as strings — and\n // `?select=avg(price),max(price)` answered `{avg_price: 5,\n // max_price: \"8.5\"}`: two functions, one column, two JSON types, and\n // arithmetic on the second one silently concatenates.\n //\n // Decided by the column's declared type rather than by trying\n // `Number()` on the value, because `min`/`max` are the two that also\n // apply to text and dates. Parsing whatever happens to look numeric\n // would turn a `min(sku)` of `\"00123\"` into `123`, which is a different\n // value, and the `Number.isNaN` guard below would not catch it.\n const numericAliases = numericAggregateAliases(\n options.aggregates,\n (collection?.properties ?? {}) as Record<string, { type?: string }>\n );\n return rows.map(row => {\n const out: Record<string, unknown> = { ...row };\n for (const alias of numericAliases) {\n if (out[alias] === null || out[alias] === undefined) continue;\n const parsed = Number(out[alias]);\n if (!Number.isNaN(parsed)) out[alias] = parsed;\n }\n return out;\n });\n }\n\n /**\n * The `ORDER BY` of a grouped aggregate: the caller's keys, then every\n * group key they did not name.\n *\n * A grouped aggregate is paged like a listing — `limit` bounds it, so more\n * groups than one page holds are reached with `offset` — and a page is only\n * a page over a total order. With no `ORDER BY` at all, Postgres returns the\n * groups in whatever order its plan produces them, so \"the next fifty\" was\n * not a thing that existed. The group keys identify a group, so ending on\n * them makes the order total and puts every page boundary in the same place\n * on every request.\n *\n * A key may name a `groupBy` field or an aggregate by its alias\n * (`count`, `sum_total`), sorted on the aggregate's own expression — a\n * drizzle selection is not aliased in the SQL, so there is no output name\n * to refer to. Anything else is a 400: Postgres would refuse it too, but as\n * a `GROUP BY` error from inside the statement.\n */\n private aggregateOrdering(\n orderBy: OrderByTuple[] | undefined,\n groupColumns: { field: string; column: AnyPgColumn }[],\n aggregates: { alias: string }[],\n selection: Record<string, SQL>\n ): SQL[] {\n const keys: ResolvedOrderKey[] = [];\n const named = new Set<string>();\n for (const [field, direction, nulls] of orderBy ?? []) {\n const group = groupColumns.find(g => g.field === field);\n const aggregate = aggregates.find(a => a.alias === field);\n const target = group?.column ?? (aggregate ? selection[aggregate.alias] : undefined);\n if (!target) {\n const sortable = [...groupColumns.map(g => g.field), ...aggregates.map(a => a.alias)];\n throw ApiError.badRequest(\n `An aggregate cannot be sorted by '${field}'. It sorts by what it returns: ` +\n `${sortable.map(name => `'${name}'`).join(\", \")}.`,\n \"INVALID_ORDER_BY\"\n );\n }\n keys.push({ field, direction, nulls, target });\n named.add(field);\n }\n for (const group of groupColumns) {\n if (!named.has(group.field)) keys.push({ field: group.field, direction: \"asc\", target: group.column });\n }\n return this.buildOrderExpressions(keys, []);\n }\n\n /**\n * Check if a field value is unique.\n *\n * Deliberately **not** narrowed by `beforeQuery`, and it is the only read\n * here that is not. The question is whether the value exists anywhere in\n * the table, not whether it exists among the rows this caller can see:\n * narrowed, it would answer \"unique\" for a value a hidden row already\n * holds, and the insert would then be rejected by the unique constraint\n * with a message about a row the caller has no way to find. Uniqueness is a\n * property of the table.\n */\n async checkUniqueField(\n collectionPath: string,\n fieldName: string,\n value: unknown,\n excludeEntityId?: string,\n _databaseId?: string\n ): Promise<boolean> {\n if (value === undefined || value === null) return true;\n\n const collection = getCollectionByPath(collectionPath, this.registry);\n const table = getTableForCollection(collection, this.registry);\n const idInfoArray = requirePrimaryKeys(collection, this.registry);\n const field = table[fieldName as keyof typeof table] as AnyPgColumn;\n\n if (!field) return true;\n\n // An address the key columns cannot hold names no row, so there is\n // nothing to exclude — a row being created, for instance.\n const excludeRow = excludeEntityId && idCanAddressTable(excludeEntityId, table, idInfoArray)\n ? rowIdentityCondition(table, idInfoArray, excludeEntityId, collectionPath)\n : undefined;\n const conditions = DrizzleConditionBuilder.buildUniqueFieldCondition(field, value, excludeRow);\n\n const result = await this.db\n .select({ count: count() })\n .from(table)\n .where(and(...conditions));\n\n const countResult = Number(result[0]?.count || 0);\n return countResult === 0;\n }\n\n /**\n * Get the RelationService instance for external use\n */\n getRelationService(): RelationService {\n return this.relationService;\n }\n\n // =============================================================\n // REST API INCLUDE-AWARE METHODS\n // =============================================================\n\n /**\n * Fetch a collection of rows, with the relations `include` names.\n *\n * ## Why this no longer uses `db.query.findMany({ with })`\n *\n * It used to, whenever a `db.query` builder existed for the table — which\n * is Drizzle's relational API, and which compiles a to-many relation into a\n * lateral join. This file's own comment on the *other* read path says what\n * that costs: \"catastrophically slow for large collections (7s+ for 350\n * rows)\". So the admin fetch avoided it and REST took it, and the fast,\n * batched loader that the admin used was unreachable from the HTTP API.\n *\n * There is one loader now, {@link loadIncludes}, and every read reaches it:\n * one query per relation per level, never one per row. That also makes this\n * method and the realtime refetch the same code, which is what stops\n * `find()` and `listen()` returning different shapes for one query.\n *\n * @param include - see {@link IncludeSpec}: names, dotted paths, `[\"*\"]`,\n * or the parametrised tree. An unknown name is a 400 `UNKNOWN_RELATION`.\n */\n async fetchCollectionForRest<M extends Record<string, unknown>>(\n collectionPath: string,\n options: {\n filter?: FilterValues<Extract<keyof M, string>>;\n /** An `or(...)`/`and(...)` group, applied alongside `filter`. */\n logical?: LogicalCondition;\n orderBy?: string | OrderByTuple[];\n order?: \"desc\" | \"asc\";\n limit?: number;\n offset?: number;\n startAfter?: Record<string, unknown>;\n searchString?: string;\n databaseId?: string;\n vectorSearch?: VectorSearchParams;\n /** Narrow to the rows reachable from a parent through a relation. */\n relatedTo?: NestedPathHop;\n /**\n * See `FetchCollectionProps.withDeleted`. Applied by\n * `fetchRowsWithConditionsRaw` — the one place this pipeline builds\n * a WHERE — so it holds for the listing, the nested-path listing\n * and the realtime refetch alike.\n */\n withDeleted?: WithDeleted;\n /** Columns to read — a projection pushed into the SELECT. */\n fields?: string[];\n /** `SELECT DISTINCT` over the projection. */\n distinct?: boolean;\n /** Ask each row which declared search fields matched. */\n searchExplain?: boolean;\n } = {},\n include?: IncludeSpec\n ): Promise<Record<string, unknown>[]> {\n // Resolve a nested path here rather than at the route, so `include`,\n // `offset` and the rest reach a child listing by the same route they\n // reach a root one.\n if (isNestedPath(collectionPath)) {\n const hop = resolveNestedPath(collectionPath, this.registry);\n if (hop) {\n return this.fetchCollectionForRest<M>(\n hop.targetCollection.slug, { ...options,\nrelatedTo: hop }, include\n );\n }\n }\n // `relatedTo` is applied by `fetchRowsWithConditionsRaw`, which builds\n // the scope condition itself — this used to build a second one here and\n // hand it to a `db.query` branch that no longer exists.\n const collection = getCollectionByPath(collectionPath, this.registry);\n\n const normalizedInclude = normalizeInclude(include);\n\n // Base rows first, relations after. One SELECT, then one query per\n // relation per level of the include tree — never one per row, and never\n // the lateral join the relational query API compiles a to-many into.\n const rows = (await this.fetchRowsWithConditionsRaw<M>(collectionPath, options))\n .map(row => toRestRow(row, collection, this.registry));\n\n if (normalizedInclude) {\n await this.loadIncludes(rows, collection, collectionPath, normalizedInclude);\n }\n\n return rows;\n }\n\n /**\n * Fetch a single row, with the relations `include` names.\n *\n * The same two steps `fetchCollectionForRest` takes, for one row, through\n * the same {@link loadIncludes}. It used to be a separate implementation on\n * `db.query.findFirst({ with })` with a hand-written N+1 fallback beneath\n * it, and the two disagreed: the primary path returned a to-one relation as\n * the target's columns, the fallback merged an `id` over them, and a\n * relation that resolved to nothing was *absent* on one path and `null` on\n * the other. `find()[0]` and `findById()` now answer with the same row.\n */\n async fetchOneForRest<M extends Record<string, unknown>>(\n collectionPath: string,\n id: string | number,\n include?: IncludeSpec,\n databaseId?: string,\n options?: {\n fields?: string[];\n /** See `FetchCollectionProps.withDeleted`. */\n withDeleted?: WithDeleted;\n }\n ): Promise<Record<string, unknown> | null> {\n if (!await this.isAddressableUnder(collectionPath, id)) return null;\n\n const collection = getCollectionByPath(collectionPath, this.registry);\n const table = getTableForCollection(collection, this.registry);\n const idInfoArray = requirePrimaryKeys(collection, this.registry);\n\n // See `fetchOne`: an unaddressable id is a 404, not a database error.\n if (!idCanAddressTable(id, table, idInfoArray)) return null;\n\n const addressed = rowIdentityCondition(table, idInfoArray, id, collectionPath);\n\n // Soft delete: a stamped row is a 404 through the REST read too, so\n // `GET /:id` and the listing agree about which rows exist.\n const withDeleted = options?.withDeleted;\n const projection = this.columnProjection(table, collection, options?.fields, idInfoArray);\n // The REST single get is narrowed by the same hook as the listing, so\n // `find()[0]` and `findById()` agree about which rows exist — and so\n // that a row excluded from a scoped listing cannot be read by guessing\n // its id.\n const narrowing = await this.narrowReadCondition(\n collectionPath, collection, table, \"get\",\n FetchService.describeRead({ fields: options?.fields })\n );\n const identity = narrowing\n ? and(addressed, narrowing) as SQL\n : addressed;\n const result = await this.db\n .select(projection as never)\n .from(table)\n .where(andSoftDelete(identity, collection, table, withDeleted))\n .limit(1);\n\n if (result.length === 0) return null;\n\n const row = toRestRow(result[0] as Record<string, unknown>, collection, this.registry);\n\n const normalizedInclude = normalizeInclude(include);\n if (normalizedInclude) {\n await this.loadIncludes([row], collection, collectionPath, normalizedInclude);\n }\n\n return row;\n }\n\n /**\n * Fetch raw rows without any relation processing (for REST fast path)\n */\n private async fetchRowsWithConditionsRaw<M extends Record<string, unknown>>(\n collectionPath: string,\n options: {\n filter?: FilterValues<Extract<keyof M, string>>;\n /**\n * An `or(...)`/`and(...)` group, applied alongside `filter`.\n *\n * Declared *and applied*, because this is the path every REST search\n * and vector read takes: `fetchCollectionForRest` skips `db.query`\n * whenever a `searchString` or a `vectorSearch` is present. The\n * group arrived here on `options` from the very beginning and was\n * simply never read, so `?searchString=x&or=(...)` served every row\n * matching `x` that RLS allowed — while `count` (which does apply\n * it) reported the narrowed total, so `meta.total` and `data`\n * described different sets of rows.\n */\n logical?: LogicalCondition;\n orderBy?: string | OrderByTuple[];\n order?: \"desc\" | \"asc\";\n limit?: number;\n offset?: number;\n startAfter?: Record<string, unknown>;\n searchString?: string;\n searchExplain?: boolean;\n vectorSearch?: VectorSearchParams;\n relatedTo?: NestedPathHop;\n /** See `FetchCollectionProps.withDeleted`. */\n withDeleted?: WithDeleted;\n /** Columns to read — see {@link columnProjection}. */\n fields?: string[];\n /** `SELECT DISTINCT` over the projection. */\n distinct?: boolean;\n } = {}\n ): Promise<Record<string, unknown>[]> {\n const collection = getCollectionByPath(collectionPath, this.registry);\n const table = getTableForCollection(collection, this.registry);\n const idInfoArray = requirePrimaryKeys(collection, this.registry);\n\n // A vector read orders by distance, and a distance is computed per\n // query and stored nowhere, so there is no value on the cursor row to\n // seek past. Seeking anyway compared ids under a distance order: page\n // two was the rows with a smaller id, which is not the next page.\n if (options.vectorSearch && options.startAfter) {\n throw ApiError.badRequest(\n \"Cursor pagination (`startAfter`) cannot be combined with a vector search. \" +\n \"Its rows are ordered by a distance computed per query, which cannot key a cursor. \" +\n \"Use `limit`/`offset` for the next page of nearest rows.\",\n \"VECTOR_CURSOR_UNSUPPORTED\"\n );\n }\n\n let vectorMeta: { orderBy: SQL; filter?: SQL; distanceSelect: SQL } | undefined;\n if (options.vectorSearch) {\n vectorMeta = DrizzleConditionBuilder.buildVectorSearchConditions(table, options.vectorSearch);\n }\n\n // A generated search column is an index in column form; `SELECT *`\n // would ship it to every caller. The projection is undefined — and the\n // SQL therefore unchanged — for any table without one. `fields`\n // narrows it further, in SQL rather than after the fact.\n const visible = this.columnProjection(table, collection, options.fields, idInfoArray, options.distinct === true);\n\n // Relevance, alongside the row, exactly as `_distance` rides along with\n // a vector search. Present only when the collection opted in and the\n // request carried a search string, so a caller can order by it, show\n // it, or blend it with a score of their own.\n const rankSelect = options.searchString\n ? DrizzleConditionBuilder.buildSearchRankExpression(options.searchString, table, collection)\n : undefined;\n\n // Only when asked: a `ts_headline` per declared field per row.\n const matchesSelect = options.searchString && options.searchExplain\n ? DrizzleConditionBuilder.buildSearchMatchesExpression(options.searchString, table, collection)\n : undefined;\n\n // `SELECT DISTINCT`, over exactly the projection above.\n //\n // Only on the plain read. A vector or relevance query selects a\n // per-row computed value beside the columns — a distance, a score —\n // and DISTINCT over a set that includes one makes every row distinct\n // by construction: it would answer 200 having done nothing, which is\n // worse than refusing.\n const wantsDistinct = options.distinct === true;\n if (wantsDistinct && (vectorMeta || rankSelect)) {\n throw ApiError.badRequest(\n \"`distinct` cannot be combined with a search or vector query: both attach a per-row \"\n + \"score to every row, so no two rows are ever equal and DISTINCT would have no effect. \"\n + \"Drop one of the two.\",\n \"DISTINCT_NOT_APPLICABLE\"\n );\n }\n const selectFrom = () => {\n const builder = wantsDistinct ? this.db.selectDistinct.bind(this.db) : this.db.select.bind(this.db);\n return visible\n ? builder(visible as never).from(table).$dynamic()\n : builder().from(table).$dynamic();\n };\n\n let query = vectorMeta\n ? this.db.select({ table_row: (visible ?? table) as never,\n_distance: vectorMeta.distanceSelect }).from(table).$dynamic()\n : rankSelect\n ? this.db.select({\n table_row: (visible ?? table) as never,\n _score: rankSelect,\n ...(matchesSelect ? { _matches: matchesSelect } : {})\n }).from(table).$dynamic()\n : selectFrom();\n const allConditions: SQL[] = [];\n\n if (options.relatedTo) allConditions.push(this.buildRelationScope(options.relatedTo));\n\n // Soft delete, on the `db.select` path. Both paths serve the same\n // request, so a deleted row must not come back down one and not the\n // other — which is exactly what happens when each grows its own filter.\n withSoftDelete(allConditions, collection, table, options.withDeleted);\n\n if (options.searchString) {\n const searchConditions = DrizzleConditionBuilder.buildSearchConditions(\n options.searchString, collection.properties, table, collection\n );\n if (searchConditions.length === 0) return [];\n allConditions.push(DrizzleConditionBuilder.combineConditionsWithOr(searchConditions)!);\n }\n\n if (options.filter) {\n const filterConditions = this.buildFilterConditions(options.filter, table, collectionPath);\n if (filterConditions.length > 0) allConditions.push(...filterConditions);\n }\n\n if (options.logical) {\n const logicalCondition = DrizzleConditionBuilder.buildLogicalConditions(options.logical, table, collectionPath, this.filterContext(collectionPath, table));\n if (logicalCondition) allConditions.push(logicalCondition);\n }\n\n // This is the one place this pipeline builds a WHERE, so it is the one\n // place the hook has to be applied for the listing, the search, the\n // vector read, the nested-path listing and the realtime refetch.\n allConditions.push(...await this.narrowRead(\n collectionPath, collection, table, \"list\", FetchService.describeRead(options)\n ));\n\n if (vectorMeta?.filter) {\n allConditions.push(vectorMeta.filter);\n }\n\n if (allConditions.length > 0) {\n const finalCondition = DrizzleConditionBuilder.combineConditionsWithAnd(allConditions);\n if (finalCondition) query = query.where(finalCondition);\n }\n\n const sortKeys = normalizeDriverOrderBy(options.orderBy, options.order) ?? [];\n\n // Postgres requires every ORDER BY expression of a `SELECT DISTINCT` to\n // be in the select list, and answers a bare `42P10` if one is not —\n // which reaches the caller as a 500 quoting SQL they never wrote. The\n // condition is knowable here, so it is a 400 that names the column and\n // the fix instead.\n if (wantsDistinct && visible) {\n const selected = new Set(Object.keys(visible));\n const missing = sortKeys.map(([field]) => field).filter(field => !selected.has(field));\n if (missing.length > 0) {\n throw ApiError.badRequest(\n `\\`distinct\\` cannot sort by ${missing.map(f => `'${f}'`).join(\", \")}: a DISTINCT read `\n + \"can only be ordered by columns it returns, or the rows it collapses have no \"\n + `defined order. Add ${missing.map(f => `'${f}'`).join(\", \")} to \\`fields\\`, or drop them `\n + \"from `orderBy`.\",\n \"DISTINCT_ORDER_BY_NOT_SELECTED\",\n { fields: missing }\n );\n }\n }\n\n // Vector search overrides ORDER BY with distance (ascending = closest first)\n const keyColumns = FetchService.keyColumns(table, idInfoArray, collectionPath);\n const orderExpressions = vectorMeta\n ? [asc(vectorMeta.orderBy), ...keyColumns.map(column => desc(column))]\n : this.buildOrderExpressions(\n // `collectionPath` too: an aggregate over a relation\n // (`count(applications)`) resolves its subquery against it, and\n // without it every such sort on this path — the REST list, the\n // SDK's `find()` — was refused as a query that \"carries none\".\n this.resolveOrderKeys(table, sortKeys, collection, options.searchString, collectionPath),\n wantsDistinct ? [] : keyColumns\n );\n if (orderExpressions.length > 0) query = query.orderBy(...orderExpressions);\n\n if (options.startAfter) {\n // Keyset seeking on the REST path. `startAfter` arrived on this\n // signature from the beginning and was never read here, so\n // `?after=` reached the driver and paged nothing: the cursor was\n // decoded, handed over, and dropped one function short of the\n // comparison built to consume it.\n const cursorConditions = this.buildCursorConditions(table, idInfoArray, options, collectionPath);\n if (cursorConditions.length > 0) {\n allConditions.push(...cursorConditions);\n const finalCondition = DrizzleConditionBuilder.combineConditionsWithAnd(allConditions);\n if (finalCondition) query = query.where(finalCondition);\n }\n }\n\n const limitValue = options.vectorSearch\n ? (options.limit || 10)\n : options.searchString ? (options.limit || 50) : options.limit;\n if (limitValue) query = query.limit(limitValue);\n\n // Offset (numeric pagination)\n if (options.offset && options.offset > 0) query = query.offset(options.offset);\n\n const rawResults = await query;\n\n if (vectorMeta) {\n return (rawResults as { table_row: Record<string, unknown>; _distance: unknown }[]).map(r => ({\n ...r.table_row,\n _distance: typeof r._distance === \"number\" ? r._distance : parseFloat(String(r._distance))\n }));\n }\n\n if (rankSelect) {\n return (rawResults as { table_row: Record<string, unknown>; _score: unknown; _matches?: unknown }[]).map(r => ({\n ...r.table_row,\n _score: typeof r._score === \"number\" ? r._score : parseFloat(String(r._score)),\n ...(matchesSelect ? { _matches: r._matches ?? [] } : {})\n }));\n }\n\n return rawResults as Record<string, unknown>[];\n }\n}\n","import { and, eq, inArray, notInArray } from \"drizzle-orm\";\nimport { AnyPgColumn } from \"drizzle-orm/pg-core\";\nimport { CollectionConfig, JUNCTION_PIVOT_KEY, Properties, Property, ResolvedManyToMany, ResolvedRelation, ResolvedVia } from \"@rebasepro/types\";\nimport { hasForeignKeyOnTarget, isManyToMany } from \"@rebasepro/types\";\nimport { getTableName, resolveCollectionRelations, findRelation, fieldKeyForColumn } from \"@rebasepro/common\";\nimport { ApiError, logger } from \"@rebasepro/server\";\nimport { DrizzleClient } from \"../interfaces\";\nimport { DrizzleConditionBuilder } from \"../utils/drizzle-conditions\";\nimport { PostgresCollectionRegistry } from \"../collections/PostgresCollectionRegistry\";\nimport {\n getTableForCollection,\n relationMisconfigured,\n requirePrimaryKeys,\n parseIdValues,\n sourceKeyField\n} from \"./collection-helpers\";\nimport type { NestedPathHop } from \"./nested-path\";\nimport { RelationService } from \"./RelationService\";\nimport type { ReadCallContextProvider } from \"./read-scope\";\nimport {\n applyJunctionMembership,\n bindJoinPathJunction,\n bindThroughJunction,\n removeJunctionLink,\n updateJunctionPivot,\n type JunctionLinkWrite,\n type VisibleTargets\n} from \"./junction-writes\";\nimport { serializePropertyToServer } from \"../data-transformer\";\nimport { reachedDatabase } from \"../utils/pg-error-utils\";\n\n/**\n * The ids in a to-many relation write, whatever shape the caller sent.\n *\n * A membership list is written as either the related rows (`[{ id: 1 }]`, what\n * the admin UI sends back after reading them) or as bare keys (`[1]`, `[\"t-1\"]`,\n * what anyone writing the API by hand sends). Only the first was read, via a\n * blind `.map(rel => rel.id)`, and a bare key therefore became `undefined`:\n * on a numeric-keyed target that surfaced as `Invalid numeric ID: undefined`,\n * and on a string-keyed one it did not surface at all — `String(undefined)`\n * wrote a junction row pointing at the literal `\"undefined\"`, which no read\n * would ever match. Both shapes are accepted here, in one place, because both\n * call sites had the same assumption.\n *\n * An element that carries no key is refused rather than skipped: dropping it\n * would silently write a shorter membership list than the caller asked for.\n */\nfunction relationTargetIds(value: unknown, relationName: string, collectionSlug: string): (string | number)[] {\n return relationLinkElements(value, relationName, collectionSlug).map(element => element.id);\n}\n\n/** One element of a membership write, as the caller sent it. */\ninterface RelationLinkElement {\n id: string | number;\n /** The `_pivot` the element carried, unvalidated and unserialized. */\n pivot?: Record<string, unknown>;\n}\n\n/**\n * The same reading, keeping the link's own values.\n *\n * An element may be `{ id, _pivot: { role: \"owner\" } }` where the relation is a\n * `manyToMany` whose `through` declares `properties`. The `_pivot` is taken off\n * here and nowhere else: it is not a column of the target, so leaving it on the\n * element would make it look like one to every later step.\n */\nfunction relationLinkElements(\n value: unknown,\n relationName: string,\n collectionSlug: string\n): RelationLinkElement[] {\n if (!Array.isArray(value)) return [];\n\n return value.map((element, index) => {\n if (typeof element === \"string\" || typeof element === \"number\") return { id: element };\n if (element && typeof element === \"object\") {\n const id = (element as { id?: unknown }).id;\n const pivot = (element as Record<string, unknown>)[JUNCTION_PIVOT_KEY];\n if (typeof id === \"string\" || typeof id === \"number\") {\n return {\n id,\n pivot: pivot && typeof pivot === \"object\" && !Array.isArray(pivot)\n ? pivot as Record<string, unknown>\n : undefined\n };\n }\n }\n throw new Error(\n `Cannot write relation \"${relationName}\" on \"${collectionSlug}\": element ${index} carries no id. ` +\n \"Pass either the related rows (`[{ id: … }]`) or their keys (`[1, 2]`), not \" +\n `${element === null ? \"null\" : typeof element}.`\n );\n });\n}\n\n/**\n * A `_pivot` as the junction table's columns.\n *\n * Exported because the payload route needs the identical reading — one function\n * so a value written through `PATCH posts/1 { tags: [{ id, _pivot }] }` and the\n * same value written through `PATCH posts/1/tags/5 { _pivot }` cannot be\n * serialized two ways.\n */\nexport function serializePivot(\n pivot: Record<string, unknown>,\n payload: Properties,\n label: string\n): Record<string, unknown> {\n const declared = Object.keys(payload);\n if (declared.length === 0) {\n throw ApiError.badRequest(\n `Relation '${label}' carries no junction columns, so there is nothing to write under ` +\n `\\`${JUNCTION_PIVOT_KEY}\\`. Declare them in \\`through.properties\\`.`,\n \"VALIDATION_UNKNOWN_FIELDS\",\n { relation: label }\n );\n }\n\n const values: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(pivot)) {\n const property = payload[key as keyof Properties] as Property | undefined;\n if (!property) {\n throw ApiError.badRequest(\n `The junction behind relation '${label}' has no column '${key}'. ` +\n `Its \\`through.properties\\` are: ${declared.sort().map(k => `'${k}'`).join(\", \")}.`,\n \"VALIDATION_UNKNOWN_FIELDS\",\n { relation: label, field: key, validFields: declared.sort() }\n );\n }\n // `undefined` means \"I have no value for this\", not \"set it to NULL\" —\n // the same reading `serializeDataToServer` gives a row's own columns.\n if (value === undefined) continue;\n const serialized = serializePropertyToServer(value, property, key);\n if (serialized !== undefined) values[key] = serialized;\n }\n return values;\n}\n\n/**\n * Writing relations: junction membership, foreign-key stamping, and the links a\n * nested path creates or removes.\n *\n * Split from {@link RelationService}, which now only reads. The two had grown\n * into one 1700-line class doing two unrelated jobs, and sharing one habit —\n * warning about a relation it could not resolve and carrying on, which surfaces\n * as an empty list on the read side and as a successful save on the write side.\n * Separating them is what made that visible as one class of defect rather than\n * eighteen scattered log lines.\n *\n * The reads it still needs — the source key a link joins on — it asks\n * {@link RelationService} for rather than reimplementing.\n */\nexport class RelationWriteService {\n private reads: RelationService;\n\n constructor(\n private db: DrizzleClient,\n private registry: PostgresCollectionRegistry,\n /**\n * How this service's reads reach the identity their `beforeQuery` hooks\n * run as. Passed by the driver that constructed it; absent only in a\n * test, where a collection declaring the hook is refused rather than\n * read unnarrowed. See `read-scope.ts`.\n */\n private callContext?: ReadCallContextProvider\n ) {\n this.reads = new RelationService(db, registry, callContext);\n }\n\n\n /**\n * Remove the junction row linking a parent to `targetId`, leaving the target\n * row itself alone.\n *\n * This is what `DELETE authors/1/tags/5` has to mean for a many-to-many: the\n * target is shared, so deleting the row would remove the tag from every other\n * post that uses it. It used to do exactly that — resolve the path to the\n * `tags` table and delete by primary key.\n */\n async unlinkRelatedEntity(\n tx: DrizzleClient,\n hop: NestedPathHop,\n targetId: string | number\n ): Promise<void> {\n if (!isManyToMany(hop.relation)) {\n throw new Error(`Relation '${hop.relationKey}' has no junction table to unlink through`);\n }\n\n const binding = bindThroughJunction(\n this.registry,\n hop.relation.through,\n `${hop.parentCollection.slug}.${hop.relationKey}`\n );\n\n await removeJunctionLink(\n tx,\n binding,\n this.parsedId(hop.parentCollection, hop.parentId),\n this.parsedId(hop.targetCollection, targetId),\n { parent: hop.parentCollection.slug, relation: hop.relationKey }\n );\n }\n\n\n /**\n * Set the columns of one existing link, leaving the membership alone.\n *\n * `PATCH /api/data/:slug/:id/:relation/:targetId` with a `_pivot` body. The\n * membership array cannot say this: one element would unlink everything\n * else, and re-sending the whole set to change one `role` reintroduces the\n * lost update the membership diff exists to avoid.\n */\n async updateRelationPivot(\n tx: DrizzleClient,\n hop: NestedPathHop,\n targetId: string | number,\n pivot: Record<string, unknown>\n ): Promise<void> {\n if (!isManyToMany(hop.relation)) {\n throw ApiError.badRequest(\n `Relation '${hop.relationKey}' on '${hop.parentCollection.slug}' is a ` +\n `${hop.relation.kind}, which has no junction row to carry \\`${JUNCTION_PIVOT_KEY}\\`. ` +\n \"Only a `manyToMany` with `through.properties` does.\",\n \"VALIDATION_UNKNOWN_FIELDS\",\n { relation: hop.relationKey, collection: hop.parentCollection.slug }\n );\n }\n\n const label = `${hop.parentCollection.slug}.${hop.relationKey}`;\n const binding = bindThroughJunction(this.registry, hop.relation.through, label);\n const values = serializePivot(pivot, hop.relation.through.properties, label);\n\n if (Object.keys(values).length === 0) {\n throw ApiError.badRequest(\n `\\`${JUNCTION_PIVOT_KEY}\\` named no junction column to set on '${label}'.`,\n \"VALIDATION_CONSTRAINT\",\n { relation: hop.relationKey, collection: hop.parentCollection.slug }\n );\n }\n\n await updateJunctionPivot(\n tx,\n binding,\n this.parsedId(hop.parentCollection, hop.parentId),\n this.parsedId(hop.targetCollection, targetId),\n values,\n { parent: hop.parentCollection.slug, relation: hop.relationKey }\n );\n }\n\n\n /**\n * The target rows of `relation` this caller can see, for a junction diff —\n * or `undefined` when that is all of them. See\n * {@link RelationService.visibleTargetCondition}.\n */\n private async visibleTargets(\n parentCollection: CollectionConfig,\n relation: ResolvedRelation,\n parentId: string | number\n ): Promise<VisibleTargets | undefined> {\n const condition = await this.reads.visibleTargetCondition(parentCollection, relation, parentId);\n if (!condition) return undefined;\n const targetCollection = relation.target();\n const table = getTableForCollection(targetCollection, this.registry);\n const [idInfo] = requirePrimaryKeys(targetCollection, this.registry);\n const idColumn = table[idInfo.fieldName as keyof typeof table] as AnyPgColumn;\n return { table, idColumn, condition };\n }\n\n /** A collection's id, parsed to the type its primary key column holds. */\n private parsedId(collection: CollectionConfig, id: string | number): unknown {\n const pks = requirePrimaryKeys(collection, this.registry);\n return parseIdValues(id, pks)[pks[0].fieldName];\n }\n\n\n /** The same, for the membership list a to-many write names. */\n private parsedIds(collection: CollectionConfig, ids: Array<string | number>): unknown[] {\n if (ids.length === 0) return [];\n const pks = requirePrimaryKeys(collection, this.registry);\n return ids.map(id => parseIdValues(id, pks)[pks[0].fieldName]);\n }\n\n /**\n * A membership list as links: the target ids parsed, and each element's\n * `_pivot` turned into the columns the junction table holds.\n *\n * The values go through `serializePropertyToServer` — the same function\n * every collection column's value goes through on a save — so a `date`\n * arrives as a Date and a `map` as JSON, keyed by the property key, which is\n * what the drizzle junction table is keyed by. A key the junction does not\n * declare is **refused**, not dropped: drizzle leaves an unknown key out of\n * the statement, so dropping it is a write that reports success having\n * stored nothing.\n */\n private parsedLinks(\n collection: CollectionConfig,\n elements: RelationLinkElement[],\n payload: Properties,\n label: string\n ): JunctionLinkWrite[] {\n if (elements.length === 0) return [];\n const pks = requirePrimaryKeys(collection, this.registry);\n return elements.map(element => ({\n id: parseIdValues(element.id, pks)[pks[0].fieldName],\n pivot: element.pivot ? serializePivot(element.pivot, payload, label) : undefined\n }));\n }\n\n\n /**\n * Update many-to-many and junction relations\n */\n async updateRelationsUsingJoins<M extends Record<string, unknown>>(\n tx: DrizzleClient,\n collection: CollectionConfig,\n id: string | number,\n relationValues: Partial<M>\n ) {\n const resolvedRelations = resolveCollectionRelations(collection);\n\n for (const [key, value] of Object.entries(relationValues)) {\n const relation = findRelation(resolvedRelations, key);\n if (!relation || relation.cardinality !== \"many\") continue;\n\n const elements = relationLinkElements(value, key, collection.slug);\n const targetEntityIds = elements.map(element => element.id);\n const targetCollection = relation.target();\n\n const label = `${collection.slug}.${key}`;\n\n if (relation.kind === \"via\") {\n await applyJunctionMembership(\n tx,\n bindJoinPathJunction(\n this.registry,\n relation.joinPath,\n getTableName(collection),\n getTableName(targetCollection),\n label\n ),\n this.parsedId(collection, id),\n // A `via` declares no payload, so `{}` refuses a `_pivot`\n // sent through one rather than storing it nowhere.\n this.parsedLinks(targetCollection, elements, {}, label),\n await this.visibleTargets(collection, relation, id)\n );\n } else if (relation.kind === \"manyToMany\") {\n await applyJunctionMembership(\n tx,\n bindThroughJunction(this.registry, relation.through, label),\n this.parsedId(collection, id),\n this.parsedLinks(targetCollection, elements, relation.through.properties, label),\n await this.visibleTargets(collection, relation, id)\n );\n } else if (relation.cardinality === \"many\" && hasForeignKeyOnTarget(relation)) {\n // Handle one-to-many (inverse) by updating target FK to point to parent\n const targetTable = getTableForCollection(targetCollection, this.registry);\n const targetPks = requirePrimaryKeys(targetCollection, this.registry);\n const targetIdInfo = targetPks[0];\n const targetIdCol = targetTable[targetIdInfo.fieldName as keyof typeof targetTable] as AnyPgColumn;\n // The wire name the target's table is keyed by, not the column.\n const fkField = fieldKeyForColumn(targetCollection, relation.foreignKeyOnTarget);\n const fkCol = targetTable[fkField as keyof typeof targetTable] as AnyPgColumn;\n\n if (!fkCol || !targetIdCol) {\n throw relationMisconfigured(\n label,\n `the target table '${getTableName(targetCollection)}' has no ` +\n `${fkCol ? `'${targetIdInfo.fieldName}' key column` : `'${relation.foreignKeyOnTarget}' foreign-key column`}`\n );\n }\n\n // What the children's foreign key must hold — the parent's id\n // unless the link declares a `sourceKey`, and then the value of\n // that column on this parent row.\n const parentKeyValue = (await this.reads.resolveSourceKeys(collection, relation, [id], tx))\n .keyByParentId.get(String(id));\n\n if (parentKeyValue === undefined) {\n throw new Error(\n `Cannot write relation '${key}' on '${collection.slug}': row '${id}' has no value in ` +\n `\\`sourceKey: \"${sourceKeyField(relation, collection, this.registry)}\"\\`, so there is ` +\n \"nothing for the related rows to point at.\"\n );\n }\n\n // Only the rows the caller could have seen linked are theirs to\n // unlink: the list they send back is the list they were shown,\n // which the target's scope and soft delete had already\n // narrowed. Clearing by the foreign key alone set it to NULL on\n // another tenant's rows and on the trashed ones.\n const visible = await this.reads.visibleTargetCondition(collection, relation, id);\n\n // Clear existing links not in the new set\n if (targetEntityIds.length > 0) {\n const parsedTargetIds = targetEntityIds.map(id => parseIdValues(id, targetPks)[targetIdInfo.fieldName]);\n await tx\n .update(targetTable)\n .set({ [fkField]: null })\n // `notInArray`, not a hand-built fragment: the sibling\n // update three lines below already uses `inArray`, and\n // the hand-built version called `sql.join` with no\n // separator — the only such call in the workspace —\n // which renders `NOT IN ($1$2$3)`. That is a syntax\n // error, so writing a `hasMany` relation with two or\n // more children aborted the whole save transaction.\n .where(and(eq(fkCol, parentKeyValue), notInArray(targetIdCol as AnyPgColumn, parsedTargetIds as unknown[]), visible));\n\n // Set FK for the provided targets\n await tx\n .update(targetTable)\n .set({ [fkField]: parentKeyValue })\n .where(inArray(targetIdCol as AnyPgColumn, parsedTargetIds as unknown[]));\n } else {\n // If empty array provided, clear all existing links for this parent\n await tx\n .update(targetTable)\n .set({ [fkField]: null })\n .where(and(eq(fkCol, parentKeyValue), visible));\n }\n } else {\n // Every to-many kind in the union is handled above — TypeScript\n // narrows `relation` to `never` here — so reaching this means a\n // relation resolved to something no writer knows. It used to be\n // skipped with a warning, which told the caller their membership\n // had been stored when nothing had happened.\n throw relationMisconfigured(\n label,\n \"it is a to-many relation with no way to write links — a `manyToMany` \" +\n \"needs `through`, a `hasMany` needs `foreignKeyOnTarget`\"\n );\n }\n }\n }\n\n\n /**\n * Update inverse relations (where FK is on the target table)\n */\n async updateInverseRelations(\n tx: DrizzleClient,\n sourceCollection: CollectionConfig,\n sourceEntityId: string | number,\n inverseRelationUpdates: Array<{\n relationKey: string;\n relation: ResolvedRelation;\n newValue: unknown;\n }>\n ) {\n for (const update of inverseRelationUpdates) {\n const { relation, newValue } = update;\n\n try {\n const targetCollection = relation.target();\n const targetTable = getTableForCollection(targetCollection, this.registry);\n const targetPks = requirePrimaryKeys(targetCollection, this.registry);\n const targetIdInfo = targetPks[0];\n const sourcePks = requirePrimaryKeys(sourceCollection, this.registry);\n const sourceIdInfo = sourcePks[0];\n\n if (relation.kind === \"via\") {\n await this.updateInverseJoinPathRelation(\n tx,\n sourceCollection,\n sourceEntityId,\n targetCollection,\n relation,\n newValue\n );\n continue;\n }\n\n // A many-to-many names its own junction. This used to walk the\n // *target's* relations looking for an owning side whose\n // `relationName` matched `inverseRelationName`, and swap its\n // source/target columns — a search that silently did nothing\n // when the far side was declared differently than expected.\n if (isManyToMany(relation)) {\n await this.updateManyToManyInverseRelation(\n tx,\n sourceCollection,\n sourceEntityId,\n targetCollection,\n relation,\n newValue,\n {\n table: relation.through.table,\n sourceColumn: relation.through.sourceColumn,\n targetColumn: relation.through.targetColumn,\n properties: relation.through.properties\n }\n );\n continue;\n }\n\n // What is left names the parent with a column on the target.\n const label = `${sourceCollection.slug}.${relation.relationName}`;\n if (!hasForeignKeyOnTarget(relation)) {\n throw relationMisconfigured(\n label,\n `a '${relation.kind}' relation names no column on the target to write the link into`\n );\n }\n\n // The wire name the target's table is keyed by, not the column.\n const fkField = fieldKeyForColumn(targetCollection, relation.foreignKeyOnTarget!);\n const foreignKeyColumn = targetTable[fkField as keyof typeof targetTable] as AnyPgColumn;\n if (!foreignKeyColumn) {\n throw relationMisconfigured(\n label,\n `'${relation.foreignKeyOnTarget}' is not a column on the target table ` +\n `'${getTableName(targetCollection)}'`\n );\n }\n\n // The value the target's foreign key holds: this row's id, or\n // the column named by `sourceKey` when the link joins on one.\n const sourceKeyValue = (await this.reads.resolveSourceKeys(sourceCollection, relation, [sourceEntityId], tx))\n .keyByParentId.get(String(sourceEntityId));\n\n if (sourceKeyValue === undefined) {\n throw new Error(\n `Cannot write relation '${relation.relationName}' on '${sourceCollection.slug}': row ` +\n `'${sourceEntityId}' has no value in \\`sourceKey: ` +\n `\"${sourceKeyField(relation, sourceCollection, this.registry)}\"\\`, so there is nothing ` +\n \"for the related row to point at.\"\n );\n }\n\n // The same reading as the to-many clear: the row this one\n // points at now is only the caller's to detach if they could\n // have seen it.\n const visible = await this.reads.visibleTargetCondition(sourceCollection, relation, sourceEntityId);\n\n if (newValue === null || newValue === undefined) {\n await tx\n .update(targetTable)\n .set({ [fkField]: null })\n .where(and(eq(foreignKeyColumn, sourceKeyValue), visible));\n } else {\n const parsedNewTargetIdObj = parseIdValues(newValue as string | number, targetPks);\n const parsedNewTargetId = parsedNewTargetIdObj[targetIdInfo.fieldName];\n const targetIdField = targetTable[targetIdInfo.fieldName as keyof typeof targetTable] as AnyPgColumn;\n\n // First, clear any existing FK that points to this source row\n await tx\n .update(targetTable)\n .set({ [fkField]: null })\n .where(and(eq(foreignKeyColumn, sourceKeyValue), visible));\n\n // Then, update the new target row to point to this source row\n await tx\n .update(targetTable)\n .set({ [fkField]: sourceKeyValue })\n .where(eq(targetIdField, parsedNewTargetId));\n }\n } catch (e) {\n // This catch is here so that one relation whose columns cannot\n // be resolved does not abort the others. A refusal is not a\n // misconfiguration: swallowing WRITE_DENIED here reported the\n // save as done while the database had rejected the write, which\n // is the whole defect this path was just fixed for. `name` as\n // well as `instanceof`, because a skewed install can hold two\n // copies of the server package. Nor is a statement the\n // database refused: it has aborted the transaction, so the\n // other relations cannot be written anyway, and swallowing it\n // left the next statement to fail with an unrelated 25P02.\n if (e instanceof ApiError || (e as Error)?.name === \"ApiError\" || reachedDatabase(e)) throw e;\n logger.warn(`Failed to update inverse relation '${relation.relationName}'`, { error: e });\n }\n }\n }\n\n\n /**\n * Handle inverse relations with joinPath\n */\n private async updateInverseJoinPathRelation(\n tx: DrizzleClient,\n sourceCollection: CollectionConfig,\n sourceEntityId: string | number,\n targetCollection: CollectionConfig,\n relation: ResolvedVia,\n newValue: unknown\n ) {\n const sourceTableName = getTableName(sourceCollection);\n const targetTableName = getTableName(targetCollection);\n\n // Only a path with exactly one table between the two ends holds links to\n // write. Anything else is a read-only reach, and skipping it is a\n // statement about the relation's shape rather than a failure to resolve\n // one — which is why this is a `return` and the binder below throws.\n const intermediateTables = relation.joinPath\n .map(step => step.table)\n .filter(table => table !== sourceTableName && table !== targetTableName);\n\n if (intermediateTables.length !== 1 || relation.cardinality !== \"many\") return;\n\n try {\n const binding = bindJoinPathJunction(\n this.registry,\n relation.joinPath,\n sourceTableName,\n targetTableName,\n `${sourceCollection.slug}.${relation.relationName}`\n );\n\n // The membership this write asks for. A single value is the\n // one-to-one case and reads as a set of one.\n const elements = Array.isArray(newValue)\n ? relationLinkElements(newValue, relation.relationName, sourceCollection.slug)\n : newValue === null || newValue === undefined\n ? []\n : relationLinkElements([newValue], relation.relationName, sourceCollection.slug);\n\n await applyJunctionMembership(\n tx,\n binding,\n this.parsedId(sourceCollection, sourceEntityId),\n this.parsedLinks(targetCollection, elements, {}, `${sourceCollection.slug}.${relation.relationName}`),\n await this.visibleTargets(sourceCollection, relation, sourceEntityId)\n );\n } catch (error) {\n logger.error(`Failed to update inverse joinPath relation '${relation.relationName}'`, { error: error });\n throw error;\n }\n }\n\n\n /**\n * Handle many-to-many inverse relation updates using junction tables\n */\n private async updateManyToManyInverseRelation(\n tx: DrizzleClient,\n sourceCollection: CollectionConfig,\n sourceEntityId: string | number,\n targetCollection: CollectionConfig,\n relation: ResolvedRelation,\n newValue: unknown,\n junctionInfo: { table: string; sourceColumn: string; targetColumn: string; properties: Properties }\n ) {\n const label = `${sourceCollection.slug}.${relation.relationName}`;\n try {\n const elements = Array.isArray(newValue)\n ? relationLinkElements(newValue, relation.relationName, sourceCollection.slug)\n : [];\n\n await applyJunctionMembership(\n tx,\n bindThroughJunction(this.registry, junctionInfo, label),\n this.parsedId(sourceCollection, sourceEntityId),\n this.parsedLinks(targetCollection, elements, junctionInfo.properties, label),\n await this.visibleTargets(sourceCollection, relation, sourceEntityId)\n );\n } catch (error) {\n logger.error(`Failed to update many-to-many inverse relation '${relation.relationName}'`, { error: error });\n throw error;\n }\n }\n\n\n /**\n * Update one-to-one relations that use joinPath\n */\n async updateJoinPathOneToOneRelations(\n tx: DrizzleClient,\n parentCollection: CollectionConfig,\n parentId: string | number,\n updates: Array<{\n relationKey: string;\n // Only a `via` relation has a join path to write through, and the\n // caller only collects those.\n relation: ResolvedVia;\n newTargetId: string | number | null;\n }>\n ) {\n for (const upd of updates) {\n const { relation, newTargetId } = upd;\n const targetCollection = relation.target();\n const targetTable = getTableForCollection(targetCollection, this.registry);\n const targetPks = requirePrimaryKeys(targetCollection, this.registry);\n const targetIdInfo = targetPks[0];\n const targetIdCol = targetTable[targetIdInfo.fieldName as keyof typeof targetTable] as AnyPgColumn;\n\n // Determine mapping of columns\n //\n // A join path is authored in SQL terms — `posts.author_id`,\n // `user_profiles.user_id` — because that is what a join is written\n // in. Everything below indexes a Drizzle table or builds a `set`\n // payload with these, and both are keyed by the wire name, so the\n // two are translated here rather than at each of the six uses.\n const { targetFKColName: targetFKColumn, parentSourceColName: parentSourceColumn } =\n this.resolveJoinPathWriteMapping(parentCollection, relation);\n const targetFKColName = fieldKeyForColumn(targetCollection, targetFKColumn);\n const parentSourceColName = fieldKeyForColumn(parentCollection, parentSourceColumn);\n const parentTable = getTableForCollection(parentCollection, this.registry);\n const parentPks = requirePrimaryKeys(parentCollection, this.registry);\n const parentIdInfo = parentPks[0];\n const parsedParentIdObj = parseIdValues(parentId, parentPks);\n const parsedParentId = parsedParentIdObj[parentIdInfo.fieldName];\n\n const parentIdCol = parentTable[parentIdInfo.fieldName as keyof typeof parentTable] as AnyPgColumn;\n const parentSourceCol = parentTable[parentSourceColName as keyof typeof parentTable] as AnyPgColumn;\n const targetFKCol = targetTable[targetFKColName as keyof typeof targetTable] as AnyPgColumn;\n\n const label = `${parentCollection.slug}.${relation.relationName}`;\n if (!parentSourceCol) {\n throw relationMisconfigured(\n label,\n `its joinPath reads '${parentSourceColName}', which is not a column on ` +\n `'${getTableName(parentCollection)}'`\n );\n }\n if (!targetFKCol) {\n throw relationMisconfigured(\n label,\n `its joinPath writes '${targetFKColName}', which is not a column on ` +\n `'${getTableName(targetCollection)}'`\n );\n }\n\n // Fetch the parent row to obtain the value for parentSourceCol\n const parentRows = await tx\n .select({ val: parentSourceCol })\n .from(parentTable)\n .where(eq(parentIdCol, parsedParentId))\n .limit(1);\n if (parentRows.length === 0) continue;\n const parentFKValue = parentRows[0].val as string | number | null;\n\n if (newTargetId === null || newTargetId === undefined) {\n // Clear any target rows currently linked to this parent via the FK\n if (parentFKValue !== null && parentFKValue !== undefined) {\n await tx.update(targetTable)\n .set({ [targetFKColName]: null })\n .where(eq(targetFKCol, String(parentFKValue)));\n }\n continue;\n }\n\n // Parse the new target id\n const parsedTargetIdObj = parseIdValues(newTargetId, targetPks);\n const parsedTargetId = parsedTargetIdObj[targetIdInfo.fieldName];\n\n // Ensure one-to-one by clearing existing link from any target rows with this parent FK\n if (parentFKValue !== null && parentFKValue !== undefined) {\n await tx.update(targetTable)\n .set({ [targetFKColName]: null })\n .where(eq(targetFKCol, String(parentFKValue)));\n } else {\n // Not a configuration problem: the row is simply missing the\n // value the link joins on, so there is nothing for the target\n // to point at. The `hasMany` writer already refuses this case\n // in the same words rather than saving a row it did not write.\n throw ApiError.badRequest(\n `Cannot write relation '${label}': row '${parentId}' has no value in ` +\n `'${parentSourceColName}', which is the column its joinPath joins on, so there ` +\n \"is nothing for the related row to point at.\",\n \"RELATION_SOURCE_KEY_EMPTY\"\n );\n }\n\n // Now set the FK on the target row\n await tx.update(targetTable)\n .set({ [targetFKColName]: parentFKValue })\n .where(eq(targetIdCol, parsedTargetId));\n }\n }\n\n\n /**\n * Resolve joinPath write mapping for one-to-one relations\n */\n resolveJoinPathWriteMapping(\n parentCollection: CollectionConfig,\n relation: ResolvedVia\n ): { targetFKColName: string; parentSourceColName: string } {\n if (!relation.joinPath || relation.joinPath.length === 0) {\n throw new Error(\"resolveJoinPathWriteMapping requires a joinPath relation\");\n }\n const parentTableName = getTableName(parentCollection);\n const joinPath = relation.joinPath;\n let index = joinPath.length - 1;\n const targetFKColName = DrizzleConditionBuilder.getColumnNamesFromColumns(joinPath[index].on.to)[0];\n let currentFrom = joinPath[index].on.from;\n\n // Back along the path to the step whose `from` is on the parent's\n // table. A `table.column` spelling names that table; a bare column —\n // the form `JoinStep` documents — is on the previous step's table, or\n // the parent's for the first step. Reading the table only from the\n // spelling meant a documented one-to-one `via` (`{ from: \"id\", to:\n // \"product_id\" }`) named no table, matched no step, and refused every\n // write with \"Could not resolve parent source column\".\n let safety = 0;\n while (safety++ < 10) {\n const qualifiedTable = DrizzleConditionBuilder.getTableNamesFromColumns(currentFrom)[0];\n const currentFromTable = qualifiedTable || (index === 0 ? parentTableName : joinPath[index - 1].table);\n if (currentFromTable === parentTableName) {\n break;\n }\n const prevIndex = qualifiedTable\n ? joinPath.findIndex((s) => {\n const to = Array.isArray(s.on.to) ? s.on.to[0] : s.on.to;\n return to === currentFrom;\n })\n : index - 1;\n if (prevIndex < 0) {\n throw new Error(`Could not resolve parent source column for joinPath relation '${relation.relationName}'`);\n }\n index = prevIndex;\n currentFrom = joinPath[index].on.from;\n }\n const parentSourceColName = DrizzleConditionBuilder.getColumnNamesFromColumns(currentFrom)[0];\n return { targetFKColName,\nparentSourceColName };\n }\n\n\n /**\n * Handle junction table creation for many-to-many path-based saves\n */\n async handleJunctionTableCreation(\n tx: DrizzleClient,\n newEntityId: string | number,\n junctionTableInfo: {\n parentCollection: CollectionConfig;\n parentId: string | number;\n // Only a junction-backed relation has a link to write, and the\n // caller only builds this for one — stated here so the body needs\n // no narrowing.\n relation: ResolvedManyToMany;\n relationKey: string;\n }\n ) {\n const { parentCollection, parentId, relation, relationKey } = junctionTableInfo;\n const targetCollection = relation.target();\n\n try {\n const binding = bindThroughJunction(\n this.registry,\n relation.through,\n `${parentCollection.slug}.${relationKey}`\n );\n\n const parsedNewEntityId = this.parsedId(targetCollection, newEntityId);\n\n // Create the junction table entry linking parent to the target row.\n const junctionData = {\n [binding.parentColumn.name]: parentId,\n [binding.targetColumn.name]: parsedNewEntityId\n };\n\n // Idempotent: a link either exists or it does not, so asking for one\n // twice is not an error. This is what lets `PUT parent/id/child/childId`\n // mean \"this row belongs to this parent's set\" — the only way to\n // attach an *existing* row, which previously had none.\n await tx.insert(binding.table).values(junctionData).onConflictDoNothing();\n\n logger.info(`Linked '${relationKey}' ${parsedNewEntityId} to ${parentId}`);\n } catch (error) {\n logger.error(`Failed to create junction table entry for relation '${relationKey}'`, { error: error });\n throw error;\n }\n }\n}\n","import { and, sql, SQL } from \"drizzle-orm\";\nimport { AnyPgColumn, PgTable } from \"drizzle-orm/pg-core\";\nimport { ApiError } from \"@rebasepro/server\";\nimport type { ParsedFieldOp } from \"@rebasepro/server\";\nimport type { ArrayPropertyValidationSchema, NumberPropertyValidationSchema, Properties } from \"@rebasepro/types\";\nimport type { DrizzleClient } from \"../interfaces\";\n\n/**\n * Turning `{ views: { $inc: 1 } }` into the `SET` clause that cannot lose a\n * concurrent write.\n *\n * The contract — which operators exist, what they accept, which property types\n * they are legal on — lives in `@rebasepro/server`. What is here is the part\n * only this driver can know: an `array` property is a native `text[]` on one\n * collection and a jsonb document on the next (see the DDL generator's `array`\n * arm), and the two need entirely different statements. Compiling from the\n * property type alone would emit `array_append` against a jsonb column, which\n * Postgres rejects at plan time with a function-does-not-exist error naming\n * neither the collection nor the field.\n *\n * Every expression is NULL-safe. `NULL + 1` is `NULL`, and a counter that\n * silently empties itself the first time it is incremented from an unset column\n * is worse than one that never worked.\n *\n * What an operation produces answers to the property's declared bounds, and\n * that check is compiled here too, as conditions for the same UPDATE — see\n * {@link compileFieldOpBounds}.\n *\n * @module\n */\n\n/** What the column is, as Postgres sees it: `text[]`, `jsonb`, `integer`, … */\nfunction sqlTypeOf(column: AnyPgColumn): string {\n try {\n return String(column.getSQLType()).toLowerCase();\n } catch {\n return \"\";\n }\n}\n\nfunction isNativeArray(type: string): boolean {\n return type.endsWith(\"[]\") || type.endsWith(\" array\");\n}\n\n/** A `json` column needs the result cast back; a `jsonb` one does not. */\nfunction isPlainJson(type: string): boolean {\n return type === \"json\";\n}\n\nfunction asList(operand: unknown): unknown[] {\n return Array.isArray(operand) ? operand : [operand];\n}\n\n/**\n * The expression one operation compiles to, or a 400 saying why it cannot.\n *\n * `field` is the property key; it is already known to be a column of `table`\n * (the caller checks that against the same table it builds the statement from).\n */\nexport function compileFieldOp(\n table: PgTable,\n field: string,\n op: ParsedFieldOp,\n context: { collectionPath: string }\n): SQL {\n const column = table[field as keyof typeof table] as AnyPgColumn | undefined;\n if (!column) {\n throw ApiError.badRequest(\n `'${field}' is not a column of \"${context.collectionPath}\", so ${op.operator} has nothing to apply to.`,\n \"INVALID_FIELD_OPERATION\"\n );\n }\n const type = sqlTypeOf(column);\n\n switch (op.operator) {\n case \"$inc\": {\n // COALESCE, not a bare `+`: an unset counter is NULL, and NULL + 1\n // is NULL — so the first increment of a column that has never been\n // written would erase it rather than start it at 1.\n return sql`COALESCE(${column}, 0) + ${op.operand as number}`;\n }\n\n case \"$push\": {\n const values = asList(op.operand);\n if (isNativeArray(type)) {\n // Folded with array_append rather than concatenating a literal:\n // the element type is inferred from the column, so the same\n // code serves text[], integer[] and numeric[] without the\n // caller (or this file) having to name the type.\n let expression: SQL = sql`${column}`;\n for (const value of values) {\n expression = sql`array_append(${expression}, ${value})`;\n }\n return expression;\n }\n if (type.startsWith(\"json\")) {\n const appended = sql`COALESCE(${column}::jsonb, '[]'::jsonb) || ${JSON.stringify(values)}::jsonb`;\n return isPlainJson(type) ? sql`(${appended})::json` : appended;\n }\n throw unsupportedColumn(field, op.operator, type, context);\n }\n\n case \"$pull\": {\n const values = asList(op.operand);\n if (isNativeArray(type)) {\n let expression: SQL = sql`${column}`;\n for (const value of values) {\n expression = sql`array_remove(${expression}, ${value})`;\n }\n return expression;\n }\n if (type.startsWith(\"json\")) {\n // jsonb has no array_remove. Re-aggregating the elements that\n // survive is the standard form, and COALESCE around it is what\n // keeps a column that loses its last element as `[]` rather\n // than NULL — jsonb_agg over an empty set is NULL.\n const removals = sql`${JSON.stringify(values)}::jsonb`;\n const filtered = sql`COALESCE((\n SELECT jsonb_agg(element)\n FROM jsonb_array_elements(COALESCE(${column}::jsonb, '[]'::jsonb)) AS element\n WHERE NOT (element IN (SELECT jsonb_array_elements(${removals})))\n ), '[]'::jsonb)`;\n return isPlainJson(type) ? sql`(${filtered})::json` : filtered;\n }\n throw unsupportedColumn(field, op.operator, type, context);\n }\n\n case \"$merge\": {\n if (!type.startsWith(\"json\")) throw unsupportedColumn(field, op.operator, type, context);\n // `||` on jsonb is a shallow merge, which is what $merge promises:\n // a nested object replaces rather than merges, and saying so is\n // better than a deep merge nobody can predict the result of.\n const merged = sql`COALESCE(${column}::jsonb, '{}'::jsonb) || ${JSON.stringify(op.operand)}::jsonb`;\n return isPlainJson(type) ? sql`(${merged})::json` : merged;\n }\n }\n}\n\nfunction unsupportedColumn(\n field: string,\n operator: string,\n type: string,\n context: { collectionPath: string }\n): ApiError {\n return ApiError.badRequest(\n `${operator} cannot be applied to '${field}' on \"${context.collectionPath}\": the column is ` +\n `${type || \"of an unknown type\"}, and ${operator} needs ` +\n (operator === \"$merge\" ? \"a json or jsonb column.\" : \"an array or json column.\"),\n \"INVALID_FIELD_OPERATION\"\n );\n}\n\n/** Compile a whole map of operations into a `SET`-ready object. */\nexport function compileFieldOps(\n table: PgTable,\n fieldOps: Record<string, ParsedFieldOp>,\n context: { collectionPath: string }\n): Record<string, SQL> {\n const compiled: Record<string, SQL> = {};\n for (const [field, op] of Object.entries(fieldOps)) {\n compiled[field] = compileFieldOp(table, field, op, context);\n }\n return compiled;\n}\n\n/**\n * One declared bound an operation's result has to stay inside.\n *\n * `code` and `message` are what a plain value breaking the same rule reports\n * (see `write-validation.ts`), so `{ stock: { $inc: -1000 } }` and\n * `{ stock: -5 }` are refused with one answer.\n */\nexport interface FieldOpBound {\n /** The property key. */\n field: string;\n code: \"min\" | \"max\" | \"more_than\" | \"less_than\" | \"positive\" | \"negative\" | \"min_items\" | \"max_items\";\n /**\n * TRUE when the value the operation writes keeps to the rule, evaluated\n * against the row the statement is changing. Never NULL: an unset column\n * reads as `0` or empty wherever the operation itself reads it so, and an\n * array the operation leaves unset has no size for a bound to judge.\n */\n holds: SQL;\n message: string;\n}\n\n/**\n * The declared bounds each operation's *result* must satisfy, as conditions\n * for the UPDATE that applies it.\n *\n * `{ stock: -5 }` on `stock: { validation: { min: 0 } }` is refused before the\n * statement is built, because the value is in the request. What\n * `{ stock: { $inc: -1000 } }` produces depends on the stored row, which the\n * request never sees, so it can only be judged where the row is: in the WHERE\n * of the UPDATE that does the arithmetic, over the row that statement holds\n * locked. Reading the row first and judging the sum would reopen the race the\n * operation exists to close — two decrements of a stock of 1 would each read\n * `1`, each find room for one more, and both go through. A condition on the\n * UPDATE cannot be raced that way: under READ COMMITTED an UPDATE blocked on a\n * concurrent writer re-evaluates its WHERE against the row that writer\n * committed, so the second decrement sees `0 - 1` and matches nothing.\n *\n * The bound is the whole declared range, applied to the result the way it\n * applies to a value: a number's `min`, `max`, `moreThan`, `lessThan`,\n * `positive` and `negative`, an array's `min` and `max` items. `integer` is not\n * here: a whole increment of a whole number stays whole, and the request\n * already refuses a fractional one. `$merge` has no bounds to hold.\n */\nexport function compileFieldOpBounds(\n table: PgTable,\n fieldOps: Record<string, ParsedFieldOp>,\n properties: Properties,\n context: { collectionPath: string }\n): FieldOpBound[] {\n const bounds: FieldOpBound[] = [];\n for (const [field, op] of Object.entries(fieldOps)) {\n const property = properties[field];\n if (!property?.validation) continue;\n if (property.type === \"number\" && op.operator === \"$inc\" && typeof op.operand === \"number\") {\n const result = compileFieldOp(table, field, op, context);\n bounds.push(...numberBounds(field, property.validation, result, op.operand));\n } else if (property.type === \"array\" && (op.operator === \"$push\" || op.operator === \"$pull\")) {\n const column = table[field as keyof typeof table] as AnyPgColumn | undefined;\n const result = compileFieldOp(table, field, op, context);\n // A native array is counted with `cardinality` (not `array_length`,\n // which is NULL for an empty array); a json document with\n // `jsonb_array_length`, through jsonb so one function serves both\n // `json` and `jsonb` columns.\n const size = column && isNativeArray(sqlTypeOf(column))\n ? sql`cardinality(${result})`\n : sql`jsonb_array_length((${result})::jsonb)`;\n bounds.push(...arrayBounds(field, property.validation, size, op.operator));\n }\n }\n return bounds;\n}\n\nfunction numberBounds(\n field: string,\n rules: NumberPropertyValidationSchema,\n result: SQL,\n operand: number\n): FieldOpBound[] {\n // The bound is a parameter cast to numeric rather than left to inference:\n // Postgres would type it from the column, and `min: 0.5` on an integer\n // column is then a failed cast rather than a comparison.\n const by = `$inc by ${operand}`;\n const bounds: FieldOpBound[] = [];\n if (rules.min !== undefined) {\n bounds.push({ field, code: \"min\", holds: sql`(${result}) >= ${rules.min}::numeric`, message: `'${field}' must be at least ${rules.min}, and ${by} would take it below that.` });\n }\n if (rules.max !== undefined) {\n bounds.push({ field, code: \"max\", holds: sql`(${result}) <= ${rules.max}::numeric`, message: `'${field}' must be at most ${rules.max}, and ${by} would take it above that.` });\n }\n if (rules.moreThan !== undefined) {\n bounds.push({ field, code: \"more_than\", holds: sql`(${result}) > ${rules.moreThan}::numeric`, message: `'${field}' must be greater than ${rules.moreThan}, and ${by} would take it to ${rules.moreThan} or below.` });\n }\n if (rules.lessThan !== undefined) {\n bounds.push({ field, code: \"less_than\", holds: sql`(${result}) < ${rules.lessThan}::numeric`, message: `'${field}' must be less than ${rules.lessThan}, and ${by} would take it to ${rules.lessThan} or above.` });\n }\n if (rules.positive) {\n bounds.push({ field, code: \"positive\", holds: sql`(${result}) > 0`, message: `'${field}' must be positive, and ${by} would take it to 0 or below.` });\n }\n if (rules.negative) {\n bounds.push({ field, code: \"negative\", holds: sql`(${result}) < 0`, message: `'${field}' must be negative, and ${by} would take it to 0 or above.` });\n }\n return bounds;\n}\n\nfunction arrayBounds(\n field: string,\n rules: ArrayPropertyValidationSchema,\n size: SQL,\n operator: \"$push\" | \"$pull\"\n): FieldOpBound[] {\n const items = (n: number) => `${n} item${n === 1 ? \"\" : \"s\"}`;\n // COALESCE to TRUE: a `$pull` from an unset native array leaves it unset,\n // and a range has nothing to say about a value that is not there — the\n // same rule a plain `null` gets.\n const bounds: FieldOpBound[] = [];\n if (rules.min !== undefined) {\n bounds.push({ field, code: \"min_items\", holds: sql`COALESCE(${size} >= ${rules.min}::numeric, TRUE)`, message: `'${field}' must have at least ${items(rules.min)}, and ${operator} would leave it with fewer.` });\n }\n if (rules.max !== undefined) {\n bounds.push({ field, code: \"max_items\", holds: sql`COALESCE(${size} <= ${rules.max}::numeric, TRUE)`, message: `'${field}' must have at most ${items(rules.max)}, and ${operator} would leave it with more.` });\n }\n return bounds;\n}\n\n/**\n * Which bounds the row, as it now stands, would break — for an UPDATE that\n * carried bounds and matched nothing.\n *\n * A guarded UPDATE matching zero rows means one of three things: the row is not\n * there for this caller (404), a row-level security policy refused the write\n * (403), or the operation's result broke a bound (400). The first two are\n * `explainZeroRowWrite`'s; this answers the third, by evaluating each bound\n * against the row over the same handle and the same key conditions. An empty\n * answer — the row is not visible, or every bound holds — leaves the decision to\n * `explainZeroRowWrite`.\n *\n * It is a second statement, so the row can change in between. A row deleted in\n * between is a 404 here as it would have been anyway. A row changed so that the\n * operation now fits reads as \"every bound holds\" and falls through to the\n * row-level-security answer; the write was refused either way, and the\n * refusal is never turned into a write.\n *\n * Only booleans come back — whether each bound holds — never the stored value.\n */\nexport async function brokenFieldOpBounds(\n handle: DrizzleClient,\n table: PgTable,\n conditions: SQL[],\n bounds: FieldOpBound[]\n): Promise<FieldOpBound[]> {\n if (bounds.length === 0) return [];\n const selection: Record<string, SQL<boolean>> = {};\n bounds.forEach((bound, index) => {\n selection[`bound${index}`] = sql<boolean>`(${bound.holds})`;\n });\n const rows = await handle.select(selection).from(table).where(and(...conditions)).limit(1);\n const row = rows[0];\n if (!row) return [];\n return bounds.filter((_bound, index) => row[`bound${index}`] === false);\n}\n\n/** The 400 a write gets when an operation's result breaks a bound. */\nexport function fieldOpBoundsError(collectionSlug: string, broken: FieldOpBound[]): ApiError {\n const violations = broken.map(({ field, code, message }) => ({ field, code, message }));\n const messages = violations.map(violation => violation.message);\n return ApiError.badRequest(messages.join(\" \"), \"VALIDATION_CONSTRAINT\", {\n collection: collectionSlug,\n violations,\n messages\n });\n}\n","import { eq, and, isNotNull, isNull, sql, SQL } from \"drizzle-orm\";\nimport { AnyPgColumn, PgTable } from \"drizzle-orm/pg-core\";\n// import { NodePgDatabase } from \"drizzle-orm/node-postgres\";\nimport { CollectionConfig, JUNCTION_PIVOT_KEY, Properties, ResolvedRelation, type ResolvedManyToMany, isManyToMany, hasForeignKeyOnTarget } from \"@rebasepro/types\";\nimport { getTableName, resolveCollectionRelations, fieldKeyForColumn } from \"@rebasepro/common\";\nimport { DrizzleConditionBuilder } from \"../utils/drizzle-conditions\";\nimport {\n assertWritableColumns,\n getCollectionByPath,\n getTableForCollection,\n getPrimaryKeys,\n requirePrimaryKeys,\n rowIdentityCondition,\n parseIdValues,\n buildCompositeId\n} from \"./collection-helpers\";\nimport { sanitizeAndConvertDates, serializeDataToServer } from \"../data-transformer\";\nimport { RelationService } from \"./RelationService\";\nimport { beforeQueryCondition, type ReadCallContextProvider } from \"./read-scope\";\nimport { RelationWriteService } from \"./RelationWriteService\";\nimport { FetchService } from \"./FetchService\";\nimport { DrizzleClient } from \"../interfaces\";\nimport { PostgresCollectionRegistry } from \"../collections/PostgresCollectionRegistry\";\nimport {\n assertWritableThrough,\n isJunctionBackedRelation,\n isNestedPath,\n resolveNestedPath,\n type NestedPathHop\n} from \"./nested-path\";\nimport { ApiError, logger, splitFieldOps } from \"@rebasepro/server\";\nimport { brokenFieldOpBounds, compileFieldOpBounds, compileFieldOps, fieldOpBoundsError } from \"./field-op-sql\";\nimport { extractPgError, extractCauseMessage, pgErrorToFriendlyMessage, isRowLevelSecurityDenial } from \"../utils/pg-error-utils\";\nimport { explainZeroRowWrite } from \"./write-denial\";\nimport { resolveSoftDelete, rowInTrashError, softDeleteColumn } from \"./soft-delete\";\n\n/**\n * The top-level properties whose value is stamped once, when the row is\n * created: `on_create` dates and `user_on_create` identities.\n *\n * What they hold is a fact about the row's creation, so a write to a row that\n * already exists has nothing to say about them — an update, and the\n * conflict-update of an upsert that met a stored row. Top level only: a stamp\n * nested inside a `map` is part of that map's value, not a column a write can\n * leave out.\n */\nexport function createStampKeys(properties: Properties | undefined): string[] {\n const keys: string[] = [];\n for (const [key, property] of Object.entries(properties ?? {})) {\n if ((property?.type === \"date\" && property.autoValue === \"on_create\")\n || (property?.type === \"string\" && property.autoValue === \"user_on_create\")) {\n keys.push(key);\n }\n }\n return keys;\n}\n\n/** How {@link PersistService.save} writes a row that may already be stored. */\nexport interface PersistSaveOptions {\n /** INSERT ... ON CONFLICT DO UPDATE. See `SaveProps.upsert`. */\n upsert?: boolean;\n /** The conflict target, instead of the primary key. See `SaveProps.onConflict`. */\n onConflict?: readonly string[];\n /**\n * Keys of `values` that belong to the INSERT alone: what the create\n * pipeline filled in rather than what the caller wrote — a declared\n * `defaultValue`, a tenant stamp. When an upsert's INSERT meets a stored\n * row, those columns keep the value they have. The create-time stamps\n * (`on_create`, `user_on_create`) are left out of the conflict-update\n * without being named here.\n */\n insertOnlyKeys?: readonly string[];\n}\n\n/**\n * Service for handling all row write operations.\n * Handles saving, deleting, and updating rows.\n */\nexport class PersistService {\n /** Reads: whether a row is under a parent, the key a link joins on. */\n private relationService: RelationService;\n /** Writes: junction membership, foreign-key stamping, links. */\n private relationWrites: RelationWriteService;\n private fetchService: FetchService;\n\n constructor(\n private db: DrizzleClient,\n private registry: PostgresCollectionRegistry,\n /**\n * How this service's reads reach the identity their `beforeQuery` hooks\n * run as. Passed by the driver that constructed it; absent only in a\n * test, where a collection declaring the hook is refused rather than\n * read unnarrowed. See `read-scope.ts`.\n */\n private callContext?: ReadCallContextProvider\n ) {\n this.relationService = new RelationService(db, registry, callContext);\n this.relationWrites = new RelationWriteService(db, registry, callContext);\n this.fetchService = new FetchService(db, registry, callContext);\n }\n\n\n /**\n * Set the columns of one many-to-many link, without touching the membership.\n *\n * `collectionPath` is the nested address — `posts/1/tags` — and `targetId`\n * the row on the far side, so this is `PATCH posts/1/tags/5` with a\n * `_pivot` body. Separate from {@link save} because the two mean different\n * things at the same URL: a body of the target's own columns edits the tag,\n * a body of `_pivot` edits the *link* to it, and conflating them would let\n * a caller who meant one silently perform the other.\n */\n async updateRelationPivot(\n collectionPath: string,\n targetId: string | number,\n pivot: Record<string, unknown>\n ): Promise<void> {\n const hop = isNestedPath(collectionPath) ? resolveNestedPath(collectionPath, this.registry) : undefined;\n if (!hop) {\n throw ApiError.badRequest(\n `\"${collectionPath}\" is not a relation on a row, so there is no link to update. ` +\n `A \\`${JUNCTION_PIVOT_KEY}\\` write addresses \\`<collection>/<id>/<relation>/<targetId>\\`.`,\n \"VALIDATION_UNKNOWN_FIELDS\",\n { path: collectionPath }\n );\n }\n assertWritableThrough(hop, collectionPath);\n\n // Membership first, for the same reason `delete` establishes it: the\n // update below matches on both keys, so \"no such link\" and \"a policy\n // refused the write\" would otherwise be the same zero rows.\n if (!await this.relationService.isRelated(hop, targetId)) {\n throw ApiError.notFound(`No row \"${targetId}\" in \"${collectionPath}\" to update the link of.`);\n }\n\n // In a transaction of its own — a savepoint inside a write — like every\n // write here; see `delete`.\n await this.db.transaction(tx => this.relationWrites.updateRelationPivot(tx, hop, targetId, pivot));\n }\n\n\n /**\n * Explain a row write that matched nothing — see {@link explainZeroRowWrite}\n * for why a zero-row write cannot be reported as success.\n */\n private explainZeroRowWrite(\n handle: DrizzleClient,\n table: PgTable,\n conditions: SQL[],\n collectionPath: string,\n id: string | number,\n operation: \"update\" | \"delete\"\n ): Promise<ApiError> {\n return explainZeroRowWrite(\n handle,\n table,\n conditions,\n `Not allowed to ${operation} \"${id}\" in \"${collectionPath}\": a row-level security policy rejected the write.`,\n `No row \"${id}\" in \"${collectionPath}\" to ${operation}.`\n );\n }\n\n /**\n * Delete an row by ID\n */\n async delete(collectionPath: string, id: string | number, _databaseId?: string): Promise<void> {\n // A nested address deletes *through* a relation, and what that removes\n // depends on who owns the target row.\n const hop = isNestedPath(collectionPath) ? resolveNestedPath(collectionPath, this.registry) : undefined;\n if (hop) {\n assertWritableThrough(hop, collectionPath);\n\n if (!await this.relationService.isRelated(hop, id)) {\n throw ApiError.notFound(`No row \"${id}\" in \"${collectionPath}\" to delete.`);\n }\n\n if (isJunctionBackedRelation(hop.relation)) {\n // Shared target: drop the link, not the row.\n if (!isManyToMany(hop.relation)) {\n throw ApiError.badRequest(\n `\"${collectionPath}\" reaches '${hop.targetCollection.slug}' through a multi-hop joinPath, ` +\n `so there is no single link to remove. Delete the row at \"${hop.targetCollection.slug}\" ` +\n \"directly if that is what you meant.\",\n \"RELATION_NOT_UNLINKABLE\"\n );\n }\n await this.db.transaction(tx => this.relationWrites.unlinkRelatedEntity(tx, hop, id));\n return;\n }\n // Owned child (inverse FK): deleting the row is the right meaning,\n // and membership above has established it is this parent's child.\n }\n\n const collection = getCollectionByPath(collectionPath, this.registry);\n const table = getTableForCollection(collection, this.registry);\n // `requirePrimaryKeys`: with no key there is no condition, and a DELETE\n // without one empties the table.\n const condition = rowIdentityCondition(table, requirePrimaryKeys(collection, this.registry), id, collectionPath);\n\n // In a transaction of its own, as `save` is: inside a write's\n // transaction that is a SAVEPOINT, so a delete the database refuses (a\n // foreign key, a trigger) is undone on its own and leaves the write\n // usable. Without one, a hook that caught a failed\n // `context.data.<c>.delete()` — which the platform says is safe to\n // catch — had aborted the whole transaction, and the write answered\n // TRANSACTION_ABORTED (or a 25P02 from the history entry after it).\n let result;\n try {\n result = await this.db.transaction(tx => tx\n .delete(table)\n .where(condition));\n } catch (error: unknown) {\n throw this.toUserFriendlyError(error, collection.slug, collection, \"delete\");\n }\n\n if ((result.rowCount ?? 0) === 0) {\n throw await this.explainZeroRowWrite(this.db, table, [condition], collectionPath, id, \"delete\");\n }\n }\n\n /**\n * Delete all rows from a collection\n */\n async deleteAll(collectionPath: string, _databaseId?: string): Promise<void> {\n const collection = getCollectionByPath(collectionPath, this.registry);\n const table = getTableForCollection(collection, this.registry);\n await this.db.delete(table);\n }\n\n /**\n * The field on the *target* row that records the parent, for a create under\n * a nested one-to-many path.\n *\n * A **field**, not the column: the value is stamped into the caller's\n * payload, which is keyed by wire names — `authorId`, never the `author_id`\n * the relation names its link by. Stamping the column instead put a key on\n * the payload that no property answers to, and `strictWrites` rejected the\n * request the framework had just written to.\n *\n * Returns `undefined` when the link is not a column at all (a multi-hop\n * `joinPath`), so the caller writes the row without stamping anything.\n *\n * `relation.localKey` is deliberately not consulted: it names a column on\n * the *source* table. Falling back to it here — which is what this used to\n * do, and first — stamped the parent's own foreign key onto the child row.\n */\n private resolveParentForeignKeyColumn(hop: NestedPathHop): string | undefined {\n const { relation } = hop;\n\n switch (relation.kind) {\n case \"hasOne\":\n case \"hasMany\":\n return fieldKeyForColumn(hop.targetCollection, relation.foreignKeyOnTarget);\n\n case \"via\":\n // The link lives in an intermediate table, not in a column on\n // the target — nothing to stamp.\n return relation.joinPath.length === 1\n ? fieldKeyForColumn(\n hop.targetCollection,\n DrizzleConditionBuilder.getColumnNamesFromColumns(relation.joinPath[0].on.to)[0]\n )\n : undefined;\n\n default:\n // `belongsTo` names a column on the *parent*, and `manyToMany`\n // is written as a junction row. Neither is a column here, and\n // `assertWritableThrough` has already rejected the first.\n return undefined;\n }\n }\n\n /**\n * Save an row (create or update)\n *\n * With `options.upsert`, the row is written with INSERT ... ON CONFLICT DO\n * UPDATE rather than a plain UPDATE. That is one statement, so it cannot\n * lose a race the way a read-then-write can, and it does not care whether\n * the row already exists — which is what a re-runnable import needs. The\n * conflict is matched on the primary key unless `options.onConflict` names\n * other columns; see {@link SaveProps.onConflict} for why that matters.\n * When the INSERT meets a stored row, the conflict-update sets what the\n * caller wrote and nothing the create alone decides — see\n * {@link PersistSaveOptions.insertOnlyKeys} — and only on a row the caller's\n * `beforeQuery` scope reaches.\n *\n * `values` may carry field operations (`{ views: { $inc: 1 } }`). They are\n * split out here rather than at the REST boundary because every request\n * boundary — HTTP, the WebSocket, in-process `context.data` — reaches this\n * one method, and a rule applied at one door is a rule the other doors do\n * not have. What they compile to is `field-op-sql.ts`.\n */\n async save<M extends Record<string, unknown>>(\n collectionPath: string,\n values: Partial<M>,\n id?: string | number,\n databaseId?: string,\n options?: PersistSaveOptions\n ): Promise<Record<string, unknown>> {\n // If saving under a nested relation path, resolve the relation it ends in.\n let effectiveCollectionPath = collectionPath;\n const effectiveValues: Partial<M> = { ...values };\n let junctionTableInfo: { parentCollection: CollectionConfig; parentId: string | number; relation: ResolvedManyToMany; relationKey: string; } | undefined;\n\n const hop = isNestedPath(collectionPath) ? resolveNestedPath(collectionPath, this.registry) : undefined;\n\n if (hop) {\n assertWritableThrough(hop, collectionPath);\n effectiveCollectionPath = hop.targetCollection.slug;\n\n const parentIdForWrite = () => {\n const parentPks = getPrimaryKeys(hop.parentCollection, this.registry);\n return parseIdValues(hop.parentId, parentPks)[parentPks[0].fieldName];\n };\n\n if (isManyToMany(hop.relation)) {\n // A junction path addresses set membership, so a write through it\n // asserts \"this row is in this parent's set\" — on create *and* on\n // update. The junction row is written after the main write below,\n // idempotently, which is what makes `PUT parent/id/child/childId`\n // able to attach a row that already exists.\n //\n // Unlike an owning foreign key, this takes the row from nobody:\n // its other parents keep it. That is why linking is safe here\n // where reparenting (below) is not.\n junctionTableInfo = {\n parentCollection: hop.parentCollection,\n parentId: parentIdForWrite(),\n relation: hop.relation,\n relationKey: hop.relationKey\n };\n } else if (id !== undefined) {\n // Updating an existing row *through* an owning parent. The parent\n // segment is an assertion about where the row already lives, not\n // an instruction to move it there: injecting the FK here silently\n // reparented whatever id was named, so `PUT authors/1/posts/43`\n // stole post 43 from its real author. Check membership instead,\n // and leave the FK to an explicit value in the body.\n if (!await this.relationService.isRelated(hop, id)) {\n throw ApiError.notFound(`No row \"${id}\" in \"${collectionPath}\" to update.`);\n }\n } else {\n // One-to-many create: stamp the parent's key onto the child's FK.\n const targetColumnName = this.resolveParentForeignKeyColumn(hop);\n\n if (targetColumnName) {\n // Not necessarily the id in the path: a link with a\n // `sourceKey` is pointed at a column on the parent row, and\n // stamping the id would create a child that belongs to\n // nobody — a row the read path would never return again.\n const parentKeyValue = hasForeignKeyOnTarget(hop.relation)\n ? await this.relationService.parentKeyValue(hop.parentCollection, hop.relation, hop.parentId)\n : parentIdForWrite();\n\n if (parentKeyValue === undefined) {\n throw ApiError.badRequest(\n `Cannot create under \"${collectionPath}\": the parent row has no value in ` +\n `\\`sourceKey\\`, so the new row has nothing to point at.`\n );\n }\n\n const existingValue = (effectiveValues as Record<string, unknown>)[targetColumnName];\n if (existingValue !== undefined && existingValue !== null && existingValue !== parentKeyValue) {\n logger.warn(`Overriding provided value '${existingValue}' for FK '${targetColumnName}' with path parent key '${parentKeyValue}'.`);\n }\n (effectiveValues as Record<string, unknown>)[targetColumnName] = parentKeyValue;\n }\n }\n }\n\n const collection = getCollectionByPath(effectiveCollectionPath, this.registry);\n const table = getTableForCollection(collection, this.registry);\n const idInfoArray = getPrimaryKeys(collection, this.registry);\n const primaryKeyFields = idInfoArray.map(info => info.fieldName);\n\n // Build an object mapping required for dynamic returning\n const returningKeys: Record<string, AnyPgColumn> = {};\n idInfoArray.forEach(info => {\n const field = table[info.fieldName as keyof typeof table] as AnyPgColumn;\n if (!field) throw new Error(`Primary key field '${info.fieldName}' not found in table for collection '${effectiveCollectionPath}'`);\n returningKeys[info.fieldName] = field;\n });\n\n // Operations on the stored value, held back from every step below:\n // `serializeDataToServer` would read `{ $inc: 1 }` on a number column\n // as a value to coerce, and `sanitizeAndConvertDates` would hand the\n // marker object to the column as data.\n const { values: plainValues, fieldOps } = splitFieldOps(effectiveValues as Record<string, unknown>);\n const fieldOpKeys = Object.keys(fieldOps);\n if (fieldOpKeys.length > 0) {\n // An operation describes a change to a value that is already\n // stored, so there is nothing for it to act on here. Saying so is\n // the whole point: `views + 1` over a row being inserted is just\n // `1`, and accepting it would make `$inc` mean two different things\n // depending on a race the caller cannot observe.\n if (!id) {\n throw ApiError.badRequest(\n `Field operations (${fieldOpKeys.map(k => `'${k}'`).join(\", \")}) need an existing row to act on, ` +\n `and this write to \"${effectiveCollectionPath}\" is an insert. Send the value itself instead.`,\n \"INVALID_FIELD_OPERATION\"\n );\n }\n if (options?.upsert) {\n throw ApiError.badRequest(\n `Field operations (${fieldOpKeys.map(k => `'${k}'`).join(\", \")}) cannot be combined with an upsert: ` +\n \"an upsert may insert, and there would be no stored value to operate on. \" +\n \"Use an update, or send the value itself.\",\n \"INVALID_FIELD_OPERATION\"\n );\n }\n }\n\n // Separate relations that require special handling\n const relationValues: Record<string, unknown> = {};\n const otherValues: Partial<M> = { ...(plainValues as Partial<M>) };\n const resolvedRelations = resolveCollectionRelations(collection);\n\n for (const key in resolvedRelations) {\n const relation = resolvedRelations[key];\n if (relation && relation.cardinality === \"many\") {\n if (Object.prototype.hasOwnProperty.call(otherValues, key)) {\n relationValues[key] = otherValues[key as keyof M];\n delete otherValues[key as keyof M];\n }\n }\n }\n\n let savedId: string | number;\n try {\n // Transform relations to IDs, then sanitize\n const serializedResult = serializeDataToServer(otherValues as M, collection.properties as Properties, collection, this.registry);\n\n // Extract relation updates from the typed result\n const inverseRelationUpdates = serializedResult.inverseRelationUpdates;\n const joinPathRelationUpdates = serializedResult.joinPathRelationUpdates;\n\n const entityData = sanitizeAndConvertDates(serializedResult.scalarData);\n\n // Everything below builds SQL from the table's own column list, so\n // a key that is not a column is silently left out rather than\n // refused. Say so here, while the key is still in hand.\n assertWritableColumns(entityData as Record<string, unknown>, table, effectiveCollectionPath);\n // The same check for the operated columns. They never reach\n // `entityData` — they are SQL, not values — so without this a\n // `$inc` on a property with no column would be dropped from the\n // statement and answered 200, which is the silent-write defect\n // `assertWritableColumns` exists to prevent.\n if (fieldOpKeys.length > 0) {\n assertWritableColumns(\n Object.fromEntries(fieldOpKeys.map(key => [key, null])),\n table,\n effectiveCollectionPath\n );\n }\n\n savedId = await this.db.transaction(async (tx) => {\n let currentId: string | number;\n\n if (id && !options?.upsert) {\n // Update existing row\n currentId = id; // `id` is already the formatted composite or singular string\n\n // Apply joinPath one-to-one relation updates BEFORE the main UPDATE.\n // This ensures parentSourceCol reads the pre-update FK value, preventing\n // stale joinPath values from corrupting related rows when an\n // intermediate FK (e.g., author_id) changes in the same save.\n // Example: changing author A→B with stale profile P1 (A's):\n // reads old author_id=A → clears P1.author_id → re-sets P1.author_id=A (no-op).\n if (joinPathRelationUpdates.length > 0) {\n await this.relationWrites.updateJoinPathOneToOneRelations(tx, collection, currentId, joinPathRelationUpdates);\n }\n\n // Only issue an UPDATE if there are scalar columns to set.\n // When the payload contains only relation data, entityData is\n // empty after relation stripping and Drizzle throws \"No values to set\".\n const scalarKeys = Object.keys(entityData as Record<string, unknown>);\n // Compiled here, inside the transaction that will run them:\n // an operation is an expression over the row's current\n // value, so it is only correct while the row is locked by\n // the very statement that reads it.\n const compiledOps = fieldOpKeys.length > 0\n ? compileFieldOps(table, fieldOps, { collectionPath: effectiveCollectionPath })\n : undefined;\n // What each operation produces, held to the property's\n // declared bounds by the same statement — see\n // `compileFieldOpBounds` for why it cannot be a read first.\n const bounds = fieldOpKeys.length > 0\n ? compileFieldOpBounds(table, fieldOps, collection.properties ?? {}, { collectionPath: effectiveCollectionPath })\n : [];\n if (scalarKeys.length > 0 || compiledOps) {\n const updateQuery = tx.update(table).set({\n ...(entityData as Record<string, unknown>),\n ...(compiledOps ?? {})\n });\n const condition = rowIdentityCondition(table, idInfoArray, id, effectiveCollectionPath);\n const updateResult = await updateQuery.where(and(condition, ...bounds.map(bound => bound.holds)));\n\n // Throwing rolls the transaction back, so relation writes\n // already applied above do not survive a rejected update.\n if ((updateResult.rowCount ?? 0) === 0) {\n const broken = await brokenFieldOpBounds(tx, table, [condition], bounds);\n if (broken.length > 0) throw fieldOpBoundsError(collection.slug, broken);\n throw await this.explainZeroRowWrite(\n tx, table, [condition], effectiveCollectionPath, currentId, \"update\"\n );\n }\n }\n } else {\n const dataForInsert = { ...(entityData as Record<string, unknown>) };\n\n // An explicit id given alongside upsert is the conflict target,\n // so fold it into the row before the empty-key strip below.\n if (id && options?.upsert) {\n Object.assign(dataForInsert, parseIdValues(id, idInfoArray));\n }\n\n // Strip empty primary keys so the database defaults (e.g. uuid_gen(), auto-increment) can trigger\n for (const info of idInfoArray) {\n if (dataForInsert[info.fieldName] === \"\" || dataForInsert[info.fieldName] === null || dataForInsert[info.fieldName] === undefined) {\n delete dataForInsert[info.fieldName];\n }\n }\n\n const insertQuery = tx.insert(table).values(dataForInsert);\n\n // ON CONFLICT needs a real conflict target. The primary key\n // is the one guaranteed to exist and is the default; a\n // caller may name another column set, which the REST layer\n // has already checked carries a uniqueness guarantee (see\n // `resolveConflictTarget`). Either way every target column\n // must be present in the row, or there is nothing to match\n // on — the row is a plain insert and a duplicate should\n // still raise.\n const targetFields = options?.onConflict && options.onConflict.length > 0\n ? [...options.onConflict]\n : idInfoArray.map((info) => info.fieldName);\n const hasFullKey = targetFields.length > 0\n && targetFields.every((field) => dataForInsert[field] !== undefined);\n\n let result;\n if (options?.upsert && hasFullKey) {\n const target = targetFields.map((field) => {\n const column = table[field as keyof typeof table] as AnyPgColumn | undefined;\n if (!column) {\n throw ApiError.badRequest(\n `'${field}' is not a column of \"${effectiveCollectionPath}\", so it cannot be an upsert target.`,\n \"INVALID_CONFLICT_TARGET\"\n );\n }\n return column;\n });\n const set = { ...dataForInsert };\n // Never reassign the target columns to themselves in the\n // UPDATE branch; Postgres rejects that against the\n // conflict target. The primary key is excluded as well\n // when the target is a natural key: a conflict means the\n // stored row already has a key, and overwriting it with\n // one the caller invented would move the row.\n for (const info of idInfoArray) delete set[info.fieldName];\n for (const field of targetFields) delete set[field];\n\n // A conflict means the row was already there, so its\n // create-time stamps (`on_create`, `user_on_create`)\n // are facts about the past and not this write's to\n // redecide. Bulk rows are saved with `status: \"new\"` on\n // purpose (an import's rows carry a natural key for\n // rows that may not exist), which computes the stamps\n // for every row — and the conflict-update then wrote\n // them over the originals. A nightly re-import reset\n // `createdAt` on everything it touched, and handed\n // `createdBy` to whoever ran it. The INSERT branch keeps\n // the values: they are right there.\n for (const key of createStampKeys(collection.properties as Properties | undefined)) {\n delete set[key];\n }\n // The same for everything else the create alone filled\n // in. A `defaultValue` describes a row the caller did\n // not finish describing, not one that exists: written\n // here it reset every omitted field of every stored row\n // an import touched — `status` back to \"draft\".\n for (const key of options.insertOnlyKeys ?? []) delete set[key];\n\n // Only a row the caller's `beforeQuery` scope reaches is\n // theirs to update. Their UPDATE is refused before it\n // runs when the row is out of scope; here the stored row\n // is only known once the INSERT meets it, so the scope\n // rides along as the conflict-update's own WHERE.\n const scope = await this.writeScope(collection, effectiveCollectionPath, table);\n // And never a row in the trash. The driver refuses an\n // upsert onto a trashed key before it gets here (see\n // `findUpsertTarget`); this is the same rule held by\n // the statement, for the row that read could not see\n // or that was trashed since.\n const softDelete = resolveSoftDelete(collection);\n const trashColumn = softDelete ? softDeleteColumn(collection, table) : undefined;\n const writable = trashColumn\n ? (scope ? and(scope, isNull(trashColumn)) : isNull(trashColumn))\n : scope;\n const updating = Object.keys(set).length > 0;\n result = updating\n ? await insertQuery.onConflictDoUpdate({ target, set, setWhere: writable }).returning(returningKeys)\n : await insertQuery.onConflictDoNothing({ target }).returning(returningKeys);\n\n if (result.length === 0) {\n // The key is taken and the statement changed nothing:\n // the stored row is outside the caller's scope, in\n // the trash, or there was nothing to change on it.\n // Asked of the row itself, through the same scope,\n // rather than falling back to an id the caller may\n // not address — which then failed the read-back\n // below as a 500.\n const keyConditions: SQL[] = target.map((column, i) => eq(column, dataForInsert[targetFields[i]]));\n if (scope) keyConditions.push(scope);\n const keyLabel = targetFields.map((field) => String(dataForInsert[field])).join(\", \");\n if (softDelete && trashColumn) {\n const [inTrash] = await tx.select(returningKeys).from(table)\n .where(and(...keyConditions, isNotNull(trashColumn))).limit(1);\n if (inTrash) throw rowInTrashError(effectiveCollectionPath, keyLabel, softDelete);\n }\n const missing = `No row \"${keyLabel}\" in \"${effectiveCollectionPath}\" to update.`;\n if (updating) {\n throw await explainZeroRowWrite(\n tx, table, keyConditions,\n `Not allowed to update \"${keyLabel}\" in \"${effectiveCollectionPath}\": a row-level security policy rejected the write.`,\n missing\n );\n }\n result = await tx.select(returningKeys).from(table).where(and(...keyConditions)).limit(1);\n if (result.length === 0) throw ApiError.notFound(missing);\n }\n } else {\n result = await insertQuery.returning(returningKeys);\n }\n\n // DO NOTHING returns no row when it skipped, and an upsert whose\n // UPDATE branch is filtered by RLS returns none either. Fall back\n // to the id we were given rather than reading undefined.\n const resultRow = result[0];\n if (!resultRow) {\n if (id) {\n currentId = id;\n } else {\n throw ApiError.forbidden(\n `Not allowed to write to \"${effectiveCollectionPath}\": the row was rejected by a row-level security policy.`,\n \"WRITE_DENIED\"\n );\n }\n } else {\n currentId = buildCompositeId(resultRow, idInfoArray);\n }\n\n // For inserts, apply joinPath after since the parent row didn't exist before\n if (joinPathRelationUpdates.length > 0) {\n await this.relationWrites.updateJoinPathOneToOneRelations(tx, collection, currentId, joinPathRelationUpdates);\n }\n }\n\n // Handle inverse relation updates\n if (inverseRelationUpdates.length > 0) {\n await this.relationWrites.updateInverseRelations(tx, collection, currentId, inverseRelationUpdates);\n }\n\n // Update many-to-many relations\n if (Object.keys(relationValues).length > 0) {\n await this.relationWrites.updateRelationsUsingJoins(tx, collection, currentId, relationValues);\n }\n\n // Attach the row to the parent's set. Runs for updates as well as\n // creates — it used to be gated on `!id`, which is why a row that\n // already existed could never be added to a parent through its\n // path. The insert is idempotent, so re-asserting a link is a\n // no-op rather than a duplicate-key error.\n if (junctionTableInfo) {\n await this.relationWrites.handleJunctionTableCreation(tx, currentId, junctionTableInfo);\n }\n\n return currentId;\n });\n } catch (error: unknown) {\n throw this.toUserFriendlyError(error, collection.slug, collection);\n }\n\n // Fetch the saved row back through the same walk `GET /:id` serves, so a\n // write's answer is the read that follows it. This used to be `fetchOne`\n // — the admin view-model walk — whose `__type: \"relation\"` refs then\n // leaked into REST responses, afterSave callbacks, history records and\n // app-level realtime payloads, none of which see that shape from any\n // read path. The admin does not need them here either: its rows arrive\n // over the realtime subscription refetch, which still serves the\n // view-model walk.\n //\n // `withDeleted` on the read-back, which is not the same question the\n // caller's own read asks. A soft delete IS a save — it stamps\n // `deleted_at` — and the default read hides stamped rows, so this walk\n // could not see the row it had just written and threw \"Could not fetch\n // row after save.\" inside the transaction, which rolled the stamp back.\n // Delete answered 500 on every soft-delete collection and the row\n // stayed live: the feature did not work at all.\n //\n // Showing it is also the correct answer on its own terms. Whoever just\n // wrote this row is the one party who must be able to see the result of\n // their own write, whatever its deleted state — and an ordinary save is\n // unaffected, because an undeleted row is visible either way.\n const finalEntity = await this.fetchService.fetchOneForRest(\n collection.slug, savedId, undefined, databaseId, { withDeleted: true }\n );\n if (!finalEntity) throw new Error(\"Could not fetch row after save.\");\n return finalEntity;\n }\n\n /**\n * The caller's `beforeQuery` scope over this table, as one condition: what\n * a stored row must satisfy for a write that meets it to change it. The\n * same scope the update gate and the single get apply; `undefined` when the\n * collection declares no hook.\n */\n private writeScope(collection: CollectionConfig, collectionPath: string, table: PgTable): Promise<SQL | undefined> {\n const [key] = getPrimaryKeys(collection, this.registry);\n return beforeQueryCondition(\n { registry: this.registry, callContext: this.callContext },\n collection, collectionPath, table as PgTable<never>,\n { operation: \"get\", query: {} },\n {\n collection,\n registry: this.registry,\n sourceIdColumn: key ? table[key.fieldName as keyof typeof table] as AnyPgColumn | undefined : undefined\n }\n );\n }\n\n /**\n * Get the RelationService instance for external use\n */\n getRelationService(): RelationService {\n return this.relationService;\n }\n\n /**\n * The write half, for external use. Separate from\n * {@link getRelationService} because they are separate objects now: reads\n * answer questions, writes change rows, and the callers of one are not the\n * callers of the other.\n */\n getRelationWriteService(): RelationWriteService {\n return this.relationWrites;\n }\n\n /**\n * Get the FetchService instance for external use\n */\n getFetchService(): FetchService {\n return this.fetchService;\n }\n\n /**\n * Translate raw PostgreSQL / Drizzle errors into user-friendly messages.\n *\n * `collection` is passed so the SQLSTATE can be turned into a *field*: the\n * error carries a physical column and the caller only ever sees wire names,\n * so `author_id` had to be translated to `authorId` by whoever read the\n * message — which nothing does. See `pgFieldViolations`.\n */\n private toUserFriendlyError(\n error: unknown,\n collectionSlug: string,\n collection?: CollectionConfig,\n operation: \"save\" | \"delete\" = \"save\"\n ): Error {\n // Deliberate API errors already carry their own status, code and wording.\n // Re-wrapping one flattens it into a generic Error, and the status is lost\n // on the way out — a policy rejection would surface as a 500. Matched by\n // name as well as instance: a duplicated module copy breaks `instanceof`.\n if (error instanceof ApiError || (error as Error)?.name === \"ApiError\") {\n return error as Error;\n }\n\n const pgError = extractPgError(error);\n\n if (pgError) {\n const { message, code, violations } = pgErrorToFriendlyMessage(pgError, collectionSlug, { collection });\n /** `details` for a 4xx, carrying the field(s) — never the values. */\n const details = violations.length > 0\n ? { collection: collectionSlug, violations }\n : undefined;\n // This is the only layer that holds the SQLSTATE, so it is the only\n // one that can say whose fault a failure was. Returning a bare\n // `Error` threw that away, and the REST layer compensated by calling\n // *every* unclassified error a 400 — so an unreachable database was\n // reported to callers as a bad request. Classes 22 (data exception)\n // and 23 (integrity constraint violation) are the caller's data;\n // everything else — a dropped connection, a missing column, a\n // *privilege* problem — is ours, and stays a 500.\n //\n // Within class 23, a conflict with rows already there is a 409\n // rather than a 400: a duplicate key, and a row other rows still\n // reference. A delete refused by a foreign key is the second\n // whichever way the key is declared — `RESTRICT` raises 23001,\n // `NO ACTION` raises 23503 — while a *save* raising 23503 named a\n // target that does not exist, which is the caller's data.\n if (/^2[23]/.test(code)) {\n const conflict = code === \"23505\" || code === \"23001\"\n || (code === \"23503\" && operation === \"delete\");\n return conflict\n ? ApiError.conflict(message, `PG_${code}`, details)\n : ApiError.badRequest(message, `PG_${code}`, details);\n }\n // With one exception inside class 42: a row-level-security policy\n // refusing the caller is not a fault at all, it is access control\n // working. It fell through to the 500 below, so the client could not\n // tell \"you may not do this\" from \"the server is broken\" — and a\n // 500's message is sanitized on the way out, so the reason was lost\n // too. `expected`, because a caller attempting what their policies\n // forbid is routine and should not page anyone; that is the same\n // treatment `unauthenticated()` gets one status code down.\n // `WRITE_DENIED`, the same code `explainZeroRowWrite` returns for an\n // UPDATE or DELETE that RLS refused. Those already answered 403; only\n // INSERT reached here, because a failed `WITH CHECK` raises 42501 while\n // a refused UPDATE simply matches no rows. Two spellings of one denial\n // should not be two status codes.\n //\n // Left at the default log level rather than `expected`: `ApiError`'s\n // own documentation puts \"a permission the database refused\" in the\n // stays-at-warn column, and the status code is the defect here. The\n // log level is a separate decision that already has an answer.\n if (isRowLevelSecurityDenial(error)) {\n return ApiError.forbidden(message, \"WRITE_DENIED\");\n }\n // `{ cause }` is load-bearing, not tidiness. The API error handler\n // walks the cause chain for a SQLSTATE and classifies from it: a\n // `42P01` becomes `SCHEMA_DRIFT` with \"table X does not exist. Run\n // `rebase db push`\", and a `42501` becomes `DB_PERMISSION_DENIED`\n // naming row-level security. A bare `new Error(message)` severs the\n // chain, so every write against a table the code expects and the\n // database lacks answered `INTERNAL_ERROR` / \"An unexpected error\n // occurred\" — the one failure with the most actionable fix in the\n // product, reported as the least actionable message in it. Reads\n // were classified correctly the whole time, which is why the\n // asymmetry went unnoticed.\n return new Error(message, { cause: error });\n }\n\n // No PG error found — try to extract a useful message from the\n // Drizzle wrapper instead of leaking the raw SQL query + params.\n const causeMessage = extractCauseMessage(error);\n if (causeMessage) {\n return new Error(`Database error in \"${collectionSlug}\": ${causeMessage}`, { cause: error });\n }\n\n // Last resort: generic message, never leak raw SQL\n if (error instanceof Error && error.message.startsWith(\"Failed query:\")) {\n return new Error(`Failed to save row in \"${collectionSlug}\". Check server logs for details.`, { cause: error });\n }\n return new Error(`Database error in \"${collectionSlug}\": ${String(error)}`, { cause: error });\n }\n}\n","// import { NodePgDatabase } from \"drizzle-orm/node-postgres\";\nimport { FilterValues, IncludeSpec, LogicalCondition, OrderByTuple } from \"@rebasepro/types\";\nimport type { VectorSearchParams } from \"@rebasepro/types\";\nimport { FetchService } from \"./FetchService\";\nimport type { ReadCallContextProvider } from \"./read-scope\";\nimport type { WithDeleted } from \"./soft-delete\";\nimport { PersistService, type PersistSaveOptions } from \"./PersistService\";\nimport { RelationService } from \"./RelationService\";\nimport { DataRepository, FetchCollectionOptions, SearchOptions, CountOptions, DrizzleClient } from \"../interfaces\";\nimport { PostgresCollectionRegistry } from \"../collections/PostgresCollectionRegistry\";\nimport { logger, rawQueryLoggingEnabled } from \"@rebasepro/server\";\n\n// Re-export data transformer functions for external use\nexport { sanitizeAndConvertDates, serializeDataToServer, parseDataFromServer } from \"../data-transformer\";\n\n// Re-export service classes for direct use\nexport { FetchService } from \"./FetchService\";\nexport { PersistService } from \"./PersistService\";\nexport { RelationService } from \"./RelationService\";\n\n// Re-export interfaces\nexport * from \"../interfaces\";\n\n/**\n * DataService - Facade for row operations.\n *\n * This class provides a unified API for row CRUD operations by delegating\n * to specialized services:\n * - FetchService: Read operations (fetch, search, count)\n * - PersistService: Write operations (save, delete)\n * - RelationService: Relation operations (fetch related, update relations)\n *\n * Implements the DataRepository interface for database abstraction.\n */\nexport class DataService implements DataRepository {\n private fetchService: FetchService;\n private persistService: PersistService;\n\n constructor(\n private db: DrizzleClient,\n private registry: PostgresCollectionRegistry,\n /**\n * How this service's reads reach the identity their `beforeQuery` hooks\n * run as. Passed by the driver that constructed it; absent only in a\n * test, where a collection declaring the hook is refused rather than\n * read unnarrowed. See `read-scope.ts`.\n */\n private callContext?: ReadCallContextProvider\n ) {\n this.fetchService = new FetchService(db, registry, callContext);\n this.persistService = new PersistService(db, registry, callContext);\n }\n\n // =============================================================\n // READ OPERATIONS - Delegated to FetchService\n // =============================================================\n\n /**\n * Fetch a single row by ID\n */\n async fetchOne<M extends Record<string, unknown>>(\n collectionPath: string,\n id: string | number,\n databaseId?: string,\n /** See `FetchCollectionProps.withDeleted`. */\n withDeleted?: WithDeleted\n ): Promise<Record<string, unknown> | undefined> {\n return this.fetchService.fetchOne<M>(collectionPath, id, databaseId, withDeleted);\n }\n\n /**\n * Fetch a collection of rows with optional filtering, ordering, and pagination\n */\n async fetchCollection<M extends Record<string, unknown>>(\n collectionPath: string,\n // The service's own options, rather than a second list of them: this\n // facade re-listed eleven fields and the driver re-listed ten of those,\n // so every option added to the fetch had two places to be dropped.\n options: Parameters<FetchService[\"fetchCollection\"]>[1] = {}\n ): Promise<Record<string, unknown>[]> {\n return this.fetchService.fetchCollection<M>(collectionPath, options);\n }\n\n /**\n * The REST read pipeline: flat rows, with exactly the relations `include`\n * names — see {@link FetchService.fetchCollectionForRest}.\n *\n * Exposed on the facade because it is what *every* consumer should be\n * reading through. The realtime refetch used to go via `fetchCollection`\n * above, which nests a relation under a `{ __type: \"relation\" }` envelope\n * and loaded every relation regardless, so a subscription and a REST list\n * answered the same query with two different row shapes.\n */\n async fetchCollectionForRest<M extends Record<string, unknown>>(\n collectionPath: string,\n options: Parameters<FetchService[\"fetchCollectionForRest\"]>[1] = {},\n include?: IncludeSpec\n ): Promise<Record<string, unknown>[]> {\n return this.fetchService.fetchCollectionForRest<M>(collectionPath, options, include);\n }\n\n /** One row, through the same pipeline. See {@link FetchService.fetchOneForRest}. */\n async fetchOneForRest<M extends Record<string, unknown>>(\n collectionPath: string,\n id: string | number,\n include?: IncludeSpec,\n databaseId?: string,\n options?: {\n fields?: string[];\n /** See `FetchCollectionProps.withDeleted`. */\n withDeleted?: WithDeleted;\n }\n ): Promise<Record<string, unknown> | null> {\n return this.fetchService.fetchOneForRest<M>(collectionPath, id, include, databaseId, options);\n }\n\n /** See {@link FetchService.cursorFor}. */\n cursorFor(\n collectionPath: string,\n row: Record<string, unknown>,\n orderBy?: OrderByTuple[]\n ): string | undefined {\n return this.fetchService.cursorFor(collectionPath, row, orderBy);\n }\n\n /**\n * Search rows by text\n */\n async searchRows<M extends Record<string, unknown>>(\n collectionPath: string,\n searchString: string,\n options: {\n filter?: FilterValues<Extract<keyof M, string>>;\n /** An `or(...)`/`and(...)` group, applied alongside `filter`. */\n logical?: LogicalCondition;\n orderBy?: string | OrderByTuple[];\n order?: \"desc\" | \"asc\";\n limit?: number;\n databaseId?: string;\n searchExplain?: boolean;\n } = {}\n ): Promise<Record<string, unknown>[]> {\n return this.fetchService.searchRows<M>(collectionPath, searchString, options);\n }\n\n /**\n * Count rows in a collection\n */\n async count<M extends Record<string, unknown>>(\n collectionPath: string,\n options: {\n filter?: FilterValues<Extract<keyof M, string>>;\n /** An `or(...)`/`and(...)` group, applied alongside `filter`. */\n logical?: LogicalCondition;\n searchString?: string;\n databaseId?: string;\n /** Only the `threshold` narrows the count — see `FetchService.count`. */\n vectorSearch?: VectorSearchParams;\n /** See `FetchCollectionProps.withDeleted`. */\n withDeleted?: WithDeleted;\n } = {}\n ): Promise<number> {\n return this.fetchService.count<M>(collectionPath, options);\n }\n\n /**\n * Check if a field value is unique in a collection\n */\n async checkUniqueField(\n collectionPath: string,\n fieldName: string,\n value: unknown,\n excludeEntityId?: string,\n databaseId?: string\n ): Promise<boolean> {\n return this.fetchService.checkUniqueField(collectionPath, fieldName, value, excludeEntityId, databaseId);\n }\n\n /**\n * Fetch rows related to a parent row\n */\n async fetchRelatedEntities<M extends Record<string, unknown>>(\n parentCollectionPath: string,\n parentId: string | number,\n relationKey: string,\n options: {\n filter?: FilterValues<Extract<keyof M, string>>;\n orderBy?: string | OrderByTuple[];\n order?: \"desc\" | \"asc\";\n limit?: number;\n startAfter?: Record<string, unknown>;\n searchString?: string;\n databaseId?: string;\n } = {}\n ): Promise<Record<string, unknown>[]> {\n const rows = await this.fetchService.getRelationService().fetchRelatedEntities<M>(\n parentCollectionPath,\n parentId,\n relationKey,\n options\n );\n return rows.map(e => ({ ...e.values, id: e.id }));\n }\n\n // =============================================================\n // WRITE OPERATIONS - Delegated to PersistService\n // =============================================================\n\n /**\n * Save an row (create or update)\n */\n async save<M extends Record<string, unknown>>(\n collectionPath: string,\n values: Partial<M>,\n id?: string | number,\n databaseId?: string,\n options?: PersistSaveOptions\n ): Promise<Record<string, unknown>> {\n return this.persistService.save<M>(collectionPath, values, id, databaseId, options);\n }\n\n /**\n * Delete an row by ID\n */\n async delete(\n collectionPath: string,\n id: string | number,\n databaseId?: string\n ): Promise<void> {\n return this.persistService.delete(collectionPath, id, databaseId);\n }\n\n /**\n * Delete all rows from a collection\n */\n /** See {@link PersistService.updateRelationPivot}. */\n async updateRelationPivot(\n collectionPath: string,\n targetId: string | number,\n pivot: Record<string, unknown>\n ): Promise<void> {\n return this.persistService.updateRelationPivot(collectionPath, targetId, pivot);\n }\n\n async deleteAll(collectionPath: string, databaseId?: string): Promise<void> {\n return this.persistService.deleteAll(collectionPath, databaseId);\n }\n\n\n /**\n * Execute raw SQL\n */\n async executeSql(sqlText: string, params?: unknown[]): Promise<Record<string, unknown>[]> {\n // Through the logger, at `debug`, behind the same switch that lifts the\n // `Failed query:` redaction. A `console.debug` gated on `NODE_ENV`\n // alone put every statement on stdout whatever `LOG_LEVEL` said — a\n // 60-second idle backend emitted 478 lines, 18 of them the job\n // poller's `UPDATE rebase.jobs … FOR UPDATE SKIP LOCKED` — and it\n // bypassed the one function where redaction lives.\n if (rawQueryLoggingEnabled()) {\n logger.debug(`Executing raw SQL: ${sqlText}${params?.length ? ` with ${params.length} params` : \"\"}`);\n }\n const { sql } = await import(\"drizzle-orm\");\n\n let result;\n if (params && params.length > 0) {\n // Build a parameterized query using Drizzle's sql tagged template.\n // Split the SQL text on $1, $2, … placeholders and interleave\n // with sql.param() calls so the underlying pg driver binds them safely.\n const parts = sqlText.split(/\\$(\\d+)/);\n const chunks: ReturnType<typeof sql.raw | typeof sql.param>[] = [];\n for (let i = 0; i < parts.length; i++) {\n if (i % 2 === 0) {\n // Literal SQL text fragment\n if (parts[i].length > 0) {\n chunks.push(sql.raw(parts[i]));\n }\n } else {\n // Parameter reference — $N (1-indexed)\n const paramIndex = Number(parts[i]) - 1;\n chunks.push(sql.param(params[paramIndex]));\n }\n }\n const query = sql.join(chunks, sql.raw(\"\"));\n result = await this.db.execute(query);\n } else {\n result = await this.db.execute(sql.raw(sqlText));\n }\n\n const rows = result.rows;\n if (rawQueryLoggingEnabled()) {\n logger.debug(`SQL executed successfully. Returned ${Array.isArray(rows) ? rows.length : \"non-array\"} rows.`);\n }\n return rows as Record<string, unknown>[];\n }\n\n // =============================================================\n // SERVICE ACCESSORS\n // =============================================================\n\n /**\n * Get the underlying FetchService for advanced use\n */\n getFetchService(): FetchService {\n return this.fetchService;\n }\n\n /**\n * Get the underlying PersistService for advanced use\n */\n getPersistService(): PersistService {\n return this.persistService;\n }\n\n /**\n * Get the underlying RelationService for advanced use\n */\n getRelationService(): RelationService {\n return this.fetchService.getRelationService();\n }\n}\n","import { AsyncLocalStorage } from \"node:async_hooks\";\nimport { sql } from \"drizzle-orm\";\nimport { ApiError, logger, type AmbientTransaction } from \"@rebasepro/server\";\nimport type { RebaseCallContext } from \"@rebasepro/types\";\nimport type { DrizzleClient } from \"../interfaces\";\nimport { extractPgError } from \"../utils/pg-error-utils\";\n\ntype RunSql = (sqlText: string, params?: unknown[]) => Promise<Record<string, unknown>[]>;\n\n/**\n * The write transaction a request is running inside, published to whatever\n * the request's callbacks call into.\n *\n * `AuthenticatedPostgresBackendDriver.withTransaction` opens one of these for\n * every read-write transaction. A collection callback runs inside that\n * transaction, and what it hands off — a job, a queue message, a topic event,\n * a history entry — has to commit with the write or not at all. Those are\n * written by code that is not handed the transaction (the job store lives in\n * `@rebasepro/server` and knows no driver), so the transaction is found here\n * instead: through `AsyncLocalStorage`, and through the resolver\n * `@rebasepro/server` reads.\n *\n * Only write transactions. A read opens its own `READ ONLY` transaction, where\n * an insert would fail with 25006, and nothing a read does is undone by\n * anything — so inside a read the scope is explicitly empty and a queued job\n * commits on its own, as it always did.\n */\nexport class WriteTransactionScope implements AmbientTransaction {\n private run?: RunSql;\n private open = true;\n private readonly inflight = new Set<Promise<unknown>>();\n private readonly commitHooks: Array<() => void> = [];\n /** The first tracked statement that failed, whether or not its caller caught it. */\n private failure?: unknown;\n private readonly settledHooks: Array<(context: RebaseCallContext) => Promise<void>> = [];\n private settledContext?: RebaseCallContext;\n\n /** The transaction handle, for statements in this package that are already written against drizzle. */\n tx?: DrizzleClient;\n\n /** Attach the transaction, once it has begun. */\n bind(tx: DrizzleClient, run: RunSql): void {\n this.tx = tx;\n this.run = run;\n }\n\n /** Bound, and not yet committing. */\n get active(): boolean {\n return this.open && this.run !== undefined;\n }\n\n exec(sqlText: string, params?: unknown[]): Promise<Record<string, unknown>[]> {\n if (!this.active) {\n return Promise.reject(new Error(\"This write's transaction is no longer open to new statements.\"));\n }\n return this.track(this.run!(sqlText, params));\n }\n\n /**\n * Count a statement as part of the transaction, so the commit waits for it.\n * A callback that queues work without awaiting it has still asked for that\n * work to be part of the write.\n *\n * Settled into a native promise first, and only that one is handed back:\n * drizzle's query builders re-run their statement on every `.then()`, so\n * observing one here and awaiting it at the call site would insert twice.\n */\n track<T>(statement: PromiseLike<T>): Promise<T> {\n const settled = Promise.resolve(statement);\n this.inflight.add(settled);\n const done = () => { this.inflight.delete(settled); };\n settled.then(done, (error: unknown) => {\n this.failure ??= error;\n done();\n });\n return settled;\n }\n\n afterCommit(fn: () => void): void {\n this.commitHooks.push(fn);\n }\n\n /**\n * Wait for every tracked statement, then close to new ones — called as the\n * last thing inside the transaction, before its commit. A statement that\n * arrives after this cannot be part of the write, so it runs on its own\n * connection, as if outside any write.\n */\n async settle(): Promise<void> {\n while (this.inflight.size > 0) {\n await Promise.allSettled([...this.inflight]);\n }\n this.open = false;\n }\n\n /**\n * Refuse to commit a transaction a failed statement has aborted.\n *\n * Once any statement fails, Postgres aborts the whole transaction, and\n * catching the error in JavaScript does not undo that. The COMMIT that\n * follows is answered with the tag `ROLLBACK` and no error, so the driver\n * resolves it: the write reported success — 200, webhooks, realtime,\n * the idempotency record — while nothing was stored. A callback that\n * wrapped a lookup or a job enqueue in `try/catch` was enough.\n *\n * So one statement is asked of the transaction before its commit. On an\n * aborted one it fails with `25P02`, and the write is refused and rolled\n * back instead. The database refusing a `context.data` create, update or\n * delete is not caught by this: each of those statements runs in a\n * savepoint, is undone on its own, and leaves the transaction usable —\n * which is what makes catching one safe. (Deletes ran without one, so a\n * caught refused delete landed here, under a message saying catching it\n * was safe.)\n */\n async assertCommittable(): Promise<void> {\n if (!this.tx) return;\n try {\n await this.tx.execute(sql`SELECT 1`);\n } catch (error) {\n if (extractPgError(error)?.code !== \"25P02\") throw error;\n logger.error(\n \"[transaction] A statement on this write's transaction failed and its error was caught; the write was rolled back\",\n { error: this.failure ?? error }\n );\n const refusal = new ApiError(\n 500,\n \"TRANSACTION_ABORTED\",\n \"A statement on this write's transaction failed and its error was caught. That aborts the \" +\n \"transaction in Postgres, so the write was rolled back and nothing was stored. Only a \" +\n \"`context.data` create, update or delete the database refused is undone on its own and may \" +\n \"be caught; any other failed statement — a read (including the one an update or delete makes \" +\n \"to find its row), a job enqueue — has to be let through.\"\n );\n refusal.cause = this.failure ?? error;\n throw refusal;\n }\n }\n\n /**\n * What a hook deferred with {@link afterSettled} is handed as `context`:\n * the same caller, on a driver where every call is a transaction of its\n * own. The write's transaction is gone by the time the hook runs, so a\n * context bound to it would be bound to a connection back in the pool.\n */\n setSettledContext(context: RebaseCallContext): void {\n this.settledContext = context;\n }\n\n /**\n * Run `fn` once this write's transaction is over, whichever way it went,\n * outside it — for what a failure hook hands off (a job, a queue message,\n * a webhook), which must not ride the transaction the failure rolls back.\n * `false` when this scope has no context to hand it; the caller runs it\n * itself then.\n */\n afterSettled(fn: (context: RebaseCallContext) => Promise<void>): boolean {\n if (!this.settledContext) return false;\n this.settledHooks.push(fn);\n return true;\n }\n\n /** The transaction committed or rolled back: run what was waiting for either. */\n async settled(): Promise<void> {\n const context = this.settledContext;\n if (!context) return;\n for (const hook of this.settledHooks.splice(0)) {\n try {\n await storage.run(undefined, () => hook(context));\n } catch (error) {\n logger.error(\"[transaction] A hook deferred to the end of the write threw\", { error });\n }\n }\n }\n\n /**\n * The transaction is over, whichever way it went. Nothing may reach it now:\n * its client is back in the pool, and a callback's detached promise still\n * carries this scope in its async context.\n */\n close(): void {\n this.open = false;\n }\n\n /** The transaction committed: run what was waiting for that, outside any write. */\n committed(): void {\n for (const hook of this.commitHooks.splice(0)) {\n try {\n storage.run(undefined, hook);\n } catch (error) {\n logger.error(\"[transaction] An after-commit hook threw; the write had already committed\", { error });\n }\n }\n }\n}\n\n/**\n * On `globalThis`, like the singleton, so two copies of this package share one\n * store: the copy that opened a transaction and the copy whose code asks for it\n * are not guaranteed to be the same.\n */\nconst STORAGE_SLOT = Symbol.for(\"rebase.postgres.writeTransactionScope\");\nconst storage: AsyncLocalStorage<WriteTransactionScope | undefined> =\n ((globalThis as Record<symbol, unknown>)[STORAGE_SLOT] ??=\n new AsyncLocalStorage<WriteTransactionScope | undefined>()) as AsyncLocalStorage<WriteTransactionScope | undefined>;\n\n/** The open write transaction the caller is inside, if any. */\nexport function currentWriteScope(): WriteTransactionScope | undefined {\n const scope = storage.getStore();\n return scope?.active ? scope : undefined;\n}\n\n/**\n * Run `fn` with `scope` as the current write transaction. `undefined` clears\n * it: a read nested inside a write's callback is not part of that write.\n */\nexport function runInWriteScope<T>(scope: WriteTransactionScope | undefined, fn: () => Promise<T>): Promise<T> {\n return storage.run(scope, fn);\n}\n\n/**\n * Publish the scope to `@rebasepro/server`'s job store and webhook dispatcher.\n *\n * Written to the slot directly, not through `setAmbientTransactionResolver`:\n * the runtime image supplies `@rebasepro/server` while a bundle brings this\n * package, and importing a function an older image does not export would fail\n * to link and stop the boot. Through the slot, an older server simply never\n * reads it. The key is frozen — `@rebasepro/server/src/db/ambient-transaction.ts`\n * reads the same one, and `write-transaction-scope-e2e.test.ts` fails if they\n * drift apart.\n */\n(globalThis as Record<symbol, unknown>)[Symbol.for(\"rebase.server.ambientTransactionResolver\")] = currentWriteScope;\n","/**\n * How deep writes started by hooks may nest — and a refusal past that.\n *\n * A hook's `context.data` write runs the target collection's hooks too, which\n * is what makes a hook that writes its own collection recurse. Unconditional,\n * it never ends: every level is one short statement, so `statement_timeout`\n * never fires, and a request held its transaction and a row lock while its\n * `afterSave` ran tens of thousands of times — long after the client had given\n * up — until the process was killed.\n *\n * Each `save`/`delete` runs as a frame in an async-local chain; a write whose\n * chain is deeper than {@link MAX_WRITE_DEPTH} is refused, naming the hook that\n * started it and the collection it wrote. Siblings — the rows of a bulk write,\n * two writes one after another in a hook — are not nested and never add up.\n */\nimport { AsyncLocalStorage } from \"node:async_hooks\";\nimport { ApiError } from \"@rebasepro/server\";\n\n/**\n * Deep enough for any chain that ends (a hook writing another collection whose\n * hook writes a third is depth 3), shallow enough that a loop stops at once.\n */\nexport const MAX_WRITE_DEPTH = 16;\n\n/** One write in progress, and which of its hooks is running. */\nexport interface WriteFrame {\n readonly path: string;\n readonly operation: \"save\" | \"delete\";\n readonly depth: number;\n readonly parent: WriteFrame | undefined;\n /** The hook running right now — what a nested write was started from. */\n stage: string | undefined;\n}\n\n/**\n * On `globalThis`, like the write-transaction scope beside it: a nested write\n * can reach a second copy of this package (a `dataAsAdmin` call from a hook),\n * and the chain has to be one chain.\n */\nconst FRAME_SLOT = Symbol.for(\"rebase.postgres.writeFrame\");\ntype GlobalWithFrames = typeof globalThis & { [FRAME_SLOT]?: AsyncLocalStorage<WriteFrame> };\nconst frames: AsyncLocalStorage<WriteFrame> =\n ((globalThis as GlobalWithFrames)[FRAME_SLOT] ??= new AsyncLocalStorage<WriteFrame>());\n\n/** Run one write as a frame, refusing it when the chain is already too deep. */\nexport function inWriteFrame<T>(\n path: string,\n operation: \"save\" | \"delete\",\n fn: (frame: WriteFrame) => Promise<T>\n): Promise<T> {\n const parent = frames.getStore();\n const frame: WriteFrame = { path, operation, depth: (parent?.depth ?? 0) + 1, parent, stage: undefined };\n if (frame.depth > MAX_WRITE_DEPTH) {\n return Promise.reject(recursionError(frame));\n }\n return frames.run(frame, () => fn(frame));\n}\n\nfunction recursionError(frame: WriteFrame): ApiError {\n const starter = frame.parent;\n const hook = starter?.stage ?? \"a hook\";\n // The distinct hook → write steps in the chain, innermost first: for the\n // usual loop that is one line, `\"orders\" afterSave → save \"orders\"`.\n const steps: string[] = [];\n for (let f: WriteFrame | undefined = frame; f?.parent; f = f.parent) {\n const step = `\"${f.parent.path}\" ${f.parent.stage ?? \"?\"} → ${f.operation} \"${f.path}\"`;\n if (!steps.includes(step)) steps.push(step);\n }\n return new ApiError(\n 500,\n \"CALLBACK_RECURSION\",\n `Writes started by hooks nested more than ${MAX_WRITE_DEPTH} deep, so this one was refused and the whole write ` +\n `rolled back. The ${hook} hook of \"${starter?.path ?? frame.path}\" ${frame.operation}s \"${frame.path}\" through ` +\n \"`context.data`, which runs that collection's hooks again, and nothing in the chain stops it: \" +\n `${steps.join(\"; \")}. Make the hook's write conditional — act on \\`status === \"new\"\\` only, or skip it ` +\n \"when `values` already hold what it would write.\",\n { chain: steps, depth: MAX_WRITE_DEPTH }\n );\n}\n","/**\n * What a collection change means for a live database.\n *\n * The live schema editor may only make changes the boot-time ensure path can\n * actually carry out, because that is the one mechanism that changes a schema\n * (see `ensure-collection-tables.ts`). Its vocabulary is small and deliberately\n * so: create a table, add a column, create an enum *type*, create an index, add\n * a foreign key, and rename a column via the legacy-name path. There is no\n * `ALTER COLUMN TYPE` and no `DROP` of anything.\n *\n * So this module answers one question per change: **can the ensure path express\n * it, and if it can, will the result actually match what the config says?**\n *\n * ## Three answers, not two\n *\n * The obvious split is safe / unsafe. It is not enough, because the most\n * dangerous case is neither: a change the ensure path *partly* applies, leaving\n * a database that does not match the configuration and says nothing about it.\n * Two of those exist today and both are documented in the code they come from:\n *\n * - **A required property added to an existing collection.** `ensure` withholds\n * `NOT NULL` on a table that already exists, because the constraint is checked\n * against live rows. The column arrives nullable. The config says required;\n * the database does not enforce it.\n * - **A value added to an existing enum.** `ensure` skips an enum type it\n * already sees — `if (existing.enums.has(name)) continue`. The new value never\n * reaches the database, and the first insert using it fails.\n *\n * Both would read as \"applied successfully\" to anyone watching. Calling them\n * `diverges` is the whole point of this module: an editor that reports them as\n * safe is worse than one that refuses them.\n *\n * ## Read from the plan, not from the properties\n *\n * Whether an edit changes the database is decided by comparing what\n * `planSchema` — the one reading of a `Property` every emitter renders — makes\n * of the collections before and after it: each table's columns, their types,\n * nullability, defaults, uniqueness and foreign keys, the junction tables,\n * indexes and triggers. It used to be decided by a hand-picked list of property\n * fields, which knew nothing of `validation.integer`, of a string becoming an\n * enum, of an array's element type or of anything about a relation beyond its\n * name — and every one of those was reported \"safe, no change\", committed, and\n * never applied. A difference in the plan cannot be missed that way: a new kind\n * of edit either changes the plan, and is classified here from the difference,\n * or it does not change the database.\n *\n * The property-level reading is still here, for the parts only it can name: a\n * property added or removed, a required flag, an enum's values, a key renamed.\n *\n * ## Why refusing is the right default for the rest\n *\n * Dropping a column, narrowing a type, changing a primary key: each is\n * expressible in SQL and none is expressible by `ensure`. They need a migration\n * somebody wrote and read. `needs-migration` says exactly that, and naming the\n * change is more useful than attempting it.\n */\nimport {\n hasForeignKeyOnTarget,\n isManyToMany,\n type CollectionConfig,\n type Property,\n type RelationProperty,\n type ResolvedRelation,\n type SchemaChange,\n type SchemaChangeKind,\n type SchemaChangeVerdict,\n type ClassifiedSchemaChanges\n} from \"@rebasepro/types\";\nimport { findRelation, getTableName, resolveCollectionRelations } from \"@rebasepro/common\";\nimport { resolveColumnName } from \"./column-plan-helpers\";\nimport { planSchema } from \"./plan/plan-schema\";\nimport { renderPgType } from \"./plan/render-ddl\";\nimport { typesAgree } from \"./column-type-drift\";\nimport type { ColumnPlan, ForeignKeyPlan, SchemaPlan, TablePlan } from \"./plan/types\";\n\n/**\n * The vocabulary lives in `@rebasepro/types` so that `@rebasepro/server`, which\n * cannot import this package, can still describe a change. Re-exported here\n * under the names this module has always used.\n */\nexport type ChangeVerdict = SchemaChangeVerdict;\nexport type ChangeKind = SchemaChangeKind;\nexport type { SchemaChange };\nexport type ClassifiedChanges = ClassifiedSchemaChanges;\n\nconst VERDICT_RANK: Record<SchemaChangeVerdict, number> = {\n safe: 0,\n diverges: 1,\n \"needs-migration\": 2\n};\n\nconst bySlug = (collections: CollectionConfig[]): Map<string, CollectionConfig> => {\n const map = new Map<string, CollectionConfig>();\n for (const collection of collections) {\n if (collection.slug) map.set(collection.slug, collection);\n }\n return map;\n};\n\nconst propertiesOf = (collection: CollectionConfig): Record<string, Property> =>\n (collection.properties ?? {}) as Record<string, Property>;\n\n/** Enum values a string property declares, or undefined when it is not an enum. */\nconst enumValuesOf = (prop: Property): string[] | undefined => {\n const values = (prop as { enum?: unknown }).enum;\n if (!Array.isArray(values)) return undefined;\n return values.map(value =>\n typeof value === \"string\" ? value : String((value as { id?: unknown })?.id ?? value)\n );\n};\n\nconst isRequired = (prop: Property): boolean => prop.validation?.required === true;\n\nconst isIdProperty = (prop: Property): boolean => Boolean((prop as { isId?: unknown }).isId);\n\n/**\n * Facts about the database a change is destined for.\n *\n * Three of the verdicts below cannot be reached from the collections alone:\n * whether a NOT NULL can be added comes down to whether the table holds rows,\n * and whether an enum value will land comes down to which values the type\n * already has. Without these, the classifier answers conservatively — the\n * change *may* diverge — which is the right answer for a caller that has no\n * database to look at, and the wrong one to show somebody staring at theirs.\n */\nexport interface SchemaFacts {\n /** `schema.table` → columns. */\n tables: Map<string, Set<string>>;\n /** Tables known to hold at least one row. */\n populatedTables?: Set<string>;\n /** `schema.table.column` for every column the database marks NOT NULL. */\n notNullColumns?: Set<string>;\n /** `schema.typename` → the values that type currently holds. */\n enumValues?: Map<string, string[]>;\n /** `schema.table.column` → the type Postgres reports for it (`udt_name`). */\n columnTypes?: Map<string, string>;\n /** `schema.table.column` of every column that has a DEFAULT. */\n columnDefaults?: Set<string>;\n}\n\n/**\n * The warning a column left behind needs when nothing will write it any more:\n * NOT NULL with no default rejects every insert that omits it — which, once no\n * property names it, is every insert. The `products.sku` case of 2026-09-08,\n * where the demo seed died with 23502 and left every later table empty.\n */\nfunction orphanedNotNullWarning(table: string, column: string, facts: SchemaFacts | undefined): string {\n const key = `${table}.${column}`;\n if (!facts?.notNullColumns?.has(key) || facts.columnDefaults?.has(key)) return \"\";\n const [schema, bare] = table.split(\".\");\n return ` \"${column}\" is NOT NULL with no default, so every insert into \"${table}\" fails until it is ` +\n `dropped or made nullable — ALTER TABLE \"${schema}\".\"${bare}\" ALTER COLUMN \"${column}\" DROP NOT NULL.`;\n}\n\n/** `products.sku`: the bare table and column, the way a commit subject names it. */\nconst shortColumn = (collection: CollectionConfig, column: string): string => `${getTableName(collection)}.${column}`;\n\n/** Whether the table behind a collection exists and is empty. */\nconst tableIsEmpty = (collection: CollectionConfig, facts?: SchemaFacts): boolean => {\n if (!facts?.populatedTables) return false;\n const key = qualifiedTable(collection);\n // A table the database does not have yet is one this change creates, and a\n // table being created has no rows to check a constraint against.\n if (!facts.tables.has(key)) return true;\n return !facts.populatedTables.has(key);\n};\n\nconst qualifiedTable = (collection: CollectionConfig): string => {\n const schema = (collection as { schema?: string }).schema ?? \"public\";\n return `${schema}.${getTableName(collection)}`;\n};\n\n/** The two plans a change is read from. `before` is absent when it cannot be planned. */\ninterface Plans {\n before?: SchemaPlan;\n after: SchemaPlan;\n}\n\nconst collectionTableOf = (plan: SchemaPlan | undefined, collection: CollectionConfig): TablePlan | undefined =>\n plan?.tables.find(table => table.kind === \"collection\" && table.slug === collection.slug);\n\nconst keyText = (table: TablePlan): string => `(${table.primaryKey.map(column => `\"${column}\"`).join(\", \")})`;\n\n/**\n * \" The primary key goes from (\"id\") to (\"id\", \"locale\").\" — or \"\" when the\n * plans do not say, or say the key is unchanged.\n *\n * Several `isId` properties are one composite key, so marking or unmarking one\n * of them moves the whole constraint, not one column's flag; naming the key on\n * both sides is what tells the reader which constraint the migration replaces.\n */\nconst keyMoveText = (before: TablePlan | undefined, after: TablePlan | undefined): string =>\n before && after && keyText(before) !== keyText(after)\n ? ` The primary key goes from ${keyText(before)} to ${keyText(after)}.`\n : \"\";\n\n/** The migration that moves a table's primary key from `before`'s to `after`'s. */\nconst keyMoveRemedy = (before: TablePlan | undefined, after: TablePlan | undefined): string =>\n \"A primary key change rewrites the table and every foreign key into it. Migration only\" +\n (before && after && keyText(before) !== keyText(after)\n ? `: ALTER TABLE \"${after.schema}\".\"${after.table}\" DROP CONSTRAINT \"${after.table}_pkey\", ` +\n `ADD PRIMARY KEY ${keyText(after)} — after checking that no two rows share the new key.`\n : \".\");\n\n/** The resolved relation behind a relation property, when it resolves. */\nconst relationOf = (collection: CollectionConfig, propName: string, prop: Property): ResolvedRelation | undefined => {\n if (prop.type !== \"relation\") return undefined;\n const name = (prop as RelationProperty).relation?.relationName ?? propName;\n try {\n return findRelation(resolveCollectionRelations(collection), name);\n } catch {\n return undefined;\n }\n};\n\n/** What one property puts in the database: its columns, and the junction tables it implies. */\ninterface Footprint {\n columns: ColumnPlan[];\n junctions: TablePlan[];\n}\n\nconst PROPERTY_SOURCES = new Set([\"property\", \"relation\", \"reference\"]);\n\nfunction footprintOf(plan: SchemaPlan | undefined, collection: CollectionConfig, propName: string, prop: Property): Footprint {\n const table = collectionTableOf(plan, collection);\n const columns = table?.columns.filter(column =>\n column.source.propName === propName && PROPERTY_SOURCES.has(column.source.kind)) ?? [];\n const junctions: TablePlan[] = [];\n const relation = relationOf(collection, propName, prop);\n if (plan && relation && isManyToMany(relation)) {\n const junction = plan.tables.find(t => t.kind === \"junction\" && t.table === relation.through.table);\n if (junction) junctions.push(junction);\n }\n return { columns, junctions };\n}\n\n/** \"column \"x\"\", \"columns \"x\", \"y\"\", \"junction table \"a_b\"\" — what a footprint is, in words. */\nfunction describeFootprint(footprint: Footprint): string {\n const parts: string[] = [];\n if (footprint.columns.length > 0) {\n const names = footprint.columns.map(c => `\"${c.column}\"`).join(\", \");\n parts.push(`${footprint.columns.length === 1 ? \"column\" : \"columns\"} ${names}`);\n }\n for (const junction of footprint.junctions) parts.push(`junction table \"${junction.qualified}\"`);\n return parts.join(\" and \");\n}\n\n/** A column's type as one comparable string. An enum is its type, not its labels. */\nfunction typeKey(column: ColumnPlan): string {\n if (column.generated) return `generated:${renderPgType(column.type)}:${column.generated.expression}`;\n if (column.sqlDefinition) return `sql:${column.sqlDefinition}`;\n if (column.type.kind === \"enum\") return `enum:${column.type.schema}.${column.type.name}`;\n const identity = column.default?.kind === \"identity\" ? \" IDENTITY\" : \"\";\n return `${renderPgType(column.type)}${identity}`;\n}\n\n/** A column's type, for a sentence. */\nfunction typeText(column: ColumnPlan): string {\n if (column.type.kind === \"enum\") return `the enum ${column.type.name}`;\n return renderPgType(column.type);\n}\n\nfunction defaultText(column: ColumnPlan): string | undefined {\n if (!column.default || column.default.kind === \"identity\") return undefined;\n return column.default.kind === \"sql\" ? column.default.expression : column.default.sql;\n}\n\nfunction foreignKeyText(fk: ForeignKeyPlan | undefined): string | undefined {\n if (!fk) return undefined;\n return `\"${fk.targetSchema}\".\"${fk.targetTable}\" (\"${fk.targetColumn}\") ON DELETE ${fk.onDelete.toUpperCase()}` +\n (fk.onUpdate ? ` ON UPDATE ${fk.onUpdate.toUpperCase()}` : \"\");\n}\n\n/** Plan a collection set, or say why it cannot be planned. */\nfunction tryPlan(collections: CollectionConfig[]): SchemaPlan | Error {\n try {\n return planSchema(collections);\n } catch (err) {\n return err instanceof Error ? err : new Error(String(err));\n }\n}\n\n/**\n * Classify the difference between two collection sets.\n *\n * `before` is what the running database was built from; `after` is what the\n * editor is proposing. Order within each array is irrelevant.\n *\n * `facts` is what the database actually looks like. Supplied by the live\n * editor; omitted by callers reasoning about collections in the abstract, who\n * get the conservative reading.\n */\nexport function classifyCollectionChanges(\n before: CollectionConfig[],\n after: CollectionConfig[],\n facts?: SchemaFacts\n): ClassifiedChanges {\n const previous = bySlug(before);\n const next = bySlug(after);\n const changes: SchemaChange[] = [];\n\n const beforePlan = tryPlan(before);\n const afterPlan = tryPlan(after);\n if (afterPlan instanceof Error) {\n // A proposal the planner refuses is not a change the database can\n // take, whatever else is true of it — and it is the planner's message\n // that says what to fix.\n changes.push({\n kind: \"invalid-collection\",\n verdict: \"needs-migration\",\n collection: [...next.keys()].find(slug => !previous.has(slug)) ?? [...next.keys()][0] ?? \"\",\n detail: `The proposed collections cannot be planned: ${afterPlan.message}`,\n remedy: \"Fix the collection so its schema can be generated, then try again.\"\n });\n }\n const plans: Plans | undefined = afterPlan instanceof Error\n ? undefined\n : { before: beforePlan instanceof Error ? undefined : beforePlan, after: afterPlan };\n\n for (const [slug, collection] of next) {\n if (!previous.has(slug)) {\n changes.push({\n kind: \"add-collection\",\n verdict: \"safe\",\n collection: slug,\n detail: `New collection \"${slug}\" — creates table \"${getTableName(collection)}\".`\n });\n continue;\n }\n const was = previous.get(slug)!;\n // The slug is the identity, so a collection that kept it and changed\n // its table is the same collection *moved*. The ensure path would\n // create the new table and never touch the old one: the collection\n // would come back empty, with every row it had left behind.\n if (qualifiedTable(was) !== qualifiedTable(collection)) {\n changes.push({\n kind: \"rename-table\",\n verdict: \"needs-migration\",\n collection: slug,\n detail:\n `\"${slug}\" moves from table \"${qualifiedTable(was)}\" to \"${qualifiedTable(collection)}\", ` +\n \"which would create the new table empty and leave every row in the old one.\",\n remedy:\n \"The ensure path never renames or moves a table. Do it in a migration you have read \" +\n \"(ALTER TABLE … RENAME TO, or SET SCHEMA), then change the collection to match.\"\n });\n continue;\n }\n classifyProperties(was, collection, changes, facts, plans);\n if (plans) {\n classifyTable(was, collection, collectionTableOf(plans.before, was), collectionTableOf(plans.after, collection), changes, facts);\n }\n }\n\n for (const [slug, collection] of previous) {\n if (next.has(slug)) continue;\n changes.push({\n kind: \"remove-collection\",\n verdict: \"needs-migration\",\n collection: slug,\n detail:\n `Collection \"${slug}\" was removed, which would drop table ` +\n `\"${getTableName(collection)}\" and everything in it.`,\n remedy:\n \"The ensure path never drops anything, so this cannot be applied here. Remove the \" +\n \"collection in a migration you have read, or keep it and stop serving it.\"\n });\n }\n\n if (plans?.before) {\n classifyJunctions(plans.before, plans.after, previous, next, changes);\n classifySecurityRules(plans.before, plans.after, changes);\n }\n\n for (const change of changes) {\n if (change.verdict === \"safe\" || change.sourceOnly) continue;\n Object.assign(change, sourceOnlyOf(change, previous.get(change.collection)));\n }\n\n const verdict = changes.reduce<ChangeVerdict>(\n (worst, change) => (VERDICT_RANK[change.verdict] > VERDICT_RANK[worst] ? change.verdict : worst),\n \"safe\"\n );\n\n return { changes, verdict, applicable: verdict === \"safe\" };\n}\n\n/**\n * What \"Edit source only\" leaves behind for the kinds whose consequence does\n * not depend on the column at hand. A kind with no entry here, and no text\n * from where it was classified, is not offered source-only: a primary key\n * moved in the source alone, a relation reading a column nobody creates, a\n * column of the wrong type re-used, a proposal the planner refuses — each\n * leaves a project that does not start or reads its data wrong.\n */\nfunction sourceOnlyOf(change: SchemaChange, was: CollectionConfig | undefined): Pick<SchemaChange, \"sourceOnly\" | \"kept\"> {\n const table = was ? getTableName(was) : change.collection;\n switch (change.kind) {\n case \"remove-collection\":\n return {\n sourceOnly: `Table \"${table}\" and every row in it stay in the database, and nothing serves them.`,\n kept: `table ${table} kept`\n };\n case \"rename-table\":\n return {\n sourceOnly: `The collection reads a new table, which the next start creates empty; every row stays in \"${table}\".`,\n kept: `rows stay in ${table}`\n };\n case \"change-required\":\n return {\n sourceOnly: `The column keeps accepting nulls; it is required in the panel only, until every row has a value and the constraint is added.`,\n kept: `${table}.${change.property ?? \"\"} still nullable`\n };\n case \"add-property\":\n return change.verdict === \"diverges\"\n ? {\n sourceOnly: \"The next start adds the column nullable: it is required in the panel only, until every row has a value and the constraint is added.\",\n kept: `${table}.${change.property ?? \"\"} added nullable`\n }\n : {};\n case \"add-enum-value\":\n return { sourceOnly: \"The value is added to the type when the server next starts.\", kept: \"enum value added at next start\" };\n case \"remove-enum-value\":\n return {\n sourceOnly: \"The type keeps the value, and the rows holding it keep it — the panel will show them as an unknown value.\",\n kept: \"enum value kept\"\n };\n default:\n return {};\n }\n}\n\n/** A change on this property already refuses it — a second refusal would only repeat it. */\nconst alreadyRefused = (changes: SchemaChange[], slug: string, property: string | undefined): boolean =>\n property !== undefined &&\n changes.some(change => change.collection === slug && change.property === property && change.verdict === \"needs-migration\");\n\nfunction classifyProperties(\n before: CollectionConfig,\n after: CollectionConfig,\n changes: SchemaChange[],\n facts: SchemaFacts | undefined,\n plans: Plans | undefined\n): void {\n const slug = after.slug ?? \"\";\n const previous = propertiesOf(before);\n const next = propertiesOf(after);\n const empty = tableIsEmpty(after, facts);\n\n // A key renamed with its column kept — the editor sets `columnName` to the\n // old column — moves no data: the database sees the same column, and the\n // plans agree. It used to read as a removal plus an addition, and was\n // refused as dropping a column nothing dropped.\n const renamed = new Map<string, string>();\n if (plans?.before) {\n const removedKeys = Object.keys(previous).filter(key => !next[key]);\n const addedKeys = Object.keys(next).filter(key => !previous[key]);\n for (const oldKey of removedKeys) {\n const was = footprintOf(plans.before, before, oldKey, previous[oldKey]).columns.map(c => c.column).sort();\n if (was.length === 0) continue;\n const newKey = addedKeys.find(key => !renamed.has(key) &&\n JSON.stringify(footprintOf(plans.after, after, key, next[key]).columns.map(c => c.column).sort()) === JSON.stringify(was));\n if (newKey) renamed.set(newKey, oldKey);\n }\n }\n const renamedFrom = new Set(renamed.values());\n\n for (const [name, prop] of Object.entries(next)) {\n const oldKey = renamed.get(name);\n if (oldKey) {\n const columns = footprintOf(plans?.after, after, name, prop).columns.map(c => `\"${c.column}\"`).join(\", \");\n changes.push({\n kind: \"rename-property\",\n verdict: \"safe\",\n collection: slug,\n property: name,\n detail:\n `\"${oldKey}\" is now \"${name}\" and keeps column ${columns} — no data moves. ` +\n `API clients reading \"${oldKey}\" read \"${name}\" from now on.`\n });\n classifyProperty(slug, after, name, previous[oldKey], prop, changes, facts, plans, oldKey);\n continue;\n }\n const old = previous[name];\n if (!old) {\n classifyAddedProperty(slug, after, name, prop, changes, empty, plans, facts);\n continue;\n }\n classifyProperty(slug, after, name, old, prop, changes, facts, plans);\n }\n\n for (const [name, prop] of Object.entries(previous)) {\n if (next[name] || renamedFrom.has(name)) continue;\n classifyRemovedProperty(slug, before, after, name, prop, changes, plans, facts);\n }\n}\n\nfunction classifyAddedProperty(\n slug: string,\n collection: CollectionConfig,\n name: string,\n prop: Property,\n changes: SchemaChange[],\n empty: boolean,\n plans: Plans | undefined,\n facts: SchemaFacts | undefined\n): void {\n const footprint = footprintOf(plans?.after, collection, name, prop);\n const physical = footprint.columns.length > 0;\n if (refuseUnbackedRelation(slug, collection, name, prop, changes, plans)) return;\n if (reusesLeftoverColumn(slug, collection, name, prop, footprint, changes, empty, facts)) return;\n\n // A NOT NULL is checked against rows that are already there, so whether\n // this is safe is a question about the data, not about the configuration.\n // On an empty table the constraint cannot fail and the ensure path applies\n // it; on a populated one it is withheld and the column arrives nullable,\n // which is a database that disagrees with its own config — still worth\n // refusing, now for a reason the reader can act on.\n if (isRequired(prop) && physical && !empty) {\n changes.push({\n kind: \"add-property\",\n verdict: \"diverges\",\n collection: slug,\n property: name,\n detail:\n `\"${name}\" is required, but \"${getTableName(collection)}\" already holds rows, so ` +\n \"NOT NULL would be checked against data that has no value for it yet. The \" +\n \"column would arrive nullable.\",\n remedy:\n \"Add it optional, backfill every row, then make it required — the editor \" +\n \"will apply the constraint once no row violates it.\"\n });\n return;\n }\n\n const what = describeFootprint(footprint);\n const relation = relationOf(collection, name, prop);\n const adds = what\n ? `adds ${what}`\n : relation && hasForeignKeyOnTarget(relation)\n ? `adds no column — its links are read from \"${relation.foreignKeyOnTarget}\" on the collection it points at`\n : \"adds nothing to the database\";\n changes.push({\n kind: \"add-property\",\n verdict: \"safe\",\n collection: slug,\n property: name,\n detail: isRequired(prop) && physical\n ? `New required property \"${name}\" — ${adds} NOT NULL, which \"${getTableName(collection)}\" ` +\n \"can take because it holds no rows.\"\n : `New ${isRequired(prop) ? \"required\" : \"optional\"} property \"${name}\" — ${adds}.`\n });\n}\n\n/**\n * A property added over a column the database already has.\n *\n * \"Edit source only\" removes a property and leaves its column — that is what\n * it is for — so the same name can come back later over a column holding the\n * old values. `ADD COLUMN IF NOT EXISTS` is then a no-op: the plan said \"adds\n * column\", nothing ran, and a property of another type read the old values as\n * its own. Re-using a column of the same type is fine and is said; a column of\n * another type needs converting first.\n */\nfunction reusesLeftoverColumn(\n slug: string,\n collection: CollectionConfig,\n name: string,\n prop: Property,\n footprint: Footprint,\n changes: SchemaChange[],\n empty: boolean,\n facts: SchemaFacts | undefined\n): boolean {\n const table = qualifiedTable(collection);\n const present = facts?.tables.get(table);\n const leftover = footprint.columns.filter(column => present?.has(column.column));\n if (leftover.length === 0) return false;\n\n for (const column of leftover) {\n const actual = facts?.columnTypes?.get(`${table}.${column.column}`);\n const declared = renderPgType(column.type);\n if (actual && !typesAgree(declared, actual)) {\n changes.push({\n kind: \"add-property\",\n verdict: \"needs-migration\",\n collection: slug,\n property: name,\n detail:\n `\"${name}\" would use column \"${column.column}\", which is already there as ${actual} — ` +\n `left by an earlier removal — while \"${name}\" declares ${declared}. The values in it ` +\n \"would be read as the wrong type.\",\n remedy:\n `Drop or convert \"${column.column}\" in a migration you have read, or give \"${name}\" ` +\n \"another `columnName`.\"\n });\n return true;\n }\n }\n const columns = leftover.map(c => `\"${c.column}\"`).join(\", \");\n changes.push(isRequired(prop) && !empty\n ? {\n kind: \"add-property\",\n verdict: \"diverges\",\n collection: slug,\n property: name,\n detail:\n `\"${name}\" re-uses column ${columns}, which is already there with the values an earlier ` +\n \"property left in it, and is required — NOT NULL would be checked against those rows.\",\n remedy: \"Add it optional, backfill the rows with no value, then make it required.\"\n }\n : {\n kind: \"add-property\",\n verdict: \"safe\",\n collection: slug,\n property: name,\n detail:\n `New ${isRequired(prop) ? \"required\" : \"optional\"} property \"${name}\" — re-uses column ` +\n `${columns}, which is already there and keeps the values an earlier property left in it.`\n });\n return true;\n}\n\n/**\n * A `hasMany`/`hasOne` reads its links from a column on the collection it\n * points at. When nothing creates that column, the relation is empty forever,\n * and nothing about planning it said so: the plan was \"safe\", the detail said\n * it added a column it did not add, and no statement ran.\n */\nfunction refuseUnbackedRelation(\n slug: string,\n collection: CollectionConfig,\n name: string,\n prop: Property,\n changes: SchemaChange[],\n plans: Plans | undefined\n): boolean {\n const relation = relationOf(collection, name, prop);\n if (!plans || !relation || !hasForeignKeyOnTarget(relation)) return false;\n let target: CollectionConfig | undefined;\n try {\n target = relation.target();\n } catch {\n return false;\n }\n const targetTable = target ? collectionTableOf(plans.after, target) : undefined;\n if (!targetTable || targetTable.columns.some(column => column.column === relation.foreignKeyOnTarget)) return false;\n changes.push({\n kind: \"change-relation\",\n verdict: \"needs-migration\",\n collection: slug,\n property: name,\n detail:\n `\"${name}\" reads its links from column \"${relation.foreignKeyOnTarget}\" of \"${targetTable.qualified}\", ` +\n \"which nothing creates — the relation would always be empty.\",\n remedy:\n `Give \"${target!.slug}\" a belongsTo whose column is \"${relation.foreignKeyOnTarget}\", or point ` +\n \"`foreignKeyOnTarget` at the column that holds the link.\"\n });\n return true;\n}\n\nfunction classifyRemovedProperty(\n slug: string,\n before: CollectionConfig,\n after: CollectionConfig,\n name: string,\n prop: Property,\n changes: SchemaChange[],\n plans: Plans | undefined,\n facts: SchemaFacts | undefined\n): void {\n const footprint = footprintOf(plans?.before, before, name, prop);\n // Only what the proposal no longer has is dropped. A column another\n // property still produces, or a junction the far side still declares,\n // stays where it is.\n const afterTable = collectionTableOf(plans?.after, after);\n const dropped: Footprint = plans?.before\n ? {\n columns: footprint.columns.filter(c => !afterTable?.columns.some(a => a.column === c.column)),\n junctions: footprint.junctions.filter(j => !plans.after.tables.some(t => t.qualified === j.qualified))\n }\n : { columns: [{ column: resolveColumnName(name, prop) } as ColumnPlan], junctions: [] };\n\n if (dropped.columns.length === 0 && dropped.junctions.length === 0) {\n changes.push({\n kind: \"remove-property\",\n verdict: \"safe\",\n collection: slug,\n property: name,\n detail: footprint.columns.length + footprint.junctions.length > 0\n ? `\"${name}\" was removed; ${describeFootprint(footprint)} stays, because the collections still use it.`\n : `\"${name}\" was removed — it had nothing in the database of its own, so nothing there changes.`\n });\n return;\n }\n const table = qualifiedTable(before);\n const warnings = dropped.columns.map(c => orphanedNotNullWarning(table, c.column, facts)).join(\"\");\n changes.push({\n kind: \"remove-property\",\n verdict: \"needs-migration\",\n collection: slug,\n property: name,\n detail:\n `\"${name}\" was removed, which would drop ${describeFootprint(dropped)} and the data in it.`,\n remedy:\n \"The ensure path never drops anything. Remove it in a migration you have read, or \" +\n \"leave the column and stop exposing the property.\",\n sourceOnly:\n `${describeFootprint(dropped)} stays in the database, holding its data, and nothing reads or ` +\n `writes it.${warnings}`,\n kept: [\n ...dropped.columns.map(c => `column ${shortColumn(before, c.column)} kept`),\n ...dropped.junctions.map(j => `junction ${j.table} kept`)\n ].join(\", \")\n });\n}\n\nfunction classifyProperty(\n slug: string,\n collection: CollectionConfig,\n name: string,\n before: Property,\n after: Property,\n changes: SchemaChange[],\n facts: SchemaFacts | undefined,\n plans: Plans | undefined,\n /** The key `before` had, when the key itself was renamed. */\n beforeName = name\n): void {\n // A relation's column is its `localKey`, which the plan comparison reads;\n // `columnName` names the column of every other property.\n if (before.type !== \"relation\" && after.type !== \"relation\") {\n const beforeColumn = resolveColumnName(beforeName, before);\n const afterColumn = resolveColumnName(name, after);\n if (beforeColumn !== afterColumn) {\n changes.push({\n kind: \"rename-column\",\n verdict: \"needs-migration\",\n collection: slug,\n property: name,\n detail: `\"${name}\" changes column from \"${beforeColumn}\" to \"${afterColumn}\".`,\n remedy:\n \"The ensure path only renames a column through its legacy-name path, which this is \" +\n \"not. Rename it in a migration, or the old column stays and the new one is created \" +\n \"empty beside it.\",\n sourceOnly:\n `The data stays in \"${beforeColumn}\". The next start adds \"${afterColumn}\" empty, and every ` +\n `row already there reads blank until the values are copied across.` +\n orphanedNotNullWarning(qualifiedTable(collection), beforeColumn, facts),\n kept: `data stays in ${shortColumn(collection, beforeColumn)}`\n });\n }\n }\n\n if (isIdProperty(before) !== isIdProperty(after)) {\n const tableBefore = collectionTableOf(plans?.before, collection);\n const tableAfter = collectionTableOf(plans?.after, collection);\n changes.push({\n kind: \"change-primary-key\",\n verdict: \"needs-migration\",\n collection: slug,\n property: name,\n detail: `\"${name}\" changes whether it is the primary key.${keyMoveText(tableBefore, tableAfter)}`,\n remedy: keyMoveRemedy(tableBefore, tableAfter)\n });\n return;\n }\n\n if (before.type === \"relation\" && after.type === \"relation\") {\n const was = relationOf(collection, name, before);\n const is = relationOf(collection, name, after);\n if (was && is && was.kind !== is.kind) {\n changes.push({\n kind: \"change-relation\",\n verdict: \"needs-migration\",\n collection: slug,\n property: name,\n detail:\n `\"${name}\" changes from a ${was.kind} to a ${is.kind} relation. Its links are stored in a ` +\n \"different place for each, and the ones already stored are not moved.\",\n remedy:\n \"Move the links in a migration you have read — copy them into the new place — then change \" +\n \"the relation to match.\",\n sourceOnly:\n `The links stay where the ${was.kind} kept them, and the ${is.kind} reads its own place, which ` +\n \"starts empty — the relation reads as empty until they are moved.\",\n kept: `links of ${slug}.${name} not moved`\n });\n return;\n }\n if (refuseUnbackedRelation(slug, collection, name, after, changes, plans)) return;\n }\n\n const afterColumn = resolveColumnName(name, after);\n if (!isRequired(before) && isRequired(after)) {\n // Tightening. `SET NOT NULL` scans the table, so this comes down to\n // whether anything in it is null — which on an empty table is nothing.\n const empty = tableIsEmpty(collection, facts);\n changes.push(empty\n ? {\n kind: \"change-required\",\n verdict: \"safe\",\n collection: slug,\n property: name,\n detail:\n `\"${name}\" became required — sets NOT NULL on \"${afterColumn}\", which ` +\n `\"${getTableName(collection)}\" can take because it holds no rows.`\n }\n : {\n kind: \"change-required\",\n verdict: \"diverges\",\n collection: slug,\n property: name,\n detail:\n `\"${name}\" became required, but \"${getTableName(collection)}\" holds rows and ` +\n \"SET NOT NULL is checked against every one of them. The database would keep \" +\n \"accepting nulls.\",\n remedy:\n `Backfill first — UPDATE the rows where \"${afterColumn}\" IS NULL — then apply ` +\n \"this again.\"\n });\n }\n\n if (isRequired(before) && !isRequired(after)) {\n // Relaxing. `DROP NOT NULL` cannot fail and cannot lose data, so this is\n // safe on any table; the editor plans it because a reviewed change is\n // the one context in which touching an existing column's constraints is\n // something somebody asked for.\n changes.push({\n kind: \"change-required\",\n verdict: \"safe\",\n collection: slug,\n property: name,\n detail: `\"${name}\" is no longer required — drops NOT NULL from \"${afterColumn}\".`\n });\n }\n\n classifyEnum(slug, name, before, after, changes, facts);\n}\n\n/**\n * Everything the plans say changed about a table that already exists — the\n * part no reading of the properties can be trusted to find.\n *\n * Each difference is something the ensure path either does (a new index, a\n * new trigger, a default) or does not (a column's type, its uniqueness, its\n * foreign key, a column the plan no longer has). A difference already refused\n * by the property-level reading — a removed property, a renamed column — is not\n * refused twice.\n */\nfunction classifyTable(\n wasCollection: CollectionConfig,\n collection: CollectionConfig,\n before: TablePlan | undefined,\n after: TablePlan | undefined,\n changes: SchemaChange[],\n facts: SchemaFacts | undefined\n): void {\n if (!before || !after) return;\n const slug = collection.slug ?? \"\";\n /** \"string to number — \" when the property's own type changed too. */\n const propertyTypes = (property: string | undefined): string => {\n if (!property) return \"\";\n const was = propertiesOf(wasCollection)[property]?.type;\n const is = propertiesOf(collection)[property]?.type;\n return was && is && was !== is ? `from ${was} to ${is} — ` : \"\";\n };\n const push = (change: Omit<SchemaChange, \"collection\">) => {\n if (alreadyRefused(changes, slug, change.property)) return;\n changes.push({ ...change, collection: slug });\n };\n const label = (column: ColumnPlan) => column.source.propName ?? column.column;\n\n // The key as a whole. A composite key is one constraint over several\n // columns, so a column joining or leaving it moves that constraint — and the\n // ensure path declares a primary key only when it creates the table. A\n // property newly added with `isId` reaches here only: as a new column it\n // reads as an addition, and applied as one it would leave the key as it was.\n // Reported once per collection; the property that changed its own `isId`\n // has already said it.\n if (keyText(before) !== keyText(after) && !changes.some(c => c.collection === slug && c.kind === \"change-primary-key\")) {\n const moved = [\n ...after.primaryKey.filter(column => !before.primaryKey.includes(column)),\n ...before.primaryKey.filter(column => !after.primaryKey.includes(column))\n ];\n const movedColumn = after.columns.find(c => moved.includes(c.column)) ?? before.columns.find(c => moved.includes(c.column));\n changes.push({\n kind: \"change-primary-key\",\n verdict: \"needs-migration\",\n collection: slug,\n property: movedColumn?.source.propName,\n detail: `The primary key of \"${after.table}\" changes.${keyMoveText(before, after)}`,\n remedy: keyMoveRemedy(before, after)\n });\n }\n\n for (const column of after.columns) {\n const was = before.columns.find(c => c.column === column.column);\n if (!was) continue;\n const property = column.source.propName ?? was.source.propName;\n const where = `\"${label(column)}\"`;\n\n if (typeKey(was) !== typeKey(column)) {\n push({\n kind: \"change-property-type\",\n verdict: \"needs-migration\",\n property,\n detail: `${where} changes ${propertyTypes(property)}column \"${column.column}\" goes from ` +\n `${typeText(was)} to ${typeText(column)}.`,\n remedy:\n \"There is no ALTER COLUMN TYPE in the ensure path, and a cast can fail on data that \" +\n \"is already there. Change it in a migration.\",\n sourceOnly:\n `Column \"${column.column}\" keeps its type, ${typeText(was)}: the property is read and written ` +\n `through it, and a value ${typeText(was)} cannot hold is refused.`,\n kept: `column ${shortColumn(collection, column.column)} stays ${typeText(was)}`\n });\n continue;\n }\n // Membership of the key, not `primaryKey`: that flag marks a key\n // declared inline on its one column, and a column that stays in the key\n // as it becomes composite loses the flag without leaving the key.\n if (before.primaryKey.includes(was.column) !== after.primaryKey.includes(column.column)) {\n push({\n kind: \"change-primary-key\",\n verdict: \"needs-migration\",\n property,\n detail: `${where} changes whether \"${column.column}\" is part of the primary key.${keyMoveText(before, after)}`,\n remedy: keyMoveRemedy(before, after)\n });\n continue;\n }\n if (was.unique !== column.unique) {\n push({\n kind: \"change-constraint\",\n verdict: \"needs-migration\",\n property,\n detail: column.unique\n ? `${where} becomes unique — a UNIQUE constraint on \"${column.column}\", checked against every existing row.`\n : `${where} is no longer unique — the UNIQUE constraint on \"${column.column}\" stays until it is dropped.`,\n remedy: column.unique\n ? `The ensure path never constrains an existing column. Check for duplicates, then ALTER TABLE ` +\n `\"${after.schema}\".\"${after.table}\" ADD UNIQUE (\"${column.column}\") in a migration.`\n : `Drop it in a migration: ALTER TABLE \"${after.schema}\".\"${after.table}\" DROP CONSTRAINT …`,\n sourceOnly: column.unique\n ? `\"${column.column}\" keeps accepting duplicates until the constraint is added.`\n : `The UNIQUE constraint on \"${column.column}\" stays and keeps refusing duplicates.`,\n kept: `${shortColumn(collection, column.column)} uniqueness unchanged`\n });\n }\n const fkWas = foreignKeyText(was.foreignKey);\n const fkIs = foreignKeyText(column.foreignKey);\n if (fkWas !== fkIs) {\n if (!fkWas) {\n push({\n kind: \"change-relation\",\n verdict: \"safe\",\n property,\n detail:\n `${where} gains a foreign key — \"${column.column}\" REFERENCES ${fkIs}. It is checked ` +\n \"against every existing row, and a value pointing at nothing makes it fail.\"\n });\n } else {\n push({\n kind: \"change-relation\",\n verdict: \"needs-migration\",\n property,\n detail: fkIs\n ? `${where} changes its foreign key from ${fkWas} to ${fkIs}. The constraint already ` +\n \"exists under its name, so the ensure path would leave the old one in place.\"\n : `${where} no longer declares its foreign key, but the constraint on \"${column.column}\" ` +\n `(${fkWas}) stays and keeps refusing values.`,\n remedy:\n \"Drop and re-create the constraint in a migration you have read — ALTER TABLE … DROP \" +\n \"CONSTRAINT, then ADD CONSTRAINT.\",\n sourceOnly: `The constraint on \"${column.column}\" stays as it is: ${fkWas}.`,\n kept: `foreign key on ${shortColumn(collection, column.column)} unchanged`\n });\n }\n }\n const defaultWas = defaultText(was);\n const defaultIs = defaultText(column);\n if (defaultWas !== defaultIs) {\n // A DEFAULT binds future writes only: setting, changing or dropping\n // it cannot fail and touches no row. `generateSchemaCommit` adds the\n // statement the additive ensure would not.\n push({\n kind: \"change-default\",\n verdict: \"safe\",\n property,\n detail: defaultIs\n ? `${where} defaults to ${defaultIs} — SET DEFAULT on \"${column.column}\"; rows already there keep their values.`\n : `${where} no longer has a default — DROP DEFAULT on \"${column.column}\"; rows already there keep their values.`\n });\n }\n }\n\n for (const column of before.columns) {\n if (after.columns.some(c => c.column === column.column)) continue;\n const property = column.source.propName;\n // A removed property, a column renamed through `columnName`, a\n // relation whose kind changed: already refused, naming the property.\n if (alreadyRefused(changes, slug, property)) continue;\n push({\n kind: column.source.kind === \"relation\" ? \"change-relation\" : \"remove-property\",\n verdict: \"needs-migration\",\n property,\n detail: column.source.kind === \"relation\"\n ? `\"${label(column)}\" no longer keeps its links in column \"${column.column}\", and the links ` +\n \"already there would not be moved — the new place starts empty.\"\n : `Column \"${column.column}\" is no longer part of \"${slug}\", which would drop it and its data.`,\n remedy:\n \"Move the data in a migration you have read, or keep the relation's column where it is \" +\n \"(`localKey`).\",\n sourceOnly:\n `Column \"${column.column}\" stays, holding its data, and nothing reads or writes it.` +\n orphanedNotNullWarning(after.qualified, column.column, facts),\n kept: `column ${shortColumn(collection, column.column)} kept`\n });\n }\n\n for (const index of after.indexes) {\n if (before.indexes.some(i => i.indexName === index.indexName)) continue;\n push({\n kind: \"change-constraint\",\n verdict: \"safe\",\n detail: `Creates ${index.unique ? \"unique \" : \"\"}index \"${index.indexName}\" on \"${after.qualified}\".`\n });\n }\n for (const index of before.indexes) {\n if (after.indexes.some(i => i.indexName === index.indexName)) continue;\n push({\n kind: \"change-constraint\",\n verdict: index.unique ? \"needs-migration\" : \"safe\",\n detail: index.unique\n ? `Unique index \"${index.indexName}\" is no longer declared, but it stays and keeps refusing duplicates.`\n : `Index \"${index.indexName}\" is no longer declared; it stays in the database until it is dropped.`,\n remedy: index.unique ? `Drop it in a migration: DROP INDEX \"${after.schema}\".\"${index.indexName}\".` : undefined,\n sourceOnly: index.unique ? `Index \"${index.indexName}\" stays and keeps refusing duplicates.` : undefined,\n kept: index.unique ? `index ${index.indexName} kept` : undefined\n });\n }\n for (const trigger of before.triggers) {\n if (after.triggers.some(t => t.name === trigger.name)) continue;\n push({\n kind: \"change-constraint\",\n verdict: \"needs-migration\",\n property: before.columns.find(c => c.column === trigger.column)?.source.propName,\n detail:\n `\"${trigger.column}\" is no longer stamped on update, but trigger \"${trigger.name}\" stays and ` +\n \"keeps stamping it.\",\n remedy: `Drop it in a migration: DROP TRIGGER \"${trigger.name}\" ON \"${after.schema}\".\"${after.table}\".`,\n sourceOnly: `Trigger \"${trigger.name}\" stays and keeps stamping \"${trigger.column}\" on every update.`,\n kept: `trigger ${trigger.name} kept`\n });\n }\n for (const trigger of after.triggers) {\n if (before.triggers.some(t => t.name === trigger.name)) continue;\n push({\n kind: \"change-constraint\",\n verdict: \"safe\",\n property: after.columns.find(c => c.column === trigger.column)?.source.propName,\n detail: `\"${trigger.column}\" is stamped with now() on every update — creates trigger \"${trigger.name}\".`\n });\n }\n}\n\n/**\n * Junction tables that appear or disappear without a collection or a property\n * being added or removed — a relation that changed kind, or the far side of a\n * many-to-many dropping out.\n */\nfunction classifyJunctions(\n before: SchemaPlan,\n after: SchemaPlan,\n previous: Map<string, CollectionConfig>,\n next: Map<string, CollectionConfig>,\n changes: SchemaChange[]\n): void {\n const mentioned = (table: string) => changes.some(change => change.detail.includes(`\"${table}\"`));\n for (const table of before.tables) {\n if (table.kind !== \"junction\" || after.tables.some(t => t.qualified === table.qualified)) continue;\n const slugs = table.declaringSlugs ?? [];\n // A removed collection already says it takes everything with it.\n if (slugs.length > 0 && slugs.every(slug => !next.has(slug))) continue;\n if (mentioned(table.qualified)) continue;\n changes.push({\n kind: \"change-relation\",\n verdict: \"needs-migration\",\n collection: slugs[0] ?? \"\",\n detail: `Junction table \"${table.qualified}\" is no longer declared, which would drop it and every link in it.`,\n remedy: \"The ensure path never drops a table. Drop it in a migration you have read, or keep the relation.\",\n sourceOnly: `Junction table \"${table.qualified}\" stays, holding its links, and nothing reads it.`,\n kept: `junction ${table.table} kept`\n });\n }\n for (const table of after.tables) {\n if (table.kind !== \"junction\" || before.tables.some(t => t.qualified === table.qualified)) continue;\n const slugs = table.declaringSlugs ?? [];\n if (slugs.length > 0 && slugs.every(slug => !previous.has(slug))) continue;\n if (mentioned(table.qualified)) continue;\n changes.push({\n kind: \"change-relation\",\n verdict: \"safe\",\n collection: slugs[0] ?? \"\",\n detail: `Creates junction table \"${table.qualified}\", empty.`\n });\n }\n}\n\n/**\n * A table whose compiled policies differ — a security rule added, removed or\n * changed.\n *\n * Safe: the policies are replaced wholesale whenever the server starts\n * (`ensureCollectionPolicies`), and nothing about the table's data changes.\n * But it is a change, with an effect on who can read what, and it was planned\n * as none and committed as `chore(schema): no change`.\n *\n * Compared as compiled policies rather than as rules, so a rule spelled\n * differently into the same policy is not a change, and one whose meaning\n * changed is — a policy's name embeds a hash of what it does.\n */\nfunction classifySecurityRules(before: SchemaPlan, after: SchemaPlan, changes: SchemaChange[]): void {\n const policyKey = (table: TablePlan) => JSON.stringify(\n table.policies.map(({ name, operation, mode, roles, using, withCheck }) =>\n ({ name, operation, mode, roles, using, withCheck }))\n .sort((a, b) => a.name.localeCompare(b.name))\n );\n for (const table of after.tables) {\n if (table.kind !== \"collection\" || !table.slug) continue;\n const was = before.tables.find(t => t.qualified === table.qualified && t.kind === \"collection\");\n if (!was || policyKey(was) === policyKey(table)) continue;\n const names = (plan: TablePlan) => new Set(plan.policies.map(p => p.name));\n const added = [...names(table)].filter(name => !names(was).has(name)).length;\n const removed = [...names(was)].filter(name => !names(table).has(name)).length;\n const counts = [\n added > 0 ? `${added} ${added === 1 ? \"policy\" : \"policies\"} added` : \"\",\n removed > 0 ? `${removed} removed` : \"\"\n ].filter(Boolean).join(\", \");\n changes.push({\n kind: \"change-security-rules\",\n verdict: \"safe\",\n collection: table.slug,\n detail:\n `Who can read and write \"${table.qualified}\" changes${counts ? ` (${counts})` : \"\"}. Row-level ` +\n \"security policies are replaced at the server's next start — at once under `rebase dev`, which \" +\n \"restarts on the commit, and at the next deploy elsewhere. Until then the old policies apply.\"\n });\n }\n}\n\nfunction classifyEnum(\n slug: string,\n name: string,\n before: Property,\n after: Property,\n changes: SchemaChange[],\n facts?: SchemaFacts\n): void {\n const oldValues = enumValuesOf(before);\n const newValues = enumValuesOf(after);\n if (!oldValues || !newValues) return;\n\n const added = newValues.filter(value => !oldValues.includes(value));\n const removed = oldValues.filter(value => !newValues.includes(value));\n\n if (added.length > 0) {\n // `ADD VALUE` is additive, idempotent with IF NOT EXISTS, and needs no\n // table scan, so the ensure path now carries it — but only when it can\n // see which values the type already has. A caller with no database\n // cannot know that, and for them this is still the change that silently\n // does not land.\n const seesTheType = facts?.enumValues !== undefined;\n changes.push(seesTheType\n ? {\n kind: \"add-enum-value\",\n verdict: \"safe\",\n collection: slug,\n property: name,\n detail: `\"${name}\" gains ${added.map(v => `\"${v}\"`).join(\", \")} — ALTER TYPE … ADD VALUE.`\n }\n : {\n kind: \"add-enum-value\",\n verdict: \"diverges\",\n collection: slug,\n property: name,\n detail:\n `\"${name}\" gains ${added.map(v => `\"${v}\"`).join(\", \")}, and the values this ` +\n \"type already has are not known here, so whether they would land cannot be said.\",\n remedy: \"Plan this against the database it is destined for.\"\n });\n }\n\n if (removed.length > 0) {\n changes.push({\n kind: \"remove-enum-value\",\n verdict: \"needs-migration\",\n collection: slug,\n property: name,\n detail: `\"${name}\" drops ${removed.map(v => `\"${v}\"`).join(\", \")}.`,\n remedy:\n \"Postgres cannot remove a value from an enum type. Recreate the type in a migration, \" +\n \"after rewriting every row still using the value.\"\n });\n }\n}\n\n/**\n * The statements a default change needs that the additive ensure does not\n * plan: it sets a DEFAULT only on a column that has none, and never changes or\n * drops one. Both are metadata-only — a DEFAULT binds future writes and touches\n * no row — so a reviewed change may make them.\n */\nexport function defaultChangeStatements(\n before: CollectionConfig[],\n after: CollectionConfig[],\n existingTables: Map<string, Set<string>>\n): string[] {\n const beforePlan = tryPlan(before);\n const afterPlan = tryPlan(after);\n if (beforePlan instanceof Error || afterPlan instanceof Error) return [];\n const statements: string[] = [];\n for (const table of afterPlan.tables) {\n const was = beforePlan.tables.find(t => t.qualified === table.qualified);\n if (!was) continue;\n for (const column of table.columns) {\n const old = was.columns.find(c => c.column === column.column);\n if (!old || !existingTables.get(table.qualified)?.has(column.column)) continue;\n if (old.default?.kind === \"identity\" || column.default?.kind === \"identity\" || column.generated) continue;\n const from = defaultText(old);\n const to = defaultText(column);\n if (from === to) continue;\n const target = `ALTER TABLE \"${table.schema}\".\"${table.table}\" ALTER COLUMN \"${column.column}\"`;\n statements.push(to ? `${target} SET DEFAULT ${to};` : `${target} DROP DEFAULT;`);\n }\n }\n return statements;\n}\n\n/** A one-line summary, for a log or a refusal message. */\nexport function summarizeChanges(classified: ClassifiedChanges): string {\n if (classified.changes.length === 0) return \"No schema changes.\";\n\n const counts = classified.changes.reduce<Record<ChangeVerdict, number>>(\n (acc, change) => ({ ...acc, [change.verdict]: (acc[change.verdict] ?? 0) + 1 }),\n { safe: 0, diverges: 0, \"needs-migration\": 0 }\n );\n\n const parts = ([\"needs-migration\", \"diverges\", \"safe\"] as ChangeVerdict[])\n .filter(verdict => counts[verdict] > 0)\n .map(verdict => `${counts[verdict]} ${verdict}`);\n\n return `${classified.changes.length} change(s): ${parts.join(\", \")}.`;\n}\n","/**\n * Everything a schema change has to write, as file contents.\n *\n * A live schema editor that only edits the collection source produces a repo\n * that does not build: `backend/src/schema.generated.ts` is a committed\n * artifact, and a stale one has broken every deploy at least once. So the unit\n * of a schema change is not a file, it is a **commit** — and this module\n * produces one, without touching a disk, a database or a network.\n *\n * Pure on purpose. The risky half of \"commit, then apply\" is generating a\n * correct commit; keeping it a function from collections to file contents is\n * what lets that half be tested by building a database from the result and\n * comparing it to the one the change describes.\n *\n * ## Where the migration comes from, and why not from Atlas\n *\n * `rebase db generate` mints migrations by running Atlas over the generated\n * `schema.sql`. Atlas is an external binary, it wants a dev database, and it\n * maintains an `atlas.sum` integrity file whose hash this module would have to\n * reproduce byte-for-byte to stay valid.\n *\n * None of that is necessary here, because of what the editor is allowed to do.\n * `classify-change.ts` refuses anything the boot-time ensure path cannot\n * express, which leaves only additive statements — and those are computable as\n * a plain difference between two ensure plans:\n *\n * plan(after, nothing) − plan(before, nothing)\n *\n * Both plans are pure functions of the collections, every statement is\n * idempotent, and the difference is exactly what the change adds. No diff\n * engine, no database, no binary.\n *\n * The statements are *returned* rather than written into a migration file. A\n * project provisioned by boot-ensure needs no migration at all — its\n * collections are the schema — while a project provisioned by migrations needs\n * the file to carry an Atlas hash, which only Atlas can mint. Writing a\n * migration this module cannot make valid would be worse than handing the\n * statements to a caller who knows which kind of project it is.\n */\nimport {\n DEFAULT_COMMIT_PATHS,\n type CollectionConfig,\n type SchemaCommitPaths\n} from \"@rebasepro/types\";\nimport { generateSchema } from \"./generate-drizzle-schema-logic\";\nimport {\n planCollectionSchemaEnsure,\n type EnsureOptions,\n type ExistingSchema,\n type WithheldConstraint\n} from \"./ensure-collection-tables\";\nimport { classifyCollectionChanges, defaultChangeStatements, type ClassifiedChanges } from \"./classify-change\";\n\n/**\n * Re-exported from the shared kernel. `@rebasepro/server` derives these for a\n * project in a subdirectory and cannot import a driver to do it, so the shape\n * and the defaults live in `@rebasepro/types`.\n */\nexport { DEFAULT_COMMIT_PATHS, type SchemaCommitPaths } from \"@rebasepro/types\";\n\nexport interface SchemaCommitFile {\n path: string;\n contents: string;\n}\n\nexport interface SchemaCommitInput {\n /** What the running database was built from. */\n before: CollectionConfig[];\n /** What the editor is proposing. */\n after: CollectionConfig[];\n /**\n * Files the caller has already produced — in practice the rewritten\n * collection source from the AST editor. Carried through unchanged so the\n * commit is complete in one object.\n */\n sourceFiles?: SchemaCommitFile[];\n paths?: Partial<SchemaCommitPaths>;\n /**\n * What the database this change is destined for actually has.\n *\n * Supplied by the live editor, which read it a moment ago; omitted by the\n * pure callers, which have no database. It decides two things a plan cannot\n * know from the collections alone — whether a table holds rows, and which\n * values an enum type already carries — and both are the difference between\n * a statement that applies and one that is rejected.\n */\n existing?: ExistingSchema;\n /**\n * Plan a commit of the source alone — \"Edit source only\". An unapplicable\n * change is not refused: it is committed with no statements, under a\n * message that names what the database keeps.\n */\n sourceOnly?: boolean;\n}\n\nexport interface SchemaCommit {\n /** Every file the commit writes, source and generated alike. */\n files: SchemaCommitFile[];\n /**\n * The additive statements this change adds, in dependency order.\n *\n * Empty when the change needs no DDL. Not written to a migration file —\n * see the module comment.\n */\n statements: string[];\n classified: ClassifiedChanges;\n /** A commit message describing the change in the terms a reader wants. */\n message: string;\n /**\n * Constraints this change asks for that the statements do not carry, and\n * why. Empty for almost every change; when it is not, it is the thing the\n * person confirming needs to read before they confirm.\n */\n withheldConstraints: WithheldConstraint[];\n}\n\nexport class SchemaCommitError extends Error {\n constructor(message: string, readonly classified: ClassifiedChanges) {\n super(message);\n this.name = \"SchemaCommitError\";\n }\n}\n\n/** An `ExistingSchema` describing a database that has nothing in it. */\nconst nothing = () => ({ tables: new Map<string, Set<string>>(), enums: new Set<string>() });\n\n/**\n * The statements that take `before` to `after`.\n *\n * Both sides are planned against the *same* database and the difference is\n * taken by exact statement text. That works because the planner is\n * deterministic: the same collections against the same schema produce the same\n * strings, so anything in the second plan and absent from the first is what\n * this change adds — and nothing else. Planning both sides is what keeps\n * pre-existing drift, which belongs to neither side of the edit, out of the\n * statements this change gets credited with.\n *\n * ## Why `existing` matters more than it looks\n *\n * Planned against `nothing()`, every table reads as one this plan is creating,\n * and the planner is then free to attach constraints that only hold on a table\n * with no rows: a new required property comes out as\n * `ADD COLUMN \"x\" TEXT NOT NULL`, which is right for a fresh table and fails\n * against a live one holding rows. Those statements would be generated,\n * committed, and then rejected by the very database they were written for.\n *\n * So a caller holding a real database passes it, and gets statements that\n * describe that database. `nothing()` stays the default for the pure uses —\n * generating a commit for inspection, and the tests that compare two plans —\n * where there is no database to describe.\n */\nexport function additiveStatements(\n before: CollectionConfig[],\n after: CollectionConfig[],\n existing: ExistingSchema = nothing(),\n options: EnsureOptions = {}\n): string[] {\n const previous = new Set(planCollectionSchemaEnsure(before, existing, options).statements);\n return planCollectionSchemaEnsure(after, existing, options).statements\n .filter(statement => !previous.has(statement));\n}\n\n/**\n * The commit of an \"Edit source only\": what changed in the source, and what\n * the database keeps — `chore(schema): remove sku from products (source only —\n * column products.sku kept)`. The body lists what each change leaves behind.\n */\nexport function sourceOnlyCommitMessage(classified: ClassifiedChanges): string {\n const blocking = classified.changes.filter(change => change.verdict !== \"safe\");\n const kept = blocking.map(change => change.kept).filter(Boolean).join(\"; \");\n const subject = `${subjectOf(classified)} (source only — ${kept || \"the database is unchanged\"})`;\n const body = classified.changes.map(change =>\n `- ${change.detail}${change.sourceOnly && change.verdict !== \"safe\" ? `\\n Left in the database: ${change.sourceOnly}` : \"\"}`\n ).join(\"\\n\");\n return `chore(schema): ${subject}\\n\\n${body}\\n`;\n}\n\n/** What a commit is about, in the words of its subject line. */\nfunction subjectOf(classified: ClassifiedChanges): string {\n const { changes } = classified;\n const collections = [...new Set(changes.map(change => change.collection))].sort();\n const only = changes.length === 1 ? changes[0] : undefined;\n if (only?.kind === \"add-collection\") return `add the ${only.collection} collection`;\n if (only?.kind === \"remove-collection\") return `remove the ${only.collection} collection`;\n if (only?.kind === \"add-property\") return `add ${only.property} to ${only.collection}`;\n if (only?.kind === \"remove-property\") return `remove ${only.property} from ${only.collection}`;\n if (changes.length > 0 && changes.every(c => c.kind === \"change-security-rules\")) {\n return `change the security rules of ${collections.join(\", \")}`;\n }\n if (collections.length === 1) return `${changes.length} change(s) to ${collections[0]}`;\n return `${changes.length} change(s) across ${collections.length} collections`;\n}\n\n/** A commit message that says what changed rather than that something did. */\nexport function commitMessage(classified: ClassifiedChanges): string {\n const { changes } = classified;\n if (changes.length === 0) return \"chore(schema): no change\";\n const body = changes.map(change => `- ${change.detail}`).join(\"\\n\");\n return `feat(schema): ${subjectOf(classified)}\\n\\n${body}\\n`;\n}\n\n/**\n * Build the commit.\n *\n * Refuses when the change is not applicable — a commit describing a schema the\n * ensure path will not produce is a commit that makes the repository lie about\n * the database. The classification travels on the error so a caller can show\n * exactly which change was the problem.\n */\nexport async function generateSchemaCommit(input: SchemaCommitInput): Promise<SchemaCommit> {\n const paths = { ...DEFAULT_COMMIT_PATHS, ...input.paths };\n const existing = input.existing ?? nothing();\n // `converge` because every statement this produces is shown to somebody\n // before it runs. See `ConstraintPolicy` for why the unattended boot does\n // not get the same latitude.\n const options: EnsureOptions = { constraints: \"converge\" };\n const classified = classifyCollectionChanges(input.before, input.after, input.existing);\n\n if (input.sourceOnly) {\n // A change with no account of what it leaves behind is one that may\n // not be written to the source alone — see `sourceOnlyOf`.\n const unoffered = classified.changes.filter(change => change.verdict !== \"safe\" && !change.sourceOnly);\n if (unoffered.length > 0) {\n throw new SchemaCommitError(\n \"This change cannot be written to the collection source alone:\\n\" +\n unoffered.map(change => ` • ${change.detail}${change.remedy ? `\\n ${change.remedy}` : \"\"}`).join(\"\\n\"),\n classified\n );\n }\n // Nothing runs: the source is committed, the database is left as it\n // is, and the message says what it keeps.\n return {\n files: [...(input.sourceFiles ?? []), { path: paths.schemaFile, contents: generateSchema(input.after) }],\n statements: [],\n classified,\n message: sourceOnlyCommitMessage(classified),\n withheldConstraints: []\n };\n }\n\n if (!classified.applicable) {\n const blocking = classified.changes.filter(change => change.verdict !== \"safe\");\n throw new SchemaCommitError(\n `This change cannot be applied to a running database:\\n` +\n blocking.map(change =>\n ` • ${change.detail}${change.remedy ? `\\n ${change.remedy}` : \"\"}`\n ).join(\"\\n\"),\n classified\n );\n }\n\n // The Drizzle schema alone. The SQL `db push` and `db generate` render is\n // not part of a schema change: both regenerate it from the collections\n // before reading it, into the gitignored `.rebase/sql/`, so a copy in the\n // commit would be read by nothing — and was worse than dead weight, since\n // it landed at the project root while the CLI writes beside the backend.\n const generated: SchemaCommitFile[] = [\n { path: paths.schemaFile, contents: generateSchema(input.after) }\n ];\n\n // Both sides planned once, here, rather than through `additiveStatements` —\n // which would plan `after` a second time for the withheld constraints. The\n // planner is pure, so a second call is only wasted work rather than a\n // correctness problem, but this runs on every `/plan` keystroke in the panel.\n const previous = planCollectionSchemaEnsure(input.before, existing, options);\n const next = planCollectionSchemaEnsure(input.after, existing, options);\n const already = new Set(previous.statements);\n const statements = next.statements.filter(statement => !already.has(statement));\n // A changed or dropped DEFAULT, which the additive ensure never makes and\n // the classifier calls safe — it binds future writes and touches no row.\n for (const statement of defaultChangeStatements(input.before, input.after, existing.tables)) {\n if (!statements.includes(statement)) statements.push(statement);\n }\n\n return {\n files: [...(input.sourceFiles ?? []), ...generated],\n statements,\n classified,\n message: commitMessage(classified),\n // From the `after` plan alone rather than differenced against `before`:\n // a constraint that was already unenforceable is still something the\n // person confirming this change should see named.\n withheldConstraints: next.withheldConstraints\n };\n}\n","import type { Connection, Pool, PoolClient, Submittable } from \"pg\";\nimport type { SqlScriptColumn, SqlScriptNotice, SqlScriptResult, SqlScriptTable } from \"@rebasepro/types\";\nimport { logger } from \"@rebasepro/server\";\n\n/**\n * Running a SQL script a person wrote — the Studio console — and saying what\n * came back, column by column.\n *\n * The console used to receive bare rows and guess, from the query's text, which\n * table a cell belonged to and which column held its key. The guess wrote\n * `UPDATE authors SET name = … WHERE id = <the post's id>` for a cell of\n * `SELECT p.id, a.name FROM posts p JOIN authors a …`, and `UPDATE users SET\n * name = …` for `lower(email) AS name`. The database knows the answer: every\n * column of a result's row description carries the table and column it was\n * read from, or zero when it was computed. This reads it off the wire.\n *\n * Values come back as the text the database wrote, never parsed. Parsed, a\n * `text[]` read as a JavaScript array and went back as JSON — a malformed array\n * literal; a `bytea` as a serialized Buffer; a `timestamp` as a Date shifted by\n * the server's timezone; a `numeric` as a float. Text is what the database\n * reads back exactly.\n */\n\n/** The parts of a `RowDescription` field this reads. */\ninterface WireField {\n name: string;\n /** The OID of the table the column was read from; 0 when it was computed. */\n tableID: number;\n /** Its attribute number in that table; 0 when it was computed. */\n columnID: number;\n dataTypeID: number;\n}\n\n/** One statement of the script, as it came back. */\ninterface StatementOutcome {\n fields: WireField[];\n rows: Record<string, string | null>[];\n /** The command tag, e.g. `SELECT 3`, `INSERT 0 1`, `CREATE TABLE`. */\n tag?: string;\n}\n\n/**\n * The script, sent as one simple query and read message by message.\n *\n * A `Submittable` rather than `client.query(text)`: node-postgres parses every\n * value through its type parsers and drops each field's origin, and for a\n * multi-statement script returns an array the driver's callers took for a\n * result with no rows. The message handlers below are the ones node-postgres\n * calls on the active query — the same protocol `pg-cursor` implements.\n */\nclass ScriptRun implements Submittable {\n /**\n * Set by node-postgres when the pool has a `query_timeout`: a wrapper that\n * clears its read timer. Called on every outcome, so the timer never\n * outlives the run.\n */\n callback?: (error: Error | null) => void;\n\n readonly statements: StatementOutcome[] = [];\n readonly done: Promise<StatementOutcome[]>;\n\n /** The statement whose row description arrived and whose command has not completed. */\n private open?: StatementOutcome;\n private settled = false;\n private resolveRun: (statements: StatementOutcome[]) => void = () => undefined;\n private rejectRun: (error: Error) => void = () => undefined;\n\n constructor(private readonly text: string) {\n this.done = new Promise((resolve, reject) => {\n this.resolveRun = resolve;\n this.rejectRun = reject;\n });\n }\n\n submit(connection: Connection): void {\n connection.query(this.text);\n }\n\n handleRowDescription(message: { fields: WireField[] }): void {\n this.open = {\n fields: message.fields.map(({ name, tableID, columnID, dataTypeID }) => ({ name, tableID, columnID, dataTypeID })),\n rows: []\n };\n this.statements.push(this.open);\n }\n\n handleDataRow(message: { fields: (string | null)[] }): void {\n const statement = this.open;\n if (!statement) return;\n const row: Record<string, string | null> = {};\n statement.fields.forEach((field, index) => {\n row[field.name] = message.fields[index] ?? null;\n });\n statement.rows.push(row);\n }\n\n handleCommandComplete(message: { text: string }): void {\n const statement = this.open ?? { fields: [], rows: [] };\n if (!this.open) this.statements.push(statement);\n this.open = undefined;\n statement.tag = message.text;\n }\n\n handleEmptyQuery(): void {\n // An empty statement (`;;`) completes nothing and returns nothing.\n }\n\n handlePortalSuspended(): void {\n // Only the extended protocol suspends a portal; a simple query never does.\n }\n\n /**\n * `COPY … FROM STDIN` waits for data the console has no way to send. Fail\n * the copy rather than leave the session waiting for it.\n */\n handleCopyInResponse(connection: Connection): void {\n if (\"sendCopyFail\" in connection && typeof connection.sendCopyFail === \"function\") {\n connection.sendCopyFail(\"COPY FROM STDIN has no source in the SQL console\");\n }\n }\n\n handleCopyData(): void {\n // `COPY … TO STDOUT` streams data the console does not show.\n }\n\n handleError(error: Error): void {\n this.settle(error);\n }\n\n handleReadyForQuery(): void {\n this.settle(null);\n }\n\n private settle(error: Error | null): void {\n if (this.settled) return;\n this.settled = true;\n this.callback?.(error);\n if (error) this.rejectRun(error);\n else this.resolveRun(this.statements);\n }\n}\n\n/**\n * `INSERT 0 1` → `INSERT`, 1. `CREATE TABLE` → `CREATE TABLE`, no count.\n *\n * Only the commands whose tag ends in a count have one; the numbers are\n * stripped from the end of every other tag, of which there are none.\n */\nexport function parseCommandTag(tag: string | undefined): { command?: string; rowCount?: number } {\n if (!tag) return {};\n const counted = /^(SELECT|INSERT|UPDATE|DELETE|MERGE|COPY|MOVE|FETCH)(?: \\d+)? (\\d+)$/.exec(tag);\n if (counted) return { command: counted[1], rowCount: Number(counted[2]) };\n return { command: tag };\n}\n\n/** `pg_class.relkind` → what {@link SqlScriptTable.kind} calls it. */\nfunction relationKind(relkind: string): SqlScriptTable[\"kind\"] {\n switch (relkind) {\n case \"r\": return \"table\";\n case \"p\": return \"partitioned table\";\n case \"v\": return \"view\";\n case \"m\": return \"materialized view\";\n case \"f\": return \"foreign table\";\n default: return \"other\";\n }\n}\n\n/**\n * Name the tables, columns and types the row description referred to by OID.\n *\n * On the run's own session, after it was reset: the catalogue is readable by\n * every role, and the OIDs belong to this database. A failure here costs the\n * result its provenance, never its rows — a column with no source is one the\n * console will not edit.\n */\nasync function describeColumns(connection: PoolClient, fields: WireField[]): Promise<{ columns: SqlScriptColumn[]; tables: SqlScriptTable[] }> {\n const unnamed = { columns: fields.map(field => ({ name: field.name })), tables: [] };\n if (fields.length === 0) return unnamed;\n try {\n const typeOids = [...new Set(fields.map(field => field.dataTypeID))];\n const tableOids = [...new Set(fields.filter(field => field.tableID !== 0).map(field => field.tableID))];\n\n const types = await connection.query<{ oid: string; name: string }>(\n \"SELECT oid::text AS oid, format_type(oid, NULL) AS name FROM pg_type WHERE oid = ANY($1::oid[])\",\n [typeOids]\n );\n const typeNames = new Map(types.rows.map(row => [Number(row.oid), row.name]));\n\n const relations = tableOids.length === 0 ? [] : (await connection.query<{\n oid: string;\n schema: string;\n table: string;\n relkind: string;\n has_inheritors: boolean;\n primary_key: string[] | null;\n }>(\n `SELECT c.oid::text AS oid, n.nspname AS schema, c.relname AS \"table\", c.relkind::text AS relkind,\n (c.relkind = 'r' AND c.relhassubclass) AS has_inheritors,\n (SELECT array_agg(a.attname::text ORDER BY k.ord)\n FROM pg_index i\n CROSS JOIN LATERAL unnest(i.indkey::int2[]) WITH ORDINALITY AS k(attnum, ord)\n JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = k.attnum\n WHERE i.indrelid = c.oid AND i.indisprimary) AS primary_key\n FROM pg_class c\n JOIN pg_namespace n ON n.oid = c.relnamespace\n WHERE c.oid = ANY($1::oid[])`,\n [tableOids]\n )).rows;\n const attributes = tableOids.length === 0 ? [] : (await connection.query<{ oid: string; attnum: number; name: string }>(\n `SELECT attrelid::text AS oid, attnum, attname::text AS name\n FROM pg_attribute\n WHERE attrelid = ANY($1::oid[]) AND attnum > 0 AND NOT attisdropped`,\n [tableOids]\n )).rows;\n\n const relationByOid = new Map(relations.map(relation => [Number(relation.oid), relation]));\n const attributeName = new Map(attributes.map(attribute => [`${attribute.oid}:${attribute.attnum}`, attribute.name]));\n\n const columns = fields.map((field): SqlScriptColumn => {\n const column: SqlScriptColumn = { name: field.name };\n const type = typeNames.get(field.dataTypeID);\n if (type) column.type = type;\n const relation = field.tableID !== 0 ? relationByOid.get(field.tableID) : undefined;\n const sourceColumn = attributeName.get(`${field.tableID}:${field.columnID}`);\n if (relation && sourceColumn) {\n column.source = { schema: relation.schema, table: relation.table, column: sourceColumn };\n }\n return column;\n });\n const tables = relations.map((relation): SqlScriptTable => ({\n schema: relation.schema,\n table: relation.table,\n kind: relationKind(relation.relkind),\n primaryKey: relation.primary_key ?? [],\n hasInheritors: relation.has_inheritors\n }));\n return { columns, tables };\n } catch (error: unknown) {\n logger.warn(\"[SQL console] Could not describe the result's columns; none of them will be editable\", { error });\n return unnamed;\n }\n}\n\n/**\n * What puts a session back after caller SQL: the session user and role, then\n * every setting. Two statements, because the extended protocol refuses a\n * multi-command string. Pinned settings (`search_path` from the connection's\n * startup options) come back as they were pinned.\n */\nexport const RESET_SESSION_STATEMENTS = [\"SET SESSION AUTHORIZATION DEFAULT\", \"RESET ALL\"] as const;\n\n/**\n * A connection's transaction state, as its last ReadyForQuery reported it:\n * `I` idle, `T` in a transaction, `E` in a failed one. `null` when the client\n * does not say (node-postgres has since 8.16; the package requires 8.22).\n */\nexport function transactionStatus(connection: object): string | null {\n if (!(\"getTransactionStatus\" in connection) || typeof connection.getTransactionStatus !== \"function\") return null;\n const status: unknown = connection.getTransactionStatus();\n return typeof status === \"string\" ? status : null;\n}\n\n/**\n * SQL a person ran began a transaction and did not end it.\n *\n * Each run is a session of its own, so a transaction cannot outlive the run\n * that began it: a `BEGIN` alone, then an `UPDATE` in the next run, then a\n * `ROLLBACK` in a third, committed the UPDATE and reported three successes.\n * Refused instead, with the transaction rolled back.\n */\nexport class SqlTransactionLeftOpenError extends Error {\n readonly code = \"SQL_TRANSACTION_LEFT_OPEN\";\n\n constructor() {\n super(\n \"This SQL began a transaction and did not end it, so it was rolled back: nothing it did was kept. \" +\n \"Each run is a session of its own, and a transaction cannot outlive the run that began it — \" +\n \"put the BEGIN and its COMMIT (or ROLLBACK) in the same run.\"\n );\n this.name = \"SqlTransactionLeftOpenError\";\n }\n}\n\nexport interface OwnSessionOptions {\n /**\n * Put the session in the role the SQL runs as. Called once, before it, at\n * session level — so the role holds for every statement, a `COMMIT` in the\n * middle included. `SET LOCAL ROLE` inside a wrapping transaction did not:\n * the caller's `COMMIT` ended that transaction, and every statement after\n * it ran as the connection owner.\n */\n assumeRole?: (connection: PoolClient) => Promise<void>;\n}\n\n/**\n * Run `work` — SQL a person wrote — on a connection of its own from `pool`,\n * and put the connection back the way it was handed out.\n *\n * A transaction the SQL left open is rolled back, and the run refused with\n * {@link SqlTransactionLeftOpenError}; a run that failed with one open says\n * that it was rolled back. The session is then reset — role, session\n * authorization, settings — and destroyed instead when it cannot be. Last,\n * `afterReset` reads what it needs on the clean session, as the owner.\n */\nexport async function onOwnSession<T>(\n pool: Pool,\n options: OwnSessionOptions,\n work: (connection: PoolClient) => Promise<T>,\n afterReset?: (connection: PoolClient, value: T) => Promise<T>\n): Promise<T> {\n const connection = await pool.connect();\n let reusable = true;\n try {\n let outcome: { value: T } | { error: unknown };\n try {\n await options.assumeRole?.(connection);\n outcome = { value: await work(connection) };\n } catch (error: unknown) {\n outcome = { error };\n // pg rejects a query on the server's ErrorResponse and records the\n // transaction status from the ReadyForQuery that follows it. When\n // the two arrive in separate packets, the status read now is the\n // one from before the run, and a transaction the run left aborted\n // goes unnoticed: no ROLLBACK, and no word that it was rolled back.\n // pg runs a queued statement only after that ReadyForQuery, and\n // this one changes nothing whatever state the session is in.\n await connection.query(\"SELECT 1\").catch(() => undefined);\n }\n\n let leftOpen = false;\n const status = transactionStatus(connection);\n if (status === \"T\" || status === \"E\") {\n leftOpen = true;\n try {\n await connection.query(\"ROLLBACK\");\n } catch {\n reusable = false;\n }\n }\n if (reusable) {\n try {\n for (const statement of RESET_SESSION_STATEMENTS) await connection.query(statement);\n } catch {\n reusable = false;\n }\n }\n\n if (\"error\" in outcome) {\n // The database's own error, kept, and told what became of the\n // transaction. On the deepest cause: that is the message the\n // socket reports, so a note on a wrapper would never be read.\n if (leftOpen) {\n const reported = deepestCause(outcome.error);\n if (reported) reported.message = `${reported.message}. The transaction this run began was rolled back: nothing it did was kept.`;\n }\n throw outcome.error;\n }\n if (leftOpen) throw new SqlTransactionLeftOpenError();\n return afterReset && reusable ? await afterReset(connection, outcome.value) : outcome.value;\n } finally {\n connection.release(reusable ? undefined : true);\n }\n}\n\n/** The innermost `Error` of a `cause` chain — Drizzle wraps the database's error in its own. */\nfunction deepestCause(error: unknown): Error | undefined {\n let deepest: Error | undefined;\n for (let current: unknown = error; current instanceof Error; current = current.cause) deepest = current;\n return deepest;\n}\n\n/** How many notices one run keeps; a loop that raises one per row is not a report. */\nconst MAX_NOTICES = 100;\n\n/**\n * Run `sql` on a connection of its own from `pool` and return what the last\n * statement returned, with each column's provenance and what the database\n * said along the way — `there is no transaction in progress` for a ROLLBACK\n * with nothing to roll back, which used to read as success.\n */\nexport async function runSqlScriptOnPool(pool: Pool, sql: string, options: OwnSessionOptions = {}): Promise<SqlScriptResult> {\n const notices: SqlScriptNotice[] = [];\n const run = await onOwnSession(pool, options, async (connection) => {\n const listen = (notice: { severity?: string; message?: string }) => {\n if (notices.length < MAX_NOTICES) notices.push({ severity: notice.severity ?? \"NOTICE\", message: notice.message ?? \"\" });\n };\n connection.on(\"notice\", listen);\n try {\n const script = new ScriptRun(sql);\n connection.query(script);\n const statements = await script.done;\n const last: StatementOutcome = statements[statements.length - 1] ?? { fields: [], rows: [] };\n return { last, described: { columns: last.fields.map((field): SqlScriptColumn => ({ name: field.name })), tables: [] as SqlScriptTable[] } };\n } finally {\n connection.removeListener(\"notice\", listen);\n }\n }, async (connection, value) => ({ ...value, described: await describeColumns(connection, value.last.fields) }));\n\n return {\n rows: run.last.rows,\n columns: run.described.columns,\n tables: run.described.tables,\n ...parseCommandTag(run.last.tag),\n notices\n };\n}\n\n/**\n * The same shape for rows that came back parsed, from a handle that is not a\n * pool (PGlite in process): every value as text, and no column with a source —\n * nothing to say where a value came from, so nothing the console will edit.\n */\nexport function sqlScriptResultFromRows(rows: Record<string, unknown>[]): SqlScriptResult {\n const names = rows.length > 0 ? Object.keys(rows[0]) : [];\n return {\n rows: rows.map(row => Object.fromEntries(Object.entries(row).map(([key, value]) => [key, valueAsText(value)]))),\n columns: names.map(name => ({ name })),\n tables: [],\n notices: []\n };\n}\n\nfunction valueAsText(value: unknown): string | null {\n if (value === null || value === undefined) return null;\n if (typeof value === \"string\") return value;\n if (value instanceof Date) return value.toISOString();\n if (value instanceof Uint8Array) return `\\\\x${Buffer.from(value).toString(\"hex\")}`;\n if (typeof value === \"object\") return JSON.stringify(value);\n return String(value);\n}\n","import { DataService } from \"./services/dataService\";\nimport { createStampKeys } from \"./services/PersistService\";\nimport { BranchService } from \"./services/BranchService\";\nimport { RealtimeService, type SubscriptionAuthContext } from \"./services/realtimeService\";\nimport { DatabasePoolManager } from \"./databasePoolManager\";\nimport { DrizzleClient } from \"./interfaces\";\nimport {\n ANONYMOUS_USER_ID,\n DatabaseAdmin,\n SQLAdmin,\n DataDriver,\n DeleteProps,\n CollectionConfig,\n FetchCollectionProps,\n FetchOneProps,\n FilterValues,\n ListenCollectionProps,\n ListenOneProps,\n RebaseCallContext,\n RebaseServerClient,\n RebaseData,\n RebaseSdkData,\n RestFetchService,\n BatchWriteProps,\n SaveManyProps,\n SaveProps,\n SqlScriptResult,\n StorageSource,\n UpdateManyProps,\n UpdateRelationPivotProps,\n DeleteManyProps,\n EntityValues,\n EntityStatus,\n Properties,\n Property,\n TableColumnInfo,\n TableForeignKeyInfo,\n TableJunctionInfo,\n TableMetadata,\n TablePolicyInfo,\n User,\n parseEnvBoolean\n} from \"@rebasepro/types\";\nimport { sql as drizzleSql } from \"drizzle-orm\";\nimport { drizzle } from \"drizzle-orm/node-postgres\";\nimport { Pool, type PoolClient } from \"pg\";\nimport { sqlRows, applyDefaultValuesOnCreate, buildPropertyCallbacks, buildSdkData, callbackRefusal, classifyTable, detectJunctionTables, getTenantConfig, requireCallbackClient, requireCallbackCollection, resolveCollectionRelations, resolveTenantWrite, restoresSoftDeletedRow, tenantBypassRoles, toCallbackError, updateDateAutoValues, updateUserAutoValues } from \"@rebasepro/common\";\nimport { PostgresCollectionRegistry } from \"./collections/PostgresCollectionRegistry\";\nimport { deriveRowAddress, getPrimaryKeys, parseIdValues } from \"./services/collection-helpers\";\nimport { isJunctionBackedRelation, isNestedPath, resolveNestedPath } from \"./services/nested-path\";\nimport { resolveSoftDelete, rowInTrashError } from \"./services/soft-delete\";\nimport { currentWriteScope, runInWriteScope, WriteTransactionScope } from \"./services/write-transaction-scope\";\nimport { inWriteFrame, type WriteFrame } from \"./services/write-depth\";\nimport { HistoryService } from \"./history/HistoryService\";\nimport { ApiError, logger, resolveBatchRefs } from \"@rebasepro/server\";\nimport { isRoleSwitchingPermissionError, reachedDatabase } from \"./utils/pg-error-utils\";\nimport { applyAuthContext } from \"./security/rls-enforcement\";\nimport { withFieldViewer } from \"./services/field-viewer\";\nimport { generateSchemaCommit } from \"./schema/generate-schema-commit\";\nimport { readSchemaFactsFor, type Queryable } from \"./schema/ensure-collection-tables\";\nimport { onOwnSession, RESET_SESSION_STATEMENTS, runSqlScriptOnPool, sqlScriptResultFromRows } from \"./services/sql-script\";\n\n/**\n * Has an operator opted out of database role switching entirely?\n *\n * `DISABLE_DB_ROLE_SWITCHING=true` is documented (README, the configuration\n * page, the backend skill) as \"run Studio SQL Editor queries as the connection\n * owner\", for deployments whose application roles have no database role behind\n * them. It is the only sanctioned way a statement that named a role runs\n * without it — every other route now refuses.\n *\n * Any spelling of yes, through the platform's one parser. It was an exact\n * `\"true\"` until that parser existed, deliberately, so that this variable would\n * not be fixed alone and left disagreeing with the rest.\n */\nexport function isRoleSwitchingOptedOut(): boolean {\n return parseEnvBoolean(process.env.DISABLE_DB_ROLE_SWITCHING) === true;\n}\n\n/**\n * The main database, when this process serves a branch of it.\n *\n * `rebase dev` on a checkout switched to a branch connects to the branch, and\n * names the database it was branched from in `REBASE_BRANCH_PARENT_DATABASE`.\n * Branches are kept there — the registry, and the default source of a new\n * one — as they are when the main database itself is served: a branch's own\n * `rebase.branches` is a snapshot, taken before its row was written.\n */\nexport function branchParentDatabase(): string | undefined {\n return process.env.REBASE_BRANCH_PARENT_DATABASE?.trim() || undefined;\n}\n\n/**\n * The role a statement will actually have run as, given the role it asked for.\n *\n * For the audit log, which recorded `options.role` — the *requested* role — and\n * so restated the caller's request as though it were the outcome. The two part\n * company exactly when {@link isRoleSwitchingOptedOut} holds, because every\n * other divergence is now an error rather than a quiet substitution.\n */\nexport function effectiveSqlRole(requestedRole?: string): string {\n if (!requestedRole) return CONNECTION_OWNER;\n return isRoleSwitchingOptedOut() ? CONNECTION_OWNER : requestedRole;\n}\n\n/** How {@link effectiveSqlRole} names \"whatever role the connection holds\". */\nexport const CONNECTION_OWNER = \"<connection owner>\";\n\n/**\n * The role a console statement ran as, given the role it asked for — said to\n * the console with its rows, so it can say when that is not the role picked.\n *\n * The requested role whenever role switching is on: a statement that cannot\n * assume its role is refused, never run as anyone else. With\n * `DISABLE_DB_ROLE_SWITCHING`, every statement runs as the connection owner,\n * named by asking a session of its own on the same database — a role the\n * session already held is then still the one requested.\n */\nexport async function sqlRoleRanAs(\n admin: Pick<SQLAdmin, \"executeSql\">,\n requestedRole: string,\n database: string | undefined\n): Promise<string> {\n if (!isRoleSwitchingOptedOut()) return requestedRole;\n try {\n const rows = await admin.executeSql(\"SELECT current_user AS role\", { database, isolateSession: true });\n const owner = rows[0]?.role;\n return typeof owner === \"string\" ? owner : CONNECTION_OWNER;\n } catch {\n return CONNECTION_OWNER;\n }\n}\n\n/**\n * A statement named a database role the connection cannot assume.\n *\n * Its own type because the tempting recovery — run it anyway, as the owner — is\n * the one thing that must not happen. Callers that catch this should report it,\n * not retry unscoped.\n */\nexport class RoleSwitchUnavailableError extends Error {\n readonly code = \"ROLE_SWITCH_UNAVAILABLE\";\n readonly role: string;\n /** The underlying Postgres error, when the refusal came from a live attempt. */\n readonly pgError?: unknown;\n\n constructor(role: string, pgError?: unknown) {\n super(\n `Cannot execute SQL as role \"${role}\": this connection is not permitted to SET ROLE. ` +\n `The statement was NOT executed — running it as the connection owner would return ` +\n `owner-visible rows, which is a different question from the one that was asked. ` +\n `Grant the connection user membership in \"${role}\", or set DISABLE_DB_ROLE_SWITCHING=true ` +\n `to run SQL Editor queries as the connection owner.`\n );\n this.name = \"RoleSwitchUnavailableError\";\n this.role = role;\n this.pgError = pgError;\n }\n}\n\n/**\n * Fold what a `beforeSave` returned into the values being saved.\n *\n * The hook returns the values that will be saved, so a key it returns replaces\n * the value outright: an array it filtered stays filtered, a map it narrowed\n * stays narrow. A key it leaves out keeps the value it was given, so a hook\n * that returns only the field it computed still saves the rest.\n *\n * Not a deep merge. `mergeDeep` merges arrays of objects element by element and\n * keeps every element past the end of the shorter one, so a hook that dropped\n * one line item of three had the last one merged back in and the order was\n * stored with a line duplicated.\n */\nfunction applyBeforeSaveResult<T extends object, R extends object>(values: T, result: R): T & R {\n return { ...values, ...result };\n}\n\n/**\n * The keys of a create's final values that its INSERT alone may write: those\n * the caller did not send, no `beforeSave` hook wrote, and that are not an\n * `on_update` stamp — a declared default, a tenant stamp, a create-time stamp.\n * See `PersistSaveOptions.insertOnlyKeys`.\n */\nfunction insertOnlyKeysOf(\n finalValues: object,\n callerValues: object | undefined,\n beforeHooks: Record<string, unknown>,\n afterHooks: Record<string, unknown>,\n properties: Properties | undefined\n): string[] {\n const sent = new Set(Object.keys(callerValues ?? {}));\n const updateStamps = new Set(Object.entries(properties ?? {})\n .filter(([, property]) => (property?.type === \"date\" && property.autoValue === \"on_update\")\n || (property?.type === \"string\" && property.autoValue === \"user_on_update\"))\n .map(([key]) => key));\n return Object.keys(finalValues).filter(key =>\n !sent.has(key)\n && !updateStamps.has(key)\n && beforeHooks[key] === afterHooks[key]);\n}\n\n/**\n * Refuse an update that stamps a `softDelete` collection's field.\n *\n * Stamping it IS the delete — every read hides the row from then on — so it is\n * `delete`'s to do, where the permission is `delete`, `beforeDelete` can veto,\n * `afterDelete` runs and history says \"delete\". Written by an update, a caller\n * holding only write access trashed rows, past all of that. Clearing it stays\n * an update: that is how a row is restored, and what a form sends back for a\n * live row.\n */\nfunction assertUpdateDoesNotSoftDelete(\n collection: CollectionConfig | undefined,\n values: Record<string, unknown> | undefined,\n path: string\n): void {\n const softDelete = resolveSoftDelete(collection);\n if (!softDelete) return;\n const value = values?.[softDelete.field];\n if (value === undefined || value === null) return;\n const slug = collection?.slug ?? path;\n const message = `'${softDelete.field}' on '${slug}' records a delete, so an update may only clear it ` +\n \"(null restores the row). Delete the row to set it.\";\n throw ApiError.badRequest(message, \"FIELD_NOT_WRITABLE\", {\n collection: slug,\n violations: [{ field: softDelete.field, code: \"soft_delete\", message }]\n });\n}\n\n/** A key that names a row: not absent, and not the empty string a form sends. */\nfunction isPresentKey(id: string | number | undefined | null): id is string | number {\n return id !== undefined && id !== null && id !== \"\";\n}\n\n/**\n * Refuse an update whose values move the row to another key.\n *\n * The row is addressed by its key, and everything after the write addresses\n * it the same way: the read-back, `afterSave`, history, the realtime event.\n * A key changed underneath them answered 500 \"Could not fetch row after save.\"\n * on every door — and through `driver.data`, which runs outside a request's\n * transaction, the row had already moved when the call threw, with no\n * `afterSave`, no history entry and no event for the move. A key equal to the\n * address (a form that sends the whole row back) is not a change.\n *\n * Asked twice: of what the caller sent, before any hook runs, and of what the\n * hooks hand on, since a `beforeSave` can set the key too.\n */\nfunction assertKeyUnchanged(\n collection: CollectionConfig | undefined,\n registry: PostgresCollectionRegistry,\n id: string | number,\n values: Record<string, unknown> | undefined,\n path: string\n): void {\n if (!collection || !values) return;\n const primaryKeys = getPrimaryKeys(collection, registry);\n if (primaryKeys.length === 0) return;\n let address: Record<string, string | number>;\n try {\n address = parseIdValues(id, primaryKeys);\n } catch {\n // An address the key cannot parse is the write's own 404 to give.\n return;\n }\n const moved = primaryKeys\n .map(key => key.fieldName)\n .filter(field => values[field] !== undefined && String(values[field]) !== String(address[field]));\n if (moved.length === 0) return;\n const slug = collection.slug ?? path;\n const violations = moved.map(field => {\n const message = `'${field}' is the key of '${slug}', and an update cannot change it: this row is \"${id}\". `\n + \"Create a row under the new key and delete this one.\";\n return { field, code: \"key_immutable\", message };\n });\n throw ApiError.badRequest(violations[0].message, \"KEY_IMMUTABLE\", { collection: slug, violations });\n}\n\n/**\n * Refuse a write that would leave a *required* `created_by` / `updated_by`\n * column null because nobody is acting.\n *\n * A `user_on_create` column takes the uid from the call context, and an\n * anonymous request, a service token and an in-process write all have none.\n * Storing `null` is the right answer where the column allows it — plenty of\n * rows are legitimately written by the server. Where the collection has said\n * `required`, it is not: that declaration means \"a row must record who made\n * it\", and a write that cannot answer the question is a write this collection\n * does not accept. Better a 400 naming the field than a 23502 naming the\n * column, three layers down, after the hooks have run.\n */\nfunction assertActingUserForAutoValues(\n properties: Properties,\n status: EntityStatus,\n uid: string | undefined,\n path: string\n): void {\n if (uid) return;\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 if (prop.autoValue !== \"user_on_create\" && prop.autoValue !== \"user_on_update\") continue;\n if (!prop.validation?.required) continue;\n // `user_on_create` says nothing about an update — the column already\n // holds the creator's uid, and this write is not rewriting it.\n if (status === \"existing\" && prop.autoValue === \"user_on_create\") continue;\n throw ApiError.badRequest(\n `'${key}' on '${path}' records the acting user and is required, and this request has none. ` +\n \"Sign in, or drop `required` from the property to let the server write rows anonymously.\",\n \"VALIDATION_CONSTRAINT\",\n {\n collection: path,\n violations: [{ field: key, code: \"required\", message: `'${key}' records the acting user, and there is none.` }]\n }\n );\n }\n}\n\n/**\n * A listener's `onError`, in the shape the realtime service calls it with.\n *\n * Registered with the subscription so a failed refetch reaches it. Before, only\n * the initial fetch did: a refetch after a change failed into a log line, and\n * the listener kept its last rows as if they were current. The error arrives as\n * thrown, not masked the way a socket frame is: this is trusted server code.\n */\nfunction fetchErrorListener(onError: ((error: Error) => void) | undefined): ((error: unknown) => void) | undefined {\n if (!onError) return undefined;\n return (error) => onError(error instanceof Error ? error : new Error(String(error)));\n}\n\n/**\n * The principal a user's reads run as, for the transaction a request opens and\n * for a subscription alike.\n *\n * One definition because there were two. The subscription's copy was built by\n * hand after the fact and left out `isAnonymous`, so a guest's refetches ran as\n * an account: a policy that excludes guests filtered the first read and not the\n * ones after a change.\n */\nfunction authContextOf(user: User | undefined): SubscriptionAuthContext {\n return {\n uid: user?.uid || ANONYMOUS_USER_ID,\n roles: user?.roles ?? [],\n isAnonymous: user?.isAnonymous === true,\n // A tenancy policy reads a claim. Spread rather than set, so a caller\n // carrying none produces no key at all.\n ...(user?.claims ? { claims: user.claims } : {})\n };\n}\n\nexport class PostgresBackendDriver implements DataDriver {\n key = \"postgres\";\n initialised = true;\n\n public dataService: DataService;\n public realtimeService: RealtimeService;\n public historyService?: HistoryService;\n public branchService?: BranchService;\n public user?: User;\n public data: RebaseSdkData;\n\n /**\n * The server singleton, attached by `initializeRebaseBackend` after boot —\n * a driver exists before the client does. Typed as what is attached: a\n * `RebaseServerClient` has no `data`, and callbacks read it as\n * `context.client`.\n */\n public client?: RebaseServerClient;\n\n /**\n * Auto-set to `true` once a `SET LOCAL ROLE` has failed with insufficient\n * privileges, so later statements refuse without spending the round trip\n * to be refused again.\n *\n * Deliberately NOT a mirror of `DISABLE_DB_ROLE_SWITCHING`, which it used\n * to be described as. The env var is an operator saying \"run these as the\n * connection owner\"; this flag is the database saying \"I cannot give you\n * the role you asked for\". The first is a decision and permits the\n * fallback, the second is a failure and must not — see the SECURITY note\n * in {@link executeSql}.\n */\n private _roleSwitchingUnavailable = false;\n\n /**\n * Restricted role that authenticated (user-context) requests run as (via\n * `SET LOCAL ROLE`) so RLS binds every statement — reads *and* writes. Set\n * by the bootstrapper after posture detection: defined when the connection\n * would otherwise bypass RLS (superuser / BYPASSRLS / table owner),\n * undefined when RLS already applies natively. The base (server-context)\n * driver never switches — it is the trusted owner plane (auth flows,\n * migrations, `dataAsAdmin`).\n */\n public rlsUserRole?: string;\n\n /**\n * When true, realtime notifications are deferred until after the\n * wrapping transaction commits. Set by `withAuth` → `withTransaction`.\n */\n _deferNotifications = false;\n _pendingNotifications: Array<{\n path: string;\n id: string;\n row: Record<string, unknown> | null;\n databaseId?: string;\n }> = [];\n\n constructor(\n public db: DrizzleClient,\n realtimeService: RealtimeService,\n public readonly registry: PostgresCollectionRegistry,\n user?: User,\n public poolManager?: DatabasePoolManager,\n historyService?: HistoryService\n ) {\n // The context every `beforeQuery` hook on this driver's reads runs\n // with, established once here rather than threaded through a dozen read\n // signatures. Lazy because `this.client` is attached after construction\n // and `this.data` reads through whichever handle this driver holds.\n this.dataService = new DataService(db, registry, () => this.buildCallContext());\n this.realtimeService = realtimeService;\n this.historyService = historyService;\n this.user = user;\n this.data = buildSdkData(this);\n\n // Initialize BranchService when adminConnectionString is configured\n if (poolManager) {\n this.branchService = new BranchService(db, poolManager, {\n database: async () => branchParentDatabase() ?? await this.appDatabaseName(),\n registry: async () => {\n const parent = branchParentDatabase();\n return parent && parent !== await this.appDatabaseName() ? poolManager.getDrizzle(parent) : this.db;\n }\n });\n }\n\n }\n\n /**\n * Typed admin capabilities (SQLAdmin + SchemaAdmin + BranchAdmin).\n * Implemented as a getter so method references are resolved at call-time,\n * allowing test spies applied after construction to take effect.\n */\n get admin(): DatabaseAdmin {\n return {\n executeSql: (...args: Parameters<NonNullable<DatabaseAdmin[\"executeSql\"]>>) => this.executeSql(...args),\n runSqlScript: (...args: Parameters<NonNullable<DatabaseAdmin[\"runSqlScript\"]>>) => this.runSqlScript(...args),\n fetchAvailableDatabases: () => this.fetchAvailableDatabases(),\n fetchAvailableRoles: () => this.fetchAvailableRoles(),\n fetchApplicationRoles: () => this.fetchApplicationRoles(),\n fetchCurrentDatabase: () => this.fetchCurrentDatabase(),\n fetchUnmappedTables: (...args: Parameters<NonNullable<DatabaseAdmin[\"fetchUnmappedTables\"]>>) => this.fetchUnmappedTables(...args),\n fetchTableMetadata: (...args: Parameters<NonNullable<DatabaseAdmin[\"fetchTableMetadata\"]>>) => this.fetchTableMetadata(...args),\n // Planning a schema change is engine-specific — it renders DDL, a\n // Drizzle schema and the declarative SQL artifacts — so it lives\n // here and the server detects it structurally, the same way it\n // detects SQL. Planning only: applying is `executeSql` above, and\n // committing belongs to whatever holds the repository.\n // Planned against what this database actually has, not against an\n // empty one. Whether a NOT NULL can be added comes down to whether\n // the table holds rows, and whether an enum value will land comes\n // down to the values the type already carries — neither is knowable\n // from the collections, and both decide whether the statements this\n // returns are accepted or rejected by the database they name.\n planSchemaChange: async (before, after, options) => generateSchemaCommit({\n before: before as CollectionConfig[],\n after: after as CollectionConfig[],\n paths: options?.paths,\n sourceOnly: options?.sourceOnly,\n existing: await readSchemaFactsFor(\n this.schemaFactsQueryable(),\n after as CollectionConfig[]\n )\n }),\n // Branch operations (only available when poolManager is configured)\n ...(this.branchService ? {\n createBranch: this.branchService.createBranch.bind(this.branchService),\n deleteBranch: this.branchService.deleteBranch.bind(this.branchService),\n listBranches: this.branchService.listBranches.bind(this.branchService),\n getBranchInfo: this.branchService.getBranchInfo.bind(this.branchService)\n } : {})\n };\n }\n\n /**\n * The catalogue-reading shim the schema planner wants.\n *\n * Text in, rows out — every statement it issues is a catalogue read keyed by\n * schema name, and schema names are identifiers rather than bindable values,\n * so there is nothing to parameterise. Runs on the driver's own handle, which\n * is the connection whose privileges are already known to work.\n */\n private schemaFactsQueryable(): Queryable {\n return {\n query: async <T>(text: string): Promise<{ rows: T[] }> => {\n return { rows: sqlRows<T>(await this.db.execute(drizzleSql.raw(text))) };\n }\n };\n }\n\n /**\n * REST-optimised fetch service (include-aware eager-loading).\n * Delegates to the underlying FetchService (include-aware eager loading),\n * then runs the afterRead pipeline on the results. The raw FetchService does\n * NOT run callbacks, so masking must be applied here — otherwise every\n * REST/SDK read leaks unmasked data (see {@link applyAfterReadForRest}).\n */\n get restFetchService(): RestFetchService {\n const raw = this.dataService.getFetchService();\n return {\n fetchCollectionForRest: async (collectionPath, options, include) => {\n const rows = await raw.fetchCollectionForRest(collectionPath, options, include);\n return this.applyAfterReadForRest(rows, collectionPath);\n },\n fetchOneForRest: async (collectionPath, id, include, databaseId, options) => {\n const row = await raw.fetchOneForRest(collectionPath, id, include, databaseId, options);\n if (!row) return row;\n const [masked] = await this.applyAfterReadForRest([row], collectionPath);\n return masked;\n },\n // Forwarded, not omitted. `FetchService.aggregate` has existed for\n // as long as the route has, and this object is the only thing the\n // route can see — so leaving it out made\n // `GET /api/data/:slug/aggregate` answer 501 on every deployment,\n // including the Postgres one whose absence the 501's own comment\n // says it is describing.\n //\n // No afterRead pass: the callbacks shape rows, and an aggregate\n // returns sums and counts rather than rows. Row-level authorization\n // is not skipped — it is applied one layer down, by the\n // request-scoped handle this service reads through.\n aggregate: async (collectionPath, options) => {\n return raw.aggregate(collectionPath, options);\n },\n // Forwarded for the same reason `aggregate` is, and it was missed\n // the same way: this object is all the route can see, so a method\n // left off it does not fall back to the real one — it reads as a\n // driver that cannot do the thing. `readPage` asks `cursorFor` for\n // the `meta.nextCursor` of every page, so without this the server\n // accepted `?after=` and never issued a cursor to put in it, and\n // keyset pagination could not be started by any client.\n //\n // No transaction and no afterRead: it derives a string from a row\n // that has already been fetched and authorized.\n cursorFor: (collectionPath, row, orderBy) => raw.cursorFor(collectionPath, row, orderBy)\n };\n }\n\n /**\n * Build the context handed to every collection callback.\n *\n * Note `data: this.data` — `this` is whichever driver is running the\n * operation, so the callback's data plane inherits that driver's privilege.\n * On a user request `AuthenticatedPostgresBackendDriver.withTransaction`\n * constructs a fresh base driver bound to the RLS-scoped transaction and\n * runs the operation on it, so `this.data` speaks through that connection\n * and policies apply. On server-context work `this` is the base driver on\n * the owner connection, and they do not. Pinned by the\n * `\"scopes context.data to the caller\"` case in the `rls-enforcement` e2e\n * suite, because it is the kind of property that is easy to break from a\n * distance and impossible to notice.\n *\n * Previously returned through `as RebaseCallContext`, which\n * disabled checking for the whole object and let `driver` — documented in\n * the callbacks guide — sit on the runtime context while absent from the\n * contract. Both are declared now, so this is a plain typed return.\n *\n * `client` went through a narrower cast, `as RebaseCallContext[\"client\"]`,\n * and it lied twice. It said `RebaseClient`, `data` included, about the\n * server singleton, which has no `data` — so `context.client.data` compiled\n * and threw in production. And it dropped the `| undefined` of a client\n * that is attached after construction. The type now leaves `data` off, and\n * a driver that was never given a client refuses by name, at the read.\n */\n private buildCallContext(): RebaseCallContext {\n const client = this.client;\n return {\n user: this.user,\n driver: this,\n data: this.data,\n get client() {\n return requireCallbackClient(client);\n },\n storageSource: client?.storage as StorageSource\n };\n }\n\n private resolveCollectionCallbacks<M extends Record<string, unknown>>(collection: CollectionConfig<M> | undefined, path: string) {\n if (!collection && !path) return {\n collection: undefined,\n callbacks: undefined,\n globalCallbacks: undefined,\n propertyCallbacks: undefined\n };\n const registryCollection = this.registry?.getCollectionByPath(path);\n const resolvedCollection = registryCollection\n ? {\n ...collection,\n ...registryCollection\n } as CollectionConfig<M>\n : collection as CollectionConfig<M>;\n\n const callbacks = resolvedCollection?.callbacks;\n const globalCallbacks = this.registry?.getGlobalCallbacks();\n const properties = resolvedCollection?.properties;\n let propertyCallbacks;\n if (properties) {\n propertyCallbacks = buildPropertyCallbacks(properties);\n }\n return {\n collection: resolvedCollection,\n callbacks,\n globalCallbacks,\n propertyCallbacks\n };\n }\n\n /**\n * Run the three-tier afterRead pipeline (global → collection → property) on a\n * single row for a collection whose callbacks have already been resolved.\n */\n private async applyAfterReadToRow(\n row: Record<string, unknown>,\n path: string,\n resolved: ReturnType<PostgresBackendDriver[\"resolveCollectionCallbacks\"]>,\n contextForCallback: RebaseCallContext\n ): Promise<Record<string, unknown>> {\n const { collection: resolvedCollection, callbacks, globalCallbacks, propertyCallbacks } = resolved;\n let out = row;\n if (!PostgresBackendDriver.hasAfterRead(resolved)) return out;\n // Resolved once for all three tiers — see `requireCallbackCollection`.\n const callbackCollection = requireCallbackCollection(resolvedCollection, path);\n if (globalCallbacks?.afterRead) {\n out = await globalCallbacks.afterRead({\n collection: callbackCollection,\n path, row: out, context: contextForCallback\n }) ?? out;\n }\n if (callbacks?.afterRead) {\n out = await callbacks.afterRead({\n collection: callbackCollection,\n path, row: out, context: contextForCallback\n }) ?? out;\n }\n if (propertyCallbacks?.afterRead) {\n out = await propertyCallbacks.afterRead({\n collection: callbackCollection,\n path, row: out, context: contextForCallback\n }) ?? out;\n }\n return out;\n }\n\n private static hasAfterRead(resolved: ReturnType<PostgresBackendDriver[\"resolveCollectionCallbacks\"]>): boolean {\n return !!(resolved.globalCallbacks?.afterRead || resolved.callbacks?.afterRead || resolved.propertyCallbacks?.afterRead);\n }\n\n /**\n * Apply afterRead to REST/SDK read results.\n *\n * The REST / `include` path fetches rows through the raw fetch service, which\n * does NOT run callbacks — so without this, `afterRead` transforms (e.g. PII\n * masking) are silently skipped on every SDK/REST read, leaking raw data.\n * This choke point guarantees afterRead runs there too, matching the driver's\n * fetchCollection/fetchOne paths.\n *\n * It also masks embedded relation data one level deep by running the TARGET\n * collection's afterRead (so `post.author.email` is masked by the authors\n * collection, not left raw).\n */\n async applyAfterReadForRest(\n rows: Record<string, unknown>[],\n path: string\n ): Promise<Record<string, unknown>[]> {\n if (!rows || rows.length === 0) return rows;\n\n const resolved = this.resolveCollectionCallbacks(undefined, path);\n const contextForCallback = this.buildCallContext();\n const hasOwn = PostgresBackendDriver.hasAfterRead(resolved);\n\n // Resolve embedded relation targets (relationKey -> { path, resolved callbacks })\n // once, keeping only those whose target collection actually has an afterRead.\n const relationTargets: Record<string, { path: string; resolved: ReturnType<PostgresBackendDriver[\"resolveCollectionCallbacks\"]> }> = {};\n if (resolved.collection) {\n try {\n const rels = resolveCollectionRelations(resolved.collection as CollectionConfig);\n for (const [key, rel] of Object.entries(rels)) {\n const target = typeof (rel as { target?: unknown }).target === \"function\"\n ? (rel as { target: () => CollectionConfig }).target()\n : undefined;\n const targetPath = target?.slug;\n if (!targetPath) continue;\n // The relation's own target, not just its slug: the target\n // config is in hand here, so a relation pointing at a\n // collection the registry cannot resolve by slug still\n // masks through the config it declared, instead of handing\n // the tiers a collection that is not there.\n const targetResolved = this.resolveCollectionCallbacks(target, targetPath);\n if (PostgresBackendDriver.hasAfterRead(targetResolved)) {\n relationTargets[key] = { path: targetPath, resolved: targetResolved };\n }\n }\n } catch {\n // Ignore relation resolution errors (e.g. incomplete config during setup)\n }\n }\n const relKeys = Object.keys(relationTargets);\n\n if (!hasOwn && relKeys.length === 0) return rows;\n\n const maskEmbedded = async (value: unknown, target: { path: string; resolved: ReturnType<PostgresBackendDriver[\"resolveCollectionCallbacks\"]> }): Promise<unknown> => {\n if (Array.isArray(value)) {\n return Promise.all(value.map((v) => maskEmbedded(v, target)));\n }\n if (!value || typeof value !== \"object\") return value;\n const obj = value as Record<string, unknown>;\n // A pre-fetched relation payload may nest the row under `.data`.\n if (obj.__type === \"relation\" && obj.data && typeof obj.data === \"object\") {\n return { ...obj, data: await this.applyAfterReadToRow(obj.data as Record<string, unknown>, target.path, target.resolved, contextForCallback) };\n }\n // A bare reference pointer carries no row data to mask.\n if (obj.__type === \"reference\") return obj;\n return this.applyAfterReadToRow(obj, target.path, target.resolved, contextForCallback);\n };\n\n return Promise.all(rows.map(async (row) => {\n let out = hasOwn ? await this.applyAfterReadToRow(row, path, resolved, contextForCallback) : row;\n for (const key of relKeys) {\n if (out[key] === undefined || out[key] === null) continue;\n out = { ...out, [key]: await maskEmbedded(out[key], relationTargets[key]) };\n }\n return out;\n }));\n }\n\n async fetchCollection<M extends Record<string, unknown>>(\n // Forwarded whole rather than re-listed, as `listenCollection` does:\n // the list named ten of the query's fields, so `logical`, `include`,\n // `fields`, `distinct`, `withDeleted` and `searchExplain` were accepted\n // and dropped — an `or(...)` read returned every row while `count`\n // with the same group answered 1.\n { path, collection, startAfter, ...query }: FetchCollectionProps<M>\n ): Promise<Record<string, unknown>[]> {\n\n const rows = await this.dataService.fetchCollection<M>(path, {\n ...query,\n startAfter: startAfter as Record<string, unknown> | undefined,\n databaseId: collection?.databaseId\n });\n\n const {\n collection: resolvedCollection,\n callbacks,\n globalCallbacks,\n propertyCallbacks\n } = this.resolveCollectionCallbacks(collection, path);\n\n if (globalCallbacks?.afterRead || callbacks?.afterRead || propertyCallbacks?.afterRead) {\n const contextForCallback = this.buildCallContext();\n const callbackCollection = requireCallbackCollection(resolvedCollection, path);\n return Promise.all(rows.map(async (row) => {\n let fetched = row;\n // `?? fetched` on every tier. An `afterRead` that mutates the row\n // and returns nothing is a reasonable thing to write — the guide's\n // own signature says the return is a transform, not a requirement\n // — and on the tiers that lacked the fallback it replaced the row\n // with `undefined`. The collection tier tolerated it, so the same\n // callback worked or emptied the response depending on which\n // block it was registered in.\n // 1. Global callbacks first\n if (globalCallbacks?.afterRead) {\n fetched = await globalCallbacks.afterRead({\n collection: callbackCollection,\n path,\n row: fetched,\n context: contextForCallback\n }) ?? fetched;\n }\n // 2. Collection callbacks second\n if (callbacks?.afterRead) {\n fetched = await callbacks.afterRead({\n collection: callbackCollection,\n path,\n row: fetched,\n context: contextForCallback\n }) ?? fetched;\n }\n // 3. Property callbacks third\n if (propertyCallbacks?.afterRead) {\n fetched = await propertyCallbacks.afterRead({\n collection: callbackCollection,\n path,\n row: fetched,\n context: contextForCallback\n }) ?? fetched;\n }\n return fetched;\n }));\n }\n\n return rows;\n }\n\n /**\n * Listen to a collection: the rows now, then again after every change.\n *\n * The realtime service runs every read, the first one included, as\n * `authContext` — which {@link AuthenticatedPostgresBackendDriver} supplies,\n * and which defaults to this driver's own user (a driver bound to a\n * request's transaction has one). With neither, the reads run as the\n * anonymous user, as the refetches always did.\n */\n listenCollection<M extends Record<string, unknown>>(\n // Forwarded whole rather than re-listed: the list named nine of the\n // query's fields, so `logical` was dropped and an `or(...)` listener\n // was handed every row. Held back, as they always were here:\n // `vectorSearch`, which a subscription cannot serve, and `page` and\n // `withDeleted`, which the stored request has no field for.\n { path, collection, startAfter, onUpdate, onError, vectorSearch: _vectorSearch, page: _page, withDeleted: _withDeleted, ...query }: ListenCollectionProps<M>,\n authContext: SubscriptionAuthContext | undefined = this.user ? authContextOf(this.user) : undefined\n ): () => void {\n const subscriptionId = this.generateSubscriptionId();\n\n this.realtimeService.startDataDriverSubscription(subscriptionId, {\n type: \"collection\",\n path,\n collectionRequest: {\n ...query,\n startAfter: startAfter as Record<string, unknown> | undefined,\n databaseId: collection?.databaseId\n },\n authContext,\n onError: fetchErrorListener(onError)\n }, (rows) => {\n if (Array.isArray(rows)) onUpdate(rows);\n });\n\n return () => this.realtimeService.unsubscribe(subscriptionId);\n }\n\n async fetchOne<M extends Record<string, unknown>>({\n path,\n id,\n databaseId,\n collection,\n withDeleted\n }: FetchOneProps<M>): Promise<Record<string, unknown> | undefined> {\n let row = await this.dataService.fetchOne<M>(\n path,\n id,\n databaseId || collection?.databaseId,\n withDeleted\n );\n\n const {\n collection: resolvedCollection,\n callbacks,\n globalCallbacks,\n propertyCallbacks\n } = this.resolveCollectionCallbacks(collection, path);\n\n if (row && (globalCallbacks?.afterRead || callbacks?.afterRead || propertyCallbacks?.afterRead)) {\n const contextForCallback = this.buildCallContext();\n const callbackCollection = requireCallbackCollection(resolvedCollection, path);\n // `?? row` on every tier — see the note in `fetchCollection`.\n // 1. Global callbacks first\n if (globalCallbacks?.afterRead) {\n row = await globalCallbacks.afterRead({\n collection: callbackCollection,\n path,\n row,\n context: contextForCallback\n }) ?? row;\n }\n // 2. Collection callbacks second\n if (callbacks?.afterRead) {\n row = await callbacks.afterRead({\n collection: callbackCollection,\n path,\n row,\n context: contextForCallback\n }) ?? row;\n }\n // 3. Property callbacks third\n if (propertyCallbacks?.afterRead) {\n row = await propertyCallbacks.afterRead({\n collection: callbackCollection,\n path,\n row,\n context: contextForCallback\n }) ?? row;\n }\n }\n\n return row;\n }\n\n /**\n * Listen to one row: the row now, then again after every change, and\n * `null` when it is not there — deleted, never created, or not readable by\n * the subscriber. Reads as {@link listenCollection} does.\n */\n listenOne<M extends Record<string, unknown>>(\n { path, id, onUpdate, onError }: ListenOneProps<M>,\n authContext: SubscriptionAuthContext | undefined = this.user ? authContextOf(this.user) : undefined\n ): () => void {\n const subscriptionId = this.generateSubscriptionId();\n\n this.realtimeService.startDataDriverSubscription(subscriptionId, {\n type: \"single\",\n path,\n id,\n authContext,\n onError: fetchErrorListener(onError)\n }, (row) => {\n // `null` is delivered, not dropped: it is how a listener learns its\n // row is gone. Dropped, the listener kept the last row it had.\n if (!Array.isArray(row)) onUpdate(row);\n });\n\n return () => this.realtimeService.unsubscribe(subscriptionId);\n }\n\n /**\n * How many membership rows a single write will read to answer \"which\n * tenants is this caller in\".\n *\n * A cap, not a limit on the feature: past it the API-level check stops\n * being able to prove a tenant is *not* the caller's, and defers to the\n * policy's `WITH CHECK`, which decides either way. The alternative — an\n * uncapped read of a membership table on every insert — makes the cost of a\n * write depend on how many organizations one user happens to belong to.\n */\n private static readonly TENANT_MEMBERSHIP_CAP = 100;\n\n /**\n * Stamp, or refuse, the tenant on a write to a `tenant`-scoped collection.\n *\n * A no-op for every collection that declares no tenancy, which is the\n * common case and costs one property read.\n */\n private async applyTenantScope<M extends Record<string, unknown>>(\n collection: CollectionConfig | undefined,\n path: string,\n values: Partial<EntityValues<M>>,\n status: EntityStatus,\n previousValues: Record<string, unknown> | undefined\n ): Promise<Partial<EntityValues<M>>> {\n const tenant = getTenantConfig(collection);\n if (!tenant) return values;\n\n // The same set the policy lets past: the trusted server context (no\n // user at all) and the bypass roles. Deciding it here rather than\n // inside the pure helper keeps \"who is calling\" in the one place that\n // knows.\n const bypassRoles = tenantBypassRoles(tenant);\n const callerRoles = (this.user?.roles ?? []).map(r =>\n typeof r === \"string\" ? r : String((r as { id?: unknown })?.id ?? r));\n const bypass = !this.user || callerRoles.some(r => bypassRoles.includes(r));\n\n const { tenants, complete } = bypass\n ? { tenants: [] as unknown[], complete: true }\n : await this.resolveCallerTenants(tenant, collection);\n\n const decision = resolveTenantWrite({\n tenant,\n values: values as Record<string, unknown>,\n status,\n callerTenants: tenants,\n callerTenantsComplete: complete,\n bypass,\n previousValues,\n slug: collection?.slug ?? path\n });\n\n if (decision.refusal) {\n const { code, field, message } = decision.refusal;\n throw ApiError.badRequest(message, code, {\n collection: path,\n violations: [{ field, code, message }]\n });\n }\n return decision.values as Partial<EntityValues<M>>;\n }\n\n /**\n * The tenants this caller may write into.\n *\n * Two sources, and they answer at different costs. A `claim` is already on\n * the request — no query. A `membership` is rows in a table, and reading\n * them is a real query, so it is capped (see\n * {@link PostgresBackendDriver.TENANT_MEMBERSHIP_CAP}) and `complete` says\n * whether the cap was hit.\n *\n * The membership read runs in the caller's own context, so the membership\n * collection's RLS applies to it — exactly as it does inside the generated\n * policy's subquery. That is deliberate: if the two disagreed, the API\n * would refuse writes the database would have accepted, or accept writes it\n * refuses. `validateCollectionConfig` is what makes sure the membership\n * collection is readable by its own members in the first place.\n */\n private async resolveCallerTenants(\n tenant: NonNullable<ReturnType<typeof getTenantConfig>>,\n collection: CollectionConfig | undefined\n ): Promise<{ tenants: unknown[]; complete: boolean }> {\n if (\"claim\" in tenant.from) {\n const value = this.user?.claims?.[tenant.from.claim];\n const empty = value === undefined || value === null || value === \"\";\n return { tenants: empty ? [] : [value], complete: true };\n }\n\n const uid = this.user?.uid;\n if (!uid) return { tenants: [], complete: true };\n\n const { collection: slug, userField, tenantField } = tenant.from.membership;\n const cap = PostgresBackendDriver.TENANT_MEMBERSHIP_CAP;\n try {\n // Read on the trusted plane for its fields — the database still\n // scopes the rows to the caller. This is the server deciding a\n // write, not a read served to anyone, and the caller's `access.read`\n // rules describe what they may receive: applied here, a tenant\n // column members may not read would leave every member in no\n // tenant at all.\n const rows = await withFieldViewer(undefined, () => this.dataService.fetchCollection(slug, {\n filter: { [userField]: [\"==\", uid] } as never,\n limit: cap + 1,\n databaseId: collection?.databaseId\n }));\n const tenants = Array.from(new Set(\n rows.slice(0, cap)\n .map(row => (row as Record<string, unknown>)[tenantField])\n .filter(value => value !== null && value !== undefined)\n .map(value => (typeof value === \"object\"\n ? (value as { id?: unknown }).id ?? value\n : value))\n ));\n return { tenants, complete: rows.length <= cap };\n } catch (err) {\n // A membership table this caller cannot read is not a reason to\n // fail the write here — the policy is about to refuse it anyway,\n // and refusing with the wrong message would be worse than letting\n // the database answer. `complete: false` is what says \"no evidence\n // of absence\"; see `resolveTenantWrite`. A statement the database\n // refused is the exception: it has aborted the write's\n // transaction, so the database can no longer answer anything but\n // 25P02, and its own refusal is the better message.\n if (reachedDatabase(err)) throw err;\n logger.debug(`[save] Could not read '${slug}' to resolve the caller's tenants`, {\n detail: err instanceof Error ? err.message : String(err)\n });\n return { tenants: [], complete: false };\n }\n }\n\n /**\n * Where an update reads the row it addresses before writing it.\n *\n * The path itself, except through a many-to-many relation: there the path\n * names the parent's set, and an update may *link* a row that exists but is\n * not in that set yet (the persistence layer attaches it). Read through the\n * path, such a row is absent, so it is read from its own collection.\n */\n private storedRowPath(path: string): string {\n if (!isNestedPath(path)) return path;\n const hop = resolveNestedPath(path, this.registry);\n return hop && isJunctionBackedRelation(hop.relation) ? hop.targetCollection.slug : path;\n }\n\n /**\n * The stored row an upsert's key names, if this caller can address it —\n * with its address and the values to update it with.\n *\n * Read as the caller reads: their policies and `beforeQuery` scope. On the\n * trusted plane for its fields only, like {@link resolveCallerTenants}:\n * the key columns are the server's to see.\n *\n * Soft-deleted rows are read too, and a key one of them holds is refused\n * with `ROW_IN_TRASH` (see {@link rowInTrashError}). Hidden, it was not\n * found here, the write went down the create pipeline, and the INSERT's\n * conflict branch wrote into the trashed row: `201 Created`, hooks told\n * \"new\", a second `create` in history, and the row still in the trash.\n *\n * `undefined` when the key is incomplete (the write is a plain insert,\n * which is the persistence layer's rule too), when a key value is not a\n * plain value, or when no such row is visible. None of those can write a\n * stored row wrongly: the statement's own conflict branch sets only what\n * the caller sent, only on a row in their scope, and never on one in the\n * trash.\n *\n * The values drop the key and the conflict target, which the stored row\n * already has: a natural-key upsert carrying a key the caller invented\n * must not move the row onto it.\n */\n private async findUpsertTarget<M extends Record<string, unknown>>(\n path: string,\n collection: CollectionConfig,\n values: Partial<EntityValues<M>>,\n id: string | number | undefined,\n onConflict: readonly string[] | undefined\n ): Promise<{ id: string; values: Partial<EntityValues<M>> } | undefined> {\n const primaryKeys = getPrimaryKeys(collection, this.registry);\n const key: Record<string, unknown> = { ...values };\n if (isPresentKey(id)) Object.assign(key, parseIdValues(id, primaryKeys));\n const targetFields = onConflict && onConflict.length > 0\n ? [...onConflict]\n : primaryKeys.map(info => info.fieldName);\n if (targetFields.length === 0) return undefined;\n\n const filter: FilterValues<string> = {};\n for (const field of targetFields) {\n const value = key[field];\n if (value === undefined || value === null || value === \"\") return undefined;\n if (typeof value !== \"string\" && typeof value !== \"number\" && typeof value !== \"boolean\") return undefined;\n filter[field] = [\"==\", value];\n }\n\n const softDelete = resolveSoftDelete(collection);\n const [row] = await withFieldViewer(undefined, () => this.dataService.fetchCollectionForRest(path, {\n filter,\n limit: 1,\n databaseId: collection.databaseId,\n withDeleted: softDelete ? true : undefined\n }));\n // The row must be the one the key names. A filter key the table does\n // not have is dropped rather than refused under the process-wide\n // \"warn\" mode, and an unfiltered `limit: 1` is somebody else's row.\n if (!row || targetFields.some(field => String(row[field]) !== String(key[field]))) return undefined;\n if (softDelete && row[softDelete.field] !== null && row[softDelete.field] !== undefined) {\n throw rowInTrashError(path, targetFields.map(field => String(key[field])).join(\", \"), softDelete);\n }\n\n const rest: Record<string, unknown> = { ...values };\n for (const field of [...primaryKeys.map(info => info.fieldName), ...targetFields]) delete rest[field];\n return {\n id: deriveRowAddress(row, collection, this.registry),\n values: rest as Partial<EntityValues<M>>\n };\n }\n\n async save<M extends Record<string, unknown>>(props: SaveProps<M>): Promise<Record<string, unknown>> {\n // A frame per write, so a hook whose `context.data` write runs that\n // hook again is refused at a bounded depth instead of looping inside\n // the request's transaction. See `write-depth.ts`.\n return inWriteFrame(props.path, \"save\", frame => this.saveInFrame(props, frame));\n }\n\n private async saveInFrame<M extends Record<string, unknown>>({\n path,\n id,\n values,\n collection,\n status,\n upsert,\n onConflict\n }: SaveProps<M>, frame: WriteFrame): Promise<Record<string, unknown>> {\n\n const {\n collection: resolvedCollection,\n callbacks,\n globalCallbacks,\n propertyCallbacks\n } = this.resolveCollectionCallbacks(collection, path);\n\n // An upsert that finds its row is an update of that row, and runs as\n // one: the update gate, `beforeSave` told \"existing\" with the stored\n // values, `on_update` stamps only, history \"update\". Every upsert door\n // (`?on_conflict=`, bulk and `_batch` upserts, the SDK, the socket)\n // reached the create pipeline instead, and the INSERT's conflict branch\n // wrote it over the stored row: omitted fields reset to their\n // `defaultValue`, `user_on_create` handed to whoever ran the import,\n // hooks told the row was new, and a row the caller's `beforeQuery`\n // hides updated and then answered 500. The read runs in the write's\n // transaction; a row it cannot see is still met safely by the\n // statement's own conflict branch below.\n if (status === \"existing\") {\n assertUpdateDoesNotSoftDelete(resolvedCollection as CollectionConfig | undefined, values, path);\n if (isPresentKey(id)) {\n assertKeyUnchanged(resolvedCollection as CollectionConfig | undefined, this.registry, id, values as Record<string, unknown>, path);\n }\n }\n\n const upserting = upsert === true && status !== \"existing\";\n if (upserting && resolvedCollection) {\n const stored = await this.findUpsertTarget<M>(\n path, resolvedCollection as CollectionConfig, values, id, onConflict\n );\n if (stored) {\n // The same write, continued as an update: the same frame.\n return this.saveInFrame<M>({ path, id: stored.id, values: stored.values, collection, status: \"existing\" }, frame);\n }\n }\n\n let updatedValues = values;\n const contextForCallback = this.buildCallContext();\n\n // Declared defaults are filled in BEFORE the hooks, not after, so a\n // `beforeSave` sees the row as it will be stored rather than a version\n // of it missing every key the caller happened to omit. A hook that\n // reads `values.currency` to pick a tax rate was reading `undefined`\n // on exactly the writes the default exists to cover.\n //\n // Before validation too: `required` on create is satisfied by a\n // default, which is why `assertWriteValuesValid` skips a property that\n // declares one.\n if ((status === \"new\" || status === \"copy\") && resolvedCollection?.properties) {\n updatedValues = applyDefaultValuesOnCreate<M>(\n updatedValues,\n resolvedCollection.properties\n ) as Partial<EntityValues<M>>;\n }\n\n // Fetch previous values for callbacks AND history recording. Same walk\n // as the saved row the callbacks receive (`fetchOneForRest`), so\n // `values` and `previousValues` compare like with like — a Date on one\n // side and its ISO string on the other reads as a change that never\n // happened.\n let previousValuesForHistory: Partial<M> | undefined;\n if (status === \"existing\" && id) {\n // An update addresses a row the caller can read, and a key that\n // reads as nothing is a 404 — answered *here*, before any hook runs\n // and before the write, as `delete` answers it. The read is the\n // caller's: their policies, their `beforeQuery`, and the trash\n // hidden. So a row in the trash is a 404 too, unless this update\n // is its restore (`restoresSoftDeletedRow`), which is the one edit\n // that reaches a stamped row.\n //\n // This used to refuse only when the collection declared a\n // `beforeQuery`, and REST's own pre-read was the rule everywhere\n // else. Every other door wrote straight through: the socket, MCP\n // and `driver.data` ran `beforeSave` for a key no row has before\n // the 404, and edited a trashed row — hooks, history and all —\n // that REST answered 404 for. And a restore read its previous\n // values with the trash hidden, found none, and history skipped\n // the update it had nothing to compare with: every restore went\n // unrecorded, and the trail said \"deleted\" for a live row.\n //\n // Through a many-to-many path an update can also *link* a row\n // that exists but is not linked yet, so there the row is read from\n // its own collection. A read that fails for another reason (a\n // collection whose key the registry cannot resolve) stays\n // best-effort enrichment and is not a reason to fail a write.\n try {\n const existing = await this.dataService.getFetchService().fetchOneForRest(\n this.storedRowPath(path), id, undefined, resolvedCollection?.databaseId,\n {\n withDeleted: restoresSoftDeletedRow(\n resolvedCollection as CollectionConfig | undefined,\n values as Record<string, unknown>\n ) ? true : undefined\n }\n );\n if (!existing) {\n throw ApiError.notFound(`No row \"${id}\" in \"${path}\" to update.`);\n }\n if (existing) {\n const { id: _existingId, ...existingValues } = existing;\n previousValuesForHistory = existingValues as Partial<M>;\n }\n } catch (err) {\n // The refusal above is the answer, not a failed enrichment.\n if (err instanceof ApiError) throw err;\n // So is a statement the database refused: on a request it\n // has aborted the write's transaction, and carrying on only\n // turns the next statement into an unrelated 25P02.\n if (reachedDatabase(err)) throw err;\n // Best-effort enrichment: callbacks and history run without\n // previous values rather than the save failing on a read the\n // write itself does not need (e.g. a collection whose key the\n // registry cannot resolve).\n logger.debug(`[save] Could not fetch previous values for \"${path}\"`, { detail: err instanceof Error ? err.message : String(err) });\n }\n }\n\n /** The row as the hooks receive it, to tell what they wrote. */\n const beforeHooks: Partial<EntityValues<M>> = updatedValues;\n\n // A `before*` callback is the application speaking, not the server\n // failing: a bare `throw` is the documented way to block a write, so it\n // answers 400 with the author's message rather than a masked 500.\n try {\n frame.stage = \"beforeSave\";\n if (globalCallbacks?.beforeSave || callbacks?.beforeSave || propertyCallbacks?.beforeSave) {\n const callbackCollection = requireCallbackCollection(resolvedCollection, path);\n // 1. Global callbacks first\n if (globalCallbacks?.beforeSave) {\n const result = await globalCallbacks.beforeSave({\n collection: callbackCollection,\n path,\n id,\n values: updatedValues,\n previousValues: previousValuesForHistory,\n status,\n context: contextForCallback\n });\n if (result) updatedValues = applyBeforeSaveResult(updatedValues, result);\n }\n\n // 2. Collection callbacks second\n if (callbacks?.beforeSave) {\n const result = await callbacks.beforeSave({\n collection: callbackCollection,\n path,\n id,\n values: updatedValues,\n previousValues: previousValuesForHistory,\n status,\n context: contextForCallback\n });\n if (result) updatedValues = applyBeforeSaveResult(updatedValues, result);\n }\n\n // 3. Property callbacks third\n if (propertyCallbacks?.beforeSave) {\n const result = await propertyCallbacks.beforeSave({\n collection: callbackCollection,\n path,\n id,\n values: updatedValues,\n previousValues: previousValuesForHistory,\n status,\n context: contextForCallback\n });\n if (result) updatedValues = applyBeforeSaveResult(updatedValues, result);\n }\n\n }\n } catch (callbackError) {\n throw toCallbackError(callbackError, \"beforeSave\", path);\n }\n const afterHooks: Partial<EntityValues<M>> = updatedValues;\n if (status === \"existing\" && isPresentKey(id)) {\n assertKeyUnchanged(resolvedCollection as CollectionConfig | undefined, this.registry, id, updatedValues as Record<string, unknown>, path);\n }\n\n // Apply autoValue timestamps (on_create / on_update) at the application layer.\n // This handles updated_at fields for all writes that flow through the Rebase backend.\n if (resolvedCollection?.properties) {\n updatedValues = updateDateAutoValues({\n inputValues: updatedValues,\n properties: resolvedCollection.properties,\n status: status ?? \"new\",\n timestampNowValue: new Date()\n });\n // The identity half of the same idea: `created_by` / `updated_by`\n // taken from the call context, never from the body. Stamped after\n // the hooks for the same reason the timestamps are — a hook must\n // not be able to attribute a write to another user either.\n assertActingUserForAutoValues(\n resolvedCollection.properties,\n status ?? \"new\",\n this.user?.uid,\n path\n );\n updatedValues = updateUserAutoValues({\n inputValues: updatedValues,\n properties: resolvedCollection.properties,\n status: status ?? \"new\",\n uid: this.user?.uid\n });\n // And an update leaves the create-time stamps alone. They record\n // who made the row and when, which no later write can change; left\n // to the body (or a hook), any row writer could reattribute a row\n // they edited — `created_by` is the column that exists to stop that.\n if (status === \"existing\") {\n const kept: Record<string, unknown> = { ...updatedValues };\n for (const key of createStampKeys(resolvedCollection.properties)) delete kept[key];\n updatedValues = kept as Partial<EntityValues<M>>;\n }\n }\n\n // Tenancy, last of the three stamps and for the same reason they are\n // here rather than in a hook: the tenant comes from the *call context*,\n // never from the body, so a caller (or a `beforeSave`) cannot attribute\n // a row to another tenant. The database enforces the same rule through\n // the restrictive tenancy policy — this only reaches the same answer\n // one layer earlier, with the field named. See `@rebasepro/common`'s\n // `resolveTenantWrite`.\n updatedValues = await this.applyTenantScope(\n resolvedCollection as CollectionConfig | undefined,\n path,\n updatedValues,\n status ?? \"new\",\n previousValuesForHistory as Record<string, unknown> | undefined\n ) as Partial<EntityValues<M>>;\n\n // A create that names its key is still a create. The persistence layer\n // reads an `id` as \"update this row\", so a new row's key travels inside\n // the values, where the INSERT takes it — which is what the REST create\n // does with a key in the body. Handed down as `id`, in-process\n // `create(values, id)`, MCP `create_document { id }` and a socket\n // `SAVE { id, status: \"new\" }` were all UPDATEs: a new id answered 404,\n // and an existing one was overwritten with create-time values instead\n // of refused. Only `status: \"existing\"` addresses a stored row; a\n // duplicate key here is the driver's 409, as it is for any create.\n let writeId = id;\n if ((status === \"new\" || status === \"copy\") && !upsert && isPresentKey(id) && resolvedCollection) {\n updatedValues = {\n ...updatedValues,\n ...parseIdValues(id, getPrimaryKeys(resolvedCollection as CollectionConfig, this.registry))\n } as Partial<EntityValues<M>>;\n writeId = undefined;\n }\n\n // For an upsert whose INSERT meets a stored row anyway — one the read\n // above could not see, or one inserted concurrently — what the\n // conflict-update may set: what the caller sent, what a hook wrote, and\n // the `on_update` stamps. Everything else here the create alone decided\n // (declared defaults, a tenant stamp; the create-time stamps are left\n // out by the persistence layer), and a stored row keeps its own.\n const insertOnlyKeys = upserting\n ? insertOnlyKeysOf(updatedValues, values, beforeHooks, afterHooks, resolvedCollection?.properties)\n : undefined;\n // A stamped soft-delete field may create a row already trashed, and\n // may not trash a stored one: that is a delete (see\n // `assertUpdateDoesNotSoftDelete`).\n const softDeleteField = resolveSoftDelete(resolvedCollection as CollectionConfig | undefined)?.field;\n if (insertOnlyKeys && softDeleteField\n && updatedValues[softDeleteField] !== undefined && updatedValues[softDeleteField] !== null) {\n insertOnlyKeys.push(softDeleteField);\n }\n\n try {\n const storedRow = await this.dataService.save<M>(\n path,\n updatedValues,\n writeId,\n resolvedCollection?.databaseId,\n { upsert: upserting, onConflict, insertOnlyKeys }\n );\n\n // Two rows from here on, and which one goes where is the point.\n // `storedRow` is what the table now holds: `afterSave` judges it,\n // history records it, its address is derived from it. `savedRow` is\n // what the CALLER reads — `afterRead`'s view, for the response and\n // its realtime echo. They used to be one variable, so a masking\n // `afterRead` (`email` → `********`) was what history recorded and\n // what a revert then wrote into the column, and a computed field it\n // added made every version un-revertable. A copy goes in, so an\n // `afterRead` that edits its argument in place edits only the view.\n let savedRow = storedRow;\n if (storedRow && (globalCallbacks?.afterRead || callbacks?.afterRead || propertyCallbacks?.afterRead)) {\n const callbackCollection = requireCallbackCollection(resolvedCollection, path);\n savedRow = { ...storedRow };\n // `?? savedRow` on every tier — see the note in `fetchCollection`.\n // Here it decided what the write's own response body contained.\n // 1. Global callbacks first\n if (globalCallbacks?.afterRead) {\n savedRow = await globalCallbacks.afterRead({\n collection: callbackCollection,\n path,\n row: savedRow,\n context: contextForCallback\n }) ?? savedRow;\n }\n // 2. Collection callbacks second\n if (callbacks?.afterRead) {\n savedRow = await callbacks.afterRead({\n collection: callbackCollection,\n path,\n row: savedRow,\n context: contextForCallback\n }) ?? savedRow;\n }\n // 3. Property callbacks third\n if (propertyCallbacks?.afterRead) {\n savedRow = await propertyCallbacks.afterRead({\n collection: callbackCollection,\n path,\n row: savedRow,\n context: contextForCallback\n }) ?? savedRow;\n }\n }\n\n // The row is exactly its columns, so its address is derived, not read\n // off it: `storedRow.id` is undefined for every table whose key is not\n // literally named `id`, and is ordinary data for a table that has such\n // a column without it being the key. From the stored row: an\n // `afterRead` may reshape or mask the key columns of the view.\n const savedId = deriveRowAddress(\n storedRow,\n (resolvedCollection ?? collection) as CollectionConfig,\n this.registry\n );\n // `values` are the row's columns — all of them, as stored. For an\n // `id`-keyed table that includes `id`, which used to be stripped here\n // because it was the synthesized address rather than the column it now is.\n const savedValues = storedRow;\n\n // `afterSave` runs INSIDE the write's transaction and is awaited, so a\n // throw here rolls the row back — the write and its consequences\n // commit together or not at all. That is the contract the docs state,\n // and it only holds if the throw is answerable: routed through the\n // same `toCallbackError` path a `before*` hook uses, it becomes a 400\n // `CALLBACK_REJECTED` naming the stage instead of a masked 500 whose\n // message only the server log ever sees. Side effects that must not\n // hold a transaction open (HTTP calls, mail) belong in a job — one\n // enqueued in a transaction that rolls back was never enqueued.\n try {\n frame.stage = \"afterSave\";\n if (globalCallbacks?.afterSave || callbacks?.afterSave || propertyCallbacks?.afterSave) {\n const callbackCollection = requireCallbackCollection(resolvedCollection, path);\n // 1. Global callbacks first\n if (globalCallbacks?.afterSave) {\n await globalCallbacks.afterSave({\n collection: callbackCollection,\n path,\n id: savedId,\n values: savedValues,\n previousValues: previousValuesForHistory,\n status,\n context: contextForCallback\n });\n }\n // 2. Collection callbacks second\n if (callbacks?.afterSave) {\n await callbacks.afterSave({\n collection: callbackCollection,\n path,\n id: savedId,\n values: savedValues as Partial<M>,\n previousValues: previousValuesForHistory,\n status,\n context: contextForCallback\n });\n }\n // 3. Property callbacks third\n if (propertyCallbacks?.afterSave) {\n await propertyCallbacks.afterSave({\n collection: callbackCollection,\n path,\n id: savedId,\n values: savedValues,\n previousValues: previousValuesForHistory,\n status,\n context: contextForCallback\n });\n }\n }\n } catch (callbackError) {\n throw toCallbackError(callbackError, \"afterSave\", path);\n }\n\n // Awaited, inside the write's transaction, so the entry commits with\n // its row. It used to be dispatched and dropped — the promise was\n // not held, the service swallowed its own errors — which meant the\n // audit trail of a collection that opted into one had silent,\n // unbounded gaps. See `HistoryService.recordHistory` for the trade.\n if (this.historyService && resolvedCollection?.history) {\n await this.historyService.recordHistory({\n // The collection's slug, which is what the history route\n // reads — not the path the write came in on. Keyed by\n // `owners/1/docs`, a write made through a parent never\n // appeared in the row's own history.\n tableName: resolvedCollection.slug,\n id: savedId,\n action: status === \"existing\" ? \"update\" : \"create\",\n values: savedValues as Record<string, unknown>,\n previousValues: previousValuesForHistory as Record<string, unknown> | undefined,\n updatedBy: this.user?.uid\n });\n }\n\n // Notify real-time subscribers (deferred if inside a transaction)\n if (this._deferNotifications) {\n this._pendingNotifications.push({\n path,\n id: savedId,\n row: savedRow,\n databaseId: resolvedCollection?.databaseId\n });\n } else {\n await this.realtimeService.notifyUpdate(\n path,\n savedId,\n savedRow,\n resolvedCollection?.databaseId\n );\n }\n\n return savedRow;\n } catch (error) {\n if (globalCallbacks?.afterSaveError || callbacks?.afterSaveError || propertyCallbacks?.afterSaveError) {\n const callbackCollection = requireCallbackCollection(resolvedCollection, path);\n // Global, then collection, then property — each on its own,\n // and guarded: a hook that throws cannot replace the failure\n // the caller is owed with its own.\n const runHooks = async (context: RebaseCallContext) => {\n // What the hook exists to see. It was documented from the\n // start and never passed, so `props.error` was `undefined`\n // in every handler ever written against the guide. `id` is\n // the caller's when there is one, and `previousValues` is\n // whatever the pre-write read managed to fetch.\n const errorProps = {\n collection: callbackCollection,\n path,\n id,\n values: updatedValues,\n previousValues: previousValuesForHistory,\n status,\n error,\n context\n };\n for (const [tier, hook] of [\n [\"global\", globalCallbacks?.afterSaveError],\n [\"collection\", callbacks?.afterSaveError],\n [\"property\", propertyCallbacks?.afterSaveError]\n ] as const) {\n if (!hook) continue;\n try {\n await hook(errorProps);\n } catch (hookError) {\n logger.error(`[save] The ${tier} afterSaveError hook for \"${path}\" threw; answering with the save's own failure`, { error: hookError });\n }\n }\n };\n // The hook is for alerting, and the documented way to alert is\n // a job. Inside a request's write it waits for that write's\n // transaction to end: it used to run inside it, so what it\n // enqueued rode the transaction this failure rolls back, and\n // vanished with it. Run after, it commits on its own.\n if (!currentWriteScope()?.afterSettled(runHooks)) {\n frame.stage = \"afterSaveError\";\n await runHooks(contextForCallback);\n }\n }\n throw error;\n }\n }\n\n /**\n * Write many rows through the same pipeline as {@link save}.\n *\n * The batch runs in one transaction of its own, so a failure part-way leaves\n * nothing behind — the point of a batch is that a re-run starts from a known\n * state. When this driver is already inside a transaction (the authenticated\n * path, via `withTransaction`) the nested call becomes a savepoint, which is\n * still atomic and still commits once.\n *\n * Rows are applied in order, so a batch that touches the same key twice ends\n * with the last write winning, exactly as separate calls would.\n */\n private bindToTransaction(tx: DrizzleClient): PostgresBackendDriver {\n // Bind the whole batch to the transaction handle. Without this the rows\n // would be written through `this.db` and survive a rollback.\n const txDriver = new PostgresBackendDriver(\n tx, this.realtimeService, this.registry, this.user, this.poolManager, this.historyService\n );\n txDriver.dataService = new DataService(tx, this.registry, () => txDriver.buildCallContext());\n txDriver.client = this.client;\n // Carry the caller's notification batching through, so a bulk write\n // nested in an outer transaction still holds its events until commit.\n txDriver._deferNotifications = this._deferNotifications;\n txDriver._pendingNotifications = this._pendingNotifications;\n return txDriver;\n }\n\n /**\n * The callback context for work already running inside `tx` on behalf of\n * `user` — for a caller that opened the user-scoped transaction itself.\n *\n * The realtime refetch is that caller: it applies the subscriber's auth\n * context and role switch on its own connection. Its `afterRead` hooks used\n * to get a context assembled by hand, and it was wrong in both directions:\n * `data` was THIS driver's — the base one, on the owner connection, outside\n * that transaction — so a hook reading related rows through `context.data`\n * bypassed RLS on every subscription frame while the REST read of the same\n * rows did not; and `client` and `storageSource` were absent, though the\n * type promises both. This builds the context the REST path builds, bound to\n * the transaction the rows came from.\n */\n callContextWithin(tx: DrizzleClient, user: User): RebaseCallContext {\n const txDriver = new PostgresBackendDriver(\n tx, this.realtimeService, this.registry, user, this.poolManager, this.historyService\n );\n txDriver.dataService = new DataService(tx, this.registry, () => txDriver.buildCallContext());\n txDriver.client = this.client;\n return txDriver.buildCallContext();\n }\n\n async saveMany<M extends Record<string, unknown>>({\n path,\n rows,\n collection,\n upsert,\n onConflict\n }: SaveManyProps<M>): Promise<Record<string, unknown>[]> {\n return this.db.transaction(async (tx) => {\n const txDriver = this.bindToTransaction(tx);\n\n const saved: Record<string, unknown>[] = [];\n\n for (let i = 0; i < rows.length; i++) {\n const values = rows[i];\n const id = (values as Record<string, unknown>)?.id as string | number | undefined;\n try {\n saved.push(await txDriver.save<M>({\n path,\n values,\n // No `id` argument, deliberately: passing one selects the\n // UPDATE path, and an import's rows usually carry a natural\n // key for a row that does not exist yet — which would 404 on\n // every one. Leaving the key inside `values` is what\n // single-row `create(data, id)` does, and it inserts.\n // Callers who want existing rows overwritten pass `upsert`.\n collection,\n status: \"new\",\n upsert,\n onConflict\n }));\n } catch (error) {\n // One bad row in ten thousand is impossible to find from a\n // message that only says the batch failed. Say which row, and\n // keep the original error as the cause so its status survives.\n const label = id !== undefined ? `id ${JSON.stringify(id)}` : \"no id\";\n throw Object.assign(\n new Error(`Row ${i} of ${rows.length} (${label}) failed: ${(error as Error)?.message ?? error}`, { cause: error }),\n {\n statusCode: (error as { statusCode?: number })?.statusCode,\n code: (error as { code?: string })?.code,\n name: (error as Error)?.name\n }\n );\n }\n }\n\n return saved;\n });\n }\n\n /**\n * Update many rows through the same pipeline as {@link save}, in one\n * transaction.\n *\n * Structurally the mirror of {@link saveMany} — same tx-bound sub-driver,\n * same deferred notifications, same per-row error labelling — but it calls\n * `save` with an explicit `id` and `status: \"existing\"`, which is precisely\n * what `saveMany` cannot do: that one passes `status: \"new\"` and keeps the\n * key inside `values`, so it inserts or upserts and can never target a\n * particular row.\n *\n * All-or-nothing, so an id matching no row aborts the batch. A partial\n * update is the outcome with no good recovery: the caller cannot tell which\n * half landed without re-reading everything.\n */\n async updateMany<M extends Record<string, unknown>>({\n path,\n updates,\n collection\n }: UpdateManyProps<M>): Promise<Record<string, unknown>[]> {\n return this.db.transaction(async (tx) => {\n const txDriver = this.bindToTransaction(tx);\n\n const saved: Record<string, unknown>[] = [];\n\n for (let i = 0; i < updates.length; i++) {\n const { id, values } = updates[i];\n try {\n // Read first so a missing row is a 404 rather than a silent\n // no-op. `save` with status \"existing\" would otherwise write\n // an UPDATE that matches nothing and report success.\n // A restore is an update of a row the default read hides;\n // see `restoresSoftDeletedRow`.\n const existing = await txDriver.fetchOne({\n path,\n id: String(id),\n collection: collection as CollectionConfig,\n withDeleted: restoresSoftDeletedRow(\n (collection as CollectionConfig | undefined) ?? this.registry?.getCollectionByPath(path),\n values as Record<string, unknown>\n ) ? true : undefined\n });\n if (!existing) {\n throw Object.assign(new Error(`No row with id ${JSON.stringify(id)}`), {\n statusCode: 404,\n code: \"NOT_FOUND\"\n });\n }\n\n saved.push(await txDriver.save<M>({\n path,\n id: String(id),\n values,\n collection,\n status: \"existing\"\n }));\n } catch (error) {\n // Say which entry, as saveMany does: \"the batch failed\" is\n // unactionable at a thousand rows.\n throw Object.assign(\n new Error(`Update ${i} of ${updates.length} (id ${JSON.stringify(id)}) failed: ${(error as Error)?.message ?? error}`, { cause: error }),\n {\n statusCode: (error as { statusCode?: number })?.statusCode,\n code: (error as { code?: string })?.code,\n name: (error as Error)?.name\n }\n );\n }\n }\n\n return saved;\n });\n }\n\n /**\n * Delete many rows in one transaction, running the full delete pipeline —\n * `beforeDelete`, the delete, `afterDelete` — for each.\n *\n * Looping the single-row {@link delete} rather than emitting one\n * `DELETE ... WHERE id = ANY($1)` is the deliberate choice: a single\n * statement would be faster and would skip every callback, so a collection\n * relying on `beforeDelete` to veto or on `afterDelete` to clean up\n * dependents would behave differently depending on how many rows the caller\n * happened to delete at once. Same pipeline, one transaction.\n */\n async deleteMany<M extends Record<string, unknown>>({\n path,\n ids,\n collection,\n hard\n }: DeleteManyProps<M>): Promise<void> {\n await this.db.transaction(async (tx) => {\n const txDriver = this.bindToTransaction(tx);\n\n for (let i = 0; i < ids.length; i++) {\n const id = ids[i];\n try {\n // No read first: `delete` reads the row itself, and an id\n // that matches nothing is its 404, which rolls the batch\n // back like any other failure.\n await txDriver.delete<M>({\n row: { id: String(id), path },\n collection,\n hard\n });\n } catch (error) {\n throw Object.assign(\n new Error(`Delete ${i} of ${ids.length} (id ${JSON.stringify(id)}) failed: ${(error as Error)?.message ?? error}`, { cause: error }),\n {\n statusCode: (error as { statusCode?: number })?.statusCode,\n code: (error as { code?: string })?.code,\n name: (error as Error)?.name\n }\n );\n }\n }\n });\n }\n\n /**\n * A mixed list of writes across collections, as one unit of work.\n *\n * Same transaction, same tx-bound sub-driver and same per-entry error\n * labelling as {@link saveMany} — deliberately the same plumbing rather\n * than a second copy of it, because the three ways this could drift\n * (notifications not deferred, the sub-driver not bound, an error losing\n * its status) are all silent.\n *\n * The one thing this adds is `$ref`. An operation may name itself, and a\n * later one may stand a `{ \"$ref\": \"order.id\" }` where a value goes; the\n * substitution happens here because inside the transaction is the only\n * place the row the reference points at exists. Backward references only —\n * the REST layer refuses a forward one before the transaction opens, so a\n * body that cannot work never costs a rollback.\n */\n async batchWrite<M extends Record<string, unknown>>({\n operations\n }: BatchWriteProps<M>): Promise<(Record<string, unknown> | null)[]> {\n return this.db.transaction(async (tx) => {\n const txDriver = this.bindToTransaction(tx);\n\n const results: (Record<string, unknown> | null)[] = [];\n /** What each named operation wrote, for the `$ref`s after it. */\n const named = new Map<string, Record<string, unknown>>();\n\n for (let i = 0; i < operations.length; i++) {\n const operation = operations[i];\n try {\n const values = operation.values\n ? resolveBatchRefs(operation.values, named) as Partial<EntityValues<M>>\n : undefined;\n const rawId = operation.id !== undefined\n ? resolveBatchRefs(operation.id, named)\n : undefined;\n const id = rawId === undefined || rawId === null ? undefined : String(rawId);\n\n let row: Record<string, unknown> | null = null;\n switch (operation.op) {\n case \"create\":\n row = await txDriver.save<M>({\n path: operation.path,\n values: values ?? ({} as Partial<EntityValues<M>>),\n collection: operation.collection,\n status: \"new\"\n });\n break;\n case \"upsert\":\n row = await txDriver.save<M>({\n path: operation.path,\n values: values ?? ({} as Partial<EntityValues<M>>),\n collection: operation.collection,\n status: \"new\",\n upsert: true,\n onConflict: operation.onConflict\n });\n break;\n case \"update\": {\n // Read first, so an id matching no row is a 404\n // rather than an UPDATE that matches nothing and\n // reports success — the same rule `updateMany` and\n // the single-row route both apply.\n const existing = await txDriver.fetchOne({\n path: operation.path,\n id: id!,\n collection: operation.collection as CollectionConfig,\n // A restore is an update of a row the default\n // read hides; see `restoresSoftDeletedRow`.\n withDeleted: restoresSoftDeletedRow(\n (operation.collection as CollectionConfig | undefined) ?? this.registry?.getCollectionByPath(operation.path),\n values as Record<string, unknown> | undefined\n ) ? true : undefined\n });\n if (!existing) {\n throw Object.assign(new Error(`No row with id ${JSON.stringify(id)}`), {\n statusCode: 404,\n code: \"NOT_FOUND\"\n });\n }\n row = await txDriver.save<M>({\n path: operation.path,\n id: id!,\n values: values ?? ({} as Partial<EntityValues<M>>),\n collection: operation.collection,\n status: \"existing\"\n });\n break;\n }\n case \"delete\":\n // `delete` reads the row itself and 404s on a miss.\n await txDriver.delete<M>({\n row: { id: id!, path: operation.path },\n collection: operation.collection\n });\n row = null;\n break;\n }\n\n if (operation.ref && row) named.set(operation.ref, row);\n results.push(row);\n } catch (error) {\n // Which operation, as the bulk methods say which row: a\n // batch mixes collections, so \"the batch failed\" does not\n // even say which table to go and look at.\n throw Object.assign(\n new Error(\n `Operation ${i} of ${operations.length} (${operation.op} on \"${operation.path}\") failed: `\n + `${(error as Error)?.message ?? error}`,\n { cause: error }\n ),\n {\n statusCode: (error as { statusCode?: number })?.statusCode,\n code: (error as { code?: string })?.code,\n name: (error as Error)?.name\n }\n );\n }\n }\n\n return results;\n });\n }\n\n async delete<M extends Record<string, unknown>>(props: DeleteProps<M>): Promise<void> {\n // A frame per write, as `save` has: see `write-depth.ts`.\n return inWriteFrame(props.row.path, \"delete\", frame => this.deleteInFrame(props, frame));\n }\n\n private async deleteInFrame<M extends Record<string, unknown>>({\n row,\n collection,\n hard\n }: DeleteProps<M>, frame: WriteFrame): Promise<void> {\n\n const targetPath = row.path;\n\n // Resolve from backend registry to restore callbacks lost during WebSocket serialization\n const {\n collection: resolvedCollection,\n callbacks,\n globalCallbacks,\n propertyCallbacks\n } = this.resolveCollectionCallbacks(collection, targetPath);\n\n // The row being deleted is read here, from the database, by this\n // driver — which on a user request is bound to that user's\n // transaction, so the read sees what their policies let them see. It\n // is what the callbacks judge and what history records. It used to\n // come in on the props, and over the WebSocket the props were the\n // client's frame: the caller wrote the audit record of their own\n // deletion, and a `beforeDelete` refusing on a column was bypassed by\n // sending `values: {}`. The in-process SDK sent `{}` on every delete.\n //\n // The same read the REST route makes, `withDeleted` included: a hard\n // delete of a row already in the trash is how the trash is emptied.\n //\n // Raw, not through `this.fetchOne`: that runs `afterRead`, which shapes\n // what a caller reads — and its view (a masked `email`, a computed\n // field) is not the row being deleted. It was what the delete hooks\n // judged and what history recorded as the row's final state.\n //\n // And the row as REST serves it — columns, dates as timestamps, a\n // foreign key as a key — which is what a save hands the same hooks and\n // history. The admin's view-model walk wrapped every date as\n // `{ __type: \"date\" }` and every relation in a ref carrying the related\n // row, and that is what the delete's history entry held: a revert to\n // it handed the envelope to Postgres as the column's value.\n const stored = await this.dataService.fetchOneForRest<M>(\n targetPath,\n row.id,\n undefined,\n resolvedCollection?.databaseId,\n { withDeleted: hard ? true : undefined }\n );\n // Not found is answered before any callback runs: a callback handed a\n // row that is not there — or not there for this caller — would be\n // judging something it cannot see.\n if (!stored) {\n throw ApiError.notFound(`No row \"${row.id}\" in \"${targetPath}\" to delete.`);\n }\n // The callbacks' `row` is the row: its columns, nothing else. The address\n // travels beside it as `id`, so merging it in here only ever invented an\n // `id` field for tables that have no such column. A copy, so a callback\n // that edits it does not edit what history records.\n const targetRow: Record<string, unknown> = { ...stored };\n\n const contextForCallback = this.buildCallContext();\n\n // A `before*` callback is the application speaking, not the server\n // failing: a bare `throw` is the documented way to block a write, so it\n // answers 400 with the author's message rather than a masked 500.\n try {\n frame.stage = \"beforeDelete\";\n if (globalCallbacks?.beforeDelete || callbacks?.beforeDelete || propertyCallbacks?.beforeDelete) {\n const callbackCollection = requireCallbackCollection(resolvedCollection, targetPath);\n let preventDefault = false;\n // 1. Global callbacks first\n if (globalCallbacks?.beforeDelete) {\n const result = await globalCallbacks.beforeDelete({\n collection: callbackCollection,\n path: targetPath,\n id: row.id,\n row: targetRow,\n context: contextForCallback\n });\n if (result === false) {\n preventDefault = true;\n }\n }\n // 2. Collection callbacks second\n if (callbacks?.beforeDelete) {\n const result = await callbacks.beforeDelete({\n collection: callbackCollection,\n path: targetPath,\n id: row.id,\n row: targetRow,\n context: contextForCallback\n });\n if (result === false) {\n preventDefault = true;\n }\n }\n // 3. Property callbacks third\n if (propertyCallbacks?.beforeDelete) {\n const result = await propertyCallbacks.beforeDelete({\n collection: callbackCollection,\n path: targetPath,\n id: row.id,\n row: targetRow,\n context: contextForCallback\n });\n if (result === false) {\n preventDefault = true;\n }\n }\n if (preventDefault) {\n // A veto, not a silent no-op. Returning early left the route\n // answering `204 No Content` — \"the row is gone\" — for a row\n // that is still there, so the panel dropped it from the list\n // and the next reload brought it back. Same code a throw\n // produces, so one branch handles both.\n throw callbackRefusal(\"beforeDelete\", targetPath);\n }\n }\n } catch (callbackError) {\n throw toCallbackError(callbackError, \"beforeDelete\", targetPath);\n }\n\n // A soft delete is a delete as far as everything above this line is\n // concerned — `beforeDelete` can still veto it, `afterDelete` still\n // fires, the realtime event below still says the row is gone. What\n // changes is only how the table records it: a timestamp in the declared\n // field instead of a `DELETE`. `hard` opts back into the real thing and\n // needs no extra permission, because it is the same verb.\n //\n // Through a many-to-many path the target row is not what is deleted:\n // it is shared, and the delete removes this parent's link to it (the\n // persistence layer's rule for a junction). Its collection's soft\n // delete says how *its* rows are removed, which this is not — stamped\n // here, it trashed the tag for every post and re-asserted the link.\n const hop = isNestedPath(targetPath) ? resolveNestedPath(targetPath, this.registry) : undefined;\n const unlinking = hop !== undefined && isJunctionBackedRelation(hop.relation);\n const softDelete = hard || unlinking\n ? undefined\n : resolveSoftDelete(resolvedCollection as CollectionConfig | undefined);\n if (softDelete) {\n await this.dataService.save(\n targetPath,\n { [softDelete.field]: new Date() } as Partial<EntityValues<M>>,\n row.id,\n resolvedCollection?.databaseId\n );\n } else {\n await this.dataService.delete(\n targetPath,\n row.id,\n resolvedCollection?.databaseId\n );\n }\n\n // Same contract as `afterSave`: inside the transaction, awaited, and a\n // throw undoes the delete rather than leaving the row gone and the\n // cleanup half-done. See the comment on the `afterSave` block.\n try {\n frame.stage = \"afterDelete\";\n if (globalCallbacks?.afterDelete || callbacks?.afterDelete || propertyCallbacks?.afterDelete) {\n const callbackCollection = requireCallbackCollection(resolvedCollection, targetPath);\n // 1. Global callbacks first\n if (globalCallbacks?.afterDelete) {\n await globalCallbacks.afterDelete({\n collection: callbackCollection,\n path: targetPath,\n id: row.id,\n row: targetRow,\n context: contextForCallback\n });\n }\n // 2. Collection callbacks second\n if (callbacks?.afterDelete) {\n await callbacks.afterDelete({\n collection: callbackCollection,\n path: targetPath,\n id: row.id,\n row: targetRow,\n context: contextForCallback\n });\n }\n // 3. Property callbacks third\n if (propertyCallbacks?.afterDelete) {\n await propertyCallbacks.afterDelete({\n collection: callbackCollection,\n path: targetPath,\n id: row.id,\n row: targetRow,\n context: contextForCallback\n });\n }\n }\n } catch (callbackError) {\n throw toCallbackError(callbackError, \"afterDelete\", targetPath);\n }\n\n // Awaited, for the same reason the save's entry is: a delete is the one\n // change whose history nothing else can reconstruct, because the row it\n // describes is gone.\n // An unlink changed a parent's set, not the target row, so it is not\n // an entry in that row's history.\n if (this.historyService && resolvedCollection?.history && !unlinking) {\n await this.historyService.recordHistory({\n // Keyed like the save's entry: the slug, whatever the path.\n tableName: resolvedCollection.slug,\n id: row.id.toString(),\n action: \"delete\",\n values: stored,\n updatedBy: this.user?.uid\n });\n }\n\n // Notify real-time subscribers (deferred if inside a transaction)\n if (this._deferNotifications) {\n this._pendingNotifications.push({\n path: targetPath,\n id: row.id.toString(),\n row: null,\n databaseId: resolvedCollection?.databaseId\n });\n } else {\n await this.realtimeService.notifyUpdate(\n targetPath,\n row.id.toString(),\n null,\n resolvedCollection?.databaseId\n );\n }\n\n }\n\n async deleteAll(path: string): Promise<void> {\n await this.dataService.deleteAll(path);\n // Notify real-time subscribers of bulk change\n await this.realtimeService.notifyUpdate(path, \"*\", null);\n }\n\n async updateRelationPivot({ path, targetId, pivot }: UpdateRelationPivotProps): Promise<void> {\n await this.dataService.updateRelationPivot(path, targetId, pivot);\n // The junction row itself is what changed, and CDC on that table already\n // reaches the subscribers of `<parent>/<id>/<relation>` — the same route\n // a link or an unlink takes. Nothing extra to emit here; a second notify\n // would only duplicate the frame CDC is about to deliver.\n }\n\n async checkUniqueField(\n path: string,\n name: string,\n value: unknown,\n id?: string,\n collection?: CollectionConfig\n ): Promise<boolean> {\n return this.dataService.checkUniqueField(\n path,\n name,\n value,\n id,\n collection?.databaseId\n );\n }\n\n async count<M extends Record<string, unknown>>({\n path,\n collection,\n filter,\n logical,\n searchString,\n vectorSearch,\n withDeleted\n }: FetchCollectionProps<M>): Promise<number> {\n return this.dataService.count(\n path,\n {\n filter,\n // Counted as well as filtered, or `meta.total` describes a\n // different set of rows from the `data` beside it. The same\n // held for a `vectorSearch` carrying a `threshold`: it narrows\n // the fetch, so it has to narrow the count. And the same for\n // soft delete: a listing that hides four rows and a total that\n // counts them is a page saying \"1 of 5\".\n logical,\n searchString,\n vectorSearch,\n withDeleted\n }\n );\n }\n\n /** The answer to {@link appDatabaseName}, once it has been given. */\n private appDatabase?: Promise<string>;\n\n /**\n * The database this driver's own connection is on: the app's, which every\n * data request and the CMS read and write.\n *\n * Asked of the connection rather than read off the admin connection\n * string, which can name another database — the server's `postgres`, or\n * the main database while this process runs on a branch. The SQL console\n * preselects this name, a statement sent to it runs on this connection,\n * and branching copies it by default.\n */\n async appDatabaseName(): Promise<string> {\n this.appDatabase ??= this.db.execute(drizzleSql.raw(\"SELECT current_database() AS name\")).then((result) => {\n const name = result.rows?.[0]?.name;\n if (typeof name !== \"string\") {\n throw new Error(\"The database did not say which database this connection is on.\");\n }\n return name;\n });\n try {\n return await this.appDatabase;\n } catch (error: unknown) {\n // Asked again next time, rather than failing every later call.\n this.appDatabase = undefined;\n throw error;\n }\n }\n\n private async getTargetDb(databaseName?: string): Promise<DrizzleClient> {\n if (!databaseName || databaseName === await this.appDatabaseName()) {\n return this.db;\n }\n if (!this.poolManager) {\n throw new Error(\n \"Cross-database execution requires adminConnectionString to be configured in the backend.\"\n );\n }\n return this.poolManager.getDrizzle(databaseName);\n }\n\n /**\n * Build one statement, binding `$n` placeholders as real parameters.\n *\n * Shared by the role-switched path (inside a transaction) and the\n * unswitched one. They held byte-identical copies of this loop, which is\n * exactly the shape where a fix lands in one copy and not the other.\n */\n private buildStatement(sqlText: string, params?: unknown[]) {\n if (!params || params.length === 0) return drizzleSql.raw(sqlText);\n const parts = sqlText.split(/\\$(\\d+)/);\n const chunks: ReturnType<typeof drizzleSql.raw | typeof drizzleSql.param>[] = [];\n for (let i = 0; i < parts.length; i++) {\n if (i % 2 === 0) {\n if (parts[i].length > 0) chunks.push(drizzleSql.raw(parts[i]));\n } else {\n chunks.push(drizzleSql.param(params[Number(parts[i]) - 1]));\n }\n }\n return drizzleSql.join(chunks, drizzleSql.raw(\"\"));\n }\n\n async executeSql(sqlText: string, options?: {\n database?: string,\n role?: string,\n params?: unknown[],\n isolateSession?: boolean\n }): Promise<Record<string, unknown>[]> {\n if (options?.isolateSession) {\n return this.onIsolatedSession(\n await this.getTargetDb(options.database),\n options.role,\n (db, role) => this.executeSqlOn(db, sqlText, { ...options, role })\n );\n }\n if (!options?.database && !options?.role) {\n return this.dataService.executeSql(sqlText, options?.params);\n }\n return this.executeSqlOn(await this.getTargetDb(options?.database), sqlText, options);\n }\n\n /**\n * Run a script a person wrote — the Studio console — and describe its\n * result. See `SQLAdmin.runSqlScript` and `services/sql-script.ts`.\n */\n async runSqlScript(sqlText: string, options?: {\n database?: string,\n role?: string\n }): Promise<SqlScriptResult> {\n const targetDb = await this.getTargetDb(options?.database);\n const pool = \"$client\" in targetDb ? targetDb.$client : undefined;\n if (!(pool instanceof Pool)) {\n // One session in process: nothing on the wire to read a column's\n // origin from, so nothing in the result is said to have one.\n return sqlScriptResultFromRows(await this.executeSql(sqlText, {\n database: options?.database,\n role: options?.role,\n isolateSession: true\n }));\n }\n const role = options?.role;\n return runSqlScriptOnPool(pool, sqlText, {\n assumeRole: role ? (connection) => this.assumeSessionRole(connection, role) : undefined\n });\n }\n\n /**\n * `SET ROLE` for the rest of a session of a script's own — not `SET LOCAL`\n * inside a transaction, which a `COMMIT` in the script ends, and every\n * statement after it ran as the connection owner. The session is reset\n * after the script, so the role does not outlive it.\n *\n * The same refusals as {@link executeSqlOn}: a role the session already\n * has is no switch; `DISABLE_DB_ROLE_SWITCHING` runs as the owner on\n * purpose; and a connection that may not `SET ROLE` refuses, never falls\n * back to the owner.\n */\n private async assumeSessionRole(connection: PoolClient, role: string): Promise<void> {\n const current = await connection.query<{ role: string }>(\"SELECT current_user AS role\");\n if (current.rows[0]?.role === role) return;\n if (isRoleSwitchingOptedOut()) {\n logger.debug(\n `[PostgresBackendDriver] DISABLE_DB_ROLE_SWITCHING=true — running as the ` +\n `connection owner rather than \"${role}\".`\n );\n return;\n }\n if (this._roleSwitchingUnavailable) throw new RoleSwitchUnavailableError(role);\n try {\n await connection.query(`SET ROLE \"${role.replace(/\"/g, \"\\\"\\\"\")}\"`);\n } catch (roleError: unknown) {\n if (isRoleSwitchingPermissionError(roleError)) {\n this._roleSwitchingUnavailable = true;\n throw new RoleSwitchUnavailableError(role, roleError);\n }\n throw roleError;\n }\n }\n\n /**\n * Run `fn` on a session of its own, and put that session back the way it\n * was handed out before anything else can use it.\n *\n * For SQL a person wrote — the Studio editor. It may `SET ROLE`,\n * `SET SESSION AUTHORIZATION` or `set_config(…, false)`, which outlive the\n * statement (and a transaction's commit) and stay on the connection. On a\n * pooled one they were inherited by whatever checked it out next: an auth\n * lookup, the job store or history running as `rebase_user`, on one\n * connection in N, until a restart. `SET SESSION AUTHORIZATION DEFAULT`\n * clears the role as well, which `RESET ALL` does not.\n *\n * On a pool, `role` is assumed for the whole session, and a transaction\n * the SQL leaves open is rolled back and the run refused — see\n * `onOwnSession`. `fn` is then handed no role: there is nothing left to\n * switch. A handle that is already one session (PGlite in process, a\n * single client) is reset in place, and `fn` switches the role itself.\n */\n private async onIsolatedSession<T>(\n targetDb: DrizzleClient,\n role: string | undefined,\n fn: (db: DrizzleClient, role: string | undefined) => Promise<T>\n ): Promise<T> {\n const pool = \"$client\" in targetDb ? targetDb.$client : undefined;\n if (!(pool instanceof Pool)) {\n try {\n return await fn(targetDb, role);\n } finally {\n for (const statement of RESET_SESSION_STATEMENTS) {\n await targetDb.execute(drizzleSql.raw(statement)).catch((error: unknown) =>\n logger.error(\"[PostgresBackendDriver] Could not reset the session after caller SQL\", { error }));\n }\n }\n }\n return onOwnSession(\n pool,\n { assumeRole: role ? (connection) => this.assumeSessionRole(connection, role) : undefined },\n (connection) => fn(drizzle(connection), undefined)\n );\n }\n\n private async executeSqlOn(targetDb: DrizzleClient, sqlText: string, options?: {\n database?: string,\n role?: string,\n params?: unknown[]\n }): Promise<Record<string, unknown>[]> {\n try {\n // Does this actually need a role switch?\n //\n // Asking for the role the session already runs as is a no-op, not a\n // downgrade — the statement really does execute as the requested\n // role — so it stays allowed even where switching is unavailable.\n // That is the ordinary Studio path: the role picker defaults to\n // `current_user`.\n let needsRoleSwitch = false;\n if (options?.role) {\n try {\n const currentRoleResult = await targetDb.execute(drizzleSql.raw(\"SELECT current_user AS role\"));\n const currentRole = (currentRoleResult.rows?.[0] as Record<string, unknown>)?.role as string | undefined;\n needsRoleSwitch = !!currentRole && currentRole !== options.role;\n } catch {\n // Current role unknown. Assume a switch is needed rather\n // than assume the session already is the requested role:\n // attempting and refusing beats guessing in our own favour.\n needsRoleSwitch = true;\n }\n }\n\n if (needsRoleSwitch && options?.role) {\n if (isRoleSwitchingOptedOut()) {\n // The one sanctioned way to run this unswitched, and a\n // decision somebody made rather than a failure: the env var\n // is documented (README, docs/getting-started/configuration)\n // as \"run SQL Editor queries as the connection owner\", for\n // deployments whose application roles have no database role\n // behind them. `effectiveSqlRole` reads the same switch, so\n // the audit log records the role that actually applied.\n logger.debug(\n `[PostgresBackendDriver] DISABLE_DB_ROLE_SWITCHING=true — running as the ` +\n `connection owner rather than \"${options.role}\".`\n );\n } else if (this._roleSwitchingUnavailable) {\n // Already learned this connection cannot SET ROLE; refuse\n // without spending the round trip to be told again.\n throw new RoleSwitchUnavailableError(options.role);\n } else {\n const safeRole = options.role.replace(/\"/g, \"\\\"\\\"\");\n try {\n return await targetDb.transaction(async (tx) => {\n await tx.execute(drizzleSql.raw(`SET LOCAL ROLE \"${safeRole}\"`));\n const result = await tx.execute(this.buildStatement(sqlText, options?.params));\n return result.rows as Record<string, unknown>[];\n });\n } catch (roleError: unknown) {\n if (isRoleSwitchingPermissionError(roleError)) {\n // SECURITY: do NOT fall through and run this as the\n // owner.\n //\n // The caller asked for a *constrained* execution.\n // Owner rows are not a degraded answer to that\n // question, they are a confident wrong one: the only\n // reason to pass a role is to see what the database\n // looks like under RLS, and owner output makes a\n // protected table read as exposed. This used to warn\n // and continue — and latch, so a single failure\n // silently unscoped every later call in the process.\n //\n // `applyAuthContext` (the user request path) and\n // `scopeDataDriver` both fail closed. This is the\n // same question, so it gets the same answer.\n this._roleSwitchingUnavailable = true;\n throw new RoleSwitchUnavailableError(options.role, roleError);\n }\n throw roleError;\n }\n }\n }\n\n const result = await targetDb.execute(this.buildStatement(sqlText, options?.params));\n return result.rows as Record<string, unknown>[];\n } catch (error: unknown) {\n if (error instanceof RoleSwitchUnavailableError) throw error;\n const msg = error instanceof Error ? error.message : String(error);\n // Provide a user-friendly message for connection/auth errors\n if (msg.includes(\"pg_hba.conf\") || msg.includes(\"no encryption\") || msg.includes(\"connection refused\")) {\n const dbName = options?.database || \"unknown\";\n throw new Error(`Cannot connect to database \"${dbName}\": the server rejected the connection. This database may require SSL or is not accessible from this host.`);\n }\n throw error;\n }\n }\n\n async fetchAvailableDatabases(): Promise<string[]> {\n // Exclude template databases, Cloud SQL internal databases, and the default 'postgres' system db\n const result = await this.executeSql(\n `SELECT datname FROM pg_database \n WHERE datistemplate = false \n AND datname NOT IN ('postgres', 'cloudsqladmin', '_cloudsqladmin')\n ORDER BY datname;`\n );\n const databases = result.map((r: Record<string, unknown>) => r.datname as string);\n // Ensure the current connected database is always first in the list\n const currentDb = await this.appDatabaseName();\n if (!databases.includes(currentDb)) {\n databases.unshift(currentDb);\n } else {\n // Move it to the front\n const idx = databases.indexOf(currentDb);\n if (idx > 0) {\n databases.splice(idx, 1);\n databases.unshift(currentDb);\n }\n }\n return databases;\n }\n\n async fetchAvailableRoles(): Promise<string[]> {\n const result = await this.executeSql(\n \"SELECT rolname FROM pg_roles WHERE pg_has_role(current_user, rolname, 'member') ORDER BY rolname;\"\n );\n return result.map((r: Record<string, unknown>) => r.rolname as string);\n }\n\n /**\n * Application-level roles actually in use in this project.\n *\n * Distinct from {@link fetchAvailableRoles}, which returns native\n * PostgreSQL roles from `pg_roles` (`postgres`, `rebase_user`, …). Those\n * are the roles the SQL editor can `SET ROLE` to. *These* are the strings\n * held in the users table's `roles` column, injected per-transaction as\n * `rebase.roles()` and matched by `SecurityRule.roles`. Feeding the pg roles\n * into a `SecurityRule.roles` field produces a condition no user can ever\n * satisfy, so the two must not be conflated.\n *\n * Roles have no registry table — they were migrated out of\n * `rebase.user_roles` onto an inline `roles TEXT[]` column — so the live\n * set is derived from what is assigned. A role that is declared in a policy\n * but held by nobody yet cannot be discovered here; callers that need it\n * should union in the roles they already know about.\n */\n async fetchApplicationRoles(): Promise<string[]> {\n // The users table lives in `rebase` for a default (public) setup, but\n // follows the configured schema otherwise — locate it rather than\n // assuming. The `roles` ARRAY column is what makes it the auth table.\n const located = await this.executeSql(`\n SELECT table_schema, table_name\n FROM information_schema.columns\n WHERE column_name = 'roles'\n AND data_type = 'ARRAY'\n AND table_name = 'users'\n AND table_schema NOT IN ('information_schema', 'pg_catalog')\n ORDER BY (table_schema = 'rebase') DESC, table_schema\n LIMIT 1;\n `);\n if (located.length === 0) return [];\n\n const schema = located[0].table_schema as string;\n const table = located[0].table_name as string;\n // Identifiers come from information_schema, not user input, but they\n // are still interpolated — quote them so odd-but-legal names survive.\n const qualified = `\"${schema.replace(/\"/g, \"\\\"\\\"\")}\".\"${table.replace(/\"/g, \"\\\"\\\"\")}\"`;\n\n const rows = await this.executeSql(`\n SELECT DISTINCT unnest(roles) AS role\n FROM ${qualified}\n WHERE roles IS NOT NULL\n ORDER BY role;\n `);\n return rows\n .map((r) => r.role as string)\n .filter((r): r is string => typeof r === \"string\" && r.length > 0);\n }\n\n async fetchCurrentDatabase(): Promise<string | undefined> {\n return this.appDatabaseName();\n }\n\n /**\n * Fetch public tables that are not yet mapped to a collection.\n * Excludes internal tables (_rebase_*, _auth_*, auth tables, etc.)\n * and junction/connection tables used for many-to-many relations.\n */\n async fetchUnmappedTables(mappedPaths?: string[]): Promise<string[]> {\n const result = await this.executeSql(`\n SELECT table_name\n FROM information_schema.tables\n WHERE table_schema = 'public'\n AND table_type = 'BASE TABLE'\n ORDER BY table_name;\n `);\n\n const allTables = result\n .map((r: Record<string, unknown>) => r.table_name as string)\n .filter((name: string) => classifyTable(name, \"public\") !== \"rebase-internal\");\n\n // Detect junction tables: tables where every column is part of a foreign key.\n // These are typically many-to-many connection tables and shouldn't be suggested.\n let junctionTables = new Set<string>();\n try {\n junctionTables = await detectJunctionTables(this.executeSql.bind(this));\n } catch (e) {\n logger.warn(\"Could not detect junction tables\", { error: e });\n }\n\n const filteredTables = allTables.filter(name => !junctionTables.has(name));\n\n if (!mappedPaths || mappedPaths.length === 0) return filteredTables;\n\n const mappedSet = new Set(mappedPaths.map(p => p.toLowerCase()));\n return filteredTables.filter((name: string) => !mappedSet.has(name.toLowerCase()));\n }\n\n /**\n * Fetch metadata for a given table from information_schema (columns, policies, constraints).\n */\n async fetchTableMetadata(tableName: string): Promise<TableMetadata> {\n // Sanitize table name as defense-in-depth (parameterized below)\n const safeName = tableName.replace(/[^a-zA-Z0-9_]/g, \"\");\n\n // 1. Fetch Columns\n const result = await this.db.execute(drizzleSql`\n SELECT column_name, data_type, udt_name, is_nullable, column_default, character_maximum_length\n FROM information_schema.columns\n WHERE table_schema = 'public'\n AND table_name = ${safeName}\n ORDER BY ordinal_position\n `);\n const columns = result.rows as Record<string, unknown>[];\n\n // Also fetch enum values for any USER-DEFINED columns\n const enumColumns = columns.filter((c) => c.data_type === \"USER-DEFINED\");\n if (enumColumns.length > 0) {\n for (const col of enumColumns) {\n try {\n const enumResult = await this.db.execute(drizzleSql`\n SELECT e.enumlabel\n FROM pg_type t\n JOIN pg_enum e ON t.oid = e.enumtypid\n WHERE t.typname = ${col.udt_name as string}\n ORDER BY e.enumsortorder\n `);\n col.enum_values = (enumResult.rows as Record<string, unknown>[]).map(e => e.enumlabel);\n } catch {\n col.enum_values = [];\n }\n }\n }\n // Rows from hand-written SQL. The element type is the one claim nothing\n // at this layer can check — no column list is available to check it\n // against — but the shape is `sqlRows`' job, not an assertion's.\n const typedColumns = sqlRows<TableColumnInfo>(columns);\n\n // 2. Fetch Foreign Keys\n const fkResult = await this.db.execute(drizzleSql`\n SELECT\n kcu.column_name as column_name,\n ccu.table_name AS foreign_table_name,\n ccu.column_name AS foreign_column_name\n FROM \n information_schema.table_constraints AS tc \n JOIN information_schema.key_column_usage AS kcu\n ON tc.constraint_name = kcu.constraint_name\n AND tc.table_schema = kcu.table_schema\n JOIN information_schema.constraint_column_usage AS ccu\n ON ccu.constraint_name = tc.constraint_name\n AND ccu.table_schema = tc.table_schema\n WHERE tc.constraint_type = 'FOREIGN KEY' AND tc.table_name = ${safeName};\n `);\n // SAFETY: Raw SQL result rows match TableForeignKeyInfo shape from the SELECT aliases\n const foreignKeys = fkResult.rows as TableForeignKeyInfo[];\n\n // 3. Fetch Junction Tables (Many-to-Many)\n // A simple junction table is one that has foreign keys to our table and other tables\n const junctionsResult = await this.db.execute(drizzleSql`\n SELECT \n tc1.table_name as junction_table_name,\n kcu1.column_name as source_column_name,\n ccu2.table_name as target_table_name,\n kcu2.column_name as target_column_name\n FROM information_schema.table_constraints tc1\n JOIN information_schema.key_column_usage kcu1 ON tc1.constraint_name = kcu1.constraint_name\n JOIN information_schema.constraint_column_usage ccu1 ON ccu1.constraint_name = tc1.constraint_name\n JOIN information_schema.table_constraints tc2 ON tc1.table_name = tc2.table_name AND tc2.constraint_type = 'FOREIGN KEY'\n JOIN information_schema.key_column_usage kcu2 ON tc2.constraint_name = kcu2.constraint_name\n JOIN information_schema.constraint_column_usage ccu2 ON ccu2.constraint_name = tc2.constraint_name\n WHERE tc1.constraint_type = 'FOREIGN KEY' \n AND ccu1.table_name = ${safeName}\n AND ccu2.table_name != ${safeName};\n `);\n // SAFETY: Raw SQL result rows match TableJunctionInfo shape from the SELECT aliases\n const junctions = junctionsResult.rows as TableJunctionInfo[];\n\n // 4. Fetch RLS Policies\n //\n // From `pg_policies`, not `pg_policy`: the importer writes each row\n // into a security rule, and needs the command named (`pg_policy.polcmd`\n // is `r`, `a`, `*`), `TO public` spelled `public` (the raw oid list\n // casts it to `-`) and whether the policy is restrictive, which a\n // rule read back as permissive would OR with every grant beside it.\n const policiesResult = await this.db.execute(drizzleSql`\n SELECT\n policyname AS policy_name,\n permissive,\n cmd,\n roles::text[] AS roles,\n qual,\n with_check\n FROM pg_policies\n WHERE schemaname = 'public' AND tablename = ${safeName};\n `);\n // SAFETY: Raw SQL result rows match TablePolicyInfo shape from the SELECT aliases\n const policies = policiesResult.rows as TablePolicyInfo[];\n\n return {\n columns: typedColumns,\n foreignKeys,\n junctions,\n policies\n };\n }\n\n private generateSubscriptionId(): string {\n return `sub_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;\n }\n\n /**\n * Create a new delegate instance with authenticated context.\n * Starts a transaction and sets the current_user_id and current_user_roles\n * configuration parameters for PostgreSQL Row Level Security.\n */\n async withAuth(user: User): Promise<DataDriver> {\n return new AuthenticatedPostgresBackendDriver(this, user);\n }\n}\n\nexport class AuthenticatedPostgresBackendDriver implements DataDriver {\n key = \"postgres\";\n initialised = true;\n\n public user: User;\n public data: RebaseSdkData;\n\n constructor(\n public delegate: PostgresBackendDriver,\n user: User\n ) {\n this.user = user;\n this.data = buildSdkData(this);\n\n // Delegate admin ops to the base driver (no RLS wrapping for admin)\n this.admin = delegate.admin;\n }\n\n /**\n * Typed admin capabilities — delegates to the base driver.\n */\n admin: DatabaseAdmin;\n\n get restFetchService(): RestFetchService {\n return {\n // The base driver's restFetchService already applies the afterRead\n // pipeline, so we only wrap it in the authenticated transaction here.\n fetchCollectionForRest: async (collectionPath, options, include) => {\n return this.withTransaction(async (delegate) => {\n return delegate.restFetchService.fetchCollectionForRest(collectionPath, options, include);\n }, { accessMode: \"read only\" });\n },\n fetchOneForRest: async (collectionPath, id, include, databaseId, options) => {\n return this.withTransaction(async (delegate) => {\n return delegate.restFetchService.fetchOneForRest(collectionPath, id, include, databaseId, options);\n }, { accessMode: \"read only\" });\n },\n // In the same read-only transaction as the two above, which is what\n // sets the RLS GUCs and drops to the restricted role. That is not a\n // detail to leave to the base implementation: an aggregate is an\n // efficient way to learn about rows the caller cannot select, and\n // `count(*)` over a table whose policies return nothing has to be\n // zero rather than the true row count.\n aggregate: async (collectionPath, options) => {\n return this.withTransaction(async (delegate) => {\n return delegate.restFetchService.aggregate!(collectionPath, options);\n }, { accessMode: \"read only\" });\n },\n // Not in a transaction, unlike its neighbours: it reads no rows. It\n // derives a cursor from a row the caller has already been served,\n // so there is nothing here for RLS to scope.\n cursorFor: (collectionPath, row, orderBy) =>\n this.delegate.restFetchService.cursorFor?.(collectionPath, row, orderBy)\n };\n }\n\n private async withTransaction<T>(\n operation: (delegate: PostgresBackendDriver) => Promise<T>,\n options?: {\n accessMode?: \"read only\" | \"read write\";\n isolationLevel?: \"read uncommitted\" | \"read committed\" | \"repeatable read\" | \"serializable\"\n }\n ): Promise<T> {\n const pendingNotifications: PostgresBackendDriver[\"_pendingNotifications\"] = [];\n\n // What a write's callbacks hand off — a job, a history entry, a\n // webhook — commits with the write or not at all, and is found through\n // this scope by code that is not handed the transaction. Writes only:\n // a read has nothing to roll back, and inside one the scope is\n // cleared, so a read nested in a write's callback is not taken for it.\n // See `write-transaction-scope.ts`.\n const writeScope = options?.accessMode === \"read only\" ? undefined : new WriteTransactionScope();\n // What a hook deferred to the end of this write (`afterSaveError`) is\n // handed: this caller, on this driver, where every call is a\n // transaction of its own — the write's is over by then.\n const client = this.delegate.client;\n writeScope?.setSettledContext({\n user: this.user,\n driver: this,\n data: this.data,\n get client() {\n return requireCallbackClient(client);\n },\n storageSource: client?.storage as StorageSource\n });\n\n // The same identity the transaction is about to hand Postgres, made\n // available to the row walk so per-field `access.read` is applied to\n // whatever this read serves. Established here rather than threaded\n // through `DataService` → `FetchService` → the pipeline for the reason\n // set out in `field-viewer.ts`: those layers carry no user, and every\n // exit from the pipeline needs it. `run` and not `enterWith`, so a\n // `dataAsAdmin` read nested inside a user request restores the user's\n // viewer when it returns.\n const result = await withFieldViewer({ roles: this.user?.roles ?? [] }, async () =>\n await runInWriteScope(writeScope, () => this.delegate.db.transaction(async (tx) => {\n if (!this.user?.uid) {\n logger.warn(\"[DataDriver] User ID (uid) is missing for authenticated delegate. Using 'anonymous'. User object\", { detail: this.user });\n }\n if (!this.user?.roles) {\n logger.warn(\"[DataDriver] User roles are missing for authenticated delegate. Using empty array. User object\", { detail: this.user });\n }\n\n // Set the RLS GUCs and downgrade to the restricted user role so RLS\n // binds every statement in this transaction — reads AND writes.\n // This is user context: the collection's securityRules are the\n // authorization model. The BASE driver never reaches here, so it\n // stays on the owner connection and bypasses RLS — but note that\n // `rebase.dataAsAdmin` is NOT the base driver: `init.ts` scopes it\n // with `withAuth(SERVICE_IDENTITY)`, so it arrives here like any\n // other user, with uid 'service' and the admin role, and its\n // statements are RLS-evaluated. The comment used to list it as a\n // bypass and five docblocks followed. The GUCs are transaction-local and\n // remain readable after the role switch, so `rebase.uid()` /\n // `rebase.roles()` in policies still resolve.\n //\n // Fails closed: if the switch cannot be performed, the transaction\n // aborts rather than falling back to an RLS-bypassing connection.\n // `isAnonymous` rides along so a policy can tell a GUEST from an\n // account. Anonymous sign-in mints a real user row and a real uid,\n // so without it the two are the same principal inside the database\n // and every rule meaning \"signed in\" also means \"anybody who called\n // POST /auth/anonymous\". See `rebase.is_anonymous()`. The\n // token's custom claims reach the database as `rebase.jwt()`,\n // which is what a tenancy policy reads. The same principal a\n // listener on this driver subscribes as — see `authContextOf`.\n await applyAuthContext(tx, authContextOf(this.user), this.delegate.rlsUserRole);\n\n // Bound to `tx`, so a `beforeQuery` on this request sees the\n // caller and reads through the same RLS-scoped transaction the\n // rows come from — the correction `callContextWithin` was\n // written for, applied to the hook as well as to `afterRead`.\n // Built at most once per request, and only for a collection\n // that declares a hook.\n let hookContext: RebaseCallContext | undefined;\n const txEntityService = new DataService(\n tx, this.delegate.registry,\n () => (hookContext ??= this.delegate.callContextWithin(tx, this.user))\n );\n const txDelegate = new PostgresBackendDriver(tx, this.delegate.realtimeService, this.delegate.registry, this.user, this.delegate.poolManager, this.delegate.historyService);\n\n txDelegate.dataService = txEntityService;\n txDelegate._deferNotifications = true;\n txDelegate._pendingNotifications = pendingNotifications;\n txDelegate.client = this.delegate.client;\n\n writeScope?.bind(tx, (sqlText, params) => txEntityService.executeSql(sqlText, params));\n let out: T;\n try {\n out = await operation(txDelegate);\n } catch (error) {\n // Closed before the rethrow, so before the ROLLBACK is\n // queued: a statement accepted after it would run on this\n // connection in autocommit, and commit.\n writeScope?.close();\n throw error;\n }\n // Last, inside the transaction: whatever the callbacks started\n // on it finishes before the commit rather than after it.\n await writeScope?.settle();\n // A failure a callback caught has still aborted the\n // transaction, and its COMMIT would be a ROLLBACK that\n // reports success. Refused here, so the caller is told.\n await writeScope?.assertCommittable();\n return out;\n }, options)).finally(() => writeScope?.close())\n ).catch(async (error: unknown) => {\n await writeScope?.settled();\n throw error;\n });\n\n writeScope?.committed();\n await writeScope?.settled();\n\n for (const notification of pendingNotifications) {\n try {\n await this.delegate.realtimeService.notifyUpdate(\n notification.path,\n notification.id,\n notification.row,\n notification.databaseId\n );\n } catch (e) {\n logger.error(\"[DataDriver] Error flushing deferred notification\", { error: e });\n }\n }\n\n return result;\n }\n\n async fetchCollection<M extends Record<string, unknown>>(props: FetchCollectionProps<M>): Promise<Record<string, unknown>[]> {\n return this.withTransaction((delegate) => delegate.fetchCollection(props), { accessMode: \"read only\" });\n }\n\n /**\n * The delegate is the base driver, on the owner connection. The user's\n * identity goes in with the subscription, so the first read runs as them\n * like every refetch after it. It used to be stamped onto the last\n * registered subscription once the delegate had returned — by which time\n * the first read had been issued unscoped, and a listener was handed rows\n * its policies deny.\n */\n listenCollection<M extends Record<string, unknown>>(props: ListenCollectionProps<M>): () => void {\n return this.delegate.listenCollection(props, authContextOf(this.user));\n }\n\n async fetchOne<M extends Record<string, unknown>>(props: FetchOneProps<M>): Promise<Record<string, unknown> | undefined> {\n return this.withTransaction((delegate) => delegate.fetchOne(props), { accessMode: \"read only\" });\n }\n\n listenOne<M extends Record<string, unknown>>(props: ListenOneProps<M>): () => void {\n return this.delegate.listenOne(props, authContextOf(this.user));\n }\n\n async save<M extends Record<string, unknown>>(props: SaveProps<M>): Promise<Record<string, unknown>> {\n return this.withTransaction((delegate) => delegate.save(props));\n }\n\n /**\n * One transaction for the whole batch, rather than one per row.\n *\n * This is the point of the method: `save` opens a transaction per call, so\n * importing 10k rows through it means 10k transactions (and, over HTTP, 10k\n * round trips). Here the RLS context is established once and every row lands\n * or none does. Realtime notifications are already deferred to commit by\n * `withTransaction`, so a batch does not flood subscribers mid-flight.\n */\n async saveMany<M extends Record<string, unknown>>(props: SaveManyProps<M>): Promise<Record<string, unknown>[]> {\n return this.withTransaction((delegate) => delegate.saveMany(props));\n }\n\n /**\n * Present for the same reason `saveMany` is, and absent until now.\n *\n * Every request that reaches the REST layer is served by *this* class — the\n * base driver never sees one — and the routes ask `if (!driver.updateMany)`\n * before doing anything. So `PATCH /api/data/<c>/bulk` and\n * `POST /api/data/<c>/bulk/delete` answered `BULK_UNSUPPORTED` on Postgres,\n * the one backend that implements them, for every authenticated caller.\n * The methods existed one class down and nothing forwarded to them.\n */\n async updateMany<M extends Record<string, unknown>>(props: UpdateManyProps<M>): Promise<Record<string, unknown>[]> {\n return this.withTransaction((delegate) => delegate.updateMany(props));\n }\n\n async deleteMany<M extends Record<string, unknown>>(props: DeleteManyProps<M>): Promise<void> {\n return this.withTransaction((delegate) => delegate.deleteMany(props));\n }\n\n /**\n * One transaction, one RLS context, every collection the batch touches.\n *\n * The whole point is that it runs as the caller: a batch that dropped to\n * the base driver would write across collections with row-level security\n * switched off, which is the opposite of what a cross-collection write\n * needs.\n */\n async batchWrite<M extends Record<string, unknown>>(\n props: BatchWriteProps<M>\n ): Promise<(Record<string, unknown> | null)[]> {\n return this.withTransaction((delegate) => delegate.batchWrite(props));\n }\n\n async delete<M extends Record<string, unknown>>(props: DeleteProps<M>): Promise<void> {\n return this.withTransaction((delegate) => delegate.delete(props));\n }\n\n async deleteAll(path: string): Promise<void> {\n return this.withTransaction((delegate) => delegate.deleteAll(path));\n }\n\n async updateRelationPivot(props: UpdateRelationPivotProps): Promise<void> {\n return this.withTransaction((delegate) => delegate.updateRelationPivot(props));\n }\n\n async checkUniqueField(\n path: string,\n name: string,\n value: unknown,\n id?: string,\n collection?: CollectionConfig\n ): Promise<boolean> {\n return this.withTransaction((delegate) => delegate.checkUniqueField(path, name, value, id, collection), { accessMode: \"read only\" });\n }\n\n async count<M extends Record<string, unknown>>(props: FetchCollectionProps<M>): Promise<number> {\n return this.withTransaction((delegate) => delegate.count(props), { accessMode: \"read only\" });\n }\n\n}\n","import { pgSchema, pgTable, uuid, timestamp, boolean, jsonb, text, unique, index, integer, bigint } from \"drizzle-orm/pg-core\";\nimport { relations } from \"drizzle-orm\";\n\n/**\n * Factory function to dynamically create the auth tables bound to the specified schema names.\n *\n * This module builds queries; it does not create tables. `ensureAuthTablesExist`\n * owns the DDL, which makes everything here a *claim* about a database it cannot\n * enforce — and the claims drifted. Every column below was declared\n * `varchar(n)` while the DDL created it as `TEXT`: `user_agent` as varchar(500),\n * `ip_address` as varchar(45), `secret_encrypted` as varchar(500), every\n * `token_hash` as varchar(255). None of it was true of any database this\n * framework ever provisioned. Harmless at runtime — drizzle does not enforce a\n * length client-side, so the widths only ever misled the next reader — but a\n * schema module that describes columns that do not exist is worse than no\n * schema module. They are `text` here now because they are TEXT there.\n */\nexport function createAuthSchema(usersSchemaName = \"rebase\") {\n const usersSchema = usersSchemaName === \"public\" ? null : pgSchema(usersSchemaName);\n\n const tableCreator = (usersSchema ? usersSchema.table.bind(usersSchema) : pgTable) as typeof pgTable;\n const usersTableCreator = tableCreator;\n\n /**\n * Users table - stores both email/password and OAuth users\n */\n const users = usersTableCreator(\"users\", {\n id: uuid(\"id\").defaultRandom().primaryKey(),\n email: text(\"email\").notNull().unique(),\n passwordHash: text(\"password_hash\"), // NULL for OAuth-only users\n displayName: text(\"display_name\"),\n photoUrl: text(\"photo_url\"),\n emailVerified: boolean(\"email_verified\").default(false).notNull(),\n emailVerificationToken: text(\"email_verification_token\"),\n emailVerificationSentAt: timestamp(\"email_verification_sent_at\"),\n isAnonymous: boolean(\"is_anonymous\").default(false).notNull(),\n roles: text(\"roles\").array().default([]).notNull(),\n metadata: jsonb(\"metadata\").$type<Record<string, unknown>>().default({}).notNull(),\n /**\n * Sessions that began before this instant are dead, whatever tokens\n * they still hold. Password resets and admin revocations stamp it.\n *\n * Deleting the user's refresh-token rows (which we also do) is not\n * sufficient on its own: a request already in flight can insert a\n * freshly rotated row microseconds after the delete and survive it.\n * This timestamp cannot be outrun that way — it is checked against\n * `refresh_tokens.session_started_at`, which rotation carries forward.\n */\n tokensValidAfter: timestamp(\"tokens_valid_after\"),\n /** When an administrator switched the account off; NULL while it is on. */\n disabledAt: timestamp(\"disabled_at\", { withTimezone: true }),\n /**\n * A self-service address change waiting for its link: the address\n * the account is moving to, the hash of the token mailed there, and\n * when it was mailed. The address is not the account's, and is not\n * reserved, until the link is followed.\n */\n pendingEmail: text(\"pending_email\"),\n emailChangeToken: text(\"email_change_token\"),\n emailChangeSentAt: timestamp(\"email_change_sent_at\", { withTimezone: true }),\n createdAt: timestamp(\"created_at\").defaultNow().notNull(),\n updatedAt: timestamp(\"updated_at\").defaultNow().notNull()\n });\n\n\n /**\n * Refresh tokens for long-lived sessions.\n *\n * A row is one token, not one device. Every token minted from the same\n * sign-in shares a `sessionId`, and rotation ADDS a row rather than\n * replacing one: the superseded token stays on file, flagged `revoked`\n * with a `rotatedAt` stamp. That record is what lets the refresh endpoint\n * tell a client replaying a token it never got an answer for (a response\n * lost to a redeploy, a second tab racing on boot) apart from a stranger\n * presenting a token that was never issued. Deleting the old row on sight\n * — the previous behaviour — made those two cases indistinguishable, and\n * the legitimate one is overwhelmingly the common one.\n *\n * There is deliberately NO unique constraint on (uid, user_agent,\n * ip_address). Keying a session on the IP meant one row per \"device\",\n * so a second browser profile behind the same NAT silently evicted the\n * first, and a phone changing networks orphaned a row on every hop.\n * User agent and IP are descriptive metadata for the sessions list;\n * `sessionId` is the identity.\n */\n const refreshTokens = tableCreator(\"refresh_tokens\", {\n id: uuid(\"id\").defaultRandom().primaryKey(),\n uid: uuid(\"uid\").notNull().references(() => users.id, { onDelete: \"cascade\" }),\n sessionId: uuid(\"session_id\").defaultRandom().notNull(),\n tokenHash: text(\"token_hash\").notNull().unique(),\n expiresAt: timestamp(\"expires_at\").notNull(),\n revoked: boolean(\"revoked\").default(false).notNull(),\n rotatedAt: timestamp(\"rotated_at\"),\n /**\n * When the sign-in this token descends from happened — carried across\n * every rotation, unlike `createdAt`. `users.tokensValidAfter` is\n * compared against this, so a revocation cannot be outrun by a token\n * that rotates immediately after it.\n */\n sessionStartedAt: timestamp(\"session_started_at\").defaultNow().notNull(),\n /**\n * The assurance level the sign-in was established at — `aal2` only\n * where a second factor was actually presented. Carried across\n * rotations, because refresh is not a new authentication and has\n * nothing else to read the level from.\n */\n aal: text(\"aal\"),\n /**\n * How the sign-in happened — `password`, `anonymous`, `magic-link`,\n * `otp`, `mfa` or a provider id — and what `providerId` says for the\n * session. Carried across rotations like `aal`.\n */\n method: text(\"method\"),\n userAgent: text(\"user_agent\"),\n ipAddress: text(\"ip_address\"),\n createdAt: timestamp(\"created_at\").defaultNow().notNull()\n }, (table) => ({\n sessionIdx: index(\"idx_refresh_tokens_session\").on(table.sessionId)\n }));\n\n /**\n * Password reset tokens for forgot password flow\n */\n const passwordResetTokens = tableCreator(\"password_reset_tokens\", {\n id: uuid(\"id\").defaultRandom().primaryKey(),\n uid: uuid(\"uid\").notNull().references(() => users.id, { onDelete: \"cascade\" }),\n tokenHash: text(\"token_hash\").notNull().unique(),\n expiresAt: timestamp(\"expires_at\").notNull(),\n usedAt: timestamp(\"used_at\"),\n createdAt: timestamp(\"created_at\").defaultNow().notNull()\n });\n\n /**\n * App config - key/value store for custom settings\n */\n const appConfig = tableCreator(\"app_config\", {\n key: text(\"key\").primaryKey(),\n value: jsonb(\"value\").notNull(),\n updatedAt: timestamp(\"updated_at\").defaultNow().notNull()\n });\n\n /**\n * User identities - maps external OAuth profiles back to local users\n */\n const userIdentities = tableCreator(\"user_identities\", {\n id: uuid(\"id\").defaultRandom().primaryKey(),\n uid: uuid(\"uid\").notNull().references(() => users.id, { onDelete: \"cascade\" }),\n provider: text(\"provider\").notNull(), // e.g. 'google', 'linkedin'\n providerId: text(\"provider_id\").notNull(),\n profileData: jsonb(\"profile_data\"),\n createdAt: timestamp(\"created_at\").defaultNow().notNull(),\n updatedAt: timestamp(\"updated_at\").defaultNow().notNull()\n }, (table) => ({\n uniqueProviderId: unique(\"unique_provider_id\").on(table.provider, table.providerId)\n }));\n\n /**\n * MFA factors table - stores enrolled MFA methods\n */\n const mfaFactors = tableCreator(\"mfa_factors\", {\n id: uuid(\"id\").defaultRandom().primaryKey(),\n uid: uuid(\"uid\").notNull().references(() => users.id, { onDelete: \"cascade\" }),\n factorType: text(\"factor_type\").notNull(), // 'totp'\n secretEncrypted: text(\"secret_encrypted\").notNull(),\n friendlyName: text(\"friendly_name\"),\n verified: boolean(\"verified\").default(false).notNull(),\n /**\n * The highest TOTP time step ever accepted for this factor. RFC 6238\n * §5.2 forbids accepting an OTP twice, and the ±1 step window that\n * exists for clock drift is also a 90-second replay window: without\n * this, one observed code buys a fresh session for a minute and a half.\n */\n lastUsedCounter: bigint(\"last_used_counter\", { mode: \"number\" }),\n createdAt: timestamp(\"created_at\").defaultNow().notNull(),\n updatedAt: timestamp(\"updated_at\").defaultNow().notNull()\n });\n\n /**\n * MFA challenges table - tracks active MFA verification attempts\n */\n const mfaChallenges = tableCreator(\"mfa_challenges\", {\n id: uuid(\"id\").defaultRandom().primaryKey(),\n factorId: uuid(\"factor_id\").notNull().references(() => mfaFactors.id, { onDelete: \"cascade\" }),\n createdAt: timestamp(\"created_at\").defaultNow().notNull(),\n verifiedAt: timestamp(\"verified_at\"),\n ipAddress: text(\"ip_address\"),\n /** Failed guesses recorded against this challenge; bounded by the route. */\n attempts: integer(\"attempts\").default(0).notNull(),\n expiresAt: timestamp(\"expires_at\").notNull()\n });\n\n /**\n * Recovery codes table - backup codes for MFA\n */\n const recoveryCodes = tableCreator(\"recovery_codes\", {\n id: uuid(\"id\").defaultRandom().primaryKey(),\n uid: uuid(\"uid\").notNull().references(() => users.id, { onDelete: \"cascade\" }),\n codeHash: text(\"code_hash\").notNull(),\n usedAt: timestamp(\"used_at\"),\n createdAt: timestamp(\"created_at\").defaultNow().notNull()\n });\n\n /**\n * Magic link tokens for passwordless email login\n */\n const magicLinkTokens = tableCreator(\"magic_link_tokens\", {\n id: uuid(\"id\").defaultRandom().primaryKey(),\n uid: uuid(\"uid\").notNull().references(() => users.id, { onDelete: \"cascade\" }),\n tokenHash: text(\"token_hash\").notNull().unique(),\n expiresAt: timestamp(\"expires_at\").notNull(),\n usedAt: timestamp(\"used_at\"),\n createdAt: timestamp(\"created_at\").defaultNow().notNull()\n });\n\n return {\n usersSchema,\n users,\n refreshTokens,\n passwordResetTokens,\n appConfig,\n userIdentities,\n mfaFactors,\n mfaChallenges,\n recoveryCodes,\n magicLinkTokens\n };\n}\n\n// Instantiate default schema and tables using the default \"rebase\" schema\nconst defaultAuthSchema = createAuthSchema(\"rebase\");\n\nexport const usersSchema = defaultAuthSchema.usersSchema;\n\nexport const users = defaultAuthSchema.users;\nexport const refreshTokens = defaultAuthSchema.refreshTokens;\nexport const passwordResetTokens = defaultAuthSchema.passwordResetTokens;\nexport const appConfig = defaultAuthSchema.appConfig;\nexport const userIdentities = defaultAuthSchema.userIdentities;\nexport const mfaFactors = defaultAuthSchema.mfaFactors;\nexport const mfaChallenges = defaultAuthSchema.mfaChallenges;\nexport const recoveryCodes = defaultAuthSchema.recoveryCodes;\nexport const magicLinkTokens = defaultAuthSchema.magicLinkTokens;\n\n// Relations\nexport const usersRelations = relations(users, ({ many }) => ({\n refreshTokens: many(refreshTokens),\n passwordResetTokens: many(passwordResetTokens),\n userIdentities: many(userIdentities),\n mfaFactors: many(mfaFactors),\n recoveryCodes: many(recoveryCodes),\n magicLinkTokens: many(magicLinkTokens)\n}));\n\nexport const refreshTokensRelations = relations(refreshTokens, ({ one }) => ({\n user: one(users, {\n fields: [refreshTokens.uid],\n references: [users.id]\n })\n}));\n\nexport const passwordResetTokensRelations = relations(passwordResetTokens, ({ one }) => ({\n user: one(users, {\n fields: [passwordResetTokens.uid],\n references: [users.id]\n })\n}));\n\nexport const userIdentitiesRelations = relations(userIdentities, ({ one }) => ({\n user: one(users, {\n fields: [userIdentities.uid],\n references: [users.id]\n })\n}));\n\nexport const mfaFactorsRelations = relations(mfaFactors, ({ one, many }) => ({\n user: one(users, {\n fields: [mfaFactors.uid],\n references: [users.id]\n }),\n challenges: many(mfaChallenges)\n}));\n\nexport const mfaChallengesRelations = relations(mfaChallenges, ({ one }) => ({\n factor: one(mfaFactors, {\n fields: [mfaChallenges.factorId],\n references: [mfaFactors.id]\n })\n}));\n\nexport const recoveryCodesRelations = relations(recoveryCodes, ({ one }) => ({\n user: one(users, {\n fields: [recoveryCodes.uid],\n references: [users.id]\n })\n}));\n\nexport const magicLinkTokensRelations = relations(magicLinkTokens, ({ one }) => ({\n user: one(users, {\n fields: [magicLinkTokens.uid],\n references: [users.id]\n })\n}));\n\n// Type exports\nexport type User = typeof users.$inferSelect;\nexport type NewUser = typeof users.$inferInsert;\nexport type RefreshToken = typeof refreshTokens.$inferSelect;\nexport type PasswordResetToken = typeof passwordResetTokens.$inferSelect;\nexport type AppConfig = typeof appConfig.$inferSelect;\nexport type UserIdentity = typeof userIdentities.$inferSelect;\nexport type NewUserIdentity = typeof userIdentities.$inferInsert;\nexport type MfaFactorRow = typeof mfaFactors.$inferSelect;\nexport type MfaChallengeRow = typeof mfaChallenges.$inferSelect;\nexport type RecoveryCodeRow = typeof recoveryCodes.$inferSelect;\nexport type MagicLinkToken = typeof magicLinkTokens.$inferSelect;\n","import { CollectionConfig, ResolvedRelation, isManyToMany } from \"@rebasepro/types\";\nimport { resolveCollectionRelations } from \"@rebasepro/common\";\n\nimport { PostgresCollectionRegistry } from \"../../collections/PostgresCollectionRegistry\";\n\n/**\n * One end of a many-to-many, as seen from the junction table.\n *\n * A junction table is not a collection, so nothing in the registry maps it to\n * one — which is why a change to it was invisible to change capture. But its\n * rows are exactly the contents of a parent's child list, so a write to it is a\n * change to `<parentSlug>/<sourceId>/<relationKey>` and to nothing else.\n */\nexport interface JunctionLink {\n schema: string;\n /** The junction table itself, e.g. `posts_tags`. */\n table: string;\n /** The collection whose relation this is, e.g. `posts`. */\n parentCollection: CollectionConfig;\n /** The relation's key — the path segment a child list is addressed by. */\n relationKey: string;\n /** Junction column holding the parent's id. */\n sourceColumn: string;\n /** Junction column holding the target's id. */\n targetColumn: string;\n}\n\n/**\n * Every junction table reachable from a registered collection, once per\n * relation that uses it.\n *\n * A junction is listed once per *direction* when both sides declare it, because\n * each direction addresses a different child list: `posts/1/tags` and\n * `tags/t/posts` both change when one link is written.\n */\nexport function collectJunctionLinks(registry: PostgresCollectionRegistry): JunctionLink[] {\n const links: JunctionLink[] = [];\n const seen = new Set<string>();\n\n for (const collection of registry.getCollections()) {\n let relations: Record<string, ResolvedRelation>;\n try {\n relations = resolveCollectionRelations(collection);\n } catch {\n // A collection whose relations cannot be resolved (an unresolvable\n // target, typically mid-migration) simply contributes none.\n continue;\n }\n\n for (const [relationKey, relation] of Object.entries(relations)) {\n if (!isManyToMany(relation)) continue;\n const through = relation.through;\n\n // Same relation registered under both its canonical name and the\n // declaring property key would otherwise notify the same path twice.\n const key = `${collection.slug}::${relationKey}::${through.table}`;\n if (seen.has(key)) continue;\n seen.add(key);\n\n links.push({\n schema: (collection as { schema?: string }).schema ?? \"public\",\n table: through.table,\n parentCollection: collection,\n relationKey,\n sourceColumn: through.sourceColumn,\n targetColumn: through.targetColumn\n });\n }\n }\n\n return links;\n}\n\n/**\n * Index {@link collectJunctionLinks} by table, under both the qualified and the\n * bare name — a change event carries whatever the trigger reports, and a\n * collection need not declare a schema.\n */\nexport function buildJunctionLinkMap(registry: PostgresCollectionRegistry): Map<string, JunctionLink[]> {\n const map = new Map<string, JunctionLink[]>();\n\n for (const link of collectJunctionLinks(registry)) {\n for (const key of [`${link.schema}.${link.table}`, link.table]) {\n const existing = map.get(key);\n if (existing) existing.push(link);\n else map.set(key, [link]);\n }\n }\n\n return map;\n}\n","import { getTableColumns } from \"drizzle-orm\";\nimport type { CollectionConfig } from \"@rebasepro/types\";\nimport { getTableName } from \"@rebasepro/common\";\nimport { toSnakeCase } from \"@rebasepro/utils\";\n\nimport type { PostgresCollectionRegistry } from \"../../collections/PostgresCollectionRegistry\";\nimport { getPrimaryKeys } from \"../collection-helpers\";\n\n/** One key field of a collection, and the column it is stored in. */\nexport interface KeyColumn {\n fieldName: string;\n columnName: string;\n}\n\n/**\n * The columns a collection's rows are addressed by.\n *\n * A change notification names its row by column — that is all a trigger sees —\n * and an address is built from the key's *field* names. Both ends read the\n * mapping from here: the provisioner, to attach the trigger with exactly these\n * columns (and nothing else leaves the database), and the consumer, to turn the\n * captured columns back into an address. A key declared as `userId` over\n * `user_id` used to be looked up by field name on the captured row, never\n * found, and every external write reached no single-row subscriber.\n */\nexport function collectionKeyColumns(collection: CollectionConfig, registry: PostgresCollectionRegistry): KeyColumn[] {\n const table = registry.getTable(getTableName(collection));\n const columns = table ? getTableColumns(table) : undefined;\n return getPrimaryKeys(collection, registry).map((pk) => {\n const property = collection.properties?.[pk.fieldName];\n const declared = property && \"columnName\" in property ? property.columnName : undefined;\n return {\n fieldName: pk.fieldName,\n columnName: columns?.[pk.fieldName]?.name ?? declared ?? toSnakeCase(pk.fieldName)\n };\n });\n}\n","import type { CollectionConfig, FetchCollectionProps } from \"@rebasepro/types\";\nimport type { FieldViewer } from \"@rebasepro/common\";\nimport { assertQueryFieldsReadable } from \"@rebasepro/server\";\n\n/**\n * Refuse a read that names a field its caller may not read, for the doors that\n * take a driver-shaped request rather than a query string: the socket's\n * `FETCH_COLLECTION`, `COUNT` and `CHECK_UNIQUE_FIELD`, and `subscribe_collection`.\n * Callers hand it the request itself, not a re-listed copy of some of its keys.\n *\n * The row strip keeps a withheld value off the wire; this is the other half, and\n * without it the value is still readable one predicate at a time — a `COUNT`\n * filtered on `passwordHash like '$2b$10$A%'` answers 1 or 0. `GET /api/data`\n * refuses the same request with `FIELD_NOT_READABLE`, and this is that rule, not\n * a copy of it: only the request's shape is translated here.\n *\n * The shape arrives as whatever JSON the client sent, so it is read defensively.\n * A malformed `orderBy` or `fields` is not this check's to reject; the entries\n * that name a field are checked and the rest are left for the driver to refuse.\n */\nexport function assertReadRequestReadable(\n request: Pick<FetchCollectionProps, \"filter\" | \"logical\" | \"orderBy\" | \"fields\" | \"include\" | \"vectorSearch\">,\n collection: CollectionConfig,\n viewer: FieldViewer\n): void {\n // Every input the REST check takes. An `include` reads its target's\n // columns — `{ staff: { where: { salary: [\">\", 100000] } } }` answers which\n // departments employ someone over the line — and a vector search measures\n // the vector it ranks by. Left out, both were refused over HTTP and\n // answered here.\n const vectorProperty = request.vectorSearch && typeof request.vectorSearch === \"object\"\n ? request.vectorSearch.property\n : undefined;\n assertQueryFieldsReadable({\n where: request.filter && typeof request.filter === \"object\" ? request.filter : undefined,\n logical: request.logical,\n orderBy: sortedFields(request.orderBy),\n fields: Array.isArray(request.fields) ? request.fields : undefined,\n include: request.include ?? undefined,\n vectorSearch: typeof vectorProperty === \"string\" ? { property: vectorProperty } : undefined\n }, collection, viewer);\n}\n\n/** The fields an `orderBy` sorts on, in either of its two spellings. */\nfunction sortedFields(orderBy: unknown): { field: string }[] | undefined {\n if (typeof orderBy === \"string\") return [{ field: orderBy }];\n if (!Array.isArray(orderBy)) return undefined;\n return orderBy.flatMap((entry: unknown) =>\n Array.isArray(entry) && typeof entry[0] === \"string\" ? [{ field: entry[0] }] : []);\n}\n","import { logger } from \"@rebasepro/server\";\nimport type { RawSqlRunner } from \"../../security/rls-enforcement\";\n\n/**\n * Trigger-based Change Data Capture (CDC).\n *\n * The preferred CDC source is the write-ahead log (logical replication), which\n * — like Supabase Realtime — sees *every* commit regardless of how it was made.\n * When logical replication is unavailable (managed Postgres without\n * `wal_level=logical`, no replication privilege, no `REPLICA IDENTITY`), this\n * trigger-based fallback provides the same guarantee at the row level:\n *\n * AFTER INSERT/UPDATE/DELETE trigger → pg_notify('rebase_cdc', payload)\n *\n * A single dedicated LISTEN client per backend instance consumes the channel\n * (see {@link CdcListener}) and feeds the change into the existing\n * `RealtimeService.notifyUpdate` pipeline, so subscribers see the change no\n * matter what wrote it — psql, a cron in another service, raw Drizzle/SQL, or\n * the Studio SQL editor.\n *\n * Provisioning runs from the framework's own bootstrap as the owner (server)\n * context, alongside the RLS role provisioning. It is idempotent.\n */\n\n/** Postgres NOTIFY channel carrying database-level change events. */\nexport const CDC_CHANNEL = \"rebase_cdc\";\n\n/** Schema-qualified name of the generic trigger function. */\nexport const CDC_TRIGGER_FUNCTION = \"rebase.rebase_cdc_notify\";\n\n/** Name of the per-table trigger (unqualified — triggers are namespaced by table). */\nexport const CDC_TRIGGER_NAME = \"rebase_cdc_trigger\";\n\n/**\n * pg_notify hard-caps payloads at 8000 bytes and *aborts the triggering\n * statement* if the limit is exceeded. The payload is an identity, so it fits\n * unless a key is enormous; one that does not is sent with no key at all (a\n * collection-wide invalidation) rather than breaking the write. 7900 leaves\n * headroom for the envelope.\n */\nconst MAX_NOTIFY_BYTES = 7900;\n\nconst quoteIdent = (name: string): string => `\"${name.replace(/\"/g, \"\\\"\\\"\")}\"`;\nconst quoteLiteral = (value: string): string => `'${value.replace(/'/g, \"''\")}'`;\n\n/**\n * SQL that (re)creates the generic CDC trigger function. Safe to run repeatedly:\n * `CREATE OR REPLACE` updates in place without dropping dependent triggers —\n * which is also how a database instrumented by an older version gets this body\n * on its next boot, for every table at once, re-attached or not.\n *\n * The function emits `{ schema, table, op, row }`, and `row` is an **identity,\n * never the tuple**. Postgres puts no privilege on `LISTEN`: any role that can\n * connect can listen on this channel, whatever it may `SELECT`. When `row` was\n * `to_jsonb(NEW)`, a login with no grant on anything received every changed row\n * of every instrumented table — `password_hash` and verification tokens from\n * the auth table included — past RLS and column grants. The consumer never\n * read anything but the key: every subscriber re-reads the row under its own\n * scope.\n *\n * The identity is, in order:\n * - the columns the trigger was attached with (`TG_ARGV`) — the key the\n * consumer addresses the row by, and for a junction table the two ids that\n * name the child list it changed; see {@link buildCdcTriggerSql};\n * - otherwise the table's primary key, read from the catalogue — a trigger\n * attached with no columns, by an older boot or on a table no collection\n * maps any more;\n * - otherwise `id`, if the table has one, and else nothing: a change the\n * consumer can only treat as collection-wide.\n */\nexport function buildCdcFunctionSql(): string {\n return `\nCREATE SCHEMA IF NOT EXISTS rebase;\n\nCREATE OR REPLACE FUNCTION ${CDC_TRIGGER_FUNCTION}() RETURNS trigger\nLANGUAGE plpgsql AS $rebase_cdc$\nDECLARE\n rec jsonb;\n ident jsonb := '{}'::jsonb;\n col text;\n payload text;\nBEGIN\n -- The tuple is read here and never leaves this function: only the\n -- identity below is put on the channel.\n IF (TG_OP = 'DELETE') THEN\n rec := to_jsonb(OLD);\n ELSE\n rec := to_jsonb(NEW);\n END IF;\n\n FOR n IN 0 .. TG_NARGS - 1 LOOP\n col := TG_ARGV[n];\n IF rec ? col THEN\n ident := ident || jsonb_build_object(col, rec -> col);\n END IF;\n END LOOP;\n\n IF ident = '{}'::jsonb THEN\n SELECT coalesce(jsonb_object_agg(a.attname, rec -> a.attname), '{}'::jsonb) INTO ident\n FROM pg_index i\n JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = ANY (i.indkey)\n WHERE i.indrelid = TG_RELID AND i.indisprimary;\n END IF;\n\n IF ident = '{}'::jsonb AND rec ? 'id' THEN\n ident := jsonb_build_object('id', rec -> 'id');\n END IF;\n\n payload := json_build_object(\n 'schema', TG_TABLE_SCHEMA,\n 'table', TG_TABLE_NAME,\n 'op', TG_OP,\n 'row', ident\n )::text;\n\n -- Never let CDC abort the write: a key too large to notify is sent as no\n -- key, which the consumer treats as a change to the whole collection.\n IF (octet_length(payload) > ${MAX_NOTIFY_BYTES}) THEN\n payload := json_build_object(\n 'schema', TG_TABLE_SCHEMA,\n 'table', TG_TABLE_NAME,\n 'op', TG_OP,\n 'row', '{}'::jsonb,\n 'truncated', true\n )::text;\n END IF;\n\n PERFORM pg_notify(${quoteLiteral(CDC_CHANNEL)}, payload);\n RETURN NULL;\nEND;\n$rebase_cdc$;\n`.trim();\n}\n\n/**\n * SQL that (re)attaches the CDC trigger to a single table. `DROP ... IF EXISTS`\n * before `CREATE` keeps it idempotent and picks up any function signature change.\n *\n * `identityColumns` are what the payload names the changed row by — see\n * {@link buildCdcFunctionSql}. Passed as trigger arguments, so one shared\n * function serves every table without a catalogue read per row. Empty means\n * \"the primary key\".\n */\nexport function buildCdcTriggerSql(schema: string, table: string, identityColumns: readonly string[] = []): string {\n const qualified = `${quoteIdent(schema)}.${quoteIdent(table)}`;\n const args = [...new Set(identityColumns)].map(quoteLiteral).join(\", \");\n return (\n `DROP TRIGGER IF EXISTS ${quoteIdent(CDC_TRIGGER_NAME)} ON ${qualified};\\n` +\n `CREATE TRIGGER ${quoteIdent(CDC_TRIGGER_NAME)} ` +\n `AFTER INSERT OR UPDATE OR DELETE ON ${qualified} ` +\n `FOR EACH ROW EXECUTE FUNCTION ${CDC_TRIGGER_FUNCTION}(${args});`\n );\n}\n\nexport interface CdcTableRef {\n schema: string;\n table: string;\n /**\n * The columns a change to this table is announced by — the collection's\n * key columns, or a junction's two id columns. Absent means the primary\n * key. Nothing else ever leaves the trigger; see {@link buildCdcFunctionSql}.\n */\n identityColumns?: string[];\n}\n\nexport interface ProvisionResult {\n /** Tables the trigger was successfully attached to. */\n installed: CdcTableRef[];\n /** Tables that could not be provisioned (e.g. not yet migrated), with the error. */\n skipped: Array<CdcTableRef & { reason: string }>;\n}\n\n/**\n * Replace an already-installed trigger function with the current body, and\n * install nothing where there is none.\n *\n * For a process that owns the schema but is not provisioning capture on this\n * boot — `REALTIME_CDC=off`, or no direct URL to listen on. The triggers an\n * earlier boot attached keep firing whether anything consumes them or not, so\n * a database instrumented by a version whose function put whole rows on the\n * channel kept doing so, for every login that cares to `LISTEN`, until capture\n * was switched back on. Returns whether a function was there to replace.\n */\nexport async function refreshInstalledCdcFunction(run: RawSqlRunner): Promise<boolean> {\n const rows = await run(`SELECT to_regprocedure(${quoteLiteral(`${CDC_TRIGGER_FUNCTION}()`)}) IS NOT NULL AS installed`);\n if (rows[0]?.installed !== true) return false;\n await run(buildCdcFunctionSql());\n return true;\n}\n\n/**\n * The columns a table's trigger is attached with: its primary key, read from\n * the catalogue, then each requested column the table actually has.\n *\n * The key is always included, because it is what the consumer falls back on\n * when a requested column was named wrong — a column guessed from a field name,\n * say — and a payload naming half a composite key would address the wrong row.\n * A column that does not exist is dropped here rather than left to be skipped\n * on every write. If the catalogue cannot be read, the request stands as given.\n */\nasync function resolveIdentityColumns(run: RawSqlRunner, ref: CdcTableRef): Promise<string[]> {\n const requested = ref.identityColumns ?? [];\n const qualified = `${quoteIdent(ref.schema)}.${quoteIdent(ref.table)}`;\n let columns: Array<{ name: string; primary: boolean }>;\n try {\n const rows = await run(\n \"SELECT a.attname AS name, coalesce(a.attnum = ANY (i.indkey), false) AS is_primary \" +\n \"FROM pg_attribute a \" +\n \"LEFT JOIN pg_index i ON i.indrelid = a.attrelid AND i.indisprimary \" +\n `WHERE a.attrelid = to_regclass(${quoteLiteral(qualified)}) AND a.attnum > 0 AND NOT a.attisdropped ` +\n \"ORDER BY a.attnum\"\n );\n columns = rows.map((row) => ({ name: String(row.name), primary: row.is_primary === true }));\n } catch {\n return [...new Set(requested)];\n }\n if (columns.length === 0) return [...new Set(requested)];\n const existing = new Set(columns.map((column) => column.name));\n return [...new Set([\n ...columns.filter((column) => column.primary).map((column) => column.name),\n ...requested.filter((column) => existing.has(column))\n ])];\n}\n\n/**\n * Idempotently install the CDC trigger function and per-table triggers.\n *\n * Runs as the owner (server) connection at bootstrap. A table that does not yet\n * exist in the database (schema drift) is skipped with a warning rather than\n * aborting the whole install, so one un-migrated collection cannot disable CDC\n * for the rest.\n */\nexport async function provisionTriggerCdc(\n run: RawSqlRunner,\n tables: CdcTableRef[]\n): Promise<ProvisionResult> {\n // 1. The shared trigger function (once).\n await run(buildCdcFunctionSql());\n\n // 2. One trigger per managed table. A table listed more than once — a\n // junction, once per direction of its relation — is attached once, with\n // every identity column any listing asked for.\n const byTable = new Map<string, CdcTableRef>();\n for (const ref of tables) {\n const key = `${ref.schema}.${ref.table}`;\n const existing = byTable.get(key);\n if (!existing) {\n byTable.set(key, { ...ref, identityColumns: [...(ref.identityColumns ?? [])] });\n } else {\n existing.identityColumns = [...new Set([...(existing.identityColumns ?? []), ...(ref.identityColumns ?? [])])];\n }\n }\n const installed: CdcTableRef[] = [];\n const skipped: ProvisionResult[\"skipped\"] = [];\n\n for (const [key, ref] of byTable) {\n try {\n const identity = await resolveIdentityColumns(run, ref);\n await run(buildCdcTriggerSql(ref.schema, ref.table, identity));\n installed.push(ref);\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n skipped.push({ ...ref, reason });\n logger.warn(\n `⚠️ [CDC] Could not attach change-capture trigger to \"${key}\" — ` +\n `is the table migrated? Writes to it won't emit database-level events.`,\n { detail: reason }\n );\n }\n }\n\n // Wiring detail. The single `Realtime source = …` line in the\n // bootstrapper is the fact a developer acts on; how many triggers it\n // took is for a diagnosis, and the skipped-table warning above still\n // fires on its own.\n logger.debug(\n `📡 [CDC] Trigger-based change capture provisioned on ${installed.length} table(s)` +\n (skipped.length ? ` (${skipped.length} skipped)` : \"\") + \".\"\n );\n\n return { installed, skipped };\n}\n","/**\n * A dedicated, self-healing Postgres `LISTEN` connection.\n *\n * Every cross-instance feature in the backend needs the same thing: one\n * connection *outside* the Drizzle pool that stays open, holds a `LISTEN`, and\n * comes back on its own after the database or the network drops it. CDC, the\n * channel bus and the cross-instance broadcast all use this one. By default the\n * **first** connect is validated and rethrown, so a caller can fall back to a\n * different strategy, while every later drop is repaired quietly in the\n * background; `retryInitialConnect` repairs the first one the same way.\n *\n * `LISTEN` is session state, so this connection must not go through a\n * transaction-mode pooler (PgBouncer): give it the direct database URL.\n */\n\nimport { Client as PgClient } from \"pg\";\nimport { logger } from \"@rebasepro/server\";\nimport { unref } from \"@rebasepro/utils\";\n\nexport interface PgNotifyListenerOptions {\n /** Direct Postgres connection string (must bypass a transaction-mode pooler). */\n connectionString: string;\n /** NOTIFY channel to LISTEN on. Must be a plain identifier — it is interpolated. */\n channel: string;\n /** Called for every notification payload received. */\n onPayload: (payload: string) => void | Promise<void>;\n /** Prefix for log lines, e.g. `\"[CDC]\"`. */\n logLabel: string;\n /** Delay before a reconnect attempt. */\n reconnectDelayMs?: number;\n /**\n * Called once the connection is listening again after a drop — never after\n * the first connect. Postgres keeps no NOTIFY for a session that is not\n * listening, so whatever was published while the connection was down is\n * gone for good; a caller that mirrors state from the channel resyncs here.\n */\n onReconnect?: () => void;\n /**\n * How often the connection proves it is alive with a round trip. `0`\n * turns the heartbeat off. See {@link PgNotifyListener} for why it exists.\n */\n heartbeatIntervalMs?: number;\n /** How long a heartbeat may take before the connection is declared dead. */\n heartbeatTimeoutMs?: number;\n /**\n * Treat a failed first connect like any later drop: `start()` resolves,\n * the listener reports itself down, and it keeps dialling in the\n * background. For a caller with nothing to fall back to, for whom a\n * database that is unreachable at boot is an outage to wait out rather\n * than a reason to run without the channel. Off by default: `start()`\n * rejects, so a caller with a fallback can take it.\n */\n retryInitialConnect?: boolean;\n}\n\n/**\n * What a listener can say about itself — read by `/health`.\n *\n * `connected` is \"listening, and the last heartbeat answered\", not \"a socket\n * object exists\": a half-open connection has a perfectly good socket object.\n */\nexport interface PgNotifyListenerStatus {\n channel: string;\n connected: boolean;\n /** Since when (epoch ms) it has been down, while it is down. */\n downSince?: number;\n /** When (epoch ms) it last answered a heartbeat. */\n lastHeartbeatAt?: number;\n}\n\nconst DEFAULT_RECONNECT_DELAY_MS = 3000;\n/**\n * Below every common idle-flow timeout: Azure's load balancer drops an idle\n * flow at 4 minutes, AWS's NLB at 350 s, and NAT and conntrack tables and\n * pooler-side reapers are often shorter. A LISTEN connection is idle by\n * nature, so it is exactly the connection those timeouts kill.\n */\nexport const DEFAULT_HEARTBEAT_INTERVAL_MS = 30_000;\nexport const DEFAULT_HEARTBEAT_TIMEOUT_MS = 10_000;\n/** Guards the identifier interpolated into `LISTEN`. */\nconst SAFE_CHANNEL = /^[A-Za-z_][A-Za-z0-9_]*$/;\n\n/**\n * A half-open connection — one whose packets stop arriving, with no FIN and no\n * RST, which is what an idle-flow timeout on a load balancer, a NAT, a dead\n * middlebox or a failed-over network looks like — fires no `error` and no\n * `end`. The connection object stays, `LISTEN` stays registered on a backend\n * that may no longer exist, and every notification from then on is lost: CDC,\n * the cross-instance broadcast and the channel bus all went quiet with no\n * error, no reconnect and a green `/health`, while writes made through this\n * instance still looked live because they are delivered locally.\n *\n * So the connection proves itself: TCP keepalive is on, and every\n * {@link DEFAULT_HEARTBEAT_INTERVAL_MS} a `SELECT 1` must come back within\n * {@link DEFAULT_HEARTBEAT_TIMEOUT_MS}. One that does not is torn down and\n * replaced, and `onReconnect` resyncs whatever was missed.\n */\nexport class PgNotifyListener {\n private client?: PgClient;\n private running = false;\n private reconnectTimer?: ReturnType<typeof setTimeout>;\n private heartbeatTimer?: ReturnType<typeof setInterval>;\n private heartbeatInFlight = false;\n private downSince?: number;\n private lastHeartbeatAt?: number;\n\n constructor(private readonly options: PgNotifyListenerOptions) {\n if (!SAFE_CHANNEL.test(options.channel)) {\n throw new Error(`Unsafe NOTIFY channel name \"${options.channel}\" — expected a plain SQL identifier.`);\n }\n }\n\n /** Whether the listener is meant to be connected right now. */\n get active(): boolean {\n return this.running;\n }\n\n /** Whether it is listening on a connection that answered its last heartbeat. */\n get connected(): boolean {\n return this.running && this.client !== undefined;\n }\n\n status(): PgNotifyListenerStatus {\n return {\n channel: this.options.channel,\n connected: this.connected,\n ...(this.downSince !== undefined && !this.connected ? { downSince: this.downSince } : {}),\n ...(this.lastHeartbeatAt !== undefined ? { lastHeartbeatAt: this.lastHeartbeatAt } : {})\n };\n }\n\n /**\n * Connect and begin listening. Idempotent.\n *\n * Rejects if the *initial* connection or `LISTEN` fails, leaving the\n * listener stopped — callers use that to degrade deliberately instead of\n * running blind against a channel nothing is delivering. With\n * `retryInitialConnect` it resolves instead, reports itself down, and\n * keeps dialling.\n */\n async start(): Promise<void> {\n if (this.running) return;\n this.running = true;\n try {\n await this.connect({ first: true });\n } catch (err) {\n this.running = false;\n throw err;\n }\n }\n\n /** Stop listening and release the connection. Idempotent. */\n async stop(): Promise<void> {\n this.running = false;\n this.stopHeartbeat();\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = undefined;\n }\n if (this.client) {\n try {\n await this.client.end();\n } catch { /* ignore close errors */ }\n this.client = undefined;\n }\n }\n\n /**\n * @param first The connect `start()` makes: never a reconnect, so it owes\n * no resync, and — unless `retryInitialConnect` — a failure is\n * thrown to the caller rather than retried.\n */\n private async connect({ first = false }: { first?: boolean } = {}): Promise<void> {\n const { connectionString, channel, onPayload, logLabel } = this.options;\n // Held here rather than only inside the `try` so the failure path can\n // still reach it: everything below `connect()` can throw, and until\n // `this.client` is assigned nothing else in this class knows the\n // connection exists. Left unreleased it stays open on the server while\n // `scheduleReconnect` opens another — one leaked backend per attempt,\n // every few seconds, for as long as the failure lasts.\n let pending: PgClient | undefined;\n try {\n // TCP keepalive as well as the heartbeat below: it costs nothing,\n // and it is what eventually errors a dead socket the heartbeat\n // has already given up on.\n const client = new PgClient({ connectionString, keepAlive: true, keepAliveInitialDelayMillis: 30_000 });\n pending = client;\n\n // Events from a connection already given up on — torn down after\n // a missed heartbeat, say — are not news about the current one.\n client.on(\"error\", (err) => {\n if (client !== this.client) return;\n logger.error(`❌ ${logLabel} LISTEN client error`, { detail: err.message });\n this.abandon(client);\n });\n\n client.on(\"end\", () => {\n if (client !== this.client) return;\n if (this.running) {\n logger.warn(`⚠️ ${logLabel} LISTEN client disconnected unexpectedly.`);\n this.abandon(client);\n }\n });\n\n client.on(\"notification\", (msg) => {\n if (!msg.payload) return;\n // A handler rejection must never surface as an unhandled\n // rejection inside the pg client's event emitter.\n Promise.resolve(onPayload(msg.payload)).catch((err) =>\n logger.error(`❌ ${logLabel} Error handling notification`, { error: err })\n );\n });\n\n await client.connect();\n await client.query(`LISTEN ${channel}`);\n this.client = client;\n // Adopted: `stop()` and `scheduleReconnect` will close it now.\n pending = undefined;\n this.downSince = undefined;\n this.lastHeartbeatAt = Date.now();\n this.startHeartbeat(client);\n logger.debug(`📡 ${logLabel} Listening on channel \"${channel}\".`);\n } catch (err) {\n // Never adopted, so nothing else will ever close it.\n if (pending) {\n try { await pending.end(); } catch { /* already dead */ }\n }\n // Surface the initial failure so callers can choose to fall back;\n // for reconnects, keep retrying quietly in the background.\n if (first && !this.options.retryInitialConnect) throw err;\n logger.error(`❌ ${logLabel} Failed to connect LISTEN client`, { error: err });\n this.scheduleReconnect();\n return;\n }\n // Outside the `try`: a throwing handler is not a failed connection,\n // and must not tear down the one that just came back.\n if (!first && this.options.onReconnect) {\n logger.warn(`⚠️ ${logLabel} LISTEN client reconnected; anything published while it was down was missed.`);\n try {\n this.options.onReconnect();\n } catch (err) {\n logger.error(`❌ ${logLabel} Error resyncing after a reconnect`, { error: err });\n }\n }\n }\n\n /**\n * Give up on the current connection: it is no longer the listener's, so\n * its late events are ignored and `connected` says so at once; it is\n * ended without waiting on it; and a replacement is scheduled.\n */\n private abandon(client: PgClient): void {\n if (client !== this.client) return;\n this.client = undefined;\n this.downSince ??= Date.now();\n this.stopHeartbeat();\n void client.end().catch(() => { /* already dead */ });\n this.scheduleReconnect();\n }\n\n private startHeartbeat(client: PgClient): void {\n this.stopHeartbeat();\n const interval = this.options.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_INTERVAL_MS;\n if (interval <= 0) return;\n this.heartbeatTimer = setInterval(() => void this.heartbeat(client), interval);\n // Housekeeping must never hold the process open.\n unref(this.heartbeatTimer);\n }\n\n private stopHeartbeat(): void {\n if (this.heartbeatTimer) clearInterval(this.heartbeatTimer);\n this.heartbeatTimer = undefined;\n this.heartbeatInFlight = false;\n }\n\n /**\n * One round trip on the LISTEN connection. Not answered in time, it is\n * treated as dead: the client is ended — which, with the query still\n * outstanding, destroys the socket rather than waiting on a goodbye that\n * cannot arrive — and replaced.\n */\n private async heartbeat(client: PgClient): Promise<void> {\n if (client !== this.client || this.heartbeatInFlight) return;\n this.heartbeatInFlight = true;\n const timeoutMs = this.options.heartbeatTimeoutMs ?? DEFAULT_HEARTBEAT_TIMEOUT_MS;\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n await Promise.race([\n client.query(\"SELECT 1\"),\n new Promise<never>((_resolve, reject) => {\n timer = setTimeout(() => reject(new Error(`no answer in ${timeoutMs}ms`)), timeoutMs);\n })\n ]);\n if (client === this.client) this.lastHeartbeatAt = Date.now();\n } catch (err) {\n if (client !== this.client) return;\n logger.warn(\n `⚠️ ${this.options.logLabel} LISTEN connection failed its heartbeat (${err instanceof Error ? err.message : String(err)}) — ` +\n \"treating it as dead and reconnecting.\"\n );\n this.abandon(client);\n } finally {\n if (timer) clearTimeout(timer);\n if (client === this.client) this.heartbeatInFlight = false;\n }\n }\n\n private scheduleReconnect(): void {\n if (!this.running || this.reconnectTimer) return;\n this.downSince ??= Date.now();\n\n this.reconnectTimer = setTimeout(async () => {\n this.reconnectTimer = undefined;\n if (!this.running) return;\n await this.connect();\n }, this.options.reconnectDelayMs ?? DEFAULT_RECONNECT_DELAY_MS);\n }\n}\n","import { logger } from \"@rebasepro/server\";\nimport { CDC_CHANNEL } from \"./trigger-cdc\";\nimport { PgNotifyListener, type PgNotifyListenerStatus } from \"../pg-notify-listener\";\n\n/**\n * A single database change captured by the CDC triggers and delivered over the\n * `rebase_cdc` NOTIFY channel.\n */\nexport interface CdcChangeEvent {\n schema: string;\n table: string;\n op: \"INSERT\" | \"UPDATE\" | \"DELETE\";\n /**\n * The changed row's identity, by column: its key, and for a junction table\n * the two ids naming the child list (from NEW for insert/update, OLD for\n * delete). Never the row's other values — any login can LISTEN; see\n * `buildCdcFunctionSql`.\n */\n row: Record<string, unknown>;\n /** True when even the key was too large to notify, so `row` is empty. */\n truncated?: boolean;\n}\n\n/**\n * Parse a `rebase_cdc` NOTIFY payload. Returns `null` for anything malformed so\n * a single bad message can never crash the listener.\n */\nexport function parseCdcPayload(payload: string): CdcChangeEvent | null {\n let parsed: unknown;\n try {\n parsed = JSON.parse(payload);\n } catch {\n return null;\n }\n if (!parsed || typeof parsed !== \"object\") return null;\n\n const obj = parsed as Record<string, unknown>;\n const schema = typeof obj.schema === \"string\" ? obj.schema : undefined;\n const table = typeof obj.table === \"string\" ? obj.table : undefined;\n const op = obj.op;\n if (!schema || !table) return null;\n if (op !== \"INSERT\" && op !== \"UPDATE\" && op !== \"DELETE\") return null;\n\n const row = obj.row && typeof obj.row === \"object\" ? (obj.row as Record<string, unknown>) : {};\n\n return {\n schema,\n table,\n op,\n row,\n truncated: obj.truncated === true\n };\n}\n\n/**\n * Dedicated Postgres LISTEN client for database-level CDC.\n *\n * A {@link PgNotifyListener} — a connection outside the Drizzle pool that stays\n * open and repairs itself — plus the parsing that turns a `rebase_cdc` payload\n * into a change event. Each backend instance runs one, so every instance\n * observes every committed change regardless of which instance (or external\n * process) made the write.\n */\nexport class CdcListener {\n private readonly listener: PgNotifyListener;\n\n /**\n * @param onReconnect Called when the connection is listening again after a\n * drop. Every change committed in the gap was notified to nobody, so\n * this is where subscribers are told to look again.\n */\n constructor(\n connectionString: string,\n onEvent: (event: CdcChangeEvent) => void | Promise<void>,\n onReconnect?: () => void\n ) {\n this.listener = new PgNotifyListener({\n connectionString,\n channel: CDC_CHANNEL,\n logLabel: \"[CDC]\",\n onReconnect,\n onPayload: (payload) => {\n const event = parseCdcPayload(payload);\n if (!event) {\n logger.warn(\"⚠️ [CDC] Dropping unparseable change notification.\");\n return;\n }\n return onEvent(event);\n }\n });\n }\n\n /**\n * Connect and begin listening. Idempotent.\n *\n * The **initial** connection is validated synchronously: if it cannot be\n * established (or `LISTEN` is refused), this rejects so callers — notably\n * `REALTIME_CDC=auto` — can detect an unusable connection and fall back to\n * app-level realtime. Once the initial connection succeeds, later drops\n * self-heal in the background.\n */\n async start(): Promise<void> {\n if (this.listener.active) {\n logger.warn(\"⚠️ [CDC] CdcListener.start() called but already running. Ignoring.\");\n return;\n }\n await this.listener.start();\n }\n\n /** Stop listening and release the connection. */\n async stop(): Promise<void> {\n await this.listener.stop();\n }\n\n /** Listening on a connection that answered its last heartbeat — see {@link PgNotifyListener}. */\n get connected(): boolean {\n return this.listener.connected;\n }\n\n status(): PgNotifyListenerStatus {\n return this.listener.status();\n }\n}\n","/**\n * Letting go of client sockets the server can no longer reach.\n *\n * A phone that loses signal, a laptop that sleeps, a NAT that forgets the\n * connection: the peer is gone, and nothing tells the server. No close frame\n * arrives and no FIN, so the socket stays open until the operating system gives\n * up on the TCP connection — about two hours on Linux. Meanwhile every\n * subscription it holds is refetched on every write to its collection, and\n * every frame for it is serialised into a send buffer nobody drains.\n *\n * Two checks catch it. The socket must answer a ping: one that has not answered\n * by the next is terminated. And it must read what it is sent: one whose unsent\n * backlog passes {@link MAX_SOCKET_BUFFERED_BYTES} is terminated at the next\n * frame for it. A terminated socket closes like any other, so the realtime\n * service drops its subscriptions, channels and presence on its `close`.\n */\n\nimport { WebSocket, type WebSocketServer } from \"ws\";\nimport { logger } from \"@rebasepro/server\";\nimport { unref } from \"@rebasepro/utils\";\n\n/**\n * How often every socket is pinged. A socket that has not answered one ping by\n * the next is terminated, so a peer that vanished is let go of within two\n * intervals. Every browser and `ws` client answers a ping by itself.\n */\nexport const SOCKET_PING_INTERVAL_MS = 30_000;\n\n/**\n * The unsent bytes a socket may have queued before it is terminated.\n *\n * A healthy client drains its queue as fast as the network allows; one whose\n * backlog keeps growing is not reading, or is so far behind that what it would\n * read is stale. Set well above a single frame — the largest body the data API\n * accepts is 10 MiB, and a list frame can approach it — so a burst of large\n * frames to a client that is keeping up does not trip it. A terminated client\n * reconnects and resubscribes, which brings it up to date with one frame per\n * subscription instead of the backlog.\n */\nexport const MAX_SOCKET_BUFFERED_BYTES = 16 * 1024 * 1024;\n\n/**\n * Ping every socket of `wss` each `intervalMs`, and terminate the ones that did\n * not answer the previous ping. Returns the function that stops it.\n */\nexport function reapSilentSockets(wss: WebSocketServer, intervalMs: number = SOCKET_PING_INTERVAL_MS): () => void {\n // Read here rather than on the first tick. A server made with\n // `clientTracking: false` has no `clients`, and finding that out inside the\n // timer throws from a callback nothing catches, which ends the process\n // `intervalMs` later — whatever it is doing by then. That is how a test\n // double without `clients` took down the jest worker that ran it, along with\n // every suite the worker had moved on to.\n const clients: Set<WebSocket> | undefined = wss.clients;\n if (!clients) {\n throw new Error(\"reapSilentSockets needs a WebSocketServer that tracks its clients; this one was created with clientTracking: false.\");\n }\n const awaitingPong = new WeakSet<WebSocket>();\n wss.on(\"connection\", (ws: WebSocket) => {\n ws.on(\"pong\", () => awaitingPong.delete(ws));\n });\n const timer = setInterval(() => {\n for (const ws of clients) {\n if (awaitingPong.has(ws)) {\n logger.debug(`[WebSocket Server] Terminated a socket that did not answer a ping within ${intervalMs}ms.`);\n ws.terminate();\n continue;\n }\n if (ws.readyState !== WebSocket.OPEN) continue;\n awaitingPong.add(ws);\n ws.ping();\n }\n }, intervalMs);\n // Housekeeping must never hold the process open.\n unref(timer);\n return () => clearInterval(timer);\n}\n\n/**\n * Terminate `ws` if its unsent backlog has passed {@link MAX_SOCKET_BUFFERED_BYTES}.\n * Returns whether it did; the caller then sends nothing.\n */\nexport function terminateIfBacklogged(ws: WebSocket, clientId: string): boolean {\n const backlog = ws.bufferedAmount;\n if (backlog > MAX_SOCKET_BUFFERED_BYTES) {\n logger.warn(\n `[WebSocket Server] Terminated ${clientId}: ${backlog} bytes sent to it are still unread, ` +\n `past the ${MAX_SOCKET_BUFFERED_BYTES}-byte ceiling for one socket.`\n );\n ws.terminate();\n return true;\n }\n return false;\n}\n","/**\n * The server's DDL bootstrapper, over a Drizzle handle.\n *\n * `createDdlBootstrapper` in `@rebasepro/server` wants a plain\n * `(sql: string) => Promise<rows>`; the driver's internal stores hold a Drizzle\n * database. This is the adapter between them, and it exists so the retry policy\n * has exactly one definition. A second copy of the SQLSTATE list living in the\n * driver is how the two drift apart, and the drift is invisible: both versions\n * work perfectly on every single-instance deployment.\n */\nimport { sql } from \"drizzle-orm\";\nimport type { NodePgDatabase } from \"drizzle-orm/node-postgres\";\nimport { createDdlBootstrapper, type DdlBootstrapper } from \"@rebasepro/server\";\nimport { sqlRows } from \"@rebasepro/common\";\n\n/**\n * A {@link DdlBootstrapper} that runs its statements through `db.execute`.\n *\n * @param db the Drizzle handle the calling store already holds\n * @param scope log prefix identifying the caller, e.g. `\"channel-presence\"`\n */\nexport function drizzleDdlBootstrapper(\n db: NodePgDatabase<Record<string, unknown>>,\n scope: string\n): DdlBootstrapper {\n return createDdlBootstrapper(async (statement: string) => {\n // `sql.raw`, because everything reaching this path is DDL assembled from\n // identifiers that were validated before they got here — there is no\n // parameter to bind, and Drizzle's tagged template would treat the whole\n // statement as one.\n return sqlRows(await db.execute(sql.raw(statement)));\n }, scope);\n}\n","/**\n * Ordered, replayable per-channel message history.\n *\n * Broadcast on its own is fire-and-forget to whoever is connected at the\n * instant it is sent: fine for presence and for \"someone saved\" notifications,\n * not enough for op-based collaborative editing, where a client that blinks\n * out for two seconds has to resync a whole document rather than catch up on\n * the four operations it missed. This adds the missing half — every retained\n * broadcast gets a per-channel sequence number, and a client can ask for\n * everything after the last one it saw.\n *\n * Three decisions worth stating, because each rules out a simpler-looking one:\n *\n * - **Retention is server-side and opt-in.** A channel is created by whoever\n * names it, so a client-supplied history depth would let any visitor commit\n * the backend to unbounded storage. And presence channels — the common case\n * — must not pay for this: with no rules configured nothing is written, no\n * table is created, and `broadcast` runs exactly the code it ran before.\n *\n * - **Sequence numbers come from the database, not from a counter in this\n * process.** They have to survive a restart and be shared across instances;\n * an in-memory counter would restart at 1 after a deploy and hand a\n * reconnecting client a replay from the wrong era, silently.\n *\n * - **The cursor row outlives the messages it numbered.** Pruning is what\n * makes retention affordable, but pruning the cursor along with the messages\n * would restart the sequence and make `sinceSeq` mean something different\n * before and after — the worst kind of bug, because replay would still\n * return rows and they would look plausible. Cursors are tiny and are kept\n * forever; see {@link prune}, which touches only `channel_messages`.\n */\n\nimport { sql } from \"drizzle-orm\";\nimport { NodePgDatabase } from \"drizzle-orm/node-postgres\";\nimport type { ChannelHistoryEntry, ChannelRetentionRule } from \"@rebasepro/types\";\nimport { logger } from \"@rebasepro/server\";\nimport { revokeInternalTableSql } from \"@rebasepro/common\";\nimport { drizzleDdlBootstrapper } from \"../schema/drizzle-ddl\";\n\n/** How many messages a replay returns when the caller does not say. */\nconst DEFAULT_REPLAY_LIMIT = 200;\n\n/**\n * Hard ceiling on one replay, whatever the caller asks for.\n *\n * A reconnecting client names its own `limit`, so this is the only thing\n * standing between a stale `sinceSeq` and a single frame carrying a channel's\n * entire retained history. A client that is further behind than this is told so\n * via `latestSeq` and can decide to resync wholesale instead of paging.\n */\nconst MAX_REPLAY_LIMIT = 1000;\n\n/** Minimum gap between two prunes of the same channel. */\nconst PRUNE_THROTTLE_MS = 30_000;\n\n/**\n * Parse a retention TTL into milliseconds.\n *\n * Accepts a raw millisecond count or a short duration string (`\"30s\"`, `\"15m\"`,\n * `\"24h\"`, `\"7d\"`). Returns undefined for anything unparseable, which the\n * caller treats as \"no TTL\" — a misspelt duration must not silently become an\n * aggressive one.\n */\nexport function parseTtlMs(ttl: number | string | undefined): number | undefined {\n if (ttl === undefined || ttl === null) return undefined;\n if (typeof ttl === \"number\") return Number.isFinite(ttl) && ttl > 0 ? ttl : undefined;\n\n const match = /^\\s*(\\d+(?:\\.\\d+)?)\\s*(ms|s|m|h|d)\\s*$/i.exec(ttl);\n if (!match) {\n logger.warn(`⚠️ [ChannelHistory] Ignoring unparseable retention ttl \"${ttl}\" — expected e.g. \"30s\", \"15m\", \"24h\", \"7d\".`);\n return undefined;\n }\n const value = parseFloat(match[1]);\n const unit = match[2].toLowerCase();\n const multiplier = unit === \"ms\" ? 1\n : unit === \"s\" ? 1_000\n : unit === \"m\" ? 60_000\n : unit === \"h\" ? 3_600_000\n : 86_400_000;\n const ms = value * multiplier;\n return ms > 0 ? ms : undefined;\n}\n\n/**\n * Whether `channel` is covered by `rule`.\n *\n * Exact match, or a trailing `*` acting as a prefix. Not a general glob: this\n * decides what reaches disk, and a pattern language whose reach is not obvious\n * at a glance is the wrong tool for that job.\n */\nexport function channelMatchesRule(channel: string, rule: ChannelRetentionRule): boolean {\n const pattern = rule.match;\n if (pattern === \"*\") return true;\n if (pattern.endsWith(\"*\")) return channel.startsWith(pattern.slice(0, -1));\n return channel === pattern;\n}\n\n/** A rule with its TTL already resolved to milliseconds. */\nexport interface ResolvedRetention {\n limit?: number;\n ttlMs?: number;\n}\n\n/**\n * A bus frame to send in the statement that numbers a retained message — see\n * {@link ChannelHistoryStore.append}.\n *\n * It is always a pointer, `{ kind: \"broadcast_ref\", sid, channel, seq }`, and\n * never the message. Postgres puts no privilege on `LISTEN`, so a NOTIFY payload\n * reaches every role that can connect to the database; the body stays in\n * `rebase.channel_messages`, behind the table grants, and each receiving\n * instance reads it back by `(channel, seq)` — the sender's id with it.\n */\nexport interface RetainedAnnouncement {\n /** The NOTIFY channel the bus listens on. */\n notifyChannel: string;\n /** The sending instance. */\n sid: string;\n}\n\n/**\n * Persistence and replay for retained channels.\n *\n * Inert unless constructed with at least one rule: {@link enabled} is false,\n * {@link ensureTables} does nothing, and {@link retentionFor} answers undefined\n * for every channel, so the realtime service never reaches the SQL below.\n */\nexport class ChannelHistoryStore {\n private rules: ChannelRetentionRule[];\n /** Resolved rule per channel name, so the match runs once per channel. */\n private resolved = new Map<string, ResolvedRetention | null>();\n /** Channel → timestamp of its last prune, for {@link PRUNE_THROTTLE_MS}. */\n private lastPruned = new Map<string, number>();\n private tablesReady = false;\n\n constructor(private db: NodePgDatabase<Record<string, unknown>>, rules: ChannelRetentionRule[] = []) {\n this.rules = rules.filter(rule => {\n if (!rule?.match) {\n logger.warn(\"⚠️ [ChannelHistory] Ignoring a retention rule with no `match`.\");\n return false;\n }\n const hasBound = rule.limit !== undefined || rule.ttl !== undefined;\n if (!hasBound) {\n // Unbounded retention is almost never intended and cannot be\n // walked back once the table has grown, so it is refused rather\n // than honoured.\n logger.warn(`⚠️ [ChannelHistory] Retention rule \"${rule.match}\" sets neither \\`limit\\` nor \\`ttl\\` — ignoring it, as it would retain forever.`);\n return false;\n }\n return true;\n });\n }\n\n /** Whether any channel retains anything at all. */\n get enabled(): boolean {\n return this.rules.length > 0;\n }\n\n /**\n * The retention that applies to `channel`, or undefined when none does.\n *\n * First matching rule wins, so callers order them most-specific first.\n */\n retentionFor(channel: string): ResolvedRetention | undefined {\n if (!this.enabled || !channel) return undefined;\n\n const cached = this.resolved.get(channel);\n if (cached !== undefined) return cached ?? undefined;\n\n const rule = this.rules.find(r => channelMatchesRule(channel, r));\n const resolved: ResolvedRetention | null = rule\n ? { limit: rule.limit, ttlMs: parseTtlMs(rule.ttl) }\n : null;\n\n // Bounded by the number of distinct channel names seen, which is the\n // same thing the in-memory channel and presence maps are bounded by.\n this.resolved.set(channel, resolved);\n return resolved ?? undefined;\n }\n\n /**\n * Create the history tables. Idempotent, and a no-op when no rule is set —\n * a deployment that never retains anything gets no schema for it.\n */\n async ensureTables(): Promise<void> {\n if (!this.enabled || this.tablesReady) return;\n\n // Contained, retrying steps rather than one straight sequence — see the\n // note on `ChannelPresenceStore.ensureTables`. The failure mode here is\n // the same and the stakes are the same: the two `REVOKE`s at the end are\n // what keep retained broadcasts off the end-user role, and a lost create\n // race used to skip them.\n const ddl = drizzleDdlBootstrapper(this.db, \"channel-history\");\n\n await ddl.ensureObject(\"rebase schema\", \"CREATE SCHEMA IF NOT EXISTS rebase\");\n\n // The primary key is exactly the replay query's access path\n // (`channel = $1 AND seq > $2 ORDER BY seq`), so it needs no further\n // index of its own.\n await ddl.ensureObject(\"channel_messages table\", `\n CREATE TABLE IF NOT EXISTS rebase.channel_messages (\n channel TEXT NOT NULL,\n seq BIGINT NOT NULL,\n event TEXT NOT NULL,\n payload JSONB,\n sender_id TEXT,\n created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n PRIMARY KEY (channel, seq)\n )\n `);\n\n // Only for the TTL arm of pruning; the limit arm rides the primary key.\n await ddl.ensureObject(\"channel_messages created_at index\", `\n CREATE INDEX IF NOT EXISTS idx_channel_messages_created\n ON rebase.channel_messages (created_at)\n `);\n\n // Never pruned — see the note at the top of this file. One row per\n // channel that has ever retained a message.\n await ddl.ensureObject(\"channel_cursors table\", `\n CREATE TABLE IF NOT EXISTS rebase.channel_cursors (\n channel TEXT PRIMARY KEY,\n last_seq BIGINT NOT NULL\n )\n `);\n\n // Retained broadcasts for every channel in one table, with no RLS: who\n // may replay a channel is decided before the read, by the channel gate\n // in `realtimeService.authorizeChannelAction` — a replay is answered\n // only for a client that has joined the channel, plus whatever an\n // installed `ChannelAuthorizer` adds. That gate is the entire reason\n // this table can sit outside the RLS model, so it fails closed; the\n // rule language it does not yet have is written up in\n // `docs/channel-authorization.md`. The driver's schema-wide\n // grant reaches these (created here, after it ran), so take the\n // privilege back.\n //\n // Driven off a probe of what exists rather than off who won each create.\n const [messagesReady, cursorsReady] = await Promise.all([\n ddl.isReadable(\"rebase.channel_messages\"),\n ddl.isReadable(\"rebase.channel_cursors\")\n ]);\n if (messagesReady) {\n await ddl.step(\"channel_messages revoke\", () =>\n this.db.execute(sql.raw(revokeInternalTableSql(\"rebase\", \"channel_messages\")))\n );\n }\n if (cursorsReady) {\n await ddl.step(\"channel_cursors revoke\", () =>\n this.db.execute(sql.raw(revokeInternalTableSql(\"rebase\", \"channel_cursors\")))\n );\n }\n\n if (!messagesReady || !cursorsReady) {\n // Left un-ready on purpose so the next call retries. Announcing\n // \"ready\" here is what would turn a half-created schema into replays\n // that answer empty forever.\n logger.warn(\n \"[ChannelHistory] Retained-channel tables are not both present; history is not ready yet.\"\n );\n return;\n }\n\n this.tablesReady = true;\n logger.info(`✅ [ChannelHistory] Retained channels ready (${this.rules.length} rule(s)).`);\n }\n\n /**\n * Append a broadcast and return the sequence number it was given.\n *\n * The sequence is allocated by the same statement that stores the message,\n * so a crash between the two is not a possibility. `ON CONFLICT DO UPDATE`\n * takes a row lock on the channel's cursor, which is what makes concurrent\n * broadcasts to one channel line up in a single order — and what keeps\n * different channels from contending with each other at all.\n */\n async append(\n channel: string,\n event: string,\n payload: unknown,\n senderId?: string,\n announce?: RetainedAnnouncement\n ): Promise<{ seq: number; at: string }> {\n const payloadJson = JSON.stringify(payload ?? null);\n // With an announcement, the bus frame is sent by the same statement\n // that takes the number. The upsert holds the channel's cursor row\n // until commit, so appends to a channel commit in sequence order — and\n // a NOTIFY is delivered at commit, in commit order. Sent afterwards, by\n // whichever instance took the number, two instances' messages reached\n // a third in whatever order their sends landed.\n //\n // The frame is the message's address and nothing else — not its event,\n // its body or its sender. Any database login can LISTEN; only a reader\n // of `rebase.channel_messages` can turn the address into the message.\n const result = announce\n ? await this.db.execute(sql`\n WITH next AS (\n INSERT INTO rebase.channel_cursors (channel, last_seq)\n VALUES (${channel}, 1)\n ON CONFLICT (channel)\n DO UPDATE SET last_seq = rebase.channel_cursors.last_seq + 1\n RETURNING last_seq\n ), stored AS (\n INSERT INTO rebase.channel_messages (channel, seq, event, payload, sender_id)\n SELECT ${channel}, next.last_seq, ${event}, ${payloadJson}::jsonb, ${senderId ?? null}\n FROM next\n RETURNING seq, created_at\n )\n SELECT stored.seq, stored.created_at, pg_notify(${announce.notifyChannel}, json_build_object(\n 'kind', 'broadcast_ref', 'sid', ${announce.sid}::text, 'channel', ${channel}::text, 'seq', stored.seq\n )::text)\n FROM stored\n `)\n : await this.db.execute(sql`\n WITH next AS (\n INSERT INTO rebase.channel_cursors (channel, last_seq)\n VALUES (${channel}, 1)\n ON CONFLICT (channel)\n DO UPDATE SET last_seq = rebase.channel_cursors.last_seq + 1\n RETURNING last_seq\n )\n INSERT INTO rebase.channel_messages (channel, seq, event, payload, sender_id)\n SELECT ${channel}, next.last_seq, ${event}, ${payloadJson}::jsonb, ${senderId ?? null}\n FROM next\n RETURNING seq, created_at\n `);\n\n const row = result.rows[0] as { seq: string | number; created_at: Date | string } | undefined;\n if (!row) throw new Error(`Failed to append to channel history for \"${channel}\"`);\n\n return {\n // BIGINT comes back as a string from node-postgres; the wire type is\n // a number, and a channel would need 2^53 messages to notice.\n seq: Number(row.seq),\n at: row.created_at instanceof Date ? row.created_at.toISOString() : String(row.created_at)\n };\n }\n\n /**\n * Everything retained for `channel` after `sinceSeq`, oldest first.\n *\n * `latestSeq` is reported whether or not the messages were capped, so a\n * client that is further behind than one page can tell.\n */\n async replay(\n channel: string,\n sinceSeq = 0,\n limit = DEFAULT_REPLAY_LIMIT\n ): Promise<{ messages: ChannelHistoryEntry[]; latestSeq: number }> {\n const capped = Math.max(1, Math.min(Math.floor(limit) || DEFAULT_REPLAY_LIMIT, MAX_REPLAY_LIMIT));\n const after = Number.isFinite(sinceSeq) && sinceSeq > 0 ? Math.floor(sinceSeq) : 0;\n\n const result = await this.db.execute(sql`\n SELECT seq, event, payload, sender_id, created_at\n FROM rebase.channel_messages\n WHERE channel = ${channel} AND seq > ${after}\n ORDER BY seq ASC\n LIMIT ${capped}\n `);\n\n const messages = (result.rows as Array<{\n seq: string | number;\n event: string;\n payload: unknown;\n sender_id: string | null;\n created_at: Date | string;\n }>).map(row => ({\n seq: Number(row.seq),\n event: row.event,\n payload: row.payload,\n senderId: row.sender_id ?? undefined,\n at: row.created_at instanceof Date ? row.created_at.toISOString() : String(row.created_at)\n }));\n\n // Read from the cursor rather than from the messages: the cursor is the\n // authority on how far the channel has got, and still says so after\n // pruning has removed the messages it counted.\n const cursor = await this.db.execute(sql`\n SELECT last_seq FROM rebase.channel_cursors WHERE channel = ${channel}\n `);\n const cursorRow = cursor.rows[0] as { last_seq: string | number } | undefined;\n const latestSeq = cursorRow ? Number(cursorRow.last_seq) : 0;\n\n return { messages, latestSeq };\n }\n\n /**\n * One retained message by its address.\n *\n * This is what makes the cross-instance pointer path work: a broadcast too\n * large to travel inside a `pg_notify` payload is already stored here, so\n * the notification carries `(channel, seq)` and each receiving instance\n * reads the body back. Returns null when the message has since been pruned\n * — a receiver that is that far behind has nothing useful to deliver, and\n * the client's own `channel_history` replay is the repair path.\n */\n async getBySeq(channel: string, seq: number): Promise<ChannelHistoryEntry | null> {\n const result = await this.db.execute(sql`\n SELECT seq, event, payload, sender_id, created_at\n FROM rebase.channel_messages\n WHERE channel = ${channel} AND seq = ${seq}\n `);\n\n const row = result.rows[0] as {\n seq: string | number;\n event: string;\n payload: unknown;\n sender_id: string | null;\n created_at: Date | string;\n } | undefined;\n if (!row) return null;\n\n return {\n seq: Number(row.seq),\n event: row.event,\n payload: row.payload,\n senderId: row.sender_id ?? undefined,\n at: row.created_at instanceof Date ? row.created_at.toISOString() : String(row.created_at)\n };\n }\n\n /**\n * Enforce a channel's retention bounds.\n *\n * Throttled per channel, so a burst of operations prunes once rather than\n * once per message — the cost then tracks elapsed time instead of write\n * volume, which is what makes retention affordable on a hot channel.\n */\n async prune(channel: string, retention: ResolvedRetention): Promise<number> {\n const now = Date.now();\n const last = this.lastPruned.get(channel) ?? 0;\n if (now - last < PRUNE_THROTTLE_MS) return 0;\n this.lastPruned.set(channel, now);\n\n let deleted = 0;\n\n if (retention.ttlMs !== undefined) {\n const result = await this.db.execute(sql`\n DELETE FROM rebase.channel_messages\n WHERE channel = ${channel}\n AND created_at < NOW() - MAKE_INTERVAL(secs => ${retention.ttlMs / 1000})\n `);\n deleted += result.rowCount ?? 0;\n }\n\n if (retention.limit !== undefined && retention.limit > 0) {\n // OFFSET past the newest `limit` rows to find the highest seq that\n // is no longer wanted, then delete everything at or below it. Fewer\n // rows than the limit leaves the subquery empty, and the comparison\n // with NULL deletes nothing.\n const result = await this.db.execute(sql`\n DELETE FROM rebase.channel_messages\n WHERE channel = ${channel}\n AND seq <= (\n SELECT seq FROM rebase.channel_messages\n WHERE channel = ${channel}\n ORDER BY seq DESC\n OFFSET ${Math.floor(retention.limit)} LIMIT 1\n )\n `);\n deleted += result.rowCount ?? 0;\n }\n\n return deleted;\n }\n\n /** Forget throttle and match caches. Called on shutdown. */\n clear(): void {\n this.resolved.clear();\n this.lastPruned.clear();\n }\n}\n","/**\n * The shared presence roster.\n *\n * Broadcast only ever needed *fan-out* to work across instances — a frame goes\n * out, whoever is connected receives it. Presence needs more than that, because\n * `presence_state` is a question (\"who is in this document?\") and a per-process\n * `Map` can only answer for the clients that happen to share a replica with the\n * asker. Two people editing the same scene through different pods would each\n * see an empty room while broadcasting cursors at each other perfectly.\n *\n * So presence gets one row per tracked client, in Postgres, readable by every\n * instance. Three consequences worth stating:\n *\n * - **The table is the roster; the in-process map is a cache of our own\n * clients.** Reads answer from the table when this store is active, so the\n * answer is the same whichever instance is asked.\n *\n * - **`last_seen` is the liveness signal, and it is already there.** The client\n * heartbeats presence every ~20 s against a 30 s window; the sweep that has\n * always reaped local stale entries now also reaps rows belonging to\n * instances that stopped writing — which is exactly what a crashed pod looks\n * like. Crash recovery is a property of the TTL, not a separate mechanism.\n *\n * - **The sweep deletes with `RETURNING`.** Whichever instance wins the delete\n * is the one that announces the departures, so a stale client produces one\n * `presence_diff` for the cluster rather than one per replica.\n */\n\nimport { sql } from \"drizzle-orm\";\nimport { NodePgDatabase } from \"drizzle-orm/node-postgres\";\nimport { revokeInternalTableSql } from \"@rebasepro/common\";\nimport { drizzleDdlBootstrapper } from \"../schema/drizzle-ddl\";\n\n/** A tracked client, as any instance sees it. */\nexport interface PresenceRow {\n channel: string;\n clientId: string;\n state: Record<string, unknown>;\n}\n\nexport class ChannelPresenceStore {\n private tablesReady = false;\n\n constructor(\n private readonly db: NodePgDatabase<Record<string, unknown>>,\n private readonly instanceId: string\n ) {}\n\n /**\n * Create the roster table. Idempotent, and safe to run on every instance at\n * once.\n *\n * Written as separate contained steps rather than one straight sequence for\n * a reason that only bites with more than one replica, which is exactly the\n * deployment shape this table exists to serve: `CREATE … IF NOT EXISTS`\n * reads the catalog and then writes to it non-atomically, so peers booting\n * together collide, and the loser used to abandon everything after it —\n * including the trailing `REVOKE`. That revoke is the only thing keeping the\n * roster off the end-user role, so losing a boot race silently left the\n * whole channel roster readable by every signed-in user.\n *\n * `tablesReady` is now set from a probe of what exists, not from having been\n * the instance that created it.\n */\n async ensureTables(): Promise<void> {\n if (this.tablesReady) return;\n\n const ddl = drizzleDdlBootstrapper(this.db, \"channel-presence\");\n\n await ddl.ensureObject(\"rebase schema\", \"CREATE SCHEMA IF NOT EXISTS rebase\");\n\n // Keyed by (channel, client_id): a client id is globally unique, so the\n // instance is a column rather than part of the identity — a client that\n // reconnects onto another replica replaces its own row instead of\n // appearing twice in the roster.\n await ddl.ensureObject(\"channel_presence table\", `\n CREATE TABLE IF NOT EXISTS rebase.channel_presence (\n channel TEXT NOT NULL,\n client_id TEXT NOT NULL,\n instance_id TEXT NOT NULL,\n state JSONB NOT NULL DEFAULT '{}'::jsonb,\n last_seen TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n PRIMARY KEY (channel, client_id)\n )\n `);\n\n // The sweep's access path; the roster read rides the primary key.\n await ddl.ensureObject(\"channel_presence last_seen index\", `\n CREATE INDEX IF NOT EXISTS idx_channel_presence_last_seen\n ON rebase.channel_presence (last_seen)\n `);\n\n // The roster of every client on every channel, with no RLS — a row\n // policy has nothing to match on here, since a channel name is a string\n // a client invents rather than a row anyone owns. What guards it is the\n // channel gate in `realtimeService.authorizeChannelAction`: presence is\n // readable only to a client that has joined the channel, plus whatever\n // an installed `ChannelAuthorizer` adds. That gate is the entire reason\n // this table can sit outside the RLS model, so it fails closed —\n // see `docs/channel-authorization.md` for what it does *not*\n // yet decide. Revoke the schema-wide grant the driver handed out\n // before this table existed.\n //\n // Driven off the probe, not off who won the create: the privilege has to\n // come off whether this instance created the table or found it.\n if (await ddl.isReadable(\"rebase.channel_presence\")) {\n await ddl.step(\"channel_presence revoke\", () =>\n this.db.execute(sql.raw(revokeInternalTableSql(\"rebase\", \"channel_presence\")))\n );\n this.tablesReady = true;\n }\n }\n\n /** Record (or refresh) a client's presence. */\n async track(channel: string, clientId: string, state: Record<string, unknown>): Promise<void> {\n await this.db.execute(sql`\n INSERT INTO rebase.channel_presence (channel, client_id, instance_id, state, last_seen)\n VALUES (${channel}, ${clientId}, ${this.instanceId}, ${JSON.stringify(state ?? {})}::jsonb, NOW())\n ON CONFLICT (channel, client_id) DO UPDATE\n SET state = EXCLUDED.state,\n instance_id = EXCLUDED.instance_id,\n last_seen = NOW()\n `);\n }\n\n /** Drop one client's presence in one channel. */\n async remove(channel: string, clientId: string): Promise<void> {\n await this.db.execute(sql`\n DELETE FROM rebase.channel_presence\n WHERE channel = ${channel} AND client_id = ${clientId}\n `);\n }\n\n /** Drop a client from every channel — used when its socket closes. */\n async removeClient(clientId: string): Promise<void> {\n await this.db.execute(sql`\n DELETE FROM rebase.channel_presence WHERE client_id = ${clientId}\n `);\n }\n\n /** The global roster for a channel. */\n async roster(channel: string): Promise<Record<string, Record<string, unknown>>> {\n const result = await this.db.execute(sql`\n SELECT client_id, state FROM rebase.channel_presence WHERE channel = ${channel}\n `);\n\n const presences: Record<string, Record<string, unknown>> = {};\n for (const row of result.rows as Array<{ client_id: string; state: Record<string, unknown> | null }>) {\n presences[row.client_id] = row.state ?? {};\n }\n return presences;\n }\n\n /**\n * Reap rows this instance is not responsible for and that have gone quiet.\n *\n * Own rows are excluded because the in-process sweep already handles them —\n * and handles them better, since it can tell \"the socket is gone\" from \"the\n * heartbeat is late\". What is left is precisely the interesting case: rows\n * written by an instance that is no longer writing.\n *\n * Returns what was removed, so the caller can announce it.\n */\n async sweepStale(ttlMs: number): Promise<PresenceRow[]> {\n const result = await this.db.execute(sql`\n DELETE FROM rebase.channel_presence\n WHERE instance_id <> ${this.instanceId}\n AND last_seen < NOW() - MAKE_INTERVAL(secs => ${ttlMs / 1000})\n RETURNING channel, client_id, state\n `);\n\n return (result.rows as Array<{ channel: string; client_id: string; state: Record<string, unknown> | null }>)\n .map(row => ({ channel: row.channel, clientId: row.client_id, state: row.state ?? {} }));\n }\n\n /**\n * Remove every row this instance owns. Called on graceful shutdown so a\n * rolling deploy does not leave a TTL window of ghosts in every roster.\n */\n async removeInstance(): Promise<void> {\n await this.db.execute(sql`\n DELETE FROM rebase.channel_presence WHERE instance_id = ${this.instanceId}\n `);\n }\n}\n","/**\n * Runtime pieces of the channel bus that are not the contract itself.\n *\n * The interface, the frame shape and the implementer's contract live in\n * `@rebasepro/types` (`types/channel_bus.ts`), so a transport shipped as its own\n * package — a Redis one, say — depends on the contract and not on this database\n * adapter. They are re-exported here for convenience: code already importing\n * from the adapter should not have to know where the types are declared.\n */\n\nimport type { ChannelBusFrame } from \"@rebasepro/types\";\n\nexport type {\n ChannelBus,\n ChannelBusFrame,\n ChannelBusHandler,\n ChannelBusConfig,\n ChannelBusSetting\n} from \"@rebasepro/types\";\nexport { isChannelBusInstance } from \"@rebasepro/types\";\n\n/**\n * The default: no cross-instance delivery at all.\n *\n * This is what every deployment ran before the bus existed, and what a\n * single-instance deployment should keep running — `publish` resolves without\n * touching the network, so the broadcast path is the same handful of `ws.send`\n * calls it always was.\n */\nexport class MemoryChannelBus {\n readonly kind = \"memory\" as const;\n readonly maxFrameBytes = Infinity;\n\n async start(): Promise<void> { /* nothing to connect */ }\n\n async publish(): Promise<void> { /* nowhere to publish to */ }\n\n async stop(): Promise<void> { /* nothing to release */ }\n}\n\n/** Encoded size of a frame, for a transport's size check. */\nexport function frameByteLength(frame: ChannelBusFrame): number {\n return Buffer.byteLength(JSON.stringify(frame), \"utf8\");\n}\n","/**\n * Channel bus over Postgres LISTEN/NOTIFY.\n *\n * Chosen because it needs nothing that a Rebase deployment does not already\n * have — the same database, the same direct URL the CDC listener uses. Three\n * properties of `NOTIFY` shape everything below:\n *\n * - **8000 bytes per payload.** Presence and cursors fit with room to spare; a\n * scene snapshot does not. A message on a *retained* channel never travels\n * here at all, whatever its size: the statement that numbers it notifies a\n * pointer — the body is already in `rebase.channel_messages` with a sequence\n * number, so the receiver reads it back (`ChannelHistoryStore.append`). That\n * is the same trick the entity path uses (notify an address, refetch the\n * row), applied to a different table, and for the same reason: any database\n * login can `LISTEN`. On an ephemeral channel there is nothing to point at,\n * so the frame carries the message, and an oversized one is refused loudly\n * instead of reaching some instances and not others.\n *\n * - **A notify is a query on the primary database.** Not a slow one, but it\n * competes with the application's real queries, and that — not throughput —\n * is what actually limits this transport. Measured, it carried ~10k\n * cross-instance messages/second and stayed flat out to eight instances; what\n * it should not do is spend 10k queries/second of the database's budget on\n * cursor movement. Hence the batching below.\n *\n * - **Delivery is best-effort.** Retained channels repair themselves through\n * the client's history replay, so a lost frame costs a live update rather\n * than correctness. That is what makes coalescing safe.\n */\n\nimport { sql } from \"drizzle-orm\";\nimport { NodePgDatabase } from \"drizzle-orm/node-postgres\";\nimport { logger } from \"@rebasepro/server\";\nimport { PgNotifyListener, type PgNotifyListenerStatus } from \"../pg-notify-listener\";\nimport { ChannelBus, ChannelBusFrame, ChannelBusHandler, frameByteLength } from \"./ChannelBus\";\nimport { unref } from \"@rebasepro/utils\";\n\n/** NOTIFY channel carrying channel-bus frames. */\nexport const CHANNEL_BUS_NOTIFY_CHANNEL = \"rebase_channel_bus\";\n\n/**\n * Postgres refuses a NOTIFY payload of 8000 bytes or more. The margin below it\n * is for nothing in particular — it is there so that a payload which passes this\n * check cannot fail at the server for being a few bytes over.\n */\nexport const PG_NOTIFY_MAX_PAYLOAD_BYTES = 7500;\n\n/**\n * How long a batching window stays open.\n *\n * Ten milliseconds is below the threshold where a human notices a cursor lag,\n * and it is the difference between one query per message and one query per\n * window under load. Set to 0 to disable coalescing entirely.\n */\nexport const DEFAULT_BATCH_WINDOW_MS = 10;\n\n/** JSON overhead per frame inside a batch: the wrapping array's comma. */\nconst BATCH_SEPARATOR_BYTES = 1;\n/** JSON overhead of the batch envelope itself: `{\"batch\":[]}`. */\nconst BATCH_ENVELOPE_BYTES = 12;\n\ninterface PendingFrame {\n frame: ChannelBusFrame;\n bytes: number;\n resolve: () => void;\n reject: (error: unknown) => void;\n}\n\nexport class PostgresChannelBus implements ChannelBus {\n readonly kind = \"postgres\" as const;\n readonly maxFrameBytes = PG_NOTIFY_MAX_PAYLOAD_BYTES;\n\n private listener?: PgNotifyListener;\n private readonly batchWindowMs: number;\n\n /**\n * Frames waiting for the current window to close.\n *\n * The window is opened by a publish that found none open, and that publish\n * is sent *immediately* rather than joining a batch — see {@link publish}.\n */\n private pending: PendingFrame[] = [];\n private pendingBytes = BATCH_ENVELOPE_BYTES;\n private windowTimer?: ReturnType<typeof setTimeout>;\n private stopped = false;\n\n constructor(\n private readonly db: NodePgDatabase<Record<string, unknown>>,\n private readonly connectionString: string,\n options: { batchWindowMs?: number } = {}\n ) {\n const configured = options.batchWindowMs;\n this.batchWindowMs = typeof configured === \"number\" && configured >= 0\n ? configured\n : DEFAULT_BATCH_WINDOW_MS;\n }\n\n async start(handler: ChannelBusHandler): Promise<void> {\n this.stopped = false;\n this.listener = new PgNotifyListener({\n connectionString: this.connectionString,\n channel: CHANNEL_BUS_NOTIFY_CHANNEL,\n logLabel: \"[ChannelBus]\",\n onPayload: async (payload) => {\n const frames = parseChannelBusPayload(payload);\n if (!frames.length) {\n logger.warn(\"⚠️ [ChannelBus] Dropping unparseable payload.\");\n return;\n }\n // In order: a batch preserves the sender's publish order, and a\n // retained channel's consumers rely on it.\n for (const frame of frames) await handler(frame);\n }\n });\n await this.listener.start();\n }\n\n /**\n * Publish, coalescing under load.\n *\n * The window is *leading edge*: a publish arriving when no window is open is\n * sent straight away and opens one, so an idle channel pays no added latency\n * at all. Frames arriving while it is open are collected and leave together\n * when it closes. The effect is that cost tracks elapsed time rather than\n * message count — one query per window instead of one per message — which is\n * the same shape as the retention pruning throttle, for the same reason.\n *\n * The returned promise settles when the frame has actually left, not when it\n * was queued, so the contract (\"reaches the other instances, or rejects\")\n * still holds.\n */\n async publish(frame: ChannelBusFrame): Promise<void> {\n if (this.batchWindowMs === 0 || this.stopped) {\n await this.send([frame]);\n return;\n }\n\n if (!this.windowTimer) {\n this.openWindow();\n await this.send([frame]);\n return;\n }\n\n const bytes = frameByteLength(frame) + BATCH_SEPARATOR_BYTES;\n\n // A batch is one NOTIFY payload, so the 8 KB ceiling applies to the\n // whole batch. Send what we have rather than let the frame push it over.\n if (this.pending.length && this.pendingBytes + bytes > this.maxFrameBytes) {\n this.flush();\n }\n\n return new Promise<void>((resolve, reject) => {\n this.pending.push({ frame, bytes, resolve, reject });\n this.pendingBytes += bytes;\n });\n }\n\n /** The LISTEN connection's state, for `/health` — see {@link PgNotifyListener}. */\n status(): PgNotifyListenerStatus | undefined {\n return this.listener?.status();\n }\n\n async stop(): Promise<void> {\n this.stopped = true;\n if (this.windowTimer) {\n clearTimeout(this.windowTimer);\n this.windowTimer = undefined;\n }\n // Anything still queued belongs to clients that are already waiting on\n // it; dropping it on shutdown would be a silent loss where a flush costs\n // one more query.\n this.flush();\n await this.listener?.stop();\n this.listener = undefined;\n }\n\n private openWindow(): void {\n this.windowTimer = setTimeout(() => {\n this.windowTimer = undefined;\n if (this.pending.length) {\n // Still busy: send this window's frames and open the next one,\n // so a sustained stream keeps costing one query per window.\n this.flush();\n this.openWindow();\n }\n // Otherwise leave it closed, so the next publish after a quiet\n // moment goes out immediately.\n }, this.batchWindowMs);\n\n // Housekeeping must never hold the process open.\n unref(this.windowTimer);\n }\n\n /** Send everything queued and settle the promises waiting on it. */\n private flush(): void {\n if (!this.pending.length) return;\n\n const batch = this.pending;\n this.pending = [];\n this.pendingBytes = BATCH_ENVELOPE_BYTES;\n\n this.send(batch.map(p => p.frame))\n .then(() => { for (const p of batch) p.resolve(); })\n .catch((error) => { for (const p of batch) p.reject(error); });\n }\n\n /**\n * One NOTIFY.\n *\n * A single frame goes out in the plain, unwrapped shape. That is not just\n * economy: during a rolling deploy an instance running the previous build\n * understands only that shape, and low-rate traffic — presence, the tail of\n * a session — is exactly what is flowing while pods restart. Batching only\n * appears under load, which shrinks the mixed-version window to almost\n * nothing.\n */\n private async send(frames: ChannelBusFrame[]): Promise<void> {\n if (!frames.length) return;\n const payload = frames.length === 1\n ? JSON.stringify(frames[0])\n : JSON.stringify({ batch: frames });\n\n await this.db.execute(sql`SELECT pg_notify(${CHANNEL_BUS_NOTIFY_CHANNEL}, ${payload})`);\n }\n}\n\n/**\n * Parse a bus payload into the frames it carries.\n *\n * Accepts both wire shapes — a bare frame and a `{ batch: [...] }` envelope —\n * so an instance on the new build understands one on the old. Returns an empty\n * array for anything unrecognisable: a malformed or future-versioned message\n * must never take the listener down.\n */\nexport function parseChannelBusPayload(payload: string): ChannelBusFrame[] {\n let parsed: unknown;\n try {\n parsed = JSON.parse(payload);\n } catch {\n return [];\n }\n if (!parsed || typeof parsed !== \"object\") return [];\n\n const batch = (parsed as { batch?: unknown }).batch;\n if (Array.isArray(batch)) {\n return batch\n .map(entry => coerceFrame(entry))\n .filter((frame): frame is ChannelBusFrame => frame !== null);\n }\n\n const single = coerceFrame(parsed);\n return single ? [single] : [];\n}\n\n/**\n * Parse a single bus frame, returning null for anything that is not a frame we\n * understand.\n */\nexport function parseChannelBusFrame(payload: string): ChannelBusFrame | null {\n try {\n return coerceFrame(JSON.parse(payload));\n } catch {\n return null;\n }\n}\n\nfunction coerceFrame(value: unknown): ChannelBusFrame | null {\n if (!value || typeof value !== \"object\") return null;\n\n const obj = value as Record<string, unknown>;\n const sid = typeof obj.sid === \"string\" ? obj.sid : undefined;\n const channel = typeof obj.channel === \"string\" ? obj.channel : undefined;\n if (!sid || !channel) return null;\n\n switch (obj.kind) {\n case \"broadcast\":\n if (typeof obj.event !== \"string\") return null;\n return {\n kind: \"broadcast\",\n sid,\n channel,\n event: obj.event,\n from: typeof obj.from === \"string\" ? obj.from : undefined,\n seq: typeof obj.seq === \"number\" ? obj.seq : undefined,\n payload: obj.payload\n };\n case \"broadcast_ref\":\n if (typeof obj.seq !== \"number\") return null;\n return {\n kind: \"broadcast_ref\",\n sid,\n channel,\n from: typeof obj.from === \"string\" ? obj.from : undefined,\n seq: obj.seq\n };\n case \"presence_diff\":\n return {\n kind: \"presence_diff\",\n sid,\n channel,\n joins: (obj.joins ?? {}) as Record<string, Record<string, unknown>>,\n leaves: (obj.leaves ?? {}) as Record<string, Record<string, unknown>>\n };\n default:\n return null;\n }\n}\n","/**\n * Resolution of the channel bus from config, environment, or a supplied instance.\n *\n * Opt-in, like every other cross-cutting realtime switch here: with nothing\n * configured a deployment gets the memory bus and behaves exactly as it did\n * before this existed. Unlike `REALTIME_CDC=auto`, there is no \"try it and see\"\n * default — a bus changes where messages go, and quietly turning on a Postgres\n * NOTIFY per broadcast because a direct URL happened to be set is not a\n * decision to make on the user's behalf.\n *\n * Two transports ship, and neither adds a service to a deployment. A third is\n * not a code change here: `realtime.bus` also accepts an already-constructed\n * {@link ChannelBus}, so a transport published as its own package plugs in\n * without this file learning about it. See `@rebasepro/types` →\n * `types/channel_bus.ts` for the contract such a package implements.\n */\n\nimport { NodePgDatabase } from \"drizzle-orm/node-postgres\";\nimport { isChannelBusInstance, type ChannelBus, type ChannelBusConfig, type ChannelBusSetting } from \"@rebasepro/types\";\nimport { logger } from \"@rebasepro/server\";\nimport { MemoryChannelBus } from \"./ChannelBus\";\nimport { PostgresChannelBus } from \"./PostgresChannelBus\";\n\nexport * from \"./ChannelBus\";\nexport {\n PostgresChannelBus,\n CHANNEL_BUS_NOTIFY_CHANNEL,\n PG_NOTIFY_MAX_PAYLOAD_BYTES,\n DEFAULT_BATCH_WINDOW_MS,\n parseChannelBusFrame,\n parseChannelBusPayload\n} from \"./PostgresChannelBus\";\n\nexport interface ChannelBusDeps {\n db: NodePgDatabase<Record<string, unknown>>;\n /**\n * Direct (non-pooled) Postgres URL for the LISTEN client. `LISTEN` is\n * session state, so behind PgBouncer in transaction mode this must be the\n * database itself and not the pooler.\n */\n directUrl?: string;\n}\n\n/**\n * Merge `REALTIME_CHANNEL_BUS` into the configured bus.\n *\n * The environment wins over a *named* built-in, so the transport can be changed\n * per deployment without a rebuild — the same reason `REALTIME_CDC` is an env\n * var. It does **not** win over a supplied instance: the env var can only name\n * transports this package knows how to construct, so honouring it there would\n * mean silently discarding the object the application handed us.\n */\nexport function resolveChannelBusSetting(configured?: ChannelBusSetting): ChannelBusSetting {\n const raw = (process.env.REALTIME_CHANNEL_BUS || \"\").trim().toLowerCase();\n\n if (isChannelBusInstance(configured)) {\n if (raw && raw !== configured.kind) {\n logger.warn(\n `⚠️ [ChannelBus] REALTIME_CHANNEL_BUS=\"${raw}\" is ignored because realtime.bus was given a ` +\n `\"${configured.kind}\" transport instance directly. Remove one of the two to make the intent clear.`\n );\n }\n return configured;\n }\n\n if (!raw) return configured ?? { type: \"memory\" };\n\n if (raw !== \"memory\" && raw !== \"postgres\") {\n logger.warn(\n `⚠️ [ChannelBus] Unknown REALTIME_CHANNEL_BUS value \"${raw}\" — expected memory|postgres, or pass a ` +\n \"ChannelBus instance as realtime.bus for a transport that ships separately. Falling back to the \" +\n \"configured bus.\"\n );\n return configured ?? { type: \"memory\" };\n }\n\n // Keep the configured options (an explicit connection string) when the env\n // var only restates the type it was already set to.\n if (configured?.type === raw) return configured;\n return raw === \"memory\" ? { type: \"memory\" } : { type: \"postgres\" };\n}\n\n/**\n * Produce the bus a setting asks for.\n *\n * An instance is handed straight back — constructing it was the application's\n * job, and this function has nothing to add. A named built-in that turns out to\n * be unusable degrades to the memory bus, with the reason logged, rather than\n * throwing: a misconfigured bus should cost a deployment its cross-instance\n * fan-out, not its ability to boot.\n */\nexport function createChannelBus(setting: ChannelBusSetting, deps: ChannelBusDeps): ChannelBus {\n if (isChannelBusInstance(setting)) return setting;\n\n switch (setting.type) {\n case \"postgres\": {\n const connectionString = setting.connectionString || deps.directUrl;\n if (!connectionString) {\n logger.warn(\n \"⚠️ [ChannelBus] realtime.bus is \\\"postgres\\\" but no direct database URL is available \" +\n \"(set DATABASE_DIRECT_URL or realtime.bus.connectionString) — channel broadcast and presence \" +\n \"stay per-instance.\"\n );\n return new MemoryChannelBus();\n }\n return new PostgresChannelBus(deps.db, connectionString, {\n batchWindowMs: setting.batchWindowMs\n });\n }\n case \"memory\":\n default:\n return new MemoryChannelBus();\n }\n}\n","import { WebSocket } from \"ws\";\nimport { EventEmitter } from \"events\";\nimport { randomUUID } from \"crypto\";\nimport { DataService } from \"./dataService\";\n\nimport { ANONYMOUS_USER_ID, FetchCollectionProps, ListenCollectionProps, ListenOneProps, DataDriver, CollectionUpdateMessage, CollectionUpdateMeta, IncludeSpec, SingleUpdateMessage, CollectionPatchMessage, WebSocketMessage, FilterValues, LogicalCondition, OrderByTuple, CollectionConfig, RebaseCallContext, resolveClientListLimit, ListLimitError } from \"@rebasepro/types\";\nimport { NodePgDatabase } from \"drizzle-orm/node-postgres\";\nimport { sql as drizzleSql } from \"drizzle-orm\";\nimport { RealtimeProvider, CollectionSubscriptionConfig, SingleSubscriptionConfig, DrizzleClient } from \"../interfaces\";\nimport { PostgresCollectionRegistry } from \"../collections/PostgresCollectionRegistry\";\nimport { buildPropertyCallbacks, getTableName, normalizeDriverOrderBy, OrderBySpecError, parseOrderBySpecStrict, requireCallbackCollection } from \"@rebasepro/common\";\nimport { applyAuthContext } from \"../security/rls-enforcement\";\nimport { withFieldViewer } from \"./field-viewer\";\nimport { buildJunctionLinkMap, type JunctionLink } from \"./cdc/junction-tables\";\nimport { collectionKeyColumns } from \"./cdc/identity-columns\";\nimport { ApiError, logger, rawQueryLoggingEnabled } from \"@rebasepro/server\";\nimport { assertReadRequestReadable } from \"./read-field-access\";\nimport { sanitizeErrorForClient } from \"../utils/pg-error-utils\";\nimport { CdcListener, type CdcChangeEvent } from \"./cdc/CdcListener\";\nimport { PgNotifyListener } from \"./pg-notify-listener\";\nimport { terminateIfBacklogged } from \"./socket-liveness\";\nimport { deriveRowAddress, getPrimaryKeys, type PrimaryKeyInfo } from \"./collection-helpers\";\nimport { isNestedPath } from \"./nested-path\";\nimport { ChannelHistoryStore, type ResolvedRetention } from \"./channel-history\";\nimport { ChannelPresenceStore } from \"./channel-presence\";\nimport { CHANNEL_BUS_NOTIFY_CHANNEL, ChannelBus, ChannelBusFrame, MemoryChannelBus, PostgresChannelBus, frameByteLength } from \"./channel-bus\";\nimport type { ChannelHistoryEntry, ChannelRetentionRule, RealtimeListenerHealth, User } from \"@rebasepro/types\";\nimport { unref } from \"@rebasepro/utils\";\n\n/** Channel name used for Postgres LISTEN/NOTIFY cross-instance realtime. */\nconst PG_NOTIFY_CHANNEL = \"rebase_entity_changes\";\n\n/**\n * Auth context stored per-subscription so real-time refetches respect RLS.\n * Mirrors the session variables set by PostgresBackendDriver.withAuth().\n */\nexport interface SubscriptionAuthContext {\n uid: string;\n roles: string[];\n /**\n * Whether this session is a guest — anonymous sign-in rather than an\n * account. Carried so a refetch's policies see the same principal the\n * initial fetch did; without it a rule that excludes guests would filter on\n * the REST read and not on the frames that follow, which is the shape that\n * makes a realtime leak invisible from the surface people test.\n */\n isAnonymous?: boolean;\n /**\n * The custom claims on the socket's token, for the same reason\n * `isAnonymous` is here: a refetch has to evaluate the same policies\n * against the same principal the initial fetch did. A tenancy policy reads\n * a claim, so a subscription that carried none would answer every frame\n * from the tenant of nobody — which is no rows, silently, on a collection\n * whose first page loaded fine.\n */\n claims?: Record<string, unknown>;\n}\n\n/**\n * A driver that can build a callback context inside a transaction it did not\n * open. `PostgresBackendDriver.callContextWithin` is the one implementation.\n */\ninterface CallContextMinter {\n callContextWithin(tx: DrizzleClient, user: User): RebaseCallContext;\n}\n\nfunction mintsCallContexts(driver: DataDriver | undefined): driver is DataDriver & CallContextMinter {\n return driver !== undefined && \"callContextWithin\" in driver && typeof driver.callContextWithin === \"function\";\n}\n\n/**\n * The context a subscription frame's `afterRead` hooks run with.\n *\n * It used to be assembled here by hand — `{ user, driver, data }` cast to\n * `RebaseCallContext` — and the cast hid two faults. `data` was the realtime\n * service's own driver, the base one on the owner connection, outside the\n * transaction this frame's rows were read in: a hook enriching rows through\n * `context.data` bypassed RLS on every frame, while the REST read of the same\n * rows, whose hooks run on the caller's transaction, did not. And `client` and\n * `storageSource`, which the type declares required, were missing, so a hook\n * that signs a URL or invokes a function worked over REST and failed every\n * `.listen()`. The driver now builds it, bound to `tx`, the way the REST path\n * builds it.\n *\n * Refuses rather than falling back: the fallback IS the unscoped context.\n */\nfunction callbackContextWithin(driver: DataDriver | undefined, tx: DrizzleClient, auth: SubscriptionAuthContext): RebaseCallContext {\n if (!mintsCallContexts(driver)) {\n throw new Error(\"This driver cannot run afterRead hooks inside the subscriber's transaction.\");\n }\n return driver.callContextWithin(tx, {\n uid: auth.uid,\n roles: auth.roles,\n isAnonymous: auth.isAnonymous === true,\n // What a socket does not carry, stated rather than invented.\n displayName: null,\n email: null,\n photoURL: null,\n providerId: \"realtime\",\n ...(auth.claims ? { claims: auth.claims } : {})\n });\n}\n\n/** What a channel frame is asking to do. */\nexport type ChannelAction = \"join\" | \"broadcast\" | \"presence\" | \"history\";\n\n/** Everything an authorizer is told about the frame it is asked to allow. */\nexport interface ChannelAuthorizationRequest {\n /** The channel the frame names, exactly as the client wrote it. */\n channel: string;\n action: ChannelAction;\n /** The socket, not the principal — one user may hold several. */\n clientId: string;\n /** The socket's authenticated principal, or the anonymous one. */\n user?: SubscriptionAuthContext;\n}\n\n/**\n * The extension point for channel access rules.\n *\n * **This is deliberately not a product API yet.** The rule *language* — a\n * config key, a per-pattern DSL, how it composes with `securityRules` — is an\n * open design question (see `docs/channel-authorization.md`), and\n * inventing one here would be inventing the answer. What exists is the single\n * place every channel frame passes through, so that whatever shape the rules\n * eventually take has exactly one seam to plug into and no arm of the switch\n * can be forgotten.\n *\n * Returning `false` — or throwing — refuses the frame. It is consulted *after*\n * the membership floor below, so an authorizer can only ever narrow access,\n * never widen it.\n */\nexport type ChannelAuthorizer = (request: ChannelAuthorizationRequest) => boolean | Promise<boolean>;\n\ninterface DataDriverWithData extends DataDriver {\n data: unknown;\n}\n\ntype RealTimeListenCollectionProps = ListenCollectionProps & {\n subscriptionId: string\n};\n\n/**\n * The narrowing a collection subscription was created with, kept so that every\n * refetch answers the same query the initial fetch did.\n *\n * Named once because it used to be written out inline in five places, and a\n * field missing from one of them is accepted over the wire and then silently\n * ignored: `offset` was declared on the incoming props and never stored, so a\n * live list on page three served page one, and `logical` was never stored\n * either, so an `or(...)` subscription was pushed every row in the table.\n */\ntype StoredCollectionRequest = {\n filter?: Record<string, unknown>;\n logical?: LogicalCondition;\n orderBy?: string | OrderByTuple[];\n order?: \"desc\" | \"asc\";\n limit?: number;\n offset?: number;\n startAfter?: Record<string, unknown>;\n databaseId?: string;\n searchString?: string;\n /** Ask each row which declared search field matched — populates `_matches`. */\n searchExplain?: boolean;\n /**\n * Relations to load, exactly as a REST list would.\n *\n * A subscription could not name any, and the refetch loaded **every**\n * relation because it passed none — so `find()` returned a row with a\n * foreign key and `listen()` returned the same row with a nested object\n * where that key was. Same query, two shapes, and a client rendering both\n * saw the row change the moment a write landed.\n */\n include?: IncludeSpec;\n /** Columns to return — the same projection `?fields=` asks for. */\n fields?: string[];\n /** `SELECT DISTINCT` over the projection. */\n distinct?: boolean;\n};\n\ntype RealTimeListenEntityProps = ListenOneProps & { subscriptionId: string };\n\n/**\n * What an in-process listener asks for: the read, and who it reads as.\n * See {@link RealtimeService.startDataDriverSubscription}.\n */\nexport type DataDriverSubscriptionRequest = {\n path: string;\n /** The subscriber. Absent reads as the anonymous user. */\n authContext?: SubscriptionAuthContext;\n onError?: (error: unknown) => void;\n} & (\n | { type: \"collection\"; collectionRequest: StoredCollectionRequest }\n | { type: \"single\"; id: string | number }\n);\n\n/**\n * A registered subscription, plus the two counters that order its deliveries.\n *\n * Every update a subscription delivers is a full re-fetch, and more than one\n * thing starts one for the same subscription without coordinating: the initial\n * fetch at subscribe time, and a debounced refetch per notification (app\n * mutation, cross-instance NOTIFY, or CDC). A fetch that started earlier can\n * finish later, and the delivery replaces everything the subscriber has — so\n * the subscriber goes back to the state before the change and stays there,\n * silently, until the next write to that collection.\n *\n * The debounce is not a fix for this. It collapses a burst into one refetch and\n * does nothing about two refetches that overlap: notification A fires its timer\n * and starts fetch A, notification B arrives while A is still in flight, and B's\n * timer fires and starts fetch B regardless. See class 44 in\n * `docs/bug-classes.md`.\n *\n * `started` is taken before the work, `delivered` after it — which makes the\n * last delivery *started* the last one *delivered*.\n */\ntype Subscription = {\n /** The id the client named it by — what every frame for it carries. */\n subscriptionId: string;\n clientId: string;\n /** The {@link SubscriptionGroup} whose refetch answers it. */\n groupKey: string;\n type: \"collection\" | \"single\";\n path: string;\n id?: string | number;\n // Store full collection request parameters for proper refetching\n collectionRequest?: StoredCollectionRequest;\n // Auth context for RLS — when set, refetches run in a transaction\n // with set_config('app.uid', ...) / set_config('app.user_roles', ...)\n authContext?: SubscriptionAuthContext;\n /**\n * An in-process listener's error callback, called when a refetch fails.\n * Kept on the subscription so it goes wherever the subscription goes: a\n * replaced or cancelled one takes its listener with it.\n */\n onError?: (error: unknown) => void;\n /** How many deliveries have been started for this subscription. */\n started: number;\n /** The highest started-sequence that has already reached the subscriber. */\n delivered: number;\n};\n\n/**\n * What `beginDelivery` returns. Called, it claims the slot for a delivery;\n * `mayReportFailure()` asks whether this delivery's failure may be reported.\n */\ntype DeliveryCheck = (() => boolean) & { mayReportFailure: () => boolean };\n\n/**\n * Subscriptions one read answers: the same path, the same request, read as the\n * same principal.\n *\n * Every delivery is a refetch under the subscriber's own scope, which is what\n * keeps a frame from carrying a row its reader may not see. It used to be one\n * refetch — and, for a list, one count — per subscription, so a write to a\n * list a thousand people had open cost two thousand transactions, queued on the\n * pool every REST request shares. Subscriptions whose refetch would run the\n * same query as the same principal get the same rows, so they share one: the\n * group's. A frame is still built from a read under the reader's own identity;\n * only who else asked the identical question changes.\n *\n * An in-process listener is a group of one — its callback receives the rows\n * themselves, and two listeners must not share an array either may mutate.\n */\ntype SubscriptionGroup = {\n key: string;\n path: string;\n type: \"collection\" | \"single\";\n /** The row a single-row group reads, as a string. */\n id?: string;\n collectionRequest?: StoredCollectionRequest;\n authContext?: SubscriptionAuthContext;\n /** The subscriptions in the group, by internal key. */\n members: Set<string>;\n /**\n * Who the next refetch is for: `\"all\"` after a change on the path, or the\n * members that asked on their own — a re-scoped subscription, say.\n */\n pending: Set<string> | \"all\";\n /** When the last change arrived; the refetch waits for a quiet debounce window. */\n lastChangeAt: number;\n timer?: ReturnType<typeof setTimeout>;\n};\n\n/** `JSON.stringify` with object keys sorted, so equal requests have one spelling. */\nfunction canonicalJson(value: unknown): string {\n return JSON.stringify(value, (_key, nested: unknown) => {\n if (!nested || typeof nested !== \"object\" || Array.isArray(nested)) return nested;\n return Object.fromEntries(Object.entries(nested).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)));\n });\n}\n\n/**\n * The default ceiling on subscriptions one socket may hold.\n *\n * Each collection subscription is a refetch on every write to its collection;\n * without a ceiling one socket could open tens of thousands and turn every\n * write anyone made into as many transactions. The SDK shares identical\n * subscriptions on a socket, so a real page holds one per distinct list or\n * record on screen — the admin panel's tables, forms and reference previews\n * stay well under this. See `REALTIME_MAX_SUBSCRIPTIONS_PER_SOCKET`.\n */\nexport const DEFAULT_MAX_SUBSCRIPTIONS_PER_SOCKET = 1000;\n\n/**\n * Read a subscription ceiling, refusing anything that is not a positive whole\n * number. A typo here does not fall back to a default: the ceiling is a limit\n * someone chose, and silently running with another one is how it stops\n * meaning anything.\n */\nexport function parseMaxSubscriptionsPerSocket(value: unknown, source: string): number {\n const parsed = typeof value === \"string\" && value.trim() !== \"\" ? Number(value.trim()) : value;\n if (typeof parsed !== \"number\" || !Number.isInteger(parsed) || parsed < 1) {\n throw new Error(\n `${source} must be a positive whole number of subscriptions per socket — got ${JSON.stringify(value)}.`\n );\n }\n return parsed;\n}\n\n/**\n * How long a client gets to answer the close frame at shutdown before its\n * socket is dropped. `ws` would otherwise wait its own 30s close timeout, which\n * is longer than the whole shutdown budget.\n */\nconst WEBSOCKET_CLOSE_GRACE_MS = 1_000;\n\n/**\n * Close each socket with 1001, and terminate the ones still open after\n * `graceMs`. Nothing here waits: the HTTP server's close() is what waits for\n * the sockets to end.\n */\nfunction closeWebSockets(sockets: WebSocket[], graceMs: number): void {\n for (const ws of sockets) {\n if (ws.readyState === WebSocket.OPEN) ws.close(1001, \"server shutting down\");\n // A no-op on a socket that has closed by the time it fires.\n if (ws.readyState !== WebSocket.CLOSED) unref(setTimeout(() => ws.terminate(), graceMs));\n }\n}\n\n/**\n * PostgreSQL-specific realtime service.\n * Handles WebSocket connections and subscriptions for real-time row updates.\n *\n * Implements the RealtimeProvider interface for database abstraction.\n */\nexport class RealtimeService extends EventEmitter implements RealtimeProvider {\n /**\n * Declares to the multi-engine router that channel frames can be handled\n * here. Read by `createRoutedRealtimeService`, which otherwise would have to\n * guess — and guessed \"the default provider\", whichever engine that is.\n */\n public readonly supportsChannels = true;\n\n private clients = new Map<string, WebSocket>();\n\n // Broadcast channels: channel name → set of client IDs\n private channels = new Map<string, Set<string>>();\n\n // Presence: channel → Map<clientId, { state, lastSeen }>\n private presence = new Map<string, Map<string, { state: Record<string, unknown>; lastSeen: number }>>();\n\n /**\n * Ordered, replayable history for channels that opt into it.\n *\n * Undefined until {@link configureChannelHistory} is called, and inert even\n * then unless retention rules were supplied — so presence and ephemeral\n * notification channels never touch it. See `channel-history.ts`.\n */\n private channelHistory?: ChannelHistoryStore;\n\n /**\n * One promise chain per retained channel, so that assigning a sequence\n * number and fanning the message out happen in the same order for every\n * message on that channel.\n *\n * Without it, two concurrent broadcasts can be numbered 4 and 5 by the\n * database and still reach subscribers as 5 then 4 — live order and replay\n * order would disagree, which is exactly the divergence sequence numbers\n * are supposed to rule out. Keyed by channel, so unrelated channels never\n * wait on each other.\n */\n private channelSendQueues = new Map<string, Promise<void>>();\n /** The receiving half of the same ordering: retained bus frames per channel, one at a time. */\n private channelReceiveQueues = new Map<string, Promise<void>>();\n\n /**\n * Cross-instance transport for channel frames and presence.\n *\n * Defaults to the memory bus, which publishes nowhere — so a single-instance\n * deployment runs the same fan-out it always did, with one resolved promise\n * per broadcast for company. See `channel-bus/ChannelBus.ts`.\n */\n private bus: ChannelBus = new MemoryChannelBus();\n\n /**\n * The shared presence roster, present only when a real bus is active.\n *\n * Fan-out alone is not enough for presence: `presence_state` has to answer\n * with everyone in the channel, and per-process maps can only answer for\n * this replica's clients. See `channel-presence.ts`.\n */\n private presenceStore?: ChannelPresenceStore;\n\n /** Sweeps roster rows left behind by instances that stopped heartbeating. */\n private presenceSweepInterval?: ReturnType<typeof setInterval>;\n\n /**\n * Channels whose oversized ephemeral broadcasts have already been reported,\n * so a hot channel logs the problem once rather than once per message.\n */\n private oversizedBroadcastWarned = new Set<string>();\n\n /**\n * Optional narrowing on top of the membership floor — see\n * {@link ChannelAuthorizer}. Unset by default, which leaves membership as\n * the whole of the rule.\n */\n private channelAuthorizer?: ChannelAuthorizer;\n\n /**\n * Whether a notification from another instance has ever arrived.\n *\n * The entity LISTEN handler sees a foreign `sid` on every cross-instance\n * change, which is proof that this deployment runs more than one pod — the\n * one fact needed to tell \"the memory bus is fine here\" from \"broadcast and\n * presence silently reach a fraction of your users\".\n */\n private foreignInstanceSeen = false;\n\n /** So the multi-pod memory-bus warning is emitted once, not once per join. */\n private memoryBusWarned = false;\n\n private presenceInterval?: ReturnType<typeof setInterval>;\n private static readonly PRESENCE_TIMEOUT_MS = 30000; // 30s\n /** How often stale roster rows from other instances are reaped. */\n private static readonly PRESENCE_SWEEP_INTERVAL_MS = 10000; // 10s\n private dataService: DataService;\n /**\n * Every subscription, by {@link subscriptionKey} — the client's own id\n * qualified by the client that chose it.\n *\n * Keyed by the bare id, this was one namespace for every socket: two raw\n * clients that both counted from `\"sub-1\"` replaced each other's\n * subscription without a word, and either one's `unsubscribe` ended the\n * other's.\n */\n private _subscriptions = new Map<string, Subscription>();\n\n /** In-process listeners' callbacks, by subscription key. */\n private subscriptionCallbacks = new Map<string, (data: Record<string, unknown>[] | Record<string, unknown> | null) => void>();\n\n /** Each client's subscriptions, by key — for its ceiling and its disconnect. */\n private subscriptionsByClient = new Map<string, Set<string>>();\n\n /** Subscriptions one refetch answers — see {@link SubscriptionGroup}. */\n private groups = new Map<string, SubscriptionGroup>();\n /**\n * The collection groups on each path, and the single-row groups at each\n * `path` + id: what a change looks up, instead of walking every\n * subscription on the server once per changed row.\n */\n private collectionGroupsByPath = new Map<string, Set<SubscriptionGroup>>();\n private singleGroupsByAddress = new Map<string, Set<SubscriptionGroup>>();\n /**\n * Every path a subscription holds, with the collection it lands on and how\n * many hold it — the candidates `aliasPaths` considers, resolved once.\n */\n private subscribedPaths = new Map<string, { slug?: string; count: number }>();\n\n /**\n * How many subscriptions one socket may hold; set from config at boot. See\n * {@link DEFAULT_MAX_SUBSCRIPTIONS_PER_SOCKET}.\n */\n public maxSubscriptionsPerSocket = DEFAULT_MAX_SUBSCRIPTIONS_PER_SOCKET;\n\n private driver?: DataDriver;\n\n // ── Cross-instance LISTEN/NOTIFY ──\n /** Unique identifier for this process instance, used to skip own notifications. */\n private readonly instanceId = `inst_${randomUUID().slice(0, 8)}`;\n /** The LISTEN connection on {@link PG_NOTIFY_CHANNEL}, outside the Drizzle pool. */\n private crossInstanceListener?: PgNotifyListener;\n /** Whether cross-instance broadcasting is active. */\n private broadcasting = false;\n /** Debounce window (ms) for coalescing rapid row updates into a single correctness refetch. */\n private static readonly REFETCH_DEBOUNCE_MS = 300;\n\n // ── Database-level Change Data Capture (CDC) ──\n /** Dedicated LISTEN client for DB-level change events (undefined unless CDC is enabled). */\n private cdcListener?: CdcListener;\n /** Whether database-level CDC is the active cross-instance change source. */\n private cdcActive = false;\n /** Junction table → the child lists its rows belong to, built when CDC starts. */\n private junctionLinkMap?: Map<string, JunctionLink[]>;\n\n /** Reverse lookup: `schema.table` (and bare `table`) → collection, built when CDC starts. */\n private cdcTableMap?: Map<string, CollectionConfig>;\n /**\n * Short-lived record of `path/id` keys this instance just fanned out via the\n * app path (a Rebase-API mutation). When CDC echoes the same committed change\n * back to *this* instance, we suppress the duplicate — the change was already\n * delivered locally. Other instances have no such record, so they still\n * deliver the CDC event. External writes (psql, cron, SQL editor) never match\n * and always flow through. Keyed → expiry timestamp (ms).\n */\n private recentAppEmits = new Map<string, number>();\n /**\n * How long an app-emit key suppresses its own CDC echo: the refetch\n * debounce, and no longer.\n *\n * The mark cannot tell an echo from another writer's change to the same\n * row, and a save that touches no row of the table (a to-many relation\n * only, an empty payload) is announced with no echo ever coming to consume\n * it. Held for seconds, it swallowed the next change psql, a cron or\n * another instance made to that row. Inside the debounce nothing is lost:\n * the refetch the app emit scheduled has not started yet — its timer fires\n * no earlier than this — and it reads after any commit whose NOTIFY has\n * already arrived. An echo later than that costs one more refetch.\n */\n private static readonly CDC_DEDUP_WINDOW_MS = RealtimeService.REFETCH_DEBOUNCE_MS;\n\n constructor(private db: NodePgDatabase<any>, private registry: PostgresCollectionRegistry) {\n super();\n // No call-context provider, deliberately. This service is the\n // *no-driver* fallback — the branches below that cannot apply a database\n // auth context either — and without a driver there is no context to\n // build a `beforeQuery` hook from. So a hooked collection's read here\n // refuses rather than being served unnarrowed, which is the direction a\n // fallback has to fail in: the fallbacks above it are already written\n // around not handing out *more* than the path they stand in for.\n this.dataService = new DataService(db, registry);\n }\n\n /**\n * Restricted role that auth-scoped refetches run as (via `SET LOCAL ROLE`)\n * so RLS `select` policies bind. Set by the bootstrapper alongside\n * `PostgresBackendDriver.rlsUserRole`; undefined when the connection\n * is already subject to RLS natively. Without this, realtime refetches\n * would leak rows the initial (isolated) fetch correctly hid.\n */\n public rlsUserRole?: string;\n\n /**\n * Verbose subscription tracing.\n *\n * Through the logger at `debug`, behind the same switch that lifts the\n * `Failed query:` redaction: these lines quote paths, filters and refetch\n * SQL, and a `console.debug` gated on `NODE_ENV` alone wrote them to stdout\n * whatever `LOG_LEVEL` said, without passing through the one function where\n * redaction lives.\n */\n private debugLog(...args: unknown[]) {\n if (!rawQueryLoggingEnabled()) return;\n logger.debug(args.map(a => (typeof a === \"string\" ? a : JSON.stringify(a))).join(\" \"));\n }\n\n setDataDriver(driver: DataDriver) {\n this.driver = driver;\n }\n\n /**\n * The live subscriptions by the id their client gave them — for tests and\n * diagnostics. Two clients may use one id; this view shows the last.\n */\n get subscriptions(): ReadonlyMap<string, Subscription> {\n return new Map([...this._subscriptions.values()].map((subscription) => [subscription.subscriptionId, subscription]));\n }\n\n // =============================================================================\n // Subscription registry and refetch groups\n // =============================================================================\n\n /** The internal key of `subscriptionId` as `clientId` named it. */\n private subscriptionKey(clientId: string, subscriptionId: string): string {\n return `${clientId}\\u0000${subscriptionId}`;\n }\n\n /**\n * The group a subscription's refetch belongs to. Socket subscriptions that\n * would run the same read as the same principal share one; an in-process\n * listener is always alone — see {@link SubscriptionGroup}.\n */\n private groupKeyFor(\n key: string,\n shared: boolean,\n target: { type: \"collection\" | \"single\"; path: string; id?: string | number; collectionRequest?: StoredCollectionRequest; authContext?: SubscriptionAuthContext }\n ): string {\n if (!shared) return `listener\\u0000${key}`;\n const auth = target.authContext ?? { uid: ANONYMOUS_USER_ID, roles: [\"anon\"] };\n return canonicalJson({\n type: target.type,\n path: target.path,\n id: target.type === \"single\" ? String(target.id) : undefined,\n request: target.type === \"collection\" ? (target.collectionRequest ?? {}) : undefined,\n // Exactly what the refetch binds: `applyAuthContext` and the field\n // viewer read these and nothing else.\n uid: auth.uid,\n roles: auth.roles ?? [],\n isAnonymous: auth.isAnonymous === true,\n claims: auth.claims\n });\n }\n\n /**\n * Register (or replace) a subscription under `key`, in its group and in\n * every index that finds it.\n */\n private registerSubscription(key: string, subscription: Subscription): void {\n const previous = this._subscriptions.get(key);\n if (previous) this.leaveGroup(key, previous);\n this._subscriptions.set(key, subscription);\n\n let owned = this.subscriptionsByClient.get(subscription.clientId);\n if (!owned) this.subscriptionsByClient.set(subscription.clientId, owned = new Set());\n owned.add(key);\n\n let group = this.groups.get(subscription.groupKey);\n if (!group) {\n group = {\n key: subscription.groupKey,\n path: subscription.path,\n type: subscription.type,\n ...(subscription.type === \"single\" ? { id: String(subscription.id) } : {}),\n collectionRequest: subscription.collectionRequest,\n authContext: subscription.authContext,\n members: new Set(),\n pending: new Set(),\n lastChangeAt: 0\n };\n this.groups.set(group.key, group);\n const [index, address] = group.type === \"collection\"\n ? [this.collectionGroupsByPath, group.path]\n : [this.singleGroupsByAddress, `${group.path}\\u0000${group.id}`];\n let bucket = index.get(address);\n if (!bucket) index.set(address, bucket = new Set());\n bucket.add(group);\n }\n group.members.add(key);\n\n // Counted once per registration; `leaveGroup` above took the replaced\n // one's count back.\n const held = this.subscribedPaths.get(subscription.path);\n if (held) held.count++;\n else this.subscribedPaths.set(subscription.path, { slug: this.collectionSlugAt(subscription.path), count: 1 });\n }\n\n /** Remove the subscription under `key` from everything that holds it. */\n private dropSubscription(key: string): void {\n const subscription = this._subscriptions.get(key);\n this.subscriptionCallbacks.delete(key);\n if (!subscription) return;\n this._subscriptions.delete(key);\n this.leaveGroup(key, subscription);\n const owned = this.subscriptionsByClient.get(subscription.clientId);\n owned?.delete(key);\n if (owned?.size === 0) this.subscriptionsByClient.delete(subscription.clientId);\n }\n\n /** Take `subscription` out of its group and path count; a group left empty goes, timer and all. */\n private leaveGroup(key: string, subscription: Subscription): void {\n const held = this.subscribedPaths.get(subscription.path);\n if (held && --held.count <= 0) this.subscribedPaths.delete(subscription.path);\n\n const group = this.groups.get(subscription.groupKey);\n if (!group) return;\n group.members.delete(key);\n if (group.pending !== \"all\") group.pending.delete(key);\n if (group.members.size > 0) return;\n if (group.timer) clearTimeout(group.timer);\n this.groups.delete(group.key);\n const [index, address] = group.type === \"collection\"\n ? [this.collectionGroupsByPath, group.path]\n : [this.singleGroupsByAddress, `${group.path}\\u0000${group.id}`];\n const bucket = index.get(address);\n bucket?.delete(group);\n if (bucket?.size === 0) index.delete(address);\n }\n\n /**\n * Ask for a refetch of `group` once changes have been quiet for the\n * debounce window — for every member, or only for `member`.\n *\n * The window is measured from the last change, as a debounce is, but the\n * timer is not re-armed per change: a 10k-row statement is 10k changes,\n * and re-arming a timer per change per group was work in proportion to\n * both. A change only moves the timestamp; the timer, when it fires early,\n * waits out the rest.\n */\n private scheduleGroupRefetch(group: SubscriptionGroup, member?: string): void {\n if (member === undefined) group.pending = \"all\";\n else if (group.pending !== \"all\") group.pending.add(member);\n group.lastChangeAt = Date.now();\n if (!group.timer) this.armGroupTimer(group, RealtimeService.REFETCH_DEBOUNCE_MS);\n }\n\n private armGroupTimer(group: SubscriptionGroup, delayMs: number): void {\n group.timer = setTimeout(() => {\n group.timer = undefined;\n if (this.groups.get(group.key) !== group) return;\n const quietFor = Date.now() - group.lastChangeAt;\n if (quietFor < RealtimeService.REFETCH_DEBOUNCE_MS) {\n this.armGroupTimer(group, RealtimeService.REFETCH_DEBOUNCE_MS - quietFor);\n return;\n }\n void this.refetchGroup(group);\n }, delayMs);\n }\n\n /**\n * One read for the group, delivered to each member it is for.\n *\n * Each member still claims its own delivery slot before the read and\n * checks it after (see {@link beginDelivery}), so a member that left, was\n * replaced or was overtaken while the read ran is skipped exactly as it\n * was when it had a read of its own. A socket frame is serialised once and\n * only its subscription id differs per member.\n */\n private async refetchGroup(group: SubscriptionGroup): Promise<void> {\n const keys = group.pending === \"all\" ? [...group.members] : [...group.pending];\n group.pending = new Set();\n\n const targets: Array<{ key: string; subscription: Subscription; canDeliver: DeliveryCheck; callback?: (data: Record<string, unknown>[] | Record<string, unknown> | null) => void }> = [];\n for (const key of keys) {\n const subscription = this._subscriptions.get(key);\n if (!subscription || subscription.groupKey !== group.key) continue;\n if (subscription.clientId === \"driver\") {\n const callback = this.subscriptionCallbacks.get(key);\n if (!callback) continue;\n targets.push({ key, subscription, canDeliver: this.beginDelivery(key, subscription), callback });\n } else if (this.clients.has(subscription.clientId)) {\n targets.push({ key, subscription, canDeliver: this.beginDelivery(key, subscription) });\n }\n }\n if (targets.length === 0) return;\n\n try {\n if (group.type === \"collection\") {\n const request = group.collectionRequest ?? {};\n const rows = await this.fetchCollectionWithAuth(group.path, request, group.authContext);\n const toSockets = targets.some((target) => !target.callback);\n const meta = toSockets\n ? await this.collectionMetaWithAuth(group.path, request, rows, group.authContext)\n : undefined;\n let frameTail: string | undefined;\n for (const target of targets) {\n if (!target.canDeliver()) continue;\n if (target.callback) {\n target.callback(rows);\n continue;\n }\n frameTail ??= this.collectionFrameTail(rows, group.path, meta);\n this.sendRaw(\n target.subscription.clientId,\n `{\"type\":\"collection_update\",\"subscriptionId\":${JSON.stringify(target.subscription.subscriptionId)},${frameTail}`\n );\n }\n } else {\n const row = (await this.fetchEntityWithAuth(group.path, group.id!, group.authContext)) ?? null;\n for (const target of targets) {\n if (!target.canDeliver()) continue;\n if (target.callback) target.callback(row);\n else this.sendSingleUpdate(target.subscription.clientId, target.subscription.subscriptionId, row);\n }\n }\n } catch (error) {\n for (const target of targets) {\n if (target.callback) {\n logger.error(`❌ [RealtimeService] Error in a refetch for DataDriver subscription ${target.subscription.subscriptionId}`, { error });\n this.reportDriverFetchFailure(target.subscription, target.canDeliver, error);\n } else {\n this.reportSocketFetchFailure(target.subscription.clientId, target.subscription.subscriptionId, group.path, error, target.canDeliver);\n }\n }\n }\n }\n\n /**\n * Claim a delivery slot for a subscription, before doing the work.\n *\n * Returns the check to run immediately before delivering. It refuses in\n * three cases, all of which used to deliver:\n *\n * - **Out of order.** A newer refetch has already delivered, so this one is\n * stale — the subscriber would go back to the state before the change.\n * - **Unsubscribed.** The subscription was cancelled while the fetch was in\n * flight. The `has(subscriptionId)` check the debounced refetches ran\n * *before* the await cannot answer this; only a check after it can.\n * - **Replaced.** The same id can name a *different* subscription by the\n * time a fetch lands — a re-subscribe overwrites the map entry, and the\n * old filter's rows would be delivered to the new subscriber.\n *\n * The last two are identity, not presence: the map has to still hold *this\n * exact object*, not merely something under this id.\n *\n * A failure goes through the same slot, by `mayReportFailure()`: an error\n * frame from a stale, cancelled or replaced fetch would mark a view that\n * already shows newer rows as failed, or fail another subscription's view.\n * It also answers yes to the delivery that already claimed the slot, since\n * that is the send itself failing (a row that will not serialise) after the\n * check passed and before anything reached the subscriber.\n */\n private beginDelivery(key: string, subscription: Subscription): DeliveryCheck {\n const seq = ++subscription.started;\n const canDeliver = () => {\n if (this._subscriptions.get(key) !== subscription) return false;\n if (seq <= subscription.delivered) return false;\n subscription.delivered = seq;\n return true;\n };\n const mayReportFailure = () =>\n canDeliver() || (this._subscriptions.get(key) === subscription && subscription.delivered === seq);\n return Object.assign(canDeliver, { mayReportFailure });\n }\n\n /**\n * Start an in-process listener: register it, then deliver its first rows.\n *\n * The first fetch runs here, as the subscriber and through a delivery slot,\n * the way every refetch after it does. `PostgresBackendDriver.listen*` used\n * to run it themselves, outside this service, and got all three wrong: a\n * listener on `withAuth(user)` read its first rows on the owner connection,\n * so it was handed rows its policies deny; a slow first fetch landed over a\n * newer refetch, and after an unsubscribe; and a row listener never heard a\n * `null`. The subscriber's identity is part of the request because a\n * subscription registered first and stamped with it afterwards has a first\n * fetch that ran before the stamp.\n *\n * No `authContext` reads as the anonymous user, first fetch and refetches\n * alike — see {@link fetchCollectionWithAuth}.\n */\n startDataDriverSubscription(\n subscriptionId: string,\n request: DataDriverSubscriptionRequest,\n callback: (data: Record<string, unknown>[] | Record<string, unknown> | null) => void\n ): void {\n this.debugLog(\"📋 [RealtimeService] Starting DataDriver subscription:\", subscriptionId, request.authContext ? \"(with auth)\" : \"(no auth)\");\n const key = this.subscriptionKey(\"driver\", subscriptionId);\n const subscription: Subscription = {\n subscriptionId,\n clientId: \"driver\",\n groupKey: this.groupKeyFor(key, false, request),\n ...request,\n started: 0,\n delivered: 0\n };\n this.registerSubscription(key, subscription);\n this.subscriptionCallbacks.set(key, callback);\n void this.deliverFirstToDataDriver(key, subscription, callback);\n }\n\n private async deliverFirstToDataDriver(\n key: string,\n subscription: Subscription,\n callback: (data: Record<string, unknown>[] | Record<string, unknown> | null) => void\n ): Promise<void> {\n // Claimed before the fetch, as the socket's first fetch claims it: this\n // is the oldest read, so a refetch started while it runs outranks it.\n const canDeliver = this.beginDelivery(key, subscription);\n try {\n const data = subscription.type === \"collection\"\n ? await this.fetchCollectionWithAuth(subscription.path, subscription.collectionRequest ?? {}, subscription.authContext)\n : (await this.fetchEntityWithAuth(subscription.path, String(subscription.id), subscription.authContext)) ?? null;\n if (canDeliver()) callback(data);\n } catch (error) {\n logger.error(`❌ [RealtimeService] Error in the first fetch for DataDriver subscription ${subscription.subscriptionId}`, { error: error });\n this.reportDriverFetchFailure(subscription, canDeliver, error);\n }\n }\n\n // =============================================================================\n // RealtimeProvider Interface Methods\n // =============================================================================\n\n /**\n * Subscribe to collection changes (RealtimeProvider interface)\n */\n subscribeToCollection(\n subscriptionId: string,\n config: CollectionSubscriptionConfig,\n callback?: (rows: Record<string, unknown>[]) => void,\n onError?: (error: unknown) => void\n ): void {\n const key = this.subscriptionKey(config.clientId, subscriptionId);\n const target = {\n type: \"collection\" as const,\n path: config.path,\n collectionRequest: {\n filter: config.filter as Record<string, unknown> | undefined,\n orderBy: config.orderBy,\n order: config.order,\n limit: config.limit,\n startAfter: config.startAfter as Record<string, unknown> | undefined,\n databaseId: config.databaseId,\n searchString: config.searchString,\n searchExplain: config.searchExplain\n }\n };\n this.registerSubscription(key, {\n subscriptionId,\n clientId: config.clientId,\n groupKey: this.groupKeyFor(key, false, target),\n ...target,\n onError,\n started: 0,\n delivered: 0\n });\n\n if (callback) {\n this.subscriptionCallbacks.set(key, callback as (data: Record<string, unknown>[] | Record<string, unknown> | null) => void);\n }\n }\n\n /**\n * Subscribe to single row changes (RealtimeProvider interface)\n */\n subscribeToOne(\n subscriptionId: string,\n config: SingleSubscriptionConfig,\n callback?: (row: Record<string, unknown> | null) => void,\n onError?: (error: unknown) => void\n ): void {\n const key = this.subscriptionKey(config.clientId, subscriptionId);\n const target = { type: \"single\" as const, path: config.path, id: config.id };\n this.registerSubscription(key, {\n subscriptionId,\n clientId: config.clientId,\n groupKey: this.groupKeyFor(key, false, target),\n ...target,\n onError,\n started: 0,\n delivered: 0\n });\n\n if (callback) {\n this.subscriptionCallbacks.set(key, callback as (data: Record<string, unknown>[] | Record<string, unknown> | null) => void);\n }\n }\n\n /**\n * Unsubscribe an in-process subscription (RealtimeProvider interface).\n *\n * By id alone, because the in-process callers hold nothing else — so a\n * socket's subscription under the same id is not this one, and is left\n * alone. A socket ends its own through the `unsubscribe` frame.\n */\n unsubscribe(subscriptionId: string): void {\n for (const [key, subscription] of [...this._subscriptions.entries()]) {\n if (subscription.subscriptionId !== subscriptionId || this.clients.has(subscription.clientId)) continue;\n this.dropSubscription(key);\n }\n }\n\n // =============================================================================\n // WebSocket Client Management\n // =============================================================================\n\n addClient(clientId: string, ws: WebSocket) {\n this.clients.set(clientId, ws);\n\n ws.on(\"close\", () => {\n this.removeClient(clientId);\n });\n\n ws.on(\"error\", (error) => {\n logger.error(\"WebSocket error for client\", { detail: clientId, error });\n this.removeClient(clientId);\n });\n }\n\n // Public method to handle messages from external sources (like main WebSocket handler)\n async handleClientMessage(clientId: string, message: WebSocketMessage, authContext?: SubscriptionAuthContext) {\n await this.handleMessage(clientId, message, authContext);\n }\n\n async removeClient(clientId: string) {\n this.clients.delete(clientId);\n\n // Remove all subscriptions and callbacks for this client; a group it\n // was the last member of goes with its pending refetch.\n for (const key of [...(this.subscriptionsByClient.get(clientId) ?? [])]) {\n this.dropSubscription(key);\n }\n\n // The shared rows go before the announcement, not after it. Every\n // `removePresence` below publishes a departure — to local members and\n // over the bus — while `sendPresenceState` answers a *newly arriving*\n // subscriber from this table. Announcing first leaves a window where the\n // table still lists someone who has left: a client hydrating inside it is\n // handed the ghost, and the diff that would have corrected it was\n // broadcast before that client existed, so it holds the ghost until the\n // TTL sweep rather than for the length of one statement.\n //\n // One statement for every channel the client was in, rather than one per\n // channel below — a disconnect is the common case, not a rare one. The\n // subscription and timer cleanup above stays synchronous on purpose: it\n // is what leaks if the database is slow, and it owes nothing to the\n // shared table. With no bus configured this is a no-op that never awaits\n // a query.\n await this.presenceStoreOp(() => this.presenceStore!.removeClient(clientId), \"client removal\");\n\n // Remove from all broadcast channels\n for (const [channel, members] of this.channels.entries()) {\n if (members.has(clientId)) {\n members.delete(clientId);\n this.removePresence(clientId, channel, { skipStore: true });\n if (members.size === 0) this.channels.delete(channel);\n }\n }\n\n // Remove from all presence channels\n for (const [channel] of this.presence) {\n this.removePresence(clientId, channel, { skipStore: true });\n }\n }\n\n private async handleMessage(clientId: string, message: WebSocketMessage, authContext?: SubscriptionAuthContext) {\n const payload = message.payload as Record<string, unknown> | undefined;\n switch (message.type) {\n case \"subscribe_collection\":\n await this.handleCollectionSubscription(clientId, message.payload as RealTimeListenCollectionProps, authContext);\n break;\n case \"subscribe_one\":\n await this.handleEntitySubscription(clientId, message.payload as RealTimeListenEntityProps, authContext);\n break;\n case \"unsubscribe\":\n await this.handleUnsubscribe(clientId, message.subscriptionId!);\n break;\n\n // ── Broadcast Channels & Presence ──\n //\n // One arm for all of them, because every one has to pass the same\n // gate and a switch with seven arms is a place to forget it once.\n // See `handleChannelMessage`.\n case \"join_channel\":\n case \"leave_channel\":\n case \"broadcast\":\n case \"channel_history\":\n case \"presence_track\":\n case \"presence_untrack\":\n case \"presence_state\":\n await this.handleChannelMessage(clientId, message.type, payload, authContext);\n break;\n\n default:\n this.sendError(clientId, \"Unknown message type \" + message.type, message.subscriptionId);\n }\n }\n\n /**\n * Refuse a new subscription past the socket's ceiling, and say so.\n *\n * Every collection subscription is a refetch on every write to its\n * collection, so a socket allowed to open them without limit could turn\n * each write anyone makes into tens of thousands of transactions. Replacing\n * a subscription the socket already holds — the same id again — is not a\n * new one and always passes. Returns whether the subscription may go ahead.\n */\n private admitSubscription(clientId: string, subscriptionId: string): boolean {\n const held = this.subscriptionsByClient.get(clientId);\n if (!held || held.size < this.maxSubscriptionsPerSocket) return true;\n if (held.has(this.subscriptionKey(clientId, subscriptionId))) return true;\n const message =\n `This connection already holds ${held.size} subscriptions, the most one connection may hold ` +\n `(REALTIME_MAX_SUBSCRIPTIONS_PER_SOCKET, or realtime.maxSubscriptionsPerSocket). Unsubscribe from one first.`;\n logger.warn(`[RealtimeService] Refused a subscription for ${clientId}: ${held.size} held, ceiling ${this.maxSubscriptionsPerSocket}`);\n this.sendError(clientId, message, subscriptionId, \"TOO_MANY_SUBSCRIPTIONS\");\n return false;\n }\n\n private async handleCollectionSubscription(clientId: string, request: RealTimeListenCollectionProps, authContext?: SubscriptionAuthContext) {\n const subscriptionId = request.subscriptionId;\n const key = this.subscriptionKey(clientId, subscriptionId);\n // Out here so the `catch` can check it. It is claimed before the fetch\n // starts, so a failed fetch always has one.\n let canDeliver: DeliveryCheck | undefined;\n\n try {\n // Early validation: ensure the requested collection exists in the registry\n const collection = this.registry.getCollectionByPath(request.path);\n if (!collection) {\n const registered = this.registry.getCollections().map(c => c.slug).join(\", \");\n const msg = `Collection not found: '${request.path}'. Registered: [${registered}]`;\n logger.error(`[RealtimeService] ${msg}`);\n this.sendError(clientId, msg, subscriptionId);\n return;\n }\n\n if (!this.admitSubscription(clientId, subscriptionId)) return;\n\n // A vector search cannot be served here, and the parameter used to\n // be read for one thing only — the limit default below — and then\n // dropped: the stored request carries no `vectorSearch` and the\n // refetch has no branch for one. So `.vectorSearch(…).listen()`\n // delivered an ordinary `id DESC` listing, with no `_distance` and\n // no error, forever. Refusing says what the silence did not.\n if (request.vectorSearch) {\n const msg =\n \"Realtime subscriptions do not support vector search: a subscription is re-run on every \" +\n \"matching write, and nothing here computes distances. Use `.vectorSearch(...).find()` for \" +\n \"the query, and subscribe without it if you need live updates.\";\n logger.warn(`[RealtimeService] ${msg}`);\n this.sendError(clientId, msg, subscriptionId, \"VECTOR_SEARCH_NOT_LIVE\");\n return;\n }\n\n // Bound the client-supplied limit with the SAME guarantee the REST\n // ingress applies (`resolveClientListLimit`): default an absent\n // limit by mode, refuse one above the ceiling. A subscription is\n // re-fetched on every matching write, so an unbounded one is a DoS\n // amplified per write — resolve it once and reuse for the stored\n // request and the initial fetch.\n //\n // Refusing matters more here than on the REST route: a\n // `collection_update` frame carries rows and nothing else — no\n // `total`, no `hasMore` — so a subscriber handed a quietly smaller\n // page has no way at all to learn it is not seeing the collection.\n let boundedLimit: number;\n try {\n boundedLimit = resolveClientListLimit(request.limit);\n } catch (e) {\n if (!(e instanceof ListLimitError)) throw e;\n logger.warn(`[RealtimeService] Refused subscription to '${request.path}': ${e.message}`);\n this.sendError(clientId, e.message, subscriptionId, \"INVALID_LIMIT\");\n return;\n }\n\n // The sort arrives as whatever JSON the client put in the frame, so\n // its *shape* is checked here the way the REST ingress checks the\n // query parameter. Unchecked, a malformed entry reads as a field\n // name that resolves to no column, and under lenient unknown-field\n // handling the subscription then streams rows in no order at all\n // while reporting nothing wrong.\n let orderBy: OrderByTuple[] | undefined;\n try {\n orderBy = parseOrderBySpecStrict(request.orderBy, request.order);\n } catch (e) {\n if (!(e instanceof OrderBySpecError)) throw e;\n logger.warn(`[RealtimeService] Refused subscription to '${request.path}': ${e.message}`);\n this.sendError(clientId, e.message, subscriptionId, e.code);\n return;\n }\n\n // No filter, sort or projection over a field the subscriber may not\n // read — the rule `GET` and the socket's request frames apply. The\n // strip keeps the value out of every frame; a subscription filtered\n // on it would still answer, row by row, whether it matches. Judged\n // against the subscriber's roles, and `anon` for a socket that never\n // signed in: an absent viewer is the trusted server plane. The\n // request itself, with the sort as parsed above — not a re-listed\n // copy of some of its keys, which is how the stored `include`\n // went unchecked while every refetch applied it.\n try {\n assertReadRequestReadable(\n { ...request, orderBy },\n collection,\n { roles: authContext?.roles ?? [\"anon\"] }\n );\n } catch (e) {\n if (!(e instanceof ApiError)) throw e;\n logger.warn(`[RealtimeService] Refused subscription to '${request.path}': ${e.message}`);\n this.sendError(clientId, e.message, subscriptionId, e.code);\n return;\n }\n\n // Store subscription with full request parameters and auth context for RLS\n const collectionRequest: StoredCollectionRequest = {\n filter: request.filter,\n logical: request.logical,\n orderBy,\n order: request.order,\n limit: boundedLimit,\n // `page` wins over `offset`, exactly as `FindParams`\n // documents and the REST parser applies it — one rule, so a\n // live list and the fetch behind it land on the same window.\n offset: request.page != null && request.page > 0\n ? (request.page - 1) * boundedLimit\n : request.offset,\n startAfter: request.startAfter as Record<string, unknown> | undefined,\n databaseId: request.collection?.databaseId,\n searchString: request.searchString,\n searchExplain: request.searchExplain,\n // Stored, so every refetch answers the same query the\n // initial fetch did. A field declared on the incoming props\n // and not stored here is accepted over the wire and then\n // silently ignored — which is what `offset` and `logical`\n // both were.\n include: request.include,\n fields: request.fields,\n distinct: request.distinct\n };\n const target = { type: \"collection\" as const, path: request.path, collectionRequest, authContext };\n const subscription: Subscription = {\n subscriptionId,\n clientId,\n groupKey: this.groupKeyFor(key, true, target),\n ...target,\n started: 0,\n delivered: 0\n };\n this.registerSubscription(key, subscription);\n\n // The subscription is registered before this fetch runs, so a write\n // arriving in that window starts a refetch of its own — with nothing\n // ordering the two. Claim a slot first: this fetch is the oldest, so\n // if the refetch answers first, this one no longer delivers.\n canDeliver = this.beginDelivery(key, subscription);\n\n // Send initial data. Built from the request the subscription just\n // stored, so the first answer and every refetch after it cannot\n // describe different queries.\n const rows = await this.fetchCollectionWithAuth(\n request.path,\n subscription.collectionRequest!,\n authContext\n );\n const meta = await this.collectionMetaWithAuth(\n request.path, subscription.collectionRequest!, rows, authContext\n );\n\n if (canDeliver()) {\n this.sendCollectionUpdate(clientId, subscriptionId, rows, request.path, meta);\n }\n\n } catch (error) {\n this.reportSocketFetchFailure(clientId, subscriptionId, request.path, error, canDeliver);\n }\n }\n\n private async handleEntitySubscription(clientId: string, request: RealTimeListenEntityProps, authContext?: SubscriptionAuthContext) {\n const subscriptionId = request.subscriptionId;\n const key = this.subscriptionKey(clientId, subscriptionId);\n // As in the collection case: claimed before the fetch, checked in the `catch`.\n let canDeliver: DeliveryCheck | undefined;\n\n try {\n // Early validation: ensure the requested collection exists in the registry\n const collection = this.registry.getCollectionByPath(request.path);\n if (!collection) {\n const registered = this.registry.getCollections().map(c => c.slug).join(\", \");\n const msg = `Collection not found: '${request.path}'. Registered: [${registered}]`;\n logger.error(`[RealtimeService] ${msg}`);\n this.sendError(clientId, msg, subscriptionId);\n return;\n }\n\n if (!this.admitSubscription(clientId, subscriptionId)) return;\n\n // Store subscription in memory with auth context for RLS\n const target = { type: \"single\" as const, path: request.path, id: request.id, authContext };\n const subscription: Subscription = {\n subscriptionId,\n clientId,\n groupKey: this.groupKeyFor(key, true, target),\n ...target,\n started: 0,\n delivered: 0\n };\n this.registerSubscription(key, subscription);\n\n // Same race as the collection case: a write landing between the\n // registration above and this fetch starts a refetch that can answer\n // first, and this one must not overwrite it afterwards.\n canDeliver = this.beginDelivery(key, subscription);\n\n // Send initial data\n const row = await this.fetchEntityWithAuth(\n request.path,\n String(request.id),\n authContext\n );\n\n if (canDeliver()) {\n this.sendSingleUpdate(clientId, subscriptionId, row || null);\n }\n\n } catch (error) {\n this.reportSocketFetchFailure(clientId, subscriptionId, request.path, error, canDeliver);\n }\n }\n\n /**\n * Answer a subscriber's failed fetch with an error frame, through the slot\n * its rows would have used.\n *\n * The error is always logged (`sanitizeErrorForClient` does that), but the\n * frame goes out only while this fetch holds the newest slot. A failed\n * straggler used to send one regardless. The client routes errors by\n * `subscriptionId`, so it called `onError` for a view already showing newer\n * rows, or for the subscription that replaced this one under the same id.\n *\n * With no slot, the subscribe failed before its fetch began. That part runs\n * synchronously, so nothing else can have answered the request yet.\n */\n private reportSocketFetchFailure(\n clientId: string,\n subscriptionId: string,\n path: string,\n error: unknown,\n canDeliver: DeliveryCheck | undefined\n ) {\n const sanitized = sanitizeErrorForClient(error, path);\n if (canDeliver && !canDeliver.mayReportFailure()) return;\n this.sendError(clientId, sanitized.message, subscriptionId, sanitized.code);\n }\n\n /**\n * Re-judge everything a socket holds open as the identity it has now.\n *\n * A socket re-authenticates on every session refresh, and when a role is\n * taken away, a tenant claim removed or a different account signs in, the\n * next token says so. Requests read as the new identity at once; the\n * subscriptions kept refetching as the one they were opened with, so a\n * demoted user went on receiving editor-only or another tenant's rows for\n * as long as the view stayed mounted.\n *\n * Each of this client's subscriptions is replaced by one carrying the new\n * identity — a replacement, so a refetch already running as the old one\n * cannot deliver — and refetched, so the view narrows now rather than at\n * the next write. A subscription whose filter, sort or projection names a\n * field the new roles may not read is ended with the refusal a new\n * subscription would get. Channel memberships are put to the authorizer\n * again, when one is installed, and left when it refuses.\n */\n async rescopeClient(clientId: string, authContext: SubscriptionAuthContext): Promise<void> {\n for (const key of [...(this.subscriptionsByClient.get(clientId) ?? [])]) {\n const subscription = this._subscriptions.get(key);\n if (!subscription) continue;\n const subscriptionId = subscription.subscriptionId;\n\n if (subscription.type === \"collection\" && subscription.collectionRequest) {\n const collection = this.registry.getCollectionByPath(subscription.path);\n const request = subscription.collectionRequest;\n try {\n if (collection) {\n assertReadRequestReadable(\n {\n // Stored from the subscription's own request, which carried it as one.\n filter: request.filter as FilterValues<string>,\n logical: request.logical,\n orderBy: request.orderBy,\n fields: request.fields\n },\n collection,\n { roles: authContext.roles ?? [\"anon\"] }\n );\n }\n } catch (e) {\n if (!(e instanceof ApiError)) throw e;\n await this.handleUnsubscribe(clientId, subscriptionId);\n this.sendError(clientId, e.message, subscriptionId, e.code);\n continue;\n }\n }\n\n // A new identity is a new group: the read it shares is now the one\n // its new principal's subscriptions run.\n const rescoped: Subscription = {\n ...subscription,\n authContext,\n groupKey: this.groupKeyFor(key, true, { ...subscription, authContext }),\n started: 0,\n delivered: 0\n };\n this.registerSubscription(key, rescoped);\n const group = this.groups.get(rescoped.groupKey);\n if (group) this.scheduleGroupRefetch(group, key);\n }\n\n const authorizer = this.channelAuthorizer;\n if (!authorizer) return;\n for (const [channel, members] of [...this.channels.entries()]) {\n if (!members.has(clientId)) continue;\n let allowed: boolean;\n try {\n allowed = await authorizer({ channel, action: \"join\", clientId, user: authContext });\n } catch (error) {\n logger.error(`❌ [Channels] Authorizer threw re-checking \"${channel}\" after a sign-in — leaving it`, { error });\n allowed = false;\n }\n if (!allowed) {\n this.leaveChannel(clientId, channel);\n this.denyChannelAction(clientId, channel, \"join\", \"refused by the channel authorizer for the identity this socket signed in as\");\n }\n }\n }\n\n /**\n * End a socket's own subscription. Only its own: the id is the client's,\n * so another socket's subscription under the same id is a different one.\n */\n private async handleUnsubscribe(clientId: string, subscriptionId: string) {\n this.dropSubscription(this.subscriptionKey(clientId, subscriptionId));\n }\n\n /**\n * Enhanced notification method that handles nested relation updates.\n * @param broadcast When true (default), also sends a pg_notify so other instances\n * pick up the change. Set to false when handling an incoming\n * cross-instance notification to avoid infinite loops.\n * @param origin `\"app\"` (default) — a Rebase-API mutation on this instance;\n * `\"cdc\"` — a database-level change observed via CDC (any writer,\n * any instance). The origin drives de-duplication: an app emit\n * records the change so this instance can suppress the matching\n * CDC echo, while an unmatched CDC event is delivered normally.\n */\n async notifyUpdate(path: string, id: string, row: Record<string, unknown> | null, databaseId?: string, broadcast = true, origin: \"app\" | \"cdc\" = \"app\") {\n this.debugLog(\"🔔 [RealtimeService] notifyUpdate called for path:\", path, \"id:\", id, \"isDelete:\", row === null, \"origin:\", origin);\n\n // De-duplicate against database-level CDC. The app path (a mutation made\n // through the Rebase API) fans out locally AND, once CDC is active, the\n // same committed change is echoed back to this instance via the WAL /\n // trigger stream. Record app emits so we can drop that echo here; deliver\n // any CDC event we did not originate (external writes, other instances).\n // The same row under the other paths a subscriber can address it by —\n // see `aliasPaths`.\n const aliases = this.aliasPaths(path);\n\n if (this.cdcActive) {\n const key = this.dedupKey(path, id, databaseId);\n if (origin === \"cdc\") {\n if (this.consumeAppEmit(key)) {\n this.debugLog(\"🔁 [RealtimeService] Suppressing CDC echo of local app mutation:\", key);\n return;\n }\n } else {\n this.markAppEmit(key);\n // CDC reports the change under the table's own collection, so a\n // write through a nested path echoes back under the root one.\n // The aliases below have already delivered it there.\n if (isNestedPath(path)) {\n const root = aliases.find(alias => !isNestedPath(alias));\n if (root) this.markAppEmit(this.dedupKey(root, id, databaseId));\n }\n }\n }\n\n // Get all paths that need to be notified - the direct path plus any parent paths\n const pathsToNotify = [path];\n\n // If this is a nested relation path (like \"posts/70/tags\"), also notify parent paths\n if (path.includes(\"/\") && path.split(\"/\").length > 1) {\n const parentPaths = this.getParentPaths(path);\n pathsToNotify.push(...parentPaths);\n this.debugLog(`🔗 [RealtimeService] Nested path detected. Will notify paths: ${pathsToNotify.join(\", \")}`);\n }\n\n // Process each path that needs notification\n for (const notifyPath of pathsToNotify) {\n this.notifyPathUpdate(notifyPath, path, id);\n }\n\n // Each alias is an address of this very row, so its single-row\n // subscribers match by id exactly as the written path's do.\n for (const alias of aliases) {\n this.notifyPathUpdate(alias, alias, id);\n }\n\n // Broadcast to other instances via pg_notify (only for local mutations).\n // When CDC is active it IS the cross-instance channel — every instance\n // observes every commit through the change stream — so the legacy\n // per-mutation broadcast is redundant (and would double-deliver). Skip it.\n if (broadcast && this.broadcasting && !this.cdcActive) {\n try {\n await this.broadcastChange(path, id, databaseId);\n } catch (err) {\n logger.error(\"❌ [RealtimeService] Failed to broadcast change via pg_notify\", { error: err });\n }\n }\n\n this.debugLog(\"🔔 [RealtimeService] notifyUpdate completed for path:\", path);\n }\n\n /**\n * Notify subscriptions for a specific path.\n *\n * **A subscriber only ever receives rows re-read under its own scope.**\n * `row` is used to decide *that* something changed, never to say *what* —\n * every delivery below goes through a refetch that binds the subscription's\n * own auth context.\n *\n * It used to be conditional. The CDC path already did the right thing: it\n * discards the captured tuple and emits `{_rebase_invalidated: true}`, and\n * that marker selected the refetch branch. But the marker is produced in\n * exactly two places, and the *other* side of each branch here shipped the\n * row it was handed straight to the socket. Two of the three entry paths\n * took that side — every API mutation (`PostgresBackendDriver.save` passes\n * the row it just wrote, read under the **writer's** scope) and the legacy\n * cross-instance LISTEN handler (which re-reads on the owner connection,\n * bypassing RLS altogether). Path matching was the only filter applied: the\n * subscription's own `filter`/`logical` was never evaluated, and any\n * `afterRead` redaction was the writer's rather than the reader's.\n *\n * A single-row subscription was the sharpest case. `subscribe_one` on a row\n * RLS denies is accepted and answered `null`; the next update then pushed\n * the full row with no later correction. The collection variant was merely\n * papered over ~300 ms later by the debounced refetch — after the bytes had\n * already reached the browser.\n *\n * The same defect was found and fixed on the Mongo driver in `065e2b615`\n * (see `packages/server-mongo/test/realtime-authorization.test.ts`); this is\n * the Postgres half, stated as one rule rather than three patched branches.\n *\n * The cost is the instant row-level patch that used to precede the refetch:\n * cross-tab feedback now waits for the debounce. That is the price of not\n * being able to know, without asking the database as this subscriber,\n * whether this subscriber may see the row at all.\n */\n private notifyPathUpdate(notifyPath: string, originalPath: string, id: string) {\n this.debugLog(`📡 [RealtimeService] Notifying path: ${notifyPath} (original: ${originalPath})`);\n\n // Every list on the path may have changed. A single-row subscription\n // is asked only at the written path, and only for its own row: a\n // parent path is a different record.\n for (const group of this.collectionGroupsByPath.get(notifyPath) ?? []) {\n this.scheduleGroupRefetch(group);\n }\n if (notifyPath !== originalPath) return;\n for (const group of this.singleGroupsByAddress.get(`${notifyPath}\\u0000${id}`) ?? []) {\n this.scheduleGroupRefetch(group);\n }\n }\n\n /**\n * Tell an in-process listener its refetch failed, through the slot the\n * rows would have used.\n *\n * The two driver-refetch `catch` blocks used to log and stop, so the\n * listener heard nothing and kept its last rows as if they were current.\n * The error goes to it as thrown: this is trusted server code, and the\n * masking on the socket path is for clients. The slot keeps a failed\n * straggler from reporting over newer rows, and keeps a failure away from\n * a subscription that was cancelled or replaced while it ran.\n */\n private reportDriverFetchFailure(\n subscription: Subscription,\n canDeliver: () => boolean,\n error: unknown\n ) {\n if (!subscription.onError || !canDeliver()) return;\n try {\n subscription.onError(error);\n } catch (listenerError) {\n // Contained: this runs in a timer, where a throw would be an\n // unhandled rejection.\n logger.error(`❌ [RealtimeService] onError threw for DataDriver subscription ${subscription.subscriptionId}`, { error: listenerError });\n }\n }\n\n /**\n * Fetch a collection with optional RLS auth context.\n * When authContext is provided, the fetch runs inside a transaction\n * with set_config calls so PostgreSQL RLS policies are enforced.\n */\n private async fetchCollectionWithAuth(\n notifyPath: string,\n collectionRequest: StoredCollectionRequest,\n authContext?: SubscriptionAuthContext\n ): Promise<Record<string, unknown>[]> {\n if (this.driver) {\n const collection = this.registry.getCollectionByPath(notifyPath);\n // Always wrap in a transaction with session vars, defaulting to anonymous context if missing.\n // Refetches are reads: apply the same GUCs + reader-role downgrade as the\n // driver's read path, so realtime cannot leak rows the initial fetch hid.\n // `READ ONLY` like that path too (here, in the count and in the row\n // refetch): an `afterRead` that writes is refused with 25006 on every\n // request read, and a frame must not be the one door where it commits\n // — once per row, per subscriber, on every change anyone makes.\n const activeAuth = authContext || { uid: ANONYMOUS_USER_ID,\nroles: [\"anon\"] };\n // The subscriber this frame is for, so per-field `access.read` is\n // applied to it. A frame is a read like any other and reaches the\n // same row pipeline; without this the initial `GET` would withhold a\n // field and the first `.listen()` update would hand it over.\n return await withFieldViewer({ roles: activeAuth.roles ?? [] }, async () =>\n await this.db.transaction(async (tx) => {\n await applyAuthContext(\n tx,\n {\n uid: activeAuth.uid,\n roles: activeAuth.roles,\n isAnonymous: activeAuth.isAnonymous === true,\n // A refetch is a read under the same identity, and a\n // tenancy policy reads a claim — without these a\n // subscriber would receive rows the initial GET hid.\n claims: activeAuth.claims\n },\n this.rlsUserRole\n );\n // Bound to the subscriber's own transaction and identity, so\n // a `beforeQuery` narrows a subscription frame exactly as it\n // narrows the `find()` that asked the same question. Lazy and\n // memoized: nothing is built for a collection with no hook.\n let hookContext: RebaseCallContext | undefined;\n const txEntityService = new DataService(\n tx, this.registry,\n () => (hookContext ??= callbackContextWithin(this.driver, tx, activeAuth))\n );\n // The REST pipeline, not the driver's own fetch — one\n // query, one include loader, one place search is decided.\n //\n // These are the rows a subscriber receives, and a subscriber\n // asked the same question a `find()` asks — so they are the\n // same rows, in the same shape, for every consumer on this\n // wire. The admin panel included: it builds the view model\n // it renders (dates as `Date`, relations as refs) in the\n // browser, from the collection config it already has — see\n // `toViewModelValues` in `buildRebaseData`. There is no\n // second wire shape to keep in step with this one.\n const fetchedEntities = await txEntityService.fetchCollectionForRest(notifyPath, {\n filter: collectionRequest.filter as FilterValues<string>,\n // The subscription stored a group; the search branch used to\n // drop it, so a filtered live search widened to every row\n // matching the text.\n logical: collectionRequest.logical,\n orderBy: collectionRequest.orderBy,\n order: collectionRequest.order,\n limit: collectionRequest.limit,\n offset: collectionRequest.offset,\n startAfter: collectionRequest.startAfter,\n searchString: collectionRequest.searchString,\n searchExplain: collectionRequest.searchExplain,\n databaseId: collectionRequest.databaseId,\n fields: collectionRequest.fields,\n distinct: collectionRequest.distinct\n }, collectionRequest.include);\n\n // Re-apply `afterRead` lifecycle hooks to ensure consistent data structures\n // between the initial driver fetch and this RLS-bound refetch.\n const registryCollection = this.registry.getCollectionByPath(notifyPath);\n // `| undefined`, and now it says so: the registry answers\n // nothing for a path it does not know. The cast used to\n // erase that one line above the `?.` that admits it.\n const resolvedCollection: CollectionConfig | undefined = collection\n ? { ...collection, ...registryCollection } as CollectionConfig\n : registryCollection;\n\n const callbacks = resolvedCollection?.callbacks;\n const globalCallbacks = this.registry?.getGlobalCallbacks();\n const propertyCallbacks = resolvedCollection?.properties ? buildPropertyCallbacks(resolvedCollection.properties) : undefined;\n\n if (globalCallbacks?.afterRead || callbacks?.afterRead || propertyCallbacks?.afterRead) {\n // Bound to THIS transaction — the subscriber's — see\n // callbackContextWithin below for what this used to be.\n const contextForCallback = callbackContextWithin(this.driver, tx, activeAuth);\n\n const callbackCollection = requireCallbackCollection(resolvedCollection, notifyPath);\n return await Promise.all(fetchedEntities.map(async (fetchedRow) => {\n let processedEntity = fetchedRow;\n // 1. Global callbacks first\n if (globalCallbacks?.afterRead) {\n processedEntity = await globalCallbacks.afterRead({\n collection: callbackCollection,\n path: notifyPath,\n row: processedEntity,\n context: contextForCallback\n }) ?? processedEntity;\n }\n // 2. Collection callbacks second\n if (callbacks?.afterRead) {\n processedEntity = await callbacks.afterRead({\n collection: callbackCollection,\n path: notifyPath,\n row: processedEntity,\n context: contextForCallback\n }) ?? processedEntity;\n }\n // 3. Property callbacks third\n if (propertyCallbacks?.afterRead) {\n processedEntity = await propertyCallbacks.afterRead({\n collection: callbackCollection,\n path: notifyPath,\n row: processedEntity,\n context: contextForCallback\n }) ?? processedEntity;\n }\n return processedEntity;\n }));\n }\n\n return fetchedEntities;\n }, { accessMode: \"read only\" })\n );\n }\n\n // No driver — use dataService directly (no auth wrapping possible).\n // The `logical` group is carried here as well: this branch answers the\n // same subscription as the one above, and a fallback that drops a\n // condition returns *more* rows than the path it stands in for.\n //\n // The field viewer IS established, unlike the database's auth context:\n // the subscriber's roles are in hand either way, and leaving the scope\n // off would make this the one path where a role-restricted field is\n // served to everybody — a fallback that hands out *more* than the path\n // it stands in for, which is the defect the paragraph above describes\n // one clause at a time.\n return await withFieldViewer({ roles: authContext?.roles ?? [] }, async () =>\n await this.dataService.fetchCollectionForRest(notifyPath, {\n filter: collectionRequest.filter as FilterValues<string>,\n logical: collectionRequest.logical,\n orderBy: collectionRequest.orderBy,\n order: collectionRequest.order,\n limit: collectionRequest.limit,\n offset: collectionRequest.offset,\n startAfter: collectionRequest.startAfter,\n searchString: collectionRequest.searchString,\n searchExplain: collectionRequest.searchExplain,\n databaseId: collectionRequest.databaseId,\n fields: collectionRequest.fields,\n distinct: collectionRequest.distinct\n }, collectionRequest.include)\n );\n }\n\n /**\n * The `meta` a `collection_update` frame carries beside its rows.\n *\n * Frames used to carry rows and primary keys and nothing else, so the\n * client issued a `GET /count` **per push** to fill in a total it needed to\n * render the same list it had just been handed — one extra round trip per\n * write, per subscriber, forever. The refetch already knows the query; it\n * counts once, here, under the same RLS transaction that read the rows, so\n * the total describes the same set they came from.\n */\n private async collectionMetaWithAuth(\n notifyPath: string,\n collectionRequest: StoredCollectionRequest,\n rows: Record<string, unknown>[],\n authContext?: SubscriptionAuthContext\n ): Promise<CollectionUpdateMeta> {\n const limit = collectionRequest.limit ?? rows.length;\n const offset = collectionRequest.offset ?? 0;\n\n const countOnce = async (service: DataService) => service.count(notifyPath, {\n filter: collectionRequest.filter as FilterValues<string>,\n logical: collectionRequest.logical,\n searchString: collectionRequest.searchString,\n databaseId: collectionRequest.databaseId\n });\n\n let total: number;\n try {\n if (this.driver) {\n const activeAuth = authContext || { uid: ANONYMOUS_USER_ID, roles: [\"anon\"] };\n total = await this.db.transaction(async (tx) => {\n await applyAuthContext(\n tx,\n {\n uid: activeAuth.uid,\n roles: activeAuth.roles,\n isAnonymous: activeAuth.isAnonymous === true,\n // A refetch is a read under the same identity, and a\n // tenancy policy reads a claim — without these a\n // subscriber would receive rows the initial GET hid.\n claims: activeAuth.claims\n },\n this.rlsUserRole\n );\n // The same narrowing as the rows it is counting: a frame\n // whose `meta.total` came from an unnarrowed count says\n // \"1 of 4 results\".\n let hookContext: RebaseCallContext | undefined;\n return countOnce(new DataService(\n tx, this.registry,\n () => (hookContext ??= callbackContextWithin(this.driver, tx, activeAuth))\n ));\n }, { accessMode: \"read only\" });\n } else {\n total = await countOnce(this.dataService);\n }\n } catch (error) {\n // A count that failed says nothing about the size of the\n // collection, and a frame with no `meta` is one the client can fall\n // back on. Reporting `rows.length` would claim a page read at\n // offset 10 held two rows.\n logger.warn(`[RealtimeService] Could not count '${notifyPath}' for a subscription frame`, { error });\n return { limit, offset, hasMore: false, partial: true };\n }\n\n const last = rows[rows.length - 1];\n const hasMore = offset + rows.length < total;\n const nextCursor = (hasMore && last)\n ? this.dataService.cursorFor?.(\n notifyPath, last, normalizeDriverOrderBy(collectionRequest.orderBy, collectionRequest.order)\n )\n : undefined;\n\n return { total, limit, offset, hasMore, ...(nextCursor && { nextCursor }) };\n }\n\n /**\n * Fetch a single row with optional RLS auth context.\n */\n private async fetchEntityWithAuth(\n notifyPath: string,\n id: string | number,\n authContext?: SubscriptionAuthContext\n ): Promise<Record<string, unknown> | undefined> {\n if (this.driver) {\n const collection = this.registry.getCollectionByPath(notifyPath);\n // Always wrap in a transaction with session vars, defaulting to anonymous context if missing.\n // Same read isolation as collection refetches: GUCs + reader-role downgrade.\n const activeAuth = authContext || { uid: ANONYMOUS_USER_ID,\nroles: [\"anon\"] };\n // The subscriber this frame is for, so per-field `access.read` is\n // applied to it. A frame is a read like any other and reaches the\n // same row pipeline; without this the initial `GET` would withhold a\n // field and the first `.listen()` update would hand it over.\n return await withFieldViewer({ roles: activeAuth.roles ?? [] }, async () =>\n await this.db.transaction(async (tx) => {\n await applyAuthContext(\n tx,\n {\n uid: activeAuth.uid,\n roles: activeAuth.roles,\n isAnonymous: activeAuth.isAnonymous === true,\n // A refetch is a read under the same identity, and a\n // tenancy policy reads a claim — without these a\n // subscriber would receive rows the initial GET hid.\n claims: activeAuth.claims\n },\n this.rlsUserRole\n );\n // Bound to the subscriber's own transaction and identity, so\n // a `beforeQuery` narrows a subscription frame exactly as it\n // narrows the `find()` that asked the same question. Lazy and\n // memoized: nothing is built for a collection with no hook.\n let hookContext: RebaseCallContext | undefined;\n const txEntityService = new DataService(\n tx, this.registry,\n () => (hookContext ??= callbackContextWithin(this.driver, tx, activeAuth))\n );\n // The REST pipeline, for the same reason the collection refetch\n // uses it: `listenById()` and `findById()` are the same read,\n // and `fetchOne` renders the admin's view model — every relation\n // eagerly loaded, each under a `{ __type: \"relation\" }`\n // envelope. A subscriber got one shape and a fetch the other.\n let processedEntity = await txEntityService.fetchOneForRest(\n notifyPath, id, undefined, collection?.databaseId\n ) ?? undefined;\n\n if (processedEntity) {\n const registryCollection = this.registry.getCollectionByPath(notifyPath);\n // See the note on the collection refetch above.\n const resolvedCollection: CollectionConfig | undefined = collection\n ? { ...collection, ...registryCollection } as CollectionConfig\n : registryCollection;\n\n const callbacks = resolvedCollection?.callbacks;\n const globalCallbacks = this.registry?.getGlobalCallbacks();\n const propertyCallbacks = resolvedCollection?.properties ? buildPropertyCallbacks(resolvedCollection.properties) : undefined;\n\n if (globalCallbacks?.afterRead || callbacks?.afterRead || propertyCallbacks?.afterRead) {\n const contextForCallback = callbackContextWithin(this.driver, tx, activeAuth);\n const callbackCollection = requireCallbackCollection(resolvedCollection, notifyPath);\n\n // 1. Global callbacks first\n if (globalCallbacks?.afterRead) {\n processedEntity = await globalCallbacks.afterRead({\n collection: callbackCollection,\n path: notifyPath,\n row: processedEntity,\n context: contextForCallback\n }) ?? processedEntity;\n }\n // 2. Collection callbacks second\n if (callbacks?.afterRead) {\n processedEntity = await callbacks.afterRead({\n collection: callbackCollection,\n path: notifyPath,\n row: processedEntity,\n context: contextForCallback\n }) ?? processedEntity;\n }\n // 3. Property callbacks third\n if (propertyCallbacks?.afterRead) {\n processedEntity = await propertyCallbacks.afterRead({\n collection: callbackCollection,\n path: notifyPath,\n row: processedEntity,\n context: contextForCallback\n }) ?? processedEntity;\n }\n }\n }\n\n return processedEntity;\n }, { accessMode: \"read only\" })\n );\n }\n\n // Same reasoning as the collection fallback above: no database auth\n // context is available here, but the subscriber's roles are.\n return await withFieldViewer(\n { roles: authContext?.roles ?? [] },\n async () => (await this.dataService.fetchOneForRest(notifyPath, id)) ?? undefined\n );\n }\n\n private sendCollectionUpdate(\n clientId: string,\n subscriptionId: string,\n rows: Record<string, unknown>[],\n path: string,\n meta?: CollectionUpdateMeta\n ) {\n const message: CollectionUpdateMessage = {\n type: \"collection_update\",\n subscriptionId,\n rows: rows,\n pks: this.primaryKeysForPath(path),\n // Beside the rows rather than fetched afterwards — see\n // {@link CollectionUpdateMeta} for the round trip this removes.\n ...(meta && { meta })\n };\n this.sendMessage(clientId, message);\n }\n\n /**\n * Everything in a `collection_update` frame after its subscription id,\n * serialised once for every member of a group: the same rows, keys and\n * meta as {@link sendCollectionUpdate} sends, in the same order.\n */\n private collectionFrameTail(rows: Record<string, unknown>[], path: string, meta?: CollectionUpdateMeta): string {\n return JSON.stringify({\n rows,\n pks: this.primaryKeysForPath(path),\n ...(meta && { meta })\n }).slice(1);\n }\n\n private sendSingleUpdate(clientId: string, subscriptionId: string, row: Record<string, unknown> | null) {\n const message: SingleUpdateMessage = {\n type: \"single_update\",\n subscriptionId,\n row: row\n };\n this.sendMessage(clientId, message);\n }\n\n /**\n * Send a lightweight row-level patch to a collection subscriber.\n * The client can merge this into its cached data for instant feedback.\n *\n * The key columns ride along: the patch names a row by address, and the\n * client has to find that row among the ones it cached — which carry\n * columns and no address. The SDK holds no collection config to derive one\n * from, so this is the only place the mapping can come from.\n */\n /** The key columns of the collection at `path`, if they can be resolved. */\n private primaryKeysForPath(path: string): PrimaryKeyInfo[] | undefined {\n try {\n const collection = this.registry.getCollectionByPath(path);\n if (!collection) return undefined;\n const keys = getPrimaryKeys(collection, this.registry);\n return keys.length > 0 ? keys : undefined;\n } catch {\n // `getCollectionByPath` throws on a path it cannot walk — and this\n // is called for parent paths too, which include entity paths like\n // `posts/1` that name no collection. Telling the subscriber nothing\n // is right here; letting it throw would drop the notification.\n return undefined;\n }\n }\n\n /**\n * `channel` addresses the error to the channel frame it is about.\n *\n * Without it the client had nowhere to deliver a channel error: channel\n * frames are fire-and-forget, so there is no pending request to reject and\n * no subscription id to match, and `CHANNEL_FORBIDDEN`, `RATE_LIMITED` and\n * the two history failures fell through every branch of the client's\n * message handler into a console warning. The client already routes\n * channel-addressed frames by name — `onChannelMessage(channel, …)` — so\n * naming the channel is all that was missing.\n *\n * Additive on the wire: a client that does not read it behaves as before.\n */\n private sendError(clientId: string, error: string, subscriptionId?: string, code?: string, channel?: string) {\n const message = {\n type: \"error\" as const,\n subscriptionId,\n ...(channel !== undefined && { channel }),\n payload: {\n error: code ? { message: error, code } : error,\n ...(channel !== undefined && { channel })\n },\n error\n };\n this.sendMessage(clientId, message);\n }\n\n private sendMessage(clientId: string, message: CollectionUpdateMessage | SingleUpdateMessage | CollectionPatchMessage | { type: string; subscriptionId?: string; error?: string; payload?: unknown }) {\n this.sendRaw(clientId, JSON.stringify(message));\n }\n\n /**\n * Send an already-serialised frame to a client, if it is still connected.\n *\n * Every frame to a socket goes through here, so this is where a client\n * that does not read what it is sent is let go of — see\n * {@link terminateIfBacklogged}. Terminated, it closes like any other\n * socket, and {@link removeClient} drops what it held.\n */\n private sendRaw(clientId: string, frame: string) {\n const client = this.clients.get(clientId);\n if (!client || client.readyState !== WebSocket.OPEN) return;\n if (terminateIfBacklogged(client, clientId)) return;\n client.send(frame);\n }\n\n /**\n * Extract parent paths from a nested path like \"posts/70/tags\"\n * Returns [\"posts\", \"posts/70\"] for the example above\n */\n /**\n * The other paths a subscriber can address the row written at `path` by.\n *\n * `authors/1/posts` and `posts` are two addresses for the same rows, and\n * subscriptions were matched by the written path's exact string. So a post\n * saved through `authors/1/posts` never reached a subscriber of `posts` or\n * of `posts/43`, and one saved through `posts` never reached a subscriber\n * of `authors/1/posts`, whose list it may just have joined or left.\n *\n * The aliases are the target collection's root path, and every nested path\n * a live subscription holds that lands on the same collection. Each still\n * refetches under its own scope and its own parent, so naming a path here\n * decides only who is asked to look again — never what they are shown.\n */\n private aliasPaths(path: string): string[] {\n const slug = this.collectionSlugAt(path);\n if (!slug) return [];\n const aliases = new Set<string>();\n if (slug !== path) aliases.add(slug);\n // The paths subscriptions hold, each resolved once when it was first\n // subscribed — not every subscription on the server, once per change.\n for (const [subscribed, held] of this.subscribedPaths) {\n if (subscribed === path || aliases.has(subscribed) || !isNestedPath(subscribed)) continue;\n if (held.slug === slug) aliases.add(subscribed);\n }\n return [...aliases];\n }\n\n /** The slug of the collection a path lands on, or `undefined` for one that names none. */\n private collectionSlugAt(path: string): string | undefined {\n try {\n return this.registry.getCollectionByPath(path)?.slug;\n } catch {\n // A malformed or stale path names no collection, and a\n // notification is not the place to refuse it.\n return undefined;\n }\n }\n\n private getParentPaths(path: string): string[] {\n const segments = path.split(\"/\").filter(s => s.length > 0);\n const parentPaths: string[] = [];\n\n // Build parent paths progressively\n for (let i = 1; i < segments.length; i += 2) {\n const parentPath = segments.slice(0, i).join(\"/\");\n if (parentPath) {\n parentPaths.push(parentPath);\n }\n\n // If there's an row ID, add the path including the row\n if (i + 1 < segments.length) {\n const pathWithEntity = segments.slice(0, i + 1).join(\"/\");\n parentPaths.push(pathWithEntity);\n }\n }\n\n return parentPaths;\n }\n\n // =============================================================================\n // Broadcast Channels\n // =============================================================================\n\n /**\n * Install a channel authorizer — see {@link ChannelAuthorizer}.\n *\n * Nothing in the framework calls this yet: it is the seam a rules API will\n * be built on, kept deliberately separate from the membership floor so the\n * floor holds whether or not anyone uses it.\n */\n setChannelAuthorizer(authorizer: ChannelAuthorizer | undefined): void {\n this.channelAuthorizer = authorizer;\n }\n\n /** Which action each channel frame is asking to perform. */\n private static readonly CHANNEL_ACTIONS: Record<string, ChannelAction> = {\n join_channel: \"join\",\n broadcast: \"broadcast\",\n channel_history: \"history\",\n presence_track: \"join\",\n presence_state: \"presence\"\n };\n\n /**\n * The one door every channel frame comes through.\n *\n * Returns synchronously — and so dispatches synchronously — unless an\n * authorizer is installed. That matters: a client sends `join_channel`,\n * `presence_state` and `channel_history` back to back on connect, and the\n * socket's message handler processes each frame up to its first `await`,\n * so a gate that always yielded would let the reads overtake the join that\n * is about to authorize them.\n */\n private handleChannelMessage(\n clientId: string,\n type: string,\n payload: Record<string, unknown> | undefined,\n authContext?: SubscriptionAuthContext\n ): void | Promise<void> {\n const channel = payload?.channel as string;\n\n // Leaving and untracking only ever remove the caller's own state, so\n // they need no permission — refusing them could only strand a client.\n if (type === \"leave_channel\") {\n this.leaveChannel(clientId, channel);\n return;\n }\n if (type === \"presence_untrack\") {\n this.removePresence(clientId, channel);\n return;\n }\n\n const action = RealtimeService.CHANNEL_ACTIONS[type];\n const allowed = this.authorizeChannelAction(clientId, channel, action, authContext);\n if (allowed === false) return;\n if (allowed === true) return this.dispatchChannelMessage(clientId, type, channel, payload);\n return allowed.then((ok) => {\n if (ok) return this.dispatchChannelMessage(clientId, type, channel, payload);\n });\n }\n\n /** Perform an already-authorized channel frame. */\n private dispatchChannelMessage(\n clientId: string,\n type: string,\n channel: string,\n payload: Record<string, unknown> | undefined\n ): void | Promise<void> {\n switch (type) {\n case \"join_channel\":\n this.joinChannel(clientId, channel);\n return;\n case \"broadcast\":\n this.broadcastToChannel(clientId, channel, payload?.event as string, payload?.payload);\n return;\n case \"channel_history\":\n return this.handleChannelHistoryRequest(\n clientId,\n channel,\n payload?.sinceSeq as number | undefined,\n payload?.limit as number | undefined\n );\n case \"presence_track\":\n // Auto-join the channel so presence works without a separate join\n this.joinChannel(clientId, channel);\n this.trackPresence(clientId, channel, payload?.state as Record<string, unknown> ?? {});\n return;\n case \"presence_state\":\n this.sendPresenceState(clientId, channel);\n return;\n }\n }\n\n /**\n * Decide whether a client may perform an action on a channel.\n *\n * **Membership is the floor.** Reading a channel's presence roster, replaying\n * its retained history and broadcasting into it all require that this client\n * has joined it. That is a low bar — joining is open to anyone who can name\n * the channel — but it is not the bar that was there before, which was none\n * at all: `channel_history` and `presence_state` answered any socket about\n * any channel, and a broadcast fanned out to members the sender had never\n * joined. Two internal tables (`rebase.channel_presence`,\n * `rebase.channel_messages`) are held outside RLS on the strength of this\n * check, so it fails closed: an authorizer that throws refuses the frame.\n *\n * Anything richer than membership belongs in a {@link ChannelAuthorizer};\n * this method is where it is consulted, and the only place.\n */\n private authorizeChannelAction(\n clientId: string,\n channel: string,\n action: ChannelAction,\n authContext?: SubscriptionAuthContext\n ): boolean | Promise<boolean> {\n // Joining is what establishes membership, so it cannot require it.\n if (action !== \"join\" && !this.channels.get(channel)?.has(clientId)) {\n this.denyChannelAction(clientId, channel, action, \"not a member of the channel\");\n return false;\n }\n\n const authorizer = this.channelAuthorizer;\n if (!authorizer) return true;\n\n let verdict: boolean | Promise<boolean>;\n try {\n verdict = authorizer({ channel, action, clientId, user: authContext });\n } catch (error) {\n logger.error(`❌ [Channels] Authorizer threw for ${action} on \"${channel}\" — refusing`, { error });\n this.denyChannelAction(clientId, channel, action, \"channel authorization failed\");\n return false;\n }\n\n if (typeof verdict === \"boolean\") {\n if (!verdict) this.denyChannelAction(clientId, channel, action, \"refused by the channel authorizer\");\n return verdict;\n }\n\n return verdict.then(\n (ok) => {\n if (!ok) this.denyChannelAction(clientId, channel, action, \"refused by the channel authorizer\");\n return ok;\n },\n (error) => {\n logger.error(`❌ [Channels] Authorizer rejected for ${action} on \"${channel}\" — refusing`, { error });\n this.denyChannelAction(clientId, channel, action, \"channel authorization failed\");\n return false;\n }\n );\n }\n\n /** Tell the client why its channel frame went nowhere, and say so in the log. */\n private denyChannelAction(clientId: string, channel: string, action: ChannelAction, reason: string): void {\n this.debugLog(`🚫 [Channels] Refused ${action} on \"${channel}\" for ${clientId}: ${reason}`);\n this.sendError(\n clientId,\n `Refused ${action} on channel \"${channel}\": ${reason}`,\n undefined,\n \"CHANNEL_FORBIDDEN\",\n channel\n );\n }\n\n /** Join a broadcast channel */\n joinChannel(clientId: string, channel: string): void {\n if (!this.channels.has(channel)) {\n this.channels.set(channel, new Set());\n }\n this.channels.get(channel)!.add(clientId);\n this.warnIfMemoryBusOnMultiplePods();\n this.debugLog(`📡 [Broadcast] Client ${clientId} joined channel: ${channel}`);\n }\n\n /**\n * Say something the first time channels are used on a deployment that is\n * demonstrably multi-pod while the bus is still the in-memory default.\n *\n * Every other warning in this subsystem covers a *configured* bus failing —\n * the case where the operator already knew a bus mattered. The common\n * misconfiguration is the opposite one: scaled to two replicas, never\n * touched `realtime.bus`, and broadcast and presence quietly serve a\n * fraction of the room. The evidence is already in the process, so use it.\n */\n private warnIfMemoryBusOnMultiplePods(): void {\n if (this.memoryBusWarned) return;\n if (this.bus.kind !== \"memory\" || !this.foreignInstanceSeen) return;\n this.memoryBusWarned = true;\n logger.warn(\n \"⚠️ [ChannelBus] Channels are in use with the in-memory bus, but notifications from another \" +\n \"instance have been seen — this deployment runs more than one process. Broadcast and presence \" +\n \"reach only the clients connected to this one. Set `realtime.bus` (or REALTIME_CHANNEL_BUS=postgres) \" +\n \"to make channels cross-instance.\"\n );\n }\n\n /** Leave a broadcast channel */\n leaveChannel(clientId: string, channel: string): void {\n const members = this.channels.get(channel);\n if (members) {\n members.delete(clientId);\n if (members.size === 0) this.channels.delete(channel);\n }\n // Also remove presence\n this.removePresence(clientId, channel);\n }\n\n /**\n * Broadcast a message to all clients in a channel except the sender.\n *\n * On a channel with no retention rule this is what it always was: a\n * synchronous fan-out to whoever is connected, with no sequence number, no\n * SQL and no await — the body below runs to completion before returning.\n *\n * On a retained channel the message is durably numbered first and only then\n * delivered, through a per-channel queue so that delivery order matches\n * sequence order. That ordering is the whole point: a client that catches up\n * with `sinceSeq` has to arrive at the same state as one that never\n * disconnected.\n */\n broadcastToChannel(clientId: string, channel: string, event: string, payload: unknown): void {\n const retention = this.channelHistory?.retentionFor(channel);\n if (!retention) {\n this.fanOutBroadcast(clientId, channel, event, payload);\n // Other instances get the same frame, but never before the clients\n // on this one: the local fan-out above is synchronous and the\n // publish is not, which is also what keeps the ephemeral path free\n // of any await for a single-instance deployment.\n this.publishBroadcast(clientId, channel, event, payload);\n return;\n }\n\n const previous = this.channelSendQueues.get(channel) ?? Promise.resolve();\n const next = previous\n // A failed predecessor must not poison the chain — the next message\n // on this channel is independent and still deserves to be sent.\n .catch(() => { /* already reported below */ })\n .then(() => this.persistAndFanOut(clientId, channel, event, payload, retention));\n\n this.channelSendQueues.set(channel, next);\n void next.finally(() => {\n // Only clear if nothing has queued behind us in the meantime.\n if (this.channelSendQueues.get(channel) === next) this.channelSendQueues.delete(channel);\n });\n }\n\n /**\n * Number a broadcast, store it, then deliver it.\n *\n * A message that cannot be stored is **not** delivered. Delivering it would\n * put it in front of live subscribers while leaving it absent from every\n * future replay — the two views of the channel would disagree permanently,\n * and no later message could repair the gap. Failing loudly to the sender\n * instead lets it retry, which for an operation stream is the only outcome\n * that keeps clients convergent.\n */\n private async persistAndFanOut(\n clientId: string,\n channel: string,\n event: string,\n payload: unknown,\n retention: ResolvedRetention\n ): Promise<void> {\n // Over the Postgres bus the message is announced by the statement that\n // numbers it, and every instance — this one too — delivers it when the\n // announcement arrives: in commit order, which is sequence order. Fanned\n // out here first, a reader on this instance got this instance's N+1\n // before another's N, and a client that keeps a watermark dropped N for\n // good. The announcement is a pointer, whatever the message's size: any\n // database login can LISTEN, so the body stays in the history table and\n // each instance reads it back. See `ChannelHistoryStore.append`.\n const ordered = this.bus instanceof PostgresChannelBus;\n let seq: number;\n try {\n ({ seq } = await this.channelHistory!.append(channel, event, payload, clientId, ordered ? {\n notifyChannel: CHANNEL_BUS_NOTIFY_CHANNEL,\n sid: this.instanceId\n } : undefined));\n } catch (error) {\n logger.error(`❌ [ChannelHistory] Could not persist broadcast on \"${channel}\" — message dropped`, { error });\n this.sendError(\n clientId,\n `Could not persist broadcast on retained channel \"${channel}\"`,\n undefined,\n \"CHANNEL_HISTORY_WRITE_FAILED\",\n channel\n );\n return;\n }\n\n if (!ordered) {\n this.fanOutBroadcast(clientId, channel, event, payload, seq);\n this.publishBroadcast(clientId, channel, event, payload, seq);\n }\n\n try {\n await this.channelHistory!.prune(channel, retention);\n } catch (error) {\n // Retention is a housekeeping concern; the message is already\n // delivered and durable, so a failed prune must not surface as a\n // broadcast failure. It will be retried on the next message.\n logger.warn(`⚠️ [ChannelHistory] Prune failed for \"${channel}\"`, { error });\n }\n }\n\n /** Deliver a broadcast frame to every member of a channel but the sender. */\n private fanOutBroadcast(clientId: string, channel: string, event: string, payload: unknown, seq?: number): void {\n const members = this.channels.get(channel);\n if (!members) return;\n\n const message = JSON.stringify({\n type: \"broadcast\",\n channel,\n event,\n payload,\n ...(seq !== undefined ? { seq } : {})\n });\n\n for (const memberId of members) {\n if (memberId === clientId) continue; // Don't echo back to sender\n this.sendRaw(memberId, message);\n }\n }\n\n // =============================================================================\n // Cross-Instance Channel Bus\n // =============================================================================\n\n /**\n * Install the transport that carries channel frames between instances.\n *\n * Called once at boot. A bus that cannot start is reported and replaced with\n * the memory bus: losing cross-instance fan-out degrades collaboration to\n * what it was before this existed, whereas refusing to boot takes the whole\n * backend down for it.\n */\n async configureChannelBus(bus: ChannelBus): Promise<void> {\n if (bus.kind === \"memory\") {\n this.bus = bus;\n return;\n }\n\n try {\n await bus.start((frame) => this.handleBusFrame(frame));\n } catch (error) {\n logger.warn(\n `⚠️ [ChannelBus] Could not start the \"${bus.kind}\" channel bus — channel broadcast and presence ` +\n \"stay per-instance. Clients served by different replicas will not see each other.\",\n { error }\n );\n await bus.stop().catch(() => { /* best effort */ });\n this.bus = new MemoryChannelBus();\n return;\n }\n\n this.bus = bus;\n\n // Presence needs shared *state*, not just shared fan-out — see\n // `channel-presence.ts`. It comes up with the bus and only with it.\n try {\n const store = new ChannelPresenceStore(this.db, this.instanceId);\n await store.ensureTables();\n this.presenceStore = store;\n this.ensurePresenceSweep();\n } catch (error) {\n logger.warn(\n \"⚠️ [ChannelBus] Could not create the shared presence table — presence rosters will only list \" +\n \"clients connected to this instance (broadcast is unaffected).\",\n { error }\n );\n this.presenceStore = undefined;\n }\n\n logger.info(\n `📡 [ChannelBus] Cross-instance channels active via ${bus.kind} (instanceId: ${this.instanceId}).`\n );\n }\n\n /** Which transport is in use — `\"memory\"` means per-instance only. */\n public getChannelBusKind(): ChannelBus[\"kind\"] {\n return this.bus.kind;\n }\n\n /**\n * Send a broadcast to the other instances.\n *\n * Fire-and-forget by design: the clients on this instance have already been\n * served, and a bus that is briefly unreachable must not turn a broadcast\n * into an error for the sender.\n */\n private publishBroadcast(clientId: string, channel: string, event: string, payload: unknown, seq?: number): void {\n if (this.bus.kind === \"memory\") return;\n\n const frame: ChannelBusFrame = {\n kind: \"broadcast\",\n sid: this.instanceId,\n channel,\n event,\n from: clientId,\n ...(seq !== undefined ? { seq } : {}),\n payload\n };\n\n // Postgres caps a NOTIFY payload at 8 KB. A retained message is already\n // durable and addressable, so it travels as a pointer and each receiver\n // reads the body back — the same shape as the entity path, which\n // notifies an address and refetches the row.\n if (frameByteLength(frame) > this.bus.maxFrameBytes) {\n if (seq === undefined) {\n this.reportOversizedBroadcast(clientId, channel);\n return;\n }\n void this.publishFrame({\n kind: \"broadcast_ref\",\n sid: this.instanceId,\n channel,\n from: clientId,\n seq\n });\n return;\n }\n\n void this.publishFrame(frame);\n }\n\n private async publishFrame(frame: ChannelBusFrame): Promise<void> {\n try {\n await this.bus.publish(frame);\n } catch (error) {\n logger.error(\"❌ [ChannelBus] Failed to publish frame — other instances did not receive it\", {\n detail: `${frame.kind} on \"${frame.channel}\"`,\n error\n });\n }\n }\n\n /**\n * Tell the sender that a message was delivered locally but nowhere else.\n *\n * Staying quiet here would be the worst option available: on one instance\n * the app works, on two it works for half the users, and nothing in the\n * logs connects the two. The fix is a one-liner in config — give the\n * channel a retention rule and the message travels as a pointer instead —\n * so the message says exactly that.\n */\n private reportOversizedBroadcast(clientId: string, channel: string): void {\n const remedy =\n `Add a retention rule for \"${channel}\" (realtime.channels) — retained messages travel by reference ` +\n \"and have no size limit.\";\n\n if (!this.oversizedBroadcastWarned.has(channel)) {\n this.oversizedBroadcastWarned.add(channel);\n logger.warn(\n `⚠️ [ChannelBus] A broadcast on ephemeral channel \"${channel}\" exceeds the ` +\n `${this.bus.maxFrameBytes}-byte limit of the ${this.bus.kind} bus and reached only this instance. ` +\n remedy\n );\n }\n this.sendError(\n clientId,\n `Broadcast on \"${channel}\" was too large to reach other instances. ${remedy}`,\n undefined,\n \"CHANNEL_BUS_PAYLOAD_TOO_LARGE\",\n channel\n );\n }\n\n /**\n * Deliver a frame published by another instance to this one's clients.\n *\n * Frames we published ourselves are dropped on arrival — the local fan-out\n * happened before the publish — exactly as the entity-change handler skips\n * its own `sid`.\n */\n private async handleBusFrame(frame: ChannelBusFrame): Promise<void> {\n const retained = (frame.kind === \"broadcast\" || frame.kind === \"broadcast_ref\") && frame.seq !== undefined;\n // Our own frames were fanned out before they were published — except a\n // retained one over the Postgres bus, which reaches this instance's\n // clients only this way. See `persistAndFanOut`.\n if (frame.sid === this.instanceId && !(retained && this.bus instanceof PostgresChannelBus)) return;\n if (!retained) return this.deliverBusFrame(frame);\n\n // In the order they arrived, per channel: a pointer is read back from\n // the history table, and the frame after it must not overtake it.\n const previous = this.channelReceiveQueues.get(frame.channel) ?? Promise.resolve();\n const next = previous.catch(() => { /* reported where it failed */ }).then(() => this.deliverBusFrame(frame));\n this.channelReceiveQueues.set(frame.channel, next);\n void next.finally(() => {\n if (this.channelReceiveQueues.get(frame.channel) === next) this.channelReceiveQueues.delete(frame.channel);\n }).catch(() => { /* reported where it failed */ });\n return next;\n }\n\n private async deliverBusFrame(frame: ChannelBusFrame): Promise<void> {\n switch (frame.kind) {\n case \"broadcast\":\n this.fanOutBroadcast(frame.from ?? \"\", frame.channel, frame.event, frame.payload, frame.seq);\n return;\n\n case \"broadcast_ref\": {\n // Nothing to read back for: skip the query rather than pay for\n // a message no client here is waiting for.\n if (!this.channels.get(frame.channel)?.size) return;\n\n const entry = await this.channelHistory?.getBySeq(frame.channel, frame.seq);\n if (!entry) {\n logger.warn(\n `⚠️ [ChannelBus] Message ${frame.seq} on \"${frame.channel}\" is no longer retained — ` +\n \"clients on this instance will need to replay (channel_history) to catch up.\"\n );\n return;\n }\n // The ordered path's pointer leaves the sender out — the stored\n // row names it, and the sender is not echoed its own message.\n this.fanOutBroadcast(frame.from ?? entry.senderId ?? \"\", frame.channel, entry.event, entry.payload, entry.seq);\n return;\n }\n\n case \"presence_diff\":\n this.deliverPresenceDiff(frame.channel, frame.joins, frame.leaves);\n return;\n }\n }\n\n // =============================================================================\n // Channel History\n // =============================================================================\n\n /**\n * Install retention rules and create the tables they need.\n *\n * Safe to call with no rules (and safe not to call at all): the store stays\n * inert, no schema is created, and broadcast keeps its original\n * fire-and-forget path.\n */\n async configureChannelHistory(\n rules: ChannelRetentionRule[] | undefined,\n options?: { provision?: boolean }\n ): Promise<void> {\n // The store is built in every process, whether or not this one creates\n // the tables: retaining a message is what a process does when it\n // *publishes* to a retained channel, and a function handler publishes as\n // readily as a websocket client does. Only the DDL is owned.\n this.channelHistory = new ChannelHistoryStore(this.db, rules ?? []);\n if (!this.channelHistory.enabled) return;\n if (options?.provision === false) return;\n await this.channelHistory.ensureTables();\n }\n\n /** Whether any channel is configured to retain messages. */\n public isChannelHistoryEnabled(): boolean {\n return this.channelHistory?.enabled ?? false;\n }\n\n /**\n * Answer a client's catch-up request.\n *\n * A channel with no retention rule is answered with `retained: false`\n * rather than an empty list, so the client can tell \"you missed nothing\"\n * apart from \"this channel never keeps anything\" — the second means its\n * reconnect strategy has to be a full resync, and silence would leave it\n * guessing.\n */\n private async handleChannelHistoryRequest(\n clientId: string,\n channel: string,\n sinceSeq?: number,\n limit?: number\n ): Promise<void> {\n if (!channel) return;\n\n const retention = this.channelHistory?.retentionFor(channel);\n if (!retention) {\n this.sendChannelHistory(clientId, channel, [], false);\n return;\n }\n\n try {\n const { messages, latestSeq } = await this.channelHistory!.replay(channel, sinceSeq, limit);\n this.sendChannelHistory(clientId, channel, messages, true, latestSeq);\n } catch (error) {\n logger.error(`❌ [ChannelHistory] Replay failed for \"${channel}\"`, { error });\n this.sendError(clientId, `Could not replay history for channel \"${channel}\"`, undefined, \"CHANNEL_HISTORY_READ_FAILED\", channel);\n }\n }\n\n private sendChannelHistory(\n clientId: string,\n channel: string,\n messages: ChannelHistoryEntry[],\n retained: boolean,\n latestSeq?: number\n ): void {\n this.sendRaw(clientId, JSON.stringify({\n type: \"channel_history\",\n channel,\n messages,\n retained,\n ...(latestSeq !== undefined ? { latestSeq } : {})\n }));\n }\n\n // =============================================================================\n // Presence\n // =============================================================================\n\n /**\n * Track presence in a channel.\n *\n * The client re-sends this every ~20s as a heartbeat against the 30s\n * timeout, so most calls carry the state that is already recorded. Those\n * refresh `last_seen` and stop there: re-announcing an unchanged state to\n * every instance would put a bus message per client per heartbeat on the\n * wire to tell everyone nothing happened.\n */\n trackPresence(clientId: string, channel: string, state: Record<string, unknown>): void {\n if (!this.presence.has(channel)) {\n this.presence.set(channel, new Map());\n }\n\n const channelPresence = this.presence.get(channel)!;\n const previous = channelPresence.get(clientId);\n const changed = !previous || JSON.stringify(previous.state) !== JSON.stringify(state);\n channelPresence.set(clientId, { state,\nlastSeen: Date.now() });\n\n // Refresh the shared roster on every heartbeat — that timestamp is what\n // tells other instances this client is still here.\n void this.presenceStoreOp(() => this.presenceStore!.track(channel, clientId, state), \"track\");\n\n // Broadcast join / state update to channel\n this.deliverPresenceDiff(channel, { [clientId]: state }, {});\n if (changed) {\n this.publishPresenceDiff(channel, { [clientId]: state }, {});\n }\n\n // Start cleanup interval if not running\n this.ensurePresenceCleanup();\n }\n\n /**\n * Remove presence from a channel.\n *\n * `skipStore` is for the socket-close path, which clears every channel at\n * once and then deletes the client's rows in a single statement instead of\n * one per channel.\n */\n removePresence(clientId: string, channel: string, options?: { skipStore?: boolean }): void {\n const channelPresence = this.presence.get(channel);\n if (!channelPresence) return;\n\n const entry = channelPresence.get(clientId);\n if (entry) {\n channelPresence.delete(clientId);\n this.deliverPresenceDiff(channel, {}, { [clientId]: entry.state });\n this.publishPresenceDiff(channel, {}, { [clientId]: entry.state });\n if (!options?.skipStore) {\n void this.presenceStoreOp(() => this.presenceStore!.remove(channel, clientId), \"remove\");\n }\n }\n\n if (channelPresence.size === 0) {\n this.presence.delete(channel);\n }\n }\n\n /**\n * Send the full roster for a channel to one client.\n *\n * Answered from the shared table when there is one, because \"who is in this\n * document?\" has a single answer that must not depend on which replica the\n * asker happens to be connected to. Without a bus there is nothing to share\n * and the local map *is* the roster — that path stays synchronous, which is\n * what it always was.\n */\n sendPresenceState(clientId: string, channel: string): void {\n if (!this.presenceStore) {\n this.sendPresenceStateMessage(clientId, channel, this.localPresences(channel));\n return;\n }\n\n void this.presenceStore.roster(channel)\n .then((presences) => {\n this.sendPresenceStateMessage(clientId, channel, presences);\n })\n .catch((error) => {\n // A roster the asker can act on beats none: fall back to the\n // clients we can see rather than leaving the request unanswered.\n logger.warn(`⚠️ [Presence] Could not read the shared roster for \"${channel}\" — answering with this instance's clients only.`, { error });\n this.sendPresenceStateMessage(clientId, channel, this.localPresences(channel));\n });\n }\n\n /** Presence of the clients connected to this instance. */\n private localPresences(channel: string): Record<string, Record<string, unknown>> {\n const channelPresence = this.presence.get(channel);\n const presences: Record<string, Record<string, unknown>> = {};\n if (channelPresence) {\n for (const [id, { state }] of channelPresence) {\n presences[id] = state;\n }\n }\n return presences;\n }\n\n private sendPresenceStateMessage(\n clientId: string,\n channel: string,\n presences: Record<string, Record<string, unknown>>\n ): void {\n this.sendRaw(clientId, JSON.stringify({\n type: \"presence_state\",\n channel,\n presences\n }));\n }\n\n /** Deliver a presence diff to this instance's members of the channel. */\n private deliverPresenceDiff(\n channel: string,\n joins: Record<string, Record<string, unknown>>,\n leaves: Record<string, Record<string, unknown>>\n ): void {\n const members = this.channels.get(channel);\n if (!members) return;\n\n const message = JSON.stringify({\n type: \"presence_diff\",\n channel,\n joins,\n leaves\n });\n\n for (const memberId of members) {\n this.sendRaw(memberId, message);\n }\n }\n\n /** Tell the other instances about a presence change. */\n private publishPresenceDiff(\n channel: string,\n joins: Record<string, Record<string, unknown>>,\n leaves: Record<string, Record<string, unknown>>\n ): void {\n if (this.bus.kind === \"memory\") return;\n void this.publishFrame({ kind: \"presence_diff\", sid: this.instanceId, channel, joins, leaves });\n }\n\n /** Run a roster write when there is a roster, and never let it throw. */\n private async presenceStoreOp(op: () => Promise<void>, label: string): Promise<void> {\n if (!this.presenceStore) return;\n try {\n await op();\n } catch (error) {\n logger.warn(`⚠️ [Presence] Shared roster ${label} failed`, { error });\n }\n }\n\n /** Periodic cleanup for stale presences */\n private ensurePresenceCleanup(): void {\n if (this.presenceInterval) return;\n this.presenceInterval = setInterval(() => {\n const now = Date.now();\n for (const [channel, channelPresence] of this.presence) {\n for (const [clientId, entry] of channelPresence) {\n if (now - entry.lastSeen > RealtimeService.PRESENCE_TIMEOUT_MS) {\n this.removePresence(clientId, channel);\n }\n }\n }\n // Stop interval if no presences tracked\n if (this.presence.size === 0 && this.presenceInterval) {\n clearInterval(this.presenceInterval);\n this.presenceInterval = undefined;\n }\n }, 10000); // Check every 10s\n }\n\n /**\n * Reap roster rows whose owning instance stopped heartbeating.\n *\n * This is the cross-instance half of the sweep above, and it doubles as\n * crash recovery: a pod that dies takes its clients with it but leaves\n * their rows behind, and after one TTL window they look exactly like any\n * other client that went quiet. The delete returns what it removed, so\n * whichever instance wins the race is the one that announces the\n * departures — once for the cluster, not once per replica.\n */\n private ensurePresenceSweep(): void {\n if (this.presenceSweepInterval || !this.presenceStore) return;\n\n this.presenceSweepInterval = setInterval(\n () => void this.sweepStalePresence(),\n RealtimeService.PRESENCE_SWEEP_INTERVAL_MS\n );\n\n // Never hold the process open for housekeeping.\n unref(this.presenceSweepInterval);\n }\n\n /** One pass of the stale-roster sweep. See {@link ensurePresenceSweep}. */\n private async sweepStalePresence(): Promise<void> {\n if (!this.presenceStore) return;\n try {\n const removed = await this.presenceStore.sweepStale(RealtimeService.PRESENCE_TIMEOUT_MS);\n for (const row of removed) {\n this.debugLog(`👻 [Presence] Reaped stale presence ${row.clientId} on \"${row.channel}\"`);\n this.deliverPresenceDiff(row.channel, {}, { [row.clientId]: row.state });\n this.publishPresenceDiff(row.channel, {}, { [row.clientId]: row.state });\n }\n } catch (error) {\n logger.warn(\"⚠️ [Presence] Stale-roster sweep failed\", { error });\n }\n }\n\n // =============================================================================\n // Lifecycle / Cleanup\n // =============================================================================\n\n /**\n * Gracefully tear down all realtime resources.\n *\n * This MUST be called during process shutdown, **before** `pool.end()`.\n * It ensures:\n * 1. All debounced refetch timers are cancelled (prevents queries after pool closes).\n * 2. All subscription state and callbacks are cleared.\n * 3. The dedicated LISTEN client (outside the pool) is disconnected.\n * 4. All WebSocket clients are closed with 1001 (\"going away\"); one that\n * does not answer the close frame is dropped after a short grace.\n */\n async destroy(): Promise<void> {\n // 1. Cancel every pending debounced refetch timer\n for (const group of this.groups.values()) {\n if (group.timer) clearTimeout(group.timer);\n group.timer = undefined;\n }\n\n // 2. Clear subscriptions, callbacks and the groups and indexes over them\n this._subscriptions.clear();\n this.subscriptionCallbacks.clear();\n this.subscriptionsByClient.clear();\n this.groups.clear();\n this.collectionGroupsByPath.clear();\n this.singleGroupsByAddress.clear();\n this.subscribedPaths.clear();\n\n // 3. Clear broadcast channels and presence\n this.channels.clear();\n this.presence.clear();\n // Pending history writes hold the pool open; let them settle before the\n // caller closes it, but never let a rejected one break shutdown.\n await Promise.allSettled([...this.channelSendQueues.values(), ...this.channelReceiveQueues.values()]);\n this.channelSendQueues.clear();\n this.channelReceiveQueues.clear();\n this.channelHistory?.clear();\n if (this.presenceInterval) {\n clearInterval(this.presenceInterval);\n this.presenceInterval = undefined;\n }\n if (this.presenceSweepInterval) {\n clearInterval(this.presenceSweepInterval);\n this.presenceSweepInterval = undefined;\n }\n this.oversizedBroadcastWarned.clear();\n\n // Drop this instance's roster rows now rather than leaving every other\n // replica to wait out a TTL window on ghosts — a rolling deploy would\n // otherwise show 30s of departed users on every restart.\n if (this.presenceStore) {\n try {\n await this.presenceStore.removeInstance();\n } catch (error) {\n logger.warn(\"⚠️ [Presence] Could not clear this instance's roster rows on shutdown\", { error });\n }\n this.presenceStore = undefined;\n }\n\n // 4. Disconnect the dedicated LISTEN client(s)\n await this.stopListening();\n await this.stopCdc();\n await this.bus.stop().catch((error) =>\n logger.warn(\"⚠️ [ChannelBus] Error while stopping the channel bus\", { error }));\n this.bus = new MemoryChannelBus();\n\n // 5. Close the client sockets. The HTTP server's close() waits on an\n // upgraded connection for as long as the browser keeps it open, and\n // closeAllConnections() does not reach one either, so a socket left\n // open here holds the whole shutdown until its force timer.\n const sockets = [...this.clients.values()];\n this.clients.clear();\n closeWebSockets(sockets, WEBSOCKET_CLOSE_GRACE_MS);\n\n this.debugLog(\"🧹 [RealtimeService] destroy() complete — all resources released.\");\n }\n\n // =============================================================================\n // Database-level Change Data Capture (CDC)\n // =============================================================================\n\n /**\n * Whether database-level change capture is the source and is listening.\n *\n * Configured is not enough: a CDC connection that went half-open used to\n * leave this `true` while nothing arrived. False while the connection is\n * down and being replaced.\n */\n public isCdcActive(): boolean {\n return this.cdcActive && this.cdcListener?.connected === true;\n }\n\n /** The LISTEN connections this service depends on — see `RealtimeProvider.health`. */\n health(): RealtimeListenerHealth[] {\n const listeners: RealtimeListenerHealth[] = [];\n if (this.cdcActive && this.cdcListener) {\n const { connected, downSince } = this.cdcListener.status();\n listeners.push({ name: \"cdc\", connected, ...(downSince !== undefined ? { downSince } : {}) });\n }\n if (this.crossInstanceListener) {\n const { connected, downSince } = this.crossInstanceListener.status();\n listeners.push({ name: \"cross-instance\", connected, ...(downSince !== undefined ? { downSince } : {}) });\n }\n const bus = this.bus instanceof PostgresChannelBus ? this.bus.status() : undefined;\n if (bus) {\n listeners.push({ name: \"channel-bus\", connected: bus.connected, ...(bus.downSince !== undefined ? { downSince: bus.downSince } : {}) });\n }\n return listeners;\n }\n\n /**\n * Enable database-level change capture as the realtime source.\n *\n * A dedicated LISTEN client consumes committed changes from the `rebase_cdc`\n * channel (fed by CDC triggers — see {@link provisionTriggerCdc}) and routes\n * them into the same {@link notifyUpdate} pipeline used by API mutations. The\n * effect: subscribers see a change no matter how it was written — psql, a\n * cron in another service, raw SQL, or the Studio SQL editor — exactly like\n * Supabase Realtime tailing the WAL.\n *\n * Because CDC observes every commit on every instance, it also *replaces* the\n * legacy per-mutation cross-instance broadcast (see the guard in\n * {@link notifyUpdate}); callers should not also call {@link startListening}.\n *\n * @param connectionString Direct Postgres connection for the LISTEN client\n * (bypass PgBouncer — LISTEN needs a session connection).\n */\n async enableCdc(connectionString: string): Promise<void> {\n if (this.cdcActive) {\n logger.warn(\"⚠️ [CDC] enableCdc called but CDC is already active. Ignoring.\");\n return;\n }\n this.cdcTableMap = this.buildCdcTableMap();\n this.junctionLinkMap = buildJunctionLinkMap(this.registry);\n this.cdcListener = new CdcListener(\n connectionString,\n (event) => this.handleCdcEvent(event),\n () => this.resyncSubscriptions()\n );\n try {\n // start() validates the initial connection; if it can't be established\n // it rejects here, and we leave CDC inactive so the caller can fall\n // back to app-level realtime rather than silently dropping events.\n await this.cdcListener.start();\n } catch (err) {\n await this.cdcListener.stop().catch(() => { /* best effort */ });\n this.cdcListener = undefined;\n this.cdcTableMap = undefined;\n this.junctionLinkMap = undefined;\n throw err;\n }\n this.cdcActive = true;\n // The bootstrapper says the same thing one line later, in the\n // vocabulary of the setting that produced it (REALTIME_CDC).\n logger.debug(\n `📡 [RealtimeService] Database-level change capture ACTIVE — writes from ANY source now emit realtime events ` +\n `(${this.cdcTableMap.size} mapped table key(s)).`\n );\n }\n\n /**\n * Refetch every live subscription, as a change to its path would.\n *\n * Called when a LISTEN connection (CDC, or the cross-instance broadcast)\n * is listening again after a drop. Postgres does not queue NOTIFY for a\n * session that is not listening, so every change committed while it was\n * down — by another instance, psql, a cron — reached nobody here, and a\n * subscriber held its pre-gap rows until some later change to the same\n * collection happened to arrive. Each refetch runs under the\n * subscription's own scope, so this decides only who looks again.\n */\n private resyncSubscriptions(): void {\n for (const group of this.groups.values()) {\n this.scheduleGroupRefetch(group);\n }\n }\n\n /** Stop the CDC listener and clear its state. */\n async stopCdc(): Promise<void> {\n this.cdcActive = false;\n if (this.cdcListener) {\n await this.cdcListener.stop();\n this.cdcListener = undefined;\n }\n this.cdcTableMap = undefined;\n this.junctionLinkMap = undefined;\n this.recentAppEmits.clear();\n }\n\n /**\n * Build the reverse map from database table → collection. A change event\n * carries `schema` + `table`; realtime subscriptions are keyed by collection\n * path (slug). We index by both `schema.table` and bare `table` so the lookup\n * works whether or not the collection declares an explicit schema.\n */\n private buildCdcTableMap(): Map<string, CollectionConfig> {\n const map = new Map<string, CollectionConfig>();\n for (const collection of this.registry.getCollections()) {\n const table = getTableName(collection);\n if (!table) continue;\n const schema = (collection as { schema?: string }).schema ?? \"public\";\n map.set(`${schema}.${table}`, collection);\n // Bare-table fallback; first registration wins to keep it deterministic.\n if (!map.has(table)) map.set(table, collection);\n }\n return map;\n }\n\n private resolveCollectionForTable(schema: string, table: string): CollectionConfig | undefined {\n if (!this.cdcTableMap) return undefined;\n return this.cdcTableMap.get(`${schema}.${table}`) ?? this.cdcTableMap.get(table);\n }\n\n /**\n * Route a captured database change into the realtime pipeline.\n *\n * Delivery is RLS-safe by construction: the event carries the changed row's\n * key and nothing else, and even that is NOT forwarded to subscribers. Instead the change is marked invalidated, so\n * every matching subscription re-reads the row under its own auth context via\n * {@link fetchCollectionWithAuth} / {@link fetchEntityWithAuth}. A subscriber\n * therefore only ever receives rows its RLS policies permit — filtering is per\n * subscriber, never per publisher.\n */\n private async handleCdcEvent(event: CdcChangeEvent): Promise<void> {\n const collection = this.resolveCollectionForTable(event.schema, event.table);\n if (!collection) {\n // A junction table backs no collection, but its rows *are* a child\n // list. Route the change to the lists it changes before giving up.\n if (await this.handleJunctionCdcEvent(event)) return;\n\n // Unmapped table (not backed by a collection) — nothing to deliver.\n this.debugLog(`📡 [CDC] Ignoring change on unmapped table ${event.schema}.${event.table}`);\n return;\n }\n\n const path = collection.slug;\n const databaseId = (collection as { databaseId?: string }).databaseId;\n const id = this.extractIdFromCdcRow(collection, event.row);\n\n // Deletes carry a null row (subscribers drop the id); inserts/updates carry\n // an invalidation marker that forces a per-subscriber RLS-bound refetch.\n const row = event.op === \"DELETE\" ? null : { _rebase_invalidated: true };\n\n await this.notifyUpdate(path, id, row, databaseId, /* broadcast */ false, /* origin */ \"cdc\");\n }\n\n /**\n * Deliver a change on a many-to-many junction table as a change to the child\n * lists it belongs to.\n *\n * Linking a tag to a post writes only `posts_tags`. That table backs no\n * collection, so change capture dropped the event as unmapped and the\n * subscribers of `posts/1/tags` never heard about it — every other write in\n * the system was realtime, and this one silently was not. The junction row\n * carries both ids, so it names its own paths exactly.\n *\n * Notifies the nested path rather than either endpoint collection, because\n * invalidation walks *parent* paths and never child ones: telling `tags` it\n * changed would not reach a subscription on `posts/1/tags`.\n *\n * Returns whether the table was recognised as a junction.\n */\n private async handleJunctionCdcEvent(event: CdcChangeEvent): Promise<boolean> {\n const links = this.junctionLinkMap?.get(`${event.schema}.${event.table}`)\n ?? this.junctionLinkMap?.get(event.table);\n if (!links?.length) return false;\n\n for (const link of links) {\n const sourceId = event.row?.[link.sourceColumn];\n const targetId = event.row?.[link.targetColumn];\n if (sourceId === undefined || sourceId === null || targetId === undefined || targetId === null) {\n this.debugLog(\n `📡 [CDC] Junction row on ${event.table} is missing '${link.sourceColumn}'/'${link.targetColumn}' — skipping.`\n );\n continue;\n }\n\n const path = `${link.parentCollection.slug}/${String(sourceId)}/${link.relationKey}`;\n // An unlink removes the target from this list; a link invalidates it\n // so each subscriber refetches under its own RLS context.\n const row = event.op === \"DELETE\" ? null : { _rebase_invalidated: true };\n\n await this.notifyUpdate(\n path,\n String(targetId),\n row,\n (link.parentCollection as { databaseId?: string }).databaseId,\n /* broadcast */ false,\n /* origin */ \"cdc\"\n );\n }\n\n return true;\n }\n\n /** Compute the canonical (possibly composite) id string from a captured row. */\n private extractIdFromCdcRow(collection: CollectionConfig, row: Record<string, unknown>): string {\n // Unaddressable falls back to a collection-level invalidation: single-row\n // subs won't match, but collection subs still refetch.\n return deriveRowAddress(this.cdcRowByKeyFields(collection, row), collection, this.registry) || \"*\";\n }\n\n /**\n * A captured row with its key under the key's *field* names.\n *\n * The trigger captures the tuple by column — `user_id` — and an address is\n * built from the key's property names — `userId`. Read by property name\n * alone, a key declared apart from its column was never on the captured\n * row, the address fell back to `*`, and no single-row subscriber heard\n * about any write made outside this server.\n */\n private cdcRowByKeyFields(collection: CollectionConfig, row: Record<string, unknown>): Record<string, unknown> {\n const keyed: Record<string, unknown> = { ...row };\n for (const { fieldName, columnName } of collectionKeyColumns(collection, this.registry)) {\n if (fieldName in row) continue;\n if (columnName in row) keyed[fieldName] = row[columnName];\n }\n return keyed;\n }\n\n // ── App/CDC de-duplication ──\n\n private dedupKey(path: string, id: string, databaseId?: string): string {\n return `${databaseId ?? \"\"}::${path}::${id}`;\n }\n\n /** Record that this instance just delivered `key` via the app path. */\n private markAppEmit(key: string): void {\n const now = Date.now();\n this.recentAppEmits.set(key, now + RealtimeService.CDC_DEDUP_WINDOW_MS);\n // Opportunistic purge so the map cannot grow unbounded under write load.\n if (this.recentAppEmits.size > 1000) {\n for (const [k, expiry] of this.recentAppEmits) {\n if (expiry <= now) this.recentAppEmits.delete(k);\n }\n }\n }\n\n /** Consume a matching app-emit record if present and unexpired; true ⇒ suppress the CDC echo. */\n private consumeAppEmit(key: string): boolean {\n const expiry = this.recentAppEmits.get(key);\n if (expiry === undefined) return false;\n this.recentAppEmits.delete(key);\n return expiry > Date.now();\n }\n\n // =============================================================================\n // Cross-Instance LISTEN/NOTIFY\n // =============================================================================\n\n /**\n * Enable cross-instance realtime broadcasting via Postgres LISTEN/NOTIFY.\n * Listens on a dedicated connection (outside the Drizzle pool) for the\n * changes other instances publish, through a {@link PgNotifyListener}: it\n * proves itself with a heartbeat, is replaced when it drops or stops\n * answering, and every subscription is refetched once it is back, since\n * whatever was published meanwhile reached nobody here.\n *\n * A database that cannot be reached at boot does not fail this call: the\n * listener reports itself down (see {@link health}) and keeps dialling.\n * There is no other cross-instance path to fall back to.\n *\n * This is an **optional** feature — if never called, the backend operates\n * in single-instance mode (the default, perfectly fine for most setups).\n *\n * @param connectionString Raw Postgres connection string for the LISTEN client.\n */\n async startListening(connectionString: string): Promise<void> {\n if (this.broadcasting) {\n logger.warn(\"⚠️ [RealtimeService] startListening called but already listening. Ignoring.\");\n return;\n }\n\n this.broadcasting = true;\n this.crossInstanceListener = new PgNotifyListener({\n connectionString,\n channel: PG_NOTIFY_CHANNEL,\n logLabel: \"[RealtimeService]\",\n onPayload: (payload) => this.handleCrossInstanceNotification(payload),\n onReconnect: () => this.resyncSubscriptions(),\n retryInitialConnect: true\n });\n await this.crossInstanceListener.start();\n logger.info(`📡 [RealtimeService] Cross-instance realtime enabled (instanceId: ${this.instanceId})`);\n }\n\n /**\n * Stop listening and clean up the dedicated LISTEN connection.\n */\n async stopListening(): Promise<void> {\n this.broadcasting = false;\n const listener = this.crossInstanceListener;\n if (!listener) return;\n this.crossInstanceListener = undefined;\n await listener.stop();\n logger.info(\"📡 [RealtimeService] Cross-instance realtime disabled.\");\n }\n\n /**\n * Broadcast a change notification to other instances via pg_notify.\n * Uses the main Drizzle connection (pooled) — NOT the LISTEN client.\n */\n private async broadcastChange(path: string, id: string, databaseId?: string): Promise<void> {\n const payload = JSON.stringify({\n sid: this.instanceId,\n p: path,\n eid: id,\n db: databaseId ?? null\n });\n await this.db.execute(drizzleSql`SELECT pg_notify(${PG_NOTIFY_CHANNEL}, ${payload})`);\n }\n\n /** One change another instance published on {@link PG_NOTIFY_CHANNEL}. */\n private async handleCrossInstanceNotification(payload: string): Promise<void> {\n try {\n const { sid, p, eid, db } = JSON.parse(payload) as {\n sid: string;\n p: string;\n eid: string;\n db: string | null;\n };\n\n // Skip our own notifications — already processed locally\n if (sid === this.instanceId) return;\n\n // A foreign sid is proof of a second process. Nothing here\n // needs that fact, but the channel path does — see\n // `warnIfMemoryBusOnMultiplePods`.\n this.foreignInstanceSeen = true;\n\n this.debugLog(`📡 [RealtimeService] Received cross-instance notification: path=${p}, id=${eid}, from=${sid}`);\n\n // Refetch the row from the DB so row subscriptions\n // receive the actual data instead of null (which the client\n // would interpret as \"deleted\").\n let refetchedRow: Record<string, unknown> | null = null;\n try {\n if (this.driver) {\n const collection = this.registry.getCollectionByPath(p);\n const fetched = await this.driver.fetchOne({\n path: p,\n id: eid,\n collection: collection\n });\n refetchedRow = fetched ?? null;\n } else {\n const fetched = await this.dataService.fetchOne(\n p, eid, db ?? undefined\n );\n refetchedRow = fetched ?? null;\n }\n } catch (fetchErr) {\n // If the fetch fails (e.g. row was deleted), refetchedRow stays null\n this.debugLog(`📡 [RealtimeService] Could not refetch row ${eid} from ${p} — treating as deleted`, fetchErr);\n }\n\n // Trigger local fan-out with broadcast=false to avoid re-broadcasting\n await this.notifyUpdate(p, eid, refetchedRow, db ?? undefined, false);\n } catch (err) {\n logger.error(\"❌ [RealtimeService] Error processing cross-instance notification\", { error: err });\n }\n }\n}\n\n/**\n * Alias for RealtimeService for consistent naming with other database implementations.\n * This allows code to use PostgresRealtimeProvider alongside future MongoRealtimeProvider, etc.\n */\nexport const PostgresRealtimeProvider = RealtimeService;\n","/**\n * SQL a person ran, as the audit log may keep it: every string literal masked.\n *\n * The SQL console's audit line wrote the statement verbatim, so `ALTER ROLE app\n * PASSWORD 'hunter2'`, `CREATE USER MAPPING … OPTIONS (password 'hunter2')` and\n * `dblink_connect('host=db password=hunter2')` put the password in the\n * production logs, where the logger's redaction — by key name — never looks.\n *\n * A literal is a value the statement carries inline, which is exactly what the\n * line already declines to write when it is bound as a parameter: the\n * statement's shape is the audit signal, the values are whatever the operator\n * was touching. So every literal goes, not only the ones beside a word that\n * looks like `password` — a secret has no reliable neighbour. Identifiers,\n * keywords, numbers and comments stay.\n *\n * Read with Postgres's own lexical rules, not a regular expression over the\n * text: a quote inside a comment, a doubled quote, an `E'…\\'…'` escape or a\n * `$tag$` body must not shift where a literal is thought to end, or the mask\n * covers the statement and leaves the secret.\n */\nexport function redactSqlLiterals(sql: string): string {\n let out = \"\";\n let i = 0;\n while (i < sql.length) {\n const char = sql[i];\n const next = sql[i + 1];\n\n // -- line comment\n if (char === \"-\" && next === \"-\") {\n const end = sql.indexOf(\"\\n\", i);\n const stop = end === -1 ? sql.length : end;\n out += sql.slice(i, stop);\n i = stop;\n continue;\n }\n\n // /* block comment */ — nested, as Postgres reads them\n if (char === \"/\" && next === \"*\") {\n let depth = 1;\n let j = i + 2;\n while (j < sql.length && depth > 0) {\n if (sql[j] === \"/\" && sql[j + 1] === \"*\") { depth++; j += 2; } else if (sql[j] === \"*\" && sql[j + 1] === \"/\") { depth--; j += 2; } else j++;\n }\n out += sql.slice(i, j);\n i = j;\n continue;\n }\n\n // \"quoted identifier\"\n if (char === \"\\\"\") {\n const end = closingQuote(sql, i + 1, \"\\\"\", false);\n out += sql.slice(i, end);\n i = end;\n continue;\n }\n\n // 'string', E'string' with backslash escapes, and the B'', X'', N'',\n // U&'' prefixes — the prefix is kept, the content masked.\n if (char === \"'\") {\n const prefix = /(?:^|[^A-Za-z0-9_$])([EeBbXxNn]|[Uu]&)$/.exec(out)?.[1] ?? \"\";\n out += \"'***'\";\n i = closingQuote(sql, i + 1, \"'\", prefix.toUpperCase() === \"E\");\n continue;\n }\n\n // $tag$ … $tag$ — a dollar-quoted string. A function or DO body is SQL\n // in its own right, and its literals are masked as these are; a\n // password written as `PASSWORD $$hunter2$$` is masked whole.\n if (char === \"$\") {\n const tag = /^\\$([A-Za-z_][A-Za-z0-9_]*)?\\$/.exec(sql.slice(i));\n const precededByIdentifier = /[A-Za-z0-9_]$/.test(out);\n if (tag && !precededByIdentifier) {\n const delimiter = tag[0];\n const bodyStart = i + delimiter.length;\n const bodyEnd = sql.indexOf(delimiter, bodyStart);\n const stop = bodyEnd === -1 ? sql.length : bodyEnd;\n const body = sql.slice(bodyStart, stop);\n const isPassword = /\\bpassword\\s*$/i.test(out);\n out += delimiter + (isPassword ? \"***\" : redactSqlLiterals(body)) + (bodyEnd === -1 ? \"\" : delimiter);\n i = bodyEnd === -1 ? sql.length : bodyEnd + delimiter.length;\n continue;\n }\n }\n\n out += char;\n i++;\n }\n return out;\n}\n\n/**\n * The index just past the quote that closes a literal or identifier opened\n * before `from`. A doubled quote is one quote inside it; with `backslashEscapes`\n * (an `E''` string) so is a backslash-escaped one. Unterminated runs to the end.\n */\nfunction closingQuote(sql: string, from: number, quote: string, backslashEscapes: boolean): number {\n let j = from;\n while (j < sql.length) {\n if (backslashEscapes && sql[j] === \"\\\\\") { j += 2; continue; }\n if (sql[j] === quote) {\n if (sql[j + 1] === quote) { j += 2; continue; }\n return j + 1;\n }\n j++;\n }\n return sql.length;\n}\n","import type { SubscriptionAuthContext } from \"./services/realtimeService\";\nimport { BranchingUnsupportedError } from \"./services/BranchService\";\nimport { PostgresBackendDriver, effectiveSqlRole, sqlRoleRanAs } from \"./PostgresBackendDriver\";\nimport { assertReadRequestReadable } from \"./services/read-field-access\";\nimport { isNestedPath, resolveNestedPath } from \"./services/nested-path\";\nimport type { CollectionConfig, DataDriver, DeleteProps, FetchCollectionProps, FetchOneProps, SaveProps, TableMetadata, BranchInfo, AuthAdapter, DataRateLimitCaller, RealtimeProvider, RealtimeSocketOptions, WebSocketMessage } from \"@rebasepro/types\";\nimport type { SqlScriptResult } from \"@rebasepro/types\";\nimport { redactSqlLiterals } from \"./utils/sql-redaction\";\nimport { reapSilentSockets, terminateIfBacklogged } from \"./services/socket-liveness\";\nimport { ANONYMOUS_USER_ID, hasAdminRole, isSQLAdmin, isSchemaAdmin, resolveClientListLimit, ListLimitError, scopeGrants, scopesForRoles, type AdminScope } from \"@rebasepro/types\";\nimport type { User } from \"@rebasepro/types\";\n\nimport { WebSocketServer, WebSocket, type RawData } from \"ws\";\nimport type { Server, IncomingMessage } from \"http\";\nimport { inspect } from \"util\";\nimport { extractUserFromToken, AccessTokenPayload, safeCompare, resolveRequireAuth, assertWriteRequestValid, assertFieldOpsValid, assertNoFieldOpsOnCreate, declaredErrorAnswer, RUNTIME_DEFAULT_MAX_BODY_SIZE, ApiError, resolveConflictTarget, assertNestedWriteAllowed, assertUserCreationBodyValid, createUserThroughAuthCollection, type NestedWriteKind } from \"@rebasepro/server\";\nimport { logger, getAccessModel, decideImpersonation, type ImpersonationCredential } from \"@rebasepro/server\";\n\n/**\n * What this socket calls on the realtime side, and all of it.\n *\n * One `RealtimeService`, or — on a project with several realtime-capable\n * sources — the routed composite over them (`createRoutedRealtimeService`).\n * The parameter used to be typed as the one implementation and the composite\n * was cast to it, so when `AUTHENTICATE` began calling `rescopeClient`, which\n * the composite never had, nothing failed until every multi-source sign-in\n * answered INTERNAL_ERROR. Declared here, by the caller, so what is handed in\n * is checked against what is called.\n */\nexport interface SocketRealtimeService {\n addClient(clientId: string, ws: WebSocket): void;\n rescopeClient(clientId: string, authContext: SubscriptionAuthContext): Promise<void>;\n handleClientMessage(clientId: string, message: WebSocketMessage, authContext?: SubscriptionAuthContext): Promise<void> | void;\n}\n\n/**\n * Narrow a bootstrapper's realtime provider to what the socket calls.\n *\n * The provider arrives typed as the engine-agnostic `RealtimeProvider`, which\n * declares none of the client-connection methods. Checked rather than cast, so\n * a provider missing one refuses at boot, naming it, instead of on a client's\n * first frame.\n */\nexport function socketRealtimeMissing(provider: RealtimeProvider): string[] {\n return ([\"addClient\", \"rescopeClient\", \"handleClientMessage\"] as const)\n .filter((method) => !(method in provider) || typeof Reflect.get(provider, method) !== \"function\");\n}\n\nexport function isSocketRealtimeService(provider: RealtimeProvider): provider is RealtimeProvider & SocketRealtimeService {\n return socketRealtimeMissing(provider).length === 0;\n}\n\n/** What a script run says besides its rows, which an `EXECUTE_SQL` answer carries as `result`. */\nfunction scriptDescription({ rows: _rows, ...description }: SqlScriptResult): Omit<SqlScriptResult, \"rows\"> {\n return description;\n}\n\n/** Minimal subset of RebaseAuthConfig used by the WebSocket layer. */\ninterface WsAuthConfig {\n requireAuth?: boolean;\n jwtSecret?: string;\n /**\n * Same static server-to-server secret the HTTP middleware accepts. Without\n * it here, a service key authenticates over HTTP but not over the socket —\n * so any SDK client using one (scripts, cron, server-to-server) connects,\n * fails realtime auth with \"jwt malformed\", and silently gets no events.\n */\n serviceKey?: string;\n}\n\n/**\n * Normalized user identity for WebSocket sessions.\n */\ninterface WsUserIdentity {\n uid: string;\n roles: string[];\n isAdmin: boolean;\n /**\n * What this session may do, when its credential narrows its person — an\n * API key. Absent for a person's own session, who holds what their roles\n * hold.\n */\n scopes?: string[];\n /**\n * Whether this session is a guest — anonymous sign-in rather than an\n * account. Read from the token, because a socket has no request to look\n * anything up on and every refetch it triggers needs the same principal the\n * initial HTTP fetch had. Required, so every way of signing in has to say:\n * the adapter path left it out, and its guests were accounts.\n */\n isAnonymous: boolean;\n /**\n * The token's custom claims, for the same reason `isAnonymous` is here: a\n * socket has no request to look anything up on, and every refetch it\n * triggers has to evaluate the same policies against the same principal.\n * A tenancy policy reads a claim.\n */\n claims?: Record<string, unknown>;\n /**\n * The administrator this session acts for, when it authenticated with\n * `impersonate`: every other field is then the impersonated user's.\n */\n impersonator?: string;\n /**\n * The API key this session authenticated with: its frames count in the\n * key's own rate-limit bucket, at the key's own `rate_limit`, as its HTTP\n * requests do.\n */\n apiKey?: { id: string; rate_limit: number | null };\n}\n\n/**\n * The uid the service key authenticates as, here and through the built-in\n * auth adapter — which is how the socket tells that credential apart from a\n * person's session.\n */\nconst SERVICE_KEY_UID = \"service\";\n\ninterface ClientSession {\n ws: WebSocket;\n user?: WsUserIdentity;\n authenticated: boolean;\n /**\n * The credential `user` was resolved from, kept so the identity can be\n * re-read: every frame asks again, and so does the sweep for a socket\n * that only listens. See `recheckIdentity`.\n */\n credential?: string;\n /**\n * The uid `AUTHENTICATE` asked to act as, kept beside the credential so\n * every re-check decides the impersonation again — a demoted\n * administrator, or a disabled target, ends the session.\n */\n impersonate?: string;\n /** When `user` was last confirmed against the credential, in ms. */\n checkedAt: number;\n /** Ends the session the instant the credential expires. */\n expiryTimer?: ReturnType<typeof setTimeout>;\n /** Sliding window message counter for rate limiting */\n messageCount: number;\n messageWindowStart: number;\n /** The same window, counted separately for channel frames. */\n channelMessageCount: number;\n channelWindowStart: number;\n}\n\n\n/** Maximum messages per client per window */\nconst WS_RATE_LIMIT = 2000;\n/** Rate limit window in milliseconds (60 seconds) */\nconst WS_RATE_WINDOW_MS = 60_000;\n\n/**\n * Channel frames get their own budget, because they are a different workload.\n *\n * 2000/minute is 33/second, which is generous for queries and subscriptions and\n * an order of magnitude below what the documented channel idiom asks for: the\n * capacity note in `docs/backend/realtime.md` uses 60 fps cursor movement as\n * its worked example, and the presence idiom re-`track()`s on every move, so\n * one client sustaining that sends ~120 frames/second — 7200 a minute. Sharing\n * one counter meant the cursor stream ate the query budget and then froze for\n * the rest of the window.\n *\n * The number is sized to that documented workload and nothing more; it is not\n * a considered product limit (see `docs/channel-authorization.md`).\n */\nconst WS_CHANNEL_RATE_LIMIT = 7200;\n\n/**\n * The largest frame a socket that has not authenticated may send, in bytes.\n *\n * All such a socket can usefully say is `AUTHENTICATE` with a token — a few\n * KiB with custom claims — and everything else is answered `UNAUTHORIZED`.\n * Without this, each of those answers came after the whole frame was buffered,\n * stringified and parsed, up to the body limit: a handful of connections that\n * never signed in could hold the process's memory. A larger frame closes the\n * socket with 1009 before it is read.\n */\nconst MAX_UNAUTHENTICATED_FRAME_BYTES = 64 * 1024;\n\n/**\n * How often a socket that only listens has its identity re-read, in ms.\n *\n * A socket that sends frames is re-checked before each one, as an HTTP request\n * is; a socket that only receives pushes sends none, so without this a\n * subscription opened by an identity that has since signed out everywhere, been\n * demoted or deleted went on receiving rows as that identity for as long as the\n * connection lasted.\n */\nconst DEFAULT_IDENTITY_RECHECK_MS = 30_000;\n\n/**\n * Channel frames are re-checked at most this often, in ms. They are the\n * high-rate traffic (see `WS_CHANNEL_RATE_LIMIT`): a database read for every\n * cursor position would make realtime presence the busiest query a backend\n * runs, for a window no wider than this.\n */\nconst CHANNEL_RECHECK_MIN_MS = 1_000;\n\n/**\n * The close code for a socket whose identity ended: signed out, revoked,\n * demoted out of existence, deleted or expired. In the 4000–4999 range the\n * protocol leaves to applications. The SDK reconnects and authenticates with\n * whatever session it holds now — a refreshed token, or none.\n */\nexport const SESSION_ENDED_CLOSE_CODE = 4001;\n\n/**\n * When a JWT expires, in ms, or undefined for a credential that is not one\n * (an API key, the service key, an adapter's opaque token).\n *\n * Read without verifying, and only ever for a token that was verified a moment\n * ago by the same request: the signature is not in question, only the instant.\n */\nfunction jwtExpiryMs(token: string): number | undefined {\n const parts = token.split(\".\");\n if (parts.length !== 3) return undefined;\n try {\n const claims: unknown = JSON.parse(Buffer.from(parts[1], \"base64url\").toString(\"utf8\"));\n if (typeof claims !== \"object\" || claims === null || !(\"exp\" in claims)) return undefined;\n return typeof claims.exp === \"number\" ? claims.exp * 1000 : undefined;\n } catch {\n return undefined;\n }\n}\n\n/** Everything about an identity that decides what its frames may do. */\nfunction identityFingerprint(user: WsUserIdentity): string {\n return JSON.stringify([user.uid, [...user.roles].sort(), user.isAdmin, user.isAnonymous, user.scopes ?? null, user.claims ?? null, user.apiKey ?? null]);\n}\n\n/** Frames counted against the channel budget rather than the general one. */\nconst CHANNEL_MESSAGE_TYPES = new Set([\n \"join_channel\",\n \"leave_channel\",\n \"broadcast\",\n \"presence_track\",\n \"presence_untrack\",\n \"presence_state\",\n \"channel_history\"\n]);\n\n/**\n * WebSocket message types on the admin plane, and the scope each needs.\n *\n * Exported so the test can READ it. A privileged verb added without a scope\n * check is what this list exists to make impossible: `PUBLIC_TYPES` below is\n * its counterpart, and a test asserts that every `case` this file handles\n * appears in exactly one of the two — so a verb added to neither fails rather\n * than defaulting to reachable.\n *\n * All of these run on the owner connection, outside RLS. `database:read`\n * covers the catalogue — including `FETCH_APPLICATION_ROLES`, which runs\n * `SELECT DISTINCT unnest(roles)` over the users table — and\n * `database:write` covers running SQL and creating or deleting branches.\n */\nexport const ADMIN_PLANE_TYPES: ReadonlyMap<string, AdminScope> = new Map<string, AdminScope>([\n [\"EXECUTE_SQL\", \"database:write\"],\n [\"CREATE_BRANCH\", \"database:write\"],\n [\"DELETE_BRANCH\", \"database:write\"],\n [\"FETCH_DATABASES\", \"database:read\"],\n [\"FETCH_ROLES\", \"database:read\"],\n [\"FETCH_APPLICATION_ROLES\", \"database:read\"],\n [\"FETCH_UNMAPPED_TABLES\", \"database:read\"],\n [\"FETCH_TABLE_METADATA\", \"database:read\"],\n [\"FETCH_CURRENT_DATABASE\", \"database:read\"],\n [\"LIST_BRANCHES\", \"database:read\"]\n]);\n\n/**\n * The data scope each data verb needs, for a session whose credential narrows\n * it. A person's own session holds the whole data plane; RLS decides its rows.\n */\nconst DATA_VERB_SCOPES: ReadonlyMap<string, string> = new Map([\n [\"FETCH_COLLECTION\", \"data:read\"],\n [\"FETCH_ONE\", \"data:read\"],\n [\"COUNT\", \"data:read\"],\n [\"CHECK_UNIQUE_FIELD\", \"data:read\"],\n [\"subscribe_collection\", \"data:read\"],\n [\"subscribe_one\", \"data:read\"],\n [\"SAVE\", \"data:write\"],\n [\"DELETE\", \"data:delete\"]\n]);\n\n/**\n * Message types deliberately reachable by a non-admin session.\n *\n * Data operations, gated per row by RLS and per request by the same write\n * checks the REST layer applies — not by this list. It exists so that \"which\n * bucket is this verb in\" is a question with an answer for every verb, and\n * adding one without answering it is a test failure.\n */\nexport const PUBLIC_TYPES = new Set([\n \"FETCH_COLLECTION\",\n \"FETCH_ONE\",\n \"COUNT\",\n \"SAVE\",\n \"DELETE\",\n \"CHECK_UNIQUE_FIELD\"\n]);\n\n/**\n * Recursively extract the deepest error message from an error's cause chain (e.g., Drizzle wrapping a PG error).\n */\nfunction extractErrorMessage(error: unknown): string {\n if (!error) return \"Unknown error\";\n if (error instanceof Error) {\n if (\"cause\" in error && error.cause) {\n return extractErrorMessage(error.cause);\n }\n return error.message;\n }\n if (typeof error === \"object\" && \"message\" in error && typeof (error as { message: unknown }).message === \"string\") {\n return (error as { message: string }).message;\n }\n return String(error);\n}\n\n/**\n * Who a socket reads and writes as, for its request frames and its\n * subscriptions alike.\n *\n * One answer because there were two, and they disagreed about guests. The\n * subscriptions carried the session's `isAnonymous`; the request frames wrote\n * `false` for every signed-in session, so a guest read and wrote as an account\n * through `FETCH_*`, `SAVE` and `DELETE` while its subscriptions did not. And a\n * socket with no session was a guest on the request frames (`true`) but not on\n * its subscriptions or over REST: an unauthenticated caller is not a guest\n * session.\n */\nfunction sessionAuthContext(session: ClientSession | undefined): SubscriptionAuthContext {\n if (!session?.user) {\n return { uid: ANONYMOUS_USER_ID, roles: [\"anon\"], isAnonymous: false };\n }\n return {\n uid: session.user.uid,\n roles: session.user.roles ?? [],\n isAnonymous: session.user.isAnonymous === true,\n // A tenancy policy reads a claim, so a frame without them answers\n // from the tenant of nobody.\n ...(session.user.claims ? { claims: session.user.claims } : {})\n };\n}\n\n/** Everything a session may do: its credential's scopes, or its roles'. */\nfunction sessionScopes(session: ClientSession | undefined): string[] {\n if (!session?.user) return [];\n return session.user.scopes ?? scopesForRoles(session.user.roles, getAccessModel());\n}\n\n/**\n * The collection a data frame addresses, for a narrowed session's scope check.\n * A nested path (`authors/1/posts`) answers undefined: its target is a\n * relation's, not a slug on the wire, so only an unqualified scope covers it.\n */\nfunction frameCollection(type: string, payload: unknown): string | undefined {\n if (typeof payload !== \"object\" || payload === null) return undefined;\n let path: unknown = \"path\" in payload ? payload.path : undefined;\n if (type === \"DELETE\" && \"row\" in payload && typeof payload.row === \"object\" && payload.row !== null && \"path\" in payload.row) {\n path = payload.row.path;\n }\n if (typeof path !== \"string\" || path === \"\" || isNestedPath(path)) return undefined;\n return path;\n}\n\n/** A frame's size in bytes, however `ws` delivered it. */\nfunction frameBytes(data: RawData): number {\n if (Array.isArray(data)) return data.reduce((total, chunk) => total + chunk.length, 0);\n return data.byteLength;\n}\n\n/**\n * Where a connection comes from, as the data API's rate limiter reads an\n * address: the upgrade request's headers (believed only as far as proxies are\n * declared) and the socket's own address.\n */\nfunction connectionOrigin(request: IncomingMessage | undefined): Omit<DataRateLimitCaller, \"uid\"> {\n return {\n header: (name) => {\n const value = request?.headers[name];\n return Array.isArray(value) ? value.join(\", \") : value;\n },\n socketAddress: request?.socket?.remoteAddress\n };\n}\n\n/**\n * `options` carry the data API's limits, for the same rows reached through this door:\n * frames larger than its body limit close the socket with 1009 before they are\n * read, and every data frame counts in the caller's data-API bucket — the one\n * their HTTP requests count in — rather than only in a budget per connection,\n * which a caller multiplied by opening more.\n */\nexport function createPostgresWebSocket(\n server: Server,\n realtimeService: SocketRealtimeService,\n driver: PostgresBackendDriver,\n authConfig?: WsAuthConfig,\n authAdapter?: AuthAdapter,\n options?: RealtimeSocketOptions\n) {\n // Session map scoped to this factory invocation — prevents stale sessions\n // leaking across hot reloads or multiple factory calls.\n const clientSessions = new Map<string, ClientSession>();\n\n const isProduction = process.env.NODE_ENV === \"production\";\n /**\n * Debug tracing for this socket.\n *\n * Through `logger.debug`, not `console.debug`. A bare console call sits\n * outside the one level system, so `LOG_LEVEL=warn` — a line the scaffold's\n * own `.env.example` ships — silenced every structured line in the process\n * and left these ones printing; the deletion of the old `console.debug`\n * override is what exposed that. It also skipped the redactor, and the\n * extras these calls carry are rows.\n *\n * The production guard stays on top of the level gate rather than being\n * replaced by it: these lines trace requests and their payloads, and\n * `LOG_LEVEL=debug` on a deployment is not consent to put a user's row on\n * stdout.\n */\n const wsDebug = (message: string, ...extras: unknown[]) => {\n if (isProduction) return;\n if (extras.length === 0) logger.debug(message);\n else logger.debug(message, { details: extras.length === 1 ? extras[0] : extras });\n };\n // `ws` defaults to 100 MiB. The data API refuses a body over its limit\n // before any handler runs; this is the same limit, enforced by `ws` while\n // it reads the frame. `0` is \"none\" to both.\n const maxPayload = options?.maxPayload ?? RUNTIME_DEFAULT_MAX_BODY_SIZE;\n const wss = new WebSocketServer({ server, maxPayload: maxPayload > 0 ? maxPayload : 0 });\n\n // Handle errors on the WSS so that EADDRINUSE from the underlying HTTP\n // server doesn't surface as an unhandled 'error' event and crash the\n // process. The dev-mode `listenWithPortRetry` utility handles retry\n // logic on the HTTP server side — we just need the WSS not to throw.\n wss.on(\"error\", (err: NodeJS.ErrnoException) => {\n if (err.code === \"EADDRINUSE\") {\n // Silently absorbed — listenWithPortRetry will retry the next port\n return;\n }\n logger.error(\"❌ [WebSocket Server] Error\", { error: err });\n });\n\n // The same predicate the HTTP data routes use, from the same function —\n // this socket is the other enforcement point for one product decision, and\n // while it computed the answer itself it computed a different one. See\n // `resolveRequireAuth` for what its local copy got wrong and why a `false`\n // here grants access rather than skipping a check.\n const requireAuth = !!authAdapter || resolveRequireAuth(authConfig as never);\n\n if (requireAuth && !authAdapter && !authConfig?.jwtSecret && !authConfig?.serviceKey) {\n logger.warn(\n \"🔐 [WebSocket Server] Authentication is required but no adapter, jwtSecret or \" +\n \"serviceKey is configured — no client can complete AUTH, so every realtime \" +\n \"message will be refused with UNAUTHORIZED.\"\n );\n }\n\n /**\n * Who a credential is, now: the same verification `AUTHENTICATE` runs, so\n * re-asking it later answers what signing in would answer. A refusal is a\n * value; a store that cannot answer throws.\n */\n async function resolveCaller(token: string): Promise<{ user: WsUserIdentity; credential: ImpersonationCredential } | { refused: string; apiKey?: true }> {\n if (token.startsWith(\"rk_\")) {\n // An API key: the same verification the HTTP middlewares run, so a\n // key means one thing on both — revoked or expired included.\n const resolved = options?.resolveApiKey ? await options.resolveApiKey(token) : undefined;\n if (!resolved || !(\"uid\" in resolved)) {\n return { refused: resolved?.message ?? \"API keys are not enabled on this server\", apiKey: true };\n }\n return {\n credential: \"api-key\",\n user: {\n uid: resolved.uid,\n roles: resolved.roles,\n isAdmin: hasAdminRole(resolved.roles),\n isAnonymous: false,\n scopes: resolved.scopes,\n ...(resolved.apiKey ? { apiKey: { id: resolved.apiKey.id, rate_limit: resolved.apiKey.rate_limit } } : {})\n }\n };\n }\n\n if (authAdapter) {\n // Custom auth, Clerk, the built-in auth — anything that is an\n // adapter. One that throws could not answer (its store is\n // unreachable); that is not a refusal, and the caller decides\n // what a question nobody could answer means.\n const adapterUser = authAdapter.verifyToken\n ? await authAdapter.verifyToken(token)\n : await authAdapter.verifyRequest(new Request(\"http://localhost/_ws_auth\", {\n headers: { Authorization: `Bearer ${token}` }\n }));\n if (!adapterUser) return { refused: \"Invalid or expired token\" };\n return {\n // The built-in adapter verifies the service key as its own\n // identity rather than as a person.\n credential: adapterUser.uid === SERVICE_KEY_UID ? \"service-key\" : \"session\",\n user: {\n uid: adapterUser.uid,\n roles: adapterUser.roles,\n isAdmin: adapterUser.isAdmin,\n // Read, as the JWT path reads it: dropped here, a guest\n // signed in through an adapter subscribed as an account.\n // Absent from an adapter with no such concept, and absent\n // reads as \"not a guest\".\n isAnonymous: adapterUser.isAnonymous === true,\n // Kept, as the JWT branch keeps them: the built-in auth is\n // an adapter, so dropping them here left every\n // claim-tenanted collection with no tenant on the socket.\n ...(adapterUser.claims ? { claims: adapterUser.claims } : {})\n }\n };\n }\n\n if (authConfig?.serviceKey && safeCompare(token, authConfig.serviceKey)) {\n // Service key: a static secret, not a JWT. Checked before\n // verification, mirroring the HTTP middleware — verifying it as a\n // JWT can only ever fail.\n return { credential: \"service-key\", user: { uid: SERVICE_KEY_UID, roles: [\"admin\"], isAdmin: true, isAnonymous: false } };\n }\n\n // Standard JWT path: the signature and the expiry, nothing more —\n // there is no account store on this path to ask.\n const jwtPayload = await extractUserFromToken(token);\n if (!jwtPayload) return { refused: \"Invalid or expired token\" };\n return {\n credential: \"session\",\n user: {\n uid: jwtPayload.uid,\n roles: jwtPayload.roles ?? [],\n isAdmin: hasAdminRole(jwtPayload.roles ?? []),\n isAnonymous: jwtPayload.isAnonymous === true,\n claims: jwtPayload.claims\n }\n };\n }\n\n /**\n * Who this socket runs as: its caller, or — when it asked to `impersonate`\n * — the user it named, by the same decision the data API takes for\n * `x-rebase-impersonate`. A refusal carries the code to answer with.\n *\n * `audit` is passed by `AUTHENTICATE` only: the re-check before every frame\n * decides again without writing a security event each time.\n */\n async function resolveIdentity(\n token: string,\n impersonate?: string,\n audit?: Record<string, unknown>\n ): Promise<{ user: WsUserIdentity } | { refused: string; apiKey?: true; code?: string }> {\n const caller = await resolveCaller(token);\n if (!(\"user\" in caller) || impersonate === undefined) return caller;\n\n const decision = await decideImpersonation({\n requestedUid: impersonate,\n credential: caller.credential,\n caller: caller.user,\n resolveUser: authAdapter?.resolveUser ? authAdapter.resolveUser.bind(authAdapter) : undefined,\n audit\n });\n if (\"refused\" in decision) return { refused: decision.refused.message, code: decision.refused.code };\n const target = decision.granted;\n return {\n user: {\n uid: target.uid,\n roles: target.roles,\n isAdmin: hasAdminRole(target.roles),\n isAnonymous: target.isAnonymous === true,\n ...(target.claims ? { claims: target.claims } : {}),\n impersonator: decision.impersonator\n }\n };\n }\n\n /** Stop honouring the session at the instant its token expires. */\n function scheduleExpiry(clientId: string, token: string): void {\n const session = clientSessions.get(clientId);\n if (!session) return;\n if (session.expiryTimer) clearTimeout(session.expiryTimer);\n session.expiryTimer = undefined;\n const expiresAt = jwtExpiryMs(token);\n if (expiresAt === undefined) return;\n // `setTimeout` overflows past 2^31-1 ms (~24.8 days) and fires at once;\n // a token living longer than that is re-checked by the sweep instead.\n const delay = Math.max(0, expiresAt - Date.now());\n if (delay > 2_147_483_647) return;\n session.expiryTimer = setTimeout(() => {\n const current = clientSessions.get(clientId);\n if (current?.credential === token) {\n endSession(clientId, \"TOKEN_EXPIRED\", \"The token this socket authenticated with has expired.\");\n }\n }, delay);\n session.expiryTimer.unref?.();\n }\n\n /**\n * The identity this socket authenticated as is over: say so, forget it,\n * and close. Closing rather than carrying on anonymously, because every\n * subscription the socket holds was opened as that identity — the SDK\n * reconnects, authenticates with whatever session it has now, and\n * re-subscribes as that.\n */\n function endSession(clientId: string, code: string, message: string): void {\n const session = clientSessions.get(clientId);\n if (!session) return;\n if (session.expiryTimer) clearTimeout(session.expiryTimer);\n session.expiryTimer = undefined;\n session.user = undefined;\n session.credential = undefined;\n session.authenticated = !requireAuth;\n wsDebug(`[WS] ${clientId} session ended: ${code}`);\n try {\n session.ws.send(JSON.stringify({ type: \"AUTH_ERROR\", payload: { error: { code, message } } }));\n } catch {\n // Already closing.\n }\n session.ws.close(SESSION_ENDED_CLOSE_CODE, message.slice(0, 120));\n }\n\n /**\n * Is the identity this socket authenticated as still the one its\n * credential names?\n *\n * Asked of the database, through the same verification as\n * `AUTHENTICATE`, before each frame and by the sweep — never by an event\n * one instance publishes to the others. A sign-out, a demotion or a\n * deletion is written to the database by whichever instance served it;\n * re-reading there is correct on every instance with nothing in between,\n * where an event would reach only the instances a bus connects (and the\n * bus is opt-in).\n *\n * - `\"live\"`: still honoured; roles or claims that changed are applied,\n * and the socket's subscriptions are re-scoped to them.\n * - `\"ended\"`: refused — revoked, signed out, deleted, expired. The\n * socket is closed.\n * - `\"unavailable\"`: the store could not answer. The frame is refused,\n * the session kept, and the next frame or sweep asks again.\n */\n async function recheckIdentity(clientId: string): Promise<\"live\" | \"ended\" | \"unavailable\"> {\n const session = clientSessions.get(clientId);\n const credential = session?.credential;\n if (!session?.user || !credential) return \"live\";\n let outcome: Awaited<ReturnType<typeof resolveIdentity>>;\n try {\n outcome = await resolveIdentity(credential, session.impersonate);\n } catch {\n return \"unavailable\";\n }\n // Re-authenticated while this was in flight: that answer is newer.\n if (session.credential !== credential || !session.user) return session.user ? \"live\" : \"ended\";\n session.checkedAt = Date.now();\n if (!(\"user\" in outcome)) {\n // An impersonation that no longer holds — the administrator was\n // demoted, the user disabled — says which; anything else is the\n // credential itself.\n if (outcome.code) endSession(clientId, outcome.code, outcome.refused);\n else endSession(clientId, \"SESSION_ENDED\", \"This session has ended: it was signed out, revoked, or its account changed. Authenticate again.\");\n return \"ended\";\n }\n if (identityFingerprint(outcome.user) !== identityFingerprint(session.user)) {\n session.user = outcome.user;\n await realtimeService.rescopeClient(clientId, sessionAuthContext(session));\n }\n return \"live\";\n }\n\n // The sweep: a socket that only listens sends nothing to be re-checked\n // before, so it is re-checked on a timer instead.\n const identityRecheckMs = Math.max(250, options?.identityRecheckIntervalMs ?? DEFAULT_IDENTITY_RECHECK_MS);\n const identitySweep = setInterval(() => {\n const now = Date.now();\n for (const [id, session] of clientSessions) {\n if (session.user && session.credential && now - session.checkedAt >= identityRecheckMs / 2) {\n void recheckIdentity(id).catch(() => undefined);\n }\n }\n }, identityRecheckMs);\n // Unref'd, so it never holds a process open; stopped with whichever goes\n // first, the socket server or the HTTP server it is attached to.\n identitySweep.unref?.();\n const stopSweep = () => clearInterval(identitySweep);\n wss.on(\"close\", stopSweep);\n if (typeof server.on === \"function\") server.on(\"close\", stopSweep);\n\n wss.on(\"connection\", (ws, request?: IncomingMessage) => {\n const clientId = `client_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;\n const origin = connectionOrigin(request);\n wsDebug(`WebSocket client connected: ${clientId}`);\n\n // Initialize client session\n clientSessions.set(clientId, { ws,\nauthenticated: !requireAuth,\ncheckedAt: Date.now(),\nmessageCount: 0,\nmessageWindowStart: Date.now(),\nchannelMessageCount: 0,\nchannelWindowStart: Date.now() });\n realtimeService.addClient(clientId, ws);\n\n ws.on(\"close\", () => {\n wsDebug(`WebSocket client disconnected: ${clientId}`);\n const session = clientSessions.get(clientId);\n if (session?.expiryTimer) clearTimeout(session.expiryTimer);\n clientSessions.delete(clientId);\n });\n\n // A frame over `maxPayload` is refused by `ws` as an `error` on this\n // socket, then closed with 1009. An `error` with no listener is thrown\n // by the emitter — out of the process — so the socket that set the\n // limit listens for it rather than trusting a realtime provider to.\n ws.on(\"error\", (error: Error) => {\n wsDebug(`WebSocket client ${clientId} error: ${error.message}`);\n });\n\n // Route all messages through RealtimeService for unified handling\n ws.on(\"message\", async (message: RawData) => {\n // Every frame is answered, so one from a client that has not read\n // its earlier answers would only add to a backlog it is not draining.\n if (terminateIfBacklogged(ws, clientId)) return;\n let requestId: string | undefined;\n try {\n if (!clientSessions.get(clientId)?.authenticated && frameBytes(message) > MAX_UNAUTHENTICATED_FRAME_BYTES) {\n ws.close(1009, \"Authenticate before sending anything larger than a token.\");\n return;\n }\n const {\n type,\n payload,\n requestId: reqId\n } = JSON.parse(message.toString());\n requestId = reqId; // Capture requestId for use in catch block\n\n wsDebug(`[WS] ${clientId} → ${type}`, requestId ? `(${requestId})` : \"\");\n\n // Handle authentication first\n // Helper: send a canonical error frame\n // `channel` is carried for a channel frame so the client can\n // deliver the error to the channel it is about. Channel frames\n // are fire-and-forget — no pending request to reject, no\n // subscription id — so without it a `RATE_LIMITED` on a\n // broadcast had nowhere to go but a console warning.\n const sendError = (errType: \"ERROR\" | \"AUTH_ERROR\", code: string, msg: string) => {\n const channel = CHANNEL_MESSAGE_TYPES.has(type)\n ? (payload as { channel?: unknown } | undefined)?.channel\n : undefined;\n ws.send(JSON.stringify({\n type: errType,\n requestId,\n ...(typeof channel === \"string\" && { channel }),\n payload: {\n error: { message: msg, code },\n ...(typeof channel === \"string\" && { channel })\n }\n }));\n };\n\n // Rate limiting: reject if client exceeds message limit.\n // Channel frames are counted against their own budget — see\n // WS_CHANNEL_RATE_LIMIT for why one shared counter starved them.\n //\n // Counted before the session is asked whether it has\n // authenticated, so a socket that never does — sending\n // `AUTHENTICATE` after `AUTHENTICATE`, each a token\n // verification — spends this budget like any other.\n {\n const session = clientSessions.get(clientId);\n if (session) {\n const now = Date.now();\n const isChannelFrame = CHANNEL_MESSAGE_TYPES.has(type);\n if (isChannelFrame) {\n if (now - session.channelWindowStart > WS_RATE_WINDOW_MS) {\n session.channelMessageCount = 0;\n session.channelWindowStart = now;\n }\n session.channelMessageCount++;\n if (session.channelMessageCount > WS_CHANNEL_RATE_LIMIT) {\n sendError(\"ERROR\", \"RATE_LIMITED\", \"Too many channel messages. Please slow down.\");\n return;\n }\n } else {\n if (now - session.messageWindowStart > WS_RATE_WINDOW_MS) {\n session.messageCount = 0;\n session.messageWindowStart = now;\n }\n session.messageCount++;\n if (session.messageCount > WS_RATE_LIMIT) {\n sendError(\"ERROR\", \"RATE_LIMITED\", \"Too many requests. Please slow down.\");\n return;\n }\n }\n }\n }\n\n if (type === \"AUTHENTICATE\") {\n const { token, impersonate } = payload || {};\n if (!token) {\n sendError(\"AUTH_ERROR\", \"INVALID_INPUT\", \"Token is required\");\n return;\n }\n if (impersonate !== undefined && typeof impersonate !== \"string\") {\n sendError(\"AUTH_ERROR\", \"INVALID_INPUT\", \"impersonate must be the uid of the user to act as\");\n return;\n }\n\n // An adapter that throws (its store unreachable) is an\n // invalid token here, as it always was: there is no\n // session yet to keep. A lookup for an impersonation that\n // throws is refused with the answer it carries when it\n // has one, and is never authenticated as the caller.\n let resolved: Awaited<ReturnType<typeof resolveIdentity>>;\n try {\n resolved = await resolveIdentity(token, impersonate, impersonate === undefined ? undefined : { door: \"socket\", clientId });\n } catch (error) {\n const answer = impersonate === undefined ? undefined : declaredErrorAnswer(error);\n if (answer) {\n sendError(\"AUTH_ERROR\", answer.code, answer.message);\n return;\n }\n if (token.startsWith(\"rk_\") || !authAdapter) throw error;\n resolved = { refused: \"Invalid or expired token\" };\n }\n if (\"refused\" in resolved && (resolved.apiKey || resolved.code)) {\n sendError(\"AUTH_ERROR\", resolved.code ?? \"INVALID_TOKEN\", resolved.refused);\n return;\n }\n const verifiedUser = \"user\" in resolved ? resolved.user : null;\n\n if (verifiedUser) {\n const session = clientSessions.get(clientId);\n if (session) {\n session.user = verifiedUser;\n session.authenticated = true;\n // Kept to be asked again: before every frame, by\n // the sweep, and — for a token that expires — at\n // the instant it does.\n session.credential = token;\n session.impersonate = impersonate;\n session.checkedAt = Date.now();\n scheduleExpiry(clientId, token);\n // What this socket already holds open — a session\n // refresh, a demotion, another account — is read\n // as the identity it has now, before it is told\n // the sign-in took.\n await realtimeService.rescopeClient(clientId, sessionAuthContext(session));\n }\n wsDebug(`[WS] replying AUTH_SUCCESS for requestId ${requestId}`);\n ws.send(JSON.stringify({\n type: \"AUTH_SUCCESS\",\n requestId,\n payload: { uid: verifiedUser.uid,\nroles: verifiedUser.roles }\n }));\n wsDebug(`🔐 [WebSocket Server] Client ${clientId} authenticated as ${verifiedUser.uid}`);\n } else {\n wsDebug(`[WS] replying AUTH_ERROR for requestId ${requestId} (invalid token)`);\n sendError(\"AUTH_ERROR\", \"INVALID_TOKEN\", \"Invalid or expired token\");\n }\n return;\n }\n\n // The identity this socket authenticated as is asked again\n // before the frame runs, as an HTTP request asks on every\n // request: a sign-out everywhere, a revoked session, a\n // demotion, a deleted account or an expired token stops being\n // honoured here, not when the socket happens to reconnect.\n {\n const session = clientSessions.get(clientId);\n if (session?.user && session.credential\n && Date.now() - session.checkedAt >= (CHANNEL_MESSAGE_TYPES.has(type) ? CHANNEL_RECHECK_MIN_MS : 0)) {\n const outcome = await recheckIdentity(clientId);\n if (outcome === \"ended\") return;\n if (outcome === \"unavailable\") {\n sendError(\"ERROR\", \"ROLE_LOOKUP_FAILED\", \"Could not verify your permissions. Please try again.\");\n return;\n }\n }\n }\n\n // Check authentication for protected operations\n if (requireAuth) {\n const session = clientSessions.get(clientId);\n if (!session?.authenticated) {\n sendError(\"ERROR\", \"UNAUTHORIZED\", \"Authentication required\");\n return;\n }\n }\n\n // Admin-plane verbs need their scope; a narrowed session needs\n // the data scope for the collection a data frame addresses, and\n // cannot use channels, which no scope covers.\n const adminScope = ADMIN_PLANE_TYPES.get(type);\n if (adminScope) {\n const session = clientSessions.get(clientId);\n if (!scopeGrants(sessionScopes(session), adminScope)) {\n sendError(\"ERROR\", \"SCOPE_MISSING\", `This operation needs the \"${adminScope}\" scope`);\n return;\n }\n }\n const narrowedScopes = clientSessions.get(clientId)?.user?.scopes;\n if (narrowedScopes) {\n const dataScope = DATA_VERB_SCOPES.get(type);\n if (dataScope) {\n const collection = frameCollection(type, payload);\n if (!scopeGrants(narrowedScopes, dataScope, collection)) {\n const wanted = collection ? `${dataScope}:${collection}` : dataScope;\n sendError(\"ERROR\", \"SCOPE_MISSING\", `This API key does not hold the \"${wanted}\" scope`);\n return;\n }\n } else if (CHANNEL_MESSAGE_TYPES.has(type)) {\n sendError(\"ERROR\", \"SCOPE_MISSING\", \"An API key cannot use channels\");\n return;\n }\n }\n\n // A data frame is a data-API request by another door, and\n // counts in the bucket that request would: this person's — or\n // the API key's, at its own rate limit, or with no account this\n // address's — shared with their HTTP requests and their other\n // sockets. The counter above is per connection, so on its own\n // a caller bought budget by opening sockets.\n if (options?.dataRateLimit && PUBLIC_TYPES.has(type)) {\n const decision = await options.dataRateLimit({\n ...origin,\n uid: clientSessions.get(clientId)?.user?.uid,\n apiKey: clientSessions.get(clientId)?.user?.apiKey\n });\n if (decision && !decision.allowed) {\n sendError(\"ERROR\", \"RATE_LIMITED\", \"Too many requests, please try again later.\");\n return;\n }\n }\n\n /**\n * Apply the REST layer's write checks to a socket payload.\n *\n * Silent when the path names no registered collection: the\n * driver decides what a path means, and refusing here would\n * turn \"unknown collection\" into a validation error.\n */\n const assertWriteRequest = (path: string | undefined, values: unknown, status?: SaveProps[\"status\"]): void => {\n if (!path || !values || typeof values !== \"object\") return;\n const collection = driver.registry?.getCollectionByPath(path);\n if (!collection) return;\n // The session's roles, and `[\"anon\"]` for a socket that\n // never authenticated — the same list `getScopedDelegate`\n // below scopes such a socket's driver with. Never\n // `undefined`: that means the trusted server plane, which\n // satisfies every role list, and a socket is not it.\n const session = clientSessions.get(clientId);\n assertWriteRequestValid(values as Record<string, unknown>, collection, {\n viewer: { roles: session?.user?.roles ?? [\"anon\"] },\n // `required` holds on a create, as on `POST`.\n status\n });\n // Field operations (`{ views: { $inc: 1 } }`) reach the\n // driver through this door exactly as they reach it through\n // `PATCH`, and are compiled to SQL in the same place. What\n // the REST route adds is the type check — `$push` on a\n // number is a 400 naming the field, not a Postgres error\n // from inside the transaction — so the socket runs it too,\n // or the same payload is refused at one boundary and\n // accepted at the other.\n assertFieldOpsValid(values as Record<string, unknown>, collection);\n };\n\n /**\n * Apply the REST layer's read check to a socket request: no\n * filter, sort, projection or uniqueness probe over a field\n * this caller may not read. The strip keeps the value off the\n * wire; without this a `COUNT` still answers whether a hidden\n * value matches a pattern, one character at a time.\n *\n * Same viewer as `assertWriteRequest`, and silent in the same\n * case: a path that names no registered collection is the\n * driver's to answer.\n */\n const assertReadRequest = (\n path: string | undefined,\n request: Parameters<typeof assertReadRequestReadable>[0]\n ): void => {\n if (!path) return;\n let collection;\n try {\n collection = driver.registry?.getCollectionByPath(path);\n } catch {\n // A malformed nested path: the driver refuses it with\n // its own message.\n return;\n }\n if (!collection) return;\n const session = clientSessions.get(clientId);\n assertReadRequestReadable(request, collection, {\n roles: session?.user?.roles ?? [\"anon\"]\n });\n };\n\n /**\n * Refuse a write through a nested path that sets a relation\n * this caller may not write — the rule REST's nested routes\n * apply, from the same function. The body is checked by\n * `assertWriteRequest`; this is what the address writes.\n *\n * Silent for a root path, and for a nested one the registry\n * cannot walk: the driver refuses that with its own message.\n */\n const assertNestedWrite = (path: unknown, kind: NestedWriteKind): void => {\n if (typeof path !== \"string\" || !isNestedPath(path) || !driver.registry) return;\n let hop;\n try {\n hop = resolveNestedPath(path, driver.registry);\n } catch {\n return;\n }\n if (!hop) return;\n const session = clientSessions.get(clientId);\n assertNestedWriteAllowed(hop, kind, path, { roles: session?.user?.roles ?? [\"anon\"] });\n };\n\n /** The auth collection at `path`, or `undefined` for any other. */\n const authCollectionAt = (path: unknown): CollectionConfig | undefined => {\n if (typeof path !== \"string\") return undefined;\n let collection;\n try {\n collection = driver.registry?.getCollectionByPath(path);\n } catch {\n return undefined;\n }\n const auth = collection?.auth;\n const isAuthCollection = auth === true || (!!auth && typeof auth === \"object\" && auth.enabled === true);\n return isAuthCollection ? collection : undefined;\n };\n /**\n * The auth collection's rows are the users, so a write to one\n * is user administration — the auth adapter's to check, as it\n * is on the REST routes and `/admin/users`. `undefined` for any\n * other collection, or an adapter with nothing to say.\n */\n const userAdminFor = (path: unknown): AuthAdapter | undefined =>\n authAdapter && authCollectionAt(path) ? authAdapter : undefined;\n\n /**\n * An upsert's options, under the rules `POST /api/data`\n * applies to `?on_conflict=`: refused on a nested path,\n * whose insert branch stamps the parent's key into the\n * DO UPDATE and so moves the row it matched under this\n * parent; and a target that is a declared unique key of\n * the registry's collection, or a 400 naming the ones\n * there are rather than Postgres's 42P10.\n */\n const upsertOptions = (request: SaveProps): Pick<SaveProps, \"upsert\" | \"onConflict\"> => {\n if (request.upsert !== true) return {};\n if (typeof request.path === \"string\" && isNestedPath(request.path)) {\n throw ApiError.badRequest(\n \"An upsert is not accepted on a nested path: a row it matched would be moved \" +\n \"under this parent. Send the upsert to the collection directly.\",\n \"INVALID_CONFLICT_TARGET\"\n );\n }\n const collection = driver.registry?.getCollectionByPath(request.path);\n const onConflict = collection\n ? resolveConflictTarget(request.onConflict, collection, { where: \"`onConflict`\" })\n : undefined;\n return { upsert: true, ...(onConflict && { onConflict }) };\n };\n\n // Helper to get correctly scoped delegate for the current request\n const getScopedDelegate = async (): Promise<DataDriver> => {\n // Check if the driver supports RLS-scoped delegates\n if (typeof driver.withAuth === \"function\") {\n try {\n const who = sessionAuthContext(clientSessions.get(clientId));\n const userForAuth: User = {\n uid: who.uid,\n displayName: null,\n email: null,\n photoURL: null,\n providerId: \"websocket\",\n isAnonymous: who.isAnonymous === true,\n roles: who.roles,\n ...(who.claims ? { claims: who.claims } : {})\n };\n return await driver.withAuth(userForAuth);\n } catch (e) {\n logger.error(\"Failed to create RLS scoped delegate for WS request\", { error: e });\n throw new Error(\"Internal authentication error\");\n }\n }\n return driver;\n };\n\n switch (type) {\n case \"FETCH_COLLECTION\": {\n wsDebug(\"📋 [WebSocket Server] Processing FETCH_COLLECTION request\");\n const request: FetchCollectionProps = payload;\n assertReadRequest(request.path, request);\n const delegate = await getScopedDelegate();\n // Bound the client-supplied limit with the SAME guarantee\n // the REST ingress and `subscribe_collection` apply\n // (`resolveClientListLimit`). Without it an absent limit\n // reached the driver as `undefined`, which emits no LIMIT\n // clause — one socket frame streamed the whole table, on\n // the one transport that skipped the ceiling every other\n // read path enforces.\n const rows = await delegate.fetchCollection({\n ...request,\n limit: resolveClientListLimit(request.limit, {\n vectorSearch: !!request.vectorSearch\n })\n });\n wsDebug(\"📋 [WebSocket Server] FETCH_COLLECTION result - rows count:\", rows.length);\n const response = {\n type: \"FETCH_COLLECTION_SUCCESS\",\n payload: { rows },\n requestId\n };\n wsDebug(\"📋 [WebSocket Server] Sending FETCH_COLLECTION_SUCCESS response\");\n ws.send(JSON.stringify(response));\n }\n break;\n\n case \"FETCH_ONE\": {\n wsDebug(\"📄 [WebSocket Server] Processing FETCH_ENTITY request\");\n const request: FetchOneProps = payload;\n const delegate = await getScopedDelegate();\n const row = await delegate.fetchOne(request);\n wsDebug(\"📄 [WebSocket Server] FETCH_ENTITY result:\", row);\n const response = {\n type: \"FETCH_ONE_SUCCESS\",\n payload: { row: row ?? null },\n requestId\n };\n wsDebug(\"📄 [WebSocket Server] Sending FETCH_ENTITY_SUCCESS response\");\n ws.send(JSON.stringify(response));\n }\n break;\n\n case \"SAVE\": {\n wsDebug(\"💾 [WebSocket Server] Processing SAVE_ENTITY request\");\n const request: SaveProps = payload;\n wsDebug(\"💾 [WebSocket Server] Saving row with request:\", inspect(request, { depth: null,\ncolors: true }));\n // The same two checks the REST write routes run, on the\n // same input, at the same point. This socket is the\n // other request boundary — the comment on `requireAuth`\n // above says so — and it used to hand the client's\n // payload straight to the driver, so a value the HTTP\n // API answers 400 for was written when it arrived here.\n //\n // The collection comes from the registry by path, never\n // from `request.collection`: that field is client-\n // supplied, and reading the rules out of it would let\n // the caller choose which rules to be checked against.\n //\n // A create on the auth collection is a user creation,\n // as on `POST /users`: the adapter's step, not a plain\n // insert, and the body checked against what that step\n // consumes (`password` is not a column). An upsert is\n // not one: it may update a user instead.\n const usersCollection = request.status !== \"existing\" && request.upsert !== true && authAdapter?.prepareUserCreation\n ? authCollectionAt(request.path)\n : undefined;\n if (usersCollection) {\n const body = (request.values ?? {}) as Record<string, unknown>;\n assertUserCreationBodyValid(authAdapter, usersCollection, body, {\n roles: clientSessions.get(clientId)?.user?.roles ?? [\"anon\"]\n });\n assertNoFieldOpsOnCreate(body, \"A create\");\n } else {\n assertWriteRequest(request.path, request.values as Record<string, unknown>, request.status);\n }\n // A create is one when the driver takes it for one:\n // no id, or a status that asks for a new row.\n assertNestedWrite(request.path, request.id === undefined || request.status !== \"existing\" ? \"create\" : \"update\");\n const delegate = await getScopedDelegate();\n // The address, the values and the status, and nothing\n // else the frame says — the DELETE case below is\n // narrowed for the same reason. A `collection` here was\n // merged under the registry's in the driver, so a key\n // the registry leaves unset was the caller's to set\n // (`history: true` on a collection declaring none).\n const userAdmin = request.status === \"existing\" && request.id !== undefined\n ? userAdminFor(request.path)\n : undefined;\n const [values] = userAdmin?.prepareUserUpdates\n ? await userAdmin.prepareUserUpdates([{ uid: String(request.id), values: request.values as Record<string, unknown> }])\n : [request.values];\n const write = (toWrite: SaveProps[\"values\"]) => delegate.save({\n path: request.path,\n ...(request.id !== undefined && { id: request.id }),\n values: toWrite,\n status: request.status,\n ...upsertOptions(request)\n });\n // The user creation answers with what `POST /users`\n // does: the row, and whether an invitation went out or\n // the temporary password that did not.\n const created = usersCollection\n ? await createUserThroughAuthCollection(authAdapter, usersCollection, (values ?? {}) as Record<string, unknown>, write)\n : undefined;\n const row = created ? { ...created.row, ...created.delivery } : await write(values);\n wsDebug(\"💾 [WebSocket Server] SAVE_ENTITY result:\", inspect(row, { depth: null,\ncolors: true }));\n const response = {\n type: \"SAVE_SUCCESS\",\n payload: { row },\n requestId\n };\n wsDebug(\"💾 [WebSocket Server] Sending SAVE_ENTITY_SUCCESS response\");\n ws.send(JSON.stringify(response));\n }\n break;\n\n case \"DELETE\": {\n wsDebug(\"🗑️ [WebSocket Server] Processing DELETE_ENTITY request\");\n const request: DeleteProps = payload;\n wsDebug(\"🗑️ [WebSocket Server] Deleting row:\", request.row);\n // Through a many-to-many path this drops the link — a\n // write of the parent's relation.\n assertNestedWrite(request.row?.path, \"unlink\");\n const delegate = await getScopedDelegate();\n // The address and `hard`, and nothing else the frame\n // says. The driver reads the row itself, and resolves\n // the collection from the registry by path — a\n // `collection` passed along was merged under the\n // registry's, so a key the registry does not declare\n // survived: `softDelete: { field: \"title\" }` turned\n // this DELETE into an UPDATE of `title` that no\n // `beforeSave` and no write validator saw.\n const userAdmin = userAdminFor(request.row?.path);\n const uids = [String(request.row.id)];\n await userAdmin?.prepareUserDeletions?.(uids);\n await delegate.delete({\n row: { id: request.row.id, path: request.row.path },\n hard: request.hard\n });\n await userAdmin?.finalizeUserDeletions?.(uids);\n wsDebug(\"🗑️ [WebSocket Server] DELETE_ENTITY completed successfully\");\n const response = {\n type: \"DELETE_SUCCESS\",\n payload: { success: true },\n requestId\n };\n wsDebug(\"🗑️ [WebSocket Server] Sending DELETE_ENTITY_SUCCESS response\");\n ws.send(JSON.stringify(response));\n }\n break;\n\n case \"CHECK_UNIQUE_FIELD\": {\n wsDebug(\"🔍 [WebSocket Server] Processing CHECK_UNIQUE_FIELD request\");\n const {\n path,\n name,\n value,\n id,\n collection\n } = payload;\n // \"Does anybody hold this value\" is a read of the field,\n // answered for every row in the table.\n if (typeof name === \"string\") assertReadRequest(path, { filter: { [name]: [\"==\", value] } });\n const delegate = await getScopedDelegate();\n const isUnique = await delegate.checkUniqueField(path, name, value, id, collection);\n wsDebug(\"🔍 [WebSocket Server] CHECK_UNIQUE_FIELD result:\", isUnique);\n const response = {\n type: \"CHECK_UNIQUE_FIELD_SUCCESS\",\n payload: { isUnique },\n requestId\n };\n wsDebug(\"🔍 [WebSocket Server] Sending CHECK_UNIQUE_FIELD_SUCCESS response\");\n ws.send(JSON.stringify(response));\n }\n break;\n\n\n case \"COUNT\": {\n // Deliberately NOT routed through `resolveClientListLimit`:\n // this answers with a scalar, and the driver drops `limit`\n // on the way to `SELECT count(*)`. Clamping here could only\n // ever make `total` describe fewer rows than the collection\n // holds — the page size is the caller's business, the total\n // is not.\n const request: FetchCollectionProps = payload;\n assertReadRequest(request.path, request);\n const delegate = await getScopedDelegate();\n const count = await delegate.count!(request);\n const response = {\n type: \"COUNT_SUCCESS\",\n payload: { count },\n requestId\n };\n ws.send(JSON.stringify(response));\n }\n break;\n\n case \"EXECUTE_SQL\": {\n const { sql, options, mode } = payload;\n try {\n const delegate = await getScopedDelegate();\n const admin = delegate.admin;\n if (!isSQLAdmin(admin)) {\n sendError(\"ERROR\", \"NOT_SUPPORTED\", \"SQL execution is not available for this driver.\");\n break;\n }\n // The console's own runs: the result says, per\n // column, which table column the database read it\n // from — what an edit of a cell is written back\n // by. The rows still go in `result`, where a\n // client that reads nothing else finds them.\n const script = mode === \"script\" && typeof admin.runSqlScript === \"function\"\n ? await admin.runSqlScript(String(sql), {\n database: typeof options?.database === \"string\" ? options.database : undefined,\n role: typeof options?.role === \"string\" ? options.role : undefined\n })\n : undefined;\n // A session of its own, reset before it goes back\n // to the pool: what a person types may `SET ROLE`,\n // and that must not reach the next request on\n // that connection. Set here, after the client's\n // options, so a frame cannot turn it off.\n const result = script ? script.rows : await admin.executeSql(sql, { ...options, isolateSession: true });\n // Which role the rows were read as, said beside them:\n // the role picked, unless the server runs every\n // statement as the connection owner.\n const ranAs = typeof options?.role === \"string\" && options.role\n ? await sqlRoleRanAs(admin, options.role, typeof options?.database === \"string\" ? options.database : undefined)\n : undefined;\n if (process.env.NODE_ENV !== \"production\") {\n wsDebug(`⚡ [WebSocket Server] SQL executed. Returned ${Array.isArray(result) ? result.length : \"non-array\"} rows.`);\n }\n const auditSession = clientSessions.get(clientId);\n // Through `logger`, not `console.log`: this line is\n // emitted in production, and a bare console call\n // has no severity, no timestamp, no JSON envelope\n // and no LOG_LEVEL gate, so it lands in Cloud\n // Logging as unstructured text the queries written\n // for every other line cannot match.\n //\n // The bound values are counted, never written — the\n // statement is the audit signal, the parameters are\n // whatever row the operator was touching. (stdout is\n // not an audit sink either; a real trail belongs in\n // a table with an actor and a retention policy.)\n //\n // And for the same reason the values written inline\n // as literals are masked: `ALTER ROLE app PASSWORD\n // '…'` put the password in the production logs.\n // Masked before the cut, so the cut cannot land\n // inside a literal and keep half of it.\n logger.info(\"[SQL Audit] WebSocket SQL execution\", {\n sql: redactSqlLiterals(String(sql)).substring(0, 500),\n database: options?.database,\n role: options?.role,\n // The role the statement asked for is above;\n // this is the one it ran as. They used to be\n // assumed identical, so an execution that fell\n // back to the owner was audited under the role\n // it had failed to assume. That fallback is now\n // an error everywhere except the documented\n // `DISABLE_DB_ROLE_SWITCHING` opt-out — which is\n // precisely the case this line still has to\n // report honestly.\n effectiveRole: effectiveSqlRole(options?.role),\n paramCount: Array.isArray(options?.params) ? options.params.length : 0,\n resultRows: Array.isArray(result) ? result.length : \"unknown\",\n uid: auditSession?.user?.uid ?? \"unknown\",\n roles: auditSession?.user?.roles ?? [],\n isAdmin: auditSession?.user?.isAdmin ?? false,\n ...(auditSession?.user?.impersonator ? { impersonatedBy: auditSession.user.impersonator } : {}),\n requestId\n });\n const response = {\n type: \"EXECUTE_SQL_SUCCESS\",\n // The rows once, as `result`; the rest of the\n // script's description beside them.\n payload: {\n ...(script ? { ...scriptDescription(script), result } : { result }),\n ...(ranAs ? { effectiveRole: ranAs } : {})\n },\n requestId\n };\n ws.send(JSON.stringify(response));\n } catch (sqlError: unknown) {\n // This is a query execution error (e.g., syntax error, permission denied).\n // We return it cleanly to the client without logging a server stack trace.\n const errMsg = extractErrorMessage(sqlError);\n sendError(\"ERROR\", \"SQL_ERROR\", errMsg);\n }\n }\n break;\n\n case \"FETCH_DATABASES\": {\n wsDebug(\"📚 [WebSocket Server] Processing FETCH_DATABASES request\");\n const delegate = await getScopedDelegate();\n const admin = delegate.admin;\n let databases: string[] = [];\n if (isSQLAdmin(admin) && admin.fetchAvailableDatabases) {\n databases = await admin.fetchAvailableDatabases();\n }\n wsDebug(`📚 [WebSocket Server] Fetched ${databases.length} databases.`);\n const response = {\n type: \"FETCH_DATABASES_SUCCESS\",\n payload: { databases },\n requestId\n };\n ws.send(JSON.stringify(response));\n }\n break;\n\n case \"FETCH_ROLES\": {\n wsDebug(\"👤 [WebSocket Server] Processing FETCH_ROLES request\");\n const delegate = await getScopedDelegate();\n const admin = delegate.admin;\n let roles: string[] = [];\n if (isSQLAdmin(admin) && admin.fetchAvailableRoles) {\n roles = await admin.fetchAvailableRoles();\n }\n wsDebug(`👤 [WebSocket Server] Fetched ${roles.length} roles.`);\n const response = {\n type: \"FETCH_ROLES_SUCCESS\",\n payload: { roles },\n requestId\n };\n ws.send(JSON.stringify(response));\n }\n break;\n\n case \"FETCH_APPLICATION_ROLES\": {\n wsDebug(\"👤 [WebSocket Server] Processing FETCH_APPLICATION_ROLES request\");\n const delegate = await getScopedDelegate();\n const admin = delegate.admin;\n let roles: string[] = [];\n if (isSQLAdmin(admin) && admin.fetchApplicationRoles) {\n roles = await admin.fetchApplicationRoles();\n }\n wsDebug(`👤 [WebSocket Server] Fetched ${roles.length} application roles.`);\n const response = {\n type: \"FETCH_APPLICATION_ROLES_SUCCESS\",\n payload: { roles },\n requestId\n };\n ws.send(JSON.stringify(response));\n }\n break;\n\n case \"FETCH_CURRENT_DATABASE\": {\n wsDebug(\"📚 [WebSocket Server] Processing FETCH_CURRENT_DATABASE request\");\n const delegate = await getScopedDelegate();\n const admin = delegate.admin;\n let database: string | undefined = undefined;\n if (isSQLAdmin(admin) && admin.fetchCurrentDatabase) {\n database = await admin.fetchCurrentDatabase();\n }\n const response = {\n type: \"FETCH_CURRENT_DATABASE_SUCCESS\",\n payload: { database },\n requestId\n };\n ws.send(JSON.stringify(response));\n }\n break;\n\n case \"FETCH_UNMAPPED_TABLES\": {\n wsDebug(\"📋 [WebSocket Server] Processing FETCH_UNMAPPED_TABLES request\");\n const delegate = await getScopedDelegate();\n const admin = delegate.admin;\n let tables: string[] = [];\n if (isSchemaAdmin(admin) && admin.fetchUnmappedTables) {\n tables = await admin.fetchUnmappedTables(payload?.mappedPaths);\n }\n wsDebug(`📋 [WebSocket Server] Fetched ${tables.length} unmapped tables.`);\n const response = {\n type: \"FETCH_UNMAPPED_TABLES_SUCCESS\",\n payload: { tables },\n requestId\n };\n ws.send(JSON.stringify(response));\n }\n break;\n\n case \"FETCH_TABLE_METADATA\": {\n wsDebug(\"📋 [WebSocket Server] Processing FETCH_TABLE_METADATA request\");\n const { tableName } = payload;\n const delegate = await getScopedDelegate();\n const admin = delegate.admin;\n let metadata: TableMetadata | undefined;\n if (isSchemaAdmin(admin) && admin.fetchTableMetadata) {\n metadata = await admin.fetchTableMetadata(tableName) as TableMetadata;\n }\n wsDebug(`📋 [WebSocket Server] Fetched metadata for table '${tableName}'. (${metadata?.columns?.length ?? 0} columns)`);\n const response = {\n type: \"FETCH_TABLE_METADATA_SUCCESS\",\n payload: { metadata },\n requestId\n };\n ws.send(JSON.stringify(response));\n }\n break;\n\n case \"CREATE_BRANCH\": {\n wsDebug(\"🌿 [WebSocket Server] Processing CREATE_BRANCH request\");\n const { name, options } = payload;\n const delegate = await getScopedDelegate();\n if (!delegate.admin?.createBranch) {\n sendError(\"ERROR\", \"NOT_SUPPORTED\", \"Database branching is not available. Configure adminConnectionString.\");\n break;\n }\n const branch: BranchInfo = await delegate.admin.createBranch(name, options);\n wsDebug(`🌿 [WebSocket Server] Branch created: ${branch.name}`);\n const response = {\n type: \"CREATE_BRANCH_SUCCESS\",\n payload: { branch },\n requestId\n };\n ws.send(JSON.stringify(response));\n }\n break;\n\n case \"DELETE_BRANCH\": {\n wsDebug(\"🗑️ [WebSocket Server] Processing DELETE_BRANCH request\");\n const { name: branchName } = payload;\n const delegate = await getScopedDelegate();\n if (!delegate.admin?.deleteBranch) {\n sendError(\"ERROR\", \"NOT_SUPPORTED\", \"Database branching is not available.\");\n break;\n }\n await delegate.admin.deleteBranch(branchName);\n wsDebug(`🗑️ [WebSocket Server] Branch deleted: ${branchName}`);\n const response = {\n type: \"DELETE_BRANCH_SUCCESS\",\n payload: { success: true },\n requestId\n };\n ws.send(JSON.stringify(response));\n }\n break;\n\n case \"LIST_BRANCHES\": {\n wsDebug(\"🌿 [WebSocket Server] Processing LIST_BRANCHES request\");\n const delegate = await getScopedDelegate();\n let branches: BranchInfo[] = [];\n if (delegate.admin?.listBranches) {\n branches = await delegate.admin.listBranches();\n }\n wsDebug(`🌿 [WebSocket Server] Listed ${branches.length} branches.`);\n const response = {\n type: \"LIST_BRANCHES_SUCCESS\",\n payload: { branches },\n requestId\n };\n ws.send(JSON.stringify(response));\n }\n break;\n\n // Route subscription messages, broadcast channels, and presence to RealtimeService\n case \"subscribe_collection\":\n case \"subscribe_one\":\n case \"unsubscribe\":\n case \"join_channel\":\n case \"leave_channel\":\n case \"broadcast\":\n case \"presence_track\":\n case \"presence_untrack\":\n case \"presence_state\":\n case \"channel_history\": {\n wsDebug(\"🔄 [WebSocket Server] Routing realtime message to RealtimeService:\", type);\n // Attach auth context from the WS session so RLS-aware\n // refetches read as the same principal the request\n // frames do.\n const authContext = sessionAuthContext(clientSessions.get(clientId));\n // Let RealtimeService handle these messages\n await realtimeService.handleClientMessage(clientId, {\n type,\n payload,\n subscriptionId: payload?.subscriptionId\n }, authContext);\n break;\n }\n\n default:\n logger.error(\"❌ [WebSocket Server] Unknown message type\", { detail: type });\n }\n } catch (error: unknown) {\n // A refused `limit` is the caller's mistake, not a server fault.\n // Left to the generic branch below it answers INTERNAL_ERROR\n // with the message suppressed in production — so the one thing\n // that would tell the caller what to send instead is exactly\n // what gets dropped. Answered here the way\n // `subscribe_collection` already answers it: INVALID_LIMIT,\n // message intact. The text names the ceiling and nothing else.\n if (error instanceof ListLimitError) {\n logger.warn(`[WebSocket Server] Refused a list read: ${error.message}`);\n ws.send(JSON.stringify({\n type: \"ERROR\",\n requestId,\n payload: { error: { message: error.message,\ncode: \"INVALID_LIMIT\" } }\n }));\n return;\n }\n // \"This server cannot branch\" is a refusal too, and the same\n // reasoning applies twice over: the generic branch would drop\n // the text in production, and the text is the only thing that\n // says what to do instead. Studio renders it where the success\n // toast used to be.\n if (error instanceof BranchingUnsupportedError\n || (error as Error)?.name === \"BranchingUnsupportedError\") {\n const refusal = error as BranchingUnsupportedError;\n logger.warn(`[WebSocket Server] Refused a branch operation: ${refusal.message}`);\n ws.send(JSON.stringify({\n type: \"ERROR\",\n requestId,\n payload: { error: { message: refusal.message,\ncode: \"BRANCHING_UNSUPPORTED\" } }\n }));\n return;\n }\n // A refused write is the caller's mistake, and its message is\n // the only thing that says what to send instead — the same\n // reasoning as `ListLimitError` above. Left to the generic\n // branch it becomes INTERNAL_ERROR with the text dropped in\n // production, so the socket would refuse the write and decline\n // to say why.\n //\n // \"Refused\" means whatever REST answers with the error's own\n // status, by the same predicate: an `ApiError`, and the\n // `RebaseApiError` every collection-callback veto is. Listing\n // only the first here is how a `beforeDelete` veto reached the\n // admin panel as \"An unexpected error occurred\".\n const answer = declaredErrorAnswer(error);\n if (answer) {\n const line = `[WebSocket Server] Refused: ${answer.code} ${answer.message}`;\n if (answer.expected) logger.debug(line);\n else logger.warn(line);\n ws.send(JSON.stringify({\n type: \"ERROR\",\n requestId,\n payload: { error: {\n message: answer.message,\n code: answer.code,\n ...(answer.details !== undefined && { details: answer.details })\n } }\n }));\n return;\n }\n logger.error(\"💥 [WebSocket Server] Error handling message\", { error: error });\n if (error instanceof Error) {\n logger.error(\"Stack trace\", { detail: error.stack });\n }\n // Unwrap the cause chain: a Drizzle failure reports itself as\n // \"Failed query: <sql> params:\", which tells the user nothing and\n // echoes the statement back at them. The reason is in the cause.\n const errorMessage = process.env.NODE_ENV === \"production\"\n ? \"An unexpected error occurred\"\n : extractErrorMessage(error);\n const errorResponse = {\n type: \"ERROR\",\n requestId,\n payload: {\n error: {\n message: errorMessage,\n code: \"INTERNAL_ERROR\"\n }\n }\n };\n ws.send(JSON.stringify(errorResponse));\n }\n });\n });\n\n // A peer that vanished without a close — lost signal, sleep, a forgotten\n // NAT mapping — is let go of once it stops answering pings. Stopped with\n // whichever goes first, like the identity sweep.\n const stopReaping = reapSilentSockets(wss);\n wss.on(\"close\", stopReaping);\n if (typeof server.on === \"function\") server.on(\"close\", stopReaping);\n}\n","import { CollectionRegistry, resolveCollectionRelations } from \"@rebasepro/common\";\nimport { type CollectionConfig } from \"@rebasepro/types\";\nimport { PgEnum, PgTable } from \"drizzle-orm/pg-core\";\nimport { Relations } from \"drizzle-orm\";\nimport { CollectionRegistryInterface } from \"../interfaces\";\nimport { getTableName } from \"@rebasepro/common\";\n\n/**\n * PostgreSQL-specific collection registry.\n * Extends the base CollectionRegistry with support for Drizzle ORM tables, enums, and relations.\n *\n * Satisfies CollectionRegistryInterface through inheritance from CollectionRegistry.\n */\nexport class PostgresCollectionRegistry extends CollectionRegistry implements CollectionRegistryInterface {\n\n private tables = new Map<string, PgTable>();\n private enums = new Map<string, PgEnum<[string, ...string[]]>>();\n private relations = new Map<string, Relations>();\n\n registerTable(table: PgTable, tableName: string) {\n this.tables.set(tableName, table);\n }\n\n getTable(tableName: string): PgTable | undefined {\n return this.tables.get(tableName);\n }\n\n /**\n * Checks if a specific collection has a registered table\n */\n hasTableForCollection(tableName: string): boolean {\n return this.tables.has(tableName);\n }\n\n /**\n * Returns all registered table names.\n */\n getTableNames(): string[] {\n return Array.from(this.tables.keys());\n }\n\n /**\n * Finds collections assigned to a specific data source that do not have a registered table.\n */\n getCollectionsWithoutTables(dataSourceKey = \"(default)\"): CollectionConfig[] {\n const collections = this.getCollections().filter(\n c => c.dataSource === dataSourceKey || (!c.dataSource && dataSourceKey === \"(default)\")\n );\n return collections.filter(c => !this.tables.has(getTableName(c)));\n }\n\n registerEnums(enums: Record<string, PgEnum<[string, ...string[]]>>) {\n Object.entries(enums).forEach(([name, value]) => this.enums.set(name, value));\n }\n\n registerRelations(relations: Record<string, Relations>) {\n Object.entries(relations).forEach(([name, value]) => this.relations.set(name, value));\n }\n\n getEnum(name: string): PgEnum<[string, ...string[]]> | undefined {\n return this.enums.get(name);\n }\n\n getRelation(name: string): Relations | undefined {\n return this.relations.get(name);\n }\n\n getAllEnums(): Record<string, PgEnum<[string, ...string[]]>> {\n return Object.fromEntries(this.enums.entries());\n }\n\n getAllRelations(): Record<string, Relations> {\n return Object.fromEntries(this.relations.entries());\n }\n\n /**\n * Get the merged schema object (tables + relations) for use with Drizzle's\n * relational query API (`db.query`).\n */\n getMergedSchema(): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n for (const [name, table] of this.tables.entries()) {\n result[name] = table;\n }\n for (const [name, relation] of this.relations.entries()) {\n result[name] = relation;\n }\n return result;\n }\n\n /**\n * Get the available Drizzle relation keys for a given collection path.\n * Maps from the collection's relation property names to the Drizzle relation names\n * defined in the schema.\n */\n getRelationKeysForCollection(collectionPath: string): string[] {\n const collection = this.getCollectionByPath(collectionPath);\n if (!collection) return [];\n // Resolved, not authored. `relationName` is optional at the authoring\n // surface and defaults to the property key or the target's slug, so\n // reading the raw field dropped every relation that relied on the\n // default — and never saw relations declared inline on a property at\n // all, since those are not in the `relations` array.\n return Object.keys(resolveCollectionRelations(collection));\n }\n\n}\n\n","/**\n * Scheduled backups, wired into the server cron system.\n *\n * A backend enables nightly (or custom-schedule) backups by dropping a cron\n * file that default-exports {@link createBackupCron}. The heavy lifting —\n * dump, upload, prune — reuses the same primitives as the CLI so behaviour\n * is identical between a manual `rebase db backup` and an automated run.\n */\nimport fs from \"fs\";\nimport type { CronJobDefinition } from \"@rebasepro/types\";\nimport type { StorageController } from \"@rebasepro/server\";\nimport {\n BackupDestination,\n parseBackupDestination,\n parseDbNameFromUrl,\n resolveConnectionString\n} from \"./pg-tools\";\n// NOTE: `./backup-service` pulls in `execa` (ESM-only). It is imported\n// lazily inside the handler so the pure `backupCronConfigFromEnv` parser can\n// be unit-tested under CommonJS without loading it.\n\nexport interface BackupCronConfig {\n /** Cron expression, e.g. `\"0 3 * * *\"` for 03:00 daily. */\n schedule: string;\n /** Postgres connection string. Defaults to `DATABASE_URL`. */\n connectionString: string;\n /** Where backups are written: a local path or an `s3://` / `gs://` URL. */\n destination: BackupDestination;\n /**\n * Storage controller used for `s3`/`gcs` destinations. Reuse the one the\n * backend already configured (S3/GCS/Local StorageController). Not needed\n * for local destinations.\n */\n storage?: StorageController;\n /** Delete backups older than this many days. `0` disables pruning. */\n retentionDays?: number;\n /** Always keep at least this many recent backups regardless of age. */\n keepMinimum?: number;\n /**\n * Schemas to exclude from the dump. Defaults to none, the same as\n * `rebase db backup` — see {@link createBackupCron} for why `rebase` in\n * particular must stay in.\n */\n excludeSchemas?: string[];\n /** Cron job display name. */\n name?: string;\n /** Whether the job is enabled. */\n enabled?: boolean;\n}\n\nexport interface EnvResolution {\n /** Resolved config, present when a schedule is configured. */\n config?: Omit<BackupCronConfig, \"storage\">;\n /** True when `BACKUP_SCHEDULE` is unset — the cron should be a no-op. */\n disabled?: boolean;\n /** Human-readable reason the config could not be built. */\n error?: string;\n}\n\n/**\n * Build backup-cron config purely from environment variables.\n * Recognised keys:\n * - `BACKUP_SCHEDULE` cron expression (required to enable)\n * - `BACKUP_DESTINATION` local path or `s3://` / `gs://` URL (required)\n * - `BACKUP_RETENTION_DAYS` integer days (optional)\n * - `BACKUP_KEEP_MINIMUM` integer count (optional)\n * - `DATABASE_URL` connection string\n *\n * Pure and side-effect free so it can be unit-tested without a database.\n */\nexport function backupCronConfigFromEnv(env: Record<string, string | undefined>): EnvResolution {\n const schedule = env.BACKUP_SCHEDULE?.trim();\n if (!schedule) {\n return { disabled: true };\n }\n\n const connectionString = resolveConnectionString(env);\n if (!connectionString) {\n return { error: \"BACKUP_SCHEDULE is set but DATABASE_URL is not configured.\" };\n }\n\n const destinationRaw = env.BACKUP_DESTINATION?.trim();\n if (!destinationRaw) {\n return { error: \"BACKUP_SCHEDULE is set but BACKUP_DESTINATION is not configured.\" };\n }\n const destination = parseBackupDestination(destinationRaw);\n\n const retentionDays = parseOptionalInt(env.BACKUP_RETENTION_DAYS);\n if (retentionDays === \"invalid\") {\n return { error: `BACKUP_RETENTION_DAYS must be an integer, got \"${env.BACKUP_RETENTION_DAYS}\".` };\n }\n const keepMinimum = parseOptionalInt(env.BACKUP_KEEP_MINIMUM);\n if (keepMinimum === \"invalid\") {\n return { error: `BACKUP_KEEP_MINIMUM must be an integer, got \"${env.BACKUP_KEEP_MINIMUM}\".` };\n }\n\n return {\n config: {\n schedule,\n connectionString,\n destination,\n retentionDays: retentionDays ?? undefined,\n keepMinimum: keepMinimum ?? undefined\n }\n };\n}\n\nfunction parseOptionalInt(value: string | undefined): number | null | \"invalid\" {\n if (value === undefined || value.trim() === \"\") return null;\n const n = Number(value);\n if (!Number.isInteger(n) || n < 0) return \"invalid\";\n return n;\n}\n\n/**\n * Create a {@link CronJobDefinition} that dumps the database, uploads the\n * result to the configured destination, and prunes old backups. Object\n * destinations require {@link BackupCronConfig.storage}.\n *\n * The dump excludes nothing unless `excludeSchemas` says otherwise, and that\n * default is not this function's to set: the option goes to `createDump`\n * untouched, so `buildPgDumpArgs` decides for the cron and `rebase db backup`\n * alike. The cron used to carry its own default of `[\"rebase\"]`, meant to\n * leave out Atlas's revision table, and it left out a lot more. `rebase` is\n * where the framework keeps\n * everything that is not a collection: the auth tables (`users`, identities,\n * refresh tokens, MFA factors, recovery codes, `app_config`, `schema_meta`),\n * API keys, record history, the job queue, cron logs, channel history,\n * branches and idempotency keys. It also holds the functions that every\n * generated RLS policy and CDC trigger calls (`rebase.uid()`,\n * `rebase.roles()`, `rebase.rebase_cdc_notify()`).\n *\n * None of that can be left for boot to rebuild. Boot recreates the tables\n * and functions, but never the rows in them. And a dump that has a policy\n * or trigger on a `public` table without the function it calls cannot be\n * restored into an empty database: `pg_restore --exit-on-error` stops at\n * the first such statement, and every collection's generated policies call\n * `rebase.uid()`. So every scheduled backup lost every user account, and\n * could not be restored anyway, while `rebase db backup` took a complete\n * one. The revision table belongs in a backup too: without it, a restored\n * database has tables but no record of the migrations that made them.\n */\nexport function createBackupCron(config: BackupCronConfig): CronJobDefinition {\n const dbName = parseDbNameFromUrl(config.connectionString) ?? \"database\";\n\n const job: CronJobDefinition = {\n name: config.name ?? \"Scheduled database backup\",\n schedule: config.schedule,\n description: \"Dumps the Postgres database and uploads it to the configured backup destination.\",\n enabled: config.enabled ?? true,\n // Backups of a large database can take a while; allow up to an hour.\n timeoutSeconds: 3600,\n async handler({ log }) {\n const { createDump, discardPartialDump, pruneBackups, uploadBackup, validateDump } =\n await import(\"./backup-service\");\n const { destination } = config;\n\n if (destination.kind !== \"local\" && !config.storage) {\n throw new Error(\n `Backup destination is ${destination.kind} but no storage controller was provided. ` +\n \"Pass the backend's configured StorageController to createBackupCron({ storage }).\"\n );\n }\n\n log(`Starting backup of \"${dbName}\"…`);\n const outDir = destination.kind === \"local\" ? destination.path : undefined;\n const dump = await createDump({\n connectionString: config.connectionString,\n dbName,\n outDir,\n excludeSchemas: config.excludeSchemas\n });\n log(`Dump created: ${dump.fileName} (${formatBytes(dump.sizeBytes)})`);\n\n // Validate BEFORE pruning: a corrupt-but-exit-0 dump must never be\n // the reason the last good backup gets deleted.\n const check = await validateDump(dump.localFile);\n if (!check.ok) {\n // Discarded for every destination, the roles sidecar with it.\n // On a local one the file *is* the backup: left behind, it\n // keeps a valid backup's name, and retention — which ranks by\n // timestamp and never opens a file — counts it among the\n // newest it keeps while pruning a real backup beneath it.\n if (dump.globalsFile) discardPartialDump(dump.globalsFile);\n discardPartialDump(dump.localFile);\n throw new Error(`New backup failed validation — skipping upload and pruning to protect existing backups. ${check.reason}`);\n }\n\n let storedKey = dump.localFile;\n try {\n if (destination.kind !== \"local\") {\n const uploaded = await uploadBackup(config.storage!, dump.localFile, destination);\n storedKey = uploaded.storageUrl;\n log(`Uploaded to ${uploaded.storageUrl}`);\n // Upload the roles sidecar so a restore can recreate the\n // roles the dump's GRANT/RLS statements depend on.\n if (dump.globalsFile && fs.existsSync(dump.globalsFile)) {\n const g = await uploadBackup(config.storage!, dump.globalsFile, destination);\n log(`Uploaded roles sidecar to ${g.storageUrl}`);\n }\n }\n } finally {\n // For object-storage destinations the local dump was a temp\n // file — remove it once uploaded (or on failure).\n if (destination.kind !== \"local\" && fs.existsSync(dump.localFile)) {\n fs.unlinkSync(dump.localFile);\n }\n if (dump.globalsFile && destination.kind !== \"local\" && fs.existsSync(dump.globalsFile)) {\n fs.unlinkSync(dump.globalsFile);\n }\n }\n\n let pruned: string[] = [];\n if (config.retentionDays && config.retentionDays > 0) {\n pruned = await pruneBackups(\n destination,\n { retentionDays: config.retentionDays, keepMinimum: config.keepMinimum },\n config.storage\n );\n if (pruned.length > 0) {\n log(`Pruned ${pruned.length} backup(s) older than ${config.retentionDays} day(s).`);\n }\n }\n\n return {\n backup: storedKey,\n sizeBytes: dump.sizeBytes,\n pruned: pruned.length\n };\n }\n };\n\n // How the Backups panel finds this job among the project's crons: its id is\n // the cron file's name and its display name is configurable. A registry\n // symbol, so `@rebasepro/server` reads it without importing this package\n // (it must not), and non-enumerable, so a spread or a JSON dump of the\n // definition is unchanged. See `readBackupSchedule` in the server package.\n Object.defineProperty(job, Symbol.for(\"rebase.backupCron\"), { value: true });\n return job;\n}\n\nfunction formatBytes(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;\n}\n","import type { PgTable, AnyPgColumn } from \"drizzle-orm/pg-core\";\n\n/** Drizzle PgTable with column access by name. Runtime Drizzle tables satisfy this shape. */\nexport type RebasePgTable = PgTable & Record<string, AnyPgColumn>;\n\n/**\n * Read a statically-declared drizzle table as one whose columns are looked up\n * by name.\n *\n * The auth services take either the tables this package declares or a host\n * application's own, and reach their columns through strings — `has(\"aal\")`,\n * `col(\"sessionId\")` — because which columns exist is a property of the\n * database in front of them, not of the schema this package compiled against.\n * That is what {@link RebasePgTable} is for.\n *\n * A concrete table does not satisfy it: its columns have specific types and it\n * carries methods and symbols besides, so `Record<string, AnyPgColumn>` is not\n * a supertype and a direct `as` is refused. Written out, that refusal was\n * answered six times with `as unknown as RebasePgTable`, which also discarded\n * the check that the thing being converted is a table at all — the parameter\n * below is what restores it.\n */\nexport function asRebasePgTable(table: PgTable): RebasePgTable {\n return table as RebasePgTable;\n}\n","import { getTableColumns } from \"drizzle-orm\";\nimport { PgTable } from \"drizzle-orm/pg-core\";\nimport { CollectionConfig, ResolvedRelation } from \"@rebasepro/types\";\nimport { getTableName, resolveCollectionRelations } from \"@rebasepro/common\";\nimport { generateForeignKeyName, legacyForeignKeyName } from \"@rebasepro/utils\";\n\nimport { PostgresCollectionRegistry } from \"./PostgresCollectionRegistry\";\nimport { getPrimaryKeys } from \"../services/collection-helpers\";\n\n/**\n * Check every relation against the schema it actually runs on, at boot.\n *\n * \"Actually runs on\" is now literal: the registry's tables are read back from\n * `information_schema` (see `catalogue-schema.ts`), so this compares the\n * collections against the database. It used to compare them against the\n * committed `schema.generated.ts`, which made a *stale file* able to refuse a\n * boot — the exact incident class from 0.13, where boot-ensure had already\n * renamed the column and the file had not caught up.\n *\n * The tagged union made the *shape* of a relation impossible to get wrong: a\n * `manyToMany` cannot carry a `foreignKeyOnTarget`, a to-many cannot carry a\n * `localKey`. What it cannot know is whether any of the names are real —\n * whether `posts_tags` is a table, whether `author_id` is a column, whether a\n * `joinPath` connects the tables it claims to. Those are facts about the\n * database, and the type system never sees them.\n *\n * Until now nothing checked them until a query ran, and the failures were the\n * quiet kind. A missing junction table logged a warning and returned no rows,\n * so `posts/1/tags` answered `[]` — indistinguishable from a post with no tags.\n * The relation looked configured, the admin drew the tab, the tab was empty,\n * and nothing anywhere said why.\n *\n * The junction default is the sharp edge this exists for. `through.table`\n * defaults to the two table names sorted and joined, so renaming a table\n * silently re-points the relation at a name that was never created. It is the\n * one default whose output changes when you edit something that looks\n * unrelated.\n */\nexport interface RelationDefect {\n /** Slug of the collection declaring the relation. */\n collection: string;\n relationName: string;\n kind: ResolvedRelation[\"kind\"];\n /** What is wrong, in terms of the schema. */\n problem: string;\n /** The edit that fixes it. */\n fix: string;\n}\n\n/**\n * Every name a column answers to: the key in the drizzle schema and the real\n * column name in Postgres. A relation may legitimately be written with either,\n * and reporting a working relation as broken is worse than not checking.\n */\nfunction columnNames(table: PgTable): Set<string> {\n const names = new Set<string>();\n for (const [key, col] of Object.entries(getTableColumns(table))) {\n names.add(key);\n const dbName = (col as { name?: string })?.name;\n if (dbName) names.add(dbName);\n }\n return names;\n}\n\nconst quote = (xs: Iterable<string>) => Array.from(xs).map(s => `\\`${s}\\``).join(\", \");\n\n/** `on.from` / `on.to` accept a single column or a composite tuple. */\nconst asColumns = (value: string | string[]): string[] => Array.isArray(value) ? value : [value];\n\n/**\n * A one-column link into a key of several columns, or `undefined`.\n *\n * A `localKey`, a `foreignKeyOnTarget` and a junction column are each one\n * column, and one column cannot reference a composite key: Postgres refuses\n * `REFERENCES t (a)` when `a` alone is not unique, and a lookup on `a` matches\n * every row that shares it, so the related row served is whichever came first.\n * The schema generators refuse to emit such a link; this refuses to serve one.\n *\n * `via` is exempt — its steps name every column they compare — and so is a\n * `hasOne`/`hasMany` with a `sourceKey`, which names the one column it points\n * at instead of the key.\n */\nfunction compositeKeyDefect(\n relation: ResolvedRelation,\n source: CollectionConfig,\n target: CollectionConfig,\n registry: PostgresCollectionRegistry\n): Pick<RelationDefect, \"problem\" | \"fix\"> | undefined {\n const viaFix = \"or express the link as `kind: \\\"via\\\"`, whose `joinPath` compares every key column\";\n const refuse = (linkColumn: string, keyed: CollectionConfig, fix: string) => {\n const keys = getPrimaryKeys(keyed, registry);\n if (keys.length < 2) return undefined;\n return {\n problem: `${linkColumn} is one column, and \\`${keyed.slug}\\` is keyed on ` +\n `${quote(keys.map(key => key.fieldName))} together — one column cannot reference a composite key`,\n fix\n };\n };\n\n switch (relation.kind) {\n case \"belongsTo\":\n return refuse(`\\`localKey: \"${relation.localKey}\"\\``, target,\n `point the relation at a collection with a single-column key, ${viaFix}`);\n case \"hasOne\":\n case \"hasMany\":\n if (relation.sourceKey) return undefined;\n return refuse(`\\`foreignKeyOnTarget: \"${relation.foreignKeyOnTarget}\"\\``, source,\n `give the relation a \\`sourceKey\\` naming a single unique column on \\`${source.slug}\\` to point at, ${viaFix}`);\n case \"manyToMany\":\n return refuse(`\\`through.sourceColumn: \"${relation.through.sourceColumn}\"\\``, source, `a junction column points at a single-column key; ${viaFix}`)\n ?? refuse(`\\`through.targetColumn: \"${relation.through.targetColumn}\"\\``, target, `a junction column points at a single-column key; ${viaFix}`);\n case \"via\":\n return undefined;\n }\n}\n\n/**\n * Distinguish \"this column name is wrong\" from \"this database was never\n * migrated\".\n *\n * They present identically here — a relation asks for a column the table does\n * not have — but they are opposite problems with opposite fixes, and getting\n * them the wrong way round is how the 0.12 → 0.13 upgrade bricked projects.\n *\n * 0.13 changed the rule that derives foreign-key names: `categories` yields\n * `category_id` where it used to yield `categorie_id`. Boot-ensure renames the\n * database column to match — but only in a process that provisions. Where\n * nothing has, the database still carries the old spelling while the config\n * derives the new one, and the generic fix (\"set `through.targetColumn` to one\n * of: …\", listing the legacy name because that is what the table still has)\n * talks the reader into pinning a column that is about to be renamed out from\n * under them.\n *\n * So when the wanted name is what the current rule derives, and the table\n * carries what the *previous* rule would have derived from the same source, say\n * that instead.\n *\n * @param wanted the column the relation asks for\n * @param available every column the registered table has\n * @param sources names the default could have been derived from (a slug, a\n * relation name) — checking against these rather than guessing\n * backwards from `wanted` keeps the match exact\n */\nfunction staleCodegenRename(\n wanted: string,\n available: Set<string>,\n sources: string[]\n): { legacy: string; current: string } | null {\n for (const source of sources) {\n if (!source) continue;\n const current = generateForeignKeyName(source);\n const legacy = legacyForeignKeyName(source);\n // Only a name that actually moved, and only when the table still has the\n // old spelling and not the new one.\n if (current !== wanted || legacy === current) continue;\n if (available.has(legacy) && !available.has(current)) return { legacy, current };\n }\n return null;\n}\n\n/** The shared explanation, so every relation kind reports it identically. */\nfunction staleCodegenDefect(\n table: string,\n { legacy, current }: { legacy: string; current: string }\n): Pick<RelationDefect, \"problem\" | \"fix\"> {\n return {\n problem:\n `the database still has \\`${legacy}\\` on \\`${table}\\`, but this release derives ` +\n `\\`${current}\\` — the column predates the foreign-key naming fix and nothing has ` +\n \"renamed it yet\",\n fix:\n \"let boot-ensure rename it (redeploy, or start with schema provisioning enabled), or \" +\n \"run `rebase db push`. Then run `rebase schema generate` so the committed schema file \" +\n `agrees. To keep \\`${legacy}\\` instead, name it explicitly on the relation.`\n };\n}\n\n/**\n * Relations whose names do not resolve against the registered schema.\n *\n * Fails open wherever it cannot see enough to be sure — an unregistered source\n * table, a target belonging to another backend — because a false alarm here\n * costs more than a missed one: it would block boot on a working app.\n */\nexport function findRelationDefects(\n collections: CollectionConfig[],\n registry: PostgresCollectionRegistry\n): RelationDefect[] {\n const defects: RelationDefect[] = [];\n const registeredSlugs = new Set(registry.getCollections().map(c => c.slug));\n\n for (const collection of collections) {\n const sourceTableName = getTableName(collection);\n const sourceTable = registry.getTable(sourceTableName);\n // Nothing to check against. Another boot warning already covers this.\n if (!sourceTable) continue;\n\n const sourceColumns = columnNames(sourceTable);\n const relations = resolveCollectionRelations(collection);\n\n for (const relation of Object.values(relations)) {\n const at = { collection: collection.slug,\nrelationName: relation.relationName,\nkind: relation.kind };\n\n let targetCollection: CollectionConfig;\n try {\n targetCollection = relation.target();\n } catch (e) {\n defects.push({\n ...at,\n problem: `its \\`target()\\` threw: ${e instanceof Error ? e.message : String(e)}`,\n fix: \"a target thunk usually throws because of a circular import — make sure it is `() => otherCollection` and not evaluated at module load\"\n });\n continue;\n }\n\n // A target this registry has never heard of belongs to another\n // backend; its tables are not ours to check.\n if (!registeredSlugs.has(targetCollection.slug)) continue;\n\n const targetTableName = getTableName(targetCollection);\n const targetTable = registry.getTable(targetTableName);\n if (!targetTable) {\n defects.push({\n ...at,\n problem: `it points at collection \\`${targetCollection.slug}\\`, which has no table \\`${targetTableName}\\` in the schema`,\n fix: `create the \\`${targetTableName}\\` table, or correct \\`table\\` on the \\`${targetCollection.slug}\\` collection`\n });\n continue;\n }\n const targetColumns = columnNames(targetTable);\n\n // A single-column link into a composite key, whichever side the\n // key is on.\n const composite = compositeKeyDefect(relation, collection, targetCollection, registry);\n if (composite) {\n defects.push({ ...at, ...composite });\n continue;\n }\n\n switch (relation.kind) {\n case \"belongsTo\": {\n if (!sourceColumns.has(relation.localKey)) {\n // `localKey` defaults to the relation name run through\n // the foreign-key rule, so it moves with that rule.\n const stale = staleCodegenRename(\n relation.localKey,\n sourceColumns,\n [relation.relationName, targetCollection.slug]\n );\n defects.push(stale\n ? { ...at, ...staleCodegenDefect(sourceTableName, stale) }\n : {\n ...at,\n problem: `\\`localKey: \"${relation.localKey}\"\\` is not a column on \\`${sourceTableName}\\``,\n fix: `add the column, or set \\`localKey\\` to one of: ${quote(sourceColumns)}`\n });\n }\n break;\n }\n\n case \"hasOne\":\n case \"hasMany\": {\n if (!targetColumns.has(relation.foreignKeyOnTarget)) {\n // The default is derived from *this* collection's slug —\n // the column on the target that points back here.\n const stale = staleCodegenRename(\n relation.foreignKeyOnTarget,\n targetColumns,\n [collection.slug]\n );\n defects.push(stale\n ? { ...at, ...staleCodegenDefect(targetTableName, stale) }\n : {\n ...at,\n problem: `\\`foreignKeyOnTarget: \"${relation.foreignKeyOnTarget}\"\\` is not a column on the target table \\`${targetTableName}\\``,\n fix: `add the column, or set \\`foreignKeyOnTarget\\` to one of: ${quote(targetColumns)}`\n });\n }\n // `sourceKey` is the easiest of the two to put on the wrong\n // side — it is the only column in a `hasMany` that lives\n // here rather than on the target, and naming a target column\n // reads perfectly well right next to `foreignKeyOnTarget`.\n if (relation.sourceKey && !sourceColumns.has(relation.sourceKey)) {\n defects.push({\n ...at,\n problem: `\\`sourceKey: \"${relation.sourceKey}\"\\` is not a column on \\`${sourceTableName}\\``,\n fix: targetColumns.has(relation.sourceKey)\n ? `it is a column on the *target* table \\`${targetTableName}\\` — \\`sourceKey\\` names ` +\n \"the column on this collection that the target's foreign key points at, so it \" +\n `must be one of: ${quote(sourceColumns)}`\n : `add the column, or set \\`sourceKey\\` to one of: ${quote(sourceColumns)}`\n });\n }\n break;\n }\n\n case \"manyToMany\": {\n const { table, sourceColumn, targetColumn } = relation.through;\n const junction = registry.getTable(table);\n if (!junction) {\n defects.push({\n ...at,\n problem: `its junction table \\`${table}\\` does not exist`,\n fix: `create \\`${table}\\`, or name the real one with \\`through: { table: \"...\" }\\`. ` +\n \"Note that an omitted `through.table` is derived from the two table names sorted \" +\n \"and joined, so renaming a table changes it\"\n });\n break;\n }\n const junctionColumns = columnNames(junction);\n // Junction columns are the ones that actually moved in 0.13:\n // each defaults to its endpoint collection's *slug* run\n // through the foreign-key rule, and slugs are plural.\n const derivedFrom = {\n sourceColumn: [collection.slug],\n targetColumn: [targetCollection.slug]\n } as const;\n for (const [label, column] of [[\"sourceColumn\", sourceColumn], [\"targetColumn\", targetColumn]] as const) {\n if (!junctionColumns.has(column)) {\n const stale = staleCodegenRename(column, junctionColumns, [...derivedFrom[label]]);\n defects.push(stale\n ? { ...at, ...staleCodegenDefect(table, stale) }\n : {\n ...at,\n problem: `\\`through.${label}: \"${column}\"\\` is not a column on the junction table \\`${table}\\``,\n fix: `set \\`through.${label}\\` to one of: ${quote(junctionColumns)}` +\n (label === \"sourceColumn\" ? \" — it is the column naming *this* collection\" : \"\")\n });\n }\n }\n break;\n }\n\n case \"via\": {\n if (relation.joinPath.length === 0) {\n defects.push({\n ...at,\n problem: \"its `joinPath` is empty, so it joins nothing\",\n fix: \"add at least one step, ending at the target's table\"\n });\n break;\n }\n\n // Walk the chain: each step's `from` names columns on the\n // previous table, its `to` names columns on its own.\n let prevName = sourceTableName;\n let prevColumns = sourceColumns;\n let broken = false;\n\n for (const [i, step] of relation.joinPath.entries()) {\n const stepTable = registry.getTable(step.table);\n if (!stepTable) {\n defects.push({\n ...at,\n problem: `step ${i + 1} of its \\`joinPath\\` joins \\`${step.table}\\`, which is not a table in the schema`,\n fix: `correct \\`joinPath[${i}].table\\``\n });\n broken = true;\n break;\n }\n const stepColumns = columnNames(stepTable);\n\n for (const column of asColumns(step.on.from)) {\n if (!prevColumns.has(column)) {\n defects.push({\n ...at,\n problem: `step ${i + 1} joins \\`${prevName}.${column}\\` → \\`${step.table}\\`, but \\`${column}\\` is not a column on \\`${prevName}\\``,\n fix: `\\`joinPath[${i}].on.from\\` names columns on ${i === 0 ? \"this collection's table\" : `the previous step's table (\\`${prevName}\\`)`}: ${quote(prevColumns)}`\n });\n }\n }\n for (const column of asColumns(step.on.to)) {\n if (!stepColumns.has(column)) {\n defects.push({\n ...at,\n problem: `step ${i + 1} joins into \\`${step.table}.${column}\\`, but \\`${column}\\` is not a column on \\`${step.table}\\``,\n fix: `\\`joinPath[${i}].on.to\\` names columns on \\`${step.table}\\`: ${quote(stepColumns)}`\n });\n }\n }\n\n if (asColumns(step.on.from).length !== asColumns(step.on.to).length) {\n defects.push({\n ...at,\n problem: `step ${i + 1} compares ${asColumns(step.on.from).length} column(s) against ${asColumns(step.on.to).length}`,\n fix: `\\`from\\` and \\`to\\` must name the same number of columns in \\`joinPath[${i}]\\``\n });\n }\n\n prevName = step.table;\n prevColumns = stepColumns;\n }\n\n // The chain has to end where the relation says it points,\n // or the rows it returns are not the target's rows.\n if (!broken && prevName !== targetTableName) {\n defects.push({\n ...at,\n problem: `its \\`joinPath\\` ends at \\`${prevName}\\`, but it targets \\`${targetCollection.slug}\\` (table \\`${targetTableName}\\`)`,\n fix: `make the last step join \\`${targetTableName}\\`, or point \\`target\\` at the collection backed by \\`${prevName}\\``\n });\n }\n break;\n }\n\n default: {\n const exhaustive: never = relation;\n throw new Error(`Unhandled relation kind: ${JSON.stringify(exhaustive)}`);\n }\n }\n }\n }\n\n return defects;\n}\n\n/**\n * Fail boot on any relation that cannot resolve, listing all of them at once.\n *\n * Deliberately fatal rather than a warning. Every one of these produces an\n * empty result at query time and nothing else — an empty tab, an empty\n * `include`, a subcollection that looks like it has no rows. A server that\n * refuses to start is recoverable in a minute; a relation that quietly answers\n * \"nothing\" is the kind of bug found in production, weeks later, by a user\n * asking where their data went.\n */\nexport function assertRelationsResolve(\n collections: CollectionConfig[],\n registry: PostgresCollectionRegistry\n): void {\n const defects = findRelationDefects(collections, registry);\n if (defects.length === 0) return;\n\n const lines = defects.map(d =>\n ` • ${d.collection}.${d.relationName} (${d.kind})\\n` +\n ` ${d.problem}\\n` +\n ` fix: ${d.fix}`\n );\n\n throw new Error(\n `${defects.length} relation${defects.length === 1 ? \"\" : \"s\"} cannot resolve against ` +\n \"the database this server is connected to.\\n\\n\" +\n \"Each of these would return no rows at query time rather than reporting an error, \" +\n \"so they are fatal at boot instead.\\n\\n\" +\n // The columns listed below were read from `information_schema`, not from\n // `schema.generated.ts`. That distinction is the whole diagnosis after an\n // upgrade: this check used to read the committed file, so a file older\n // than the database refused a boot that was fine, and its advice named\n // columns that no longer existed. What is left is a real disagreement\n // between the collections and the database — either the schema has not\n // been applied here, or the relation names something that was never\n // there.\n \"If the schema has not been applied to this database yet, apply it — `rebase db push`\\n\" +\n \"(dev), `rebase db migrate` (prod), or a redeploy, which creates tables at boot. If it\\n\" +\n \"has, then the collection is what disagrees with the database:\\n\\n\" +\n lines.join(\"\\n\\n\") + \"\\n\"\n );\n}\n","import { isTable, getTableName, Relations } from \"drizzle-orm\";\nimport { PgEnum, PgTable } from \"drizzle-orm/pg-core\";\nimport { CollectionConfig } from \"@rebasepro/types\";\nimport { logger } from \"@rebasepro/server\";\nimport { PostgresCollectionRegistry } from \"./PostgresCollectionRegistry\";\nimport { warnOnKeysTheAdminCannotResolve } from \"../services/collection-helpers\";\nimport { assertRelationsResolve } from \"./validate-relations\";\n\n/**\n * Everything a registry is built from: the collections, and the drizzle schema\n * they are backed by. In BaaS mode all of it is introspected from the live\n * database; when collections are declared it comes from the config and the generated schema.\n */\nexport interface RegistrySchema {\n collections?: CollectionConfig[];\n tables?: Record<string, unknown>;\n enums?: Record<string, PgEnum<[string, ...string[]]>>;\n relations?: Record<string, Relations>;\n}\n\n/**\n * Build the collection registry for a driver.\n *\n * The order matters and is the reason this is one function rather than a run of\n * statements in the bootstrapper. Keys are resolved from the drizzle schema, so\n * anything that inspects them has to run *after* the tables are registered —\n * and `warnOnKeysTheAdminCannotResolve` fails open if it does not, because a\n * collection whose table it cannot look up is one it has nothing to say about.\n * Warned too early, it would skip every collection and report nothing, which\n * reads exactly like having nothing to report.\n */\nexport function buildCollectionRegistry(schema: RegistrySchema): PostgresCollectionRegistry {\n const registry = new PostgresCollectionRegistry();\n\n if (schema.collections) {\n registry.registerMultiple(schema.collections);\n // `Auto-discovered collections` already reports the count and the\n // directory they came from; this is the same fact with the names.\n logger.debug(\n `📋 [PostgresRegistry] Registered ${registry.getCollections().length} collections: ` +\n `[${registry.getCollections().map(c => c.slug).join(\", \")}]`\n );\n }\n\n if (schema.tables) {\n Object.values(schema.tables).forEach((table) => {\n if (isTable(table)) {\n registry.registerTable(table as PgTable, getTableName(table));\n }\n });\n }\n\n if (schema.enums) registry.registerEnums(schema.enums);\n if (schema.relations) registry.registerRelations(schema.relations);\n\n // Now that the keys resolve: say which of them the admin cannot see. It\n // compiles the same collection files into its bundle but never the drizzle\n // schema, and nothing serves it one, so only an edit to the config fixes it.\n warnOnKeysTheAdminCannotResolve(registry.getCollections(), registry);\n\n // And now that the tables resolve: refuse to start on a relation whose\n // names do not exist. The union checks a relation's shape at compile time;\n // only here is there a schema to check its *names* against. Every one of\n // these used to surface as an empty result at query time and nothing else.\n assertRelationsResolve(registry.getCollections(), registry);\n\n return registry;\n}\n","import { eq, getTableName, sql } from \"drizzle-orm\";\nimport { NodePgDatabase } from \"drizzle-orm/node-postgres\";\nimport { getTableConfig, type AnyPgColumn } from \"drizzle-orm/pg-core\";\nimport { asRebasePgTable, type RebasePgTable } from \"../types\";\nimport { users, refreshTokens, passwordResetTokens, userIdentities, magicLinkTokens } from \"../schema/auth-schema\";\nimport {\n UserRepository,\n TokenRepository,\n MfaRepository,\n AuthRepository,\n UserData,\n CreateUserData,\n RefreshTokenInfo,\n RefreshTokenSession,\n PasswordResetTokenInfo,\n MagicLinkTokenInfo,\n UserIdentityData,\n ListUsersOptions,\n PaginatedUsersResult,\n MfaFactor,\n MfaChallengeInfo,\n AccountAccessState,\n PendingEmailChange,\n ApiError\n} from \"@rebasepro/server\";\nimport { toSnakeCase, camelCase } from \"@rebasepro/utils\";\nimport { escapeLikePattern } from \"../utils/drizzle-conditions\";\nimport { extractPgError } from \"../utils/pg-error-utils\";\n\nexport interface AuthSchemaTables {\n users: RebasePgTable;\n refreshTokens: RebasePgTable;\n passwordResetTokens: RebasePgTable;\n appConfig: RebasePgTable;\n userIdentities: RebasePgTable;\n}\n\nfunction getColumnKey(table: RebasePgTable | undefined, ...keys: string[]): string | undefined {\n if (!table) return undefined;\n for (const key of keys) {\n if (key in table) return key;\n const snake = toSnakeCase(key);\n if (snake in table) return snake;\n const camel = camelCase(key);\n if (camel in table) return camel;\n }\n return undefined;\n}\n\n/** `\"schema\".\"table\"`, for the statements written as raw SQL. */\nfunction qualifiedTableName(table: RebasePgTable): string {\n const schema = getTableConfig(table).schema || \"public\";\n return `\"${schema}\".\"${getTableName(table)}\"`;\n}\n\n/** The shape of a session id sign-in mints (`randomUUID()`). */\nconst UUID_SHAPE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\nfunction getColumn(table: RebasePgTable | undefined, ...keys: string[]): RebasePgTable[string] | undefined {\n if (!table) return undefined;\n const key = getColumnKey(table, ...keys);\n return key ? table[key] : undefined;\n}\n\n/**\n * The single definition of what an email address looks like in storage.\n *\n * Reads have always folded case; writes did not, and normalising was left to\n * each caller. That asymmetry is only ever one forgotten `.toLowerCase()` away\n * from a row no lookup can find — the account exists, every sign-in path\n * reports no such user, and the byte-exact UNIQUE on the column does not stop a\n * duplicate differing only in case. Applied on both sides here so the guarantee\n * belongs to the repository rather than to its callers' discipline; the\n * `lower(email)` unique index added in `ensureAuthTablesExist` is the database\n * half of the same rule.\n *\n * Whitespace goes too: a trailing space survives the fold and reproduces the\n * problem exactly.\n *\n * Re-exported rather than defined here: `@rebasepro/server` and\n * `@rebasepro/server-mongo` write this column too, and a second copy of this\n * rule is the defect it exists to prevent.\n */\nimport { normalizeEmail } from \"@rebasepro/common\";\nexport { normalizeEmail };\n\n/**\n * PostgreSQL implementation of UserRepository.\n * Handles all user-related database operations using Drizzle ORM.\n */\nexport class UserService implements UserRepository {\n private usersTable: RebasePgTable;\n private userIdentitiesTable: RebasePgTable;\n /** Read by `getAccountAccessState`, to say whether one sign-in is still live. */\n private refreshTokensTable: RebasePgTable;\n\n constructor(\n private db: NodePgDatabase,\n tableOrTables?: RebasePgTable | Partial<AuthSchemaTables>\n ) {\n if (tableOrTables && ((tableOrTables as Partial<AuthSchemaTables>).users)) {\n const tables = tableOrTables as Partial<AuthSchemaTables>;\n this.usersTable = (tables.users || users) as RebasePgTable;\n this.userIdentitiesTable = (tables.userIdentities || userIdentities) as RebasePgTable;\n this.refreshTokensTable = (tables.refreshTokens || refreshTokens) as RebasePgTable;\n } else {\n const table = tableOrTables as RebasePgTable | undefined;\n this.usersTable = table || asRebasePgTable(users);\n this.userIdentitiesTable = asRebasePgTable(userIdentities);\n this.refreshTokensTable = asRebasePgTable(refreshTokens);\n }\n }\n\n private getQualifiedUsersTableName(): string {\n const name = getTableName(this.usersTable);\n const schema = getTableConfig(this.usersTable).schema || \"public\";\n return `\"${schema}\".\"${name}\"`;\n }\n\n /**\n * Run a privileged auth write with an explicitly cleared RLS context.\n *\n * The auth services run on the base/owner connection, which by design\n * carries a NULL `app.uid` so the `rebase.uid() IS NULL` server-escape\n * in the default policies applies. That NULL is normally guaranteed by\n * `set_config(..., is_local = true)` resetting at transaction end — but a\n * GUC that survives on a pooled connection (or a connection role that\n * doesn't bypass RLS: FORCE ROW LEVEL SECURITY, or a non-owner role)\n * turns the trusted write into an RLS-scoped one and denies it with\n * SQLSTATE 42501. Clearing the GUCs here, transaction-locally at the\n * single chokepoint, makes the server context deterministic instead of\n * trusting whatever state the pool hands us. `rebase.uid()` reads '' as\n * NULL via NULLIF, so '' is the server context.\n */\n private async withServerContext<T>(fn: (db: NodePgDatabase) => Promise<T>): Promise<T> {\n return await this.db.transaction(async (tx) => {\n await tx.execute(sql`\n SELECT set_config('app.uid', '', true),\n set_config('app.user_id', '', true),\n set_config('app.user_roles', '', true),\n set_config('app.jwt', '', true)\n `);\n return await fn(tx as NodePgDatabase);\n });\n }\n\n private mapRowToUser(row: Record<string, unknown>): UserData {\n if (!row) return row as UserData;\n\n const id = (row.id ?? row.uid) as string;\n const email = row.email as string;\n const passwordHash = (row.password_hash ?? row.passwordHash ?? null) as string | null | undefined;\n const displayName = (row.display_name ?? row.displayName ?? null) as string | null | undefined;\n const photoUrl = (row.photo_url ?? row.photoUrl ?? row.photoURL ?? null) as string | null | undefined;\n const emailVerified = (row.email_verified ?? row.emailVerified ?? false) as boolean;\n const emailVerificationToken = (row.email_verification_token ?? row.emailVerificationToken ?? null) as string | null | undefined;\n const emailVerificationSentAt = (row.email_verification_sent_at ?? row.emailVerificationSentAt ?? null) as string | number | Date | null;\n const isAnonymous = (row.is_anonymous ?? row.isAnonymous ?? false) as boolean;\n const disabledAt = row.disabled_at ?? row.disabledAt ?? null;\n const createdAt = (row.created_at ?? row.createdAt) as string | number | Date | undefined;\n const updatedAt = (row.updated_at ?? row.updatedAt) as string | number | Date | undefined;\n\n const metadata: Record<string, any> = { ...((row.metadata as Record<string, any> | undefined) || {}) };\n\n const knownKeys = new Set([\n \"id\", \"uid\", \"email\",\n \"password_hash\", \"passwordHash\",\n \"display_name\", \"displayName\",\n \"photo_url\", \"photoUrl\", \"photoURL\",\n \"email_verified\", \"emailVerified\",\n \"email_verification_token\", \"emailVerificationToken\",\n \"email_verification_sent_at\", \"emailVerificationSentAt\",\n \"is_anonymous\", \"isAnonymous\",\n \"disabled_at\", \"disabledAt\",\n \"tokens_valid_after\", \"tokensValidAfter\",\n // Never metadata: the change is the account's business, and the\n // token's hash is a secret.\n \"pending_email\", \"pendingEmail\",\n \"email_change_token\", \"emailChangeToken\",\n \"email_change_sent_at\", \"emailChangeSentAt\",\n \"roles\",\n \"created_at\", \"createdAt\",\n \"updated_at\", \"updatedAt\",\n \"metadata\"\n ]);\n\n for (const [key, val] of Object.entries(row)) {\n if (!knownKeys.has(key)) {\n const camelKey = camelCase(key);\n metadata[camelKey] = val;\n }\n }\n\n return {\n id,\n email,\n passwordHash,\n displayName,\n photoUrl,\n emailVerified,\n emailVerificationToken,\n emailVerificationSentAt: emailVerificationSentAt ? new Date(emailVerificationSentAt) : null,\n isAnonymous,\n ...(disabledAt ? { disabled: true } : {}),\n createdAt: createdAt ? new Date(createdAt) : new Date(),\n updatedAt: updatedAt ? new Date(updatedAt) : new Date(),\n metadata\n };\n }\n\n private mapPayload(data: Partial<CreateUserData>): Record<string, unknown> {\n if (!data) return {};\n\n const payload: Record<string, unknown> = {};\n\n const idKey = getColumnKey(this.usersTable, \"id\") || \"id\";\n const emailKey = getColumnKey(this.usersTable, \"email\") || \"email\";\n const passwordHashKey = getColumnKey(this.usersTable, \"passwordHash\", \"password_hash\") || \"passwordHash\";\n const displayNameKey = getColumnKey(this.usersTable, \"displayName\", \"display_name\") || \"displayName\";\n const photoUrlKey = getColumnKey(this.usersTable, \"photoUrl\", \"photo_url\") || \"photoUrl\";\n const emailVerifiedKey = getColumnKey(this.usersTable, \"emailVerified\", \"email_verified\") || \"emailVerified\";\n const emailVerificationTokenKey = getColumnKey(this.usersTable, \"emailVerificationToken\", \"email_verification_token\") || \"emailVerificationToken\";\n const emailVerificationSentAtKey = getColumnKey(this.usersTable, \"emailVerificationSentAt\", \"email_verification_sent_at\") || \"emailVerificationSentAt\";\n const isAnonymousKey = getColumnKey(this.usersTable, \"isAnonymous\", \"is_anonymous\") || \"isAnonymous\";\n const createdAtKey = getColumnKey(this.usersTable, \"createdAt\", \"created_at\") || \"createdAt\";\n const updatedAtKey = getColumnKey(this.usersTable, \"updatedAt\", \"updated_at\") || \"updatedAt\";\n const metadataKey = getColumnKey(this.usersTable, \"metadata\") || \"metadata\";\n\n if (\"id\" in data) payload[idKey] = data.id;\n if (\"email\" in data) payload[emailKey] = normalizeEmail(data.email);\n if (\"passwordHash\" in data) payload[passwordHashKey] = data.passwordHash;\n if (\"displayName\" in data) payload[displayNameKey] = data.displayName;\n if (\"photoUrl\" in data) payload[photoUrlKey] = data.photoUrl;\n if (\"emailVerified\" in data) payload[emailVerifiedKey] = data.emailVerified;\n if (\"emailVerificationToken\" in data) payload[emailVerificationTokenKey] = data.emailVerificationToken;\n if (\"emailVerificationSentAt\" in data) payload[emailVerificationSentAtKey] = data.emailVerificationSentAt;\n if (\"isAnonymous\" in data) payload[isAnonymousKey] = data.isAnonymous;\n if (\"createdAt\" in data) payload[createdAtKey] = data.createdAt;\n if (\"updatedAt\" in data) payload[updatedAtKey] = data.updatedAt;\n\n const metadata: Record<string, any> = { ...(data.metadata || {}) };\n const remainingMetadata: Record<string, any> = {};\n\n for (const [key, val] of Object.entries(metadata)) {\n const tableColKey = getColumnKey(this.usersTable, key);\n if (tableColKey &&\n tableColKey !== idKey &&\n tableColKey !== emailKey &&\n tableColKey !== passwordHashKey &&\n tableColKey !== displayNameKey &&\n tableColKey !== photoUrlKey &&\n tableColKey !== emailVerifiedKey &&\n tableColKey !== emailVerificationTokenKey &&\n tableColKey !== emailVerificationSentAtKey &&\n tableColKey !== isAnonymousKey &&\n tableColKey !== createdAtKey &&\n tableColKey !== updatedAtKey &&\n tableColKey !== metadataKey) {\n payload[tableColKey] = val;\n } else {\n remainingMetadata[key] = val;\n }\n }\n\n if (metadataKey in this.usersTable) {\n payload[metadataKey] = remainingMetadata;\n }\n\n return payload;\n }\n\n /**\n * @see UserRepository.createUser — an email already in use is a 409.\n *\n * The route checks first and answers 409; this is the same answer for the\n * requests that get past the check, which two clicks on a signup button\n * are enough to produce. `PersistService` has mapped `23505` to a conflict\n * for collection writes since the layer that holds the SQLSTATE was made\n * responsible for saying whose fault a failure is; the auth writes never\n * got the same treatment and reached the client as \"Internal Server Error\".\n */\n async createUser(data: CreateUserData): Promise<UserData> {\n const payload = this.mapPayload(data);\n try {\n const [row] = await this.withServerContext(async (db) =>\n (await db.insert(this.usersTable).values(payload).returning()) as Record<string, unknown>[]\n );\n return this.mapRowToUser(row);\n } catch (error) {\n // Drizzle wraps the pg error, so the SQLSTATE is down the `cause`\n // chain rather than on the error itself.\n if (extractPgError(error)?.code === \"23505\") {\n throw ApiError.conflict(\"Email already registered\", \"EMAIL_EXISTS\");\n }\n throw error;\n }\n }\n\n async getUserById(id: string): Promise<UserData | null> {\n const idCol = getColumn(this.usersTable, \"id\");\n if (!idCol) return null;\n const [row] = await this.db.select().from(this.usersTable).where(eq(idCol, id));\n return row ? this.mapRowToUser(row as Record<string, unknown>) : null;\n }\n\n async getUserByEmail(email: string): Promise<UserData | null> {\n const emailCol = getColumn(this.usersTable, \"email\");\n if (!emailCol) return null;\n const [row] = await this.db.select().from(this.usersTable).where(eq(emailCol, normalizeEmail(email)));\n return row ? this.mapRowToUser(row as Record<string, unknown>) : null;\n }\n\n async getUserByIdentity(provider: string, providerId: string): Promise<UserData | null> {\n const userIdCol = getColumn(this.usersTable, \"id\");\n if (!userIdCol) return null;\n\n const result = await this.db\n .select({ user: this.usersTable })\n .from(this.usersTable)\n .innerJoin(this.userIdentitiesTable, eq(userIdCol, this.userIdentitiesTable.uid))\n .where(\n sql`${this.userIdentitiesTable.provider} = ${provider} AND ${this.userIdentitiesTable.providerId} = ${providerId}`\n )\n .limit(1);\n\n if (result.length === 0) return null;\n return this.mapRowToUser(result[0].user as Record<string, unknown>);\n }\n\n async getUserIdentities(uid: string): Promise<UserIdentityData[]> {\n const schema = getTableConfig(this.userIdentitiesTable).schema || \"public\";\n const result = await this.db.execute(sql`\n SELECT id, uid, provider, provider_id, profile_data, created_at, updated_at\n FROM ${sql.raw(`\"${schema}\".\"user_identities\"`)}\n WHERE uid = ${uid}\n `);\n\n return result.rows.map((row: Record<string, unknown>) => ({\n id: row.id as string,\n uid: row.uid as string,\n provider: row.provider as string,\n providerId: row.provider_id as string,\n profileData: (row.profile_data as Record<string, unknown> | null) ?? null,\n createdAt: row.created_at as Date,\n updatedAt: row.updated_at as Date\n }));\n }\n\n /**\n * @see UserRepository.unlinkUserIdentity — scoped to the user as well as\n * the identity, so it can only ever detach an identity from the account\n * it names.\n */\n async unlinkUserIdentity(uid: string, provider: string, providerId: string): Promise<void> {\n await this.withServerContext(async (db) => db.delete(this.userIdentitiesTable).where(\n sql`${this.userIdentitiesTable.uid} = ${uid} AND ${this.userIdentitiesTable.provider} = ${provider} AND ${this.userIdentitiesTable.providerId} = ${providerId}`\n ));\n }\n\n async linkUserIdentity(uid: string, provider: string, providerId: string, profileData?: Record<string, unknown>): Promise<void> {\n await this.withServerContext(async (db) => db.insert(this.userIdentitiesTable).values({\n uid,\n provider,\n providerId,\n profileData: profileData || null\n }).onConflictDoNothing({ target: [this.userIdentitiesTable.provider, this.userIdentitiesTable.providerId] }));\n }\n\n async updateUser(id: string, data: Partial<Omit<CreateUserData, \"id\">>): Promise<UserData | null> {\n const idCol = getColumn(this.usersTable, \"id\");\n if (!idCol) return null;\n const payload = this.mapPayload(data);\n const updatedAtKey = getColumnKey(this.usersTable, \"updatedAt\", \"updated_at\") || \"updatedAt\";\n payload[updatedAtKey] = new Date();\n\n let row: Record<string, unknown> | undefined;\n try {\n [row] = await this.withServerContext(async (db) =>\n (await db\n .update(this.usersTable)\n .set(payload)\n .where(eq(idCol, id))\n .returning()) as Record<string, unknown>[]\n );\n } catch (error) {\n // The unique index `createUser` maps, reached by an email change\n // onto an address another account holds — the same 409.\n if (extractPgError(error)?.code === \"23505\") {\n throw ApiError.conflict(\"Email already registered\", \"EMAIL_EXISTS\");\n }\n throw error;\n }\n return row ? this.mapRowToUser(row) : null;\n }\n\n async deleteUser(id: string): Promise<void> {\n const idCol = getColumn(this.usersTable, \"id\");\n if (!idCol) return;\n await this.withServerContext(async (db) => db.delete(this.usersTable).where(eq(idCol, id)));\n }\n\n async listUsers(): Promise<UserData[]> {\n const rows = await this.db.select().from(this.usersTable);\n return (rows as Record<string, unknown>[]).map(row => this.mapRowToUser(row));\n }\n\n async listUsersPaginated(options?: ListUsersOptions): Promise<PaginatedUsersResult> {\n const limit = options?.limit ?? 25;\n const offset = options?.offset ?? 0;\n const search = options?.search?.trim() || \"\";\n const orderBy = options?.orderBy || \"createdAt\";\n const orderDir = options?.orderDir || \"desc\";\n const roleId = options?.roleId;\n\n const orderCol = getColumn(this.usersTable, orderBy);\n const orderColumn = orderCol ? orderCol.name : \"created_at\";\n const direction = orderDir === \"asc\" ? sql`ASC` : sql`DESC`;\n\n const emailCol = getColumn(this.usersTable, \"email\");\n const emailColumn = emailCol ? emailCol.name : \"email\";\n const displayNameCol = getColumn(this.usersTable, \"displayName\", \"display_name\");\n const displayNameColumn = displayNameCol ? displayNameCol.name : \"display_name\";\n const idCol = getColumn(this.usersTable, \"id\");\n const idColumn = idCol ? idCol.name : \"id\";\n\n const usersTableName = this.getQualifiedUsersTableName();\n const conditions = [];\n if (roleId) {\n conditions.push(sql`${roleId} = ANY(${sql.raw(usersTableName)}.roles)`);\n }\n if (search) {\n // `search` is a substring search over the admin user list, not a\n // pattern the caller writes: the same reasoning as the collection\n // search path, so it shares that path's helper rather than growing\n // a second copy that can drift. See `escapeLikePattern`.\n //\n // Email, name, any role the user holds, and the uid: what an\n // administrator has in hand when looking someone up — Studio's\n // \"Run as\" picker says it searches by name, email or role, and a\n // uid is what the users table and the logs show.\n const pattern = `%${escapeLikePattern(search)}%`;\n const table = sql.raw(usersTableName);\n conditions.push(sql`(${table}.${sql.raw(emailColumn)} ILIKE ${pattern}\n OR ${table}.${sql.raw(displayNameColumn)} ILIKE ${pattern}\n OR EXISTS (SELECT 1 FROM unnest(${table}.roles) AS user_role(name) WHERE user_role.name ILIKE ${pattern})\n OR CAST(${table}.${sql.raw(idColumn)} AS text) ILIKE ${pattern})`);\n }\n\n const whereClause = conditions.length > 0 ? sql`WHERE ${sql.join(conditions, sql` AND `)}` : sql``;\n\n // Sorting: users with roles first if no role filter, then by requested order\n const orderByClause = roleId\n ? sql`ORDER BY ${sql.raw(usersTableName)}.${sql.raw(orderColumn)} ${direction}`\n : sql`ORDER BY array_length(${sql.raw(usersTableName)}.roles, 1) DESC NULLS LAST, ${sql.raw(usersTableName)}.${sql.raw(orderColumn)} ${direction}`;\n\n const countResult = await this.db.execute(sql`\n SELECT count(*)::int as total FROM ${sql.raw(usersTableName)}\n ${whereClause}\n `);\n const total = (countResult.rows[0] as { total: number }).total;\n\n const dataResult = await this.db.execute(sql`\n SELECT * FROM ${sql.raw(usersTableName)}\n ${whereClause}\n ${orderByClause}\n LIMIT ${limit} OFFSET ${offset}\n `);\n const rows = dataResult.rows;\n\n // Map rows to camelCase UserData\n const mappedUsers: UserData[] = (rows as Record<string, unknown>[]).map((row) => this.mapRowToUser(row));\n\n return { users: mappedUsers,\n total,\n limit,\n offset };\n }\n\n /**\n * Update user's password hash; `null` removes the password.\n */\n async updatePassword(id: string, passwordHash: string | null): Promise<void> {\n const idCol = getColumn(this.usersTable, \"id\");\n if (!idCol) return;\n const passwordHashColKey = getColumnKey(this.usersTable, \"passwordHash\", \"password_hash\") || \"passwordHash\";\n const updatedAtColKey = getColumnKey(this.usersTable, \"updatedAt\", \"updated_at\") || \"updatedAt\";\n\n await this.withServerContext(async (db) => db\n .update(this.usersTable)\n .set({\n [passwordHashColKey]: passwordHash,\n [updatedAtColKey]: new Date()\n })\n .where(eq(idCol, id)));\n }\n\n /**\n * Set email verification status\n */\n async setEmailVerified(id: string, verified: boolean): Promise<void> {\n const idCol = getColumn(this.usersTable, \"id\");\n if (!idCol) return;\n const emailVerifiedColKey = getColumnKey(this.usersTable, \"emailVerified\", \"email_verified\") || \"emailVerified\";\n const emailVerificationTokenColKey = getColumnKey(this.usersTable, \"emailVerificationToken\", \"email_verification_token\") || \"emailVerificationToken\";\n const updatedAtColKey = getColumnKey(this.usersTable, \"updatedAt\", \"updated_at\") || \"updatedAt\";\n\n await this.withServerContext(async (db) => db\n .update(this.usersTable)\n .set({\n [emailVerifiedColKey]: verified,\n [emailVerificationTokenColKey]: null,\n [updatedAtColKey]: new Date()\n })\n .where(eq(idCol, id)));\n }\n\n /**\n * Set email verification token\n */\n async setVerificationToken(id: string, token: string | null): Promise<void> {\n const idCol = getColumn(this.usersTable, \"id\");\n if (!idCol) return;\n const emailVerificationTokenColKey = getColumnKey(this.usersTable, \"emailVerificationToken\", \"email_verification_token\") || \"emailVerificationToken\";\n const emailVerificationSentAtColKey = getColumnKey(this.usersTable, \"emailVerificationSentAt\", \"email_verification_sent_at\") || \"emailVerificationSentAt\";\n const updatedAtColKey = getColumnKey(this.usersTable, \"updatedAt\", \"updated_at\") || \"updatedAt\";\n\n await this.withServerContext(async (db) => db\n .update(this.usersTable)\n .set({\n [emailVerificationTokenColKey]: token,\n [emailVerificationSentAtColKey]: token ? new Date() : null,\n [updatedAtColKey]: new Date()\n })\n .where(eq(idCol, id)));\n }\n\n /**\n * Find user by email verification token\n */\n async getUserByVerificationToken(token: string): Promise<UserData | null> {\n const tokenCol = getColumn(this.usersTable, \"emailVerificationToken\", \"email_verification_token\");\n if (!tokenCol) return null;\n const [row] = await this.db\n .select()\n .from(this.usersTable)\n .where(eq(tokenCol, token));\n return row ? this.mapRowToUser(row as Record<string, unknown>) : null;\n }\n\n /**\n * Get role IDs for a user\n */\n async getUserRoleIds(uid: string): Promise<string[]> {\n const usersTableName = this.getQualifiedUsersTableName();\n const result = await this.db.execute(sql`\n SELECT roles FROM ${sql.raw(usersTableName)} WHERE id = ${uid}\n `);\n\n if (result.rows.length === 0) return [];\n\n const row = result.rows[0] as { roles: string[] | null };\n return row.roles ?? [];\n }\n\n /**\n * Set roles for a user (replaces existing roles)\n */\n async setUserRoles(uid: string, roleIds: string[]): Promise<void> {\n const usersTableName = this.getQualifiedUsersTableName();\n const rolesArray = `{${roleIds.join(\",\")}}`;\n await this.withServerContext(async (db) => db.execute(sql`\n UPDATE ${sql.raw(usersTableName)}\n SET roles = ${rolesArray}::text[], updated_at = NOW()\n WHERE id = ${uid}\n `));\n }\n\n /**\n * Assign a specific role to new user (appends if not present)\n */\n async assignDefaultRole(uid: string, roleId: string): Promise<void> {\n const usersTableName = this.getQualifiedUsersTableName();\n await this.withServerContext(async (db) => db.execute(sql`\n UPDATE ${sql.raw(usersTableName)}\n SET roles = array_append(roles, ${roleId}), updated_at = NOW()\n WHERE id = ${uid} AND NOT (${roleId} = ANY(roles))\n `));\n }\n\n /**\n * One read of what an access token is judged against: whether the account\n * exists, its roles, and its revocation watermark. See\n * `TokenRepository.getAccountAccessState`.\n *\n * The watermark is read only where the table declares it, as\n * `RefreshTokenService.getTokensValidAfter` reads it — a table without the\n * column has no watermark rather than an error on every request.\n */\n async getAccountAccessState(uid: string, sessionId?: string): Promise<AccountAccessState | null> {\n const usersTableName = this.getQualifiedUsersTableName();\n const watermark = getColumnKey(this.usersTable, \"tokensValidAfter\", \"tokens_valid_after\")\n ? sql`u.tokens_valid_after`\n : sql`NULL::timestamptz`;\n // A session is live while one of its refresh tokens is not revoked:\n // logout and `DELETE /auth/sessions/:id` revoke every row of it, and\n // signing out everywhere deletes them. Asked only for an id shaped like\n // the ones sign-in mints, so a `uuid` column is never handed text it\n // cannot cast, and only where the table groups tokens by session.\n const tokens = this.refreshTokensTable;\n const canAskSession = Boolean(sessionId)\n && UUID_SHAPE.test(sessionId ?? \"\")\n && Boolean(getColumnKey(tokens, \"sessionId\", \"session_id\"));\n const revokedClause = getColumnKey(tokens, \"revoked\")\n ? sql`AND rt.revoked IS NOT TRUE`\n : sql``;\n const sessionActive = canAskSession\n ? sql`EXISTS (\n SELECT 1 FROM ${sql.raw(qualifiedTableName(tokens))} rt\n WHERE rt.uid = u.id AND rt.session_id = ${sessionId} ${revokedClause}\n )`\n : sql`NULL::boolean`;\n const disabled = await this.hasDisabledColumn()\n ? sql`u.disabled_at IS NOT NULL`\n : sql`FALSE`;\n const result = await this.db.execute(sql`\n SELECT u.roles, ${watermark} AS tokens_valid_after, ${sessionActive} AS session_active, ${disabled} AS disabled\n FROM ${sql.raw(usersTableName)} u WHERE u.id = ${uid}\n `);\n if (result.rows.length === 0) return null;\n const row = result.rows[0] as { roles: string[] | null; tokens_valid_after: Date | string | null; session_active: boolean | null; disabled: boolean };\n return {\n roles: row.roles ?? [],\n tokensValidAfter: row.tokens_valid_after ? new Date(row.tokens_valid_after) : null,\n ...(row.session_active === null ? {} : { sessionActive: row.session_active }),\n ...(row.disabled ? { disabled: true } : {})\n };\n }\n\n /**\n * Whether the users table has `disabled_at`. `ensureAuthTablesExist` adds\n * it to every auth table at boot, but the app's own drizzle object may\n * predate it, so the database is asked — once — rather than the object.\n */\n private disabledColumn?: Promise<boolean>;\n private hasDisabledColumn(): Promise<boolean> {\n this.disabledColumn ??= (async () => {\n const name = getTableName(this.usersTable);\n const schema = getTableConfig(this.usersTable).schema || \"public\";\n const result = await this.db.execute(sql`\n SELECT 1 FROM information_schema.columns\n WHERE table_schema = ${schema} AND table_name = ${name} AND column_name = 'disabled_at'\n `);\n return result.rows.length > 0;\n })().catch(() => {\n this.disabledColumn = undefined;\n return false;\n });\n return this.disabledColumn;\n }\n\n /**\n * Whether the users table has the address-change columns. Added at boot to\n * every auth table, but the app's drizzle object never declares them, so\n * the database is asked — once — as for `disabled_at`.\n */\n private emailChangeColumns?: Promise<boolean>;\n private hasEmailChangeColumns(): Promise<boolean> {\n this.emailChangeColumns ??= (async () => {\n const name = getTableName(this.usersTable);\n const schema = getTableConfig(this.usersTable).schema || \"public\";\n const result = await this.db.execute(sql`\n SELECT count(*)::int AS present FROM information_schema.columns\n WHERE table_schema = ${schema} AND table_name = ${name}\n AND column_name IN ('pending_email', 'email_change_token', 'email_change_sent_at')\n `);\n return (result.rows[0] as { present: number }).present === 3;\n })().catch(() => {\n this.emailChangeColumns = undefined;\n return false;\n });\n return this.emailChangeColumns;\n }\n\n private async requireEmailChangeColumns(): Promise<void> {\n if (!(await this.hasEmailChangeColumns())) {\n throw new ApiError(501, \"NOT_SUPPORTED\", \"The users table has no pending_email column; restart the server so it is added.\");\n }\n }\n\n /** @see UserRepository.setPendingEmailChange */\n async setPendingEmailChange(uid: string, change: { email: string; tokenHash: string } | null): Promise<void> {\n await this.requireEmailChangeColumns();\n const usersTableName = this.getQualifiedUsersTableName();\n await this.withServerContext(async (db) => db.execute(change\n ? sql`\n UPDATE ${sql.raw(usersTableName)}\n SET pending_email = ${normalizeEmail(change.email)}, email_change_token = ${change.tokenHash},\n email_change_sent_at = NOW(), updated_at = NOW()\n WHERE id = ${uid}`\n : sql`\n UPDATE ${sql.raw(usersTableName)}\n SET pending_email = NULL, email_change_token = NULL, email_change_sent_at = NULL, updated_at = NOW()\n WHERE id = ${uid}`));\n }\n\n /** @see UserRepository.getPendingEmailChange */\n async getPendingEmailChange(uid: string): Promise<PendingEmailChange | null> {\n if (!(await this.hasEmailChangeColumns())) return null;\n const usersTableName = this.getQualifiedUsersTableName();\n const result = await this.db.execute(sql`\n SELECT pending_email, email_change_sent_at FROM ${sql.raw(usersTableName)}\n WHERE id = ${uid} AND pending_email IS NOT NULL AND email_change_sent_at IS NOT NULL\n `);\n const row = result.rows[0] as { pending_email: string; email_change_sent_at: Date | string } | undefined;\n return row ? { email: row.pending_email, sentAt: new Date(row.email_change_sent_at) } : null;\n }\n\n /** @see UserRepository.findPendingEmailChange */\n async findPendingEmailChange(tokenHash: string): Promise<{ user: UserData; change: PendingEmailChange } | null> {\n if (!(await this.hasEmailChangeColumns())) return null;\n const usersTableName = this.getQualifiedUsersTableName();\n const result = await this.db.execute(sql`\n SELECT * FROM ${sql.raw(usersTableName)}\n WHERE email_change_token = ${tokenHash} AND pending_email IS NOT NULL AND email_change_sent_at IS NOT NULL\n `);\n const row = result.rows[0] as Record<string, unknown> | undefined;\n if (!row) return null;\n return {\n user: this.mapRowToUser(row),\n change: { email: row.pending_email as string, sentAt: new Date(row.email_change_sent_at as Date | string) }\n };\n }\n\n /** @see UserRepository.applyPendingEmailChange — one write, held to the token. */\n async applyPendingEmailChange(uid: string, tokenHash: string): Promise<UserData | null> {\n await this.requireEmailChangeColumns();\n const usersTableName = this.getQualifiedUsersTableName();\n try {\n const result = await this.withServerContext(async (db) => db.execute(sql`\n UPDATE ${sql.raw(usersTableName)}\n SET email = pending_email, email_verified = TRUE,\n email_verification_token = NULL, email_verification_sent_at = NULL,\n pending_email = NULL, email_change_token = NULL, email_change_sent_at = NULL,\n updated_at = NOW()\n WHERE id = ${uid} AND email_change_token = ${tokenHash} AND pending_email IS NOT NULL\n RETURNING *\n `));\n const row = result.rows[0] as Record<string, unknown> | undefined;\n return row ? this.mapRowToUser(row) : null;\n } catch (error) {\n // The address was taken while the link waited: the unique index\n // on it says so, and the change does not happen.\n if (extractPgError(error)?.code === \"23505\") {\n throw ApiError.conflict(\"Email already registered\", \"EMAIL_EXISTS\");\n }\n throw error;\n }\n }\n\n /** @see UserRepository.setUserDisabled */\n async setUserDisabled(uid: string, disabled: boolean): Promise<void> {\n if (!(await this.hasDisabledColumn())) {\n throw new ApiError(501, \"NOT_SUPPORTED\", \"The users table has no disabled_at column; restart the server so it is added.\");\n }\n const usersTableName = this.getQualifiedUsersTableName();\n await this.withServerContext(async (db) => db.execute(sql`\n UPDATE ${sql.raw(usersTableName)}\n SET disabled_at = ${disabled ? sql`NOW()` : sql`NULL`}, updated_at = NOW()\n WHERE id = ${uid}\n `));\n }\n\n /**\n * Get user with their roles\n */\n async getUserWithRoles(uid: string): Promise<{ user: UserData; roles: string[] } | null> {\n const user = await this.getUserById(uid);\n if (!user) return null;\n\n const roles = await this.getUserRoleIds(uid);\n return { user,\n roles };\n }\n}\n\n\nexport class RefreshTokenService {\n private refreshTokensTable: RebasePgTable;\n private usersTable: RebasePgTable | null;\n\n constructor(\n private db: NodePgDatabase,\n tableOrTables?: RebasePgTable | Partial<AuthSchemaTables>\n ) {\n if (tableOrTables && ((tableOrTables as Partial<AuthSchemaTables>).refreshTokens || (tableOrTables as Partial<AuthSchemaTables>).users)) {\n this.refreshTokensTable = ((tableOrTables as Partial<AuthSchemaTables>).refreshTokens || refreshTokens) as RebasePgTable;\n this.usersTable = ((tableOrTables as Partial<AuthSchemaTables>).users || users) as RebasePgTable;\n } else {\n this.refreshTokensTable = (tableOrTables as RebasePgTable) || asRebasePgTable(refreshTokens);\n this.usersTable = asRebasePgTable(users);\n }\n }\n\n /**\n * Whether the table actually carries a column, so a host application that\n * supplied its own `refresh_tokens` table — one that predates session\n * grouping — degrades instead of throwing on every sign-in.\n */\n private has(column: string): boolean {\n return Boolean((this.refreshTokensTable as Record<string, unknown>)[column]);\n }\n\n private col(column: string) {\n return this.refreshTokensTable[column];\n }\n\n /** The columns to read back, narrowed to the ones this table has. */\n private selection() {\n // `AnyPgColumn`, not `Record<string, never>`. `never` is assignable to\n // everything, so the old type made this object fit whatever `.select()`\n // wanted while checking nothing about what was in it — including that\n // these are columns at all. `RebasePgTable` already indexes to\n // `AnyPgColumn`, so this needs no conversion either way.\n const selection: Record<string, AnyPgColumn> = {\n id: this.refreshTokensTable.id,\n uid: this.refreshTokensTable.uid,\n tokenHash: this.refreshTokensTable.tokenHash,\n expiresAt: this.refreshTokensTable.expiresAt,\n createdAt: this.refreshTokensTable.createdAt,\n userAgent: this.refreshTokensTable.userAgent,\n ipAddress: this.refreshTokensTable.ipAddress\n };\n for (const optional of [\"sessionId\", \"rotatedAt\", \"revoked\", \"sessionStartedAt\", \"aal\", \"method\"]) {\n if (this.has(optional)) selection[optional] = this.col(optional);\n }\n return selection;\n }\n\n async createToken(\n uid: string,\n tokenHash: string,\n expiresAt: Date,\n userAgent?: string,\n ipAddress?: string,\n session?: RefreshTokenSession\n ): Promise<boolean> {\n // Empty strings rather than NULLs: the device-session UNIQUE constraint\n // that needed them is gone, but sessions-list UIs already render \"\" as\n // \"unknown device\" and would start showing blanks otherwise.\n const safeUserAgent = userAgent || \"\";\n const safeIpAddress = ipAddress || \"\";\n\n // A plain INSERT. Rotation ADDS a token; it does not replace a device's\n // row. Two refreshes racing on the same session therefore both succeed\n // and both end holding a usable token, where the previous upsert had\n // them overwrite each other and logged one of the two tabs out.\n const values: Record<string, unknown> = {\n uid,\n tokenHash,\n expiresAt,\n userAgent: safeUserAgent,\n ipAddress: safeIpAddress\n };\n if (session && this.has(\"sessionId\")) values.sessionId = session.id;\n if (session && this.has(\"sessionStartedAt\")) values.sessionStartedAt = session.startedAt;\n // Written on every token of the session, including the ones rotation\n // mints, because refresh reads the level off whichever row was\n // presented. A table without the column degrades to `aal1` on read,\n // which is the restrictive answer rather than a bypass.\n if (session?.aal && this.has(\"aal\")) values.aal = session.aal;\n // The sign-in method, on every token of the session for the same\n // reason: refresh reads it off whichever row was presented.\n if (session?.method && this.has(\"method\")) values.method = session.method;\n\n // A rotation writes only while the token it replaces is live. Without\n // a `rotatedAt` column `markRotated` deletes that token, so there is\n // no row left to hold the rotation to and the write goes ahead as it\n // always did.\n const parentHash = session?.rotatedFrom && this.has(\"rotatedAt\") ? session.rotatedFrom : undefined;\n if (!parentHash) {\n await this.db.insert(this.refreshTokensTable).values(values);\n return true;\n }\n return await this.db.transaction(async (tx) => {\n // Share-locked until the insert commits. A sign-out that got to\n // the row first has revoked or deleted it by the time this lock is\n // granted, so the read finds nothing; one that comes after waits\n // for this transaction — see `revokeSession`.\n const live = this.has(\"revoked\")\n ? sql`${this.refreshTokensTable.tokenHash} = ${parentHash} AND ${this.col(\"revoked\")} IS NOT TRUE`\n : eq(this.refreshTokensTable.tokenHash, parentHash);\n const [parent] = await tx\n .select({ id: this.refreshTokensTable.id })\n .from(this.refreshTokensTable)\n .where(live)\n .for(\"share\");\n if (!parent) return false;\n await tx.insert(this.refreshTokensTable).values(values);\n return true;\n });\n }\n\n /**\n * Build a {@link RefreshTokenInfo} out of a row of {@link selection}.\n *\n * `selection()` is assembled at runtime from the columns this table actually\n * has, so drizzle types the row as an open record and nothing static\n * connects it to the interface. Both call sites used to assert the\n * connection — `row as unknown as RefreshTokenInfo` — which claims every\n * optional field is present and correctly typed on a row where, by\n * construction, some of them were never selected.\n *\n * Constructing it says the true thing instead: the five required fields come\n * from columns `selection()` always includes, and each optional one is\n * copied only when the row carries it. A host application's own\n * `refresh_tokens` table predating session grouping is the case this whole\n * class is shaped around, and it is exactly the case the assertion\n * misdescribed.\n */\n private static toInfo(row: Record<string, unknown>): RefreshTokenInfo {\n // node-postgres returns `timestamptz` as a Date; a custom table mapped\n // through a different driver can hand back the ISO string instead.\n const asDate = (value: unknown): Date => value instanceof Date ? value : new Date(String(value));\n const info: RefreshTokenInfo = {\n // Copied as the driver returned them, not coerced. `String(...)`\n // here would turn a missing column into the *string* `\"undefined\"`,\n // which then travels into comparisons and logs looking like a value;\n // and rejecting a row whose `id` came back as a number would log out\n // every user of a host table that declares it `serial`. Which type a\n // column has is the database's business, and these five are always\n // in `selection()`, so absence is not a case that arises.\n id: row.id as string,\n uid: row.uid as string,\n tokenHash: row.tokenHash as string,\n expiresAt: asDate(row.expiresAt),\n createdAt: asDate(row.createdAt)\n };\n if (row.userAgent !== undefined) info.userAgent = row.userAgent as string | null;\n if (row.ipAddress !== undefined) info.ipAddress = row.ipAddress as string | null;\n if (row.sessionId !== undefined && row.sessionId !== null) info.sessionId = String(row.sessionId);\n if (row.rotatedAt !== undefined) info.rotatedAt = row.rotatedAt === null ? null : asDate(row.rotatedAt);\n if (row.revoked !== undefined) info.revoked = Boolean(row.revoked);\n if (row.sessionStartedAt !== undefined && row.sessionStartedAt !== null) {\n info.sessionStartedAt = asDate(row.sessionStartedAt);\n }\n // Checked rather than asserted, because this one decides an assurance\n // level. The column is a plain text column on a table the host may own,\n // so a value that is neither label is reachable — and asserting the\n // union would have carried it through as if it were one. Anything that\n // is not exactly \"aal2\" is left unset, which the reader treats as\n // `aal1`: the restrictive answer, as the interface documents.\n if (row.aal === \"aal1\" || row.aal === \"aal2\") info.aal = row.aal;\n // A label, not a grant: any non-empty text is carried as it is.\n if (typeof row.method === \"string\" && row.method.length > 0) info.method = row.method;\n return info;\n }\n\n async findByHash(tokenHash: string): Promise<RefreshTokenInfo | null> {\n const [token] = await this.db\n .select(this.selection())\n .from(this.refreshTokensTable)\n .where(eq(this.refreshTokensTable.tokenHash, tokenHash));\n\n return token ? RefreshTokenService.toInfo(token) : null;\n }\n\n /**\n * Record that a token was rotated away, keeping the row.\n *\n * The row is what lets `/auth/refresh` distinguish \"you already used this,\n * here is a fresh one\" from \"no idea what this is\". Deleting it — which is\n * what this used to do — collapsed both into a 401 and signed the user out\n * for the crime of losing a response.\n */\n async markRotated(tokenHash: string): Promise<void> {\n if (!this.has(\"rotatedAt\")) {\n await this.deleteByHash(tokenHash);\n return;\n }\n await this.db\n .update(this.refreshTokensTable)\n .set({ rotatedAt: new Date() })\n .where(eq(this.refreshTokensTable.tokenHash, tokenHash));\n }\n\n /**\n * Final kill of one sign-in: logout, or revoking a device remotely.\n *\n * Lock first, write second, in one transaction. A rotation in flight\n * holds a share lock on its parent — a row of this session — until the\n * token it mints is committed (see `createToken`), so the lock here waits\n * for it, and the write, being a new statement, reads a snapshot that has\n * that token in it. A lone UPDATE would work from a snapshot taken before\n * the token existed and leave it live: the sign-out a refresh in flight\n * used to undo.\n */\n async revokeSession(sessionId: string): Promise<void> {\n if (!this.has(\"sessionId\")) return;\n const inSession = eq(this.col(\"sessionId\"), sessionId);\n await this.db.transaction(async (tx) => {\n await tx\n .select({ id: this.refreshTokensTable.id })\n .from(this.refreshTokensTable)\n .where(inSession)\n .for(\"update\");\n if (this.has(\"revoked\")) {\n await tx\n .update(this.refreshTokensTable)\n .set({ revoked: true, ...(this.has(\"rotatedAt\") ? { rotatedAt: new Date() } : {}) })\n .where(inSession);\n return;\n }\n await tx.delete(this.refreshTokensTable).where(inSession);\n });\n }\n\n /**\n * Housekeeping: rotation would otherwise leave a row per refresh forever.\n * Superseded rows are only needed for as long as a straggler might still\n * present them, and expired ones are dead weight everywhere.\n */\n async prune(uid: string, sessionId: string, supersededBefore: Date): Promise<void> {\n const uidCol = this.refreshTokensTable.uid;\n const expiresCol = this.refreshTokensTable.expiresAt;\n if (!this.has(\"rotatedAt\") || !this.has(\"sessionId\")) {\n await this.db.delete(this.refreshTokensTable)\n .where(sql`${uidCol} = ${uid} AND ${expiresCol} < NOW()`);\n return;\n }\n const rotatedCol = this.col(\"rotatedAt\");\n const sessionCol = this.col(\"sessionId\");\n await this.db.delete(this.refreshTokensTable).where(sql`\n ${uidCol} = ${uid}\n AND (\n ${expiresCol} < NOW()\n OR (\n ${sessionCol} = ${sessionId}\n AND ${rotatedCol} IS NOT NULL\n AND ${rotatedCol} < ${supersededBefore}\n )\n )\n `);\n }\n\n async getTokensValidAfter(uid: string): Promise<Date | null> {\n if (!this.usersTable || !(this.usersTable as Record<string, unknown>).tokensValidAfter) return null;\n const [row] = await this.db\n .select({ tokensValidAfter: this.usersTable.tokensValidAfter })\n .from(this.usersTable)\n .where(eq(this.usersTable.id, uid));\n const value = (row as { tokensValidAfter?: Date | string | null } | undefined)?.tokensValidAfter;\n return value ? new Date(value) : null;\n }\n\n async setTokensValidAfter(uid: string, at: Date): Promise<void> {\n if (!this.usersTable || !(this.usersTable as Record<string, unknown>).tokensValidAfter) return;\n await this.db\n .update(this.usersTable)\n .set({ tokensValidAfter: at })\n .where(eq(this.usersTable.id, uid));\n }\n\n async deleteByHash(tokenHash: string): Promise<void> {\n await this.db.delete(this.refreshTokensTable).where(eq(this.refreshTokensTable.tokenHash, tokenHash));\n }\n\n async deleteAllForUser(uid: string): Promise<void> {\n await this.db.delete(this.refreshTokensTable).where(eq(this.refreshTokensTable.uid, uid));\n }\n\n async listForUser(uid: string): Promise<RefreshTokenInfo[]> {\n const tokens = await this.db\n .select(this.selection())\n .from(this.refreshTokensTable)\n .where(eq(this.refreshTokensTable.uid, uid))\n .orderBy(this.refreshTokensTable.createdAt);\n\n return tokens.map(RefreshTokenService.toInfo);\n }\n\n async deleteById(id: string, uid: string): Promise<void> {\n await this.db.delete(this.refreshTokensTable)\n .where(sql`${this.refreshTokensTable.id} = ${id} AND ${this.refreshTokensTable.uid} = ${uid}`);\n }\n\n /**\n * Every token past its expiry, whoever holds it. `prune` clears a user's\n * expired rows when that user refreshes; a session nobody comes back to\n * is only ever cleared here.\n */\n async deleteExpired(): Promise<void> {\n await this.db.delete(this.refreshTokensTable)\n .where(sql`${this.refreshTokensTable.expiresAt} < NOW()`);\n }\n}\n\n/**\n * Password reset token service\n */\nexport class PasswordResetTokenService {\n private passwordResetTokensTable: RebasePgTable;\n\n constructor(\n private db: NodePgDatabase,\n tableOrTables?: RebasePgTable | Partial<AuthSchemaTables>\n ) {\n if (tableOrTables && ((tableOrTables as Partial<AuthSchemaTables>).passwordResetTokens || (tableOrTables as Partial<AuthSchemaTables>).users)) {\n this.passwordResetTokensTable = ((tableOrTables as Partial<AuthSchemaTables>).passwordResetTokens || passwordResetTokens) as RebasePgTable;\n } else {\n this.passwordResetTokensTable = (tableOrTables as RebasePgTable) || asRebasePgTable(passwordResetTokens);\n }\n }\n\n private getQualifiedPasswordResetTokensTableName(): string {\n const name = getTableName(this.passwordResetTokensTable);\n const schema = getTableConfig(this.passwordResetTokensTable).schema || \"public\";\n return `\"${schema}\".\"${name}\"`;\n }\n\n /**\n * Create a password reset token\n */\n async createToken(uid: string, tokenHash: string, expiresAt: Date): Promise<void> {\n // Delete any existing unused tokens for this user\n const tableName = this.getQualifiedPasswordResetTokensTableName();\n await this.db.execute(sql`\n DELETE FROM ${sql.raw(tableName)} \n WHERE uid = ${uid} AND used_at IS NULL\n `);\n\n await this.db.insert(this.passwordResetTokensTable).values({\n uid,\n tokenHash,\n expiresAt\n });\n }\n\n /**\n * Find a valid (not expired, not used) token by hash\n */\n async findValidByHash(tokenHash: string): Promise<{ uid: string; expiresAt: Date } | null> {\n const [token] = await this.db\n .select({\n uid: this.passwordResetTokensTable.uid,\n expiresAt: this.passwordResetTokensTable.expiresAt\n })\n .from(this.passwordResetTokensTable)\n .where(eq(this.passwordResetTokensTable.tokenHash, tokenHash)) as Array<{ uid: string; expiresAt: Date }>;\n\n if (!token) return null;\n\n // Check if expired or used\n const tableName = this.getQualifiedPasswordResetTokensTableName();\n const result = await this.db.execute(sql`\n SELECT uid, expires_at \n FROM ${sql.raw(tableName)} \n WHERE token_hash = ${tokenHash} \n AND used_at IS NULL \n AND expires_at > NOW()\n `);\n\n if (result.rows.length === 0) return null;\n\n const row = result.rows[0] as { uid: string; expires_at: string | number | Date };\n return {\n uid: row.uid,\n expiresAt: new Date(row.expires_at)\n };\n }\n\n /**\n * Mark token as used\n */\n async markAsUsed(tokenHash: string): Promise<void> {\n await this.db\n .update(this.passwordResetTokensTable)\n .set({ usedAt: new Date() })\n .where(eq(this.passwordResetTokensTable.tokenHash, tokenHash));\n }\n\n /**\n * Delete all tokens for a user\n */\n async deleteAllForUser(uid: string): Promise<void> {\n await this.db.delete(this.passwordResetTokensTable).where(eq(this.passwordResetTokensTable.uid, uid));\n }\n\n /**\n * Clean up expired tokens\n */\n async deleteExpired(): Promise<void> {\n const tableName = this.getQualifiedPasswordResetTokensTableName();\n await this.db.execute(sql`\n DELETE FROM ${sql.raw(tableName)} \n WHERE expires_at < NOW()\n `);\n }\n}\n\n/**\n * Magic link token service.\n * Handles magic link token storage for passwordless email login.\n */\nexport class MagicLinkTokenService {\n private magicLinkTokensTable: RebasePgTable;\n\n constructor(\n private db: NodePgDatabase,\n tableOrTables?: RebasePgTable | Partial<AuthSchemaTables>\n ) {\n this.magicLinkTokensTable = asRebasePgTable(magicLinkTokens);\n }\n\n private getQualifiedTableName(): string {\n const name = getTableName(this.magicLinkTokensTable);\n const schema = getTableConfig(this.magicLinkTokensTable).schema || \"public\";\n return `\"${schema}\".\"${name}\"`;\n }\n\n async createToken(uid: string, tokenHash: string, expiresAt: Date): Promise<void> {\n // Delete any existing unused tokens for this user\n const tableName = this.getQualifiedTableName();\n await this.db.execute(sql`\n DELETE FROM ${sql.raw(tableName)} \n WHERE uid = ${uid} AND used_at IS NULL\n `);\n\n await this.db.insert(this.magicLinkTokensTable).values({\n uid,\n tokenHash,\n expiresAt\n });\n }\n\n async findValidByHash(tokenHash: string): Promise<MagicLinkTokenInfo | null> {\n const tableName = this.getQualifiedTableName();\n const result = await this.db.execute(sql`\n SELECT uid, expires_at \n FROM ${sql.raw(tableName)} \n WHERE token_hash = ${tokenHash} \n AND used_at IS NULL \n AND expires_at > NOW()\n `);\n\n if (result.rows.length === 0) return null;\n\n const row = result.rows[0] as { uid: string; expires_at: string | number | Date };\n return {\n uid: row.uid,\n expiresAt: new Date(row.expires_at)\n };\n }\n\n /** Links and email codes past their expiry, used or not. */\n async deleteExpired(): Promise<void> {\n const tableName = this.getQualifiedTableName();\n await this.db.execute(sql`\n DELETE FROM ${sql.raw(tableName)}\n WHERE expires_at < NOW()\n `);\n }\n\n async markAsUsed(tokenHash: string): Promise<void> {\n await this.db\n .update(this.magicLinkTokensTable)\n .set({ usedAt: new Date() })\n .where(eq(this.magicLinkTokensTable.tokenHash, tokenHash));\n }\n}\n\n/**\n * PostgreSQL implementation of TokenRepository.\n * Combines refresh token and password reset token operations.\n */\nexport class PostgresTokenRepository implements TokenRepository {\n private refreshTokenService: RefreshTokenService;\n private passwordResetTokenService: PasswordResetTokenService;\n private magicLinkTokenService: MagicLinkTokenService;\n\n constructor(\n private db: NodePgDatabase,\n tableOrTables?: RebasePgTable | Partial<AuthSchemaTables>\n ) {\n this.refreshTokenService = new RefreshTokenService(db, tableOrTables);\n this.passwordResetTokenService = new PasswordResetTokenService(db, tableOrTables);\n this.magicLinkTokenService = new MagicLinkTokenService(db, tableOrTables);\n }\n\n // Refresh token operations\n\n async createRefreshToken(uid: string, tokenHash: string, expiresAt: Date, userAgent?: string, ipAddress?: string, session?: RefreshTokenSession): Promise<boolean> {\n return this.refreshTokenService.createToken(uid, tokenHash, expiresAt, userAgent, ipAddress, session);\n }\n\n async markRefreshTokenRotated(tokenHash: string): Promise<void> {\n await this.refreshTokenService.markRotated(tokenHash);\n }\n\n async revokeRefreshTokenSession(sessionId: string): Promise<void> {\n await this.refreshTokenService.revokeSession(sessionId);\n }\n\n async pruneRefreshTokens(uid: string, sessionId: string, supersededBefore: Date): Promise<void> {\n await this.refreshTokenService.prune(uid, sessionId, supersededBefore);\n }\n\n async getTokensValidAfter(uid: string): Promise<Date | null> {\n return this.refreshTokenService.getTokensValidAfter(uid);\n }\n\n async setTokensValidAfter(uid: string, at: Date): Promise<void> {\n await this.refreshTokenService.setTokensValidAfter(uid, at);\n }\n\n async findRefreshTokenByHash(tokenHash: string): Promise<RefreshTokenInfo | null> {\n return this.refreshTokenService.findByHash(tokenHash);\n }\n\n async deleteRefreshToken(tokenHash: string): Promise<void> {\n await this.refreshTokenService.deleteByHash(tokenHash);\n }\n\n async deleteAllRefreshTokensForUser(uid: string): Promise<void> {\n await this.refreshTokenService.deleteAllForUser(uid);\n }\n\n async listRefreshTokensForUser(uid: string): Promise<RefreshTokenInfo[]> {\n return this.refreshTokenService.listForUser(uid);\n }\n\n async deleteRefreshTokenById(id: string, uid: string): Promise<void> {\n await this.refreshTokenService.deleteById(id, uid);\n }\n\n // Password reset token operations\n\n async createPasswordResetToken(uid: string, tokenHash: string, expiresAt: Date): Promise<void> {\n await this.passwordResetTokenService.createToken(uid, tokenHash, expiresAt);\n }\n\n async findValidPasswordResetToken(tokenHash: string): Promise<PasswordResetTokenInfo | null> {\n return this.passwordResetTokenService.findValidByHash(tokenHash);\n }\n\n async markPasswordResetTokenUsed(tokenHash: string): Promise<void> {\n await this.passwordResetTokenService.markAsUsed(tokenHash);\n }\n\n async deleteAllPasswordResetTokensForUser(uid: string): Promise<void> {\n await this.passwordResetTokenService.deleteAllForUser(uid);\n }\n\n /**\n * @see TokenRepository.deleteExpiredTokens — reset links, magic links and\n * email codes, and refresh tokens, each past its own expiry.\n */\n async deleteExpiredTokens(): Promise<void> {\n await this.passwordResetTokenService.deleteExpired();\n await this.magicLinkTokenService.deleteExpired();\n await this.refreshTokenService.deleteExpired();\n }\n\n // Magic link token operations\n\n async createMagicLinkToken(uid: string, tokenHash: string, expiresAt: Date): Promise<void> {\n await this.magicLinkTokenService.createToken(uid, tokenHash, expiresAt);\n }\n\n async findValidMagicLinkToken(tokenHash: string): Promise<MagicLinkTokenInfo | null> {\n return this.magicLinkTokenService.findValidByHash(tokenHash);\n }\n\n async markMagicLinkTokenUsed(tokenHash: string): Promise<void> {\n await this.magicLinkTokenService.markAsUsed(tokenHash);\n }\n}\n\n/**\n * PostgreSQL implementation of AuthRepository.\n * Combines user, role, and token repository operations.\n * This provides a convenient single-class interface for all auth operations.\n */\nexport class PostgresAuthRepository implements AuthRepository {\n private userService: UserService;\n private tokenRepository: PostgresTokenRepository;\n\n constructor(\n private db: NodePgDatabase,\n tableOrTables?: RebasePgTable | Partial<AuthSchemaTables>\n ) {\n this.userService = new UserService(db, tableOrTables);\n this.tokenRepository = new PostgresTokenRepository(db, tableOrTables);\n }\n\n // User operations (delegate to UserService)\n\n async createUser(data: CreateUserData): Promise<UserData> {\n return this.userService.createUser(data);\n }\n\n async getUserById(id: string): Promise<UserData | null> {\n return this.userService.getUserById(id);\n }\n\n async getUserByEmail(email: string): Promise<UserData | null> {\n return this.userService.getUserByEmail(email);\n }\n\n async getUserByIdentity(provider: string, providerId: string): Promise<UserData | null> {\n return this.userService.getUserByIdentity(provider, providerId);\n }\n\n async getUserIdentities(uid: string): Promise<UserIdentityData[]> {\n return this.userService.getUserIdentities(uid);\n }\n\n async linkUserIdentity(uid: string, provider: string, providerId: string, profileData?: Record<string, unknown>): Promise<void> {\n return this.userService.linkUserIdentity(uid, provider, providerId, profileData);\n }\n\n async unlinkUserIdentity(uid: string, provider: string, providerId: string): Promise<void> {\n await this.userService.unlinkUserIdentity(uid, provider, providerId);\n }\n\n async updateUser(id: string, data: Partial<Omit<CreateUserData, \"id\">>): Promise<UserData | null> {\n return this.userService.updateUser(id, data);\n }\n\n async deleteUser(id: string): Promise<void> {\n await this.userService.deleteUser(id);\n }\n\n async listUsers(): Promise<UserData[]> {\n return this.userService.listUsers();\n }\n\n async listUsersPaginated(options?: ListUsersOptions): Promise<PaginatedUsersResult> {\n return this.userService.listUsersPaginated(options);\n }\n\n async updatePassword(id: string, passwordHash: string | null): Promise<void> {\n await this.userService.updatePassword(id, passwordHash);\n }\n\n async setEmailVerified(id: string, verified: boolean): Promise<void> {\n await this.userService.setEmailVerified(id, verified);\n }\n\n async setVerificationToken(id: string, token: string | null): Promise<void> {\n await this.userService.setVerificationToken(id, token);\n }\n\n async getUserByVerificationToken(token: string): Promise<UserData | null> {\n return this.userService.getUserByVerificationToken(token);\n }\n\n async getUserRoleIds(uid: string): Promise<string[]> {\n return this.userService.getUserRoleIds(uid);\n }\n\n async setUserRoles(uid: string, roleIds: string[]): Promise<void> {\n await this.userService.setUserRoles(uid, roleIds);\n }\n\n async assignDefaultRole(uid: string, roleId: string): Promise<void> {\n await this.userService.assignDefaultRole(uid, roleId);\n }\n\n async getUserWithRoles(uid: string): Promise<{ user: UserData; roles: string[] } | null> {\n return this.userService.getUserWithRoles(uid);\n }\n\n async getAccountAccessState(uid: string, sessionId?: string): Promise<AccountAccessState | null> {\n return this.userService.getAccountAccessState(uid, sessionId);\n }\n\n async setUserDisabled(uid: string, disabled: boolean): Promise<void> {\n await this.userService.setUserDisabled(uid, disabled);\n }\n\n async setPendingEmailChange(uid: string, change: { email: string; tokenHash: string } | null): Promise<void> {\n await this.userService.setPendingEmailChange(uid, change);\n }\n\n async getPendingEmailChange(uid: string): Promise<PendingEmailChange | null> {\n return this.userService.getPendingEmailChange(uid);\n }\n\n async findPendingEmailChange(tokenHash: string): Promise<{ user: UserData; change: PendingEmailChange } | null> {\n return this.userService.findPendingEmailChange(tokenHash);\n }\n\n async applyPendingEmailChange(uid: string, tokenHash: string): Promise<UserData | null> {\n return this.userService.applyPendingEmailChange(uid, tokenHash);\n }\n\n // Token operations (delegate to PostgresTokenRepository)\n\n async createRefreshToken(uid: string, tokenHash: string, expiresAt: Date, userAgent?: string, ipAddress?: string, session?: RefreshTokenSession): Promise<boolean> {\n return this.tokenRepository.createRefreshToken(uid, tokenHash, expiresAt, userAgent, ipAddress, session);\n }\n\n async markRefreshTokenRotated(tokenHash: string): Promise<void> {\n await this.tokenRepository.markRefreshTokenRotated(tokenHash);\n }\n\n async revokeRefreshTokenSession(sessionId: string): Promise<void> {\n await this.tokenRepository.revokeRefreshTokenSession(sessionId);\n }\n\n async pruneRefreshTokens(uid: string, sessionId: string, supersededBefore: Date): Promise<void> {\n await this.tokenRepository.pruneRefreshTokens(uid, sessionId, supersededBefore);\n }\n\n async getTokensValidAfter(uid: string): Promise<Date | null> {\n return this.tokenRepository.getTokensValidAfter(uid);\n }\n\n async setTokensValidAfter(uid: string, at: Date): Promise<void> {\n await this.tokenRepository.setTokensValidAfter(uid, at);\n }\n\n async findRefreshTokenByHash(tokenHash: string): Promise<RefreshTokenInfo | null> {\n return this.tokenRepository.findRefreshTokenByHash(tokenHash);\n }\n\n async deleteRefreshToken(tokenHash: string): Promise<void> {\n await this.tokenRepository.deleteRefreshToken(tokenHash);\n }\n\n async deleteAllRefreshTokensForUser(uid: string): Promise<void> {\n await this.tokenRepository.deleteAllRefreshTokensForUser(uid);\n }\n\n async listRefreshTokensForUser(uid: string): Promise<RefreshTokenInfo[]> {\n return this.tokenRepository.listRefreshTokensForUser(uid);\n }\n\n async deleteRefreshTokenById(id: string, uid: string): Promise<void> {\n await this.tokenRepository.deleteRefreshTokenById(id, uid);\n }\n\n async createPasswordResetToken(uid: string, tokenHash: string, expiresAt: Date): Promise<void> {\n await this.tokenRepository.createPasswordResetToken(uid, tokenHash, expiresAt);\n }\n\n async findValidPasswordResetToken(tokenHash: string): Promise<PasswordResetTokenInfo | null> {\n return this.tokenRepository.findValidPasswordResetToken(tokenHash);\n }\n\n async markPasswordResetTokenUsed(tokenHash: string): Promise<void> {\n await this.tokenRepository.markPasswordResetTokenUsed(tokenHash);\n }\n\n async deleteAllPasswordResetTokensForUser(uid: string): Promise<void> {\n await this.tokenRepository.deleteAllPasswordResetTokensForUser(uid);\n }\n\n async deleteExpiredTokens(): Promise<void> {\n await this.tokenRepository.deleteExpiredTokens();\n await this.getMfaService().deleteExpiredChallenges();\n }\n\n // Magic link token operations\n\n async createMagicLinkToken(uid: string, tokenHash: string, expiresAt: Date): Promise<void> {\n await this.tokenRepository.createMagicLinkToken(uid, tokenHash, expiresAt);\n }\n\n async findValidMagicLinkToken(tokenHash: string): Promise<MagicLinkTokenInfo | null> {\n return this.tokenRepository.findValidMagicLinkToken(tokenHash);\n }\n\n async markMagicLinkTokenUsed(tokenHash: string): Promise<void> {\n await this.tokenRepository.markMagicLinkTokenUsed(tokenHash);\n }\n\n // MFA operations (delegate to MfaService)\n\n private _mfaService: MfaService | null = null;\n private getMfaService(): MfaService {\n if (!this._mfaService) {\n this._mfaService = new MfaService(this.db);\n }\n return this._mfaService;\n }\n\n async createMfaFactor(uid: string, factorType: \"totp\", secretEncrypted: string, friendlyName?: string): Promise<MfaFactor> {\n return this.getMfaService().createMfaFactor(uid, factorType, secretEncrypted, friendlyName);\n }\n\n async getMfaFactors(uid: string): Promise<MfaFactor[]> {\n return this.getMfaService().getMfaFactors(uid);\n }\n\n async getMfaFactorById(factorId: string): Promise<(MfaFactor & { secretEncrypted: string }) | null> {\n return this.getMfaService().getMfaFactorById(factorId);\n }\n\n async verifyMfaFactor(factorId: string): Promise<void> {\n return this.getMfaService().verifyMfaFactor(factorId);\n }\n\n async updateMfaFactorSecret(factorId: string, secretEncrypted: string): Promise<void> {\n return this.getMfaService().updateMfaFactorSecret(factorId, secretEncrypted);\n }\n\n async deleteMfaFactor(factorId: string, uid: string): Promise<void> {\n return this.getMfaService().deleteMfaFactor(factorId, uid);\n }\n\n async createMfaChallenge(factorId: string, ipAddress?: string): Promise<MfaChallengeInfo> {\n return this.getMfaService().createMfaChallenge(factorId, ipAddress);\n }\n\n async getMfaChallengeById(challengeId: string): Promise<MfaChallengeInfo | null> {\n return this.getMfaService().getMfaChallengeById(challengeId);\n }\n\n async verifyMfaChallenge(challengeId: string): Promise<void> {\n return this.getMfaService().verifyMfaChallenge(challengeId);\n }\n\n async createRecoveryCodes(uid: string, codeHashes: string[]): Promise<void> {\n return this.getMfaService().createRecoveryCodes(uid, codeHashes);\n }\n\n async useRecoveryCode(uid: string, codeHash: string): Promise<boolean> {\n return this.getMfaService().useRecoveryCode(uid, codeHash);\n }\n\n async getUnusedRecoveryCodeCount(uid: string): Promise<number> {\n return this.getMfaService().getUnusedRecoveryCodeCount(uid);\n }\n\n async deleteAllRecoveryCodes(uid: string): Promise<void> {\n return this.getMfaService().deleteAllRecoveryCodes(uid);\n }\n\n async hasVerifiedMfaFactors(uid: string): Promise<boolean> {\n return this.getMfaService().hasVerifiedMfaFactors(uid);\n }\n\n async claimMfaFactorCounter(factorId: string, counter: number): Promise<boolean> {\n return this.getMfaService().claimMfaFactorCounter(factorId, counter);\n }\n\n async recordMfaChallengeAttempt(challengeId: string): Promise<number> {\n return this.getMfaService().recordMfaChallengeAttempt(challengeId);\n }\n}\n\n// =============================================================================\n// MFA SERVICE\n// =============================================================================\n\n/**\n * PostgreSQL implementation of MfaRepository.\n * Handles all MFA-related database operations.\n */\nexport class MfaService implements MfaRepository {\n constructor(private db: NodePgDatabase, private schemaName = \"rebase\") {}\n\n private qualify(tableName: string): string {\n return `\"${this.schemaName}\".\"${tableName}\"`;\n }\n\n async createMfaFactor(\n uid: string,\n factorType: \"totp\",\n secretEncrypted: string,\n friendlyName?: string\n ): Promise<MfaFactor> {\n const tableName = this.qualify(\"mfa_factors\");\n const result = await this.db.execute(sql`\n INSERT INTO ${sql.raw(tableName)} (uid, factor_type, secret_encrypted, friendly_name)\n VALUES (${uid}, ${factorType}, ${secretEncrypted}, ${friendlyName ?? null})\n RETURNING id, uid, factor_type, friendly_name, verified, created_at, updated_at\n `);\n\n const row = result.rows[0] as Record<string, unknown>;\n return {\n id: row.id as string,\n uid: row.uid as string,\n factorType: row.factor_type as \"totp\",\n friendlyName: (row.friendly_name as string | null) ?? undefined,\n verified: row.verified as boolean,\n createdAt: new Date(row.created_at as string),\n updatedAt: new Date(row.updated_at as string)\n };\n }\n\n async getMfaFactors(uid: string): Promise<MfaFactor[]> {\n const tableName = this.qualify(\"mfa_factors\");\n const result = await this.db.execute(sql`\n SELECT id, uid, factor_type, friendly_name, verified, created_at, updated_at\n FROM ${sql.raw(tableName)}\n WHERE uid = ${uid}\n ORDER BY created_at\n `);\n\n return (result.rows as Array<Record<string, unknown>>).map(row => ({\n id: row.id as string,\n uid: row.uid as string,\n factorType: row.factor_type as \"totp\",\n friendlyName: (row.friendly_name as string | null) ?? undefined,\n verified: row.verified as boolean,\n createdAt: new Date(row.created_at as string),\n updatedAt: new Date(row.updated_at as string)\n }));\n }\n\n async getMfaFactorById(factorId: string): Promise<(MfaFactor & { secretEncrypted: string }) | null> {\n const tableName = this.qualify(\"mfa_factors\");\n const result = await this.db.execute(sql`\n SELECT id, uid, factor_type, secret_encrypted, friendly_name, verified, last_used_counter, created_at, updated_at\n FROM ${sql.raw(tableName)}\n WHERE id = ${factorId}\n `);\n\n if (result.rows.length === 0) return null;\n\n const row = result.rows[0] as Record<string, unknown>;\n return {\n id: row.id as string,\n uid: row.uid as string,\n factorType: row.factor_type as \"totp\",\n secretEncrypted: row.secret_encrypted as string,\n friendlyName: (row.friendly_name as string | null) ?? undefined,\n verified: row.verified as boolean,\n // BIGINT comes back as a string from node-postgres.\n lastUsedCounter: row.last_used_counter === null || row.last_used_counter === undefined\n ? null\n : Number(row.last_used_counter),\n createdAt: new Date(row.created_at as string),\n updatedAt: new Date(row.updated_at as string)\n };\n }\n\n /**\n * Spend a TOTP time step, once and only once.\n *\n * One statement: the `WHERE` is the check, the `UPDATE` is the act, and\n * `RETURNING` reports which of two concurrent requests carrying the same\n * six digits won. Reading the counter and then writing it would let both\n * pass — the exact replay this closes.\n */\n async claimMfaFactorCounter(factorId: string, counter: number): Promise<boolean> {\n const tableName = this.qualify(\"mfa_factors\");\n const result = await this.db.execute(sql`\n UPDATE ${sql.raw(tableName)}\n SET last_used_counter = ${counter}, updated_at = NOW()\n WHERE id = ${factorId}\n AND (last_used_counter IS NULL OR last_used_counter < ${counter})\n RETURNING id\n `);\n\n return result.rows.length > 0;\n }\n\n async verifyMfaFactor(factorId: string): Promise<void> {\n const tableName = this.qualify(\"mfa_factors\");\n await this.db.execute(sql`\n UPDATE ${sql.raw(tableName)}\n SET verified = TRUE, updated_at = NOW()\n WHERE id = ${factorId}\n `);\n }\n\n async updateMfaFactorSecret(factorId: string, secretEncrypted: string): Promise<void> {\n const tableName = this.qualify(\"mfa_factors\");\n await this.db.execute(sql`\n UPDATE ${sql.raw(tableName)}\n SET secret_encrypted = ${secretEncrypted}, updated_at = NOW()\n WHERE id = ${factorId}\n `);\n }\n\n async deleteMfaFactor(factorId: string, uid: string): Promise<void> {\n const tableName = this.qualify(\"mfa_factors\");\n await this.db.execute(sql`\n DELETE FROM ${sql.raw(tableName)}\n WHERE id = ${factorId} AND uid = ${uid}\n `);\n }\n\n async createMfaChallenge(factorId: string, ipAddress?: string): Promise<MfaChallengeInfo> {\n const tableName = this.qualify(\"mfa_challenges\");\n // Challenges expire in 5 minutes\n const expiresAt = new Date(Date.now() + 5 * 60 * 1000);\n const result = await this.db.execute(sql`\n INSERT INTO ${sql.raw(tableName)} (factor_id, ip_address, expires_at)\n VALUES (${factorId}, ${ipAddress ?? null}, ${expiresAt})\n RETURNING id, factor_id, created_at, verified_at, ip_address\n `);\n\n const row = result.rows[0] as Record<string, unknown>;\n return {\n id: row.id as string,\n factorId: row.factor_id as string,\n createdAt: new Date(row.created_at as string),\n verifiedAt: row.verified_at ? new Date(row.verified_at as string) : undefined,\n ipAddress: (row.ip_address as string | null) ?? undefined\n };\n }\n\n async getMfaChallengeById(challengeId: string): Promise<MfaChallengeInfo | null> {\n const tableName = this.qualify(\"mfa_challenges\");\n const result = await this.db.execute(sql`\n SELECT id, factor_id, created_at, verified_at, ip_address, attempts, expires_at\n FROM ${sql.raw(tableName)}\n WHERE id = ${challengeId} AND expires_at > NOW() AND verified_at IS NULL\n `);\n\n if (result.rows.length === 0) return null;\n\n const row = result.rows[0] as Record<string, unknown>;\n return {\n id: row.id as string,\n factorId: row.factor_id as string,\n createdAt: new Date(row.created_at as string),\n verifiedAt: row.verified_at ? new Date(row.verified_at as string) : undefined,\n ipAddress: (row.ip_address as string | null) ?? undefined,\n attempts: Number(row.attempts ?? 0)\n };\n }\n\n /**\n * Count one guess against a challenge and report the new total. The route\n * claims it before judging the code, so the total it returns is what the\n * cap is decided on.\n *\n * Incremented in the database rather than in the route so that guesses\n * arriving in parallel — the shape any real brute-force takes — cannot\n * share a single increment.\n */\n async recordMfaChallengeAttempt(challengeId: string): Promise<number> {\n const tableName = this.qualify(\"mfa_challenges\");\n const result = await this.db.execute(sql`\n UPDATE ${sql.raw(tableName)}\n SET attempts = attempts + 1\n WHERE id = ${challengeId}\n RETURNING attempts\n `);\n\n if (result.rows.length === 0) return 0;\n return Number((result.rows[0] as { attempts: number | string }).attempts);\n }\n\n /** Challenges past their five minutes, answered or not. */\n async deleteExpiredChallenges(): Promise<void> {\n const tableName = this.qualify(\"mfa_challenges\");\n await this.db.execute(sql`\n DELETE FROM ${sql.raw(tableName)}\n WHERE expires_at < NOW()\n `);\n }\n\n async verifyMfaChallenge(challengeId: string): Promise<void> {\n const tableName = this.qualify(\"mfa_challenges\");\n await this.db.execute(sql`\n UPDATE ${sql.raw(tableName)}\n SET verified_at = NOW()\n WHERE id = ${challengeId}\n `);\n }\n\n async createRecoveryCodes(uid: string, codeHashes: string[]): Promise<void> {\n const tableName = this.qualify(\"recovery_codes\");\n // Delete existing codes first\n await this.db.execute(sql`\n DELETE FROM ${sql.raw(tableName)} WHERE uid = ${uid}\n `);\n\n // Insert new codes\n for (const hash of codeHashes) {\n await this.db.execute(sql`\n INSERT INTO ${sql.raw(tableName)} (uid, code_hash)\n VALUES (${uid}, ${hash})\n `);\n }\n }\n\n async useRecoveryCode(uid: string, codeHash: string): Promise<boolean> {\n const tableName = this.qualify(\"recovery_codes\");\n const result = await this.db.execute(sql`\n UPDATE ${sql.raw(tableName)}\n SET used_at = NOW()\n WHERE uid = ${uid} AND code_hash = ${codeHash} AND used_at IS NULL\n RETURNING id\n `);\n\n return result.rows.length > 0;\n }\n\n async getUnusedRecoveryCodeCount(uid: string): Promise<number> {\n const tableName = this.qualify(\"recovery_codes\");\n const result = await this.db.execute(sql`\n SELECT COUNT(*)::int as count FROM ${sql.raw(tableName)}\n WHERE uid = ${uid} AND used_at IS NULL\n `);\n\n return (result.rows[0] as { count: number }).count;\n }\n\n async deleteAllRecoveryCodes(uid: string): Promise<void> {\n const tableName = this.qualify(\"recovery_codes\");\n await this.db.execute(sql`\n DELETE FROM ${sql.raw(tableName)} WHERE uid = ${uid}\n `);\n }\n\n async hasVerifiedMfaFactors(uid: string): Promise<boolean> {\n const tableName = this.qualify(\"mfa_factors\");\n const result = await this.db.execute(sql`\n SELECT COUNT(*)::int as count FROM ${sql.raw(tableName)}\n WHERE uid = ${uid} AND verified = TRUE\n `);\n\n return (result.rows[0] as { count: number }).count > 0;\n }\n}\n\n// =============================================================================\n// PostgreSQL Type Aliases (for consistent naming with other implementations)\n// =============================================================================\n\n/** PostgreSQL user repository implementation */\nexport type PostgresUserRepository = UserService;\n","import { sql } from \"drizzle-orm\";\nimport { NodePgDatabase } from \"drizzle-orm/node-postgres\";\nimport { logger } from \"@rebasepro/server\";\nimport { REBASE_USER_ROLE, revokeInternalTableSql } from \"@rebasepro/common\";\n\n/** How a history entry gets written from inside the write it records. */\nexport const RECORD_HISTORY_FUNCTION = \"rebase.record_history\";\nconst RECORD_HISTORY_SIGNATURE = `${RECORD_HISTORY_FUNCTION}(text, text, text, text[], jsonb, jsonb, text)`;\n\n/**\n * Auto-create the row history table if it doesn't exist.\n * This runs on startup when history is enabled, following the same\n * pattern as `ensureAuthTablesExist`.\n *\n * Resolves `true` when {@link RECORD_HISTORY_FUNCTION} exists, so an entry can\n * be written on the write's own transaction; `false` sends entries through the\n * pool as before, committing on their own.\n */\nexport async function ensureHistoryTableExists(db: NodePgDatabase): Promise<boolean> {\n logger.debug(\"🔍 Checking row history table...\");\n\n try {\n // Create the rebase schema (idempotent — may already exist from auth init)\n await db.execute(sql`CREATE SCHEMA IF NOT EXISTS rebase`);\n\n await db.execute(sql`\n CREATE TABLE IF NOT EXISTS rebase.entity_history (\n id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,\n table_name TEXT NOT NULL,\n entity_id TEXT NOT NULL,\n action TEXT NOT NULL,\n changed_fields TEXT[],\n \"values\" JSONB,\n previous_values JSONB,\n updated_by TEXT,\n updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()\n )\n `);\n\n await db.execute(sql`\n CREATE INDEX IF NOT EXISTS idx_history_entity\n ON rebase.entity_history(table_name, entity_id)\n `);\n\n await db.execute(sql`\n CREATE INDEX IF NOT EXISTS idx_history_time\n ON rebase.entity_history(table_name, entity_id, updated_at DESC)\n `);\n\n // Every previous value of every audited row, in one table with no RLS\n // and no tenant scoping — so a readable copy defeats the row policies on\n // the tables it shadows. The driver's schema-wide grant reaches it\n // (created here, after that grant ran), so take it back.\n await db.execute(sql.raw(revokeInternalTableSql(\"rebase\", \"entity_history\")));\n\n logger.debug(\"✅ Entity history table ready\");\n } catch (error) {\n logger.error(\"❌ Failed to create row history table\", { error: error });\n logger.warn(\"⚠️ Continuing without creating history table.\");\n return false;\n }\n\n // The entry has to commit with the row it describes, so it is written on\n // the write's transaction — which runs as the request role, revoked from\n // this table just above. A `SECURITY DEFINER` function is the one door:\n // it inserts exactly an entry, with the owner's rights, for that single\n // statement, and only the request role may call it. The same reasoning,\n // at more length, is on `rebase.enqueue_job` in the job store.\n try {\n await db.execute(sql.raw(`\n CREATE OR REPLACE FUNCTION ${RECORD_HISTORY_FUNCTION}(\n p_table_name text, p_entity_id text, p_action text, p_changed_fields text[],\n p_values jsonb, p_previous_values jsonb, p_updated_by text\n ) RETURNS void\n LANGUAGE sql\n SECURITY DEFINER\n SET search_path = pg_catalog, pg_temp\n AS $fn$\n INSERT INTO rebase.entity_history\n (table_name, entity_id, action, changed_fields, \"values\", previous_values, updated_by)\n VALUES (p_table_name, p_entity_id, p_action, p_changed_fields, p_values, p_previous_values, p_updated_by)\n $fn$\n `));\n await db.execute(sql.raw(`REVOKE ALL ON FUNCTION ${RECORD_HISTORY_SIGNATURE} FROM PUBLIC`));\n await db.execute(sql.raw(`\n DO $grant$ BEGIN\n IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = '${REBASE_USER_ROLE}') THEN\n GRANT EXECUTE ON FUNCTION ${RECORD_HISTORY_SIGNATURE} TO ${REBASE_USER_ROLE};\n END IF;\n END $grant$\n `));\n return true;\n } catch (error) {\n logger.warn(\n `⚠️ ${RECORD_HISTORY_FUNCTION} could not be created, so a history entry commits on its own: ` +\n \"it survives a write that rolls back.\",\n { error }\n );\n return false;\n }\n}\n","import { sql } from \"drizzle-orm\";\nimport { NodePgDatabase } from \"drizzle-orm/node-postgres\";\nimport { logger } from \"@rebasepro/server\";\nimport type { EntityHistoryEntry } from \"@rebasepro/types\";\n\nexport type {\n RecordHistoryParams,\n FetchHistoryOptions,\n HistoryRetentionConfig\n} from \"@rebasepro/types\";\nimport type { RecordHistoryParams, FetchHistoryOptions, HistoryRetentionConfig } from \"@rebasepro/types\";\nimport { firstSqlRow, sqlRows } from \"@rebasepro/common\";\nimport { currentWriteScope } from \"../services/write-transaction-scope\";\nimport { RECORD_HISTORY_FUNCTION } from \"./ensure-history-table\";\n\n/**\n * A Postgres history row is already the wire shape — `updated_at` comes back\n * from the driver as a string. Kept as an alias because the name is used\n * throughout this package and in `PostgresBackendDriver`.\n */\nexport type HistoryEntry = EntityHistoryEntry;\n\nconst DEFAULT_RETENTION: HistoryRetentionConfig = {\n maxEntries: 200,\n ttlDays: 90\n};\n\n/**\n * Service for recording and querying row change history.\n * Stores history entries in the `rebase.entity_history` table.\n */\nexport class HistoryService {\n public retention: HistoryRetentionConfig;\n\n /**\n * Whether {@link RECORD_HISTORY_FUNCTION} exists, so an entry recorded\n * inside a write goes on that write's transaction. Without it, entries go\n * through the pool and commit on their own.\n */\n private readonly inTransaction: boolean;\n\n constructor(\n private db: NodePgDatabase,\n retention?: Partial<HistoryRetentionConfig>,\n options?: { inTransaction?: boolean }\n ) {\n this.retention = { ...DEFAULT_RETENTION,\n...retention };\n this.inTransaction = options?.inTransaction === true;\n }\n\n /**\n * Record a history entry for a row change.\n *\n * ## Why this is allowed to fail the write\n *\n * It used to swallow every error into a log line, and the driver called it\n * without `await`, so the promise was dropped on the floor. Both halves of\n * that made the same claim: that the audit trail is a nice-to-have. It is\n * not — `history: true` is opted into by collections whose changes somebody\n * has to be able to reconstruct, and a trail with gaps is worse than no\n * trail, because nothing distinguishes \"no change was made\" from \"the entry\n * did not get written\". The gaps were silent and unbounded: a transient\n * error on the history insert lost the entry for a row that committed\n * anyway, and nobody found out.\n *\n * So it throws, and the driver awaits it inside the write's transaction.\n * The row and its history entry commit together or neither does — which\n * needs the entry written *on* that transaction, not merely while it is\n * open. This service is built once, on the pool, and for a long time it\n * wrote there: a batch that rolled back after its first row left that\n * row's entry behind, describing a row that never existed. Inside a write\n * it now goes through {@link RECORD_HISTORY_FUNCTION} on the write's own\n * transaction (found through `currentWriteScope`). That is a\n * real trade — a broken `rebase.entity_history` now fails writes to the\n * collections that declare history, where before it failed quietly — and it\n * is the right side of it for a table that exists to answer \"who changed\n * this\". The table is created at boot (`ensureHistoryTableExists`), so the\n * failure mode is a database that is broken in other ways too.\n *\n * The pruning pass stays non-blocking: a prune that does not run leaves\n * *extra* history, which is not a loss and is fixed by the next write.\n */\n async recordHistory(params: RecordHistoryParams): Promise<void> {\n const {\n tableName,\n id,\n action,\n values,\n previousValues,\n updatedBy\n } = params;\n\n const changedFields = previousValues && values\n ? findChangedFields(previousValues, values)\n : null;\n\n\n // Skip recording if this is an update with zero actual changes\n\n if (action === \"update\" && (!changedFields || changedFields.length === 0)) {\n return;\n }\n\n try {\n // Checked and used with no `await` between, so the transaction\n // cannot close in the gap.\n const scope = this.inTransaction ? currentWriteScope() : undefined;\n if (scope?.tx) {\n await scope.track(scope.tx.execute(sql`\n SELECT ${sql.raw(RECORD_HISTORY_FUNCTION)}(\n ${tableName},\n ${String(id)},\n ${action},\n ${changedFields ? sql`ARRAY[${sql.join(changedFields.map(f => sql`${f}`), sql`, `)}]::text[]` : sql`NULL::text[]`},\n ${values ? sql`${JSON.stringify(values)}::jsonb` : sql`NULL::jsonb`},\n ${previousValues ? sql`${JSON.stringify(previousValues)}::jsonb` : sql`NULL::jsonb`},\n ${updatedBy ?? null}::text\n )\n `));\n } else await this.db.execute(sql`\n INSERT INTO rebase.entity_history\n (table_name, entity_id, action, changed_fields, \"values\", previous_values, updated_by)\n VALUES (\n ${tableName},\n ${String(id)},\n ${action},\n ${changedFields ? sql`ARRAY[${sql.join(changedFields.map(f => sql`${f}`), sql`, `)}]::text[]` : sql`NULL`},\n ${values ? sql`${JSON.stringify(values)}::jsonb` : sql`NULL`},\n ${previousValues ? sql`${JSON.stringify(previousValues)}::jsonb` : sql`NULL`},\n ${updatedBy ?? null}\n )\n `);\n } catch (error) {\n logger.error(\"Failed to record row history\", { error });\n // Rethrown, not swallowed. See the docblock: the entry commits with\n // its row or the write does not happen. Wrapped so the caller reads\n // a sentence naming the audit trail rather than a bare SQLSTATE\n // from a table they did not know they were writing to.\n throw new Error(\n `Could not record the history entry for \"${tableName}\" (${String(id)}). ` +\n \"The write was rolled back: this collection declares `history: true`, and a row \" +\n \"that commits without its audit entry leaves a gap nothing can distinguish from \" +\n \"\\\"no change was made\\\".\",\n { cause: error }\n );\n }\n\n // Non-blocking prune for this specific row. Outside the try, and\n // deliberately not awaited: a prune that fails leaves *extra* history,\n // which is not a loss and is fixed by the next write to this row.\n this.pruneEntity(tableName, id).catch(err =>\n logger.error(\"History prune failed\", { error: err })\n );\n }\n\n /**\n * Fetch history entries for an row, ordered by most recent first.\n */\n async fetchHistory(\n tableName: string,\n id: string,\n options: FetchHistoryOptions = {}\n ): Promise<{ data: HistoryEntry[]; total: number }> {\n const limit = options.limit ?? 20;\n const offset = options.offset ?? 0;\n\n const [countResult, dataResult] = await Promise.all([\n this.db.execute(sql`\n SELECT COUNT(*) as count\n FROM rebase.entity_history\n WHERE table_name = ${tableName}\n AND entity_id = ${String(id)}\n `),\n this.db.execute(sql`\n SELECT id, table_name, entity_id, action, changed_fields,\n \"values\", previous_values, updated_by, updated_at\n FROM rebase.entity_history\n WHERE table_name = ${tableName}\n AND entity_id = ${String(id)}\n ORDER BY updated_at DESC\n LIMIT ${limit}\n OFFSET ${offset}\n `)\n ]);\n\n const total = parseInt(\n (countResult.rows[0] as Record<string, string>)?.count ?? \"0\",\n 10\n );\n\n return {\n data: sqlRows<HistoryEntry>(dataResult),\n total\n };\n }\n\n /**\n * Fetch a single history entry by ID.\n */\n async fetchHistoryEntry(historyId: string): Promise<HistoryEntry | null> {\n const result = await this.db.execute(sql`\n SELECT id, table_name, entity_id, action, changed_fields,\n \"values\", previous_values, updated_by, updated_at\n FROM rebase.entity_history\n WHERE id = ${historyId}\n `);\n\n return firstSqlRow<HistoryEntry>(result) ?? null;\n }\n\n // ───────── Retention / Pruning ─────────\n\n /**\n * Prune history for a single row: enforce maxEntries and TTL.\n */\n async pruneEntity(tableName: string, id: string): Promise<number> {\n let deleted = 0;\n\n // 1. TTL — delete entries older than ttlDays\n const ttlResult = await this.db.execute(sql`\n DELETE FROM rebase.entity_history\n WHERE table_name = ${tableName}\n AND entity_id = ${String(id)}\n AND updated_at < NOW() - MAKE_INTERVAL(days => ${this.retention.ttlDays})\n `);\n deleted += ttlResult.rowCount ?? 0;\n\n // 2. Max entries — keep the newest maxEntries, delete the rest\n const maxResult = await this.db.execute(sql`\n DELETE FROM rebase.entity_history\n WHERE id IN (\n SELECT id FROM rebase.entity_history\n WHERE table_name = ${tableName}\n AND entity_id = ${String(id)}\n ORDER BY updated_at DESC\n OFFSET ${this.retention.maxEntries}\n )\n `);\n deleted += maxResult.rowCount ?? 0;\n\n return deleted;\n }\n\n /**\n * Global prune: enforce TTL across ALL rows in a single sweep.\n * Intended to be called periodically (e.g. once per hour or daily).\n */\n async pruneExpired(): Promise<number> {\n const result = await this.db.execute(sql`\n DELETE FROM rebase.entity_history\n WHERE updated_at < NOW() - MAKE_INTERVAL(days => ${this.retention.ttlDays})\n `);\n return result.rowCount ?? 0;\n }\n}\n\n\n/**\n * Deep equality without JSON.stringify.\n * Handles primitives, arrays, Dates, and plain objects recursively.\n */\nfunction deepEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (a == null || b == null) return false;\n if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime();\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false;\n return a.every((v, i) => deepEqual(v, b[i]));\n }\n if (typeof a === \"object\" && typeof b === \"object\") {\n const aObj = a as Record<string, unknown>;\n const bObj = b as Record<string, unknown>;\n const aKeys = Object.keys(aObj);\n const bKeys = Object.keys(bObj);\n if (aKeys.length !== bKeys.length) return false;\n return aKeys.every(k => deepEqual(aObj[k], bObj[k]));\n }\n return false;\n}\n\n/**\n * Shallow comparison to find top-level keys that changed between two objects.\n */\nexport function findChangedFields(\n oldValues: Record<string, unknown>,\n newValues: Record<string, unknown>\n): string[] | null {\n const changed: string[] = [];\n const allKeys = new Set([\n ...Object.keys(oldValues),\n ...Object.keys(newValues)\n ]);\n\n for (const key of allKeys) {\n const oldVal = oldValues[key];\n const newVal = newValues[key];\n\n // Skip internal metadata\n if (key.startsWith(\"__\")) continue;\n\n if (oldVal !== newVal) {\n // For objects/arrays, use structural comparison\n if (\n typeof oldVal === \"object\" && oldVal !== null &&\n typeof newVal === \"object\" && newVal !== null\n ) {\n if (!deepEqual(oldVal, newVal)) {\n changed.push(key);\n }\n } else {\n changed.push(key);\n }\n }\n }\n\n return changed.length > 0 ? changed : null;\n}\n","import { getTableColumns } from \"drizzle-orm\";\nimport { PgArray, PgTable } from \"drizzle-orm/pg-core\";\nimport { logger } from \"@rebasepro/server\";\n\n/**\n * Patches all PgArray columns on the given tables to handle NULL values safely.\n *\n * Drizzle ORM's `PgArray.mapFromDriverValue` calls `value.map(...)` without\n * guarding against `null`. When a PostgreSQL native array column (`text[]`,\n * `integer[]`, etc.) contains NULL, the pg driver returns `null` in JavaScript,\n * and `null.map(...)` throws `TypeError: value.map is not a function`.\n *\n * This function walks every column of every registered table and, for any\n * `PgArray` column, wraps its `mapFromDriverValue` to return `null` when the\n * database value is nullish.\n *\n * This is a workaround for a known Drizzle ORM issue. Should be removed once\n * Drizzle handles nullable arrays natively.\n */\nexport function patchPgArrayNullSafety(tables: Record<string, unknown>): void {\n let patchedCount = 0;\n\n for (const tableOrRelation of Object.values(tables)) {\n if (!(tableOrRelation instanceof PgTable)) continue;\n\n const columns = getTableColumns(tableOrRelation);\n for (const column of Object.values(columns)) {\n if (column instanceof PgArray) {\n const original = column.mapFromDriverValue.bind(column);\n column.mapFromDriverValue = function (value: unknown) {\n if (value == null) return null;\n return original(value as string | unknown[]);\n };\n patchedCount++;\n }\n }\n }\n\n if (patchedCount > 0) {\n logger.debug(`[PgArray] Patched ${patchedCount} array column(s) for null-safety`);\n }\n}\n","import { getTableColumns } from \"drizzle-orm\";\nimport { PgArray, PgNumeric, PgTable } from \"drizzle-orm/pg-core\";\nimport { logger } from \"@rebasepro/server\";\n\n/**\n * Serve NUMERIC columns as JavaScript numbers.\n *\n * A property declared `type: \"number\"` without `validation.integer` becomes a\n * Postgres `numeric` column (see `generate-drizzle-schema-logic`). Postgres\n * sends `numeric` as text, node-postgres registers no parser for OID 1700, and\n * drizzle's `PgNumeric.mapFromDriverValue` keeps the string — so a price\n * written as `2.5` reads back as `\"2.5\"`. The generated SDK types that field\n * `number`, the OpenAPI document this server publishes says `type: number`, and\n * the admin's view model parses it as a number, so the string is the one shape\n * nothing in the system claims. It also breaks *asymmetrically*: the create\n * response carries the number and the read that follows carries the string, so\n * a client that multiplies a price works until the first refresh.\n *\n * Cast here — in the driver's row mapping — rather than in one of the read\n * paths, because there are several of them (the relational query builder, the\n * `select` fallback, the realtime frame, the admin's view model) and only the\n * REST renderer used to coerce. Every one of them goes through the column's\n * `mapFromDriverValue`, so this is the single place that cannot be bypassed.\n *\n * The cast is safe for what a `number` property can hold: declaring a property\n * `number` already promises double precision — that is what the admin has\n * always parsed it to and what the generated `Row` type says. A column wider\n * than a double (a hand-written `numeric(38, 0)` reached through an\n * introspected collection) loses precision, which is the trade the declared\n * type already made.\n *\n * `null` stays `null`; an unparseable value stays whatever the driver sent,\n * because inventing `NaN` for it would be worse than passing it through.\n */\nexport function patchPgNumericToNumber(tables: Record<string, unknown>): void {\n let patchedCount = 0;\n\n const patch = (column: PgNumeric<never>): void => {\n // Idempotent: bootstrapping twice in one process (two data sources\n // sharing a table object) must not stack wrappers.\n const marked = column as unknown as { __rebaseNumericPatched?: boolean };\n if (marked.__rebaseNumericPatched) return;\n marked.__rebaseNumericPatched = true;\n\n column.mapFromDriverValue = function (value: unknown) {\n if (value === null || value === undefined) return value as never;\n if (typeof value === \"number\") return value as never;\n const parsed = Number(value);\n return (Number.isNaN(parsed) ? value : parsed) as never;\n };\n patchedCount++;\n };\n\n for (const tableOrRelation of Object.values(tables)) {\n if (!(tableOrRelation instanceof PgTable)) continue;\n\n for (const column of Object.values(getTableColumns(tableOrRelation))) {\n if (column instanceof PgNumeric) {\n patch(column as PgNumeric<never>);\n continue;\n }\n // `numeric[]`: the array column parses the literal and hands each\n // element to its base column, so the base column is what to patch.\n if (column instanceof PgArray && column.baseColumn instanceof PgNumeric) {\n patch(column.baseColumn as PgNumeric<never>);\n }\n }\n }\n\n if (patchedCount > 0) {\n logger.debug(`[PgNumeric] ${patchedCount} numeric column(s) will be served as numbers`);\n }\n}\n","/**\n * Runtime introspection — builds collections in memory from the live database.\n *\n * This is what makes BaaS mode work with zero configuration: instead of loading\n * collection files from disk, the server reads `information_schema` at boot and\n * derives a collection per table, so any database is served over REST without a\n * single config file.\n *\n * Distinct from `introspect-db.ts`, which runs the same queries but emits\n * TypeScript *source* for a developer to edit and commit (declared collections). The two\n * share the mapping helpers in `introspect-db-logic.ts` so a table is described\n * the same way whether it was generated or introspected.\n */\nimport type { PostgresCollectionConfig } from \"@rebasepro/types\";\n\nimport {\n TableRow,\n TableColumn,\n EnumValue,\n PrimaryKeyRow,\n ForeignKeyRow,\n TableMeta,\n buildTablesMap,\n buildEnumMap,\n identifyJoinTables,\n singularize,\n mapPgType,\n getIconForTable\n} from \"./introspect-db-logic\";\nimport { humanize } from \"./introspect-db-naming\";\nimport { firstFreeKey, toWireKey } from \"@rebasepro/utils\";\n\nexport interface IntrospectedSchema {\n tablesMap: Map<string, TableMeta>;\n enumMap: Map<string, string[]>;\n joinTables: Set<string>;\n}\n\n/** Whether a table carries an authorization model of its own. */\nexport interface TableRlsStatus {\n table: string;\n /** ALTER TABLE … ENABLE ROW LEVEL SECURITY has been run. */\n rlsEnabled: boolean;\n /** Policies attached to it. RLS enabled with none = nothing is visible. */\n policyCount: number;\n}\n\n/**\n * Read the RLS posture of each table in a schema.\n *\n * This is what decides whether baas mode may serve a table. A table with RLS\n * disabled has no authorization model: since every authenticated request runs\n * as `rebase_user`, and that role is granted DML on the schema, serving such a\n * table hands every row to every logged-in user.\n */\nexport async function readRlsStatus(client: Queryable, pgSchema: string): Promise<Map<string, TableRlsStatus>> {\n const { rows } = await client.query<{ table: string; rls_enabled: boolean; policy_count: string | number }>(\n `SELECT c.relname AS table,\n c.relrowsecurity AS rls_enabled,\n (SELECT count(*) FROM pg_policy p WHERE p.polrelid = c.oid) AS policy_count\n FROM pg_class c\n JOIN pg_namespace n ON n.oid = c.relnamespace\n WHERE n.nspname = $1 AND c.relkind = 'r'`,\n [pgSchema]\n );\n\n return new Map(\n rows.map((r) => [\n r.table,\n { table: r.table, rlsEnabled: r.rls_enabled === true, policyCount: Number(r.policy_count ?? 0) }\n ])\n );\n}\n\n/** Minimal query surface — satisfied by pg.Client and pg.Pool alike. */\nexport interface Queryable {\n query<R>(text: string, values?: unknown[]): Promise<{ rows: R[] }>;\n}\n\n/**\n * Read tables, columns, enums, primary keys and foreign keys for a schema.\n * Mirrors the queries in introspect-db.ts.\n */\nexport async function introspectSchema(client: Queryable, pgSchema: string): Promise<IntrospectedSchema> {\n const { rows: tables } = await client.query<TableRow>(\n `SELECT table_name\n FROM information_schema.tables\n WHERE table_schema = $1 AND table_type = 'BASE TABLE'\n AND table_name NOT LIKE 'drizzle_%'\n AND table_name NOT LIKE 'rebase_%'\n ORDER BY table_name`,\n [pgSchema]\n );\n\n const { rows: columns } = await client.query<TableColumn>(\n `SELECT\n c.table_name,\n c.column_name,\n c.data_type,\n c.udt_name,\n c.is_nullable,\n c.column_default,\n -- A stored generated column must be declared as one, or drizzle\n -- writes DEFAULT into it on every INSERT. Postgres tolerates that\n -- today; the generated module has always declared them properly,\n -- and the runtime tables are built to match it.\n c.is_generated,\n c.generation_expression,\n (SELECT a.atttypmod FROM pg_attribute a\n JOIN pg_class pc ON a.attrelid = pc.oid\n WHERE pc.relname = c.table_name\n AND a.attname = c.column_name\n AND pc.relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = c.table_schema)) as atttypmod\n FROM information_schema.columns c\n WHERE c.table_schema = $1`,\n [pgSchema]\n );\n\n const { rows: enumValues } = await client.query<EnumValue>(\n `SELECT t.typname AS enum_name,\n e.enumlabel AS enum_value,\n e.enumsortorder AS sort_order\n FROM pg_type t\n JOIN pg_enum e ON t.oid = e.enumtypid\n JOIN pg_namespace n ON t.typnamespace = n.oid\n WHERE n.nspname = $1\n ORDER BY t.typname, e.enumsortorder`,\n [pgSchema]\n );\n\n const { rows: pks } = await client.query<PrimaryKeyRow>(\n `SELECT t.relname as table_name, a.attname as column_name\n FROM pg_index i\n JOIN pg_attribute a ON a.attrelid = i.indrelid\n AND a.attnum = ANY(i.indkey)\n JOIN pg_class t ON t.oid = i.indrelid\n JOIN pg_namespace n ON n.oid = t.relnamespace\n WHERE i.indisprimary AND n.nspname = $1`,\n [pgSchema]\n );\n\n const { rows: fks } = await client.query<ForeignKeyRow>(\n `SELECT\n tc.table_name,\n kcu.column_name,\n ccu.table_name AS foreign_table_name,\n ccu.column_name AS foreign_column_name\n FROM information_schema.table_constraints AS tc\n JOIN information_schema.key_column_usage AS kcu\n ON tc.constraint_name = kcu.constraint_name\n AND tc.table_schema = kcu.table_schema\n JOIN information_schema.constraint_column_usage AS ccu\n ON ccu.constraint_name = tc.constraint_name\n AND ccu.table_schema = tc.table_schema\n WHERE tc.constraint_type = 'FOREIGN KEY' AND tc.table_schema = $1`,\n [pgSchema]\n );\n\n const tablesMap = buildTablesMap(tables, columns, pks, fks);\n return {\n tablesMap,\n enumMap: buildEnumMap(enumValues),\n joinTables: identifyJoinTables(tablesMap)\n };\n}\n\n/** Derive the `isId` flavour for a primary-key column. */\nfunction idKindFor(col: TableColumn, propType: string): \"uuid\" | \"increment\" | true {\n if (col.data_type.toLowerCase() === \"uuid\") return \"uuid\";\n if (propType === \"number\") return \"increment\";\n return true;\n}\n\nfunction buildProperties(\n meta: TableMeta,\n enumMap: Map<string, string[]>\n): Record<string, Record<string, unknown>> {\n const properties: Record<string, Record<string, unknown>> = {};\n const takenKeys = new Set<string>();\n\n for (const col of meta.columns) {\n const isPk = meta.pks.includes(col.column_name);\n // Foreign keys surface as relations below, unless they are also part of\n // the primary key, in which case the column itself must stay.\n const isFk = meta.fks.some((fk) => fk.column_name === col.column_name);\n if (isFk && !isPk) continue;\n\n const enumValues = enumMap.get(col.udt_name);\n const isEnum = col.data_type === \"USER-DEFINED\" && enumValues !== undefined;\n const isVector = col.udt_name === \"vector\";\n const propType = isEnum ? \"string\" : isVector ? \"vector\" : mapPgType(col.data_type);\n\n const property: Record<string, unknown> = {\n name: humanize(col.column_name),\n columnName: col.column_name,\n type: propType\n };\n\n // The wire name, with `columnName` above carrying the column. The two\n // are different names for different things — this used to serve the\n // column, so a runtime-introspected collection answered `user_id` while\n // every authored one beside it answered `displayName`.\n //\n // `user_id` and `userId` as two real columns camel-case to one key, so\n // the first free candidate wins and the second falls back to its own\n // column name, then to a numbered tail. Never dropped, and stable for a\n // given database: columns arrive in ordinal order.\n const key = firstFreeKey([toWireKey(col.column_name), col.column_name], takenKeys);\n takenKeys.add(key);\n\n if (isPk) {\n property.isId = idKindFor(col, propType);\n } else if (col.is_nullable === \"NO\" && col.column_default === null) {\n property.validation = { required: true };\n }\n\n if (isEnum && enumValues) {\n property.enum = enumValues.map((value) => ({ id: value, label: humanize(value) }));\n }\n\n properties[key] = property;\n }\n\n return properties;\n}\n\n/**\n * Owning relations, derived from this table's foreign keys — the same shape\n * `generateCollectionFile` writes into a collection file: a `relation` property\n * whose nested descriptor carries `kind`, a `target` thunk and the `localKey`.\n *\n * The shape is load-bearing, not cosmetic. `resolveCollectionRelations` reads\n * relations from `property.relation` and `resolveRelation` requires `target` to\n * be a thunk; this used to emit `target`/`cardinality`/`localKey` flat on the\n * property with the slug as a bare string, which satisfies neither. Nothing\n * threw — the resolver simply skipped every such property and reported that the\n * collection had no relations. So an introspected BaaS collection had its FK\n * columns removed from `properties` (they \"surface as relations\") and then no\n * resolvable relation to surface as, which is why writing the FK column\n * directly came back as `has no field 'product_id'`: `assertKnownWriteFields`\n * learns that column from the resolved relation's `localKey`.\n *\n * The thunk closes over the collections being built in this same pass rather\n * than importing a module, which is what a runtime introspection has instead of\n * generated files. It is called lazily, after the map is fully populated, so a\n * table may reference one introspected later.\n */\nfunction buildRelations(\n meta: TableMeta,\n slugByTable: Map<string, string>,\n collectionBySlug: Map<string, PostgresCollectionConfig>\n): Record<string, Record<string, unknown>> {\n const relations: Record<string, Record<string, unknown>> = {};\n\n for (const fk of meta.fks) {\n const targetSlug = slugByTable.get(fk.foreign_table_name);\n if (!targetSlug) continue;\n\n // Strip the conventional _id suffix: author_id -> author\n let key = toWireKey(fk.column_name.replace(/_id$/, \"\"));\n if (meta.pks.includes(fk.column_name) && key === fk.column_name) {\n // The FK is also the PK and its name doesn't imply a relation (e.g.\n // \"id\"), so naming the relation after the column would collide with\n // the primary-key property.\n key = fk.foreign_table_name;\n }\n\n relations[key] = {\n name: humanize(key),\n type: \"relation\",\n relation: {\n kind: \"belongsTo\",\n target: () => collectionBySlug.get(targetSlug),\n localKey: fk.column_name\n }\n };\n }\n\n return relations;\n}\n\n/**\n * Turn an introspected schema into collections.\n *\n * Join tables are skipped: they carry no identity of their own and exist to\n * express a many-to-many edge between two other tables.\n */\nexport function buildCollectionsFromSchema(\n { tablesMap, enumMap, joinTables }: IntrospectedSchema,\n pgSchema: string\n): PostgresCollectionConfig[] {\n const slugByTable = new Map<string, string>();\n for (const tableName of tablesMap.keys()) {\n if (!joinTables.has(tableName)) slugByTable.set(tableName, tableName);\n }\n\n const collections: PostgresCollectionConfig[] = [];\n // Filled as we go; the relation thunks read it lazily, so a table may point\n // at one that has not been built yet at the moment its relation is created.\n const collectionBySlug = new Map<string, PostgresCollectionConfig>();\n\n for (const [tableName, meta] of tablesMap) {\n if (joinTables.has(tableName)) continue;\n\n const collectionName = humanize(tableName);\n const collection = {\n name: collectionName,\n singularName: singularize(collectionName),\n slug: tableName,\n table: tableName,\n schema: pgSchema,\n icon: getIconForTable(tableName),\n properties: {\n ...buildProperties(meta, enumMap),\n ...buildRelations(meta, slugByTable, collectionBySlug)\n }\n // A collection synthesised from a catalogue read, not authored.\n // `PostgresCollectionConfig` is generic over the row type `M`, and\n // there is no `M` here — the columns were discovered a moment ago —\n // so the object is built from the fields introspection can supply\n // and asserted into the config the rest of the server consumes.\n } as unknown as PostgresCollectionConfig;\n\n collections.push(collection);\n collectionBySlug.set(tableName, collection);\n }\n\n return collections;\n}\n\n/** Introspect the database and return ready-to-serve collections. */\nexport async function introspectCollections(\n client: Queryable,\n pgSchema: string\n): Promise<PostgresCollectionConfig[]> {\n const schema = await introspectSchema(client, pgSchema);\n return buildCollectionsFromSchema(schema, pgSchema);\n}\n","/**\n * Build drizzle tables at runtime from an introspected schema.\n *\n * **Every** data source's tables are built here, from `information_schema`,\n * after boot-ensure has run — a project with declared collections included. The\n * committed `schema.generated.ts` is a build artefact for Atlas, `db push`,\n * `eject` and user code; the runtime does not read it for its tables, because a\n * file that is one commit behind the database used to change what the server\n * served (a renamed foreign-key column 500'd every request, a lost relation\n * silently emptied a list). See `catalogue-schema.ts` for the caller.\n *\n * These are handed to drizzle as its schema, which keeps the relational query\n * path (`db.query.*`) working; without them FetchService would fall back to\n * plain selects and lose relation loading.\n *\n * Two things a caller has to supply for the tables to match what the generated\n * module used to produce, both about *names*: the Drizzle object is keyed by the\n * wire name of a column and not by the column (`authorId`, not `author_id`), and\n * a relation is keyed by the collection's own relation key rather than by a\n * guess made from the foreign key. Both come from the collections — see\n * `columnKeysFromCollections` and `buildDrizzleRelationsFromCollections`.\n */\nimport {\n bigint,\n boolean,\n char,\n cidr,\n customType,\n date,\n doublePrecision,\n geometry,\n inet,\n integer,\n interval,\n json,\n jsonb,\n line,\n macaddr,\n macaddr8,\n numeric,\n pgSchema,\n pgTable,\n point,\n primaryKey,\n real,\n smallint,\n text,\n time,\n timestamp,\n uuid,\n varchar,\n vector,\n type PgColumnBuilderBase,\n type PgTable\n} from \"drizzle-orm/pg-core\";\n\nimport { getTableColumns, relations, sql, type Relations } from \"drizzle-orm\";\nimport { toWireKey } from \"@rebasepro/utils\";\n\nimport type { TableColumn, TableMeta } from \"./introspect-db-logic\";\n\n/**\n * The Drizzle object key a column is served under, given its table.\n *\n * Defaults to the column name — right for a junction table and for a database\n * nothing declares — and is supplied from the collections wherever there is\n * one, because there the wire name is the property key. Every read and write\n * path in this driver indexes tables and rows by that key.\n */\nexport type ColumnKeyResolver = (tableName: string, columnName: string) => string;\n\nconst columnNameAsKey: ColumnKeyResolver = (_table, column) => column;\n\n/** drizzle ships no bytea builder; binary must round-trip as a Buffer. */\nconst bytea = customType<{ data: Buffer; driverData: Buffer }>({\n dataType: () => \"bytea\"\n});\n\n/**\n * A full-text search column, declared with its real SQL type.\n *\n * Not cosmetic: `isSearchIndexColumn` asks the column for its type and drops\n * every `tsvector` from the projection, so a search column that fell through to\n * `text` (the old default for an unrecognised type) would be selected, returned\n * to callers, and rendered in the admin as a wall of lexemes. drizzle ships no\n * builder for these two.\n */\nconst tsvector = customType<{ data: string; driverData: string }>({\n dataType: () => \"tsvector\"\n});\n\nconst tsquery = customType<{ data: string; driverData: string }>({\n dataType: () => \"tsquery\"\n});\n\n/**\n * Postgres stores a column's type modifier (varchar length, vector dimensions)\n * in `atttypmod`. For length-carrying string types it is length + VARHDRSZ(4);\n * for pgvector it is the dimension count as-is. -1 means unspecified.\n */\nfunction varlenLength(col: TableColumn): number | undefined {\n return col.atttypmod && col.atttypmod > 4 ? col.atttypmod - 4 : undefined;\n}\n\n/**\n * Map a Postgres type to a drizzle column builder, keyed on `udt_name` — the\n * concrete underlying type, which is exact where `data_type` reports umbrella\n * values like \"ARRAY\" or \"USER-DEFINED\".\n *\n * Unknown types fall back to `text`: the driver still reads and writes them,\n * with the value passing through as a string, which beats dropping the column.\n */\nfunction scalarBuilder(udtName: string, name: string, col: TableColumn): PgColumnBuilderBase {\n switch (udtName) {\n case \"uuid\":\n return uuid(name);\n case \"bool\":\n return boolean(name);\n case \"int2\":\n return smallint(name);\n case \"int4\":\n return integer(name);\n case \"int8\":\n return bigint(name, { mode: \"number\" });\n case \"float4\":\n return real(name);\n case \"float8\":\n return doublePrecision(name);\n case \"numeric\":\n case \"money\":\n return numeric(name);\n case \"json\":\n return json(name);\n case \"jsonb\":\n return jsonb(name);\n case \"date\":\n return date(name);\n case \"time\":\n return time(name);\n case \"timetz\":\n return time(name, { withTimezone: true });\n // `mode: \"string\"`, both of them, because that is what the generated\n // module declared for every `date` property: the value Postgres sent,\n // untouched. Drizzle's default mode is `date`, which parses it into a\n // `Date` — the same instant in a different shape, and a difference that\n // would have appeared only on the read path of a project that changed\n // nothing. (`parsePropertyFromServer` normalises either shape to an ISO\n // string for a declared `date` property, so what a REST caller sees is\n // the same; a raw `db.select()` is where the two differ.)\n case \"timestamp\":\n return timestamp(name, { mode: \"string\" });\n case \"timestamptz\":\n return timestamp(name, { withTimezone: true, mode: \"string\" });\n case \"interval\":\n return interval(name);\n case \"bytea\":\n return bytea(name);\n case \"inet\":\n return inet(name);\n case \"cidr\":\n return cidr(name);\n case \"macaddr\":\n return macaddr(name);\n case \"macaddr8\":\n return macaddr8(name);\n case \"point\":\n return point(name);\n case \"line\":\n return line(name);\n case \"geometry\":\n return geometry(name);\n case \"vector\": {\n // drizzle requires the dimension count; without it fall back to text\n // rather than declaring a vector of unknown width.\n const dimensions = col.atttypmod && col.atttypmod > 0 ? col.atttypmod : undefined;\n return dimensions ? vector(name, { dimensions }) : text(name);\n }\n case \"bpchar\": {\n const length = varlenLength(col);\n return length ? char(name, { length }) : char(name);\n }\n case \"varchar\": {\n const length = varlenLength(col);\n return length ? varchar(name, { length }) : text(name);\n }\n case \"tsvector\":\n return tsvector(name);\n case \"tsquery\":\n return tsquery(name);\n default:\n // Includes text, enums (pg enums are strings on the wire), citext,\n // geography, and anything else this driver hasn't met yet.\n //\n // An enum deliberately stays `text`, which is what the generated\n // module's `pgEnum` column also serves: drizzle does no runtime\n // validation of enum members either way, and Postgres rejects a\n // label it does not have. The difference is only in the type the\n // generated *types* carry, and those come from the collection.\n return text(name);\n }\n}\n\n/**\n * The fluent methods this file calls on a column builder.\n *\n * Drizzle's `PgColumnBuilderBase` exposes none of them by design: they live on\n * the concrete `PgColumnBuilder` subclasses, each generic over the column's\n * TypeScript type, its name and whether it is notNull / hasDefault — a chain\n * resolved when you *write* a schema, and not resolvable when you build one\n * from a catalogue read at runtime, which is all this file does.\n *\n * So a conversion here is irreducible; what was avoidable was doing it four\n * separate times, each declaring its own one-method slice of the builder and\n * each free to disagree with the others about a name or a signature. Declaring\n * the methods as an *extension* of the base also drops the `as unknown as`: the\n * two types overlap, so tsc will still refuse this if drizzle ever gives\n * `PgColumnBuilderBase` a conflicting member.\n */\ninterface FluentColumnBuilder extends PgColumnBuilderBase {\n array(): PgColumnBuilderBase;\n notNull(): PgColumnBuilderBase;\n primaryKey(): PgColumnBuilderBase;\n generatedAlwaysAs(expr: unknown): PgColumnBuilderBase;\n}\n\n/** See {@link FluentColumnBuilder}. */\nconst fluent = (builder: PgColumnBuilderBase): FluentColumnBuilder => builder as FluentColumnBuilder;\n\nfunction columnBuilderFor(col: TableColumn): PgColumnBuilderBase {\n // Array types are named after their element with a leading underscore\n // (_int4 = int4[]), so the element mapping is reused verbatim.\n if (col.udt_name.startsWith(\"_\")) {\n const element = scalarBuilder(col.udt_name.slice(1), col.column_name, col);\n return fluent(element).array();\n }\n return scalarBuilder(col.udt_name, col.column_name, col);\n}\n\n/**\n * Build one drizzle table per introspected table, keyed by table name.\n *\n * @param columnKey how a column is named on the wire — see {@link ColumnKeyResolver}.\n */\nexport function buildDrizzleTablesFromSchema(\n tablesMap: Map<string, TableMeta>,\n pgSchemaName = \"public\",\n columnKey: ColumnKeyResolver = columnNameAsKey\n): Record<string, PgTable> {\n const schema = pgSchemaName === \"public\" ? null : pgSchema(pgSchemaName);\n // The column set is only known at runtime, so drizzle's generic table\n // signature can't be satisfied statically; call it through a loose type.\n const createTable = (schema ? schema.table.bind(schema) : pgTable) as (\n name: string,\n columns: Record<string, PgColumnBuilderBase>,\n extras?: (self: Record<string, unknown>) => unknown[]\n ) => PgTable;\n\n const tables: Record<string, PgTable> = {};\n\n for (const [tableName, meta] of tablesMap) {\n const columns: Record<string, PgColumnBuilderBase> = {};\n const keyOf = new Map<string, string>();\n\n for (const col of meta.columns) {\n let builder = columnBuilderFor(col);\n\n if (col.is_nullable === \"NO\") {\n builder = fluent(builder).notNull();\n }\n // Single-column primary keys are marked inline; composite keys are\n // declared in the table extras below.\n if (meta.pks.length === 1 && meta.pks[0] === col.column_name) {\n builder = fluent(builder).primaryKey();\n }\n // A stored generated column must be left out of every INSERT, which\n // is what `.generatedAlwaysAs` tells drizzle. The expression is\n // carried through so a drizzle-kit run over these tables describes\n // the column it actually found; nothing at runtime evaluates it.\n if (col.is_generated === \"ALWAYS\") {\n builder = fluent(builder).generatedAlwaysAs(sql.raw(col.generation_expression ?? \"\"));\n }\n\n const key = columnKey(tableName, col.column_name);\n keyOf.set(col.column_name, key);\n columns[key] = builder;\n }\n\n const isComposite = meta.pks.length > 1;\n tables[tableName] = createTable(\n tableName,\n columns,\n isComposite\n ? (t) => [primaryKey({ columns: meta.pks.map((pk) => t[keyOf.get(pk) ?? pk]) as never })]\n : undefined\n );\n }\n\n return tables;\n}\n\n/**\n * Build drizzle `relations()` for the foreign keys, so the relational query\n * path can actually load them.\n *\n * FetchService asks drizzle for `with: { <key>: true }`, keyed by the relation\n * property on the collection. Tables alone don't satisfy that — without these,\n * `?include=author` silently returns the raw `author_id` and no author.\n *\n * The keys here must match `buildRelations` in introspect-runtime.ts, which is\n * what names the collection's relation properties.\n */\nexport function buildDrizzleRelationsFromSchema(\n tablesMap: Map<string, TableMeta>,\n tables: Record<string, PgTable>,\n columnKey: ColumnKeyResolver = columnNameAsKey\n): Record<string, Relations> {\n /** Owning side, per table: the `one()` relations from its foreign keys. */\n const owning = new Map<string, { key: string; targetTable: string; fkColumn: string; targetColumn: string; relationName: string }[]>();\n /** Inverse side, per referenced table: the matching `many()` back-references. */\n const inverse = new Map<string, { key: string; sourceTable: string; relationName: string }[]>();\n\n for (const [tableName, meta] of tablesMap) {\n if (!tables[tableName]) continue;\n // The keys the table actually carries — what a relation key must not\n // collide with, since both live in one Drizzle object.\n const columnNames = new Set(meta.columns.map((c) => columnKey(tableName, c.column_name)));\n\n for (const fk of meta.fks) {\n if (!tables[fk.foreign_table_name]) continue;\n\n // Mirrors buildRelations in introspect-runtime: author_id -> author,\n // falling back to the target table when the column name carries no\n // hint. `toWireKey` for the same reason it is there — the two keys\n // must be the same string or the collection advertises a relation key\n // drizzle has never heard of (`author_ref` here, `authorRef` there).\n let key = toWireKey(fk.column_name.replace(/_id$/, \"\"));\n if (meta.pks.includes(fk.column_name) && key === fk.column_name) {\n key = fk.foreign_table_name;\n }\n // A relation key must not shadow a real column.\n if (columnNames.has(key)) continue;\n\n // Pairs the two sides. Drizzle matches a named one() to the many()\n // carrying the same name, and disambiguates multiple foreign keys\n // into the same table.\n const relationName = `${tableName}_${fk.column_name}`;\n\n owning.set(tableName, [\n ...(owning.get(tableName) ?? []),\n {\n key,\n targetTable: fk.foreign_table_name,\n // The Drizzle keys, not the columns: `fields`/`references`\n // index the built table objects, which are keyed by the\n // wire name wherever a collection supplies one.\n fkColumn: columnKey(tableName, fk.column_name),\n targetColumn: columnKey(fk.foreign_table_name, fk.foreign_column_name),\n relationName\n }\n ]);\n\n const backKey = tableName;\n const targetColumns = new Set(\n (tablesMap.get(fk.foreign_table_name)?.columns ?? [])\n .map((c) => columnKey(fk.foreign_table_name, c.column_name))\n );\n if (targetColumns.has(backKey)) continue;\n\n inverse.set(fk.foreign_table_name, [\n ...(inverse.get(fk.foreign_table_name) ?? []),\n { key: backKey, sourceTable: tableName, relationName }\n ]);\n }\n }\n\n const built: Record<string, Relations> = {};\n\n for (const tableName of tablesMap.keys()) {\n const table = tables[tableName];\n const ones = owning.get(tableName) ?? [];\n const manys = inverse.get(tableName) ?? [];\n if (!table || (ones.length === 0 && manys.length === 0)) continue;\n\n built[`${tableName}Relations`] = relations(table, ({ one, many }) => {\n const map: Record<string, unknown> = {};\n\n for (const rel of ones) {\n map[rel.key] = one(tables[rel.targetTable], {\n // `getTableColumns` is drizzle's own accessor for this.\n // Indexing the table object — which is what the casts here\n // were for — also reaches its methods and symbols, so a\n // foreign key named after one of them yields something that\n // is not a column; and the target type they used,\n // `Record<string, never>`, makes every lookup `never`,\n // which is assignable to anything and therefore checks\n // nothing at all.\n fields: [getTableColumns(table)[rel.fkColumn]],\n references: [getTableColumns(tables[rel.targetTable])[rel.targetColumn]],\n relationName: rel.relationName\n });\n }\n\n // Drizzle needs the inverse of every named one(); without it,\n // normalizeRelation throws and the relational path is dead.\n for (const rel of manys) {\n // An inverse must not collide with an owning key on this table.\n if (map[rel.key]) continue;\n map[rel.key] = many(tables[rel.sourceTable], { relationName: rel.relationName });\n }\n\n return map as never;\n });\n }\n\n return built;\n}\n","/**\n * Build drizzle `relations()` for declared collections, from the collections.\n *\n * The heuristic builder in `dynamic-tables.ts` names a relation after the\n * foreign-key column (`author_id` → `author`), which is the only evidence a\n * database offers and is right for BaaS mode. It is wrong for a project that\n * declares its collections: there the relation's name is whatever the author\n * called it, `include=writer` addresses that name, and a relation whose key was\n * guessed from the column simply does not answer to it — the read returns the\n * bare foreign key and no target, silently, which is how a demo lost every\n * relation in its list view while the detail view (a different code path) kept\n * working.\n *\n * So when there are collections, they are the authority for *which* relations\n * exist and what they are called; the live catalogue is the authority for the\n * columns underneath. This is the same pairing `generate-drizzle-schema-logic`\n * emits into `schema.generated.ts` — kind for kind, key for key, and paired by\n * the same {@link sharedRelationName} — only built against tables read back from\n * `information_schema` rather than against a file.\n */\nimport { getTableColumns, relations, type Relations } from \"drizzle-orm\";\nimport type { PgTable } from \"drizzle-orm/pg-core\";\nimport { CollectionConfig, ResolvedRelation, type ResolvedForeignKeyOnTarget } from \"@rebasepro/types\";\nimport { fieldKeyForColumn, getTableName, resolveCollectionRelations } from \"@rebasepro/common\";\nimport { logger } from \"@rebasepro/server\";\n\nimport { sharedRelationName } from \"./relation-names\";\n\n/** A collection's table, with any schema prefix stripped — as the DDL creates it. */\nexport function bareTableName(name: string): string {\n return name.includes(\".\") ? name.split(\".\").pop()! : name;\n}\n\n/**\n * The field key a collection's rows are addressed by, as the generated module\n * spells it: the property marked `isId`, else `id`.\n *\n * Deliberately the config's answer rather than the catalogue's primary key —\n * `references: [users.id]` in the generated file is keyed the same way, and the\n * two have to name the same Drizzle key for a relation to compile at all.\n */\nfunction primaryKeyFieldKey(collection: CollectionConfig): string {\n for (const [key, prop] of Object.entries(collection.properties ?? {})) {\n if (prop && typeof prop === \"object\" && \"isId\" in prop && Boolean((prop as { isId?: unknown }).isId)) {\n return key;\n }\n }\n return \"id\";\n}\n\n/**\n * The field key on the source table that a `hasOne`/`hasMany` foreign key\n * holds: its `sourceKey` when the link names one, else the primary key.\n */\nfunction sourceKeyFieldKey(\n relation: ResolvedForeignKeyOnTarget,\n collection: CollectionConfig\n): string {\n return relation.sourceKey\n ? fieldKeyForColumn(collection, relation.sourceKey)\n : primaryKeyFieldKey(collection);\n}\n\n/**\n * A column of a built table, by its Drizzle key or by its column name.\n *\n * The key is the answer in the ordinary case, and the name is the fallback for\n * the one place a caller holds a column and not a field: a junction table's two\n * foreign keys, which are keyed by column *unless* the project also declares a\n * collection over that table, in which case they are keyed by its property\n * names. Looking both up costs a scan of one table and removes the difference.\n */\nfunction columnOf(table: PgTable | undefined, key: string): unknown {\n if (!table) return undefined;\n // Drizzle's column map is keyed by the JS property name, which is exactly\n // the \"keyed by its property names\" case this function's docblock\n // describes — so `getTableColumns` answers it, and indexing the table\n // object (which also reaches its methods and symbols) was never needed.\n const direct = getTableColumns(table)[key];\n if (direct) return direct;\n for (const column of Object.values(getTableColumns(table))) {\n if ((column as { name?: unknown })?.name === key) return column;\n }\n return undefined;\n}\n\ninterface OneSpec {\n key: string;\n target: PgTable;\n fields: unknown[];\n references: unknown[];\n relationName: string;\n}\n\ninterface ManySpec {\n key: string;\n target: PgTable;\n relationName: string;\n}\n\n/**\n * Drizzle relations for every declared collection, keyed `<table>Relations`.\n *\n * @param collections every collection backed by a table in `tables`\n * @param tables catalogue-built tables, keyed by bare table name\n */\nexport function buildDrizzleRelationsFromCollections(\n collections: CollectionConfig[],\n tables: Record<string, PgTable>\n): Record<string, Relations> {\n const ones = new Map<string, OneSpec[]>();\n const manys = new Map<string, ManySpec[]>();\n /** `<relationName>::<kind>` already emitted on a table — the generator dedupes the same way. */\n const emitted = new Map<string, Set<string>>();\n\n const claim = (table: string, name: string, kind: string): boolean => {\n const seen = emitted.get(table) ?? new Set<string>();\n emitted.set(table, seen);\n const key = `${name}::${kind}`;\n if (seen.has(key)) return false;\n seen.add(key);\n return true;\n };\n\n const addOne = (table: string, spec: OneSpec) => ones.set(table, [...(ones.get(table) ?? []), spec]);\n const addMany = (table: string, spec: ManySpec) => manys.set(table, [...(manys.get(table) ?? []), spec]);\n\n const byTable = new Map<string, CollectionConfig>();\n for (const collection of collections) {\n const table = bareTableName(getTableName(collection));\n if (table && tables[table]) byTable.set(table, collection);\n }\n\n for (const [tableName, collection] of byTable) {\n const table = tables[tableName];\n\n for (const [relationKey, relation] of Object.entries(resolveCollectionRelations(collection))) {\n let target: CollectionConfig;\n try {\n target = relation.target();\n } catch (err) {\n // Nothing to build against, and nothing to fail over: the boot\n // relation check reports an unresolvable target by name.\n logger.debug(`[schema] Relation ${collection.slug}.${relationKey} has no resolvable target`, { error: err });\n continue;\n }\n const targetTableName = bareTableName(getTableName(target));\n const targetTable = tables[targetTableName];\n const name = sharedRelationName(relation, collection);\n\n switch (relation.kind) {\n case \"belongsTo\": {\n if (!targetTable) continue;\n if (!claim(tableName, name, relation.kind)) continue;\n const localKey = fieldKeyForColumn(collection, relation.localKey);\n const referenced = primaryKeyFieldKey(target);\n const field = columnOf(table, localKey);\n const reference = columnOf(targetTable, referenced);\n // A column the catalogue does not have would make drizzle\n // throw `Cannot read properties of undefined` from inside\n // `normalizeRelation`, three frames from anything nameable.\n // The boot relation check reports the same defect in terms\n // of the collection; this only declines to build it.\n if (!field || !reference) continue;\n addOne(tableName, {\n key: relationKey,\n target: targetTable,\n fields: [field],\n references: [reference],\n relationName: name\n });\n break;\n }\n\n case \"hasOne\": {\n if (!targetTable) continue;\n if (!claim(tableName, name, relation.kind)) continue;\n // The foreign key lives on the target, so this side states\n // the join from its own end: its source key against the\n // target's foreign key. Drizzle has no `one()` that is paired\n // by `relationName` alone — `createOne` reads\n // `config.fields.reduce(...)` as the relations are evaluated,\n // which is at `drizzle({ schema })` — and a bare `one(target)`\n // is paired by table, which is ambiguous as soon as the target\n // has two links back here or the link is to its own table.\n // These are the columns drizzle would infer from the owning\n // side, given outright.\n const field = columnOf(table, sourceKeyFieldKey(relation, collection));\n const reference = columnOf(targetTable, fieldKeyForColumn(target, relation.foreignKeyOnTarget));\n if (!field || !reference) continue;\n addOne(tableName, {\n key: relationKey,\n target: targetTable,\n fields: [field],\n references: [reference],\n relationName: name\n });\n break;\n }\n\n case \"hasMany\": {\n if (!targetTable) continue;\n if (!claim(tableName, name, relation.kind)) continue;\n addMany(tableName, { key: relationKey, target: targetTable, relationName: name });\n break;\n }\n\n case \"manyToMany\": {\n const junctionName = bareTableName(relation.through.table);\n const junction = tables[junctionName];\n if (!junction) continue;\n if (!claim(tableName, name, relation.kind)) continue;\n addMany(tableName, { key: relationKey, target: junction, relationName: name });\n addJunctionSides(relation, collection, target, junctionName, tables, addOne, claim);\n break;\n }\n\n case \"via\":\n // A join chain is resolved at query time, not modelled as a\n // drizzle relation — same as the generator.\n break;\n }\n }\n }\n\n // The reciprocal a `many()` needs. A `hasMany` gives drizzle no columns to\n // join on; it finds them on the owning side, which exists only if the other\n // collection happens to declare a `belongsTo` back. Where it does not, the\n // generated file synthesizes one under a `_synth_` key, and so does this.\n // A `hasOne` names its own columns, and gets the same owning side so the\n // target's table carries the link back under the same `relationName`.\n for (const [tableName, collection] of byTable) {\n const table = tables[tableName];\n for (const [, relation] of Object.entries(resolveCollectionRelations(collection))) {\n if (relation.kind !== \"hasMany\" && relation.kind !== \"hasOne\") continue;\n let target: CollectionConfig;\n try {\n target = relation.target();\n } catch {\n continue;\n }\n const targetTableName = bareTableName(getTableName(target));\n const targetTable = tables[targetTableName];\n if (!targetTable) continue;\n\n const name = sharedRelationName(relation, collection);\n if (!claim(targetTableName, name, \"belongsTo\")) continue;\n\n const fkKey = fieldKeyForColumn(target, relation.foreignKeyOnTarget);\n const field = columnOf(targetTable, fkKey);\n const reference = columnOf(table, sourceKeyFieldKey(relation, collection));\n if (!field || !reference) continue;\n\n addOne(targetTableName, {\n key: `_synth_${targetTableName}_${fkKey}`,\n target: tables[tableName],\n fields: [field],\n references: [reference],\n relationName: name\n });\n }\n }\n\n const built: Record<string, Relations> = {};\n for (const tableName of new Set([...ones.keys(), ...manys.keys()])) {\n const table = tables[tableName];\n if (!table) continue;\n const tableOnes = ones.get(tableName) ?? [];\n const tableManys = manys.get(tableName) ?? [];\n\n built[`${tableName}Relations`] = relations(table, ({ one, many }) => {\n const map: Record<string, unknown> = {};\n for (const spec of tableOnes) {\n map[spec.key] = one(spec.target, {\n fields: spec.fields as never,\n references: spec.references as never,\n relationName: spec.relationName\n });\n }\n for (const spec of tableManys) {\n if (map[spec.key]) continue;\n map[spec.key] = many(spec.target, { relationName: spec.relationName });\n }\n return map as never;\n });\n }\n\n return built;\n}\n\n/**\n * The junction table's own two `one()` sides.\n *\n * A many-to-many is a `many(junction)` on each endpoint, so the junction has to\n * carry the matching `one()` back to each — without them drizzle can join a\n * post to its `posts_tags` rows and no further, and the tags come back as\n * junction rows. Keyed by the junction's own columns, which is how the\n * generated file keys them too.\n */\nfunction addJunctionSides(\n relation: Extract<ResolvedRelation, { kind: \"manyToMany\" }>,\n sourceCollection: CollectionConfig,\n targetCollection: CollectionConfig,\n junctionName: string,\n tables: Record<string, PgTable>,\n addOne: (table: string, spec: OneSpec) => void,\n claim: (table: string, name: string, kind: string) => boolean\n): void {\n const junction = tables[junctionName];\n const sourceTable = tables[bareTableName(getTableName(sourceCollection))];\n const targetTable = tables[bareTableName(getTableName(targetCollection))];\n if (!junction || !sourceTable || !targetTable) return;\n\n const sourceName = sharedRelationName(relation, sourceCollection);\n // The far endpoint pairs with whatever *it* calls this junction, so the two\n // sides are looked up rather than assumed: an inverse declared on the target\n // owns the name, and only a junction nothing points back through needs a\n // synthesized one.\n const inverse = Object.values(resolveCollectionRelations(targetCollection))\n .find(r => r.kind === \"manyToMany\"\n && bareTableName(r.through.table) === junctionName);\n const targetName = inverse\n ? sharedRelationName(inverse, targetCollection)\n : `${junctionName}_${relation.through.targetColumn}`;\n\n const pairs: [string, PgTable, string, CollectionConfig, string][] = [\n [relation.through.sourceColumn, sourceTable, sourceName, sourceCollection, \"source\"],\n [relation.through.targetColumn, targetTable, targetName, targetCollection, \"target\"]\n ];\n\n for (const [column, endpointTable, relationName, endpointCollection] of pairs) {\n if (!claim(junctionName, relationName, \"junction\")) continue;\n const field = columnOf(junction, column);\n const reference = columnOf(endpointTable, primaryKeyFieldKey(endpointCollection));\n if (!field || !reference) continue;\n addOne(junctionName, {\n key: column,\n target: endpointTable,\n fields: [field],\n references: [reference],\n relationName\n });\n }\n}\n","/**\n * The runtime's Drizzle schema, read back from the database it is about to\n * serve.\n *\n * This is the whole point of the module: **the live catalogue is the schema**.\n * Boot-ensure has already created and altered whatever the collections describe\n * by the time this runs, so `information_schema` is both current and complete,\n * and the committed `schema.generated.ts` — which is a build artefact for Atlas,\n * `db push`, `eject` and user code — never decides what the server serves.\n *\n * It used to. The default data source was handed the bundle's generated module\n * and nothing reconciled it, so a file one commit behind the database changed\n * behaviour in ways that looked nothing like \"your schema file is old\":\n *\n * - a foreign-key column renamed by boot-ensure (`categorie_id` → `category_id`)\n * left every request 500ing, because the module still declared the old name;\n * - a property added to a collection answered `400 VALIDATION_UNKNOWN_FIELDS`\n * on first save, because the write check reads the table and the table came\n * from the file, while the column itself existed;\n * - relations missing from the module were silently missing from list reads.\n *\n * The catalogue cannot drift from itself. What the generated module is still\n * good for is telling the developer their file is stale — see\n * `generated-schema-diff.ts`, which is now its only reader at boot.\n */\nimport type { PgTable } from \"drizzle-orm/pg-core\";\nimport type { Relations } from \"drizzle-orm\";\nimport { CollectionConfig } from \"@rebasepro/types\";\nimport { fieldKeyForColumn, getJunctionCollectionConfig, getTableName, resolveJunctionSpecs } from \"@rebasepro/common\";\n\nimport { buildDrizzleRelationsFromSchema, buildDrizzleTablesFromSchema, type ColumnKeyResolver } from \"./dynamic-tables\";\nimport { bareTableName, buildDrizzleRelationsFromCollections } from \"./config-relations\";\nimport { introspectSchema, type Queryable } from \"./introspect-runtime\";\nimport type { TableMeta } from \"./introspect-db-logic\";\nimport { searchColumnNames } from \"./search-column\";\n\n/** The Postgres schema a collection's table lives in. */\nexport function schemaOfCollection(collection: CollectionConfig, fallback: string): string {\n const declared = (collection as { schema?: unknown }).schema;\n return typeof declared === \"string\" && declared.length > 0 ? declared : fallback;\n}\n\n/**\n * How a column of each table is keyed in the Drizzle object.\n *\n * The wire name, which is the property key — `authorId`, never `author_id` —\n * because that is what every read and write path in this driver indexes tables\n * and rows by, and what the generated module has always emitted. Getting this\n * wrong does not throw: drizzle leaves an unknown key out of the statement and\n * the write reports success having stored nothing.\n *\n * Two exceptions, both columns no property declares:\n *\n * - the generated search columns keep their raw names, because `search_vector`\n * is how `searchColumnNames` excludes them from responses and how the\n * condition builder finds the one to match against;\n * - a table no collection claims (a junction) is keyed by its **key** columns'\n * names, which is also what the generated file does for them — but a\n * junction that declares `through.properties` keys those payload columns by\n * their property key, for the same reason a collection does. `joinedAt` with\n * `columnName: \"joined_at\"` has to be one name on both sides or a `_pivot`\n * write is silently dropped by drizzle and the read serves a key the caller\n * never declared.\n */\nexport function columnKeysFromCollections(\n collections: CollectionConfig[]\n): ColumnKeyResolver {\n const byTable = new Map<string, CollectionConfig>();\n const rawColumns = new Map<string, Set<string>>();\n for (const collection of collections) {\n const table = bareTableName(getTableName(collection));\n if (!table) continue;\n byTable.set(table, collection);\n rawColumns.set(table, new Set(searchColumnNames(collection)));\n }\n // The junctions' payload columns, as the synthetic collection that owns\n // them — the same one the planner emitted the columns from, so the runtime\n // table and the generated one cannot key them differently.\n const junctionByTable = new Map<string, CollectionConfig>();\n for (const spec of resolveJunctionSpecs(collections).values()) {\n if (Object.keys(spec.properties).length === 0) continue;\n junctionByTable.set(bareTableName(spec.table), getJunctionCollectionConfig(spec));\n }\n return (tableName, columnName) => {\n const collection = byTable.get(tableName);\n if (!collection) {\n const junction = junctionByTable.get(tableName);\n return junction ? fieldKeyForColumn(junction, columnName) : columnName;\n }\n if (rawColumns.get(tableName)?.has(columnName)) return columnName;\n return fieldKeyForColumn(collection, columnName);\n };\n}\n\nexport interface CatalogueSchema {\n /** Drizzle tables, keyed by bare table name. */\n tables: Record<string, PgTable>;\n /** Drizzle relations, keyed `<table>Relations`. */\n relations: Record<string, Relations>;\n /** Introspected metadata per table — what the boot diff compares against. */\n meta: Map<string, TableMeta>;\n /** Tables a collection declares that the database does not have. */\n missing: string[];\n}\n\n/**\n * Read the tables this data source serves out of `information_schema`.\n *\n * Restricted to the collections handed in, because the driver is given every\n * collection the project declares while each database holds only its own: an\n * unfiltered read had a second source (`database(\"analytics\")`) reporting the\n * default's tables as its own. Junction tables come too — they back no\n * collection and a many-to-many is unreadable without them.\n *\n * Every schema the collections name is read, not just `public`. The users\n * collection lives in `rebase`, and a run restricted to `public` would leave the\n * registry with no table for it at all.\n */\nexport async function readCatalogueSchema(options: {\n client: Queryable;\n collections: CollectionConfig[];\n defaultSchema: string;\n}): Promise<CatalogueSchema> {\n const { client, collections, defaultSchema } = options;\n\n /** schema → the tables wanted from it. */\n const wanted = new Map<string, Set<string>>();\n const want = (schema: string, table: string) => {\n const set = wanted.get(schema) ?? new Set<string>();\n wanted.set(schema, set);\n set.add(table);\n };\n\n for (const collection of collections) {\n const table = bareTableName(getTableName(collection));\n if (table) want(schemaOfCollection(collection, defaultSchema), table);\n }\n // Junctions are created in `public`, full stop — `resolveJunctionSpecs`\n // hardcodes it and the three generators agree with it.\n for (const spec of resolveJunctionSpecs(collections).values()) {\n want(spec.schema, bareTableName(spec.table));\n }\n\n const columnKey = columnKeysFromCollections(collections);\n const tables: Record<string, PgTable> = {};\n const meta = new Map<string, TableMeta>();\n const missing: string[] = [];\n\n for (const [schema, names] of wanted) {\n const live = await introspectSchema(client, schema);\n const present = new Map<string, TableMeta>();\n for (const name of names) {\n const found = live.tablesMap.get(name);\n if (!found) {\n missing.push(schema === defaultSchema ? name : `${schema}.${name}`);\n continue;\n }\n // First schema to carry a name wins. Two schemas holding a table of\n // the same name is the `search_path` hazard the drift report\n // explains; the registry is keyed by bare name either way, so this\n // only picks which one is served — the one whose collection asked.\n if (tables[name]) continue;\n present.set(name, found);\n meta.set(name, found);\n }\n Object.assign(tables, buildDrizzleTablesFromSchema(present, schema, columnKey));\n }\n\n return {\n tables,\n relations: buildDrizzleRelationsFromCollections(collections, tables),\n meta,\n missing\n };\n}\n\n/**\n * The BaaS variant: no collections are declared, so every table in one schema is\n * served and both the collections and the relations are derived from the\n * foreign keys.\n *\n * Kept apart from {@link readCatalogueSchema} because the two answer different\n * questions. Here the database is the entire specification; there it supplies\n * the columns for a specification the project already wrote down.\n */\nexport function buildBaasSchema(\n tablesMap: Map<string, TableMeta>,\n pgSchemaName: string,\n collections: CollectionConfig[]\n): { tables: Record<string, PgTable>; relations: Record<string, Relations> } {\n const columnKey = columnKeysFromCollections(collections);\n const tables = buildDrizzleTablesFromSchema(tablesMap, pgSchemaName, columnKey);\n return {\n tables,\n relations: buildDrizzleRelationsFromSchema(tablesMap, tables, columnKey)\n };\n}\n","/**\n * Compare the committed `schema.generated.ts` against the live catalogue, and\n * say so — once per difference, at boot, as a warning and nothing more.\n *\n * The runtime stopped reading that file for its tables (see\n * `catalogue-schema.ts`), which removes an entire class of production failure\n * and creates a smaller one: a stale file is now *invisible*. It is still real —\n * it is what Atlas plans migrations from, what `db push` diffs, what `eject`\n * writes out and what user code imports — so a project whose file no longer\n * describes its database will hit it later, in a tool, with no clue that boot\n * already knew.\n *\n * Hence this: a guard rather than a doc. It never fails boot and never changes\n * what is served — the previous arrangement, where the file could do both,\n * is exactly what was removed.\n *\n * `generated-schema-staleness.ts` answers a narrower question (does the file\n * name every table the collections do) from the file's *source text*. This one\n * compares the loaded module against the database, column by column.\n */\nimport { getTableColumns, getTableName as drizzleTableName, isTable } from \"drizzle-orm\";\nimport type { PgTable } from \"drizzle-orm/pg-core\";\nimport { CollectionConfig } from \"@rebasepro/types\";\nimport { fieldKeyForColumn, getTableName } from \"@rebasepro/common\";\nimport { logger } from \"@rebasepro/server\";\n\nimport { AUTH_USERS_COLUMNS, isAuthCollection } from \"./auth-users-columns\";\nimport type { TableMeta } from \"./introspect-db-logic\";\nimport { bareTableName } from \"./config-relations\";\n\nexport type SchemaDifferenceKind =\n | \"missing-table\"\n | \"missing-column\"\n | \"extra-column\"\n | \"type\"\n | \"nullability\";\n\nexport interface SchemaDifference {\n kind: SchemaDifferenceKind;\n /** Bare table name, as both sides spell it. */\n table: string;\n /** The collection backed by that table, when one is. */\n collection?: string;\n /** The column, in database terms. */\n column?: string;\n /** The field it is served under — the property key, where there is one. */\n property?: string;\n /** What `schema.generated.ts` says. */\n generated?: string;\n /** What the database says. */\n live?: string;\n}\n\n/**\n * The families a boot warning is worth raising over.\n *\n * Deliberately coarse. `varchar(120)` and `text` are the same family because the\n * runtime treats them identically and a warning about the difference would fire\n * on every boot of a correct project — the widths still reach Postgres, which\n * enforces them. What this is looking for is the kind of drift that changes what\n * a value *is*: a number that became text, a timestamp that became a date, a\n * column that stopped existing.\n */\nexport function typeFamily(sqlType: string | undefined): string {\n if (!sqlType) return \"unknown\";\n let type = sqlType.trim().toLowerCase();\n const isArray = type.endsWith(\"[]\") || type.startsWith(\"_\");\n type = type.replace(/\\[\\]$/, \"\").replace(/^_/, \"\");\n // Strip the modifier: varchar(255), numeric(10,2), vector(1536), char(2).\n type = type.replace(/\\s*\\([^)]*\\)\\s*$/, \"\").trim();\n\n const family = FAMILIES[type] ?? type;\n return isArray ? `${family}[]` : family;\n}\n\nconst FAMILIES: Record<string, string> = {\n // strings\n text: \"text\",\n varchar: \"text\",\n \"character varying\": \"text\",\n bpchar: \"text\",\n char: \"text\",\n character: \"text\",\n citext: \"text\",\n name: \"text\",\n // integers\n int2: \"integer\",\n smallint: \"integer\",\n smallserial: \"integer\",\n int4: \"integer\",\n int: \"integer\",\n integer: \"integer\",\n serial: \"integer\",\n int8: \"bigint\",\n bigint: \"bigint\",\n bigserial: \"bigint\",\n // approximate and exact numerics: kept apart, because one rounds\n float4: \"float\",\n real: \"float\",\n float8: \"float\",\n \"double precision\": \"float\",\n numeric: \"numeric\",\n decimal: \"numeric\",\n money: \"numeric\",\n // the rest\n bool: \"boolean\",\n boolean: \"boolean\",\n uuid: \"uuid\",\n json: \"json\",\n jsonb: \"jsonb\",\n date: \"date\",\n time: \"time\",\n \"time without time zone\": \"time\",\n timetz: \"time\",\n \"time with time zone\": \"time\",\n timestamp: \"timestamp\",\n \"timestamp without time zone\": \"timestamp\",\n timestamptz: \"timestamptz\",\n \"timestamp with time zone\": \"timestamptz\",\n interval: \"interval\",\n bytea: \"bytea\",\n tsvector: \"tsvector\",\n tsquery: \"tsquery\",\n vector: \"vector\"\n};\n\n/** One catalogue column, reduced to the three facts this compares. */\ninterface CatalogueColumn {\n name: string;\n family: string;\n notNull: boolean;\n}\n\nfunction catalogueColumns(meta: TableMeta): Map<string, CatalogueColumn> {\n const columns = new Map<string, CatalogueColumn>();\n for (const col of meta.columns) {\n columns.set(col.column_name, {\n name: col.column_name,\n // `udt_name` over `data_type`: the latter reports \"ARRAY\" and\n // \"USER-DEFINED\" where the former is the concrete type, and an enum\n // has to compare by its own type name against the module's pgEnum.\n family: typeFamily(col.udt_name || col.data_type),\n notNull: col.is_nullable === \"NO\"\n });\n }\n return columns;\n}\n\n/**\n * Every way the generated module and the database disagree, for the tables both\n * describe.\n *\n * One-directional in one respect only: a table the module has and the database\n * does not is reported (that is the stale-file case), while a table the database\n * has and the module does not is left alone — a database may hold anything, and\n * the collections-vs-file question is `generated-schema-staleness`'s.\n */\nexport function diffGeneratedSchemaAgainstCatalogue(options: {\n /** The `tables` export of the bundle's schema module. */\n generated: Record<string, unknown> | undefined;\n /** What was introspected for this data source, keyed by bare table name. */\n catalogue: Map<string, TableMeta>;\n /** Collections of this source, so a column can be named as a property. */\n collections: CollectionConfig[];\n}): SchemaDifference[] {\n const { generated, catalogue, collections } = options;\n if (!generated) return [];\n\n const collectionByTable = new Map<string, CollectionConfig>();\n for (const collection of collections) {\n const table = getTableName(collection);\n if (table) collectionByTable.set(bareTableName(table), collection);\n }\n\n const differences: SchemaDifference[] = [];\n\n for (const value of Object.values(generated)) {\n if (!isTable(value)) continue;\n const table = drizzleTableName(value as PgTable);\n const collection = collectionByTable.get(table);\n const at = { table, collection: collection?.slug };\n\n const meta = catalogue.get(table);\n if (!meta) {\n // Only for a table a collection of this source claims: the module\n // carries every table of the project, including the ones another\n // data source holds, and those are legitimately absent here.\n if (collection) differences.push({ ...at, kind: \"missing-table\" });\n continue;\n }\n\n const live = catalogueColumns(meta);\n const seen = new Set<string>();\n\n // On the auth table, auth owns its own columns' contract and the\n // generated file deliberately does not carry it: `render-drizzle` drops\n // the columns a users collection does not declare (`is_anonymous`,\n // `tokens_valid_after`) because drizzle-kit creates no auth table, and\n // the collection's own property definitions say nothing about the\n // `NOT NULL DEFAULT` that `ensureAuthTablesExist` applies.\n //\n // Comparing them anyway reported seven differences on a project nobody\n // had touched yet, and prescribed `rebase schema generate` — which\n // regenerates the file from the same collection and cannot change any\n // of them. A warning that survives its own remedy teaches the reader to\n // ignore the next one, so these are not differences. A *type* mismatch\n // still is: that is the file and the database disagreeing about\n // something neither side intends.\n const authOwned = isAuthCollection(collection)\n ? new Set(AUTH_USERS_COLUMNS.map(c => c.column))\n : undefined;\n\n for (const [key, column] of Object.entries(getTableColumns(value as PgTable))) {\n const name = (column as { name?: string }).name ?? key;\n seen.add(name);\n const property = collection ? fieldKeyForColumn(collection, name) : key;\n const generatedFamily = typeFamily((column as { getSQLType?: () => string }).getSQLType?.());\n const generatedNotNull = Boolean((column as { notNull?: boolean }).notNull);\n\n const found = live.get(name);\n if (!found) {\n differences.push({ ...at, kind: \"missing-column\", column: name, property, generated: generatedFamily });\n continue;\n }\n if (found.family !== generatedFamily) {\n differences.push({\n ...at,\n kind: \"type\",\n column: name,\n property,\n generated: generatedFamily,\n live: found.family\n });\n }\n if (found.notNull !== generatedNotNull && !authOwned?.has(name)) {\n differences.push({\n ...at,\n kind: \"nullability\",\n column: name,\n property,\n generated: generatedNotNull ? \"NOT NULL\" : \"nullable\",\n live: found.notNull ? \"NOT NULL\" : \"nullable\"\n });\n }\n }\n\n for (const [name, column] of live) {\n if (seen.has(name)) continue;\n if (authOwned?.has(name)) continue;\n differences.push({\n ...at,\n kind: \"extra-column\",\n column: name,\n property: collection ? fieldKeyForColumn(collection, name) : name,\n live: column.family\n });\n }\n }\n\n return differences;\n}\n\n/** One line, in the terms the reader has: collection, property, column. */\nexport function describeSchemaDifference(difference: SchemaDifference): string {\n const where = difference.collection\n ? `${difference.collection} (table \"${difference.table}\")`\n : `table \"${difference.table}\"`;\n const field = difference.column\n ? `${difference.property && difference.property !== difference.column\n ? `${difference.property} → \"${difference.column}\"`\n : `\"${difference.column}\"`}`\n : undefined;\n\n switch (difference.kind) {\n case \"missing-table\":\n return `${where} is in schema.generated.ts but not in the database`;\n case \"missing-column\":\n return `${where}: ${field} is in schema.generated.ts but not in the database`;\n case \"extra-column\":\n return `${where}: ${field} is in the database but not in schema.generated.ts (${difference.live})`;\n case \"type\":\n return `${where}: ${field} is ${difference.generated} in schema.generated.ts and ${difference.live} in the database`;\n case \"nullability\":\n return `${where}: ${field} is ${difference.generated} in schema.generated.ts and ${difference.live} in the database`;\n }\n}\n\n/**\n * Report the differences, one warning each, and never throw.\n *\n * Per difference rather than one summary block on purpose: these are read out of\n * a log aggregator as often as a terminal, and a single multi-line message\n * cannot be filtered down to the one collection somebody is looking at.\n */\nexport function warnOnGeneratedSchemaDrift(differences: SchemaDifference[]): void {\n if (differences.length === 0) return;\n\n logger.warn(\n `📄 [schema] schema.generated.ts no longer describes the database (${differences.length} difference` +\n `${differences.length === 1 ? \"\" : \"s\"}). The server serves the database, so nothing here is broken — ` +\n \"but Atlas, `rebase db push`, `rebase eject` and any code importing that file are all reading a stale \" +\n \"description. Run `rebase schema generate`.\"\n );\n\n for (const difference of differences) {\n logger.warn(` • ${describeSchemaDifference(difference)}`, {\n schemaDrift: {\n kind: difference.kind,\n table: difference.table,\n collection: difference.collection,\n column: difference.column,\n property: difference.property,\n generated: difference.generated,\n live: difference.live\n },\n fix: \"rebase schema generate\"\n });\n }\n}\n","/**\n * PostgresBootstrapper\n *\n * Implements the `BackendBootstrapper` interface for PostgreSQL.\n */\n\nimport { Relations, sql } from \"drizzle-orm\";\nimport { NodePgDatabase } from \"drizzle-orm/node-postgres\";\nimport { PgTable } from \"drizzle-orm/pg-core\";\nimport { asRebasePgTable, type RebasePgTable } from \"./types\";\nimport {\n type AuthAdapter,\n BackendBootstrapper,\n BootstrappedAuth,\n DatabaseAdmin,\n type DataDriver,\n CollectionConfig,\n DEFAULT_DATA_SOURCE_KEY,\n isRelationalCollectionConfig,\n type HistoryConfig,\n InitializedDriver,\n RealtimeProvider,\n type RealtimeChannelsConfig,\n type RealtimeSocketOptions\n} from \"@rebasepro/types\";\nimport { PostgresBackendDriver } from \"./PostgresBackendDriver\";\nimport { RealtimeService, parseMaxSubscriptionsPerSocket } from \"./services/realtimeService\";\nimport { buildCollectionRegistry } from \"./collections/buildRegistry\";\nimport { DatabasePoolManager } from \"./databasePoolManager\";\nimport { PostgresCollectionRegistry } from \"./collections/PostgresCollectionRegistry\";\nimport { createEmailService, type EmailConfig, type EmailService, logger } from \"@rebasepro/server\";\nimport { getTableName as getCollectionTableName, sqlRows } from \"@rebasepro/common\";\nimport { ensureAuthTablesExist } from \"./auth/ensure-tables\";\nimport { probeAuthSchema, resolveAuthSchema } from \"./auth/schema-version\";\nimport { AuthSchemaTables, PostgresAuthRepository, UserService } from \"./auth/services\";\nimport { createAuthSchema } from \"./schema/auth-schema\";\nimport { HistoryService } from \"./history/HistoryService\";\nimport { ensureHistoryTableExists } from \"./history/ensure-history-table\";\nimport { patchPgArrayNullSafety } from \"./utils/pg-array-null-patch\";\nimport { patchPgNumericToNumber } from \"./utils/pg-numeric-number-patch\";\nimport { buildCollectionsFromSchema, introspectSchema, readRlsStatus } from \"./schema/introspect-runtime\";\nimport { buildBaasSchema, readCatalogueSchema } from \"./schema/catalogue-schema\";\nimport { diffGeneratedSchemaAgainstCatalogue, warnOnGeneratedSchemaDrift } from \"./schema/generated-schema-diff\";\nimport type { TableMeta } from \"./schema/introspect-db-logic\";\nimport { detectConnectionPosture, ensureAppRole, revokeUserRoleSql, validatePolicyPgRoles, warnOnAnonymousGrants, warnOnLegacyRlsFunctions, warnOnRoleSchemaCollision, REBASE_USER_ROLE, type RawSqlRunner } from \"./security/rls-enforcement\";\nimport { provisionTriggerCdc, refreshInstalledCdcFunction, type CdcTableRef } from \"./services/cdc/trigger-cdc\";\nimport { collectJunctionLinks } from \"./services/cdc/junction-tables\";\nimport { collectionKeyColumns } from \"./services/cdc/identity-columns\";\nimport { createChannelBus, resolveChannelBusSetting } from \"./services/channel-bus\";\nimport { isChannelBusInstance } from \"@rebasepro/types\";\nimport { configureUnknownFilterFields, type UnknownFilterFieldsMode } from \"./utils/drizzle-conditions\";\n\nexport interface PostgresDriverConfig {\n connectionString?: string;\n adminConnectionString?: string;\n readConnectionString?: string;\n connection?: unknown;\n /**\n * The project's generated Drizzle module, for **diagnostics only**.\n *\n * The tables this driver serves are read from `information_schema` at boot\n * (see `catalogue-schema.ts`); the only thing this is used for is telling\n * the developer that their committed `schema.generated.ts` no longer\n * describes the database, which still matters for Atlas, `db push`, `eject`\n * and any code importing it. Leaving it out costs the warning and nothing\n * else.\n */\n schema?: {\n tables?: Record<string, unknown>;\n enums?: Record<string, unknown>;\n relations?: Record<string, unknown>;\n };\n /**\n * PostgreSQL schema to read when deriving collections from the database\n * (BaaS mode). Defaults to `public`.\n */\n introspectionSchema?: string;\n /**\n * Realtime options, both opt-in:\n *\n * - `channels` — retention. Without rules no channel keeps any history and\n * broadcast stays fire-and-forget. See {@link ChannelRetentionRule}.\n * - `bus` — the cross-instance transport for channel broadcast and\n * presence. Defaults to in-process only, which is correct for a single\n * instance and wrong for two. See {@link ChannelBusConfig}.\n */\n realtime?: RealtimeChannelsConfig;\n /**\n * What to do with a filter field that resolves to no column at all.\n * Defaults to `\"error\"` — a filter that cannot be compiled would otherwise\n * be dropped, and a dropped condition can only widen the result set.\n * Set to `\"warn\"` to restore the pre-fix behaviour of dropping it silently.\n */\n unknownFilterFields?: UnknownFilterFieldsMode;\n}\n\n/**\n * Opaque internals bag that PostgresBootstrapper stores during `initializeDriver()`\n * and re-uses in subsequent lifecycle hooks.\n */\nexport interface PostgresDriverInternals {\n db: NodePgDatabase<any>;\n readDb?: NodePgDatabase<any>;\n registry: PostgresCollectionRegistry;\n realtimeService: RealtimeService;\n driver: PostgresBackendDriver;\n poolManager?: DatabasePoolManager;\n /**\n * Attach CDC triggers to tables that did not exist when the driver\n * bootstrapped. Only set when database-level capture is actually active.\n *\n * Auth owns its own tables and creates them later in boot, so at driver\n * bootstrap they are legitimately missing and get skipped; without this\n * they would stay uninstrumented until the next restart.\n */\n provisionCdcForTables?: (tables: CdcTableRef[]) => Promise<void>;\n}\n\n// Re-export from shared CLI error utilities\nimport { deepestErrorMessage, diagnoseDbError, isEconnrefused, parseHostInfo } from \"./cli-errors\";\nimport { classifyConnectFailure } from \"./utils/pg-error-utils\";\n\n/**\n * Which table name the boot-time drift check should look for, for one collection.\n *\n * A declared `table` IS the table name, not a hint to be second-guessed. This\n * used to ask the registry whether it had indexed the declared name and fall\n * back to the SLUG when it had not — but \"the registry does not know this table\"\n * is exactly the condition the drift check exists to report, so the fallback\n * fired precisely when it was most harmful.\n *\n * A collection with `slug: \"usage-daily\", table: \"usage_daily\"` was reported as\n * missing table `usage-daily`: a name that does not exist, should never exist,\n * and that nobody can find by looking. Worse, the remediation the caller prints\n * says to run `rebase db push` — which would then CREATE that invented table\n * beside the correct one, the same \"second copy\" hazard the misplaced-schema\n * branch further down exists to prevent. Seen in production, where a correctly\n * migrated database reported drift on every boot.\n *\n * The slug is used only when nothing was declared, which is the config shape\n * where the slug genuinely is the table name.\n *\n * Exported for its own test: the caller needs a live pool and a real database,\n * and this is the part that was wrong.\n */\nexport function resolveDriftCheckName(\n col: CollectionConfig,\n registeredTableNames: string[]\n): string {\n const declaredTable = isRelationalCollectionConfig(col) ? col.table : undefined;\n return declaredTable\n ?? registeredTableNames.find((k) => k === col.slug)\n ?? col.slug;\n}\n\n/**\n * Why the tables this backend serves are not in the database — the part of the\n * drift warning that has to be true rather than merely plausible.\n *\n * The three answers need three different actions, and only the caller knows\n * which one applies. This warning used to assert the first (\"this runtime\n * applies the collection schema at boot unless REBASE_MIGRATE_ON_BOOT=none\")\n * and then point at that variable and at driver-version skew. For an app whose\n * boot path contained no provisioning step at all, every word of that was a\n * dead end: nothing read the variable, and the driver was current. The advice\n * cost an investigation, which is a strictly worse outcome than saying less.\n *\n * Exported for its own test: the surrounding check needs a live pool and a real\n * database, and this is the part that was wrong.\n */\nexport function describeSchemaDriftCause(\n provisioning: { attempted: boolean; reason?: string } | undefined\n): string[] {\n // A caller too old to send the signal gets no claim either way — just where\n // to look. Guessing is what got this wrong the first time.\n if (provisioning === undefined) {\n return [\n \" This runtime could not determine whether a schema-creation step ran\",\n \" before this check (the caller predates that signal).\",\n \" • Look for a \\\"Collection schema:\\\" line above. No such line at all\",\n \" means nothing tried to create these tables in this process.\"\n ];\n }\n if (provisioning.attempted) {\n return [\n \" A schema-creation step DID run this boot and these tables are still\",\n \" missing, so it did not create them — check the \\\"schema:\\\" lines above\",\n \" for what it did instead, and for DDL errors.\",\n \" • A collection routed to another engine or data source is not\",\n \" created here; that is reported separately at boot.\",\n \" • Otherwise this is a bug worth reporting, with those lines.\"\n ];\n }\n return [\n \" No schema-creation step ran this boot:\",\n ` ${provisioning.reason ?? \"no reason was given.\"}`,\n \" Resolve that reason — the drift is its consequence, not a separate\",\n \" problem, and re-running a migration tool will not change it.\"\n ];\n}\n\n/**\n * Is this the local database `rebase init` scaffolds — i.e. the one case where\n * \"you are connected as a superuser\" is not news?\n *\n * The scaffold's own `docker-compose.yml` sets `POSTGRES_USER: rebase_app`,\n * which makes that role the cluster superuser, so the superuser advisory below\n * was the only WARN a brand-new project ever saw and it was about a decision\n * the tool had made for the developer.\n *\n * Of the two available fixes — provision a non-superuser table-owner role in\n * the scaffold, or recognise the local shape and stay quiet — this is the\n * second, because the first breaks the scaffold it is meant to improve: a\n * non-superuser owner cannot `CREATE EXTENSION` (search collections need\n * `pg_trgm`/`unaccent`, applied by `rebase db push` and again by the boot\n * schema-ensure), so the very first `pnpm run db:push` on a scaffolded project\n * with a search block would fail. Trading a working first run for a quieter log\n * line is the wrong trade.\n *\n * The condition is deliberately narrow — a *non-production* process talking to\n * a database on the loopback interface. A genuine production superuser\n * connection still warns, and so does a non-production process pointed at a\n * remote database (the usual \"my dev machine writes to staging\" mistake, where\n * the advisory is exactly right). NODE_ENV alone would not do: the scaffold\n * ships `NODE_ENV=development` and some deployments inherit it.\n */\nexport function isScaffoldedLocalDatabase(connectionString: string | undefined): boolean {\n if (process.env.NODE_ENV === \"production\") return false;\n if (!connectionString) return false;\n let host: string;\n try {\n host = new URL(connectionString).hostname;\n } catch {\n return false;\n }\n // `new URL` keeps IPv6 literals in brackets.\n const bare = host.replace(/^\\[|\\]$/g, \"\").toLowerCase();\n return bare === \"localhost\"\n || bare === \"::1\"\n || bare === \"0.0.0.0\"\n || bare === \"\"\n || /^127\\./.test(bare)\n || bare.endsWith(\".localhost\");\n}\n\n/**\n * Default PostgreSQL bootstrapper.\n *\n * Use it to register Postgres with `initializeRebaseBackend()`:\n * ```typescript\n * initializeRebaseBackend({\n * ...config,\n * bootstrappers: [postgresBootstrapper()]\n * });\n * ```\n */\n/**\n * Where the collections schema stamp lives.\n *\n * The runtime's own internal schema, always — unlike the auth stamp, which\n * follows the users collection. `rebase` and `auth` sit outside\n * `introspectionSchema` by construction, so nothing here is ever served as a\n * collection.\n */\nconst SCHEMA_META_SCHEMA = \"rebase\";\n\n/**\n * One line about a statement the ensure could not apply and chose to survive.\n *\n * Each kind degrades differently and the operator's next move differs with it,\n * so they do not share a sentence. What they share is the shape: what is still\n * true, what is no longer true, and the database's own reason — never a bare\n * \"failed\", which is the version of this that taught nobody anything.\n */\nfunction describeEnsureFailure(failure: { kind: string; target: string; error: string }): string {\n switch (failure.kind) {\n case \"comment-column\":\n // The stamp records which `search` block the generated column was\n // built from. Without it the next boot cannot tell a changed block\n // from an unchanged one and adopts the column again instead of\n // refusing.\n return (\n `🔍 [schema] Could not record the search fingerprint on \"${failure.target}\" — search works, ` +\n `but a later change to the \\`search\\` block will not be detected: ${failure.error}`\n );\n case \"create-index\":\n // Not fatal, on purpose: the collection serves, every query returns\n // the same rows, and only latency changes. Said at `warn` every boot\n // because the alternative is a table that is permanently unindexed\n // and looks exactly like one that is not.\n return (\n `🐢 [schema] Could not create an index on \"${failure.target}\" — the collection serves and returns ` +\n \"the same rows, but queries that would have used this index will scan instead. Boot is not failed \" +\n `over an index: ${failure.error}`\n );\n default:\n return (\n `🔗 [schema] Could not add foreign key \"${failure.target}\" — the column exists and the ` +\n `collection still serves, but rows are not policed by this constraint: ${failure.error}`\n );\n }\n}\n\n/**\n * Say what a failed `SELECT 1` actually means, and whether boot can go on.\n *\n * Returns the error to throw, or `undefined` when the failure could be\n * transient and the pool may still recover.\n *\n * Two problems this exists to keep fixed. First, the logged error is Drizzle's\n * wrapper — `Failed query: SELECT 1` and a stack through drizzle internals —\n * while the sentence that actually says what is wrong (\"connect ECONNREFUSED\n * 127.0.0.1:5432\", \"password authentication failed for user …\", \"database …\n * does not exist\") sits in `.cause`, or inside the `AggregateError` a\n * dual-stack host produces. A developer with a stopped database, or a typo in\n * their `DATABASE_URL`, got two walls of stack trace and no cause.\n *\n * Second, \"continuing… the pool may recover\" is only true for a transient\n * fault. A wrong password or a missing database is settled: the next query\n * fails the same way, so the process died seconds later anyway — after printing\n * a reassurance.\n *\n * The banners come from `diagnoseDbError`, the same ones the CLI prints, so a\n * developer sees one diagnosis whether the database was unreachable during\n * `rebase db push` or during boot.\n */\nfunction diagnoseConnectFailure(err: unknown, connectionString: string | undefined): Error | undefined {\n const url = connectionString ?? \"\";\n const hostInfo = parseHostInfo(url);\n\n if (isEconnrefused(err)) {\n logger.error(diagnoseDbError(err, url) ?? `❌ Cannot connect to PostgreSQL at ${hostInfo}`);\n const reason = deepestErrorMessage(err) ?? \"connection refused\";\n return new Error(\n `Cannot connect to PostgreSQL at ${hostInfo}: ${reason}. Is the database running?`,\n { cause: err }\n );\n }\n\n const { fatal, reason, code } = classifyConnectFailure(err);\n const detail = code ? ` [${code}]` : \"\";\n\n if (fatal) {\n logger.error(\n diagnoseDbError(err, url) ??\n `\\n` +\n `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\\n` +\n ` ❌ PostgreSQL at ${hostInfo} refused the connection\\n` +\n `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\\n` +\n `\\n` +\n ` ${reason}${detail}\\n` +\n `\\n` +\n ` The server is reachable, so this is the credentials or the\\n` +\n ` database name in DATABASE_URL — check them in your .env.\\n` +\n `\\n` +\n `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\\n`\n );\n return new Error(`PostgreSQL at ${hostInfo} refused the connection: ${reason}${detail}`, { cause: err });\n }\n\n logger.error(`❌ Failed to connect to PostgreSQL at ${hostInfo}: ${reason}${detail}`, { error: err });\n return undefined;\n}\n\nexport function createPostgresBootstrapper(pgConfig: PostgresDriverConfig): BackendBootstrapper {\n // Applied at construction rather than threaded through every read: the\n // condition builder's static methods are reached from call sites that\n // carry no config. See `UnknownFilterFieldsMode`.\n if (pgConfig.unknownFilterFields) {\n configureUnknownFilterFields(pgConfig.unknownFilterFields);\n }\n\n /**\n * The handle the schema/policy hooks issue their DDL through.\n *\n * Both hooks run BEFORE `initializeDriver`, so `driverResult` is a stand-in\n * the caller may not have: the bundle path can synthesize one from the\n * connection its coordinator opened, but an application that built this\n * adapter itself never handed the framework a connection — it handed it to\n * *us*, as `pgConfig.connection`. Falling back to that is what lets a\n * self-built adapter provision at all; requiring the argument is what left\n * those apps with no tables and a 500 on every data route.\n *\n * Either handle is equivalent here. The driver's `schemaAwareDb` differs\n * only by the drizzle schema object registered on it — relevant to the query\n * builder, not to `execute(sql.raw(...))` — and every statement these hooks\n * emit is schema-qualified DDL, so neither depends on `search_path`.\n */\n /**\n * The drizzle handle itself, for the statements that want parameters.\n *\n * `provisioningQueryable` below hands back a `query(text)` shim because the\n * DDL it serves is built as text. The schema stamp writes a *value*, so it\n * wants the parameterised form — building that string by hand would be one\n * more place a quoted literal has to be got right for no benefit.\n */\n const provisioningDb = (driverResult?: InitializedDriver) => {\n const internals = driverResult?.internals as PostgresDriverInternals | undefined;\n return internals?.db ?? (pgConfig.connection as PostgresDriverInternals[\"db\"] | undefined);\n };\n\n const requireProvisioningDb = (driverResult?: InitializedDriver) => {\n const db = provisioningDb(driverResult);\n if (!db) {\n throw new Error(\n \"Cannot provision the collection schema: this Postgres adapter was created without a \" +\n \"`connection`, and no initialized driver was supplied to fall back on. Pass `connection` \" +\n \"to `createPostgresAdapter` (see `createPostgresDatabaseConnection`).\"\n );\n }\n return db;\n };\n\n const provisioningQueryable = (driverResult?: InitializedDriver) => {\n const db = requireProvisioningDb(driverResult);\n return {\n async query<T>(text: string): Promise<{ rows: T[] }> {\n return { rows: sqlRows<T>(await db.execute(sql.raw(text))) };\n }\n };\n };\n\n /**\n * Create the `rebase` schema and the helper functions every generated\n * policy calls — `rebase.uid()`, `rebase.roles()` and their siblings — on\n * whichever database the queryable points at.\n *\n * Idempotent and cheap to repeat: `CREATE SCHEMA IF NOT EXISTS` plus a\n * handful of `CREATE OR REPLACE FUNCTION`.\n */\n const provisionRlsRuntime = async (driverResult?: InitializedDriver): Promise<void> => {\n const db = requireProvisioningDb(driverResult);\n const { RLS_BOOTSTRAP_STATEMENTS } = await import(\"./schema/rls-bootstrap-sql\");\n // One statement per call — this handle speaks the extended query\n // protocol, which rejects multi-command strings. Advisory-locked\n // for the same reason the auth path is, and on the same key: two\n // instances booting against one fresh database must not race on\n // CREATE OR REPLACE.\n //\n // A transaction lock, inside the transaction the statements run in,\n // exactly as `auth/ensure-tables.ts` takes it. The handle is a pool,\n // and a session lock taken by one statement and released by another\n // could be released on a connection that never held it — behind a\n // transaction-mode pooler, a different server connection every time.\n // The unlock then answered `false`, the lock stayed on an idle pooled\n // connection, and the next boot waited on it until\n // `statement_timeout`. This one is released by the commit or the\n // rollback, on the connection that holds it.\n await db.transaction(async (tx) => {\n await tx.execute(sql`SELECT pg_advisory_xact_lock(hashtext('rebase_auth_functions_init'))`);\n for (const statement of RLS_BOOTSTRAP_STATEMENTS) {\n await tx.execute(sql.raw(statement));\n }\n });\n };\n\n /**\n * Tables the policies step could not lock, whose user-role privileges it\n * took back instead — `schema.table` → its parts.\n *\n * Kept because the boot grants again after that step: the final posture\n * check runs `ensureAppRole`, whose `GRANT … ON ALL TABLES IN SCHEMA`\n * covers these tables too. It handed every withdrawn privilege straight\n * back — RLS off, DML granted, every authenticated request reading every\n * row — while the log said the table was closed. So whatever grants\n * schema-wide after the policies step withholds these again.\n */\n const withheldTables = new Map<string, { schema: string; table: string }>();\n\n const withholdAgain = async (runSql: RawSqlRunner): Promise<void> => {\n for (const [qualified, { schema, table }] of withheldTables) {\n try {\n await runSql(revokeUserRoleSql(schema, table));\n } catch (err) {\n throw new Error(\n `Refusing to start: row-level security could not be enabled on \"${qualified}\", and the ` +\n `privileges the schema-wide grant just gave ${REBASE_USER_ROLE} on it could not be taken ` +\n \"back. The table would be served with no row filtering. Fix the database permissions \" +\n \"(the connection role must own the table, or be able to ALTER it) and boot again: \" +\n (err instanceof Error ? err.message : String(err))\n );\n }\n }\n };\n\n return {\n type: \"postgres\",\n\n async initializeDriver(config: unknown): Promise<InitializedDriver> {\n // config is passed from coordinator, we merge it with our internal pgConfig if needed\n // Currently config from init.ts is `{ collections, collectionRegistry, mode }`\n const { collections, collectionRegistry, introspectCollections, baas, schemaProvisioning, realtime } = config as {\n collections?: CollectionConfig[];\n collectionRegistry?: unknown;\n introspectCollections?: boolean;\n baas?: { unprotectedTables?: \"exclude\" | \"serve\" };\n schemaProvisioning?: { attempted: boolean; reason?: string };\n realtime?: { subscribe: boolean; provision: boolean };\n };\n // Absent means a caller that predates the field, and every one of\n // those is a single process that both serves websockets and owns the\n // schema. Defaulting to false here would silently disable realtime\n // for them — the failure this whole area is prone to.\n const realtimeSubscribes = realtime?.subscribe ?? true;\n const realtimeProvisions = realtime?.provision ?? true;\n // Secure by default: a table with no RLS is not served.\n const unprotectedTables = baas?.unprotectedTables ?? \"exclude\";\n\n const connection = pgConfig.connection;\n const rawClient = (connection && typeof connection === \"object\" && \"$client\" in connection\n ? (connection as Record<string, unknown>).$client\n : connection) as import(\"pg\").Pool;\n\n // ── No declared collections: derive the schema from the database ──\n // No collection files and no generated drizzle schema exist, so read\n // the live database and build both from what is actually there.\n let introspectedCollections: CollectionConfig[] | undefined;\n let introspectedTables: Record<string, PgTable> | undefined;\n let introspectedRelations: Record<string, Relations> | undefined;\n if (introspectCollections && (!collections || collections.length === 0)) {\n const pgSchemaName = pgConfig.introspectionSchema ?? \"public\";\n const schema = await introspectSchema(rawClient, pgSchemaName);\n\n // ── Only serve what the database protects ────────────────\n // Requests run as rebase_user, which is granted DML on the\n // schema. A table with RLS disabled therefore has no\n // authorization model at all: serving it hands every row to\n // every authenticated user. baas never runs `db push`, so\n // nothing here would have enabled RLS on the user's behalf.\n const rlsStatus = await readRlsStatus(rawClient, pgSchemaName);\n const unprotected = [...schema.tablesMap.keys()].filter(\n (t) => !schema.joinTables.has(t) && !rlsStatus.get(t)?.rlsEnabled\n );\n const policyless = [...schema.tablesMap.keys()].filter(\n (t) => !schema.joinTables.has(t) && rlsStatus.get(t)?.rlsEnabled && rlsStatus.get(t)?.policyCount === 0\n );\n\n if (unprotected.length > 0) {\n if (unprotectedTables === \"serve\") {\n logger.warn(\n `🔓 [rls] Serving ${unprotected.length} table(s) with row-level security DISABLED: ${unprotected.join(\", \")}. ` +\n \"Every authenticated request can read and write every row of these. \" +\n \"This is baas.unprotectedTables: \\\"serve\\\".\"\n );\n } else {\n logger.warn(\n `🔒 [rls] Not serving ${unprotected.length} table(s) — row-level security is disabled, so they have no ` +\n `authorization model: ${unprotected.join(\", \")}\\n` +\n unprotected.map((t) => ` ALTER TABLE \"${pgSchemaName}\".\"${t}\" ENABLE ROW LEVEL SECURITY; -- then add a policy`).join(\"\\n\") +\n \"\\n Set baas.unprotectedTables: \\\"serve\\\" to expose them regardless.\"\n );\n for (const t of unprotected) schema.tablesMap.delete(t);\n }\n }\n if (policyless.length > 0) {\n // Legal, and silently returns nothing — worth saying out loud,\n // since an empty table reads exactly like one with no rows.\n logger.warn(\n `🔒 [rls] ${policyless.length} table(s) have RLS enabled but no policies, so they will return no rows: ${policyless.join(\", \")}`\n );\n }\n\n introspectedCollections = buildCollectionsFromSchema(schema, pgSchemaName);\n // Tables keyed the way the collections above name their fields,\n // and relations derived from the foreign keys — the only\n // evidence a database with no config offers. Without the\n // relations, drizzle's relational path can't resolve the ones\n // those collections advertise.\n ({ tables: introspectedTables, relations: introspectedRelations } =\n buildBaasSchema(schema.tablesMap, pgSchemaName, introspectedCollections));\n logger.info(\n `🔍 [PostgresRegistry] BaaS mode: derived ${introspectedCollections.length} collections from schema \"${pgSchemaName}\" [${introspectedCollections.map(c => c.slug).join(\", \")}]`\n );\n }\n\n // ── Declared collections: read the tables back from the database ──\n //\n // Every source, the default included. The bundle's generated Drizzle\n // module used to be authoritative here for the default source, and a\n // file one commit behind the database changed what the server did:\n // a renamed foreign-key column 500'd every request in production, a\n // property boot-ensure had already created answered 400\n // VALIDATION_UNKNOWN_FIELDS on first save, and relations the module\n // lacked went silently missing from list reads. Boot-ensure runs\n // before this, so `information_schema` is the current, complete\n // description of what is actually there — and it cannot drift from\n // itself. The module is now read only to tell the developer their\n // file is stale (`warnOnGeneratedSchemaDrift`, below).\n //\n // Restricted to the collections that are this source's, because the\n // driver is handed every collection the project declares and must\n // not go looking for the default's tables in the analytics database.\n // Without that filter the registry held no table for `events`, and\n // every routed write failed with \"table not found\" on a database\n // that had the table.\n let ownTables: Record<string, PgTable> | undefined;\n let ownRelations: Record<string, Relations> | undefined;\n let ownCatalogue: Map<string, TableMeta> | undefined;\n let ownCollections: CollectionConfig[] = [];\n const sourceKey = (config as { dataSourceKey?: string }).dataSourceKey ?? DEFAULT_DATA_SOURCE_KEY;\n if (!introspectedCollections && collections && collections.length > 0) {\n ownCollections = collections.filter(c =>\n c.dataSource === sourceKey || (!c.dataSource && sourceKey === DEFAULT_DATA_SOURCE_KEY)\n );\n if (ownCollections.length > 0) {\n // Reading the catalogue is the first query this driver\n // issues, so it is the one that meets an unreachable\n // database — and the diagnosis that names the host, the\n // port and `docker compose up -d db` lives on the `SELECT\n // 1` further down, which would never be reached. Asked\n // here, in the same words, so a stopped database still says\n // so instead of failing inside the introspector.\n try {\n await rawClient.query(\"SELECT 1\");\n } catch (err) {\n const fatal = diagnoseConnectFailure(err, pgConfig.connectionString);\n if (fatal) throw fatal;\n }\n const catalogue = await readCatalogueSchema({\n client: rawClient,\n collections: ownCollections,\n defaultSchema: pgConfig.introspectionSchema ?? \"public\"\n });\n ownTables = catalogue.tables;\n ownRelations = catalogue.relations;\n ownCatalogue = catalogue.meta;\n if (catalogue.missing.length > 0) {\n logger.warn(\n `[PostgresRegistry] Data source \"${sourceKey}\" has no table yet for: ${catalogue.missing.join(\", \")}. ` +\n \"Their collections will answer \\\"table not found\\\" until the schema is provisioned.\"\n );\n }\n }\n }\n\n const activeCollections = introspectedCollections ?? collections;\n const schemaTables = introspectedTables ?? ownTables;\n const schemaRelations = introspectedRelations ?? ownRelations;\n\n // The one remaining reader of the generated module: a warning per\n // difference, and no effect on anything served. Wrapped because a\n // diagnostic must never be the reason a server does not start.\n if (ownCatalogue) {\n try {\n warnOnGeneratedSchemaDrift(diffGeneratedSchemaAgainstCatalogue({\n generated: pgConfig.schema?.tables,\n catalogue: ownCatalogue,\n collections: ownCollections\n }));\n } catch (err) {\n logger.debug(\"[schema] Could not compare schema.generated.ts against the database\", { error: err });\n }\n }\n\n // Create a fresh registry for this driver. Registration order is\n // load-bearing, so it lives in one place — see `buildCollectionRegistry`.\n //\n // No enums: a catalogue-built enum column is `text`, which is what a\n // generated `pgEnum` column served anyway (drizzle validates no\n // labels; Postgres does), and nothing reads the registry's enum map.\n const registry = buildCollectionRegistry({\n collections: activeCollections,\n tables: schemaTables,\n relations: schemaRelations\n });\n\n // Patch Drizzle's PgArray columns to handle NULL values safely.\n // Drizzle's mapFromDriverValue crashes with \"value.map is not a function\"\n // when a native array column (text[], integer[], etc.) contains NULL.\n if (schemaTables) {\n patchPgArrayNullSafety(schemaTables as Record<string, unknown>);\n // A `number` property is a `numeric` column unless it declared\n // itself integral, and Postgres sends `numeric` as text. Cast it\n // back here, where every read path goes through.\n patchPgNumericToNumber(schemaTables as Record<string, unknown>);\n }\n\n // Build schema-aware Drizzle connection\n const mergedSchema: Record<string, unknown> = {\n ...schemaTables,\n ...(schemaRelations || {})\n };\n const { drizzle: createDrizzle } = await import(\"drizzle-orm/node-postgres\");\n const schemaAwareDb = createDrizzle(rawClient, { schema: mergedSchema });\n\n // Verify connection — fail fast if the database is unreachable\n try {\n await schemaAwareDb.execute(sql`SELECT 1`);\n } catch (err: unknown) {\n const fatal = diagnoseConnectFailure(err, pgConfig.connectionString);\n if (fatal) throw fatal;\n logger.warn(\"⚠️ Continuing without initial database verification. Drizzle/PG will attempt to connect on subsequent queries.\");\n }\n\n // ── Which collections are THIS database's? ────────────────────────\n //\n // The driver is handed every collection the project declares —\n // relations resolve across sources, and the API routes by slug — but\n // three passes below speak about this database and only this one:\n // the change-capture attach, the drift check and the RLS/grants\n // pass. Reading the registry unfiltered made a second source\n // (`database(\"analytics\")`) report the default's tables as SCHEMA\n // DRIFT, try to attach CDC triggers to tables it does not hold, and\n // validate the default's policies against its own catalogue. Same\n // rule as the table read-back above.\n //\n // In BaaS mode the registry holds what THIS database was introspected\n // for, so every registered collection is already this source's.\n const introspectedHere = Boolean(introspectedCollections);\n const collectionsOnThisSource = (): CollectionConfig[] => {\n const all = registry.getCollections() as CollectionConfig[];\n if (introspectedHere) return all;\n return all.filter(c => c.dataSource === sourceKey || (!c.dataSource && sourceKey === DEFAULT_DATA_SOURCE_KEY));\n };\n\n // Create services\n const realtimeService = new RealtimeService(schemaAwareDb, registry);\n // The per-socket subscription ceiling. Checked here, before anything\n // else boots: a ceiling someone set and mistyped must not quietly\n // become another number.\n const ceilingFromEnv = process.env.REALTIME_MAX_SUBSCRIPTIONS_PER_SOCKET?.trim();\n if (ceilingFromEnv) {\n realtimeService.maxSubscriptionsPerSocket = parseMaxSubscriptionsPerSocket(\n ceilingFromEnv, \"REALTIME_MAX_SUBSCRIPTIONS_PER_SOCKET\"\n );\n } else if (pgConfig.realtime?.maxSubscriptionsPerSocket !== undefined) {\n realtimeService.maxSubscriptionsPerSocket = parseMaxSubscriptionsPerSocket(\n pgConfig.realtime.maxSubscriptionsPerSocket, \"realtime.maxSubscriptionsPerSocket\"\n );\n }\n\n // Initialize read replica connection if configured\n let readDb: import(\"drizzle-orm/node-postgres\").NodePgDatabase<any> | undefined;\n const readUrl = process.env.DATABASE_READ_URL;\n if (readUrl && readUrl !== pgConfig.connectionString) {\n try {\n const { createReadReplicaConnection } = await import(\"./connection\");\n const readResources = createReadReplicaConnection(readUrl, mergedSchema);\n readDb = readResources.db;\n logger.info(\"📖 [PostgresBootstrapper] Read replica connection established\");\n } catch (err) {\n logger.warn(\"⚠️ Could not connect to read replica, falling back to primary for all queries\", { error: err });\n }\n }\n const poolManager = pgConfig.adminConnectionString\n ? new DatabasePoolManager(pgConfig.adminConnectionString)\n : undefined;\n const driver = new PostgresBackendDriver(schemaAwareDb, realtimeService, registry, undefined, poolManager);\n realtimeService.setDataDriver(driver);\n\n // ── RLS enforcement (user context) ───────────────────────────────\n // Authenticated requests are authorized entirely by RLS policies,\n // but a privileged connection (superuser / BYPASSRLS / table owner)\n // bypasses RLS. Detect the posture and, when privileged, provision\n // the restricted `rebase_user` role and route authenticated\n // requests (reads AND writes) through it. The server context (base\n // driver / auth flows / `rebase.sql`) stays on the owner connection\n // and bypasses; `rebase.dataAsAdmin` does NOT — it is scoped with\n // `withAuth({ uid: \"service\", roles: [\"admin\"] })` at boot, so it\n // runs as `rebase_user` and its policies are evaluated.\n // Default-on: a privileged connection that cannot be\n // isolated fails the boot rather than serving unenforced requests.\n {\n const runSql: RawSqlRunner = async (text) => {\n const res = await schemaAwareDb.execute(sql.raw(text));\n return (res.rows ?? []) as Record<string, unknown>[];\n };\n // Said before anything else touches the schema: if the role and\n // a schema share a name, unqualified SQL from any tool that does\n // not pin `search_path` has been landing in the wrong place, and\n // that is worth knowing before reading the drift report below.\n await warnOnRoleSchemaCollision(runSql);\n\n const collectionSchemas = registry.getCollections()\n .map((c) => (c as { schema?: string }).schema)\n .filter((s): s is string => typeof s === \"string\");\n // `auth` is deliberately absent: the RLS helpers moved into\n // `rebase`, and granting USAGE on a schema Rebase does not\n // own would, on a Supabase database, hand the end-user role\n // access to theirs.\n const managedSchemas = [\"public\", \"rebase\", ...collectionSchemas];\n\n const posture = await detectConnectionPosture(runSql, managedSchemas);\n if (posture.privileged) {\n // Two different failures hide behind one call here, and they\n // deserve different answers. A connection that bypasses RLS\n // *right now* and cannot be switched must not serve: that is\n // the original contract and it stays fatal. A connection that\n // merely *could become* the owner — it owns nothing yet, but\n // may create tables in a schema the server provisions — is\n // constrained by RLS today, so refusing to boot over it would\n // break deployments that have been correct all along\n // (Postgres before 15 grants CREATE on public to PUBLIC, so\n // that describes a great many ordinary app roles).\n //\n // For the second case the answer is: try, warn, and ask again\n // once provisioning has run. See finalizeSecurityPosture.\n const bypassesNow = posture.superuser || posture.bypassRLS || posture.ownsTables;\n let switched = true;\n try {\n await ensureAppRole(runSql, managedSchemas);\n } catch (err) {\n if (bypassesNow) throw err;\n switched = false;\n logger.warn(\n `⚠️ RLS enforcement: connection role \"${posture.role}\" owns no tables yet and is ` +\n `subject to RLS, but it may create tables in ${managedSchemas.join(\", \")} — and a ` +\n \"table's owner bypasses every non-FORCE policy on it. The restricted role could not \" +\n `be provisioned: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n\n if (switched) {\n driver.rlsUserRole = REBASE_USER_ROLE;\n realtimeService.rlsUserRole = REBASE_USER_ROLE;\n const why = posture.superuser\n ? \"superuser\"\n : posture.bypassRLS\n ? \"BYPASSRLS\"\n : posture.ownsTables\n ? \"table owner\"\n : \"may create (and would then own) tables in the schemas it serves\";\n logger.info(`🔐 RLS enforcement active: authenticated requests run as \"${REBASE_USER_ROLE}\" (connection \"${posture.role}\" bypasses RLS: ${why})`);\n }\n\n if (posture.superuser || posture.bypassRLS) {\n const message =\n `The database connection runs as ${posture.superuser ? \"a superuser\" : \"a BYPASSRLS role\"} (\"${posture.role}\"). ` +\n `User requests are isolated via SET LOCAL ROLE, but connect as a non-superuser ` +\n `table-owner role in production so the server/owner context is least-privilege.`;\n if (isScaffoldedLocalDatabase(pgConfig.connectionString)) {\n logger.debug(`🔐 ${message}`);\n } else {\n logger.warn(`⚠️ ${message}`);\n }\n }\n } else {\n logger.info(`🔐 RLS enforcement: connection role \"${posture.role}\" is subject to RLS natively; no role switch needed.`);\n }\n\n // Independent of posture: a policy targeting a role the request\n // never runs as filters every row, so the collection reads as\n // empty rather than erroring. Applies to both branches — the\n // role that matters is whichever one requests actually use.\n await validatePolicyPgRoles(\n runSql,\n collectionsOnThisSource() as never,\n driver.rlsUserRole ?? posture.role\n );\n\n // The same habit one surface over, and the dangerous direction:\n // a rule that reads as \"signed in only\" but is true for every\n // caller grants the data away rather than hiding it.\n warnOnAnonymousGrants(collectionsOnThisSource() as never);\n\n // Raw policy SQL written against the pre-1.0 helper schema. It\n // is rewritten on compile, so this is the only place the project\n // is ever told the spelling moved.\n warnOnLegacyRlsFunctions(collectionsOnThisSource() as never);\n }\n\n // Ensure branch metadata table exists when branching is available\n if (driver.branchService) {\n try {\n await driver.branchService.ensureBranchMetadataTable();\n } catch (err) {\n logger.warn(\"⚠️ Could not initialize branch metadata table\", { error: err });\n }\n }\n\n // ── Channel history ──────────────────────────────────────────────\n // Opt-in per channel pattern. With no rules this creates no tables\n // and leaves broadcast on its original fire-and-forget path, so\n // presence-only apps pay nothing for it.\n try {\n await realtimeService.configureChannelHistory(\n pgConfig.realtime?.channels,\n { provision: realtimeProvisions }\n );\n } catch (err) {\n logger.warn(\"⚠️ Could not initialize channel history tables — retained channels will not replay\", { error: err });\n }\n\n // ── Realtime change source ───────────────────────────────────────\n // Prefer DATABASE_DIRECT_URL to bypass PgBouncer for LISTEN/NOTIFY.\n const directUrl = process.env.DATABASE_DIRECT_URL || pgConfig.connectionString;\n\n // ── Cross-instance channel bus ───────────────────────────────────\n // Entity changes already span instances (CDC / LISTEN below).\n // Channel broadcast and presence did not — they lived in per-process\n // maps, so behind two replicas the clients of one were invisible to\n // the other. Opt-in, and a no-op when left at \"memory\".\n try {\n const busSetting = resolveChannelBusSetting(pgConfig.realtime?.bus);\n // A supplied instance is always installed; a named built-in only\n // when it is not the in-process default, so the common case\n // touches none of this machinery.\n const wantsBus = isChannelBusInstance(busSetting) || busSetting.type !== \"memory\";\n if (wantsBus) {\n await realtimeService.configureChannelBus(\n createChannelBus(busSetting, {\n db: schemaAwareDb as NodePgDatabase<Record<string, unknown>>,\n directUrl\n })\n );\n }\n } catch (err) {\n logger.warn(\"⚠️ [ChannelBus] Could not configure the channel bus — channel broadcast and presence stay per-instance\", { error: err });\n }\n\n // Database-level Change Data Capture. When active, realtime events are\n // emitted for EVERY committed write — including ones that bypass the\n // Rebase API (psql, another service's cron, raw SQL, the Studio SQL\n // editor) — matching Supabase Realtime's WAL-tailing model. CDC also\n // becomes the cross-instance channel, so the legacy per-mutation\n // LISTEN/NOTIFY is not started alongside it.\n // REALTIME_CDC=auto → default: enable where the connection supports\n // it; silently fall back to app-level otherwise\n // REALTIME_CDC=trigger → force trigger-based capture (warns if it can't)\n // REALTIME_CDC=wal → prefer WAL logical replication (degrades to trigger)\n // REALTIME_CDC=off → app-level realtime only\n const validModes = new Set([\"auto\", \"wal\", \"trigger\", \"off\"]);\n let cdcMode = (process.env.REALTIME_CDC || \"auto\").trim().toLowerCase();\n if (!validModes.has(cdcMode)) {\n logger.warn(`⚠️ [CDC] Unknown REALTIME_CDC value \"${cdcMode}\" — expected auto|wal|trigger|off. Defaulting to \"auto\".`);\n cdcMode = \"auto\";\n }\n\n // `auto` tries CDC but treats \"can't\" as a normal outcome (info log);\n // explicit trigger/wal was asked for, so a failure is worth a warning.\n // A process that consumes nothing needs no capture *started*, and a\n // process that does not own the schema installs no triggers. They\n // come apart: the `api` in a split with an external migration Job\n // subscribes without provisioning, and both answers are correct.\n const wantsCdc = cdcMode !== \"off\" && (realtimeSubscribes || realtimeProvisions);\n const explicitCdc = cdcMode === \"trigger\" || cdcMode === \"wal\";\n let cdcEnabled = false;\n let provisionCdcForTables: PostgresDriverInternals[\"provisionCdcForTables\"];\n const cdcRunSql: RawSqlRunner = async (text) => {\n const res = await schemaAwareDb.execute(sql.raw(text));\n return (res.rows ?? []) as Record<string, unknown>[];\n };\n /** Whether this boot left the trigger function at the current body. */\n let cdcFunctionCurrent = false;\n\n if (wantsCdc && !directUrl) {\n const reason = \"no direct database connection is available for the realtime LISTEN client (set DATABASE_DIRECT_URL)\";\n if (explicitCdc) logger.warn(`⚠️ [CDC] REALTIME_CDC=${cdcMode} but ${reason} — using app-level realtime.`);\n else logger.info(`ℹ️ [CDC] Using app-level realtime — ${reason}.`);\n } else if (wantsCdc && directUrl) {\n if (cdcMode === \"wal\") {\n // Native WAL logical-replication streaming requires wal_level=logical,\n // a replication-privileged role and a replication slot, none of which\n // are bundled with this adapter yet. Degrade to trigger-based capture,\n // which provides equivalent database-level coverage.\n logger.warn(\n \"⚠️ [CDC] REALTIME_CDC=wal: native WAL streaming is not bundled in this build; \" +\n \"using trigger-based change capture instead (equivalent database-level coverage).\"\n );\n }\n try {\n // Each table is attached with the columns its rows are\n // addressed by, and the payload carries those and nothing\n // else — any login can LISTEN. See `buildCdcFunctionSql`.\n const cdcTables: CdcTableRef[] = collectionsOnThisSource()\n .map((c) => ({\n schema: (c as { schema?: string }).schema ?? \"public\",\n table: getCollectionTableName(c),\n identityColumns: collectionKeyColumns(c, registry).map((k) => k.columnName)\n }))\n .filter((t) => Boolean(t.table) && registry.hasTableForCollection(t.table));\n // Junction tables back no collection, so the list above misses\n // them — and a link or unlink is a write nobody would hear\n // about. Their rows are the contents of a child list, which is\n // as much a change as a write to the rows themselves.\n // Filtered by the parent's source for the same reason as the\n // list above: `posts_tags` lives in the database `posts`\n // lives in, and asking a second database to instrument it\n // produced one \"Could not attach change-capture trigger\"\n // warning per junction on every boot.\n const ownSlugs = new Set(collectionsOnThisSource().map(c => c.slug));\n for (const link of collectJunctionLinks(registry)) {\n if (!ownSlugs.has(link.parentCollection.slug)) continue;\n // A junction row is addressed by the two ids that name\n // the child list it belongs to, whatever its own key is.\n cdcTables.push({\n schema: link.schema,\n table: link.table,\n identityColumns: [link.sourceColumn, link.targetColumn]\n });\n }\n // Provisioning throws only when the connection can't create the\n // trigger function (insufficient privilege); enableCdc throws when\n // the LISTEN connection can't be established. Either → fall back.\n if (realtimeProvisions) {\n await provisionTriggerCdc(cdcRunSql, cdcTables);\n cdcFunctionCurrent = true;\n }\n if (realtimeSubscribes) await realtimeService.enableCdc(directUrl);\n cdcEnabled = true;\n // Boot steps that create their own tables (auth) run after\n // this one and use it to instrument what they just created.\n // Left undefined where this process installs nothing, so a\n // later boot step cannot re-enter the DDL path by the side\n // door — the callers already treat it as optional, because a\n // driver without CDC never sets it either.\n if (realtimeProvisions) {\n provisionCdcForTables = async (tables) => {\n await provisionTriggerCdc(cdcRunSql, tables);\n };\n }\n // Say which half ran. \"All writes now emit realtime events\"\n // is a claim about the database and stays true for a process\n // that only installed the triggers; what changes is whether\n // *this* process is listening, and an operator reading one\n // pod's log should not have to infer that from its role.\n logger.info(\n `📡 [CDC] Realtime source = database-level change capture (mode: ${cdcMode === \"wal\" ? \"wal→trigger\" : \"trigger\"}). ` +\n `All writes now emit realtime events regardless of origin.` +\n (realtimeSubscribes ? \"\" : \" This process installs the capture but does not consume it.\")\n );\n } catch (err) {\n if (explicitCdc) {\n logger.warn(\"⚠️ [CDC] Could not enable database-level change capture — falling back to app-level realtime.\", { error: err });\n } else {\n logger.info(\n \"ℹ️ [CDC] Database-level change capture unavailable (likely insufficient privileges to create triggers, \" +\n \"or the LISTEN connection was refused) — using app-level realtime. Set REALTIME_CDC=off to silence this.\",\n { detail: err instanceof Error ? err.message : String(err) }\n );\n }\n }\n }\n\n // Triggers an earlier boot attached keep firing whether or not this\n // one provisions capture, so a function from a version that sent\n // whole rows has to be replaced even when capture is off — any\n // login can LISTEN to what it sends. Only a function that is\n // already there: nothing is installed here.\n if (realtimeProvisions && !cdcFunctionCurrent) {\n try {\n await refreshInstalledCdcFunction(cdcRunSql);\n } catch (err) {\n logger.warn(\n \"⚠️ [CDC] A change-capture trigger function from an earlier boot is installed and could not be \" +\n \"brought up to date. Older versions of it send every changed row on a channel any database \" +\n \"login can LISTEN to; if capture is not wanted, remove it with \" +\n \"`DROP FUNCTION rebase.rebase_cdc_notify() CASCADE`.\",\n { detail: err instanceof Error ? err.message : String(err) }\n );\n }\n }\n\n // Legacy cross-instance realtime (app-level). Skipped when CDC is\n // active because CDC already spans instances.\n if (!cdcEnabled && directUrl && realtimeSubscribes) {\n try {\n await realtimeService.startListening(directUrl);\n } catch (err) {\n logger.warn(\"⚠️ Cross-instance realtime could not be started\", { error: err });\n }\n }\n\n // ── Startup Schema Validation ────────────────────────────────────\n // One-directional: only checks collections → DB (extra DB tables\n // that aren't mapped to collections are perfectly fine).\n try {\n const registeredCollections = collectionsOnThisSource();\n if (registeredCollections.length > 0) {\n // Deliberately unfiltered by schema: a table that is missing\n // from where the collection says it lives is very often\n // sitting in another schema entirely (see the misplaced-table\n // report below), and knowing *which* is the whole answer.\n const result = await schemaAwareDb.execute(sql.raw(`\n SELECT table_name, table_schema\n FROM information_schema.tables\n WHERE table_schema NOT IN ('pg_catalog', 'information_schema')\n AND table_type = 'BASE TABLE'\n `));\n const tablesByName = new Map<string, string[]>();\n for (const row of result.rows as Array<{ table_name: string; table_schema: string }>) {\n const schemas = tablesByName.get(row.table_name) ?? [];\n schemas.push(row.table_schema);\n tablesByName.set(row.table_name, schemas);\n }\n const dbTables = new Set(\n (result.rows as Array<{ table_name: string; table_schema: string }>).map(r =>\n r.table_schema === \"public\" ? r.table_name : `${r.table_schema}.${r.table_name}`\n )\n );\n const missing: Array<{ slug: string; table: string; foundIn: string[] }> = [];\n for (const col of registeredCollections) {\n // Auth owns its table and creates it later in this same\n // boot (initializeAuth → ensureAuthTablesExist), so it is\n // legitimately absent right now. Reporting it as drift\n // tells the user to `db:push` a table that is about to\n // exist — and on an introspected database, one that the\n // database was never supposed to hold.\n if ((col as { auth?: { enabled?: boolean } }).auth?.enabled) continue;\n\n const schemaName = \"schema\" in col && col.schema ? col.schema : \"public\";\n const checkName = resolveDriftCheckName(col, registry.getTableNames());\n const fullCheckName = schemaName === \"public\" ? checkName : `${schemaName}.${checkName}`;\n if (!dbTables.has(fullCheckName)) {\n // Report what was actually looked up: an unqualified\n // \"users\" sends people hunting for public.users.\n missing.push({ slug: col.slug,\ntable: fullCheckName,\nfoundIn: (tablesByName.get(checkName) ?? []).filter(s => s !== schemaName) });\n }\n }\n if (missing.length > 0) {\n const lines = missing.map(\n m => ` • collection \"${m.slug}\" → table \"${m.table}\"` +\n (m.foundIn.length > 0\n ? ` — but a table of that name exists in ${m.foundIn.map(s => `\"${s}\"`).join(\", \")}`\n : \"\")\n );\n // A table that exists under the same name in another\n // schema is not ordinary drift: it is almost always this\n // framework's own `search_path` hazard. Postgres resolves\n // unqualified SQL through `\"$user\", public`, and this\n // project creates a schema named `rebase` while every\n // template names the database role `rebase` too — so an\n // unqualified CREATE TABLE (a hand-written migration, the\n // SQL editor, drizzle-kit) lands in `rebase`, and the\n // runtime, which now pins `search_path=public`, cannot see\n // it. Say so, because \"missing table\" sends people to\n // re-run a push that will create a *second* copy.\n const misplaced = missing.filter(m => m.foundIn.length > 0);\n const misplacedHelp = misplaced.length === 0 ? [] : [\n \" Those tables exist — in the wrong schema. Unqualified SQL run by a\",\n \" role whose name matches a schema (`rebase` is both, by default)\",\n \" resolves through search_path's \\\"$user\\\" and creates there. Move them:\",\n ...misplaced.map(m =>\n ` ALTER TABLE \"${m.foundIn[0]}\".\"${m.table.split(\".\").pop()}\" SET SCHEMA \"${m.table.includes(\".\") ? m.table.split(\".\")[0] : \"public\"}\";`\n ),\n \" and qualify the SQL that created them, or pin search_path in DATABASE_URL\",\n \" (`?options=-c%20search_path%3Dpublic`).\",\n \"\"\n ];\n // What to tell the operator depends entirely on whether a\n // create step ran in this process, and the caller is the\n // only thing that knows. This warning used to assert that\n // it had (\"this runtime applies the collection schema at\n // boot unless REBASE_MIGRATE_ON_BOOT=none\") and send\n // people to that variable and to driver-version skew. For\n // an app whose boot path contained no provisioning step,\n // both were dead ends: nothing read that variable, and the\n // driver was current. Say which case this is instead of\n // guessing, and say nothing when the caller is too old to\n // tell us.\n const cause = describeSchemaDriftCause(schemaProvisioning);\n logger.warn([\n \"\",\n \"⚠️ SCHEMA DRIFT — the database is missing tables this backend serves:\",\n ...lines,\n \"\",\n ...misplacedHelp,\n ...cause,\n \"\",\n \" To apply this project's schema:\",\n \" • Managed cloud: the runtime creates tables and RLS at boot. `rebase db\",\n \" push` cannot reach a tenant's in-cluster database — redeploy instead.\",\n \" • Self-host: run `rebase db push` (dev) or `rebase db migrate` (prod)\",\n \" against DATABASE_URL.\",\n \"\"\n ].join(\"\\n\"));\n }\n }\n } catch (err) {\n logger.warn(\"⚠️ Startup schema validation could not run\", {\n error: err instanceof Error ? err.message : String(err)\n });\n }\n\n const internals: PostgresDriverInternals = {\n db: schemaAwareDb,\n readDb,\n registry,\n realtimeService,\n driver,\n poolManager,\n provisionCdcForTables\n };\n\n return {\n driver,\n realtimeProvider: realtimeService,\n collectionRegistry: registry,\n // Only set in baas mode — tells the server which collections the\n // database turned out to have.\n collections: introspectedCollections,\n internals\n };\n },\n\n async initializeAuth(config: unknown, driverResult: InitializedDriver): Promise<BootstrappedAuth | undefined> {\n const authConfig = config as Record<string, unknown> | undefined;\n if (!authConfig) return undefined;\n\n const internals = driverResult.internals as PostgresDriverInternals;\n const db = internals.db;\n const registry = internals.registry;\n\n // Resolve the auth collection from the explicit config.\n // This replaces the old `registry.getTable(\"users\")` magic string lookup.\n const authCollection = authConfig.collection as CollectionConfig | undefined;\n\n // ensureAuthTablesExist works with the collection abstraction — no Drizzle leakage.\n await ensureAuthTablesExist(db, authCollection);\n\n // The driver bootstrapped before these tables existed, so CDC skipped\n // them. Instrument them now, or writes to the user table emit no\n // realtime events until the next restart.\n if (authCollection && internals.provisionCdcForTables) {\n const authSchema = \"schema\" in authCollection && typeof authCollection.schema === \"string\"\n ? authCollection.schema\n : \"rebase\";\n const authTable = \"table\" in authCollection && typeof authCollection.table === \"string\"\n ? authCollection.table\n : authCollection.slug;\n if (authTable) {\n try {\n await internals.provisionCdcForTables([{\n schema: authSchema,\n table: authTable,\n // The auth table above all: its rows carry password\n // hashes and verification tokens, and the payload\n // names the row by its key and nothing else.\n identityColumns: collectionKeyColumns(authCollection, registry).map((k) => k.columnName)\n }]);\n } catch (err) {\n logger.warn(\n `⚠️ [CDC] Could not attach change-capture to the auth table \"${authSchema}.${authTable}\" — ` +\n \"writes to it won't emit database-level events.\",\n { detail: err instanceof Error ? err.message : String(err) }\n );\n }\n }\n }\n\n let emailService: EmailService | undefined;\n if (authConfig.email) {\n emailService = createEmailService(authConfig.email as EmailConfig);\n }\n\n // Resolve the Drizzle table for the internal UserService/AuthRepository.\n // These are internal Postgres-specific services that need the Drizzle table reference.\n const tableName = authCollection\n ? (\"table\" in authCollection && typeof authCollection.table === \"string\"\n ? authCollection.table\n : authCollection.slug)\n : undefined;\n const usersTable = tableName\n ? registry.getTable(tableName) as RebasePgTable | undefined\n : undefined;\n\n let usersSchemaName = \"rebase\";\n if (authCollection && \"schema\" in authCollection && typeof authCollection.schema === \"string\") {\n usersSchemaName = authCollection.schema;\n }\n\n // Named field by field through `asRebasePgTable`, not asserted\n // wholesale. `createAuthSchema` returns ten tables and\n // `AuthSchemaTables` declares five, so the assertion also claimed\n // the other five were absent while handing them straight through —\n // and if one of the five were ever renamed there, nothing here would\n // have said so.\n const schema = createAuthSchema(usersSchemaName);\n const authTables: AuthSchemaTables = {\n users: asRebasePgTable(schema.users),\n refreshTokens: asRebasePgTable(schema.refreshTokens),\n passwordResetTokens: asRebasePgTable(schema.passwordResetTokens),\n appConfig: asRebasePgTable(schema.appConfig),\n userIdentities: asRebasePgTable(schema.userIdentities)\n };\n if (usersTable) {\n authTables.users = usersTable as RebasePgTable;\n }\n\n const userService = new UserService(db, authTables);\n const authRepository = new PostgresAuthRepository(db, authTables);\n\n return { userService,\nemailService,\nauthRepository,\n// Bound to the same schema `ensureAuthTablesExist` just migrated, so the\n// health endpoint reports on the tables auth actually reads.\nschemaHealthCheck: () => probeAuthSchema(db, resolveAuthSchema(authCollection)) };\n },\n\n async initializeHistory(config: HistoryConfig, driverResult: InitializedDriver): Promise<{ historyService: HistoryService } | undefined> {\n if (!config) return undefined;\n\n const internals = driverResult.internals as PostgresDriverInternals;\n const db = internals.db;\n\n const inTransaction = await ensureHistoryTableExists(db);\n\n const retention = typeof config === \"object\" ? config.retention : undefined;\n const historyService = new HistoryService(db, retention ? { ttlDays: retention } : undefined, { inTransaction });\n\n return { historyService };\n },\n\n async initializeRealtime(_config: unknown, driverResult: InitializedDriver): Promise<RealtimeProvider | undefined> {\n const internals = driverResult.internals as PostgresDriverInternals;\n return internals.realtimeService;\n },\n\n /**\n * The cheapest round trip there is, run before boot's first real query.\n *\n * Boot provisions the collection schema *before* `initializeDriver`, so\n * the connection diagnosis above — the box naming the host, the port and\n * `docker compose up -d db` — never ran for the one failure it was\n * written for. A developer whose database was not running saw Drizzle's\n * `Failed query: [redacted]` and a stack through drizzle internals.\n *\n * Same diagnosis, one step earlier. Anything short of \"the database\n * answered\" is fatal here: provisioning is the next thing to happen and\n * it needs the connection, so continuing only moves the failure to a\n * frame with less to say about it.\n */\n async verifyConnection(driverResult?: InitializedDriver): Promise<void> {\n const db = provisioningDb(driverResult);\n // No handle to probe through is not a failure to report: an adapter\n // constructed without a `connection` is the case\n // `provisioningQueryable` already refuses, with a message about the\n // adapter rather than about the network.\n if (!db) return;\n try {\n await db.execute(sql`SELECT 1`);\n } catch (err: unknown) {\n throw diagnoseConnectFailure(err, pgConfig.connectionString)\n ?? new Error(\n `Cannot reach PostgreSQL at ${parseHostInfo(pgConfig.connectionString ?? \"\")} to provision the collection schema.`,\n { cause: err }\n );\n }\n },\n\n /**\n * Create any collection tables, columns and enum types the database is\n * missing — additively, never destructively.\n *\n * This is what lets the managed runtime boot a project against a fresh\n * database and actually serve it. Before this, only auth tables were\n * ensured, so a managed tenant came up with working sign-in and a 500 on\n * every data route.\n *\n * Runs through the drizzle handle's underlying session so it uses the\n * same connection (and therefore the same privileges) the driver already\n * proved it can bootstrap with.\n */\n async ensureCollectionSchema(\n collections: unknown[],\n driverResult?: InitializedDriver,\n log?: (message: string) => void\n ): Promise<{ applied: number }> {\n const { ensureCollectionTables } = await import(\"./schema/ensure-collection-tables\");\n // Runs through the drizzle handle the driver already bootstrapped\n // with, so it uses exactly the connection and privileges that were\n // proven to work. Every statement is DDL or a catalogue read with no\n // bindable values (schema names are identifiers), and the module\n // validates them before they reach a string.\n const queryable = provisioningQueryable(driverResult);\n const plan = await ensureCollectionTables(\n queryable,\n collections as Parameters<typeof ensureCollectionTables>[1],\n log\n );\n for (const failure of plan.failures) {\n logger.warn(describeEnsureFailure(failure));\n }\n return { applied: plan.actions.length - plan.failures.length };\n },\n\n /**\n * Apply the collections' RLS policies — ENABLE ROW LEVEL SECURITY and the\n * `securityRules` compiled to `CREATE POLICY` — so a freshly provisioned\n * database serves data instead of denying every user-context read.\n *\n * The companion to {@link ensureCollectionSchema}: that creates the\n * tables, this makes them servable. Boot runs it *after* auth\n * initialization, because the generated policies call `rebase.uid()` /\n * `rebase.roles()`, and `CREATE POLICY` validates those functions exist.\n *\n * Runs through the same drizzle handle, one statement at a time (that\n * handle speaks the extended query protocol, which rejects multi-command\n * strings). Failures are per-table and non-fatal: a table left un-policed\n * stays RLS-enabled, so it denies rather than leaks.\n */\n /**\n * The helper functions every generated policy calls — `rebase.uid()`,\n * `rebase.roles()` and their siblings — on THIS database.\n *\n * On the default source they arrive with the auth tables, because auth\n * lives there. A second database (`database(\"analytics\")`) has no auth\n * tables and used to have no helpers either, so every `CREATE POLICY`\n * on it failed and every user-context read was denied. Same statements\n * as the auth path and the migration preamble, from the same constant.\n */\n async ensureRlsRuntime(driverResult?: InitializedDriver): Promise<void> {\n await provisionRlsRuntime(driverResult);\n },\n\n async ensureCollectionPolicies(\n collections: unknown[],\n driverResult?: InitializedDriver,\n log?: (message: string) => void\n ): Promise<{ applied: number }> {\n const { ensureCollectionPolicies } = await import(\"./schema/ensure-collection-policies\");\n const queryable = provisioningQueryable(driverResult);\n // The order this depends on is set by the caller, and a caller that\n // gets it wrong fails silently-but-permanently: `CREATE POLICY`\n // validates the functions its expression calls, so every policy on\n // the source is refused and the table stays RLS-enabled with no way\n // in. That is what a second database did for a whole release — the\n // helpers arrived with the auth tables, and auth lives on the\n // default source only. So the invariant is checked here, where the\n // policies are actually written, instead of being an ordering rule\n // three modules away. One catalogue lookup when it already holds.\n const { rows } = await queryable.query<{ present: boolean }>(\n \"SELECT to_regprocedure('rebase.roles()') IS NOT NULL AS present\"\n );\n if (!rows[0]?.present) await provisionRlsRuntime(driverResult);\n const outcome = await ensureCollectionPolicies(\n queryable,\n collections as CollectionConfig[],\n log\n );\n // This run covers every collection on the source, so what it could\n // not lock is the whole set — a table locked since is dropped from it.\n withheldTables.clear();\n for (const u of outcome.unsecured.filter(u => u.grantWithdrawn)) {\n const dot = u.table.indexOf(\".\");\n withheldTables.set(u.table, { schema: u.table.slice(0, dot), table: u.table.slice(dot + 1) });\n }\n\n for (const skip of outcome.skipped) {\n logger.warn(`🔐 [rls] Policies not applied to \"${skip.table}\": ${skip.reason}`);\n }\n for (const failure of outcome.failures) {\n logger.warn(\n `🔐 [rls] Could not fully apply policies to \"${failure.table}\" — RLS is on, so it denies until this is resolved: ${failure.error}`\n );\n }\n\n // RLS could not be turned on at all. The schema-wide grant to the\n // user role has already run by this point, so without the revoke\n // below the table is readable and writable by every authenticated\n // request with no row filtering — the one state this boot path must\n // never leave behind, and the one it used to describe as \"locked\".\n const unrevoked = outcome.unsecured.filter(u => !u.grantWithdrawn);\n for (const u of outcome.unsecured.filter(u => u.grantWithdrawn)) {\n logger.error(\n `🔐 [rls] Could not enable row-level security on \"${u.table}\": ${u.error}. ` +\n `Its privileges have been revoked from ${REBASE_USER_ROLE}, so the table is ` +\n \"unreachable rather than unprotected. Reads and writes to that collection will \" +\n \"fail until RLS can be enabled.\"\n );\n }\n if (unrevoked.length > 0) {\n // Neither securable nor closable. Serving here would mean\n // handing every authenticated caller unfiltered access, so the\n // boot fails instead — this is the one failure that is not\n // survivable per-table.\n throw new Error(\n \"Refusing to start: row-level security could not be enabled on \" +\n unrevoked.map(u => `\"${u.table}\" (${u.error})`).join(\", \") +\n `, and the privileges granted to ${REBASE_USER_ROLE} could not be revoked either. ` +\n \"The table would be served with no row filtering. Fix the database permissions \" +\n \"(the connection role must own these tables, or be able to ALTER them) and boot again.\"\n );\n }\n\n // Retire the pre-1.0 `auth` schema now that the policies above no\n // longer call into it. Deliberately after, and deliberately quiet:\n // Postgres refuses to drop a function an RLS policy still\n // references, so on a database where some table has not been\n // recompiled yet this is expected to fail and succeed on a later\n // boot. See DROP_LEGACY_AUTH_SCHEMA_SQL for the guards that keep it\n // off a Supabase `auth` schema.\n try {\n const { dropLegacyAuthSchema } = await import(\"./schema/rls-bootstrap-sql\");\n await dropLegacyAuthSchema(\n async (text) => (await queryable.query<Record<string, unknown>>(text)).rows,\n { info: (m) => logger.info(m), warn: (m) => logger.warn(m) }\n );\n } catch (err) {\n logger.info(\n \"Left the legacy `auth` schema in place: \" +\n (err instanceof Error ? err.message : String(err))\n );\n }\n\n return { applied: outcome.policiesApplied };\n },\n\n /**\n * Read what the last provisioning boot recorded, or `null`.\n *\n * The meta schema is `rebase` rather than the auth stamp's — see\n * `schema/collections-schema-version.ts` for why the two can differ.\n */\n async readCollectionsSchemaVersion(driverResult?: InitializedDriver): Promise<string | null> {\n const db = provisioningDb(driverResult);\n if (!db) return null;\n const { readCollectionsSchemaVersion } = await import(\"./schema/collections-schema-version\");\n return readCollectionsSchemaVersion(db as never, SCHEMA_META_SCHEMA);\n },\n\n /** Record what this process just applied. Only the provisioning process calls this. */\n async stampCollectionsSchemaVersion(version: string, driverResult?: InitializedDriver): Promise<void> {\n const db = provisioningDb(driverResult);\n if (!db) return;\n const { stampCollectionsSchemaVersion } = await import(\"./schema/collections-schema-version\");\n await stampCollectionsSchemaVersion(db as never, SCHEMA_META_SCHEMA, version);\n },\n\n /**\n * Ask the RLS question again, now that the schema exists.\n *\n * `initializeDriver` decides the posture before anything auth or\n * introspection creates, and on a genuinely fresh database the catalogue\n * has nothing to answer with: the role owns no tables, so it does not\n * bypass, so no role switch is configured — and then this very process\n * creates the tables and owns every one of them. Table owners bypass all\n * non-FORCE policies, so from that point on the process serves every\n * request unfiltered, for its whole lifetime, while the boot log records\n * that RLS was fine. The next restart sees the tables and quietly fixes\n * it, which is why the state is so easy to miss.\n *\n * This runs after provisioning and after auth has created its own tables,\n * and asks once more. Three outcomes:\n *\n * - a switch is already configured — nothing to do;\n * - the connection still owns nothing — the original answer held;\n * - the connection now owns tables — the answer has changed under us, so\n * configure the switch, and if that is impossible, FAIL. A boot that\n * crashes with the reason in its logs is recoverable. A boot that\n * serves every row to everyone is not.\n */\n async finalizeSecurityPosture(driverResult: InitializedDriver): Promise<void> {\n const internals = driverResult.internals as PostgresDriverInternals;\n const { driver, realtimeService, registry, db } = internals;\n if (!db) return;\n\n const runSql: RawSqlRunner = async (text) => {\n const res = await db.execute(sql.raw(text));\n return (res.rows ?? []) as Record<string, unknown>[];\n };\n\n const collectionSchemas = registry.getCollections()\n .map((c) => (c as { schema?: string }).schema)\n .filter((s): s is string => typeof s === \"string\");\n const managedSchemas = [\"public\", \"rebase\", ...collectionSchemas];\n\n // The switch was configured at connect — and its grants were made\n // then, on the schemas that existed then. On a fresh database that\n // is `public` alone: `rebase` (the RLS helpers, the history table,\n // `rebase.record_history`, the job store) and a collection's own\n // schema are created by provisioning, after. The grant skipped for\n // a missing schema was never made, so every write to a\n // `history: true` collection answered \"permission denied for schema\n // rebase\" until a restart found the schema at connect time. Made\n // now, once provisioning has created them; idempotent otherwise.\n if (driver.rlsUserRole) {\n try {\n await ensureAppRole(runSql, managedSchemas);\n } catch (err) {\n // Fails closed — a missing grant refuses requests, it does\n // not widen them — so a warning, not a refusal to serve.\n logger.warn(\n `⚠️ RLS enforcement: could not grant \"${REBASE_USER_ROLE}\" on the schemas provisioning ` +\n `created (${managedSchemas.join(\", \")}). Requests that touch them will be refused with ` +\n `\"permission denied for schema …\" until a boot can: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n await withholdAgain(runSql);\n return;\n }\n\n const posture = await detectConnectionPosture(runSql, managedSchemas);\n // Only ownership matters here. `canCreateTables` was already weighed\n // at boot, and re-failing on it now would turn the tolerant branch\n // above into a hard failure by the back door.\n if (!posture.superuser && !posture.bypassRLS && !posture.ownsTables) return;\n\n try {\n await ensureAppRole(runSql, managedSchemas);\n } catch (err) {\n throw new Error(\n `The database connection role \"${posture.role}\" now owns tables in this database, so it ` +\n \"bypasses every row-level security policy on them — but the restricted role it should \" +\n \"switch to could not be provisioned, so authenticated requests would be served with no \" +\n `policy applied at all. Refusing to serve.\\n\\n${err instanceof Error ? err.message : String(err)}`\n );\n }\n await withholdAgain(runSql);\n\n driver.rlsUserRole = REBASE_USER_ROLE;\n realtimeService.rlsUserRole = REBASE_USER_ROLE;\n logger.info(\n `🔐 RLS enforcement active: the connection role \"${posture.role}\" became a table owner during ` +\n `this boot, so authenticated requests now run as \"${REBASE_USER_ROLE}\".`\n );\n },\n\n getAdmin(driverResult: InitializedDriver): DatabaseAdmin | undefined {\n const internals = driverResult.internals as PostgresDriverInternals;\n return internals.driver.admin;\n },\n\n mountRoutes(app: unknown, basePath: string, driverResult: InitializedDriver): void {\n // The coordinator handles auth/storage/data routes.\n // This hook is for driver-specific extensions only.\n // Currently Postgres doesn't need additional routes beyond what the coordinator mounts.\n },\n\n async initializeWebsockets(server: unknown, realtimeService: RealtimeProvider, driver: DataDriver, config?: unknown, adapter?: unknown, limits?: RealtimeSocketOptions): Promise<void> {\n const { createPostgresWebSocket, isSocketRealtimeService, socketRealtimeMissing } = await import(\"./websocket\");\n // Narrowed, not cast: on a multi-source project this is the routed\n // composite, and a cast to the Postgres service is what let the\n // socket call a method the composite did not have.\n if (!isSocketRealtimeService(realtimeService)) {\n throw new Error(\n \"The realtime service handed to the Postgres socket cannot serve it — it has no \" +\n `${socketRealtimeMissing(realtimeService).join(\", \")}. Every socket would fail on its first frame.`\n );\n }\n createPostgresWebSocket(\n server as import(\"http\").Server,\n realtimeService,\n driver as PostgresBackendDriver,\n config as { requireAuth?: boolean; jwtSecret?: string; serviceKey?: string },\n adapter as AuthAdapter | undefined,\n limits\n );\n }\n };\n}\n","import { DatabaseAdapter, InitializedDriver, RealtimeProvider, DataDriver, DatabaseAdmin, BootstrappedAuth } from \"@rebasepro/types\";\nimport { createPostgresBootstrapper } from \"./PostgresBootstrapper\";\nimport type { PostgresDriverConfig } from \"./PostgresBootstrapper\";\n\n/**\n * Creates a Postgres database adapter for Rebase.\n */\nexport function createPostgresAdapter(pgConfig: PostgresDriverConfig): DatabaseAdapter {\n const bootstrapper = createPostgresBootstrapper(pgConfig);\n\n return {\n type: bootstrapper.type,\n\n async initializeDriver(config) {\n return bootstrapper.initializeDriver(config);\n },\n\n async initializeRealtime(driverResult) {\n if (bootstrapper.initializeRealtime) {\n return bootstrapper.initializeRealtime({}, driverResult);\n }\n return undefined;\n },\n\n async initializeAuth(config, driverResult) {\n if (bootstrapper.initializeAuth) {\n return bootstrapper.initializeAuth(config, driverResult);\n }\n return undefined;\n },\n\n async initializeHistory(config, driverResult) {\n if (bootstrapper.initializeHistory) {\n return bootstrapper.initializeHistory(config, driverResult);\n }\n return undefined;\n },\n\n // `adapter` and `limits` are forwarded for the same reason the schema\n // hooks below are: dropping an argument here is invisible at the call\n // site and silent at runtime. `adapter` decided whether the realtime\n // socket authenticates at all — without it, a server whose auth comes\n // from an AuthAdapter fell back to \"is a jwtSecret set?\", answered no,\n // and admitted every client. `limits` are the data API's body and rate\n // limits; dropped, the socket has neither.\n initializeWebsockets(server, realtimeService, driver, config, adapter, limits) {\n if (bootstrapper.initializeWebsockets) {\n return bootstrapper.initializeWebsockets(server, realtimeService, driver, config, adapter, limits);\n }\n },\n\n // Forwarded so the boot-time schema/RLS provisioning is reachable when\n // this adapter is wrapped back into a bootstrapper. Omitting either left\n // a managed tenant with no tables (500 on every data route) or tables\n // but no policies (401 on every read) — the create step never ran.\n verifyConnection: bootstrapper.verifyConnection\n ? (driverResult) => bootstrapper.verifyConnection!(driverResult)\n : undefined,\n\n ensureCollectionSchema: bootstrapper.ensureCollectionSchema\n ? (collections, driverResult, log) =>\n bootstrapper.ensureCollectionSchema!(collections, driverResult, log)\n : undefined,\n\n // Same rule, and it bit at once: the method existed on the bootstrapper,\n // this wrapper did not forward it, and a second database booted with\n // tables and no helper functions — every policy on it silently failed\n // to create. The adapter type is where a forgotten forward should fail\n // to compile; `adapter-forwarding.test.ts` sweeps for exactly this.\n ensureRlsRuntime: bootstrapper.ensureRlsRuntime\n ? (driverResult) => bootstrapper.ensureRlsRuntime!(driverResult)\n : undefined,\n\n ensureCollectionPolicies: bootstrapper.ensureCollectionPolicies\n ? (collections, driverResult, log) =>\n bootstrapper.ensureCollectionPolicies!(collections, driverResult, log)\n : undefined,\n\n finalizeSecurityPosture: bootstrapper.finalizeSecurityPosture\n ? (driverResult) => bootstrapper.finalizeSecurityPosture!(driverResult)\n : undefined,\n\n // Same forwarding rule, third instance. Dropping these is not a type\n // error and not a runtime error: the stamp is simply never written and\n // never read, so a split deployment loses the only thing that would tell\n // it a unit is serving against a schema it was not built for — and a\n // check that is off looks exactly like a check that passed. This one was\n // in fact dropped on the first attempt, and the e2e caught it.\n readCollectionsSchemaVersion: bootstrapper.readCollectionsSchemaVersion\n ? (driverResult) => bootstrapper.readCollectionsSchemaVersion!(driverResult)\n : undefined,\n\n stampCollectionsSchemaVersion: bootstrapper.stampCollectionsSchemaVersion\n ? (version, driverResult) => bootstrapper.stampCollectionsSchemaVersion!(version, driverResult)\n : undefined,\n\n getAdmin(driverResult) {\n if (bootstrapper.getAdmin) {\n return bootstrapper.getAdmin(driverResult);\n }\n return undefined;\n },\n\n mountRoutes(app, basePath, driverResult) {\n if (bootstrapper.mountRoutes) {\n bootstrapper.mountRoutes(app, basePath, driverResult);\n }\n }\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuCA,IAAM,YAAU,IAAI,kBAA2C;;;;;;;;AAS/D,SAAgB,gBAAmB,QAAiC,IAAgB;CAChF,OAAO,UAAQ,IAAI,QAAQ,EAAE;AACjC;;;;;;;;;;AAWA,SAAgB,qBAA8C;CAC1D,OAAO,UAAQ,SAAS;AAC5B;;;;AC7BA,SAAgB,cAAc,KAAqC;CAK/D,OAAO;EACH,YAAY,OAAO,IAAI,eAAe,WAAW,IAAI,aAAa,KAAA;EAClE,UAAU,OAAO,IAAI,aAAa,WAAW,IAAI,WAAW,KAAA;EAC5D,SAAS,OAAO,IAAI,YAAY,YAAY,IAAI,UAAU,KAAA;CAC9D;AACJ;;;;;;;;;;;AAYA,SAAgB,kBACZ,IACA,OACA,aACO;CAUP,OAAO,gBAAgB,IATF,YAAY,KAAI,SAAQ;EACzC,MAAM,MAAM,MAAM,KAAK;EACvB,MAAM,OAAO,MAAM,cAAc,GAAG,IAAI,KAAA;EAGxC,IAAI,CAAC,MAAM,YAAY,OAAO;EAC9B,OAAO;GAAE,GAAG;GACR,QAAQ,KAAK,eAAe;EAAS;CAC7C,CAC2B,CAAY;AAC3C;;;;;;;;;;AAWA,SAAgB,qBACZ,OACA,aACA,IACA,gBACG;CAGH,MAAM,UAAU,kBAAkB,OAAO,aAAa,cAAc;CACpE,MAAM,SAAS,cAAc,IAAI,WAAW;CAC5C,MAAM,aAAa,QAAQ,KAAK,QAAQ,MAAM,GAAG,QAAQ,OAAO,YAAY,EAAE,CAAC,UAAU,CAAC;CAC1F,OAAO,WAAW,WAAW,IAAI,WAAW,KAAK,IAAI,GAAG,UAAU;AACtE;;;;;;;;AASA,SAAS,2BAA2B,OAA+B;CAC/D,IAAI;EAEA,OADe,eAAe,KACtB,CAAA,CAAO,cAAc,EAAE,EAAE,WAAW,CAAC;CACjD,QAAQ;EACJ,OAAO,CAAC;CACZ;AACJ;;;;;;;;;;;;AAaA,SAAS,gBAAgB,OAAgB,QAA6B;CAClE,KAAK,MAAM,CAAC,KAAK,cAAc,OAAO,QAAQ,gBAAgB,KAAK,CAAC,GAChE,IAAI,cAAc,UAAU,UAAU,SAAS,OAAO,MAAM,OAAO;CAEvE,OAAO,OAAO;AAClB;AAEA,SAAgB,oBAAoB,gBAAwB,UAAwD;CAChH,MAAM,aAAa,SAAS,oBAAoB,cAAc;CAC9D,IAAI,CAAC,YAAY;EACb,MAAM,aAAa,SAAS,eAAe,CAAC,CAAC,KAAI,MAAK,EAAE,IAAI,CAAC,CAAC,KAAK,IAAI;EACvE,MAAM,IAAI,MAAM,yBAAyB,eAAe,6BAA6B,WAAW,EAAE;CACtG;CACA,OAAO;AACX;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmCA,SAAgB,sBACZ,QACA,OACA,gBACI;CAKJ,MAAM,UAAU,gBAAgB,KAAK;CACrC,IAAI,CAAC,WAAW,OAAO,KAAK,OAAO,CAAC,CAAC,WAAW,GAAG;CAEnD,MAAM,UAAU,OAAO,KAAK,MAAM,CAAC,CAAC,QAAO,QAAO,EAAE,OAAO,QAAQ;CACnE,IAAI,QAAQ,WAAW,GAAG;CAO1B,MAAM,+BAAe,IAAI,IAAoB;CAC7C,KAAK,MAAM,CAAC,KAAK,WAAW,OAAO,QAAQ,OAAO,GAAG;EACjD,MAAM,OAAQ,QAA2C;EACzD,IAAI,OAAO,SAAS,YAAY,SAAS,KAAK,aAAa,IAAI,MAAM,GAAG;CAC5E;CACA,MAAM,aAAa,QAAQ,QAAO,QAAO,aAAa,IAAI,GAAG,CAAC;CAC9D,IAAI,WAAW,WAAW,QAAQ,QAC9B,MAAM,SAAS,WACX,IAAI,eAAe,iBAAiB,WAAW,KAAI,QAAO,IAAI,IAAI,EAAE,CAAC,CAAC,KAAK,IAAI,EAAE,KAC9E,WAAW,SAAS,IAAI,2BAA2B,wBAAwB,UACrE,WAAW,KAAI,QAAO,IAAI,aAAa,IAAI,GAAG,EAAE,EAAE,CAAC,CAAC,KAAK,IAAI,EAAE,4GAExE,2BACJ;CAaJ,MAAM,SAAS,WACX,IAAI,eAAe,iBAAiB,QAAQ,SAAS,IAAI,MAAM,GAAG,GAC/D,QAAQ,KAAI,QAAO,IAAI,IAAI,EAAE,CAAC,CAAC,KAAK,IAAI,EAAE,gBAAgB,QAAQ,SAAS,IAAI,MAAM,GAAG,2FAExD,QAAQ,SAAS,IAAI,QAAQ,OAChE,qHAEA,2BACJ;AACJ;;;;;;;;;;;;;;;AAgBA,SAAgB,sBAAsB,OAAe,QAA0B;CAC3E,OAAO,SAAS,SACZ,aAAa,MAAM,oDAAoD,OAAO,oPAI9E,wBACJ;AACJ;AAEA,SAAgB,sBAAsB,YAA8B,UAAoD;CACpH,MAAM,YAAY,eAAa,UAAU;CACzC,MAAM,QAAQ,SAAS,SAAS,SAAS;CACzC,IAAI,CAAC,OACD,MAAM,IAAI,MAAM,mCAAmC,WAAW,KAAK,YAAY,UAAU,EAAE;CAE/F,OAAO;AACX;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BA,SAAgB,eAAe,YAA8B,UAAwD;CAEjH,IAAI,WAAW,YAAY;EACvB,MAAM,UAAU,OAAO,QAAQ,WAAW,UAAU,CAAC,CAChD,QAAQ,CAAC,GAAG,UAAU,UAAW,QAAmB,QAAS,KAA4B,IAAI,CAAC,CAAC,CAC/F,KAAK,CAAC,KAAK,WAAW;GACnB,WAAW;GACX,MAAM,KAAK,SAAS,WAAW,WAAoB;GACnD,QAAS,KAA4B,SAAS;EAClD,EAAE;EAEN,IAAI,QAAQ,SAAS,GACjB,OAAO;CAEf;CAKA,MAAM,QAAQ,SAAS,SAAS,eAAa,UAAU,CAAC;CACxD,IAAI,CAAC,OAAO,OAAO,CAAC;CAGpB,MAAM,OAAyB,CAAC;CAChC,KAAK,MAAM,CAAC,KAAK,WAAW,OAAO,QAAQ,KAAK,GAAG;EAC/C,MAAM,MAAM;EACZ,IAAI,OAAO,OAAO,QAAQ,YAAY,aAAa,OAAO,IAAI,SAAS;GACnE,MAAM,OAAO,cAAc,GAAG;GAC9B,MAAM,OAAO,IAAI,aAAa,YAAY,KAAK,eAAe,cAAc,KAAK,eAAe,cAAc,WAAW;GACzH,MAAM,SAAS,KAAK,eAAe;GACnC,KAAK,KAAK;IAAE,WAAW;IACnC;IACA;GAAO,CAAC;EACA;CACJ;CAQA,IAAI,KAAK,WAAW,GAChB,KAAK,MAAM,UAAU,2BAA2B,KAAK,GAAG;EACpD,MAAM,OAAO,cAAc,MAAM;EACjC,KAAK,KAAK;GACN,WAAW,gBAAgB,OAAO,MAAM;GACxC,MAAM,OAAO,aAAa,YAAY,KAAK,eAAe,cAAc,KAAK,eAAe,cAAc,WAAW;GACrH,QAAQ,KAAK,eAAe;EAChC,CAAC;CACL;CAMJ,IAAI,KAAK,WAAW,KAAK,QAAQ,OAAO;EACpC,MAAM,QAAQ,MAAM;EACpB,MAAM,SAAS,cAAc,KAAK;EAClC,MAAM,OAAO,MAAM,aAAa,YAAY,OAAO,eAAe,cAAc,OAAO,eAAe,cAAc,WAAW;EAC/H,MAAM,SAAS,OAAO,eAAe;EACrC,KAAK,KAAK;GAAE,WAAW;GAC/B;GACA;EAAO,CAAC;CACJ;CAEA,OAAO;AACX;;;;;;;;;;;AAYA,SAAgB,mBAAmB,YAA8B,UAAwD;CACrH,MAAM,OAAO,eAAe,YAAY,QAAQ;CAChD,IAAI,KAAK,WAAW,GAChB,MAAM,IAAI,MACN,eAAe,WAAW,KAAK,yJAEnC;CAEJ,OAAO;AACX;;;;;;;;;;AAWA,SAAgB,kBACZ,OACA,aACA,gBACa;CACb,IAAI,YAAY,WAAW,GACvB,MAAM,IAAI,MAAM,eAAe,eAAe,uDAAuD;CAEzG,OAAO,YAAY,KAAI,SAAQ;EAC3B,MAAM,SAAS,MAAM,KAAK;EAC1B,IAAI,CAAC,QACD,MAAM,IAAI,MAAM,aAAa,KAAK,UAAU,uCAAuC,eAAe,EAAE;EAExG,OAAO;CACX,CAAC;AACL;;;;;;;;;;;;;AAcA,SAAgB,eACZ,UACA,kBACA,UACM;CAON,IAAI,SAAS,WAAW,OAAO,kBAAkB,kBAAkB,SAAS,SAAS;CACrF,OAAO,mBAAmB,kBAAkB,QAAQ,CAAC,CAAC,EAAE,CAAC;AAC7D;;;;;;;;;;AAWA,SAAgB,kBACZ,UACA,kBACA,UACO;CACP,IAAI,CAAC,SAAS,WAAW,OAAO;CAEhC,OAAO,kBAAkB,kBAAkB,SAAS,SAAS,MACrD,mBAAmB,kBAAkB,QAAQ,CAAC,CAAC,EAAE,CAAC;AAC9D;;;;;;;;;;;;;;;;;;;;;;;;AAyBA,SAAgB,+BACZ,aACA,UACyF;CACzF,MAAM,WAAsG,CAAC;CAE7G,KAAK,MAAM,cAAc,aAAa;EAElC,IAAI,uBAAuB,UAAU,CAAC,CAAC,SAAS,GAAG;EAInD,MAAM,OAAO,eAAe,YAAY,QAAQ;EAChD,IAAI,KAAK,WAAW,GAAG;EAIvB,IAAI,KAAK,WAAW,KAAK,KAAK,EAAE,CAAC,cAAc,MAAM;EAErD,SAAS,KAAK;GACV;GACA;GACA,sBAAsB,QAAQ,WAAW,YAAY,EAAE;EAC3D,CAAC;CACL;CAEA,OAAO;AACX;;;;;;;;;;;;;;;;;;;;;AAsBA,SAAS,oBAAoB,KAAqB,MAAwB,YAAsC;CAC5G,IAAI,KAAK,SAAS,GAAG,OAAO;CAE5B,MAAM,mCAAmB,IAAI,IAAY;CACzC,KAAK,MAAM,YAAY,OAAO,OAAO,2BAA2B,UAAU,CAAC,GACvE,IAAI,SAAS,SAAS,aAAa,iBAAiB,IAAI,kBAAkB,YAAY,SAAS,QAAQ,CAAC;CAE5G,KAAK,MAAM,CAAC,MAAM,aAAa,OAAO,QAAQ,WAAW,cAAc,CAAC,CAAC,GACrE,IAAK,SAAsB,SAAS,aAAa,iBAAiB,IAAI,IAAI;CAE9E,IAAI,iBAAiB,IAAI,IAAI,SAAS,GAAG,OAAO;CAEhD,IAAI,IAAI,QAAQ,OAAO;CACvB,IAAI,IAAI,SAAS,UAAU,OAAO;CAClC,OAAO;AACX;;;;;;;;AASA,SAAgB,gCACZ,aACA,UACI;CACJ,MAAM,WAAW,+BAA+B,aAAa,QAAQ;CACrE,IAAI,SAAS,WAAW,GAAG;CAE3B,MAAM,QAAQ,MAAwE;EAIlF,MAAM,aAAa,EAAE,KAAK,SAAQ,QAAO;GACrC,MAAM,QAAQ,EAAE,WAAW,aAAa,IAAI,WAAA,EAAqC;GACjF,OAAO,SAAS,KAAA,KAAa,SAAS,YAAY,SAAS,YAAY,SAAS,cAC1E,CAAC,KAAK,IAAI,UAAU,UAAU,KAAK,aAAa,IAChD,CAAC;EACX,CAAC;EACD,IAAI,WAAW,SAAS,GACpB,OAAO,GAAG,EAAE,WAAW,KAAK,qBAAqB,WAAW,KAAK,OAAO,EAAE,2GAElE,EAAE,KAAK,KAAI,QAAO,KAAK,IAAI,UAAU,GAAG,CAAC,CAAC,KAAK,IAAI,EAAE;EAEjE,MAAM,QAAQ,EAAE,KAAK,KAAI,SAAQ;GAC7B,OAAO,KAAK,IAAI,UAAU;GAC1B,UAAU,WAAW,oBAAoB,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE;EACxE,EAAE;EAEF,MAAM,OADO,MAAM,OAAM,SAAQ,KAAK,aAAa,MAAM,EAAE,CAAC,QAC/C,IACP,GAAG,MAAM,KAAI,SAAQ,KAAK,KAAK,CAAC,CAAC,KAAK,OAAO,EAAE,QAAQ,MAAM,EAAE,CAAC,aAChE,MAAM,KAAI,SAAQ,GAAG,KAAK,MAAM,QAAQ,KAAK,UAAU,CAAC,CAAC,KAAK,OAAO;EAG3E,MAAM,YAAY,EAAE,KAAK,SAAS,IAC5B,uDAAuD,EAAE,KAAK,KAAI,QAAO,IAAI,IAAI,UAAU,EAAE,CAAC,CAAC,KAAK,sBAAsB,EAAE,MAC5H;EACN,OAAO,GAAG,EAAE,WAAW,KAAK,SAAS,OAAO;CAChD;CAEA,MAAM,WAAW,SAAS,QAAO,MAAK,EAAE,oBAAoB;CAC5D,MAAM,SAAS,SAAS,QAAO,MAAK,CAAC,EAAE,oBAAoB;CAE3D,IAAI,SAAS,SAAS,GAClB,OAAO,KACH,sUAIA,SAAS,KAAI,MAAK,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,IAAI,IAAI,IACrD;CAGJ,IAAI,OAAO,SAAS,GAChB,OAAO,KACH,mOAGA,OAAO,KAAI,MAAK,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,IAAI,IAAI,IACnD;AAER;;;;;;;;;;AAWA,SAAgB,iBACZ,KACA,YACA,UACM;CACN,MAAM,YAAY,iBAAiB,KAAK,eAAe,YAAY,QAAQ,CAAC;CAG5E,IAAI,aAAa,UAAU,MAAM,sBAAsB,CAAC,CAAC,MAAK,SAAQ,SAAS,EAAE,GAC7E,OAAO;CAEX,IAAI,IAAI,OAAO,KAAA,KAAa,IAAI,OAAO,MAAM,OAAO,OAAO,IAAI,EAAE;CACjE,OAAO;AACX;;;;;;;;ACjmBA,IAAM,kCAAkC;;;;;;;;;AASxC,IAAM,yBAAyB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoD/B,IAAa,qBAAqB,UAC9B,MAAM,QAAQ,YAAW,OAAM,KAAK,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;AA0B5C,SAAS,qBAAqB,cAA6B;CACvD,uBAAO,IAAI,MACP,2CAA2C,aAAa,0RAI5D;AACJ;AAEA,IAAM,kBACF,OACA,YACA,WAC0B;CAC1B,MAAM,MAAM,kBAAkB,YAAY,MAAM;CAChD,QAAQ,OAAO,QAAQ,MAAM,OAA4C,KAAA,MAAc,KAAA;AAC3F;;;;;;;;;;;;AAaA,SAAgB,WAAW,OAAqB,MAAuC;CACnF,MAAM,UAAU,gBAAgB,KAAK;CACrC,MAAM,SAAS,cAAc,IAAI;CACjC,OAAO,QAAQ,WAAW,OAAO,OAAO,OAAO,CAAC,CAAC,MAAK,cAAa,UAAU,SAAS,MAAM;AAChG;;AAGA,IAAM,YAAY,aAA6D;CAC3E,IAAI;EACA,OAAO,SAAS,OAAO;CAC3B,QAAQ;EACJ;CACJ;AACJ;;;;;;;;;;AAWA,IAAM,gBAAkD;CACpD,KAAK,IAAI,IAAI,KAAK;CAClB,KAAK,IAAI,IAAI,KAAK;CAClB,OAAO,IAAI,IAAI,OAAO;CACtB,KAAK,IAAI,IAAI,KAAK;CAClB,KAAK,IAAI,IAAI,KAAK;AACtB;;AAGA,IAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;AAuBxB,IAAM,iBAAiB,WAAiC;CACpD,MAAM,UAAU,OAAO,QAAQ,eAAe,aAAa,OAAO,WAAW,CAAC,CAAC,YAAY,IAAI;CAC/F,OAAO,gBAAgB,KAAK,OAAO;AACvC;;;;;;;;;;;;;;AAeA,IAAI,iCAA0D;;AAG9D,SAAgB,6BAA6B,MAAqC;CAC9E,iCAAiC;AACrC;;AAGA,SAAgB,6BAAsD;CAClE,OAAO;AACX;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6GA,SAAS,uBAAuB,OAAsE;CAClG,MAAM,MAAM,MAAM,QAAQ,GAAG;CAC7B,IAAI,OAAO,KAAK,QAAQ,MAAM,SAAS,GAAG,OAAO,KAAA;CACjD,OAAO;EAAE,aAAa,MAAM,MAAM,GAAG,GAAG;EAAG,UAAU,MAAM,MAAM,MAAM,CAAC;CAAE;AAC9E;AAEA,SAAS,mBAAmB,OAAkE;CAC1F,IAAI,CAAC,MAAM,SAAS,IAAI,GAAG,OAAO,KAAA;CAElC,MAAM,WAAW,MAAM,MAAM,MAAM,CAAC,CAAC,KAAI,MAAK,EAAE,KAAK,CAAC,CAAC,CAAC,OAAO,OAAO;CACtE,IAAI,SAAS,SAAS,GAAG,OAAO,KAAA;CAEhC,MAAM,CAAC,WAAW,GAAG,QAAQ;CAC7B,OAAO;EAAE;EAAW;CAAK;AAC7B;;;;;;;AAQA,SAAS,0BAA0B,OAAyB;CACxD,IAAI,MAAM,QAAQ,KAAK,GAAG,OAAO,MAAM,IAAI,yBAAyB;CACpE,MAAM,WAAW,0BAA0B,KAAK;CAChD,OAAO,WAAW,SAAS,KAAK;AACpC;;;;;;;;;;;;;;AAeA,SAAS,iBAAiB,OAA2B;CACjD,OAAO,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC,KAAK;AAChD;;;;;;;;;;AAmBA,IAAa,0BAAb,MAAa,wBAAwB;;;;;;;;;;;;;;;;;;;;;CAsBjC,OAAO,4BACH,UAYA,QAMA,UACA,aACA,gBACA,UACG;EAEH,MAAM,aAAa,QACf,IAAI,KAAK,IAAI,KAAK,EAAE,QAAQ,YAAY,GAAG,GAAG,OAAO,KAAK,OAAO,GAAG,GAAG,OAAO;EAElF,QAAQ,SAAS,MAAjB;GACI,KAAK,OAAO;IACR,MAAM,EAAE,OAAO,QAAQ,OAAO;IAC9B,OAAO,KAAK,4BACR,SAAS,UAAU,OAAO,KAAK,aAAa,QAChD;GACJ;GAEA,KAAK,cAAc;IACf,MAAM,EAAE,OAAO,cAAc,cAAc,iBAAiB,SAAS;IACrE,MAAM,gBAAgB,SAAS,SAAS,YAAY;IACpD,IAAI,CAAC,eACD,MAAM,qBAAqB,YAAY;IAE3C,MAAM,YAAY,cAAc;IAChC,MAAM,YAAY,cAAc;IAChC,IAAI,CAAC,aAAa,CAAC,WACf,MAAM,IAAI,MACN,qBAAqB,aAAa,KAAK,aAAa,kBAAkB,aAAa,EACvF;IAmBJ,MAAM,gBAAgB;IACtB,MAAM,eAAe,WACjB,GAAG,GAAG,IAAI,WAAW,aAAa,EAAE,GAAG,IAAI,WAAW,OAAO,IAAI;IACrE,OAAO,GAAG,yBAAyB,cAAc,MAAM,IAAI,WAAW,aAAa,EAAE,SAAS,YAAY,SAAS,EAAE,KAAK,eAAe,OAAO,YAAY,SAAS,EAAE,KAAK,SAAS;GACzL;GAEA,KAAK;GACL,KAAK,WAAW;IACZ,MAAM,WAAW,eAAe,aAAa,SAAS,QAAQ,GAAG,SAAS,kBAAkB;IAC5F,IAAI,CAAC,UACD,MAAM,IAAI,MACN,uBAAuB,SAAS,mBAAmB,+CACtC,SAAS,aAAa,GACvC;IAEJ,IAAI,CAAC,SAAS,WAAW,OAAO,GAAG,UAAU,QAAQ;IAQrD,MAAM,EAAE,OAAO,QAAQ,OAAO;IAC9B,OAAO,GAAG,GAAG,SAAS,aAAa,IAAI,WAAW,SAAS,SAAS,EAAE,QAAQ,MAAM,SAAS,UAAU,GAAG,EAAE;GAChH;GAEA,KAAK,aAAa;IAEd,MAAM,EAAE,OAAO,QAAQ,OAAO;IAC9B,OAAO,GAAG,GAAG,eAAe,aAAa,IAAI,WAAW,SAAS,QAAQ,EAAE,QAAQ,MAAM,SAAS,UAAU,GAAG,EAAE;GACrH;GAEA,SAII,MAAM,IAAI,MAAM,0BAA0B,KAAK,UAAU,QAAU,GAAG;EAE9E;CACJ;;;;;;;;;;CAWA,OAAe,4BACX,UACA,aAEA,WACA,aACA,UACG;EACH,MAAM,cAAc;EACpB,MAAM,YAAY,UAAkB,UAAU;EAI9C,MAAM,WAAW,WAAmB,WAChC,GAAG,GAAG,IAAI,WAAW,cAAc,IAAI,cAAc,SAAS,YAAY,CAAC,CAAC,EAAE,GAAG,IAAI,WAAW,cAAc,MAAM,CAAC;EAEzH,MAAM,SAAS,SAAmD;GAC9D,MAAM,OAAO,MAAM,QAAQ,KAAK,GAAG,IAAI,IAAI,KAAK,GAAG,OAAO,CAAC,KAAK,GAAG,IAAI;GACvE,MAAM,KAAK,MAAM,QAAQ,KAAK,GAAG,EAAE,IAAI,KAAK,GAAG,KAAK,CAAC,KAAK,GAAG,EAAE;GAC/D,IAAI,KAAK,WAAW,GAAG,QACnB,MAAM,IAAI,MAAM,iBAAiB,KAAK,MAAM,QAAQ,KAAK,OAAO,wBAAwB,GAAG,OAAO,gBAAgB;GAEtH,OAAO,KAAK,KAAK,GAAG,OAAO;IAAE,MAAM;IAAG,IAAI,GAAG;GAAG,EAAE;EACtD;EAEA,MAAM,QAAQ,SAAS,MAAM,GAAG,EAAE;EAClC,MAAM,OAAO,SAAS,SAAS,SAAS;EAExC,MAAM,QAAe,MAAM,KAAK,MAAM,UAAU;GAC5C,MAAM,QAAQ,SAAS,SAAS,KAAK,KAAK;GAC1C,IAAI,CAAC,OAAO,MAAM,IAAI,MAAM,yBAAyB,KAAK,OAAO;GACjE,MAAM,KAAK,MAAM,IAAI,CAAC,CAAC,KAAK,EAAE,MAAM,SAChC,GAAG,GAAG,QAAQ,OAAO,IAAI,EAAE,KAAK,IAAI,WAAW,SAAS,KAAK,CAAC,EAAE,GAAG,IAAI,WAAW,cAAc,EAAE,CAAC,GACvG;GACA,OAAO,GAAG,QAAQ,MAAM,MAAM,IAAI,WAAW,SAAS,KAAK,CAAC,EAAE,MAAM,IAAI,KAAK,IAAI,GAAG,OAAO;EAC/F,CAAC;EAYD,MAAM,gBAAgB,SAA8B;GAChD,MAAM,SAAS,WAAW,aAAa,IAAI;GAC3C,IAAI,CAAC,QACD,MAAM,IAAI,MAAM,qBAAqB,KAAK,iDAAiD;GAE/F,OAAO;EACX;EAEA,MAAM,cAAc,IAAI,KACpB,MAAM,IAAI,CAAC,CAAC,KAAK,EAAE,MAAM,SAAS,GAAG,GAAG,QAAQ,MAAM,QAAQ,IAAI,EAAE,KAAK,aAAa,EAAE,GAAG,GAC3F,GAAG,OACP;EAEA,MAAM,WAAW,MAAM,SAAS,IAAI,GAAG,IAAI,IAAI,KAAK,OAAO,GAAG,GAAG,MAAM,GAAG;EAE1E,MAAM,YAAY,IAAI,KAClB,UAAU,KAAK,EAAE,QAAQ,YAAY,GAAG,GAAG,IAAI,WAAW,WAAW,EAAE,GAAG,IAAI,WAAW,OAAO,IAAI,EAAE,KAAK,OAAO,GAClH,GAAG,OACP;EAEA,OAAO,GAAG,yBAAyB,YAAY,MAAM,IAAI,WAAW,WAAW,IAAI,SAAS,SAAS,UAAU,OAAO,YAAY;CACtI;;;;;;;;;;;;;;;;;;;;;;;;;;CA2BA,OAAe,oBACX,OACA,OACA,gBACA,MACA,SACwB;EACxB,MAAM,EAAE,YAAY,UAAU,mBAAmB;EAEjD,MAAM,YAAY,SACb,OAAO,QAAQ,MAAM,OAA4C,KAAA,MAAc,KAAA;EAEpF,MAAM,SAAS,SAAS,KAAK;EAC7B,IAAI,QAAQ,OAAO;GAAE,MAAM;GAAU,QAAQ;EAAO;EAIpD,MAAM,WAAW,mBAAmB,KAAK;EACzC,IAAI,UAAU;GACV,MAAM,OAAO,SAAS,SAAS,SAAS;GACxC,IAAI,MAAM;IACN,MAAM,OAAO,cAAc,IAAI;IAI/B,IAAI,KAAK,aAAa,UAAU,KAAK,eAAe,aAAa,KAAK,eAAe,UACjF,MAAM,SAAS,WACX,yBAAyB,SAAS,UAAU,wCAC5C,sBACJ;IAEJ,OAAO;KAAE,MAAM;KAAQ,QAAQ;KAAM,MAAM,SAAS;IAAK;GAC7D;EACJ;EAEA,IAAI,YAAY;GACZ,MAAM,WAAW,2BAA2B,UAAU,CAAC,CAAC;GAGxD,IAAI,UAAU,SAAS,aAAa;IAChC,MAAM,aAAa,eAAe,OAAO,YAAY,SAAS,QAAQ;IACtE,IAAI,YAAY,OAAO;KAAE,MAAM;KAAU,QAAQ;IAAW;GAChE;GAMA,IAAI,aAAa,sBAAsB,QAAQ,KAAK,aAAa,QAAQ,MAAM,YAAY,gBAAgB;IAMvG,MAAM,oBAAoB,sBAAsB,QAAQ,KAAK,SAAS,YAChE,eAAe,OAAO,YAAY,SAAS,SAAS,IACpD;IACN,IAAI,CAAC,mBACD,MAAM,IAAI,MACN,iBAAkB,SAAwC,UAAU,mBAChE,SAAS,aAAa,wBAAwB,eAAe,kEAErE;IAEJ,OAAO;KAAE,MAAM;KAAY;KAAU;KAAU,gBAAgB;IAAkB;GACrF;GAKA,MAAM,gBAAgB,uBAAuB,KAAK;GAClD,IAAI,iBAAiB,YAAY,gBAAgB;IAC7C,MAAM,SAAS,KAAK,2BAChB,OAAO,eAAe,YAAY,UAAU,gBAAgB,OAAO,cACvE;IACA,IAAI,QAAQ,OAAO;GACvB;EACJ;EAOA,KAAK,MAAM,SAAS;GAChB,GAAG,MAAM;GACT,UAAU,uBAAuB,KAAK,CAAC;GACvC,GAAG,MAAM;GACT,uBAAuB,KAAK;EAChC,GAAG;GACC,MAAM,aAAa,SAAS,KAAK;GACjC,IAAI,YAAY,OAAO;IAAE,MAAM;IAAU,QAAQ;GAAW;EAChE;EAEA,IAAI,SAAS,QAAQ;GACjB,OAAO,KAAK,uBAAuB,MAAM,oDAAoD,eAAe,EAAE;GAC9G;EACJ;EAEA,IAAI,cAAwB,CAAC;EAC7B,IAAI;GACA,cAAc,OAAO,KAAK,gBAAgB,KAAK,CAAC,CAAC,CAAC,KAAK;EAC3D,QAAQ,CAGR;EAMA,MAAM,SAAS,WACX,yBAAyB,MAAM,mBAAmB,eAAe,MAChE,YAAY,SAAS,IAAI,mBAAmB,YAAY,KAAK,IAAI,MAAM,KACxE,wBACA;GAAE;GAAO,YAAY;GAAgB,GAAI,YAAY,SAAS,KAAK,EAAE,YAAY;EAAG,CACxF;CACJ;;;;;;;;;;;;;;;;;CAkBA,OAAe,2BACX,OACA,MACA,YACA,UACA,gBACA,OACA,gBACwB;EACxB,MAAM,WAAW,2BAA2B,UAAU,CAAC,CAAC,KAAK;EAC7D,IAAI,CAAC,UAAU,OAAO,KAAA;EAEtB,IAAI,SAAS,SAAS,OAClB,MAAM,SAAS,WACX,qBAAqB,MAAM,mBAAmB,eAAe,MAAM,KAAK,YAAY,0HAGpF,+BACA;GAAE;GAAO,YAAY;GAAgB,UAAU,KAAK;GAAa,MAAM,SAAS;EAAK,CACzF;EAGJ,MAAM,mBAAmB,SAAS,QAAQ;EAC1C,MAAM,cAAc,oBAAoB,SAAS,SAAS,eAAa,gBAAgB,CAAC;EACxF,IAAI,CAAC,oBAAoB,CAAC,aACtB,MAAM,IAAI,MACN,+CAA+C,SAAS,aAAa,QAAQ,eAAe,SACrF,MAAM,iCACjB;EAGJ,MAAM,eAAe,eAAe,aAAa,kBAAkB,KAAK,QAAQ,MACxE,KAAK,YAAY,cACf,YAAY,KAAK,YACjB,KAAA;EACV,IAAI,CAAC,cAAc;GACf,IAAI,cAAwB,CAAC;GAC7B,IAAI;IACA,cAAc,OAAO,KAAK,gBAAgB,WAAW,CAAC,CAAC,CAAC,KAAK;GACjE,QAAQ,CAGR;GACA,MAAM,SAAS,WACX,kBAAkB,KAAK,SAAS,QAAQ,iBAAiB,KAAK,6BAC1D,KAAK,YAAY,mBAAmB,eAAe,MACtD,YAAY,SAAS,IAAI,mBAAmB,YAAY,KAAK,IAAI,MAAM,KACxE,wBACA;IACI;IACA,YAAY;IACZ,UAAU,KAAK;IACf,kBAAkB,iBAAiB;IACnC,GAAI,YAAY,SAAS,KAAK,EAAE,YAAY;GAChD,CACJ;EACJ;EAMA,MAAM,oBAAoB,SAAS,SAAS,cACtC,eAAe,OAAO,YAAY,SAAS,QAAQ,IACnD,sBAAsB,QAAQ,KAAK,SAAS,YACxC,eAAe,OAAO,YAAY,SAAS,SAAS,IACpD;EACV,IAAI,CAAC,mBACD,MAAM,IAAI,MACN,aAAa,SAAS,aAAa,QAAQ,eAAe,gDAC5C,MAAM,oCACxB;EAGJ,OAAO;GACH,MAAM;GACN;GACA;GACA,gBAAgB;GAChB;GACA;EACJ;CACJ;;;;CAKA,OAAO,sBACH,QACA,OACA,gBACA,UAAoC,CAAC,GAChC;EACL,MAAM,OAAO,QAAQ,iBAAiB;EACtC,MAAM,aAAoB,CAAC;EAE3B,KAAK,MAAM,CAAC,OAAO,gBAAgB,OAAO,QAAQ,MAAM,GAAG;GACvD,IAAI,CAAC,aAAa;GAElB,MAAM,SAAS,KAAK,oBAAoB,OAAO,OAAO,gBAAgB,MAAM,OAAO;GACnF,IAAI,CAAC,QAAQ;GAIb,KAAK,MAAM,CAAC,IAAI,UAAU,eAAe,WAAW,GAAG;IACnD,MAAM,YAAY,KAAK,oBAAoB,QAAQ,IAAI,OAAO,OAAO,cAAc;IACnF,IAAI,WACA,WAAW,KAAK,SAAS;GAEjC;EACJ;EAEA,OAAO;CACX;;;;CAKA,OAAO,uBACH,MACA,OACA,gBACA,UAAoC,CAAC,GAC3B;EACV,IAAI,UAAU,MAAM;GAChB,MAAM,UAAU,KAAK,WAChB,KAAI,MAAK,KAAK,uBAAuB,GAAG,OAAO,gBAAgB,OAAO,CAAC,CAAC,CACxE,QAAQ,QAAoB,QAAQ,IAAI;GAC7C,IAAI,QAAQ,WAAW,GAAG,OAAO;GACjC,IAAI,KAAK,SAAS,MAAM,OAAO,GAAG,GAAG,OAAO,KAAK;GACjD,MAAM,cAAc,IAAI,GAAG,OAAO,KAAK;GAUvC,IAAI,KAAK,SAAS,OAAO,OAAO,cAAc,IAAI,WAAW,IAAI;GACjE,OAAO;EACX,OAAO;GAKH,MAAM,SAAS,KAAK,oBAChB,OACA,KAAK,QACL,gBACA,QAAQ,iBAAiB,gCACzB,OACJ;GACA,IAAI,CAAC,QAAQ,OAAO;GACpB,OAAO,KAAK,oBACR,QAAQ,KAAK,UAA2B,KAAK,OAAO,KAAK,QAAQ,cACrE;EACJ;CACJ;;CAGA,OAAe,oBACX,QACA,IACA,OACA,OACA,gBACU;EACV,IAAI,OAAO,SAAS,UAChB,OAAO,KAAK,2BAA2B,OAAO,QAAQ,IAAI,KAAK;EAEnE,IAAI,OAAO,SAAS,QAChB,OAAO,KAAK,uBAAuB,OAAO,QAAQ,OAAO,MAAM,IAAI,KAAK;EAE5E,IAAI,OAAO,SAAS,kBAChB,OAAO,KAAK,4BAA4B,QAAQ,IAAI,OAAO,OAAO,cAAc;EAEpF,OAAO,KAAK,6BACR,OAAO,UAAU,IAAI,OAAO,OAAO,gBAAgB,OAAO,UAAU,OAAO,cAC/E;CACJ;;;;;;;;;;;;;;;;;;;;;;;;;CA0BA,OAAe,uBACX,QACA,MACA,IACA,OACU;EAGV,IAAI,OAAY,GAAG,GAAG;EACtB,KAAK,MAAM,OAAO,KAAK,MAAM,GAAG,EAAE,GAC9B,OAAO,GAAG,GAAG,KAAK,MAAM;EAE5B,MAAM,OAAO,GAAG,GAAG,KAAK,OAAO,KAAK,KAAK,SAAS;EAKlD,IAH0B,OAAO,UAAU,aACtC,OAAO,OAAO,OAAO,QAAQ,OAAO,OAAO,OAAO,OAEhC;GAGnB,MAAM,UAAU,GAAG,aAAa,KAAK,oCAAoC,KAAK;GAC9E,QAAQ,IAAR;IACI,KAAK,KAAK,OAAO,GAAG,GAAG,QAAQ,KAAK;IACpC,KAAK,MAAM,OAAO,GAAG,GAAG,QAAQ,MAAM;IACtC,KAAK,KAAK,OAAO,GAAG,GAAG,QAAQ,KAAK;IACpC,KAAK,MAAM,OAAO,GAAG,GAAG,QAAQ,MAAM;GAC1C;EACJ;EAEA,MAAM,UAAU,MAAuB,OAAO,MAAM,YAAY,OAAO,CAAC,IAAI,OAAO,CAAC;EAEpF,QAAQ,IAAR;GACI,KAAK,MACD,OAAO,UAAU,QAAQ,UAAU,KAAA,IAAY,GAAG,GAAG,KAAK,YAAY,GAAG,GAAG,KAAK,KAAK,OAAO,KAAK;GACtG,KAAK,MACD,OAAO,UAAU,QAAQ,UAAU,KAAA,IAAY,GAAG,GAAG,KAAK,gBAAgB,GAAG,GAAG,KAAK,MAAM,OAAO,KAAK;GAC3G,KAAK,KAAK,OAAO,GAAG,GAAG,KAAK,KAAK,OAAO,KAAK;GAC7C,KAAK,MAAM,OAAO,GAAG,GAAG,KAAK,MAAM,OAAO,KAAK;GAC/C,KAAK,KAAK,OAAO,GAAG,GAAG,KAAK,KAAK,OAAO,KAAK;GAC7C,KAAK,MAAM,OAAO,GAAG,GAAG,KAAK,MAAM,OAAO,KAAK;GAC/C,KAAK,QAAQ,OAAO,GAAG,GAAG,KAAK,QAAQ,OAAO,KAAK;GACnD,KAAK,SAAS,OAAO,GAAG,GAAG,KAAK,SAAS,OAAO,KAAK;GACrD,KAAK,YAAY,OAAO,GAAG,GAAG,KAAK,YAAY,OAAO,KAAK;GAC3D,KAAK,aAAa,OAAO,GAAG,GAAG,KAAK,aAAa,OAAO,KAAK;GAC7D,KAAK,WAAW,OAAO,GAAG,GAAG,KAAK;GAClC,KAAK,eAAe,OAAO,GAAG,GAAG,KAAK;GACtC,KAAK;GACL,KAAK,UAAU;IACX,IAAI,UAAU,QAAQ,UAAU,KAAA,GAC5B,OAAO,OAAO,OAAO,GAAG,GAAG,KAAK,YAAY,GAAG,GAAG,KAAK;IAE3D,MAAM,SAAS,iBAAiB,KAAK,CAAC,CAAC,IAAI,MAAM;IAIjD,IAAI,OAAO,WAAW,GAAG,OAAO,OAAO,OAAO,GAAG,UAAU,GAAG;IAC9D,MAAM,OAAO,IAAI,KAAK,OAAO,KAAI,MAAK,GAAG,GAAG,GAAG,GAAG,GAAG,IAAI;IACzD,OAAO,OAAO,OAAO,GAAG,GAAG,KAAK,OAAO,KAAK,KAAK,GAAG,GAAG,KAAK,WAAW,KAAK;GAChF;GACA,SAII,MAAM,SAAS,WACX,aAAa,GAAG,kEAChB,yBACJ;EACR;CACJ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkCA,OAAO,6BACH,UACA,IACA,OACA,gBACA,UACA,OACA,gBACG;EACH,MAAM,QAAQ;EACd,MAAM,OAAO,WACT,GAAG,GAAG,IAAI,WAAW,KAAK,EAAE,GAAG,IAAI,WAAW,OAAO,IAAI;EAE7D,IAAI;EACJ,IAAI;EACJ,IAAI;EAEJ,IAAI,SAAS,SAAS,cAAc;GAChC,MAAM,EAAE,OAAO,cAAc,cAAc,iBAAiB,SAAS;GACrE,MAAM,gBAAgB,SAAS,SAAS,YAAY;GACpD,IAAI,CAAC,eACD,MAAM,qBAAqB,YAAY;GAE3C,MAAM,YAAY,cAAc;GAChC,MAAM,YAAY,cAAc;GAChC,IAAI,CAAC,aAAa,CAAC,WACf,MAAM,IAAI,MACN,qBAAqB,aAAa,KAAK,aAAa,kBAAkB,aAAa,EACvF;GAEJ,YAAY;GACZ,cAAc,GAAG,GAAG,IAAI,SAAS,EAAE,KAAK;GACxC,iBAAiB;EACrB,OAAO;GACH,MAAM,mBAAmB,SAAS,OAAO;GACzC,MAAM,cAAc,SAAS,SAAS,eAAa,gBAAgB,CAAC;GACpE,IAAI,CAAC,aACD,MAAM,IAAI,MACN,+CAA+C,SAAS,aAAa,iBACrD,iBAAiB,KAAK,GAC1C;GAEJ,MAAM,WAAW,eAAe,aAAa,kBAAkB,SAAS,kBAAkB;GAC1F,IAAI,CAAC,UACD,MAAM,IAAI,MACN,uBAAuB,SAAS,mBAAmB,+CACtC,SAAS,aAAa,GACvC;GAIJ,MAAM,iBAAiB,KAAK,iBAAiB,WAAW;GACxD,IAAI,CAAC,gBACD,MAAM,IAAI,MACN,kEAAkE,SAAS,aAAa,mDAE5F;GAEJ,YAAY;GACZ,cAAc,GAAG,GAAG,IAAI,QAAQ,EAAE,KAAK;GACvC,iBAAiB;EACrB;EAEA,MAAM,EAAE,WAAW,WAAW,KAAK,6BAC/B,IAAI,cAAc,GAAG,IAAI,OAAO,OAAO,cAC3C;EAEA,MAAM,QAAQ,YAAY,GAAG,GAAG,YAAY,OAAO,cAAc;EACjE,MAAM,SAAS,GAAG,yBAAyB,UAAU,MAAM,IAAI,WAAW,KAAK,EAAE,SAAS,MAAM;EAChG,OAAO,SAAS,GAAG,OAAO,WAAW;CACzC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6CA,OAAe,6BACX,KACA,IACA,OACA,OACA,gBACoC;EACpC,QAAQ,0BAA0B,KAAK;EACvC,MAAM,YAAY,UAAU,QAAQ,UAAU,KAAA;EAE9C,MAAM,eAAe,GAAG,GAAG,IAAI,KAAK;EACpC,MAAM,eAAe;GAKjB,MAAM,SAAS,iBAAiB,KAAK;GACrC,OAAO,OAAO,WAAW,IACnB,GAAG,UACH,GAAG,GAAG,IAAI,OAAO,IAAI,KAAK,OAAO,KAAI,MAAK,GAAG,GAAG,GAAG,GAAG,GAAG,IAAI,EAAE;EACzE;EAEA,QAAQ,IAAR;GACI,KAAK,MACD,OAAO,YAAY,EAAE,QAAQ,KAAK,IAAI;IAAE,WAAW,OAAO;IAAG,QAAQ;GAAM;GAC/E,KAAK,MACD,OAAO,YAAY,EAAE,QAAQ,MAAM,IAAI;IAAE,WAAW,OAAO;IAAG,QAAQ;GAAK;GAC/E,KAAK,MACD,OAAO,YAAY,EAAE,QAAQ,KAAK,IAAI;IAAE,WAAW,OAAO;IAAG,QAAQ;GAAM;GAC/E,KAAK,UACD,OAAO,YAAY,EAAE,QAAQ,MAAM,IAAI;IAAE,WAAW,OAAO;IAAG,QAAQ;GAAK;GAC/E,KAAK,WACD,OAAO,EAAE,QAAQ,KAAK;GAC1B,KAAK,eACD,OAAO,EAAE,QAAQ,MAAM;GAO3B,KAAK,kBACD,OAAO,YAAY,EAAE,QAAQ,KAAK,IAAI;IAAE,WAAW,OAAO;IAAG,QAAQ;GAAM;GAC/E,KAAK,sBACD,OAAO,YAAY,EAAE,QAAQ,KAAK,IAAI;IAAE,WAAW,OAAO;IAAG,QAAQ;GAAM;GAC/E,SACI,MAAM,SAAS,WACX,aAAa,GAAG,yCAAyC,MAAM,mBAC3D,eAAe,oJAGnB,wCACA;IAAE;IAAO,YAAY;IAAgB,UAAU;GAAG,CACtD;EACR;CACJ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwCA,OAAO,4BACH,QACA,IACA,OACA,OACA,gBACG;EACH,MAAM,QAAQ;EACd,MAAM,EAAE,UAAU,aAAa,iBAAiB;EAChD,MAAM,MAAM,GAAG,GAAG,IAAI,WAAW,KAAK,EAAE,GAAG,IAAI,WAAW,aAAa,IAAI;EAE3E,MAAM,EAAE,WAAW,WAAW,KAAK,6BAC/B,KAAK,cAAc,IAAI,OAAO,OAAO,cACzC;EAMA,IAAI;EACJ,IAAI;EAEJ,IAAI,SAAS,SAAS,cAAc;GAChC,MAAM,EAAE,OAAO,cAAc,cAAc,cAAc,yBAAyB,SAAS;GAC3F,MAAM,gBAAgB,OAAO,SAAS,SAAS,YAAY;GAC3D,IAAI,CAAC,eACD,MAAM,qBAAqB,YAAY;GAE3C,MAAM,YAAY,cAAc;GAChC,MAAM,YAAY,cAAc;GAChC,IAAI,CAAC,aAAa,CAAC,WACf,MAAM,IAAI,MACN,qBAAqB,aAAa,KAAK,qBAAqB,kBAAkB,aAAa,EAC/F;GAEJ,MAAM,YAAY,KAAK,iBAAiB,WAAW;GACnD,IAAI,CAAC,WACD,MAAM,IAAI,MACN,kEAAkE,SAAS,aAAa,SACjF,MAAM,4CACjB;GAEJ,MAAM,gBAAgB;GACtB,OAAO,GAAG,GAAG,YAAY,MAAM,IAAI,WAAW,KAAK,EAAE,cAAc,cAAc,MAAM,IAAI,WAAW,aAAa,EAAE,MAAM,IAAI,WAAW,aAAa,EAAE,GAAG,IAAI,WAAW,UAAU,IAAI,EAAE,KAAK,IAAI,WAAW,KAAK,EAAE,GAAG,IAAI,WAAW,UAAU,IAAI;GACtP,cAAc,GAAG,GAAG,IAAI,WAAW,aAAa,EAAE,GAAG,IAAI,WAAW,UAAU,IAAI,EAAE,KAAK,OAAO;EACpG,OAAO,IAAI,SAAS,SAAS,aAAa;GACtC,MAAM,YAAY,KAAK,iBAAiB,WAAW;GACnD,IAAI,CAAC,WACD,MAAM,IAAI,MACN,kEAAkE,SAAS,aAAa,SACjF,MAAM,oCACjB;GAEJ,OAAO,GAAG,GAAG,YAAY,MAAM,IAAI,WAAW,KAAK;GACnD,cAAc,GAAG,GAAG,IAAI,WAAW,KAAK,EAAE,GAAG,IAAI,WAAW,UAAU,IAAI,EAAE,KAAK,OAAO;EAC5F,OAAO;GACH,MAAM,aAAa,eAAe,aAAa,SAAS,QAAQ,GAAG,SAAS,kBAAkB;GAC9F,IAAI,CAAC,YACD,MAAM,IAAI,MACN,uBAAuB,SAAS,mBAAmB,+CACtC,SAAS,aAAa,GACvC;GAEJ,OAAO,GAAG,GAAG,YAAY,MAAM,IAAI,WAAW,KAAK;GACnD,cAAc,GAAG,GAAG,IAAI,WAAW,KAAK,EAAE,GAAG,IAAI,WAAW,WAAW,IAAI,EAAE,KAAK,OAAO;EAC7F;EAEA,MAAM,QAAQ,YAAY,GAAG,GAAG,YAAY,OAAO,cAAc;EACjE,MAAM,SAAS,GAAG,yBAAyB,KAAK,SAAS,MAAM;EAC/D,OAAO,SAAS,GAAG,OAAO,WAAW;CACzC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgCA,OAAe,6BACX,KACA,QACA,IACA,OACA,OACA,gBACoC;EACpC,QAAQ,0BAA0B,KAAK;EACvC,MAAM,YAAY,UAAU,QAAQ,UAAU,KAAA;EAE9C,MAAM,eAAe,GAAG,GAAG,IAAI,KAAK;EACpC,MAAM,eAAoB;GACtB,MAAM,SAAS,iBAAiB,KAAK;GAIrC,OAAO,OAAO,WAAW,IACnB,GAAG,UACH,GAAG,GAAG,IAAI,OAAO,IAAI,KAAK,OAAO,KAAI,MAAK,GAAG,GAAG,GAAG,GAAG,GAAG,IAAI,EAAE;EACzE;EACA,MAAM,eAAe,GAAG,GAAG,IAAI;EAC/B,MAAM,iBAAsB;GACxB,MAAM,OAAO,cAAc,MAAM;GACjC,MAAM,gBAAgB,KAAK,aAAa,WAAW,KAAK,eAAe;GACvE,IAAI,OAAO,sBAAsB;IAC7B,IAAI,MAAM,QAAQ,KAAK,KAAK,MAAM,WAAW,GAAG,OAAO,GAAG;IAC1D,IAAI,MAAM,QAAQ,KAAK,KAAK,MAAM,SAAS,GACvC,OAAO,gBACD,GAAG,GAAG,IAAI,YAAY,IAAI,KAAK,MAAM,KAAI,MAAK,GAAG,GAAG,GAAG,GAAG,GAAG,IAAI,EAAE,KACnE,GAAG,GAAG,IAAI,YAAY,IAAI,KAAK,MAAM,KAAI,MAAK,GAAG,GAAG,OAAO,CAAC,GAAG,GAAG,GAAG,IAAI,EAAE;GAEzF;GACA,OAAO,gBACD,GAAG,GAAG,IAAI,YAAY,MAAM,KAC5B,GAAG,GAAG,IAAI,MAAM,KAAK,UAAU,CAAC,KAAK,CAAC;EAChD;EAEA,QAAQ,IAAR;GACI,KAAK,MACD,OAAO;IAAE,WAAW,YAAY,OAAO,IAAI,OAAO;IAAG,QAAQ;GAAM;GACvE,KAAK,MACD,OAAO;IAAE,WAAW,YAAY,OAAO,IAAI,OAAO;IAAG,QAAQ;GAAK;GACtE,KAAK,KACD,OAAO;IAAE,WAAW,GAAG,GAAG,IAAI,KAAK;IAAS,QAAQ;GAAM;GAC9D,KAAK,MACD,OAAO;IAAE,WAAW,GAAG,GAAG,IAAI,MAAM;IAAS,QAAQ;GAAM;GAC/D,KAAK,KACD,OAAO;IAAE,WAAW,GAAG,GAAG,IAAI,KAAK;IAAS,QAAQ;GAAM;GAC9D,KAAK,MACD,OAAO;IAAE,WAAW,GAAG,GAAG,IAAI,MAAM;IAAS,QAAQ;GAAM;GAC/D,KAAK,MACD,OAAO;IAAE,WAAW,YAAY,OAAO,IAAI,OAAO;IAAG,QAAQ;GAAM;GACvE,KAAK,UACD,OAAO;IAAE,WAAW,YAAY,OAAO,IAAI,OAAO;IAAG,QAAQ;GAAK;GACtE,KAAK,QACD,OAAO;IAAE,WAAW,GAAG,GAAG,IAAI,QAAQ,OAAO,KAAK;IAAK,QAAQ;GAAM;GACzE,KAAK,YACD,OAAO;IAAE,WAAW,GAAG,GAAG,IAAI,QAAQ,OAAO,KAAK;IAAK,QAAQ;GAAK;GACxE,KAAK;GACL,KAAK;IAKD,IAAI,CAAC,cAAc,MAAM,GACrB,MAAM,SAAS,WACX,aAAa,GAAG,0BAA0B,MAAM,mBAAmB,eAAe,MAC9E,OAAO,KAAK,OAAO,OAAO,aAAa,KAAK,oBAAoB,2DAEpE,wCACA;KAAE;KAAO,YAAY;KAAgB,UAAU;IAAG,CACtD;IAEJ,OAAO;KAAE,WAAW,GAAG,GAAG,IAAI,SAAS,OAAO,KAAK;KAAK,QAAQ,OAAO;IAAY;GAEvF,KAAK,WACD,OAAO;IAAE,WAAW,OAAO;IAAG,QAAQ;GAAM;GAChD,KAAK,eACD,OAAO;IAAE,WAAW,GAAG,GAAG,IAAI;IAAe,QAAQ;GAAM;GAC/D,KAAK;GACL,KAAK,sBACD,OAAO;IAAE,WAAW,SAAS;IAAG,QAAQ;GAAM;GAClD,SACI,MAAM,SAAS,WACX,4BAA4B,GAAG,sBAAsB,qBAAqB,KAAK,IAAI,EAAE,IACrF,2BACA;IAAE,UAAU;IAAI,gBAAgB;GAAqB,CACzD;EACR;CACJ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgCA,OAAO,iCACH,MACA,OACA,YACA,UACA,gBACA,gBACA,aACG;EACH,MAAM,WAAW,2BAA2B,UAAU,CAAC,CAAC,KAAK;EAC7D,IAAI,CAAC,UACD,MAAM,SAAS,WACX,mBAAmB,4BAA4B,IAAI,EAAE,mBAAmB,eAAe,MACnF,KAAK,SAAS,6BAClB,0BACA;GAAE,OAAO,4BAA4B,IAAI;GAAG,YAAY;GAAgB,UAAU,KAAK;EAAS,CACpG;EAEJ,IAAI,SAAS,SAAS,OAClB,MAAM,SAAS,WACX,mBAAmB,4BAA4B,IAAI,EAAE,mBAAmB,eAAe,uGAEvF,+BACA;GAAE,OAAO,4BAA4B,IAAI;GAAG,YAAY;GAAgB,MAAM,SAAS;EAAK,CAChG;EAGJ,MAAM,mBAAmB,SAAS,QAAQ;EAC1C,MAAM,cAAc,oBAAoB,SAAS,SAAS,eAAa,gBAAgB,CAAC;EACxF,IAAI,CAAC,oBAAoB,CAAC,aACtB,MAAM,IAAI,MACN,+CAA+C,SAAS,aAAa,QAAQ,eAAe,qCAEhG;EAGJ,MAAM,QAAQ;EAId,IAAI,YAAiB,GAAG;EACxB,IAAI,KAAK,OAAO;GACZ,MAAM,SAAS,eAAe,aAAa,kBAAkB,KAAK,KAAK,MAC/D,KAAK,SAAS,cACZ,YAAY,KAAK,SACjB,KAAA;GACV,IAAI,CAAC,QAAQ;IACT,IAAI,cAAwB,CAAC;IAC7B,IAAI;KACA,cAAc,OAAO,KAAK,gBAAgB,WAAW,CAAC,CAAC,CAAC,KAAK;IACjE,QAAQ,CAER;IACA,MAAM,SAAS,WACX,kBAAkB,KAAK,MAAM,QAAQ,iBAAiB,KAAK,6BACvD,KAAK,SAAS,mBAAmB,eAAe,MACnD,YAAY,SAAS,IAAI,mBAAmB,YAAY,KAAK,IAAI,MAAM,KACxE,0BACA;KACI,OAAO,4BAA4B,IAAI;KACvC,YAAY;KACZ,UAAU,KAAK;KACf,kBAAkB,iBAAiB;KACnC,GAAI,YAAY,SAAS,KAAK,EAAE,YAAY;IAChD,CACJ;GACJ;GACA,YAAY,GAAG,GAAG,IAAI,WAAW,KAAK,EAAE,GAAG,IAAI,WAAW,OAAO,IAAI;EACzE,OAAO,IAAI,KAAK,QAAQ,SACpB,MAAM,SAAS,WACX,IAAI,KAAK,IAAI,0EACb,+BACA;GAAE,OAAO,4BAA4B,IAAI;GAAG,YAAY;EAAe,CAC3E;EAMJ,MAAM,SAAc,gBAAgB,KAAA,IAAY,GAAG,GAAG,mBAAmB,GAAG,GAAG;EAE/E,IAAI;EACJ,IAAI;EAEJ,IAAI,SAAS,SAAS,cAAc;GAChC,MAAM,EAAE,OAAO,cAAc,cAAc,iBAAiB,SAAS;GACrE,MAAM,gBAAgB,SAAS,SAAS,YAAY;GACpD,IAAI,CAAC,eACD,MAAM,qBAAqB,YAAY;GAE3C,MAAM,YAAY,cAAc;GAChC,MAAM,YAAY,cAAc;GAChC,IAAI,CAAC,aAAa,CAAC,WACf,MAAM,IAAI,MACN,qBAAqB,aAAa,KAAK,aAAa,kBAAkB,aAAa,EACvF;GAEJ,MAAM,YAAY,KAAK,iBAAiB,WAAW;GACnD,IAAI,CAAC,WACD,MAAM,IAAI,MACN,kEAAkE,SAAS,aAAa,GAC5F;GAEJ,MAAM,gBAAgB;GACtB,OAAO,GAAG,GAAG,YAAY,MAAM,IAAI,WAAW,KAAK,EAAE,cAAc,cAAc,MAAM,IAAI,WAAW,aAAa,EAAE,MAAM,IAAI,WAAW,aAAa,EAAE,GAAG,IAAI,WAAW,UAAU,IAAI,EAAE,KAAK,IAAI,WAAW,KAAK,EAAE,GAAG,IAAI,WAAW,UAAU,IAAI;GACtP,cAAc,GAAG,GAAG,IAAI,WAAW,aAAa,EAAE,GAAG,IAAI,WAAW,UAAU,IAAI,EAAE,KAAK;EAC7F,OAAO,IAAI,SAAS,SAAS,aAAa;GACtC,MAAM,YAAY,KAAK,iBAAiB,WAAW;GACnD,MAAM,WAAW,eAAe,OAAO,YAAY,SAAS,QAAQ;GACpE,IAAI,CAAC,aAAa,CAAC,UACf,MAAM,IAAI,MACN,aAAa,SAAS,aAAa,QAAQ,eAAe,8EAE9D;GAEJ,OAAO,GAAG,GAAG,YAAY,MAAM,IAAI,WAAW,KAAK;GAInD,MAAM,QAAa,gBAAgB,KAAA,IAC7B,GAAG,GAAG,aACN,GAAG,WAAW,IAAI,WAAW,SAAS,IAAI,EAAE,QAAQ,MAAM,SAAS,eAAe,KAAK,YAAY;GACzG,cAAc,GAAG,GAAG,IAAI,WAAW,KAAK,EAAE,GAAG,IAAI,WAAW,UAAU,IAAI,EAAE,KAAK;EACrF,OAAO;GACH,MAAM,aAAa,eAAe,aAAa,kBAAkB,SAAS,kBAAkB;GAC5F,IAAI,CAAC,YACD,MAAM,IAAI,MACN,uBAAuB,SAAS,mBAAmB,+CACtC,SAAS,aAAa,GACvC;GAKJ,MAAM,kBAAkB,SAAS,YAC3B,eAAe,OAAO,YAAY,SAAS,SAAS,IACpD;GACN,IAAI,CAAC,iBACD,MAAM,IAAI,MACN,iBAAiB,SAAS,UAAU,mBAAmB,SAAS,aAAa,wBAC/D,eAAe,GACjC;GAEJ,MAAM,SAAc,gBAAgB,KAAA,IAC9B,GAAG,GAAG,oBACN,SAAS,YACL,GAAG,WAAW,IAAI,WAAW,gBAAgB,IAAI,EAAE,QAAQ,MAAM,SAAS,eAAe,KAAK,YAAY,KAC1G,GAAG,GAAG;GAChB,OAAO,GAAG,GAAG,YAAY,MAAM,IAAI,WAAW,KAAK;GACnD,cAAc,GAAG,GAAG,IAAI,WAAW,KAAK,EAAE,GAAG,IAAI,WAAW,WAAW,IAAI,EAAE,KAAK;EACtF;EAMA,OAAO,GAAG,WADQ,cAAc,KAAK,KACN,GAAG,UAAU,SAAS,KAAK,SAAS,YAAY;CACnF;;CAGA,OAAe,iBAAiB,OAA8C;EAC1E,OAAQ,OAAO,OAAO,KAAK,CAAC,CAAC,MAAM,QAAiC,IAAI,OAAO,KACxE,OAAO,OAAO,KAAK,CAAC,CAAC,MAAM,QAAiC,IAAI,SAAS,IAAI;CACxF;;;;CAKA,OAAO,2BACH,QACA,IACA,OACU;EACV,QAAQ,0BAA0B,KAAK;EACvC,QAAQ,IAAR;GACI,KAAK;IACD,IAAI,UAAU,QAAQ,UAAU,KAAA,GAC5B,OAAO,GAAG,GAAG,OAAO;IAExB,OAAO,GAAG,QAAQ,KAAK;GAC3B,KAAK;IACD,IAAI,UAAU,QAAQ,UAAU,KAAA,GAC5B,OAAO,GAAG,GAAG,OAAO;IAExB,OAAO,GAAG,GAAG,OAAO,MAAM;GAC9B,KAAK,KACD,OAAO,GAAG,GAAG,OAAO,KAAK;GAC7B,KAAK,MACD,OAAO,GAAG,GAAG,OAAO,MAAM;GAC9B,KAAK,KACD,OAAO,GAAG,GAAG,OAAO,KAAK;GAC7B,KAAK,MACD,OAAO,GAAG,GAAG,OAAO,MAAM;GAC9B,KAAK,MAAM;IAOP,IAAI,UAAU,QAAQ,UAAU,KAAA,GAC5B,OAAO,GAAG,GAAG,OAAO;IAExB,MAAM,SAAS,iBAAiB,KAAK;IAOrC,OAAO,OAAO,WAAW,IAAI,GAAG,UAAU,QAAQ,QAAQ,MAAM;GACpE;GACA,KAAK,kBAAkB;IACnB,MAAM,OAAO,cAAc,MAAM;IACjC,IAAI,KAAK,aAAa,WAAW,KAAK,eAAe,WACjD,OAAO,GAAG,GAAG,OAAO,YAAY,MAAM;IAG1C,OAAO,GAAG,GAAG,OAAO,MAAM,KAAK,UAAU,CAAC,KAAK,CAAC;GACpD;GACA,KAAK,sBAAsB;IACvB,MAAM,OAAO,cAAc,MAAM;IACjC,MAAM,gBAAgB,KAAK,aAAa,WAAW,KAAK,eAAe;IAKvE,IAAI,MAAM,QAAQ,KAAK,KAAK,MAAM,WAAW,GACzC,OAAO,GAAG;IAEd,IAAI,MAAM,QAAQ,KAAK,KAAK,MAAM,SAAS,GACvC,IAAI,eACA,OAAO,GAAG,GAAG,OAAO,YAAY,IAAI,KAAK,MAAM,KAAI,MAAK,GAAG,GAAG,GAAG,GAAG,GAAG,IAAI,EAAE;SAC1E;KAEH,MAAM,aAAa,MAAM,KAAI,MAAK,OAAO,CAAC,CAAC;KAC3C,OAAO,GAAG,GAAG,OAAO,YAAY,IAAI,KAAK,WAAW,KAAI,MAAK,GAAG,GAAG,GAAG,GAAG,GAAG,IAAI,EAAE;IACtF;IAGJ,IAAI,eACA,OAAO,GAAG,GAAG,OAAO,YAAY,MAAM;IAE1C,OAAO,GAAG,GAAG,OAAO,MAAM,KAAK,UAAU,CAAC,KAAK,CAAC;GACpD;GACA,KAAK,UAAU;IAKX,IAAI,UAAU,QAAQ,UAAU,KAAA,GAC5B,OAAO,GAAG,GAAG,OAAO;IAExB,MAAM,SAAS,iBAAiB,KAAK;IACrC,IAAI,OAAO,WAAW,GAAG,OAAO,GAAG;IACnC,OAAO,GAAG,GAAG,OAAO,WAAW,IAAI,KAAK,OAAO,KAAI,MAAK,GAAG,GAAG,GAAG,GAAG,GAAG,IAAI,EAAE;GACjF;GACA,KAAK,QACD,OAAO,GAAG,GAAG,OAAO,QAAQ,OAAO,KAAK;GAC5C,KAAK,SACD,OAAO,GAAG,GAAG,OAAO,SAAS,OAAO,KAAK;GAC7C,KAAK,YACD,OAAO,GAAG,GAAG,OAAO,YAAY,OAAO,KAAK;GAChD,KAAK,aACD,OAAO,GAAG,GAAG,OAAO,aAAa,OAAO,KAAK;GACjD,KAAK,WACD,OAAO,GAAG,GAAG,OAAO;GACxB,KAAK,eACD,OAAO,GAAG,GAAG,OAAO;GACxB,SAcI,MAAM,SAAS,WACX,4BAA4B,GAAG,sBAAsB,qBAAqB,KAAK,IAAI,EAAE,IACrF,2BACA;IAAE,UAAU;IAAI,gBAAgB;GAAqB,CACzD;EACR;CACJ;;;;;;;;;;;;;;;;;;CAmBA,OAAO,wBACH,UACA,UACA,aACA,aACA,gBACA,gBACA,UAIF;EACE,MAAM,iBAA4D,CAAC;EACnE,MAAM,kBAAyB,CAAC;EAEhC,QAAQ,SAAS,MAAjB;GACI,KAAK,OAAO;IACR,MAAM,EAAE,OAAO,mBAAmB,KAAK,wBACnC,SAAS,UAAU,aAAa,aAAa,gBAAgB,UAAU,QAC3E;IACA,eAAe,KAAK,GAAG,KAAK;IAC5B,gBAAgB,KAAK,cAAc;IACnC;GACJ;GAEA,KAAK,cAAc;IACf,MAAM,iBAAiB,KAAK,6BACxB,SAAS,SAAS,gBAAgB,UAAU,QAChD;IACA,eAAe,KAAK,eAAe,IAAI;IACvC,gBAAgB,KAAK,eAAe,SAAS;IAC7C;GACJ;GAEA,KAAK;GACL,KAAK;GACL,KAAK;IACD,gBAAgB,KACZ,KAAK,6BAA6B,UAAU,aAAa,aAAa,QAAQ,CAClF;IACA;GAEJ,SAEI,MAAM,IAAI,MAAM,0BAA0B,KAAK,UAAU,QAAU,GAAG;EAE9E;EAEA,OAAO;GAAE;GACjB;EAAgB;CACZ;;;;CAKA,OAAe,wBACX,UACA,aACA,aACA,gBACA,UACA,UAIF;EACE,MAAM,QAAmD,CAAC;EAC1D,IAAI,eAAe;EACnB,MAAM,kBAAkB,aAAiB,WAAW;EAGpD,KAAK,IAAI,QAAQ,SAAS,SAAS,GAAG,SAAS,GAAG,SAAS;GACvD,MAAM,WAAW,SAAS;GAC1B,MAAM,EAAE,eAAe,gBAAgB,KAAK,mBAAmB,UAAU,OAAO,eAAe;GAC/F,MAAM,cAAc,KAAK,0BAA0B,SAAS,GAAG,IAAI,CAAC,CAAC;GACrE,MAAM,YAAY,KAAK,0BAA0B,SAAS,GAAG,EAAE,CAAC,CAAC;GAEjE,MAAM,YAAY,SAAS,SAAS,aAAa;GACjD,MAAM,UAAU,SAAS,SAAS,WAAW;GAE7C,IAAI,CAAC,aAAa,CAAC,SACf,MAAM,IAAI,MAAM,wCAAwC,cAAc,MAAM,aAAa;GAG7F,MAAM,EACF,WACA,WACA,oBACA,KAAK,yBACL,cACA,WACA,SACA,aACA,WACA,eACA,aACA,QACJ;GAEA,MAAM,KAAK;IACP,OAAO;IACP;GACJ,CAAC;GACD,eAAe;GAGf,IAAI,mBAAmB,gBAAgB,SAAS,GAC5C,MAAM,KAAK,GAAG,eAAe;EAErC;EAIA,IAAI,iBAAiB,aAAa;GAa9B,MAAM,mBAAmB,aAAiB,YAAY;GACtD,MAAM,kBAAkB,aAAiB,WAAW;GAQpD,IAAI,CAAC,iBAAiB,SAAS,GAAG,GAC9B,MAAM,IAAI,MAAM,oEAAoE,iBAAiB,YAAY,iBAAiB;EAE1I;EAOA,OAAO;GACH;GACA,gBANmB,MAAM,QAAQ,QAAQ,IACvC,QAAQ,gBAAgB,QAAQ,IAChC,GAAG,gBAAgB,QAAQ;EAKjC;CACJ;;;;CAKA,OAAe,yBACX,cACA,WACA,SACA,aACA,WACA,eACA,aACA,UACwG;EACxG,IAAI;EACJ,IAAI;EACJ,MAAM,kBAA6D,CAAC;EAEpE,IAAI,iBAAiB,SAAS;GAQ1B,MAAM,OAAO,WAAW,WAAW,WAAW;GAC9C,MAAM,QAAQ,WAAW,cAAc,SAAS;GAEhD,IAAI,CAAC,QAAQ,CAAC,OAAO;IAEjB,IAAI,UAAU;KACV,MAAM,iBAAiB,KAAK,qBACxB,cACA,WACA,aACA,WACA,eACA,aACA,QACJ;KACA,IAAI,gBACA,OAAO;IAEf;IACA,MAAM,IAAI,MAAM,2BAA2B,cAAc,GAAG,YAAY,KAAK,YAAY,GAAG,WAAW;GAC3G;GAEA,YAAY;GACZ,YAAY,GAAG,MAAM,KAAK;EAC9B,OAAO,IAAI,iBAAiB,WAAW;GAEnC,MAAM,OAAO,WAAW,SAAS,SAAS;GAC1C,MAAM,QAAQ,WAAW,cAAc,WAAW;GAElD,IAAI,CAAC,QAAQ,CAAC,OAAO;IAEjB,IAAI,UAAU;KACV,MAAM,iBAAiB,KAAK,qBACxB,cACA,SACA,aACA,WACA,eACA,aACA,QACJ;KACA,IAAI,gBACA,OAAO;IAEf;IACA,MAAM,IAAI,MAAM,2BAA2B,YAAY,GAAG,UAAU,KAAK,cAAc,GAAG,aAAa;GAC3G;GAEA,YAAY;GACZ,YAAY,GAAG,MAAM,KAAK;EAC9B,OACI,MAAM,IAAI,MAAM,8EAA8E,cAAc,UAAU,aAAa;EAGvI,OAAO;GACH;GACA;GACA;EACJ;CACJ;;;;CAKA,OAAe,qBACX,cACA,aACA,aACA,WACA,eACA,aACA,UAC8G;EAG9G,MAAM,wBAAwB;GAC1B,GAAG,cAAc,GAAG;GACpB,GAAG,YAAY,GAAG;GAClB,GAAG,gBAAgB,YAAY,OAAO,CAAC,CAAC,CAAC,YAAY,IAAI,YAAY,MAAM,CAAC;GAC5E,GAAG,cAAc,cAAc,OAAO,CAAC,CAAC,CAAC,YAAY,IAAI,cAAc,MAAM,CAAC;EAClF;EAEA,KAAK,MAAM,gBAAgB,uBAAuB;GAC9C,MAAM,gBAAgB,SAAS,SAAS,YAAY;GACpD,IAAI,eAAe;IAEf,MAAM,gBAAgB,GAAG,cAAc,MAAM,GAAG,EAAE,EAAE;IACpD,MAAM,gBAAgB,GAAG,YAAY,MAAM,GAAG,EAAE,EAAE;IAElD,MAAM,oBAAoB,cAAc;IACxC,MAAM,oBAAoB,cAAc;IAExC,IAAI,qBAAqB,mBAAmB;KAExC,MAAM,oBAAoB,OAAO,OAAO,YAAY,CAAC,CAAC,MAAM,QAAiC,IAAI,OAAO;KACxG,MAAM,mBAAmB,OAAO,OAAO,WAAW,CAAC,CAAC,MAAM,QAAiC,IAAI,OAAO;KAEtG,IAAI,CAAC,qBAAqB,CAAC,kBACvB;KAIJ,IAAI,iBAAiB,aAEjB,OAAO;MACH,WAAW;MACX,WAAW,GAAG,kBAAkB,iBAAiB;MACjD,iBAAiB,CACb;OACI,OAAO;OACP,WAAW,GAAG,mBAAmB,iBAAiB;MACtD,CACJ;KACJ;UAGA,OAAO;MACH,WAAW;MACX,WAAW,GAAG,mBAAmB,iBAAiB;MAClD,iBAAiB,CACb;OACI,OAAO;OACP,WAAW,GAAG,kBAAkB,iBAAiB;MACrD,CACJ;KACJ;IAER;GACJ;EACJ;EAEA,OAAO;CACX;;;;CAKA,OAAe,6BACX,SACA,gBACA,UACA,UACiE;EACjE,MAAM,gBAAgB,SAAS,SAAS,QAAQ,KAAK;EACrD,IAAI,CAAC,eACD,MAAM,qBAAqB,QAAQ,KAAK;EAG5C,MAAM,oBAAoB,cAAc,QAAQ;EAChD,MAAM,oBAAoB,cAAc,QAAQ;EAEhD,IAAI,CAAC,mBACD,MAAM,IAAI,MAAM,kBAAkB,QAAQ,aAAa,iCAAiC,QAAQ,MAAM,EAAE;EAE5G,IAAI,CAAC,mBACD,MAAM,IAAI,MAAM,kBAAkB,QAAQ,aAAa,iCAAiC,QAAQ,MAAM,EAAE;EAI5G,MAAM,YAAY,MAAM,QAAQ,QAAQ,IAClC,QAAQ,mBAAmB,QAAQ,IACnC,GAAG,mBAAmB,QAAQ;EAEpC,OAAO;GACH,MAAM;IACF,OAAO;IACP,WAAW,GAAG,gBAAgB,iBAAiB;GACnD;GACA;EACJ;CACJ;;;;;;;;;;CAYA,OAAe,6BACX,UACA,aACA,aACA,UACG;EACH,MAAM,SAAS,WACX,MAAM,QAAQ,QAAQ,IAAI,QAAQ,QAAQ,QAAQ,IAAI,GAAG,QAAQ,QAAQ;EAE7E,IAAI,SAAS,SAAS,aAAa;GAG/B,MAAM,cAAc,KAAK,iBAAiB,WAAW;GACrD,IAAI,CAAC,aACD,MAAM,IAAI,MACN,kEAAkE,SAAS,aAAa,GAC5F;GAEJ,OAAO,MAAM,WAAW;EAC5B;EAEA,MAAM,gBAAgB,eAAe,aAAa,SAAS,QAAQ,GAAG,SAAS,kBAAkB;EACjG,IAAI,CAAC,eACD,MAAM,IAAI,MACN,uBAAuB,SAAS,mBAAmB,+CAC/C,SAAS,aAAa,wDAC9B;EAEJ,OAAO,MAAM,aAAa;CAC9B;;;;CAKA,OAAO,yBAAyB,YAAoC;EAChE,IAAI,WAAW,WAAW,GAAG,OAAO,KAAA;EACpC,IAAI,WAAW,WAAW,GAAG,OAAO,WAAW;EAC/C,OAAO,IAAI,GAAG,UAAU;CAC5B;;;;CAKA,OAAO,wBAAwB,YAAoC;EAC/D,IAAI,WAAW,WAAW,GAAG,OAAO,KAAA;EACpC,IAAI,WAAW,WAAW,GAAG,OAAO,WAAW;EAC/C,OAAO,GAAG,GAAG,UAAU;CAC3B;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6BA,OAAO,sBACH,cACA,YACA,OACA,YACK;EACL,MAAM,mBAA0B,CAAC;EAEjC,MAAM,eAAe,aACf,wBAAwB,uBAAuB,cAAc,OAAO,UAAU,IAC9E,KAAA;EACN,IAAI,cAAc,OAAO,CAAC,YAAY;EAEtC,IAAI,2BAA2B;EAC/B,MAAM,SAAS,mBAAmB;EAClC,MAAM,oBAAmC,CAAC;EAE1C,KAAK,MAAM,CAAC,KAAK,SAAS,OAAO,QAAQ,UAAU,GAAG;GAClD,MAAM,IAAI;GAOV,IAAI,CAAC,aAAa,MAAkB,MAAM,GAAG;GAG7C,IAAI,EAAE,SAAS,YAAY,CAAC,EAAE,QAAQ,EAAE,SAAS,QAAQ;IACrD;IACA,MAAM,cAAc,MAAM;IAC1B,IAAI,eAAe,cAAc,WAAW,GACxC,kBAAkB,KAAK,WAAW;GAE1C;EACJ;EAOA,IAAI,2BAA2B,KAAK,kBAAkB,WAAW,GAC7D,OAAO,KACH,aAAa,YAAY,QAAQ,aAAa,aAAa,yBAAyB,8KAGxF;EAEJ,IAAI,kBAAkB,WAAW,GAAG,OAAO,CAAC;EAU5C,MAAM,QAAQ,iBAAiB,YAAY;EAC3C,MAAM,WAAW,MAAM,SAAS,IAAI,QAAQ,CAAC,YAAY,EAAA,CAAG,KAAI,SAAQ;GACpE,MAAM,UAAU,IAAI,kBAAkB,IAAI,EAAE;GAC5C,OAAO,wBAAwB,wBAC3B,kBAAkB,KAAI,WAAU,MAAM,QAAQ,OAAO,CAAC,CAC1D;EACJ,CAAC;EAMD,iBAAiB,KAAK,wBAAwB,yBAAyB,OAAO,CAAQ;EAEtF,OAAO;CACX;;;;;;;;;;;;;;;CAgBA,OAAO,uBACH,cACA,OACA,YACe;EACf,IAAI;EACJ,IAAI;GACA,OAAO,sBAAsB,UAAU;EAC3C,QAAQ;GAEJ;EACJ;EACA,IAAI,CAAC,MAAM,OAAO,KAAA;EAElB,MAAM,SAAS,MAAM,KAAK;EAC1B,IAAI,CAAC,QAGD;EAIJ,MAAM,QAAQ,GAAG,GAAG,OAAO,MADb,wBAAwB,kBAAkB,cAAc,IACrC;EAcjC,MAAM,YAAY,KAAK,SAAS,WAC1B,wBAAwB,8BAA8B,cAAc,IAAI,IACxE,KAAA;EACN,MAAM,UAAU,YAAY,GAAG,IAAI,MAAM,MAAM,UAAU,KAAK;EAE9D,IAAI,CAAC,KAAK,OAAO,OAAO;EAExB,MAAM,cAAc,MAAM,KAAK,MAAM;EACrC,IAAI,CAAC,aAAa,OAAO;EAEzB,MAAM,SAAS,KAAK,WACd,GAAG,GAAG,IAAI,IAAI,kBAAkB,EAAE,GAAG,aAAa,KAClD,GAAG,GAAG;EAgBZ,MAAM,UAAU,GAAG,0BAA0B,OAAO,IAAI,YAAY,OAAO,KAAK,MAAM;EAWtF,OAAO,GAAG,IAAI,QAAQ,MAJR,KAAK,MAAM,YAAY,kCAC/B,GAAG,IAAI,OAAO,uBAAuB,YAAY,OAAO,QAAQ,KAChE,QAE4B;CACtC;;;;;;;;;;;;;;;;;;;;;;CAuBA,OAAO,8BACH,cACA,MACe;EACf,IAAI,KAAK,OAAO,WAAW,GAAG,OAAO,KAAA;EAErC,MAAM,UAAU,wBAAwB,cAAc,YAAY,CAAC,CAAC,KAAI,WACpE,wBAAwB,wBACpB,KAAK,OAAO,KAAI,MAAK,GAAG,GAAG,IAAI,IAAI,EAAE,aAAa,EAAE,SAAS,QAAQ,CACzE,CACJ;EAEA,OAAO,wBAAwB,yBAAyB,OAAO;CACnE;;;;;;;;;CAUA,OAAO,kBAAkB,cAAsB,MAAgB;EAC3D,OAAO,wBAAwB,wBAC3B,wBAAwB,cAAc,YAAY,CAAC,CAC9C,KAAI,WAAU,GAAG,GAAG,KAAK,SAAS,QAAQ,CACnD;CACJ;;;;;;;;;;;;CAaA,OAAe,cAAc,cAA6B;EACtD,MAAM,QAAQ,iBAAiB,YAAY;EAE3C,QADkB,MAAM,SAAS,IAAI,QAAQ,CAAC,YAAY,EAAA,CACzC,KAAI,SACjB,GAAG,GAAG,IAAI,IAAI,kBAAkB,EAAE,GAAG,IAAI,kBAAkB,IAAI,EAAE,GAAG,EACxE;CACJ;;;;;;;;CASA,OAAO,kBAAkB,cAAsB,MAA6B;EACxE,MAAM,aAAa,KAAK,WAClB,GAAG,GAAG,IAAI,IAAI,kBAAkB,EAAE,GAAG,aAAa,KAClD,GAAG,GAAG;EACZ,OAAO,GAAG,wBAAwB,KAAK,SAAS,IAAI,WAAW;CACnE;;;;;;;;;;;;;;;;;;;;;;;;CAyBA,OAAO,6BACH,cACA,OACA,YACe;EACf,IAAI;EACJ,IAAI;GACA,OAAO,sBAAsB,UAAU;EAC3C,QAAQ;GACJ;EACJ;EACA,IAAI,CAAC,QAAQ,KAAK,OAAO,WAAW,GAAG,OAAO,KAAA;EAC9C,IAAI,CAAC,MAAM,KAAK,SAA+B,OAAO,KAAA;EAEtD,MAAM,QAAQ,wBAAwB,kBAAkB,cAAc,IAAI;EAC1E,MAAM,SAAS,GAAG,GAAG,KAAK;EAW1B,MAAM,OAAO,KAAK,OAAO,KAAK,GAAG,MAC7B,GAAG,IAAI,EAAE,IAAI,EAAE,KAAK,IAAI,IAAI,IAAI,EAAE,OAAO,EAAE,IAAI,IAAI,IAAI,EAAE,aAAa,EAAE,EAC5E;EAOA,MAAM,eAAe,KAAK,SAAS,WAC7B,GAAG,gBAAgB,OAAO,yBAAyB,MAAM,MAAM,wBAAwB,kBAAkB,cAAc,GAAG,UAAU,EAAE,KACtI,GAAG,eAAe,OAAO,yBAAyB;EAExD,OAAO,GAAG;;2BAES,IAAI,KAAK,MAAM,GAAG,IAAI,EAAE;;;;6CAIN,OAAO,sBAAsB,MAAM;;;wBAGxD,aAAa;;;CAGjC;;;;;CAMA,OAAO,0BACH,cACA,OACA,YACe;EACf,IAAI;EACJ,IAAI;GACA,OAAO,sBAAsB,UAAU;EAC3C,QAAQ;GACJ;EACJ;EACA,IAAI,CAAC,MAAM,OAAO,KAAA;EAClB,MAAM,SAAS,MAAM,KAAK;EAC1B,IAAI,CAAC,QAAQ,OAAO,KAAA;EAEpB,MAAM,OAAO,GAAG,WAAW,OAAO,IAAI,wBAAwB,kBAAkB,cAAc,IAAI,EAAE;EAepG,MAAM,OAAO,KAAK,SAAS,kBACd;GACL,MAAM,YAAY,wBAAwB,8BAA8B,cAAc,IAAI;GAK1F,OAAO,YACD,GAAG,IAAI,KAAK,eAAe,UAAU,QAAQ,IAAI,IAAI,GAAG,uBAAuB,OAAO,EAAE,gBACxF;EACV,EAAA,CAAG,IACD;EAEN,IAAI,CAAC,KAAK,OAAO,OAAO;EAExB,MAAM,cAAc,MAAM,KAAK,MAAM;EACrC,IAAI,CAAC,aAAa,OAAO;EAgBzB,OAAO,GAAG,IAAI,KAAK,4BAHJ,KAAK,WACd,GAAG,GAAG,IAAI,IAAI,kBAAkB,EAAE,GAAG,aAAa,KAClD,GAAG,GAAG,eAC0C,IAAI,YAAY;CAC1E;;;;;;;;;CAUA,OAAO,0BACH,aACA,OACA,YACK;EACL,MAAM,aAAoB,CAAC,GAAG,aAAa,KAAK,CAAC;EAEjD,IAAI,YACA,WAAW,KAAK,IAAI,UAAU,CAAC;EAGnC,OAAO;CACX;;;;CAKA,OAAO,mBACH,WACA,UACA,UACA,aACA,aACA,gBACA,gBACA,UACA,mBACC;EACD,MAAM,EAAE,gBAAgB,oBAAoB,KAAK,wBAC7C,UACA,UACA,aACA,aACA,gBACA,gBACA,QACJ;EAEA,IAAI,QAAQ;EAGZ,KAAK,MAAM,EAAE,OAAO,eAAe,gBAC/B,QAAQ,MAAM,UAAU,OAAO,SAAS;EAI5C,MAAM,gBAAgB,CAAC,GAAG,eAAe;EACzC,IAAI,mBACA,cAAc,KAAK,GAAG,iBAAiB;EAI3C,IAAI,cAAc,SAAS,GACvB,QAAQ,MAAM,MAAM,IAAI,GAAG,aAAa,CAAC;EAG7C,OAAO;CACX;;;;;;;;CASA,OAAO,wBACH,gBACA,UACA,UACA,aACA,aACA,gBACA,gBACA,UACA,mBACC;EACD,QAAQ,SAAS,MAAjB;GACI,KAAK,OACD,OAAO,KAAK,wBACR,gBAAgB,SAAS,UAAU,aAAa,aAChD,gBAAgB,UAAU,UAAU,iBACxC;GAEJ,KAAK,cACD,OAAO,KAAK,wBACR,gBAAgB,SAAS,SAAS,gBAAgB,UAAU,UAAU,iBAC1E;GAEJ,KAAK;GACL,KAAK;GACL,KAAK,WAAW;IACZ,MAAM,gBAAgB,CAClB,KAAK,6BAA6B,UAAU,aAAa,aAAa,QAAQ,GAC9E,GAAI,qBAAqB,CAAC,CAC9B;IACA,OAAO,eAAe,MAAM,IAAI,GAAG,aAAa,CAAC;GACrD;GAEA,SAEI,MAAM,IAAI,MAAM,0BAA0B,KAAK,UAAU,QAAU,GAAG;EAE9E;CACJ;;;;CAKA,OAAe,wBACX,gBACA,UACA,aACA,aACA,gBACA,UACA,UACA,mBACC;EACD,IAAI,QAAQ;EACZ,IAAI,eAAe;EACnB,MAAM,kBAAkB,aAAiB,WAAW;EAGpD,KAAK,IAAI,QAAQ,SAAS,SAAS,GAAG,SAAS,GAAG,SAAS;GACvD,MAAM,WAAW,SAAS;GAC1B,MAAM,EAAE,eAAe,gBAAgB,KAAK,mBAAmB,UAAU,OAAO,eAAe;GAC/F,MAAM,cAAc,KAAK,0BAA0B,SAAS,GAAG,IAAI,CAAC,CAAC;GACrE,MAAM,YAAY,KAAK,0BAA0B,SAAS,GAAG,EAAE,CAAC,CAAC;GAEjE,MAAM,YAAY,SAAS,SAAS,aAAa;GACjD,MAAM,UAAU,SAAS,SAAS,WAAW;GAE7C,IAAI,CAAC,aAAa,CAAC,SACf,MAAM,IAAI,MAAM,wCAAwC,cAAc,MAAM,aAAa;GAG7F,MAAM,EAAE,WAAW,cAAc,KAAK,yBAClC,cACA,WACA,SACA,aACA,WACA,eACA,WACJ;GAEA,QAAQ,MAAM,UAAU,WAAW,SAAS;GAC5C,eAAe;EACnB;EAEA,IAAI,iBAAiB,aACjB,MAAM,IAAI,MAAM,wDAAwD;EAG5E,MAAM,gBAAgB,CAAC,GAAG,gBAAgB,QAAQ,CAAC;EACnD,IAAI,mBACA,cAAc,KAAK,GAAG,iBAAiB;EAG3C,OAAO,MAAM,MAAM,IAAI,GAAG,aAAa,CAAC;CAC5C;;;;CAKA,OAAe,wBACX,gBACA,SACA,gBACA,UACA,UACA,mBACC;EACD,MAAM,gBAAgB,SAAS,SAAS,QAAQ,KAAK;EACrD,IAAI,CAAC,eACD,MAAM,qBAAqB,QAAQ,KAAK;EAG5C,MAAM,oBAAoB,cAAc,QAAQ;EAChD,MAAM,oBAAoB,cAAc,QAAQ;EAEhD,IAAI,CAAC,mBACD,MAAM,IAAI,MAAM,kBAAkB,QAAQ,aAAa,iCAAiC,QAAQ,MAAM,EAAE;EAE5G,IAAI,CAAC,mBACD,MAAM,IAAI,MAAM,kBAAkB,QAAQ,aAAa,iCAAiC,QAAQ,MAAM,EAAE;EAG5G,MAAM,iBAAiB,CAAC,GAAG,mBAAmB,QAAQ,CAAC;EACvD,IAAI,qBAAqB,kBAAkB,SAAS,GAChD,eAAe,KAAK,GAAG,iBAAiB;EAG5C,OAAO,eACF,UAAU,eAAe,GAAG,gBAAgB,iBAAiB,CAAC,CAAC,CAC/D,MAAM,IAAI,GAAG,cAAc,CAAC;CACrC;;;;;;;;;;;;;;;;;;CAoBA,OAAe,mBACX,UACA,OACA,iBAC8C;EAC9C,MAAM,OAAO,SAAS;EACtB,OAAO;GACH,eAAe,KAAK,yBAAyB,KAAK,GAAG,IAAI,CAAC,CAAC,OACnD,UAAU,IAAI,kBAAkB,SAAS,QAAQ,EAAE,CAAC;GAC5D,aAAa,KAAK,yBAAyB,KAAK,GAAG,EAAE,CAAC,CAAC,MAAM,KAAK;EACtE;CACJ;CAEA,OAAO,yBAAyB,SAAsC;EAClE,IAAI,MAAM,QAAQ,OAAO,GACrB,OAAO,QAAQ,KAAI,QAAO,IAAI,SAAS,GAAG,IAAI,IAAI,MAAM,GAAG,CAAC,CAAC,KAAK,EAAE;EAExE,OAAO,CAAC,QAAQ,SAAS,GAAG,IAAI,QAAQ,MAAM,GAAG,CAAC,CAAC,KAAK,EAAE;CAC9D;;;;CAKA,OAAO,0BAA0B,SAAsC;EACnE,IAAI,MAAM,QAAQ,OAAO,GACrB,OAAO,QAAQ,KAAI,QAAO,cAAc,GAAG,CAAC;EAEhD,OAAO,CAAC,cAAc,OAAO,CAAC;CAClC;;;;;;;;;;;;;;;;;;;CAqBA,OAAO,4BACH,OACA,cAMmD;EACnD,MAAM,SAAS,wBAAwB,oBAAoB,OAAO,aAAa,QAAQ;EAOvF,IACI,CAAC,MAAM,QAAQ,aAAa,MAAM,KAClC,aAAa,OAAO,WAAW,KAC/B,CAAC,aAAa,OAAO,OAAO,MAAM,OAAO,MAAM,YAAY,OAAO,SAAS,CAAC,CAAC,GAE7E,MAAM,IAAI,MAAM,4DAA4D;EAGhF,MAAM,gBAAgB,KAAK,aAAa,OAAO,KAAK,GAAG,EAAE;EACzD,MAAM,aAAa,aAAa,YAAY;EAE5C,IAAI;EACJ,QAAQ,YAAR;GACI,KAAK;IACD,WAAW;IACX;GACJ,KAAK;IACD,WAAW;IACX;GACJ,KAAK;IACD,WAAW;IACX;EACR;EAIA,OAAO;GACH,SAAS,GAHW,GAAG,OAAO,GAAG,IAAI,IAAI,QAAQ,EAAE,GAAG,IAAI,IAAI,aAAa;GAI3E,QAAQ,aAAa,aAAa,OAC5B,GAAG,IAAI,OAAO,GAAG,IAAI,IAAI,QAAQ,EAAE,GAAG,IAAI,IAAI,aAAa,EAAE,MAAM,aAAa,cAChF,KAAA;GACN,gBAAgB,GAAG,IAAI,OAAO,GAAG,IAAI,IAAI,QAAQ,EAAE,GAAG,IAAI,IAAI,aAAa,EAAE;EACjF;CACJ;;;;;;;;;;CAWA,OAAe,oBAAoB,OAAqB,UAA+B;EACnF,MAAM,UAAU,gBAAgB,KAAK;EACrC,MAAM,SAAS,UAAU;EACzB,IAAI,CAAC,QAAQ;GACT,MAAM,QAAQ,OAAO,QAAQ,WAAW,CAAC,CAAC,CAAC,CACtC,QAAQ,GAAG,OAAO,eAAe,CAAC,CAAC,CAAC,CACpC,KAAK,CAAC,UAAU,IAAI;GACzB,MAAM,SAAS,WACX,4BAA4B,SAAS,QACpC,MAAM,SAAS,IACV,4CAA4C,MAAM,KAAK,IAAI,EAAE,KAC7D,6DACN,yBACJ;EACJ;EACA,IAAI,CAAC,eAAe,MAAM,GACtB,MAAM,SAAS,WACX,aAAa,SAAS,oCAAoC,cAAc,MAAM,KAAK,UAAU,6FAE7F,yBACJ;EAEJ,OAAO;CACX;AACJ;;AAGA,IAAM,iBAAiB,WAA4B;CAC/C,MAAM,aAAc,QAA0C;CAC9D,OAAO,OAAO,eAAe,aAAa,WAAW,KAAK,MAAM,CAAC,CAAC,YAAY,IAAI;AACtF;;AAGA,IAAM,kBAAkB,WACpB,gCAAgC,KAAK,cAAc,MAAM,CAAC;;;;;AAM9D,IAAa,2BAA2B;;;;;;;;;;ACr6FxC,SAAgB,mBAAmB,UAAqC;CAEpE,IAAI,aAAa,QAAQ,GAAG,OAAO;CAEnC,OAAO,SAAS,SAAS,SAAS,SAAS,SAAS,SAAS;AACjE;;;;;;;;;;;;;;;AAgBA,SAAS,kBACL,MACA,UACuB;CACvB,MAAM,UAAU,aAAa,QAAQ,IAAI,SAAS,QAAQ,aAAa,KAAA;CACvE,MAAM,YAAY,OAAO,KAAK,IAAI,CAAC,CAAC,MAChC,QAAO,QAAQ,sBACR,EAAE,WAAW,OAAO,YACpB,OAAO,KAAK,SAAS,YAAY,KAAK,SAAS,QAAQ,CAAC,MAAM,QAAQ,KAAK,IAAI,CAC1F;CACA,OAAO,YAAY,KAAK,aAAwC;AACpE;;;;;;;;;;;;;;;;AAiBA,SAAS,qBAAqB,OAAgB,UAAyC;CACnF,IAAI,UAAU,SAAS,YAAY,OAAO,UAAU,UAAU,OAAO;CACrE,MAAM,SAAS,WAAW,KAAK;CAC/B,OAAO,MAAM,MAAM,IAAI,OAAO;AAClC;;;;;;;;;;;;;;;AAgBA,SAAS,WAAW,OAAyB;CACzC,IAAI,iBAAiB,MAAM,OAAO,MAAM,MAAM,QAAQ,CAAC,IAAI,OAAO,MAAM,YAAY;CAEpF,IAAI,SAAS,OAAO,UAAU,YAAa,MAA+B,WAAW,QACjF,OAAQ,MAA8B,SAAS;CAEnD,IAAI,OAAO,UAAU,YAAY,OAAO,UAAU,UAAU,OAAO;CACnE,IAAI,OAAO,UAAU,YAAY,sBAAsB,KAAK,KAAK,GAAG,OAAO;CAC3E,MAAM,OAAO,IAAI,KAAK,KAAK;CAG3B,OAAO,MAAM,KAAK,QAAQ,CAAC,IAAI,QAAQ,KAAK,YAAY;AAC5D;;;;;AAMA,SAAS,aAAa,OAAgB,UAAyC;CAC3E,IAAI,UAAU,SAAS,QAAQ,OAAO,WAAW,KAAK;CACtD,OAAO,qBAAqB,OAAO,QAAQ;AAC/C;;;;;;;;;AAUA,SAAgB,aACZ,KACA,YACuB;CACvB,MAAM,aAAa,WAAW;CAC9B,IAAI,CAAC,YAAY,OAAO;CAExB,MAAM,MAA+B,CAAC;CACtC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,GAAG,GACzC,IAAI,OAAO,aAAa,OAAO,WAAW,IAA4B;CAE1E,OAAO;AACX;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCA,SAAgB,gBACZ,KACA,YACuB;CACvB,MAAM,aAAa,WAAW;CAC9B,IAAI,CAAC,YAAY,OAAO;CAExB,MAAM,SAAS,mBAAmB;CAClC,MAAM,yBAAS,IAAI,IAAY;CAC/B,MAAM,QAAQ,SAAiB;EAC3B,OAAO,IAAI,IAAI;EACf,OAAO,IAAI;CACf;CAGA,IAAI;CAEJ,KAAK,MAAM,CAAC,KAAK,aAAa,OAAO,QAAQ,UAAU,GAAG;EACtD,IAAI,aAAa,UAAU,MAAM,GAAG;EACpC,KAAK,GAAG;EACR,IAAI,SAAS,YAAY,KAAK,SAAS,UAAU;EACjD,IAAI,SAAS,SAAS,YAAY;GAC9B,cAAc,2BAA2B,UAAU;GACnD,MAAM,WAAW,aAAa,WAAW,GAAG;GAC5C,IAAI,UAAU,SAAS,aAAa;IAChC,KAAK,kBAAkB,YAAY,SAAS,QAAQ,CAAC;IACrD,KAAK,SAAS,QAAQ;GAC1B;EACJ;CACJ;CAEA,IAAI,OAAO,OAAO,KAAK,MAAM,QAAQ,IAAI,QAAQ,GAC7C,IAAI,WAAY,IAAI,SAAuB,QACvC,UAAS,CAAC,OAAO,IAAI,OAAO,UAAU,WAAW,QAAQ,OAAQ,OAA+B,KAAK,CAAC,CAC1G;CAEJ,OAAO;AACX;AAEA,SAAS,aACL,WACA,kBACA,OACA,UACO;CACP,IAAI,UAAU,UAGV,OAAO,gBAAgB,aAAa,EAAE,GAAG,UAAU,GAAG,gBAAgB,GAAG,gBAAgB;CAG7F,MAAM,UAAU,sBAAsB,WAAW,kBAAkB,QAAQ;CAC3E,MAAM,OAAO,iBAAiB;CAC9B,OAAO,0BAA0B,SAAS,MAAM;EAC5C,IAAI;EACJ;EAOA,QAAQ,gBACJ,kBAAkB,WAAW,gBAAgB,GAC7C,gBACJ;CACJ,CAAC;AACL;;;;;;;;;;;AAYA,SAAgB,sBACZ,WACA,kBACA,UACM;CACN,MAAM,UAAU,iBAAiB,WAAW,kBAAkB,QAAQ;CACtE,IAAI,SAAS,OAAO;CACpB,OAAO,OAAO,UAAU,OAAO,KAAK,SAAS,CAAC,CAAC,OAAO,EAAE;AAC5D;;;;;;;;AASA,SAAgB,UACZ,KACA,YACA,UACuB;CACvB,MAAM,oBAAoB,2BAA2B,UAAU;CAC/D,MAAM,aAAa,kBAAkB,KAAK,UAAU;CAKpD,KAAK,MAAM,CAAC,KAAK,aAAa,OAAO,QAAQ,iBAAiB,GAAG;EAC7D,MAAM,UAAU,IAAI,SAAS,gBAAgB;EAC7C,IAAI,YAAY,KAAA,KAAa,YAAY,MAAM;EAE/C,IAAI,SAAS,gBAAgB,UAAU,MAAM,QAAQ,OAAO,GAAG;GAC3D,MAAM,mBAAmB,SAAS,OAAO;GACzC,WAAW,OAAO,QAAQ,KAAK,SAC3B,aACI,mBAAmB,QAAQ,IAAI,kBAAkB,MAAM,QAAQ,IAAI,MACnE,kBACA,OACA,QACJ,CAAC;EACT,OAAO,IAAI,SAAS,gBAAgB,SAAS,OAAO,YAAY,YAAY,CAAC,MAAM,QAAQ,OAAO,GAC9F,WAAW,OAAO,aAAa,SAAoC,SAAS,OAAO,GAAG,OAAO,QAAQ;CAE7G;CAEA,OAAO,gBAAgB,YAAY,UAAU;AACjD;;;;;;;;;;;;;;;AAgBA,SAAgB,UACZ,KACA,YACA,UACuB;CACvB,MAAM,oBAAoB,2BAA2B,UAAU;CAC/D,MAAM,OAAgC,CAAC;CAEvC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,GAAG,GAAG;EAC5C,MAAM,WAAW,aAAa,mBAAmB,GAAG;EAEpD,IAAI,YAAY,MAAM,QAAQ,KAAK,GAC/B,KAAK,OAAO,MAAM,KAAK,SACnB,aACI,mBAAmB,QAAQ,IAAI,kBAAkB,MAAM,QAAQ,IAAI,MACnE,SAAS,OAAO,GAChB,UACA,QACJ,CAAC;OACF,IAAI,YAAY,OAAO,UAAU,YAAY,UAAU,MAC1D,KAAK,OAAO,aAAa,OAAkC,SAAS,OAAO,GAAG,UAAU,QAAQ;OAEhG,KAAK,OAAO,aAAa,OAAO,WAAW,aAAa,IAA4B;CAE5F;CAEA,OAAO,gBAAgB,MAAM,UAAU;AAC3C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACzTA,eAAsB,oBAClB,QACA,OACA,YACA,QACA,SACiB;CAOjB,KAAI,MANkB,OACjB,OAAO,EAAE,SAAS,GAAW,IAAI,CAAC,CAAC,CACnC,KAAK,KAAK,CAAC,CACX,MAAM,IAAI,GAAG,UAAU,CAAC,CAAC,CACzB,MAAM,CAAC,EAAA,CAEA,SAAS,GACjB,OAAO,SAAS,UAAU,QAAQ,cAAc;CAGpD,OAAO,SAAS,SAAS,OAAO;AACpC;;;;ACmBA,IAAM,iBAAiB,OAAe,WAClC,sBAAsB,OAAO,MAAM;AAEvC,SAAS,OAAO,OAAgB,MAA0B,OAAe,MAA2B;CAChG,MAAM,MAAM,OAAO,MAAM,QAA6C,KAAA;CACtE,IAAI,CAAC,KACD,MAAM,cAAc,OAAO,MAAM,KAAK,WAAW,QAAQ,IAAI,wBAAwB;CAEzF,OAAO;AACX;;AAGA,SAAgB,oBACZ,UACA,SACA,OACe;CACf,MAAM,QAAQ,SAAS,SAAS,QAAQ,KAAK;CAC7C,IAAI,CAAC,OACD,MAAM,cAAc,OAAO,aAAa,QAAQ,MAAM,kBAAkB;CAE5E,OAAO;EACH;EACA,cAAc,OAAO,OAAO,QAAQ,cAAc,OAAO,QAAQ;EACjE,cAAc,OAAO,OAAO,QAAQ,cAAc,OAAO,QAAQ;EACjE;EACA,SAAS,QAAQ,cAAc,CAAC;CACpC;AACJ;;AAGA,IAAM,cAAc,SAAoC;CACpD,MAAM,MAAM,MAAM,QAAQ,IAAI,IAAI,KAAK,KAAK;CAC5C,OAAO,IAAI,SAAS,GAAG,IAAI,IAAI,MAAM,GAAG,CAAC,CAAC,KAAK;AACnD;;AAGA,IAAM,aAAa,SAAgD;CAC/D,MAAM,MAAM,MAAM,QAAQ,IAAI,IAAI,KAAK,KAAK;CAC5C,OAAO,IAAI,SAAS,GAAG,IAAI,IAAI,MAAM,GAAG,CAAC,CAAC,KAAK,KAAA;AACnD;;;;;;;;;AAUA,SAAgB,qBACZ,UACA,UACA,iBACA,iBACA,OACe;CACf,MAAM,eAAe,SAChB,KAAI,SAAQ,KAAK,KAAK,CAAC,CACvB,MAAK,UAAS,UAAU,mBAAmB,UAAU,eAAe;CAEzE,IAAI,CAAC,cACD,MAAM,cAAc,OAAO,kEAAkE;CAGjG,MAAM,QAAQ,SAAS,SAAS,YAAY;CAC5C,IAAI,CAAC,OACD,MAAM,cAAc,OAAO,aAAa,aAAa,kBAAkB;CAG3E,IAAI;CACJ,IAAI;CACJ,IAAI,gBAAgB;CAEpB,KAAK,MAAM,QAAQ,UAAU;EACzB,MAAM,YAAY,UAAU,KAAK,GAAG,IAAI,KAAK;EAC7C,MAAM,UAAU,UAAU,KAAK,GAAG,EAAE,KAAK,KAAK;EAI9C,IAAI,YAAY,gBAAgB,cAAc,iBAC1C,mBAAmB,WAAW,KAAK,GAAG,EAAE;OACrC,IAAI,cAAc,gBAAgB,YAAY,iBACjD,mBAAmB,WAAW,KAAK,GAAG,IAAI;OACvC,IAAI,YAAY,gBAAgB,cAAc,iBACjD,mBAAmB,WAAW,KAAK,GAAG,EAAE;OACrC,IAAI,cAAc,gBAAgB,YAAY,iBACjD,mBAAmB,WAAW,KAAK,GAAG,IAAI;EAG9C,gBAAgB,KAAK;CACzB;CAEA,IAAI,CAAC,oBAAoB,CAAC,kBACtB,MAAM,cACF,OACA,kCAAkC,gBAAgB,SAAS,gBAAgB,aAAa,aAAa,EACzG;CAGJ,OAAO;EACH;EACA,cAAc,OAAO,OAAO,kBAAkB,OAAO,QAAQ;EAC7D,cAAc,OAAO,OAAO,kBAAkB,OAAO,QAAQ;EAC7D;EAGA,SAAS,CAAC;CACd;AACJ;;;;;;;;AASA,eAAsB,mBAClB,IACA,SACA,UACA,UACA,SACa;CACb,MAAM,aAAa,CACf,GAAG,QAAQ,cAAc,QAAQ,GACjC,GAAG,QAAQ,cAAc,QAAQ,CACrC;CAMA,MAAK,MALgB,GAAG,OAAO,QAAQ,KAAK,CAAC,CAAC,MAAM,IAAI,GAAG,UAAU,CAAC,EAAA,CAK1D,YAAY,OAAO,GAC3B,MAAM,MAAM,oBACR,IACA,QAAQ,OACR,YACA,0BAA0B,SAAS,UAAU,QAAQ,OAAO,KAAK,SAAS,qDAE1E,OAAO,QAAQ,SAAS,kBAAkB,QAAQ,OAAO,KAAK,SAAS,SAC/D,SAAS,aACrB;CAGJ,OAAO,KAAK,aAAa,QAAQ,SAAS,IAAI,SAAS,QAAQ,QAAQ,OAAO,GAAG,UAAU;AAC/F;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2CA,eAAsB,wBAClB,IACA,SACA,UACA,OACA,gBACa;CACb,MAAM,eAAe,iBACf,MAAM,GACH,OAAO,EAAE,UAAU,QAAQ,aAAa,CAAC,CAAC,CAC1C,KAAK,QAAQ,KAAK,CAAC,CACnB,UAAU,eAAe,OAAO,GAAG,QAAQ,cAAc,eAAe,QAAQ,CAAC,CAAC,CAClF,MAAM,IAAI,GAAG,QAAQ,cAAc,QAAQ,GAAG,eAAe,SAAS,CAAC,IAC1E,MAAM,GACH,OAAO,EAAE,UAAU,QAAQ,aAAa,CAAC,CAAC,CAC1C,KAAK,QAAQ,KAAK,CAAC,CACnB,MAAM,GAAG,QAAQ,cAAc,QAAQ,CAAC;CAKjD,MAAM,+BAAe,IAAI,IAAqB;CAC9C,KAAK,MAAM,OAAO,cAA8C;EAC5D,IAAI,IAAI,aAAa,QAAQ,IAAI,aAAa,KAAA,GAAW;EACzD,aAAa,IAAI,OAAO,IAAI,QAAQ,GAAG,IAAI,QAAQ;CACvD;CAEA,MAAM,6BAAa,IAAI,IAA+B;CACtD,KAAK,MAAM,QAAQ,OAAO;EACtB,IAAI,KAAK,OAAO,QAAQ,KAAK,OAAO,KAAA,GAAW;EAC/C,WAAW,IAAI,OAAO,KAAK,EAAE,GAAG,IAAI;CACxC;CAEA,MAAM,UAAU,CAAC,GAAG,aAAa,QAAQ,CAAC,CAAC,CACtC,QAAQ,CAAC,SAAS,CAAC,WAAW,IAAI,GAAG,CAAC,CAAC,CACvC,KAAK,GAAG,WAAW,KAAK;CAC7B,MAAM,QAAQ,CAAC,GAAG,WAAW,QAAQ,CAAC,CAAC,CAClC,QAAQ,CAAC,SAAS,CAAC,aAAa,IAAI,GAAG,CAAC,CAAC,CACzC,KAAK,GAAG,UAAU,IAAI;CAG3B,MAAM,cAAc,CAAC,GAAG,WAAW,QAAQ,CAAC,CAAC,CACxC,QAAQ,CAAC,KAAK,UAAU,aAAa,IAAI,GAAG,KAAK,KAAK,SAAS,OAAO,KAAK,KAAK,KAAK,CAAC,CAAC,SAAS,CAAC,CAAC,CAClG,KAAK,GAAG,UAAU,IAAI;CAE3B,IAAI,QAAQ,SAAS,GACjB,MAAM,YAAY,IAAI,SAAS,UAAU,OAAO;CAGpD,IAAI,MAAM,SAAS,GACf,MAAM,GAAG,OAAO,QAAQ,KAAK,CAAC,CACzB,OAAO,MAAM,KAAI,UAAS;GACtB,QAAQ,aAAa,OAAO;GAC5B,QAAQ,aAAa,OAAO,KAAK;EAClC,GAAI,KAAK,SAAS,CAAC;CACvB,EAAE,CAAC,CAAC,CACH,oBAAoB;CAG7B,KAAK,MAAM,QAAQ,aACf,MAAM,GAAG,OAAO,QAAQ,KAAK,CAAC,CACzB,IAAI,KAAK,KAAM,CAAC,CAChB,MAAM,IACH,GAAG,QAAQ,cAAc,QAAQ,GACjC,GAAG,QAAQ,cAAc,KAAK,EAAE,CACpC,CAAC;AAEb;;;;;;;;;;;;;;;;;AAkBA,eAAsB,oBAClB,IACA,SACA,UACA,UACA,QACA,SACa;CACb,MAAM,aAAa,CACf,GAAG,QAAQ,cAAc,QAAQ,GACjC,GAAG,QAAQ,cAAc,QAAQ,CACrC;CAGA,MAAK,MAFgB,GAAG,OAAO,QAAQ,KAAK,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,MAAM,IAAI,GAAG,UAAU,CAAC,EAAA,CAEtE,YAAY,OAAO,GAC3B,MAAM,MAAM,oBACR,IACA,QAAQ,OACR,YACA,2CAA2C,QAAQ,OAAO,KAAK,SAAS,SACpE,SAAS,qDACb,OAAO,QAAQ,SAAS,kBAAkB,QAAQ,OAAO,KAAK,SAAS,SAC/D,SAAS,aACrB;CAGJ,OAAO,KAAK,YAAY,QAAQ,SAAS,SAAS,SAAS,MAAM,QAAQ,OAAO,GAAG,UAAU;AACjG;;;;;;;;;;AAWA,eAAe,YACX,IACA,SACA,UACA,SACa;CACb,MAAM,aAAa,CACf,GAAG,QAAQ,cAAc,QAAQ,GACjC,QAAQ,QAAQ,cAAc,OAAO,CACzC;CAEA,MAAM,WAAU,MADK,GAAG,OAAO,QAAQ,KAAK,CAAC,CAAC,MAAM,IAAI,GAAG,UAAU,CAAC,EAAA,CAC/C,YAAY;CAEnC,IAAI,WAAW,QAAQ,QAAQ;CAW/B,KAAI,MAToB,GACnB,OAAO,EAAE,SAAS,GAAW,IAAI,CAAC,CAAC,CACnC,KAAK,QAAQ,KAAK,CAAC,CACnB,MAAM,IAAI,GAAG,UAAU,CAAC,CAAC,CACzB,MAAM,CAAC,EAAA,CAKE,WAAW,GAAG;CAE5B,MAAM,SAAS,UACX,yBAAyB,QAAQ,SAAS,QAAQ,MAAM,QAAQ,OAAO,yBACtD,QAAQ,MAAM,qDAC/B,cACJ;AACJ;;;;;;;;;;;AChXA,SAAgB,kBAAkB,YAAuE;CACrG,MAAM,QAAQ,kBAAkB,UAAU;CAC1C,IAAI,CAAC,OAAO,OAAO,KAAA;CAEnB,OAAO;EACH;EACA,cAHc,YAAY,WAAA,GAAqE,OAAA,EAGzE,cAAc,YAAY,KAAK;CACzD;AACJ;;;;;;;;AASA,SAAgB,iBACZ,YACA,OACuB;CACvB,MAAM,WAAW,kBAAkB,UAAU;CAC7C,IAAI,CAAC,UAAU,OAAO,KAAA;CAItB,MAAM,UAAW,QAAQ,gBAAgB,KAAK,IAAI,KAAA;CAClD,IAAI,CAAC,SAAS,OAAO,KAAA;CACrB,MAAM,SAAS,QAAQ,SAAS,UAAU,QAAQ,SAAS;CAC3D,IAAI,QAAQ,OAAO;CACnB,KAAK,MAAM,UAAU,OAAO,OAAO,OAAO,GACtC,IAAK,OAA6B,SAAS,SAAS,YAAY,OAAO;AAG/E;;;;;;;;AASA,SAAgB,oBACZ,YACA,OACA,aACe;CACf,IAAI,gBAAgB,MAAM,OAAO,KAAA;CACjC,MAAM,SAAS,iBAAiB,YAAY,KAAK;CACjD,IAAI,CAAC,QAAQ,OAAO,KAAA;CACpB,OAAO,gBAAgB,SAAS,UAAU,MAAM,IAAI,OAAO,MAAM;AACrE;;;;;;;AAQA,SAAgB,eACZ,YACA,YACA,OACA,aACK;CACL,MAAM,YAAY,oBAAoB,YAAY,OAAO,WAAW;CACpE,IAAI,WAAW,WAAW,KAAK,SAAS;CACxC,OAAO;AACX;;;;;;AAOA,SAAgB,cACZ,OACA,YACA,OACA,aACe;CACf,MAAM,YAAY,oBAAoB,YAAY,OAAO,WAAW;CACpE,IAAI,CAAC,WAAW,OAAO;CACvB,OAAO,QAAQ,IAAI,OAAO,SAAS,IAAI;AAC3C;;;;;;;;;;;AAYA,SAAgB,gBAAgB,MAAc,KAAa,YAAuC;CAC9F,OAAO,SAAS,SACZ,IAAI,IAAI,QAAQ,KAAK,6FACI,WAAW,MAAM,uEAC1C,cACJ;AACJ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AChEA,SAAS,mBACL,UACA,YACiD;CACjD,MAAM,QAA2D,CAAC;CAClE,MAAM,SAAS,UAAU,mBAAmB;CAC5C,IAAI,QAAQ,aAAa,MAAM,KAAK,OAAO,WAAW;CACtD,MAAM,MAAM,YAAY;CACxB,IAAI,KAAK,aAAa,MAAM,KAAK,IAAI,WAAW;CAChD,OAAO;AACX;;;;;;;;;AAyCA,eAAsB,sBAClB,KACA,YACA,MACA,OACA,aACA,eACc;CACd,MAAM,QAAQ,mBAAmB,IAAI,UAAU,UAAU;CACzD,IAAI,MAAM,WAAW,GAAG,OAAO,CAAC;CAEhC,IAAI,CAAC,YAID,MAAM,IAAI,MACN,kBAAkB,KAAK,iLAE3B;CAGJ,IAAI,CAAC,IAAI,aACL,MAAM,IAAI,MACN,kBAAkB,WAAW,KAAK,iVAItC;CAEJ,MAAM,UAAU,IAAI,YAAY;CAOhC,MAAM,gBAAyC;CAC/C,MAAM,UAAoC;EAAE,GAAG;EAAe;CAAc;CAE5E,MAAM,aAAoB,CAAC;CAC3B,KAAK,MAAM,QAAQ,OAAO;EAQtB,MAAM,YAAmC,MAAM,KAAK;GANhD;GACA;GACA,WAAW,YAAY;GACvB,OAAO,YAAY;GACnB;EAEgD,CAAK;EAEzD,IAAI,CAAC,WAAW;EAOhB,IAAI,CAAC,UAAU,UAAU,CAAC,UAAU,SAChC,MAAM,IAAI,MACN,kBAAkB,WAAW,KAAK,+LAGtC;EAGJ,IAAI,UAAU,QACV,WAAW,KAAK,GAAG,wBAAwB,sBACvC,UAAU,QAAQ,OAAO,MAAM,OACnC,CAAC;EAEL,IAAI,UAAU,SAAS;GACnB,MAAM,UAAU,wBAAwB,uBACpC,UAAU,SAAS,OAAO,MAAM,OACpC;GACA,IAAI,SAAS,WAAW,KAAK,OAAO;EACxC;CACJ;CACA,OAAO;AACX;;;;;AAMA,eAAsB,qBAClB,KACA,YACA,MACA,OACA,aACA,eACwB;CACxB,MAAM,aAAa,MAAM,sBAAsB,KAAK,YAAY,MAAM,OAAO,aAAa,aAAa;CACvG,IAAI,WAAW,WAAW,GAAG,OAAO,KAAA;CACpC,IAAI,WAAW,WAAW,GAAG,OAAO,WAAW;CAC/C,OAAO,IAAI,GAAG,UAAU;AAC5B;;;;;;;;;;;;;AChKA,SAAgB,iBAAoB,OAAU,WAAoB,WAAmB;CACjF,OAAQ,MAA0D,UAAU,WAAW,SAAS;AACpG;;;;;;;;;AAUA,SAAgB,kBACZ,cACA,WACA,MACwC;CACxC,MAAM,OAAO,MAAM,QAAQ,KAAK,GAAG,IAAI,IAAI,KAAK,GAAG,OAAO,CAAC,KAAK,GAAG,IAAI;CACvE,MAAM,KAAK,MAAM,QAAQ,KAAK,GAAG,EAAE,IAAI,KAAK,GAAG,KAAK,CAAC,KAAK,GAAG,EAAE;CAC/D,MAAM,QAAe,CAAC;CACtB,KAAK,IAAI,IAAI,GAAG,IAAI,KAAK,IAAI,KAAK,QAAQ,GAAG,MAAM,GAAG,KAAK;EACvD,MAAM,aAAa,KAAK,MAAM;EAC9B,MAAM,WAAW,GAAG,MAAM;EAG1B,MAAM,UAAU,WAAW,cAAc,UAAU;EACnD,MAAM,QAAQ,WAAW,WAAW,QAAQ;EAC5C,IAAI,CAAC,WAAW,CAAC,OAAO,OAAO,EAAE,SAAS,GAAG,WAAW,MAAM,WAAW;EACzE,MAAM,KAAK,GAAG,SAAS,KAAK,CAAC;CACjC;CACA,OAAO,EAAE,WAAW,MAAM,WAAW,IAAI,MAAM,KAAK,IAAI,GAAG,KAAK,EAAG;AACvE;;;;;;AAOA,SAAS,0BACL,OACA,GAAG,MACF;CACD,OAAO,wBAAwB,mBAAmB,GAAG,IAAI;AAC7D;;;;;;;;;AAUA,SAAS,SAAS,MAAW,QAAmB;CAC5C,OAAO,SAAS,IAAI,MAAM,MAAM,IAAK;AACzC;;;;;;;;AASA,SAAS,WAAW,GAAS,GAA0B;CACnD,IAAI,KAAK,GAAG,OAAO,IAAI,GAAG,CAAC;CAC3B,OAAO,KAAK;AAChB;;;;;;;;;;;;AAaA,SAAS,kBAAkB,gBAAwB,OAAe,MAAqB;CACnF,uBAAO,IAAI,MACP,wBAAwB,MAAM,YAAY,KAAK,eAAe,eAAe,yMAIjF;AACJ;AA6BA,IAAa,kBAAb,MAA6B;CAEb;CACA;CAOA;CATZ,YACI,IACA,UAOA,aACF;EATU,KAAA,KAAA;EACA,KAAA,WAAA;EAOA,KAAA,cAAA;CACR;;;;;;;;;;;;;;;CAgBJ,iBACI,kBACA,UACA,UACwB;EACxB,MAAM,mBAAmB,SAAS,OAAO;EACzC,MAAM,cAAc,sBAAsB,kBAAkB,KAAK,QAAQ;EACzE,OAAO,qBACH;GAAE,UAAU,KAAK;GAAU,aAAa,KAAK;EAAY,GACzD,kBACA,iBAAiB,MACjB,aACA;GACI,WAAW;GACX,OAAO,EACH,WAAW;IACP,YAAY,iBAAiB;IAC7B;IACA,cAAc,SAAS;GAC3B,EACJ;EACJ,GACA;GAAE,YAAY;GAAkB,UAAU,KAAK;EAAS,CAC5D;CACJ;;;;;;;;;;;CAYA,MAAM,uBACF,kBACA,UACA,UACwB;EACxB,MAAM,mBAAmB,SAAS,OAAO;EACzC,MAAM,cAAc,sBAAsB,kBAAkB,KAAK,QAAQ;EACzE,OAAO,cACH,MAAM,KAAK,iBAAiB,kBAAkB,UAAU,QAAQ,GAChE,kBACA,WACJ;CACJ;;;;;;;;;;;;;;;CAgBA,MAAc,aACV,WACA,kBACA,WACA,SACsB;EACtB,MAAM,SAAS,SAAS,gBAClB,MAAM,oBAAoB,WAAW,kBAAkB,KAAK,IAAI,KAAK,QAAQ,IAC7E,MAAM,oBAAoB,WAAW,gBAAgB;EAS3D,gBAAgB,QAAmC,gBAAgB;EAEnE,OAAO;GAGH,IAAI,mBAAiB,WAAW,SAAS;GACzC,MAAM,iBAAiB;GACf;EACZ;CACJ;;;;;;;;;;;;;;;;CAiBA,MAAc,UACV,aACA,gBACA,aACgC;EAChC,MAAM,MAA+B,CAAC;EACtC,KAAK,MAAM,OAAO,aACd,IAAI,OAAO,aAAa,IAAI,OAAO,YAAY;EAGnD,OAAO,gBAAgB,MADF,oBAAoB,KAAK,cAAc,GAC7B,cAAc;CACjD;;;;;;;;;;CAWA,mBACI,aACA,WACA,WACG;EACH,MAAM,aAAa,cAAsB;GACrC,MAAM,MAAM,YAAY;GACxB,IAAI,CAAC,KAAK,MAAM,IAAI,MAAM,eAAe,UAAU,4BAA4B;GAC/E,OAAO;EACX;EAEA,IAAI,UAAU,WAAW,GAAG;GACxB,MAAM,SAAS,UAAU,KAAI,OAAM,gBAAc,IAAI,SAAS,CAAC,CAAC,UAAU,EAAE,CAAC,UAAU;GACvF,OAAO,QAAQ,UAAU,UAAU,EAAE,CAAC,SAAS,GAAG,MAAM;EAC5D;EAMA,OAAO,GAAG,GAJQ,UAAU,KAAI,OAAM;GAClC,MAAM,SAAS,gBAAc,IAAI,SAAS;GAC1C,OAAO,IAAI,GAAG,UAAU,KAAI,OAAM,GAAG,UAAU,GAAG,SAAS,GAAG,OAAO,GAAG,UAAU,CAAC,CAAC;EACxF,CACa,CAAS;CAC1B;;;;;;;;;CAUA,2BACI,kBACA,WACA,KACA,UACI;EAKJ,IAAI,YAAY,sBAAsB,QAAQ,KAAK,SAAS,WAAW;EAEvE,IAAI,UAAU,SAAS,GACnB,MAAM,IAAI,MACN,gBAAgB,iBAAiB,KAAK,UAAU,IAAI,uCAChD,iBAAiB,KAAK,gBAAgB,UAAU,KAAI,MAAK,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC,KAAK,KAAK,EAAE,iNAIjG;CAER;;;;;;;;;;;;;;;;;;;;;;CAuBA,MAAM,eACF,kBACA,UACA,UACA,KAAoB,KAAK,IACW;EACpC,MAAM,EAAE,kBAAkB,MAAM,KAAK,kBAAkB,kBAAkB,UAAU,CAAC,QAAQ,GAAG,EAAE;EACjG,OAAO,cAAc,IAAI,OAAO,QAAQ,CAAC;CAC7C;;;;;CAMA,MAAM,kBACF,kBACA,UACA,WAIA,KAAoB,KAAK,IAC4E;EACrG,MAAM,gCAAgB,IAAI,IAA6B;EACvD,MAAM,gCAAgB,IAAI,IAA6B;EAEvD,MAAM,YAAY,mBAAmB,kBAAkB,KAAK,QAAQ;EAEpE,IAAI,CAAC,kBAAkB,UAAU,kBAAkB,KAAK,QAAQ,GAAG;GAK/D,KAAK,MAAM,MAAM,WAAW;IACxB,MAAM,QAAQ,gBAAc,IAAI,SAAS,CAAC,CAAC,UAAU,EAAE,CAAC;IACxD,cAAc,IAAI,OAAO,EAAE,GAAG,KAAK;IACnC,cAAc,IAAI,OAAO,KAAK,GAAG,EAAE;GACvC;GACA,OAAO;IAAE;IAAe;GAAc;EAC1C;EAEA,MAAM,QAAQ,eAAe,UAAU,kBAAkB,KAAK,QAAQ;EACtE,MAAM,cAAc,sBAAsB,kBAAkB,KAAK,QAAQ;EAEzE,IAAI,CADc,YAAY,QAE1B,MAAM,IAAI,MACN,iBAAiB,MAAM,mBAAmB,SAAS,aAAa,wBAC5D,iBAAiB,KAAK,6DAC9B;EAGJ,MAAM,OAAO,MAAM,GACd,OAAO,CAAC,CACR,KAAK,WAAW,CAAC,CACjB,MAAM,KAAK,mBAAmB,aAAa,WAAW,SAAS,CAAC;EAErE,KAAK,MAAM,OAAO,MAAwC;GACtD,MAAM,WAAW,IAAI;GACrB,IAAI,aAAa,QAAQ,aAAa,KAAA,GAAW;GACjD,MAAM,WAAW,mBAAiB,KAAK,SAAS;GAChD,cAAc,IAAI,OAAO,QAAQ,GAAG,QAAQ;GAI5C,MAAM,WAAW,cAAc,IAAI,OAAO,QAAQ,CAAC;GACnD,IAAI,aAAa,KAAA,KAAa,OAAO,QAAQ,MAAM,OAAO,QAAQ,GAC9D,MAAM,IAAI,MACN,iBAAiB,MAAM,mBAAmB,SAAS,aAAa,sBAC5D,iBAAiB,KAAK,WAAW,SAAS,SAAS,SAAS,eAC5D,SAAS,2HAEjB;GAEJ,cAAc,IAAI,OAAO,QAAQ,GAAG,QAAQ;EAChD;EAEA,OAAO;GAAE;GAAe;EAAc;CAC1C;;;;CAKA,MAAM,qBACF,sBACA,UACA,aACA,UAQI,CAAC,GACmB;EACxB,MAAM,mBAAmB,oBAAoB,sBAAsB,KAAK,QAAQ;EAChF,MAAM,oBAAoB,2BAA2B,gBAAgB;EACrE,MAAM,WAAW,aAAa,mBAAmB,WAAW;EAE5D,IAAI,CAAC,UAAU;GACX,MAAM,YAAY,OAAO,KAAK,iBAAiB,CAAC,CAAC,KAAK,IAAI,KAAK;GAW/D,MAAM,SAAS,SACX,aAAa,YAAY,6BAA6B,qBAAqB,2BAA2B,UAAU,IAChH,kBACJ;EACJ;EAEA,OAAO,KAAK,wBAA2B,kBAAkB,UAAU,UAAU,OAAO;CACxF;;;;CAKA,MAAM,wBACF,kBACA,UACA,UACA,UAQI,CAAC,GACmB;EACxB,MAAM,mBAAmB,SAAS,OAAO;EACzC,MAAM,cAAc,sBAAsB,kBAAkB,KAAK,QAAQ;EACzE,MAAM,SAAS,mBAAmB,kBAAkB,KAAK,QAAQ;EACjE,MAAM,UAAU,YAAY,OAAO,EAAE,CAAC;EAEtC,MAAM,YAAY,mBAAmB,kBAAkB,KAAK,QAAQ;EACpE,MAAM,eAAe,UAAU;EAE/B,MAAM,iBADoB,gBAAc,UAAU,SAC3B,CAAA,CAAkB,aAAa;EACtD,MAAM,kBAAkB,eAAa,gBAAgB;EACrD,MAAM,cAAc,KAAK,SAAS,SAAS,eAAe;EAC1D,IAAI,CAAC,aAAa,MAAM,kBAAkB,iBAAiB,MAAM,iBAAiB,QAAQ;EAC1F,MAAM,cAAc,YAAY,aAAa;EAM7C,MAAM,mBAAmB,MAAM,KAAK,iBAAiB,kBAAkB,UAAU,QAAQ;EAGzF,IAAI,SAAS,SAAS,OAAO;GACzB,IAAI,QAAQ,KAAK,GAAG,OAAO,CAAC,CAAC,KAAK,WAAW,CAAC,CAAC,SAAS;GACxD,IAAI,eAAe;GAGnB,KAAK,MAAM,QAAQ,SAAS,UAAU;IAClC,MAAM,YAAY,KAAK,SAAS,SAAS,KAAK,KAAK;IACnD,IAAI,CAAC,WACD,MAAM,IAAI,MAAM,yBAAyB,KAAK,OAAO;IAGzD,MAAM,OAAO,kBAAkB,cAAc,WAAW,IAAI;IAC5D,IAAI,aAAa,MACb,MAAM,IAAI,MAAM,2BAA2B,KAAK,SAAS;IAG7D,QAAQ,iBAAiB,OAAO,WAAW,KAAK,SAAS;IACzD,eAAe;GACnB;GAQA,QAAQ,MAAM,MAAM,SAChB,qBAAqB,aAAa,WAAW,UAAU,iBAAiB,IAAI,GAC5E,cAAc,kBAAkB,kBAAkB,WAAW,CACjE,CAAC;GAED,IAAI,QAAQ,OACR,QAAQ,MAAM,MAAM,QAAQ,KAAK;GAGrC,MAAM,UAAU,MAAM;GACtB,MAAM,kBAAkB,SAAS,SAAS,SAAS,SAAS,SAAS,EAAE,CAAC;GAGxE,MAAM,OAAwB,CAAC;GAC/B,KAAK,MAAM,OAAO,SAA2C;IACzD,MAAM,YAAa,IAAI,oBAAgD;IACvE,KAAK,KAAK,MAAM,KAAK,aAAgB,WAAW,kBAAkB,QAAQ,EAAE,eAAe,KAAK,CAAC,CAAC;GACtG;GAEA,OAAO;EACX;EAQA,MAAM,aAAa,sBAAsB,QAAQ,KAC1C,MAAM,KAAK,kBAAkB,kBAAkB,UAAU,CAAC,QAAQ,CAAC,EAAA,CACjE,cAAc,IAAI,OAAO,QAAQ,CAAC,IACrC;EAIN,IAAI,eAAe,KAAA,GAAW,OAAO,CAAC;EAEtC,IAAI,QAAQ,KAAK,GAAG,OAAO,CAAC,CAAC,KAAK,WAAW,CAAC,CAAC,SAAS;EAGxD,MAAM,oBAA2B,CAAC;EAIlC,MAAM,oBAAoB,oBAAoB,kBAAkB,WAAW;EAC3E,IAAI,mBAAmB,kBAAkB,KAAK,iBAAiB;EAG/D,IAAI,kBAAkB,kBAAkB,KAAK,gBAAgB;EAG7D,IAAI,QAAQ,cAAc;GACtB,MAAM,mBAAmB,wBAAwB,sBAC7C,QAAQ,cACR,iBAAiB,YACjB,aACA,gBACJ;GAEA,IAAI,iBAAiB,WAAW,GAE5B,OAAO,CAAC;GAGZ,MAAM,iBAAiB,wBAAwB,wBAAwB,gBAAgB;GACvF,IAAI,gBACA,kBAAkB,KAAK,cAAc;EAE7C;EAGA,QAAQ,0BACJ,OACA,OACA,UACA,YACA,aACA,aACA,aACA,SACA,KAAK,UACL,iBACJ;EAEA,IAAI,QAAQ,OACR,QAAQ,MAAM,MAAM,QAAQ,KAAK;EAGrC,MAAM,UAAU,MAAM;EAGtB,IAAI,CAAC,WAAW,CAAC,MAAM,QAAQ,OAAO,GAClC,OAAO,CAAC;EAGZ,MAAM,OAAwB,CAAC;EAC/B,KAAK,MAAM,OAAO,SAAS;GACvB,MAAM,YAAY,IAAI,eAAa,gBAAgB,MAAM;GACzD,KAAK,KAAK,MAAM,KAAK,aAAgB,WAAsC,kBAAkB,QAAQ,EAAE,eAAe,KAAK,CAAC,CAAC;EACjI;EAEA,OAAO;CACX;;;;CAKA,MAAM,qBACF,sBACA,UACA,aACA,UAAoF,CAAC,GACtE;EACf,MAAM,mBAAmB,oBAAoB,sBAAsB,KAAK,QAAQ;EAChF,MAAM,oBAAoB,2BAA2B,gBAAgB;EACrE,MAAM,WAAW,aAAa,mBAAmB,WAAW;EAC5D,IAAI,CAAC,UAAU;GACX,MAAM,YAAY,OAAO,KAAK,iBAAiB,CAAC,CAAC,KAAK,IAAI,KAAK;GAW/D,MAAM,SAAS,SACX,aAAa,YAAY,6BAA6B,qBAAqB,2BAA2B,UAAU,IAChH,kBACJ;EACJ;EAEA,OAAO,KAAK,iBAAiB,kBAAkB,UAAU,UAAU,CAAC,CAAC;CACzE;;;;;;;;;;CAWA,MAAc,iBACV,kBACA,UACA,UACA,mBACe;EACf,MAAM,mBAAmB,SAAS,OAAO;EACzC,MAAM,cAAc,sBAAsB,kBAAkB,KAAK,QAAQ;EAGzE,MAAM,gBAAgB,YAFJ,mBAAmB,kBAAkB,KAAK,QACvC,CAAA,CAAU,EACG,CAAa;EAE/C,MAAM,YAAY,mBAAmB,kBAAkB,KAAK,QAAQ;EACpE,MAAM,eAAe,UAAU;EAC/B,MAAM,oBAAoB,gBAAc,UAAU,SAAS;EAC3D,MAAM,iBAAiB,kBAAkB,aAAa;EACtD,MAAM,kBAAkB,eAAa,gBAAgB;EACrD,MAAM,cAAc,KAAK,SAAS,SAAS,eAAe;EAC1D,IAAI,CAAC,aAAa,MAAM,kBAAkB,iBAAiB,MAAM,iBAAiB,QAAQ;EAC1F,MAAM,cAAc,YAAY,aAAa;EAK7C,MAAM,aAAa,sBAAsB,QAAQ,KAC1C,MAAM,KAAK,kBAAkB,kBAAkB,UAAU,CAAC,QAAQ,CAAC,EAAA,CACjE,cAAc,IAAI,OAAO,QAAQ,CAAC,IACrC;EAEN,IAAI,eAAe,KAAA,GAAW,OAAO;EAIrC,MAAM,kBAAkB,oBAAoB,kBAAkB,WAAW;EACzE,IAAI,iBAAiB,oBAAoB,CAAC,GAAG,mBAAmB,eAAe;EAM/E,MAAM,iBAAiB,MAAM,KAAK,iBAAiB,kBAAkB,UAAU,QAAQ;EACvF,IAAI,gBAAgB,oBAAoB,CAAC,GAAG,mBAAmB,cAAc;EAG7E,IAAI,QAAQ,KAAK,GAAG,OAAO,EAAE,OAAO,GAAW,kBAAkB,cAAc,GAAG,CAAC,CAAC,CAAC,KAAK,WAAW,CAAC,CAAC,SAAS;EAiBhH,IAAI,SAAS,SAAS,OAAO;GACzB,MAAM,QAAQ,wBAAwB,4BAClC,iBACO;IACH,OAAO;IACP,KAAK,kBAAkB,aAAa,WAAW,iBAAiB,IAAI,CAAC,CAChE,KAAK,QAAQ,OAAO;KAAE;KAAQ,OAAO,kBAAkB,UAAU,EAAE,CAAC;IAAW,EAAE;GAC1F,IACA,UACA,aACA,eACA,KAAK,QACT;GACA,MAAM,SAAS,MAAM,MAAM,MAAM,IAAI,OAAO,GAAG,iBAAiB,CAAC;GACjE,OAAO,OAAO,OAAO,EAAE,EAAE,SAAS,CAAC;EACvC;EAGA,QAAQ,wBAAwB,wBAC5B,OACA,UACA,YACA,aACA,aACA,aACA,eACA,KAAK,UACL,iBACJ;EAEA,MAAM,SAAS,MAAM;EACrB,OAAO,OAAO,OAAO,EAAE,EAAE,SAAS,CAAC;CACvC;;;;;;;;;CAUA,MAAM,UAAU,KAAoB,UAA6C;EAC7E,MAAM,cAAc,sBAAsB,IAAI,kBAAkB,KAAK,QAAQ;EAC7E,MAAM,YAAY,mBAAmB,IAAI,kBAAkB,KAAK,QAAQ;EACxE,MAAM,iBAAiB,gBAAc,UAAU,SAAS;EAExD,MAAM,WAAkB,UAAU,KAAI,OAAM;GACxC,MAAM,SAAS,YAAY,GAAG;GAC9B,IAAI,CAAC,QACD,MAAM,IAAI,MAAM,aAAa,GAAG,UAAU,uCAAuC,IAAI,iBAAiB,KAAK,EAAE;GAEjH,OAAO,GAAG,QAAQ,eAAe,GAAG,UAAU;EAClD,CAAC;EAED,OAAQ,MAAM,KAAK,iBAAiB,IAAI,kBAAkB,IAAI,UAAU,IAAI,UAAU,QAAQ,IAAK;CACvG;;;;;;;;;CAUA,MAAM,0BACF,sBACA,WACA,cACA,UACA,QACyD;EACzD,IAAI,UAAU,WAAW,GAAG,uBAAO,IAAI,IAAI;EAE3C,MAAM,mBAAmB,oBAAoB,sBAAsB,KAAK,QAAQ;EAChF,MAAM,mBAAmB,SAAS,OAAO;EACzC,MAAM,cAAc,sBAAsB,kBAAkB,KAAK,QAAQ;EACzE,MAAM,YAAY,mBAAmB,kBAAkB,KAAK,QAAQ;EACpE,MAAM,eAAe,UAAU;EAC/B,MAAM,gBAAgB,YAAY,aAAa;EAE/C,MAAM,YAAY,mBAAmB,kBAAkB,KAAK,QAAQ;EACpE,MAAM,eAAe,UAAU;EAC/B,MAAM,kBAAkB,eAAa,gBAAgB;EACrD,MAAM,cAAc,KAAK,SAAS,SAAS,eAAe;EAC1D,IAAI,CAAC,aAAa,MAAM,kBAAkB,iBAAiB,MAAM,iBAAiB,QAAQ;EAC1F,MAAM,cAAc,YAAY,aAAa;EAG7C,MAAM,kBAAkB,UAAU,KAAI,OAAM,gBAAc,IAAI,SAAS,CAAC,CAAC,aAAa,UAAU;EAchG,MAAM,eAAe,cACjB,WAAW,QAAQ,MAAM,KAAK,iBAAiB,kBAAkB,QAAQ,CAAC,GAC1E,kBAAkB,WACtB;EAGA,IAAI,SAAS,SAAS,OAAO;GACzB,IAAI,QAAQ,KAAK,GAAG,OAAO,CAAC,CAAC,KAAK,WAAW,CAAC,CAAC,SAAS;GACxD,IAAI,eAAe;GAGnB,KAAK,MAAM,QAAQ,SAAS,UAAU;IAClC,MAAM,YAAY,KAAK,SAAS,SAAS,KAAK,KAAK;IACnD,IAAI,CAAC,WACD,MAAM,IAAI,MAAM,yBAAyB,KAAK,OAAO;IAGzD,MAAM,OAAO,kBAAkB,cAAc,WAAW,IAAI;IAC5D,IAAI,aAAa,MACb,MAAM,IAAI,MAAM,2BAA2B,KAAK,SAAS;IAG7D,QAAQ,iBAAiB,OAAO,WAAW,KAAK,SAAS;IACzD,eAAe;GACnB;GAGA,QAAQ,MAAM,MAAM,SAAS,KAAK,mBAAmB,aAAa,WAAW,SAAS,GAAG,YAAY,CAAC;GAEtG,MAAM,UAAU,MAAM;GACtB,MAAM,kBAAkB,SAAS,SAAS,SAAS,SAAS,SAAS,EAAE,CAAC;GACxE,MAAM,4BAAY,IAAI,IAAiD;GAIvE,KAAK,MAAM,OAAO,SAA2C;IACzD,MAAM,YAAa,IAAI,eAAa,gBAAgB,MAAM;IAC1D,MAAM,YAAa,IAAI,oBAAoB;IAE3C,UAAU,IACN,mBAAiB,WAAW,SAAS,GACrC,MAAM,KAAK,aAAa,WAAW,kBAAkB,SAAS,CAClE;GACJ;GAEA,OAAO;EACX;EAQA,IAAI,SAAS,SAAS,aAAa;GAC/B,KAAK,2BAA2B,kBAAkB,WAAW,SAAS,QAAQ;GAI9E,MAAM,cAAc,YADE,kBAAkB,kBAAkB,SAAS,QACnC;GAChC,IAAI,CAAC,aACD,MAAM,IAAI,MAAM,qBAAqB,SAAS,SAAS,4BAA4B;GAIvF,MAAM,SAAS,MAAM,KAAK,GACrB,OAAO;IACJ,UAAU;IACV,SAAS;GACb,CAAC,CAAC,CACD,KAAK,WAAW,CAAC,CACjB,MAAM,QAAQ,aAAa,eAAe,CAAC;GAGhD,MAAM,6BAAa,IAAI,IAA6B;GACpD,MAAM,iBAAsC,CAAC;GAC7C,MAAM,0BAAU,IAAI,IAAY;GAEhC,KAAK,MAAM,OAAO,QAAiF;IAC/F,IAAI,IAAI,WAAW,MAAM;IACzB,WAAW,IAAI,OAAO,IAAI,QAAQ,GAAG,IAAI,OAAO;IAChD,MAAM,QAAQ,OAAO,IAAI,OAAO;IAChC,IAAI,CAAC,QAAQ,IAAI,KAAK,GAAG;KACrB,QAAQ,IAAI,KAAK;KACjB,eAAe,KAAK,IAAI,OAAO;IACnC;GACJ;GAEA,IAAI,eAAe,WAAW,GAAG,uBAAO,IAAI,IAAI;GAGhD,MAAM,gBAAgB,MAAM,KAAK,GAC5B,OAAO,CAAC,CACR,KAAK,WAAW,CAAC,CACjB,MAAM,SAAS,QAAQ,eAAe,cAAc,GAAG,YAAY,CAAC;GAGzE,MAAM,6BAAa,IAAI,IAAqC;GAC5D,KAAK,MAAM,OAAO,eAAiD;IAC/D,MAAM,MAAM,OAAO,IAAI,aAAa,UAAU;IAC9C,WAAW,IAAI,KAAK,GAAG;GAC3B;GAGA,MAAM,4BAAY,IAAI,IAAiD;GACvE,KAAK,MAAM,CAAC,aAAa,YAAY,YAAY;IAC7C,MAAM,YAAY,WAAW,IAAI,OAAO,OAAO,CAAC;IAChD,IAAI,WACA,UAAU,IAAI,aAAa,MAAM,KAAK,aAAa,WAAW,kBAAkB,SAAS,CAAC;GAElG;GAEA,OAAO;EACX;EAKA,KAAK,2BACD,kBACA,WACA,sBAAsB,QAAQ,IAAI,SAAS,qBAAqB,SAAS,cACzE,QACJ;EAKA,MAAM,EAAE,eAAe,kBAAkB,sBAAsB,QAAQ,IACjE,MAAM,KAAK,kBAAkB,kBAAkB,UAAU,SAAS,IAClE;GAAE,+BAAe,IAAI,IAA6B;GAAG,+BAAe,IAAI,IAA6B;EAAE;EAE7G,MAAM,cAAc,sBAAsB,QAAQ,IAC5C,CAAC,GAAG,cAAc,OAAO,CAAC,IAC1B;EACN,IAAI,YAAY,WAAW,GAAG,uBAAO,IAAI,IAAI;EAE7C,IAAI,QAAQ,KAAK,GAAG,OAAO,CAAC,CAAC,KAAK,WAAW,CAAC,CAAC,SAAS;EAGxD,QAAQ,0BACJ,OACA,OACA,UACA,aACA,aACA,aACA,aACA,eACA,KAAK,UAIL,eAAe,CAAC,YAAY,IAAI,CAAC,CACrC;EAEA,MAAM,UAAU,MAAM;EACtB,MAAM,4BAAY,IAAI,IAAiD;EAGvE,KAAK,MAAM,OAAO,SAA2C;GACzD,MAAM,YAAa,IAAI,eAAa,gBAAgB,MAAM;GAO1D,IAAI,CAAC,sBAAsB,QAAQ,GAAG;GAItC,MAAM,kBAAkB,UACpB,kBAAkB,kBAAkB,SAAS,kBAAkB;GAEnE,IAAI,oBAAoB,KAAA,KAAa,oBAAoB,MAAM;GAE/D,MAAM,WAAW,cAAc,IAAI,OAAO,eAAe,CAAC;GAC1D,IAAI,aAAa,KAAA,GACb,UAAU,IAAI,OAAO,QAAQ,GAAG,MAAM,KAAK,aAAa,WAAW,kBAAkB,SAAS,CAAC;EAEvG;EAEA,OAAO;CACX;;;;;;CAOA,MAAM,8BACF,sBACA,WACA,cACA,UAEA,QAC2D;EAC3D,IAAI,UAAU,WAAW,GAAG,uBAAO,IAAI,IAAI;EAE3C,MAAM,mBAAmB,oBAAoB,sBAAsB,KAAK,QAAQ;EAChF,MAAM,mBAAmB,SAAS,OAAO;EACzC,MAAM,cAAc,sBAAsB,kBAAkB,KAAK,QAAQ;EACzE,MAAM,YAAY,mBAAmB,kBAAkB,KAAK,QAAQ;EAEpE,MAAM,gBAAgB,YADD,UAAU,EACG,CAAa;EAE/C,MAAM,YAAY,mBAAmB,kBAAkB,KAAK,QAAQ;EACpE,MAAM,eAAe,UAAU;EAC/B,MAAM,kBAAkB,eAAa,gBAAgB;EACrD,MAAM,cAAc,KAAK,SAAS,SAAS,eAAe;EAC1D,IAAI,CAAC,aAAa,MAAM,kBAAkB,iBAAiB,MAAM,iBAAiB,QAAQ;EAC1F,MAAM,cAAc,YAAY,aAAa;EAE7C,MAAM,kBAAkB,UAAU,KAAI,OAAM,gBAAc,IAAI,SAAS,CAAC,CAAC,aAAa,UAAU;EAchG,MAAM,eAAe,cACjB,WAAW,QAAQ,MAAM,KAAK,iBAAiB,kBAAkB,QAAQ,CAAC,GAC1E,kBAAkB,WACtB;EAGA,IAAI,SAAS,SAAS,OAAO;GACzB,IAAI,QAAQ,KAAK,GAAG,OAAO,CAAC,CAAC,KAAK,WAAW,CAAC,CAAC,SAAS;GACxD,IAAI,eAAe;GAEnB,KAAK,MAAM,QAAQ,SAAS,UAAU;IAClC,MAAM,YAAY,KAAK,SAAS,SAAS,KAAK,KAAK;IACnD,IAAI,CAAC,WAAW,MAAM,IAAI,MAAM,yBAAyB,KAAK,OAAO;IAErE,MAAM,OAAO,kBAAkB,cAAc,WAAW,IAAI;IAC5D,IAAI,aAAa,MAAM,MAAM,IAAI,MAAM,2BAA2B,KAAK,SAAS;IAEhF,QAAQ,iBAAiB,OAAO,WAAW,KAAK,SAAS;IACzD,eAAe;GACnB;GAEA,QAAQ,MAAM,MAAM,SAAS,KAAK,mBAAmB,aAAa,WAAW,SAAS,GAAG,YAAY,CAAC;GAEtG,MAAM,UAAU,MAAM;GACtB,MAAM,kBAAkB,SAAS,SAAS,SAAS,SAAS,SAAS,EAAE,CAAC;GACxE,MAAM,4BAAY,IAAI,IAAmD;GAEzE,KAAK,MAAM,OAAO,SAA2C;IACzD,MAAM,YAAa,IAAI,eAAa,gBAAgB,MAAM;IAC1D,MAAM,YAAa,IAAI,oBAAoB;IAC3C,MAAM,WAAW,mBAAiB,WAAW,SAAS;IACtD,MAAM,MAAM,UAAU,IAAI,QAAQ,KAAK,CAAC;IACxC,IAAI,KAAK,MAAM,KAAK,aAAa,WAAW,kBAAkB,SAAS,CAAC;IACxE,UAAU,IAAI,UAAU,GAAG;GAC/B;GAEA,OAAO;EACX;EAKA,IAAI,SAAS,SAAS,cAAc;GAGhC,KAAK,2BAA2B,kBAAkB,WAAW,GAAG,SAAS,QAAQ,MAAM,GAAG,SAAS,QAAQ,cAAc;GAMzH,MAAM,EAAE,OAAO,eAAe,cAAc,mBAAmB,cAAc,sBACzE,oBACI,KAAK,UACL,SAAS,SACT,GAAG,iBAAiB,KAAK,GAAG,SAAS,cACzC;GAaJ,MAAM,UAAU,MANF,KAAK,GACd,OAAO,CAAC,CACR,KAAK,aAAa,CAAC,CACnB,UAAU,aAAa,GAAG,mBAAmB,aAAa,CAAC,CAAC,CAC5D,MAAM,SAAS,QAAQ,mBAAmB,eAAe,GAAG,YAAY,CAEvD;GACtB,MAAM,4BAAY,IAAI,IAAmD;GACzE,MAAM,kBAAkB,eAAa,gBAAgB;GAIrD,MAAM,cAAc,OAAO,KAAK,SAAS,QAAQ,UAAU;GAC3D,MAAM,iBAAiB,YAAY,SAAS,IACtC,6BAA6B,SAAS,OAAO,IAC7C,KAAA;GAEN,KAAK,MAAM,OAAO,SAA2C;IAEzD,MAAM,eAAgB,IAAI,SAAS,QAAQ,UAAU;IACrD,MAAM,aAAc,IAAI,oBAAoB;IAE5C,MAAM,WAAW,OAAO,aAAa,SAAS,QAAQ,aAAa;IACnE,MAAM,MAAM,UAAU,IAAI,QAAQ,KAAK,CAAC;IACxC,MAAM,UAAU,MAAM,KAAK,aAAa,YAAY,kBAAkB,SAAS;IAC/E,IAAI,gBACA,QAAQ,QAAQ,MAAM,KAAK,UAAU,cAAc,gBAAgB,WAAW;IAElF,IAAI,KAAK,OAAO;IAChB,UAAU,IAAI,UAAU,GAAG;GAC/B;GAEA,OAAO;EACX;EAIA,KAAK,2BACD,kBACA,WACA,sBAAsB,QAAQ,IAAI,SAAS,qBAAqB,SAAS,cACzE,QACJ;EAEA,MAAM,EAAE,eAAe,kBAAkB,sBAAsB,QAAQ,IACjE,MAAM,KAAK,kBAAkB,kBAAkB,UAAU,SAAS,IAClE;GAAE,+BAAe,IAAI,IAA6B;GAAG,+BAAe,IAAI,IAA6B;EAAE;EAE7G,MAAM,cAAc,sBAAsB,QAAQ,IAC5C,CAAC,GAAG,cAAc,OAAO,CAAC,IAC1B;EACN,IAAI,YAAY,WAAW,GAAG,uBAAO,IAAI,IAAI;EAE7C,IAAI,QAAQ,KAAK,GAAG,OAAO,CAAC,CAAC,KAAK,WAAW,CAAC,CAAC,SAAS;EAExD,QAAQ,0BACJ,OACA,OACA,UACA,aACA,aACA,aACA,aACA,eACA,KAAK,UACL,eAAe,CAAC,YAAY,IAAI,CAAC,CACrC;EAEA,MAAM,UAAU,MAAM;EACtB,MAAM,4BAAY,IAAI,IAAmD;EAEzE,KAAK,MAAM,OAAO,SAA2C;GACzD,MAAM,YAAa,IAAI,eAAa,gBAAgB,MAAM;GAI1D,IAAI,CAAC,sBAAsB,QAAQ,GAAG;GAEtC,MAAM,kBAAkB,UACpB,kBAAkB,kBAAkB,SAAS,kBAAkB;GAEnE,IAAI,oBAAoB,KAAA,KAAa,oBAAoB,MAAM;GAE/D,MAAM,WAAW,cAAc,IAAI,OAAO,eAAe,CAAC;GAC1D,IAAI,aAAa,KAAA,GAAW;IACxB,MAAM,MAAM,OAAO,QAAQ;IAC3B,MAAM,MAAM,UAAU,IAAI,GAAG,KAAK,CAAC;IACnC,IAAI,KAAK,MAAM,KAAK,aAAa,WAAW,kBAAkB,SAAS,CAAC;IACxE,UAAU,IAAI,KAAK,GAAG;GAC1B;EACJ;EAEA,OAAO;CACX;AACJ;;;;;;ACzuCA,SAAgB,wBAAwB,KAAuB;CAC3D,IAAI,QAAQ,QAAQ,QAAQ,KAAA,GACxB,OAAO;CAGX,IAAI,OAAO,QAAQ,YAAY,MAAM,GAAG,GACpC,OAAO;CAGX,IAAI,OAAO,QAAQ,YAAY,IAAI,YAAY,MAAM,OACjD,OAAO;CAGX,IAAI,MAAM,QAAQ,GAAG,GACjB,OAAO,IAAI,KAAI,MAAK,wBAAwB,CAAC,CAAC;CAGlD,IAAI,eAAe,MACf,OAAO,IAAI,YAAY;CAG3B,IAAI,OAAO,QAAQ,UAAU;EACzB,MAAM,SAAkC,CAAC;EACzC,KAAK,MAAM,OAAO,KAAK;GACnB,IAAI,CAAC,OAAO,UAAU,eAAe,KAAK,KAAK,GAAG,GAAG;GAOrD,IAAI,wBAAwB,GAAG,GAAG;GAClC,OAAO,OAAO,wBAAyB,IAAgC,IAAI;EAC/E;EACA,OAAO;CACX;CAEA,IAAI,OAAO,QAAQ;MAGX,mDAAa,KAAK,GAAG,KAAK,kEAAY,KAAK,GAAG,GAAG;GACjD,MAAM,OAAO,IAAI,KAAK,GAAG;GACzB,IAAI,CAAC,MAAM,KAAK,QAAQ,CAAC,GACrB,OAAO,KAAK,YAAY;EAEhC;;CAGJ,OAAO;AACX;;;;AAKA,SAAgB,sBACZ,KACA,YACA,YACA,UACoB;CACpB,IAAI,CAAC,OAAO,CAAC,YAAY,OAAO;EAAE,YAAY,OAAO,CAAC;EAC1D,wBAAwB,CAAC;EACzB,yBAAyB,CAAC;CAAE;CAExB,MAAM,SAAkC,CAAC;CAGzC,MAAM,oBAAoB,aAAa,2BAA2B,UAAU,IAAI,CAAC;CAGjF,MAAM,yBAID,CAAC;CACN,MAAM,0BAID,CAAC;CAMN,MAAM,8BAAc,IAAI,IAAY;CACpC,OAAO,OAAO,iBAAiB,CAAC,CAAC,SAAQ,aAAY;EACjD,IAAI,SAAS,SAAS,aAAa,YAAY,IAAI,kBAAkB,YAAY,SAAS,QAAQ,CAAC;CACvG,CAAC;CAED,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,GAAG,GAAG;EAG5C,IAAI,wBAAwB,GAAG,GAAG;EAUlC,IAAI,UAAU,KAAA,GAAW;EAEzB,MAAM,WAAW,WAAW;EAG5B,MAAM,iBAAkB,YAAY,IAAI,GAAG,KAAK,UAAU,KAAM,OAAO;EAEvE,IAAI,CAAC,UAAU;GACX,OAAO,OAAO;GACd;EACJ;EAIA,IAAI,SAAS,SAAS,cAAc,YAAY;GAC5C,MAAM,WAAW,aAAa,mBAAmB,GAAG;GACpD,IAAI;QACI,SAAS,SAAS,aAAa;KAE/B,MAAM,kBAAkB,0BAA0B,gBAAgB,UAAU,GAAG;KAC/E,IAAI,oBAAoB,KAAA,GAGpB,OAAO,kBAAkB,YAAY,SAAS,QAAQ,KAAK;KAG/D;IACJ,OAAO,IAAI,sBAAsB,QAAQ,GAAG;KAExC,MAAM,kBAAkB,0BAA0B,gBAAgB,UAAU,GAAG;KAC/E,uBAAuB,KAAK;MACxB,aAAa;MACb;MACA,UAAU;KACd,CAAC;KAED;IACJ,OAAO,IAAI,SAAS,SAAS,OAAO;KAMhC,MAAM,kBAAkB,0BAA0B,gBAAgB,UAAU,GAAG;KAC/E,IAAI,SAAS,gBAAgB,OAIzB,wBAAwB,KAAK;MACzB,aAAa;MACb;MACA,aAAa;KACjB,CAAC;UAED,uBAAuB,KAAK;MACxB,aAAa;MACb;MACA,UAAU;KACd,CAAC;KAEL;IACJ;;EAER;EAEA,OAAO,OAAO,0BAA0B,gBAAgB,UAAU,GAAG;CACzE;CAEA,OAAO;EACH,YAAY;EACZ;EACA;CACJ;AACJ;;;;;;;;AASA,SAAS,cAAc,OAAwB;CAC3C,IAAI,UAAU,MAAM,OAAO;CAC3B,IAAI,MAAM,QAAQ,KAAK,GAAG,OAAO;CACjC,IAAI,iBAAiB,MAAM,OAAO;CAClC,MAAM,OAAO,OAAO;CACpB,OAAO,SAAS,WAAW,cAAc,KAAK;AAClD;;;;;;;;;;AAWA,IAAM,cAAc;;;;;AAMpB,SAAS,gBAAgB,MAAgC;CACrD,MAAM,QAAQ,YAAY,KAAK,KAAK,KAAK,CAAC;CAC1C,IAAI,CAAC,OAAO,OAAO,KAAA;CACnB,MAAM,GAAG,IAAI,IAAI,KAAK,MAAM,WAAW,MAAM;CAC7C,MAAM,uBAAO,IAAI,KAAK,cAAc,GAAG,GAAG,GAAG,GAAG,KAAK,WAAW,SAAS,MAAM,GAAG,CAAC,IAAI,GAAG,EAAE;CAC5F,OAAO,MAAM,KAAK,QAAQ,CAAC,IAAI,KAAA,IAAY;AAC/C;;;;;;AAOA,SAAS,gBAAgB,OAAyB;CAC9C,IAAI,OAAO,UAAU,YAAY,YAAY,KAAK,MAAM,KAAK,CAAC,GAAG,OAAO,MAAM,KAAK;CACnF,MAAM,OAAO,iBAAiB,OAAO,QAAQ,OAAO,UAAU,WAAW,IAAI,KAAK,KAAK,IAAI,KAAA;CAC3F,IAAI,CAAC,QAAQ,MAAM,KAAK,QAAQ,CAAC,GAAG,OAAO;CAC3C,OAAO,KAAK,YAAY,CAAC,CAAC,MAAM,IAAI,EAAE;AAC1C;AAEA,SAAgB,0BAA0B,OAAgB,UAAoB,aAA+B;CACzG,IAAI,UAAU,QAAQ,UAAU,KAAA,GAC5B,OAAO;CAGX,MAAM,aAAa,cAAc,IAAI,YAAY,KAAK,MAAM,SAAS,KAAK;CAI1E,QAFqB,SAAS,MAE9B;EACI,KAAK,QACD,OAAO,SAAS,eAAe,SAAS,gBAAgB,KAAK,IAAI;EAErE,KAAK;GACD,IAAI,MAAM,QAAQ,KAAK,GACnB,OAAO,MAAM,KAAI,MAAK,0BAA0B,GAAG,UAAU,WAAW,CAAC;QACtE,IAAI,OAAO,UAAU,YAAY,UAAU,QAAQ,QAAQ,OAC9D,OAAQ,MAAkC;GAE9C,IAAI,UAAU,IAAI,OAAO;GACzB,OAAO;EAEX,KAAK;GACD,IAAI,MAAM,QAAQ,KAAK,GAAG;IACtB,IAAI,SAAS,IACT,OAAO,MAAM,KAAI,SAAQ,0BAA0B,MAAM,SAAS,IAAgB,WAAW,CAAC;SAC3F,IAAI,SAAS,OAAO;KACvB,MAAM,YAAY,SAAS,MAAM,aAAa;KAC9C,MAAM,aAAa,SAAS,MAAM,cAAc;KAChD,OAAO,MAAM,KAAK,MAAM;MACpB,IAAI,MAAM,MAAM,OAAO;MACvB,IAAI,OAAO,MAAM,UAAU,OAAO;MAClC,MAAM,MAAM;MACZ,MAAM,OAAO,IAAI;MACjB,MAAM,gBAAgB,SAAS,OAAO,WAAW;MACjD,IAAI,CAAC,QAAQ,CAAC,eAAe,OAAO;MACpC,OAAO;QACF,YAAY;QACZ,aAAa,0BAA0B,IAAI,aAAa,eAAe,WAAW;MACvF;KACJ,CAAC;IACL;IACA,OAAO;GACX;GAYA,MAAM,SAAS,WACX,GAAG,WAAW,kCAAkC,cAAc,KAAK,EAAE,IACrE,0BACJ;EAEJ,KAAK,YAAY;GAKb,IAAI,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK,GAChD,MAAM,SAAS,WACX,GAAG,WAAW,+EACE,cAAc,KAAK,EAAE,IACrC,0BACJ;GAEJ,MAAM,QAAQ;GACd,IAAI,OAAO,MAAM,aAAa,YAAY,OAAO,MAAM,cAAc,UACjE,MAAM,SAAS,WACX,GAAG,WAAW,0EACd,0BACJ;GAEJ,OAAO;IAAE,UAAU,MAAM;IACrC,WAAW,MAAM;GAAU;EACnB;EAGA,KAAK;GACD,IAAI,OAAO,UAAU,YAAY,SAAS,YAAY;IAClD,MAAM,SAAkC,CAAC;IACzC,KAAK,MAAM,CAAC,QAAQ,aAAa,OAAO,QAAQ,KAAK,GAAG;KACpD,MAAM,cAAe,SAAS,WAA0B;KACxD,IAAI,aACA,OAAO,UAAU,0BAA0B,UAAU,aAAa,cAAc,GAAG,YAAY,GAAG,WAAW,MAAM;UAEnH,OAAO,UAAU;IAEzB;IACA,OAAO;GACX;GACA,OAAO;EACX,KAAK;GACD,IAAI,iBAAiB,QACjB,OAAO,MAAM;GAEjB,IAAI,SAAS,OAAO,UAAU,YAAY,WAAW,SAAS,MAAM,QAAS,MAA6B,KAAK,GAC3G,OAAQ,MAA+B,MAAM,IAAI,MAAM;GAE3D,IAAI,MAAM,QAAQ,KAAK,GACnB,OAAO,MAAM,IAAI,MAAM;GAE3B,OAAO;EAGX,KAAK,UAAU;GACX,MAAM,UAAU,uBAAuB,KAAK;GAC5C,IAAI,SAAS,OAAO;GACpB,IAAI,OAAO,SAAS,KAAK,GAAG,OAAO;GACnC,OAAO;EACX;EAGA,SAAS;GACL,MAAM,UAAU,uBAAuB,KAAK;GAC5C,IAAI,SAAS,OAAO;GACpB,OAAO;EACX;CACJ;AACJ;;;;AAKA,eAAsB,oBAClB,MACA,YACA,IACA,UACU;CACV,MAAM,aAAa,WAAW;CAC9B,IAAI,CAAC,QAAQ,CAAC,YAAY,OAAO;CAGjC,MAAM,oBAAoB,2BAA2B,UAAU;CAG/D,MAAM,SAAS,sBAAsB,MAAM,YAAY,YAAY,mBAAmB,EAAE,eAAe,MAAM,CAAC;CAG9G,KAAK,MAAM,CAAC,SAAS,aAAa,OAAO,QAAQ,UAAU,GACvD,IAAI,SAAS,SAAS,cAAc,EAAE,WAAW,SAAS;EAEtD,MAAM,WAAW,aAAa,mBAAmB,OAAO;EACxD,IAAI,UAAU;GACV,MAAM,aAAa,SAAS,SAAS,cAC/B,kBAAkB,YAAY,SAAS,QAAQ,IAC/C;GACN,IAAI,SAAS,SAAS,eAAe,cAAc,MAAM;IAErD,MAAM,UAAU,KAAK;IACrB,IAAI,YAAY,QAAQ,YAAY,KAAA,GAChC,IAAI;KACA,MAAM,mBAAmB,SAAS,OAAO;KACzC,OAAO,WAAW,kBAAkB,QAAQ,SAAS,GAAG,iBAAiB,IAAI;IACjF,SAAS,GAAG;KACR,OAAO,KAAK,8DAA8D,WAAW,EAAE,OAAO,EAAE,CAAC;IACrG;GAER,OAAO,IAAI,sBAAsB,QAAQ,KAAK,MAAM,UAEhD,IAAI;IACA,MAAM,mBAAmB,SAAS,OAAO;IACzC,MAAM,cAAc,SAAS,SAAS,eAAa,gBAAgB,CAAC;IACpE,MAAM,MAAM,eAAe,YAAY,QAAS;IAKhD,MAAM,YAAY,SAAS,YACpB,KAAiC,SAAS,aAC3C,mBAAiB,MAAM,GAAG;IAEhC,IAAI,eAAe,cAAc,KAAA,KAAa,cAAc,QAAQ,cAAc,IAAI;KAElF,MAAM,mBAAmB,YADN,kBAAkB,kBAAkB,SAAS,kBAC3B;KACrC,IAAI,kBAAkB;MAElB,MAAM,cAAc,MAAM,GACrB,OAAO,CAAC,CACR,KAAK,WAAW,CAAC,CACjB,MAAM,GAAG,kBAAkB,SAAS,CAAC,CAAC,CACtC,MAAM,SAAS,gBAAgB,QAAQ,IAAI,GAAG;MAEnD,IAAI,YAAY,SAAS,GACrB,IAAI,SAAS,gBAAgB,OAAO;OAEhC,MAAM,YAAY,eAAe,kBAAkB,QAAS;OAC5D,MAAM,aAAa,YAAY;OAC/B,OAAO,WAAW,kBAAkB,mBAAiB,YAAY,SAAS,GAAG,iBAAiB,IAAI;MACtG,OAAO;OAEH,MAAM,YAAY,eAAe,kBAAkB,QAAS;OAC5D,OAAO,WAAW,YAAY,KAAK,QAC/B,kBAAkB,mBAAiB,KAAK,SAAS,GAAG,iBAAiB,IAAI,CAC7E;MACJ;KAER;IACJ;GACJ,SAAS,GAAG;IACR,OAAO,KAAK,gDAAgD,WAAW,EAAE,OAAO,EAAE,CAAC;GACvF;QACG,IAAI,SAAS,SAAS,SAAS,MAAM,UAExC,IAAI;IACA,MAAM,mBAAmB,SAAS,OAAO;IACzC,MAAM,MAAM,eAAe,YAAY,QAAS;IAChD,MAAM,YAAY,mBAAiB,MAAM,GAAG;IAE5C,IAAI,WAAW;KAEX,MAAM,cAAc,SAAS,SAAS,eAAa,UAAU,CAAC;KAC9D,IAAI,CAAC,aAAa;MACd,OAAO,KAAK,0CAA0C,WAAW,MAAM;MACvE;KACJ;KAEA,IAAI,QAAQ,GAAG,OAAO,CAAC,CAAC,KAAK,WAAW;KACxC,IAAI,eAAe;KAGnB,KAAK,MAAM,QAAQ,SAAS,UAAU;MAClC,MAAM,YAAY,SAAS,SAAS,KAAK,KAAK;MAC9C,IAAI,CAAC,WAAW;OACZ,OAAO,KAAK,yBAAyB,KAAK,OAAO;OACjD;MACJ;MAGA,MAAM,OAAO,kBAAkB,cAAc,WAAW,IAAI;MAC5D,IAAI,aAAa,MAAM;OACnB,OAAO,KAAK,2BAA2B,KAAK,SAAS;OACrD;MACJ;MAEA,QAAQ,iBAAiB,OAAO,WAAW,KAAK,SAAS;MACzD,eAAe;KACnB;KAMA,MAAM,aAAa,qBAAqB,aAAa,KAAK,WAAW,WAAW,IAAI;KAGpF,MAAM,cAAc,MAAM,MAAM,MAAM,UAAU,CAAC,CAAC,MAAM,SAAS,gBAAgB,QAAQ,IAAI,GAAG;KAEhG,IAAI,YAAY,SAAS,GAAG;MACxB,MAAM,YAAY,eAAe,kBAAkB,QAAS;MAC5D,MAAM,kBAAkB,SAAS,SAAS,SAAS,SAAS,SAAS,EAAE,CAAC;MAExE,IAAI,SAAS,gBAAgB,OAAO;OAEhC,MAAM,aAAa,YAAY;OAE/B,OAAO,WAAW,kBAAkB,mBADjB,WAAW,oBAAoB,YACc,SAAS,GAAG,iBAAiB,IAAI;MACrG,OAEI,OAAO,WAAW,YAAY,KAAK,eAAwC;OAEvE,OAAO,kBAAkB,mBADN,WAAW,oBAAoB,YACG,SAAS,GAAG,iBAAiB,IAAI;MAC1F,CAAC;KAET;IACJ;GACJ,SAAS,GAAG;IACR,OAAO,KAAK,kDAAkD,WAAW,EAAE,OAAO,EAAE,CAAC;GACzF;EAER;CACJ;CAGJ,OAAO;AACX;;;;;AAMA,SAAS,uBAAuB,OAA+B;CAC3D,IAAI,OAAO,UAAU,UAAU,OAAO;CACtC,IAAI,CAAC,MAAM,WAAW,uCAAuC,GAAG,OAAO;CACvE,MAAM,aAAa,MAAM,MAAM,GAAG,CAAC,CAAC;CACpC,OAAO,aAAa,OAAO,KAAK,YAAY,QAAQ,IAAI;AAC5D;;;;;;AAOA,SAAS,iBAAiB,OAA+B;CACrD,IAAI,OAAO,SAAS,KAAK,GAAG,OAAO;CACnC,IAAI,OAAO,UAAU,YAAY,UAAU,MAAM;EAC7C,MAAM,SAAS;EACf,IAAI,OAAO,SAAS,YAAY,MAAM,QAAQ,OAAO,IAAI,GACrD,OAAO,OAAO,KAAK,OAAO,IAAgB;CAElD;CACA,OAAO;AACX;;;;;AAMA,SAAS,uBAAuB,KAAqB;CACjD,KAAK,IAAI,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;EACjC,MAAM,IAAI,IAAI;EAEd,KAAK,IAAI,MAAM,IAAI,QAAQ,MAAM,KAAK,MAAM,MAAM,MAAM,IACpD,OAAO,wCAAwC,IAAI,SAAS,QAAQ;CAE5E;CACA,OAAO,IAAI,SAAS,MAAM;AAC9B;AAEA,SAAgB,wBAAwB,OAAgB,UAAoB,YAA8B,aAA+B;CACrI,IAAI,UAAU,QAAQ,UAAU,KAAA,GAAW,OAAO;CAElD,QAAQ,SAAS,MAAjB;EACI,KAAK,UAAU;GACX,MAAM,MAAM,iBAAiB,KAAK;GAClC,IAAI,KACA,OAAO,wCAAwC,IAAI,SAAS,QAAQ;GAExE,OAAO;EACX;EAEA,KAAK,UAAU;GACX,IAAI,OAAO,UAAU,UAAU,OAAO;GAGtC,MAAM,MAAM,iBAAiB,KAAK;GAClC,IAAI,KACA,OAAO,uBAAuB,GAAG;GAGrC,IAAI,OAAO,UAAU,YAAY,UAAU,MACvC,IAAI;IACA,OAAO,KAAK,UAAU,KAAK;GAC/B,QAAQ;IACJ,OAAO,OAAO,KAAK;GACvB;GAEJ,OAAO,OAAO,KAAK;EACvB;EAEA,KAAK;GAED,IAAI,OAAO,UAAU,YAAY,OAAO,UAAU,UAAU;IAIxD,IAAI,cAA6C,SAA8B;IAC/E,IAAI,CAAC,eAAe,aAChB,cAAc,aAAa,2BAA2B,UAAU,GAAG,WAAW;IAGlF,IAAI,CAAC,aAAa;KACd,OAAO,KAAK,qDAAqD,eAAe,WAAW;KAC3F,OAAO;IACX;IAEA,IAAI;KACA,MAAM,mBAAmB,YAAY,OAAO;KAC5C,OAAO,kBAAkB,MAAM,SAAS,GAAG,iBAAiB,IAAI;IACpE,SAAS,GAAG;KACR,OAAO,KAAK,8DAA8D,eAAe,aAAa,EAAE,OAAO,EAAE,CAAC;KAClH,OAAO;IACX;GACJ;GACA,OAAO;EAEX,KAAK;GACD,IAAI,MAAM,QAAQ,KAAK;QACf,SAAS,IACT,OAAO,MAAM,KAAI,SAAQ,wBAAwB,MAAM,SAAS,IAAgB,UAAU,CAAC;SACxF,IAAI,SAAS,OAAO;KACvB,MAAM,YAAY,SAAS,MAAM,aAAa;KAC9C,MAAM,aAAa,SAAS,MAAM,cAAc;KAChD,OAAO,MAAM,KAAK,MAAM;MACpB,IAAI,MAAM,MAAM,OAAO;MACvB,IAAI,OAAO,MAAM,UAAU,OAAO;MAClC,MAAM,MAAM;MACZ,MAAM,OAAO,IAAI;MACjB,MAAM,gBAAgB,SAAS,OAAO,WAAW;MACjD,IAAI,CAAC,QAAQ,CAAC,eAAe,OAAO;MACpC,OAAO;QACF,YAAY;QACZ,aAAa,wBAAwB,IAAI,aAAa,eAAe,UAAU;MACpF;KACJ,CAAC;IACL;UACG;IAEH,OAAO,KAAK,wDAAwD,OAAO,MAAM,qBAAqB;IACtG,OAAO,OAAO,UAAU,WAAW,CAAC,KAAK,IAAI,CAAC;GAClD;GACA,OAAO;EAEX,KAAK;GACD,IAAI,OAAO,UAAU,YAAY,SAAS,YAAY;IAClD,MAAM,SAAkC,CAAC;IACzC,KAAK,MAAM,CAAC,QAAQ,aAAa,OAAO,QAAQ,KAAK,GAAG;KACpD,MAAM,cAAe,SAAS,WAA0B;KACxD,IAAI,aACA,OAAO,UAAU,wBAAwB,UAAU,aAAa,UAAU;UAE1E,OAAO,UAAU;IAEzB;IACA,OAAO;GACX;GACA,OAAO;EAEX,KAAK;GACD,IAAI,OAAO,UAAU,UAAU;IAC3B,MAAM,SAAS,WAAW,KAAK;IAC/B,OAAO,MAAM,MAAM,IAAI,OAAO;GAClC;GACA,OAAO;EAEX,KAAK,YAKD,OAAO;EAEX,KAAK,UAAU;GACX,IAAI,OAAiB,CAAC;GACtB,IAAI,OAAO,UAAU,UACjB,OAAO,MAAM,MAAM,GAAG,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,MAAM;QAC5C,IAAI,MAAM,QAAQ,KAAK,GAC1B,OAAO,MAAM,IAAI,MAAM;QACpB,IAAI,iBAAiB,QACxB,OAAO,MAAM;QACV,IAAI,OAAO,UAAU,YAAY,UAAU,QAAQ,WAAW,OAAO;IACxE,MAAM,SAAS;IACf,IAAI,MAAM,QAAQ,OAAO,KAAK,GAC1B,OAAO,OAAO,MAAM,IAAI,MAAM;GAEtC;GACA,OAAO;IACH,QAAQ;IACR,OAAO;GACX;EACJ;EAEA,KAAK,QAAQ;GACT,IAAI;GACJ,IAAI,SAAS,eAAe,UAAU,OAAO,UAAU,UAInD,OAAO,gBAAgB,KAAK;QACzB,IAAI,iBAAiB,MACxB,OAAO;QACJ,IAAI,OAAO,UAAU,YAAY,OAAO,UAAU,UAAU;IAC/D,MAAM,aAAa,IAAI,KAAK,KAAK;IACjC,IAAI,CAAC,MAAM,WAAW,QAAQ,CAAC,GAC3B,OAAO;GAEf;GACA,IAAI,MACA,OAAO;IACH,QAAQ;IACR,OAAO,KAAK,YAAY;GAC5B;GAEJ,OAAO;EACX;EAEA,SAAS;GAEL,MAAM,MAAM,iBAAiB,KAAK;GAClC,IAAI,KACA,OAAO,uBAAuB,GAAG;GAErC,OAAO;EACX;CACJ;AACJ;;;;;;;;;;;;;;;;;AAkBA,IAAM,sCAAsB,IAAI,IAAI;CAAC;CAAU;CAAa;AAAU,CAAC;AAEvE,SAAS,sBACL,MACA,YACA,YACA,mBACA,SACuB;CACvB,MAAM,SAAkC,CAAC;CAGzC,MAAM,oCAAoB,IAAI,IAAY;CAC1C,OAAO,OAAO,iBAAiB,CAAC,CAAC,SAAQ,aAAY;EACjD,IAAI,SAAS,SAAS,aAAa;EAGnC,MAAM,aAAa,kBAAkB,YAAY,SAAS,QAAQ;EAClE,IAAI,CAAC,WAAW,aAAa,kBAAkB,IAAI,UAAU;CACjE,CAAC;CAED,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,IAAI,GAAG;EAE7C,IAAI,kBAAkB,IAAI,GAAG,GAAG;GAC5B,OAAO,OAAO,UAAU,OAAO,OAAQ,OAAO,UAAU,WAAW,QAAQ,OAAO,KAAK;GACvF;EACJ;EAMA,IAAI,oBAAoB,IAAI,GAAG,GAAG;GAC9B,OAAO,OAAO;GACd;EACJ;EAEA,MAAM,WAAW,WAAW;EAC5B,IAAI,CAAC,UAAU;EAEf,IAAI,QAAQ,iBAAiB,SAAS,SAAS,YAAY;EAE3D,OAAO,OAAO,wBAAwB,OAAO,UAAU,YAAY,GAAG;CAC1E;CAEA,OAAO;AACX;;;;;;;;;;AAWA,SAAgB,kBACZ,MACA,YACC;CACD,MAAM,aAAa,WAAW;CAC9B,IAAI,CAAC,QAAQ,CAAC,YAAY,OAAO;CAGjC,OAAO,sBAAsB,MAAM,YAAY,YADrB,2BAA2B,UACM,GAAmB,EAAE,eAAe,KAAK,CAAC;AACzG;;;;;;;;;;;;AC9yBA,SAAgB,aAAa,MAAuB;CAChD,OAAO,KAAK,SAAS,GAAG;AAC5B;AAEA,SAAgB,kBAAkB,MAAwB;CACtD,OAAO,KAAK,MAAM,GAAG,CAAC,CAAC,QAAO,MAAK,KAAK,MAAM,WAAW;AAC7D;;;;;;;;;AAUA,SAAgB,kBACZ,MACA,UACyB;CACzB,IAAI,CAAC,aAAa,IAAI,GAAG,OAAO,KAAA;CAEhC,MAAM,WAAW,kBAAkB,IAAI;CACvC,IAAI,SAAS,SAAS,KAAK,SAAS,SAAS,MAAM,GAC/C,MAAM,IAAI,MAAM,0BAA0B,KAAK,0CAA0C;CAG7F,IAAI,mBAAmB,oBAAoB,SAAS,IAAI,QAAQ;CAChE,IAAI,WAAW,SAAS;CAExB,KAAK,IAAI,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK,GAAG;EACzC,MAAM,cAAc,SAAS;EAC7B,MAAM,oBAAoB,2BAA2B,gBAAgB;EACrE,MAAM,WAAW,aAAa,mBAAmB,WAAW;EAE5D,IAAI,CAAC,UAAU;GACX,MAAM,YAAY,OAAO,KAAK,iBAAiB,CAAC,CAAC,KAAK,IAAI,KAAK;GAW/D,MAAM,SAAS,SACX,aAAa,YAAY,6BAA6B,iBAAiB,KAAK,2BAA2B,UAAU,IACjH,kBACJ;EACJ;EAEA,MAAM,mBAAmB,SAAS,OAAO;EAEzC,IAAI,MAAM,SAAS,SAAS,GACxB,OAAO;GACH;GACA;GACA;GACA;GACA;EACJ;EAGJ,mBAAmB;EACnB,WAAW,SAAS,IAAI;CAC5B;CAIA,MAAM,IAAI,MAAM,2BAA2B,MAAM;AACrD;;;;;;AAOA,SAAgB,yBAAyB,UAAqC;CAC1E,OAAO,SAAS;AACpB;;;;;;;;;;;AAYA,SAAgB,sBAAsB,KAAoB,MAAoB;CAC1E,MAAM,EAAE,aAAa;CAOrB,IAAI,SAAS,YAAY,SAAS,gBAAgB,QAAQ;CAE1D,IAAI,CAAC,SAAS,UACV,MAAM,SAAS,WACX,IAAI,KAAK,aAAa,IAAI,YAAY,gHACiB,IAAI,iBAAiB,KAAK,cACjF,uBACJ;CAGJ,MAAM,SAAS,WACX,IAAI,KAAK,iCAAiC,IAAI,YAAY,sFACP,IAAI,iBAAiB,KAAK,aACzE,IAAI,iBAAiB,KAAK,8BAA8B,IAAI,iBAAiB,KAAK,aAClF,IAAI,YAAY,2BACpB,uBACJ;AACJ;;;;;;;;;;;;;ACrFA,SAAS,eAAe,GAAY,GAAY,WAA4B;CACxE,MAAM,QAAQ,MAAM,QAAQ,MAAM,KAAA;CAClC,MAAM,QAAQ,MAAM,QAAQ,MAAM,KAAA;CAClC,IAAI,SAAS,OAAO,OAAO;CAC3B,IAAI,OAAO,OAAO,YAAY,IAAI;CAClC,IAAI,OAAO,OAAO,YAAY,KAAK;CAGnC,MAAM,KAAK,aAAa,OAAO,EAAE,QAAQ,IAAI;CAC7C,MAAM,KAAK,aAAa,OAAO,EAAE,QAAQ,IAAI;CAC7C,IAAI,OAAO,OAAO,YAAY,OAAO,OAAO,UAAU,OAAO,KAAK;CAClE,IAAI,OAAO,OAAO,aAAa,OAAO,OAAO,WAAW,QAAQ,KAAK,IAAI,MAAM,KAAK,IAAI;CACxF,OAAO,OAAO,EAAE,CAAC,CAAC,cAAc,OAAO,EAAE,CAAC;AAC9C;;;;;;;;;;;;;;;;;;;;;;;AAwBA,SAAgB,wBACZ,YACA,YACW;CACX,MAAM,iBAAiB,UACnB,QAAQ,KAAK,KAAK,WAAW,MAAgB,EAAE,SAAS;CAC5D,OAAO,IAAI,IACP,WACK,QAAO,MACJ,EAAE,OAAO,WAAW,EAAE,OAAO,SAAS,EAAE,OAAO,UAC1C,EAAE,OAAO,SAAS,EAAE,OAAO,UAAU,cAAc,EAAE,KAAK,CAAE,CAAC,CACrE,KAAI,MAAK,EAAE,KAAK,CACzB;AACJ;AAEA,IAAa,eAAb,MAAa,aAAa;CAIV;CACA;CAOA;CAXZ;CAEA,YACI,IACA,UAOA,aACF;EATU,KAAA,KAAA;EACA,KAAA,WAAA;EAOA,KAAA,cAAA;EAER,KAAK,kBAAkB,IAAI,gBAAgB,IAAI,UAAU,WAAW;CACxE;;;;;CAMA,gBAAwB,WAAsG;EAE1H,OADe,KAAK,GAAmC,QACxC;CACnB;;;;;;;;;;;;;;;;;CAkBA,cAAsB,gBAAwB,OAA+C;EACzF,MAAM,aAAa,KAAK,SAAS,oBAAoB,cAAc,KAAK,KAAA;EACxE,OAAO;GACH;GACA,UAAU,KAAK;GACf,gBAAgB,aAAa,KAAK,gBAAgB,YAAY,KAAK,IAAI,KAAA;EAC3E;CACJ;;;;;;;;;;;;;;;CAgBA,WACI,gBACA,YACA,OACA,WACA,OACc;EACd,OAAO,sBACH;GAAE,UAAU,KAAK;GAAU,aAAa,KAAK;EAAY,GACzD,YAAY,gBAAgB,OAC5B;GAAE;GAAW;EAAM,GAAG,KAAK,cAAc,gBAAgB,KAAK,CAClE;CACJ;;CAGA,oBACI,gBACA,YACA,OACA,WACA,OACwB;EACxB,OAAO,qBACH;GAAE,UAAU,KAAK;GAAU,aAAa,KAAK;EAAY,GACzD,YAAY,gBAAgB,OAC5B;GAAE;GAAW;EAAM,GAAG,KAAK,cAAc,gBAAgB,KAAK,CAClE;CACJ;;;;;;;;CASA,OAAe,aAAa,SAUd;EACV,MAAM,UAAU,uBAAuB,QAAQ,SAAS,QAAQ,KAAK;EACrE,OAAO;GACH,QAAQ,QAAQ;GAChB,SAAS,QAAQ;GACjB,cAAc,QAAQ;GACtB,OAAO,QAAQ;GACf,QAAQ,QAAQ;GAChB,SAAS,WAAW,QAAQ,SAAS,IAAI,UAAU,KAAA;GACnD,QAAQ,QAAQ;GAChB,WAAW,QAAQ,YACb;IACE,YAAY,QAAQ,UAAU,iBAAiB;IAC/C,UAAU,QAAQ,UAAU;IAC5B,cAAc,QAAQ,UAAU,SAAS;GAC7C,IACE,KAAA;EACV;CACJ;;;;;;;;;CAUA,gBAAwB,YAA8B,OAA8C;EAChG,MAAM,CAAC,UAAU,eAAe,YAAY,KAAK,QAAQ;EACzD,IAAI,CAAC,QAAQ,OAAO,KAAA;EACpB,OAAO,MAAM,OAAO;CACxB;;;;;CAMA,sBACI,QACA,OACA,gBACK;EACL,OAAO,wBAAwB,sBAC3B,QAAQ,OAAO,gBAAgB,KAAK,cAAc,gBAAgB,KAAK,CAC3E;CACJ;;;;;;;;;;;;;;;;;;;;;;;;CA6BA,OAAgB,cAAc;;;;;CAM9B,OAAgB,iBAAiB;CAEjC,mBACI,OACA,SACA,YACA,cAC6B;EAC7B,IAAI,YAAY,aAAa,eAAe,cAAc,cAAc;GACpE,MAAM,OAAO,wBAAwB,0BAA0B,cAAc,OAAO,UAAU;GAC9F,IAAI,MAAM,OAAO;EACrB;EACA,OAAO,KAAK,oBAAoB,OAAO,SAAS,UAAU;CAC9D;;;;;;;;;;;;;CAcA,4BACI,OACA,SACA,YACA,gBACA,UACe;EACf,MAAM,OAAO,2BAA2B,OAAO;EAC/C,IAAI,CAAC,MAAM,OAAO,KAAA;EAClB,IAAI,CAAC,cAAc,CAAC,gBAChB,MAAM,SAAS,WACX,mBAAmB,QAAQ,gGAE3B,+BACA,EAAE,OAAO,QAAQ,CACrB;EAEJ,MAAM,cAAc,eAAe,YAAY,KAAK,QAAQ;EAC5D,MAAM,WAAW,YAAY,WAAW,IAClC,MAAM,YAAY,EAAE,CAAC,aACrB,KAAA;EACN,IAAI,CAAC,UACD,MAAM,SAAS,WACX,mBAAmB,QAAQ,mBAAmB,eAAe,0GAE7D,+BACA;GAAE,OAAO;GAAS,YAAY;EAAe,CACjD;EAEJ,OAAO,wBAAwB,iCAC3B,MAAM,OAAO,YAAY,KAAK,UAAU,UAAU,gBAAgB,QACtE;CACJ;;;;;;;;;;CAWA,iBACI,OACA,MACA,YACA,cACA,gBACA,UACkB;EAClB,MAAM,WAA+B,CAAC;EACtC,KAAK,MAAM,CAAC,OAAO,WAAW,UAAU,MAAM;GAG1C,MAAM,YAAY,KAAK,4BAA4B,OAAO,OAAO,YAAY,cAAc;GAC3F,IAAI,WAAW;IACX,SAAS,KAAK;KACV;KACA;KACA;KACA,QAAQ;KAIR,GAAI,aAAa,KAAA,KAAa,EAC1B,cAAc,KAAK,4BACf,OAAO,OAAO,YAAY,gBAAgB,QAC9C,EACJ;IACJ,CAAC;IACD;GACJ;GACA,MAAM,SAAS,KAAK,mBAAmB,OAAO,OAAO,YAAY,YAAY;GAC7E,IAAI,QAAQ,SAAS,KAAK;IAAE;IACxC;IACA;IACA;GAAO,CAAC;EACA;EACA,OAAO;CACX;;;;;;;;;;;;;;;CAgBA,OAAe,UAAU,KAA6D;EAClF,IAAI,IAAI,OAAO,OAAO,IAAI,UAAU;EACpC,OAAO,IAAI,cAAc;CAC7B;;;;;;;;;;;;;;;;;;;;;;;CAwBA,sBACI,MAUA,YACK;EAML,MAAM,cAAc,KAAK,KAAK,QAAQ;GAClC,MAAM,YAAY,aAAa,UAAU,GAAG;GAC5C,IAAI,IAAI,cAAc,OAClB,OAAO,YAAY,GAAG,GAAG,IAAI,OAAO,mBAAmB,GAAG,GAAG,IAAI,OAAO;GAE5E,OAAO,YAAY,GAAG,GAAG,IAAI,OAAO,oBAAoB,GAAG,GAAG,IAAI,OAAO;EAC7E,CAAC;EACD,KAAK,MAAM,UAAU,YAAY,YAAY,KAAK,KAAK,MAAM,CAAC;EAC9D,OAAO;CACX;CAEA,oBACI,OACA,SACA,YACuB;EACvB,MAAM,YAAY,SACb,OAAO,QAAQ,MAAM,OAA4C,KAAA,MAAc,KAAA;EAEpF,MAAM,SAAS,SAAS,OAAO;EAC/B,IAAI,QAAQ,OAAO;EAGnB,MAAM,mBAAmB,aAAa,2BAA2B,UAAU,CAAC,CAAC,WAAW,KAAA;EACxF,IAAI,kBAAkB,SAAS,aAAa;GAExC,MAAM,aAAa,SAAS,kBAAkB,YAAY,iBAAiB,QAAQ,CAAC;GACpF,IAAI,YAAY,OAAO;EAC3B;EAMA,KAAK,MAAM,SAAS;GAChB,GAAG,QAAQ;GACX,UAAU,uBAAuB,OAAO,CAAC;GACzC,GAAG,QAAQ;GACX,uBAAuB,OAAO;EAClC,GAAG;GACC,MAAM,aAAa,SAAS,KAAK;GACjC,IAAI,YAAY,OAAO;EAC3B;EAcA,MAAM,iBAAiB,YAAY,QAAQ,YAAY;EACvD,MAAM,eAAe,iBAAiB,mBAAmB,eAAe,KAAK;EAO7E,IAAI,oBAAoB,iBAAiB,SAAS,aAC9C,MAAM,SAAS,WACX,mBAAmB,QAAQ,GAAG,aAAa,8BACvC,iBAAiB,KAAK,oDAC1B,+BACA;GAAE,OAAO;GAAS,MAAM,iBAAiB;GAAM,GAAI,kBAAkB,EAAE,YAAY,eAAe;EAAG,CACzG;EAGJ,IAAI,2BAA2B,MAAM,QAAQ;GACzC,OAAO,KACH,qBAAqB,QAAQ,GAAG,aAAa,6FAEjD;GACA;EACJ;EAEA,IAAI,cAAwB,CAAC;EAC7B,IAAI;GACA,cAAc,OAAO,KAAK,gBAAgB,KAAK,CAAC,CAAC,CAAC,KAAK;EAC3D,QAAQ,CAGR;EAEA,MAAM,SAAS,WACX,0BAA0B,QAAQ,GAAG,kBACpC,YAAY,SAAS,IAAI,mBAAmB,YAAY,KAAK,IAAI,MAAM,KACxE,0BACA;GACI,OAAO;GACP,GAAI,kBAAkB,EAAE,YAAY,eAAe;GACnD,GAAI,YAAY,SAAS,KAAK,EAAE,YAAY;EAChD,CACJ;CACJ;;;;;;;;;CA0BA,oBACI,YACA,MACA,UACgE;EAChE,MAAM,oBAAoB,2BAA2B,UAAU;EAC/D,MAAM,MAAwE,CAAC;EAE/E,IAAI,UAGA,KAAK,MAAM,CAAC,KAAK,aAAa,OAAO,QAAQ,iBAAiB,GAC1D,IAAI,KAAK;GAAE;GAAK,MAAM,KAAK,QAAQ,EAAE,UAAU,CAAC,EAAE;GAAG;EAAS,CAAC;EAIvE,KAAK,MAAM,CAAC,KAAK,SAAS,OAAO,QAAQ,IAAI,GAAG;GAC5C,IAAI,YAAY,OAAO,mBAAmB;GAC1C,MAAM,WAAW,kBAAkB;GACnC,IAAI,CAAC,UAAU;IACX,MAAM,QAAQ,OAAO,KAAK,iBAAiB,CAAC,CAAC,KAAK;IAClD,MAAM,SAAS,WACX,qBAAqB,IAAI,mBAAmB,WAAW,QAAQ,WAAW,KAAK,MAC5E,MAAM,SAAS,IACZ,wBAAwB,MAAM,KAAK,IAAI,MACvC,gCACN,oBACA;KAAE,UAAU;KAAK,YAAY,WAAW,QAAQ,WAAW;KAAM,GAAI,MAAM,SAAS,KAAK,EAAE,gBAAgB,MAAM;IAAG,CACxH;GACJ;GACA,IAAI,KAAK;IAAE;IAAK;IAAM;GAAS,CAAC;EACpC;EAEA,OAAO;CACX;;;;;;;;CASA,iBAAyB,MAAmB,kBAAqD;EAC7F,IAAI,CAAC,KAAK,SAAS,CAAC,KAAK,SAAS,OAAO,KAAA;EACzC,MAAM,aAAa,iBAAiB;EACpC,MAAM,cAAc,sBAAsB,kBAAkB,KAAK,QAAQ;EACzE,MAAM,aAAoB,CAAC;EAC3B,IAAI,KAAK,OACL,WAAW,KAAK,GAAG,KAAK,sBAAsB,KAAK,OAAO,aAAa,UAAU,CAAC;EAEtF,IAAI,KAAK,SAAS;GACd,MAAM,UAAU,wBAAwB,uBACpC,KAAK,SAAS,aAAa,YAAY,KAAK,cAAc,YAAY,WAAW,CACrF;GACA,IAAI,SAAS,WAAW,KAAK,OAAO;EACxC;EACA,OAAO,WAAW,SAAS,IACrB,wBAAwB,yBAAyB,UAAU,IAC3D,KAAA;CACV;;;;;;;;;;;CAYA,iBACI,MACA,MACA,kBACyB;EACzB,IAAI,MAAM;EACV,IAAI,KAAK,WAAW,KAAK,QAAQ,SAAS,GAAG;GACzC,MAAM,OAAO,KAAK;GAClB,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,MAAM,GAAG,MAAM;IAC1B,KAAK,MAAM,CAAC,OAAO,WAAW,UAAU,MAAM;KAC1C,MAAM,MAAM,eAAe,EAAE,QAAQ,EAAE,QAAQ,aAAa,UAAU;MAAE;MAAW;KAAM,CAAC,CAAC;KAC3F,IAAI,QAAQ,GAAG,OAAO,cAAc,SAAS,CAAC,MAAM;IACxD;IACA,OAAO;GACX,CAAC;EACL;EACA,IAAI,KAAK,UAAU,KAAA,GAAW,MAAM,IAAI,MAAM,GAAG,KAAK,KAAK;EAC3D,IAAI,KAAK,UAAU,KAAK,OAAO,SAAS,GAAG;GACvC,MAAM,OAAO,IAAI,IAAI,KAAK,MAAM;GAGhC,KAAK,MAAM,MAAM,eAAe,kBAAkB,KAAK,QAAQ,GAAG,KAAK,IAAI,GAAG,SAAS;GAMvF,KAAK,IAAI,kBAAkB;GAC3B,MAAM,IAAI,KAAI,QAAO;IACjB,MAAM,YAAqC,CAAC;IAC5C,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,GAAG,GACzC,IAAI,KAAK,IAAI,GAAG,GAAG,UAAU,OAAO;IAExC,OAAO;GACX,CAAC;EACL;EACA,OAAO;CACX;;;;;;;;;;;CAYA,MAAc,aACV,MACA,YACA,gBACA,SACA,QAAQ,GACK;EACb,IAAI,KAAK,WAAW,KAAK,QAAQ,mBAAmB;EAEpD,MAAM,cAAc,eAAe,YAAY,KAAK,QAAQ;EAC5D,IAAI,YAAY,WAAW,GAAG;EAK9B,MAAM,aAAa,QAAqD;GACpE,MAAM,UAAU,mBAAiB,KAAK,WAAW;GACjD,OAAO,WAAW,QAAQ,MAAM,wBAAsB,CAAC,CAAC,MAAK,SAAQ,SAAS,EAAE,IAC1E,UACA,KAAA;EACV;EACA,MAAM,cAAc,KAAK,QAAO,QAAO,UAAU,GAAG,MAAM,KAAA,CAAS;EACnE,IAAI,YAAY,WAAW,GAAG;EAC9B,MAAM,SAAS,YAAY,KAAI,QAAO,UAAU,GAAG,CAAW;EAE9D,MAAM,SAAS,KAAK,oBAAoB,YAAY,QAAQ,MAAM,QAAQ,QAAQ;EAElF,KAAK,MAAM,EAAE,KAAK,MAAM,cAAc,QAAQ;GAC1C,MAAM,mBAAmB,SAAS,OAAO;GAKzC,MAAM,SAA0B,KAAK,iBAAiB,MAAM,gBAAgB;GAK5E,MAAM,SAAoC,CAAC;GAE3C,IAAI;IACA,IAAI,SAAS,gBAAgB,OAAO;KAChC,MAAM,UAAU,MAAM,KAAK,gBAAgB,0BACvC,gBAAgB,QAAQ,KAAK,UAAU,MAC3C;KACA,KAAK,MAAM,OAAO,aAAa;MAC3B,MAAM,UAAU,QAAQ,IAAI,OAAO,UAAU,GAAG,CAAC,CAAC;MAIlD,IAAI,CAAC,SAAS;OAAE,IAAI,OAAO;OAAM;MAAU;MAM3C,MAAM,CAAC,UAAU,KAAK,iBAClB,CAAC,aAAa,EAAE,GAAG,QAAQ,OAAO,GAAG,gBAAgB,CAAC,GAAG,MAAM,gBACnE;MACA,IAAI,OAAO;MACX,OAAO,KAAK,MAAM;KACtB;IACJ,OAAO;KACH,MAAM,UAAU,MAAM,KAAK,gBAAgB,8BACvC,gBAAgB,QAAQ,KAAK,UAAU,MAC3C;KACA,KAAK,MAAM,OAAO,aAAa;MAC3B,MAAM,UAAU,QAAQ,IAAI,OAAO,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC;MACxD,MAAM,SAAS,KAAK,iBAShB,QAAQ,KAAI,MAAM,EAAE,QACd;OAAE,GAAG,aAAa,EAAE,GAAG,EAAE,OAAO,GAAG,gBAAgB;QAAI,qBAAqB,EAAE;MAAM,IACpF,aAAa,EAAE,GAAG,EAAE,OAAO,GAAG,gBAAgB,CAAE,GACtD,MAAM,gBACV;MACA,IAAI,OAAO;MACX,OAAO,KAAK,GAAG,MAAM;KACzB;IACJ;GACJ,SAAS,GAAG;IAKR,IAAI,aAAa,UAAU,MAAM;IACjC,IAAI,gBAAgB,CAAC,GAAG,MAAM;IAC9B,OAAO,KAAK,sCAAsC,IAAI,OAAO,kBAAkB,EAAE,OAAO,EAAE,CAAC;IAC3F;GACJ;GAEA,MAAM,WAAW,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,SAAS,IAC/C;IAAE,UAAU;IAAO,MAAM,KAAK;GAAS,IACvC,KAAA;GACN,IAAI,YAAY,OAAO,SAAS,GAC5B,MAAM,KAAK,aACP,QAAQ,kBAAkB,iBAAiB,MAAM,UAAU,QAAQ,CACvE;EAER;CACJ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqCA,iBACI,OACA,YACA,QACA,aACA,UACmC;EACnC,MAAM,UAAU,wBAAwB,gBAAgB,KAAK,GAAG,UAAU;EAC1E,IAAI,CAAC,UAAU,OAAO,WAAW,GAAG,OAAO;EAE3C,IAAI;EACJ,IAAI;GACA,eAAe,gBAAgB,KAAK;EACxC,QAAQ;GAGJ,OAAO;EACX;EACA,MAAM,YAAY,WAAW;EAE7B,MAAM,OAAO,IAAI,IAAI,MAAM;EAC3B,IAAI,CAAC,UAAU,KAAK,MAAM,MAAM,aAAa,KAAK,IAAI,GAAG,SAAS;EAClE,MAAM,YAAY,2BAA2B,UAAU;EAEvD,MAAM,aAAsC,CAAC;EAC7C,KAAK,MAAM,QAAQ,MAAM;GACrB,MAAM,SAAS,UAAU;GACzB,IAAI,WAAW,KAAA,GAAW;IAAE,WAAW,QAAQ;IAAQ;GAAU;GAGjE,IAAI,aAAa,UAAU,KAAA,GAAW;GAItC,IAAI,QAAQ,WAAW;GACvB,MAAM,SAAS,WACX,kBAAkB,KAAK,iCACjB,WAAW,QAAQ,WAAW,KAAK,qBACvC,OAAO,KAAK,SAAS,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,IAAI,GACzC,iBACA;IAAE,OAAO;IAAM,YAAY,WAAW,QAAQ,WAAW;GAAK,CAClE;EACJ;EACA,OAAO;CACX;;;;;;;;;CAUA,UACI,gBACA,KACA,SACkB;EAMlB,IAAI,SAAS,MAAM,CAAC,WAAW,UAAU,aAAa,WAAW,GAAG,OAAO,KAAA;EAG3E,IAAI,aAAa,kBAAkB,KAAK,OAAO,KAAA;EAC/C,IAAI;GAEA,MAAM,MAAM,eADO,oBAAoB,gBAAgB,KAAK,QACjC,GAAY,KAAK,QAAQ;GACpD,IAAI,IAAI,WAAW,GAAG,OAAO,KAAA;GAC7B,MAAM,UAAU,mBAAiB,KAAK,GAAG;GACzC,IAAI,CAAC,WAAW,CAAC,QAAQ,MAAM,wBAAsB,CAAC,CAAC,MAAK,SAAQ,SAAS,EAAE,GAAG,OAAO,KAAA;GAKzF,OAAO,aAAa,SAAS,KAAK,IAAI,WAAW,IAAI,IAAI,IAAI,EAAE,CAAC,aAAa,OAAO;EACxF,QAAQ;GAGJ;EACJ;CACJ;;;;;;;;;;;;;;;;CAiBA,MAAc,iBACV,KACA,SACA,YACA,gBACa;EACb,IAAI,CAAC,SAAS;EACd,MAAM,SAAS,YACX,0BAA0B,QAAQ,IAAI,QAAQ,MAAM;GAChD,IAAI,QAAQ;GACZ,MAAM,QAAQ;GACd,QAAQ,QAAQ;EACpB,CAAC;EAEL,KAAK,MAAM,CAAC,KAAK,aAAa,OAAO,QAAQ,2BAA2B,UAAU,CAAC,GAC/E,IAAI;GACA,IAAI,SAAS,gBAAgB,OAAO;IAChC,MAAM,WAAW,MAAM,KAAK,gBAAgB,0BACxC,gBAAgB,CAAC,OAAO,GAAG,KAAK,QACpC,EAAA,CAAG,IAAI,OAAO;IACd,IAAI,SAAS,IAAI,OAAO,MAAM,OAAO;GACzC,OAII,IAAI,SAHa,MAAM,KAAK,gBAAgB,8BACxC,gBAAgB,CAAC,OAAO,GAAG,KAAK,QACpC,EAAA,CAAG,IAAI,OAAO,KAAK,CAAC,EAAA,CACD,IAAI,KAAK;EAEpC,SAAS,GAAG;GACR,IAAI,aAAa,UAAU,MAAM;GACjC,IAAI,gBAAgB,CAAC,GAAG,MAAM;GAC9B,OAAO,KAAK,2CAA2C,IAAI,OAAO,kBAAkB,EAAE,OAAO,EAAE,CAAC;EACpG;CAER;;;;;;;;;;CAWA,sBACI,OACA,aACA,SACA,gBACK;EACL,IAAI,CAAC,QAAQ,YAAY,OAAO,CAAC;EACjC,MAAM,SAAS,QAAQ;EACvB,MAAM,OAAO,uBAAuB,QAAQ,SAAS,QAAQ,KAAK;EAClE,MAAM,aAAa,aAAa,WAAW,OAAO,aAAa,cAAc;EAC7E,MAAM,YAAY,aAAa,UAAU,QAAQ,aAAa,cAAc;EAE5E,IAAI,MAAM;GAON,IAAI,KAAK,MAAM,CAAC,WAAW,UAAU,aAAa,WAAW,GACzD,MAAM,SAAS,WACX,0OAGA,4BACA,EAAE,OAAO,aAAa,YAAY,CACtC;GAEJ,MAAM,aAAa,oBAAoB,gBAAgB,KAAK,QAAQ;GACpE,MAAM,WAAW,KAAK,iBAClB,OAAO,MAAM,YAAY,KAAA,GAAW,gBAIpC,WAAW,WAAW,IAAI,UAAU,KAAK,KAAA,CAC7C;GAEA,IAAI,SAAS,SAAS,KAAK,WAAW;IAClC,MAAM,eAAe,OAAO;IAK5B,MAAM,SAAS,SAAS,KAAK,EAAE,OAAO,mBAAmB,eAKnD,OACC,gBAAgB,SAAS,eAAgB,aAAa,SAAS,OAAO,MAAM;IAMnF,IAAI,OAAO,OAAO,OAAO,MAAM,SAAS,EAAE,CAAC,gBAAgB,UAAU,KAAA,CAAS,GAAG;KAC7E,MAAM,aAAa,OAAO,KAAK,OAAO,MAClC,KAAK,mBAAmB,OAAO,SAAS,EAAE,CAAC,QAAQ,OAAO,YAAY,SAAS,CAAC;KACpF,OAAO,CAAC,KAAK,sBAAsB,UAAU,YAAY,YAAY,SAAS,CAAC;IACnF;GACJ;EACJ,OAAO,IAAI,WACP,OAAO,CAAC,aAAa,SAAS,YAAY,SAAS,CAAC;EAGxD,OAAO,CAAC;CACZ;;CAGA,OAAe,WAAW,OAAqB,aAA+B,gBAAuC;EACjH,OAAO,YAAY,KAAI,SAAQ;GAC3B,MAAM,SAAS,MAAM,KAAK;GAC1B,IAAI,CAAC,QACD,MAAM,IAAI,MAAM,aAAa,KAAK,UAAU,uCAAuC,eAAe,EAAE;GAExG,OAAO;EACX,CAAC;CACL;;;;;;;;;;CAWA,OAAe,UACX,QACA,aACA,gBACqB;EACrB,MAAM,UAAU,OAAO;EACvB,IAAI,YAAY,WAAW,GAAG;GAC1B,MAAM,QAAQ,WAAW,OAAO,YAAY,EAAE,CAAC;GAC/C,OAAO,UAAU,KAAA,KAAa,UAAU,OAAO,KAAA,IAAY,CAAC,KAAK;EACrE;EACA,IAAI,YAAY,KAAA,KAAa,YAAY,MAAM;GAC3C,MAAM,QAAQ,YAAY,KAAI,SAAQ,OAAO,KAAK,UAAU;GAC5D,OAAO,MAAM,MAAK,SAAQ,SAAS,KAAA,KAAa,SAAS,IAAI,IAAI,KAAA,IAAY;EACjF;EACA,IAAI;EACJ,IAAI;GACA,SAAS,gBAAc,OAAO,OAAO,GAAG,WAAW;EACvD,QAAQ;GACJ,MAAM,SAAS,WACX,iEAAiE,eAAe,KAC5E,YAAY,KAAI,SAAQ,KAAK,SAAS,CAAC,CAAC,KAAK,IAAI,EAAE,iHAEvD,gBACJ;EACJ;EACA,OAAO,YAAY,KAAI,SAAQ,OAAO,KAAK,UAAU;CACzD;;;;;;;;;CAUA,OAAe,SAAS,YAA2B,WAA2B;EAC1E,IAAI,WAAW,WAAW,GAAG,OAAO,GAAG,WAAW,IAAI,UAAU,EAAE;EAClE,MAAM,SAAS,WAAW,KAAK,QAAQ,MAAM,IAAI,MAAM,UAAU,IAAI,MAAM,CAAC;EAC5E,OAAO,GAAG,IAAI,IAAI,KAAK,YAAY,GAAG,IAAI,EAAE,OAAO,IAAI,KAAK,QAAQ,GAAG,IAAI,EAAE;CACjF;;;;;;;;;;;;;;;;;;;CAoBA,mBACI,OACA,QACA,OACA,YACA,WACO;EACP,IAAI,UAAU,QAAQ,UAAU,KAAA,GAAW,OAAO;EAClD,IAAI,CAAC,GAAG,QAAQ,WAAW,KAAK,CAAC,GAAG,QAAQ,iBAAiB,GAAG,OAAO;EAEvE,IAAI,OAAO,cAAc,KAAA,KAAa,OAAO,aAAa,GAAG,OAAO;EAEpE,MAAM,SAAS,GAAG,QAAQ,iBAAiB,OAAO,MAAM,YAAY,IAAI,OAAO,KAAK,EAAE,MAAM,IAAI,IAAI,OAAO,WAAW,CAAC,EAAE;EACzH,MAAM,QAAQ,IAAI,WAAW,cAAc;EAC3C,MAAM,SAAS,GAAG,GAAG,MAAM,GAAG,IAAI,WAAW,OAAO,IAAI;EAGxD,OAAO,GAAG,oBAAoB,OAAO,QAAQ,MAAM,MAAM,MAAM,SAF7C,IAAI,KAAK,WAAW,KAAK,KAAK,MAC5C,GAAG,GAAG,MAAM,GAAG,IAAI,WAAW,IAAI,IAAI,EAAE,KAAK,IAAI,MAAM,UAAU,IAAI,GAAG,GAAG,GAAG,GAAG,OACb,EAAU,OAAO,OAAO,MAAM,OAAO,OAAO,OAAO,KAAK,OAAO,wCAAwC,OAAO;CAC1L;;;;;;;;;;;;;;CAeA,sBACI,MACA,QACA,YACA,WACA,QAAQ,GACL;EAGH,IAAI,SAAS,KAAK,QAAQ,OAAO,aAAa,SAAS,YAAY,SAAS;EAE5E,MAAM,EAAE,WAAW,iBAAiB,KAAK;EAKzC,MAAM,YAAY,aAAa,UAAU,KAAK,MAAM;EAMpD,MAAM,SAAS,KAAK,MAAM,CAAC;EAC3B,MAAM,QAAQ,OAAO;EACrB,MAAM,OAAO,KAAK,sBAAsB,MAAM,QAAQ,YAAY,WAAW,QAAQ,CAAC;EActF,MAAM,SAAS,GAAsB,MACjC,cAAc,QAAQ,GAAG,GAAkB,CAAC,IAAI,GAAG,GAAkB,CAAC;EAC1E,MAAM,YAAY,GAAsB,MACpC,cAAc,QAAQ,GAAG,GAAG,EAAE,KAAK,MAAM,GAAG,GAAG,EAAE,KAAK;EAE1D,IAAI,cACA,OAAO,YAID,GACE,IAAI,OAAO,YAAY,GAAG,OAAO,MAAM,GAAG,IAAI,GAC9C,IACI,UAAU,YAAY,GACtB,GACI,SAAS,QAAQ,YAAY,GAC7B,OAAO,MAAM,GACb,IAAI,GAAG,GAAG,OAAO,KAAK,gBAAgB,IAAI,CAC9C,CACJ,CACJ,IAIE,GACE,IAAI,OAAO,YAAY,GAAG,GAAG,UAAU,MAAM,GAAG,IAAI,OAAO,MAAM,GAAG,IAAI,CAAC,CAAC,GAC1E,IACI,UAAU,YAAY,GACtB,GACI,SAAS,QAAQ,YAAY,GAC7B,IAAI,GAAG,GAAG,OAAO,KAAK,gBAAgB,IAAI,CAC9C,CACJ,CACJ;EAGR,IAAI,UAAU,MAEV,OAAO,YAED,IAAI,OAAO,MAAM,GAAG,IAAI,IAExB,GAAG,UAAU,MAAM,GAAG,IAAI,OAAO,MAAM,GAAG,IAAI,CAAC;EAGzD,OAAO,YAED,GAAG,MAAM,QAAQ,KAAK,GAAG,OAAO,MAAM,GAAG,IAAI,GAAG,QAAQ,KAAK,GAAG,IAAI,CAAC,IAErE,GAAG,MAAM,QAAQ,KAAK,GAAG,IAAI,GAAG,QAAQ,KAAK,GAAG,IAAI,CAAC;CAC/D;;;;;CAMA,mBAA2B,KAAyB;EAChD,MAAM,YAAY,mBAAmB,IAAI,kBAAkB,KAAK,QAAQ;EACxE,MAAM,eAAe,gBAAc,IAAI,UAAU,SAAS;EAG1D,MAAM,iBAAiB,aAAa,UAAU,EAAE,CAAC;EAGjD,MAAM,eAAe;GACjB,MAAM,QAAQ,sBAAsB,IAAI,kBAAkB,KAAK,QAAQ;GAEvE,OAAO;IACH;IACA,KAHY,kBAAkB,OAAO,WAAW,IAAI,iBAAiB,IAGhE,CAAA,CAAQ,KAAK,QAAQ,OAAO;KAAE;KAAQ,OAAO,aAAa,UAAU,EAAE,CAAC;IAAW,EAAE;GAC7F;EACJ;EAEA,MAAM,cAAc,sBAAsB,IAAI,kBAAkB,KAAK,QAAQ;EAC7E,MAAM,YAAY,mBAAmB,IAAI,kBAAkB,KAAK,QAAQ;EACxE,MAAM,iBAAiB,YAAY,UAAU,EAAE,CAAC;EAChD,IAAI,CAAC,gBACD,MAAM,IAAI,MAAM,aAAa,UAAU,EAAE,CAAC,UAAU,uCAAuC,IAAI,iBAAiB,KAAK,EAAE;EAG3H,OAAO,wBAAwB,4BAC3B,IAAI,UACJ,QACA,gBACA,aACA,gBACA,KAAK,QACT;CACJ;;;;;;;;;;;CAYA,MAAc,mBAAmB,gBAAwB,IAAuC;EAC5F,IAAI,CAAC,aAAa,cAAc,GAAG,OAAO;EAC1C,MAAM,MAAM,kBAAkB,gBAAgB,KAAK,QAAQ;EAC3D,IAAI,CAAC,KAAK,OAAO;EACjB,OAAO,KAAK,gBAAgB,UAAU,KAAK,EAAE;CACjD;;;;CAKA,MAAM,SACF,gBACA,IACA,YACA,aAC4C;EAC5C,IAAI,CAAC,MAAM,KAAK,mBAAmB,gBAAgB,EAAE,GAAG,OAAO,KAAA;EAE/D,MAAM,aAAa,oBAAoB,gBAAgB,KAAK,QAAQ;EACpE,MAAM,QAAQ,sBAAsB,YAAY,KAAK,QAAQ;EAC7D,MAAM,cAAc,mBAAmB,YAAY,KAAK,QAAQ;EAKhE,IAAI,CAAC,kBAAkB,IAAI,OAAO,WAAW,GAAG,OAAO,KAAA;EAEvD,MAAM,YAAY,qBAAqB,OAAO,aAAa,IAAI,cAAc;EAI7E,MAAM,YAAY,MAAM,KAAK,oBACzB,gBAAgB,YAAY,OAAO,OAAO,CAAC,CAC/C;EACA,MAAM,WAAW,YACX,IAAI,WAAW,SAAS,IACxB;EAGN,MAAM,YAAY,aAAa,KAAK;EAEpC,MAAM,KAAK,KAAK,gBAAgB,SAAS;EACzC,IAAI,IAAI;GAGJ,IAAI,MAAkD;GACtD,IAAI;IACA,MAAM,SAAS,oBAAoB,gBAAgB,KAAK,GAAG,UAAU;IAErE,MAAM,MAAM,GAAG,UAAU;KAIrB,OAAO,cAAc,UAAU,YAAY,OAAO,WAAW;KAC7D,GAAI,SAAS,EAAE,SAAS,OAAO,IAAI,CAAC;IACxC,CAAuD;GAC3D,SAAS,GAAG;IACR,IAAI,gBAAgB,CAAC,GAAG,MAAM;IAC9B,OAAO,KAAK,gDAAgD,eAAe,8BAA8B,EAAE,OAAO,EAAE,CAAC;GACzH;GAEA,IAAI,QAAQ,KAAA,GAAW,OAAO,KAAA;GAC9B,IAAI,QAAQ,MAAM;IACd,MAAM,UAAU,UAAU,KAAK,YAAY,KAAK,QAAQ;IACxD,MAAM,KAAK,iBAAiB,SAAS,mBAAiB,KAAK,WAAW,GAAG,YAAY,cAAc;IAInG,OAAO,gBAAgB,SAAS,UAAU;GAC9C;EACJ;EAGA,MAAM,aAAa,wBAAwB,gBAAgB,KAAK,GAAG,UAAU;EAC7E,MAAM,SAAS,MAAM,KAAK,GACrB,OAAO,UAAmB,CAAC,CAC3B,KAAK,KAAK,CAAC,CACX,MAAM,cAAc,UAAU,YAAY,OAAO,WAAW,CAAC,CAAC,CAC9D,MAAM,CAAC;EAEZ,IAAI,OAAO,WAAW,GAAG,OAAO,KAAA;EAEhC,MAAM,MAAM,OAAO;EAInB,MAAM,SAAS,MAAM,oBAAoB,KAAK,UAAU;EACxD,MAAM,KAAK,iBAAiB,QAAQ,mBAAiB,KAAK,WAAW,GAAG,YAAY,cAAc;EAKlG,gBAAgB,QAAQ,UAAU;EAElC,OAAO;GACH,GAAG;GACH,IAAI,GAAG,SAAS;EACpB;CACJ;;;;CAKA,MAAM,wBACF,gBACA,UA+BI,CAAC,GAC6B;EAClC,MAAM,aAAa,oBAAoB,gBAAgB,KAAK,QAAQ;EACpE,MAAM,QAAQ,sBAAsB,YAAY,KAAK,QAAQ;EAC7D,MAAM,cAAc,mBAAmB,YAAY,KAAK,QAAQ;EAChE,MAAM,SAAS,YAAY;EAG3B,IAAI,CAFY,MAAM,OAAO,YAGzB,MAAM,IAAI,MAAM,aAAa,OAAO,UAAU,uCAAuC,eAAe,EAAE;EAY1G,MAAM,UAAU,MAAM,KAAK,2BAA8B,gBAAgB,OAAO;EAEhF,OAAO,KAAK,kBACR,SAAS,YAAY,gBAAgB,QAAQ,QAAQ,YACrD,iBAAiB,QAAQ,OAAO,GAAG,WACvC;CACJ;;;;;;;;;;CAWA,MAAc,kBACV,SACA,YACA,gBACA,QACA,aAUA,SACA,cACkC;EAClC,IAAI,QAAQ,WAAW,GAAG,OAAO,CAAC;EASlC,MAAM,SAAS,MAAM,QAAQ,IAAI,QAAQ,IAAI,OAAO,WAChD,MAAM,oBAAoB,QAAa,UAAU,CAA4B,CAAC;EAKlF,IAAI,SACA,MAAM,KAAK,aAAa,QAAQ,YAAY,gBAAgB,OAAO;EASvE,KAAK,MAAM,OAAO,QAAQ,gBAAgB,KAAK,UAAU;EAGzD,OAAO;CACX;;;;CAKA,MAAM,gBACF,gBACA,UA6BI,CAAC,GAC6B;EAQlC,MAAM,MAAM,aAAa,cAAc,IAAI,kBAAkB,gBAAgB,KAAK,QAAQ,IAAI,KAAA;EAC9F,OAAO,KAAK,wBACR,MAAM,IAAI,iBAAiB,OAAO,gBAClC;GAAE,GAAG;GAAS,WAAW;EAAI,CACjC;CACJ;;;;CAKA,MAAM,WACF,gBACA,cACA,UAiBI,CAAC,GAC6B;EAClC,OAAO,KAAK,wBAA2B,gBAAgB;GACnD,GAAG;GACH;EACJ,CAAC;CACL;;;;CAKA,MAAM,MACF,gBACA,UAeI,CAAC,GACU;EAIf,MAAM,MAAM,aAAa,cAAc,IAAI,kBAAkB,gBAAgB,KAAK,QAAQ,IAAI,KAAA;EAC9F,MAAM,gBAAgB,MAAM,IAAI,iBAAiB,OAAO;EAExD,MAAM,aAAa,oBAAoB,eAAe,KAAK,QAAQ;EACnE,MAAM,QAAQ,sBAAsB,YAAY,KAAK,QAAQ;EAE7D,IAAI,QAAQ,KAAK,GAAG,OAAO,EAAE,OAAO,MAAM,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,SAAS;EACpE,MAAM,gBAAuB,CAAC;EAE9B,IAAI,KAAK,cAAc,KAAK,KAAK,mBAAmB,GAAG,CAAC;EAIxD,eAAe,eAAe,YAAY,OAAO,QAAQ,WAAW;EAEpE,IAAI,QAAQ,cAAc;GACtB,MAAM,mBAAmB,wBAAwB,sBAC7C,QAAQ,cAAc,WAAW,YAAY,OAAO,UACxD;GACA,IAAI,iBAAiB,WAAW,GAAG,OAAO;GAC1C,cAAc,KAAK,wBAAwB,wBAAwB,gBAAgB,CAAE;EACzF;EAEA,IAAI,QAAQ,QAAQ;GAChB,MAAM,mBAAmB,KAAK,sBAAsB,QAAQ,QAAQ,OAAO,aAAa;GACxF,IAAI,iBAAiB,SAAS,GAAG,cAAc,KAAK,GAAG,gBAAgB;EAC3E;EAEA,IAAI,QAAQ,SAAS;GACjB,MAAM,mBAAmB,wBAAwB,uBAC7C,QAAQ,SAAS,OAAO,eAAe,KAAK,cAAc,eAAe,KAAK,CAClF;GACA,IAAI,kBAAkB,cAAc,KAAK,gBAAgB;EAC7D;EAMA,cAAc,KAAK,GAAG,MAAM,KAAK,WAC7B,eAAe,YAAY,OAAO,SAClC,aAAa,aAAa;GAAE,GAAG;GAAS,WAAW;EAAI,CAAC,CAC5D,CAAC;EAQD,IAAI,QAAQ,cAAc;GAOtB,MAAM,aAAa,wBAAwB,4BAA4B,OAAO,QAAQ,YAAY;GAClG,IAAI,WAAW,QAAQ,cAAc,KAAK,WAAW,MAAM;EAC/D;EAEA,IAAI,cAAc,SAAS,GAAG;GAC1B,MAAM,iBAAiB,wBAAwB,yBAAyB,aAAa;GACrF,IAAI,gBAAgB,QAAQ,MAAM,MAAM,cAAc;EAC1D;EAEA,MAAM,SAAS,MAAM;EACrB,OAAO,OAAO,OAAO,EAAE,EAAE,SAAS,CAAC;CACvC;;;;;;;;;;;;;;;;CAiBA,MAAM,UACF,gBACA,SAiBkC;EAClC,MAAM,aAAa,oBAAoB,gBAAgB,KAAK,QAAQ;EACpE,MAAM,QAAQ,sBAAsB,YAAY,KAAK,QAAQ;EAC7D,MAAM,UAAU,gBAAgB,KAAK;EAErC,MAAM,aAAa,OAAe,YAAiC;GAC/D,MAAM,SAAS,QAAQ;GACvB,IAAI,CAAC,QACD,MAAM,SAAS,WACX,kBAAkB,MAAM,OAAO,QAAQ,kBAAkB,OAAO,KAAK,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,IAAI,KAC9F,yBACJ;GAEJ,OAAO;EACX;EAEA,MAAM,YAAiC,CAAC;EAExC,KAAK,MAAM,aAAa,QAAQ,YAAY;GACxC,IAAI,UAAU,OAAO,WAAW,CAAC,UAAU,OAAO;IAC9C,UAAU,UAAU,SAAS,GAAG;IAChC;GACJ;GACA,MAAM,SAAS,UAAU,UAAU,OAAiB,GAAG,UAAU,GAAG,GAAG;GACvE,QAAQ,UAAU,IAAlB;IACI,KAAK;KAAS,UAAU,UAAU,SAAS,GAAG,SAAS,OAAO;KAAI;IAKlE,KAAK;KAAO,UAAU,UAAU,SAAS,GAAG,OAAO,OAAO;KAAa;IACvE,KAAK;KAAO,UAAU,UAAU,SAAS,GAAG,OAAO,OAAO;KAAa;IACvE,KAAK;KAAO,UAAU,UAAU,SAAS,GAAG,OAAO,OAAO;KAAI;IAC9D,KAAK;KAAO,UAAU,UAAU,SAAS,GAAG,OAAO,OAAO;KAAI;GAClE;EACJ;EAEA,MAAM,gBAAgB,QAAQ,WAAW,CAAC,EAAA,CAAG,KAAI,WAAU;GACvD;GACA,QAAQ,UAAU,OAAO,SAAS;EACtC,EAAE;EACF,KAAK,MAAM,SAAS,cAChB,UAAU,MAAM,SAAS,GAAG,GAAG,MAAM;EAGzC,IAAI,QAAQ,KAAK,GAAG,OAAO,SAAS,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,SAAS;EAE3D,MAAM,aAAoB,CAAC;EAG3B,eAAe,YAAY,YAAY,OAAO,QAAQ,WAAW;EACjE,IAAI,QAAQ,cAAc;GACtB,MAAM,mBAAmB,wBAAwB,sBAC7C,QAAQ,cAAc,WAAW,YAAY,OAAO,UACxD;GAGA,IAAI,iBAAiB,WAAW,GAAG,OAAO,CAAC;GAC3C,WAAW,KAAK,wBAAwB,wBAAwB,gBAAgB,CAAQ;EAC5F;EACA,IAAI,QAAQ,QACR,WAAW,KAAK,GAAG,KAAK,sBAAsB,QAAQ,QAAQ,OAAO,cAAc,CAAC;EAExF,IAAI,QAAQ,SAAS;GACjB,MAAM,mBAAmB,wBAAwB,uBAC7C,QAAQ,SAAS,OAAO,gBAAgB,KAAK,cAAc,gBAAgB,KAAK,CACpF;GACA,IAAI,kBAAkB,WAAW,KAAK,gBAAgB;EAC1D;EAIA,WAAW,KAAK,GAAG,MAAM,KAAK,WAC1B,gBAAgB,YAAY,OAAO,aAAa,aAAa,aAAa,OAAO,CACrF,CAAC;EACD,IAAI,WAAW,SAAS,GAAG;GACvB,MAAM,iBAAiB,wBAAwB,yBAAyB,UAAU;GAClF,IAAI,gBAAgB,QAAQ,MAAM,MAAM,cAAc;EAC1D;EAEA,IAAI,aAAa,SAAS,GAAG;GACzB,QAAQ,MAAM,QAAQ,GAAG,aAAa,KAAI,MAAK,EAAE,MAAM,CAAC;GACxD,QAAQ,MAAM,QAAQ,GAAG,KAAK,kBAAkB,QAAQ,SAAS,cAAc,QAAQ,YAAY,SAAS,CAAC;GAI7G,IAAI,QAAQ,OAAO,QAAQ,MAAM,MAAM,QAAQ,KAAK;GACpD,IAAI,QAAQ,QAAQ,QAAQ,MAAM,OAAO,QAAQ,MAAM;EAC3D;EAEA,MAAM,OAAO,MAAM;EAmBnB,MAAM,iBAAiB,wBACnB,QAAQ,YACP,YAAY,cAAc,CAAC,CAChC;EACA,OAAO,KAAK,KAAI,QAAO;GACnB,MAAM,MAA+B,EAAE,GAAG,IAAI;GAC9C,KAAK,MAAM,SAAS,gBAAgB;IAChC,IAAI,IAAI,WAAW,QAAQ,IAAI,WAAW,KAAA,GAAW;IACrD,MAAM,SAAS,OAAO,IAAI,MAAM;IAChC,IAAI,CAAC,OAAO,MAAM,MAAM,GAAG,IAAI,SAAS;GAC5C;GACA,OAAO;EACX,CAAC;CACL;;;;;;;;;;;;;;;;;;;CAoBA,kBACI,SACA,cACA,YACA,WACK;EACL,MAAM,OAA2B,CAAC;EAClC,MAAM,wBAAQ,IAAI,IAAY;EAC9B,KAAK,MAAM,CAAC,OAAO,WAAW,UAAU,WAAW,CAAC,GAAG;GACnD,MAAM,QAAQ,aAAa,MAAK,MAAK,EAAE,UAAU,KAAK;GACtD,MAAM,YAAY,WAAW,MAAK,MAAK,EAAE,UAAU,KAAK;GACxD,MAAM,SAAS,OAAO,WAAW,YAAY,UAAU,UAAU,SAAS,KAAA;GAC1E,IAAI,CAAC,QAAQ;IACT,MAAM,WAAW,CAAC,GAAG,aAAa,KAAI,MAAK,EAAE,KAAK,GAAG,GAAG,WAAW,KAAI,MAAK,EAAE,KAAK,CAAC;IACpF,MAAM,SAAS,WACX,qCAAqC,MAAM,kCACxC,SAAS,KAAI,SAAQ,IAAI,KAAK,EAAE,CAAC,CAAC,KAAK,IAAI,EAAE,IAChD,kBACJ;GACJ;GACA,KAAK,KAAK;IAAE;IAAO;IAAW;IAAO;GAAO,CAAC;GAC7C,MAAM,IAAI,KAAK;EACnB;EACA,KAAK,MAAM,SAAS,cAChB,IAAI,CAAC,MAAM,IAAI,MAAM,KAAK,GAAG,KAAK,KAAK;GAAE,OAAO,MAAM;GAAO,WAAW;GAAO,QAAQ,MAAM;EAAO,CAAC;EAEzG,OAAO,KAAK,sBAAsB,MAAM,CAAC,CAAC;CAC9C;;;;;;;;;;;;CAaA,MAAM,iBACF,gBACA,WACA,OACA,iBACA,aACgB;EAChB,IAAI,UAAU,KAAA,KAAa,UAAU,MAAM,OAAO;EAElD,MAAM,aAAa,oBAAoB,gBAAgB,KAAK,QAAQ;EACpE,MAAM,QAAQ,sBAAsB,YAAY,KAAK,QAAQ;EAC7D,MAAM,cAAc,mBAAmB,YAAY,KAAK,QAAQ;EAChE,MAAM,QAAQ,MAAM;EAEpB,IAAI,CAAC,OAAO,OAAO;EAInB,MAAM,aAAa,mBAAmB,kBAAkB,iBAAiB,OAAO,WAAW,IACrF,qBAAqB,OAAO,aAAa,iBAAiB,cAAc,IACxE,KAAA;EACN,MAAM,aAAa,wBAAwB,0BAA0B,OAAO,OAAO,UAAU;EAE7F,MAAM,SAAS,MAAM,KAAK,GACrB,OAAO,EAAE,OAAO,MAAM,EAAE,CAAC,CAAC,CAC1B,KAAK,KAAK,CAAC,CACX,MAAM,IAAI,GAAG,UAAU,CAAC;EAG7B,OADoB,OAAO,OAAO,EAAE,EAAE,SAAS,CACxC,MAAgB;CAC3B;;;;CAKA,qBAAsC;EAClC,OAAO,KAAK;CAChB;;;;;;;;;;;;;;;;;;;;;CA0BA,MAAM,uBACF,gBACA,UA2BI,CAAC,GACL,SACkC;EAIlC,IAAI,aAAa,cAAc,GAAG;GAC9B,MAAM,MAAM,kBAAkB,gBAAgB,KAAK,QAAQ;GAC3D,IAAI,KACA,OAAO,KAAK,uBACR,IAAI,iBAAiB,MAAM;IAAE,GAAG;IACpD,WAAW;GAAI,GAAG,OACF;EAER;EAIA,MAAM,aAAa,oBAAoB,gBAAgB,KAAK,QAAQ;EAEpE,MAAM,oBAAoB,iBAAiB,OAAO;EAKlD,MAAM,QAAQ,MAAM,KAAK,2BAA8B,gBAAgB,OAAO,EAAA,CACzE,KAAI,QAAO,UAAU,KAAK,YAAY,KAAK,QAAQ,CAAC;EAEzD,IAAI,mBACA,MAAM,KAAK,aAAa,MAAM,YAAY,gBAAgB,iBAAiB;EAG/E,OAAO;CACX;;;;;;;;;;;;CAaA,MAAM,gBACF,gBACA,IACA,SACA,YACA,SAKuC;EACvC,IAAI,CAAC,MAAM,KAAK,mBAAmB,gBAAgB,EAAE,GAAG,OAAO;EAE/D,MAAM,aAAa,oBAAoB,gBAAgB,KAAK,QAAQ;EACpE,MAAM,QAAQ,sBAAsB,YAAY,KAAK,QAAQ;EAC7D,MAAM,cAAc,mBAAmB,YAAY,KAAK,QAAQ;EAGhE,IAAI,CAAC,kBAAkB,IAAI,OAAO,WAAW,GAAG,OAAO;EAEvD,MAAM,YAAY,qBAAqB,OAAO,aAAa,IAAI,cAAc;EAI7E,MAAM,cAAc,SAAS;EAC7B,MAAM,aAAa,KAAK,iBAAiB,OAAO,YAAY,SAAS,QAAQ,WAAW;EAKxF,MAAM,YAAY,MAAM,KAAK,oBACzB,gBAAgB,YAAY,OAAO,OACnC,aAAa,aAAa,EAAE,QAAQ,SAAS,OAAO,CAAC,CACzD;EACA,MAAM,WAAW,YACX,IAAI,WAAW,SAAS,IACxB;EACN,MAAM,SAAS,MAAM,KAAK,GACrB,OAAO,UAAmB,CAAC,CAC3B,KAAK,KAAK,CAAC,CACX,MAAM,cAAc,UAAU,YAAY,OAAO,WAAW,CAAC,CAAC,CAC9D,MAAM,CAAC;EAEZ,IAAI,OAAO,WAAW,GAAG,OAAO;EAEhC,MAAM,MAAM,UAAU,OAAO,IAA+B,YAAY,KAAK,QAAQ;EAErF,MAAM,oBAAoB,iBAAiB,OAAO;EAClD,IAAI,mBACA,MAAM,KAAK,aAAa,CAAC,GAAG,GAAG,YAAY,gBAAgB,iBAAiB;EAGhF,OAAO;CACX;;;;CAKA,MAAc,2BACV,gBACA,UA8BI,CAAC,GAC6B;EAClC,MAAM,aAAa,oBAAoB,gBAAgB,KAAK,QAAQ;EACpE,MAAM,QAAQ,sBAAsB,YAAY,KAAK,QAAQ;EAC7D,MAAM,cAAc,mBAAmB,YAAY,KAAK,QAAQ;EAMhE,IAAI,QAAQ,gBAAgB,QAAQ,YAChC,MAAM,SAAS,WACX,uNAGA,2BACJ;EAGJ,IAAI;EACJ,IAAI,QAAQ,cACR,aAAa,wBAAwB,4BAA4B,OAAO,QAAQ,YAAY;EAOhG,MAAM,UAAU,KAAK,iBAAiB,OAAO,YAAY,QAAQ,QAAQ,aAAa,QAAQ,aAAa,IAAI;EAM/G,MAAM,aAAa,QAAQ,eACrB,wBAAwB,0BAA0B,QAAQ,cAAc,OAAO,UAAU,IACzF,KAAA;EAGN,MAAM,gBAAgB,QAAQ,gBAAgB,QAAQ,gBAChD,wBAAwB,6BAA6B,QAAQ,cAAc,OAAO,UAAU,IAC5F,KAAA;EASN,MAAM,gBAAgB,QAAQ,aAAa;EAC3C,IAAI,kBAAkB,cAAc,aAChC,MAAM,SAAS,WACX,gMAGA,yBACJ;EAEJ,MAAM,mBAAmB;GACrB,MAAM,UAAU,gBAAgB,KAAK,GAAG,eAAe,KAAK,KAAK,EAAE,IAAI,KAAK,GAAG,OAAO,KAAK,KAAK,EAAE;GAClG,OAAO,UACD,QAAQ,OAAgB,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,SAAS,IAC/C,QAAQ,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,SAAS;EACzC;EAEA,IAAI,QAAQ,aACN,KAAK,GAAG,OAAO;GAAE,WAAY,WAAW;GACtD,WAAW,WAAW;EAAe,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,SAAS,IAC/C,aACI,KAAK,GAAG,OAAO;GACb,WAAY,WAAW;GACvB,QAAQ;GACR,GAAI,gBAAgB,EAAE,UAAU,cAAc,IAAI,CAAC;EACvD,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,SAAS,IACtB,WAAW;EACrB,MAAM,gBAAuB,CAAC;EAE9B,IAAI,QAAQ,WAAW,cAAc,KAAK,KAAK,mBAAmB,QAAQ,SAAS,CAAC;EAKpF,eAAe,eAAe,YAAY,OAAO,QAAQ,WAAW;EAEpE,IAAI,QAAQ,cAAc;GACtB,MAAM,mBAAmB,wBAAwB,sBAC7C,QAAQ,cAAc,WAAW,YAAY,OAAO,UACxD;GACA,IAAI,iBAAiB,WAAW,GAAG,OAAO,CAAC;GAC3C,cAAc,KAAK,wBAAwB,wBAAwB,gBAAgB,CAAE;EACzF;EAEA,IAAI,QAAQ,QAAQ;GAChB,MAAM,mBAAmB,KAAK,sBAAsB,QAAQ,QAAQ,OAAO,cAAc;GACzF,IAAI,iBAAiB,SAAS,GAAG,cAAc,KAAK,GAAG,gBAAgB;EAC3E;EAEA,IAAI,QAAQ,SAAS;GACjB,MAAM,mBAAmB,wBAAwB,uBAAuB,QAAQ,SAAS,OAAO,gBAAgB,KAAK,cAAc,gBAAgB,KAAK,CAAC;GACzJ,IAAI,kBAAkB,cAAc,KAAK,gBAAgB;EAC7D;EAKA,cAAc,KAAK,GAAG,MAAM,KAAK,WAC7B,gBAAgB,YAAY,OAAO,QAAQ,aAAa,aAAa,OAAO,CAChF,CAAC;EAED,IAAI,YAAY,QACZ,cAAc,KAAK,WAAW,MAAM;EAGxC,IAAI,cAAc,SAAS,GAAG;GAC1B,MAAM,iBAAiB,wBAAwB,yBAAyB,aAAa;GACrF,IAAI,gBAAgB,QAAQ,MAAM,MAAM,cAAc;EAC1D;EAEA,MAAM,WAAW,uBAAuB,QAAQ,SAAS,QAAQ,KAAK,KAAK,CAAC;EAO5E,IAAI,iBAAiB,SAAS;GAC1B,MAAM,WAAW,IAAI,IAAI,OAAO,KAAK,OAAO,CAAC;GAC7C,MAAM,UAAU,SAAS,KAAK,CAAC,WAAW,KAAK,CAAC,CAAC,QAAO,UAAS,CAAC,SAAS,IAAI,KAAK,CAAC;GACrF,IAAI,QAAQ,SAAS,GACjB,MAAM,SAAS,WACX,+BAA+B,QAAQ,KAAI,MAAK,IAAI,EAAE,EAAE,CAAC,CAAC,KAAK,IAAI,EAAE,mHAE7C,QAAQ,KAAI,MAAK,IAAI,EAAE,EAAE,CAAC,CAAC,KAAK,IAAI,EAAE,iDAE9D,kCACA,EAAE,QAAQ,QAAQ,CACtB;EAER;EAGA,MAAM,aAAa,aAAa,WAAW,OAAO,aAAa,cAAc;EAC7E,MAAM,mBAAmB,aACnB,CAAC,IAAI,WAAW,OAAO,GAAG,GAAG,WAAW,KAAI,WAAU,KAAK,MAAM,CAAC,CAAC,IACnE,KAAK,sBAKH,KAAK,iBAAiB,OAAO,UAAU,YAAY,QAAQ,cAAc,cAAc,GACvF,gBAAgB,CAAC,IAAI,UACzB;EACJ,IAAI,iBAAiB,SAAS,GAAG,QAAQ,MAAM,QAAQ,GAAG,gBAAgB;EAE1E,IAAI,QAAQ,YAAY;GAMpB,MAAM,mBAAmB,KAAK,sBAAsB,OAAO,aAAa,SAAS,cAAc;GAC/F,IAAI,iBAAiB,SAAS,GAAG;IAC7B,cAAc,KAAK,GAAG,gBAAgB;IACtC,MAAM,iBAAiB,wBAAwB,yBAAyB,aAAa;IACrF,IAAI,gBAAgB,QAAQ,MAAM,MAAM,cAAc;GAC1D;EACJ;EAEA,MAAM,aAAa,QAAQ,eACpB,QAAQ,SAAS,KAClB,QAAQ,eAAgB,QAAQ,SAAS,KAAM,QAAQ;EAC7D,IAAI,YAAY,QAAQ,MAAM,MAAM,UAAU;EAG9C,IAAI,QAAQ,UAAU,QAAQ,SAAS,GAAG,QAAQ,MAAM,OAAO,QAAQ,MAAM;EAE7E,MAAM,aAAa,MAAM;EAEzB,IAAI,YACA,OAAQ,WAA4E,KAAI,OAAM;GAC1F,GAAG,EAAE;GACL,WAAW,OAAO,EAAE,cAAc,WAAW,EAAE,YAAY,WAAW,OAAO,EAAE,SAAS,CAAC;EAC7F,EAAE;EAGN,IAAI,YACA,OAAQ,WAA6F,KAAI,OAAM;GAC3G,GAAG,EAAE;GACL,QAAQ,OAAO,EAAE,WAAW,WAAW,EAAE,SAAS,WAAW,OAAO,EAAE,MAAM,CAAC;GAC7E,GAAI,gBAAgB,EAAE,UAAU,EAAE,YAAY,CAAC,EAAE,IAAI,CAAC;EAC1D,EAAE;EAGN,OAAO;CACX;AACJ;;;;;;;;;;;ACpwEA,SAAS,qBACL,OACA,cACA,gBACqB;CACrB,IAAI,CAAC,MAAM,QAAQ,KAAK,GAAG,OAAO,CAAC;CAEnC,OAAO,MAAM,KAAK,SAAS,UAAU;EACjC,IAAI,OAAO,YAAY,YAAY,OAAO,YAAY,UAAU,OAAO,EAAE,IAAI,QAAQ;EACrF,IAAI,WAAW,OAAO,YAAY,UAAU;GACxC,MAAM,KAAM,QAA6B;GACzC,MAAM,QAAS,QAAoC;GACnD,IAAI,OAAO,OAAO,YAAY,OAAO,OAAO,UACxC,OAAO;IACH;IACA,OAAO,SAAS,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK,IAC3D,QACA,KAAA;GACV;EAER;EACA,MAAM,IAAI,MACN,0BAA0B,aAAa,QAAQ,eAAe,aAAa,MAAM,iGAE9E,YAAY,OAAO,SAAS,OAAO,QAAQ,EAClD;CACJ,CAAC;AACL;;;;;;;;;AAUA,SAAgB,eACZ,OACA,SACA,OACuB;CACvB,MAAM,WAAW,OAAO,KAAK,OAAO;CACpC,IAAI,SAAS,WAAW,GACpB,MAAM,SAAS,WACX,aAAa,MAAM,sEACd,mBAAmB,8CACxB,6BACA,EAAE,UAAU,MAAM,CACtB;CAGJ,MAAM,SAAkC,CAAC;CACzC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,KAAK,GAAG;EAC9C,MAAM,WAAW,QAAQ;EACzB,IAAI,CAAC,UACD,MAAM,SAAS,WACX,iCAAiC,MAAM,mBAAmB,IAAI,qCAC3B,SAAS,KAAK,CAAC,CAAC,KAAI,MAAK,IAAI,EAAE,EAAE,CAAC,CAAC,KAAK,IAAI,EAAE,IACjF,6BACA;GAAE,UAAU;GAAO,OAAO;GAAK,aAAa,SAAS,KAAK;EAAE,CAChE;EAIJ,IAAI,UAAU,KAAA,GAAW;EACzB,MAAM,aAAa,0BAA0B,OAAO,UAAU,GAAG;EACjE,IAAI,eAAe,KAAA,GAAW,OAAO,OAAO;CAChD;CACA,OAAO;AACX;;;;;;;;;;;;;;;AAgBA,IAAa,uBAAb,MAAkC;CAIlB;CACA;CAOA;CAXZ;CAEA,YACI,IACA,UAOA,aACF;EATU,KAAA,KAAA;EACA,KAAA,WAAA;EAOA,KAAA,cAAA;EAER,KAAK,QAAQ,IAAI,gBAAgB,IAAI,UAAU,WAAW;CAC9D;;;;;;;;;;CAYA,MAAM,oBACF,IACA,KACA,UACa;EACb,IAAI,CAAC,aAAa,IAAI,QAAQ,GAC1B,MAAM,IAAI,MAAM,aAAa,IAAI,YAAY,0CAA0C;EAS3F,MAAM,mBACF,IAPY,oBACZ,KAAK,UACL,IAAI,SAAS,SACb,GAAG,IAAI,iBAAiB,KAAK,GAAG,IAAI,aAKpC,GACA,KAAK,SAAS,IAAI,kBAAkB,IAAI,QAAQ,GAChD,KAAK,SAAS,IAAI,kBAAkB,QAAQ,GAC5C;GAAE,QAAQ,IAAI,iBAAiB;GAAM,UAAU,IAAI;EAAY,CACnE;CACJ;;;;;;;;;CAWA,MAAM,oBACF,IACA,KACA,UACA,OACa;EACb,IAAI,CAAC,aAAa,IAAI,QAAQ,GAC1B,MAAM,SAAS,WACX,aAAa,IAAI,YAAY,QAAQ,IAAI,iBAAiB,KAAK,SAC5D,IAAI,SAAS,KAAK,yCAAyC,mBAAmB,8DAEjF,6BACA;GAAE,UAAU,IAAI;GAAa,YAAY,IAAI,iBAAiB;EAAK,CACvE;EAGJ,MAAM,QAAQ,GAAG,IAAI,iBAAiB,KAAK,GAAG,IAAI;EAClD,MAAM,UAAU,oBAAoB,KAAK,UAAU,IAAI,SAAS,SAAS,KAAK;EAC9E,MAAM,SAAS,eAAe,OAAO,IAAI,SAAS,QAAQ,YAAY,KAAK;EAE3E,IAAI,OAAO,KAAK,MAAM,CAAC,CAAC,WAAW,GAC/B,MAAM,SAAS,WACX,KAAK,mBAAmB,yCAAyC,MAAM,KACvE,yBACA;GAAE,UAAU,IAAI;GAAa,YAAY,IAAI,iBAAiB;EAAK,CACvE;EAGJ,MAAM,oBACF,IACA,SACA,KAAK,SAAS,IAAI,kBAAkB,IAAI,QAAQ,GAChD,KAAK,SAAS,IAAI,kBAAkB,QAAQ,GAC5C,QACA;GAAE,QAAQ,IAAI,iBAAiB;GAAM,UAAU,IAAI;EAAY,CACnE;CACJ;;;;;;CAQA,MAAc,eACV,kBACA,UACA,UACmC;EACnC,MAAM,YAAY,MAAM,KAAK,MAAM,uBAAuB,kBAAkB,UAAU,QAAQ;EAC9F,IAAI,CAAC,WAAW,OAAO,KAAA;EACvB,MAAM,mBAAmB,SAAS,OAAO;EACzC,MAAM,QAAQ,sBAAsB,kBAAkB,KAAK,QAAQ;EACnE,MAAM,CAAC,UAAU,mBAAmB,kBAAkB,KAAK,QAAQ;EAEnE,OAAO;GAAE;GAAO,UADC,MAAM,OAAO;GACJ;EAAU;CACxC;;CAGA,SAAiB,YAA8B,IAA8B;EACzE,MAAM,MAAM,mBAAmB,YAAY,KAAK,QAAQ;EACxD,OAAO,gBAAc,IAAI,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;CACzC;;CAIA,UAAkB,YAA8B,KAAwC;EACpF,IAAI,IAAI,WAAW,GAAG,OAAO,CAAC;EAC9B,MAAM,MAAM,mBAAmB,YAAY,KAAK,QAAQ;EACxD,OAAO,IAAI,KAAI,OAAM,gBAAc,IAAI,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,UAAU;CACjE;;;;;;;;;;;;;CAcA,YACI,YACA,UACA,SACA,OACmB;EACnB,IAAI,SAAS,WAAW,GAAG,OAAO,CAAC;EACnC,MAAM,MAAM,mBAAmB,YAAY,KAAK,QAAQ;EACxD,OAAO,SAAS,KAAI,aAAY;GAC5B,IAAI,gBAAc,QAAQ,IAAI,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;GAC1C,OAAO,QAAQ,QAAQ,eAAe,QAAQ,OAAO,SAAS,KAAK,IAAI,KAAA;EAC3E,EAAE;CACN;;;;CAMA,MAAM,0BACF,IACA,YACA,IACA,gBACF;EACE,MAAM,oBAAoB,2BAA2B,UAAU;EAE/D,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,cAAc,GAAG;GACvD,MAAM,WAAW,aAAa,mBAAmB,GAAG;GACpD,IAAI,CAAC,YAAY,SAAS,gBAAgB,QAAQ;GAElD,MAAM,WAAW,qBAAqB,OAAO,KAAK,WAAW,IAAI;GACjE,MAAM,kBAAkB,SAAS,KAAI,YAAW,QAAQ,EAAE;GAC1D,MAAM,mBAAmB,SAAS,OAAO;GAEzC,MAAM,QAAQ,GAAG,WAAW,KAAK,GAAG;GAEpC,IAAI,SAAS,SAAS,OAClB,MAAM,wBACF,IACA,qBACI,KAAK,UACL,SAAS,UACT,eAAa,UAAU,GACvB,eAAa,gBAAgB,GAC7B,KACJ,GACA,KAAK,SAAS,YAAY,EAAE,GAG5B,KAAK,YAAY,kBAAkB,UAAU,CAAC,GAAG,KAAK,GACtD,MAAM,KAAK,eAAe,YAAY,UAAU,EAAE,CACtD;QACG,IAAI,SAAS,SAAS,cACzB,MAAM,wBACF,IACA,oBAAoB,KAAK,UAAU,SAAS,SAAS,KAAK,GAC1D,KAAK,SAAS,YAAY,EAAE,GAC5B,KAAK,YAAY,kBAAkB,UAAU,SAAS,QAAQ,YAAY,KAAK,GAC/E,MAAM,KAAK,eAAe,YAAY,UAAU,EAAE,CACtD;QACG,IAAI,SAAS,gBAAgB,UAAU,sBAAsB,QAAQ,GAAG;IAE3E,MAAM,cAAc,sBAAsB,kBAAkB,KAAK,QAAQ;IACzE,MAAM,YAAY,mBAAmB,kBAAkB,KAAK,QAAQ;IACpE,MAAM,eAAe,UAAU;IAC/B,MAAM,cAAc,YAAY,aAAa;IAE7C,MAAM,UAAU,kBAAkB,kBAAkB,SAAS,kBAAkB;IAC/E,MAAM,QAAQ,YAAY;IAE1B,IAAI,CAAC,SAAS,CAAC,aACX,MAAM,sBACF,OACA,qBAAqB,eAAa,gBAAgB,EAAE,WACjD,QAAQ,IAAI,aAAa,UAAU,gBAAgB,IAAI,SAAS,mBAAmB,uBAC1F;IAMJ,MAAM,kBAAkB,MAAM,KAAK,MAAM,kBAAkB,YAAY,UAAU,CAAC,EAAE,GAAG,EAAE,EAAA,CACpF,cAAc,IAAI,OAAO,EAAE,CAAC;IAEjC,IAAI,mBAAmB,KAAA,GACnB,MAAM,IAAI,MACN,0BAA0B,IAAI,QAAQ,WAAW,KAAK,UAAU,GAAG,kCAClD,eAAe,UAAU,YAAY,KAAK,QAAQ,EAAE,2DAEzE;IAQJ,MAAM,UAAU,MAAM,KAAK,MAAM,uBAAuB,YAAY,UAAU,EAAE;IAGhF,IAAI,gBAAgB,SAAS,GAAG;KAC5B,MAAM,kBAAkB,gBAAgB,KAAI,OAAM,gBAAc,IAAI,SAAS,CAAC,CAAC,aAAa,UAAU;KACtG,MAAM,GACD,OAAO,WAAW,CAAC,CACnB,IAAI,GAAG,UAAU,KAAK,CAAC,CAAC,CAQxB,MAAM,IAAI,GAAG,OAAO,cAAc,GAAG,WAAW,aAA4B,eAA4B,GAAG,OAAO,CAAC;KAGxH,MAAM,GACD,OAAO,WAAW,CAAC,CACnB,IAAI,GAAG,UAAU,eAAe,CAAC,CAAC,CAClC,MAAM,QAAQ,aAA4B,eAA4B,CAAC;IAChF,OAEI,MAAM,GACD,OAAO,WAAW,CAAC,CACnB,IAAI,GAAG,UAAU,KAAK,CAAC,CAAC,CACxB,MAAM,IAAI,GAAG,OAAO,cAAc,GAAG,OAAO,CAAC;GAE1D,OAMI,MAAM,sBACF,OACA,8HAEJ;EAER;CACJ;;;;CAMA,MAAM,uBACF,IACA,kBACA,gBACA,wBAKF;EACE,KAAK,MAAM,UAAU,wBAAwB;GACzC,MAAM,EAAE,UAAU,aAAa;GAE/B,IAAI;IACA,MAAM,mBAAmB,SAAS,OAAO;IACzC,MAAM,cAAc,sBAAsB,kBAAkB,KAAK,QAAQ;IACzE,MAAM,YAAY,mBAAmB,kBAAkB,KAAK,QAAQ;IACpE,MAAM,eAAe,UAAU;IAEV,mBADgB,kBAAkB,KAAK,QACvC,CAAA,CAAU;IAE/B,IAAI,SAAS,SAAS,OAAO;KACzB,MAAM,KAAK,8BACP,IACA,kBACA,gBACA,kBACA,UACA,QACJ;KACA;IACJ;IAOA,IAAI,aAAa,QAAQ,GAAG;KACxB,MAAM,KAAK,gCACP,IACA,kBACA,gBACA,kBACA,UACA,UACA;MACI,OAAO,SAAS,QAAQ;MACxB,cAAc,SAAS,QAAQ;MAC/B,cAAc,SAAS,QAAQ;MAC/B,YAAY,SAAS,QAAQ;KACjC,CACJ;KACA;IACJ;IAGA,MAAM,QAAQ,GAAG,iBAAiB,KAAK,GAAG,SAAS;IACnD,IAAI,CAAC,sBAAsB,QAAQ,GAC/B,MAAM,sBACF,OACA,MAAM,SAAS,KAAK,gEACxB;IAIJ,MAAM,UAAU,kBAAkB,kBAAkB,SAAS,kBAAmB;IAChF,MAAM,mBAAmB,YAAY;IACrC,IAAI,CAAC,kBACD,MAAM,sBACF,OACA,IAAI,SAAS,mBAAmB,yCAC5B,eAAa,gBAAgB,EAAE,EACvC;IAKJ,MAAM,kBAAkB,MAAM,KAAK,MAAM,kBAAkB,kBAAkB,UAAU,CAAC,cAAc,GAAG,EAAE,EAAA,CACtG,cAAc,IAAI,OAAO,cAAc,CAAC;IAE7C,IAAI,mBAAmB,KAAA,GACnB,MAAM,IAAI,MACN,0BAA0B,SAAS,aAAa,QAAQ,iBAAiB,KAAK,UAC1E,eAAe,kCACf,eAAe,UAAU,kBAAkB,KAAK,QAAQ,EAAE,0DAElE;IAMJ,MAAM,UAAU,MAAM,KAAK,MAAM,uBAAuB,kBAAkB,UAAU,cAAc;IAElG,IAAI,aAAa,QAAQ,aAAa,KAAA,GAClC,MAAM,GACD,OAAO,WAAW,CAAC,CACnB,IAAI,GAAG,UAAU,KAAK,CAAC,CAAC,CACxB,MAAM,IAAI,GAAG,kBAAkB,cAAc,GAAG,OAAO,CAAC;SAC1D;KAEH,MAAM,oBADuB,gBAAc,UAA6B,SAC9C,CAAA,CAAqB,aAAa;KAC5D,MAAM,gBAAgB,YAAY,aAAa;KAG/C,MAAM,GACD,OAAO,WAAW,CAAC,CACnB,IAAI,GAAG,UAAU,KAAK,CAAC,CAAC,CACxB,MAAM,IAAI,GAAG,kBAAkB,cAAc,GAAG,OAAO,CAAC;KAG7D,MAAM,GACD,OAAO,WAAW,CAAC,CACnB,IAAI,GAAG,UAAU,eAAe,CAAC,CAAC,CAClC,MAAM,GAAG,eAAe,iBAAiB,CAAC;IACnD;GACJ,SAAS,GAAG;IAWR,IAAI,aAAa,YAAa,GAAa,SAAS,cAAc,gBAAgB,CAAC,GAAG,MAAM;IAC5F,OAAO,KAAK,sCAAsC,SAAS,aAAa,IAAI,EAAE,OAAO,EAAE,CAAC;GAC5F;EACJ;CACJ;;;;CAMA,MAAc,8BACV,IACA,kBACA,gBACA,kBACA,UACA,UACF;EACE,MAAM,kBAAkB,eAAa,gBAAgB;EACrD,MAAM,kBAAkB,eAAa,gBAAgB;EAUrD,IAJ2B,SAAS,SAC/B,KAAI,SAAQ,KAAK,KAAK,CAAC,CACvB,QAAO,UAAS,UAAU,mBAAmB,UAAU,eAExD,CAAA,CAAmB,WAAW,KAAK,SAAS,gBAAgB,QAAQ;EAExE,IAAI;GACA,MAAM,UAAU,qBACZ,KAAK,UACL,SAAS,UACT,iBACA,iBACA,GAAG,iBAAiB,KAAK,GAAG,SAAS,cACzC;GAIA,MAAM,WAAW,MAAM,QAAQ,QAAQ,IACjC,qBAAqB,UAAU,SAAS,cAAc,iBAAiB,IAAI,IAC3E,aAAa,QAAQ,aAAa,KAAA,IAC9B,CAAC,IACD,qBAAqB,CAAC,QAAQ,GAAG,SAAS,cAAc,iBAAiB,IAAI;GAEvF,MAAM,wBACF,IACA,SACA,KAAK,SAAS,kBAAkB,cAAc,GAC9C,KAAK,YAAY,kBAAkB,UAAU,CAAC,GAAG,GAAG,iBAAiB,KAAK,GAAG,SAAS,cAAc,GACpG,MAAM,KAAK,eAAe,kBAAkB,UAAU,cAAc,CACxE;EACJ,SAAS,OAAO;GACZ,OAAO,MAAM,+CAA+C,SAAS,aAAa,IAAI,EAAS,MAAM,CAAC;GACtG,MAAM;EACV;CACJ;;;;CAMA,MAAc,gCACV,IACA,kBACA,gBACA,kBACA,UACA,UACA,cACF;EACE,MAAM,QAAQ,GAAG,iBAAiB,KAAK,GAAG,SAAS;EACnD,IAAI;GACA,MAAM,WAAW,MAAM,QAAQ,QAAQ,IACjC,qBAAqB,UAAU,SAAS,cAAc,iBAAiB,IAAI,IAC3E,CAAC;GAEP,MAAM,wBACF,IACA,oBAAoB,KAAK,UAAU,cAAc,KAAK,GACtD,KAAK,SAAS,kBAAkB,cAAc,GAC9C,KAAK,YAAY,kBAAkB,UAAU,aAAa,YAAY,KAAK,GAC3E,MAAM,KAAK,eAAe,kBAAkB,UAAU,cAAc,CACxE;EACJ,SAAS,OAAO;GACZ,OAAO,MAAM,mDAAmD,SAAS,aAAa,IAAI,EAAS,MAAM,CAAC;GAC1G,MAAM;EACV;CACJ;;;;CAMA,MAAM,gCACF,IACA,kBACA,UACA,SAOF;EACE,KAAK,MAAM,OAAO,SAAS;GACvB,MAAM,EAAE,UAAU,gBAAgB;GAClC,MAAM,mBAAmB,SAAS,OAAO;GACzC,MAAM,cAAc,sBAAsB,kBAAkB,KAAK,QAAQ;GACzE,MAAM,YAAY,mBAAmB,kBAAkB,KAAK,QAAQ;GACpE,MAAM,eAAe,UAAU;GAC/B,MAAM,cAAc,YAAY,aAAa;GAS7C,MAAM,EAAE,iBAAiB,gBAAgB,qBAAqB,uBAC1D,KAAK,4BAA4B,kBAAkB,QAAQ;GAC/D,MAAM,kBAAkB,kBAAkB,kBAAkB,cAAc;GAC1E,MAAM,sBAAsB,kBAAkB,kBAAkB,kBAAkB;GAClF,MAAM,cAAc,sBAAsB,kBAAkB,KAAK,QAAQ;GACzE,MAAM,YAAY,mBAAmB,kBAAkB,KAAK,QAAQ;GACpE,MAAM,eAAe,UAAU;GAE/B,MAAM,iBADoB,gBAAc,UAAU,SAC3B,CAAA,CAAkB,aAAa;GAEtD,MAAM,cAAc,YAAY,aAAa;GAC7C,MAAM,kBAAkB,YAAY;GACpC,MAAM,cAAc,YAAY;GAEhC,MAAM,QAAQ,GAAG,iBAAiB,KAAK,GAAG,SAAS;GACnD,IAAI,CAAC,iBACD,MAAM,sBACF,OACA,uBAAuB,oBAAoB,+BACvC,eAAa,gBAAgB,EAAE,EACvC;GAEJ,IAAI,CAAC,aACD,MAAM,sBACF,OACA,wBAAwB,gBAAgB,+BACpC,eAAa,gBAAgB,EAAE,EACvC;GAIJ,MAAM,aAAa,MAAM,GACpB,OAAO,EAAE,KAAK,gBAAgB,CAAC,CAAC,CAChC,KAAK,WAAW,CAAC,CACjB,MAAM,GAAG,aAAa,cAAc,CAAC,CAAC,CACtC,MAAM,CAAC;GACZ,IAAI,WAAW,WAAW,GAAG;GAC7B,MAAM,gBAAgB,WAAW,EAAE,CAAC;GAEpC,IAAI,gBAAgB,QAAQ,gBAAgB,KAAA,GAAW;IAEnD,IAAI,kBAAkB,QAAQ,kBAAkB,KAAA,GAC5C,MAAM,GAAG,OAAO,WAAW,CAAC,CACvB,IAAI,GAAG,kBAAkB,KAAK,CAAC,CAAC,CAChC,MAAM,GAAG,aAAa,OAAO,aAAa,CAAC,CAAC;IAErD;GACJ;GAIA,MAAM,iBADoB,gBAAc,aAAa,SAC9B,CAAA,CAAkB,aAAa;GAGtD,IAAI,kBAAkB,QAAQ,kBAAkB,KAAA,GAC5C,MAAM,GAAG,OAAO,WAAW,CAAC,CACvB,IAAI,GAAG,kBAAkB,KAAK,CAAC,CAAC,CAChC,MAAM,GAAG,aAAa,OAAO,aAAa,CAAC,CAAC;QAMjD,MAAM,SAAS,WACX,0BAA0B,MAAM,UAAU,SAAS,qBAC/C,oBAAoB,qGAExB,2BACJ;GAIJ,MAAM,GAAG,OAAO,WAAW,CAAC,CACvB,IAAI,GAAG,kBAAkB,cAAc,CAAC,CAAC,CACzC,MAAM,GAAG,aAAa,cAAc,CAAC;EAC9C;CACJ;;;;CAMA,4BACI,kBACA,UACwD;EACxD,IAAI,CAAC,SAAS,YAAY,SAAS,SAAS,WAAW,GACnD,MAAM,IAAI,MAAM,0DAA0D;EAE9E,MAAM,kBAAkB,eAAa,gBAAgB;EACrD,MAAM,WAAW,SAAS;EAC1B,IAAI,QAAQ,SAAS,SAAS;EAC9B,MAAM,kBAAkB,wBAAwB,0BAA0B,SAAS,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC;EACjG,IAAI,cAAc,SAAS,MAAM,CAAC,GAAG;EASrC,IAAI,SAAS;EACb,OAAO,WAAW,IAAI;GAClB,MAAM,iBAAiB,wBAAwB,yBAAyB,WAAW,CAAC,CAAC;GAErF,KADyB,mBAAmB,UAAU,IAAI,kBAAkB,SAAS,QAAQ,EAAE,CAAC,YACvE,iBACrB;GAEJ,MAAM,YAAY,iBACZ,SAAS,WAAW,MAAM;IAExB,QADW,MAAM,QAAQ,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,GAAG,KAAK,EAAE,GAAG,QACxC;GAClB,CAAC,IACC,QAAQ;GACd,IAAI,YAAY,GACZ,MAAM,IAAI,MAAM,iEAAiE,SAAS,aAAa,EAAE;GAE7G,QAAQ;GACR,cAAc,SAAS,MAAM,CAAC,GAAG;EACrC;EAEA,OAAO;GAAE;GACjB,qBAFoC,wBAAwB,0BAA0B,WAAW,CAAC,CAAC;EAE/E;CAChB;;;;CAMA,MAAM,4BACF,IACA,aACA,mBASF;EACE,MAAM,EAAE,kBAAkB,UAAU,UAAU,gBAAgB;EAC9D,MAAM,mBAAmB,SAAS,OAAO;EAEzC,IAAI;GACA,MAAM,UAAU,oBACZ,KAAK,UACL,SAAS,SACT,GAAG,iBAAiB,KAAK,GAAG,aAChC;GAEA,MAAM,oBAAoB,KAAK,SAAS,kBAAkB,WAAW;GAGrE,MAAM,eAAe;KAChB,QAAQ,aAAa,OAAO;KAC5B,QAAQ,aAAa,OAAO;GACjC;GAMA,MAAM,GAAG,OAAO,QAAQ,KAAK,CAAC,CAAC,OAAO,YAAY,CAAC,CAAC,oBAAoB;GAExE,OAAO,KAAK,WAAW,YAAY,IAAI,kBAAkB,MAAM,UAAU;EAC7E,SAAS,OAAO;GACZ,OAAO,MAAM,uDAAuD,YAAY,IAAI,EAAS,MAAM,CAAC;GACpG,MAAM;EACV;CACJ;AACJ;;;;;;;;;;;;;;;;;;;;;;;;;;;ACl0BA,SAAS,UAAU,QAA6B;CAC5C,IAAI;EACA,OAAO,OAAO,OAAO,WAAW,CAAC,CAAC,CAAC,YAAY;CACnD,QAAQ;EACJ,OAAO;CACX;AACJ;AAEA,SAAS,cAAc,MAAuB;CAC1C,OAAO,KAAK,SAAS,IAAI,KAAK,KAAK,SAAS,QAAQ;AACxD;;AAGA,SAAS,YAAY,MAAuB;CACxC,OAAO,SAAS;AACpB;AAEA,SAAS,OAAO,SAA6B;CACzC,OAAO,MAAM,QAAQ,OAAO,IAAI,UAAU,CAAC,OAAO;AACtD;;;;;;;AAQA,SAAgB,eACZ,OACA,OACA,IACA,SACG;CACH,MAAM,SAAS,MAAM;CACrB,IAAI,CAAC,QACD,MAAM,SAAS,WACX,IAAI,MAAM,wBAAwB,QAAQ,eAAe,QAAQ,GAAG,SAAS,4BAC7E,yBACJ;CAEJ,MAAM,OAAO,UAAU,MAAM;CAE7B,QAAQ,GAAG,UAAX;EACI,KAAK,QAID,OAAO,GAAG,YAAY,OAAO,SAAS,GAAG;EAG7C,KAAK,SAAS;GACV,MAAM,SAAS,OAAO,GAAG,OAAO;GAChC,IAAI,cAAc,IAAI,GAAG;IAKrB,IAAI,aAAkB,GAAG,GAAG;IAC5B,KAAK,MAAM,SAAS,QAChB,aAAa,GAAG,gBAAgB,WAAW,IAAI,MAAM;IAEzD,OAAO;GACX;GACA,IAAI,KAAK,WAAW,MAAM,GAAG;IACzB,MAAM,WAAW,GAAG,YAAY,OAAO,2BAA2B,KAAK,UAAU,MAAM,EAAE;IACzF,OAAO,YAAY,IAAI,IAAI,GAAG,IAAI,SAAS,WAAW;GAC1D;GACA,MAAM,kBAAkB,OAAO,GAAG,UAAU,MAAM,OAAO;EAC7D;EAEA,KAAK,SAAS;GACV,MAAM,SAAS,OAAO,GAAG,OAAO;GAChC,IAAI,cAAc,IAAI,GAAG;IACrB,IAAI,aAAkB,GAAG,GAAG;IAC5B,KAAK,MAAM,SAAS,QAChB,aAAa,GAAG,gBAAgB,WAAW,IAAI,MAAM;IAEzD,OAAO;GACX;GACA,IAAI,KAAK,WAAW,MAAM,GAAG;IAMzB,MAAM,WAAW,GAAG;;yDAEqB,OAAO;yEACS,GAJrC,GAAG,KAAK,UAAU,MAAM,EAAE,SAIoB;;IAElE,OAAO,YAAY,IAAI,IAAI,GAAG,IAAI,SAAS,WAAW;GAC1D;GACA,MAAM,kBAAkB,OAAO,GAAG,UAAU,MAAM,OAAO;EAC7D;EAEA,KAAK,UAAU;GACX,IAAI,CAAC,KAAK,WAAW,MAAM,GAAG,MAAM,kBAAkB,OAAO,GAAG,UAAU,MAAM,OAAO;GAIvF,MAAM,SAAS,GAAG,YAAY,OAAO,2BAA2B,KAAK,UAAU,GAAG,OAAO,EAAE;GAC3F,OAAO,YAAY,IAAI,IAAI,GAAG,IAAI,OAAO,WAAW;EACxD;CACJ;AACJ;AAEA,SAAS,kBACL,OACA,UACA,MACA,SACQ;CACR,OAAO,SAAS,WACZ,GAAG,SAAS,yBAAyB,MAAM,QAAQ,QAAQ,eAAe,mBACvE,QAAQ,qBAAqB,QAAQ,SAAS,YAChD,aAAa,WAAW,4BAA4B,6BACrD,yBACJ;AACJ;;AAGA,SAAgB,gBACZ,OACA,UACA,SACmB;CACnB,MAAM,WAAgC,CAAC;CACvC,KAAK,MAAM,CAAC,OAAO,OAAO,OAAO,QAAQ,QAAQ,GAC7C,SAAS,SAAS,eAAe,OAAO,OAAO,IAAI,OAAO;CAE9D,OAAO;AACX;;;;;;;;;;;;;;;;;;;;;;;AA6CA,SAAgB,qBACZ,OACA,UACA,YACA,SACc;CACd,MAAM,SAAyB,CAAC;CAChC,KAAK,MAAM,CAAC,OAAO,OAAO,OAAO,QAAQ,QAAQ,GAAG;EAChD,MAAM,WAAW,WAAW;EAC5B,IAAI,CAAC,UAAU,YAAY;EAC3B,IAAI,SAAS,SAAS,YAAY,GAAG,aAAa,UAAU,OAAO,GAAG,YAAY,UAAU;GACxF,MAAM,SAAS,eAAe,OAAO,OAAO,IAAI,OAAO;GACvD,OAAO,KAAK,GAAG,aAAa,OAAO,SAAS,YAAY,QAAQ,GAAG,OAAO,CAAC;EAC/E,OAAO,IAAI,SAAS,SAAS,YAAY,GAAG,aAAa,WAAW,GAAG,aAAa,UAAU;GAC1F,MAAM,SAAS,MAAM;GACrB,MAAM,SAAS,eAAe,OAAO,OAAO,IAAI,OAAO;GAKvD,MAAM,OAAO,UAAU,cAAc,UAAU,MAAM,CAAC,IAChD,GAAG,eAAe,OAAO,KACzB,GAAG,uBAAuB,OAAO;GACvC,OAAO,KAAK,GAAG,YAAY,OAAO,SAAS,YAAY,MAAM,GAAG,QAAQ,CAAC;EAC7E;CACJ;CACA,OAAO;AACX;AAEA,SAAS,aACL,OACA,OACA,QACA,SACc;CAId,MAAM,KAAK,WAAW;CACtB,MAAM,SAAyB,CAAC;CAChC,IAAI,MAAM,QAAQ,KAAA,GACd,OAAO,KAAK;EAAE;EAAO,MAAM;EAAO,OAAO,GAAG,IAAI,OAAO,OAAO,MAAM,IAAI;EAAY,SAAS,IAAI,MAAM,qBAAqB,MAAM,IAAI,QAAQ,GAAG;CAA4B,CAAC;CAElL,IAAI,MAAM,QAAQ,KAAA,GACd,OAAO,KAAK;EAAE;EAAO,MAAM;EAAO,OAAO,GAAG,IAAI,OAAO,OAAO,MAAM,IAAI;EAAY,SAAS,IAAI,MAAM,oBAAoB,MAAM,IAAI,QAAQ,GAAG;CAA4B,CAAC;CAEjL,IAAI,MAAM,aAAa,KAAA,GACnB,OAAO,KAAK;EAAE;EAAO,MAAM;EAAa,OAAO,GAAG,IAAI,OAAO,MAAM,MAAM,SAAS;EAAY,SAAS,IAAI,MAAM,yBAAyB,MAAM,SAAS,QAAQ,GAAG,oBAAoB,MAAM,SAAS;CAAY,CAAC;CAExN,IAAI,MAAM,aAAa,KAAA,GACnB,OAAO,KAAK;EAAE;EAAO,MAAM;EAAa,OAAO,GAAG,IAAI,OAAO,MAAM,MAAM,SAAS;EAAY,SAAS,IAAI,MAAM,sBAAsB,MAAM,SAAS,QAAQ,GAAG,oBAAoB,MAAM,SAAS;CAAY,CAAC;CAErN,IAAI,MAAM,UACN,OAAO,KAAK;EAAE;EAAO,MAAM;EAAY,OAAO,GAAG,IAAI,OAAO;EAAQ,SAAS,IAAI,MAAM,0BAA0B,GAAG;CAA+B,CAAC;CAExJ,IAAI,MAAM,UACN,OAAO,KAAK;EAAE;EAAO,MAAM;EAAY,OAAO,GAAG,IAAI,OAAO;EAAQ,SAAS,IAAI,MAAM,0BAA0B,GAAG;CAA+B,CAAC;CAExJ,OAAO;AACX;AAEA,SAAS,YACL,OACA,OACA,MACA,UACc;CACd,MAAM,SAAS,MAAc,GAAG,EAAE,OAAO,MAAM,IAAI,KAAK;CAIxD,MAAM,SAAyB,CAAC;CAChC,IAAI,MAAM,QAAQ,KAAA,GACd,OAAO,KAAK;EAAE;EAAO,MAAM;EAAa,OAAO,GAAG,YAAY,KAAK,MAAM,MAAM,IAAI;EAAmB,SAAS,IAAI,MAAM,uBAAuB,MAAM,MAAM,GAAG,EAAE,QAAQ,SAAS;CAA6B,CAAC;CAEpN,IAAI,MAAM,QAAQ,KAAA,GACd,OAAO,KAAK;EAAE;EAAO,MAAM;EAAa,OAAO,GAAG,YAAY,KAAK,MAAM,MAAM,IAAI;EAAmB,SAAS,IAAI,MAAM,sBAAsB,MAAM,MAAM,GAAG,EAAE,QAAQ,SAAS;CAA4B,CAAC;CAElN,OAAO;AACX;;;;;;;;;;;;;;;;;;;;;AAsBA,eAAsB,oBAClB,QACA,OACA,YACA,QACuB;CACvB,IAAI,OAAO,WAAW,GAAG,OAAO,CAAC;CACjC,MAAM,YAA0C,CAAC;CACjD,OAAO,SAAS,OAAO,UAAU;EAC7B,UAAU,QAAQ,WAAW,GAAY,IAAI,MAAM,MAAM;CAC7D,CAAC;CAED,MAAM,OAAM,MADO,OAAO,OAAO,SAAS,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,MAAM,IAAI,GAAG,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,EAAA,CACxE;CACjB,IAAI,CAAC,KAAK,OAAO,CAAC;CAClB,OAAO,OAAO,QAAQ,QAAQ,UAAU,IAAI,QAAQ,aAAa,KAAK;AAC1E;;AAGA,SAAgB,mBAAmB,gBAAwB,QAAkC;CACzF,MAAM,aAAa,OAAO,KAAK,EAAE,OAAO,MAAM,eAAe;EAAE;EAAO;EAAM;CAAQ,EAAE;CACtF,MAAM,WAAW,WAAW,KAAI,cAAa,UAAU,OAAO;CAC9D,OAAO,SAAS,WAAW,SAAS,KAAK,GAAG,GAAG,yBAAyB;EACpE,YAAY;EACZ;EACA;CACJ,CAAC;AACL;;;;;;;;;;;;;ACjSA,SAAgB,gBAAgB,YAA8C;CAC1E,MAAM,OAAiB,CAAC;CACxB,KAAK,MAAM,CAAC,KAAK,aAAa,OAAO,QAAQ,cAAc,CAAC,CAAC,GACzD,IAAK,UAAU,SAAS,UAAU,SAAS,cAAc,eACjD,UAAU,SAAS,YAAY,SAAS,cAAc,kBAC1D,KAAK,KAAK,GAAG;CAGrB,OAAO;AACX;;;;;AAuBA,IAAa,iBAAb,MAA4B;CAQZ;CACA;CAOA;;CAdZ;;CAEA;CACA;CAEA,YACI,IACA,UAOA,aACF;EATU,KAAA,KAAA;EACA,KAAA,WAAA;EAOA,KAAA,cAAA;EAER,KAAK,kBAAkB,IAAI,gBAAgB,IAAI,UAAU,WAAW;EACpE,KAAK,iBAAiB,IAAI,qBAAqB,IAAI,UAAU,WAAW;EACxE,KAAK,eAAe,IAAI,aAAa,IAAI,UAAU,WAAW;CAClE;;;;;;;;;;;CAaA,MAAM,oBACF,gBACA,UACA,OACa;EACb,MAAM,MAAM,aAAa,cAAc,IAAI,kBAAkB,gBAAgB,KAAK,QAAQ,IAAI,KAAA;EAC9F,IAAI,CAAC,KACD,MAAM,SAAS,WACX,IAAI,eAAe,mEACZ,mBAAmB,kEAC1B,6BACA,EAAE,MAAM,eAAe,CAC3B;EAEJ,sBAAsB,KAAK,cAAc;EAKzC,IAAI,CAAC,MAAM,KAAK,gBAAgB,UAAU,KAAK,QAAQ,GACnD,MAAM,SAAS,SAAS,WAAW,SAAS,QAAQ,eAAe,yBAAyB;EAKhG,MAAM,KAAK,GAAG,aAAY,OAAM,KAAK,eAAe,oBAAoB,IAAI,KAAK,UAAU,KAAK,CAAC;CACrG;;;;;CAOA,oBACI,QACA,OACA,YACA,gBACA,IACA,WACiB;EACjB,OAAO,oBACH,QACA,OACA,YACA,kBAAkB,UAAU,IAAI,GAAG,QAAQ,eAAe,qDAC1D,WAAW,GAAG,QAAQ,eAAe,OAAO,UAAU,EAC1D;CACJ;;;;CAKA,MAAM,OAAO,gBAAwB,IAAqB,aAAqC;EAG3F,MAAM,MAAM,aAAa,cAAc,IAAI,kBAAkB,gBAAgB,KAAK,QAAQ,IAAI,KAAA;EAC9F,IAAI,KAAK;GACL,sBAAsB,KAAK,cAAc;GAEzC,IAAI,CAAC,MAAM,KAAK,gBAAgB,UAAU,KAAK,EAAE,GAC7C,MAAM,SAAS,SAAS,WAAW,GAAG,QAAQ,eAAe,aAAa;GAG9E,IAAI,yBAAyB,IAAI,QAAQ,GAAG;IAExC,IAAI,CAAC,aAAa,IAAI,QAAQ,GAC1B,MAAM,SAAS,WACX,IAAI,eAAe,aAAa,IAAI,iBAAiB,KAAK,2FACE,IAAI,iBAAiB,KAAK,wCAEtF,yBACJ;IAEJ,MAAM,KAAK,GAAG,aAAY,OAAM,KAAK,eAAe,oBAAoB,IAAI,KAAK,EAAE,CAAC;IACpF;GACJ;EAGJ;EAEA,MAAM,aAAa,oBAAoB,gBAAgB,KAAK,QAAQ;EACpE,MAAM,QAAQ,sBAAsB,YAAY,KAAK,QAAQ;EAG7D,MAAM,YAAY,qBAAqB,OAAO,mBAAmB,YAAY,KAAK,QAAQ,GAAG,IAAI,cAAc;EAS/G,IAAI;EACJ,IAAI;GACA,SAAS,MAAM,KAAK,GAAG,aAAY,OAAM,GACpC,OAAO,KAAK,CAAC,CACb,MAAM,SAAS,CAAC;EACzB,SAAS,OAAgB;GACrB,MAAM,KAAK,oBAAoB,OAAO,WAAW,MAAM,YAAY,QAAQ;EAC/E;EAEA,KAAK,OAAO,YAAY,OAAO,GAC3B,MAAM,MAAM,KAAK,oBAAoB,KAAK,IAAI,OAAO,CAAC,SAAS,GAAG,gBAAgB,IAAI,QAAQ;CAEtG;;;;CAKA,MAAM,UAAU,gBAAwB,aAAqC;EAEzE,MAAM,QAAQ,sBADK,oBAAoB,gBAAgB,KAAK,QACxB,GAAY,KAAK,QAAQ;EAC7D,MAAM,KAAK,GAAG,OAAO,KAAK;CAC9B;;;;;;;;;;;;;;;;;;CAmBA,8BAAsC,KAAwC;EAC1E,MAAM,EAAE,aAAa;EAErB,QAAQ,SAAS,MAAjB;GACI,KAAK;GACL,KAAK,WACD,OAAO,kBAAkB,IAAI,kBAAkB,SAAS,kBAAkB;GAE9E,KAAK,OAGD,OAAO,SAAS,SAAS,WAAW,IAC9B,kBACE,IAAI,kBACJ,wBAAwB,0BAA0B,SAAS,SAAS,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC,EAClF,IACE,KAAA;GAEV,SAII;EACR;CACJ;;;;;;;;;;;;;;;;;;;;;CAsBA,MAAM,KACF,gBACA,QACA,IACA,YACA,SACgC;EAEhC,IAAI,0BAA0B;EAC9B,MAAM,kBAA8B,EAAE,GAAG,OAAO;EAChD,IAAI;EAEJ,MAAM,MAAM,aAAa,cAAc,IAAI,kBAAkB,gBAAgB,KAAK,QAAQ,IAAI,KAAA;EAE9F,IAAI,KAAK;GACL,sBAAsB,KAAK,cAAc;GACzC,0BAA0B,IAAI,iBAAiB;GAE/C,MAAM,yBAAyB;IAC3B,MAAM,YAAY,eAAe,IAAI,kBAAkB,KAAK,QAAQ;IACpE,OAAO,gBAAc,IAAI,UAAU,SAAS,CAAC,CAAC,UAAU,EAAE,CAAC;GAC/D;GAEA,IAAI,aAAa,IAAI,QAAQ,GAUzB,oBAAoB;IAChB,kBAAkB,IAAI;IACtB,UAAU,iBAAiB;IAC3B,UAAU,IAAI;IACd,aAAa,IAAI;GACrB;QACG,IAAI,OAAO,KAAA;QAOV,CAAC,MAAM,KAAK,gBAAgB,UAAU,KAAK,EAAE,GAC7C,MAAM,SAAS,SAAS,WAAW,GAAG,QAAQ,eAAe,aAAa;GAAA,OAE3E;IAEH,MAAM,mBAAmB,KAAK,8BAA8B,GAAG;IAE/D,IAAI,kBAAkB;KAKlB,MAAM,iBAAiB,sBAAsB,IAAI,QAAQ,IACnD,MAAM,KAAK,gBAAgB,eAAe,IAAI,kBAAkB,IAAI,UAAU,IAAI,QAAQ,IAC1F,iBAAiB;KAEvB,IAAI,mBAAmB,KAAA,GACnB,MAAM,SAAS,WACX,wBAAwB,eAAe,yFAE3C;KAGJ,MAAM,gBAAiB,gBAA4C;KACnE,IAAI,kBAAkB,KAAA,KAAa,kBAAkB,QAAQ,kBAAkB,gBAC3E,OAAO,KAAK,8BAA8B,cAAc,YAAY,iBAAiB,0BAA0B,eAAe,GAAG;KAErI,gBAA6C,oBAAoB;IACrE;GACJ;EACJ;EAEA,MAAM,aAAa,oBAAoB,yBAAyB,KAAK,QAAQ;EAC7E,MAAM,QAAQ,sBAAsB,YAAY,KAAK,QAAQ;EAC7D,MAAM,cAAc,eAAe,YAAY,KAAK,QAAQ;EACnC,YAAY,KAAI,SAAQ,KAAK,SAAS;EAG/D,MAAM,gBAA6C,CAAC;EACpD,YAAY,SAAQ,SAAQ;GACxB,MAAM,QAAQ,MAAM,KAAK;GACzB,IAAI,CAAC,OAAO,MAAM,IAAI,MAAM,sBAAsB,KAAK,UAAU,uCAAuC,wBAAwB,EAAE;GAClI,cAAc,KAAK,aAAa;EACpC,CAAC;EAMD,MAAM,EAAE,QAAQ,aAAa,aAAa,cAAc,eAA0C;EAClG,MAAM,cAAc,OAAO,KAAK,QAAQ;EACxC,IAAI,YAAY,SAAS,GAAG;GAMxB,IAAI,CAAC,IACD,MAAM,SAAS,WACX,qBAAqB,YAAY,KAAI,MAAK,IAAI,EAAE,EAAE,CAAC,CAAC,KAAK,IAAI,EAAE,uDACzC,wBAAwB,iDAC9C,yBACJ;GAEJ,IAAI,SAAS,QACT,MAAM,SAAS,WACX,qBAAqB,YAAY,KAAI,MAAK,IAAI,EAAE,EAAE,CAAC,CAAC,KAAK,IAAI,EAAE,wJAG/D,yBACJ;EAER;EAGA,MAAM,iBAA0C,CAAC;EACjD,MAAM,cAA0B,EAAE,GAAI,YAA2B;EACjE,MAAM,oBAAoB,2BAA2B,UAAU;EAE/D,KAAK,MAAM,OAAO,mBAAmB;GACjC,MAAM,WAAW,kBAAkB;GACnC,IAAI,YAAY,SAAS,gBAAgB;QACjC,OAAO,UAAU,eAAe,KAAK,aAAa,GAAG,GAAG;KACxD,eAAe,OAAO,YAAY;KAClC,OAAO,YAAY;IACvB;;EAER;EAEA,IAAI;EACJ,IAAI;GAEA,MAAM,mBAAmB,sBAAsB,aAAkB,WAAW,YAA0B,YAAY,KAAK,QAAQ;GAG/H,MAAM,yBAAyB,iBAAiB;GAChD,MAAM,0BAA0B,iBAAiB;GAEjD,MAAM,aAAa,wBAAwB,iBAAiB,UAAU;GAKtE,sBAAsB,YAAuC,OAAO,uBAAuB;GAM3F,IAAI,YAAY,SAAS,GACrB,sBACI,OAAO,YAAY,YAAY,KAAI,QAAO,CAAC,KAAK,IAAI,CAAC,CAAC,GACtD,OACA,uBACJ;GAGJ,UAAU,MAAM,KAAK,GAAG,YAAY,OAAO,OAAO;IAC9C,IAAI;IAEJ,IAAI,MAAM,CAAC,SAAS,QAAQ;KAExB,YAAY;KAQZ,IAAI,wBAAwB,SAAS,GACjC,MAAM,KAAK,eAAe,gCAAgC,IAAI,YAAY,WAAW,uBAAuB;KAMhH,MAAM,aAAa,OAAO,KAAK,UAAqC;KAKpE,MAAM,cAAc,YAAY,SAAS,IACnC,gBAAgB,OAAO,UAAU,EAAE,gBAAgB,wBAAwB,CAAC,IAC5E,KAAA;KAIN,MAAM,SAAS,YAAY,SAAS,IAC9B,qBAAqB,OAAO,UAAU,WAAW,cAAc,CAAC,GAAG,EAAE,gBAAgB,wBAAwB,CAAC,IAC9G,CAAC;KACP,IAAI,WAAW,SAAS,KAAK,aAAa;MACtC,MAAM,cAAc,GAAG,OAAO,KAAK,CAAC,CAAC,IAAI;OACrC,GAAI;OACJ,GAAI,eAAe,CAAC;MACxB,CAAC;MACD,MAAM,YAAY,qBAAqB,OAAO,aAAa,IAAI,uBAAuB;MAKtF,MAAK,MAJsB,YAAY,MAAM,IAAI,WAAW,GAAG,OAAO,KAAI,UAAS,MAAM,KAAK,CAAC,CAAC,EAAA,CAI9E,YAAY,OAAO,GAAG;OACpC,MAAM,SAAS,MAAM,oBAAoB,IAAI,OAAO,CAAC,SAAS,GAAG,MAAM;OACvE,IAAI,OAAO,SAAS,GAAG,MAAM,mBAAmB,WAAW,MAAM,MAAM;OACvE,MAAM,MAAM,KAAK,oBACb,IAAI,OAAO,CAAC,SAAS,GAAG,yBAAyB,WAAW,QAChE;MACJ;KACJ;IACJ,OAAO;KACH,MAAM,gBAAgB,EAAE,GAAI,WAAuC;KAInE,IAAI,MAAM,SAAS,QACf,OAAO,OAAO,eAAe,gBAAc,IAAI,WAAW,CAAC;KAI/D,KAAK,MAAM,QAAQ,aACf,IAAI,cAAc,KAAK,eAAe,MAAM,cAAc,KAAK,eAAe,QAAQ,cAAc,KAAK,eAAe,KAAA,GACpH,OAAO,cAAc,KAAK;KAIlC,MAAM,cAAc,GAAG,OAAO,KAAK,CAAC,CAAC,OAAO,aAAa;KAUzD,MAAM,eAAe,SAAS,cAAc,QAAQ,WAAW,SAAS,IAClE,CAAC,GAAG,QAAQ,UAAU,IACtB,YAAY,KAAK,SAAS,KAAK,SAAS;KAC9C,MAAM,aAAa,aAAa,SAAS,KAClC,aAAa,OAAO,UAAU,cAAc,WAAW,KAAA,CAAS;KAEvE,IAAI;KACJ,IAAI,SAAS,UAAU,YAAY;MAC/B,MAAM,SAAS,aAAa,KAAK,UAAU;OACvC,MAAM,SAAS,MAAM;OACrB,IAAI,CAAC,QACD,MAAM,SAAS,WACX,IAAI,MAAM,wBAAwB,wBAAwB,uCAC1D,yBACJ;OAEJ,OAAO;MACX,CAAC;MACD,MAAM,MAAM,EAAE,GAAG,cAAc;MAO/B,KAAK,MAAM,QAAQ,aAAa,OAAO,IAAI,KAAK;MAChD,KAAK,MAAM,SAAS,cAAc,OAAO,IAAI;MAa7C,KAAK,MAAM,OAAO,gBAAgB,WAAW,UAAoC,GAC7E,OAAO,IAAI;MAOf,KAAK,MAAM,OAAO,QAAQ,kBAAkB,CAAC,GAAG,OAAO,IAAI;MAO3D,MAAM,QAAQ,MAAM,KAAK,WAAW,YAAY,yBAAyB,KAAK;MAM9E,MAAM,aAAa,kBAAkB,UAAU;MAC/C,MAAM,cAAc,aAAa,iBAAiB,YAAY,KAAK,IAAI,KAAA;MACvE,MAAM,WAAW,cACV,QAAQ,IAAI,OAAO,OAAO,WAAW,CAAC,IAAI,OAAO,WAAW,IAC7D;MACN,MAAM,WAAW,OAAO,KAAK,GAAG,CAAC,CAAC,SAAS;MAC3C,SAAS,WACH,MAAM,YAAY,mBAAmB;OAAE;OAAQ;OAAK,UAAU;MAAS,CAAC,CAAC,CAAC,UAAU,aAAa,IACjG,MAAM,YAAY,oBAAoB,EAAE,OAAO,CAAC,CAAC,CAAC,UAAU,aAAa;MAE/E,IAAI,OAAO,WAAW,GAAG;OAQrB,MAAM,gBAAuB,OAAO,KAAK,QAAQ,MAAM,GAAG,QAAQ,cAAc,aAAa,GAAG,CAAC;OACjG,IAAI,OAAO,cAAc,KAAK,KAAK;OACnC,MAAM,WAAW,aAAa,KAAK,UAAU,OAAO,cAAc,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI;OACpF,IAAI,cAAc,aAAa;QAC3B,MAAM,CAAC,WAAW,MAAM,GAAG,OAAO,aAAa,CAAC,CAAC,KAAK,KAAK,CAAC,CACvD,MAAM,IAAI,GAAG,eAAe,UAAU,WAAW,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;QACjE,IAAI,SAAS,MAAM,gBAAgB,yBAAyB,UAAU,UAAU;OACpF;OACA,MAAM,UAAU,WAAW,SAAS,QAAQ,wBAAwB;OACpE,IAAI,UACA,MAAM,MAAM,oBACR,IAAI,OAAO,eACX,0BAA0B,SAAS,QAAQ,wBAAwB,qDACnE,OACJ;OAEJ,SAAS,MAAM,GAAG,OAAO,aAAa,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,MAAM,IAAI,GAAG,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC;OACxF,IAAI,OAAO,WAAW,GAAG,MAAM,SAAS,SAAS,OAAO;MAC5D;KACJ,OACI,SAAS,MAAM,YAAY,UAAU,aAAa;KAMtD,MAAM,YAAY,OAAO;KACzB,IAAI,CAAC,WACD,IAAI,IACA,YAAY;UAEZ,MAAM,SAAS,UACX,4BAA4B,wBAAwB,0DACpD,cACJ;UAGJ,YAAY,mBAAiB,WAAW,WAAW;KAIvD,IAAI,wBAAwB,SAAS,GACjC,MAAM,KAAK,eAAe,gCAAgC,IAAI,YAAY,WAAW,uBAAuB;IAEpH;IAGA,IAAI,uBAAuB,SAAS,GAChC,MAAM,KAAK,eAAe,uBAAuB,IAAI,YAAY,WAAW,sBAAsB;IAItG,IAAI,OAAO,KAAK,cAAc,CAAC,CAAC,SAAS,GACrC,MAAM,KAAK,eAAe,0BAA0B,IAAI,YAAY,WAAW,cAAc;IAQjG,IAAI,mBACA,MAAM,KAAK,eAAe,4BAA4B,IAAI,WAAW,iBAAiB;IAG1F,OAAO;GACX,CAAC;EACL,SAAS,OAAgB;GACrB,MAAM,KAAK,oBAAoB,OAAO,WAAW,MAAM,UAAU;EACrE;EAuBA,MAAM,cAAc,MAAM,KAAK,aAAa,gBACxC,WAAW,MAAM,SAAS,KAAA,GAAW,YAAY,EAAE,aAAa,KAAK,CACzE;EACA,IAAI,CAAC,aAAa,MAAM,IAAI,MAAM,iCAAiC;EACnE,OAAO;CACX;;;;;;;CAQA,WAAmB,YAA8B,gBAAwB,OAA0C;EAC/G,MAAM,CAAC,OAAO,eAAe,YAAY,KAAK,QAAQ;EACtD,OAAO,qBACH;GAAE,UAAU,KAAK;GAAU,aAAa,KAAK;EAAY,GACzD,YAAY,gBAAgB,OAC5B;GAAE,WAAW;GAAO,OAAO,CAAC;EAAE,GAC9B;GACI;GACA,UAAU,KAAK;GACf,gBAAgB,MAAM,MAAM,IAAI,aAA8D,KAAA;EAClG,CACJ;CACJ;;;;CAKA,qBAAsC;EAClC,OAAO,KAAK;CAChB;;;;;;;CAQA,0BAAgD;EAC5C,OAAO,KAAK;CAChB;;;;CAKA,kBAAgC;EAC5B,OAAO,KAAK;CAChB;;;;;;;;;CAUA,oBACI,OACA,gBACA,YACA,YAA+B,QAC1B;EAKL,IAAI,iBAAiB,YAAa,OAAiB,SAAS,YACxD,OAAO;EAGX,MAAM,UAAU,eAAe,KAAK;EAEpC,IAAI,SAAS;GACT,MAAM,EAAE,SAAS,MAAM,eAAe,yBAAyB,SAAS,gBAAgB,EAAE,WAAW,CAAC;;GAEtG,MAAM,UAAU,WAAW,SAAS,IAC9B;IAAE,YAAY;IAAgB;GAAW,IACzC,KAAA;GAgBN,IAAI,SAAS,KAAK,IAAI,GAGlB,OAFiB,SAAS,WAAW,SAAS,WACtC,SAAS,WAAW,cAAc,WAEpC,SAAS,SAAS,SAAS,MAAM,QAAQ,OAAO,IAChD,SAAS,WAAW,SAAS,MAAM,QAAQ,OAAO;GAoB5D,IAAI,yBAAyB,KAAK,GAC9B,OAAO,SAAS,UAAU,SAAS,cAAc;GAarD,OAAO,IAAI,MAAM,SAAS,EAAE,OAAO,MAAM,CAAC;EAC9C;EAIA,MAAM,eAAe,oBAAoB,KAAK;EAC9C,IAAI,cACA,OAAO,IAAI,MAAM,sBAAsB,eAAe,KAAK,gBAAgB,EAAE,OAAO,MAAM,CAAC;EAI/F,IAAI,iBAAiB,SAAS,MAAM,QAAQ,WAAW,eAAe,GAClE,OAAO,IAAI,MAAM,0BAA0B,eAAe,oCAAoC,EAAE,OAAO,MAAM,CAAC;EAElH,OAAO,IAAI,MAAM,sBAAsB,eAAe,KAAK,OAAO,KAAK,KAAK,EAAE,OAAO,MAAM,CAAC;CAChG;AACJ;;;;;;;;;;;;;;ACpzBA,IAAa,cAAb,MAAmD;CAKnC;CACA;CAOA;CAZZ;CACA;CAEA,YACI,IACA,UAOA,aACF;EATU,KAAA,KAAA;EACA,KAAA,WAAA;EAOA,KAAA,cAAA;EAER,KAAK,eAAe,IAAI,aAAa,IAAI,UAAU,WAAW;EAC9D,KAAK,iBAAiB,IAAI,eAAe,IAAI,UAAU,WAAW;CACtE;;;;CASA,MAAM,SACF,gBACA,IACA,YAEA,aAC4C;EAC5C,OAAO,KAAK,aAAa,SAAY,gBAAgB,IAAI,YAAY,WAAW;CACpF;;;;CAKA,MAAM,gBACF,gBAIA,UAA0D,CAAC,GACzB;EAClC,OAAO,KAAK,aAAa,gBAAmB,gBAAgB,OAAO;CACvE;;;;;;;;;;;CAYA,MAAM,uBACF,gBACA,UAAiE,CAAC,GAClE,SACkC;EAClC,OAAO,KAAK,aAAa,uBAA0B,gBAAgB,SAAS,OAAO;CACvF;;CAGA,MAAM,gBACF,gBACA,IACA,SACA,YACA,SAKuC;EACvC,OAAO,KAAK,aAAa,gBAAmB,gBAAgB,IAAI,SAAS,YAAY,OAAO;CAChG;;CAGA,UACI,gBACA,KACA,SACkB;EAClB,OAAO,KAAK,aAAa,UAAU,gBAAgB,KAAK,OAAO;CACnE;;;;CAKA,MAAM,WACF,gBACA,cACA,UASI,CAAC,GAC6B;EAClC,OAAO,KAAK,aAAa,WAAc,gBAAgB,cAAc,OAAO;CAChF;;;;CAKA,MAAM,MACF,gBACA,UAUI,CAAC,GACU;EACf,OAAO,KAAK,aAAa,MAAS,gBAAgB,OAAO;CAC7D;;;;CAKA,MAAM,iBACF,gBACA,WACA,OACA,iBACA,YACgB;EAChB,OAAO,KAAK,aAAa,iBAAiB,gBAAgB,WAAW,OAAO,iBAAiB,UAAU;CAC3G;;;;CAKA,MAAM,qBACF,sBACA,UACA,aACA,UAQI,CAAC,GAC6B;EAOlC,QAAO,MANY,KAAK,aAAa,mBAAmB,CAAC,CAAC,qBACtD,sBACA,UACA,aACA,OACJ,EAAA,CACY,KAAI,OAAM;GAAE,GAAG,EAAE;GAAQ,IAAI,EAAE;EAAG,EAAE;CACpD;;;;CASA,MAAM,KACF,gBACA,QACA,IACA,YACA,SACgC;EAChC,OAAO,KAAK,eAAe,KAAQ,gBAAgB,QAAQ,IAAI,YAAY,OAAO;CACtF;;;;CAKA,MAAM,OACF,gBACA,IACA,YACa;EACb,OAAO,KAAK,eAAe,OAAO,gBAAgB,IAAI,UAAU;CACpE;;;;;CAMA,MAAM,oBACF,gBACA,UACA,OACa;EACb,OAAO,KAAK,eAAe,oBAAoB,gBAAgB,UAAU,KAAK;CAClF;CAEA,MAAM,UAAU,gBAAwB,YAAoC;EACxE,OAAO,KAAK,eAAe,UAAU,gBAAgB,UAAU;CACnE;;;;CAMA,MAAM,WAAW,SAAiB,QAAwD;EAOtF,IAAI,uBAAuB,GACvB,OAAO,MAAM,sBAAsB,UAAU,QAAQ,SAAS,SAAS,OAAO,OAAO,WAAW,IAAI;EAExG,MAAM,EAAE,QAAQ,MAAM,OAAO;EAE7B,IAAI;EACJ,IAAI,UAAU,OAAO,SAAS,GAAG;GAI7B,MAAM,QAAQ,QAAQ,MAAM,SAAS;GACrC,MAAM,SAA0D,CAAC;GACjE,KAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAC9B,IAAI,IAAI,MAAM;QAEN,MAAM,EAAE,CAAC,SAAS,GAClB,OAAO,KAAK,IAAI,IAAI,MAAM,EAAE,CAAC;GAAA,OAE9B;IAEH,MAAM,aAAa,OAAO,MAAM,EAAE,IAAI;IACtC,OAAO,KAAK,IAAI,MAAM,OAAO,WAAW,CAAC;GAC7C;GAEJ,MAAM,QAAQ,IAAI,KAAK,QAAQ,IAAI,IAAI,EAAE,CAAC;GAC1C,SAAS,MAAM,KAAK,GAAG,QAAQ,KAAK;EACxC,OACI,SAAS,MAAM,KAAK,GAAG,QAAQ,IAAI,IAAI,OAAO,CAAC;EAGnD,MAAM,OAAO,OAAO;EACpB,IAAI,uBAAuB,GACvB,OAAO,MAAM,uCAAuC,MAAM,QAAQ,IAAI,IAAI,KAAK,SAAS,YAAY,OAAO;EAE/G,OAAO;CACX;;;;CASA,kBAAgC;EAC5B,OAAO,KAAK;CAChB;;;;CAKA,oBAAoC;EAChC,OAAO,KAAK;CAChB;;;;CAKA,qBAAsC;EAClC,OAAO,KAAK,aAAa,mBAAmB;CAChD;AACJ;;;;;;;;;;;;;;;;;;;;;ACrSA,IAAa,wBAAb,MAAiE;CAC7D;CACA,OAAe;CACf,2BAA4B,IAAI,IAAsB;CACtD,cAAkD,CAAC;;CAEnD;CACA,eAAsF,CAAC;CACvF;;CAGA;;CAGA,KAAK,IAAmB,KAAmB;EACvC,KAAK,KAAK;EACV,KAAK,MAAM;CACf;;CAGA,IAAI,SAAkB;EAClB,OAAO,KAAK,QAAQ,KAAK,QAAQ,KAAA;CACrC;CAEA,KAAK,SAAiB,QAAwD;EAC1E,IAAI,CAAC,KAAK,QACN,OAAO,QAAQ,uBAAO,IAAI,MAAM,+DAA+D,CAAC;EAEpG,OAAO,KAAK,MAAM,KAAK,IAAK,SAAS,MAAM,CAAC;CAChD;;;;;;;;;;CAWA,MAAS,WAAuC;EAC5C,MAAM,UAAU,QAAQ,QAAQ,SAAS;EACzC,KAAK,SAAS,IAAI,OAAO;EACzB,MAAM,aAAa;GAAE,KAAK,SAAS,OAAO,OAAO;EAAG;EACpD,QAAQ,KAAK,OAAO,UAAmB;GACnC,KAAK,YAAY;GACjB,KAAK;EACT,CAAC;EACD,OAAO;CACX;CAEA,YAAY,IAAsB;EAC9B,KAAK,YAAY,KAAK,EAAE;CAC5B;;;;;;;CAQA,MAAM,SAAwB;EAC1B,OAAO,KAAK,SAAS,OAAO,GACxB,MAAM,QAAQ,WAAW,CAAC,GAAG,KAAK,QAAQ,CAAC;EAE/C,KAAK,OAAO;CAChB;;;;;;;;;;;;;;;;;;;;CAqBA,MAAM,oBAAmC;EACrC,IAAI,CAAC,KAAK,IAAI;EACd,IAAI;GACA,MAAM,KAAK,GAAG,QAAQ,GAAG,UAAU;EACvC,SAAS,OAAO;GACZ,IAAI,eAAe,KAAK,CAAC,EAAE,SAAS,SAAS,MAAM;GACnD,OAAO,MACH,oHACA,EAAE,OAAO,KAAK,WAAW,MAAM,CACnC;GACA,MAAM,UAAU,IAAI,SAChB,KACA,uBACA,8ZAKJ;GACA,QAAQ,QAAQ,KAAK,WAAW;GAChC,MAAM;EACV;CACJ;;;;;;;CAQA,kBAAkB,SAAkC;EAChD,KAAK,iBAAiB;CAC1B;;;;;;;;CASA,aAAa,IAA4D;EACrE,IAAI,CAAC,KAAK,gBAAgB,OAAO;EACjC,KAAK,aAAa,KAAK,EAAE;EACzB,OAAO;CACX;;CAGA,MAAM,UAAyB;EAC3B,MAAM,UAAU,KAAK;EACrB,IAAI,CAAC,SAAS;EACd,KAAK,MAAM,QAAQ,KAAK,aAAa,OAAO,CAAC,GACzC,IAAI;GACA,MAAM,QAAQ,IAAI,KAAA,SAAiB,KAAK,OAAO,CAAC;EACpD,SAAS,OAAO;GACZ,OAAO,MAAM,+DAA+D,EAAE,MAAM,CAAC;EACzF;CAER;;;;;;CAOA,QAAc;EACV,KAAK,OAAO;CAChB;;CAGA,YAAkB;EACd,KAAK,MAAM,QAAQ,KAAK,YAAY,OAAO,CAAC,GACxC,IAAI;GACA,QAAQ,IAAI,KAAA,GAAW,IAAI;EAC/B,SAAS,OAAO;GACZ,OAAO,MAAM,6EAA6E,EAAE,MAAM,CAAC;EACvG;CAER;AACJ;;;;;;AAOA,IAAM,eAAe,OAAO,IAAI,uCAAuC;AACvE,IAAM,UACD,WAAwC,kBACrC,IAAI,kBAAqD;;AAGjE,SAAgB,oBAAuD;CACnE,MAAM,QAAQ,QAAQ,SAAS;CAC/B,OAAO,OAAO,SAAS,QAAQ,KAAA;AACnC;;;;;AAMA,SAAgB,gBAAmB,OAA0C,IAAkC;CAC3G,OAAO,QAAQ,IAAI,OAAO,EAAE;AAChC;;;;;;;;;;;;AAaA,WAAwC,OAAO,IAAI,0CAA0C,KAAK;;;;;;AC/LlG,IAAM,aAAa,OAAO,IAAI,4BAA4B;AAE1D,IAAM,SACD,WAAiC,gBAAgB,IAAI,kBAA8B;;AAGxF,SAAgB,aACZ,MACA,WACA,IACU;CACV,MAAM,SAAS,OAAO,SAAS;CAC/B,MAAM,QAAoB;EAAE;EAAM;EAAW,QAAQ,QAAQ,SAAS,KAAK;EAAG;EAAQ,OAAO,KAAA;CAAU;CACvG,IAAI,MAAM,QAAA,IACN,OAAO,QAAQ,OAAO,eAAe,KAAK,CAAC;CAE/C,OAAO,OAAO,IAAI,aAAa,GAAG,KAAK,CAAC;AAC5C;AAEA,SAAS,eAAe,OAA6B;CACjD,MAAM,UAAU,MAAM;CACtB,MAAM,OAAO,SAAS,SAAS;CAG/B,MAAM,QAAkB,CAAC;CACzB,KAAK,IAAI,IAA4B,OAAO,GAAG,QAAQ,IAAI,EAAE,QAAQ;EACjE,MAAM,OAAO,IAAI,EAAE,OAAO,KAAK,IAAI,EAAE,OAAO,SAAS,IAAI,KAAK,EAAE,UAAU,IAAI,EAAE,KAAK;EACrF,IAAI,CAAC,MAAM,SAAS,IAAI,GAAG,MAAM,KAAK,IAAI;CAC9C;CACA,OAAO,IAAI,SACP,KACA,sBACA,kHACoB,KAAK,YAAY,SAAS,QAAQ,MAAM,KAAK,IAAI,MAAM,UAAU,KAAK,MAAM,KAAK,2GAElG,MAAM,KAAK,IAAI,EAAE,uIAEpB;EAAE,OAAO;EAAO,OAAA;CAAuB,CAC3C;AACJ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACOA,IAAM,eAAoD;CACtD,MAAM;CACN,UAAU;CACV,mBAAmB;AACvB;AAEA,IAAM,UAAU,gBAAmE;CAC/E,MAAM,sBAAM,IAAI,IAA8B;CAC9C,KAAK,MAAM,cAAc,aACrB,IAAI,WAAW,MAAM,IAAI,IAAI,WAAW,MAAM,UAAU;CAE5D,OAAO;AACX;AAEA,IAAM,gBAAgB,eACjB,WAAW,cAAc,CAAC;;AAG/B,IAAM,gBAAgB,SAAyC;CAC3D,MAAM,SAAU,KAA4B;CAC5C,IAAI,CAAC,MAAM,QAAQ,MAAM,GAAG,OAAO,KAAA;CACnC,OAAO,OAAO,KAAI,UACd,OAAO,UAAU,WAAW,QAAQ,OAAQ,OAA4B,MAAM,KAAK,CACvF;AACJ;AAEA,IAAM,cAAc,SAA4B,KAAK,YAAY,aAAa;AAE9E,IAAM,gBAAgB,SAA4B,QAAS,KAA4B,IAAI;;;;;;;AAiC3F,SAAS,uBAAuB,OAAe,QAAgB,OAAwC;CACnG,MAAM,MAAM,GAAG,MAAM,GAAG;CACxB,IAAI,CAAC,OAAO,gBAAgB,IAAI,GAAG,KAAK,MAAM,gBAAgB,IAAI,GAAG,GAAG,OAAO;CAC/E,MAAM,CAAC,QAAQ,QAAQ,MAAM,MAAM,GAAG;CACtC,OAAO,KAAK,OAAO,uDAAuD,MAAM,8DACjC,OAAO,KAAK,KAAK,kBAAkB,OAAO;AAC7F;;AAGA,IAAM,eAAe,YAA8B,WAA2B,GAAG,eAAa,UAAU,EAAE,GAAG;;AAG7G,IAAM,gBAAgB,YAA8B,UAAiC;CACjF,IAAI,CAAC,OAAO,iBAAiB,OAAO;CACpC,MAAM,MAAM,eAAe,UAAU;CAGrC,IAAI,CAAC,MAAM,OAAO,IAAI,GAAG,GAAG,OAAO;CACnC,OAAO,CAAC,MAAM,gBAAgB,IAAI,GAAG;AACzC;AAEA,IAAM,kBAAkB,eAAyC;CAE7D,OAAO,GADS,WAAmC,UAAU,SAC5C,GAAG,eAAa,UAAU;AAC/C;AAQA,IAAM,qBAAqB,MAA8B,eACrD,MAAM,OAAO,MAAK,UAAS,MAAM,SAAS,gBAAgB,MAAM,SAAS,WAAW,IAAI;AAE5F,IAAM,WAAW,UAA6B,IAAI,MAAM,WAAW,KAAI,WAAU,IAAI,OAAO,EAAE,CAAC,CAAC,KAAK,IAAI,EAAE;;;;;;;;;AAU3G,IAAM,eAAe,QAA+B,UAChD,UAAU,SAAS,QAAQ,MAAM,MAAM,QAAQ,KAAK,IAC9C,8BAA8B,QAAQ,MAAM,EAAE,MAAM,QAAQ,KAAK,EAAE,KACnE;;AAGV,IAAM,iBAAiB,QAA+B,UAClD,2FACC,UAAU,SAAS,QAAQ,MAAM,MAAM,QAAQ,KAAK,IAC/C,kBAAkB,MAAM,OAAO,KAAK,MAAM,MAAM,qBAAqB,MAAM,MAAM,0BAC9D,QAAQ,KAAK,EAAE,yDAClC;;AAGV,IAAM,cAAc,YAA8B,UAAkB,SAAiD;CACjH,IAAI,KAAK,SAAS,YAAY,OAAO,KAAA;CACrC,MAAM,OAAQ,KAA0B,UAAU,gBAAgB;CAClE,IAAI;EACA,OAAO,aAAa,2BAA2B,UAAU,GAAG,IAAI;CACpE,QAAQ;EACJ;CACJ;AACJ;AAQA,IAAM,mCAAmB,IAAI,IAAI;CAAC;CAAY;CAAY;AAAW,CAAC;AAEtE,SAAS,YAAY,MAA8B,YAA8B,UAAkB,MAA2B;CAE1H,MAAM,UADQ,kBAAkB,MAAM,UACtB,CAAA,EAAO,QAAQ,QAAO,WAClC,OAAO,OAAO,aAAa,YAAY,iBAAiB,IAAI,OAAO,OAAO,IAAI,CAAC,KAAK,CAAC;CACzF,MAAM,YAAyB,CAAC;CAChC,MAAM,WAAW,WAAW,YAAY,UAAU,IAAI;CACtD,IAAI,QAAQ,YAAY,aAAa,QAAQ,GAAG;EAC5C,MAAM,WAAW,KAAK,OAAO,MAAK,MAAK,EAAE,SAAS,cAAc,EAAE,UAAU,SAAS,QAAQ,KAAK;EAClG,IAAI,UAAU,UAAU,KAAK,QAAQ;CACzC;CACA,OAAO;EAAE;EAAS;CAAU;AAChC;;AAGA,SAAS,kBAAkB,WAA8B;CACrD,MAAM,QAAkB,CAAC;CACzB,IAAI,UAAU,QAAQ,SAAS,GAAG;EAC9B,MAAM,QAAQ,UAAU,QAAQ,KAAI,MAAK,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC,KAAK,IAAI;EACnE,MAAM,KAAK,GAAG,UAAU,QAAQ,WAAW,IAAI,WAAW,UAAU,GAAG,OAAO;CAClF;CACA,KAAK,MAAM,YAAY,UAAU,WAAW,MAAM,KAAK,mBAAmB,SAAS,UAAU,EAAE;CAC/F,OAAO,MAAM,KAAK,OAAO;AAC7B;;AAGA,SAAS,QAAQ,QAA4B;CACzC,IAAI,OAAO,WAAW,OAAO,aAAa,aAAa,OAAO,IAAI,EAAE,GAAG,OAAO,UAAU;CACxF,IAAI,OAAO,eAAe,OAAO,OAAO,OAAO;CAC/C,IAAI,OAAO,KAAK,SAAS,QAAQ,OAAO,QAAQ,OAAO,KAAK,OAAO,GAAG,OAAO,KAAK;CAClF,MAAM,WAAW,OAAO,SAAS,SAAS,aAAa,cAAc;CACrE,OAAO,GAAG,aAAa,OAAO,IAAI,IAAI;AAC1C;;AAGA,SAAS,SAAS,QAA4B;CAC1C,IAAI,OAAO,KAAK,SAAS,QAAQ,OAAO,YAAY,OAAO,KAAK;CAChE,OAAO,aAAa,OAAO,IAAI;AACnC;AAEA,SAAS,YAAY,QAAwC;CACzD,IAAI,CAAC,OAAO,WAAW,OAAO,QAAQ,SAAS,YAAY,OAAO,KAAA;CAClE,OAAO,OAAO,QAAQ,SAAS,QAAQ,OAAO,QAAQ,aAAa,OAAO,QAAQ;AACtF;AAEA,SAAS,eAAe,IAAoD;CACxE,IAAI,CAAC,IAAI,OAAO,KAAA;CAChB,OAAO,IAAI,GAAG,aAAa,KAAK,GAAG,YAAY,MAAM,GAAG,aAAa,eAAe,GAAG,SAAS,YAAY,OACvG,GAAG,WAAW,cAAc,GAAG,SAAS,YAAY,MAAM;AACnE;;AAGA,SAAS,QAAQ,aAAqD;CAClE,IAAI;EACA,OAAO,WAAW,WAAW;CACjC,SAAS,KAAK;EACV,OAAO,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC;CAC7D;AACJ;;;;;;;;;;;AAYA,SAAgB,0BACZ,QACA,OACA,OACiB;CACjB,MAAM,WAAW,OAAO,MAAM;CAC9B,MAAM,OAAO,OAAO,KAAK;CACzB,MAAM,UAA0B,CAAC;CAEjC,MAAM,aAAa,QAAQ,MAAM;CACjC,MAAM,YAAY,QAAQ,KAAK;CAC/B,IAAI,qBAAqB,OAIrB,QAAQ,KAAK;EACT,MAAM;EACN,SAAS;EACT,YAAY,CAAC,GAAG,KAAK,KAAK,CAAC,CAAC,CAAC,MAAK,SAAQ,CAAC,SAAS,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,KAAK,KAAK,CAAC,CAAC,CAAC,MAAM;EACzF,QAAQ,+CAA+C,UAAU;EACjE,QAAQ;CACZ,CAAC;CAEL,MAAM,QAA2B,qBAAqB,QAChD,KAAA,IACA;EAAE,QAAQ,sBAAsB,QAAQ,KAAA,IAAY;EAAY,OAAO;CAAU;CAEvF,KAAK,MAAM,CAAC,MAAM,eAAe,MAAM;EACnC,IAAI,CAAC,SAAS,IAAI,IAAI,GAAG;GACrB,QAAQ,KAAK;IACT,MAAM;IACN,SAAS;IACT,YAAY;IACZ,QAAQ,mBAAmB,KAAK,qBAAqB,eAAa,UAAU,EAAE;GAClF,CAAC;GACD;EACJ;EACA,MAAM,MAAM,SAAS,IAAI,IAAI;EAK7B,IAAI,eAAe,GAAG,MAAM,eAAe,UAAU,GAAG;GACpD,QAAQ,KAAK;IACT,MAAM;IACN,SAAS;IACT,YAAY;IACZ,QACI,IAAI,KAAK,sBAAsB,eAAe,GAAG,EAAE,QAAQ,eAAe,UAAU,EAAE;IAE1F,QACI;GAER,CAAC;GACD;EACJ;EACA,mBAAmB,KAAK,YAAY,SAAS,OAAO,KAAK;EACzD,IAAI,OACA,gBAAc,KAAK,YAAY,kBAAkB,MAAM,QAAQ,GAAG,GAAG,kBAAkB,MAAM,OAAO,UAAU,GAAG,SAAS,KAAK;CAEvI;CAEA,KAAK,MAAM,CAAC,MAAM,eAAe,UAAU;EACvC,IAAI,KAAK,IAAI,IAAI,GAAG;EACpB,QAAQ,KAAK;GACT,MAAM;GACN,SAAS;GACT,YAAY;GACZ,QACI,eAAe,KAAK,yCAChB,eAAa,UAAU,EAAE;GACjC,QACI;EAER,CAAC;CACL;CAEA,IAAI,OAAO,QAAQ;EACf,kBAAkB,MAAM,QAAQ,MAAM,OAAO,UAAU,MAAM,OAAO;EACpE,sBAAsB,MAAM,QAAQ,MAAM,OAAO,OAAO;CAC5D;CAEA,KAAK,MAAM,UAAU,SAAS;EAC1B,IAAI,OAAO,YAAY,UAAU,OAAO,YAAY;EACpD,OAAO,OAAO,QAAQ,aAAa,QAAQ,SAAS,IAAI,OAAO,UAAU,CAAC,CAAC;CAC/E;CAEA,MAAM,UAAU,QAAQ,QACnB,OAAO,WAAY,aAAa,OAAO,WAAW,aAAa,SAAS,OAAO,UAAU,OAC1F,MACJ;CAEA,OAAO;EAAE;EAAS;EAAS,YAAY,YAAY;CAAO;AAC9D;;;;;;;;;AAUA,SAAS,aAAa,QAAsB,KAA8E;CACtH,MAAM,QAAQ,MAAM,eAAa,GAAG,IAAI,OAAO;CAC/C,QAAQ,OAAO,MAAf;EACI,KAAK,qBACD,OAAO;GACH,YAAY,UAAU,MAAM;GAC5B,MAAM,SAAS,MAAM;EACzB;EACJ,KAAK,gBACD,OAAO;GACH,YAAY,6FAA6F,MAAM;GAC/G,MAAM,gBAAgB;EAC1B;EACJ,KAAK,mBACD,OAAO;GACH,YAAY;GACZ,MAAM,GAAG,MAAM,GAAG,OAAO,YAAY,GAAG;EAC5C;EACJ,KAAK,gBACD,OAAO,OAAO,YAAY,aACpB;GACE,YAAY;GACZ,MAAM,GAAG,MAAM,GAAG,OAAO,YAAY,GAAG;EAC5C,IACE,CAAC;EACX,KAAK,kBACD,OAAO;GAAE,YAAY;GAA+D,MAAM;EAAiC;EAC/H,KAAK,qBACD,OAAO;GACH,YAAY;GACZ,MAAM;EACV;EACJ,SACI,OAAO,CAAC;CAChB;AACJ;;AAGA,IAAM,kBAAkB,SAAyB,MAAc,aAC3D,aAAa,KAAA,KACb,QAAQ,MAAK,WAAU,OAAO,eAAe,QAAQ,OAAO,aAAa,YAAY,OAAO,YAAY,iBAAiB;AAE7H,SAAS,mBACL,QACA,OACA,SACA,OACA,OACI;CACJ,MAAM,OAAO,MAAM,QAAQ;CAC3B,MAAM,WAAW,aAAa,MAAM;CACpC,MAAM,OAAO,aAAa,KAAK;CAC/B,MAAM,QAAQ,aAAa,OAAO,KAAK;CAMvC,MAAM,0BAAU,IAAI,IAAoB;CACxC,IAAI,OAAO,QAAQ;EACf,MAAM,cAAc,OAAO,KAAK,QAAQ,CAAC,CAAC,QAAO,QAAO,CAAC,KAAK,IAAI;EAClE,MAAM,YAAY,OAAO,KAAK,IAAI,CAAC,CAAC,QAAO,QAAO,CAAC,SAAS,IAAI;EAChE,KAAK,MAAM,UAAU,aAAa;GAC9B,MAAM,MAAM,YAAY,MAAM,QAAQ,QAAQ,QAAQ,SAAS,OAAO,CAAC,CAAC,QAAQ,KAAI,MAAK,EAAE,MAAM,CAAC,CAAC,KAAK;GACxG,IAAI,IAAI,WAAW,GAAG;GACtB,MAAM,SAAS,UAAU,MAAK,QAAO,CAAC,QAAQ,IAAI,GAAG,KACjD,KAAK,UAAU,YAAY,MAAM,OAAO,OAAO,KAAK,KAAK,IAAI,CAAC,CAAC,QAAQ,KAAI,MAAK,EAAE,MAAM,CAAC,CAAC,KAAK,CAAC,MAAM,KAAK,UAAU,GAAG,CAAC;GAC7H,IAAI,QAAQ,QAAQ,IAAI,QAAQ,MAAM;EAC1C;CACJ;CACA,MAAM,cAAc,IAAI,IAAI,QAAQ,OAAO,CAAC;CAE5C,KAAK,MAAM,CAAC,MAAM,SAAS,OAAO,QAAQ,IAAI,GAAG;EAC7C,MAAM,SAAS,QAAQ,IAAI,IAAI;EAC/B,IAAI,QAAQ;GACR,MAAM,UAAU,YAAY,OAAO,OAAO,OAAO,MAAM,IAAI,CAAC,CAAC,QAAQ,KAAI,MAAK,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC,KAAK,IAAI;GACxG,QAAQ,KAAK;IACT,MAAM;IACN,SAAS;IACT,YAAY;IACZ,UAAU;IACV,QACI,IAAI,OAAO,YAAY,KAAK,qBAAqB,QAAQ,yCACjC,OAAO,UAAU,KAAK;GACtD,CAAC;GACD,iBAAiB,MAAM,OAAO,MAAM,SAAS,SAAS,MAAM,SAAS,OAAO,OAAO,MAAM;GACzF;EACJ;EACA,MAAM,MAAM,SAAS;EACrB,IAAI,CAAC,KAAK;GACN,sBAAsB,MAAM,OAAO,MAAM,MAAM,SAAS,OAAO,OAAO,KAAK;GAC3E;EACJ;EACA,iBAAiB,MAAM,OAAO,MAAM,KAAK,MAAM,SAAS,OAAO,KAAK;CACxE;CAEA,KAAK,MAAM,CAAC,MAAM,SAAS,OAAO,QAAQ,QAAQ,GAAG;EACjD,IAAI,KAAK,SAAS,YAAY,IAAI,IAAI,GAAG;EACzC,wBAAwB,MAAM,QAAQ,OAAO,MAAM,MAAM,SAAS,OAAO,KAAK;CAClF;AACJ;AAEA,SAAS,sBACL,MACA,YACA,MACA,MACA,SACA,OACA,OACA,OACI;CACJ,MAAM,YAAY,YAAY,OAAO,OAAO,YAAY,MAAM,IAAI;CAClE,MAAM,WAAW,UAAU,QAAQ,SAAS;CAC5C,IAAI,uBAAuB,MAAM,YAAY,MAAM,MAAM,SAAS,KAAK,GAAG;CAC1E,IAAI,qBAAqB,MAAM,YAAY,MAAM,MAAM,WAAW,SAAS,OAAO,KAAK,GAAG;CAQ1F,IAAI,WAAW,IAAI,KAAK,YAAY,CAAC,OAAO;EACxC,QAAQ,KAAK;GACT,MAAM;GACN,SAAS;GACT,YAAY;GACZ,UAAU;GACV,QACI,IAAI,KAAK,sBAAsB,eAAa,UAAU,EAAE;GAG5D,QACI;EAER,CAAC;EACD;CACJ;CAEA,MAAM,OAAO,kBAAkB,SAAS;CACxC,MAAM,WAAW,WAAW,YAAY,MAAM,IAAI;CAClD,MAAM,OAAO,OACP,QAAQ,SACR,YAAY,sBAAsB,QAAQ,IACtC,6CAA6C,SAAS,mBAAmB,oCACzE;CACV,QAAQ,KAAK;EACT,MAAM;EACN,SAAS;EACT,YAAY;EACZ,UAAU;EACV,QAAQ,WAAW,IAAI,KAAK,WACtB,0BAA0B,KAAK,MAAM,KAAK,oBAAoB,eAAa,UAAU,EAAE,wCAEvF,OAAO,WAAW,IAAI,IAAI,aAAa,WAAW,aAAa,KAAK,MAAM,KAAK;CACzF,CAAC;AACL;;;;;;;;;;;AAYA,SAAS,qBACL,MACA,YACA,MACA,MACA,WACA,SACA,OACA,OACO;CACP,MAAM,QAAQ,eAAe,UAAU;CACvC,MAAM,UAAU,OAAO,OAAO,IAAI,KAAK;CACvC,MAAM,WAAW,UAAU,QAAQ,QAAO,WAAU,SAAS,IAAI,OAAO,MAAM,CAAC;CAC/E,IAAI,SAAS,WAAW,GAAG,OAAO;CAElC,KAAK,MAAM,UAAU,UAAU;EAC3B,MAAM,SAAS,OAAO,aAAa,IAAI,GAAG,MAAM,GAAG,OAAO,QAAQ;EAClE,MAAM,WAAW,aAAa,OAAO,IAAI;EACzC,IAAI,UAAU,CAAC,WAAW,UAAU,MAAM,GAAG;GACzC,QAAQ,KAAK;IACT,MAAM;IACN,SAAS;IACT,YAAY;IACZ,UAAU;IACV,QACI,IAAI,KAAK,sBAAsB,OAAO,OAAO,+BAA+B,OAAO,yCAC5C,KAAK,aAAa,SAAS;IAEtE,QACI,oBAAoB,OAAO,OAAO,2CAA2C,KAAK;GAE1F,CAAC;GACD,OAAO;EACX;CACJ;CACA,MAAM,UAAU,SAAS,KAAI,MAAK,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC,KAAK,IAAI;CAC5D,QAAQ,KAAK,WAAW,IAAI,KAAK,CAAC,QAC5B;EACE,MAAM;EACN,SAAS;EACT,YAAY;EACZ,UAAU;EACV,QACI,IAAI,KAAK,mBAAmB,QAAQ;EAExC,QAAQ;CACZ,IACE;EACE,MAAM;EACN,SAAS;EACT,YAAY;EACZ,UAAU;EACV,QACI,OAAO,WAAW,IAAI,IAAI,aAAa,WAAW,aAAa,KAAK,qBACjE,QAAQ;CACnB,CAAC;CACL,OAAO;AACX;;;;;;;AAQA,SAAS,uBACL,MACA,YACA,MACA,MACA,SACA,OACO;CACP,MAAM,WAAW,WAAW,YAAY,MAAM,IAAI;CAClD,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,sBAAsB,QAAQ,GAAG,OAAO;CACpE,IAAI;CACJ,IAAI;EACA,SAAS,SAAS,OAAO;CAC7B,QAAQ;EACJ,OAAO;CACX;CACA,MAAM,cAAc,SAAS,kBAAkB,MAAM,OAAO,MAAM,IAAI,KAAA;CACtE,IAAI,CAAC,eAAe,YAAY,QAAQ,MAAK,WAAU,OAAO,WAAW,SAAS,kBAAkB,GAAG,OAAO;CAC9G,QAAQ,KAAK;EACT,MAAM;EACN,SAAS;EACT,YAAY;EACZ,UAAU;EACV,QACI,IAAI,KAAK,iCAAiC,SAAS,mBAAmB,QAAQ,YAAY,UAAU;EAExG,QACI,SAAS,OAAQ,KAAK,iCAAiC,SAAS,mBAAmB;CAE3F,CAAC;CACD,OAAO;AACX;AAEA,SAAS,wBACL,MACA,QACA,OACA,MACA,MACA,SACA,OACA,OACI;CACJ,MAAM,YAAY,YAAY,OAAO,QAAQ,QAAQ,MAAM,IAAI;CAI/D,MAAM,aAAa,kBAAkB,OAAO,OAAO,KAAK;CACxD,MAAM,UAAqB,OAAO,SAC5B;EACE,SAAS,UAAU,QAAQ,QAAO,MAAK,CAAC,YAAY,QAAQ,MAAK,MAAK,EAAE,WAAW,EAAE,MAAM,CAAC;EAC5F,WAAW,UAAU,UAAU,QAAO,MAAK,CAAC,MAAM,MAAM,OAAO,MAAK,MAAK,EAAE,cAAc,EAAE,SAAS,CAAC;CACzG,IACE;EAAE,SAAS,CAAC,EAAE,QAAQ,kBAAkB,MAAM,IAAI,EAAE,CAAe;EAAG,WAAW,CAAC;CAAE;CAE1F,IAAI,QAAQ,QAAQ,WAAW,KAAK,QAAQ,UAAU,WAAW,GAAG;EAChE,QAAQ,KAAK;GACT,MAAM;GACN,SAAS;GACT,YAAY;GACZ,UAAU;GACV,QAAQ,UAAU,QAAQ,SAAS,UAAU,UAAU,SAAS,IAC1D,IAAI,KAAK,iBAAiB,kBAAkB,SAAS,EAAE,iDACvD,IAAI,KAAK;EACnB,CAAC;EACD;CACJ;CACA,MAAM,QAAQ,eAAe,MAAM;CACnC,MAAM,WAAW,QAAQ,QAAQ,KAAI,MAAK,uBAAuB,OAAO,EAAE,QAAQ,KAAK,CAAC,CAAC,CAAC,KAAK,EAAE;CACjG,QAAQ,KAAK;EACT,MAAM;EACN,SAAS;EACT,YAAY;EACZ,UAAU;EACV,QACI,IAAI,KAAK,kCAAkC,kBAAkB,OAAO,EAAE;EAC1E,QACI;EAEJ,YACI,GAAG,kBAAkB,OAAO,EAAE,2EACjB;EACjB,MAAM,CACF,GAAG,QAAQ,QAAQ,KAAI,MAAK,UAAU,YAAY,QAAQ,EAAE,MAAM,EAAE,MAAM,GAC1E,GAAG,QAAQ,UAAU,KAAI,MAAK,YAAY,EAAE,MAAM,MAAM,CAC5D,CAAC,CAAC,KAAK,IAAI;CACf,CAAC;AACL;AAEA,SAAS,iBACL,MACA,YACA,MACA,QACA,OACA,SACA,OACA,OAEA,aAAa,MACT;CAGJ,IAAI,OAAO,SAAS,cAAc,MAAM,SAAS,YAAY;EACzD,MAAM,eAAe,kBAAkB,YAAY,MAAM;EACzD,MAAM,cAAc,kBAAkB,MAAM,KAAK;EACjD,IAAI,iBAAiB,aACjB,QAAQ,KAAK;GACT,MAAM;GACN,SAAS;GACT,YAAY;GACZ,UAAU;GACV,QAAQ,IAAI,KAAK,yBAAyB,aAAa,QAAQ,YAAY;GAC3E,QACI;GAGJ,YACI,sBAAsB,aAAa,0BAA0B,YAAY,wFAEzE,uBAAuB,eAAe,UAAU,GAAG,cAAc,KAAK;GAC1E,MAAM,iBAAiB,YAAY,YAAY,YAAY;EAC/D,CAAC;CAET;CAEA,IAAI,aAAa,MAAM,MAAM,aAAa,KAAK,GAAG;EAC9C,MAAM,cAAc,kBAAkB,OAAO,QAAQ,UAAU;EAC/D,MAAM,aAAa,kBAAkB,OAAO,OAAO,UAAU;EAC7D,QAAQ,KAAK;GACT,MAAM;GACN,SAAS;GACT,YAAY;GACZ,UAAU;GACV,QAAQ,IAAI,KAAK,0CAA0C,YAAY,aAAa,UAAU;GAC9F,QAAQ,cAAc,aAAa,UAAU;EACjD,CAAC;EACD;CACJ;CAEA,IAAI,OAAO,SAAS,cAAc,MAAM,SAAS,YAAY;EACzD,MAAM,MAAM,WAAW,YAAY,MAAM,MAAM;EAC/C,MAAM,KAAK,WAAW,YAAY,MAAM,KAAK;EAC7C,IAAI,OAAO,MAAM,IAAI,SAAS,GAAG,MAAM;GACnC,QAAQ,KAAK;IACT,MAAM;IACN,SAAS;IACT,YAAY;IACZ,UAAU;IACV,QACI,IAAI,KAAK,mBAAmB,IAAI,KAAK,QAAQ,GAAG,KAAK;IAEzD,QACI;IAEJ,YACI,4BAA4B,IAAI,KAAK,sBAAsB,GAAG,KAAK;IAEvE,MAAM,YAAY,KAAK,GAAG,KAAK;GACnC,CAAC;GACD;EACJ;EACA,IAAI,uBAAuB,MAAM,YAAY,MAAM,OAAO,SAAS,KAAK,GAAG;CAC/E;CAEA,MAAM,cAAc,kBAAkB,MAAM,KAAK;CACjD,IAAI,CAAC,WAAW,MAAM,KAAK,WAAW,KAAK,GAAG;EAG1C,MAAM,QAAQ,aAAa,YAAY,KAAK;EAC5C,QAAQ,KAAK,QACP;GACE,MAAM;GACN,SAAS;GACT,YAAY;GACZ,UAAU;GACV,QACI,IAAI,KAAK,wCAAwC,YAAY,YACzD,eAAa,UAAU,EAAE;EACrC,IACE;GACE,MAAM;GACN,SAAS;GACT,YAAY;GACZ,UAAU;GACV,QACI,IAAI,KAAK,0BAA0B,eAAa,UAAU,EAAE;GAGhE,QACI,2CAA2C,YAAY;EAE/D,CAAC;CACT;CAEA,IAAI,WAAW,MAAM,KAAK,CAAC,WAAW,KAAK,GAKvC,QAAQ,KAAK;EACT,MAAM;EACN,SAAS;EACT,YAAY;EACZ,UAAU;EACV,QAAQ,IAAI,KAAK,iDAAiD,YAAY;CAClF,CAAC;CAGL,aAAa,MAAM,MAAM,QAAQ,OAAO,SAAS,KAAK;AAC1D;;;;;;;;;;;AAYA,SAAS,gBACL,eACA,YACA,QACA,OACA,SACA,OACI;CACJ,IAAI,CAAC,UAAU,CAAC,OAAO;CACvB,MAAM,OAAO,WAAW,QAAQ;;CAEhC,MAAM,iBAAiB,aAAyC;EAC5D,IAAI,CAAC,UAAU,OAAO;EACtB,MAAM,MAAM,aAAa,aAAa,CAAC,CAAC,SAAS,EAAE;EACnD,MAAM,KAAK,aAAa,UAAU,CAAC,CAAC,SAAS,EAAE;EAC/C,OAAO,OAAO,MAAM,QAAQ,KAAK,QAAQ,IAAI,MAAM,GAAG,OAAO;CACjE;CACA,MAAM,QAAQ,WAA6C;EACvD,IAAI,eAAe,SAAS,MAAM,OAAO,QAAQ,GAAG;EACpD,QAAQ,KAAK;GAAE,GAAG;GAAQ,YAAY;EAAK,CAAC;CAChD;CACA,MAAM,SAAS,WAAuB,OAAO,OAAO,YAAY,OAAO;CASvE,IAAI,QAAQ,MAAM,MAAM,QAAQ,KAAK,KAAK,CAAC,QAAQ,MAAK,MAAK,EAAE,eAAe,QAAQ,EAAE,SAAS,oBAAoB,GAAG;EACpH,MAAM,QAAQ,CACV,GAAG,MAAM,WAAW,QAAO,WAAU,CAAC,OAAO,WAAW,SAAS,MAAM,CAAC,GACxE,GAAG,OAAO,WAAW,QAAO,WAAU,CAAC,MAAM,WAAW,SAAS,MAAM,CAAC,CAC5E;EACA,MAAM,cAAc,MAAM,QAAQ,MAAK,MAAK,MAAM,SAAS,EAAE,MAAM,CAAC,KAAK,OAAO,QAAQ,MAAK,MAAK,MAAM,SAAS,EAAE,MAAM,CAAC;EAC1H,QAAQ,KAAK;GACT,MAAM;GACN,SAAS;GACT,YAAY;GACZ,UAAU,aAAa,OAAO;GAC9B,QAAQ,uBAAuB,MAAM,MAAM,YAAY,YAAY,QAAQ,KAAK;GAChF,QAAQ,cAAc,QAAQ,KAAK;EACvC,CAAC;CACL;CAEA,KAAK,MAAM,UAAU,MAAM,SAAS;EAChC,MAAM,MAAM,OAAO,QAAQ,MAAK,MAAK,EAAE,WAAW,OAAO,MAAM;EAC/D,IAAI,CAAC,KAAK;EACV,MAAM,WAAW,OAAO,OAAO,YAAY,IAAI,OAAO;EACtD,MAAM,QAAQ,IAAI,MAAM,MAAM,EAAE;EAEhC,IAAI,QAAQ,GAAG,MAAM,QAAQ,MAAM,GAAG;GAClC,KAAK;IACD,MAAM;IACN,SAAS;IACT;IACA,QAAQ,GAAG,MAAM,WAAW,cAAc,QAAQ,EAAE,UAAU,OAAO,OAAO,cACrE,SAAS,GAAG,EAAE,MAAM,SAAS,MAAM,EAAE;IAC5C,QACI;IAEJ,YACI,WAAW,OAAO,OAAO,oBAAoB,SAAS,GAAG,EAAE,6DAChC,SAAS,GAAG,EAAE;IAC7C,MAAM,UAAU,YAAY,YAAY,OAAO,MAAM,EAAE,SAAS,SAAS,GAAG;GAChF,CAAC;GACD;EACJ;EAIA,IAAI,OAAO,WAAW,SAAS,IAAI,MAAM,MAAM,MAAM,WAAW,SAAS,OAAO,MAAM,GAAG;GACrF,KAAK;IACD,MAAM;IACN,SAAS;IACT;IACA,QAAQ,GAAG,MAAM,oBAAoB,OAAO,OAAO,+BAA+B,YAAY,QAAQ,KAAK;IAC3G,QAAQ,cAAc,QAAQ,KAAK;GACvC,CAAC;GACD;EACJ;EACA,IAAI,IAAI,WAAW,OAAO,QACtB,KAAK;GACD,MAAM;GACN,SAAS;GACT;GACA,QAAQ,OAAO,SACT,GAAG,MAAM,4CAA4C,OAAO,OAAO,0CACnE,GAAG,MAAM,mDAAmD,OAAO,OAAO;GAChF,QAAQ,OAAO,SACT,gGACI,MAAM,OAAO,KAAK,MAAM,MAAM,iBAAiB,OAAO,OAAO,sBACjE,wCAAwC,MAAM,OAAO,KAAK,MAAM,MAAM;GAC5E,YAAY,OAAO,SACb,IAAI,OAAO,OAAO,+DAClB,6BAA6B,OAAO,OAAO;GACjD,MAAM,GAAG,YAAY,YAAY,OAAO,MAAM,EAAE;EACpD,CAAC;EAEL,MAAM,QAAQ,eAAe,IAAI,UAAU;EAC3C,MAAM,OAAO,eAAe,OAAO,UAAU;EAC7C,IAAI,UAAU,MACV,IAAI,CAAC,OACD,KAAK;GACD,MAAM;GACN,SAAS;GACT;GACA,QACI,GAAG,MAAM,0BAA0B,OAAO,OAAO,eAAe,KAAK;EAE7E,CAAC;OAED,KAAK;GACD,MAAM;GACN,SAAS;GACT;GACA,QAAQ,OACF,GAAG,MAAM,gCAAgC,MAAM,MAAM,KAAK,wGAE1D,GAAG,MAAM,8DAA8D,OAAO,OAAO,KACjF,MAAM;GAChB,QACI;GAEJ,YAAY,sBAAsB,OAAO,OAAO,oBAAoB,MAAM;GAC1E,MAAM,kBAAkB,YAAY,YAAY,OAAO,MAAM,EAAE;EACnE,CAAC;EAGT,MAAM,aAAa,YAAY,GAAG;EAClC,MAAM,YAAY,YAAY,MAAM;EACpC,IAAI,eAAe,WAIf,KAAK;GACD,MAAM;GACN,SAAS;GACT;GACA,QAAQ,YACF,GAAG,MAAM,eAAe,UAAU,qBAAqB,OAAO,OAAO,4CACrE,GAAG,MAAM,8CAA8C,OAAO,OAAO;EAC/E,CAAC;CAET;CAEA,KAAK,MAAM,UAAU,OAAO,SAAS;EACjC,IAAI,MAAM,QAAQ,MAAK,MAAK,EAAE,WAAW,OAAO,MAAM,GAAG;EACzD,MAAM,WAAW,OAAO,OAAO;EAG/B,IAAI,eAAe,SAAS,MAAM,QAAQ,GAAG;EAC7C,KAAK;GACD,MAAM,OAAO,OAAO,SAAS,aAAa,oBAAoB;GAC9D,SAAS;GACT;GACA,QAAQ,OAAO,OAAO,SAAS,aACzB,IAAI,MAAM,MAAM,EAAE,yCAAyC,OAAO,OAAO,mFAEzE,WAAW,OAAO,OAAO,0BAA0B,KAAK;GAC9D,QACI;GAEJ,YACI,WAAW,OAAO,OAAO,8DACzB,uBAAuB,MAAM,WAAW,OAAO,QAAQ,KAAK;GAChE,MAAM,UAAU,YAAY,YAAY,OAAO,MAAM,EAAE;EAC3D,CAAC;CACL;CAEA,KAAK,MAAM,SAAS,MAAM,SAAS;EAC/B,IAAI,OAAO,QAAQ,MAAK,MAAK,EAAE,cAAc,MAAM,SAAS,GAAG;EAC/D,KAAK;GACD,MAAM;GACN,SAAS;GACT,QAAQ,WAAW,MAAM,SAAS,YAAY,GAAG,SAAS,MAAM,UAAU,QAAQ,MAAM,UAAU;EACtG,CAAC;CACL;CACA,KAAK,MAAM,SAAS,OAAO,SAAS;EAChC,IAAI,MAAM,QAAQ,MAAK,MAAK,EAAE,cAAc,MAAM,SAAS,GAAG;EAC9D,KAAK;GACD,MAAM;GACN,SAAS,MAAM,SAAS,oBAAoB;GAC5C,QAAQ,MAAM,SACR,iBAAiB,MAAM,UAAU,wEACjC,UAAU,MAAM,UAAU;GAChC,QAAQ,MAAM,SAAS,uCAAuC,MAAM,OAAO,KAAK,MAAM,UAAU,MAAM,KAAA;GACtG,YAAY,MAAM,SAAS,UAAU,MAAM,UAAU,0CAA0C,KAAA;GAC/F,MAAM,MAAM,SAAS,SAAS,MAAM,UAAU,SAAS,KAAA;EAC3D,CAAC;CACL;CACA,KAAK,MAAM,WAAW,OAAO,UAAU;EACnC,IAAI,MAAM,SAAS,MAAK,MAAK,EAAE,SAAS,QAAQ,IAAI,GAAG;EACvD,KAAK;GACD,MAAM;GACN,SAAS;GACT,UAAU,OAAO,QAAQ,MAAK,MAAK,EAAE,WAAW,QAAQ,MAAM,CAAC,EAAE,OAAO;GACxE,QACI,IAAI,QAAQ,OAAO,iDAAiD,QAAQ,KAAK;GAErF,QAAQ,yCAAyC,QAAQ,KAAK,QAAQ,MAAM,OAAO,KAAK,MAAM,MAAM;GACpG,YAAY,YAAY,QAAQ,KAAK,8BAA8B,QAAQ,OAAO;GAClF,MAAM,WAAW,QAAQ,KAAK;EAClC,CAAC;CACL;CACA,KAAK,MAAM,WAAW,MAAM,UAAU;EAClC,IAAI,OAAO,SAAS,MAAK,MAAK,EAAE,SAAS,QAAQ,IAAI,GAAG;EACxD,KAAK;GACD,MAAM;GACN,SAAS;GACT,UAAU,MAAM,QAAQ,MAAK,MAAK,EAAE,WAAW,QAAQ,MAAM,CAAC,EAAE,OAAO;GACvE,QAAQ,IAAI,QAAQ,OAAO,6DAA6D,QAAQ,KAAK;EACzG,CAAC;CACL;AACJ;;;;;;AAOA,SAAS,kBACL,QACA,OACA,UACA,MACA,SACI;CACJ,MAAM,aAAa,UAAkB,QAAQ,MAAK,WAAU,OAAO,OAAO,SAAS,IAAI,MAAM,EAAE,CAAC;CAChG,KAAK,MAAM,SAAS,OAAO,QAAQ;EAC/B,IAAI,MAAM,SAAS,cAAc,MAAM,OAAO,MAAK,MAAK,EAAE,cAAc,MAAM,SAAS,GAAG;EAC1F,MAAM,QAAQ,MAAM,kBAAkB,CAAC;EAEvC,IAAI,MAAM,SAAS,KAAK,MAAM,OAAM,SAAQ,CAAC,KAAK,IAAI,IAAI,CAAC,GAAG;EAC9D,IAAI,UAAU,MAAM,SAAS,GAAG;EAChC,QAAQ,KAAK;GACT,MAAM;GACN,SAAS;GACT,YAAY,MAAM,MAAM;GACxB,QAAQ,mBAAmB,MAAM,UAAU;GAC3C,QAAQ;GACR,YAAY,mBAAmB,MAAM,UAAU;GAC/C,MAAM,YAAY,MAAM,MAAM;EAClC,CAAC;CACL;CACA,KAAK,MAAM,SAAS,MAAM,QAAQ;EAC9B,IAAI,MAAM,SAAS,cAAc,OAAO,OAAO,MAAK,MAAK,EAAE,cAAc,MAAM,SAAS,GAAG;EAC3F,MAAM,QAAQ,MAAM,kBAAkB,CAAC;EACvC,IAAI,MAAM,SAAS,KAAK,MAAM,OAAM,SAAQ,CAAC,SAAS,IAAI,IAAI,CAAC,GAAG;EAClE,IAAI,UAAU,MAAM,SAAS,GAAG;EAChC,QAAQ,KAAK;GACT,MAAM;GACN,SAAS;GACT,YAAY,MAAM,MAAM;GACxB,QAAQ,2BAA2B,MAAM,UAAU;EACvD,CAAC;CACL;AACJ;;;;;;;;;;;;;;AAeA,SAAS,sBAAsB,QAAoB,OAAmB,SAA+B;CACjG,MAAM,aAAa,UAAqB,KAAK,UACzC,MAAM,SAAS,KAAK,EAAE,MAAM,WAAW,MAAM,OAAO,OAAO,iBACtD;EAAE;EAAM;EAAW;EAAM;EAAO;EAAO;CAAU,EAAE,CAAC,CACpD,MAAM,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC,CACpD;CACA,KAAK,MAAM,SAAS,MAAM,QAAQ;EAC9B,IAAI,MAAM,SAAS,gBAAgB,CAAC,MAAM,MAAM;EAChD,MAAM,MAAM,OAAO,OAAO,MAAK,MAAK,EAAE,cAAc,MAAM,aAAa,EAAE,SAAS,YAAY;EAC9F,IAAI,CAAC,OAAO,UAAU,GAAG,MAAM,UAAU,KAAK,GAAG;EACjD,MAAM,SAAS,SAAoB,IAAI,IAAI,KAAK,SAAS,KAAI,MAAK,EAAE,IAAI,CAAC;EACzE,MAAM,QAAQ,CAAC,GAAG,MAAM,KAAK,CAAC,CAAC,CAAC,QAAO,SAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC;EACtE,MAAM,UAAU,CAAC,GAAG,MAAM,GAAG,CAAC,CAAC,CAAC,QAAO,SAAQ,CAAC,MAAM,KAAK,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC;EACxE,MAAM,SAAS,CACX,QAAQ,IAAI,GAAG,MAAM,GAAG,UAAU,IAAI,WAAW,WAAW,UAAU,IACtE,UAAU,IAAI,GAAG,QAAQ,YAAY,EACzC,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,IAAI;EAC3B,QAAQ,KAAK;GACT,MAAM;GACN,SAAS;GACT,YAAY,MAAM;GAClB,QACI,2BAA2B,MAAM,UAAU,WAAW,SAAS,KAAK,OAAO,KAAK,GAAG;EAG3F,CAAC;CACL;AACJ;AAEA,SAAS,aACL,MACA,MACA,QACA,OACA,SACA,OACI;CACJ,MAAM,YAAY,aAAa,MAAM;CACrC,MAAM,YAAY,aAAa,KAAK;CACpC,IAAI,CAAC,aAAa,CAAC,WAAW;CAE9B,MAAM,QAAQ,UAAU,QAAO,UAAS,CAAC,UAAU,SAAS,KAAK,CAAC;CAClE,MAAM,UAAU,UAAU,QAAO,UAAS,CAAC,UAAU,SAAS,KAAK,CAAC;CAEpE,IAAI,MAAM,SAAS,GAAG;EAMlB,MAAM,cAAc,OAAO,eAAe,KAAA;EAC1C,QAAQ,KAAK,cACP;GACE,MAAM;GACN,SAAS;GACT,YAAY;GACZ,UAAU;GACV,QAAQ,IAAI,KAAK,UAAU,MAAM,KAAI,MAAK,IAAI,EAAE,EAAE,CAAC,CAAC,KAAK,IAAI,EAAE;EACnE,IACE;GACE,MAAM;GACN,SAAS;GACT,YAAY;GACZ,UAAU;GACV,QACI,IAAI,KAAK,UAAU,MAAM,KAAI,MAAK,IAAI,EAAE,EAAE,CAAC,CAAC,KAAK,IAAI,EAAE;GAE3D,QAAQ;EACZ,CAAC;CACT;CAEA,IAAI,QAAQ,SAAS,GACjB,QAAQ,KAAK;EACT,MAAM;EACN,SAAS;EACT,YAAY;EACZ,UAAU;EACV,QAAQ,IAAI,KAAK,UAAU,QAAQ,KAAI,MAAK,IAAI,EAAE,EAAE,CAAC,CAAC,KAAK,IAAI,EAAE;EACjE,QACI;CAER,CAAC;AAET;;;;;;;AAQA,SAAgB,wBACZ,QACA,OACA,gBACQ;CACR,MAAM,aAAa,QAAQ,MAAM;CACjC,MAAM,YAAY,QAAQ,KAAK;CAC/B,IAAI,sBAAsB,SAAS,qBAAqB,OAAO,OAAO,CAAC;CACvE,MAAM,aAAuB,CAAC;CAC9B,KAAK,MAAM,SAAS,UAAU,QAAQ;EAClC,MAAM,MAAM,WAAW,OAAO,MAAK,MAAK,EAAE,cAAc,MAAM,SAAS;EACvE,IAAI,CAAC,KAAK;EACV,KAAK,MAAM,UAAU,MAAM,SAAS;GAChC,MAAM,MAAM,IAAI,QAAQ,MAAK,MAAK,EAAE,WAAW,OAAO,MAAM;GAC5D,IAAI,CAAC,OAAO,CAAC,eAAe,IAAI,MAAM,SAAS,CAAC,EAAE,IAAI,OAAO,MAAM,GAAG;GACtE,IAAI,IAAI,SAAS,SAAS,cAAc,OAAO,SAAS,SAAS,cAAc,OAAO,WAAW;GACjG,MAAM,OAAO,YAAY,GAAG;GAC5B,MAAM,KAAK,YAAY,MAAM;GAC7B,IAAI,SAAS,IAAI;GACjB,MAAM,SAAS,gBAAgB,MAAM,OAAO,KAAK,MAAM,MAAM,kBAAkB,OAAO,OAAO;GAC7F,WAAW,KAAK,KAAK,GAAG,OAAO,eAAe,GAAG,KAAK,GAAG,OAAO,eAAe;EACnF;CACJ;CACA,OAAO;AACX;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACnmCA,IAAa,oBAAb,cAAuC,MAAM;CACH;CAAtC,YAAY,SAAiB,YAAwC;EACjE,MAAM,OAAO;EADqB,KAAA,aAAA;EAElC,KAAK,OAAO;CAChB;AACJ;;AAGA,IAAM,iBAAiB;CAAE,wBAAQ,IAAI,IAAyB;CAAG,uBAAO,IAAI,IAAY;AAAE;;;;;;AA2C1F,SAAgB,wBAAwB,YAAuC;CAE3E,MAAM,OADW,WAAW,QAAQ,QAAO,WAAU,OAAO,YAAY,MAC3D,CAAA,CAAS,KAAI,WAAU,OAAO,IAAI,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,IAAI;CAK1E,OAAO,kBAAkB,GAJN,UAAU,UAAU,EAAE,kBAAkB,QAAQ,4BAA4B,GAI9D,MAHpB,WAAW,QAAQ,KAAI,WAChC,KAAK,OAAO,SAAS,OAAO,cAAc,OAAO,YAAY,SAAS,6BAA6B,OAAO,eAAe,IAC7H,CAAC,CAAC,KAAK,IACgC,EAAK;AAChD;;AAGA,SAAS,UAAU,YAAuC;CACtD,MAAM,EAAE,YAAY;CACpB,MAAM,cAAc,CAAC,GAAG,IAAI,IAAI,QAAQ,KAAI,WAAU,OAAO,UAAU,CAAC,CAAC,CAAC,CAAC,KAAK;CAChF,MAAM,OAAO,QAAQ,WAAW,IAAI,QAAQ,KAAK,KAAA;CACjD,IAAI,MAAM,SAAS,kBAAkB,OAAO,WAAW,KAAK,WAAW;CACvE,IAAI,MAAM,SAAS,qBAAqB,OAAO,cAAc,KAAK,WAAW;CAC7E,IAAI,MAAM,SAAS,gBAAgB,OAAO,OAAO,KAAK,SAAS,MAAM,KAAK;CAC1E,IAAI,MAAM,SAAS,mBAAmB,OAAO,UAAU,KAAK,SAAS,QAAQ,KAAK;CAClF,IAAI,QAAQ,SAAS,KAAK,QAAQ,OAAM,MAAK,EAAE,SAAS,uBAAuB,GAC3E,OAAO,gCAAgC,YAAY,KAAK,IAAI;CAEhE,IAAI,YAAY,WAAW,GAAG,OAAO,GAAG,QAAQ,OAAO,gBAAgB,YAAY;CACnF,OAAO,GAAG,QAAQ,OAAO,oBAAoB,YAAY,OAAO;AACpE;;AAGA,SAAgB,cAAc,YAAuC;CACjE,MAAM,EAAE,YAAY;CACpB,IAAI,QAAQ,WAAW,GAAG,OAAO;CACjC,MAAM,OAAO,QAAQ,KAAI,WAAU,KAAK,OAAO,QAAQ,CAAC,CAAC,KAAK,IAAI;CAClE,OAAO,iBAAiB,UAAU,UAAU,EAAE,MAAM,KAAK;AAC7D;;;;;;;;;AAUA,eAAsB,qBAAqB,OAAiD;CACxF,MAAM,QAAQ;EAAE,GAAG;EAAsB,GAAG,MAAM;CAAM;CACxD,MAAM,WAAW,MAAM,YAAY,QAAQ;CAI3C,MAAM,UAAyB,EAAE,aAAa,WAAW;CACzD,MAAM,aAAa,0BAA0B,MAAM,QAAQ,MAAM,OAAO,MAAM,QAAQ;CAEtF,IAAI,MAAM,YAAY;EAGlB,MAAM,YAAY,WAAW,QAAQ,QAAO,WAAU,OAAO,YAAY,UAAU,CAAC,OAAO,UAAU;EACrG,IAAI,UAAU,SAAS,GACnB,MAAM,IAAI,kBACN,oEACA,UAAU,KAAI,WAAU,OAAO,OAAO,SAAS,OAAO,SAAS,SAAS,OAAO,WAAW,IAAI,CAAC,CAAC,KAAK,IAAI,GACzG,UACJ;EAIJ,OAAO;GACH,OAAO,CAAC,GAAI,MAAM,eAAe,CAAC,GAAI;IAAE,MAAM,MAAM;IAAY,UAAU,eAAe,MAAM,KAAK;GAAE,CAAC;GACvG,YAAY,CAAC;GACb;GACA,SAAS,wBAAwB,UAAU;GAC3C,qBAAqB,CAAC;EAC1B;CACJ;CAEA,IAAI,CAAC,WAAW,YAEZ,MAAM,IAAI,kBACN,2DAFa,WAAW,QAAQ,QAAO,WAAU,OAAO,YAAY,MAGpE,CAAA,CAAS,KAAI,WACT,OAAO,OAAO,SAAS,OAAO,SAAS,SAAS,OAAO,WAAW,IACtE,CAAC,CAAC,KAAK,IAAI,GACX,UACJ;CAQJ,MAAM,YAAgC,CAClC;EAAE,MAAM,MAAM;EAAY,UAAU,eAAe,MAAM,KAAK;CAAE,CACpE;CAMA,MAAM,WAAW,2BAA2B,MAAM,QAAQ,UAAU,OAAO;CAC3E,MAAM,OAAO,2BAA2B,MAAM,OAAO,UAAU,OAAO;CACtE,MAAM,UAAU,IAAI,IAAI,SAAS,UAAU;CAC3C,MAAM,aAAa,KAAK,WAAW,QAAO,cAAa,CAAC,QAAQ,IAAI,SAAS,CAAC;CAG9E,KAAK,MAAM,aAAa,wBAAwB,MAAM,QAAQ,MAAM,OAAO,SAAS,MAAM,GACtF,IAAI,CAAC,WAAW,SAAS,SAAS,GAAG,WAAW,KAAK,SAAS;CAGlE,OAAO;EACH,OAAO,CAAC,GAAI,MAAM,eAAe,CAAC,GAAI,GAAG,SAAS;EAClD;EACA;EACA,SAAS,cAAc,UAAU;EAIjC,qBAAqB,KAAK;CAC9B;AACJ;;;;;;;;;;;;AC1OA,IAAM,YAAN,MAAuC;CAiBN;;;;;;CAX7B;CAEA,aAA0C,CAAC;CAC3C;;CAGA;CACA,UAAkB;CAClB,mBAAqE,KAAA;CACrE,kBAAkD,KAAA;CAElD,YAAY,MAA+B;EAAd,KAAA,OAAA;EACzB,KAAK,OAAO,IAAI,SAAS,SAAS,WAAW;GACzC,KAAK,aAAa;GAClB,KAAK,YAAY;EACrB,CAAC;CACL;CAEA,OAAO,YAA8B;EACjC,WAAW,MAAM,KAAK,IAAI;CAC9B;CAEA,qBAAqB,SAAwC;EACzD,KAAK,OAAO;GACR,QAAQ,QAAQ,OAAO,KAAK,EAAE,MAAM,SAAS,UAAU,kBAAkB;IAAE;IAAM;IAAS;IAAU;GAAW,EAAE;GACjH,MAAM,CAAC;EACX;EACA,KAAK,WAAW,KAAK,KAAK,IAAI;CAClC;CAEA,cAAc,SAA8C;EACxD,MAAM,YAAY,KAAK;EACvB,IAAI,CAAC,WAAW;EAChB,MAAM,MAAqC,CAAC;EAC5C,UAAU,OAAO,SAAS,OAAO,UAAU;GACvC,IAAI,MAAM,QAAQ,QAAQ,OAAO,UAAU;EAC/C,CAAC;EACD,UAAU,KAAK,KAAK,GAAG;CAC3B;CAEA,sBAAsB,SAAiC;EACnD,MAAM,YAAY,KAAK,QAAQ;GAAE,QAAQ,CAAC;GAAG,MAAM,CAAC;EAAE;EACtD,IAAI,CAAC,KAAK,MAAM,KAAK,WAAW,KAAK,SAAS;EAC9C,KAAK,OAAO,KAAA;EACZ,UAAU,MAAM,QAAQ;CAC5B;CAEA,mBAAyB,CAEzB;CAEA,wBAA8B,CAE9B;;;;;CAMA,qBAAqB,YAA8B;EAC/C,IAAI,kBAAkB,cAAc,OAAO,WAAW,iBAAiB,YACnE,WAAW,aAAa,kDAAkD;CAElF;CAEA,iBAAuB,CAEvB;CAEA,YAAY,OAAoB;EAC5B,KAAK,OAAO,KAAK;CACrB;CAEA,sBAA4B;EACxB,KAAK,OAAO,IAAI;CACpB;CAEA,OAAe,OAA2B;EACtC,IAAI,KAAK,SAAS;EAClB,KAAK,UAAU;EACf,KAAK,WAAW,KAAK;EACrB,IAAI,OAAO,KAAK,UAAU,KAAK;OAC1B,KAAK,WAAW,KAAK,UAAU;CACxC;AACJ;;;;;;;AAQA,SAAgB,gBAAgB,KAAkE;CAC9F,IAAI,CAAC,KAAK,OAAO,CAAC;CAClB,MAAM,UAAU,uEAAuE,KAAK,GAAG;CAC/F,IAAI,SAAS,OAAO;EAAE,SAAS,QAAQ;EAAI,UAAU,OAAO,QAAQ,EAAE;CAAE;CACxE,OAAO,EAAE,SAAS,IAAI;AAC1B;;AAGA,SAAS,aAAa,SAAyC;CAC3D,QAAQ,SAAR;EACI,KAAK,KAAK,OAAO;EACjB,KAAK,KAAK,OAAO;EACjB,KAAK,KAAK,OAAO;EACjB,KAAK,KAAK,OAAO;EACjB,KAAK,KAAK,OAAO;EACjB,SAAS,OAAO;CACpB;AACJ;;;;;;;;;AAUA,eAAe,gBAAgB,YAAwB,QAAwF;CAC3I,MAAM,UAAU;EAAE,SAAS,OAAO,KAAI,WAAU,EAAE,MAAM,MAAM,KAAK,EAAE;EAAG,QAAQ,CAAC;CAAE;CACnF,IAAI,OAAO,WAAW,GAAG,OAAO;CAChC,IAAI;EACA,MAAM,WAAW,CAAC,GAAG,IAAI,IAAI,OAAO,KAAI,UAAS,MAAM,UAAU,CAAC,CAAC;EACnE,MAAM,YAAY,CAAC,GAAG,IAAI,IAAI,OAAO,QAAO,UAAS,MAAM,YAAY,CAAC,CAAC,CAAC,KAAI,UAAS,MAAM,OAAO,CAAC,CAAC;EAEtG,MAAM,QAAQ,MAAM,WAAW,MAC3B,mGACA,CAAC,QAAQ,CACb;EACA,MAAM,YAAY,IAAI,IAAI,MAAM,KAAK,KAAI,QAAO,CAAC,OAAO,IAAI,GAAG,GAAG,IAAI,IAAI,CAAC,CAAC;EAE5E,MAAM,YAAY,UAAU,WAAW,IAAI,CAAC,KAAK,MAAM,WAAW,MAQ9D;;;;;;;;;6CAUA,CAAC,SAAS,CACd,EAAA,CAAG;EACH,MAAM,aAAa,UAAU,WAAW,IAAI,CAAC,KAAK,MAAM,WAAW,MAC/D;;oFAGA,CAAC,SAAS,CACd,EAAA,CAAG;EAEH,MAAM,gBAAgB,IAAI,IAAI,UAAU,KAAI,aAAY,CAAC,OAAO,SAAS,GAAG,GAAG,QAAQ,CAAC,CAAC;EACzF,MAAM,gBAAgB,IAAI,IAAI,WAAW,KAAI,cAAa,CAAC,GAAG,UAAU,IAAI,GAAG,UAAU,UAAU,UAAU,IAAI,CAAC,CAAC;EAoBnH,OAAO;GAAE,SAlBO,OAAO,KAAK,UAA2B;IACnD,MAAM,SAA0B,EAAE,MAAM,MAAM,KAAK;IACnD,MAAM,OAAO,UAAU,IAAI,MAAM,UAAU;IAC3C,IAAI,MAAM,OAAO,OAAO;IACxB,MAAM,WAAW,MAAM,YAAY,IAAI,cAAc,IAAI,MAAM,OAAO,IAAI,KAAA;IAC1E,MAAM,eAAe,cAAc,IAAI,GAAG,MAAM,QAAQ,GAAG,MAAM,UAAU;IAC3E,IAAI,YAAY,cACZ,OAAO,SAAS;KAAE,QAAQ,SAAS;KAAQ,OAAO,SAAS;KAAO,QAAQ;IAAa;IAE3F,OAAO;GACX,CAQS;GAAS,QAPH,UAAU,KAAK,cAA8B;IACxD,QAAQ,SAAS;IACjB,OAAO,SAAS;IAChB,MAAM,aAAa,SAAS,OAAO;IACnC,YAAY,SAAS,eAAe,CAAC;IACrC,eAAe,SAAS;GAC5B,EACkB;EAAO;CAC7B,SAAS,OAAgB;EACrB,OAAO,KAAK,wFAAwF,EAAE,MAAM,CAAC;EAC7G,OAAO;CACX;AACJ;;;;;;;AAQA,IAAa,2BAA2B,CAAC,qCAAqC,WAAW;;;;;;AAOzF,SAAgB,kBAAkB,YAAmC;CACjE,IAAI,EAAE,0BAA0B,eAAe,OAAO,WAAW,yBAAyB,YAAY,OAAO;CAC7G,MAAM,SAAkB,WAAW,qBAAqB;CACxD,OAAO,OAAO,WAAW,WAAW,SAAS;AACjD;;;;;;;;;AAUA,IAAa,8BAAb,cAAiD,MAAM;CACnD,OAAgB;CAEhB,cAAc;EACV,MACI,yPAGJ;EACA,KAAK,OAAO;CAChB;AACJ;;;;;;;;;;;AAuBA,eAAsB,aAClB,MACA,SACA,MACA,YACU;CACV,MAAM,aAAa,MAAM,KAAK,QAAQ;CACtC,IAAI,WAAW;CACf,IAAI;EACA,IAAI;EACJ,IAAI;GACA,MAAM,QAAQ,aAAa,UAAU;GACrC,UAAU,EAAE,OAAO,MAAM,KAAK,UAAU,EAAE;EAC9C,SAAS,OAAgB;GACrB,UAAU,EAAE,MAAM;GAQlB,MAAM,WAAW,MAAM,UAAU,CAAC,CAAC,YAAY,KAAA,CAAS;EAC5D;EAEA,IAAI,WAAW;EACf,MAAM,SAAS,kBAAkB,UAAU;EAC3C,IAAI,WAAW,OAAO,WAAW,KAAK;GAClC,WAAW;GACX,IAAI;IACA,MAAM,WAAW,MAAM,UAAU;GACrC,QAAQ;IACJ,WAAW;GACf;EACJ;EACA,IAAI,UACA,IAAI;GACA,KAAK,MAAM,aAAa,0BAA0B,MAAM,WAAW,MAAM,SAAS;EACtF,QAAQ;GACJ,WAAW;EACf;EAGJ,IAAI,WAAW,SAAS;GAIpB,IAAI,UAAU;IACV,MAAM,WAAW,aAAa,QAAQ,KAAK;IAC3C,IAAI,UAAU,SAAS,UAAU,GAAG,SAAS,QAAQ;GACzD;GACA,MAAM,QAAQ;EAClB;EACA,IAAI,UAAU,MAAM,IAAI,4BAA4B;EACpD,OAAO,cAAc,WAAW,MAAM,WAAW,YAAY,QAAQ,KAAK,IAAI,QAAQ;CAC1F,UAAU;EACN,WAAW,QAAQ,WAAW,KAAA,IAAY,IAAI;CAClD;AACJ;;AAGA,SAAS,aAAa,OAAmC;CACrD,IAAI;CACJ,KAAK,IAAI,UAAmB,OAAO,mBAAmB,OAAO,UAAU,QAAQ,OAAO,UAAU;CAChG,OAAO;AACX;;AAGA,IAAM,cAAc;;;;;;;AAQpB,eAAsB,mBAAmB,MAAY,KAAa,UAA6B,CAAC,GAA6B;CACzH,MAAM,UAA6B,CAAC;CACpC,MAAM,MAAM,MAAM,aAAa,MAAM,SAAS,OAAO,eAAe;EAChE,MAAM,UAAU,WAAoD;GAChE,IAAI,QAAQ,SAAS,aAAa,QAAQ,KAAK;IAAE,UAAU,OAAO,YAAY;IAAU,SAAS,OAAO,WAAW;GAAG,CAAC;EAC3H;EACA,WAAW,GAAG,UAAU,MAAM;EAC9B,IAAI;GACA,MAAM,SAAS,IAAI,UAAU,GAAG;GAChC,WAAW,MAAM,MAAM;GACvB,MAAM,aAAa,MAAM,OAAO;GAChC,MAAM,OAAyB,WAAW,WAAW,SAAS,MAAM;IAAE,QAAQ,CAAC;IAAG,MAAM,CAAC;GAAE;GAC3F,OAAO;IAAE;IAAM,WAAW;KAAE,SAAS,KAAK,OAAO,KAAK,WAA4B,EAAE,MAAM,MAAM,KAAK,EAAE;KAAG,QAAQ,CAAC;IAAsB;GAAE;EAC/I,UAAU;GACN,WAAW,eAAe,UAAU,MAAM;EAC9C;CACJ,GAAG,OAAO,YAAY,WAAW;EAAE,GAAG;EAAO,WAAW,MAAM,gBAAgB,YAAY,MAAM,KAAK,MAAM;CAAE,EAAE;CAE/G,OAAO;EACH,MAAM,IAAI,KAAK;EACf,SAAS,IAAI,UAAU;EACvB,QAAQ,IAAI,UAAU;EACtB,GAAG,gBAAgB,IAAI,KAAK,GAAG;EAC/B;CACJ;AACJ;;;;;;AAOA,SAAgB,wBAAwB,MAAkD;CACtF,MAAM,QAAQ,KAAK,SAAS,IAAI,OAAO,KAAK,KAAK,EAAE,IAAI,CAAC;CACxD,OAAO;EACH,MAAM,KAAK,KAAI,QAAO,OAAO,YAAY,OAAO,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,KAAK,WAAW,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC;EAC9G,SAAS,MAAM,KAAI,UAAS,EAAE,KAAK,EAAE;EACrC,QAAQ,CAAC;EACT,SAAS,CAAC;CACd;AACJ;AAEA,SAAS,YAAY,OAA+B;CAChD,IAAI,UAAU,QAAQ,UAAU,KAAA,GAAW,OAAO;CAClD,IAAI,OAAO,UAAU,UAAU,OAAO;CACtC,IAAI,iBAAiB,MAAM,OAAO,MAAM,YAAY;CACpD,IAAI,iBAAiB,YAAY,OAAO,MAAM,OAAO,KAAK,KAAK,CAAC,CAAC,SAAS,KAAK;CAC/E,IAAI,OAAO,UAAU,UAAU,OAAO,KAAK,UAAU,KAAK;CAC1D,OAAO,OAAO,KAAK;AACvB;;;;;;;;;;;;;;;;AClWA,SAAgB,0BAAmC;CAC/C,OAAO,gBAAgB,QAAQ,IAAI,yBAAyB,MAAM;AACtE;;;;;;;;;;AAWA,SAAgB,uBAA2C;CACvD,OAAO,QAAQ,IAAI,+BAA+B,KAAK,KAAK,KAAA;AAChE;;;;;;;;;AAUA,SAAgB,iBAAiB,eAAgC;CAC7D,IAAI,CAAC,eAAe,OAAO;CAC3B,OAAO,wBAAwB,IAAI,mBAAmB;AAC1D;;AAGA,IAAa,mBAAmB;;;;;;;;;;;AAYhC,eAAsB,aAClB,OACA,eACA,UACe;CACf,IAAI,CAAC,wBAAwB,GAAG,OAAO;CACvC,IAAI;EAEA,MAAM,SAAQ,MADK,MAAM,WAAW,+BAA+B;GAAE;GAAU,gBAAgB;EAAK,CAAC,EAAA,CAClF,EAAE,EAAE;EACvB,OAAO,OAAO,UAAU,WAAW,QAAQ;CAC/C,QAAQ;EACJ,OAAO;CACX;AACJ;;;;;;;;AASA,IAAa,6BAAb,cAAgD,MAAM;CAClD,OAAgB;CAChB;;CAEA;CAEA,YAAY,MAAc,SAAmB;EACzC,MACI,+BAA+B,KAAK,4PAGQ,KAAK,4FAErD;EACA,KAAK,OAAO;EACZ,KAAK,OAAO;EACZ,KAAK,UAAU;CACnB;AACJ;;;;;;;;;;;;;;AAeA,SAAS,sBAA0D,QAAW,QAAkB;CAC5F,OAAO;EAAE,GAAG;EAAQ,GAAG;CAAO;AAClC;;;;;;;AAQA,SAAS,iBACL,aACA,cACA,aACA,YACA,YACQ;CACR,MAAM,OAAO,IAAI,IAAI,OAAO,KAAK,gBAAgB,CAAC,CAAC,CAAC;CACpD,MAAM,eAAe,IAAI,IAAI,OAAO,QAAQ,cAAc,CAAC,CAAC,CAAC,CACxD,QAAQ,GAAG,cAAe,UAAU,SAAS,UAAU,SAAS,cAAc,eACvE,UAAU,SAAS,YAAY,SAAS,cAAc,gBAAiB,CAAC,CAC/E,KAAK,CAAC,SAAS,GAAG,CAAC;CACxB,OAAO,OAAO,KAAK,WAAW,CAAC,CAAC,QAAO,QACnC,CAAC,KAAK,IAAI,GAAG,KACV,CAAC,aAAa,IAAI,GAAG,KACrB,YAAY,SAAS,WAAW,IAAI;AAC/C;;;;;;;;;;;AAYA,SAAS,8BACL,YACA,QACA,MACI;CACJ,MAAM,aAAa,kBAAkB,UAAU;CAC/C,IAAI,CAAC,YAAY;CACjB,MAAM,QAAQ,SAAS,WAAW;CAClC,IAAI,UAAU,KAAA,KAAa,UAAU,MAAM;CAC3C,MAAM,OAAO,YAAY,QAAQ;CACjC,MAAM,UAAU,IAAI,WAAW,MAAM,QAAQ,KAAK;CAElD,MAAM,SAAS,WAAW,SAAS,sBAAsB;EACrD,YAAY;EACZ,YAAY,CAAC;GAAE,OAAO,WAAW;GAAO,MAAM;GAAe;EAAQ,CAAC;CAC1E,CAAC;AACL;;AAGA,SAAS,aAAa,IAA+D;CACjF,OAAO,OAAO,KAAA,KAAa,OAAO,QAAQ,OAAO;AACrD;;;;;;;;;;;;;;;AAgBA,SAAS,mBACL,YACA,UACA,IACA,QACA,MACI;CACJ,IAAI,CAAC,cAAc,CAAC,QAAQ;CAC5B,MAAM,cAAc,eAAe,YAAY,QAAQ;CACvD,IAAI,YAAY,WAAW,GAAG;CAC9B,IAAI;CACJ,IAAI;EACA,UAAU,gBAAc,IAAI,WAAW;CAC3C,QAAQ;EAEJ;CACJ;CACA,MAAM,QAAQ,YACT,KAAI,QAAO,IAAI,SAAS,CAAC,CACzB,QAAO,UAAS,OAAO,WAAW,KAAA,KAAa,OAAO,OAAO,MAAM,MAAM,OAAO,QAAQ,MAAM,CAAC;CACpG,IAAI,MAAM,WAAW,GAAG;CACxB,MAAM,OAAO,WAAW,QAAQ;CAChC,MAAM,aAAa,MAAM,KAAI,UAAS;EAGlC,OAAO;GAAE;GAAO,MAAM;GAAiB,SAAA,IAFnB,MAAM,mBAAmB,KAAK,kDAAkD,GAAG;EAExD;CACnD,CAAC;CACD,MAAM,SAAS,WAAW,WAAW,EAAE,CAAC,SAAS,iBAAiB;EAAE,YAAY;EAAM;CAAW,CAAC;AACtG;;;;;;;;;;;;;;AAeA,SAAS,8BACL,YACA,QACA,KACA,MACI;CACJ,IAAI,KAAK;CACT,KAAK,MAAM,CAAC,KAAK,aAAa,OAAO,QAAQ,UAAU,GAAG;EACtD,MAAM,OAAO;EACb,IAAI,CAAC,QAAQ,KAAK,SAAS,UAAU;EACrC,IAAI,KAAK,cAAc,oBAAoB,KAAK,cAAc,kBAAkB;EAChF,IAAI,CAAC,KAAK,YAAY,UAAU;EAGhC,IAAI,WAAW,cAAc,KAAK,cAAc,kBAAkB;EAClE,MAAM,SAAS,WACX,IAAI,IAAI,QAAQ,KAAK,kKAErB,yBACA;GACI,YAAY;GACZ,YAAY,CAAC;IAAE,OAAO;IAAK,MAAM;IAAY,SAAS,IAAI,IAAI;GAA+C,CAAC;EAClH,CACJ;CACJ;AACJ;;;;;;;;;AAUA,SAAS,mBAAmB,SAAuF;CAC/G,IAAI,CAAC,SAAS,OAAO,KAAA;CACrB,QAAQ,UAAU,QAAQ,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC,CAAC;AACvF;;;;;;;;;;AAWA,SAAS,cAAc,MAAiD;CACpE,OAAO;EACH,KAAK,MAAM,OAAO;EAClB,OAAO,MAAM,SAAS,CAAC;EACvB,aAAa,MAAM,gBAAgB;EAGnC,GAAI,MAAM,SAAS,EAAE,QAAQ,KAAK,OAAO,IAAI,CAAC;CAClD;AACJ;AAEA,IAAa,wBAAb,MAAa,sBAA4C;CAyD1C;CAES;CAET;CA5DX,MAAM;CACN,cAAc;CAEd;CACA;CACA;CACA;CACA;CACA;;;;;;;CAQA;;;;;;;;;;;;;CAcA,4BAAoC;;;;;;;;;;CAWpC;;;;;CAMA,sBAAsB;CACtB,wBAKK,CAAC;CAEN,YACI,IACA,iBACA,UACA,MACA,aACA,gBACF;EANS,KAAA,KAAA;EAES,KAAA,WAAA;EAET,KAAA,cAAA;EAOP,KAAK,cAAc,IAAI,YAAY,IAAI,gBAAgB,KAAK,iBAAiB,CAAC;EAC9E,KAAK,kBAAkB;EACvB,KAAK,iBAAiB;EACtB,KAAK,OAAO;EACZ,KAAK,OAAO,aAAa,IAAI;EAG7B,IAAI,aACA,KAAK,gBAAgB,IAAI,cAAc,IAAI,aAAa;GACpD,UAAU,YAAY,qBAAqB,KAAK,MAAM,KAAK,gBAAgB;GAC3E,UAAU,YAAY;IAClB,MAAM,SAAS,qBAAqB;IACpC,OAAO,UAAU,WAAW,MAAM,KAAK,gBAAgB,IAAI,YAAY,WAAW,MAAM,IAAI,KAAK;GACrG;EACJ,CAAC;CAGT;;;;;;CAOA,IAAI,QAAuB;EACvB,OAAO;GACH,aAAa,GAAG,SAA+D,KAAK,WAAW,GAAG,IAAI;GACtG,eAAe,GAAG,SAAiE,KAAK,aAAa,GAAG,IAAI;GAC5G,+BAA+B,KAAK,wBAAwB;GAC5D,2BAA2B,KAAK,oBAAoB;GACpD,6BAA6B,KAAK,sBAAsB;GACxD,4BAA4B,KAAK,qBAAqB;GACtD,sBAAsB,GAAG,SAAwE,KAAK,oBAAoB,GAAG,IAAI;GACjI,qBAAqB,GAAG,SAAuE,KAAK,mBAAmB,GAAG,IAAI;GAY9H,kBAAkB,OAAO,QAAQ,OAAO,YAAY,qBAAqB;IAC7D;IACD;IACP,OAAO,SAAS;IAChB,YAAY,SAAS;IACrB,UAAU,MAAM,mBACZ,KAAK,qBAAqB,GAC1B,KACJ;GACJ,CAAC;GAED,GAAI,KAAK,gBAAgB;IACrB,cAAc,KAAK,cAAc,aAAa,KAAK,KAAK,aAAa;IACrE,cAAc,KAAK,cAAc,aAAa,KAAK,KAAK,aAAa;IACrE,cAAc,KAAK,cAAc,aAAa,KAAK,KAAK,aAAa;IACrE,eAAe,KAAK,cAAc,cAAc,KAAK,KAAK,aAAa;GAC3E,IAAI,CAAC;EACT;CACJ;;;;;;;;;CAUA,uBAA0C;EACtC,OAAO,EACH,OAAO,OAAU,SAAyC;GACtD,OAAO,EAAE,MAAM,QAAW,MAAM,KAAK,GAAG,QAAQ,IAAW,IAAI,IAAI,CAAC,CAAC,EAAE;EAC3E,EACJ;CACJ;;;;;;;;CASA,IAAI,mBAAqC;EACrC,MAAM,MAAM,KAAK,YAAY,gBAAgB;EAC7C,OAAO;GACH,wBAAwB,OAAO,gBAAgB,SAAS,YAAY;IAChE,MAAM,OAAO,MAAM,IAAI,uBAAuB,gBAAgB,SAAS,OAAO;IAC9E,OAAO,KAAK,sBAAsB,MAAM,cAAc;GAC1D;GACA,iBAAiB,OAAO,gBAAgB,IAAI,SAAS,YAAY,YAAY;IACzE,MAAM,MAAM,MAAM,IAAI,gBAAgB,gBAAgB,IAAI,SAAS,YAAY,OAAO;IACtF,IAAI,CAAC,KAAK,OAAO;IACjB,MAAM,CAAC,UAAU,MAAM,KAAK,sBAAsB,CAAC,GAAG,GAAG,cAAc;IACvE,OAAO;GACX;GAYA,WAAW,OAAO,gBAAgB,YAAY;IAC1C,OAAO,IAAI,UAAU,gBAAgB,OAAO;GAChD;GAWA,YAAY,gBAAgB,KAAK,YAAY,IAAI,UAAU,gBAAgB,KAAK,OAAO;EAC3F;CACJ;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4BA,mBAA8C;EAC1C,MAAM,SAAS,KAAK;EACpB,OAAO;GACH,MAAM,KAAK;GACX,QAAQ;GACR,MAAM,KAAK;GACX,IAAI,SAAS;IACT,OAAO,sBAAsB,MAAM;GACvC;GACA,eAAe,QAAQ;EAC3B;CACJ;CAEA,2BAAsE,YAA6C,MAAc;EAC7H,IAAI,CAAC,cAAc,CAAC,MAAM,OAAO;GAC7B,YAAY,KAAA;GACZ,WAAW,KAAA;GACX,iBAAiB,KAAA;GACjB,mBAAmB,KAAA;EACvB;EACA,MAAM,qBAAqB,KAAK,UAAU,oBAAoB,IAAI;EAClE,MAAM,qBAAqB,qBACrB;GACE,GAAG;GACH,GAAG;EACP,IACE;EAEN,MAAM,YAAY,oBAAoB;EACtC,MAAM,kBAAkB,KAAK,UAAU,mBAAmB;EAC1D,MAAM,aAAa,oBAAoB;EACvC,IAAI;EACJ,IAAI,YACA,oBAAoB,uBAAuB,UAAU;EAEzD,OAAO;GACH,YAAY;GACZ;GACA;GACA;EACJ;CACJ;;;;;CAMA,MAAc,oBACV,KACA,MACA,UACA,oBACgC;EAChC,MAAM,EAAE,YAAY,oBAAoB,WAAW,iBAAiB,sBAAsB;EAC1F,IAAI,MAAM;EACV,IAAI,CAAC,sBAAsB,aAAa,QAAQ,GAAG,OAAO;EAE1D,MAAM,qBAAqB,0BAA0B,oBAAoB,IAAI;EAC7E,IAAI,iBAAiB,WACjB,MAAM,MAAM,gBAAgB,UAAU;GAClC,YAAY;GACZ;GAAM,KAAK;GAAK,SAAS;EAC7B,CAAC,KAAK;EAEV,IAAI,WAAW,WACX,MAAM,MAAM,UAAU,UAAU;GAC5B,YAAY;GACZ;GAAM,KAAK;GAAK,SAAS;EAC7B,CAAC,KAAK;EAEV,IAAI,mBAAmB,WACnB,MAAM,MAAM,kBAAkB,UAAU;GACpC,YAAY;GACZ;GAAM,KAAK;GAAK,SAAS;EAC7B,CAAC,KAAK;EAEV,OAAO;CACX;CAEA,OAAe,aAAa,UAAoF;EAC5G,OAAO,CAAC,EAAE,SAAS,iBAAiB,aAAa,SAAS,WAAW,aAAa,SAAS,mBAAmB;CAClH;;;;;;;;;;;;;;CAeA,MAAM,sBACF,MACA,MACkC;EAClC,IAAI,CAAC,QAAQ,KAAK,WAAW,GAAG,OAAO;EAEvC,MAAM,WAAW,KAAK,2BAA2B,KAAA,GAAW,IAAI;EAChE,MAAM,qBAAqB,KAAK,iBAAiB;EACjD,MAAM,SAAS,sBAAsB,aAAa,QAAQ;EAI1D,MAAM,kBAA+H,CAAC;EACtI,IAAI,SAAS,YACT,IAAI;GACA,MAAM,OAAO,2BAA2B,SAAS,UAA8B;GAC/E,KAAK,MAAM,CAAC,KAAK,QAAQ,OAAO,QAAQ,IAAI,GAAG;IAC3C,MAAM,SAAS,OAAQ,IAA6B,WAAW,aACxD,IAA2C,OAAO,IACnD,KAAA;IACN,MAAM,aAAa,QAAQ;IAC3B,IAAI,CAAC,YAAY;IAMjB,MAAM,iBAAiB,KAAK,2BAA2B,QAAQ,UAAU;IACzE,IAAI,sBAAsB,aAAa,cAAc,GACjD,gBAAgB,OAAO;KAAE,MAAM;KAAY,UAAU;IAAe;GAE5E;EACJ,QAAQ,CAER;EAEJ,MAAM,UAAU,OAAO,KAAK,eAAe;EAE3C,IAAI,CAAC,UAAU,QAAQ,WAAW,GAAG,OAAO;EAE5C,MAAM,eAAe,OAAO,OAAgB,WAA0H;GAClK,IAAI,MAAM,QAAQ,KAAK,GACnB,OAAO,QAAQ,IAAI,MAAM,KAAK,MAAM,aAAa,GAAG,MAAM,CAAC,CAAC;GAEhE,IAAI,CAAC,SAAS,OAAO,UAAU,UAAU,OAAO;GAChD,MAAM,MAAM;GAEZ,IAAI,IAAI,WAAW,cAAc,IAAI,QAAQ,OAAO,IAAI,SAAS,UAC7D,OAAO;IAAE,GAAG;IAAK,MAAM,MAAM,KAAK,oBAAoB,IAAI,MAAiC,OAAO,MAAM,OAAO,UAAU,kBAAkB;GAAE;GAGjJ,IAAI,IAAI,WAAW,aAAa,OAAO;GACvC,OAAO,KAAK,oBAAoB,KAAK,OAAO,MAAM,OAAO,UAAU,kBAAkB;EACzF;EAEA,OAAO,QAAQ,IAAI,KAAK,IAAI,OAAO,QAAQ;GACvC,IAAI,MAAM,SAAS,MAAM,KAAK,oBAAoB,KAAK,MAAM,UAAU,kBAAkB,IAAI;GAC7F,KAAK,MAAM,OAAO,SAAS;IACvB,IAAI,IAAI,SAAS,KAAA,KAAa,IAAI,SAAS,MAAM;IACjD,MAAM;KAAE,GAAG;MAAM,MAAM,MAAM,aAAa,IAAI,MAAM,gBAAgB,IAAI;IAAE;GAC9E;GACA,OAAO;EACX,CAAC,CAAC;CACN;CAEA,MAAM,gBAMF,EAAE,MAAM,YAAY,YAAY,GAAG,SACD;EAElC,MAAM,OAAO,MAAM,KAAK,YAAY,gBAAmB,MAAM;GACzD,GAAG;GACS;GACZ,YAAY,YAAY;EAC5B,CAAC;EAED,MAAM,EACF,YAAY,oBACZ,WACA,iBACA,sBACA,KAAK,2BAA2B,YAAY,IAAI;EAEpD,IAAI,iBAAiB,aAAa,WAAW,aAAa,mBAAmB,WAAW;GACpF,MAAM,qBAAqB,KAAK,iBAAiB;GACjD,MAAM,qBAAqB,0BAA0B,oBAAoB,IAAI;GAC7E,OAAO,QAAQ,IAAI,KAAK,IAAI,OAAO,QAAQ;IACvC,IAAI,UAAU;IASd,IAAI,iBAAiB,WACjB,UAAU,MAAM,gBAAgB,UAAU;KACtC,YAAY;KACZ;KACA,KAAK;KACL,SAAS;IACb,CAAC,KAAK;IAGV,IAAI,WAAW,WACX,UAAU,MAAM,UAAU,UAAU;KAChC,YAAY;KACZ;KACA,KAAK;KACL,SAAS;IACb,CAAC,KAAK;IAGV,IAAI,mBAAmB,WACnB,UAAU,MAAM,kBAAkB,UAAU;KACxC,YAAY;KACZ;KACA,KAAK;KACL,SAAS;IACb,CAAC,KAAK;IAEV,OAAO;GACX,CAAC,CAAC;EACN;EAEA,OAAO;CACX;;;;;;;;;;CAWA,iBAMI,EAAE,MAAM,YAAY,YAAY,UAAU,SAAS,cAAc,eAAe,MAAM,OAAO,aAAa,cAAc,GAAG,SAC3H,cAAmD,KAAK,OAAO,cAAc,KAAK,IAAI,IAAI,KAAA,GAChF;EACV,MAAM,iBAAiB,KAAK,uBAAuB;EAEnD,KAAK,gBAAgB,4BAA4B,gBAAgB;GAC7D,MAAM;GACN;GACA,mBAAmB;IACf,GAAG;IACS;IACZ,YAAY,YAAY;GAC5B;GACA;GACA,SAAS,mBAAmB,OAAO;EACvC,IAAI,SAAS;GACT,IAAI,MAAM,QAAQ,IAAI,GAAG,SAAS,IAAI;EAC1C,CAAC;EAED,aAAa,KAAK,gBAAgB,YAAY,cAAc;CAChE;CAEA,MAAM,SAA4C,EACO,MACA,IACA,YACA,YACA,eAC+D;EACpH,IAAI,MAAM,MAAM,KAAK,YAAY,SAC7B,MACA,IACA,cAAc,YAAY,YAC1B,WACJ;EAEA,MAAM,EACF,YAAY,oBACZ,WACA,iBACA,sBACA,KAAK,2BAA2B,YAAY,IAAI;EAEpD,IAAI,QAAQ,iBAAiB,aAAa,WAAW,aAAa,mBAAmB,YAAY;GAC7F,MAAM,qBAAqB,KAAK,iBAAiB;GACjD,MAAM,qBAAqB,0BAA0B,oBAAoB,IAAI;GAG7E,IAAI,iBAAiB,WACjB,MAAM,MAAM,gBAAgB,UAAU;IAClC,YAAY;IACZ;IACA;IACA,SAAS;GACb,CAAC,KAAK;GAGV,IAAI,WAAW,WACX,MAAM,MAAM,UAAU,UAAU;IAC5B,YAAY;IACZ;IACA;IACA,SAAS;GACb,CAAC,KAAK;GAGV,IAAI,mBAAmB,WACnB,MAAM,MAAM,kBAAkB,UAAU;IACpC,YAAY;IACZ;IACA;IACA,SAAS;GACb,CAAC,KAAK;EAEd;EAEA,OAAO;CACX;;;;;;CAOA,UACI,EAAE,MAAM,IAAI,UAAU,WACtB,cAAmD,KAAK,OAAO,cAAc,KAAK,IAAI,IAAI,KAAA,GAChF;EACV,MAAM,iBAAiB,KAAK,uBAAuB;EAEnD,KAAK,gBAAgB,4BAA4B,gBAAgB;GAC7D,MAAM;GACN;GACA;GACA;GACA,SAAS,mBAAmB,OAAO;EACvC,IAAI,QAAQ;GAGR,IAAI,CAAC,MAAM,QAAQ,GAAG,GAAG,SAAS,GAAG;EACzC,CAAC;EAED,aAAa,KAAK,gBAAgB,YAAY,cAAc;CAChE;;;;;;;;;;;CAYA,OAAwB,wBAAwB;;;;;;;CAQhD,MAAc,iBACV,YACA,MACA,QACA,QACA,gBACiC;EACjC,MAAM,SAAS,gBAAgB,UAAU;EACzC,IAAI,CAAC,QAAQ,OAAO;EAMpB,MAAM,cAAc,kBAAkB,MAAM;EAC5C,MAAM,eAAe,KAAK,MAAM,SAAS,CAAC,EAAA,CAAG,KAAI,MAC7C,OAAO,MAAM,WAAW,IAAI,OAAQ,GAAwB,MAAM,CAAC,CAAC;EACxE,MAAM,SAAS,CAAC,KAAK,QAAQ,YAAY,MAAK,MAAK,YAAY,SAAS,CAAC,CAAC;EAE1E,MAAM,EAAE,SAAS,aAAa,SACxB;GAAE,SAAS,CAAC;GAAgB,UAAU;EAAK,IAC3C,MAAM,KAAK,qBAAqB,QAAQ,UAAU;EAExD,MAAM,WAAW,mBAAmB;GAChC;GACQ;GACR;GACA,eAAe;GACf,uBAAuB;GACvB;GACA;GACA,MAAM,YAAY,QAAQ;EAC9B,CAAC;EAED,IAAI,SAAS,SAAS;GAClB,MAAM,EAAE,MAAM,OAAO,YAAY,SAAS;GAC1C,MAAM,SAAS,WAAW,SAAS,MAAM;IACrC,YAAY;IACZ,YAAY,CAAC;KAAE;KAAO;KAAM;IAAQ,CAAC;GACzC,CAAC;EACL;EACA,OAAO,SAAS;CACpB;;;;;;;;;;;;;;;;;CAkBA,MAAc,qBACV,QACA,YACkD;EAClD,IAAI,WAAW,OAAO,MAAM;GACxB,MAAM,QAAQ,KAAK,MAAM,SAAS,OAAO,KAAK;GAE9C,OAAO;IAAE,SADK,UAAU,KAAA,KAAa,UAAU,QAAQ,UAAU,KACvC,CAAC,IAAI,CAAC,KAAK;IAAG,UAAU;GAAK;EAC3D;EAEA,MAAM,MAAM,KAAK,MAAM;EACvB,IAAI,CAAC,KAAK,OAAO;GAAE,SAAS,CAAC;GAAG,UAAU;EAAK;EAE/C,MAAM,EAAE,YAAY,MAAM,WAAW,gBAAgB,OAAO,KAAK;EACjE,MAAM,MAAM,sBAAsB;EAClC,IAAI;GAOA,MAAM,OAAO,MAAM,gBAAgB,KAAA,SAAiB,KAAK,YAAY,gBAAgB,MAAM;IACvF,QAAQ,GAAG,YAAY,CAAC,MAAM,GAAG,EAAE;IACnC,OAAO,MAAM;IACb,YAAY,YAAY;GAC5B,CAAC,CAAC;GASF,OAAO;IAAE,SARO,MAAM,KAAK,IAAI,IAC3B,KAAK,MAAM,GAAG,GAAG,CAAC,CACb,KAAI,QAAQ,IAAgC,YAAY,CAAC,CACzD,QAAO,UAAS,UAAU,QAAQ,UAAU,KAAA,CAAS,CAAC,CACtD,KAAI,UAAU,OAAO,UAAU,WACzB,MAA2B,MAAM,QAClC,KAAM,CACpB,CACS;IAAS,UAAU,KAAK,UAAU;GAAI;EACnD,SAAS,KAAK;GASV,IAAI,gBAAgB,GAAG,GAAG,MAAM;GAChC,OAAO,MAAM,0BAA0B,KAAK,oCAAoC,EAC5E,QAAQ,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAC3D,CAAC;GACD,OAAO;IAAE,SAAS,CAAC;IAAG,UAAU;GAAM;EAC1C;CACJ;;;;;;;;;CAUA,cAAsB,MAAsB;EACxC,IAAI,CAAC,aAAa,IAAI,GAAG,OAAO;EAChC,MAAM,MAAM,kBAAkB,MAAM,KAAK,QAAQ;EACjD,OAAO,OAAO,yBAAyB,IAAI,QAAQ,IAAI,IAAI,iBAAiB,OAAO;CACvF;;;;;;;;;;;;;;;;;;;;;;;;;;CA2BA,MAAc,iBACV,MACA,YACA,QACA,IACA,YACqE;EACrE,MAAM,cAAc,eAAe,YAAY,KAAK,QAAQ;EAC5D,MAAM,MAA+B,EAAE,GAAG,OAAO;EACjD,IAAI,aAAa,EAAE,GAAG,OAAO,OAAO,KAAK,gBAAc,IAAI,WAAW,CAAC;EACvE,MAAM,eAAe,cAAc,WAAW,SAAS,IACjD,CAAC,GAAG,UAAU,IACd,YAAY,KAAI,SAAQ,KAAK,SAAS;EAC5C,IAAI,aAAa,WAAW,GAAG,OAAO,KAAA;EAEtC,MAAM,SAA+B,CAAC;EACtC,KAAK,MAAM,SAAS,cAAc;GAC9B,MAAM,QAAQ,IAAI;GAClB,IAAI,UAAU,KAAA,KAAa,UAAU,QAAQ,UAAU,IAAI,OAAO,KAAA;GAClE,IAAI,OAAO,UAAU,YAAY,OAAO,UAAU,YAAY,OAAO,UAAU,WAAW,OAAO,KAAA;GACjG,OAAO,SAAS,CAAC,MAAM,KAAK;EAChC;EAEA,MAAM,aAAa,kBAAkB,UAAU;EAC/C,MAAM,CAAC,OAAO,MAAM,gBAAgB,KAAA,SAAiB,KAAK,YAAY,uBAAuB,MAAM;GAC/F;GACA,OAAO;GACP,YAAY,WAAW;GACvB,aAAa,aAAa,OAAO,KAAA;EACrC,CAAC,CAAC;EAIF,IAAI,CAAC,OAAO,aAAa,MAAK,UAAS,OAAO,IAAI,MAAM,MAAM,OAAO,IAAI,MAAM,CAAC,GAAG,OAAO,KAAA;EAC1F,IAAI,cAAc,IAAI,WAAW,WAAW,QAAQ,IAAI,WAAW,WAAW,KAAA,GAC1E,MAAM,gBAAgB,MAAM,aAAa,KAAI,UAAS,OAAO,IAAI,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,UAAU;EAGpG,MAAM,OAAgC,EAAE,GAAG,OAAO;EAClD,KAAK,MAAM,SAAS,CAAC,GAAG,YAAY,KAAI,SAAQ,KAAK,SAAS,GAAG,GAAG,YAAY,GAAG,OAAO,KAAK;EAC/F,OAAO;GACH,IAAI,iBAAiB,KAAK,YAAY,KAAK,QAAQ;GACnD,QAAQ;EACZ;CACJ;CAEA,MAAM,KAAwC,OAAuD;EAIjG,OAAO,aAAa,MAAM,MAAM,SAAQ,UAAS,KAAK,YAAY,OAAO,KAAK,CAAC;CACnF;CAEA,MAAc,YAA+C,EACL,MACA,IACA,QACA,YACA,QACA,QACA,cACa,OAAqD;EAEtH,MAAM,EACF,YAAY,oBACZ,WACA,iBACA,sBACA,KAAK,2BAA2B,YAAY,IAAI;EAapD,IAAI,WAAW,YAAY;GACvB,8BAA8B,oBAAoD,QAAQ,IAAI;GAC9F,IAAI,aAAa,EAAE,GACf,mBAAmB,oBAAoD,KAAK,UAAU,IAAI,QAAmC,IAAI;EAEzI;EAEA,MAAM,YAAY,WAAW,QAAQ,WAAW;EAChD,IAAI,aAAa,oBAAoB;GACjC,MAAM,SAAS,MAAM,KAAK,iBACtB,MAAM,oBAAwC,QAAQ,IAAI,UAC9D;GACA,IAAI,QAEA,OAAO,KAAK,YAAe;IAAE;IAAM,IAAI,OAAO;IAAI,QAAQ,OAAO;IAAQ;IAAY,QAAQ;GAAW,GAAG,KAAK;EAExH;EAEA,IAAI,gBAAgB;EACpB,MAAM,qBAAqB,KAAK,iBAAiB;EAWjD,KAAK,WAAW,SAAS,WAAW,WAAW,oBAAoB,YAC/D,gBAAgB,2BACZ,eACA,mBAAmB,UACvB;EAQJ,IAAI;EACJ,IAAI,WAAW,cAAc,IAwBzB,IAAI;GACA,MAAM,WAAW,MAAM,KAAK,YAAY,gBAAgB,CAAC,CAAC,gBACtD,KAAK,cAAc,IAAI,GAAG,IAAI,KAAA,GAAW,oBAAoB,YAC7D,EACI,aAAa,uBACT,oBACA,MACJ,IAAI,OAAO,KAAA,EACf,CACJ;GACA,IAAI,CAAC,UACD,MAAM,SAAS,SAAS,WAAW,GAAG,QAAQ,KAAK,aAAa;GAEpE,IAAI,UAAU;IACV,MAAM,EAAE,IAAI,aAAa,GAAG,mBAAmB;IAC/C,2BAA2B;GAC/B;EACJ,SAAS,KAAK;GAEV,IAAI,eAAe,UAAU,MAAM;GAInC,IAAI,gBAAgB,GAAG,GAAG,MAAM;GAKhC,OAAO,MAAM,+CAA+C,KAAK,IAAI,EAAE,QAAQ,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE,CAAC;EACrI;;EAIJ,MAAM,cAAwC;EAK9C,IAAI;GACA,MAAM,QAAQ;GACd,IAAI,iBAAiB,cAAc,WAAW,cAAc,mBAAmB,YAAY;IACvF,MAAM,qBAAqB,0BAA0B,oBAAoB,IAAI;IAE7E,IAAI,iBAAiB,YAAY;KAC7B,MAAM,SAAS,MAAM,gBAAgB,WAAW;MAC5C,YAAY;MACZ;MACA;MACA,QAAQ;MACR,gBAAgB;MAChB;MACA,SAAS;KACb,CAAC;KACD,IAAI,QAAQ,gBAAgB,sBAAsB,eAAe,MAAM;IAC3E;IAGA,IAAI,WAAW,YAAY;KACvB,MAAM,SAAS,MAAM,UAAU,WAAW;MACtC,YAAY;MACZ;MACA;MACA,QAAQ;MACR,gBAAgB;MAChB;MACA,SAAS;KACb,CAAC;KACD,IAAI,QAAQ,gBAAgB,sBAAsB,eAAe,MAAM;IAC3E;IAGA,IAAI,mBAAmB,YAAY;KAC/B,MAAM,SAAS,MAAM,kBAAkB,WAAW;MAC9C,YAAY;MACZ;MACA;MACA,QAAQ;MACR,gBAAgB;MAChB;MACA,SAAS;KACb,CAAC;KACD,IAAI,QAAQ,gBAAgB,sBAAsB,eAAe,MAAM;IAC3E;GAEJ;EACJ,SAAS,eAAe;GACpB,MAAM,gBAAgB,eAAe,cAAc,IAAI;EAC3D;EACA,MAAM,aAAuC;EAC7C,IAAI,WAAW,cAAc,aAAa,EAAE,GACxC,mBAAmB,oBAAoD,KAAK,UAAU,IAAI,eAA0C,IAAI;EAK5I,IAAI,oBAAoB,YAAY;GAChC,gBAAgB,qBAAqB;IACjC,aAAa;IACb,YAAY,mBAAmB;IAC/B,QAAQ,UAAU;IAClB,mCAAmB,IAAI,KAAK;GAChC,CAAC;GAKD,8BACI,mBAAmB,YACnB,UAAU,OACV,KAAK,MAAM,KACX,IACJ;GACA,gBAAgB,qBAAqB;IACjC,aAAa;IACb,YAAY,mBAAmB;IAC/B,QAAQ,UAAU;IAClB,KAAK,KAAK,MAAM;GACpB,CAAC;GAKD,IAAI,WAAW,YAAY;IACvB,MAAM,OAAgC,EAAE,GAAG,cAAc;IACzD,KAAK,MAAM,OAAO,gBAAgB,mBAAmB,UAAU,GAAG,OAAO,KAAK;IAC9E,gBAAgB;GACpB;EACJ;EASA,gBAAgB,MAAM,KAAK,iBACvB,oBACA,MACA,eACA,UAAU,OACV,wBACJ;EAWA,IAAI,UAAU;EACd,KAAK,WAAW,SAAS,WAAW,WAAW,CAAC,UAAU,aAAa,EAAE,KAAK,oBAAoB;GAC9F,gBAAgB;IACZ,GAAG;IACH,GAAG,gBAAc,IAAI,eAAe,oBAAwC,KAAK,QAAQ,CAAC;GAC9F;GACA,UAAU,KAAA;EACd;EAQA,MAAM,iBAAiB,YACjB,iBAAiB,eAAe,QAAQ,aAAa,YAAY,oBAAoB,UAAU,IAC/F,KAAA;EAIN,MAAM,kBAAkB,kBAAkB,kBAAkD,CAAC,EAAE;EAC/F,IAAI,kBAAkB,mBACf,cAAc,qBAAqB,KAAA,KAAa,cAAc,qBAAqB,MACtF,eAAe,KAAK,eAAe;EAGvC,IAAI;GACA,MAAM,YAAY,MAAM,KAAK,YAAY,KACrC,MACA,eACA,SACA,oBAAoB,YACpB;IAAE,QAAQ;IAAW;IAAY;GAAe,CACpD;GAWA,IAAI,WAAW;GACf,IAAI,cAAc,iBAAiB,aAAa,WAAW,aAAa,mBAAmB,YAAY;IACnG,MAAM,qBAAqB,0BAA0B,oBAAoB,IAAI;IAC7E,WAAW,EAAE,GAAG,UAAU;IAI1B,IAAI,iBAAiB,WACjB,WAAW,MAAM,gBAAgB,UAAU;KACvC,YAAY;KACZ;KACA,KAAK;KACL,SAAS;IACb,CAAC,KAAK;IAGV,IAAI,WAAW,WACX,WAAW,MAAM,UAAU,UAAU;KACjC,YAAY;KACZ;KACA,KAAK;KACL,SAAS;IACb,CAAC,KAAK;IAGV,IAAI,mBAAmB,WACnB,WAAW,MAAM,kBAAkB,UAAU;KACzC,YAAY;KACZ;KACA,KAAK;KACL,SAAS;IACb,CAAC,KAAK;GAEd;GAOA,MAAM,UAAU,iBACZ,WACC,sBAAsB,YACvB,KAAK,QACT;GAIA,MAAM,cAAc;GAWpB,IAAI;IACA,MAAM,QAAQ;IACd,IAAI,iBAAiB,aAAa,WAAW,aAAa,mBAAmB,WAAW;KACpF,MAAM,qBAAqB,0BAA0B,oBAAoB,IAAI;KAE7E,IAAI,iBAAiB,WACjB,MAAM,gBAAgB,UAAU;MAC5B,YAAY;MACZ;MACA,IAAI;MACJ,QAAQ;MACR,gBAAgB;MAChB;MACA,SAAS;KACb,CAAC;KAGL,IAAI,WAAW,WACX,MAAM,UAAU,UAAU;MACtB,YAAY;MACZ;MACA,IAAI;MACJ,QAAQ;MACR,gBAAgB;MAChB;MACA,SAAS;KACb,CAAC;KAGL,IAAI,mBAAmB,WACnB,MAAM,kBAAkB,UAAU;MAC9B,YAAY;MACZ;MACA,IAAI;MACJ,QAAQ;MACR,gBAAgB;MAChB;MACA,SAAS;KACb,CAAC;IAET;GACJ,SAAS,eAAe;IACpB,MAAM,gBAAgB,eAAe,aAAa,IAAI;GAC1D;GAOA,IAAI,KAAK,kBAAkB,oBAAoB,SAC3C,MAAM,KAAK,eAAe,cAAc;IAKpC,WAAW,mBAAmB;IAC9B,IAAI;IACJ,QAAQ,WAAW,aAAa,WAAW;IAC3C,QAAQ;IACR,gBAAgB;IAChB,WAAW,KAAK,MAAM;GAC1B,CAAC;GAIL,IAAI,KAAK,qBACL,KAAK,sBAAsB,KAAK;IAC5B;IACA,IAAI;IACJ,KAAK;IACL,YAAY,oBAAoB;GACpC,CAAC;QAED,MAAM,KAAK,gBAAgB,aACvB,MACA,SACA,UACA,oBAAoB,UACxB;GAGJ,OAAO;EACX,SAAS,OAAO;GACZ,IAAI,iBAAiB,kBAAkB,WAAW,kBAAkB,mBAAmB,gBAAgB;IACnG,MAAM,qBAAqB,0BAA0B,oBAAoB,IAAI;IAI7E,MAAM,WAAW,OAAO,YAA+B;KAMnD,MAAM,aAAa;MACf,YAAY;MACZ;MACA;MACA,QAAQ;MACR,gBAAgB;MAChB;MACA;MACA;KACJ;KACA,KAAK,MAAM,CAAC,MAAM,SAAS;MACvB,CAAC,UAAU,iBAAiB,cAAc;MAC1C,CAAC,cAAc,WAAW,cAAc;MACxC,CAAC,YAAY,mBAAmB,cAAc;KAClD,GAAY;MACR,IAAI,CAAC,MAAM;MACX,IAAI;OACA,MAAM,KAAK,UAAU;MACzB,SAAS,WAAW;OAChB,OAAO,MAAM,cAAc,KAAK,4BAA4B,KAAK,iDAAiD,EAAE,OAAO,UAAU,CAAC;MAC1I;KACJ;IACJ;IAMA,IAAI,CAAC,kBAAkB,CAAC,EAAE,aAAa,QAAQ,GAAG;KAC9C,MAAM,QAAQ;KACd,MAAM,SAAS,kBAAkB;IACrC;GACJ;GACA,MAAM;EACV;CACJ;;;;;;;;;;;;;CAcA,kBAA0B,IAA0C;EAGhE,MAAM,WAAW,IAAI,sBACjB,IAAI,KAAK,iBAAiB,KAAK,UAAU,KAAK,MAAM,KAAK,aAAa,KAAK,cAC/E;EACA,SAAS,cAAc,IAAI,YAAY,IAAI,KAAK,gBAAgB,SAAS,iBAAiB,CAAC;EAC3F,SAAS,SAAS,KAAK;EAGvB,SAAS,sBAAsB,KAAK;EACpC,SAAS,wBAAwB,KAAK;EACtC,OAAO;CACX;;;;;;;;;;;;;;;CAgBA,kBAAkB,IAAmB,MAA+B;EAChE,MAAM,WAAW,IAAI,sBACjB,IAAI,KAAK,iBAAiB,KAAK,UAAU,MAAM,KAAK,aAAa,KAAK,cAC1E;EACA,SAAS,cAAc,IAAI,YAAY,IAAI,KAAK,gBAAgB,SAAS,iBAAiB,CAAC;EAC3F,SAAS,SAAS,KAAK;EACvB,OAAO,SAAS,iBAAiB;CACrC;CAEA,MAAM,SAA4C,EACI,MACA,MACA,YACA,QACA,cACqD;EACvG,OAAO,KAAK,GAAG,YAAY,OAAO,OAAO;GACrC,MAAM,WAAW,KAAK,kBAAkB,EAAE;GAE1C,MAAM,QAAmC,CAAC;GAE1C,KAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;IAClC,MAAM,SAAS,KAAK;IACpB,MAAM,KAAM,QAAoC;IAChD,IAAI;KACA,MAAM,KAAK,MAAM,SAAS,KAAQ;MAC9B;MACA;MAOA;MACA,QAAQ;MACR;MACA;KACJ,CAAC,CAAC;IACN,SAAS,OAAO;KAIZ,MAAM,QAAQ,OAAO,KAAA,IAAY,MAAM,KAAK,UAAU,EAAE,MAAM;KAC9D,MAAM,OAAO,OACT,IAAI,MAAM,OAAO,EAAE,MAAM,KAAK,OAAO,IAAI,MAAM,YAAa,OAAiB,WAAW,SAAS,EAAE,OAAO,MAAM,CAAC,GACjH;MACI,YAAa,OAAmC;MAChD,MAAO,OAA6B;MACpC,MAAO,OAAiB;KAC5B,CACJ;IACJ;GACJ;GAEA,OAAO;EACX,CAAC;CACL;;;;;;;;;;;;;;;;CAiBA,MAAM,WAA8C,EAChD,MACA,SACA,cACuD;EACvD,OAAO,KAAK,GAAG,YAAY,OAAO,OAAO;GACrC,MAAM,WAAW,KAAK,kBAAkB,EAAE;GAE1C,MAAM,QAAmC,CAAC;GAE1C,KAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;IACrC,MAAM,EAAE,IAAI,WAAW,QAAQ;IAC/B,IAAI;KAeA,IAAI,CAAC,MATkB,SAAS,SAAS;MACrC;MACA,IAAI,OAAO,EAAE;MACD;MACZ,aAAa,uBACR,cAA+C,KAAK,UAAU,oBAAoB,IAAI,GACvF,MACJ,IAAI,OAAO,KAAA;KACf,CAAC,GAEG,MAAM,OAAO,uBAAO,IAAI,MAAM,kBAAkB,KAAK,UAAU,EAAE,GAAG,GAAG;MACnE,YAAY;MACZ,MAAM;KACV,CAAC;KAGL,MAAM,KAAK,MAAM,SAAS,KAAQ;MAC9B;MACA,IAAI,OAAO,EAAE;MACb;MACA;MACA,QAAQ;KACZ,CAAC,CAAC;IACN,SAAS,OAAO;KAGZ,MAAM,OAAO,OACT,IAAI,MAAM,UAAU,EAAE,MAAM,QAAQ,OAAO,OAAO,KAAK,UAAU,EAAE,EAAE,YAAa,OAAiB,WAAW,SAAS,EAAE,OAAO,MAAM,CAAC,GACvI;MACI,YAAa,OAAmC;MAChD,MAAO,OAA6B;MACpC,MAAO,OAAiB;KAC5B,CACJ;IACJ;GACJ;GAEA,OAAO;EACX,CAAC;CACL;;;;;;;;;;;;CAaA,MAAM,WAA8C,EAChD,MACA,KACA,YACA,QACkC;EAClC,MAAM,KAAK,GAAG,YAAY,OAAO,OAAO;GACpC,MAAM,WAAW,KAAK,kBAAkB,EAAE;GAE1C,KAAK,IAAI,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;IACjC,MAAM,KAAK,IAAI;IACf,IAAI;KAIA,MAAM,SAAS,OAAU;MACrB,KAAK;OAAE,IAAI,OAAO,EAAE;OAAG;MAAK;MAC5B;MACA;KACJ,CAAC;IACL,SAAS,OAAO;KACZ,MAAM,OAAO,OACT,IAAI,MAAM,UAAU,EAAE,MAAM,IAAI,OAAO,OAAO,KAAK,UAAU,EAAE,EAAE,YAAa,OAAiB,WAAW,SAAS,EAAE,OAAO,MAAM,CAAC,GACnI;MACI,YAAa,OAAmC;MAChD,MAAO,OAA6B;MACpC,MAAO,OAAiB;KAC5B,CACJ;IACJ;GACJ;EACJ,CAAC;CACL;;;;;;;;;;;;;;;;;CAkBA,MAAM,WAA8C,EAChD,cACgE;EAChE,OAAO,KAAK,GAAG,YAAY,OAAO,OAAO;GACrC,MAAM,WAAW,KAAK,kBAAkB,EAAE;GAE1C,MAAM,UAA8C,CAAC;;GAErD,MAAM,wBAAQ,IAAI,IAAqC;GAEvD,KAAK,IAAI,IAAI,GAAG,IAAI,WAAW,QAAQ,KAAK;IACxC,MAAM,YAAY,WAAW;IAC7B,IAAI;KACA,MAAM,SAAS,UAAU,SACnB,iBAAiB,UAAU,QAAQ,KAAK,IACxC,KAAA;KACN,MAAM,QAAQ,UAAU,OAAO,KAAA,IACzB,iBAAiB,UAAU,IAAI,KAAK,IACpC,KAAA;KACN,MAAM,KAAK,UAAU,KAAA,KAAa,UAAU,OAAO,KAAA,IAAY,OAAO,KAAK;KAE3E,IAAI,MAAsC;KAC1C,QAAQ,UAAU,IAAlB;MACI,KAAK;OACD,MAAM,MAAM,SAAS,KAAQ;QACzB,MAAM,UAAU;QAChB,QAAQ,UAAW,CAAC;QACpB,YAAY,UAAU;QACtB,QAAQ;OACZ,CAAC;OACD;MACJ,KAAK;OACD,MAAM,MAAM,SAAS,KAAQ;QACzB,MAAM,UAAU;QAChB,QAAQ,UAAW,CAAC;QACpB,YAAY,UAAU;QACtB,QAAQ;QACR,QAAQ;QACR,YAAY,UAAU;OAC1B,CAAC;OACD;MACJ,KAAK;OAgBD,IAAI,CAAC,MAXkB,SAAS,SAAS;QACrC,MAAM,UAAU;QACZ;QACJ,YAAY,UAAU;QAGtB,aAAa,uBACR,UAAU,cAA+C,KAAK,UAAU,oBAAoB,UAAU,IAAI,GAC3G,MACJ,IAAI,OAAO,KAAA;OACf,CAAC,GAEG,MAAM,OAAO,uBAAO,IAAI,MAAM,kBAAkB,KAAK,UAAU,EAAE,GAAG,GAAG;QACnE,YAAY;QACZ,MAAM;OACV,CAAC;OAEL,MAAM,MAAM,SAAS,KAAQ;QACzB,MAAM,UAAU;QACZ;QACJ,QAAQ,UAAW,CAAC;QACpB,YAAY,UAAU;QACtB,QAAQ;OACZ,CAAC;OACD;MAEJ,KAAK;OAED,MAAM,SAAS,OAAU;QACrB,KAAK;SAAM;SAAK,MAAM,UAAU;QAAK;QACrC,YAAY,UAAU;OAC1B,CAAC;OACD,MAAM;OACN;KACR;KAEA,IAAI,UAAU,OAAO,KAAK,MAAM,IAAI,UAAU,KAAK,GAAG;KACtD,QAAQ,KAAK,GAAG;IACpB,SAAS,OAAO;KAIZ,MAAM,OAAO,OACT,IAAI,MACA,aAAa,EAAE,MAAM,WAAW,OAAO,IAAI,UAAU,GAAG,OAAO,UAAU,KAAK,aACxE,OAAiB,WAAW,SAClC,EAAE,OAAO,MAAM,CACnB,GACA;MACI,YAAa,OAAmC;MAChD,MAAO,OAA6B;MACpC,MAAO,OAAiB;KAC5B,CACJ;IACJ;GACJ;GAEA,OAAO;EACX,CAAC;CACL;CAEA,MAAM,OAA0C,OAAsC;EAElF,OAAO,aAAa,MAAM,IAAI,MAAM,WAAU,UAAS,KAAK,cAAc,OAAO,KAAK,CAAC;CAC3F;CAEA,MAAc,cAAiD,EACL,KACA,YACA,QACe,OAAkC;EAEvG,MAAM,aAAa,IAAI;EAGvB,MAAM,EACF,YAAY,oBACZ,WACA,iBACA,sBACA,KAAK,2BAA2B,YAAY,UAAU;EAyB1D,MAAM,SAAS,MAAM,KAAK,YAAY,gBAClC,YACA,IAAI,IACJ,KAAA,GACA,oBAAoB,YACpB,EAAE,aAAa,OAAO,OAAO,KAAA,EAAU,CAC3C;EAIA,IAAI,CAAC,QACD,MAAM,SAAS,SAAS,WAAW,IAAI,GAAG,QAAQ,WAAW,aAAa;EAM9E,MAAM,YAAqC,EAAE,GAAG,OAAO;EAEvD,MAAM,qBAAqB,KAAK,iBAAiB;EAKjD,IAAI;GACA,MAAM,QAAQ;GACd,IAAI,iBAAiB,gBAAgB,WAAW,gBAAgB,mBAAmB,cAAc;IAC7F,MAAM,qBAAqB,0BAA0B,oBAAoB,UAAU;IACnF,IAAI,iBAAiB;IAErB,IAAI,iBAAiB;SAQb,MAPiB,gBAAgB,aAAa;MAC9C,YAAY;MACZ,MAAM;MACN,IAAI,IAAI;MACR,KAAK;MACL,SAAS;KACb,CAAC,MACc,OACX,iBAAiB;IAAA;IAIzB,IAAI,WAAW;SAQP,MAPiB,UAAU,aAAa;MACxC,YAAY;MACZ,MAAM;MACN,IAAI,IAAI;MACR,KAAK;MACL,SAAS;KACb,CAAC,MACc,OACX,iBAAiB;IAAA;IAIzB,IAAI,mBAAmB;SAQf,MAPiB,kBAAkB,aAAa;MAChD,YAAY;MACZ,MAAM;MACN,IAAI,IAAI;MACR,KAAK;MACL,SAAS;KACb,CAAC,MACc,OACX,iBAAiB;IAAA;IAGzB,IAAI,gBAMA,MAAM,gBAAgB,gBAAgB,UAAU;GAExD;EACJ,SAAS,eAAe;GACpB,MAAM,gBAAgB,eAAe,gBAAgB,UAAU;EACnE;EAcA,MAAM,MAAM,aAAa,UAAU,IAAI,kBAAkB,YAAY,KAAK,QAAQ,IAAI,KAAA;EACtF,MAAM,YAAY,QAAQ,KAAA,KAAa,yBAAyB,IAAI,QAAQ;EAC5E,MAAM,aAAa,QAAQ,YACrB,KAAA,IACA,kBAAkB,kBAAkD;EAC1E,IAAI,YACA,MAAM,KAAK,YAAY,KACnB,YACA,GAAG,WAAW,wBAAQ,IAAI,KAAK,EAAE,GACjC,IAAI,IACJ,oBAAoB,UACxB;OAEA,MAAM,KAAK,YAAY,OACnB,YACA,IAAI,IACJ,oBAAoB,UACxB;EAMJ,IAAI;GACA,MAAM,QAAQ;GACd,IAAI,iBAAiB,eAAe,WAAW,eAAe,mBAAmB,aAAa;IAC1F,MAAM,qBAAqB,0BAA0B,oBAAoB,UAAU;IAEnF,IAAI,iBAAiB,aACjB,MAAM,gBAAgB,YAAY;KAC9B,YAAY;KACZ,MAAM;KACN,IAAI,IAAI;KACR,KAAK;KACL,SAAS;IACb,CAAC;IAGL,IAAI,WAAW,aACX,MAAM,UAAU,YAAY;KACxB,YAAY;KACZ,MAAM;KACN,IAAI,IAAI;KACR,KAAK;KACL,SAAS;IACb,CAAC;IAGL,IAAI,mBAAmB,aACnB,MAAM,kBAAkB,YAAY;KAChC,YAAY;KACZ,MAAM;KACN,IAAI,IAAI;KACR,KAAK;KACL,SAAS;IACb,CAAC;GAET;EACJ,SAAS,eAAe;GACpB,MAAM,gBAAgB,eAAe,eAAe,UAAU;EAClE;EAOA,IAAI,KAAK,kBAAkB,oBAAoB,WAAW,CAAC,WACvD,MAAM,KAAK,eAAe,cAAc;GAEpC,WAAW,mBAAmB;GAC9B,IAAI,IAAI,GAAG,SAAS;GACpB,QAAQ;GACR,QAAQ;GACR,WAAW,KAAK,MAAM;EAC1B,CAAC;EAIL,IAAI,KAAK,qBACL,KAAK,sBAAsB,KAAK;GAC5B,MAAM;GACN,IAAI,IAAI,GAAG,SAAS;GACpB,KAAK;GACL,YAAY,oBAAoB;EACpC,CAAC;OAED,MAAM,KAAK,gBAAgB,aACvB,YACA,IAAI,GAAG,SAAS,GAChB,MACA,oBAAoB,UACxB;CAGR;CAEA,MAAM,UAAU,MAA6B;EACzC,MAAM,KAAK,YAAY,UAAU,IAAI;EAErC,MAAM,KAAK,gBAAgB,aAAa,MAAM,KAAK,IAAI;CAC3D;CAEA,MAAM,oBAAoB,EAAE,MAAM,UAAU,SAAkD;EAC1F,MAAM,KAAK,YAAY,oBAAoB,MAAM,UAAU,KAAK;CAKpE;CAEA,MAAM,iBACF,MACA,MACA,OACA,IACA,YACgB;EAChB,OAAO,KAAK,YAAY,iBACpB,MACA,MACA,OACA,IACA,YAAY,UAChB;CACJ;CAEA,MAAM,MAAyC,EACY,MACA,YACA,QACA,SACA,cACA,cACA,eACyC;EAChG,OAAO,KAAK,YAAY,MACpB,MACA;GACI;GAOA;GACA;GACA;GACA;EACJ,CACJ;CACJ;;CAGA;;;;;;;;;;;CAYA,MAAM,kBAAmC;EACrC,KAAK,gBAAgB,KAAK,GAAG,QAAQ,IAAW,IAAI,mCAAmC,CAAC,CAAC,CAAC,MAAM,WAAW;GACvG,MAAM,OAAO,OAAO,OAAO,EAAE,EAAE;GAC/B,IAAI,OAAO,SAAS,UAChB,MAAM,IAAI,MAAM,gEAAgE;GAEpF,OAAO;EACX,CAAC;EACD,IAAI;GACA,OAAO,MAAM,KAAK;EACtB,SAAS,OAAgB;GAErB,KAAK,cAAc,KAAA;GACnB,MAAM;EACV;CACJ;CAEA,MAAc,YAAY,cAA+C;EACrE,IAAI,CAAC,gBAAgB,iBAAiB,MAAM,KAAK,gBAAgB,GAC7D,OAAO,KAAK;EAEhB,IAAI,CAAC,KAAK,aACN,MAAM,IAAI,MACN,0FACJ;EAEJ,OAAO,KAAK,YAAY,WAAW,YAAY;CACnD;;;;;;;;CASA,eAAuB,SAAiB,QAAoB;EACxD,IAAI,CAAC,UAAU,OAAO,WAAW,GAAG,OAAO,IAAW,IAAI,OAAO;EACjE,MAAM,QAAQ,QAAQ,MAAM,SAAS;EACrC,MAAM,SAAwE,CAAC;EAC/E,KAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAC9B,IAAI,IAAI,MAAM;OACN,MAAM,EAAE,CAAC,SAAS,GAAG,OAAO,KAAK,IAAW,IAAI,MAAM,EAAE,CAAC;EAAA,OAE7D,OAAO,KAAK,IAAW,MAAM,OAAO,OAAO,MAAM,EAAE,IAAI,EAAE,CAAC;EAGlE,OAAO,IAAW,KAAK,QAAQ,IAAW,IAAI,EAAE,CAAC;CACrD;CAEA,MAAM,WAAW,SAAiB,SAKK;EACnC,IAAI,SAAS,gBACT,OAAO,KAAK,kBACR,MAAM,KAAK,YAAY,QAAQ,QAAQ,GACvC,QAAQ,OACP,IAAI,SAAS,KAAK,aAAa,IAAI,SAAS;GAAE,GAAG;GAAS;EAAK,CAAC,CACrE;EAEJ,IAAI,CAAC,SAAS,YAAY,CAAC,SAAS,MAChC,OAAO,KAAK,YAAY,WAAW,SAAS,SAAS,MAAM;EAE/D,OAAO,KAAK,aAAa,MAAM,KAAK,YAAY,SAAS,QAAQ,GAAG,SAAS,OAAO;CACxF;;;;;CAMA,MAAM,aAAa,SAAiB,SAGP;EACzB,MAAM,WAAW,MAAM,KAAK,YAAY,SAAS,QAAQ;EACzD,MAAM,OAAO,aAAa,WAAW,SAAS,UAAU,KAAA;EACxD,IAAI,EAAE,gBAAgB,OAGlB,OAAO,wBAAwB,MAAM,KAAK,WAAW,SAAS;GAC1D,UAAU,SAAS;GACnB,MAAM,SAAS;GACf,gBAAgB;EACpB,CAAC,CAAC;EAEN,MAAM,OAAO,SAAS;EACtB,OAAO,mBAAmB,MAAM,SAAS,EACrC,YAAY,QAAQ,eAAe,KAAK,kBAAkB,YAAY,IAAI,IAAI,KAAA,EAClF,CAAC;CACL;;;;;;;;;;;;CAaA,MAAc,kBAAkB,YAAwB,MAA6B;EAEjF,KAAI,MADkB,WAAW,MAAwB,6BAA6B,EAAA,CAC1E,KAAK,EAAE,EAAE,SAAS,MAAM;EACpC,IAAI,wBAAwB,GAAG;GAC3B,OAAO,MACH,yGACiC,KAAK,GAC1C;GACA;EACJ;EACA,IAAI,KAAK,2BAA2B,MAAM,IAAI,2BAA2B,IAAI;EAC7E,IAAI;GACA,MAAM,WAAW,MAAM,aAAa,KAAK,QAAQ,MAAM,MAAM,EAAE,EAAE;EACrE,SAAS,WAAoB;GACzB,IAAI,+BAA+B,SAAS,GAAG;IAC3C,KAAK,4BAA4B;IACjC,MAAM,IAAI,2BAA2B,MAAM,SAAS;GACxD;GACA,MAAM;EACV;CACJ;;;;;;;;;;;;;;;;;;;CAoBA,MAAc,kBACV,UACA,MACA,IACU;EACV,MAAM,OAAO,aAAa,WAAW,SAAS,UAAU,KAAA;EACxD,IAAI,EAAE,gBAAgB,OAClB,IAAI;GACA,OAAO,MAAM,GAAG,UAAU,IAAI;EAClC,UAAU;GACN,KAAK,MAAM,aAAa,0BACpB,MAAM,SAAS,QAAQ,IAAW,IAAI,SAAS,CAAC,CAAC,CAAC,OAAO,UACrD,OAAO,MAAM,wEAAwE,EAAE,MAAM,CAAC,CAAC;EAE3G;EAEJ,OAAO,aACH,MACA,EAAE,YAAY,QAAQ,eAAe,KAAK,kBAAkB,YAAY,IAAI,IAAI,KAAA,EAAU,IACzF,eAAe,GAAG,QAAQ,UAAU,GAAG,KAAA,CAAS,CACrD;CACJ;CAEA,MAAc,aAAa,UAAyB,SAAiB,SAI9B;EACnC,IAAI;GAQA,IAAI,kBAAkB;GACtB,IAAI,SAAS,MACT,IAAI;IAEA,MAAM,gBAAe,MADW,SAAS,QAAQ,IAAW,IAAI,6BAA6B,CAAC,EAAA,CACvD,OAAO,GAAA,EAAgC;IAC9E,kBAAkB,CAAC,CAAC,eAAe,gBAAgB,QAAQ;GAC/D,QAAQ;IAIJ,kBAAkB;GACtB;GAGJ,IAAI,mBAAmB,SAAS,MAC5B,IAAI,wBAAwB,GAQxB,OAAO,MACH,yGACiC,QAAQ,KAAK,GAClD;QACG,IAAI,KAAK,2BAGZ,MAAM,IAAI,2BAA2B,QAAQ,IAAI;QAC9C;IACH,MAAM,WAAW,QAAQ,KAAK,QAAQ,MAAM,MAAM;IAClD,IAAI;KACA,OAAO,MAAM,SAAS,YAAY,OAAO,OAAO;MAC5C,MAAM,GAAG,QAAQ,IAAW,IAAI,mBAAmB,SAAS,EAAE,CAAC;MAE/D,QAAO,MADc,GAAG,QAAQ,KAAK,eAAe,SAAS,SAAS,MAAM,CAAC,EAAA,CAC/D;KAClB,CAAC;IACL,SAAS,WAAoB;KACzB,IAAI,+BAA+B,SAAS,GAAG;MAgB3C,KAAK,4BAA4B;MACjC,MAAM,IAAI,2BAA2B,QAAQ,MAAM,SAAS;KAChE;KACA,MAAM;IACV;GACJ;GAIJ,QAAO,MADc,SAAS,QAAQ,KAAK,eAAe,SAAS,SAAS,MAAM,CAAC,EAAA,CACrE;EAClB,SAAS,OAAgB;GACrB,IAAI,iBAAiB,4BAA4B,MAAM;GACvD,MAAM,MAAM,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;GAEjE,IAAI,IAAI,SAAS,aAAa,KAAK,IAAI,SAAS,eAAe,KAAK,IAAI,SAAS,oBAAoB,GAAG;IACpG,MAAM,SAAS,SAAS,YAAY;IACpC,MAAM,IAAI,MAAM,+BAA+B,OAAO,0GAA0G;GACpK;GACA,MAAM;EACV;CACJ;CAEA,MAAM,0BAA6C;EAQ/C,MAAM,aAAY,MANG,KAAK,WACtB;;;+BAIJ,EAAA,CACyB,KAAK,MAA+B,EAAE,OAAiB;EAEhF,MAAM,YAAY,MAAM,KAAK,gBAAgB;EAC7C,IAAI,CAAC,UAAU,SAAS,SAAS,GAC7B,UAAU,QAAQ,SAAS;OACxB;GAEH,MAAM,MAAM,UAAU,QAAQ,SAAS;GACvC,IAAI,MAAM,GAAG;IACT,UAAU,OAAO,KAAK,CAAC;IACvB,UAAU,QAAQ,SAAS;GAC/B;EACJ;EACA,OAAO;CACX;CAEA,MAAM,sBAAyC;EAI3C,QAAO,MAHc,KAAK,WACtB,mGACJ,EAAA,CACc,KAAK,MAA+B,EAAE,OAAiB;CACzE;;;;;;;;;;;;;;;;;;CAmBA,MAAM,wBAA2C;EAI7C,MAAM,UAAU,MAAM,KAAK,WAAW;;;;;;;;;SASrC;EACD,IAAI,QAAQ,WAAW,GAAG,OAAO,CAAC;EAElC,MAAM,SAAS,QAAQ,EAAE,CAAC;EAC1B,MAAM,QAAQ,QAAQ,EAAE,CAAC;EAGzB,MAAM,YAAY,IAAI,OAAO,QAAQ,MAAM,MAAM,EAAE,KAAK,MAAM,QAAQ,MAAM,MAAM,EAAE;EAQpF,QAAO,MANY,KAAK,WAAW;;mBAExB,UAAU;;;SAGpB,EAAA,CAEI,KAAK,MAAM,EAAE,IAAc,CAAC,CAC5B,QAAQ,MAAmB,OAAO,MAAM,YAAY,EAAE,SAAS,CAAC;CACzE;CAEA,MAAM,uBAAoD;EACtD,OAAO,KAAK,gBAAgB;CAChC;;;;;;CAOA,MAAM,oBAAoB,aAA2C;EASjE,MAAM,aAAY,MARG,KAAK,WAAW;;;;;;SAMpC,EAAA,CAGI,KAAK,MAA+B,EAAE,UAAoB,CAAC,CAC3D,QAAQ,SAAiB,cAAc,MAAM,QAAQ,MAAM,iBAAiB;EAIjF,IAAI,iCAAiB,IAAI,IAAY;EACrC,IAAI;GACA,iBAAiB,MAAM,qBAAqB,KAAK,WAAW,KAAK,IAAI,CAAC;EAC1E,SAAS,GAAG;GACR,OAAO,KAAK,oCAAoC,EAAE,OAAO,EAAE,CAAC;EAChE;EAEA,MAAM,iBAAiB,UAAU,QAAO,SAAQ,CAAC,eAAe,IAAI,IAAI,CAAC;EAEzE,IAAI,CAAC,eAAe,YAAY,WAAW,GAAG,OAAO;EAErD,MAAM,YAAY,IAAI,IAAI,YAAY,KAAI,MAAK,EAAE,YAAY,CAAC,CAAC;EAC/D,OAAO,eAAe,QAAQ,SAAiB,CAAC,UAAU,IAAI,KAAK,YAAY,CAAC,CAAC;CACrF;;;;CAKA,MAAM,mBAAmB,WAA2C;EAEhE,MAAM,WAAW,UAAU,QAAQ,kBAAkB,EAAE;EAUvD,MAAM,WAAU,MAPK,KAAK,GAAG,QAAQ,GAAU;;;;iCAItB,SAAS;;SAEjC,EAAA,CACsB;EAGvB,MAAM,cAAc,QAAQ,QAAQ,MAAM,EAAE,cAAc,cAAc;EACxE,IAAI,YAAY,SAAS,GACrB,KAAK,MAAM,OAAO,aACd,IAAI;GAQA,IAAI,eAAe,MAPM,KAAK,GAAG,QAAQ,GAAU;;;;4CAI3B,IAAI,SAAmB;;qBAE9C,EAAA,CAC6B,KAAmC,KAAI,MAAK,EAAE,SAAS;EACzF,QAAQ;GACJ,IAAI,cAAc,CAAC;EACvB;EAqER,OAAO;GACH,SAhEiB,QAAyB,OAgEjC;GACT,cA9CgB,MAhBG,KAAK,GAAG,QAAQ,GAAU;;;;;;;;;;;;;2EAakB,SAAS;SAC3E,EAAA,CAE4B;GA+CzB,YA1Bc,MAjBY,KAAK,GAAG,QAAQ,GAAU;;;;;;;;;;;;;sCAa1B,SAAS;uCACR,SAAS;SACvC,EAAA,CAEiC;GA2B9B,WANa,MAZY,KAAK,GAAG,QAAQ,GAAU;;;;;;;;;0DASL,SAAS;SAC1D,EAAA,CAE+B;EAOhC;CACJ;CAEA,yBAAyC;EACrC,OAAO,OAAO,KAAK,IAAI,EAAE,GAAG,KAAK,OAAO,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,UAAU,GAAG,CAAC;CACzE;;;;;;CAOA,MAAM,SAAS,MAAiC;EAC5C,OAAO,IAAI,mCAAmC,MAAM,IAAI;CAC5D;AACJ;AAEA,IAAa,qCAAb,MAAsE;CAQvD;CAPX,MAAM;CACN,cAAc;CAEd;CACA;CAEA,YACI,UACA,MACF;EAFS,KAAA,WAAA;EAGP,KAAK,OAAO;EACZ,KAAK,OAAO,aAAa,IAAI;EAG7B,KAAK,QAAQ,SAAS;CAC1B;;;;CAKA;CAEA,IAAI,mBAAqC;EACrC,OAAO;GAGH,wBAAwB,OAAO,gBAAgB,SAAS,YAAY;IAChE,OAAO,KAAK,gBAAgB,OAAO,aAAa;KAC5C,OAAO,SAAS,iBAAiB,uBAAuB,gBAAgB,SAAS,OAAO;IAC5F,GAAG,EAAE,YAAY,YAAY,CAAC;GAClC;GACA,iBAAiB,OAAO,gBAAgB,IAAI,SAAS,YAAY,YAAY;IACzE,OAAO,KAAK,gBAAgB,OAAO,aAAa;KAC5C,OAAO,SAAS,iBAAiB,gBAAgB,gBAAgB,IAAI,SAAS,YAAY,OAAO;IACrG,GAAG,EAAE,YAAY,YAAY,CAAC;GAClC;GAOA,WAAW,OAAO,gBAAgB,YAAY;IAC1C,OAAO,KAAK,gBAAgB,OAAO,aAAa;KAC5C,OAAO,SAAS,iBAAiB,UAAW,gBAAgB,OAAO;IACvE,GAAG,EAAE,YAAY,YAAY,CAAC;GAClC;GAIA,YAAY,gBAAgB,KAAK,YAC7B,KAAK,SAAS,iBAAiB,YAAY,gBAAgB,KAAK,OAAO;EAC/E;CACJ;CAEA,MAAc,gBACV,WACA,SAIU;EACV,MAAM,uBAAuE,CAAC;EAQ9E,MAAM,aAAa,SAAS,eAAe,cAAc,KAAA,IAAY,IAAI,sBAAsB;EAI/F,MAAM,SAAS,KAAK,SAAS;EAC7B,YAAY,kBAAkB;GAC1B,MAAM,KAAK;GACX,QAAQ;GACR,MAAM,KAAK;GACX,IAAI,SAAS;IACT,OAAO,sBAAsB,MAAM;GACvC;GACA,eAAe,QAAQ;EAC3B,CAAC;EAUD,MAAM,SAAS,MAAM,gBAAgB,EAAE,OAAO,KAAK,MAAM,SAAS,CAAC,EAAE,GAAG,YACpE,MAAM,gBAAgB,kBAAkB,KAAK,SAAS,GAAG,YAAY,OAAO,OAAO;GAC/E,IAAI,CAAC,KAAK,MAAM,KACZ,OAAO,KAAK,oGAAoG,EAAE,QAAQ,KAAK,KAAK,CAAC;GAEzI,IAAI,CAAC,KAAK,MAAM,OACZ,OAAO,KAAK,kGAAkG,EAAE,QAAQ,KAAK,KAAK,CAAC;GA0BvI,MAAM,iBAAiB,IAAI,cAAc,KAAK,IAAI,GAAG,KAAK,SAAS,WAAW;GAQ9E,IAAI;GACJ,MAAM,kBAAkB,IAAI,YACxB,IAAI,KAAK,SAAS,gBACX,gBAAgB,KAAK,SAAS,kBAAkB,IAAI,KAAK,IAAI,CACxE;GACA,MAAM,aAAa,IAAI,sBAAsB,IAAI,KAAK,SAAS,iBAAiB,KAAK,SAAS,UAAU,KAAK,MAAM,KAAK,SAAS,aAAa,KAAK,SAAS,cAAc;GAE1K,WAAW,cAAc;GACzB,WAAW,sBAAsB;GACjC,WAAW,wBAAwB;GACnC,WAAW,SAAS,KAAK,SAAS;GAElC,YAAY,KAAK,KAAK,SAAS,WAAW,gBAAgB,WAAW,SAAS,MAAM,CAAC;GACrF,IAAI;GACJ,IAAI;IACA,MAAM,MAAM,UAAU,UAAU;GACpC,SAAS,OAAO;IAIZ,YAAY,MAAM;IAClB,MAAM;GACV;GAGA,MAAM,YAAY,OAAO;GAIzB,MAAM,YAAY,kBAAkB;GACpC,OAAO;EACX,GAAG,OAAO,CAAC,CAAC,CAAC,cAAc,YAAY,MAAM,CAAC,CAClD,CAAC,CAAC,MAAM,OAAO,UAAmB;GAC9B,MAAM,YAAY,QAAQ;GAC1B,MAAM;EACV,CAAC;EAED,YAAY,UAAU;EACtB,MAAM,YAAY,QAAQ;EAE1B,KAAK,MAAM,gBAAgB,sBACvB,IAAI;GACA,MAAM,KAAK,SAAS,gBAAgB,aAChC,aAAa,MACb,aAAa,IACb,aAAa,KACb,aAAa,UACjB;EACJ,SAAS,GAAG;GACR,OAAO,MAAM,qDAAqD,EAAE,OAAO,EAAE,CAAC;EAClF;EAGJ,OAAO;CACX;CAEA,MAAM,gBAAmD,OAAoE;EACzH,OAAO,KAAK,iBAAiB,aAAa,SAAS,gBAAgB,KAAK,GAAG,EAAE,YAAY,YAAY,CAAC;CAC1G;;;;;;;;;CAUA,iBAAoD,OAA6C;EAC7F,OAAO,KAAK,SAAS,iBAAiB,OAAO,cAAc,KAAK,IAAI,CAAC;CACzE;CAEA,MAAM,SAA4C,OAAuE;EACrH,OAAO,KAAK,iBAAiB,aAAa,SAAS,SAAS,KAAK,GAAG,EAAE,YAAY,YAAY,CAAC;CACnG;CAEA,UAA6C,OAAsC;EAC/E,OAAO,KAAK,SAAS,UAAU,OAAO,cAAc,KAAK,IAAI,CAAC;CAClE;CAEA,MAAM,KAAwC,OAAuD;EACjG,OAAO,KAAK,iBAAiB,aAAa,SAAS,KAAK,KAAK,CAAC;CAClE;;;;;;;;;;CAWA,MAAM,SAA4C,OAA6D;EAC3G,OAAO,KAAK,iBAAiB,aAAa,SAAS,SAAS,KAAK,CAAC;CACtE;;;;;;;;;;;CAYA,MAAM,WAA8C,OAA+D;EAC/G,OAAO,KAAK,iBAAiB,aAAa,SAAS,WAAW,KAAK,CAAC;CACxE;CAEA,MAAM,WAA8C,OAA0C;EAC1F,OAAO,KAAK,iBAAiB,aAAa,SAAS,WAAW,KAAK,CAAC;CACxE;;;;;;;;;CAUA,MAAM,WACF,OAC2C;EAC3C,OAAO,KAAK,iBAAiB,aAAa,SAAS,WAAW,KAAK,CAAC;CACxE;CAEA,MAAM,OAA0C,OAAsC;EAClF,OAAO,KAAK,iBAAiB,aAAa,SAAS,OAAO,KAAK,CAAC;CACpE;CAEA,MAAM,UAAU,MAA6B;EACzC,OAAO,KAAK,iBAAiB,aAAa,SAAS,UAAU,IAAI,CAAC;CACtE;CAEA,MAAM,oBAAoB,OAAgD;EACtE,OAAO,KAAK,iBAAiB,aAAa,SAAS,oBAAoB,KAAK,CAAC;CACjF;CAEA,MAAM,iBACF,MACA,MACA,OACA,IACA,YACgB;EAChB,OAAO,KAAK,iBAAiB,aAAa,SAAS,iBAAiB,MAAM,MAAM,OAAO,IAAI,UAAU,GAAG,EAAE,YAAY,YAAY,CAAC;CACvI;CAEA,MAAM,MAAyC,OAAiD;EAC5F,OAAO,KAAK,iBAAiB,aAAa,SAAS,MAAM,KAAK,GAAG,EAAE,YAAY,YAAY,CAAC;CAChG;AAEJ;;;;;;;;;;;;;;;;;AC1+FA,SAAgB,iBAAiB,kBAAkB,UAAU;CACzD,MAAM,cAAc,oBAAoB,WAAW,OAAO,SAAS,eAAe;CAElF,MAAM,eAAgB,cAAc,YAAY,MAAM,KAAK,WAAW,IAAI;;;;CAM1E,MAAM,QAAQ,aAAkB,SAAS;EACrC,IAAI,KAAK,IAAI,CAAC,CAAC,cAAc,CAAC,CAAC,WAAW;EAC1C,OAAO,KAAK,OAAO,CAAC,CAAC,QAAQ,CAAC,CAAC,OAAO;EACtC,cAAc,KAAK,eAAe;EAClC,aAAa,KAAK,cAAc;EAChC,UAAU,KAAK,WAAW;EAC1B,eAAe,QAAQ,gBAAgB,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,QAAQ;EAChE,wBAAwB,KAAK,0BAA0B;EACvD,yBAAyB,UAAU,4BAA4B;EAC/D,aAAa,QAAQ,cAAc,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,QAAQ;EAC5D,OAAO,KAAK,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ;EACjD,UAAU,MAAM,UAAU,CAAC,CAAC,MAA+B,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ;;;;;;;;;;;EAWjF,kBAAkB,UAAU,oBAAoB;;EAEhD,YAAY,UAAU,eAAe,EAAE,cAAc,KAAK,CAAC;;;;;;;EAO3D,cAAc,KAAK,eAAe;EAClC,kBAAkB,KAAK,oBAAoB;EAC3C,mBAAmB,UAAU,wBAAwB,EAAE,cAAc,KAAK,CAAC;EAC3E,WAAW,UAAU,YAAY,CAAC,CAAC,WAAW,CAAC,CAAC,QAAQ;EACxD,WAAW,UAAU,YAAY,CAAC,CAAC,WAAW,CAAC,CAAC,QAAQ;CAC5D,CAAC;;;;;;;;;;;;;;;;;;;;;CAuBD,MAAM,gBAAgB,aAAa,kBAAkB;EACjD,IAAI,KAAK,IAAI,CAAC,CAAC,cAAc,CAAC,CAAC,WAAW;EAC1C,KAAK,KAAK,KAAK,CAAC,CAAC,QAAQ,CAAC,CAAC,iBAAiB,MAAM,IAAI,EAAE,UAAU,UAAU,CAAC;EAC7E,WAAW,KAAK,YAAY,CAAC,CAAC,cAAc,CAAC,CAAC,QAAQ;EACtD,WAAW,KAAK,YAAY,CAAC,CAAC,QAAQ,CAAC,CAAC,OAAO;EAC/C,WAAW,UAAU,YAAY,CAAC,CAAC,QAAQ;EAC3C,SAAS,QAAQ,SAAS,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,QAAQ;EACnD,WAAW,UAAU,YAAY;;;;;;;EAOjC,kBAAkB,UAAU,oBAAoB,CAAC,CAAC,WAAW,CAAC,CAAC,QAAQ;;;;;;;EAOvE,KAAK,KAAK,KAAK;;;;;;EAMf,QAAQ,KAAK,QAAQ;EACrB,WAAW,KAAK,YAAY;EAC5B,WAAW,KAAK,YAAY;EAC5B,WAAW,UAAU,YAAY,CAAC,CAAC,WAAW,CAAC,CAAC,QAAQ;CAC5D,IAAI,WAAW,EACX,YAAY,MAAM,4BAA4B,CAAC,CAAC,GAAG,MAAM,SAAS,EACtE,EAAE;;;;CAKF,MAAM,sBAAsB,aAAa,yBAAyB;EAC9D,IAAI,KAAK,IAAI,CAAC,CAAC,cAAc,CAAC,CAAC,WAAW;EAC1C,KAAK,KAAK,KAAK,CAAC,CAAC,QAAQ,CAAC,CAAC,iBAAiB,MAAM,IAAI,EAAE,UAAU,UAAU,CAAC;EAC7E,WAAW,KAAK,YAAY,CAAC,CAAC,QAAQ,CAAC,CAAC,OAAO;EAC/C,WAAW,UAAU,YAAY,CAAC,CAAC,QAAQ;EAC3C,QAAQ,UAAU,SAAS;EAC3B,WAAW,UAAU,YAAY,CAAC,CAAC,WAAW,CAAC,CAAC,QAAQ;CAC5D,CAAC;;;;CAKD,MAAM,YAAY,aAAa,cAAc;EACzC,KAAK,KAAK,KAAK,CAAC,CAAC,WAAW;EAC5B,OAAO,MAAM,OAAO,CAAC,CAAC,QAAQ;EAC9B,WAAW,UAAU,YAAY,CAAC,CAAC,WAAW,CAAC,CAAC,QAAQ;CAC5D,CAAC;;;;CAKD,MAAM,iBAAiB,aAAa,mBAAmB;EACnD,IAAI,KAAK,IAAI,CAAC,CAAC,cAAc,CAAC,CAAC,WAAW;EAC1C,KAAK,KAAK,KAAK,CAAC,CAAC,QAAQ,CAAC,CAAC,iBAAiB,MAAM,IAAI,EAAE,UAAU,UAAU,CAAC;EAC7E,UAAU,KAAK,UAAU,CAAC,CAAC,QAAQ;EACnC,YAAY,KAAK,aAAa,CAAC,CAAC,QAAQ;EACxC,aAAa,MAAM,cAAc;EACjC,WAAW,UAAU,YAAY,CAAC,CAAC,WAAW,CAAC,CAAC,QAAQ;EACxD,WAAW,UAAU,YAAY,CAAC,CAAC,WAAW,CAAC,CAAC,QAAQ;CAC5D,IAAI,WAAW,EACX,kBAAkB,OAAO,oBAAoB,CAAC,CAAC,GAAG,MAAM,UAAU,MAAM,UAAU,EACtF,EAAE;;;;CAKF,MAAM,aAAa,aAAa,eAAe;EAC3C,IAAI,KAAK,IAAI,CAAC,CAAC,cAAc,CAAC,CAAC,WAAW;EAC1C,KAAK,KAAK,KAAK,CAAC,CAAC,QAAQ,CAAC,CAAC,iBAAiB,MAAM,IAAI,EAAE,UAAU,UAAU,CAAC;EAC7E,YAAY,KAAK,aAAa,CAAC,CAAC,QAAQ;EACxC,iBAAiB,KAAK,kBAAkB,CAAC,CAAC,QAAQ;EAClD,cAAc,KAAK,eAAe;EAClC,UAAU,QAAQ,UAAU,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,QAAQ;;;;;;;EAOrD,iBAAiB,OAAO,qBAAqB,EAAE,MAAM,SAAS,CAAC;EAC/D,WAAW,UAAU,YAAY,CAAC,CAAC,WAAW,CAAC,CAAC,QAAQ;EACxD,WAAW,UAAU,YAAY,CAAC,CAAC,WAAW,CAAC,CAAC,QAAQ;CAC5D,CAAC;CAuCD,OAAO;EACH;EACA;EACA;EACA;EACA;EACA;EACA;EACA,eA1CkB,aAAa,kBAAkB;GACjD,IAAI,KAAK,IAAI,CAAC,CAAC,cAAc,CAAC,CAAC,WAAW;GAC1C,UAAU,KAAK,WAAW,CAAC,CAAC,QAAQ,CAAC,CAAC,iBAAiB,WAAW,IAAI,EAAE,UAAU,UAAU,CAAC;GAC7F,WAAW,UAAU,YAAY,CAAC,CAAC,WAAW,CAAC,CAAC,QAAQ;GACxD,YAAY,UAAU,aAAa;GACnC,WAAW,KAAK,YAAY;;GAE5B,UAAU,QAAQ,UAAU,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ;GACjD,WAAW,UAAU,YAAY,CAAC,CAAC,QAAQ;EAC/C,CAiCI;EACA,eA7BkB,aAAa,kBAAkB;GACjD,IAAI,KAAK,IAAI,CAAC,CAAC,cAAc,CAAC,CAAC,WAAW;GAC1C,KAAK,KAAK,KAAK,CAAC,CAAC,QAAQ,CAAC,CAAC,iBAAiB,MAAM,IAAI,EAAE,UAAU,UAAU,CAAC;GAC7E,UAAU,KAAK,WAAW,CAAC,CAAC,QAAQ;GACpC,QAAQ,UAAU,SAAS;GAC3B,WAAW,UAAU,YAAY,CAAC,CAAC,WAAW,CAAC,CAAC,QAAQ;EAC5D,CAuBI;EACA,iBAnBoB,aAAa,qBAAqB;GACtD,IAAI,KAAK,IAAI,CAAC,CAAC,cAAc,CAAC,CAAC,WAAW;GAC1C,KAAK,KAAK,KAAK,CAAC,CAAC,QAAQ,CAAC,CAAC,iBAAiB,MAAM,IAAI,EAAE,UAAU,UAAU,CAAC;GAC7E,WAAW,KAAK,YAAY,CAAC,CAAC,QAAQ,CAAC,CAAC,OAAO;GAC/C,WAAW,UAAU,YAAY,CAAC,CAAC,QAAQ;GAC3C,QAAQ,UAAU,SAAS;GAC3B,WAAW,UAAU,YAAY,CAAC,CAAC,WAAW,CAAC,CAAC,QAAQ;EAC5D,CAYI;CACJ;AACJ;AAGA,IAAM,oBAAoB,iBAAiB,QAAQ;AAEnD,IAAa,cAAc,kBAAkB;AAE7C,IAAa,QAAQ,kBAAkB;AACvC,IAAa,gBAAgB,kBAAkB;AAC/C,IAAa,sBAAsB,kBAAkB;AACrD,IAAa,YAAY,kBAAkB;AAC3C,IAAa,iBAAiB,kBAAkB;AAChD,IAAa,aAAa,kBAAkB;AAC5C,IAAa,gBAAgB,kBAAkB;AAC/C,IAAa,gBAAgB,kBAAkB;AAC/C,IAAa,kBAAkB,kBAAkB;AAGjD,IAAa,iBAAiB,UAAU,QAAQ,EAAE,YAAY;CAC1D,eAAe,KAAK,aAAa;CACjC,qBAAqB,KAAK,mBAAmB;CAC7C,gBAAgB,KAAK,cAAc;CACnC,YAAY,KAAK,UAAU;CAC3B,eAAe,KAAK,aAAa;CACjC,iBAAiB,KAAK,eAAe;AACzC,EAAE;AAEF,IAAa,yBAAyB,UAAU,gBAAgB,EAAE,WAAW,EACzE,MAAM,IAAI,OAAO;CACb,QAAQ,CAAC,cAAc,GAAG;CAC1B,YAAY,CAAC,MAAM,EAAE;AACzB,CAAC,EACL,EAAE;AAEF,IAAa,+BAA+B,UAAU,sBAAsB,EAAE,WAAW,EACrF,MAAM,IAAI,OAAO;CACb,QAAQ,CAAC,oBAAoB,GAAG;CAChC,YAAY,CAAC,MAAM,EAAE;AACzB,CAAC,EACL,EAAE;AAEF,IAAa,0BAA0B,UAAU,iBAAiB,EAAE,WAAW,EAC3E,MAAM,IAAI,OAAO;CACb,QAAQ,CAAC,eAAe,GAAG;CAC3B,YAAY,CAAC,MAAM,EAAE;AACzB,CAAC,EACL,EAAE;AAEF,IAAa,sBAAsB,UAAU,aAAa,EAAE,KAAK,YAAY;CACzE,MAAM,IAAI,OAAO;EACb,QAAQ,CAAC,WAAW,GAAG;EACvB,YAAY,CAAC,MAAM,EAAE;CACzB,CAAC;CACD,YAAY,KAAK,aAAa;AAClC,EAAE;AAEF,IAAa,yBAAyB,UAAU,gBAAgB,EAAE,WAAW,EACzE,QAAQ,IAAI,YAAY;CACpB,QAAQ,CAAC,cAAc,QAAQ;CAC/B,YAAY,CAAC,WAAW,EAAE;AAC9B,CAAC,EACL,EAAE;AAEF,IAAa,yBAAyB,UAAU,gBAAgB,EAAE,WAAW,EACzE,MAAM,IAAI,OAAO;CACb,QAAQ,CAAC,cAAc,GAAG;CAC1B,YAAY,CAAC,MAAM,EAAE;AACzB,CAAC,EACL,EAAE;AAEF,IAAa,2BAA2B,UAAU,kBAAkB,EAAE,WAAW,EAC7E,MAAM,IAAI,OAAO;CACb,QAAQ,CAAC,gBAAgB,GAAG;CAC5B,YAAY,CAAC,MAAM,EAAE;AACzB,CAAC,EACL,EAAE;;;;;;;;;;;AC1QF,SAAgB,qBAAqB,UAAsD;CACvF,MAAM,QAAwB,CAAC;CAC/B,MAAM,uBAAO,IAAI,IAAY;CAE7B,KAAK,MAAM,cAAc,SAAS,eAAe,GAAG;EAChD,IAAI;EACJ,IAAI;GACA,YAAY,2BAA2B,UAAU;EACrD,QAAQ;GAGJ;EACJ;EAEA,KAAK,MAAM,CAAC,aAAa,aAAa,OAAO,QAAQ,SAAS,GAAG;GAC7D,IAAI,CAAC,aAAa,QAAQ,GAAG;GAC7B,MAAM,UAAU,SAAS;GAIzB,MAAM,MAAM,GAAG,WAAW,KAAK,IAAI,YAAY,IAAI,QAAQ;GAC3D,IAAI,KAAK,IAAI,GAAG,GAAG;GACnB,KAAK,IAAI,GAAG;GAEZ,MAAM,KAAK;IACP,QAAS,WAAmC,UAAU;IACtD,OAAO,QAAQ;IACf,kBAAkB;IAClB;IACA,cAAc,QAAQ;IACtB,cAAc,QAAQ;GAC1B,CAAC;EACL;CACJ;CAEA,OAAO;AACX;;;;;;AAOA,SAAgB,qBAAqB,UAAmE;CACpG,MAAM,sBAAM,IAAI,IAA4B;CAE5C,KAAK,MAAM,QAAQ,qBAAqB,QAAQ,GAC5C,KAAK,MAAM,OAAO,CAAC,GAAG,KAAK,OAAO,GAAG,KAAK,SAAS,KAAK,KAAK,GAAG;EAC5D,MAAM,WAAW,IAAI,IAAI,GAAG;EAC5B,IAAI,UAAU,SAAS,KAAK,IAAI;OAC3B,IAAI,IAAI,KAAK,CAAC,IAAI,CAAC;CAC5B;CAGJ,OAAO;AACX;;;;;;;;;;;;;;ACjEA,SAAgB,qBAAqB,YAA8B,UAAmD;CAClH,MAAM,QAAQ,SAAS,SAAS,eAAa,UAAU,CAAC;CACxD,MAAM,UAAU,QAAQ,gBAAgB,KAAK,IAAI,KAAA;CACjD,OAAO,eAAe,YAAY,QAAQ,CAAC,CAAC,KAAK,OAAO;EACpD,MAAM,WAAW,WAAW,aAAa,GAAG;EAC5C,MAAM,WAAW,YAAY,gBAAgB,WAAW,SAAS,aAAa,KAAA;EAC9E,OAAO;GACH,WAAW,GAAG;GACd,YAAY,UAAU,GAAG,UAAU,EAAE,QAAQ,YAAY,YAAY,GAAG,SAAS;EACrF;CACJ,CAAC;AACL;;;;;;;;;;;;;;;;;;;AChBA,SAAgB,0BACZ,SACA,YACA,QACI;CAMJ,MAAM,iBAAiB,QAAQ,gBAAgB,OAAO,QAAQ,iBAAiB,WACzE,QAAQ,aAAa,WACrB,KAAA;CACN,0BAA0B;EACtB,OAAO,QAAQ,UAAU,OAAO,QAAQ,WAAW,WAAW,QAAQ,SAAS,KAAA;EAC/E,SAAS,QAAQ;EACjB,SAAS,aAAa,QAAQ,OAAO;EACrC,QAAQ,MAAM,QAAQ,QAAQ,MAAM,IAAI,QAAQ,SAAS,KAAA;EACzD,SAAS,QAAQ,WAAW,KAAA;EAC5B,cAAc,OAAO,mBAAmB,WAAW,EAAE,UAAU,eAAe,IAAI,KAAA;CACtF,GAAG,YAAY,MAAM;AACzB;;AAGA,SAAS,aAAa,SAAmD;CACrE,IAAI,OAAO,YAAY,UAAU,OAAO,CAAC,EAAE,OAAO,QAAQ,CAAC;CAC3D,IAAI,CAAC,MAAM,QAAQ,OAAO,GAAG,OAAO,KAAA;CACpC,OAAO,QAAQ,SAAS,UACpB,MAAM,QAAQ,KAAK,KAAK,OAAO,MAAM,OAAO,WAAW,CAAC,EAAE,OAAO,MAAM,GAAG,CAAC,IAAI,CAAC,CAAC;AACzF;;;;;;;;;;;;;;;;;;;;;;;;ACxBA,IAAa,cAAc;;AAG3B,IAAa,uBAAuB;;AAGpC,IAAa,mBAAmB;;;;;;;;AAShC,IAAM,mBAAmB;AAEzB,IAAM,cAAc,SAAyB,IAAI,KAAK,QAAQ,MAAM,MAAM,EAAE;AAC5E,IAAM,gBAAgB,UAA0B,IAAI,MAAM,QAAQ,MAAM,IAAI,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;AA2B9E,SAAgB,sBAA8B;CAC1C,OAAO;;;6BAGkB,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kCA2ChB,iBAAiB;;;;;;;;;;wBAU3B,aAAa,WAAW,EAAE;;;;EAIhD,KAAK;AACP;;;;;;;;;;AAWA,SAAgB,mBAAmB,QAAgB,OAAe,kBAAqC,CAAC,GAAW;CAC/G,MAAM,YAAY,GAAG,WAAW,MAAM,EAAE,GAAG,WAAW,KAAK;CAC3D,MAAM,OAAO,CAAC,GAAG,IAAI,IAAI,eAAe,CAAC,CAAC,CAAC,IAAI,YAAY,CAAC,CAAC,KAAK,IAAI;CACtE,OACI,0BAA0B,WAAW,gBAAgB,EAAE,MAAM,UAAU,oBACrD,WAAW,gBAAgB,EAAE,uCACR,UAAU,iCAChB,qBAAqB,GAAG,KAAK;AAEtE;;;;;;;;;;;;AA+BA,eAAsB,4BAA4B,KAAqC;CAEnF,KAAI,MADe,IAAI,0BAA0B,aAAa,4BAA2B,EAAE,2BAA2B,EAAA,CAC7G,EAAE,EAAE,cAAc,MAAM,OAAO;CACxC,MAAM,IAAI,oBAAoB,CAAC;CAC/B,OAAO;AACX;;;;;;;;;;;AAYA,eAAe,uBAAuB,KAAmB,KAAqC;CAC1F,MAAM,YAAY,IAAI,mBAAmB,CAAC;CAC1C,MAAM,YAAY,GAAG,WAAW,IAAI,MAAM,EAAE,GAAG,WAAW,IAAI,KAAK;CACnE,IAAI;CACJ,IAAI;EAQA,WAAU,MAPS,IACf,4MAGkC,aAAa,SAAS,EAAE,4DAE9D,EAAA,CACe,KAAK,SAAS;GAAE,MAAM,OAAO,IAAI,IAAI;GAAG,SAAS,IAAI,eAAe;EAAK,EAAE;CAC9F,QAAQ;EACJ,OAAO,CAAC,GAAG,IAAI,IAAI,SAAS,CAAC;CACjC;CACA,IAAI,QAAQ,WAAW,GAAG,OAAO,CAAC,GAAG,IAAI,IAAI,SAAS,CAAC;CACvD,MAAM,WAAW,IAAI,IAAI,QAAQ,KAAK,WAAW,OAAO,IAAI,CAAC;CAC7D,OAAO,CAAC,mBAAG,IAAI,IAAI,CACf,GAAG,QAAQ,QAAQ,WAAW,OAAO,OAAO,CAAC,CAAC,KAAK,WAAW,OAAO,IAAI,GACzE,GAAG,UAAU,QAAQ,WAAW,SAAS,IAAI,MAAM,CAAC,CACxD,CAAC,CAAC;AACN;;;;;;;;;AAUA,eAAsB,oBAClB,KACA,QACwB;CAExB,MAAM,IAAI,oBAAoB,CAAC;CAK/B,MAAM,0BAAU,IAAI,IAAyB;CAC7C,KAAK,MAAM,OAAO,QAAQ;EACtB,MAAM,MAAM,GAAG,IAAI,OAAO,GAAG,IAAI;EACjC,MAAM,WAAW,QAAQ,IAAI,GAAG;EAChC,IAAI,CAAC,UACD,QAAQ,IAAI,KAAK;GAAE,GAAG;GAAK,iBAAiB,CAAC,GAAI,IAAI,mBAAmB,CAAC,CAAE;EAAE,CAAC;OAE9E,SAAS,kBAAkB,CAAC,mBAAG,IAAI,IAAI,CAAC,GAAI,SAAS,mBAAmB,CAAC,GAAI,GAAI,IAAI,mBAAmB,CAAC,CAAE,CAAC,CAAC;CAErH;CACA,MAAM,YAA2B,CAAC;CAClC,MAAM,UAAsC,CAAC;CAE7C,KAAK,MAAM,CAAC,KAAK,QAAQ,SACrB,IAAI;EACA,MAAM,WAAW,MAAM,uBAAuB,KAAK,GAAG;EACtD,MAAM,IAAI,mBAAmB,IAAI,QAAQ,IAAI,OAAO,QAAQ,CAAC;EAC7D,UAAU,KAAK,GAAG;CACtB,SAAS,KAAK;EACV,MAAM,SAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;EAC9D,QAAQ,KAAK;GAAE,GAAG;GAAK;EAAO,CAAC;EAC/B,OAAO,KACH,wDAAwD,IAAI,4EAE5D,EAAE,QAAQ,OAAO,CACrB;CACJ;CAOJ,OAAO,MACH,wDAAwD,UAAU,OAAO,cACxE,QAAQ,SAAS,KAAK,QAAQ,OAAO,aAAa,MAAM,GAC7D;CAEA,OAAO;EAAE;EAAW;CAAQ;AAChC;;;;;;;;;;;;;;;;;ACnNA,IAAM,6BAA6B;;AAUnC,IAAM,eAAe;;;;;;;;;;;;;;;;AAiBrB,IAAa,mBAAb,MAA8B;CASG;CAR7B;CACA,UAAkB;CAClB;CACA;CACA,oBAA4B;CAC5B;CACA;CAEA,YAAY,SAAmD;EAAlC,KAAA,UAAA;EACzB,IAAI,CAAC,aAAa,KAAK,QAAQ,OAAO,GAClC,MAAM,IAAI,MAAM,+BAA+B,QAAQ,QAAQ,qCAAqC;CAE5G;;CAGA,IAAI,SAAkB;EAClB,OAAO,KAAK;CAChB;;CAGA,IAAI,YAAqB;EACrB,OAAO,KAAK,WAAW,KAAK,WAAW,KAAA;CAC3C;CAEA,SAAiC;EAC7B,OAAO;GACH,SAAS,KAAK,QAAQ;GACtB,WAAW,KAAK;GAChB,GAAI,KAAK,cAAc,KAAA,KAAa,CAAC,KAAK,YAAY,EAAE,WAAW,KAAK,UAAU,IAAI,CAAC;GACvF,GAAI,KAAK,oBAAoB,KAAA,IAAY,EAAE,iBAAiB,KAAK,gBAAgB,IAAI,CAAC;EAC1F;CACJ;;;;;;;;;;CAWA,MAAM,QAAuB;EACzB,IAAI,KAAK,SAAS;EAClB,KAAK,UAAU;EACf,IAAI;GACA,MAAM,KAAK,QAAQ,EAAE,OAAO,KAAK,CAAC;EACtC,SAAS,KAAK;GACV,KAAK,UAAU;GACf,MAAM;EACV;CACJ;;CAGA,MAAM,OAAsB;EACxB,KAAK,UAAU;EACf,KAAK,cAAc;EACnB,IAAI,KAAK,gBAAgB;GACrB,aAAa,KAAK,cAAc;GAChC,KAAK,iBAAiB,KAAA;EAC1B;EACA,IAAI,KAAK,QAAQ;GACb,IAAI;IACA,MAAM,KAAK,OAAO,IAAI;GAC1B,QAAQ,CAA4B;GACpC,KAAK,SAAS,KAAA;EAClB;CACJ;;;;;;CAOA,MAAc,QAAQ,EAAE,QAAQ,UAA+B,CAAC,GAAkB;EAC9E,MAAM,EAAE,kBAAkB,SAAS,WAAW,aAAa,KAAK;EAOhE,IAAI;EACJ,IAAI;GAIA,MAAM,SAAS,IAAI,OAAS;IAAE;IAAkB,WAAW;IAAM,6BAA6B;GAAO,CAAC;GACtG,UAAU;GAIV,OAAO,GAAG,UAAU,QAAQ;IACxB,IAAI,WAAW,KAAK,QAAQ;IAC5B,OAAO,MAAM,KAAK,SAAS,uBAAuB,EAAE,QAAQ,IAAI,QAAQ,CAAC;IACzE,KAAK,QAAQ,MAAM;GACvB,CAAC;GAED,OAAO,GAAG,aAAa;IACnB,IAAI,WAAW,KAAK,QAAQ;IAC5B,IAAI,KAAK,SAAS;KACd,OAAO,KAAK,MAAM,SAAS,0CAA0C;KACrE,KAAK,QAAQ,MAAM;IACvB;GACJ,CAAC;GAED,OAAO,GAAG,iBAAiB,QAAQ;IAC/B,IAAI,CAAC,IAAI,SAAS;IAGlB,QAAQ,QAAQ,UAAU,IAAI,OAAO,CAAC,CAAC,CAAC,OAAO,QAC3C,OAAO,MAAM,KAAK,SAAS,+BAA+B,EAAE,OAAO,IAAI,CAAC,CAC5E;GACJ,CAAC;GAED,MAAM,OAAO,QAAQ;GACrB,MAAM,OAAO,MAAM,UAAU,SAAS;GACtC,KAAK,SAAS;GAEd,UAAU,KAAA;GACV,KAAK,YAAY,KAAA;GACjB,KAAK,kBAAkB,KAAK,IAAI;GAChC,KAAK,eAAe,MAAM;GAC1B,OAAO,MAAM,MAAM,SAAS,yBAAyB,QAAQ,GAAG;EACpE,SAAS,KAAK;GAEV,IAAI,SACA,IAAI;IAAE,MAAM,QAAQ,IAAI;GAAG,QAAQ,CAAqB;GAI5D,IAAI,SAAS,CAAC,KAAK,QAAQ,qBAAqB,MAAM;GACtD,OAAO,MAAM,KAAK,SAAS,mCAAmC,EAAE,OAAO,IAAI,CAAC;GAC5E,KAAK,kBAAkB;GACvB;EACJ;EAGA,IAAI,CAAC,SAAS,KAAK,QAAQ,aAAa;GACpC,OAAO,KAAK,MAAM,SAAS,6EAA6E;GACxG,IAAI;IACA,KAAK,QAAQ,YAAY;GAC7B,SAAS,KAAK;IACV,OAAO,MAAM,KAAK,SAAS,qCAAqC,EAAE,OAAO,IAAI,CAAC;GAClF;EACJ;CACJ;;;;;;CAOA,QAAgB,QAAwB;EACpC,IAAI,WAAW,KAAK,QAAQ;EAC5B,KAAK,SAAS,KAAA;EACd,KAAK,cAAc,KAAK,IAAI;EAC5B,KAAK,cAAc;EACnB,OAAY,IAAI,CAAC,CAAC,YAAY,CAAqB,CAAC;EACpD,KAAK,kBAAkB;CAC3B;CAEA,eAAuB,QAAwB;EAC3C,KAAK,cAAc;EACnB,MAAM,WAAW,KAAK,QAAQ,uBAAA;EAC9B,IAAI,YAAY,GAAG;EACnB,KAAK,iBAAiB,kBAAkB,KAAK,KAAK,UAAU,MAAM,GAAG,QAAQ;EAE7E,MAAM,KAAK,cAAc;CAC7B;CAEA,gBAA8B;EAC1B,IAAI,KAAK,gBAAgB,cAAc,KAAK,cAAc;EAC1D,KAAK,iBAAiB,KAAA;EACtB,KAAK,oBAAoB;CAC7B;;;;;;;CAQA,MAAc,UAAU,QAAiC;EACrD,IAAI,WAAW,KAAK,UAAU,KAAK,mBAAmB;EACtD,KAAK,oBAAoB;EACzB,MAAM,YAAY,KAAK,QAAQ,sBAAA;EAC/B,IAAI;EACJ,IAAI;GACA,MAAM,QAAQ,KAAK,CACf,OAAO,MAAM,UAAU,GACvB,IAAI,SAAgB,UAAU,WAAW;IACrC,QAAQ,iBAAiB,uBAAO,IAAI,MAAM,gBAAgB,UAAU,GAAG,CAAC,GAAG,SAAS;GACxF,CAAC,CACL,CAAC;GACD,IAAI,WAAW,KAAK,QAAQ,KAAK,kBAAkB,KAAK,IAAI;EAChE,SAAS,KAAK;GACV,IAAI,WAAW,KAAK,QAAQ;GAC5B,OAAO,KACH,MAAM,KAAK,QAAQ,SAAS,2CAA2C,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE,0CAE5H;GACA,KAAK,QAAQ,MAAM;EACvB,UAAU;GACN,IAAI,OAAO,aAAa,KAAK;GAC7B,IAAI,WAAW,KAAK,QAAQ,KAAK,oBAAoB;EACzD;CACJ;CAEA,oBAAkC;EAC9B,IAAI,CAAC,KAAK,WAAW,KAAK,gBAAgB;EAC1C,KAAK,cAAc,KAAK,IAAI;EAE5B,KAAK,iBAAiB,WAAW,YAAY;GACzC,KAAK,iBAAiB,KAAA;GACtB,IAAI,CAAC,KAAK,SAAS;GACnB,MAAM,KAAK,QAAQ;EACvB,GAAG,KAAK,QAAQ,oBAAoB,0BAA0B;CAClE;AACJ;;;;;;;AClSA,SAAgB,gBAAgB,SAAwC;CACpE,IAAI;CACJ,IAAI;EACA,SAAS,KAAK,MAAM,OAAO;CAC/B,QAAQ;EACJ,OAAO;CACX;CACA,IAAI,CAAC,UAAU,OAAO,WAAW,UAAU,OAAO;CAElD,MAAM,MAAM;CACZ,MAAM,SAAS,OAAO,IAAI,WAAW,WAAW,IAAI,SAAS,KAAA;CAC7D,MAAM,QAAQ,OAAO,IAAI,UAAU,WAAW,IAAI,QAAQ,KAAA;CAC1D,MAAM,KAAK,IAAI;CACf,IAAI,CAAC,UAAU,CAAC,OAAO,OAAO;CAC9B,IAAI,OAAO,YAAY,OAAO,YAAY,OAAO,UAAU,OAAO;CAIlE,OAAO;EACH;EACA;EACA;EACA,KANQ,IAAI,OAAO,OAAO,IAAI,QAAQ,WAAY,IAAI,MAAkC,CAAC;EAOzF,WAAW,IAAI,cAAc;CACjC;AACJ;;;;;;;;;;AAWA,IAAa,cAAb,MAAyB;CACrB;;;;;;CAOA,YACI,kBACA,SACA,aACF;EACE,KAAK,WAAW,IAAI,iBAAiB;GACjC;GACA,SAAS;GACT,UAAU;GACV;GACA,YAAY,YAAY;IACpB,MAAM,QAAQ,gBAAgB,OAAO;IACrC,IAAI,CAAC,OAAO;KACR,OAAO,KAAK,oDAAoD;KAChE;IACJ;IACA,OAAO,QAAQ,KAAK;GACxB;EACJ,CAAC;CACL;;;;;;;;;;CAWA,MAAM,QAAuB;EACzB,IAAI,KAAK,SAAS,QAAQ;GACtB,OAAO,KAAK,oEAAoE;GAChF;EACJ;EACA,MAAM,KAAK,SAAS,MAAM;CAC9B;;CAGA,MAAM,OAAsB;EACxB,MAAM,KAAK,SAAS,KAAK;CAC7B;;CAGA,IAAI,YAAqB;EACrB,OAAO,KAAK,SAAS;CACzB;CAEA,SAAiC;EAC7B,OAAO,KAAK,SAAS,OAAO;CAChC;AACJ;;;;;;;;;;;;;;;;;;;;;;;;AChGA,IAAa,0BAA0B;;;;;;;;;;;;AAavC,IAAa,4BAA4B,KAAK,OAAO;;;;;AAMrD,SAAgB,kBAAkB,KAAsB,aAAqB,yBAAqC;CAO9G,MAAM,UAAsC,IAAI;CAChD,IAAI,CAAC,SACD,MAAM,IAAI,MAAM,qHAAqH;CAEzI,MAAM,+BAAe,IAAI,QAAmB;CAC5C,IAAI,GAAG,eAAe,OAAkB;EACpC,GAAG,GAAG,cAAc,aAAa,OAAO,EAAE,CAAC;CAC/C,CAAC;CACD,MAAM,QAAQ,kBAAkB;EAC5B,KAAK,MAAM,MAAM,SAAS;GACtB,IAAI,aAAa,IAAI,EAAE,GAAG;IACtB,OAAO,MAAM,4EAA4E,WAAW,IAAI;IACxG,GAAG,UAAU;IACb;GACJ;GACA,IAAI,GAAG,eAAe,UAAU,MAAM;GACtC,aAAa,IAAI,EAAE;GACnB,GAAG,KAAK;EACZ;CACJ,GAAG,UAAU;CAEb,MAAM,KAAK;CACX,aAAa,cAAc,KAAK;AACpC;;;;;AAMA,SAAgB,sBAAsB,IAAe,UAA2B;CAC5E,MAAM,UAAU,GAAG;CACnB,IAAI,UAAA,UAAqC;EACrC,OAAO,KACH,iCAAiC,SAAS,IAAI,QAAQ,+CAC1C,0BAA0B,8BAC1C;EACA,GAAG,UAAU;EACb,OAAO;CACX;CACA,OAAO;AACX;;;;;;;;;;;;;;;;;;;ACvEA,SAAgB,uBACZ,IACA,OACe;CACf,OAAO,sBAAsB,OAAO,cAAsB;EAKtD,OAAO,QAAQ,MAAM,GAAG,QAAQ,IAAI,IAAI,SAAS,CAAC,CAAC;CACvD,GAAG,KAAK;AACZ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACQA,IAAM,uBAAuB;;;;;;;;;AAU7B,IAAM,mBAAmB;;AAGzB,IAAM,oBAAoB;;;;;;;;;AAU1B,SAAgB,WAAW,KAAsD;CAC7E,IAAI,QAAQ,KAAA,KAAa,QAAQ,MAAM,OAAO,KAAA;CAC9C,IAAI,OAAO,QAAQ,UAAU,OAAO,OAAO,SAAS,GAAG,KAAK,MAAM,IAAI,MAAM,KAAA;CAE5E,MAAM,QAAQ,0CAA0C,KAAK,GAAG;CAChE,IAAI,CAAC,OAAO;EACR,OAAO,KAAK,2DAA2D,IAAI,6CAA6C;EACxH;CACJ;CACA,MAAM,QAAQ,WAAW,MAAM,EAAE;CACjC,MAAM,OAAO,MAAM,EAAE,CAAC,YAAY;CAMlC,MAAM,KAAK,SALQ,SAAS,OAAO,IAC7B,SAAS,MAAM,MACX,SAAS,MAAM,MACX,SAAS,MAAM,OACX;CAElB,OAAO,KAAK,IAAI,KAAK,KAAA;AACzB;;;;;;;;AASA,SAAgB,mBAAmB,SAAiB,MAAqC;CACrF,MAAM,UAAU,KAAK;CACrB,IAAI,YAAY,KAAK,OAAO;CAC5B,IAAI,QAAQ,SAAS,GAAG,GAAG,OAAO,QAAQ,WAAW,QAAQ,MAAM,GAAG,EAAE,CAAC;CACzE,OAAO,YAAY;AACvB;;;;;;;;AAgCA,IAAa,sBAAb,MAAiC;CAQT;CAPpB;;CAEA,2BAAmB,IAAI,IAAsC;;CAE7D,6BAAqB,IAAI,IAAoB;CAC7C,cAAsB;CAEtB,YAAY,IAAqD,QAAgC,CAAC,GAAG;EAAjF,KAAA,KAAA;EAChB,KAAK,QAAQ,MAAM,QAAO,SAAQ;GAC9B,IAAI,CAAC,MAAM,OAAO;IACd,OAAO,KAAK,gEAAgE;IAC5E,OAAO;GACX;GAEA,IAAI,EADa,KAAK,UAAU,KAAA,KAAa,KAAK,QAAQ,KAAA,IAC3C;IAIX,OAAO,KAAK,uCAAuC,KAAK,MAAM,gFAAgF;IAC9I,OAAO;GACX;GACA,OAAO;EACX,CAAC;CACL;;CAGA,IAAI,UAAmB;EACnB,OAAO,KAAK,MAAM,SAAS;CAC/B;;;;;;CAOA,aAAa,SAAgD;EACzD,IAAI,CAAC,KAAK,WAAW,CAAC,SAAS,OAAO,KAAA;EAEtC,MAAM,SAAS,KAAK,SAAS,IAAI,OAAO;EACxC,IAAI,WAAW,KAAA,GAAW,OAAO,UAAU,KAAA;EAE3C,MAAM,OAAO,KAAK,MAAM,MAAK,MAAK,mBAAmB,SAAS,CAAC,CAAC;EAChE,MAAM,WAAqC,OACrC;GAAE,OAAO,KAAK;GAAO,OAAO,WAAW,KAAK,GAAG;EAAE,IACjD;EAIN,KAAK,SAAS,IAAI,SAAS,QAAQ;EACnC,OAAO,YAAY,KAAA;CACvB;;;;;CAMA,MAAM,eAA8B;EAChC,IAAI,CAAC,KAAK,WAAW,KAAK,aAAa;EAOvC,MAAM,MAAM,uBAAuB,KAAK,IAAI,iBAAiB;EAE7D,MAAM,IAAI,aAAa,iBAAiB,oCAAoC;EAK5E,MAAM,IAAI,aAAa,0BAA0B;;;;;;;;;;SAUhD;EAGD,MAAM,IAAI,aAAa,qCAAqC;;;SAG3D;EAID,MAAM,IAAI,aAAa,yBAAyB;;;;;SAK/C;EAcD,MAAM,CAAC,eAAe,gBAAgB,MAAM,QAAQ,IAAI,CACpD,IAAI,WAAW,yBAAyB,GACxC,IAAI,WAAW,wBAAwB,CAC3C,CAAC;EACD,IAAI,eACA,MAAM,IAAI,KAAK,iCACX,KAAK,GAAG,QAAQ,IAAI,IAAI,uBAAuB,UAAU,kBAAkB,CAAC,CAAC,CACjF;EAEJ,IAAI,cACA,MAAM,IAAI,KAAK,gCACX,KAAK,GAAG,QAAQ,IAAI,IAAI,uBAAuB,UAAU,iBAAiB,CAAC,CAAC,CAChF;EAGJ,IAAI,CAAC,iBAAiB,CAAC,cAAc;GAIjC,OAAO,KACH,0FACJ;GACA;EACJ;EAEA,KAAK,cAAc;EACnB,OAAO,KAAK,+CAA+C,KAAK,MAAM,OAAO,WAAW;CAC5F;;;;;;;;;;CAWA,MAAM,OACF,SACA,OACA,SACA,UACA,UACoC;EACpC,MAAM,cAAc,KAAK,UAAU,WAAW,IAAI;EA4ClD,MAAM,OAjCS,WACT,MAAM,KAAK,GAAG,QAAQ,GAAG;;;8BAGT,QAAQ;;;;;;6BAMT,QAAQ,mBAAmB,MAAM,IAAI,YAAY,WAAW,YAAY,KAAK;;;;kEAIxC,SAAS,cAAc;sDACnC,SAAS,IAAI,qBAAqB,QAAQ;;;aAGnF,IACC,MAAM,KAAK,GAAG,QAAQ,GAAG;;;8BAGT,QAAQ;;;;;;yBAMb,QAAQ,mBAAmB,MAAM,IAAI,YAAY,WAAW,YAAY,KAAK;;;aAGzF,EAAA,CAEc,KAAK;EACxB,IAAI,CAAC,KAAK,MAAM,IAAI,MAAM,4CAA4C,QAAQ,EAAE;EAEhF,OAAO;GAGH,KAAK,OAAO,IAAI,GAAG;GACnB,IAAI,IAAI,sBAAsB,OAAO,IAAI,WAAW,YAAY,IAAI,OAAO,IAAI,UAAU;EAC7F;CACJ;;;;;;;CAQA,MAAM,OACF,SACA,WAAW,GACX,QAAQ,sBACuD;EAC/D,MAAM,SAAS,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,MAAM,KAAK,KAAK,sBAAsB,gBAAgB,CAAC;EAChG,MAAM,QAAQ,OAAO,SAAS,QAAQ,KAAK,WAAW,IAAI,KAAK,MAAM,QAAQ,IAAI;EAUjF,MAAM,YAAY,MARG,KAAK,GAAG,QAAQ,GAAG;;;8BAGlB,QAAQ,aAAa,MAAM;;oBAErC,OAAO;SAClB,EAAA,CAEwB,KAMrB,KAAI,SAAQ;GACZ,KAAK,OAAO,IAAI,GAAG;GACnB,OAAO,IAAI;GACX,SAAS,IAAI;GACb,UAAU,IAAI,aAAa,KAAA;GAC3B,IAAI,IAAI,sBAAsB,OAAO,IAAI,WAAW,YAAY,IAAI,OAAO,IAAI,UAAU;EAC7F,EAAE;EAQF,MAAM,aAAY,MAHG,KAAK,GAAG,QAAQ,GAAG;0EAC0B,QAAQ;SACzE,EAAA,CACwB,KAAK;EAG9B,OAAO;GAAE;GAAU,WAFD,YAAY,OAAO,UAAU,QAAQ,IAAI;EAE9B;CACjC;;;;;;;;;;;CAYA,MAAM,SAAS,SAAiB,KAAkD;EAO9E,MAAM,OAAM,MANS,KAAK,GAAG,QAAQ,GAAG;;;8BAGlB,QAAQ,aAAa,IAAI;SAC9C,EAAA,CAEkB,KAAK;EAOxB,IAAI,CAAC,KAAK,OAAO;EAEjB,OAAO;GACH,KAAK,OAAO,IAAI,GAAG;GACnB,OAAO,IAAI;GACX,SAAS,IAAI;GACb,UAAU,IAAI,aAAa,KAAA;GAC3B,IAAI,IAAI,sBAAsB,OAAO,IAAI,WAAW,YAAY,IAAI,OAAO,IAAI,UAAU;EAC7F;CACJ;;;;;;;;CASA,MAAM,MAAM,SAAiB,WAA+C;EACxE,MAAM,MAAM,KAAK,IAAI;EAErB,IAAI,OADS,KAAK,WAAW,IAAI,OAAO,KAAK,KAC5B,mBAAmB,OAAO;EAC3C,KAAK,WAAW,IAAI,SAAS,GAAG;EAEhC,IAAI,UAAU;EAEd,IAAI,UAAU,UAAU,KAAA,GAAW;GAC/B,MAAM,SAAS,MAAM,KAAK,GAAG,QAAQ,GAAG;;kCAElB,QAAQ;mEACyB,UAAU,QAAQ,IAAK;aAC7E;GACD,WAAW,OAAO,YAAY;EAClC;EAEA,IAAI,UAAU,UAAU,KAAA,KAAa,UAAU,QAAQ,GAAG;GAKtD,MAAM,SAAS,MAAM,KAAK,GAAG,QAAQ,GAAG;;kCAElB,QAAQ;;;wCAGF,QAAQ;;+BAEjB,KAAK,MAAM,UAAU,KAAK,EAAE;;aAE9C;GACD,WAAW,OAAO,YAAY;EAClC;EAEA,OAAO;CACX;;CAGA,QAAc;EACV,KAAK,SAAS,MAAM;EACpB,KAAK,WAAW,MAAM;CAC1B;AACJ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC/aA,IAAa,uBAAb,MAAkC;CAIT;CACA;CAJrB,cAAsB;CAEtB,YACI,IACA,YACF;EAFmB,KAAA,KAAA;EACA,KAAA,aAAA;CAClB;;;;;;;;;;;;;;;;;CAkBH,MAAM,eAA8B;EAChC,IAAI,KAAK,aAAa;EAEtB,MAAM,MAAM,uBAAuB,KAAK,IAAI,kBAAkB;EAE9D,MAAM,IAAI,aAAa,iBAAiB,oCAAoC;EAM5E,MAAM,IAAI,aAAa,0BAA0B;;;;;;;;;SAShD;EAGD,MAAM,IAAI,aAAa,oCAAoC;;;SAG1D;EAeD,IAAI,MAAM,IAAI,WAAW,yBAAyB,GAAG;GACjD,MAAM,IAAI,KAAK,iCACX,KAAK,GAAG,QAAQ,IAAI,IAAI,uBAAuB,UAAU,kBAAkB,CAAC,CAAC,CACjF;GACA,KAAK,cAAc;EACvB;CACJ;;CAGA,MAAM,MAAM,SAAiB,UAAkB,OAA+C;EAC1F,MAAM,KAAK,GAAG,QAAQ,GAAG;;sBAEX,QAAQ,IAAI,SAAS,IAAI,KAAK,WAAW,IAAI,KAAK,UAAU,SAAS,CAAC,CAAC,EAAE;;;;;SAKtF;CACL;;CAGA,MAAM,OAAO,SAAiB,UAAiC;EAC3D,MAAM,KAAK,GAAG,QAAQ,GAAG;;8BAEH,QAAQ,mBAAmB,SAAS;SACzD;CACL;;CAGA,MAAM,aAAa,UAAiC;EAChD,MAAM,KAAK,GAAG,QAAQ,GAAG;oEACmC,SAAS;SACpE;CACL;;CAGA,MAAM,OAAO,SAAmE;EAC5E,MAAM,SAAS,MAAM,KAAK,GAAG,QAAQ,GAAG;mFACmC,QAAQ;SAClF;EAED,MAAM,YAAqD,CAAC;EAC5D,KAAK,MAAM,OAAO,OAAO,MACrB,UAAU,IAAI,aAAa,IAAI,SAAS,CAAC;EAE7C,OAAO;CACX;;;;;;;;;;;CAYA,MAAM,WAAW,OAAuC;EAQpD,QAAQ,MAPa,KAAK,GAAG,QAAQ,GAAG;;mCAEb,KAAK,WAAW;8DACW,QAAQ,IAAK;;SAElE,EAAA,CAEc,KACV,KAAI,SAAQ;GAAE,SAAS,IAAI;GAAS,UAAU,IAAI;GAAW,OAAO,IAAI,SAAS,CAAC;EAAE,EAAE;CAC/F;;;;;CAMA,MAAM,iBAAgC;EAClC,MAAM,KAAK,GAAG,QAAQ,GAAG;sEACqC,KAAK,WAAW;SAC7E;CACL;AACJ;;;;;;;;;;;AC3JA,IAAa,mBAAb,MAA8B;CAC1B,OAAgB;CAChB,gBAAyB;CAEzB,MAAM,QAAuB,CAA2B;CAExD,MAAM,UAAyB,CAA8B;CAE7D,MAAM,OAAsB,CAA2B;AAC3D;;AAGA,SAAgB,gBAAgB,OAAgC;CAC5D,OAAO,OAAO,WAAW,KAAK,UAAU,KAAK,GAAG,MAAM;AAC1D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACLA,IAAa,6BAA6B;;;;;;AAO1C,IAAa,8BAA8B;;;;;;;;AAS3C,IAAa,0BAA0B;;AAGvC,IAAM,wBAAwB;;AAE9B,IAAM,uBAAuB;AAS7B,IAAa,qBAAb,MAAsD;CAmB7B;CACA;CAnBrB,OAAgB;CAChB,gBAAyB;CAEzB;CACA;;;;;;;CAQA,UAAkC,CAAC;CACnC,eAAuB;CACvB;CACA,UAAkB;CAElB,YACI,IACA,kBACA,UAAsC,CAAC,GACzC;EAHmB,KAAA,KAAA;EACA,KAAA,mBAAA;EAGjB,MAAM,aAAa,QAAQ;EAC3B,KAAK,gBAAgB,OAAO,eAAe,YAAY,cAAc,IAC/D,aAAA;CAEV;CAEA,MAAM,MAAM,SAA2C;EACnD,KAAK,UAAU;EACf,KAAK,WAAW,IAAI,iBAAiB;GACjC,kBAAkB,KAAK;GACvB,SAAS;GACT,UAAU;GACV,WAAW,OAAO,YAAY;IAC1B,MAAM,SAAS,uBAAuB,OAAO;IAC7C,IAAI,CAAC,OAAO,QAAQ;KAChB,OAAO,KAAK,+CAA+C;KAC3D;IACJ;IAGA,KAAK,MAAM,SAAS,QAAQ,MAAM,QAAQ,KAAK;GACnD;EACJ,CAAC;EACD,MAAM,KAAK,SAAS,MAAM;CAC9B;;;;;;;;;;;;;;;CAgBA,MAAM,QAAQ,OAAuC;EACjD,IAAI,KAAK,kBAAkB,KAAK,KAAK,SAAS;GAC1C,MAAM,KAAK,KAAK,CAAC,KAAK,CAAC;GACvB;EACJ;EAEA,IAAI,CAAC,KAAK,aAAa;GACnB,KAAK,WAAW;GAChB,MAAM,KAAK,KAAK,CAAC,KAAK,CAAC;GACvB;EACJ;EAEA,MAAM,QAAQ,gBAAgB,KAAK,IAAI;EAIvC,IAAI,KAAK,QAAQ,UAAU,KAAK,eAAe,QAAQ,KAAK,eACxD,KAAK,MAAM;EAGf,OAAO,IAAI,SAAe,SAAS,WAAW;GAC1C,KAAK,QAAQ,KAAK;IAAE;IAAO;IAAO;IAAS;GAAO,CAAC;GACnD,KAAK,gBAAgB;EACzB,CAAC;CACL;;CAGA,SAA6C;EACzC,OAAO,KAAK,UAAU,OAAO;CACjC;CAEA,MAAM,OAAsB;EACxB,KAAK,UAAU;EACf,IAAI,KAAK,aAAa;GAClB,aAAa,KAAK,WAAW;GAC7B,KAAK,cAAc,KAAA;EACvB;EAIA,KAAK,MAAM;EACX,MAAM,KAAK,UAAU,KAAK;EAC1B,KAAK,WAAW,KAAA;CACpB;CAEA,aAA2B;EACvB,KAAK,cAAc,iBAAiB;GAChC,KAAK,cAAc,KAAA;GACnB,IAAI,KAAK,QAAQ,QAAQ;IAGrB,KAAK,MAAM;IACX,KAAK,WAAW;GACpB;EAGJ,GAAG,KAAK,aAAa;EAGrB,MAAM,KAAK,WAAW;CAC1B;;CAGA,QAAsB;EAClB,IAAI,CAAC,KAAK,QAAQ,QAAQ;EAE1B,MAAM,QAAQ,KAAK;EACnB,KAAK,UAAU,CAAC;EAChB,KAAK,eAAe;EAEpB,KAAK,KAAK,MAAM,KAAI,MAAK,EAAE,KAAK,CAAC,CAAC,CAC7B,WAAW;GAAE,KAAK,MAAM,KAAK,OAAO,EAAE,QAAQ;EAAG,CAAC,CAAC,CACnD,OAAO,UAAU;GAAE,KAAK,MAAM,KAAK,OAAO,EAAE,OAAO,KAAK;EAAG,CAAC;CACrE;;;;;;;;;;;CAYA,MAAc,KAAK,QAA0C;EACzD,IAAI,CAAC,OAAO,QAAQ;EACpB,MAAM,UAAU,OAAO,WAAW,IAC5B,KAAK,UAAU,OAAO,EAAE,IACxB,KAAK,UAAU,EAAE,OAAO,OAAO,CAAC;EAEtC,MAAM,KAAK,GAAG,QAAQ,GAAG,oBAAoB,2BAA2B,IAAI,QAAQ,EAAE;CAC1F;AACJ;;;;;;;;;AAUA,SAAgB,uBAAuB,SAAoC;CACvE,IAAI;CACJ,IAAI;EACA,SAAS,KAAK,MAAM,OAAO;CAC/B,QAAQ;EACJ,OAAO,CAAC;CACZ;CACA,IAAI,CAAC,UAAU,OAAO,WAAW,UAAU,OAAO,CAAC;CAEnD,MAAM,QAAS,OAA+B;CAC9C,IAAI,MAAM,QAAQ,KAAK,GACnB,OAAO,MACF,KAAI,UAAS,YAAY,KAAK,CAAC,CAAC,CAChC,QAAQ,UAAoC,UAAU,IAAI;CAGnE,MAAM,SAAS,YAAY,MAAM;CACjC,OAAO,SAAS,CAAC,MAAM,IAAI,CAAC;AAChC;;;;;AAMA,SAAgB,qBAAqB,SAAyC;CAC1E,IAAI;EACA,OAAO,YAAY,KAAK,MAAM,OAAO,CAAC;CAC1C,QAAQ;EACJ,OAAO;CACX;AACJ;AAEA,SAAS,YAAY,OAAwC;CACzD,IAAI,CAAC,SAAS,OAAO,UAAU,UAAU,OAAO;CAEhD,MAAM,MAAM;CACZ,MAAM,MAAM,OAAO,IAAI,QAAQ,WAAW,IAAI,MAAM,KAAA;CACpD,MAAM,UAAU,OAAO,IAAI,YAAY,WAAW,IAAI,UAAU,KAAA;CAChE,IAAI,CAAC,OAAO,CAAC,SAAS,OAAO;CAE7B,QAAQ,IAAI,MAAZ;EACI,KAAK;GACD,IAAI,OAAO,IAAI,UAAU,UAAU,OAAO;GAC1C,OAAO;IACH,MAAM;IACN;IACA;IACA,OAAO,IAAI;IACX,MAAM,OAAO,IAAI,SAAS,WAAW,IAAI,OAAO,KAAA;IAChD,KAAK,OAAO,IAAI,QAAQ,WAAW,IAAI,MAAM,KAAA;IAC7C,SAAS,IAAI;GACjB;EACJ,KAAK;GACD,IAAI,OAAO,IAAI,QAAQ,UAAU,OAAO;GACxC,OAAO;IACH,MAAM;IACN;IACA;IACA,MAAM,OAAO,IAAI,SAAS,WAAW,IAAI,OAAO,KAAA;IAChD,KAAK,IAAI;GACb;EACJ,KAAK,iBACD,OAAO;GACH,MAAM;GACN;GACA;GACA,OAAQ,IAAI,SAAS,CAAC;GACtB,QAAS,IAAI,UAAU,CAAC;EAC5B;EACJ,SACI,OAAO;CACf;AACJ;;;;;;;;;;;;AC9PA,SAAgB,yBAAyB,YAAmD;CACxF,MAAM,OAAO,QAAQ,IAAI,wBAAwB,GAAA,CAAI,KAAK,CAAC,CAAC,YAAY;CAExE,IAAI,uBAAqB,UAAU,GAAG;EAClC,IAAI,OAAO,QAAQ,WAAW,MAC1B,OAAO,KACH,yCAAyC,IAAI,iDACzC,WAAW,KAAK,+EACxB;EAEJ,OAAO;CACX;CAEA,IAAI,CAAC,KAAK,OAAO,cAAc,EAAE,MAAM,SAAS;CAEhD,IAAI,QAAQ,YAAY,QAAQ,YAAY;EACxC,OAAO,KACH,uDAAuD,IAAI,uJAG/D;EACA,OAAO,cAAc,EAAE,MAAM,SAAS;CAC1C;CAIA,IAAI,YAAY,SAAS,KAAK,OAAO;CACrC,OAAO,QAAQ,WAAW,EAAE,MAAM,SAAS,IAAI,EAAE,MAAM,WAAW;AACtE;;;;;;;;;;AAWA,SAAgB,iBAAiB,SAA4B,MAAkC;CAC3F,IAAI,uBAAqB,OAAO,GAAG,OAAO;CAE1C,QAAQ,QAAQ,MAAhB;EACI,KAAK,YAAY;GACb,MAAM,mBAAmB,QAAQ,oBAAoB,KAAK;GAC1D,IAAI,CAAC,kBAAkB;IACnB,OAAO,KACH,qMAGJ;IACA,OAAO,IAAI,iBAAiB;GAChC;GACA,OAAO,IAAI,mBAAmB,KAAK,IAAI,kBAAkB,EACrD,eAAe,QAAQ,cAC3B,CAAC;EACL;EAEA,SACI,OAAO,IAAI,iBAAiB;CACpC;AACJ;;;;ACnFA,IAAM,oBAAoB;AAoC1B,SAAS,kBAAkB,QAA0E;CACjG,OAAO,WAAW,KAAA,KAAa,uBAAuB,UAAU,OAAO,OAAO,sBAAsB;AACxG;;;;;;;;;;;;;;;;;AAkBA,SAAS,sBAAsB,QAAgC,IAAmB,MAAkD;CAChI,IAAI,CAAC,kBAAkB,MAAM,GACzB,MAAM,IAAI,MAAM,6EAA6E;CAEjG,OAAO,OAAO,kBAAkB,IAAI;EAChC,KAAK,KAAK;EACV,OAAO,KAAK;EACZ,aAAa,KAAK,gBAAgB;EAElC,aAAa;EACb,OAAO;EACP,UAAU;EACV,YAAY;EACZ,GAAI,KAAK,SAAS,EAAE,QAAQ,KAAK,OAAO,IAAI,CAAC;CACjD,CAAC;AACL;;AAwLA,SAAS,cAAc,OAAwB;CAC3C,OAAO,KAAK,UAAU,QAAQ,MAAM,WAAoB;EACpD,IAAI,CAAC,UAAU,OAAO,WAAW,YAAY,MAAM,QAAQ,MAAM,GAAG,OAAO;EAC3E,OAAO,OAAO,YAAY,OAAO,QAAQ,MAAM,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAQ,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,CAAE,CAAC;CACrG,CAAC;AACL;;;;;;;;;;;AAYA,IAAa,uCAAuC;;;;;;;AAQpD,SAAgB,+BAA+B,OAAgB,QAAwB;CACnF,MAAM,SAAS,OAAO,UAAU,YAAY,MAAM,KAAK,MAAM,KAAK,OAAO,MAAM,KAAK,CAAC,IAAI;CACzF,IAAI,OAAO,WAAW,YAAY,CAAC,OAAO,UAAU,MAAM,KAAK,SAAS,GACpE,MAAM,IAAI,MACN,GAAG,OAAO,qEAAqE,KAAK,UAAU,KAAK,EAAE,EACzG;CAEJ,OAAO;AACX;;;;;;AAOA,IAAM,2BAA2B;;;;;;AAOjC,SAAS,gBAAgB,SAAsB,SAAuB;CAClE,KAAK,MAAM,MAAM,SAAS;EACtB,IAAI,GAAG,eAAe,UAAU,MAAM,GAAG,MAAM,MAAM,sBAAsB;EAE3E,IAAI,GAAG,eAAe,UAAU,QAAQ,MAAM,iBAAiB,GAAG,UAAU,GAAG,OAAO,CAAC;CAC3F;AACJ;;;;;;;AAQA,IAAa,kBAAb,MAAa,wBAAwB,aAAyC;CAgLtD;CAAiC;;;;;;CA1KrD,mBAAmC;CAEnC,0BAAkB,IAAI,IAAuB;CAG7C,2BAAmB,IAAI,IAAyB;CAGhD,2BAAmB,IAAI,IAA+E;;;;;;;;CAStG;;;;;;;;;;;;CAaA,oCAA4B,IAAI,IAA2B;;CAE3D,uCAA+B,IAAI,IAA2B;;;;;;;;CAS9D,MAA0B,IAAI,iBAAiB;;;;;;;;CAS/C;;CAGA;;;;;CAMA,2CAAmC,IAAI,IAAY;;;;;;CAOnD;;;;;;;;;CAUA,sBAA8B;;CAG9B,kBAA0B;CAE1B;CACA,OAAwB,sBAAsB;;CAE9C,OAAwB,6BAA6B;CACrD;;;;;;;;;;CAUA,iCAAyB,IAAI,IAA0B;;CAGvD,wCAAgC,IAAI,IAAwF;;CAG5H,wCAAgC,IAAI,IAAyB;;CAG7D,yBAAiB,IAAI,IAA+B;;;;;;CAMpD,yCAAiC,IAAI,IAAoC;CACzE,wCAAgC,IAAI,IAAoC;;;;;CAKxE,kCAA0B,IAAI,IAA8C;;;;;CAM5E,4BAAmC;CAEnC;;CAIA,aAA8B,QAAQ,WAAW,CAAC,CAAC,MAAM,GAAG,CAAC;;CAE7D;;CAEA,eAAuB;;CAEvB,OAAwB,sBAAsB;;CAI9C;;CAEA,YAAoB;;CAEpB;;CAGA;;;;;;;;;CASA,iCAAyB,IAAI,IAAoB;;;;;;;;;;;;;;CAcjD,OAAwB,sBAAsB,gBAAgB;CAE9D,YAAY,IAAiC,UAA8C;EACvF,MAAM;EADU,KAAA,KAAA;EAAiC,KAAA,WAAA;EASjD,KAAK,cAAc,IAAI,YAAY,IAAI,QAAQ;CACnD;;;;;;;;CASA;;;;;;;;;;CAWA,SAAiB,GAAG,MAAiB;EACjC,IAAI,CAAC,uBAAuB,GAAG;EAC/B,OAAO,MAAM,KAAK,KAAI,MAAM,OAAO,MAAM,WAAW,IAAI,KAAK,UAAU,CAAC,CAAE,CAAC,CAAC,KAAK,GAAG,CAAC;CACzF;CAEA,cAAc,QAAoB;EAC9B,KAAK,SAAS;CAClB;;;;;CAMA,IAAI,gBAAmD;EACnD,OAAO,IAAI,IAAI,CAAC,GAAG,KAAK,eAAe,OAAO,CAAC,CAAC,CAAC,KAAK,iBAAiB,CAAC,aAAa,gBAAgB,YAAY,CAAC,CAAC;CACvH;;CAOA,gBAAwB,UAAkB,gBAAgC;EACtE,OAAO,GAAG,SAAS,QAAQ;CAC/B;;;;;;CAOA,YACI,KACA,QACA,QACM;EACN,IAAI,CAAC,QAAQ,OAAO,iBAAiB;EACrC,MAAM,OAAO,OAAO,eAAe;GAAE,KAAK;GAAmB,OAAO,CAAC,MAAM;EAAE;EAC7E,OAAO,cAAc;GACjB,MAAM,OAAO;GACb,MAAM,OAAO;GACb,IAAI,OAAO,SAAS,WAAW,OAAO,OAAO,EAAE,IAAI,KAAA;GACnD,SAAS,OAAO,SAAS,eAAgB,OAAO,qBAAqB,CAAC,IAAK,KAAA;GAG3E,KAAK,KAAK;GACV,OAAO,KAAK,SAAS,CAAC;GACtB,aAAa,KAAK,gBAAgB;GAClC,QAAQ,KAAK;EACjB,CAAC;CACL;;;;;CAMA,qBAA6B,KAAa,cAAkC;EACxE,MAAM,WAAW,KAAK,eAAe,IAAI,GAAG;EAC5C,IAAI,UAAU,KAAK,WAAW,KAAK,QAAQ;EAC3C,KAAK,eAAe,IAAI,KAAK,YAAY;EAEzC,IAAI,QAAQ,KAAK,sBAAsB,IAAI,aAAa,QAAQ;EAChE,IAAI,CAAC,OAAO,KAAK,sBAAsB,IAAI,aAAa,UAAU,wBAAQ,IAAI,IAAI,CAAC;EACnF,MAAM,IAAI,GAAG;EAEb,IAAI,QAAQ,KAAK,OAAO,IAAI,aAAa,QAAQ;EACjD,IAAI,CAAC,OAAO;GACR,QAAQ;IACJ,KAAK,aAAa;IAClB,MAAM,aAAa;IACnB,MAAM,aAAa;IACnB,GAAI,aAAa,SAAS,WAAW,EAAE,IAAI,OAAO,aAAa,EAAE,EAAE,IAAI,CAAC;IACxE,mBAAmB,aAAa;IAChC,aAAa,aAAa;IAC1B,yBAAS,IAAI,IAAI;IACjB,yBAAS,IAAI,IAAI;IACjB,cAAc;GAClB;GACA,KAAK,OAAO,IAAI,MAAM,KAAK,KAAK;GAChC,MAAM,CAAC,OAAO,WAAW,MAAM,SAAS,eAClC,CAAC,KAAK,wBAAwB,MAAM,IAAI,IACxC,CAAC,KAAK,uBAAuB,GAAG,MAAM,KAAK,QAAQ,MAAM,IAAI;GACnE,IAAI,SAAS,MAAM,IAAI,OAAO;GAC9B,IAAI,CAAC,QAAQ,MAAM,IAAI,SAAS,yBAAS,IAAI,IAAI,CAAC;GAClD,OAAO,IAAI,KAAK;EACpB;EACA,MAAM,QAAQ,IAAI,GAAG;EAIrB,MAAM,OAAO,KAAK,gBAAgB,IAAI,aAAa,IAAI;EACvD,IAAI,MAAM,KAAK;OACV,KAAK,gBAAgB,IAAI,aAAa,MAAM;GAAE,MAAM,KAAK,iBAAiB,aAAa,IAAI;GAAG,OAAO;EAAE,CAAC;CACjH;;CAGA,iBAAyB,KAAmB;EACxC,MAAM,eAAe,KAAK,eAAe,IAAI,GAAG;EAChD,KAAK,sBAAsB,OAAO,GAAG;EACrC,IAAI,CAAC,cAAc;EACnB,KAAK,eAAe,OAAO,GAAG;EAC9B,KAAK,WAAW,KAAK,YAAY;EACjC,MAAM,QAAQ,KAAK,sBAAsB,IAAI,aAAa,QAAQ;EAClE,OAAO,OAAO,GAAG;EACjB,IAAI,OAAO,SAAS,GAAG,KAAK,sBAAsB,OAAO,aAAa,QAAQ;CAClF;;CAGA,WAAmB,KAAa,cAAkC;EAC9D,MAAM,OAAO,KAAK,gBAAgB,IAAI,aAAa,IAAI;EACvD,IAAI,QAAQ,EAAE,KAAK,SAAS,GAAG,KAAK,gBAAgB,OAAO,aAAa,IAAI;EAE5E,MAAM,QAAQ,KAAK,OAAO,IAAI,aAAa,QAAQ;EACnD,IAAI,CAAC,OAAO;EACZ,MAAM,QAAQ,OAAO,GAAG;EACxB,IAAI,MAAM,YAAY,OAAO,MAAM,QAAQ,OAAO,GAAG;EACrD,IAAI,MAAM,QAAQ,OAAO,GAAG;EAC5B,IAAI,MAAM,OAAO,aAAa,MAAM,KAAK;EACzC,KAAK,OAAO,OAAO,MAAM,GAAG;EAC5B,MAAM,CAAC,OAAO,WAAW,MAAM,SAAS,eAClC,CAAC,KAAK,wBAAwB,MAAM,IAAI,IACxC,CAAC,KAAK,uBAAuB,GAAG,MAAM,KAAK,QAAQ,MAAM,IAAI;EACnE,MAAM,SAAS,MAAM,IAAI,OAAO;EAChC,QAAQ,OAAO,KAAK;EACpB,IAAI,QAAQ,SAAS,GAAG,MAAM,OAAO,OAAO;CAChD;;;;;;;;;;;CAYA,qBAA6B,OAA0B,QAAuB;EAC1E,IAAI,WAAW,KAAA,GAAW,MAAM,UAAU;OACrC,IAAI,MAAM,YAAY,OAAO,MAAM,QAAQ,IAAI,MAAM;EAC1D,MAAM,eAAe,KAAK,IAAI;EAC9B,IAAI,CAAC,MAAM,OAAO,KAAK,cAAc,OAAO,gBAAgB,mBAAmB;CACnF;CAEA,cAAsB,OAA0B,SAAuB;EACnE,MAAM,QAAQ,iBAAiB;GAC3B,MAAM,QAAQ,KAAA;GACd,IAAI,KAAK,OAAO,IAAI,MAAM,GAAG,MAAM,OAAO;GAC1C,MAAM,WAAW,KAAK,IAAI,IAAI,MAAM;GACpC,IAAI,WAAW,gBAAgB,qBAAqB;IAChD,KAAK,cAAc,OAAO,gBAAgB,sBAAsB,QAAQ;IACxE;GACJ;GACA,KAAU,aAAa,KAAK;EAChC,GAAG,OAAO;CACd;;;;;;;;;;CAWA,MAAc,aAAa,OAAyC;EAChE,MAAM,OAAO,MAAM,YAAY,QAAQ,CAAC,GAAG,MAAM,OAAO,IAAI,CAAC,GAAG,MAAM,OAAO;EAC7E,MAAM,0BAAU,IAAI,IAAI;EAExB,MAAM,UAAgL,CAAC;EACvL,KAAK,MAAM,OAAO,MAAM;GACpB,MAAM,eAAe,KAAK,eAAe,IAAI,GAAG;GAChD,IAAI,CAAC,gBAAgB,aAAa,aAAa,MAAM,KAAK;GAC1D,IAAI,aAAa,aAAa,UAAU;IACpC,MAAM,WAAW,KAAK,sBAAsB,IAAI,GAAG;IACnD,IAAI,CAAC,UAAU;IACf,QAAQ,KAAK;KAAE;KAAK;KAAc,YAAY,KAAK,cAAc,KAAK,YAAY;KAAG;IAAS,CAAC;GACnG,OAAO,IAAI,KAAK,QAAQ,IAAI,aAAa,QAAQ,GAC7C,QAAQ,KAAK;IAAE;IAAK;IAAc,YAAY,KAAK,cAAc,KAAK,YAAY;GAAE,CAAC;EAE7F;EACA,IAAI,QAAQ,WAAW,GAAG;EAE1B,IAAI;GACA,IAAI,MAAM,SAAS,cAAc;IAC7B,MAAM,UAAU,MAAM,qBAAqB,CAAC;IAC5C,MAAM,OAAO,MAAM,KAAK,wBAAwB,MAAM,MAAM,SAAS,MAAM,WAAW;IAEtF,MAAM,OADY,QAAQ,MAAM,WAAW,CAAC,OAAO,QACtC,IACP,MAAM,KAAK,uBAAuB,MAAM,MAAM,SAAS,MAAM,MAAM,WAAW,IAC9E,KAAA;IACN,IAAI;IACJ,KAAK,MAAM,UAAU,SAAS;KAC1B,IAAI,CAAC,OAAO,WAAW,GAAG;KAC1B,IAAI,OAAO,UAAU;MACjB,OAAO,SAAS,IAAI;MACpB;KACJ;KACA,cAAc,KAAK,oBAAoB,MAAM,MAAM,MAAM,IAAI;KAC7D,KAAK,QACD,OAAO,aAAa,UACpB,gDAAgD,KAAK,UAAU,OAAO,aAAa,cAAc,EAAE,GAAG,WAC1G;IACJ;GACJ,OAAO;IACH,MAAM,MAAO,MAAM,KAAK,oBAAoB,MAAM,MAAM,MAAM,IAAK,MAAM,WAAW,KAAM;IAC1F,KAAK,MAAM,UAAU,SAAS;KAC1B,IAAI,CAAC,OAAO,WAAW,GAAG;KAC1B,IAAI,OAAO,UAAU,OAAO,SAAS,GAAG;UACnC,KAAK,iBAAiB,OAAO,aAAa,UAAU,OAAO,aAAa,gBAAgB,GAAG;IACpG;GACJ;EACJ,SAAS,OAAO;GACZ,KAAK,MAAM,UAAU,SACjB,IAAI,OAAO,UAAU;IACjB,OAAO,MAAM,sEAAsE,OAAO,aAAa,kBAAkB,EAAE,MAAM,CAAC;IAClI,KAAK,yBAAyB,OAAO,cAAc,OAAO,YAAY,KAAK;GAC/E,OACI,KAAK,yBAAyB,OAAO,aAAa,UAAU,OAAO,aAAa,gBAAgB,MAAM,MAAM,OAAO,OAAO,UAAU;EAGhJ;CACJ;;;;;;;;;;;;;;;;;;;;;;;;;;CA2BA,cAAsB,KAAa,cAA2C;EAC1E,MAAM,MAAM,EAAE,aAAa;EAC3B,MAAM,mBAAmB;GACrB,IAAI,KAAK,eAAe,IAAI,GAAG,MAAM,cAAc,OAAO;GAC1D,IAAI,OAAO,aAAa,WAAW,OAAO;GAC1C,aAAa,YAAY;GACzB,OAAO;EACX;EACA,MAAM,yBACF,WAAW,KAAM,KAAK,eAAe,IAAI,GAAG,MAAM,gBAAgB,aAAa,cAAc;EACjG,OAAO,OAAO,OAAO,YAAY,EAAE,iBAAiB,CAAC;CACzD;;;;;;;;;;;;;;;;;CAkBA,4BACI,gBACA,SACA,UACI;EACJ,KAAK,SAAS,0DAA0D,gBAAgB,QAAQ,cAAc,gBAAgB,WAAW;EACzI,MAAM,MAAM,KAAK,gBAAgB,UAAU,cAAc;EACzD,MAAM,eAA6B;GAC/B;GACA,UAAU;GACV,UAAU,KAAK,YAAY,KAAK,OAAO,OAAO;GAC9C,GAAG;GACH,SAAS;GACT,WAAW;EACf;EACA,KAAK,qBAAqB,KAAK,YAAY;EAC3C,KAAK,sBAAsB,IAAI,KAAK,QAAQ;EAC5C,KAAU,yBAAyB,KAAK,cAAc,QAAQ;CAClE;CAEA,MAAc,yBACV,KACA,cACA,UACa;EAGb,MAAM,aAAa,KAAK,cAAc,KAAK,YAAY;EACvD,IAAI;GACA,MAAM,OAAO,aAAa,SAAS,eAC7B,MAAM,KAAK,wBAAwB,aAAa,MAAM,aAAa,qBAAqB,CAAC,GAAG,aAAa,WAAW,IACnH,MAAM,KAAK,oBAAoB,aAAa,MAAM,OAAO,aAAa,EAAE,GAAG,aAAa,WAAW,KAAM;GAChH,IAAI,WAAW,GAAG,SAAS,IAAI;EACnC,SAAS,OAAO;GACZ,OAAO,MAAM,4EAA4E,aAAa,kBAAkB,EAAS,MAAM,CAAC;GACxI,KAAK,yBAAyB,cAAc,YAAY,KAAK;EACjE;CACJ;;;;CASA,sBACI,gBACA,QACA,UACA,SACI;EACJ,MAAM,MAAM,KAAK,gBAAgB,OAAO,UAAU,cAAc;EAChE,MAAM,SAAS;GACX,MAAM;GACN,MAAM,OAAO;GACb,mBAAmB;IACf,QAAQ,OAAO;IACf,SAAS,OAAO;IAChB,OAAO,OAAO;IACd,OAAO,OAAO;IACd,YAAY,OAAO;IACnB,YAAY,OAAO;IACnB,cAAc,OAAO;IACrB,eAAe,OAAO;GAC1B;EACJ;EACA,KAAK,qBAAqB,KAAK;GAC3B;GACA,UAAU,OAAO;GACjB,UAAU,KAAK,YAAY,KAAK,OAAO,MAAM;GAC7C,GAAG;GACH;GACA,SAAS;GACT,WAAW;EACf,CAAC;EAED,IAAI,UACA,KAAK,sBAAsB,IAAI,KAAK,QAAsF;CAElI;;;;CAKA,eACI,gBACA,QACA,UACA,SACI;EACJ,MAAM,MAAM,KAAK,gBAAgB,OAAO,UAAU,cAAc;EAChE,MAAM,SAAS;GAAE,MAAM;GAAmB,MAAM,OAAO;GAAM,IAAI,OAAO;EAAG;EAC3E,KAAK,qBAAqB,KAAK;GAC3B;GACA,UAAU,OAAO;GACjB,UAAU,KAAK,YAAY,KAAK,OAAO,MAAM;GAC7C,GAAG;GACH;GACA,SAAS;GACT,WAAW;EACf,CAAC;EAED,IAAI,UACA,KAAK,sBAAsB,IAAI,KAAK,QAAsF;CAElI;;;;;;;;CASA,YAAY,gBAA8B;EACtC,KAAK,MAAM,CAAC,KAAK,iBAAiB,CAAC,GAAG,KAAK,eAAe,QAAQ,CAAC,GAAG;GAClE,IAAI,aAAa,mBAAmB,kBAAkB,KAAK,QAAQ,IAAI,aAAa,QAAQ,GAAG;GAC/F,KAAK,iBAAiB,GAAG;EAC7B;CACJ;CAMA,UAAU,UAAkB,IAAe;EACvC,KAAK,QAAQ,IAAI,UAAU,EAAE;EAE7B,GAAG,GAAG,eAAe;GACjB,KAAK,aAAa,QAAQ;EAC9B,CAAC;EAED,GAAG,GAAG,UAAU,UAAU;GACtB,OAAO,MAAM,8BAA8B;IAAE,QAAQ;IAAU;GAAM,CAAC;GACtE,KAAK,aAAa,QAAQ;EAC9B,CAAC;CACL;CAGA,MAAM,oBAAoB,UAAkB,SAA2B,aAAuC;EAC1G,MAAM,KAAK,cAAc,UAAU,SAAS,WAAW;CAC3D;CAEA,MAAM,aAAa,UAAkB;EACjC,KAAK,QAAQ,OAAO,QAAQ;EAI5B,KAAK,MAAM,OAAO,CAAC,GAAI,KAAK,sBAAsB,IAAI,QAAQ,KAAK,CAAC,CAAE,GAClE,KAAK,iBAAiB,GAAG;EAkB7B,MAAM,KAAK,sBAAsB,KAAK,cAAe,aAAa,QAAQ,GAAG,gBAAgB;EAG7F,KAAK,MAAM,CAAC,SAAS,YAAY,KAAK,SAAS,QAAQ,GACnD,IAAI,QAAQ,IAAI,QAAQ,GAAG;GACvB,QAAQ,OAAO,QAAQ;GACvB,KAAK,eAAe,UAAU,SAAS,EAAE,WAAW,KAAK,CAAC;GAC1D,IAAI,QAAQ,SAAS,GAAG,KAAK,SAAS,OAAO,OAAO;EACxD;EAIJ,KAAK,MAAM,CAAC,YAAY,KAAK,UACzB,KAAK,eAAe,UAAU,SAAS,EAAE,WAAW,KAAK,CAAC;CAElE;CAEA,MAAc,cAAc,UAAkB,SAA2B,aAAuC;EAC5G,MAAM,UAAU,QAAQ;EACxB,QAAQ,QAAQ,MAAhB;GACI,KAAK;IACD,MAAM,KAAK,6BAA6B,UAAU,QAAQ,SAA0C,WAAW;IAC/G;GACJ,KAAK;IACD,MAAM,KAAK,yBAAyB,UAAU,QAAQ,SAAsC,WAAW;IACvG;GACJ,KAAK;IACD,MAAM,KAAK,kBAAkB,UAAU,QAAQ,cAAe;IAC9D;GAOJ,KAAK;GACL,KAAK;GACL,KAAK;GACL,KAAK;GACL,KAAK;GACL,KAAK;GACL,KAAK;IACD,MAAM,KAAK,qBAAqB,UAAU,QAAQ,MAAM,SAAS,WAAW;IAC5E;GAEJ,SACI,KAAK,UAAU,UAAU,0BAA0B,QAAQ,MAAM,QAAQ,cAAc;EAC/F;CACJ;;;;;;;;;;CAWA,kBAA0B,UAAkB,gBAAiC;EACzE,MAAM,OAAO,KAAK,sBAAsB,IAAI,QAAQ;EACpD,IAAI,CAAC,QAAQ,KAAK,OAAO,KAAK,2BAA2B,OAAO;EAChE,IAAI,KAAK,IAAI,KAAK,gBAAgB,UAAU,cAAc,CAAC,GAAG,OAAO;EACrE,MAAM,UACF,iCAAiC,KAAK,KAAK;EAE/C,OAAO,KAAK,gDAAgD,SAAS,IAAI,KAAK,KAAK,iBAAiB,KAAK,2BAA2B;EACpI,KAAK,UAAU,UAAU,SAAS,gBAAgB,wBAAwB;EAC1E,OAAO;CACX;CAEA,MAAc,6BAA6B,UAAkB,SAAwC,aAAuC;EACxI,MAAM,iBAAiB,QAAQ;EAC/B,MAAM,MAAM,KAAK,gBAAgB,UAAU,cAAc;EAGzD,IAAI;EAEJ,IAAI;GAEA,MAAM,aAAa,KAAK,SAAS,oBAAoB,QAAQ,IAAI;GACjE,IAAI,CAAC,YAAY;IACb,MAAM,aAAa,KAAK,SAAS,eAAe,CAAC,CAAC,KAAI,MAAK,EAAE,IAAI,CAAC,CAAC,KAAK,IAAI;IAC5E,MAAM,MAAM,0BAA0B,QAAQ,KAAK,kBAAkB,WAAW;IAChF,OAAO,MAAM,qBAAqB,KAAK;IACvC,KAAK,UAAU,UAAU,KAAK,cAAc;IAC5C;GACJ;GAEA,IAAI,CAAC,KAAK,kBAAkB,UAAU,cAAc,GAAG;GAQvD,IAAI,QAAQ,cAAc;IACtB,MAAM,MACF;IAGJ,OAAO,KAAK,qBAAqB,KAAK;IACtC,KAAK,UAAU,UAAU,KAAK,gBAAgB,wBAAwB;IACtE;GACJ;GAaA,IAAI;GACJ,IAAI;IACA,eAAe,uBAAuB,QAAQ,KAAK;GACvD,SAAS,GAAG;IACR,IAAI,EAAE,aAAa,iBAAiB,MAAM;IAC1C,OAAO,KAAK,8CAA8C,QAAQ,KAAK,KAAK,EAAE,SAAS;IACvF,KAAK,UAAU,UAAU,EAAE,SAAS,gBAAgB,eAAe;IACnE;GACJ;GAQA,IAAI;GACJ,IAAI;IACA,UAAU,uBAAuB,QAAQ,SAAS,QAAQ,KAAK;GACnE,SAAS,GAAG;IACR,IAAI,EAAE,aAAa,mBAAmB,MAAM;IAC5C,OAAO,KAAK,8CAA8C,QAAQ,KAAK,KAAK,EAAE,SAAS;IACvF,KAAK,UAAU,UAAU,EAAE,SAAS,gBAAgB,EAAE,IAAI;IAC1D;GACJ;GAWA,IAAI;IACA,0BACI;KAAE,GAAG;KAAS;IAAQ,GACtB,YACA,EAAE,OAAO,aAAa,SAAS,CAAC,MAAM,EAAE,CAC5C;GACJ,SAAS,GAAG;IACR,IAAI,EAAE,aAAa,WAAW,MAAM;IACpC,OAAO,KAAK,8CAA8C,QAAQ,KAAK,KAAK,EAAE,SAAS;IACvF,KAAK,UAAU,UAAU,EAAE,SAAS,gBAAgB,EAAE,IAAI;IAC1D;GACJ;GAGA,MAAM,oBAA6C;IAC/C,QAAQ,QAAQ;IAChB,SAAS,QAAQ;IACjB;IACA,OAAO,QAAQ;IACf,OAAO;IAIP,QAAQ,QAAQ,QAAQ,QAAQ,QAAQ,OAAO,KACxC,QAAQ,OAAO,KAAK,eACrB,QAAQ;IACd,YAAY,QAAQ;IACpB,YAAY,QAAQ,YAAY;IAChC,cAAc,QAAQ;IACtB,eAAe,QAAQ;IAMvB,SAAS,QAAQ;IACjB,QAAQ,QAAQ;IAChB,UAAU,QAAQ;GACtB;GACA,MAAM,SAAS;IAAE,MAAM;IAAuB,MAAM,QAAQ;IAAM;IAAmB;GAAY;GACjG,MAAM,eAA6B;IAC/B;IACA;IACA,UAAU,KAAK,YAAY,KAAK,MAAM,MAAM;IAC5C,GAAG;IACH,SAAS;IACT,WAAW;GACf;GACA,KAAK,qBAAqB,KAAK,YAAY;GAM3C,aAAa,KAAK,cAAc,KAAK,YAAY;GAKjD,MAAM,OAAO,MAAM,KAAK,wBACpB,QAAQ,MACR,aAAa,mBACb,WACJ;GACA,MAAM,OAAO,MAAM,KAAK,uBACpB,QAAQ,MAAM,aAAa,mBAAoB,MAAM,WACzD;GAEA,IAAI,WAAW,GACX,KAAK,qBAAqB,UAAU,gBAAgB,MAAM,QAAQ,MAAM,IAAI;EAGpF,SAAS,OAAO;GACZ,KAAK,yBAAyB,UAAU,gBAAgB,QAAQ,MAAM,OAAO,UAAU;EAC3F;CACJ;CAEA,MAAc,yBAAyB,UAAkB,SAAoC,aAAuC;EAChI,MAAM,iBAAiB,QAAQ;EAC/B,MAAM,MAAM,KAAK,gBAAgB,UAAU,cAAc;EAEzD,IAAI;EAEJ,IAAI;GAGA,IAAI,CADe,KAAK,SAAS,oBAAoB,QAAQ,IACxD,GAAY;IACb,MAAM,aAAa,KAAK,SAAS,eAAe,CAAC,CAAC,KAAI,MAAK,EAAE,IAAI,CAAC,CAAC,KAAK,IAAI;IAC5E,MAAM,MAAM,0BAA0B,QAAQ,KAAK,kBAAkB,WAAW;IAChF,OAAO,MAAM,qBAAqB,KAAK;IACvC,KAAK,UAAU,UAAU,KAAK,cAAc;IAC5C;GACJ;GAEA,IAAI,CAAC,KAAK,kBAAkB,UAAU,cAAc,GAAG;GAGvD,MAAM,SAAS;IAAE,MAAM;IAAmB,MAAM,QAAQ;IAAM,IAAI,QAAQ;IAAI;GAAY;GAC1F,MAAM,eAA6B;IAC/B;IACA;IACA,UAAU,KAAK,YAAY,KAAK,MAAM,MAAM;IAC5C,GAAG;IACH,SAAS;IACT,WAAW;GACf;GACA,KAAK,qBAAqB,KAAK,YAAY;GAK3C,aAAa,KAAK,cAAc,KAAK,YAAY;GAGjD,MAAM,MAAM,MAAM,KAAK,oBACnB,QAAQ,MACR,OAAO,QAAQ,EAAE,GACjB,WACJ;GAEA,IAAI,WAAW,GACX,KAAK,iBAAiB,UAAU,gBAAgB,OAAO,IAAI;EAGnE,SAAS,OAAO;GACZ,KAAK,yBAAyB,UAAU,gBAAgB,QAAQ,MAAM,OAAO,UAAU;EAC3F;CACJ;;;;;;;;;;;;;;CAeA,yBACI,UACA,gBACA,MACA,OACA,YACF;EACE,MAAM,YAAY,uBAAuB,OAAO,IAAI;EACpD,IAAI,cAAc,CAAC,WAAW,iBAAiB,GAAG;EAClD,KAAK,UAAU,UAAU,UAAU,SAAS,gBAAgB,UAAU,IAAI;CAC9E;;;;;;;;;;;;;;;;;;;CAoBA,MAAM,cAAc,UAAkB,aAAqD;EACvF,KAAK,MAAM,OAAO,CAAC,GAAI,KAAK,sBAAsB,IAAI,QAAQ,KAAK,CAAC,CAAE,GAAG;GACrE,MAAM,eAAe,KAAK,eAAe,IAAI,GAAG;GAChD,IAAI,CAAC,cAAc;GACnB,MAAM,iBAAiB,aAAa;GAEpC,IAAI,aAAa,SAAS,gBAAgB,aAAa,mBAAmB;IACtE,MAAM,aAAa,KAAK,SAAS,oBAAoB,aAAa,IAAI;IACtE,MAAM,UAAU,aAAa;IAC7B,IAAI;KACA,IAAI,YACA,0BACI;MAEI,QAAQ,QAAQ;MAChB,SAAS,QAAQ;MACjB,SAAS,QAAQ;MACjB,QAAQ,QAAQ;KACpB,GACA,YACA,EAAE,OAAO,YAAY,SAAS,CAAC,MAAM,EAAE,CAC3C;IAER,SAAS,GAAG;KACR,IAAI,EAAE,aAAa,WAAW,MAAM;KACpC,MAAM,KAAK,kBAAkB,UAAU,cAAc;KACrD,KAAK,UAAU,UAAU,EAAE,SAAS,gBAAgB,EAAE,IAAI;KAC1D;IACJ;GACJ;GAIA,MAAM,WAAyB;IAC3B,GAAG;IACH;IACA,UAAU,KAAK,YAAY,KAAK,MAAM;KAAE,GAAG;KAAc;IAAY,CAAC;IACtE,SAAS;IACT,WAAW;GACf;GACA,KAAK,qBAAqB,KAAK,QAAQ;GACvC,MAAM,QAAQ,KAAK,OAAO,IAAI,SAAS,QAAQ;GAC/C,IAAI,OAAO,KAAK,qBAAqB,OAAO,GAAG;EACnD;EAEA,MAAM,aAAa,KAAK;EACxB,IAAI,CAAC,YAAY;EACjB,KAAK,MAAM,CAAC,SAAS,YAAY,CAAC,GAAG,KAAK,SAAS,QAAQ,CAAC,GAAG;GAC3D,IAAI,CAAC,QAAQ,IAAI,QAAQ,GAAG;GAC5B,IAAI;GACJ,IAAI;IACA,UAAU,MAAM,WAAW;KAAE;KAAS,QAAQ;KAAQ;KAAU,MAAM;IAAY,CAAC;GACvF,SAAS,OAAO;IACZ,OAAO,MAAM,8CAA8C,QAAQ,iCAAiC,EAAE,MAAM,CAAC;IAC7G,UAAU;GACd;GACA,IAAI,CAAC,SAAS;IACV,KAAK,aAAa,UAAU,OAAO;IACnC,KAAK,kBAAkB,UAAU,SAAS,QAAQ,6EAA6E;GACnI;EACJ;CACJ;;;;;CAMA,MAAc,kBAAkB,UAAkB,gBAAwB;EACtE,KAAK,iBAAiB,KAAK,gBAAgB,UAAU,cAAc,CAAC;CACxE;;;;;;;;;;;;CAaA,MAAM,aAAa,MAAc,IAAY,KAAqC,YAAqB,YAAY,MAAM,SAAwB,OAAO;EACpJ,KAAK,SAAS,sDAAsD,MAAM,OAAO,IAAI,aAAa,QAAQ,MAAM,WAAW,MAAM;EASjI,MAAM,UAAU,KAAK,WAAW,IAAI;EAEpC,IAAI,KAAK,WAAW;GAChB,MAAM,MAAM,KAAK,SAAS,MAAM,IAAI,UAAU;GAC9C,IAAI,WAAW;QACP,KAAK,eAAe,GAAG,GAAG;KAC1B,KAAK,SAAS,oEAAoE,GAAG;KACrF;IACJ;UACG;IACH,KAAK,YAAY,GAAG;IAIpB,IAAI,aAAa,IAAI,GAAG;KACpB,MAAM,OAAO,QAAQ,MAAK,UAAS,CAAC,aAAa,KAAK,CAAC;KACvD,IAAI,MAAM,KAAK,YAAY,KAAK,SAAS,MAAM,IAAI,UAAU,CAAC;IAClE;GACJ;EACJ;EAGA,MAAM,gBAAgB,CAAC,IAAI;EAG3B,IAAI,KAAK,SAAS,GAAG,KAAK,KAAK,MAAM,GAAG,CAAC,CAAC,SAAS,GAAG;GAClD,MAAM,cAAc,KAAK,eAAe,IAAI;GAC5C,cAAc,KAAK,GAAG,WAAW;GACjC,KAAK,SAAS,iEAAiE,cAAc,KAAK,IAAI,GAAG;EAC7G;EAGA,KAAK,MAAM,cAAc,eACrB,KAAK,iBAAiB,YAAY,MAAM,EAAE;EAK9C,KAAK,MAAM,SAAS,SAChB,KAAK,iBAAiB,OAAO,OAAO,EAAE;EAO1C,IAAI,aAAa,KAAK,gBAAgB,CAAC,KAAK,WACxC,IAAI;GACA,MAAM,KAAK,gBAAgB,MAAM,IAAI,UAAU;EACnD,SAAS,KAAK;GACV,OAAO,MAAM,gEAAgE,EAAE,OAAO,IAAI,CAAC;EAC/F;EAGJ,KAAK,SAAS,yDAAyD,IAAI;CAC/E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqCA,iBAAyB,YAAoB,cAAsB,IAAY;EAC3E,KAAK,SAAS,wCAAwC,WAAW,cAAc,aAAa,EAAE;EAK9F,KAAK,MAAM,SAAS,KAAK,uBAAuB,IAAI,UAAU,KAAK,CAAC,GAChE,KAAK,qBAAqB,KAAK;EAEnC,IAAI,eAAe,cAAc;EACjC,KAAK,MAAM,SAAS,KAAK,sBAAsB,IAAI,GAAG,WAAW,QAAQ,IAAI,KAAK,CAAC,GAC/E,KAAK,qBAAqB,KAAK;CAEvC;;;;;;;;;;;;CAaA,yBACI,cACA,YACA,OACF;EACE,IAAI,CAAC,aAAa,WAAW,CAAC,WAAW,GAAG;EAC5C,IAAI;GACA,aAAa,QAAQ,KAAK;EAC9B,SAAS,eAAe;GAGpB,OAAO,MAAM,iEAAiE,aAAa,kBAAkB,EAAE,OAAO,cAAc,CAAC;EACzI;CACJ;;;;;;CAOA,MAAc,wBACV,YACA,mBACA,aACkC;EAClC,IAAI,KAAK,QAAQ;GACb,MAAM,aAAa,KAAK,SAAS,oBAAoB,UAAU;GAQ/D,MAAM,aAAa,eAAe;IAAE,KAAK;IACrD,OAAO,CAAC,MAAM;GAAE;GAKJ,OAAO,MAAM,gBAAgB,EAAE,OAAO,WAAW,SAAS,CAAC,EAAE,GAAG,YAC5D,MAAM,KAAK,GAAG,YAAY,OAAO,OAAO;IACpC,MAAM,iBACF,IACA;KACI,KAAK,WAAW;KAChB,OAAO,WAAW;KAClB,aAAa,WAAW,gBAAgB;KAIxC,QAAQ,WAAW;IACvB,GACA,KAAK,WACT;IAKA,IAAI;IAgBJ,MAAM,kBAAkB,MAAM,IAfF,YACxB,IAAI,KAAK,gBACF,gBAAgB,sBAAsB,KAAK,QAAQ,IAAI,UAAU,CAa9C,CAAA,CAAgB,uBAAuB,YAAY;KAC7E,QAAQ,kBAAkB;KAI1B,SAAS,kBAAkB;KAC3B,SAAS,kBAAkB;KAC3B,OAAO,kBAAkB;KACzB,OAAO,kBAAkB;KACzB,QAAQ,kBAAkB;KAC1B,YAAY,kBAAkB;KAC9B,cAAc,kBAAkB;KAChC,eAAe,kBAAkB;KACjC,YAAY,kBAAkB;KAC9B,QAAQ,kBAAkB;KAC1B,UAAU,kBAAkB;IAChC,GAAG,kBAAkB,OAAO;IAI5B,MAAM,qBAAqB,KAAK,SAAS,oBAAoB,UAAU;IAIvE,MAAM,qBAAmD,aACnD;KAAE,GAAG;KAAY,GAAG;IAAmB,IACvC;IAEN,MAAM,YAAY,oBAAoB;IACtC,MAAM,kBAAkB,KAAK,UAAU,mBAAmB;IAC1D,MAAM,oBAAoB,oBAAoB,aAAa,uBAAuB,mBAAmB,UAAU,IAAI,KAAA;IAEnH,IAAI,iBAAiB,aAAa,WAAW,aAAa,mBAAmB,WAAW;KAGpF,MAAM,qBAAqB,sBAAsB,KAAK,QAAQ,IAAI,UAAU;KAE5E,MAAM,qBAAqB,0BAA0B,oBAAoB,UAAU;KACnF,OAAO,MAAM,QAAQ,IAAI,gBAAgB,IAAI,OAAO,eAAe;MAC/D,IAAI,kBAAkB;MAEtB,IAAI,iBAAiB,WACjB,kBAAkB,MAAM,gBAAgB,UAAU;OAC9C,YAAY;OACZ,MAAM;OACN,KAAK;OACL,SAAS;MACb,CAAC,KAAK;MAGV,IAAI,WAAW,WACX,kBAAkB,MAAM,UAAU,UAAU;OACxC,YAAY;OACZ,MAAM;OACN,KAAK;OACL,SAAS;MACb,CAAC,KAAK;MAGV,IAAI,mBAAmB,WACnB,kBAAkB,MAAM,kBAAkB,UAAU;OAChD,YAAY;OACZ,MAAM;OACN,KAAK;OACL,SAAS;MACb,CAAC,KAAK;MAEV,OAAO;KACX,CAAC,CAAC;IACN;IAEA,OAAO;GACX,GAAG,EAAE,YAAY,YAAY,CAAC,CAClC;EACJ;EAaA,OAAO,MAAM,gBAAgB,EAAE,OAAO,aAAa,SAAS,CAAC,EAAE,GAAG,YAC9D,MAAM,KAAK,YAAY,uBAAuB,YAAY;GACtD,QAAQ,kBAAkB;GAC1B,SAAS,kBAAkB;GAC3B,SAAS,kBAAkB;GAC3B,OAAO,kBAAkB;GACzB,OAAO,kBAAkB;GACzB,QAAQ,kBAAkB;GAC1B,YAAY,kBAAkB;GAC9B,cAAc,kBAAkB;GAChC,eAAe,kBAAkB;GACjC,YAAY,kBAAkB;GAC9B,QAAQ,kBAAkB;GAC1B,UAAU,kBAAkB;EAChC,GAAG,kBAAkB,OAAO,CAChC;CACJ;;;;;;;;;;;CAYA,MAAc,uBACV,YACA,mBACA,MACA,aAC6B;EAC7B,MAAM,QAAQ,kBAAkB,SAAS,KAAK;EAC9C,MAAM,SAAS,kBAAkB,UAAU;EAE3C,MAAM,YAAY,OAAO,YAAyB,QAAQ,MAAM,YAAY;GACxE,QAAQ,kBAAkB;GAC1B,SAAS,kBAAkB;GAC3B,cAAc,kBAAkB;GAChC,YAAY,kBAAkB;EAClC,CAAC;EAED,IAAI;EACJ,IAAI;GACA,IAAI,KAAK,QAAQ;IACb,MAAM,aAAa,eAAe;KAAE,KAAK;KAAmB,OAAO,CAAC,MAAM;IAAE;IAC5E,QAAQ,MAAM,KAAK,GAAG,YAAY,OAAO,OAAO;KAC5C,MAAM,iBACF,IACA;MACI,KAAK,WAAW;MAChB,OAAO,WAAW;MAClB,aAAa,WAAW,gBAAgB;MAIxC,QAAQ,WAAW;KACvB,GACA,KAAK,WACT;KAIA,IAAI;KACJ,OAAO,UAAU,IAAI,YACjB,IAAI,KAAK,gBACF,gBAAgB,sBAAsB,KAAK,QAAQ,IAAI,UAAU,CAC5E,CAAC;IACL,GAAG,EAAE,YAAY,YAAY,CAAC;GAClC,OACI,QAAQ,MAAM,UAAU,KAAK,WAAW;EAEhD,SAAS,OAAO;GAKZ,OAAO,KAAK,sCAAsC,WAAW,6BAA6B,EAAE,MAAM,CAAC;GACnG,OAAO;IAAE;IAAO;IAAQ,SAAS;IAAO,SAAS;GAAK;EAC1D;EAEA,MAAM,OAAO,KAAK,KAAK,SAAS;EAChC,MAAM,UAAU,SAAS,KAAK,SAAS;EACvC,MAAM,aAAc,WAAW,OACzB,KAAK,YAAY,YACf,YAAY,MAAM,uBAAuB,kBAAkB,SAAS,kBAAkB,KAAK,CAC/F,IACE,KAAA;EAEN,OAAO;GAAE;GAAO;GAAO;GAAQ;GAAS,GAAI,cAAc,EAAE,WAAW;EAAG;CAC9E;;;;CAKA,MAAc,oBACV,YACA,IACA,aAC4C;EAC5C,IAAI,KAAK,QAAQ;GACb,MAAM,aAAa,KAAK,SAAS,oBAAoB,UAAU;GAG/D,MAAM,aAAa,eAAe;IAAE,KAAK;IACrD,OAAO,CAAC,MAAM;GAAE;GAKJ,OAAO,MAAM,gBAAgB,EAAE,OAAO,WAAW,SAAS,CAAC,EAAE,GAAG,YAC5D,MAAM,KAAK,GAAG,YAAY,OAAO,OAAO;IACpC,MAAM,iBACF,IACA;KACI,KAAK,WAAW;KAChB,OAAO,WAAW;KAClB,aAAa,WAAW,gBAAgB;KAIxC,QAAQ,WAAW;IACvB,GACA,KAAK,WACT;IAKA,IAAI;IAUJ,IAAI,kBAAkB,MAAM,IATA,YACxB,IAAI,KAAK,gBACF,gBAAgB,sBAAsB,KAAK,QAAQ,IAAI,UAAU,CAOhD,CAAA,CAAgB,gBACxC,YAAY,IAAI,KAAA,GAAW,YAAY,UAC3C,KAAK,KAAA;IAEL,IAAI,iBAAiB;KACjB,MAAM,qBAAqB,KAAK,SAAS,oBAAoB,UAAU;KAEvE,MAAM,qBAAmD,aACnD;MAAE,GAAG;MAAY,GAAG;KAAmB,IACvC;KAEN,MAAM,YAAY,oBAAoB;KACtC,MAAM,kBAAkB,KAAK,UAAU,mBAAmB;KAC1D,MAAM,oBAAoB,oBAAoB,aAAa,uBAAuB,mBAAmB,UAAU,IAAI,KAAA;KAEnH,IAAI,iBAAiB,aAAa,WAAW,aAAa,mBAAmB,WAAW;MACpF,MAAM,qBAAqB,sBAAsB,KAAK,QAAQ,IAAI,UAAU;MAC5E,MAAM,qBAAqB,0BAA0B,oBAAoB,UAAU;MAGnF,IAAI,iBAAiB,WACjB,kBAAkB,MAAM,gBAAgB,UAAU;OAC9C,YAAY;OACZ,MAAM;OACN,KAAK;OACL,SAAS;MACb,CAAC,KAAK;MAGV,IAAI,WAAW,WACX,kBAAkB,MAAM,UAAU,UAAU;OACxC,YAAY;OACZ,MAAM;OACN,KAAK;OACL,SAAS;MACb,CAAC,KAAK;MAGV,IAAI,mBAAmB,WACnB,kBAAkB,MAAM,kBAAkB,UAAU;OAChD,YAAY;OACZ,MAAM;OACN,KAAK;OACL,SAAS;MACb,CAAC,KAAK;KAEd;IACJ;IAEA,OAAO;GACX,GAAG,EAAE,YAAY,YAAY,CAAC,CAClC;EACJ;EAIA,OAAO,MAAM,gBACT,EAAE,OAAO,aAAa,SAAS,CAAC,EAAE,GAClC,YAAa,MAAM,KAAK,YAAY,gBAAgB,YAAY,EAAE,KAAM,KAAA,CAC5E;CACJ;CAEA,qBACI,UACA,gBACA,MACA,MACA,MACF;EACE,MAAM,UAAmC;GACrC,MAAM;GACN;GACM;GACN,KAAK,KAAK,mBAAmB,IAAI;GAGjC,GAAI,QAAQ,EAAE,KAAK;EACvB;EACA,KAAK,YAAY,UAAU,OAAO;CACtC;;;;;;CAOA,oBAA4B,MAAiC,MAAc,MAAqC;EAC5G,OAAO,KAAK,UAAU;GAClB;GACA,KAAK,KAAK,mBAAmB,IAAI;GACjC,GAAI,QAAQ,EAAE,KAAK;EACvB,CAAC,CAAC,CAAC,MAAM,CAAC;CACd;CAEA,iBAAyB,UAAkB,gBAAwB,KAAqC;EACpG,MAAM,UAA+B;GACjC,MAAM;GACN;GACK;EACT;EACA,KAAK,YAAY,UAAU,OAAO;CACtC;;;;;;;;;;;CAYA,mBAA2B,MAA4C;EACnE,IAAI;GACA,MAAM,aAAa,KAAK,SAAS,oBAAoB,IAAI;GACzD,IAAI,CAAC,YAAY,OAAO,KAAA;GACxB,MAAM,OAAO,eAAe,YAAY,KAAK,QAAQ;GACrD,OAAO,KAAK,SAAS,IAAI,OAAO,KAAA;EACpC,QAAQ;GAKJ;EACJ;CACJ;;;;;;;;;;;;;;CAeA,UAAkB,UAAkB,OAAe,gBAAyB,MAAe,SAAkB;EACzG,MAAM,UAAU;GACZ,MAAM;GACN;GACA,GAAI,YAAY,KAAA,KAAa,EAAE,QAAQ;GACvC,SAAS;IACL,OAAO,OAAO;KAAE,SAAS;KAAO;IAAK,IAAI;IACzC,GAAI,YAAY,KAAA,KAAa,EAAE,QAAQ;GAC3C;GACA;EACJ;EACA,KAAK,YAAY,UAAU,OAAO;CACtC;CAEA,YAAoB,UAAkB,SAAgK;EAClM,KAAK,QAAQ,UAAU,KAAK,UAAU,OAAO,CAAC;CAClD;;;;;;;;;CAUA,QAAgB,UAAkB,OAAe;EAC7C,MAAM,SAAS,KAAK,QAAQ,IAAI,QAAQ;EACxC,IAAI,CAAC,UAAU,OAAO,eAAe,UAAU,MAAM;EACrD,IAAI,sBAAsB,QAAQ,QAAQ,GAAG;EAC7C,OAAO,KAAK,KAAK;CACrB;;;;;;;;;;;;;;;;;;;CAoBA,WAAmB,MAAwB;EACvC,MAAM,OAAO,KAAK,iBAAiB,IAAI;EACvC,IAAI,CAAC,MAAM,OAAO,CAAC;EACnB,MAAM,0BAAU,IAAI,IAAY;EAChC,IAAI,SAAS,MAAM,QAAQ,IAAI,IAAI;EAGnC,KAAK,MAAM,CAAC,YAAY,SAAS,KAAK,iBAAiB;GACnD,IAAI,eAAe,QAAQ,QAAQ,IAAI,UAAU,KAAK,CAAC,aAAa,UAAU,GAAG;GACjF,IAAI,KAAK,SAAS,MAAM,QAAQ,IAAI,UAAU;EAClD;EACA,OAAO,CAAC,GAAG,OAAO;CACtB;;CAGA,iBAAyB,MAAkC;EACvD,IAAI;GACA,OAAO,KAAK,SAAS,oBAAoB,IAAI,CAAC,EAAE;EACpD,QAAQ;GAGJ;EACJ;CACJ;CAEA,eAAuB,MAAwB;EAC3C,MAAM,WAAW,KAAK,MAAM,GAAG,CAAC,CAAC,QAAO,MAAK,EAAE,SAAS,CAAC;EACzD,MAAM,cAAwB,CAAC;EAG/B,KAAK,IAAI,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK,GAAG;GACzC,MAAM,aAAa,SAAS,MAAM,GAAG,CAAC,CAAC,CAAC,KAAK,GAAG;GAChD,IAAI,YACA,YAAY,KAAK,UAAU;GAI/B,IAAI,IAAI,IAAI,SAAS,QAAQ;IACzB,MAAM,iBAAiB,SAAS,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,KAAK,GAAG;IACxD,YAAY,KAAK,cAAc;GACnC;EACJ;EAEA,OAAO;CACX;;;;;;;;CAaA,qBAAqB,YAAiD;EAClE,KAAK,oBAAoB;CAC7B;;CAGA,OAAwB,kBAAiD;EACrE,cAAc;EACd,WAAW;EACX,iBAAiB;EACjB,gBAAgB;EAChB,gBAAgB;CACpB;;;;;;;;;;;CAYA,qBACI,UACA,MACA,SACA,aACoB;EACpB,MAAM,UAAU,SAAS;EAIzB,IAAI,SAAS,iBAAiB;GAC1B,KAAK,aAAa,UAAU,OAAO;GACnC;EACJ;EACA,IAAI,SAAS,oBAAoB;GAC7B,KAAK,eAAe,UAAU,OAAO;GACrC;EACJ;EAEA,MAAM,SAAS,gBAAgB,gBAAgB;EAC/C,MAAM,UAAU,KAAK,uBAAuB,UAAU,SAAS,QAAQ,WAAW;EAClF,IAAI,YAAY,OAAO;EACvB,IAAI,YAAY,MAAM,OAAO,KAAK,uBAAuB,UAAU,MAAM,SAAS,OAAO;EACzF,OAAO,QAAQ,MAAM,OAAO;GACxB,IAAI,IAAI,OAAO,KAAK,uBAAuB,UAAU,MAAM,SAAS,OAAO;EAC/E,CAAC;CACL;;CAGA,uBACI,UACA,MACA,SACA,SACoB;EACpB,QAAQ,MAAR;GACI,KAAK;IACD,KAAK,YAAY,UAAU,OAAO;IAClC;GACJ,KAAK;IACD,KAAK,mBAAmB,UAAU,SAAS,SAAS,OAAiB,SAAS,OAAO;IACrF;GACJ,KAAK,mBACD,OAAO,KAAK,4BACR,UACA,SACA,SAAS,UACT,SAAS,KACb;GACJ,KAAK;IAED,KAAK,YAAY,UAAU,OAAO;IAClC,KAAK,cAAc,UAAU,SAAS,SAAS,SAAoC,CAAC,CAAC;IACrF;GACJ,KAAK;IACD,KAAK,kBAAkB,UAAU,OAAO;IACxC;EACR;CACJ;;;;;;;;;;;;;;;;;CAkBA,uBACI,UACA,SACA,QACA,aAC0B;EAE1B,IAAI,WAAW,UAAU,CAAC,KAAK,SAAS,IAAI,OAAO,CAAC,EAAE,IAAI,QAAQ,GAAG;GACjE,KAAK,kBAAkB,UAAU,SAAS,QAAQ,6BAA6B;GAC/E,OAAO;EACX;EAEA,MAAM,aAAa,KAAK;EACxB,IAAI,CAAC,YAAY,OAAO;EAExB,IAAI;EACJ,IAAI;GACA,UAAU,WAAW;IAAE;IAAS;IAAQ;IAAU,MAAM;GAAY,CAAC;EACzE,SAAS,OAAO;GACZ,OAAO,MAAM,qCAAqC,OAAO,OAAO,QAAQ,eAAe,EAAE,MAAM,CAAC;GAChG,KAAK,kBAAkB,UAAU,SAAS,QAAQ,8BAA8B;GAChF,OAAO;EACX;EAEA,IAAI,OAAO,YAAY,WAAW;GAC9B,IAAI,CAAC,SAAS,KAAK,kBAAkB,UAAU,SAAS,QAAQ,mCAAmC;GACnG,OAAO;EACX;EAEA,OAAO,QAAQ,MACV,OAAO;GACJ,IAAI,CAAC,IAAI,KAAK,kBAAkB,UAAU,SAAS,QAAQ,mCAAmC;GAC9F,OAAO;EACX,IACC,UAAU;GACP,OAAO,MAAM,wCAAwC,OAAO,OAAO,QAAQ,eAAe,EAAE,MAAM,CAAC;GACnG,KAAK,kBAAkB,UAAU,SAAS,QAAQ,8BAA8B;GAChF,OAAO;EACX,CACJ;CACJ;;CAGA,kBAA0B,UAAkB,SAAiB,QAAuB,QAAsB;EACtG,KAAK,SAAS,yBAAyB,OAAO,OAAO,QAAQ,QAAQ,SAAS,IAAI,QAAQ;EAC1F,KAAK,UACD,UACA,WAAW,OAAO,eAAe,QAAQ,KAAK,UAC9C,KAAA,GACA,qBACA,OACJ;CACJ;;CAGA,YAAY,UAAkB,SAAuB;EACjD,IAAI,CAAC,KAAK,SAAS,IAAI,OAAO,GAC1B,KAAK,SAAS,IAAI,yBAAS,IAAI,IAAI,CAAC;EAExC,KAAK,SAAS,IAAI,OAAO,CAAC,CAAE,IAAI,QAAQ;EACxC,KAAK,8BAA8B;EACnC,KAAK,SAAS,yBAAyB,SAAS,mBAAmB,SAAS;CAChF;;;;;;;;;;;CAYA,gCAA8C;EAC1C,IAAI,KAAK,iBAAiB;EAC1B,IAAI,KAAK,IAAI,SAAS,YAAY,CAAC,KAAK,qBAAqB;EAC7D,KAAK,kBAAkB;EACvB,OAAO,KACH,8TAIJ;CACJ;;CAGA,aAAa,UAAkB,SAAuB;EAClD,MAAM,UAAU,KAAK,SAAS,IAAI,OAAO;EACzC,IAAI,SAAS;GACT,QAAQ,OAAO,QAAQ;GACvB,IAAI,QAAQ,SAAS,GAAG,KAAK,SAAS,OAAO,OAAO;EACxD;EAEA,KAAK,eAAe,UAAU,OAAO;CACzC;;;;;;;;;;;;;;CAeA,mBAAmB,UAAkB,SAAiB,OAAe,SAAwB;EACzF,MAAM,YAAY,KAAK,gBAAgB,aAAa,OAAO;EAC3D,IAAI,CAAC,WAAW;GACZ,KAAK,gBAAgB,UAAU,SAAS,OAAO,OAAO;GAKtD,KAAK,iBAAiB,UAAU,SAAS,OAAO,OAAO;GACvD;EACJ;EAGA,MAAM,QADW,KAAK,kBAAkB,IAAI,OAAO,KAAK,QAAQ,QAAQ,EAAA,CAInE,YAAY,CAA+B,CAAC,CAAC,CAC7C,WAAW,KAAK,iBAAiB,UAAU,SAAS,OAAO,SAAS,SAAS,CAAC;EAEnF,KAAK,kBAAkB,IAAI,SAAS,IAAI;EACxC,KAAU,cAAc;GAEpB,IAAI,KAAK,kBAAkB,IAAI,OAAO,MAAM,MAAM,KAAK,kBAAkB,OAAO,OAAO;EAC3F,CAAC;CACL;;;;;;;;;;;CAYA,MAAc,iBACV,UACA,SACA,OACA,SACA,WACa;EASb,MAAM,UAAU,KAAK,eAAe;EACpC,IAAI;EACJ,IAAI;GACA,CAAC,CAAE,OAAQ,MAAM,KAAK,eAAgB,OAAO,SAAS,OAAO,SAAS,UAAU,UAAU;IACtF,eAAe;IACf,KAAK,KAAK;GACd,IAAI,KAAA,CAAS;EACjB,SAAS,OAAO;GACZ,OAAO,MAAM,sDAAsD,QAAQ,sBAAsB,EAAE,MAAM,CAAC;GAC1G,KAAK,UACD,UACA,oDAAoD,QAAQ,IAC5D,KAAA,GACA,gCACA,OACJ;GACA;EACJ;EAEA,IAAI,CAAC,SAAS;GACV,KAAK,gBAAgB,UAAU,SAAS,OAAO,SAAS,GAAG;GAC3D,KAAK,iBAAiB,UAAU,SAAS,OAAO,SAAS,GAAG;EAChE;EAEA,IAAI;GACA,MAAM,KAAK,eAAgB,MAAM,SAAS,SAAS;EACvD,SAAS,OAAO;GAIZ,OAAO,KAAK,yCAAyC,QAAQ,IAAI,EAAE,MAAM,CAAC;EAC9E;CACJ;;CAGA,gBAAwB,UAAkB,SAAiB,OAAe,SAAkB,KAAoB;EAC5G,MAAM,UAAU,KAAK,SAAS,IAAI,OAAO;EACzC,IAAI,CAAC,SAAS;EAEd,MAAM,UAAU,KAAK,UAAU;GAC3B,MAAM;GACN;GACA;GACA;GACA,GAAI,QAAQ,KAAA,IAAY,EAAE,IAAI,IAAI,CAAC;EACvC,CAAC;EAED,KAAK,MAAM,YAAY,SAAS;GAC5B,IAAI,aAAa,UAAU;GAC3B,KAAK,QAAQ,UAAU,OAAO;EAClC;CACJ;;;;;;;;;CAcA,MAAM,oBAAoB,KAAgC;EACtD,IAAI,IAAI,SAAS,UAAU;GACvB,KAAK,MAAM;GACX;EACJ;EAEA,IAAI;GACA,MAAM,IAAI,OAAO,UAAU,KAAK,eAAe,KAAK,CAAC;EACzD,SAAS,OAAO;GACZ,OAAO,KACH,wCAAwC,IAAI,KAAK,kIAEjD,EAAE,MAAM,CACZ;GACA,MAAM,IAAI,KAAK,CAAC,CAAC,YAAY,CAAoB,CAAC;GAClD,KAAK,MAAM,IAAI,iBAAiB;GAChC;EACJ;EAEA,KAAK,MAAM;EAIX,IAAI;GACA,MAAM,QAAQ,IAAI,qBAAqB,KAAK,IAAI,KAAK,UAAU;GAC/D,MAAM,MAAM,aAAa;GACzB,KAAK,gBAAgB;GACrB,KAAK,oBAAoB;EAC7B,SAAS,OAAO;GACZ,OAAO,KACH,8JAEA,EAAE,MAAM,CACZ;GACA,KAAK,gBAAgB,KAAA;EACzB;EAEA,OAAO,KACH,sDAAsD,IAAI,KAAK,gBAAgB,KAAK,WAAW,GACnG;CACJ;;CAGA,oBAA+C;EAC3C,OAAO,KAAK,IAAI;CACpB;;;;;;;;CASA,iBAAyB,UAAkB,SAAiB,OAAe,SAAkB,KAAoB;EAC7G,IAAI,KAAK,IAAI,SAAS,UAAU;EAEhC,MAAM,QAAyB;GAC3B,MAAM;GACN,KAAK,KAAK;GACV;GACA;GACA,MAAM;GACN,GAAI,QAAQ,KAAA,IAAY,EAAE,IAAI,IAAI,CAAC;GACnC;EACJ;EAMA,IAAI,gBAAgB,KAAK,IAAI,KAAK,IAAI,eAAe;GACjD,IAAI,QAAQ,KAAA,GAAW;IACnB,KAAK,yBAAyB,UAAU,OAAO;IAC/C;GACJ;GACA,KAAU,aAAa;IACnB,MAAM;IACN,KAAK,KAAK;IACV;IACA,MAAM;IACN;GACJ,CAAC;GACD;EACJ;EAEA,KAAU,aAAa,KAAK;CAChC;CAEA,MAAc,aAAa,OAAuC;EAC9D,IAAI;GACA,MAAM,KAAK,IAAI,QAAQ,KAAK;EAChC,SAAS,OAAO;GACZ,OAAO,MAAM,+EAA+E;IACxF,QAAQ,GAAG,MAAM,KAAK,OAAO,MAAM,QAAQ;IAC3C;GACJ,CAAC;EACL;CACJ;;;;;;;;;;CAWA,yBAAiC,UAAkB,SAAuB;EACtE,MAAM,SACF,6BAA6B,QAAQ;EAGzC,IAAI,CAAC,KAAK,yBAAyB,IAAI,OAAO,GAAG;GAC7C,KAAK,yBAAyB,IAAI,OAAO;GACzC,OAAO,KACH,qDAAqD,QAAQ,gBAC1D,KAAK,IAAI,cAAc,qBAAqB,KAAK,IAAI,KAAK,yCAC7D,MACJ;EACJ;EACA,KAAK,UACD,UACA,iBAAiB,QAAQ,4CAA4C,UACrE,KAAA,GACA,iCACA,OACJ;CACJ;;;;;;;;CASA,MAAc,eAAe,OAAuC;EAChE,MAAM,YAAY,MAAM,SAAS,eAAe,MAAM,SAAS,oBAAoB,MAAM,QAAQ,KAAA;EAIjG,IAAI,MAAM,QAAQ,KAAK,cAAc,EAAE,YAAY,KAAK,eAAe,qBAAqB;EAC5F,IAAI,CAAC,UAAU,OAAO,KAAK,gBAAgB,KAAK;EAKhD,MAAM,QADW,KAAK,qBAAqB,IAAI,MAAM,OAAO,KAAK,QAAQ,QAAQ,EAAA,CAC3D,YAAY,CAAiC,CAAC,CAAC,CAAC,WAAW,KAAK,gBAAgB,KAAK,CAAC;EAC5G,KAAK,qBAAqB,IAAI,MAAM,SAAS,IAAI;EACjD,KAAU,cAAc;GACpB,IAAI,KAAK,qBAAqB,IAAI,MAAM,OAAO,MAAM,MAAM,KAAK,qBAAqB,OAAO,MAAM,OAAO;EAC7G,CAAC,CAAC,CAAC,YAAY,CAAiC,CAAC;EACjD,OAAO;CACX;CAEA,MAAc,gBAAgB,OAAuC;EACjE,QAAQ,MAAM,MAAd;GACI,KAAK;IACD,KAAK,gBAAgB,MAAM,QAAQ,IAAI,MAAM,SAAS,MAAM,OAAO,MAAM,SAAS,MAAM,GAAG;IAC3F;GAEJ,KAAK,iBAAiB;IAGlB,IAAI,CAAC,KAAK,SAAS,IAAI,MAAM,OAAO,CAAC,EAAE,MAAM;IAE7C,MAAM,QAAQ,MAAM,KAAK,gBAAgB,SAAS,MAAM,SAAS,MAAM,GAAG;IAC1E,IAAI,CAAC,OAAO;KACR,OAAO,KACH,2BAA2B,MAAM,IAAI,OAAO,MAAM,QAAQ,sGAE9D;KACA;IACJ;IAGA,KAAK,gBAAgB,MAAM,QAAQ,MAAM,YAAY,IAAI,MAAM,SAAS,MAAM,OAAO,MAAM,SAAS,MAAM,GAAG;IAC7G;GACJ;GAEA,KAAK;IACD,KAAK,oBAAoB,MAAM,SAAS,MAAM,OAAO,MAAM,MAAM;IACjE;EACR;CACJ;;;;;;;;CAaA,MAAM,wBACF,OACA,SACa;EAKb,KAAK,iBAAiB,IAAI,oBAAoB,KAAK,IAAI,SAAS,CAAC,CAAC;EAClE,IAAI,CAAC,KAAK,eAAe,SAAS;EAClC,IAAI,SAAS,cAAc,OAAO;EAClC,MAAM,KAAK,eAAe,aAAa;CAC3C;;CAGA,0BAA0C;EACtC,OAAO,KAAK,gBAAgB,WAAW;CAC3C;;;;;;;;;;CAWA,MAAc,4BACV,UACA,SACA,UACA,OACa;EACb,IAAI,CAAC,SAAS;EAGd,IAAI,CADc,KAAK,gBAAgB,aAAa,OAAO,GAC3C;GACZ,KAAK,mBAAmB,UAAU,SAAS,CAAC,GAAG,KAAK;GACpD;EACJ;EAEA,IAAI;GACA,MAAM,EAAE,UAAU,cAAc,MAAM,KAAK,eAAgB,OAAO,SAAS,UAAU,KAAK;GAC1F,KAAK,mBAAmB,UAAU,SAAS,UAAU,MAAM,SAAS;EACxE,SAAS,OAAO;GACZ,OAAO,MAAM,yCAAyC,QAAQ,IAAI,EAAE,MAAM,CAAC;GAC3E,KAAK,UAAU,UAAU,yCAAyC,QAAQ,IAAI,KAAA,GAAW,+BAA+B,OAAO;EACnI;CACJ;CAEA,mBACI,UACA,SACA,UACA,UACA,WACI;EACJ,KAAK,QAAQ,UAAU,KAAK,UAAU;GAClC,MAAM;GACN;GACA;GACA;GACA,GAAI,cAAc,KAAA,IAAY,EAAE,UAAU,IAAI,CAAC;EACnD,CAAC,CAAC;CACN;;;;;;;;;;CAeA,cAAc,UAAkB,SAAiB,OAAsC;EACnF,IAAI,CAAC,KAAK,SAAS,IAAI,OAAO,GAC1B,KAAK,SAAS,IAAI,yBAAS,IAAI,IAAI,CAAC;EAGxC,MAAM,kBAAkB,KAAK,SAAS,IAAI,OAAO;EACjD,MAAM,WAAW,gBAAgB,IAAI,QAAQ;EAC7C,MAAM,UAAU,CAAC,YAAY,KAAK,UAAU,SAAS,KAAK,MAAM,KAAK,UAAU,KAAK;EACpF,gBAAgB,IAAI,UAAU;GAAE;GACxC,UAAU,KAAK,IAAI;EAAE,CAAC;EAId,KAAU,sBAAsB,KAAK,cAAe,MAAM,SAAS,UAAU,KAAK,GAAG,OAAO;EAG5F,KAAK,oBAAoB,SAAS,GAAG,WAAW,MAAM,GAAG,CAAC,CAAC;EAC3D,IAAI,SACA,KAAK,oBAAoB,SAAS,GAAG,WAAW,MAAM,GAAG,CAAC,CAAC;EAI/D,KAAK,sBAAsB;CAC/B;;;;;;;;CASA,eAAe,UAAkB,SAAiB,SAAyC;EACvF,MAAM,kBAAkB,KAAK,SAAS,IAAI,OAAO;EACjD,IAAI,CAAC,iBAAiB;EAEtB,MAAM,QAAQ,gBAAgB,IAAI,QAAQ;EAC1C,IAAI,OAAO;GACP,gBAAgB,OAAO,QAAQ;GAC/B,KAAK,oBAAoB,SAAS,CAAC,GAAG,GAAG,WAAW,MAAM,MAAM,CAAC;GACjE,KAAK,oBAAoB,SAAS,CAAC,GAAG,GAAG,WAAW,MAAM,MAAM,CAAC;GACjE,IAAI,CAAC,SAAS,WACV,KAAU,sBAAsB,KAAK,cAAe,OAAO,SAAS,QAAQ,GAAG,QAAQ;EAE/F;EAEA,IAAI,gBAAgB,SAAS,GACzB,KAAK,SAAS,OAAO,OAAO;CAEpC;;;;;;;;;;CAWA,kBAAkB,UAAkB,SAAuB;EACvD,IAAI,CAAC,KAAK,eAAe;GACrB,KAAK,yBAAyB,UAAU,SAAS,KAAK,eAAe,OAAO,CAAC;GAC7E;EACJ;EAEA,KAAU,cAAc,OAAO,OAAO,CAAC,CAClC,MAAM,cAAc;GACjB,KAAK,yBAAyB,UAAU,SAAS,SAAS;EAC9D,CAAC,CAAC,CACD,OAAO,UAAU;GAGd,OAAO,KAAK,uDAAuD,QAAQ,mDAAmD,EAAE,MAAM,CAAC;GACvI,KAAK,yBAAyB,UAAU,SAAS,KAAK,eAAe,OAAO,CAAC;EACjF,CAAC;CACT;;CAGA,eAAuB,SAA0D;EAC7E,MAAM,kBAAkB,KAAK,SAAS,IAAI,OAAO;EACjD,MAAM,YAAqD,CAAC;EAC5D,IAAI,iBACA,KAAK,MAAM,CAAC,IAAI,EAAE,YAAY,iBAC1B,UAAU,MAAM;EAGxB,OAAO;CACX;CAEA,yBACI,UACA,SACA,WACI;EACJ,KAAK,QAAQ,UAAU,KAAK,UAAU;GAClC,MAAM;GACN;GACA;EACJ,CAAC,CAAC;CACN;;CAGA,oBACI,SACA,OACA,QACI;EACJ,MAAM,UAAU,KAAK,SAAS,IAAI,OAAO;EACzC,IAAI,CAAC,SAAS;EAEd,MAAM,UAAU,KAAK,UAAU;GAC3B,MAAM;GACN;GACA;GACA;EACJ,CAAC;EAED,KAAK,MAAM,YAAY,SACnB,KAAK,QAAQ,UAAU,OAAO;CAEtC;;CAGA,oBACI,SACA,OACA,QACI;EACJ,IAAI,KAAK,IAAI,SAAS,UAAU;EAChC,KAAU,aAAa;GAAE,MAAM;GAAiB,KAAK,KAAK;GAAY;GAAS;GAAO;EAAO,CAAC;CAClG;;CAGA,MAAc,gBAAgB,IAAyB,OAA8B;EACjF,IAAI,CAAC,KAAK,eAAe;EACzB,IAAI;GACA,MAAM,GAAG;EACb,SAAS,OAAO;GACZ,OAAO,KAAK,+BAA+B,MAAM,UAAU,EAAE,MAAM,CAAC;EACxE;CACJ;;CAGA,wBAAsC;EAClC,IAAI,KAAK,kBAAkB;EAC3B,KAAK,mBAAmB,kBAAkB;GACtC,MAAM,MAAM,KAAK,IAAI;GACrB,KAAK,MAAM,CAAC,SAAS,oBAAoB,KAAK,UAC1C,KAAK,MAAM,CAAC,UAAU,UAAU,iBAC5B,IAAI,MAAM,MAAM,WAAW,gBAAgB,qBACvC,KAAK,eAAe,UAAU,OAAO;GAKjD,IAAI,KAAK,SAAS,SAAS,KAAK,KAAK,kBAAkB;IACnD,cAAc,KAAK,gBAAgB;IACnC,KAAK,mBAAmB,KAAA;GAC5B;EACJ,GAAG,GAAK;CACZ;;;;;;;;;;;CAYA,sBAAoC;EAChC,IAAI,KAAK,yBAAyB,CAAC,KAAK,eAAe;EAEvD,KAAK,wBAAwB,kBACnB,KAAK,KAAK,mBAAmB,GACnC,gBAAgB,0BACpB;EAGA,MAAM,KAAK,qBAAqB;CACpC;;CAGA,MAAc,qBAAoC;EAC9C,IAAI,CAAC,KAAK,eAAe;EACzB,IAAI;GACA,MAAM,UAAU,MAAM,KAAK,cAAc,WAAW,gBAAgB,mBAAmB;GACvF,KAAK,MAAM,OAAO,SAAS;IACvB,KAAK,SAAS,uCAAuC,IAAI,SAAS,OAAO,IAAI,QAAQ,EAAE;IACvF,KAAK,oBAAoB,IAAI,SAAS,CAAC,GAAG,GAAG,IAAI,WAAW,IAAI,MAAM,CAAC;IACvE,KAAK,oBAAoB,IAAI,SAAS,CAAC,GAAG,GAAG,IAAI,WAAW,IAAI,MAAM,CAAC;GAC3E;EACJ,SAAS,OAAO;GACZ,OAAO,KAAK,2CAA2C,EAAE,MAAM,CAAC;EACpE;CACJ;;;;;;;;;;;;CAiBA,MAAM,UAAyB;EAE3B,KAAK,MAAM,SAAS,KAAK,OAAO,OAAO,GAAG;GACtC,IAAI,MAAM,OAAO,aAAa,MAAM,KAAK;GACzC,MAAM,QAAQ,KAAA;EAClB;EAGA,KAAK,eAAe,MAAM;EAC1B,KAAK,sBAAsB,MAAM;EACjC,KAAK,sBAAsB,MAAM;EACjC,KAAK,OAAO,MAAM;EAClB,KAAK,uBAAuB,MAAM;EAClC,KAAK,sBAAsB,MAAM;EACjC,KAAK,gBAAgB,MAAM;EAG3B,KAAK,SAAS,MAAM;EACpB,KAAK,SAAS,MAAM;EAGpB,MAAM,QAAQ,WAAW,CAAC,GAAG,KAAK,kBAAkB,OAAO,GAAG,GAAG,KAAK,qBAAqB,OAAO,CAAC,CAAC;EACpG,KAAK,kBAAkB,MAAM;EAC7B,KAAK,qBAAqB,MAAM;EAChC,KAAK,gBAAgB,MAAM;EAC3B,IAAI,KAAK,kBAAkB;GACvB,cAAc,KAAK,gBAAgB;GACnC,KAAK,mBAAmB,KAAA;EAC5B;EACA,IAAI,KAAK,uBAAuB;GAC5B,cAAc,KAAK,qBAAqB;GACxC,KAAK,wBAAwB,KAAA;EACjC;EACA,KAAK,yBAAyB,MAAM;EAKpC,IAAI,KAAK,eAAe;GACpB,IAAI;IACA,MAAM,KAAK,cAAc,eAAe;GAC5C,SAAS,OAAO;IACZ,OAAO,KAAK,yEAAyE,EAAE,MAAM,CAAC;GAClG;GACA,KAAK,gBAAgB,KAAA;EACzB;EAGA,MAAM,KAAK,cAAc;EACzB,MAAM,KAAK,QAAQ;EACnB,MAAM,KAAK,IAAI,KAAK,CAAC,CAAC,OAAO,UACzB,OAAO,KAAK,wDAAwD,EAAE,MAAM,CAAC,CAAC;EAClF,KAAK,MAAM,IAAI,iBAAiB;EAMhC,MAAM,UAAU,CAAC,GAAG,KAAK,QAAQ,OAAO,CAAC;EACzC,KAAK,QAAQ,MAAM;EACnB,gBAAgB,SAAS,wBAAwB;EAEjD,KAAK,SAAS,mEAAmE;CACrF;;;;;;;;CAaA,cAA8B;EAC1B,OAAO,KAAK,aAAa,KAAK,aAAa,cAAc;CAC7D;;CAGA,SAAmC;EAC/B,MAAM,YAAsC,CAAC;EAC7C,IAAI,KAAK,aAAa,KAAK,aAAa;GACpC,MAAM,EAAE,WAAW,cAAc,KAAK,YAAY,OAAO;GACzD,UAAU,KAAK;IAAE,MAAM;IAAO;IAAW,GAAI,cAAc,KAAA,IAAY,EAAE,UAAU,IAAI,CAAC;GAAG,CAAC;EAChG;EACA,IAAI,KAAK,uBAAuB;GAC5B,MAAM,EAAE,WAAW,cAAc,KAAK,sBAAsB,OAAO;GACnE,UAAU,KAAK;IAAE,MAAM;IAAkB;IAAW,GAAI,cAAc,KAAA,IAAY,EAAE,UAAU,IAAI,CAAC;GAAG,CAAC;EAC3G;EACA,MAAM,MAAM,KAAK,eAAe,qBAAqB,KAAK,IAAI,OAAO,IAAI,KAAA;EACzE,IAAI,KACA,UAAU,KAAK;GAAE,MAAM;GAAe,WAAW,IAAI;GAAW,GAAI,IAAI,cAAc,KAAA,IAAY,EAAE,WAAW,IAAI,UAAU,IAAI,CAAC;EAAG,CAAC;EAE1I,OAAO;CACX;;;;;;;;;;;;;;;;;;CAmBA,MAAM,UAAU,kBAAyC;EACrD,IAAI,KAAK,WAAW;GAChB,OAAO,KAAK,gEAAgE;GAC5E;EACJ;EACA,KAAK,cAAc,KAAK,iBAAiB;EACzC,KAAK,kBAAkB,qBAAqB,KAAK,QAAQ;EACzD,KAAK,cAAc,IAAI,YACnB,mBACC,UAAU,KAAK,eAAe,KAAK,SAC9B,KAAK,oBAAoB,CACnC;EACA,IAAI;GAIA,MAAM,KAAK,YAAY,MAAM;EACjC,SAAS,KAAK;GACV,MAAM,KAAK,YAAY,KAAK,CAAC,CAAC,YAAY,CAAoB,CAAC;GAC/D,KAAK,cAAc,KAAA;GACnB,KAAK,cAAc,KAAA;GACnB,KAAK,kBAAkB,KAAA;GACvB,MAAM;EACV;EACA,KAAK,YAAY;EAGjB,OAAO,MACH,gHACI,KAAK,YAAY,KAAK,uBAC9B;CACJ;;;;;;;;;;;;CAaA,sBAAoC;EAChC,KAAK,MAAM,SAAS,KAAK,OAAO,OAAO,GACnC,KAAK,qBAAqB,KAAK;CAEvC;;CAGA,MAAM,UAAyB;EAC3B,KAAK,YAAY;EACjB,IAAI,KAAK,aAAa;GAClB,MAAM,KAAK,YAAY,KAAK;GAC5B,KAAK,cAAc,KAAA;EACvB;EACA,KAAK,cAAc,KAAA;EACnB,KAAK,kBAAkB,KAAA;EACvB,KAAK,eAAe,MAAM;CAC9B;;;;;;;CAQA,mBAA0D;EACtD,MAAM,sBAAM,IAAI,IAA8B;EAC9C,KAAK,MAAM,cAAc,KAAK,SAAS,eAAe,GAAG;GACrD,MAAM,QAAQ,eAAa,UAAU;GACrC,IAAI,CAAC,OAAO;GACZ,MAAM,SAAU,WAAmC,UAAU;GAC7D,IAAI,IAAI,GAAG,OAAO,GAAG,SAAS,UAAU;GAExC,IAAI,CAAC,IAAI,IAAI,KAAK,GAAG,IAAI,IAAI,OAAO,UAAU;EAClD;EACA,OAAO;CACX;CAEA,0BAAkC,QAAgB,OAA6C;EAC3F,IAAI,CAAC,KAAK,aAAa,OAAO,KAAA;EAC9B,OAAO,KAAK,YAAY,IAAI,GAAG,OAAO,GAAG,OAAO,KAAK,KAAK,YAAY,IAAI,KAAK;CACnF;;;;;;;;;;;CAYA,MAAc,eAAe,OAAsC;EAC/D,MAAM,aAAa,KAAK,0BAA0B,MAAM,QAAQ,MAAM,KAAK;EAC3E,IAAI,CAAC,YAAY;GAGb,IAAI,MAAM,KAAK,uBAAuB,KAAK,GAAG;GAG9C,KAAK,SAAS,8CAA8C,MAAM,OAAO,GAAG,MAAM,OAAO;GACzF;EACJ;EAEA,MAAM,OAAO,WAAW;EACxB,MAAM,aAAc,WAAuC;EAC3D,MAAM,KAAK,KAAK,oBAAoB,YAAY,MAAM,GAAG;EAIzD,MAAM,MAAM,MAAM,OAAO,WAAW,OAAO,EAAE,qBAAqB,KAAK;EAEvE,MAAM,KAAK,aAAa,MAAM,IAAI,KAAK,YAA4B,OAAoB,KAAK;CAChG;;;;;;;;;;;;;;;;;CAkBA,MAAc,uBAAuB,OAAyC;EAC1E,MAAM,QAAQ,KAAK,iBAAiB,IAAI,GAAG,MAAM,OAAO,GAAG,MAAM,OAAO,KACjE,KAAK,iBAAiB,IAAI,MAAM,KAAK;EAC5C,IAAI,CAAC,OAAO,QAAQ,OAAO;EAE3B,KAAK,MAAM,QAAQ,OAAO;GACtB,MAAM,WAAW,MAAM,MAAM,KAAK;GAClC,MAAM,WAAW,MAAM,MAAM,KAAK;GAClC,IAAI,aAAa,KAAA,KAAa,aAAa,QAAQ,aAAa,KAAA,KAAa,aAAa,MAAM;IAC5F,KAAK,SACD,4BAA4B,MAAM,MAAM,eAAe,KAAK,aAAa,KAAK,KAAK,aAAa,cACpG;IACA;GACJ;GAEA,MAAM,OAAO,GAAG,KAAK,iBAAiB,KAAK,GAAG,OAAO,QAAQ,EAAE,GAAG,KAAK;GAGvE,MAAM,MAAM,MAAM,OAAO,WAAW,OAAO,EAAE,qBAAqB,KAAK;GAEvE,MAAM,KAAK,aACP,MACA,OAAO,QAAQ,GACf,KACC,KAAK,iBAA6C,YACnC,OACH,KACjB;EACJ;EAEA,OAAO;CACX;;CAGA,oBAA4B,YAA8B,KAAsC;EAG5F,OAAO,iBAAiB,KAAK,kBAAkB,YAAY,GAAG,GAAG,YAAY,KAAK,QAAQ,KAAK;CACnG;;;;;;;;;;CAWA,kBAA0B,YAA8B,KAAuD;EAC3G,MAAM,QAAiC,EAAE,GAAG,IAAI;EAChD,KAAK,MAAM,EAAE,WAAW,gBAAgB,qBAAqB,YAAY,KAAK,QAAQ,GAAG;GACrF,IAAI,aAAa,KAAK;GACtB,IAAI,cAAc,KAAK,MAAM,aAAa,IAAI;EAClD;EACA,OAAO;CACX;CAIA,SAAiB,MAAc,IAAY,YAA6B;EACpE,OAAO,GAAG,cAAc,GAAG,IAAI,KAAK,IAAI;CAC5C;;CAGA,YAAoB,KAAmB;EACnC,MAAM,MAAM,KAAK,IAAI;EACrB,KAAK,eAAe,IAAI,KAAK,MAAM,gBAAgB,mBAAmB;EAEtE,IAAI,KAAK,eAAe,OAAO;QACtB,MAAM,CAAC,GAAG,WAAW,KAAK,gBAC3B,IAAI,UAAU,KAAK,KAAK,eAAe,OAAO,CAAC;EAAA;CAG3D;;CAGA,eAAuB,KAAsB;EACzC,MAAM,SAAS,KAAK,eAAe,IAAI,GAAG;EAC1C,IAAI,WAAW,KAAA,GAAW,OAAO;EACjC,KAAK,eAAe,OAAO,GAAG;EAC9B,OAAO,SAAS,KAAK,IAAI;CAC7B;;;;;;;;;;;;;;;;;;CAuBA,MAAM,eAAe,kBAAyC;EAC1D,IAAI,KAAK,cAAc;GACnB,OAAO,KAAK,6EAA6E;GACzF;EACJ;EAEA,KAAK,eAAe;EACpB,KAAK,wBAAwB,IAAI,iBAAiB;GAC9C;GACA,SAAS;GACT,UAAU;GACV,YAAY,YAAY,KAAK,gCAAgC,OAAO;GACpE,mBAAmB,KAAK,oBAAoB;GAC5C,qBAAqB;EACzB,CAAC;EACD,MAAM,KAAK,sBAAsB,MAAM;EACvC,OAAO,KAAK,qEAAqE,KAAK,WAAW,EAAE;CACvG;;;;CAKA,MAAM,gBAA+B;EACjC,KAAK,eAAe;EACpB,MAAM,WAAW,KAAK;EACtB,IAAI,CAAC,UAAU;EACf,KAAK,wBAAwB,KAAA;EAC7B,MAAM,SAAS,KAAK;EACpB,OAAO,KAAK,wDAAwD;CACxE;;;;;CAMA,MAAc,gBAAgB,MAAc,IAAY,YAAoC;EACxF,MAAM,UAAU,KAAK,UAAU;GAC3B,KAAK,KAAK;GACV,GAAG;GACH,KAAK;GACL,IAAI,cAAc;EACtB,CAAC;EACD,MAAM,KAAK,GAAG,QAAQ,GAAU,oBAAoB,kBAAkB,IAAI,QAAQ,EAAE;CACxF;;CAGA,MAAc,gCAAgC,SAAgC;EAC1E,IAAI;GACA,MAAM,EAAE,KAAK,GAAG,KAAK,OAAO,KAAK,MAAM,OAAO;GAQ9C,IAAI,QAAQ,KAAK,YAAY;GAK7B,KAAK,sBAAsB;GAE3B,KAAK,SAAS,mEAAmE,EAAE,OAAO,IAAI,SAAS,KAAK;GAK5G,IAAI,eAA+C;GACnD,IAAI;IACA,IAAI,KAAK,QAAQ;KACb,MAAM,aAAa,KAAK,SAAS,oBAAoB,CAAC;KAMtD,eAAe,MALO,KAAK,OAAO,SAAS;MACvC,MAAM;MACN,IAAI;MACQ;KAChB,CAAC,KACyB;IAC9B,OAII,eAAe,MAHO,KAAK,YAAY,SACnC,GAAG,KAAK,MAAM,KAAA,CAClB,KAC0B;GAElC,SAAS,UAAU;IAEf,KAAK,SAAS,8CAA8C,IAAI,QAAQ,EAAE,yBAAyB,QAAQ;GAC/G;GAGA,MAAM,KAAK,aAAa,GAAG,KAAK,cAAc,MAAM,KAAA,GAAW,KAAK;EACxE,SAAS,KAAK;GACV,OAAO,MAAM,oEAAoE,EAAE,OAAO,IAAI,CAAC;EACnG;CACJ;AACJ;;;;;AAMA,IAAa,2BAA2B;;;;;;;;;;;;;;;;;;;;;;;AC7wGxC,SAAgB,kBAAkB,KAAqB;CACnD,IAAI,MAAM;CACV,IAAI,IAAI;CACR,OAAO,IAAI,IAAI,QAAQ;EACnB,MAAM,OAAO,IAAI;EACjB,MAAM,OAAO,IAAI,IAAI;EAGrB,IAAI,SAAS,OAAO,SAAS,KAAK;GAC9B,MAAM,MAAM,IAAI,QAAQ,MAAM,CAAC;GAC/B,MAAM,OAAO,QAAQ,KAAK,IAAI,SAAS;GACvC,OAAO,IAAI,MAAM,GAAG,IAAI;GACxB,IAAI;GACJ;EACJ;EAGA,IAAI,SAAS,OAAO,SAAS,KAAK;GAC9B,IAAI,QAAQ;GACZ,IAAI,IAAI,IAAI;GACZ,OAAO,IAAI,IAAI,UAAU,QAAQ,GAC7B,IAAI,IAAI,OAAO,OAAO,IAAI,IAAI,OAAO,KAAK;IAAE;IAAS,KAAK;GAAG,OAAO,IAAI,IAAI,OAAO,OAAO,IAAI,IAAI,OAAO,KAAK;IAAE;IAAS,KAAK;GAAG,OAAO;GAE5I,OAAO,IAAI,MAAM,GAAG,CAAC;GACrB,IAAI;GACJ;EACJ;EAGA,IAAI,SAAS,MAAM;GACf,MAAM,MAAM,aAAa,KAAK,IAAI,GAAG,MAAM,KAAK;GAChD,OAAO,IAAI,MAAM,GAAG,GAAG;GACvB,IAAI;GACJ;EACJ;EAIA,IAAI,SAAS,KAAK;GACd,MAAM,SAAS,0CAA0C,KAAK,GAAG,CAAC,GAAG,MAAM;GAC3E,OAAO;GACP,IAAI,aAAa,KAAK,IAAI,GAAG,KAAK,OAAO,YAAY,MAAM,GAAG;GAC9D;EACJ;EAKA,IAAI,SAAS,KAAK;GACd,MAAM,MAAM,iCAAiC,KAAK,IAAI,MAAM,CAAC,CAAC;GAC9D,MAAM,uBAAuB,gBAAgB,KAAK,GAAG;GACrD,IAAI,OAAO,CAAC,sBAAsB;IAC9B,MAAM,YAAY,IAAI;IACtB,MAAM,YAAY,IAAI,UAAU;IAChC,MAAM,UAAU,IAAI,QAAQ,WAAW,SAAS;IAChD,MAAM,OAAO,YAAY,KAAK,IAAI,SAAS;IAC3C,MAAM,OAAO,IAAI,MAAM,WAAW,IAAI;IACtC,MAAM,aAAa,kBAAkB,KAAK,GAAG;IAC7C,OAAO,aAAa,aAAa,QAAQ,kBAAkB,IAAI,MAAM,YAAY,KAAK,KAAK;IAC3F,IAAI,YAAY,KAAK,IAAI,SAAS,UAAU,UAAU;IACtD;GACJ;EACJ;EAEA,OAAO;EACP;CACJ;CACA,OAAO;AACX;;;;;;AAOA,SAAS,aAAa,KAAa,MAAc,OAAe,kBAAmC;CAC/F,IAAI,IAAI;CACR,OAAO,IAAI,IAAI,QAAQ;EACnB,IAAI,oBAAoB,IAAI,OAAO,MAAM;GAAE,KAAK;GAAG;EAAU;EAC7D,IAAI,IAAI,OAAO,OAAO;GAClB,IAAI,IAAI,IAAI,OAAO,OAAO;IAAE,KAAK;IAAG;GAAU;GAC9C,OAAO,IAAI;EACf;EACA;CACJ;CACA,OAAO,IAAI;AACf;;;;;;;;;;;;;;;;;;;AC/DA,SAAgB,sBAAsB,UAAsC;CACxE,OAAQ;EAAC;EAAa;EAAiB;CAAqB,CAAC,CACxD,QAAQ,WAAW,EAAE,UAAU,aAAa,OAAO,QAAQ,IAAI,UAAU,MAAM,MAAM,UAAU;AACxG;AAEA,SAAgB,wBAAwB,UAAkF;CACtH,OAAO,sBAAsB,QAAQ,CAAC,CAAC,WAAW;AACtD;;AAGA,SAAS,kBAAkB,EAAE,MAAM,OAAO,GAAG,eAA+D;CACxG,OAAO;AACX;;;;;;AA6DA,IAAM,kBAAkB;;AAgCxB,IAAM,gBAAgB;;AAEtB,IAAM,oBAAoB;;;;;;;;;;;;;;;AAgB1B,IAAM,wBAAwB;;;;;;;;;;;AAY9B,IAAM,kCAAkC,KAAK;;;;;;;;;;AAW7C,IAAM,8BAA8B;;;;;;;AAQpC,IAAM,yBAAyB;;;;;;;AAQ/B,IAAa,2BAA2B;;;;;;;;AASxC,SAAS,YAAY,OAAmC;CACpD,MAAM,QAAQ,MAAM,MAAM,GAAG;CAC7B,IAAI,MAAM,WAAW,GAAG,OAAO,KAAA;CAC/B,IAAI;EACA,MAAM,SAAkB,KAAK,MAAM,OAAO,KAAK,MAAM,IAAI,WAAW,CAAC,CAAC,SAAS,MAAM,CAAC;EACtF,IAAI,OAAO,WAAW,YAAY,WAAW,QAAQ,EAAE,SAAS,SAAS,OAAO,KAAA;EAChF,OAAO,OAAO,OAAO,QAAQ,WAAW,OAAO,MAAM,MAAO,KAAA;CAChE,QAAQ;EACJ;CACJ;AACJ;;AAGA,SAAS,oBAAoB,MAA8B;CACvD,OAAO,KAAK,UAAU;EAAC,KAAK;EAAK,CAAC,GAAG,KAAK,KAAK,CAAC,CAAC,KAAK;EAAG,KAAK;EAAS,KAAK;EAAa,KAAK,UAAU;EAAM,KAAK,UAAU;EAAM,KAAK,UAAU;CAAI,CAAC;AAC3J;;AAGA,IAAM,wCAAwB,IAAI,IAAI;CAClC;CACA;CACA;CACA;CACA;CACA;CACA;AACJ,CAAC;;;;;;;;;;;;;;;AAgBD,IAAa,oCAAqD,IAAI,IAAwB;CAC1F,CAAC,eAAe,gBAAgB;CAChC,CAAC,iBAAiB,gBAAgB;CAClC,CAAC,iBAAiB,gBAAgB;CAClC,CAAC,mBAAmB,eAAe;CACnC,CAAC,eAAe,eAAe;CAC/B,CAAC,2BAA2B,eAAe;CAC3C,CAAC,yBAAyB,eAAe;CACzC,CAAC,wBAAwB,eAAe;CACxC,CAAC,0BAA0B,eAAe;CAC1C,CAAC,iBAAiB,eAAe;AACrC,CAAC;;;;;AAMD,IAAM,mCAAgD,IAAI,IAAI;CAC1D,CAAC,oBAAoB,WAAW;CAChC,CAAC,aAAa,WAAW;CACzB,CAAC,SAAS,WAAW;CACrB,CAAC,sBAAsB,WAAW;CAClC,CAAC,wBAAwB,WAAW;CACpC,CAAC,iBAAiB,WAAW;CAC7B,CAAC,QAAQ,YAAY;CACrB,CAAC,UAAU,aAAa;AAC5B,CAAC;;;;;;;;;AAUD,IAAa,+BAAe,IAAI,IAAI;CAChC;CACA;CACA;CACA;CACA;CACA;AACJ,CAAC;;;;AAKD,SAAS,oBAAoB,OAAwB;CACjD,IAAI,CAAC,OAAO,OAAO;CACnB,IAAI,iBAAiB,OAAO;EACxB,IAAI,WAAW,SAAS,MAAM,OAC1B,OAAO,oBAAoB,MAAM,KAAK;EAE1C,OAAO,MAAM;CACjB;CACA,IAAI,OAAO,UAAU,YAAY,aAAa,SAAS,OAAQ,MAA+B,YAAY,UACtG,OAAQ,MAA8B;CAE1C,OAAO,OAAO,KAAK;AACvB;;;;;;;;;;;;;AAcA,SAAS,mBAAmB,SAA6D;CACrF,IAAI,CAAC,SAAS,MACV,OAAO;EAAE,KAAK;EAAmB,OAAO,CAAC,MAAM;EAAG,aAAa;CAAM;CAEzE,OAAO;EACH,KAAK,QAAQ,KAAK;EAClB,OAAO,QAAQ,KAAK,SAAS,CAAC;EAC9B,aAAa,QAAQ,KAAK,gBAAgB;EAG1C,GAAI,QAAQ,KAAK,SAAS,EAAE,QAAQ,QAAQ,KAAK,OAAO,IAAI,CAAC;CACjE;AACJ;;AAGA,SAAS,cAAc,SAA8C;CACjE,IAAI,CAAC,SAAS,MAAM,OAAO,CAAC;CAC5B,OAAO,QAAQ,KAAK,UAAU,eAAe,QAAQ,KAAK,OAAO,eAAe,CAAC;AACrF;;;;;;AAOA,SAAS,gBAAgB,MAAc,SAAsC;CACzE,IAAI,OAAO,YAAY,YAAY,YAAY,MAAM,OAAO,KAAA;CAC5D,IAAI,OAAgB,UAAU,UAAU,QAAQ,OAAO,KAAA;CACvD,IAAI,SAAS,YAAY,SAAS,WAAW,OAAO,QAAQ,QAAQ,YAAY,QAAQ,QAAQ,QAAQ,UAAU,QAAQ,KACtH,OAAO,QAAQ,IAAI;CAEvB,IAAI,OAAO,SAAS,YAAY,SAAS,MAAM,aAAa,IAAI,GAAG,OAAO,KAAA;CAC1E,OAAO;AACX;;AAGA,SAAS,WAAW,MAAuB;CACvC,IAAI,MAAM,QAAQ,IAAI,GAAG,OAAO,KAAK,QAAQ,OAAO,UAAU,QAAQ,MAAM,QAAQ,CAAC;CACrF,OAAO,KAAK;AAChB;;;;;;AAOA,SAAS,iBAAiB,SAAwE;CAC9F,OAAO;EACH,SAAS,SAAS;GACd,MAAM,QAAQ,SAAS,QAAQ;GAC/B,OAAO,MAAM,QAAQ,KAAK,IAAI,MAAM,KAAK,IAAI,IAAI;EACrD;EACA,eAAe,SAAS,QAAQ;CACpC;AACJ;;;;;;;;AASA,SAAgB,wBACZ,QACA,iBACA,QACA,YACA,aACA,SACF;CAGE,MAAM,iCAAiB,IAAI,IAA2B;CAEtD,MAAM,eAAA,QAAA,IAAA,aAAwC;;;;;;;;;;;;;;;;CAgB9C,MAAM,WAAW,SAAiB,GAAG,WAAsB;EACvD,IAAI,cAAc;EAClB,IAAI,OAAO,WAAW,GAAG,OAAO,MAAM,OAAO;OACxC,OAAO,MAAM,SAAS,EAAE,SAAS,OAAO,WAAW,IAAI,OAAO,KAAK,OAAO,CAAC;CACpF;CAIA,MAAM,aAAa,SAAS,cAAc;CAC1C,MAAM,MAAM,IAAI,gBAAgB;EAAE;EAAQ,YAAY,aAAa,IAAI,aAAa;CAAE,CAAC;CAMvF,IAAI,GAAG,UAAU,QAA+B;EAC5C,IAAI,IAAI,SAAS,cAEb;EAEJ,OAAO,MAAM,8BAA8B,EAAE,OAAO,IAAI,CAAC;CAC7D,CAAC;CAOD,MAAM,cAAc,CAAC,CAAC,eAAe,mBAAmB,UAAmB;CAE3E,IAAI,eAAe,CAAC,eAAe,CAAC,YAAY,aAAa,CAAC,YAAY,YACtE,OAAO,KACH,oMAGJ;;;;;;CAQJ,eAAe,cAAc,OAA4H;EACrJ,IAAI,MAAM,WAAW,KAAK,GAAG;GAGzB,MAAM,WAAW,SAAS,gBAAgB,MAAM,QAAQ,cAAc,KAAK,IAAI,KAAA;GAC/E,IAAI,CAAC,YAAY,EAAE,SAAS,WACxB,OAAO;IAAE,SAAS,UAAU,WAAW;IAA2C,QAAQ;GAAK;GAEnG,OAAO;IACH,YAAY;IACZ,MAAM;KACF,KAAK,SAAS;KACd,OAAO,SAAS;KAChB,SAAS,aAAa,SAAS,KAAK;KACpC,aAAa;KACb,QAAQ,SAAS;KACjB,GAAI,SAAS,SAAS,EAAE,QAAQ;MAAE,IAAI,SAAS,OAAO;MAAI,YAAY,SAAS,OAAO;KAAW,EAAE,IAAI,CAAC;IAC5G;GACJ;EACJ;EAEA,IAAI,aAAa;GAKb,MAAM,cAAc,YAAY,cAC1B,MAAM,YAAY,YAAY,KAAK,IACnC,MAAM,YAAY,cAAc,IAAI,QAAQ,6BAA6B,EACvE,SAAS,EAAE,eAAe,UAAU,QAAQ,EAChD,CAAC,CAAC;GACN,IAAI,CAAC,aAAa,OAAO,EAAE,SAAS,2BAA2B;GAC/D,OAAO;IAGH,YAAY,YAAY,QAAQ,kBAAkB,gBAAgB;IAClE,MAAM;KACF,KAAK,YAAY;KACjB,OAAO,YAAY;KACnB,SAAS,YAAY;KAKrB,aAAa,YAAY,gBAAgB;KAIzC,GAAI,YAAY,SAAS,EAAE,QAAQ,YAAY,OAAO,IAAI,CAAC;IAC/D;GACJ;EACJ;EAEA,IAAI,YAAY,cAAc,YAAY,OAAO,WAAW,UAAU,GAIlE,OAAO;GAAE,YAAY;GAAe,MAAM;IAAE,KAAK;IAAiB,OAAO,CAAC,OAAO;IAAG,SAAS;IAAM,aAAa;GAAM;EAAE;EAK5H,MAAM,aAAa,MAAM,qBAAqB,KAAK;EACnD,IAAI,CAAC,YAAY,OAAO,EAAE,SAAS,2BAA2B;EAC9D,OAAO;GACH,YAAY;GACZ,MAAM;IACF,KAAK,WAAW;IAChB,OAAO,WAAW,SAAS,CAAC;IAC5B,SAAS,aAAa,WAAW,SAAS,CAAC,CAAC;IAC5C,aAAa,WAAW,gBAAgB;IACxC,QAAQ,WAAW;GACvB;EACJ;CACJ;;;;;;;;;CAUA,eAAe,gBACX,OACA,aACA,OACqF;EACrF,MAAM,SAAS,MAAM,cAAc,KAAK;EACxC,IAAI,EAAE,UAAU,WAAW,gBAAgB,KAAA,GAAW,OAAO;EAE7D,MAAM,WAAW,MAAM,oBAAoB;GACvC,cAAc;GACd,YAAY,OAAO;GACnB,QAAQ,OAAO;GACf,aAAa,aAAa,cAAc,YAAY,YAAY,KAAK,WAAW,IAAI,KAAA;GACpF;EACJ,CAAC;EACD,IAAI,aAAa,UAAU,OAAO;GAAE,SAAS,SAAS,QAAQ;GAAS,MAAM,SAAS,QAAQ;EAAK;EACnG,MAAM,SAAS,SAAS;EACxB,OAAO,EACH,MAAM;GACF,KAAK,OAAO;GACZ,OAAO,OAAO;GACd,SAAS,aAAa,OAAO,KAAK;GAClC,aAAa,OAAO,gBAAgB;GACpC,GAAI,OAAO,SAAS,EAAE,QAAQ,OAAO,OAAO,IAAI,CAAC;GACjD,cAAc,SAAS;EAC3B,EACJ;CACJ;;CAGA,SAAS,eAAe,UAAkB,OAAqB;EAC3D,MAAM,UAAU,eAAe,IAAI,QAAQ;EAC3C,IAAI,CAAC,SAAS;EACd,IAAI,QAAQ,aAAa,aAAa,QAAQ,WAAW;EACzD,QAAQ,cAAc,KAAA;EACtB,MAAM,YAAY,YAAY,KAAK;EACnC,IAAI,cAAc,KAAA,GAAW;EAG7B,MAAM,QAAQ,KAAK,IAAI,GAAG,YAAY,KAAK,IAAI,CAAC;EAChD,IAAI,QAAQ,YAAe;EAC3B,QAAQ,cAAc,iBAAiB;GAEnC,IADgB,eAAe,IAAI,QAC/B,CAAA,EAAS,eAAe,OACxB,WAAW,UAAU,iBAAiB,uDAAuD;EAErG,GAAG,KAAK;EACR,QAAQ,YAAY,QAAQ;CAChC;;;;;;;;CASA,SAAS,WAAW,UAAkB,MAAc,SAAuB;EACvE,MAAM,UAAU,eAAe,IAAI,QAAQ;EAC3C,IAAI,CAAC,SAAS;EACd,IAAI,QAAQ,aAAa,aAAa,QAAQ,WAAW;EACzD,QAAQ,cAAc,KAAA;EACtB,QAAQ,OAAO,KAAA;EACf,QAAQ,aAAa,KAAA;EACrB,QAAQ,gBAAgB,CAAC;EACzB,QAAQ,QAAQ,SAAS,kBAAkB,MAAM;EACjD,IAAI;GACA,QAAQ,GAAG,KAAK,KAAK,UAAU;IAAE,MAAM;IAAc,SAAS,EAAE,OAAO;KAAE;KAAM;IAAQ,EAAE;GAAE,CAAC,CAAC;EACjG,QAAQ,CAER;EACA,QAAQ,GAAG,MAAM,0BAA0B,QAAQ,MAAM,GAAG,GAAG,CAAC;CACpE;;;;;;;;;;;;;;;;;;;;CAqBA,eAAe,gBAAgB,UAA6D;EACxF,MAAM,UAAU,eAAe,IAAI,QAAQ;EAC3C,MAAM,aAAa,SAAS;EAC5B,IAAI,CAAC,SAAS,QAAQ,CAAC,YAAY,OAAO;EAC1C,IAAI;EACJ,IAAI;GACA,UAAU,MAAM,gBAAgB,YAAY,QAAQ,WAAW;EACnE,QAAQ;GACJ,OAAO;EACX;EAEA,IAAI,QAAQ,eAAe,cAAc,CAAC,QAAQ,MAAM,OAAO,QAAQ,OAAO,SAAS;EACvF,QAAQ,YAAY,KAAK,IAAI;EAC7B,IAAI,EAAE,UAAU,UAAU;GAItB,IAAI,QAAQ,MAAM,WAAW,UAAU,QAAQ,MAAM,QAAQ,OAAO;QAC/D,WAAW,UAAU,iBAAiB,iGAAiG;GAC5I,OAAO;EACX;EACA,IAAI,oBAAoB,QAAQ,IAAI,MAAM,oBAAoB,QAAQ,IAAI,GAAG;GACzE,QAAQ,OAAO,QAAQ;GACvB,MAAM,gBAAgB,cAAc,UAAU,mBAAmB,OAAO,CAAC;EAC7E;EACA,OAAO;CACX;CAIA,MAAM,oBAAoB,KAAK,IAAI,KAAK,SAAS,6BAA6B,2BAA2B;CACzG,MAAM,gBAAgB,kBAAkB;EACpC,MAAM,MAAM,KAAK,IAAI;EACrB,KAAK,MAAM,CAAC,IAAI,YAAY,gBACxB,IAAI,QAAQ,QAAQ,QAAQ,cAAc,MAAM,QAAQ,aAAa,oBAAoB,GACrF,gBAAqB,EAAE,CAAC,CAAC,YAAY,KAAA,CAAS;CAG1D,GAAG,iBAAiB;CAGpB,cAAc,QAAQ;CACtB,MAAM,kBAAkB,cAAc,aAAa;CACnD,IAAI,GAAG,SAAS,SAAS;CACzB,IAAI,OAAO,OAAO,OAAO,YAAY,OAAO,GAAG,SAAS,SAAS;CAEjE,IAAI,GAAG,eAAe,IAAI,YAA8B;EACpD,MAAM,WAAW,UAAU,KAAK,IAAI,EAAE,GAAG,KAAK,OAAO,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,UAAU,GAAG,CAAC;EAClF,MAAM,SAAS,iBAAiB,OAAO;EACvC,QAAQ,+BAA+B,UAAU;EAGjD,eAAe,IAAI,UAAU;GAAE;GACvC,eAAe,CAAC;GAChB,WAAW,KAAK,IAAI;GACpB,cAAc;GACd,oBAAoB,KAAK,IAAI;GAC7B,qBAAqB;GACrB,oBAAoB,KAAK,IAAI;EAAE,CAAC;EACxB,gBAAgB,UAAU,UAAU,EAAE;EAEtC,GAAG,GAAG,eAAe;GACjB,QAAQ,kCAAkC,UAAU;GACpD,MAAM,UAAU,eAAe,IAAI,QAAQ;GAC3C,IAAI,SAAS,aAAa,aAAa,QAAQ,WAAW;GAC1D,eAAe,OAAO,QAAQ;EAClC,CAAC;EAMD,GAAG,GAAG,UAAU,UAAiB;GAC7B,QAAQ,oBAAoB,SAAS,UAAU,MAAM,SAAS;EAClE,CAAC;EAGD,GAAG,GAAG,WAAW,OAAO,YAAqB;GAGzC,IAAI,sBAAsB,IAAI,QAAQ,GAAG;GACzC,IAAI;GACJ,IAAI;IACA,IAAI,CAAC,eAAe,IAAI,QAAQ,CAAC,EAAE,iBAAiB,WAAW,OAAO,IAAI,iCAAiC;KACvG,GAAG,MAAM,MAAM,2DAA2D;KAC1E;IACJ;IACA,MAAM,EACF,MACA,SACA,WAAW,UACX,KAAK,MAAM,QAAQ,SAAS,CAAC;IACjC,YAAY;IAEZ,QAAQ,QAAQ,SAAS,KAAK,QAAQ,YAAY,IAAI,UAAU,KAAK,EAAE;IASvE,MAAM,aAAa,SAAiC,MAAc,QAAgB;KAC9E,MAAM,UAAU,sBAAsB,IAAI,IAAI,IACvC,SAA+C,UAChD,KAAA;KACN,GAAG,KAAK,KAAK,UAAU;MACnB,MAAM;MACN;MACA,GAAI,OAAO,YAAY,YAAY,EAAE,QAAQ;MAC7C,SAAS;OACL,OAAO;QAAE,SAAS;QAAK;OAAK;OAC5B,GAAI,OAAO,YAAY,YAAY,EAAE,QAAQ;MACjD;KACJ,CAAC,CAAC;IACN;IAUA;KACI,MAAM,UAAU,eAAe,IAAI,QAAQ;KAC3C,IAAI,SAAS;MACT,MAAM,MAAM,KAAK,IAAI;MAErB,IADuB,sBAAsB,IAAI,IAC7C,GAAgB;OAChB,IAAI,MAAM,QAAQ,qBAAqB,mBAAmB;QACtD,QAAQ,sBAAsB;QAC9B,QAAQ,qBAAqB;OACjC;OACA,QAAQ;OACR,IAAI,QAAQ,sBAAsB,uBAAuB;QACrD,UAAU,SAAS,gBAAgB,8CAA8C;QACjF;OACJ;MACJ,OAAO;OACH,IAAI,MAAM,QAAQ,qBAAqB,mBAAmB;QACtD,QAAQ,eAAe;QACvB,QAAQ,qBAAqB;OACjC;OACA,QAAQ;OACR,IAAI,QAAQ,eAAe,eAAe;QACtC,UAAU,SAAS,gBAAgB,sCAAsC;QACzE;OACJ;MACJ;KACJ;IACJ;IAEA,IAAI,SAAS,gBAAgB;KACzB,MAAM,EAAE,OAAO,gBAAgB,WAAW,CAAC;KAC3C,IAAI,CAAC,OAAO;MACR,UAAU,cAAc,iBAAiB,mBAAmB;MAC5D;KACJ;KACA,IAAI,gBAAgB,KAAA,KAAa,OAAO,gBAAgB,UAAU;MAC9D,UAAU,cAAc,iBAAiB,mDAAmD;MAC5F;KACJ;KAOA,IAAI;KACJ,IAAI;MACA,WAAW,MAAM,gBAAgB,OAAO,aAAa,gBAAgB,KAAA,IAAY,KAAA,IAAY;OAAE,MAAM;OAAU;MAAS,CAAC;KAC7H,SAAS,OAAO;MACZ,MAAM,SAAS,gBAAgB,KAAA,IAAY,KAAA,IAAY,oBAAoB,KAAK;MAChF,IAAI,QAAQ;OACR,UAAU,cAAc,OAAO,MAAM,OAAO,OAAO;OACnD;MACJ;MACA,IAAI,MAAM,WAAW,KAAK,KAAK,CAAC,aAAa,MAAM;MACnD,WAAW,EAAE,SAAS,2BAA2B;KACrD;KACA,IAAI,aAAa,aAAa,SAAS,UAAU,SAAS,OAAO;MAC7D,UAAU,cAAc,SAAS,QAAQ,iBAAiB,SAAS,OAAO;MAC1E;KACJ;KACA,MAAM,eAAe,UAAU,WAAW,SAAS,OAAO;KAE1D,IAAI,cAAc;MACd,MAAM,UAAU,eAAe,IAAI,QAAQ;MAC3C,IAAI,SAAS;OACT,QAAQ,OAAO;OACf,QAAQ,gBAAgB;OAIxB,QAAQ,aAAa;OACrB,QAAQ,cAAc;OACtB,QAAQ,YAAY,KAAK,IAAI;OAC7B,eAAe,UAAU,KAAK;OAK9B,MAAM,gBAAgB,cAAc,UAAU,mBAAmB,OAAO,CAAC;MAC7E;MACA,QAAQ,4CAA4C,WAAW;MAC/D,GAAG,KAAK,KAAK,UAAU;OACnB,MAAM;OACN;OACA,SAAS;QAAE,KAAK,aAAa;QACzD,OAAO,aAAa;OAAM;MACF,CAAC,CAAC;MACF,QAAQ,gCAAgC,SAAS,oBAAoB,aAAa,KAAK;KAC3F,OAAO;MACH,QAAQ,0CAA0C,UAAU,iBAAiB;MAC7E,UAAU,cAAc,iBAAiB,0BAA0B;KACvE;KACA;IACJ;IAOA;KACI,MAAM,UAAU,eAAe,IAAI,QAAQ;KAC3C,IAAI,SAAS,QAAQ,QAAQ,cACtB,KAAK,IAAI,IAAI,QAAQ,cAAc,sBAAsB,IAAI,IAAI,IAAI,yBAAyB,IAAI;MACrG,MAAM,UAAU,MAAM,gBAAgB,QAAQ;MAC9C,IAAI,YAAY,SAAS;MACzB,IAAI,YAAY,eAAe;OAC3B,UAAU,SAAS,sBAAsB,sDAAsD;OAC/F;MACJ;KACJ;IACJ;IAGA,IAAI;SAEI,CADY,eAAe,IAAI,QAC9B,CAAA,EAAS,eAAe;MACzB,UAAU,SAAS,gBAAgB,yBAAyB;MAC5D;KACJ;;IAMJ,MAAM,aAAa,kBAAkB,IAAI,IAAI;IAC7C,IAAI;SAEI,CAAC,YAAY,cADD,eAAe,IAAI,QACJ,CAAO,GAAG,UAAU,GAAG;MAClD,UAAU,SAAS,iBAAiB,6BAA6B,WAAW,QAAQ;MACpF;KACJ;;IAEJ,MAAM,iBAAiB,eAAe,IAAI,QAAQ,CAAC,EAAE,MAAM;IAC3D,IAAI,gBAAgB;KAChB,MAAM,YAAY,iBAAiB,IAAI,IAAI;KAC3C,IAAI,WAAW;MACX,MAAM,aAAa,gBAAgB,MAAM,OAAO;MAChD,IAAI,CAAC,YAAY,gBAAgB,WAAW,UAAU,GAAG;OAErD,UAAU,SAAS,iBAAiB,mCADrB,aAAa,GAAG,UAAU,GAAG,eAAe,UACmB,QAAQ;OACtF;MACJ;KACJ,OAAO,IAAI,sBAAsB,IAAI,IAAI,GAAG;MACxC,UAAU,SAAS,iBAAiB,gCAAgC;MACpE;KACJ;IACJ;IAQA,IAAI,SAAS,iBAAiB,aAAa,IAAI,IAAI,GAAG;KAClD,MAAM,WAAW,MAAM,QAAQ,cAAc;MACzC,GAAG;MACH,KAAK,eAAe,IAAI,QAAQ,CAAC,EAAE,MAAM;MACzC,QAAQ,eAAe,IAAI,QAAQ,CAAC,EAAE,MAAM;KAChD,CAAC;KACD,IAAI,YAAY,CAAC,SAAS,SAAS;MAC/B,UAAU,SAAS,gBAAgB,4CAA4C;MAC/E;KACJ;IACJ;;;;;;;;IASA,MAAM,sBAAsB,MAA0B,QAAiB,WAAuC;KAC1G,IAAI,CAAC,QAAQ,CAAC,UAAU,OAAO,WAAW,UAAU;KACpD,MAAM,aAAa,OAAO,UAAU,oBAAoB,IAAI;KAC5D,IAAI,CAAC,YAAY;KAOjB,wBAAwB,QAAmC,YAAY;MACnE,QAAQ,EAAE,OAFE,eAAe,IAAI,QAEd,CAAA,EAAS,MAAM,SAAS,CAAC,MAAM,EAAE;MAElD;KACJ,CAAC;KASD,oBAAoB,QAAmC,UAAU;IACrE;;;;;;;;;;;;IAaA,MAAM,qBACF,MACA,YACO;KACP,IAAI,CAAC,MAAM;KACX,IAAI;KACJ,IAAI;MACA,aAAa,OAAO,UAAU,oBAAoB,IAAI;KAC1D,QAAQ;MAGJ;KACJ;KACA,IAAI,CAAC,YAAY;KACjB,MAAM,UAAU,eAAe,IAAI,QAAQ;KAC3C,0BAA0B,SAAS,YAAY,EAC3C,OAAO,SAAS,MAAM,SAAS,CAAC,MAAM,EAC1C,CAAC;IACL;;;;;;;;;;IAWA,MAAM,qBAAqB,MAAe,SAAgC;KACtE,IAAI,OAAO,SAAS,YAAY,CAAC,aAAa,IAAI,KAAK,CAAC,OAAO,UAAU;KACzE,IAAI;KACJ,IAAI;MACA,MAAM,kBAAkB,MAAM,OAAO,QAAQ;KACjD,QAAQ;MACJ;KACJ;KACA,IAAI,CAAC,KAAK;KACV,MAAM,UAAU,eAAe,IAAI,QAAQ;KAC3C,yBAAyB,KAAK,MAAM,MAAM,EAAE,OAAO,SAAS,MAAM,SAAS,CAAC,MAAM,EAAE,CAAC;IACzF;;IAGA,MAAM,oBAAoB,SAAgD;KACtE,IAAI,OAAO,SAAS,UAAU,OAAO,KAAA;KACrC,IAAI;KACJ,IAAI;MACA,aAAa,OAAO,UAAU,oBAAoB,IAAI;KAC1D,QAAQ;MACJ;KACJ;KACA,MAAM,OAAO,YAAY;KAEzB,OADyB,SAAS,QAAS,CAAC,CAAC,QAAQ,OAAO,SAAS,YAAY,KAAK,YAAY,OACxE,aAAa,KAAA;IAC3C;;;;;;;IAOA,MAAM,gBAAgB,SAClB,eAAe,iBAAiB,IAAI,IAAI,cAAc,KAAA;;;;;;;;;;IAW1D,MAAM,iBAAiB,YAAiE;KACpF,IAAI,QAAQ,WAAW,MAAM,OAAO,CAAC;KACrC,IAAI,OAAO,QAAQ,SAAS,YAAY,aAAa,QAAQ,IAAI,GAC7D,MAAM,SAAS,WACX,8IAEA,yBACJ;KAEJ,MAAM,aAAa,OAAO,UAAU,oBAAoB,QAAQ,IAAI;KACpE,MAAM,aAAa,aACb,sBAAsB,QAAQ,YAAY,YAAY,EAAE,OAAO,eAAe,CAAC,IAC/E,KAAA;KACN,OAAO;MAAE,QAAQ;MAAM,GAAI,cAAc,EAAE,WAAW;KAAG;IAC7D;IAGA,MAAM,oBAAoB,YAAiC;KAEvD,IAAI,OAAO,OAAO,aAAa,YAC3B,IAAI;MACA,MAAM,MAAM,mBAAmB,eAAe,IAAI,QAAQ,CAAC;MAC3D,MAAM,cAAoB;OACtB,KAAK,IAAI;OACT,aAAa;OACb,OAAO;OACP,UAAU;OACV,YAAY;OACZ,aAAa,IAAI,gBAAgB;OACjC,OAAO,IAAI;OACX,GAAI,IAAI,SAAS,EAAE,QAAQ,IAAI,OAAO,IAAI,CAAC;MAC/C;MACA,OAAO,MAAM,OAAO,SAAS,WAAW;KAC5C,SAAS,GAAG;MACR,OAAO,MAAM,uDAAuD,EAAE,OAAO,EAAE,CAAC;MAChF,MAAM,IAAI,MAAM,+BAA+B;KACnD;KAEJ,OAAO;IACX;IAEA,QAAQ,MAAR;KACI,KAAK;MAAoB;OACrB,QAAQ,2DAA2D;OACnE,MAAM,UAAgC;OACtC,kBAAkB,QAAQ,MAAM,OAAO;OASvC,MAAM,OAAO,OAAM,MARI,kBAAkB,EAAA,CAQb,gBAAgB;QACxC,GAAG;QACH,OAAO,uBAAuB,QAAQ,OAAO,EACzC,cAAc,CAAC,CAAC,QAAQ,aAC5B,CAAC;OACL,CAAC;OACD,QAAQ,+DAA+D,KAAK,MAAM;OAClF,MAAM,WAAW;QACb,MAAM;QACN,SAAS,EAAE,KAAK;QAChB;OACJ;OACA,QAAQ,iEAAiE;OACzE,GAAG,KAAK,KAAK,UAAU,QAAQ,CAAC;MACpC;MACI;KAEJ,KAAK;MAAa;OACd,QAAQ,uDAAuD;OAC/D,MAAM,UAAyB;OAE/B,MAAM,MAAM,OAAM,MADK,kBAAkB,EAAA,CACd,SAAS,OAAO;OAC3C,QAAQ,8CAA8C,GAAG;OACzD,MAAM,WAAW;QACb,MAAM;QACN,SAAS,EAAE,KAAK,OAAO,KAAK;QAC5B;OACJ;OACA,QAAQ,6DAA6D;OACrE,GAAG,KAAK,KAAK,UAAU,QAAQ,CAAC;MACpC;MACI;KAEJ,KAAK;MAAQ;OACT,QAAQ,sDAAsD;OAC9D,MAAM,UAAqB;OAC3B,QAAQ,kDAAkD,QAAQ,SAAS;QAAE,OAAO;QAC5G,QAAQ;OAAK,CAAC,CAAC;OAkBS,MAAM,kBAAkB,QAAQ,WAAW,cAAc,QAAQ,WAAW,QAAQ,aAAa,sBAC3F,iBAAiB,QAAQ,IAAI,IAC7B,KAAA;OACN,IAAI,iBAAiB;QACjB,MAAM,OAAQ,QAAQ,UAAU,CAAC;QACjC,4BAA4B,aAAa,iBAAiB,MAAM,EAC5D,OAAO,eAAe,IAAI,QAAQ,CAAC,EAAE,MAAM,SAAS,CAAC,MAAM,EAC/D,CAAC;QACD,yBAAyB,MAAM,UAAU;OAC7C,OACI,mBAAmB,QAAQ,MAAM,QAAQ,QAAmC,QAAQ,MAAM;OAI9F,kBAAkB,QAAQ,MAAM,QAAQ,OAAO,KAAA,KAAa,QAAQ,WAAW,aAAa,WAAW,QAAQ;OAC/G,MAAM,WAAW,MAAM,kBAAkB;OAOzC,MAAM,YAAY,QAAQ,WAAW,cAAc,QAAQ,OAAO,KAAA,IAC5D,aAAa,QAAQ,IAAI,IACzB,KAAA;OACN,MAAM,CAAC,UAAU,WAAW,qBACtB,MAAM,UAAU,mBAAmB,CAAC;QAAE,KAAK,OAAO,QAAQ,EAAE;QAAG,QAAQ,QAAQ;OAAkC,CAAC,CAAC,IACnH,CAAC,QAAQ,MAAM;OACrB,MAAM,SAAS,YAAiC,SAAS,KAAK;QAC1D,MAAM,QAAQ;QACd,GAAI,QAAQ,OAAO,KAAA,KAAa,EAAE,IAAI,QAAQ,GAAG;QACjD,QAAQ;QACR,QAAQ,QAAQ;QAChB,GAAG,cAAc,OAAO;OAC5B,CAAC;OAID,MAAM,UAAU,kBACV,MAAM,gCAAgC,aAAa,iBAAkB,UAAU,CAAC,GAA+B,KAAK,IACpH,KAAA;OACN,MAAM,MAAM,UAAU;QAAE,GAAG,QAAQ;QAAK,GAAG,QAAQ;OAAS,IAAI,MAAM,MAAM,MAAM;OAClF,QAAQ,6CAA6C,QAAQ,KAAK;QAAE,OAAO;QACnG,QAAQ;OAAK,CAAC,CAAC;OACS,MAAM,WAAW;QACb,MAAM;QACN,SAAS,EAAE,IAAI;QACf;OACJ;OACA,QAAQ,4DAA4D;OACpE,GAAG,KAAK,KAAK,UAAU,QAAQ,CAAC;MACpC;MACI;KAEJ,KAAK;MAAU;OACX,QAAQ,yDAAyD;OACjE,MAAM,UAAuB;OAC7B,QAAQ,wCAAwC,QAAQ,GAAG;OAG3D,kBAAkB,QAAQ,KAAK,MAAM,QAAQ;OAC7C,MAAM,WAAW,MAAM,kBAAkB;OASzC,MAAM,YAAY,aAAa,QAAQ,KAAK,IAAI;OAChD,MAAM,OAAO,CAAC,OAAO,QAAQ,IAAI,EAAE,CAAC;OACpC,MAAM,WAAW,uBAAuB,IAAI;OAC5C,MAAM,SAAS,OAAO;QAClB,KAAK;SAAE,IAAI,QAAQ,IAAI;SAAI,MAAM,QAAQ,IAAI;QAAK;QAClD,MAAM,QAAQ;OAClB,CAAC;OACD,MAAM,WAAW,wBAAwB,IAAI;OAC7C,QAAQ,6DAA6D;OACrE,MAAM,WAAW;QACb,MAAM;QACN,SAAS,EAAE,SAAS,KAAK;QACzB;OACJ;OACA,QAAQ,+DAA+D;OACvE,GAAG,KAAK,KAAK,UAAU,QAAQ,CAAC;MACpC;MACI;KAEJ,KAAK;MAAsB;OACvB,QAAQ,6DAA6D;OACrE,MAAM,EACF,MACA,MACA,OACA,IACA,eACA;OAGJ,IAAI,OAAO,SAAS,UAAU,kBAAkB,MAAM,EAAE,QAAQ,GAAG,OAAO,CAAC,MAAM,KAAK,EAAE,EAAE,CAAC;OAE3F,MAAM,WAAW,OAAM,MADA,kBAAkB,EAAA,CACT,iBAAiB,MAAM,MAAM,OAAO,IAAI,UAAU;OAClF,QAAQ,oDAAoD,QAAQ;OACpE,MAAM,WAAW;QACb,MAAM;QACN,SAAS,EAAE,SAAS;QACpB;OACJ;OACA,QAAQ,mEAAmE;OAC3E,GAAG,KAAK,KAAK,UAAU,QAAQ,CAAC;MACpC;MACI;KAGJ,KAAK;MAAS;OAOV,MAAM,UAAgC;OACtC,kBAAkB,QAAQ,MAAM,OAAO;OAGvC,MAAM,WAAW;QACb,MAAM;QACN,SAAS,EAAE,OAAA,OAHK,MADG,kBAAkB,EAAA,CACZ,MAAO,OAAO,EAGtB;QACjB;OACJ;OACA,GAAG,KAAK,KAAK,UAAU,QAAQ,CAAC;MACpC;MACI;KAEJ,KAAK;MAAe;OAChB,MAAM,EAAE,KAAK,SAAS,SAAS;OAC/B,IAAI;QAEA,MAAM,SAAQ,MADS,kBAAkB,EAAA,CAClB;QACvB,IAAI,CAAC,WAAW,KAAK,GAAG;SACpB,UAAU,SAAS,iBAAiB,iDAAiD;SACrF;QACJ;QAMA,MAAM,SAAS,SAAS,YAAY,OAAO,MAAM,iBAAiB,aAC5D,MAAM,MAAM,aAAa,OAAO,GAAG,GAAG;SACpC,UAAU,OAAO,SAAS,aAAa,WAAW,QAAQ,WAAW,KAAA;SACrE,MAAM,OAAO,SAAS,SAAS,WAAW,QAAQ,OAAO,KAAA;QAC7D,CAAC,IACC,KAAA;QAMN,MAAM,SAAS,SAAS,OAAO,OAAO,MAAM,MAAM,WAAW,KAAK;SAAE,GAAG;SAAS,gBAAgB;QAAK,CAAC;QAItG,MAAM,QAAQ,OAAO,SAAS,SAAS,YAAY,QAAQ,OACrD,MAAM,aAAa,OAAO,QAAQ,MAAM,OAAO,SAAS,aAAa,WAAW,QAAQ,WAAW,KAAA,CAAS,IAC5G,KAAA;QACN,IAAA,QAAA,IAAA,aAA6B,cACzB,QAAQ,+CAA+C,MAAM,QAAQ,MAAM,IAAI,OAAO,SAAS,YAAY,OAAO;QAEtH,MAAM,eAAe,eAAe,IAAI,QAAQ;QAmBhD,OAAO,KAAK,uCAAuC;SAC/C,KAAK,kBAAkB,OAAO,GAAG,CAAC,CAAC,CAAC,UAAU,GAAG,GAAG;SACpD,UAAU,SAAS;SACnB,MAAM,SAAS;SAUf,eAAe,iBAAiB,SAAS,IAAI;SAC7C,YAAY,MAAM,QAAQ,SAAS,MAAM,IAAI,QAAQ,OAAO,SAAS;SACrE,YAAY,MAAM,QAAQ,MAAM,IAAI,OAAO,SAAS;SACpD,KAAK,cAAc,MAAM,OAAO;SAChC,OAAO,cAAc,MAAM,SAAS,CAAC;SACrC,SAAS,cAAc,MAAM,WAAW;SACxC,GAAI,cAAc,MAAM,eAAe,EAAE,gBAAgB,aAAa,KAAK,aAAa,IAAI,CAAC;SAC7F;QACJ,CAAC;QACD,MAAM,WAAW;SACb,MAAM;SAGN,SAAS;UACL,GAAI,SAAS;WAAE,GAAG,kBAAkB,MAAM;WAAG;UAAO,IAAI,EAAE,OAAO;UACjE,GAAI,QAAQ,EAAE,eAAe,MAAM,IAAI,CAAC;SAC5C;SACA;QACJ;QACA,GAAG,KAAK,KAAK,UAAU,QAAQ,CAAC;OACpC,SAAS,UAAmB;QAIxB,UAAU,SAAS,aADJ,oBAAoB,QACH,CAAM;OAC1C;MACJ;MACI;KAEJ,KAAK;MAAmB;OACpB,QAAQ,0DAA0D;OAElE,MAAM,SAAQ,MADS,kBAAkB,EAAA,CAClB;OACvB,IAAI,YAAsB,CAAC;OAC3B,IAAI,WAAW,KAAK,KAAK,MAAM,yBAC3B,YAAY,MAAM,MAAM,wBAAwB;OAEpD,QAAQ,iCAAiC,UAAU,OAAO,YAAY;OACtE,MAAM,WAAW;QACb,MAAM;QACN,SAAS,EAAE,UAAU;QACrB;OACJ;OACA,GAAG,KAAK,KAAK,UAAU,QAAQ,CAAC;MACpC;MACI;KAEJ,KAAK;MAAe;OAChB,QAAQ,sDAAsD;OAE9D,MAAM,SAAQ,MADS,kBAAkB,EAAA,CAClB;OACvB,IAAI,QAAkB,CAAC;OACvB,IAAI,WAAW,KAAK,KAAK,MAAM,qBAC3B,QAAQ,MAAM,MAAM,oBAAoB;OAE5C,QAAQ,iCAAiC,MAAM,OAAO,QAAQ;OAC9D,MAAM,WAAW;QACb,MAAM;QACN,SAAS,EAAE,MAAM;QACjB;OACJ;OACA,GAAG,KAAK,KAAK,UAAU,QAAQ,CAAC;MACpC;MACI;KAEJ,KAAK;MAA2B;OAC5B,QAAQ,kEAAkE;OAE1E,MAAM,SAAQ,MADS,kBAAkB,EAAA,CAClB;OACvB,IAAI,QAAkB,CAAC;OACvB,IAAI,WAAW,KAAK,KAAK,MAAM,uBAC3B,QAAQ,MAAM,MAAM,sBAAsB;OAE9C,QAAQ,iCAAiC,MAAM,OAAO,oBAAoB;OAC1E,MAAM,WAAW;QACb,MAAM;QACN,SAAS,EAAE,MAAM;QACjB;OACJ;OACA,GAAG,KAAK,KAAK,UAAU,QAAQ,CAAC;MACpC;MACI;KAEJ,KAAK;MAA0B;OAC3B,QAAQ,iEAAiE;OAEzE,MAAM,SAAQ,MADS,kBAAkB,EAAA,CAClB;OACvB,IAAI,WAA+B,KAAA;OACnC,IAAI,WAAW,KAAK,KAAK,MAAM,sBAC3B,WAAW,MAAM,MAAM,qBAAqB;OAEhD,MAAM,WAAW;QACb,MAAM;QACN,SAAS,EAAE,SAAS;QACpB;OACJ;OACA,GAAG,KAAK,KAAK,UAAU,QAAQ,CAAC;MACpC;MACI;KAEJ,KAAK;MAAyB;OAC1B,QAAQ,gEAAgE;OAExE,MAAM,SAAQ,MADS,kBAAkB,EAAA,CAClB;OACvB,IAAI,SAAmB,CAAC;OACxB,IAAI,cAAc,KAAK,KAAK,MAAM,qBAC9B,SAAS,MAAM,MAAM,oBAAoB,SAAS,WAAW;OAEjE,QAAQ,iCAAiC,OAAO,OAAO,kBAAkB;OACzE,MAAM,WAAW;QACb,MAAM;QACN,SAAS,EAAE,OAAO;QAClB;OACJ;OACA,GAAG,KAAK,KAAK,UAAU,QAAQ,CAAC;MACpC;MACI;KAEJ,KAAK;MAAwB;OACzB,QAAQ,+DAA+D;OACvE,MAAM,EAAE,cAAc;OAEtB,MAAM,SAAQ,MADS,kBAAkB,EAAA,CAClB;OACvB,IAAI;OACJ,IAAI,cAAc,KAAK,KAAK,MAAM,oBAC9B,WAAW,MAAM,MAAM,mBAAmB,SAAS;OAEvD,QAAQ,qDAAqD,UAAU,MAAM,UAAU,SAAS,UAAU,EAAE,UAAU;OACtH,MAAM,WAAW;QACb,MAAM;QACN,SAAS,EAAE,SAAS;QACpB;OACJ;OACA,GAAG,KAAK,KAAK,UAAU,QAAQ,CAAC;MACpC;MACI;KAEJ,KAAK;MAAiB;OAClB,QAAQ,wDAAwD;OAChE,MAAM,EAAE,MAAM,YAAY;OAC1B,MAAM,WAAW,MAAM,kBAAkB;OACzC,IAAI,CAAC,SAAS,OAAO,cAAc;QAC/B,UAAU,SAAS,iBAAiB,uEAAuE;QAC3G;OACJ;OACA,MAAM,SAAqB,MAAM,SAAS,MAAM,aAAa,MAAM,OAAO;OAC1E,QAAQ,yCAAyC,OAAO,MAAM;OAC9D,MAAM,WAAW;QACb,MAAM;QACN,SAAS,EAAE,OAAO;QAClB;OACJ;OACA,GAAG,KAAK,KAAK,UAAU,QAAQ,CAAC;MACpC;MACI;KAEJ,KAAK;MAAiB;OAClB,QAAQ,yDAAyD;OACjE,MAAM,EAAE,MAAM,eAAe;OAC7B,MAAM,WAAW,MAAM,kBAAkB;OACzC,IAAI,CAAC,SAAS,OAAO,cAAc;QAC/B,UAAU,SAAS,iBAAiB,sCAAsC;QAC1E;OACJ;OACA,MAAM,SAAS,MAAM,aAAa,UAAU;OAC5C,QAAQ,0CAA0C,YAAY;OAC9D,MAAM,WAAW;QACb,MAAM;QACN,SAAS,EAAE,SAAS,KAAK;QACzB;OACJ;OACA,GAAG,KAAK,KAAK,UAAU,QAAQ,CAAC;MACpC;MACI;KAEJ,KAAK;MAAiB;OAClB,QAAQ,wDAAwD;OAChE,MAAM,WAAW,MAAM,kBAAkB;OACzC,IAAI,WAAyB,CAAC;OAC9B,IAAI,SAAS,OAAO,cAChB,WAAW,MAAM,SAAS,MAAM,aAAa;OAEjD,QAAQ,gCAAgC,SAAS,OAAO,WAAW;OACnE,MAAM,WAAW;QACb,MAAM;QACN,SAAS,EAAE,SAAS;QACpB;OACJ;OACA,GAAG,KAAK,KAAK,UAAU,QAAQ,CAAC;MACpC;MACI;KAGJ,KAAK;KACL,KAAK;KACL,KAAK;KACL,KAAK;KACL,KAAK;KACL,KAAK;KACL,KAAK;KACL,KAAK;KACL,KAAK;KACL,KAAK,mBAAmB;MACpB,QAAQ,sEAAsE,IAAI;MAIlF,MAAM,cAAc,mBAAmB,eAAe,IAAI,QAAQ,CAAC;MAEnE,MAAM,gBAAgB,oBAAoB,UAAU;OAChD;OACA;OACA,gBAAgB,SAAS;MAC7B,GAAG,WAAW;MACd;KACJ;KAEA,SACI,OAAO,MAAM,6CAA6C,EAAE,QAAQ,KAAK,CAAC;IAClF;GACJ,SAAS,OAAgB;IAQrB,IAAI,iBAAiB,gBAAgB;KACjC,OAAO,KAAK,2CAA2C,MAAM,SAAS;KACtE,GAAG,KAAK,KAAK,UAAU;MACnB,MAAM;MACN;MACA,SAAS,EAAE,OAAO;OAAE,SAAS,MAAM;OAC3D,MAAM;MAAgB,EAAE;KACJ,CAAC,CAAC;KACF;IACJ;IAMA,IAAI,iBAAiB,6BACb,OAAiB,SAAS,6BAA6B;KAC3D,MAAM,UAAU;KAChB,OAAO,KAAK,kDAAkD,QAAQ,SAAS;KAC/E,GAAG,KAAK,KAAK,UAAU;MACnB,MAAM;MACN;MACA,SAAS,EAAE,OAAO;OAAE,SAAS,QAAQ;OAC7D,MAAM;MAAwB,EAAE;KACZ,CAAC,CAAC;KACF;IACJ;IAaA,MAAM,SAAS,oBAAoB,KAAK;IACxC,IAAI,QAAQ;KACR,MAAM,OAAO,+BAA+B,OAAO,KAAK,GAAG,OAAO;KAClE,IAAI,OAAO,UAAU,OAAO,MAAM,IAAI;UACjC,OAAO,KAAK,IAAI;KACrB,GAAG,KAAK,KAAK,UAAU;MACnB,MAAM;MACN;MACA,SAAS,EAAE,OAAO;OACd,SAAS,OAAO;OAChB,MAAM,OAAO;OACb,GAAI,OAAO,YAAY,KAAA,KAAa,EAAE,SAAS,OAAO,QAAQ;MAClE,EAAE;KACN,CAAC,CAAC;KACF;IACJ;IACA,OAAO,MAAM,gDAAgD,EAAS,MAAM,CAAC;IAC7E,IAAI,iBAAiB,OACjB,OAAO,MAAM,eAAe,EAAE,QAAQ,MAAM,MAAM,CAAC;IAKvD,MAAM,eAAA,QAAA,IAAA,aAAwC,eACxC,iCACA,oBAAoB,KAAK;IAC/B,MAAM,gBAAgB;KAClB,MAAM;KACN;KACA,SAAS,EACL,OAAO;MACH,SAAS;MACT,MAAM;KACV,EACJ;IACJ;IACA,GAAG,KAAK,KAAK,UAAU,aAAa,CAAC;GACzC;EACJ,CAAC;CACL,CAAC;CAKD,MAAM,cAAc,kBAAkB,GAAG;CACzC,IAAI,GAAG,SAAS,WAAW;CAC3B,IAAI,OAAO,OAAO,OAAO,YAAY,OAAO,GAAG,SAAS,WAAW;AACvE;;;;;;;;;AChoDA,IAAa,6BAAb,cAAgD,mBAA0D;CAEtG,yBAAiB,IAAI,IAAqB;CAC1C,wBAAgB,IAAI,IAA2C;CAC/D,4BAAoB,IAAI,IAAuB;CAE/C,cAAc,OAAgB,WAAmB;EAC7C,KAAK,OAAO,IAAI,WAAW,KAAK;CACpC;CAEA,SAAS,WAAwC;EAC7C,OAAO,KAAK,OAAO,IAAI,SAAS;CACpC;;;;CAKA,sBAAsB,WAA4B;EAC9C,OAAO,KAAK,OAAO,IAAI,SAAS;CACpC;;;;CAKA,gBAA0B;EACtB,OAAO,MAAM,KAAK,KAAK,OAAO,KAAK,CAAC;CACxC;;;;CAKA,4BAA4B,gBAAgB,aAAiC;EAIzE,OAHoB,KAAK,eAAe,CAAC,CAAC,QACtC,MAAK,EAAE,eAAe,iBAAkB,CAAC,EAAE,cAAc,kBAAkB,WAExE,CAAA,CAAY,QAAO,MAAK,CAAC,KAAK,OAAO,IAAI,eAAa,CAAC,CAAC,CAAC;CACpE;CAEA,cAAc,OAAsD;EAChE,OAAO,QAAQ,KAAK,CAAC,CAAC,SAAS,CAAC,MAAM,WAAW,KAAK,MAAM,IAAI,MAAM,KAAK,CAAC;CAChF;CAEA,kBAAkB,WAAsC;EACpD,OAAO,QAAQ,SAAS,CAAC,CAAC,SAAS,CAAC,MAAM,WAAW,KAAK,UAAU,IAAI,MAAM,KAAK,CAAC;CACxF;CAEA,QAAQ,MAAyD;EAC7D,OAAO,KAAK,MAAM,IAAI,IAAI;CAC9B;CAEA,YAAY,MAAqC;EAC7C,OAAO,KAAK,UAAU,IAAI,IAAI;CAClC;CAEA,cAA6D;EACzD,OAAO,OAAO,YAAY,KAAK,MAAM,QAAQ,CAAC;CAClD;CAEA,kBAA6C;EACzC,OAAO,OAAO,YAAY,KAAK,UAAU,QAAQ,CAAC;CACtD;;;;;CAMA,kBAA2C;EACvC,MAAM,SAAkC,CAAC;EACzC,KAAK,MAAM,CAAC,MAAM,UAAU,KAAK,OAAO,QAAQ,GAC5C,OAAO,QAAQ;EAEnB,KAAK,MAAM,CAAC,MAAM,aAAa,KAAK,UAAU,QAAQ,GAClD,OAAO,QAAQ;EAEnB,OAAO;CACX;;;;;;CAOA,6BAA6B,gBAAkC;EAC3D,MAAM,aAAa,KAAK,oBAAoB,cAAc;EAC1D,IAAI,CAAC,YAAY,OAAO,CAAC;EAMzB,OAAO,OAAO,KAAK,2BAA2B,UAAU,CAAC;CAC7D;AAEJ;;;;;;;;;;;;;;;;;;;;;;ACpCA,SAAgB,wBAAwB,KAAwD;CAC5F,MAAM,WAAW,IAAI,iBAAiB,KAAK;CAC3C,IAAI,CAAC,UACD,OAAO,EAAE,UAAU,KAAK;CAG5B,MAAM,mBAAmB,wBAAwB,GAAG;CACpD,IAAI,CAAC,kBACD,OAAO,EAAE,OAAO,6DAA6D;CAGjF,MAAM,iBAAiB,IAAI,oBAAoB,KAAK;CACpD,IAAI,CAAC,gBACD,OAAO,EAAE,OAAO,mEAAmE;CAEvF,MAAM,cAAc,uBAAuB,cAAc;CAEzD,MAAM,gBAAgB,iBAAiB,IAAI,qBAAqB;CAChE,IAAI,kBAAkB,WAClB,OAAO,EAAE,OAAO,kDAAkD,IAAI,sBAAsB,IAAI;CAEpG,MAAM,cAAc,iBAAiB,IAAI,mBAAmB;CAC5D,IAAI,gBAAgB,WAChB,OAAO,EAAE,OAAO,gDAAgD,IAAI,oBAAoB,IAAI;CAGhG,OAAO,EACH,QAAQ;EACJ;EACA;EACA;EACA,eAAe,iBAAiB,KAAA;EAChC,aAAa,eAAe,KAAA;CAChC,EACJ;AACJ;AAEA,SAAS,iBAAiB,OAAsD;CAC5E,IAAI,UAAU,KAAA,KAAa,MAAM,KAAK,MAAM,IAAI,OAAO;CACvD,MAAM,IAAI,OAAO,KAAK;CACtB,IAAI,CAAC,OAAO,UAAU,CAAC,KAAK,IAAI,GAAG,OAAO;CAC1C,OAAO;AACX;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BA,SAAgB,iBAAiB,QAA6C;CAC1E,MAAM,SAAS,mBAAmB,OAAO,gBAAgB,KAAK;CAE9D,MAAM,MAAyB;EAC3B,MAAM,OAAO,QAAQ;EACrB,UAAU,OAAO;EACjB,aAAa;EACb,SAAS,OAAO,WAAW;EAE3B,gBAAgB;EAChB,MAAM,QAAQ,EAAE,OAAO;GACnB,MAAM,EAAE,YAAY,oBAAoB,cAAc,cAAc,iBAChE,MAAM,OAAO,+BAAA,CAAA,MAAA,MAAA,EAAA,CAAA;GACjB,MAAM,EAAE,gBAAgB;GAExB,IAAI,YAAY,SAAS,WAAW,CAAC,OAAO,SACxC,MAAM,IAAI,MACN,yBAAyB,YAAY,KAAK,2HAE9C;GAGJ,IAAI,uBAAuB,OAAO,GAAG;GACrC,MAAM,SAAS,YAAY,SAAS,UAAU,YAAY,OAAO,KAAA;GACjE,MAAM,OAAO,MAAM,WAAW;IAC1B,kBAAkB,OAAO;IACzB;IACA;IACA,gBAAgB,OAAO;GAC3B,CAAC;GACD,IAAI,iBAAiB,KAAK,SAAS,IAAI,YAAY,KAAK,SAAS,EAAE,EAAE;GAIrE,MAAM,QAAQ,MAAM,aAAa,KAAK,SAAS;GAC/C,IAAI,CAAC,MAAM,IAAI;IAMX,IAAI,KAAK,aAAa,mBAAmB,KAAK,WAAW;IACzD,mBAAmB,KAAK,SAAS;IACjC,MAAM,IAAI,MAAM,2FAA2F,MAAM,QAAQ;GAC7H;GAEA,IAAI,YAAY,KAAK;GACrB,IAAI;IACA,IAAI,YAAY,SAAS,SAAS;KAC9B,MAAM,WAAW,MAAM,aAAa,OAAO,SAAU,KAAK,WAAW,WAAW;KAChF,YAAY,SAAS;KACrB,IAAI,eAAe,SAAS,YAAY;KAGxC,IAAI,KAAK,eAAe,GAAG,WAAW,KAAK,WAAW,GAElD,IAAI,8BAA6B,MADjB,aAAa,OAAO,SAAU,KAAK,aAAa,WAAW,EAAA,CACxC,YAAY;IAEvD;GACJ,UAAU;IAGN,IAAI,YAAY,SAAS,WAAW,GAAG,WAAW,KAAK,SAAS,GAC5D,GAAG,WAAW,KAAK,SAAS;IAEhC,IAAI,KAAK,eAAe,YAAY,SAAS,WAAW,GAAG,WAAW,KAAK,WAAW,GAClF,GAAG,WAAW,KAAK,WAAW;GAEtC;GAEA,IAAI,SAAmB,CAAC;GACxB,IAAI,OAAO,iBAAiB,OAAO,gBAAgB,GAAG;IAClD,SAAS,MAAM,aACX,aACA;KAAE,eAAe,OAAO;KAAe,aAAa,OAAO;IAAY,GACvE,OAAO,OACX;IACA,IAAI,OAAO,SAAS,GAChB,IAAI,UAAU,OAAO,OAAO,wBAAwB,OAAO,cAAc,SAAS;GAE1F;GAEA,OAAO;IACH,QAAQ;IACR,WAAW,KAAK;IAChB,QAAQ,OAAO;GACnB;EACJ;CACJ;CAOA,OAAO,eAAe,KAAK,OAAO,IAAI,mBAAmB,GAAG,EAAE,OAAO,KAAK,CAAC;CAC3E,OAAO;AACX;AAEA,SAAS,YAAY,OAAuB;CACxC,IAAI,QAAQ,MAAM,OAAO,GAAG,MAAM;CAClC,IAAI,QAAQ,OAAO,MAAM,OAAO,IAAI,QAAQ,KAAA,CAAM,QAAQ,CAAC,EAAE;CAC7D,IAAI,QAAQ,OAAO,OAAO,MAAM,OAAO,IAAI,SAAS,OAAO,MAAA,CAAO,QAAQ,CAAC,EAAE;CAC7E,OAAO,IAAI,SAAS,OAAO,OAAO,MAAA,CAAO,QAAQ,CAAC,EAAE;AACxD;;;;;;;;;;;;;;;;;;;;AChOA,SAAgB,gBAAgB,OAA+B;CAC3D,OAAO;AACX;;;;;;;;AC8BA,SAAS,YAAY,OAA6B;CAC9C,MAAM,wBAAQ,IAAI,IAAY;CAC9B,KAAK,MAAM,CAAC,KAAK,QAAQ,OAAO,QAAQ,gBAAgB,KAAK,CAAC,GAAG;EAC7D,MAAM,IAAI,GAAG;EACb,MAAM,SAAU,KAA2B;EAC3C,IAAI,QAAQ,MAAM,IAAI,MAAM;CAChC;CACA,OAAO;AACX;AAEA,IAAM,SAAS,OAAyB,MAAM,KAAK,EAAE,CAAC,CAAC,KAAI,MAAK,KAAK,EAAE,GAAG,CAAC,CAAC,KAAK,IAAI;;AAGrF,IAAM,aAAa,UAAuC,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC,KAAK;;;;;;;;;;;;;;AAe/F,SAAS,mBACL,UACA,QACA,QACA,UACmD;CACnD,MAAM,SAAS;CACf,MAAM,UAAU,YAAoB,OAAyB,QAAgB;EACzE,MAAM,OAAO,eAAe,OAAO,QAAQ;EAC3C,IAAI,KAAK,SAAS,GAAG,OAAO,KAAA;EAC5B,OAAO;GACH,SAAS,GAAG,WAAW,wBAAwB,MAAM,KAAK,iBACnD,MAAM,KAAK,KAAI,QAAO,IAAI,SAAS,CAAC,EAAE;GAC7C;EACJ;CACJ;CAEA,QAAQ,SAAS,MAAjB;EACI,KAAK,aACD,OAAO,OAAO,gBAAgB,SAAS,SAAS,MAAM,QAClD,gEAAgE,QAAQ;EAChF,KAAK;EACL,KAAK;GACD,IAAI,SAAS,WAAW,OAAO,KAAA;GAC/B,OAAO,OAAO,0BAA0B,SAAS,mBAAmB,MAAM,QACtE,wEAAwE,OAAO,KAAK,kBAAkB,QAAQ;EACtH,KAAK,cACD,OAAO,OAAO,4BAA4B,SAAS,QAAQ,aAAa,MAAM,QAAQ,oDAAoD,QAAQ,KAC3I,OAAO,4BAA4B,SAAS,QAAQ,aAAa,MAAM,QAAQ,oDAAoD,QAAQ;EACtJ,KAAK,OACD;CACR;AACJ;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BA,SAAS,mBACL,QACA,WACA,SAC0C;CAC1C,KAAK,MAAM,UAAU,SAAS;EAC1B,IAAI,CAAC,QAAQ;EACb,MAAM,UAAU,uBAAuB,MAAM;EAC7C,MAAM,SAAS,qBAAqB,MAAM;EAG1C,IAAI,YAAY,UAAU,WAAW,SAAS;EAC9C,IAAI,UAAU,IAAI,MAAM,KAAK,CAAC,UAAU,IAAI,OAAO,GAAG,OAAO;GAAE;GAAQ;EAAQ;CACnF;CACA,OAAO;AACX;;AAGA,SAAS,mBACL,OACA,EAAE,QAAQ,WAC6B;CACvC,OAAO;EACH,SACI,4BAA4B,OAAO,UAAU,MAAM,iCAC9C,QAAQ;EAEjB,KACI,kMAEqB,OAAO;CACpC;AACJ;;;;;;;;AASA,SAAgB,oBACZ,aACA,UACgB;CAChB,MAAM,UAA4B,CAAC;CACnC,MAAM,kBAAkB,IAAI,IAAI,SAAS,eAAe,CAAC,CAAC,KAAI,MAAK,EAAE,IAAI,CAAC;CAE1E,KAAK,MAAM,cAAc,aAAa;EAClC,MAAM,kBAAkB,eAAa,UAAU;EAC/C,MAAM,cAAc,SAAS,SAAS,eAAe;EAErD,IAAI,CAAC,aAAa;EAElB,MAAM,gBAAgB,YAAY,WAAW;EAC7C,MAAM,YAAY,2BAA2B,UAAU;EAEvD,KAAK,MAAM,YAAY,OAAO,OAAO,SAAS,GAAG;GAC7C,MAAM,KAAK;IAAE,YAAY,WAAW;IAChD,cAAc,SAAS;IACvB,MAAM,SAAS;GAAK;GAER,IAAI;GACJ,IAAI;IACA,mBAAmB,SAAS,OAAO;GACvC,SAAS,GAAG;IACR,QAAQ,KAAK;KACT,GAAG;KACH,SAAS,2BAA2B,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC;KAC7E,KAAK;IACT,CAAC;IACD;GACJ;GAIA,IAAI,CAAC,gBAAgB,IAAI,iBAAiB,IAAI,GAAG;GAEjD,MAAM,kBAAkB,eAAa,gBAAgB;GACrD,MAAM,cAAc,SAAS,SAAS,eAAe;GACrD,IAAI,CAAC,aAAa;IACd,QAAQ,KAAK;KACT,GAAG;KACH,SAAS,6BAA6B,iBAAiB,KAAK,2BAA2B,gBAAgB;KACvG,KAAK,gBAAgB,gBAAgB,0CAA0C,iBAAiB,KAAK;IACzG,CAAC;IACD;GACJ;GACA,MAAM,gBAAgB,YAAY,WAAW;GAI7C,MAAM,YAAY,mBAAmB,UAAU,YAAY,kBAAkB,QAAQ;GACrF,IAAI,WAAW;IACX,QAAQ,KAAK;KAAE,GAAG;KAAI,GAAG;IAAU,CAAC;IACpC;GACJ;GAEA,QAAQ,SAAS,MAAjB;IACI,KAAK;KACD,IAAI,CAAC,cAAc,IAAI,SAAS,QAAQ,GAAG;MAGvC,MAAM,QAAQ,mBACV,SAAS,UACT,eACA,CAAC,SAAS,cAAc,iBAAiB,IAAI,CACjD;MACA,QAAQ,KAAK,QACP;OAAE,GAAG;OAAI,GAAG,mBAAmB,iBAAiB,KAAK;MAAE,IACvD;OACE,GAAG;OACH,SAAS,gBAAgB,SAAS,SAAS,2BAA2B,gBAAgB;OACtF,KAAK,kDAAkD,MAAM,aAAa;MAC9E,CAAC;KACT;KACA;IAGJ,KAAK;IACL,KAAK;KACD,IAAI,CAAC,cAAc,IAAI,SAAS,kBAAkB,GAAG;MAGjD,MAAM,QAAQ,mBACV,SAAS,oBACT,eACA,CAAC,WAAW,IAAI,CACpB;MACA,QAAQ,KAAK,QACP;OAAE,GAAG;OAAI,GAAG,mBAAmB,iBAAiB,KAAK;MAAE,IACvD;OACE,GAAG;OACH,SAAS,0BAA0B,SAAS,mBAAmB,4CAA4C,gBAAgB;OAC3H,KAAK,4DAA4D,MAAM,aAAa;MACxF,CAAC;KACT;KAKA,IAAI,SAAS,aAAa,CAAC,cAAc,IAAI,SAAS,SAAS,GAC3D,QAAQ,KAAK;MACT,GAAG;MACH,SAAS,iBAAiB,SAAS,UAAU,2BAA2B,gBAAgB;MACxF,KAAK,cAAc,IAAI,SAAS,SAAS,IACnC,0CAA0C,gBAAgB,wHAEvC,MAAM,aAAa,MACtC,mDAAmD,MAAM,aAAa;KAChF,CAAC;KAEL;IAGJ,KAAK,cAAc;KACf,MAAM,EAAE,OAAO,cAAc,iBAAiB,SAAS;KACvD,MAAM,WAAW,SAAS,SAAS,KAAK;KACxC,IAAI,CAAC,UAAU;MACX,QAAQ,KAAK;OACT,GAAG;OACH,SAAS,wBAAwB,MAAM;OACvC,KAAK,YAAY,MAAM;MAG3B,CAAC;MACD;KACJ;KACA,MAAM,kBAAkB,YAAY,QAAQ;KAI5C,MAAM,cAAc;MAChB,cAAc,CAAC,WAAW,IAAI;MAC9B,cAAc,CAAC,iBAAiB,IAAI;KACxC;KACA,KAAK,MAAM,CAAC,OAAO,WAAW,CAAC,CAAC,gBAAgB,YAAY,GAAG,CAAC,gBAAgB,YAAY,CAAC,GACzF,IAAI,CAAC,gBAAgB,IAAI,MAAM,GAAG;MAC9B,MAAM,QAAQ,mBAAmB,QAAQ,iBAAiB,CAAC,GAAG,YAAY,MAAM,CAAC;MACjF,QAAQ,KAAK,QACP;OAAE,GAAG;OAAI,GAAG,mBAAmB,OAAO,KAAK;MAAE,IAC7C;OACE,GAAG;OACH,SAAS,aAAa,MAAM,KAAK,OAAO,8CAA8C,MAAM;OAC5F,KAAK,iBAAiB,MAAM,gBAAgB,MAAM,eAAe,OAC5D,UAAU,iBAAiB,iDAAiD;MACrF,CAAC;KACT;KAEJ;IACJ;IAEA,KAAK,OAAO;KACR,IAAI,SAAS,SAAS,WAAW,GAAG;MAChC,QAAQ,KAAK;OACT,GAAG;OACH,SAAS;OACT,KAAK;MACT,CAAC;MACD;KACJ;KAIA,IAAI,WAAW;KACf,IAAI,cAAc;KAClB,IAAI,SAAS;KAEb,KAAK,MAAM,CAAC,GAAG,SAAS,SAAS,SAAS,QAAQ,GAAG;MACjD,MAAM,YAAY,SAAS,SAAS,KAAK,KAAK;MAC9C,IAAI,CAAC,WAAW;OACZ,QAAQ,KAAK;QACT,GAAG;QACH,SAAS,QAAQ,IAAI,EAAE,+BAA+B,KAAK,MAAM;QACjE,KAAK,sBAAsB,EAAE;OACjC,CAAC;OACD,SAAS;OACT;MACJ;MACA,MAAM,cAAc,YAAY,SAAS;MAEzC,KAAK,MAAM,UAAU,UAAU,KAAK,GAAG,IAAI,GACvC,IAAI,CAAC,YAAY,IAAI,MAAM,GACvB,QAAQ,KAAK;OACT,GAAG;OACH,SAAS,QAAQ,IAAI,EAAE,WAAW,SAAS,GAAG,OAAO,SAAS,KAAK,MAAM,YAAY,OAAO,0BAA0B,SAAS;OAC/H,KAAK,cAAc,EAAE,+BAA+B,MAAM,IAAI,4BAA4B,gCAAgC,SAAS,KAAK,IAAI,MAAM,WAAW;MACjK,CAAC;MAGT,KAAK,MAAM,UAAU,UAAU,KAAK,GAAG,EAAE,GACrC,IAAI,CAAC,YAAY,IAAI,MAAM,GACvB,QAAQ,KAAK;OACT,GAAG;OACH,SAAS,QAAQ,IAAI,EAAE,gBAAgB,KAAK,MAAM,GAAG,OAAO,YAAY,OAAO,0BAA0B,KAAK,MAAM;OACpH,KAAK,cAAc,EAAE,+BAA+B,KAAK,MAAM,MAAM,MAAM,WAAW;MAC1F,CAAC;MAIT,IAAI,UAAU,KAAK,GAAG,IAAI,CAAC,CAAC,WAAW,UAAU,KAAK,GAAG,EAAE,CAAC,CAAC,QACzD,QAAQ,KAAK;OACT,GAAG;OACH,SAAS,QAAQ,IAAI,EAAE,YAAY,UAAU,KAAK,GAAG,IAAI,CAAC,CAAC,OAAO,qBAAqB,UAAU,KAAK,GAAG,EAAE,CAAC,CAAC;OAC7G,KAAK,0EAA0E,EAAE;MACrF,CAAC;MAGL,WAAW,KAAK;MAChB,cAAc;KAClB;KAIA,IAAI,CAAC,UAAU,aAAa,iBACxB,QAAQ,KAAK;MACT,GAAG;MACH,SAAS,8BAA8B,SAAS,uBAAuB,iBAAiB,KAAK,cAAc,gBAAgB;MAC3H,KAAK,6BAA6B,gBAAgB,wDAAwD,SAAS;KACvH,CAAC;KAEL;IACJ;IAEA,SAEI,MAAM,IAAI,MAAM,4BAA4B,KAAK,UAAU,QAAU,GAAG;GAEhF;EACJ;CACJ;CAEA,OAAO;AACX;;;;;;;;;;;AAYA,SAAgB,uBACZ,aACA,UACI;CACJ,MAAM,UAAU,oBAAoB,aAAa,QAAQ;CACzD,IAAI,QAAQ,WAAW,GAAG;CAE1B,MAAM,QAAQ,QAAQ,KAAI,MACtB,OAAO,EAAE,WAAW,GAAG,EAAE,aAAa,IAAI,EAAE,KAAK,WACxC,EAAE,QAAQ,eACL,EAAE,KACpB;CAEA,MAAM,IAAI,MACN,GAAG,QAAQ,OAAO,WAAW,QAAQ,WAAW,IAAI,KAAK,IAAI;;;;;;;;IAe7D,MAAM,KAAK,MAAM,IAAI,IACzB;AACJ;;;;;;;;;;;;;;AC5aA,SAAgB,wBAAwB,QAAoD;CACxF,MAAM,WAAW,IAAI,2BAA2B;CAEhD,IAAI,OAAO,aAAa;EACpB,SAAS,iBAAiB,OAAO,WAAW;EAG5C,OAAO,MACH,oCAAoC,SAAS,eAAe,CAAC,CAAC,OAAO,iBACjE,SAAS,eAAe,CAAC,CAAC,KAAI,MAAK,EAAE,IAAI,CAAC,CAAC,KAAK,IAAI,EAAE,EAC9D;CACJ;CAEA,IAAI,OAAO,QACP,OAAO,OAAO,OAAO,MAAM,CAAC,CAAC,SAAS,UAAU;EAC5C,IAAI,QAAQ,KAAK,GACb,SAAS,cAAc,OAAkB,aAAa,KAAK,CAAC;CAEpE,CAAC;CAGL,IAAI,OAAO,OAAO,SAAS,cAAc,OAAO,KAAK;CACrD,IAAI,OAAO,WAAW,SAAS,kBAAkB,OAAO,SAAS;CAKjE,gCAAgC,SAAS,eAAe,GAAG,QAAQ;CAMnE,uBAAuB,SAAS,eAAe,GAAG,QAAQ;CAE1D,OAAO;AACX;;;;;;;;;;;;;;;;;;;;;;AC9BA,SAAS,aAAa,OAAkC,GAAG,MAAoC;CAC3F,IAAI,CAAC,OAAO,OAAO,KAAA;CACnB,KAAK,MAAM,OAAO,MAAM;EACpB,IAAI,OAAO,OAAO,OAAO;EACzB,MAAM,QAAQ,YAAY,GAAG;EAC7B,IAAI,SAAS,OAAO,OAAO;EAC3B,MAAM,QAAQ,UAAU,GAAG;EAC3B,IAAI,SAAS,OAAO,OAAO;CAC/B;AAEJ;;AAGA,SAAS,mBAAmB,OAA8B;CAEtD,OAAO,IADQ,eAAe,KAAK,CAAC,CAAC,UAAU,SAC7B,KAAK,aAAa,KAAK,EAAE;AAC/C;;AAGA,IAAM,aAAa;AAEnB,SAAS,UAAU,OAAkC,GAAG,MAAmD;CACvG,IAAI,CAAC,OAAO,OAAO,KAAA;CACnB,MAAM,MAAM,aAAa,OAAO,GAAG,IAAI;CACvC,OAAO,MAAM,MAAM,OAAO,KAAA;AAC9B;;;;;AA4BA,IAAa,cAAb,MAAmD;CAOnC;CANZ;CACA;;CAEA;CAEA,YACI,IACA,eACF;EAFU,KAAA,KAAA;EAGR,IAAI,iBAAmB,cAA4C,OAAQ;GACvE,MAAM,SAAS;GACf,KAAK,aAAc,OAAO,SAAS;GACnC,KAAK,sBAAuB,OAAO,kBAAkB;GACrD,KAAK,qBAAsB,OAAO,iBAAiB;EACvD,OAAO;GACH,MAAM,QAAQ;GACd,KAAK,aAAa,SAAS,gBAAgB,KAAK;GAChD,KAAK,sBAAsB,gBAAgB,cAAc;GACzD,KAAK,qBAAqB,gBAAgB,aAAa;EAC3D;CACJ;CAEA,6BAA6C;EACzC,MAAM,OAAO,aAAa,KAAK,UAAU;EAEzC,OAAO,IADQ,eAAe,KAAK,UAAU,CAAC,CAAC,UAAU,SACvC,KAAK,KAAK;CAChC;;;;;;;;;;;;;;;;CAiBA,MAAc,kBAAqB,IAAoD;EACnF,OAAO,MAAM,KAAK,GAAG,YAAY,OAAO,OAAO;GAC3C,MAAM,GAAG,QAAQ,GAAG;;;;;aAKnB;GACD,OAAO,MAAM,GAAG,EAAoB;EACxC,CAAC;CACL;CAEA,aAAqB,KAAwC;EACzD,IAAI,CAAC,KAAK,OAAO;EAEjB,MAAM,KAAM,IAAI,MAAM,IAAI;EAC1B,MAAM,QAAQ,IAAI;EAClB,MAAM,eAAgB,IAAI,iBAAiB,IAAI,gBAAgB;EAC/D,MAAM,cAAe,IAAI,gBAAgB,IAAI,eAAe;EAC5D,MAAM,WAAY,IAAI,aAAa,IAAI,YAAY,IAAI,YAAY;EACnE,MAAM,gBAAiB,IAAI,kBAAkB,IAAI,iBAAiB;EAClE,MAAM,yBAA0B,IAAI,4BAA4B,IAAI,0BAA0B;EAC9F,MAAM,0BAA2B,IAAI,8BAA8B,IAAI,2BAA2B;EAClG,MAAM,cAAe,IAAI,gBAAgB,IAAI,eAAe;EAC5D,MAAM,aAAa,IAAI,eAAe,IAAI,cAAc;EACxD,MAAM,YAAa,IAAI,cAAc,IAAI;EACzC,MAAM,YAAa,IAAI,cAAc,IAAI;EAEzC,MAAM,WAAgC,EAAE,GAAK,IAAI,YAAgD,CAAC,EAAG;EAErG,MAAM,4BAAY,IAAI,IAAI;GACtB;GAAM;GAAO;GACb;GAAiB;GACjB;GAAgB;GAChB;GAAa;GAAY;GACzB;GAAkB;GAClB;GAA4B;GAC5B;GAA8B;GAC9B;GAAgB;GAChB;GAAe;GACf;GAAsB;GAGtB;GAAiB;GACjB;GAAsB;GACtB;GAAwB;GACxB;GACA;GAAc;GACd;GAAc;GACd;EACJ,CAAC;EAED,KAAK,MAAM,CAAC,KAAK,QAAQ,OAAO,QAAQ,GAAG,GACvC,IAAI,CAAC,UAAU,IAAI,GAAG,GAAG;GACrB,MAAM,WAAW,UAAU,GAAG;GAC9B,SAAS,YAAY;EACzB;EAGJ,OAAO;GACH;GACA;GACA;GACA;GACA;GACA;GACA;GACA,yBAAyB,0BAA0B,IAAI,KAAK,uBAAuB,IAAI;GACvF;GACA,GAAI,aAAa,EAAE,UAAU,KAAK,IAAI,CAAC;GACvC,WAAW,YAAY,IAAI,KAAK,SAAS,oBAAI,IAAI,KAAK;GACtD,WAAW,YAAY,IAAI,KAAK,SAAS,oBAAI,IAAI,KAAK;GACtD;EACJ;CACJ;CAEA,WAAmB,MAAwD;EACvE,IAAI,CAAC,MAAM,OAAO,CAAC;EAEnB,MAAM,UAAmC,CAAC;EAE1C,MAAM,QAAQ,aAAa,KAAK,YAAY,IAAI,KAAK;EACrD,MAAM,WAAW,aAAa,KAAK,YAAY,OAAO,KAAK;EAC3D,MAAM,kBAAkB,aAAa,KAAK,YAAY,gBAAgB,eAAe,KAAK;EAC1F,MAAM,iBAAiB,aAAa,KAAK,YAAY,eAAe,cAAc,KAAK;EACvF,MAAM,cAAc,aAAa,KAAK,YAAY,YAAY,WAAW,KAAK;EAC9E,MAAM,mBAAmB,aAAa,KAAK,YAAY,iBAAiB,gBAAgB,KAAK;EAC7F,MAAM,4BAA4B,aAAa,KAAK,YAAY,0BAA0B,0BAA0B,KAAK;EACzH,MAAM,6BAA6B,aAAa,KAAK,YAAY,2BAA2B,4BAA4B,KAAK;EAC7H,MAAM,iBAAiB,aAAa,KAAK,YAAY,eAAe,cAAc,KAAK;EACvF,MAAM,eAAe,aAAa,KAAK,YAAY,aAAa,YAAY,KAAK;EACjF,MAAM,eAAe,aAAa,KAAK,YAAY,aAAa,YAAY,KAAK;EACjF,MAAM,cAAc,aAAa,KAAK,YAAY,UAAU,KAAK;EAEjE,IAAI,QAAQ,MAAM,QAAQ,SAAS,KAAK;EACxC,IAAI,WAAW,MAAM,QAAQ,YAAY,eAAe,KAAK,KAAK;EAClE,IAAI,kBAAkB,MAAM,QAAQ,mBAAmB,KAAK;EAC5D,IAAI,iBAAiB,MAAM,QAAQ,kBAAkB,KAAK;EAC1D,IAAI,cAAc,MAAM,QAAQ,eAAe,KAAK;EACpD,IAAI,mBAAmB,MAAM,QAAQ,oBAAoB,KAAK;EAC9D,IAAI,4BAA4B,MAAM,QAAQ,6BAA6B,KAAK;EAChF,IAAI,6BAA6B,MAAM,QAAQ,8BAA8B,KAAK;EAClF,IAAI,iBAAiB,MAAM,QAAQ,kBAAkB,KAAK;EAC1D,IAAI,eAAe,MAAM,QAAQ,gBAAgB,KAAK;EACtD,IAAI,eAAe,MAAM,QAAQ,gBAAgB,KAAK;EAEtD,MAAM,WAAgC,EAAE,GAAI,KAAK,YAAY,CAAC,EAAG;EACjE,MAAM,oBAAyC,CAAC;EAEhD,KAAK,MAAM,CAAC,KAAK,QAAQ,OAAO,QAAQ,QAAQ,GAAG;GAC/C,MAAM,cAAc,aAAa,KAAK,YAAY,GAAG;GACrD,IAAI,eACA,gBAAgB,SAChB,gBAAgB,YAChB,gBAAgB,mBAChB,gBAAgB,kBAChB,gBAAgB,eAChB,gBAAgB,oBAChB,gBAAgB,6BAChB,gBAAgB,8BAChB,gBAAgB,kBAChB,gBAAgB,gBAChB,gBAAgB,gBAChB,gBAAgB,aAChB,QAAQ,eAAe;QAEvB,kBAAkB,OAAO;EAEjC;EAEA,IAAI,eAAe,KAAK,YACpB,QAAQ,eAAe;EAG3B,OAAO;CACX;;;;;;;;;;;CAYA,MAAM,WAAW,MAAyC;EACtD,MAAM,UAAU,KAAK,WAAW,IAAI;EACpC,IAAI;GACA,MAAM,CAAC,OAAO,MAAM,KAAK,kBAAkB,OAAO,OAC7C,MAAM,GAAG,OAAO,KAAK,UAAU,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,UAAU,CAChE;GACA,OAAO,KAAK,aAAa,GAAG;EAChC,SAAS,OAAO;GAGZ,IAAI,eAAe,KAAK,CAAC,EAAE,SAAS,SAChC,MAAM,SAAS,SAAS,4BAA4B,cAAc;GAEtE,MAAM;EACV;CACJ;CAEA,MAAM,YAAY,IAAsC;EACpD,MAAM,QAAQ,UAAU,KAAK,YAAY,IAAI;EAC7C,IAAI,CAAC,OAAO,OAAO;EACnB,MAAM,CAAC,OAAO,MAAM,KAAK,GAAG,OAAO,CAAC,CAAC,KAAK,KAAK,UAAU,CAAC,CAAC,MAAM,GAAG,OAAO,EAAE,CAAC;EAC9E,OAAO,MAAM,KAAK,aAAa,GAA8B,IAAI;CACrE;CAEA,MAAM,eAAe,OAAyC;EAC1D,MAAM,WAAW,UAAU,KAAK,YAAY,OAAO;EACnD,IAAI,CAAC,UAAU,OAAO;EACtB,MAAM,CAAC,OAAO,MAAM,KAAK,GAAG,OAAO,CAAC,CAAC,KAAK,KAAK,UAAU,CAAC,CAAC,MAAM,GAAG,UAAU,eAAe,KAAK,CAAC,CAAC;EACpG,OAAO,MAAM,KAAK,aAAa,GAA8B,IAAI;CACrE;CAEA,MAAM,kBAAkB,UAAkB,YAA8C;EACpF,MAAM,YAAY,UAAU,KAAK,YAAY,IAAI;EACjD,IAAI,CAAC,WAAW,OAAO;EAEvB,MAAM,SAAS,MAAM,KAAK,GACrB,OAAO,EAAE,MAAM,KAAK,WAAW,CAAC,CAAC,CACjC,KAAK,KAAK,UAAU,CAAC,CACrB,UAAU,KAAK,qBAAqB,GAAG,WAAW,KAAK,oBAAoB,GAAG,CAAC,CAAC,CAChF,MACG,GAAG,GAAG,KAAK,oBAAoB,SAAS,KAAK,SAAS,OAAO,KAAK,oBAAoB,WAAW,KAAK,YAC1G,CAAC,CACA,MAAM,CAAC;EAEZ,IAAI,OAAO,WAAW,GAAG,OAAO;EAChC,OAAO,KAAK,aAAa,OAAO,EAAE,CAAC,IAA+B;CACtE;CAEA,MAAM,kBAAkB,KAA0C;EAC9D,MAAM,SAAS,eAAe,KAAK,mBAAmB,CAAC,CAAC,UAAU;EAOlE,QAAO,MANc,KAAK,GAAG,QAAQ,GAAG;;mBAE7B,IAAI,IAAI,IAAI,OAAO,oBAAoB,EAAE;0BAClC,IAAI;SACrB,EAAA,CAEa,KAAK,KAAK,SAAkC;GACtD,IAAI,IAAI;GACR,KAAK,IAAI;GACT,UAAU,IAAI;GACd,YAAY,IAAI;GAChB,aAAc,IAAI,gBAAmD;GACrE,WAAW,IAAI;GACf,WAAW,IAAI;EACnB,EAAE;CACN;;;;;;CAOA,MAAM,mBAAmB,KAAa,UAAkB,YAAmC;EACvF,MAAM,KAAK,kBAAkB,OAAO,OAAO,GAAG,OAAO,KAAK,mBAAmB,CAAC,CAAC,MAC3E,GAAG,GAAG,KAAK,oBAAoB,IAAI,KAAK,IAAI,OAAO,KAAK,oBAAoB,SAAS,KAAK,SAAS,OAAO,KAAK,oBAAoB,WAAW,KAAK,YACvJ,CAAC;CACL;CAEA,MAAM,iBAAiB,KAAa,UAAkB,YAAoB,aAAsD;EAC5H,MAAM,KAAK,kBAAkB,OAAO,OAAO,GAAG,OAAO,KAAK,mBAAmB,CAAC,CAAC,OAAO;GAClF;GACA;GACA;GACA,aAAa,eAAe;EAChC,CAAC,CAAC,CAAC,oBAAoB,EAAE,QAAQ,CAAC,KAAK,oBAAoB,UAAU,KAAK,oBAAoB,UAAU,EAAE,CAAC,CAAC;CAChH;CAEA,MAAM,WAAW,IAAY,MAAqE;EAC9F,MAAM,QAAQ,UAAU,KAAK,YAAY,IAAI;EAC7C,IAAI,CAAC,OAAO,OAAO;EACnB,MAAM,UAAU,KAAK,WAAW,IAAI;EACpC,MAAM,eAAe,aAAa,KAAK,YAAY,aAAa,YAAY,KAAK;EACjF,QAAQ,gCAAgB,IAAI,KAAK;EAEjC,IAAI;EACJ,IAAI;GACA,CAAC,OAAO,MAAM,KAAK,kBAAkB,OAAO,OACvC,MAAM,GACF,OAAO,KAAK,UAAU,CAAC,CACvB,IAAI,OAAO,CAAC,CACZ,MAAM,GAAG,OAAO,EAAE,CAAC,CAAC,CACpB,UAAU,CACnB;EACJ,SAAS,OAAO;GAGZ,IAAI,eAAe,KAAK,CAAC,EAAE,SAAS,SAChC,MAAM,SAAS,SAAS,4BAA4B,cAAc;GAEtE,MAAM;EACV;EACA,OAAO,MAAM,KAAK,aAAa,GAAG,IAAI;CAC1C;CAEA,MAAM,WAAW,IAA2B;EACxC,MAAM,QAAQ,UAAU,KAAK,YAAY,IAAI;EAC7C,IAAI,CAAC,OAAO;EACZ,MAAM,KAAK,kBAAkB,OAAO,OAAO,GAAG,OAAO,KAAK,UAAU,CAAC,CAAC,MAAM,GAAG,OAAO,EAAE,CAAC,CAAC;CAC9F;CAEA,MAAM,YAAiC;EAEnC,QAAQ,MADW,KAAK,GAAG,OAAO,CAAC,CAAC,KAAK,KAAK,UAAU,EAAA,CACb,KAAI,QAAO,KAAK,aAAa,GAAG,CAAC;CAChF;CAEA,MAAM,mBAAmB,SAA2D;EAChF,MAAM,QAAQ,SAAS,SAAS;EAChC,MAAM,SAAS,SAAS,UAAU;EAClC,MAAM,SAAS,SAAS,QAAQ,KAAK,KAAK;EAC1C,MAAM,UAAU,SAAS,WAAW;EACpC,MAAM,WAAW,SAAS,YAAY;EACtC,MAAM,SAAS,SAAS;EAExB,MAAM,WAAW,UAAU,KAAK,YAAY,OAAO;EACnD,MAAM,cAAc,WAAW,SAAS,OAAO;EAC/C,MAAM,YAAY,aAAa,QAAQ,GAAG,QAAQ,GAAG;EAErD,MAAM,WAAW,UAAU,KAAK,YAAY,OAAO;EACnD,MAAM,cAAc,WAAW,SAAS,OAAO;EAC/C,MAAM,iBAAiB,UAAU,KAAK,YAAY,eAAe,cAAc;EAC/E,MAAM,oBAAoB,iBAAiB,eAAe,OAAO;EACjE,MAAM,QAAQ,UAAU,KAAK,YAAY,IAAI;EAC7C,MAAM,WAAW,QAAQ,MAAM,OAAO;EAEtC,MAAM,iBAAiB,KAAK,2BAA2B;EACvD,MAAM,aAAa,CAAC;EACpB,IAAI,QACA,WAAW,KAAK,GAAG,GAAG,OAAO,SAAS,IAAI,IAAI,cAAc,EAAE,QAAQ;EAE1E,IAAI,QAAQ;GAUR,MAAM,UAAU,IAAI,kBAAkB,MAAM,EAAE;GAC9C,MAAM,QAAQ,IAAI,IAAI,cAAc;GACpC,WAAW,KAAK,GAAG,IAAI,MAAM,GAAG,IAAI,IAAI,WAAW,EAAE,SAAS,QAAQ;qBAC7D,MAAM,GAAG,IAAI,IAAI,iBAAiB,EAAE,SAAS,QAAQ;kDACxB,MAAM,wDAAwD,QAAQ;0BAC9F,MAAM,GAAG,IAAI,IAAI,QAAQ,EAAE,kBAAkB,QAAQ,EAAE;EACzE;EAEA,MAAM,cAAc,WAAW,SAAS,IAAI,GAAG,SAAS,IAAI,KAAK,YAAY,GAAG,OAAO,MAAM,GAAG;EAGhG,MAAM,gBAAgB,SAChB,GAAG,YAAY,IAAI,IAAI,cAAc,EAAE,GAAG,IAAI,IAAI,WAAW,EAAE,GAAG,cAClE,GAAG,yBAAyB,IAAI,IAAI,cAAc,EAAE,8BAA8B,IAAI,IAAI,cAAc,EAAE,GAAG,IAAI,IAAI,WAAW,EAAE,GAAG;EAM3I,MAAM,SAAS,MAJW,KAAK,GAAG,QAAQ,GAAG;iDACJ,IAAI,IAAI,cAAc,EAAE;cAC3D,YAAY;SACjB,EAAA,CAC0B,KAAK,EAAE,CAAuB;EAazD,OAAO;GAAE,QALI,MANY,KAAK,GAAG,QAAQ,GAAG;4BACxB,IAAI,IAAI,cAAc,EAAE;cACtC,YAAY;cACZ,cAAc;oBACR,MAAM,UAAU,OAAO;SAClC,EAAA,CACuB,KAG4C,KAAK,QAAQ,KAAK,aAAa,GAAG,CAEtF;GACZ;GACA;GACA;EAAO;CACf;;;;CAKA,MAAM,eAAe,IAAY,cAA4C;EACzE,MAAM,QAAQ,UAAU,KAAK,YAAY,IAAI;EAC7C,IAAI,CAAC,OAAO;EACZ,MAAM,qBAAqB,aAAa,KAAK,YAAY,gBAAgB,eAAe,KAAK;EAC7F,MAAM,kBAAkB,aAAa,KAAK,YAAY,aAAa,YAAY,KAAK;EAEpF,MAAM,KAAK,kBAAkB,OAAO,OAAO,GACtC,OAAO,KAAK,UAAU,CAAC,CACvB,IAAI;IACA,qBAAqB;IACrB,kCAAkB,IAAI,KAAK;EAChC,CAAC,CAAC,CACD,MAAM,GAAG,OAAO,EAAE,CAAC,CAAC;CAC7B;;;;CAKA,MAAM,iBAAiB,IAAY,UAAkC;EACjE,MAAM,QAAQ,UAAU,KAAK,YAAY,IAAI;EAC7C,IAAI,CAAC,OAAO;EACZ,MAAM,sBAAsB,aAAa,KAAK,YAAY,iBAAiB,gBAAgB,KAAK;EAChG,MAAM,+BAA+B,aAAa,KAAK,YAAY,0BAA0B,0BAA0B,KAAK;EAC5H,MAAM,kBAAkB,aAAa,KAAK,YAAY,aAAa,YAAY,KAAK;EAEpF,MAAM,KAAK,kBAAkB,OAAO,OAAO,GACtC,OAAO,KAAK,UAAU,CAAC,CACvB,IAAI;IACA,sBAAsB;IACtB,+BAA+B;IAC/B,kCAAkB,IAAI,KAAK;EAChC,CAAC,CAAC,CACD,MAAM,GAAG,OAAO,EAAE,CAAC,CAAC;CAC7B;;;;CAKA,MAAM,qBAAqB,IAAY,OAAqC;EACxE,MAAM,QAAQ,UAAU,KAAK,YAAY,IAAI;EAC7C,IAAI,CAAC,OAAO;EACZ,MAAM,+BAA+B,aAAa,KAAK,YAAY,0BAA0B,0BAA0B,KAAK;EAC5H,MAAM,gCAAgC,aAAa,KAAK,YAAY,2BAA2B,4BAA4B,KAAK;EAChI,MAAM,kBAAkB,aAAa,KAAK,YAAY,aAAa,YAAY,KAAK;EAEpF,MAAM,KAAK,kBAAkB,OAAO,OAAO,GACtC,OAAO,KAAK,UAAU,CAAC,CACvB,IAAI;IACA,+BAA+B;IAC/B,gCAAgC,wBAAQ,IAAI,KAAK,IAAI;IACrD,kCAAkB,IAAI,KAAK;EAChC,CAAC,CAAC,CACD,MAAM,GAAG,OAAO,EAAE,CAAC,CAAC;CAC7B;;;;CAKA,MAAM,2BAA2B,OAAyC;EACtE,MAAM,WAAW,UAAU,KAAK,YAAY,0BAA0B,0BAA0B;EAChG,IAAI,CAAC,UAAU,OAAO;EACtB,MAAM,CAAC,OAAO,MAAM,KAAK,GACpB,OAAO,CAAC,CACR,KAAK,KAAK,UAAU,CAAC,CACrB,MAAM,GAAG,UAAU,KAAK,CAAC;EAC9B,OAAO,MAAM,KAAK,aAAa,GAA8B,IAAI;CACrE;;;;CAKA,MAAM,eAAe,KAAgC;EACjD,MAAM,iBAAiB,KAAK,2BAA2B;EACvD,MAAM,SAAS,MAAM,KAAK,GAAG,QAAQ,GAAG;gCAChB,IAAI,IAAI,cAAc,EAAE,cAAc,IAAI;SACjE;EAED,IAAI,OAAO,KAAK,WAAW,GAAG,OAAO,CAAC;EAGtC,OADY,OAAO,KAAK,EACjB,CAAI,SAAS,CAAC;CACzB;;;;CAKA,MAAM,aAAa,KAAa,SAAkC;EAC9D,MAAM,iBAAiB,KAAK,2BAA2B;EACvD,MAAM,aAAa,IAAI,QAAQ,KAAK,GAAG,EAAE;EACzC,MAAM,KAAK,kBAAkB,OAAO,OAAO,GAAG,QAAQ,GAAG;qBAC5C,IAAI,IAAI,cAAc,EAAE;0BACnB,WAAW;yBACZ,IAAI;SACpB,CAAC;CACN;;;;CAKA,MAAM,kBAAkB,KAAa,QAA+B;EAChE,MAAM,iBAAiB,KAAK,2BAA2B;EACvD,MAAM,KAAK,kBAAkB,OAAO,OAAO,GAAG,QAAQ,GAAG;qBAC5C,IAAI,IAAI,cAAc,EAAE;8CACC,OAAO;yBAC5B,IAAI,YAAY,OAAO;SACvC,CAAC;CACN;;;;;;;;;;CAWA,MAAM,sBAAsB,KAAa,WAAwD;EAC7F,MAAM,iBAAiB,KAAK,2BAA2B;EACvD,MAAM,YAAY,aAAa,KAAK,YAAY,oBAAoB,oBAAoB,IAClF,GAAG,yBACH,GAAG;EAMT,MAAM,SAAS,KAAK;EACpB,MAAM,gBAAgB,QAAQ,SAAS,KAChC,WAAW,KAAK,aAAa,EAAE,KAC/B,QAAQ,aAAa,QAAQ,aAAa,YAAY,CAAC;EAC9D,MAAM,gBAAgB,aAAa,QAAQ,SAAS,IAC9C,GAAG,+BACH,GAAG;EACT,MAAM,gBAAgB,gBAChB,GAAG;gCACe,IAAI,IAAI,mBAAmB,MAAM,CAAC,EAAE;0DACV,UAAU,GAAG,cAAc;iBAEvE,GAAG;EACT,MAAM,WAAW,MAAM,KAAK,kBAAkB,IACxC,GAAG,8BACH,GAAG;EACT,MAAM,SAAS,MAAM,KAAK,GAAG,QAAQ,GAAG;8BAClB,UAAU,0BAA0B,cAAc,sBAAsB,SAAS;mBAC5F,IAAI,IAAI,cAAc,EAAE,kBAAkB,IAAI;SACxD;EACD,IAAI,OAAO,KAAK,WAAW,GAAG,OAAO;EACrC,MAAM,MAAM,OAAO,KAAK;EACxB,OAAO;GACH,OAAO,IAAI,SAAS,CAAC;GACrB,kBAAkB,IAAI,qBAAqB,IAAI,KAAK,IAAI,kBAAkB,IAAI;GAC9E,GAAI,IAAI,mBAAmB,OAAO,CAAC,IAAI,EAAE,eAAe,IAAI,eAAe;GAC3E,GAAI,IAAI,WAAW,EAAE,UAAU,KAAK,IAAI,CAAC;EAC7C;CACJ;;;;;;CAOA;CACA,oBAA8C;EAC1C,KAAK,oBAAoB,YAAY;GACjC,MAAM,OAAO,aAAa,KAAK,UAAU;GACzC,MAAM,SAAS,eAAe,KAAK,UAAU,CAAC,CAAC,UAAU;GAKzD,QAAO,MAJc,KAAK,GAAG,QAAQ,GAAG;;uCAEb,OAAO,oBAAoB,KAAK;aAC1D,EAAA,CACa,KAAK,SAAS;EAChC,EAAA,CAAG,CAAC,CAAC,YAAY;GACb,KAAK,iBAAiB,KAAA;GACtB,OAAO;EACX,CAAC;EACD,OAAO,KAAK;CAChB;;;;;;CAOA;CACA,wBAAkD;EAC9C,KAAK,wBAAwB,YAAY;GACrC,MAAM,OAAO,aAAa,KAAK,UAAU;GACzC,MAAM,SAAS,eAAe,KAAK,UAAU,CAAC,CAAC,UAAU;GAMzD,QAAQ,MALa,KAAK,GAAG,QAAQ,GAAG;;uCAEb,OAAO,oBAAoB,KAAK;;aAE1D,EAAA,CACc,KAAK,EAAE,CAAyB,YAAY;EAC/D,EAAA,CAAG,CAAC,CAAC,YAAY;GACb,KAAK,qBAAqB,KAAA;GAC1B,OAAO;EACX,CAAC;EACD,OAAO,KAAK;CAChB;CAEA,MAAc,4BAA2C;EACrD,IAAI,CAAE,MAAM,KAAK,sBAAsB,GACnC,MAAM,IAAI,SAAS,KAAK,iBAAiB,iFAAiF;CAElI;;CAGA,MAAM,sBAAsB,KAAa,QAAoE;EACzG,MAAM,KAAK,0BAA0B;EACrC,MAAM,iBAAiB,KAAK,2BAA2B;EACvD,MAAM,KAAK,kBAAkB,OAAO,OAAO,GAAG,QAAQ,SAChD,GAAG;yBACQ,IAAI,IAAI,cAAc,EAAE;sCACX,eAAe,OAAO,KAAK,EAAE,yBAAyB,OAAO,UAAU;;6BAEhF,QACf,GAAG;yBACQ,IAAI,IAAI,cAAc,EAAE;;6BAEpB,KAAK,CAAC;CAC/B;;CAGA,MAAM,sBAAsB,KAAiD;EACzE,IAAI,CAAE,MAAM,KAAK,sBAAsB,GAAI,OAAO;EAClD,MAAM,iBAAiB,KAAK,2BAA2B;EAKvD,MAAM,OAAM,MAJS,KAAK,GAAG,QAAQ,GAAG;8DACc,IAAI,IAAI,cAAc,EAAE;yBAC7D,IAAI;SACpB,EAAA,CACkB,KAAK;EACxB,OAAO,MAAM;GAAE,OAAO,IAAI;GAAe,QAAQ,IAAI,KAAK,IAAI,oBAAoB;EAAE,IAAI;CAC5F;;CAGA,MAAM,uBAAuB,WAAmF;EAC5G,IAAI,CAAE,MAAM,KAAK,sBAAsB,GAAI,OAAO;EAClD,MAAM,iBAAiB,KAAK,2BAA2B;EAKvD,MAAM,OAAM,MAJS,KAAK,GAAG,QAAQ,GAAG;4BACpB,IAAI,IAAI,cAAc,EAAE;yCACX,UAAU;SAC1C,EAAA,CACkB,KAAK;EACxB,IAAI,CAAC,KAAK,OAAO;EACjB,OAAO;GACH,MAAM,KAAK,aAAa,GAAG;GAC3B,QAAQ;IAAE,OAAO,IAAI;IAAyB,QAAQ,IAAI,KAAK,IAAI,oBAAqC;GAAE;EAC9G;CACJ;;CAGA,MAAM,wBAAwB,KAAa,WAA6C;EACpF,MAAM,KAAK,0BAA0B;EACrC,MAAM,iBAAiB,KAAK,2BAA2B;EACvD,IAAI;GAUA,MAAM,OAAM,MATS,KAAK,kBAAkB,OAAO,OAAO,GAAG,QAAQ,GAAG;yBAC3D,IAAI,IAAI,cAAc,EAAE;;;;;6BAKpB,IAAI,4BAA4B,UAAU;;aAE1D,CAAC,EAAA,CACiB,KAAK;GACxB,OAAO,MAAM,KAAK,aAAa,GAAG,IAAI;EAC1C,SAAS,OAAO;GAGZ,IAAI,eAAe,KAAK,CAAC,EAAE,SAAS,SAChC,MAAM,SAAS,SAAS,4BAA4B,cAAc;GAEtE,MAAM;EACV;CACJ;;CAGA,MAAM,gBAAgB,KAAa,UAAkC;EACjE,IAAI,CAAE,MAAM,KAAK,kBAAkB,GAC/B,MAAM,IAAI,SAAS,KAAK,iBAAiB,+EAA+E;EAE5H,MAAM,iBAAiB,KAAK,2BAA2B;EACvD,MAAM,KAAK,kBAAkB,OAAO,OAAO,GAAG,QAAQ,GAAG;qBAC5C,IAAI,IAAI,cAAc,EAAE;gCACb,WAAW,GAAG,UAAU,GAAG,OAAO;yBACzC,IAAI;SACpB,CAAC;CACN;;;;CAKA,MAAM,iBAAiB,KAAkE;EACrF,MAAM,OAAO,MAAM,KAAK,YAAY,GAAG;EACvC,IAAI,CAAC,MAAM,OAAO;EAGlB,OAAO;GAAE;GACL,OAAA,MAFgB,KAAK,eAAe,GAAG;EAEjC;CACd;AACJ;AAGA,IAAa,sBAAb,MAAa,oBAAoB;CAKjB;CAJZ;CACA;CAEA,YACI,IACA,eACF;EAFU,KAAA,KAAA;EAGR,IAAI,kBAAmB,cAA4C,iBAAkB,cAA4C,QAAQ;GACrI,KAAK,qBAAuB,cAA4C,iBAAiB;GACzF,KAAK,aAAe,cAA4C,SAAS;EAC7E,OAAO;GACH,KAAK,qBAAsB,iBAAmC,gBAAgB,aAAa;GAC3F,KAAK,aAAa,gBAAgB,KAAK;EAC3C;CACJ;;;;;;CAOA,IAAY,QAAyB;EACjC,OAAO,QAAS,KAAK,mBAA+C,OAAO;CAC/E;CAEA,IAAY,QAAgB;EACxB,OAAO,KAAK,mBAAmB;CACnC;;CAGA,YAAoB;EAMhB,MAAM,YAAyC;GAC3C,IAAI,KAAK,mBAAmB;GAC5B,KAAK,KAAK,mBAAmB;GAC7B,WAAW,KAAK,mBAAmB;GACnC,WAAW,KAAK,mBAAmB;GACnC,WAAW,KAAK,mBAAmB;GACnC,WAAW,KAAK,mBAAmB;GACnC,WAAW,KAAK,mBAAmB;EACvC;EACA,KAAK,MAAM,YAAY;GAAC;GAAa;GAAa;GAAW;GAAoB;GAAO;EAAQ,GAC5F,IAAI,KAAK,IAAI,QAAQ,GAAG,UAAU,YAAY,KAAK,IAAI,QAAQ;EAEnE,OAAO;CACX;CAEA,MAAM,YACF,KACA,WACA,WACA,WACA,WACA,SACgB;EAWhB,MAAM,SAAkC;GACpC;GACA;GACA;GACA,WAXkB,aAAa;GAY/B,WAXkB,aAAa;EAYnC;EACA,IAAI,WAAW,KAAK,IAAI,WAAW,GAAG,OAAO,YAAY,QAAQ;EACjE,IAAI,WAAW,KAAK,IAAI,kBAAkB,GAAG,OAAO,mBAAmB,QAAQ;EAK/E,IAAI,SAAS,OAAO,KAAK,IAAI,KAAK,GAAG,OAAO,MAAM,QAAQ;EAG1D,IAAI,SAAS,UAAU,KAAK,IAAI,QAAQ,GAAG,OAAO,SAAS,QAAQ;EAMnE,MAAM,aAAa,SAAS,eAAe,KAAK,IAAI,WAAW,IAAI,QAAQ,cAAc,KAAA;EACzF,IAAI,CAAC,YAAY;GACb,MAAM,KAAK,GAAG,OAAO,KAAK,kBAAkB,CAAC,CAAC,OAAO,MAAM;GAC3D,OAAO;EACX;EACA,OAAO,MAAM,KAAK,GAAG,YAAY,OAAO,OAAO;GAK3C,MAAM,OAAO,KAAK,IAAI,SAAS,IACzB,GAAG,GAAG,KAAK,mBAAmB,UAAU,KAAK,WAAW,OAAO,KAAK,IAAI,SAAS,EAAE,gBACnF,GAAG,KAAK,mBAAmB,WAAW,UAAU;GACtD,MAAM,CAAC,UAAU,MAAM,GAClB,OAAO,EAAE,IAAI,KAAK,mBAAmB,GAAG,CAAC,CAAC,CAC1C,KAAK,KAAK,kBAAkB,CAAC,CAC7B,MAAM,IAAI,CAAC,CACX,IAAI,OAAO;GAChB,IAAI,CAAC,QAAQ,OAAO;GACpB,MAAM,GAAG,OAAO,KAAK,kBAAkB,CAAC,CAAC,OAAO,MAAM;GACtD,OAAO;EACX,CAAC;CACL;;;;;;;;;;;;;;;;;;CAmBA,OAAe,OAAO,KAAgD;EAGlE,MAAM,UAAU,UAAyB,iBAAiB,OAAO,QAAQ,IAAI,KAAK,OAAO,KAAK,CAAC;EAC/F,MAAM,OAAyB;GAQ3B,IAAI,IAAI;GACR,KAAK,IAAI;GACT,WAAW,IAAI;GACf,WAAW,OAAO,IAAI,SAAS;GAC/B,WAAW,OAAO,IAAI,SAAS;EACnC;EACA,IAAI,IAAI,cAAc,KAAA,GAAW,KAAK,YAAY,IAAI;EACtD,IAAI,IAAI,cAAc,KAAA,GAAW,KAAK,YAAY,IAAI;EACtD,IAAI,IAAI,cAAc,KAAA,KAAa,IAAI,cAAc,MAAM,KAAK,YAAY,OAAO,IAAI,SAAS;EAChG,IAAI,IAAI,cAAc,KAAA,GAAW,KAAK,YAAY,IAAI,cAAc,OAAO,OAAO,OAAO,IAAI,SAAS;EACtG,IAAI,IAAI,YAAY,KAAA,GAAW,KAAK,UAAU,QAAQ,IAAI,OAAO;EACjE,IAAI,IAAI,qBAAqB,KAAA,KAAa,IAAI,qBAAqB,MAC/D,KAAK,mBAAmB,OAAO,IAAI,gBAAgB;EAQvD,IAAI,IAAI,QAAQ,UAAU,IAAI,QAAQ,QAAQ,KAAK,MAAM,IAAI;EAE7D,IAAI,OAAO,IAAI,WAAW,YAAY,IAAI,OAAO,SAAS,GAAG,KAAK,SAAS,IAAI;EAC/E,OAAO;CACX;CAEA,MAAM,WAAW,WAAqD;EAClE,MAAM,CAAC,SAAS,MAAM,KAAK,GACtB,OAAO,KAAK,UAAU,CAAC,CAAC,CACxB,KAAK,KAAK,kBAAkB,CAAC,CAC7B,MAAM,GAAG,KAAK,mBAAmB,WAAW,SAAS,CAAC;EAE3D,OAAO,QAAQ,oBAAoB,OAAO,KAAK,IAAI;CACvD;;;;;;;;;CAUA,MAAM,YAAY,WAAkC;EAChD,IAAI,CAAC,KAAK,IAAI,WAAW,GAAG;GACxB,MAAM,KAAK,aAAa,SAAS;GACjC;EACJ;EACA,MAAM,KAAK,GACN,OAAO,KAAK,kBAAkB,CAAC,CAC/B,IAAI,EAAE,2BAAW,IAAI,KAAK,EAAE,CAAC,CAAC,CAC9B,MAAM,GAAG,KAAK,mBAAmB,WAAW,SAAS,CAAC;CAC/D;;;;;;;;;;;;CAaA,MAAM,cAAc,WAAkC;EAClD,IAAI,CAAC,KAAK,IAAI,WAAW,GAAG;EAC5B,MAAM,YAAY,GAAG,KAAK,IAAI,WAAW,GAAG,SAAS;EACrD,MAAM,KAAK,GAAG,YAAY,OAAO,OAAO;GACpC,MAAM,GACD,OAAO,EAAE,IAAI,KAAK,mBAAmB,GAAG,CAAC,CAAC,CAC1C,KAAK,KAAK,kBAAkB,CAAC,CAC7B,MAAM,SAAS,CAAC,CAChB,IAAI,QAAQ;GACjB,IAAI,KAAK,IAAI,SAAS,GAAG;IACrB,MAAM,GACD,OAAO,KAAK,kBAAkB,CAAC,CAC/B,IAAI;KAAE,SAAS;KAAM,GAAI,KAAK,IAAI,WAAW,IAAI,EAAE,2BAAW,IAAI,KAAK,EAAE,IAAI,CAAC;IAAG,CAAC,CAAC,CACnF,MAAM,SAAS;IACpB;GACJ;GACA,MAAM,GAAG,OAAO,KAAK,kBAAkB,CAAC,CAAC,MAAM,SAAS;EAC5D,CAAC;CACL;;;;;;CAOA,MAAM,MAAM,KAAa,WAAmB,kBAAuC;EAC/E,MAAM,SAAS,KAAK,mBAAmB;EACvC,MAAM,aAAa,KAAK,mBAAmB;EAC3C,IAAI,CAAC,KAAK,IAAI,WAAW,KAAK,CAAC,KAAK,IAAI,WAAW,GAAG;GAClD,MAAM,KAAK,GAAG,OAAO,KAAK,kBAAkB,CAAC,CACxC,MAAM,GAAG,GAAG,OAAO,KAAK,IAAI,OAAO,WAAW,SAAS;GAC5D;EACJ;EACA,MAAM,aAAa,KAAK,IAAI,WAAW;EACvC,MAAM,aAAa,KAAK,IAAI,WAAW;EACvC,MAAM,KAAK,GAAG,OAAO,KAAK,kBAAkB,CAAC,CAAC,MAAM,GAAG;cACjD,OAAO,KAAK,IAAI;;kBAEZ,WAAW;;sBAEP,WAAW,KAAK,UAAU;0BACtB,WAAW;0BACX,WAAW,KAAK,iBAAiB;;;SAGlD;CACL;CAEA,MAAM,oBAAoB,KAAmC;EACzD,IAAI,CAAC,KAAK,cAAc,CAAE,KAAK,WAAuC,kBAAkB,OAAO;EAC/F,MAAM,CAAC,OAAO,MAAM,KAAK,GACpB,OAAO,EAAE,kBAAkB,KAAK,WAAW,iBAAiB,CAAC,CAAC,CAC9D,KAAK,KAAK,UAAU,CAAC,CACrB,MAAM,GAAG,KAAK,WAAW,IAAI,GAAG,CAAC;EACtC,MAAM,QAAS,KAAiE;EAChF,OAAO,QAAQ,IAAI,KAAK,KAAK,IAAI;CACrC;CAEA,MAAM,oBAAoB,KAAa,IAAyB;EAC5D,IAAI,CAAC,KAAK,cAAc,CAAE,KAAK,WAAuC,kBAAkB;EACxF,MAAM,KAAK,GACN,OAAO,KAAK,UAAU,CAAC,CACvB,IAAI,EAAE,kBAAkB,GAAG,CAAC,CAAC,CAC7B,MAAM,GAAG,KAAK,WAAW,IAAI,GAAG,CAAC;CAC1C;CAEA,MAAM,aAAa,WAAkC;EACjD,MAAM,KAAK,GAAG,OAAO,KAAK,kBAAkB,CAAC,CAAC,MAAM,GAAG,KAAK,mBAAmB,WAAW,SAAS,CAAC;CACxG;CAEA,MAAM,iBAAiB,KAA4B;EAC/C,MAAM,KAAK,GAAG,OAAO,KAAK,kBAAkB,CAAC,CAAC,MAAM,GAAG,KAAK,mBAAmB,KAAK,GAAG,CAAC;CAC5F;CAEA,MAAM,YAAY,KAA0C;EAOxD,QAAO,MANc,KAAK,GACrB,OAAO,KAAK,UAAU,CAAC,CAAC,CACxB,KAAK,KAAK,kBAAkB,CAAC,CAC7B,MAAM,GAAG,KAAK,mBAAmB,KAAK,GAAG,CAAC,CAAC,CAC3C,QAAQ,KAAK,mBAAmB,SAAS,EAAA,CAEhC,IAAI,oBAAoB,MAAM;CAChD;CAEA,MAAM,WAAW,IAAY,KAA4B;EACrD,MAAM,KAAK,GAAG,OAAO,KAAK,kBAAkB,CAAC,CACxC,MAAM,GAAG,GAAG,KAAK,mBAAmB,GAAG,KAAK,GAAG,OAAO,KAAK,mBAAmB,IAAI,KAAK,KAAK;CACrG;;;;;;CAOA,MAAM,gBAA+B;EACjC,MAAM,KAAK,GAAG,OAAO,KAAK,kBAAkB,CAAC,CACxC,MAAM,GAAG,GAAG,KAAK,mBAAmB,UAAU,SAAS;CAChE;AACJ;;;;AAKA,IAAa,4BAAb,MAAuC;CAIvB;CAHZ;CAEA,YACI,IACA,eACF;EAFU,KAAA,KAAA;EAGR,IAAI,kBAAmB,cAA4C,uBAAwB,cAA4C,QACnI,KAAK,2BAA6B,cAA4C,uBAAuB;OAErG,KAAK,2BAA4B,iBAAmC,gBAAgB,mBAAmB;CAE/G;CAEA,2CAA2D;EACvD,MAAM,OAAO,aAAa,KAAK,wBAAwB;EAEvD,OAAO,IADQ,eAAe,KAAK,wBAAwB,CAAC,CAAC,UAAU,SACrD,KAAK,KAAK;CAChC;;;;CAKA,MAAM,YAAY,KAAa,WAAmB,WAAgC;EAE9E,MAAM,YAAY,KAAK,yCAAyC;EAChE,MAAM,KAAK,GAAG,QAAQ,GAAG;0BACP,IAAI,IAAI,SAAS,EAAE;0BACnB,IAAI;SACrB;EAED,MAAM,KAAK,GAAG,OAAO,KAAK,wBAAwB,CAAC,CAAC,OAAO;GACvD;GACA;GACA;EACJ,CAAC;CACL;;;;CAKA,MAAM,gBAAgB,WAAqE;EACvF,MAAM,CAAC,SAAS,MAAM,KAAK,GACtB,OAAO;GACJ,KAAK,KAAK,yBAAyB;GACnC,WAAW,KAAK,yBAAyB;EAC7C,CAAC,CAAC,CACD,KAAK,KAAK,wBAAwB,CAAC,CACnC,MAAM,GAAG,KAAK,yBAAyB,WAAW,SAAS,CAAC;EAEjE,IAAI,CAAC,OAAO,OAAO;EAGnB,MAAM,YAAY,KAAK,yCAAyC;EAChE,MAAM,SAAS,MAAM,KAAK,GAAG,QAAQ,GAAG;;mBAE7B,IAAI,IAAI,SAAS,EAAE;iCACL,UAAU;;;SAGlC;EAED,IAAI,OAAO,KAAK,WAAW,GAAG,OAAO;EAErC,MAAM,MAAM,OAAO,KAAK;EACxB,OAAO;GACH,KAAK,IAAI;GACT,WAAW,IAAI,KAAK,IAAI,UAAU;EACtC;CACJ;;;;CAKA,MAAM,WAAW,WAAkC;EAC/C,MAAM,KAAK,GACN,OAAO,KAAK,wBAAwB,CAAC,CACrC,IAAI,EAAE,wBAAQ,IAAI,KAAK,EAAE,CAAC,CAAC,CAC3B,MAAM,GAAG,KAAK,yBAAyB,WAAW,SAAS,CAAC;CACrE;;;;CAKA,MAAM,iBAAiB,KAA4B;EAC/C,MAAM,KAAK,GAAG,OAAO,KAAK,wBAAwB,CAAC,CAAC,MAAM,GAAG,KAAK,yBAAyB,KAAK,GAAG,CAAC;CACxG;;;;CAKA,MAAM,gBAA+B;EACjC,MAAM,YAAY,KAAK,yCAAyC;EAChE,MAAM,KAAK,GAAG,QAAQ,GAAG;0BACP,IAAI,IAAI,SAAS,EAAE;;SAEpC;CACL;AACJ;;;;;AAMA,IAAa,wBAAb,MAAmC;CAInB;CAHZ;CAEA,YACI,IACA,eACF;EAFU,KAAA,KAAA;EAGR,KAAK,uBAAuB,gBAAgB,eAAe;CAC/D;CAEA,wBAAwC;EACpC,MAAM,OAAO,aAAa,KAAK,oBAAoB;EAEnD,OAAO,IADQ,eAAe,KAAK,oBAAoB,CAAC,CAAC,UAAU,SACjD,KAAK,KAAK;CAChC;CAEA,MAAM,YAAY,KAAa,WAAmB,WAAgC;EAE9E,MAAM,YAAY,KAAK,sBAAsB;EAC7C,MAAM,KAAK,GAAG,QAAQ,GAAG;0BACP,IAAI,IAAI,SAAS,EAAE;0BACnB,IAAI;SACrB;EAED,MAAM,KAAK,GAAG,OAAO,KAAK,oBAAoB,CAAC,CAAC,OAAO;GACnD;GACA;GACA;EACJ,CAAC;CACL;CAEA,MAAM,gBAAgB,WAAuD;EACzE,MAAM,YAAY,KAAK,sBAAsB;EAC7C,MAAM,SAAS,MAAM,KAAK,GAAG,QAAQ,GAAG;;mBAE7B,IAAI,IAAI,SAAS,EAAE;iCACL,UAAU;;;SAGlC;EAED,IAAI,OAAO,KAAK,WAAW,GAAG,OAAO;EAErC,MAAM,MAAM,OAAO,KAAK;EACxB,OAAO;GACH,KAAK,IAAI;GACT,WAAW,IAAI,KAAK,IAAI,UAAU;EACtC;CACJ;;CAGA,MAAM,gBAA+B;EACjC,MAAM,YAAY,KAAK,sBAAsB;EAC7C,MAAM,KAAK,GAAG,QAAQ,GAAG;0BACP,IAAI,IAAI,SAAS,EAAE;;SAEpC;CACL;CAEA,MAAM,WAAW,WAAkC;EAC/C,MAAM,KAAK,GACN,OAAO,KAAK,oBAAoB,CAAC,CACjC,IAAI,EAAE,wBAAQ,IAAI,KAAK,EAAE,CAAC,CAAC,CAC3B,MAAM,GAAG,KAAK,qBAAqB,WAAW,SAAS,CAAC;CACjE;AACJ;;;;;AAMA,IAAa,0BAAb,MAAgE;CAMhD;CALZ;CACA;CACA;CAEA,YACI,IACA,eACF;EAFU,KAAA,KAAA;EAGR,KAAK,sBAAsB,IAAI,oBAAoB,IAAI,aAAa;EACpE,KAAK,4BAA4B,IAAI,0BAA0B,IAAI,aAAa;EAChF,KAAK,wBAAwB,IAAI,sBAAsB,IAAI,aAAa;CAC5E;CAIA,MAAM,mBAAmB,KAAa,WAAmB,WAAiB,WAAoB,WAAoB,SAAiD;EAC/J,OAAO,KAAK,oBAAoB,YAAY,KAAK,WAAW,WAAW,WAAW,WAAW,OAAO;CACxG;CAEA,MAAM,wBAAwB,WAAkC;EAC5D,MAAM,KAAK,oBAAoB,YAAY,SAAS;CACxD;CAEA,MAAM,0BAA0B,WAAkC;EAC9D,MAAM,KAAK,oBAAoB,cAAc,SAAS;CAC1D;CAEA,MAAM,mBAAmB,KAAa,WAAmB,kBAAuC;EAC5F,MAAM,KAAK,oBAAoB,MAAM,KAAK,WAAW,gBAAgB;CACzE;CAEA,MAAM,oBAAoB,KAAmC;EACzD,OAAO,KAAK,oBAAoB,oBAAoB,GAAG;CAC3D;CAEA,MAAM,oBAAoB,KAAa,IAAyB;EAC5D,MAAM,KAAK,oBAAoB,oBAAoB,KAAK,EAAE;CAC9D;CAEA,MAAM,uBAAuB,WAAqD;EAC9E,OAAO,KAAK,oBAAoB,WAAW,SAAS;CACxD;CAEA,MAAM,mBAAmB,WAAkC;EACvD,MAAM,KAAK,oBAAoB,aAAa,SAAS;CACzD;CAEA,MAAM,8BAA8B,KAA4B;EAC5D,MAAM,KAAK,oBAAoB,iBAAiB,GAAG;CACvD;CAEA,MAAM,yBAAyB,KAA0C;EACrE,OAAO,KAAK,oBAAoB,YAAY,GAAG;CACnD;CAEA,MAAM,uBAAuB,IAAY,KAA4B;EACjE,MAAM,KAAK,oBAAoB,WAAW,IAAI,GAAG;CACrD;CAIA,MAAM,yBAAyB,KAAa,WAAmB,WAAgC;EAC3F,MAAM,KAAK,0BAA0B,YAAY,KAAK,WAAW,SAAS;CAC9E;CAEA,MAAM,4BAA4B,WAA2D;EACzF,OAAO,KAAK,0BAA0B,gBAAgB,SAAS;CACnE;CAEA,MAAM,2BAA2B,WAAkC;EAC/D,MAAM,KAAK,0BAA0B,WAAW,SAAS;CAC7D;CAEA,MAAM,oCAAoC,KAA4B;EAClE,MAAM,KAAK,0BAA0B,iBAAiB,GAAG;CAC7D;;;;;CAMA,MAAM,sBAAqC;EACvC,MAAM,KAAK,0BAA0B,cAAc;EACnD,MAAM,KAAK,sBAAsB,cAAc;EAC/C,MAAM,KAAK,oBAAoB,cAAc;CACjD;CAIA,MAAM,qBAAqB,KAAa,WAAmB,WAAgC;EACvF,MAAM,KAAK,sBAAsB,YAAY,KAAK,WAAW,SAAS;CAC1E;CAEA,MAAM,wBAAwB,WAAuD;EACjF,OAAO,KAAK,sBAAsB,gBAAgB,SAAS;CAC/D;CAEA,MAAM,uBAAuB,WAAkC;EAC3D,MAAM,KAAK,sBAAsB,WAAW,SAAS;CACzD;AACJ;;;;;;AAOA,IAAa,yBAAb,MAA8D;CAK9C;CAJZ;CACA;CAEA,YACI,IACA,eACF;EAFU,KAAA,KAAA;EAGR,KAAK,cAAc,IAAI,YAAY,IAAI,aAAa;EACpD,KAAK,kBAAkB,IAAI,wBAAwB,IAAI,aAAa;CACxE;CAIA,MAAM,WAAW,MAAyC;EACtD,OAAO,KAAK,YAAY,WAAW,IAAI;CAC3C;CAEA,MAAM,YAAY,IAAsC;EACpD,OAAO,KAAK,YAAY,YAAY,EAAE;CAC1C;CAEA,MAAM,eAAe,OAAyC;EAC1D,OAAO,KAAK,YAAY,eAAe,KAAK;CAChD;CAEA,MAAM,kBAAkB,UAAkB,YAA8C;EACpF,OAAO,KAAK,YAAY,kBAAkB,UAAU,UAAU;CAClE;CAEA,MAAM,kBAAkB,KAA0C;EAC9D,OAAO,KAAK,YAAY,kBAAkB,GAAG;CACjD;CAEA,MAAM,iBAAiB,KAAa,UAAkB,YAAoB,aAAsD;EAC5H,OAAO,KAAK,YAAY,iBAAiB,KAAK,UAAU,YAAY,WAAW;CACnF;CAEA,MAAM,mBAAmB,KAAa,UAAkB,YAAmC;EACvF,MAAM,KAAK,YAAY,mBAAmB,KAAK,UAAU,UAAU;CACvE;CAEA,MAAM,WAAW,IAAY,MAAqE;EAC9F,OAAO,KAAK,YAAY,WAAW,IAAI,IAAI;CAC/C;CAEA,MAAM,WAAW,IAA2B;EACxC,MAAM,KAAK,YAAY,WAAW,EAAE;CACxC;CAEA,MAAM,YAAiC;EACnC,OAAO,KAAK,YAAY,UAAU;CACtC;CAEA,MAAM,mBAAmB,SAA2D;EAChF,OAAO,KAAK,YAAY,mBAAmB,OAAO;CACtD;CAEA,MAAM,eAAe,IAAY,cAA4C;EACzE,MAAM,KAAK,YAAY,eAAe,IAAI,YAAY;CAC1D;CAEA,MAAM,iBAAiB,IAAY,UAAkC;EACjE,MAAM,KAAK,YAAY,iBAAiB,IAAI,QAAQ;CACxD;CAEA,MAAM,qBAAqB,IAAY,OAAqC;EACxE,MAAM,KAAK,YAAY,qBAAqB,IAAI,KAAK;CACzD;CAEA,MAAM,2BAA2B,OAAyC;EACtE,OAAO,KAAK,YAAY,2BAA2B,KAAK;CAC5D;CAEA,MAAM,eAAe,KAAgC;EACjD,OAAO,KAAK,YAAY,eAAe,GAAG;CAC9C;CAEA,MAAM,aAAa,KAAa,SAAkC;EAC9D,MAAM,KAAK,YAAY,aAAa,KAAK,OAAO;CACpD;CAEA,MAAM,kBAAkB,KAAa,QAA+B;EAChE,MAAM,KAAK,YAAY,kBAAkB,KAAK,MAAM;CACxD;CAEA,MAAM,iBAAiB,KAAkE;EACrF,OAAO,KAAK,YAAY,iBAAiB,GAAG;CAChD;CAEA,MAAM,sBAAsB,KAAa,WAAwD;EAC7F,OAAO,KAAK,YAAY,sBAAsB,KAAK,SAAS;CAChE;CAEA,MAAM,gBAAgB,KAAa,UAAkC;EACjE,MAAM,KAAK,YAAY,gBAAgB,KAAK,QAAQ;CACxD;CAEA,MAAM,sBAAsB,KAAa,QAAoE;EACzG,MAAM,KAAK,YAAY,sBAAsB,KAAK,MAAM;CAC5D;CAEA,MAAM,sBAAsB,KAAiD;EACzE,OAAO,KAAK,YAAY,sBAAsB,GAAG;CACrD;CAEA,MAAM,uBAAuB,WAAmF;EAC5G,OAAO,KAAK,YAAY,uBAAuB,SAAS;CAC5D;CAEA,MAAM,wBAAwB,KAAa,WAA6C;EACpF,OAAO,KAAK,YAAY,wBAAwB,KAAK,SAAS;CAClE;CAIA,MAAM,mBAAmB,KAAa,WAAmB,WAAiB,WAAoB,WAAoB,SAAiD;EAC/J,OAAO,KAAK,gBAAgB,mBAAmB,KAAK,WAAW,WAAW,WAAW,WAAW,OAAO;CAC3G;CAEA,MAAM,wBAAwB,WAAkC;EAC5D,MAAM,KAAK,gBAAgB,wBAAwB,SAAS;CAChE;CAEA,MAAM,0BAA0B,WAAkC;EAC9D,MAAM,KAAK,gBAAgB,0BAA0B,SAAS;CAClE;CAEA,MAAM,mBAAmB,KAAa,WAAmB,kBAAuC;EAC5F,MAAM,KAAK,gBAAgB,mBAAmB,KAAK,WAAW,gBAAgB;CAClF;CAEA,MAAM,oBAAoB,KAAmC;EACzD,OAAO,KAAK,gBAAgB,oBAAoB,GAAG;CACvD;CAEA,MAAM,oBAAoB,KAAa,IAAyB;EAC5D,MAAM,KAAK,gBAAgB,oBAAoB,KAAK,EAAE;CAC1D;CAEA,MAAM,uBAAuB,WAAqD;EAC9E,OAAO,KAAK,gBAAgB,uBAAuB,SAAS;CAChE;CAEA,MAAM,mBAAmB,WAAkC;EACvD,MAAM,KAAK,gBAAgB,mBAAmB,SAAS;CAC3D;CAEA,MAAM,8BAA8B,KAA4B;EAC5D,MAAM,KAAK,gBAAgB,8BAA8B,GAAG;CAChE;CAEA,MAAM,yBAAyB,KAA0C;EACrE,OAAO,KAAK,gBAAgB,yBAAyB,GAAG;CAC5D;CAEA,MAAM,uBAAuB,IAAY,KAA4B;EACjE,MAAM,KAAK,gBAAgB,uBAAuB,IAAI,GAAG;CAC7D;CAEA,MAAM,yBAAyB,KAAa,WAAmB,WAAgC;EAC3F,MAAM,KAAK,gBAAgB,yBAAyB,KAAK,WAAW,SAAS;CACjF;CAEA,MAAM,4BAA4B,WAA2D;EACzF,OAAO,KAAK,gBAAgB,4BAA4B,SAAS;CACrE;CAEA,MAAM,2BAA2B,WAAkC;EAC/D,MAAM,KAAK,gBAAgB,2BAA2B,SAAS;CACnE;CAEA,MAAM,oCAAoC,KAA4B;EAClE,MAAM,KAAK,gBAAgB,oCAAoC,GAAG;CACtE;CAEA,MAAM,sBAAqC;EACvC,MAAM,KAAK,gBAAgB,oBAAoB;EAC/C,MAAM,KAAK,cAAc,CAAC,CAAC,wBAAwB;CACvD;CAIA,MAAM,qBAAqB,KAAa,WAAmB,WAAgC;EACvF,MAAM,KAAK,gBAAgB,qBAAqB,KAAK,WAAW,SAAS;CAC7E;CAEA,MAAM,wBAAwB,WAAuD;EACjF,OAAO,KAAK,gBAAgB,wBAAwB,SAAS;CACjE;CAEA,MAAM,uBAAuB,WAAkC;EAC3D,MAAM,KAAK,gBAAgB,uBAAuB,SAAS;CAC/D;CAIA,cAAyC;CACzC,gBAAoC;EAChC,IAAI,CAAC,KAAK,aACN,KAAK,cAAc,IAAI,WAAW,KAAK,EAAE;EAE7C,OAAO,KAAK;CAChB;CAEA,MAAM,gBAAgB,KAAa,YAAoB,iBAAyB,cAA2C;EACvH,OAAO,KAAK,cAAc,CAAC,CAAC,gBAAgB,KAAK,YAAY,iBAAiB,YAAY;CAC9F;CAEA,MAAM,cAAc,KAAmC;EACnD,OAAO,KAAK,cAAc,CAAC,CAAC,cAAc,GAAG;CACjD;CAEA,MAAM,iBAAiB,UAA6E;EAChG,OAAO,KAAK,cAAc,CAAC,CAAC,iBAAiB,QAAQ;CACzD;CAEA,MAAM,gBAAgB,UAAiC;EACnD,OAAO,KAAK,cAAc,CAAC,CAAC,gBAAgB,QAAQ;CACxD;CAEA,MAAM,sBAAsB,UAAkB,iBAAwC;EAClF,OAAO,KAAK,cAAc,CAAC,CAAC,sBAAsB,UAAU,eAAe;CAC/E;CAEA,MAAM,gBAAgB,UAAkB,KAA4B;EAChE,OAAO,KAAK,cAAc,CAAC,CAAC,gBAAgB,UAAU,GAAG;CAC7D;CAEA,MAAM,mBAAmB,UAAkB,WAA+C;EACtF,OAAO,KAAK,cAAc,CAAC,CAAC,mBAAmB,UAAU,SAAS;CACtE;CAEA,MAAM,oBAAoB,aAAuD;EAC7E,OAAO,KAAK,cAAc,CAAC,CAAC,oBAAoB,WAAW;CAC/D;CAEA,MAAM,mBAAmB,aAAoC;EACzD,OAAO,KAAK,cAAc,CAAC,CAAC,mBAAmB,WAAW;CAC9D;CAEA,MAAM,oBAAoB,KAAa,YAAqC;EACxE,OAAO,KAAK,cAAc,CAAC,CAAC,oBAAoB,KAAK,UAAU;CACnE;CAEA,MAAM,gBAAgB,KAAa,UAAoC;EACnE,OAAO,KAAK,cAAc,CAAC,CAAC,gBAAgB,KAAK,QAAQ;CAC7D;CAEA,MAAM,2BAA2B,KAA8B;EAC3D,OAAO,KAAK,cAAc,CAAC,CAAC,2BAA2B,GAAG;CAC9D;CAEA,MAAM,uBAAuB,KAA4B;EACrD,OAAO,KAAK,cAAc,CAAC,CAAC,uBAAuB,GAAG;CAC1D;CAEA,MAAM,sBAAsB,KAA+B;EACvD,OAAO,KAAK,cAAc,CAAC,CAAC,sBAAsB,GAAG;CACzD;CAEA,MAAM,sBAAsB,UAAkB,SAAmC;EAC7E,OAAO,KAAK,cAAc,CAAC,CAAC,sBAAsB,UAAU,OAAO;CACvE;CAEA,MAAM,0BAA0B,aAAsC;EAClE,OAAO,KAAK,cAAc,CAAC,CAAC,0BAA0B,WAAW;CACrE;AACJ;;;;;AAUA,IAAa,aAAb,MAAiD;CACzB;CAA4B;CAAhD,YAAY,IAA4B,aAAqB,UAAU;EAAnD,KAAA,KAAA;EAA4B,KAAA,aAAA;CAAwB;CAExE,QAAgB,WAA2B;EACvC,OAAO,IAAI,KAAK,WAAW,KAAK,UAAU;CAC9C;CAEA,MAAM,gBACF,KACA,YACA,iBACA,cACkB;EAClB,MAAM,YAAY,KAAK,QAAQ,aAAa;EAO5C,MAAM,OAAM,MANS,KAAK,GAAG,QAAQ,GAAG;0BACtB,IAAI,IAAI,SAAS,EAAE;sBACvB,IAAI,IAAI,WAAW,IAAI,gBAAgB,IAAI,gBAAgB,KAAK;;SAE7E,EAAA,CAEkB,KAAK;EACxB,OAAO;GACH,IAAI,IAAI;GACR,KAAK,IAAI;GACT,YAAY,IAAI;GAChB,cAAe,IAAI,iBAAmC,KAAA;GACtD,UAAU,IAAI;GACd,WAAW,IAAI,KAAK,IAAI,UAAoB;GAC5C,WAAW,IAAI,KAAK,IAAI,UAAoB;EAChD;CACJ;CAEA,MAAM,cAAc,KAAmC;EACnD,MAAM,YAAY,KAAK,QAAQ,aAAa;EAQ5C,QAAQ,MAPa,KAAK,GAAG,QAAQ,GAAG;;mBAE7B,IAAI,IAAI,SAAS,EAAE;0BACZ,IAAI;;SAErB,EAAA,CAEc,KAAwC,KAAI,SAAQ;GAC/D,IAAI,IAAI;GACR,KAAK,IAAI;GACT,YAAY,IAAI;GAChB,cAAe,IAAI,iBAAmC,KAAA;GACtD,UAAU,IAAI;GACd,WAAW,IAAI,KAAK,IAAI,UAAoB;GAC5C,WAAW,IAAI,KAAK,IAAI,UAAoB;EAChD,EAAE;CACN;CAEA,MAAM,iBAAiB,UAA6E;EAChG,MAAM,YAAY,KAAK,QAAQ,aAAa;EAC5C,MAAM,SAAS,MAAM,KAAK,GAAG,QAAQ,GAAG;;mBAE7B,IAAI,IAAI,SAAS,EAAE;yBACb,SAAS;SACzB;EAED,IAAI,OAAO,KAAK,WAAW,GAAG,OAAO;EAErC,MAAM,MAAM,OAAO,KAAK;EACxB,OAAO;GACH,IAAI,IAAI;GACR,KAAK,IAAI;GACT,YAAY,IAAI;GAChB,iBAAiB,IAAI;GACrB,cAAe,IAAI,iBAAmC,KAAA;GACtD,UAAU,IAAI;GAEd,iBAAiB,IAAI,sBAAsB,QAAQ,IAAI,sBAAsB,KAAA,IACvE,OACA,OAAO,IAAI,iBAAiB;GAClC,WAAW,IAAI,KAAK,IAAI,UAAoB;GAC5C,WAAW,IAAI,KAAK,IAAI,UAAoB;EAChD;CACJ;;;;;;;;;CAUA,MAAM,sBAAsB,UAAkB,SAAmC;EAC7E,MAAM,YAAY,KAAK,QAAQ,aAAa;EAS5C,QAAO,MARc,KAAK,GAAG,QAAQ,GAAG;qBAC3B,IAAI,IAAI,SAAS,EAAE;sCACF,QAAQ;yBACrB,SAAS;sEACoC,QAAQ;;SAErE,EAAA,CAEa,KAAK,SAAS;CAChC;CAEA,MAAM,gBAAgB,UAAiC;EACnD,MAAM,YAAY,KAAK,QAAQ,aAAa;EAC5C,MAAM,KAAK,GAAG,QAAQ,GAAG;qBACZ,IAAI,IAAI,SAAS,EAAE;;yBAEf,SAAS;SACzB;CACL;CAEA,MAAM,sBAAsB,UAAkB,iBAAwC;EAClF,MAAM,YAAY,KAAK,QAAQ,aAAa;EAC5C,MAAM,KAAK,GAAG,QAAQ,GAAG;qBACZ,IAAI,IAAI,SAAS,EAAE;qCACH,gBAAgB;yBAC5B,SAAS;SACzB;CACL;CAEA,MAAM,gBAAgB,UAAkB,KAA4B;EAChE,MAAM,YAAY,KAAK,QAAQ,aAAa;EAC5C,MAAM,KAAK,GAAG,QAAQ,GAAG;0BACP,IAAI,IAAI,SAAS,EAAE;yBACpB,SAAS,aAAa,IAAI;SAC1C;CACL;CAEA,MAAM,mBAAmB,UAAkB,WAA+C;EACtF,MAAM,YAAY,KAAK,QAAQ,gBAAgB;EAE/C,MAAM,YAAY,IAAI,KAAK,KAAK,IAAI,IAAI,MAAS,GAAI;EAOrD,MAAM,OAAM,MANS,KAAK,GAAG,QAAQ,GAAG;0BACtB,IAAI,IAAI,SAAS,EAAE;sBACvB,SAAS,IAAI,aAAa,KAAK,IAAI,UAAU;;SAE1D,EAAA,CAEkB,KAAK;EACxB,OAAO;GACH,IAAI,IAAI;GACR,UAAU,IAAI;GACd,WAAW,IAAI,KAAK,IAAI,UAAoB;GAC5C,YAAY,IAAI,cAAc,IAAI,KAAK,IAAI,WAAqB,IAAI,KAAA;GACpE,WAAY,IAAI,cAAgC,KAAA;EACpD;CACJ;CAEA,MAAM,oBAAoB,aAAuD;EAC7E,MAAM,YAAY,KAAK,QAAQ,gBAAgB;EAC/C,MAAM,SAAS,MAAM,KAAK,GAAG,QAAQ,GAAG;;mBAE7B,IAAI,IAAI,SAAS,EAAE;yBACb,YAAY;SAC5B;EAED,IAAI,OAAO,KAAK,WAAW,GAAG,OAAO;EAErC,MAAM,MAAM,OAAO,KAAK;EACxB,OAAO;GACH,IAAI,IAAI;GACR,UAAU,IAAI;GACd,WAAW,IAAI,KAAK,IAAI,UAAoB;GAC5C,YAAY,IAAI,cAAc,IAAI,KAAK,IAAI,WAAqB,IAAI,KAAA;GACpE,WAAY,IAAI,cAAgC,KAAA;GAChD,UAAU,OAAO,IAAI,YAAY,CAAC;EACtC;CACJ;;;;;;;;;;CAWA,MAAM,0BAA0B,aAAsC;EAClE,MAAM,YAAY,KAAK,QAAQ,gBAAgB;EAC/C,MAAM,SAAS,MAAM,KAAK,GAAG,QAAQ,GAAG;qBAC3B,IAAI,IAAI,SAAS,EAAE;;yBAEf,YAAY;;SAE5B;EAED,IAAI,OAAO,KAAK,WAAW,GAAG,OAAO;EACrC,OAAO,OAAQ,OAAO,KAAK,EAAE,CAAmC,QAAQ;CAC5E;;CAGA,MAAM,0BAAyC;EAC3C,MAAM,YAAY,KAAK,QAAQ,gBAAgB;EAC/C,MAAM,KAAK,GAAG,QAAQ,GAAG;0BACP,IAAI,IAAI,SAAS,EAAE;;SAEpC;CACL;CAEA,MAAM,mBAAmB,aAAoC;EACzD,MAAM,YAAY,KAAK,QAAQ,gBAAgB;EAC/C,MAAM,KAAK,GAAG,QAAQ,GAAG;qBACZ,IAAI,IAAI,SAAS,EAAE;;yBAEf,YAAY;SAC5B;CACL;CAEA,MAAM,oBAAoB,KAAa,YAAqC;EACxE,MAAM,YAAY,KAAK,QAAQ,gBAAgB;EAE/C,MAAM,KAAK,GAAG,QAAQ,GAAG;0BACP,IAAI,IAAI,SAAS,EAAE,eAAe,IAAI;SACvD;EAGD,KAAK,MAAM,QAAQ,YACf,MAAM,KAAK,GAAG,QAAQ,GAAG;8BACP,IAAI,IAAI,SAAS,EAAE;0BACvB,IAAI,IAAI,KAAK;aAC1B;CAET;CAEA,MAAM,gBAAgB,KAAa,UAAoC;EACnE,MAAM,YAAY,KAAK,QAAQ,gBAAgB;EAQ/C,QAAO,MAPc,KAAK,GAAG,QAAQ,GAAG;qBAC3B,IAAI,IAAI,SAAS,EAAE;;0BAEd,IAAI,mBAAmB,SAAS;;SAEjD,EAAA,CAEa,KAAK,SAAS;CAChC;CAEA,MAAM,2BAA2B,KAA8B;EAC3D,MAAM,YAAY,KAAK,QAAQ,gBAAgB;EAM/C,QAAQ,MALa,KAAK,GAAG,QAAQ,GAAG;iDACC,IAAI,IAAI,SAAS,EAAE;0BAC1C,IAAI;SACrB,EAAA,CAEc,KAAK,EAAE,CAAuB;CACjD;CAEA,MAAM,uBAAuB,KAA4B;EACrD,MAAM,YAAY,KAAK,QAAQ,gBAAgB;EAC/C,MAAM,KAAK,GAAG,QAAQ,GAAG;0BACP,IAAI,IAAI,SAAS,EAAE,eAAe,IAAI;SACvD;CACL;CAEA,MAAM,sBAAsB,KAA+B;EACvD,MAAM,YAAY,KAAK,QAAQ,aAAa;EAM5C,QAAQ,MALa,KAAK,GAAG,QAAQ,GAAG;iDACC,IAAI,IAAI,SAAS,EAAE;0BAC1C,IAAI;SACrB,EAAA,CAEc,KAAK,EAAE,CAAuB,QAAQ;CACzD;AACJ;;;;AC33DA,IAAa,0BAA0B;AACvC,IAAM,2BAA2B,GAAG,wBAAwB;;;;;;;;;;AAW5D,eAAsB,yBAAyB,IAAsC;CACjF,OAAO,MAAM,kCAAkC;CAE/C,IAAI;EAEA,MAAM,GAAG,QAAQ,GAAG,oCAAoC;EAExD,MAAM,GAAG,QAAQ,GAAG;;;;;;;;;;;;SAYnB;EAED,MAAM,GAAG,QAAQ,GAAG;;;SAGnB;EAED,MAAM,GAAG,QAAQ,GAAG;;;SAGnB;EAMD,MAAM,GAAG,QAAQ,IAAI,IAAI,uBAAuB,UAAU,gBAAgB,CAAC,CAAC;EAE5E,OAAO,MAAM,8BAA8B;CAC/C,SAAS,OAAO;EACZ,OAAO,MAAM,wCAAwC,EAAS,MAAM,CAAC;EACrE,OAAO,KAAK,+CAA+C;EAC3D,OAAO;CACX;CAQA,IAAI;EACA,MAAM,GAAG,QAAQ,IAAI,IAAI;yCACQ,wBAAwB;;;;;;;;;;;;SAYxD,CAAC;EACF,MAAM,GAAG,QAAQ,IAAI,IAAI,0BAA0B,yBAAyB,aAAa,CAAC;EAC1F,MAAM,GAAG,QAAQ,IAAI,IAAI;;qEAEoC,mBAAiB;gDACtC,yBAAyB,MAAM,mBAAiB;;;SAGvF,CAAC;EACF,OAAO;CACX,SAAS,OAAO;EACZ,OAAO,KACH,MAAM,wBAAwB,qGAE9B,EAAE,MAAM,CACZ;EACA,OAAO;CACX;AACJ;;;AC9EA,IAAM,oBAA4C;CAC9C,YAAY;CACZ,SAAS;AACb;;;;;AAMA,IAAa,iBAAb,MAA4B;CAWZ;CAVZ;;;;;;CAOA;CAEA,YACI,IACA,WACA,SACF;EAHU,KAAA,KAAA;EAIR,KAAK,YAAY;GAAE,GAAG;GAC9B,GAAG;EAAU;EACL,KAAK,gBAAgB,SAAS,kBAAkB;CACpD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkCA,MAAM,cAAc,QAA4C;EAC5D,MAAM,EACF,WACA,IACA,QACA,QACA,gBACA,cACA;EAEJ,MAAM,gBAAgB,kBAAkB,SAClC,kBAAkB,gBAAgB,MAAM,IACxC;EAKN,IAAI,WAAW,aAAa,CAAC,iBAAiB,cAAc,WAAW,IACnE;EAGJ,IAAI;GAGA,MAAM,QAAQ,KAAK,gBAAgB,kBAAkB,IAAI,KAAA;GACzD,IAAI,OAAO,IACP,MAAM,MAAM,MAAM,MAAM,GAAG,QAAQ,GAAG;6BACzB,IAAI,IAAI,uBAAuB,EAAE;0BACpC,UAAU;0BACV,OAAO,EAAE,EAAE;0BACX,OAAO;0BACP,gBAAgB,GAAG,SAAS,IAAI,KAAK,cAAc,KAAI,MAAK,GAAG,GAAG,GAAG,GAAG,GAAG,IAAI,EAAE,aAAa,GAAG,eAAe;0BAChH,SAAS,GAAG,GAAG,KAAK,UAAU,MAAM,EAAE,WAAW,GAAG,cAAc;0BAClE,iBAAiB,GAAG,GAAG,KAAK,UAAU,cAAc,EAAE,WAAW,GAAG,cAAc;0BAClF,aAAa,KAAK;;iBAE3B,CAAC;QACC,MAAM,KAAK,GAAG,QAAQ,GAAG;;;;sBAItB,UAAU;sBACV,OAAO,EAAE,EAAE;sBACX,OAAO;sBACP,gBAAgB,GAAG,SAAS,IAAI,KAAK,cAAc,KAAI,MAAK,GAAG,GAAG,GAAG,GAAG,GAAG,IAAI,EAAE,aAAa,GAAG,OAAO;sBACxG,SAAS,GAAG,GAAG,KAAK,UAAU,MAAM,EAAE,WAAW,GAAG,OAAO;sBAC3D,iBAAiB,GAAG,GAAG,KAAK,UAAU,cAAc,EAAE,WAAW,GAAG,OAAO;sBAC3E,aAAa,KAAK;;aAE3B;EACL,SAAS,OAAO;GACZ,OAAO,MAAM,gCAAgC,EAAE,MAAM,CAAC;GAKtD,MAAM,IAAI,MACN,2CAA2C,UAAU,KAAK,OAAO,EAAE,EAAE,2LAIrE,EAAE,OAAO,MAAM,CACnB;EACJ;EAKA,KAAK,YAAY,WAAW,EAAE,CAAC,CAAC,OAAM,QAClC,OAAO,MAAM,wBAAwB,EAAE,OAAO,IAAI,CAAC,CACvD;CACJ;;;;CAKA,MAAM,aACF,WACA,IACA,UAA+B,CAAC,GACgB;EAChD,MAAM,QAAQ,QAAQ,SAAS;EAC/B,MAAM,SAAS,QAAQ,UAAU;EAEjC,MAAM,CAAC,aAAa,cAAc,MAAM,QAAQ,IAAI,CAChD,KAAK,GAAG,QAAQ,GAAG;;;qCAGM,UAAU;oCACX,OAAO,EAAE,EAAE;aAClC,GACD,KAAK,GAAG,QAAQ,GAAG;;;;qCAIM,UAAU;oCACX,OAAO,EAAE,EAAE;;wBAEvB,MAAM;yBACL,OAAO;aACnB,CACL,CAAC;EAED,MAAM,QAAQ,SACT,YAAY,KAAK,EAAE,EAA6B,SAAS,KAC1D,EACJ;EAEA,OAAO;GACH,MAAM,QAAsB,UAAU;GACtC;EACJ;CACJ;;;;CAKA,MAAM,kBAAkB,WAAiD;EAQrE,OAAO,YAA0B,MAPZ,KAAK,GAAG,QAAQ,GAAG;;;;yBAIvB,UAAU;SAC1B,CAEsC,KAAK;CAChD;;;;CAOA,MAAM,YAAY,WAAmB,IAA6B;EAC9D,IAAI,UAAU;EAGd,MAAM,YAAY,MAAM,KAAK,GAAG,QAAQ,GAAG;;iCAElB,UAAU;gCACX,OAAO,EAAE,EAAE;+DACoB,KAAK,UAAU,QAAQ;SAC7E;EACD,WAAW,UAAU,YAAY;EAGjC,MAAM,YAAY,MAAM,KAAK,GAAG,QAAQ,GAAG;;;;qCAId,UAAU;oCACX,OAAO,EAAE,EAAE;;yBAEtB,KAAK,UAAU,WAAW;;SAE1C;EACD,WAAW,UAAU,YAAY;EAEjC,OAAO;CACX;;;;;CAMA,MAAM,eAAgC;EAKlC,QAAO,MAJc,KAAK,GAAG,QAAQ,GAAG;;+DAEe,KAAK,UAAU,QAAQ;SAC7E,EAAA,CACa,YAAY;CAC9B;AACJ;;;;;AAOA,SAAS,UAAU,GAAY,GAAqB;CAChD,IAAI,MAAM,GAAG,OAAO;CACpB,IAAI,KAAK,QAAQ,KAAK,MAAM,OAAO;CACnC,IAAI,aAAa,QAAQ,aAAa,MAAM,OAAO,EAAE,QAAQ,MAAM,EAAE,QAAQ;CAC7E,IAAI,MAAM,QAAQ,CAAC,KAAK,MAAM,QAAQ,CAAC,GAAG;EACtC,IAAI,EAAE,WAAW,EAAE,QAAQ,OAAO;EAClC,OAAO,EAAE,OAAO,GAAG,MAAM,UAAU,GAAG,EAAE,EAAE,CAAC;CAC/C;CACA,IAAI,OAAO,MAAM,YAAY,OAAO,MAAM,UAAU;EAChD,MAAM,OAAO;EACb,MAAM,OAAO;EACb,MAAM,QAAQ,OAAO,KAAK,IAAI;EAC9B,MAAM,QAAQ,OAAO,KAAK,IAAI;EAC9B,IAAI,MAAM,WAAW,MAAM,QAAQ,OAAO;EAC1C,OAAO,MAAM,OAAM,MAAK,UAAU,KAAK,IAAI,KAAK,EAAE,CAAC;CACvD;CACA,OAAO;AACX;;;;AAKA,SAAgB,kBACZ,WACA,WACe;CACf,MAAM,UAAoB,CAAC;CAC3B,MAAM,0BAAU,IAAI,IAAI,CACpB,GAAG,OAAO,KAAK,SAAS,GACxB,GAAG,OAAO,KAAK,SAAS,CAC5B,CAAC;CAED,KAAK,MAAM,OAAO,SAAS;EACvB,MAAM,SAAS,UAAU;EACzB,MAAM,SAAS,UAAU;EAGzB,IAAI,IAAI,WAAW,IAAI,GAAG;EAE1B,IAAI,WAAW,QAEX,IACI,OAAO,WAAW,YAAY,WAAW,QACzC,OAAO,WAAW,YAAY,WAAW;OAErC,CAAC,UAAU,QAAQ,MAAM,GACzB,QAAQ,KAAK,GAAG;EAAA,OAGpB,QAAQ,KAAK,GAAG;CAG5B;CAEA,OAAO,QAAQ,SAAS,IAAI,UAAU;AAC1C;;;;;;;;;;;;;;;;;;AC1SA,SAAgB,uBAAuB,QAAuC;CAC1E,IAAI,eAAe;CAEnB,KAAK,MAAM,mBAAmB,OAAO,OAAO,MAAM,GAAG;EACjD,IAAI,EAAE,2BAA2B,UAAU;EAE3C,MAAM,UAAU,gBAAgB,eAAe;EAC/C,KAAK,MAAM,UAAU,OAAO,OAAO,OAAO,GACtC,IAAI,kBAAkB,SAAS;GAC3B,MAAM,WAAW,OAAO,mBAAmB,KAAK,MAAM;GACtD,OAAO,qBAAqB,SAAU,OAAgB;IAClD,IAAI,SAAS,MAAM,OAAO;IAC1B,OAAO,SAAS,KAA2B;GAC/C;GACA;EACJ;CAER;CAEA,IAAI,eAAe,GACf,OAAO,MAAM,qBAAqB,aAAa,iCAAiC;AAExF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACPA,SAAgB,uBAAuB,QAAuC;CAC1E,IAAI,eAAe;CAEnB,MAAM,SAAS,WAAmC;EAG9C,MAAM,SAAS;EACf,IAAI,OAAO,wBAAwB;EACnC,OAAO,yBAAyB;EAEhC,OAAO,qBAAqB,SAAU,OAAgB;GAClD,IAAI,UAAU,QAAQ,UAAU,KAAA,GAAW,OAAO;GAClD,IAAI,OAAO,UAAU,UAAU,OAAO;GACtC,MAAM,SAAS,OAAO,KAAK;GAC3B,OAAQ,OAAO,MAAM,MAAM,IAAI,QAAQ;EAC3C;EACA;CACJ;CAEA,KAAK,MAAM,mBAAmB,OAAO,OAAO,MAAM,GAAG;EACjD,IAAI,EAAE,2BAA2B,UAAU;EAE3C,KAAK,MAAM,UAAU,OAAO,OAAO,gBAAgB,eAAe,CAAC,GAAG;GAClE,IAAI,kBAAkB,WAAW;IAC7B,MAAM,MAA0B;IAChC;GACJ;GAGA,IAAI,kBAAkB,WAAW,OAAO,sBAAsB,WAC1D,MAAM,OAAO,UAA8B;EAEnD;CACJ;CAEA,IAAI,eAAe,GACf,OAAO,MAAM,eAAe,aAAa,6CAA6C;AAE9F;;;;;;;;;;;ACjBA,eAAsB,cAAc,QAAmB,UAAwD;CAC3G,MAAM,EAAE,SAAS,MAAM,OAAO,MAC1B;;;;;oDAMA,CAAC,QAAQ,CACb;CAEA,OAAO,IAAI,IACP,KAAK,KAAK,MAAM,CACZ,EAAE,OACF;EAAE,OAAO,EAAE;EAAO,YAAY,EAAE,gBAAgB;EAAM,aAAa,OAAO,EAAE,gBAAgB,CAAC;CAAE,CACnG,CAAC,CACL;AACJ;;;;;AAWA,eAAsB,iBAAiB,QAAmB,UAA+C;CACrG,MAAM,EAAE,MAAM,WAAW,MAAM,OAAO,MAClC;;;;;+BAMA,CAAC,QAAQ,CACb;CAEA,MAAM,EAAE,MAAM,YAAY,MAAM,OAAO,MACnC;;;;;;;;;;;;;;;;;;;qCAoBA,CAAC,QAAQ,CACb;CAEA,MAAM,EAAE,MAAM,eAAe,MAAM,OAAO,MACtC;;;;;;;+CAQA,CAAC,QAAQ,CACb;CAEA,MAAM,EAAE,MAAM,QAAQ,MAAM,OAAO,MAC/B;;;;;;oDAOA,CAAC,QAAQ,CACb;CAEA,MAAM,EAAE,MAAM,QAAQ,MAAM,OAAO,MAC/B;;;;;;;;;;;;6EAaA,CAAC,QAAQ,CACb;CAEA,MAAM,YAAY,eAAe,QAAQ,SAAS,KAAK,GAAG;CAC1D,OAAO;EACH;EACA,SAAS,aAAa,UAAU;EAChC,YAAY,mBAAmB,SAAS;CAC5C;AACJ;;AAGA,SAAS,UAAU,KAAkB,UAA+C;CAChF,IAAI,IAAI,UAAU,YAAY,MAAM,QAAQ,OAAO;CACnD,IAAI,aAAa,UAAU,OAAO;CAClC,OAAO;AACX;AAEA,SAAS,gBACL,MACA,SACuC;CACvC,MAAM,aAAsD,CAAC;CAC7D,MAAM,4BAAY,IAAI,IAAY;CAElC,KAAK,MAAM,OAAO,KAAK,SAAS;EAC5B,MAAM,OAAO,KAAK,IAAI,SAAS,IAAI,WAAW;EAI9C,IADa,KAAK,IAAI,MAAM,OAAO,GAAG,gBAAgB,IAAI,WACtD,KAAQ,CAAC,MAAM;EAEnB,MAAM,aAAa,QAAQ,IAAI,IAAI,QAAQ;EAC3C,MAAM,SAAS,IAAI,cAAc,kBAAkB,eAAe,KAAA;EAClE,MAAM,WAAW,IAAI,aAAa;EAClC,MAAM,WAAW,SAAS,WAAW,WAAW,WAAW,UAAU,IAAI,SAAS;EAElF,MAAM,WAAoC;GACtC,MAAM,SAAS,IAAI,WAAW;GAC9B,YAAY,IAAI;GAChB,MAAM;EACV;EAWA,MAAM,MAAM,aAAa,CAAC,UAAU,IAAI,WAAW,GAAG,IAAI,WAAW,GAAG,SAAS;EACjF,UAAU,IAAI,GAAG;EAEjB,IAAI,MACA,SAAS,OAAO,UAAU,KAAK,QAAQ;OACpC,IAAI,IAAI,gBAAgB,QAAQ,IAAI,mBAAmB,MAC1D,SAAS,aAAa,EAAE,UAAU,KAAK;EAG3C,IAAI,UAAU,YACV,SAAS,OAAO,WAAW,KAAK,WAAW;GAAE,IAAI;GAAO,OAAO,SAAS,KAAK;EAAE,EAAE;EAGrF,WAAW,OAAO;CACtB;CAEA,OAAO;AACX;;;;;;;;;;;;;;;;;;;;;;AAuBA,SAAS,eACL,MACA,aACA,kBACuC;CACvC,MAAM,YAAqD,CAAC;CAE5D,KAAK,MAAM,MAAM,KAAK,KAAK;EACvB,MAAM,aAAa,YAAY,IAAI,GAAG,kBAAkB;EACxD,IAAI,CAAC,YAAY;EAGjB,IAAI,MAAM,UAAU,GAAG,YAAY,QAAQ,QAAQ,EAAE,CAAC;EACtD,IAAI,KAAK,IAAI,SAAS,GAAG,WAAW,KAAK,QAAQ,GAAG,aAIhD,MAAM,GAAG;EAGb,UAAU,OAAO;GACb,MAAM,SAAS,GAAG;GAClB,MAAM;GACN,UAAU;IACN,MAAM;IACN,cAAc,iBAAiB,IAAI,UAAU;IAC7C,UAAU,GAAG;GACjB;EACJ;CACJ;CAEA,OAAO;AACX;;;;;;;AAQA,SAAgB,2BACZ,EAAE,WAAW,SAAS,cACtB,UAC0B;CAC1B,MAAM,8BAAc,IAAI,IAAoB;CAC5C,KAAK,MAAM,aAAa,UAAU,KAAK,GACnC,IAAI,CAAC,WAAW,IAAI,SAAS,GAAG,YAAY,IAAI,WAAW,SAAS;CAGxE,MAAM,cAA0C,CAAC;CAGjD,MAAM,mCAAmB,IAAI,IAAsC;CAEnE,KAAK,MAAM,CAAC,WAAW,SAAS,WAAW;EACvC,IAAI,WAAW,IAAI,SAAS,GAAG;EAE/B,MAAM,iBAAiB,SAAS,SAAS;EACzC,MAAM,aAAa;GACf,MAAM;GACN,cAAc,YAAY,cAAc;GACxC,MAAM;GACN,OAAO;GACP,QAAQ;GACR,MAAM,gBAAgB,SAAS;GAC/B,YAAY;IACR,GAAG,gBAAgB,MAAM,OAAO;IAChC,GAAG,eAAe,MAAM,aAAa,gBAAgB;GACzD;EAMJ;EAEA,YAAY,KAAK,UAAU;EAC3B,iBAAiB,IAAI,WAAW,UAAU;CAC9C;CAEA,OAAO;AACX;;;;;;;;;;;;;;;;;;;;;;;;;ACjQA,IAAM,mBAAsC,QAAQ,WAAW;;AAG/D,IAAM,QAAQ,WAAiD,EAC3D,gBAAgB,QACpB,CAAC;;;;;;;;;;AAWD,IAAM,WAAW,WAAiD,EAC9D,gBAAgB,WACpB,CAAC;AAED,IAAM,UAAU,WAAiD,EAC7D,gBAAgB,UACpB,CAAC;;;;;;AAOD,SAAS,aAAa,KAAsC;CACxD,OAAO,IAAI,aAAa,IAAI,YAAY,IAAI,IAAI,YAAY,IAAI,KAAA;AACpE;;;;;;;;;AAUA,SAAS,cAAc,SAAiB,MAAc,KAAuC;CACzF,QAAQ,SAAR;EACI,KAAK,QACD,OAAO,KAAK,IAAI;EACpB,KAAK,QACD,OAAO,QAAQ,IAAI;EACvB,KAAK,QACD,OAAO,SAAS,IAAI;EACxB,KAAK,QACD,OAAO,QAAQ,IAAI;EACvB,KAAK,QACD,OAAO,OAAO,MAAM,EAAE,MAAM,SAAS,CAAC;EAC1C,KAAK,UACD,OAAO,KAAK,IAAI;EACpB,KAAK,UACD,OAAO,gBAAgB,IAAI;EAC/B,KAAK;EACL,KAAK,SACD,OAAO,QAAQ,IAAI;EACvB,KAAK,QACD,OAAO,KAAK,IAAI;EACpB,KAAK,SACD,OAAO,MAAM,IAAI;EACrB,KAAK,QACD,OAAO,KAAK,IAAI;EACpB,KAAK,QACD,OAAO,KAAK,IAAI;EACpB,KAAK,UACD,OAAO,KAAK,MAAM,EAAE,cAAc,KAAK,CAAC;EAS5C,KAAK,aACD,OAAO,UAAU,MAAM,EAAE,MAAM,SAAS,CAAC;EAC7C,KAAK,eACD,OAAO,UAAU,MAAM;GAAE,cAAc;GAAM,MAAM;EAAS,CAAC;EACjE,KAAK,YACD,OAAO,SAAS,IAAI;EACxB,KAAK,SACD,OAAO,MAAM,IAAI;EACrB,KAAK,QACD,OAAO,KAAK,IAAI;EACpB,KAAK,QACD,OAAO,KAAK,IAAI;EACpB,KAAK,WACD,OAAO,QAAQ,IAAI;EACvB,KAAK,YACD,OAAO,SAAS,IAAI;EACxB,KAAK,SACD,OAAO,MAAM,IAAI;EACrB,KAAK,QACD,OAAO,KAAK,IAAI;EACpB,KAAK,YACD,OAAO,SAAS,IAAI;EACxB,KAAK,UAAU;GAGX,MAAM,aAAa,IAAI,aAAa,IAAI,YAAY,IAAI,IAAI,YAAY,KAAA;GACxE,OAAO,aAAa,OAAO,MAAM,EAAE,WAAW,CAAC,IAAI,KAAK,IAAI;EAChE;EACA,KAAK,UAAU;GACX,MAAM,SAAS,aAAa,GAAG;GAC/B,OAAO,SAAS,KAAK,MAAM,EAAE,OAAO,CAAC,IAAI,KAAK,IAAI;EACtD;EACA,KAAK,WAAW;GACZ,MAAM,SAAS,aAAa,GAAG;GAC/B,OAAO,SAAS,QAAQ,MAAM,EAAE,OAAO,CAAC,IAAI,KAAK,IAAI;EACzD;EACA,KAAK,YACD,OAAO,SAAS,IAAI;EACxB,KAAK,WACD,OAAO,QAAQ,IAAI;EACvB,SASI,OAAO,KAAK,IAAI;CACxB;AACJ;;AA0BA,IAAM,UAAU,YAAsD;AAEtE,SAAS,iBAAiB,KAAuC;CAG7D,IAAI,IAAI,SAAS,WAAW,GAAG,GAE3B,OAAO,OADS,cAAc,IAAI,SAAS,MAAM,CAAC,GAAG,IAAI,aAAa,GACxD,CAAO,CAAC,CAAC,MAAM;CAEjC,OAAO,cAAc,IAAI,UAAU,IAAI,aAAa,GAAG;AAC3D;;;;;;AAOA,SAAgB,6BACZ,WACA,eAAe,UACf,YAA+B,iBACR;CACvB,MAAM,SAAS,iBAAiB,WAAW,OAAO,SAAS,YAAY;CAGvE,MAAM,cAAe,SAAS,OAAO,MAAM,KAAK,MAAM,IAAI;CAM1D,MAAM,SAAkC,CAAC;CAEzC,KAAK,MAAM,CAAC,WAAW,SAAS,WAAW;EACvC,MAAM,UAA+C,CAAC;EACtD,MAAM,wBAAQ,IAAI,IAAoB;EAEtC,KAAK,MAAM,OAAO,KAAK,SAAS;GAC5B,IAAI,UAAU,iBAAiB,GAAG;GAElC,IAAI,IAAI,gBAAgB,MACpB,UAAU,OAAO,OAAO,CAAC,CAAC,QAAQ;GAItC,IAAI,KAAK,IAAI,WAAW,KAAK,KAAK,IAAI,OAAO,IAAI,aAC7C,UAAU,OAAO,OAAO,CAAC,CAAC,WAAW;GAMzC,IAAI,IAAI,iBAAiB,UACrB,UAAU,OAAO,OAAO,CAAC,CAAC,kBAAkB,IAAI,IAAI,IAAI,yBAAyB,EAAE,CAAC;GAGxF,MAAM,MAAM,UAAU,WAAW,IAAI,WAAW;GAChD,MAAM,IAAI,IAAI,aAAa,GAAG;GAC9B,QAAQ,OAAO;EACnB;EAGA,OAAO,aAAa,YAChB,WACA,SAHgB,KAAK,IAAI,SAAS,KAK3B,MAAM,CAAC,WAAW,EAAE,SAAS,KAAK,IAAI,KAAK,OAAO,EAAE,MAAM,IAAI,EAAE,KAAK,GAAG,EAAW,CAAC,CAAC,IACtF,KAAA,CACV;CACJ;CAEA,OAAO;AACX;;;;;;;;;;;;AAaA,SAAgB,gCACZ,WACA,QACA,YAA+B,iBACN;;CAEzB,MAAM,yBAAS,IAAI,IAAkH;;CAErI,MAAM,0BAAU,IAAI,IAA0E;CAE9F,KAAK,MAAM,CAAC,WAAW,SAAS,WAAW;EACvC,IAAI,CAAC,OAAO,YAAY;EAGxB,MAAM,cAAc,IAAI,IAAI,KAAK,QAAQ,KAAK,MAAM,UAAU,WAAW,EAAE,WAAW,CAAC,CAAC;EAExF,KAAK,MAAM,MAAM,KAAK,KAAK;GACvB,IAAI,CAAC,OAAO,GAAG,qBAAqB;GAOpC,IAAI,MAAM,UAAU,GAAG,YAAY,QAAQ,QAAQ,EAAE,CAAC;GACtD,IAAI,KAAK,IAAI,SAAS,GAAG,WAAW,KAAK,QAAQ,GAAG,aAChD,MAAM,GAAG;GAGb,IAAI,YAAY,IAAI,GAAG,GAAG;GAK1B,MAAM,eAAe,GAAG,UAAU,GAAG,GAAG;GAExC,OAAO,IAAI,WAAW,CAClB,GAAI,OAAO,IAAI,SAAS,KAAK,CAAC,GAC9B;IACI;IACA,aAAa,GAAG;IAIhB,UAAU,UAAU,WAAW,GAAG,WAAW;IAC7C,cAAc,UAAU,GAAG,oBAAoB,GAAG,mBAAmB;IACrE;GACJ,CACJ,CAAC;GAED,MAAM,UAAU;GAKhB,IAAI,IAJsB,KACrB,UAAU,IAAI,GAAG,kBAAkB,CAAC,EAAE,WAAW,CAAC,EAAA,CAC9C,KAAK,MAAM,UAAU,GAAG,oBAAoB,EAAE,WAAW,CAAC,CAE/D,CAAA,CAAc,IAAI,OAAO,GAAG;GAEhC,QAAQ,IAAI,GAAG,oBAAoB,CAC/B,GAAI,QAAQ,IAAI,GAAG,kBAAkB,KAAK,CAAC,GAC3C;IAAE,KAAK;IAAS,aAAa;IAAW;GAAa,CACzD,CAAC;EACL;CACJ;CAEA,MAAM,QAAmC,CAAC;CAE1C,KAAK,MAAM,aAAa,UAAU,KAAK,GAAG;EACtC,MAAM,QAAQ,OAAO;EACrB,MAAM,OAAO,OAAO,IAAI,SAAS,KAAK,CAAC;EACvC,MAAM,QAAQ,QAAQ,IAAI,SAAS,KAAK,CAAC;EACzC,IAAI,CAAC,SAAU,KAAK,WAAW,KAAK,MAAM,WAAW,GAAI;EAEzD,MAAM,GAAG,UAAU,cAAc,UAAU,QAAQ,EAAE,KAAK,WAAW;GACjE,MAAM,MAA+B,CAAC;GAEtC,KAAK,MAAM,OAAO,MACd,IAAI,IAAI,OAAO,IAAI,OAAO,IAAI,cAAc;IASxC,QAAQ,CAAC,gBAAgB,KAAK,CAAC,CAAC,IAAI,SAAS;IAC7C,YAAY,CAAC,gBAAgB,OAAO,IAAI,YAAY,CAAC,CAAC,IAAI,aAAa;IACvE,cAAc,IAAI;GACtB,CAAC;GAKL,KAAK,MAAM,OAAO,OAAO;IAErB,IAAI,IAAI,IAAI,MAAM;IAClB,IAAI,IAAI,OAAO,KAAK,OAAO,IAAI,cAAc,EAAE,cAAc,IAAI,aAAa,CAAC;GACnF;GAEA,OAAO;EACX,CAAC;CACL;CAEA,OAAO;AACX;;;;;;;;;;;;;;;;;;;;;;;;AClYA,SAAgB,cAAc,MAAsB;CAChD,OAAO,KAAK,SAAS,GAAG,IAAI,KAAK,MAAM,GAAG,CAAC,CAAC,IAAI,IAAK;AACzD;;;;;;;;;AAUA,SAAS,mBAAmB,YAAsC;CAC9D,KAAK,MAAM,CAAC,KAAK,SAAS,OAAO,QAAQ,WAAW,cAAc,CAAC,CAAC,GAChE,IAAI,QAAQ,OAAO,SAAS,YAAY,UAAU,QAAQ,QAAS,KAA4B,IAAI,GAC/F,OAAO;CAGf,OAAO;AACX;;;;;AAMA,SAAS,kBACL,UACA,YACM;CACN,OAAO,SAAS,YACV,kBAAkB,YAAY,SAAS,SAAS,IAChD,mBAAmB,UAAU;AACvC;;;;;;;;;;AAWA,SAAS,SAAS,OAA4B,KAAsB;CAChE,IAAI,CAAC,OAAO,OAAO,KAAA;CAKnB,MAAM,SAAS,gBAAgB,KAAK,CAAC,CAAC;CACtC,IAAI,QAAQ,OAAO;CACnB,KAAK,MAAM,UAAU,OAAO,OAAO,gBAAgB,KAAK,CAAC,GACrD,IAAK,QAA+B,SAAS,KAAK,OAAO;AAGjE;;;;;;;AAsBA,SAAgB,qCACZ,aACA,QACyB;CACzB,MAAM,uBAAO,IAAI,IAAuB;CACxC,MAAM,wBAAQ,IAAI,IAAwB;;CAE1C,MAAM,0BAAU,IAAI,IAAyB;CAE7C,MAAM,SAAS,OAAe,MAAc,SAA0B;EAClE,MAAM,OAAO,QAAQ,IAAI,KAAK,qBAAK,IAAI,IAAY;EACnD,QAAQ,IAAI,OAAO,IAAI;EACvB,MAAM,MAAM,GAAG,KAAK,IAAI;EACxB,IAAI,KAAK,IAAI,GAAG,GAAG,OAAO;EAC1B,KAAK,IAAI,GAAG;EACZ,OAAO;CACX;CAEA,MAAM,UAAU,OAAe,SAAkB,KAAK,IAAI,OAAO,CAAC,GAAI,KAAK,IAAI,KAAK,KAAK,CAAC,GAAI,IAAI,CAAC;CACnG,MAAM,WAAW,OAAe,SAAmB,MAAM,IAAI,OAAO,CAAC,GAAI,MAAM,IAAI,KAAK,KAAK,CAAC,GAAI,IAAI,CAAC;CAEvG,MAAM,0BAAU,IAAI,IAA8B;CAClD,KAAK,MAAM,cAAc,aAAa;EAClC,MAAM,QAAQ,cAAc,eAAa,UAAU,CAAC;EACpD,IAAI,SAAS,OAAO,QAAQ,QAAQ,IAAI,OAAO,UAAU;CAC7D;CAEA,KAAK,MAAM,CAAC,WAAW,eAAe,SAAS;EAC3C,MAAM,QAAQ,OAAO;EAErB,KAAK,MAAM,CAAC,aAAa,aAAa,OAAO,QAAQ,2BAA2B,UAAU,CAAC,GAAG;GAC1F,IAAI;GACJ,IAAI;IACA,SAAS,SAAS,OAAO;GAC7B,SAAS,KAAK;IAGV,OAAO,MAAM,qBAAqB,WAAW,KAAK,GAAG,YAAY,4BAA4B,EAAE,OAAO,IAAI,CAAC;IAC3G;GACJ;GAEA,MAAM,cAAc,OADI,cAAc,eAAa,MAAM,CAC9B;GAC3B,MAAM,OAAO,mBAAmB,UAAU,UAAU;GAEpD,QAAQ,SAAS,MAAjB;IACI,KAAK,aAAa;KACd,IAAI,CAAC,aAAa;KAClB,IAAI,CAAC,MAAM,WAAW,MAAM,SAAS,IAAI,GAAG;KAC5C,MAAM,WAAW,kBAAkB,YAAY,SAAS,QAAQ;KAChE,MAAM,aAAa,mBAAmB,MAAM;KAC5C,MAAM,QAAQ,SAAS,OAAO,QAAQ;KACtC,MAAM,YAAY,SAAS,aAAa,UAAU;KAMlD,IAAI,CAAC,SAAS,CAAC,WAAW;KAC1B,OAAO,WAAW;MACd,KAAK;MACL,QAAQ;MACR,QAAQ,CAAC,KAAK;MACd,YAAY,CAAC,SAAS;MACtB,cAAc;KAClB,CAAC;KACD;IACJ;IAEA,KAAK,UAAU;KACX,IAAI,CAAC,aAAa;KAClB,IAAI,CAAC,MAAM,WAAW,MAAM,SAAS,IAAI,GAAG;KAW5C,MAAM,QAAQ,SAAS,OAAO,kBAAkB,UAAU,UAAU,CAAC;KACrE,MAAM,YAAY,SAAS,aAAa,kBAAkB,QAAQ,SAAS,kBAAkB,CAAC;KAC9F,IAAI,CAAC,SAAS,CAAC,WAAW;KAC1B,OAAO,WAAW;MACd,KAAK;MACL,QAAQ;MACR,QAAQ,CAAC,KAAK;MACd,YAAY,CAAC,SAAS;MACtB,cAAc;KAClB,CAAC;KACD;IACJ;IAEA,KAAK;KACD,IAAI,CAAC,aAAa;KAClB,IAAI,CAAC,MAAM,WAAW,MAAM,SAAS,IAAI,GAAG;KAC5C,QAAQ,WAAW;MAAE,KAAK;MAAa,QAAQ;MAAa,cAAc;KAAK,CAAC;KAChF;IAGJ,KAAK,cAAc;KACf,MAAM,eAAe,cAAc,SAAS,QAAQ,KAAK;KACzD,MAAM,WAAW,OAAO;KACxB,IAAI,CAAC,UAAU;KACf,IAAI,CAAC,MAAM,WAAW,MAAM,SAAS,IAAI,GAAG;KAC5C,QAAQ,WAAW;MAAE,KAAK;MAAa,QAAQ;MAAU,cAAc;KAAK,CAAC;KAC7E,iBAAiB,UAAU,YAAY,QAAQ,cAAc,QAAQ,QAAQ,KAAK;KAClF;IACJ;IAEA,KAAK,OAGD;GACR;EACJ;CACJ;CAQA,KAAK,MAAM,CAAC,WAAW,eAAe,SAAS;EAC3C,MAAM,QAAQ,OAAO;EACrB,KAAK,MAAM,GAAG,aAAa,OAAO,QAAQ,2BAA2B,UAAU,CAAC,GAAG;GAC/E,IAAI,SAAS,SAAS,aAAa,SAAS,SAAS,UAAU;GAC/D,IAAI;GACJ,IAAI;IACA,SAAS,SAAS,OAAO;GAC7B,QAAQ;IACJ;GACJ;GACA,MAAM,kBAAkB,cAAc,eAAa,MAAM,CAAC;GAC1D,MAAM,cAAc,OAAO;GAC3B,IAAI,CAAC,aAAa;GAElB,MAAM,OAAO,mBAAmB,UAAU,UAAU;GACpD,IAAI,CAAC,MAAM,iBAAiB,MAAM,WAAW,GAAG;GAEhD,MAAM,QAAQ,kBAAkB,QAAQ,SAAS,kBAAkB;GACnE,MAAM,QAAQ,SAAS,aAAa,KAAK;GACzC,MAAM,YAAY,SAAS,OAAO,kBAAkB,UAAU,UAAU,CAAC;GACzE,IAAI,CAAC,SAAS,CAAC,WAAW;GAE1B,OAAO,iBAAiB;IACpB,KAAK,UAAU,gBAAgB,GAAG;IAClC,QAAQ,OAAO;IACf,QAAQ,CAAC,KAAK;IACd,YAAY,CAAC,SAAS;IACtB,cAAc;GAClB,CAAC;EACL;CACJ;CAEA,MAAM,QAAmC,CAAC;CAC1C,KAAK,MAAM,6BAAa,IAAI,IAAI,CAAC,GAAG,KAAK,KAAK,GAAG,GAAG,MAAM,KAAK,CAAC,CAAC,GAAG;EAChE,MAAM,QAAQ,OAAO;EACrB,IAAI,CAAC,OAAO;EACZ,MAAM,YAAY,KAAK,IAAI,SAAS,KAAK,CAAC;EAC1C,MAAM,aAAa,MAAM,IAAI,SAAS,KAAK,CAAC;EAE5C,MAAM,GAAG,UAAU,cAAc,UAAU,QAAQ,EAAE,KAAK,WAAW;GACjE,MAAM,MAA+B,CAAC;GACtC,KAAK,MAAM,QAAQ,WACf,IAAI,KAAK,OAAO,IAAI,KAAK,QAAQ;IAC7B,QAAQ,KAAK;IACb,YAAY,KAAK;IACjB,cAAc,KAAK;GACvB,CAAC;GAEL,KAAK,MAAM,QAAQ,YAAY;IAC3B,IAAI,IAAI,KAAK,MAAM;IACnB,IAAI,KAAK,OAAO,KAAK,KAAK,QAAQ,EAAE,cAAc,KAAK,aAAa,CAAC;GACzE;GACA,OAAO;EACX,CAAC;CACL;CAEA,OAAO;AACX;;;;;;;;;;AAWA,SAAS,iBACL,UACA,kBACA,kBACA,cACA,QACA,QACA,OACI;CACJ,MAAM,WAAW,OAAO;CACxB,MAAM,cAAc,OAAO,cAAc,eAAa,gBAAgB,CAAC;CACvE,MAAM,cAAc,OAAO,cAAc,eAAa,gBAAgB,CAAC;CACvE,IAAI,CAAC,YAAY,CAAC,eAAe,CAAC,aAAa;CAE/C,MAAM,aAAa,mBAAmB,UAAU,gBAAgB;CAKhE,MAAM,UAAU,OAAO,OAAO,2BAA2B,gBAAgB,CAAC,CAAC,CACtE,MAAK,MAAK,EAAE,SAAS,gBACf,cAAc,EAAE,QAAQ,KAAK,MAAM,YAAY;CAC1D,MAAM,aAAa,UACb,mBAAmB,SAAS,gBAAgB,IAC5C,GAAG,aAAa,GAAG,SAAS,QAAQ;CAE1C,MAAM,QAA+D,CACjE;EAAC,SAAS,QAAQ;EAAc;EAAa;EAAY;EAAkB;CAAQ,GACnF;EAAC,SAAS,QAAQ;EAAc;EAAa;EAAY;EAAkB;CAAQ,CACvF;CAEA,KAAK,MAAM,CAAC,QAAQ,eAAe,cAAc,uBAAuB,OAAO;EAC3E,IAAI,CAAC,MAAM,cAAc,cAAc,UAAU,GAAG;EACpD,MAAM,QAAQ,SAAS,UAAU,MAAM;EACvC,MAAM,YAAY,SAAS,eAAe,mBAAmB,kBAAkB,CAAC;EAChF,IAAI,CAAC,SAAS,CAAC,WAAW;EAC1B,OAAO,cAAc;GACjB,KAAK;GACL,QAAQ;GACR,QAAQ,CAAC,KAAK;GACd,YAAY,CAAC,SAAS;GACtB;EACJ,CAAC;CACL;AACJ;;;;AClTA,SAAgB,mBAAmB,YAA8B,UAA0B;CACvF,MAAM,WAAY,WAAoC;CACtD,OAAO,OAAO,aAAa,YAAY,SAAS,SAAS,IAAI,WAAW;AAC5E;;;;;;;;;;;;;;;;;;;;;;;AAwBA,SAAgB,0BACZ,aACiB;CACjB,MAAM,0BAAU,IAAI,IAA8B;CAClD,MAAM,6BAAa,IAAI,IAAyB;CAChD,KAAK,MAAM,cAAc,aAAa;EAClC,MAAM,QAAQ,cAAc,eAAa,UAAU,CAAC;EACpD,IAAI,CAAC,OAAO;EACZ,QAAQ,IAAI,OAAO,UAAU;EAC7B,WAAW,IAAI,OAAO,IAAI,IAAI,kBAAkB,UAAU,CAAC,CAAC;CAChE;CAIA,MAAM,kCAAkB,IAAI,IAA8B;CAC1D,KAAK,MAAM,QAAQ,qBAAqB,WAAW,CAAC,CAAC,OAAO,GAAG;EAC3D,IAAI,OAAO,KAAK,KAAK,UAAU,CAAC,CAAC,WAAW,GAAG;EAC/C,gBAAgB,IAAI,cAAc,KAAK,KAAK,GAAG,4BAA4B,IAAI,CAAC;CACpF;CACA,QAAQ,WAAW,eAAe;EAC9B,MAAM,aAAa,QAAQ,IAAI,SAAS;EACxC,IAAI,CAAC,YAAY;GACb,MAAM,WAAW,gBAAgB,IAAI,SAAS;GAC9C,OAAO,WAAW,kBAAkB,UAAU,UAAU,IAAI;EAChE;EACA,IAAI,WAAW,IAAI,SAAS,CAAC,EAAE,IAAI,UAAU,GAAG,OAAO;EACvD,OAAO,kBAAkB,YAAY,UAAU;CACnD;AACJ;;;;;;;;;;;;;;AA0BA,eAAsB,oBAAoB,SAIb;CACzB,MAAM,EAAE,QAAQ,aAAa,kBAAkB;;CAG/C,MAAM,yBAAS,IAAI,IAAyB;CAC5C,MAAM,QAAQ,QAAgB,UAAkB;EAC5C,MAAM,MAAM,OAAO,IAAI,MAAM,qBAAK,IAAI,IAAY;EAClD,OAAO,IAAI,QAAQ,GAAG;EACtB,IAAI,IAAI,KAAK;CACjB;CAEA,KAAK,MAAM,cAAc,aAAa;EAClC,MAAM,QAAQ,cAAc,eAAa,UAAU,CAAC;EACpD,IAAI,OAAO,KAAK,mBAAmB,YAAY,aAAa,GAAG,KAAK;CACxE;CAGA,KAAK,MAAM,QAAQ,qBAAqB,WAAW,CAAC,CAAC,OAAO,GACxD,KAAK,KAAK,QAAQ,cAAc,KAAK,KAAK,CAAC;CAG/C,MAAM,YAAY,0BAA0B,WAAW;CACvD,MAAM,SAAkC,CAAC;CACzC,MAAM,uBAAO,IAAI,IAAuB;CACxC,MAAM,UAAoB,CAAC;CAE3B,KAAK,MAAM,CAAC,QAAQ,UAAU,QAAQ;EAClC,MAAM,OAAO,MAAM,iBAAiB,QAAQ,MAAM;EAClD,MAAM,0BAAU,IAAI,IAAuB;EAC3C,KAAK,MAAM,QAAQ,OAAO;GACtB,MAAM,QAAQ,KAAK,UAAU,IAAI,IAAI;GACrC,IAAI,CAAC,OAAO;IACR,QAAQ,KAAK,WAAW,gBAAgB,OAAO,GAAG,OAAO,GAAG,MAAM;IAClE;GACJ;GAKA,IAAI,OAAO,OAAO;GAClB,QAAQ,IAAI,MAAM,KAAK;GACvB,KAAK,IAAI,MAAM,KAAK;EACxB;EACA,OAAO,OAAO,QAAQ,6BAA6B,SAAS,QAAQ,SAAS,CAAC;CAClF;CAEA,OAAO;EACH;EACA,WAAW,qCAAqC,aAAa,MAAM;EACnE;EACA;CACJ;AACJ;;;;;;;;;;AAWA,SAAgB,gBACZ,WACA,cACA,aACyE;CACzE,MAAM,YAAY,0BAA0B,WAAW;CACvD,MAAM,SAAS,6BAA6B,WAAW,cAAc,SAAS;CAC9E,OAAO;EACH;EACA,WAAW,gCAAgC,WAAW,QAAQ,SAAS;CAC3E;AACJ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACrIA,SAAgB,WAAW,SAAqC;CAC5D,IAAI,CAAC,SAAS,OAAO;CACrB,IAAI,OAAO,QAAQ,KAAK,CAAC,CAAC,YAAY;CACtC,MAAM,UAAU,KAAK,SAAS,IAAI,KAAK,KAAK,WAAW,GAAG;CAC1D,OAAO,KAAK,QAAQ,SAAS,EAAE,CAAC,CAAC,QAAQ,MAAM,EAAE;CAEjD,OAAO,KAAK,QAAQ,oBAAoB,EAAE,CAAC,CAAC,KAAK;CAEjD,MAAM,SAAS,SAAS,SAAS;CACjC,OAAO,UAAU,GAAG,OAAO,MAAM;AACrC;AAEA,IAAM,WAAmC;CAErC,MAAM;CACN,SAAS;CACT,qBAAqB;CACrB,QAAQ;CACR,MAAM;CACN,WAAW;CACX,QAAQ;CACR,MAAM;CAEN,MAAM;CACN,UAAU;CACV,aAAa;CACb,MAAM;CACN,KAAK;CACL,SAAS;CACT,QAAQ;CACR,MAAM;CACN,QAAQ;CACR,WAAW;CAEX,QAAQ;CACR,MAAM;CACN,QAAQ;CACR,oBAAoB;CACpB,SAAS;CACT,SAAS;CACT,OAAO;CAEP,MAAM;CACN,SAAS;CACT,MAAM;CACN,MAAM;CACN,OAAO;CACP,MAAM;CACN,MAAM;CACN,0BAA0B;CAC1B,QAAQ;CACR,uBAAuB;CACvB,WAAW;CACX,+BAA+B;CAC/B,aAAa;CACb,4BAA4B;CAC5B,UAAU;CACV,OAAO;CACP,UAAU;CACV,SAAS;CACT,QAAQ;AACZ;AASA,SAAS,iBAAiB,MAA+C;CACrE,MAAM,0BAAU,IAAI,IAA6B;CACjD,KAAK,MAAM,OAAO,KAAK,SACnB,QAAQ,IAAI,IAAI,aAAa;EACzB,MAAM,IAAI;EAIV,QAAQ,WAAW,IAAI,YAAY,IAAI,SAAS;EAChD,SAAS,IAAI,gBAAgB;CACjC,CAAC;CAEL,OAAO;AACX;;;;;;;;;;AAWA,SAAgB,oCAAoC,SAO7B;CACnB,MAAM,EAAE,WAAW,WAAW,gBAAgB;CAC9C,IAAI,CAAC,WAAW,OAAO,CAAC;CAExB,MAAM,oCAAoB,IAAI,IAA8B;CAC5D,KAAK,MAAM,cAAc,aAAa;EAClC,MAAM,QAAQ,eAAa,UAAU;EACrC,IAAI,OAAO,kBAAkB,IAAI,cAAc,KAAK,GAAG,UAAU;CACrE;CAEA,MAAM,cAAkC,CAAC;CAEzC,KAAK,MAAM,SAAS,OAAO,OAAO,SAAS,GAAG;EAC1C,IAAI,CAAC,QAAQ,KAAK,GAAG;EACrB,MAAM,QAAQ,aAAiB,KAAgB;EAC/C,MAAM,aAAa,kBAAkB,IAAI,KAAK;EAC9C,MAAM,KAAK;GAAE;GAAO,YAAY,YAAY;EAAK;EAEjD,MAAM,OAAO,UAAU,IAAI,KAAK;EAChC,IAAI,CAAC,MAAM;GAIP,IAAI,YAAY,YAAY,KAAK;IAAE,GAAG;IAAI,MAAM;GAAgB,CAAC;GACjE;EACJ;EAEA,MAAM,OAAO,iBAAiB,IAAI;EAClC,MAAM,uBAAO,IAAI,IAAY;EAgB7B,MAAM,YAAY,iBAAiB,UAAU,IACvC,IAAI,IAAI,mBAAmB,KAAI,MAAK,EAAE,MAAM,CAAC,IAC7C,KAAA;EAEN,KAAK,MAAM,CAAC,KAAK,WAAW,OAAO,QAAQ,gBAAgB,KAAgB,CAAC,GAAG;GAC3E,MAAM,OAAQ,OAA6B,QAAQ;GACnD,KAAK,IAAI,IAAI;GACb,MAAM,WAAW,aAAa,kBAAkB,YAAY,IAAI,IAAI;GACpE,MAAM,kBAAkB,WAAY,OAAyC,aAAa,CAAC;GAC3F,MAAM,mBAAmB,QAAS,OAAiC,OAAO;GAE1E,MAAM,QAAQ,KAAK,IAAI,IAAI;GAC3B,IAAI,CAAC,OAAO;IACR,YAAY,KAAK;KAAE,GAAG;KAAI,MAAM;KAAkB,QAAQ;KAAM;KAAU,WAAW;IAAgB,CAAC;IACtG;GACJ;GACA,IAAI,MAAM,WAAW,iBACjB,YAAY,KAAK;IACb,GAAG;IACH,MAAM;IACN,QAAQ;IACR;IACA,WAAW;IACX,MAAM,MAAM;GAChB,CAAC;GAEL,IAAI,MAAM,YAAY,oBAAoB,CAAC,WAAW,IAAI,IAAI,GAC1D,YAAY,KAAK;IACb,GAAG;IACH,MAAM;IACN,QAAQ;IACR;IACA,WAAW,mBAAmB,aAAa;IAC3C,MAAM,MAAM,UAAU,aAAa;GACvC,CAAC;EAET;EAEA,KAAK,MAAM,CAAC,MAAM,WAAW,MAAM;GAC/B,IAAI,KAAK,IAAI,IAAI,GAAG;GACpB,IAAI,WAAW,IAAI,IAAI,GAAG;GAC1B,YAAY,KAAK;IACb,GAAG;IACH,MAAM;IACN,QAAQ;IACR,UAAU,aAAa,kBAAkB,YAAY,IAAI,IAAI;IAC7D,MAAM,OAAO;GACjB,CAAC;EACL;CACJ;CAEA,OAAO;AACX;;AAGA,SAAgB,yBAAyB,YAAsC;CAC3E,MAAM,QAAQ,WAAW,aACnB,GAAG,WAAW,WAAW,WAAW,WAAW,MAAM,MACrD,UAAU,WAAW,MAAM;CACjC,MAAM,QAAQ,WAAW,SACnB,GAAG,WAAW,YAAY,WAAW,aAAa,WAAW,SACzD,GAAG,WAAW,SAAS,MAAM,WAAW,OAAO,KAC/C,IAAI,WAAW,OAAO,OAC1B,KAAA;CAEN,QAAQ,WAAW,MAAnB;EACI,KAAK,iBACD,OAAO,GAAG,MAAM;EACpB,KAAK,kBACD,OAAO,GAAG,MAAM,IAAI,MAAM;EAC9B,KAAK,gBACD,OAAO,GAAG,MAAM,IAAI,MAAM,sDAAsD,WAAW,KAAK;EACpG,KAAK,QACD,OAAO,GAAG,MAAM,IAAI,MAAM,MAAM,WAAW,UAAU,8BAA8B,WAAW,KAAK;EACvG,KAAK,eACD,OAAO,GAAG,MAAM,IAAI,MAAM,MAAM,WAAW,UAAU,8BAA8B,WAAW,KAAK;CAC3G;AACJ;;;;;;;;AASA,SAAgB,2BAA2B,aAAuC;CAC9E,IAAI,YAAY,WAAW,GAAG;CAE9B,OAAO,KACH,qEAAqE,YAAY,OAAO,aACrF,YAAY,WAAW,IAAI,KAAK,IAAI,qNAG3C;CAEA,KAAK,MAAM,cAAc,aACrB,OAAO,KAAK,QAAQ,yBAAyB,UAAU,KAAK;EACxD,aAAa;GACT,MAAM,WAAW;GACjB,OAAO,WAAW;GAClB,YAAY,WAAW;GACvB,QAAQ,WAAW;GACnB,UAAU,WAAW;GACrB,WAAW,WAAW;GACtB,MAAM,WAAW;EACrB;EACA,KAAK;CACT,CAAC;AAET;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC7KA,SAAgB,sBACZ,KACA,sBACM;CAEN,QADsB,6BAA6B,GAAG,IAAI,IAAI,QAAQ,KAAA,MAE/D,qBAAqB,MAAM,MAAM,MAAM,IAAI,IAAI,KAC/C,IAAI;AACf;;;;;;;;;;;;;;;;AAiBA,SAAgB,yBACZ,cACQ;CAGR,IAAI,iBAAiB,KAAA,GACjB,OAAO;EACH;EACA;EACA;EACA;CACJ;CAEJ,IAAI,aAAa,WACb,OAAO;EACH;EACA;EACA;EACA;EACA;EACA;CACJ;CAEJ,OAAO;EACH;EACA,OAAO,aAAa,UAAU;EAC9B;EACA;CACJ;AACJ;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BA,SAAgB,0BAA0B,kBAA+C;CACrF,IAAA,QAAA,IAAA,aAA6B,cAAc,OAAO;CAClD,IAAI,CAAC,kBAAkB,OAAO;CAC9B,IAAI;CACJ,IAAI;EACA,OAAO,IAAI,IAAI,gBAAgB,CAAC,CAAC;CACrC,QAAQ;EACJ,OAAO;CACX;CAEA,MAAM,OAAO,KAAK,QAAQ,YAAY,EAAE,CAAC,CAAC,YAAY;CACtD,OAAO,SAAS,eACT,SAAS,SACT,SAAS,aACT,SAAS,MACT,SAAS,KAAK,IAAI,KAClB,KAAK,SAAS,YAAY;AACrC;;;;;;;;;;;;;;;;;;;;AAqBA,IAAM,qBAAqB;;;;;;;;;AAU3B,SAAS,sBAAsB,SAAkE;CAC7F,QAAQ,QAAQ,MAAhB;EACI,KAAK,kBAKD,OACI,2DAA2D,QAAQ,OAAO,qFACN,QAAQ;EAEpF,KAAK,gBAKD,OACI,6CAA6C,QAAQ,OAAO,wJAE1C,QAAQ;EAElC,SACI,OACI,0CAA0C,QAAQ,OAAO,sGACgB,QAAQ;CAE7F;AACJ;;;;;;;;;;;;;;;;;;;;;;;;AAyBA,SAAS,uBAAuB,KAAc,kBAAyD;CACnG,MAAM,MAAM,oBAAoB;CAChC,MAAM,WAAW,cAAc,GAAG;CAElC,IAAI,eAAe,GAAG,GAAG;EACrB,OAAO,MAAM,gBAAgB,KAAK,GAAG,KAAK,qCAAqC,UAAU;EACzF,MAAM,SAAS,oBAAoB,GAAG,KAAK;EAC3C,OAAO,IAAI,MACP,mCAAmC,SAAS,IAAI,OAAO,6BACvD,EAAE,OAAO,IAAI,CACjB;CACJ;CAEA,MAAM,EAAE,OAAO,QAAQ,SAAS,uBAAuB,GAAG;CAC1D,MAAM,SAAS,OAAO,KAAK,KAAK,KAAK;CAErC,IAAI,OAAO;EACP,OAAO,MACH,gBAAgB,KAAK,GAAG,KACxB;;qBAEsB,SAAS,6FAG1B,SAAS,OAAO,+LAMzB;EACA,OAAO,IAAI,MAAM,iBAAiB,SAAS,2BAA2B,SAAS,UAAU,EAAE,OAAO,IAAI,CAAC;CAC3G;CAEA,OAAO,MAAM,wCAAwC,SAAS,IAAI,SAAS,UAAU,EAAE,OAAO,IAAI,CAAC;AAEvG;AAEA,SAAgB,2BAA2B,UAAqD;CAI5F,IAAI,SAAS,qBACT,6BAA6B,SAAS,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;CA2B7D,MAAM,kBAAkB,iBAAqC;EAEzD,QADkB,cAAc,UAAA,EACd,MAAO,SAAS;CACtC;CAEA,MAAM,yBAAyB,iBAAqC;EAChE,MAAM,KAAK,eAAe,YAAY;EACtC,IAAI,CAAC,IACD,MAAM,IAAI,MACN,kPAGJ;EAEJ,OAAO;CACX;CAEA,MAAM,yBAAyB,iBAAqC;EAChE,MAAM,KAAK,sBAAsB,YAAY;EAC7C,OAAO,EACH,MAAM,MAAS,MAAsC;GACjD,OAAO,EAAE,MAAM,QAAW,MAAM,GAAG,QAAQ,IAAI,IAAI,IAAI,CAAC,CAAC,EAAE;EAC/D,EACJ;CACJ;;;;;;;;;CAUA,MAAM,sBAAsB,OAAO,iBAAoD;EACnF,MAAM,KAAK,sBAAsB,YAAY;EAC7C,MAAM,EAAE,6BAA6B,MAAM,OAAO,kCAAA,CAAA,MAAA,MAAA,EAAA,CAAA;EAgBlD,MAAM,GAAG,YAAY,OAAO,OAAO;GAC/B,MAAM,GAAG,QAAQ,GAAG,sEAAsE;GAC1F,KAAK,MAAM,aAAa,0BACpB,MAAM,GAAG,QAAQ,IAAI,IAAI,SAAS,CAAC;EAE3C,CAAC;CACL;;;;;;;;;;;;CAaA,MAAM,iCAAiB,IAAI,IAA+C;CAE1E,MAAM,gBAAgB,OAAO,WAAwC;EACjE,KAAK,MAAM,CAAC,WAAW,EAAE,QAAQ,YAAY,gBACzC,IAAI;GACA,MAAM,OAAO,kBAAkB,QAAQ,KAAK,CAAC;EACjD,SAAS,KAAK;GACV,MAAM,IAAI,MACN,kEAAkE,UAAU,wDAC9B,iBAAiB,oMAG9D,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EACpD;EACJ;CAER;CAEA,OAAO;EACH,MAAM;EAEN,MAAM,iBAAiB,QAA6C;GAGhE,MAAM,EAAE,aAAa,oBAAoB,uBAAuB,MAAM,oBAAoB,aAAa;GAYvG,MAAM,qBAAqB,UAAU,aAAa;GAClD,MAAM,qBAAqB,UAAU,aAAa;GAElD,MAAM,oBAAoB,MAAM,qBAAqB;GAErD,MAAM,aAAa,SAAS;GAC5B,MAAM,YAAa,cAAc,OAAO,eAAe,YAAY,aAAa,aACzE,WAAuC,UACxC;GAKN,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI,0BAA0B,CAAC,eAAe,YAAY,WAAW,IAAI;IACrE,MAAM,eAAe,SAAS,uBAAuB;IACrD,MAAM,SAAS,MAAM,iBAAiB,WAAW,YAAY;IAQ7D,MAAM,YAAY,MAAM,cAAc,WAAW,YAAY;IAC7D,MAAM,cAAc,CAAC,GAAG,OAAO,UAAU,KAAK,CAAC,CAAC,CAAC,QAC5C,MAAM,CAAC,OAAO,WAAW,IAAI,CAAC,KAAK,CAAC,UAAU,IAAI,CAAC,CAAC,EAAE,UAC3D;IACA,MAAM,aAAa,CAAC,GAAG,OAAO,UAAU,KAAK,CAAC,CAAC,CAAC,QAC3C,MAAM,CAAC,OAAO,WAAW,IAAI,CAAC,KAAK,UAAU,IAAI,CAAC,CAAC,EAAE,cAAc,UAAU,IAAI,CAAC,CAAC,EAAE,gBAAgB,CAC1G;IAEA,IAAI,YAAY,SAAS,GACrB,IAAI,sBAAsB,SACtB,OAAO,KACH,oBAAoB,YAAY,OAAO,8CAA8C,YAAY,KAAK,IAAI,EAAE,8GAGhH;SACG;KACH,OAAO,KACH,wBAAwB,YAAY,OAAO,mFACnB,YAAY,KAAK,IAAI,EAAE,MAC/C,YAAY,KAAK,MAAM,sBAAsB,aAAa,KAAK,EAAE,mDAAmD,CAAC,CAAC,KAAK,IAAI,IAC/H,0EACJ;KACA,KAAK,MAAM,KAAK,aAAa,OAAO,UAAU,OAAO,CAAC;IAC1D;IAEJ,IAAI,WAAW,SAAS,GAGpB,OAAO,KACH,YAAY,WAAW,OAAO,2EAA2E,WAAW,KAAK,IAAI,GACjI;IAGJ,0BAA0B,2BAA2B,QAAQ,YAAY;IAMzE,CAAC,CAAE,QAAQ,oBAAoB,WAAW,yBACtC,gBAAgB,OAAO,WAAW,cAAc,uBAAuB;IAC3E,OAAO,KACH,4CAA4C,wBAAwB,OAAO,4BAA4B,aAAa,KAAK,wBAAwB,KAAI,MAAK,EAAE,IAAI,CAAC,CAAC,KAAK,IAAI,EAAE,EACjL;GACJ;GAsBA,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI,iBAAqC,CAAC;GAC1C,MAAM,YAAa,OAAsC,iBAAiB;GAC1E,IAAI,CAAC,2BAA2B,eAAe,YAAY,SAAS,GAAG;IACnE,iBAAiB,YAAY,QAAO,MAChC,EAAE,eAAe,aAAc,CAAC,EAAE,cAAc,cAAc,uBAClE;IACA,IAAI,eAAe,SAAS,GAAG;KAQ3B,IAAI;MACA,MAAM,UAAU,MAAM,UAAU;KACpC,SAAS,KAAK;MACV,MAAM,QAAQ,uBAAuB,KAAK,SAAS,gBAAgB;MACnE,IAAI,OAAO,MAAM;KACrB;KACA,MAAM,YAAY,MAAM,oBAAoB;MACxC,QAAQ;MACR,aAAa;MACb,eAAe,SAAS,uBAAuB;KACnD,CAAC;KACD,YAAY,UAAU;KACtB,eAAe,UAAU;KACzB,eAAe,UAAU;KACzB,IAAI,UAAU,QAAQ,SAAS,GAC3B,OAAO,KACH,mCAAmC,UAAU,0BAA0B,UAAU,QAAQ,KAAK,IAAI,EAAE,mFAExG;IAER;GACJ;GAEA,MAAM,oBAAoB,2BAA2B;GACrD,MAAM,eAAe,sBAAsB;GAC3C,MAAM,kBAAkB,yBAAyB;GAKjD,IAAI,cACA,IAAI;IACA,2BAA2B,oCAAoC;KAC3D,WAAW,SAAS,QAAQ;KAC5B,WAAW;KACX,aAAa;IACjB,CAAC,CAAC;GACN,SAAS,KAAK;IACV,OAAO,MAAM,uEAAuE,EAAE,OAAO,IAAI,CAAC;GACtG;GASJ,MAAM,WAAW,wBAAwB;IACrC,aAAa;IACb,QAAQ;IACR,WAAW;GACf,CAAC;GAKD,IAAI,cAAc;IACd,uBAAuB,YAAuC;IAI9D,uBAAuB,YAAuC;GAClE;GAGA,MAAM,eAAwC;IAC1C,GAAG;IACH,GAAI,mBAAmB,CAAC;GAC5B;GACA,MAAM,EAAE,SAAS,kBAAkB,MAAM,OAAO;GAChD,MAAM,gBAAgB,cAAc,WAAW,EAAE,QAAQ,aAAa,CAAC;GAGvE,IAAI;IACA,MAAM,cAAc,QAAQ,GAAG,UAAU;GAC7C,SAAS,KAAc;IACnB,MAAM,QAAQ,uBAAuB,KAAK,SAAS,gBAAgB;IACnE,IAAI,OAAO,MAAM;IACjB,OAAO,KAAK,gHAAgH;GAChI;GAgBA,MAAM,mBAAmB,QAAQ,uBAAuB;GACxD,MAAM,gCAAoD;IACtD,MAAM,MAAM,SAAS,eAAe;IACpC,IAAI,kBAAkB,OAAO;IAC7B,OAAO,IAAI,QAAO,MAAK,EAAE,eAAe,aAAc,CAAC,EAAE,cAAc,cAAc,uBAAwB;GACjH;GAGA,MAAM,kBAAkB,IAAI,gBAAgB,eAAe,QAAQ;GAInE,MAAM,iBAAiB,QAAQ,IAAI,uCAAuC,KAAK;GAC/E,IAAI,gBACA,gBAAgB,4BAA4B,+BACxC,gBAAgB,uCACpB;QACG,IAAI,SAAS,UAAU,8BAA8B,KAAA,GACxD,gBAAgB,4BAA4B,+BACxC,SAAS,SAAS,2BAA2B,oCACjD;GAIJ,IAAI;GACJ,MAAM,UAAU,QAAQ,IAAI;GAC5B,IAAI,WAAW,YAAY,SAAS,kBAChC,IAAI;IACA,MAAM,EAAE,gCAAgC,MAAM,OAAO,oCAAA,CAAA,MAAA,MAAA,EAAA,CAAA;IAErD,SADsB,4BAA4B,SAAS,YAClD,CAAA,CAAc;IACvB,OAAO,KAAK,+DAA+D;GAC/E,SAAS,KAAK;IACV,OAAO,KAAK,iFAAiF,EAAE,OAAO,IAAI,CAAC;GAC/G;GAEJ,MAAM,cAAc,SAAS,wBACvB,IAAI,oBAAoB,SAAS,qBAAqB,IACtD,KAAA;GACN,MAAM,SAAS,IAAI,sBAAsB,eAAe,iBAAiB,UAAU,KAAA,GAAW,WAAW;GACzG,gBAAgB,cAAc,MAAM;GAcpC;IACI,MAAM,SAAuB,OAAO,SAAS;KAEzC,QAAQ,MADU,cAAc,QAAQ,IAAI,IAAI,IAAI,CAAC,EAAA,CACzC,QAAQ,CAAC;IACzB;IAKA,MAAM,0BAA0B,MAAM;IAStC,MAAM,iBAAiB;KAAC;KAAU;KAAU,GAPlB,SAAS,eAAe,CAAC,CAC9C,KAAK,MAAO,EAA0B,MAAM,CAAC,CAC7C,QAAQ,MAAmB,OAAO,MAAM,QAKE;IAAiB;IAEhE,MAAM,UAAU,MAAM,wBAAwB,QAAQ,cAAc;IACpE,IAAI,QAAQ,YAAY;KAcpB,MAAM,cAAc,QAAQ,aAAa,QAAQ,aAAa,QAAQ;KACtE,IAAI,WAAW;KACf,IAAI;MACA,MAAM,cAAc,QAAQ,cAAc;KAC9C,SAAS,KAAK;MACV,IAAI,aAAa,MAAM;MACvB,WAAW;MACX,OAAO,KACH,wCAAwC,QAAQ,KAAK,0EACN,eAAe,KAAK,IAAI,EAAE,8GAEtD,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,GACtE;KACJ;KAEA,IAAI,UAAU;MACV,OAAO,cAAc;MACrB,gBAAgB,cAAc;MAC9B,MAAM,MAAM,QAAQ,YACd,cACA,QAAQ,YACJ,cACA,QAAQ,aACJ,gBACA;MACd,OAAO,KAAK,6DAA6D,iBAAiB,iBAAiB,QAAQ,KAAK,kBAAkB,IAAI,EAAE;KACpJ;KAEA,IAAI,QAAQ,aAAa,QAAQ,WAAW;MACxC,MAAM,UACF,mCAAmC,QAAQ,YAAY,gBAAgB,mBAAmB,KAAK,QAAQ,KAAK;MAGhH,IAAI,0BAA0B,SAAS,gBAAgB,GACnD,OAAO,MAAM,MAAM,SAAS;WAE5B,OAAO,KAAK,MAAM,SAAS;KAEnC;IACJ,OACI,OAAO,KAAK,wCAAwC,QAAQ,KAAK,qDAAqD;IAO1H,MAAM,sBACF,QACA,wBAAwB,GACxB,OAAO,eAAe,QAAQ,IAClC;IAKA,sBAAsB,wBAAwB,CAAU;IAKxD,yBAAyB,wBAAwB,CAAU;GAC/D;GAGA,IAAI,OAAO,eACP,IAAI;IACA,MAAM,OAAO,cAAc,0BAA0B;GACzD,SAAS,KAAK;IACV,OAAO,KAAK,iDAAiD,EAAE,OAAO,IAAI,CAAC;GAC/E;GAOJ,IAAI;IACA,MAAM,gBAAgB,wBAClB,SAAS,UAAU,UACnB,EAAE,WAAW,mBAAmB,CACpC;GACJ,SAAS,KAAK;IACV,OAAO,KAAK,sFAAsF,EAAE,OAAO,IAAI,CAAC;GACpH;GAIA,MAAM,YAAY,QAAQ,IAAI,uBAAuB,SAAS;GAO9D,IAAI;IACA,MAAM,aAAa,yBAAyB,SAAS,UAAU,GAAG;IAKlE,IADiB,uBAAqB,UAAU,KAAK,WAAW,SAAS,UAErE,MAAM,gBAAgB,oBAClB,iBAAiB,YAAY;KACzB,IAAI;KACJ;IACJ,CAAC,CACL;GAER,SAAS,KAAK;IACV,OAAO,KAAK,0GAA0G,EAAE,OAAO,IAAI,CAAC;GACxI;GAaA,MAAM,6BAAa,IAAI,IAAI;IAAC;IAAQ;IAAO;IAAW;GAAK,CAAC;GAC5D,IAAI,WAAW,QAAQ,IAAI,gBAAgB,OAAA,CAAQ,KAAK,CAAC,CAAC,YAAY;GACtE,IAAI,CAAC,WAAW,IAAI,OAAO,GAAG;IAC1B,OAAO,KAAK,wCAAwC,QAAQ,yDAAyD;IACrH,UAAU;GACd;GAQA,MAAM,WAAW,YAAY,UAAU,sBAAsB;GAC7D,MAAM,cAAc,YAAY,aAAa,YAAY;GACzD,IAAI,aAAa;GACjB,IAAI;GACJ,MAAM,YAA0B,OAAO,SAAS;IAE5C,QAAQ,MADU,cAAc,QAAQ,IAAI,IAAI,IAAI,CAAC,EAAA,CACzC,QAAQ,CAAC;GACzB;;GAEA,IAAI,qBAAqB;GAEzB,IAAI,YAAY,CAAC,WAAW;IACxB,MAAM,SAAS;IACf,IAAI,aAAa,OAAO,KAAK,yBAAyB,QAAQ,OAAO,OAAO,6BAA6B;SACpG,OAAO,KAAK,uCAAuC,OAAO,EAAE;GACrE,OAAO,IAAI,YAAY,WAAW;IAC9B,IAAI,YAAY,OAKZ,OAAO,KACH,gKAEJ;IAEJ,IAAI;KAIA,MAAM,YAA2B,wBAAwB,CAAC,CACrD,KAAK,OAAO;MACT,QAAS,EAA0B,UAAU;MAC7C,OAAO,eAAuB,CAAC;MAC/B,iBAAiB,qBAAqB,GAAG,QAAQ,CAAC,CAAC,KAAK,MAAM,EAAE,UAAU;KAC9E,EAAE,CAAC,CACF,QAAQ,MAAM,QAAQ,EAAE,KAAK,KAAK,SAAS,sBAAsB,EAAE,KAAK,CAAC;KAU9E,MAAM,WAAW,IAAI,IAAI,wBAAwB,CAAC,CAAC,KAAI,MAAK,EAAE,IAAI,CAAC;KACnE,KAAK,MAAM,QAAQ,qBAAqB,QAAQ,GAAG;MAC/C,IAAI,CAAC,SAAS,IAAI,KAAK,iBAAiB,IAAI,GAAG;MAG/C,UAAU,KAAK;OACX,QAAQ,KAAK;OACb,OAAO,KAAK;OACZ,iBAAiB,CAAC,KAAK,cAAc,KAAK,YAAY;MAC1D,CAAC;KACL;KAIA,IAAI,oBAAoB;MACpB,MAAM,oBAAoB,WAAW,SAAS;MAC9C,qBAAqB;KACzB;KACA,IAAI,oBAAoB,MAAM,gBAAgB,UAAU,SAAS;KACjE,aAAa;KAOb,IAAI,oBACA,wBAAwB,OAAO,WAAW;MACtC,MAAM,oBAAoB,WAAW,MAAM;KAC/C;KAOJ,OAAO,KACH,mEAAmE,YAAY,QAAQ,gBAAgB,UAAU,iEAEhH,qBAAqB,KAAK,8DAC/B;IACJ,SAAS,KAAK;KACV,IAAI,aACA,OAAO,KAAK,iGAAiG,EAAE,OAAO,IAAI,CAAC;UAE3H,OAAO,KACH,kNAEA,EAAE,QAAQ,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE,CAC/D;IAER;GACJ;GAOA,IAAI,sBAAsB,CAAC,oBACvB,IAAI;IACA,MAAM,4BAA4B,SAAS;GAC/C,SAAS,KAAK;IACV,OAAO,KACH,6SAIA,EAAE,QAAQ,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE,CAC/D;GACJ;GAKJ,IAAI,CAAC,cAAc,aAAa,oBAC5B,IAAI;IACA,MAAM,gBAAgB,eAAe,SAAS;GAClD,SAAS,KAAK;IACV,OAAO,KAAK,mDAAmD,EAAE,OAAO,IAAI,CAAC;GACjF;GAMJ,IAAI;IACA,MAAM,wBAAwB,wBAAwB;IACtD,IAAI,sBAAsB,SAAS,GAAG;KAKlC,MAAM,SAAS,MAAM,cAAc,QAAQ,IAAI,IAAI;;;;;qBAKlD,CAAC;KACF,MAAM,+BAAe,IAAI,IAAsB;KAC/C,KAAK,MAAM,OAAO,OAAO,MAA6D;MAClF,MAAM,UAAU,aAAa,IAAI,IAAI,UAAU,KAAK,CAAC;MACrD,QAAQ,KAAK,IAAI,YAAY;MAC7B,aAAa,IAAI,IAAI,YAAY,OAAO;KAC5C;KACA,MAAM,WAAW,IAAI,IAChB,OAAO,KAA6D,KAAI,MACrE,EAAE,iBAAiB,WAAW,EAAE,aAAa,GAAG,EAAE,aAAa,GAAG,EAAE,YACxE,CACJ;KACA,MAAM,UAAqE,CAAC;KAC5E,KAAK,MAAM,OAAO,uBAAuB;MAOrC,IAAK,IAAyC,MAAM,SAAS;MAE7D,MAAM,aAAa,YAAY,OAAO,IAAI,SAAS,IAAI,SAAS;MAChE,MAAM,YAAY,sBAAsB,KAAK,SAAS,cAAc,CAAC;MACrE,MAAM,gBAAgB,eAAe,WAAW,YAAY,GAAG,WAAW,GAAG;MAC7E,IAAI,CAAC,SAAS,IAAI,aAAa,GAG3B,QAAQ,KAAK;OAAE,MAAM,IAAI;OACrD,OAAO;OACP,UAAU,aAAa,IAAI,SAAS,KAAK,CAAC,EAAA,CAAG,QAAO,MAAK,MAAM,UAAU;MAAE,CAAC;KAExD;KACA,IAAI,QAAQ,SAAS,GAAG;MACpB,MAAM,QAAQ,QAAQ,KAClB,MAAK,qBAAqB,EAAE,KAAK,aAAa,EAAE,MAAM,MACjD,EAAE,QAAQ,SAAS,IACd,yCAAyC,EAAE,QAAQ,KAAI,MAAK,IAAI,EAAE,EAAE,CAAC,CAAC,KAAK,IAAI,MAC/E,GACd;MAYA,MAAM,YAAY,QAAQ,QAAO,MAAK,EAAE,QAAQ,SAAS,CAAC;MAC1D,MAAM,gBAAgB,UAAU,WAAW,IAAI,CAAC,IAAI;OAChD;OACA;OACA;OACA,GAAG,UAAU,KAAI,MACb,sBAAsB,EAAE,QAAQ,GAAG,KAAK,EAAE,MAAM,MAAM,GAAG,CAAC,CAAC,IAAI,EAAE,gBAAgB,EAAE,MAAM,SAAS,GAAG,IAAI,EAAE,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,SAAS,GAC9I;OACA;OACA;OACA;MACJ;MAYA,MAAM,QAAQ,yBAAyB,kBAAkB;MACzD,OAAO,KAAK;OACR;OACA;OACA,GAAG;OACH;OACA,GAAG;OACH,GAAG;OACH;OACA;OACA;OACA;OACA;OACA;OACA;MACJ,CAAC,CAAC,KAAK,IAAI,CAAC;KAChB;IACJ;GACJ,SAAS,KAAK;IACV,OAAO,KAAK,8CAA8C,EACtD,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAC1D,CAAC;GACL;GAYA,OAAO;IACH;IACA,kBAAkB;IAClB,oBAAoB;IAGpB,aAAa;IACb,WAAA;KAhBA,IAAI;KACJ;KACA;KACA;KACA;KACA;KACA;IAUA;GACJ;EACJ;EAEA,MAAM,eAAe,QAAiB,cAAwE;GAC1G,MAAM,aAAa;GACnB,IAAI,CAAC,YAAY,OAAO,KAAA;GAExB,MAAM,YAAY,aAAa;GAC/B,MAAM,KAAK,UAAU;GACrB,MAAM,WAAW,UAAU;GAI3B,MAAM,iBAAiB,WAAW;GAGlC,MAAM,sBAAsB,IAAI,cAAc;GAK9C,IAAI,kBAAkB,UAAU,uBAAuB;IACnD,MAAM,aAAa,YAAY,kBAAkB,OAAO,eAAe,WAAW,WAC5E,eAAe,SACf;IACN,MAAM,YAAY,WAAW,kBAAkB,OAAO,eAAe,UAAU,WACzE,eAAe,QACf,eAAe;IACrB,IAAI,WACA,IAAI;KACA,MAAM,UAAU,sBAAsB,CAAC;MACnC,QAAQ;MACR,OAAO;MAIP,iBAAiB,qBAAqB,gBAAgB,QAAQ,CAAC,CAAC,KAAK,MAAM,EAAE,UAAU;KAC3F,CAAC,CAAC;IACN,SAAS,KAAK;KACV,OAAO,KACH,+DAA+D,WAAW,GAAG,UAAU,qDAEvF,EAAE,QAAQ,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE,CAC/D;IACJ;GAER;GAEA,IAAI;GACJ,IAAI,WAAW,OACX,eAAe,mBAAmB,WAAW,KAAoB;GAKrE,MAAM,YAAY,iBACX,WAAW,kBAAkB,OAAO,eAAe,UAAU,WAC1D,eAAe,QACf,eAAe,OACnB,KAAA;GACN,MAAM,aAAa,YACb,SAAS,SAAS,SAAS,IAC3B,KAAA;GAEN,IAAI,kBAAkB;GACtB,IAAI,kBAAkB,YAAY,kBAAkB,OAAO,eAAe,WAAW,UACjF,kBAAkB,eAAe;GASrC,MAAM,SAAS,iBAAiB,eAAe;GAC/C,MAAM,aAA+B;IACjC,OAAO,gBAAgB,OAAO,KAAK;IACnC,eAAe,gBAAgB,OAAO,aAAa;IACnD,qBAAqB,gBAAgB,OAAO,mBAAmB;IAC/D,WAAW,gBAAgB,OAAO,SAAS;IAC3C,gBAAgB,gBAAgB,OAAO,cAAc;GACzD;GACA,IAAI,YACA,WAAW,QAAQ;GAGvB,MAAM,cAAc,IAAI,YAAY,IAAI,UAAU;GAClD,MAAM,iBAAiB,IAAI,uBAAuB,IAAI,UAAU;GAEhE,OAAO;IAAE;IACrB;IACA;IAGA,yBAAyB,gBAAgB,IAAI,kBAAkB,cAAc,CAAC;GAAE;EACxE;EAEA,MAAM,kBAAkB,QAAuB,cAA0F;GACrI,IAAI,CAAC,QAAQ,OAAO,KAAA;GAGpB,MAAM,KADY,aAAa,UACV;GAErB,MAAM,gBAAgB,MAAM,yBAAyB,EAAE;GAEvD,MAAM,YAAY,OAAO,WAAW,WAAW,OAAO,YAAY,KAAA;GAGlE,OAAO,EAAE,gBAAA,IAFkB,eAAe,IAAI,YAAY,EAAE,SAAS,UAAU,IAAI,KAAA,GAAW,EAAE,cAAc,CAErG,EAAe;EAC5B;EAEA,MAAM,mBAAmB,SAAkB,cAAwE;GAE/G,OADkB,aAAa,UACd;EACrB;;;;;;;;;;;;;;;EAgBA,MAAM,iBAAiB,cAAiD;GACpE,MAAM,KAAK,eAAe,YAAY;GAKtC,IAAI,CAAC,IAAI;GACT,IAAI;IACA,MAAM,GAAG,QAAQ,GAAG,UAAU;GAClC,SAAS,KAAc;IACnB,MAAM,uBAAuB,KAAK,SAAS,gBAAgB,KACpD,IAAI,MACH,8BAA8B,cAAc,SAAS,oBAAoB,EAAE,EAAE,uCAC7E,EAAE,OAAO,IAAI,CACjB;GACR;EACJ;;;;;;;;;;;;;;EAeA,MAAM,uBACF,aACA,cACA,KAC4B;GAC5B,MAAM,EAAE,2BAA2B,MAAM,OAAO,yCAAA,CAAA,MAAA,MAAA,EAAA,CAAA;GAOhD,MAAM,OAAO,MAAM,uBADD,sBAAsB,YAEpC,GACA,aACA,GACJ;GACA,KAAK,MAAM,WAAW,KAAK,UACvB,OAAO,KAAK,sBAAsB,OAAO,CAAC;GAE9C,OAAO,EAAE,SAAS,KAAK,QAAQ,SAAS,KAAK,SAAS,OAAO;EACjE;;;;;;;;;;;;;;;;;;;;;;;;;;EA2BA,MAAM,iBAAiB,cAAiD;GACpE,MAAM,oBAAoB,YAAY;EAC1C;EAEA,MAAM,yBACF,aACA,cACA,KAC4B;GAC5B,MAAM,EAAE,6BAA6B,MAAM,OAAO;GAClD,MAAM,YAAY,sBAAsB,YAAY;GAUpD,MAAM,EAAE,SAAS,MAAM,UAAU,MAC7B,iEACJ;GACA,IAAI,CAAC,KAAK,EAAE,EAAE,SAAS,MAAM,oBAAoB,YAAY;GAC7D,MAAM,UAAU,MAAM,yBAClB,WACA,aACA,GACJ;GAGA,eAAe,MAAM;GACrB,KAAK,MAAM,KAAK,QAAQ,UAAU,QAAO,MAAK,EAAE,cAAc,GAAG;IAC7D,MAAM,MAAM,EAAE,MAAM,QAAQ,GAAG;IAC/B,eAAe,IAAI,EAAE,OAAO;KAAE,QAAQ,EAAE,MAAM,MAAM,GAAG,GAAG;KAAG,OAAO,EAAE,MAAM,MAAM,MAAM,CAAC;IAAE,CAAC;GAChG;GAEA,KAAK,MAAM,QAAQ,QAAQ,SACvB,OAAO,KAAK,qCAAqC,KAAK,MAAM,KAAK,KAAK,QAAQ;GAElF,KAAK,MAAM,WAAW,QAAQ,UAC1B,OAAO,KACH,+CAA+C,QAAQ,MAAM,sDAAsD,QAAQ,OAC/H;GAQJ,MAAM,YAAY,QAAQ,UAAU,QAAO,MAAK,CAAC,EAAE,cAAc;GACjE,KAAK,MAAM,KAAK,QAAQ,UAAU,QAAO,MAAK,EAAE,cAAc,GAC1D,OAAO,MACH,oDAAoD,EAAE,MAAM,KAAK,EAAE,MAAM,0CAChC,iBAAiB,+HAG9D;GAEJ,IAAI,UAAU,SAAS,GAKnB,MAAM,IAAI,MACN,mEACA,UAAU,KAAI,MAAK,IAAI,EAAE,MAAM,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC,KAAK,IAAI,IACzD,mCAAmC,iBAAiB,kMAGxD;GAUJ,IAAI;IACA,MAAM,EAAE,yBAAyB,MAAM,OAAO,kCAAA,CAAA,MAAA,MAAA,EAAA,CAAA;IAC9C,MAAM,qBACF,OAAO,UAAU,MAAM,UAAU,MAA+B,IAAI,EAAA,CAAG,MACvE;KAAE,OAAO,MAAM,OAAO,KAAK,CAAC;KAAG,OAAO,MAAM,OAAO,KAAK,CAAC;IAAE,CAC/D;GACJ,SAAS,KAAK;IACV,OAAO,KACH,8CACC,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EACpD;GACJ;GAEA,OAAO,EAAE,SAAS,QAAQ,gBAAgB;EAC9C;;;;;;;EAQA,MAAM,6BAA6B,cAA0D;GACzF,MAAM,KAAK,eAAe,YAAY;GACtC,IAAI,CAAC,IAAI,OAAO;GAChB,MAAM,EAAE,iCAAiC,MAAM,OAAO;GACtD,OAAO,6BAA6B,IAAa,kBAAkB;EACvE;;EAGA,MAAM,8BAA8B,SAAiB,cAAiD;GAClG,MAAM,KAAK,eAAe,YAAY;GACtC,IAAI,CAAC,IAAI;GACT,MAAM,EAAE,kCAAkC,MAAM,OAAO;GACvD,MAAM,8BAA8B,IAAa,oBAAoB,OAAO;EAChF;;;;;;;;;;;;;;;;;;;;;;;;EAyBA,MAAM,wBAAwB,cAAgD;GAE1E,MAAM,EAAE,QAAQ,iBAAiB,UAAU,OADzB,aAAa;GAE/B,IAAI,CAAC,IAAI;GAET,MAAM,SAAuB,OAAO,SAAS;IAEzC,QAAQ,MADU,GAAG,QAAQ,IAAI,IAAI,IAAI,CAAC,EAAA,CAC9B,QAAQ,CAAC;GACzB;GAKA,MAAM,iBAAiB;IAAC;IAAU;IAAU,GAHlB,SAAS,eAAe,CAAC,CAC9C,KAAK,MAAO,EAA0B,MAAM,CAAC,CAC7C,QAAQ,MAAmB,OAAO,MAAM,QACE;GAAiB;GAWhE,IAAI,OAAO,aAAa;IACpB,IAAI;KACA,MAAM,cAAc,QAAQ,cAAc;IAC9C,SAAS,KAAK;KAGV,OAAO,KACH,wCAAwC,iBAAiB,yCAC7C,eAAe,KAAK,IAAI,EAAE,sGACgB,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,GACzG;IACJ;IACA,MAAM,cAAc,MAAM;IAC1B;GACJ;GAEA,MAAM,UAAU,MAAM,wBAAwB,QAAQ,cAAc;GAIpE,IAAI,CAAC,QAAQ,aAAa,CAAC,QAAQ,aAAa,CAAC,QAAQ,YAAY;GAErE,IAAI;IACA,MAAM,cAAc,QAAQ,cAAc;GAC9C,SAAS,KAAK;IACV,MAAM,IAAI,MACN,iCAAiC,QAAQ,KAAK,oQAGE,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,GACnG;GACJ;GACA,MAAM,cAAc,MAAM;GAE1B,OAAO,cAAc;GACrB,gBAAgB,cAAc;GAC9B,OAAO,KACH,mDAAmD,QAAQ,KAAK,iFACZ,iBAAiB,GACzE;EACJ;EAEA,SAAS,cAA4D;GAEjE,OADkB,aAAa,UACd,OAAO;EAC5B;EAEA,YAAY,KAAc,UAAkB,cAAuC,CAInF;EAEA,MAAM,qBAAqB,QAAiB,iBAAmC,QAAoB,QAAkB,SAAmB,QAA+C;GACnL,MAAM,EAAE,yBAAyB,yBAAyB,0BAA0B,MAAA,QAAA,QAAA,CAAA,CAAA,WAAA,iBAAA;GAIpF,IAAI,CAAC,wBAAwB,eAAe,GACxC,MAAM,IAAI,MACN,kFACG,sBAAsB,eAAe,CAAC,CAAC,KAAK,IAAI,EAAE,8CACzD;GAEJ,wBACI,QACA,iBACA,QACA,QACA,SACA,MACJ;EACJ;CACJ;AACJ;;;;;;ACpmDA,SAAgB,sBAAsB,UAAiD;CACnF,MAAM,eAAe,2BAA2B,QAAQ;CAExD,OAAO;EACH,MAAM,aAAa;EAEnB,MAAM,iBAAiB,QAAQ;GAC3B,OAAO,aAAa,iBAAiB,MAAM;EAC/C;EAEA,MAAM,mBAAmB,cAAc;GACnC,IAAI,aAAa,oBACb,OAAO,aAAa,mBAAmB,CAAC,GAAG,YAAY;EAG/D;EAEA,MAAM,eAAe,QAAQ,cAAc;GACvC,IAAI,aAAa,gBACb,OAAO,aAAa,eAAe,QAAQ,YAAY;EAG/D;EAEA,MAAM,kBAAkB,QAAQ,cAAc;GAC1C,IAAI,aAAa,mBACb,OAAO,aAAa,kBAAkB,QAAQ,YAAY;EAGlE;EASA,qBAAqB,QAAQ,iBAAiB,QAAQ,QAAQ,SAAS,QAAQ;GAC3E,IAAI,aAAa,sBACb,OAAO,aAAa,qBAAqB,QAAQ,iBAAiB,QAAQ,QAAQ,SAAS,MAAM;EAEzG;EAMA,kBAAkB,aAAa,oBACxB,iBAAiB,aAAa,iBAAkB,YAAY,IAC7D,KAAA;EAEN,wBAAwB,aAAa,0BAC9B,aAAa,cAAc,QAC1B,aAAa,uBAAwB,aAAa,cAAc,GAAG,IACrE,KAAA;EAON,kBAAkB,aAAa,oBACxB,iBAAiB,aAAa,iBAAkB,YAAY,IAC7D,KAAA;EAEN,0BAA0B,aAAa,4BAChC,aAAa,cAAc,QAC1B,aAAa,yBAA0B,aAAa,cAAc,GAAG,IACvE,KAAA;EAEN,yBAAyB,aAAa,2BAC/B,iBAAiB,aAAa,wBAAyB,YAAY,IACpE,KAAA;EAQN,8BAA8B,aAAa,gCACpC,iBAAiB,aAAa,6BAA8B,YAAY,IACzE,KAAA;EAEN,+BAA+B,aAAa,iCACrC,SAAS,iBAAiB,aAAa,8BAA+B,SAAS,YAAY,IAC5F,KAAA;EAEN,SAAS,cAAc;GACnB,IAAI,aAAa,UACb,OAAO,aAAa,SAAS,YAAY;EAGjD;EAEA,YAAY,KAAK,UAAU,cAAc;GACrC,IAAI,aAAa,aACb,aAAa,YAAY,KAAK,UAAU,YAAY;EAE5D;CACJ;AACJ"}
1
+ {"version":3,"file":"index.es.js","names":[],"sources":["../src/backup/backup-cron.ts","../src/PostgresAdapter.ts"],"sourcesContent":["/**\n * Scheduled backups, wired into the server cron system.\n *\n * A backend enables nightly (or custom-schedule) backups by dropping a cron\n * file that default-exports {@link createBackupCron}. The heavy lifting —\n * dump, upload, prune — reuses the same primitives as the CLI so behaviour\n * is identical between a manual `rebase db backup` and an automated run.\n */\nimport fs from \"fs\";\nimport type { CronJobDefinition } from \"@rebasepro/types\";\nimport type { StorageController } from \"@rebasepro/server\";\nimport { logger } from \"@rebasepro/server\";\nimport { pgToolVariable, resolvePgBinary, type PgTool } from \"./pg-binary\";\nimport {\n BackupDestination,\n parseBackupDestination,\n parseDbNameFromUrl,\n resolveConnectionString\n} from \"./pg-tools\";\n// NOTE: `./backup-service` pulls in `execa` (ESM-only). It is imported\n// lazily inside the handler so the pure `backupCronConfigFromEnv` parser can\n// be unit-tested under CommonJS without loading it.\n\nexport interface BackupCronConfig {\n /** Cron expression, e.g. `\"0 3 * * *\"` for 03:00 daily. */\n schedule: string;\n /** Postgres connection string. Defaults to `DATABASE_URL`. */\n connectionString: string;\n /** Where backups are written: a local path or an `s3://` / `gs://` URL. */\n destination: BackupDestination;\n /**\n * Storage controller used for `s3`/`gcs` destinations. Reuse the one the\n * backend already configured (S3/GCS/Local StorageController). Not needed\n * for local destinations.\n */\n storage?: StorageController;\n /** Delete backups older than this many days. `0` disables pruning. */\n retentionDays?: number;\n /** Always keep at least this many recent backups regardless of age. */\n keepMinimum?: number;\n /**\n * Schemas to exclude from the dump. Defaults to none, the same as\n * `rebase db backup` — see {@link createBackupCron} for why `rebase` in\n * particular must stay in.\n */\n excludeSchemas?: string[];\n /** Cron job display name. */\n name?: string;\n /** Whether the job is enabled. */\n enabled?: boolean;\n}\n\nexport interface EnvResolution {\n /** Resolved config, present when a schedule is configured. */\n config?: Omit<BackupCronConfig, \"storage\">;\n /** True when `BACKUP_SCHEDULE` is unset — the cron should be a no-op. */\n disabled?: boolean;\n /** Human-readable reason the config could not be built. */\n error?: string;\n}\n\n/**\n * Build backup-cron config purely from environment variables.\n * Recognised keys:\n * - `BACKUP_SCHEDULE` cron expression (required to enable)\n * - `BACKUP_DESTINATION` local path or `s3://` / `gs://` URL (required)\n * - `BACKUP_RETENTION_DAYS` integer days (optional)\n * - `BACKUP_KEEP_MINIMUM` integer count (optional)\n * - `DATABASE_URL` connection string\n *\n * Pure and side-effect free so it can be unit-tested without a database.\n */\nexport function backupCronConfigFromEnv(env: Record<string, string | undefined>): EnvResolution {\n const schedule = env.BACKUP_SCHEDULE?.trim();\n if (!schedule) {\n return { disabled: true };\n }\n\n const connectionString = resolveConnectionString(env);\n if (!connectionString) {\n return { error: \"BACKUP_SCHEDULE is set but DATABASE_URL is not configured.\" };\n }\n\n const destinationRaw = env.BACKUP_DESTINATION?.trim();\n if (!destinationRaw) {\n return { error: \"BACKUP_SCHEDULE is set but BACKUP_DESTINATION is not configured.\" };\n }\n const destination = parseBackupDestination(destinationRaw);\n\n const retentionDays = parseOptionalInt(env.BACKUP_RETENTION_DAYS);\n if (retentionDays === \"invalid\") {\n return { error: `BACKUP_RETENTION_DAYS must be an integer, got \"${env.BACKUP_RETENTION_DAYS}\".` };\n }\n const keepMinimum = parseOptionalInt(env.BACKUP_KEEP_MINIMUM);\n if (keepMinimum === \"invalid\") {\n return { error: `BACKUP_KEEP_MINIMUM must be an integer, got \"${env.BACKUP_KEEP_MINIMUM}\".` };\n }\n\n return {\n config: {\n schedule,\n connectionString,\n destination,\n retentionDays: retentionDays ?? undefined,\n keepMinimum: keepMinimum ?? undefined\n }\n };\n}\n\nfunction parseOptionalInt(value: string | undefined): number | null | \"invalid\" {\n if (value === undefined || value.trim() === \"\") return null;\n const n = Number(value);\n if (!Number.isInteger(n) || n < 0) return \"invalid\";\n return n;\n}\n\n/**\n * Create a {@link CronJobDefinition} that dumps the database, uploads the\n * result to the configured destination, and prunes old backups. Object\n * destinations require {@link BackupCronConfig.storage}.\n *\n * The dump excludes nothing unless `excludeSchemas` says otherwise, and that\n * default is not this function's to set: the option goes to `createDump`\n * untouched, so `buildPgDumpArgs` decides for the cron and `rebase db backup`\n * alike. The cron used to carry its own default of `[\"rebase\"]`, meant to\n * leave out Atlas's revision table, and it left out a lot more. `rebase` is\n * where the framework keeps\n * everything that is not a collection: the auth tables (`users`, identities,\n * refresh tokens, MFA factors, recovery codes, `app_config`, `schema_meta`),\n * API keys, record history, the job queue, cron logs, channel history,\n * branches and idempotency keys. It also holds the functions that every\n * generated RLS policy and CDC trigger calls (`rebase.uid()`,\n * `rebase.roles()`, `rebase.rebase_cdc_notify()`).\n *\n * None of that can be left for boot to rebuild. Boot recreates the tables\n * and functions, but never the rows in them. And a dump that has a policy\n * or trigger on a `public` table without the function it calls cannot be\n * restored into an empty database: `pg_restore --exit-on-error` stops at\n * the first such statement, and every collection's generated policies call\n * `rebase.uid()`. So every scheduled backup lost every user account, and\n * could not be restored anyway, while `rebase db backup` took a complete\n * one. The revision table belongs in a backup too: without it, a restored\n * database has tables but no record of the migrations that made them.\n */\nexport function createBackupCron(config: BackupCronConfig): CronJobDefinition {\n const dbName = parseDbNameFromUrl(config.connectionString) ?? \"database\";\n if (config.enabled ?? true) warnOfMissingTools(config.name ?? \"Scheduled database backup\");\n\n const job: CronJobDefinition = {\n name: config.name ?? \"Scheduled database backup\",\n schedule: config.schedule,\n description: \"Dumps the Postgres database and uploads it to the configured backup destination.\",\n enabled: config.enabled ?? true,\n // Backups of a large database can take a while; allow up to an hour.\n timeoutSeconds: 3600,\n async handler({ log }) {\n const { createDump, discardPartialDump, pruneBackups, uploadBackup, validateDump } =\n await import(\"./backup-service\");\n const { destination } = config;\n\n if (destination.kind !== \"local\" && !config.storage) {\n throw new Error(\n `Backup destination is ${destination.kind} but no storage controller was provided. ` +\n \"Pass the backend's configured StorageController to createBackupCron({ storage }).\"\n );\n }\n\n log(`Starting backup of \"${dbName}\"…`);\n const outDir = destination.kind === \"local\" ? destination.path : undefined;\n const dump = await createDump({\n connectionString: config.connectionString,\n dbName,\n outDir,\n excludeSchemas: config.excludeSchemas\n });\n log(`Dump created: ${dump.fileName} (${formatBytes(dump.sizeBytes)})`);\n\n // Validate BEFORE pruning: a corrupt-but-exit-0 dump must never be\n // the reason the last good backup gets deleted.\n const check = await validateDump(dump.localFile);\n if (!check.ok) {\n // Discarded for every destination, the roles sidecar with it.\n // On a local one the file *is* the backup: left behind, it\n // keeps a valid backup's name, and retention — which ranks by\n // timestamp and never opens a file — counts it among the\n // newest it keeps while pruning a real backup beneath it.\n if (dump.globalsFile) discardPartialDump(dump.globalsFile);\n discardPartialDump(dump.localFile);\n throw new Error(`New backup failed validation — skipping upload and pruning to protect existing backups. ${check.reason}`);\n }\n\n let storedKey = dump.localFile;\n try {\n if (destination.kind !== \"local\") {\n const uploaded = await uploadBackup(config.storage!, dump.localFile, destination);\n storedKey = uploaded.storageUrl;\n log(`Uploaded to ${uploaded.storageUrl}`);\n // Upload the roles sidecar so a restore can recreate the\n // roles the dump's GRANT/RLS statements depend on.\n if (dump.globalsFile && fs.existsSync(dump.globalsFile)) {\n const g = await uploadBackup(config.storage!, dump.globalsFile, destination);\n log(`Uploaded roles sidecar to ${g.storageUrl}`);\n }\n }\n } finally {\n // For object-storage destinations the local dump was a temp\n // file — remove it once uploaded (or on failure).\n if (destination.kind !== \"local\" && fs.existsSync(dump.localFile)) {\n fs.unlinkSync(dump.localFile);\n }\n if (dump.globalsFile && destination.kind !== \"local\" && fs.existsSync(dump.globalsFile)) {\n fs.unlinkSync(dump.globalsFile);\n }\n }\n\n let pruned: string[] = [];\n if (config.retentionDays && config.retentionDays > 0) {\n pruned = await pruneBackups(\n destination,\n { retentionDays: config.retentionDays, keepMinimum: config.keepMinimum },\n config.storage\n );\n if (pruned.length > 0) {\n log(`Pruned ${pruned.length} backup(s) older than ${config.retentionDays} day(s).`);\n }\n }\n\n return {\n backup: storedKey,\n sizeBytes: dump.sizeBytes,\n pruned: pruned.length\n };\n }\n };\n\n // How the Backups panel finds this job among the project's crons: its id is\n // the cron file's name and its display name is configurable. A registry\n // symbol, so `@rebasepro/server` reads it without importing this package\n // (it must not), and non-enumerable, so a spread or a JSON dump of the\n // definition is unchanged. See `readBackupSchedule` in the server package.\n Object.defineProperty(job, Symbol.for(\"rebase.backupCron\"), { value: true });\n return job;\n}\n\n/**\n * Say at registration, not at 03:00, that the job cannot run.\n *\n * Each run needs `pg_dump` for the dump and `pg_dumpall` for the roles\n * sidecar. Without them every run fails with \"Could not find the 'pg_dump'\n * binary\", and that was the first an operator heard of it — a runtime image\n * once shipped without the client tools, and every nightly backup failed.\n */\nfunction warnOfMissingTools(jobName: string): void {\n const needed: PgTool[] = [\"pg_dump\", \"pg_dumpall\"];\n const missing = needed.filter(tool => !resolvePgBinary(tool));\n if (missing.length === 0) return;\n logger.error(\n `\"${jobName}\" is scheduled, but ${missing.map(tool => `'${tool}'`).join(\" and \")} ` +\n `${missing.length > 1 ? \"were\" : \"was\"} not found on this machine, so every run will fail. ` +\n \"Install the PostgreSQL client tools (the same major version as the server, or newer), \" +\n `or set ${missing.map(pgToolVariable).join(\" / \")} to the binary.`\n );\n}\n\nfunction formatBytes(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;\n}\n","import { DatabaseAdapter, InitializedDriver, RealtimeProvider, DataDriver, DatabaseAdmin, BootstrappedAuth } from \"@rebasepro/types\";\nimport { createPostgresBootstrapper } from \"./PostgresBootstrapper\";\nimport type { PostgresDriverConfig } from \"./PostgresBootstrapper\";\n\n/**\n * Creates a Postgres database adapter for Rebase.\n */\nexport function createPostgresAdapter(pgConfig: PostgresDriverConfig): DatabaseAdapter {\n const bootstrapper = createPostgresBootstrapper(pgConfig);\n\n return {\n type: bootstrapper.type,\n\n async initializeDriver(config) {\n return bootstrapper.initializeDriver(config);\n },\n\n async initializeRealtime(driverResult) {\n if (bootstrapper.initializeRealtime) {\n return bootstrapper.initializeRealtime({}, driverResult);\n }\n return undefined;\n },\n\n async initializeAuth(config, driverResult) {\n if (bootstrapper.initializeAuth) {\n return bootstrapper.initializeAuth(config, driverResult);\n }\n return undefined;\n },\n\n async initializeHistory(config, driverResult) {\n if (bootstrapper.initializeHistory) {\n return bootstrapper.initializeHistory(config, driverResult);\n }\n return undefined;\n },\n\n // `adapter` and `limits` are forwarded for the same reason the schema\n // hooks below are: dropping an argument here is invisible at the call\n // site and silent at runtime. `adapter` decided whether the realtime\n // socket authenticates at all — without it, a server whose auth comes\n // from an AuthAdapter fell back to \"is a jwtSecret set?\", answered no,\n // and admitted every client. `limits` are the data API's body and rate\n // limits; dropped, the socket has neither.\n initializeWebsockets(server, realtimeService, driver, config, adapter, limits) {\n if (bootstrapper.initializeWebsockets) {\n return bootstrapper.initializeWebsockets(server, realtimeService, driver, config, adapter, limits);\n }\n },\n\n // Forwarded so the boot-time schema/RLS provisioning is reachable when\n // this adapter is wrapped back into a bootstrapper. Omitting either left\n // a managed tenant with no tables (500 on every data route) or tables\n // but no policies (401 on every read) — the create step never ran.\n verifyConnection: bootstrapper.verifyConnection\n ? (driverResult) => bootstrapper.verifyConnection!(driverResult)\n : undefined,\n\n ensureCollectionSchema: bootstrapper.ensureCollectionSchema\n ? (collections, driverResult, log) =>\n bootstrapper.ensureCollectionSchema!(collections, driverResult, log)\n : undefined,\n\n // Same rule, and it bit at once: the method existed on the bootstrapper,\n // this wrapper did not forward it, and a second database booted with\n // tables and no helper functions — every policy on it silently failed\n // to create. The adapter type is where a forgotten forward should fail\n // to compile; `adapter-forwarding.test.ts` sweeps for exactly this.\n ensureRlsRuntime: bootstrapper.ensureRlsRuntime\n ? (driverResult) => bootstrapper.ensureRlsRuntime!(driverResult)\n : undefined,\n\n ensureCollectionPolicies: bootstrapper.ensureCollectionPolicies\n ? (collections, driverResult, log) =>\n bootstrapper.ensureCollectionPolicies!(collections, driverResult, log)\n : undefined,\n\n finalizeSecurityPosture: bootstrapper.finalizeSecurityPosture\n ? (driverResult) => bootstrapper.finalizeSecurityPosture!(driverResult)\n : undefined,\n\n // Same forwarding rule, third instance. Dropping these is not a type\n // error and not a runtime error: the stamp is simply never written and\n // never read, so a split deployment loses the only thing that would tell\n // it a unit is serving against a schema it was not built for — and a\n // check that is off looks exactly like a check that passed. This one was\n // in fact dropped on the first attempt, and the e2e caught it.\n readCollectionsSchemaVersion: bootstrapper.readCollectionsSchemaVersion\n ? (driverResult) => bootstrapper.readCollectionsSchemaVersion!(driverResult)\n : undefined,\n\n stampCollectionsSchemaVersion: bootstrapper.stampCollectionsSchemaVersion\n ? (version, driverResult) => bootstrapper.stampCollectionsSchemaVersion!(version, driverResult)\n : undefined,\n\n getAdmin(driverResult) {\n if (bootstrapper.getAdmin) {\n return bootstrapper.getAdmin(driverResult);\n }\n return undefined;\n }\n };\n}\n"],"mappings":"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"}