@lunora/sql-store 1.0.0-alpha.9 → 1.0.0-alpha.91

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.
package/dist/index.d.ts CHANGED
@@ -1,139 +1,202 @@
1
- import { ServerDefaultContextLike, DatabaseWriterLike, SchedulerLike, SchemaLike, TableDefinitionLike, ValidatorLike } from '@lunora/do';
2
- import { SqlDialect, SqlRunResult } from "./dialect.js";
1
+ import { TableDefinitionLike, SchemaLike, ServerDefaultContextLike, DatabaseWriterLike, CrossShardReadArgs, QueryPage, SchedulerLike, CdcChange, ValidatorLike } from '@lunora/shard-engine';
2
+ import { SqlRunResult, SqlDialect } from "./dialect.js";
3
3
  export type { SqlExec } from "./dialect.js";
4
4
  import 'drizzle-orm';
5
5
  /**
6
- * Async SQL surface the D1 ORM needs: `all` for reads, `run` for writes.
7
- * Satisfied by a `D1Session`/`D1Client` in production and a `node:sqlite`
8
- * adapter in tests, so the query logic runs against a real SQLite engine.
9
- */
6
+ * Async SQL surface the D1 ORM needs: `all` for reads, `run` for writes.
7
+ * Satisfied by a `D1Session`/`D1Client` in production and a `node:sqlite`
8
+ * adapter in tests, so the query logic runs against a real SQLite engine.
9
+ */
10
10
  interface SqlCtxExec {
11
11
  all: (sql: string, parameters: ReadonlyArray<unknown>) => Promise<Record<string, unknown>[]>;
12
+ batch?: (statements: ReadonlyArray<{
13
+ params: ReadonlyArray<unknown>;
14
+ sql: string;
15
+ }>) => Promise<void>;
12
16
  run: (sql: string, parameters: ReadonlyArray<unknown>) => Promise<SqlRunResult | void>;
13
17
  }
18
+ /**
19
+ * Decode a SELECTed row back into a document: `id` → `_id`, `_creationTime`
20
+ * preserved, and every column run through the shared {@link sqliteDecode} so the
21
+ * stored form is reversed back into its JS shape. Exported so the data-browser
22
+ * (`introspect.ts`) and admin export/import paths share the exact same decode.
23
+ *
24
+ * The decode is engine-agnostic: every backend stores SQLite-shaped values
25
+ * (boolean → 1/0, JSON → text, bigint → decimal string), and `sqliteDecode` is
26
+ * robust to a driver returning either the stored string OR a natively-parsed
27
+ * value (e.g. mysql2 returns JSON columns pre-parsed) — so the same decoder is
28
+ * correct on SQLite, Postgres and MySQL.
29
+ */
30
+ declare const decodeGlobalRow: (definition: TableDefinitionLike, row: Record<string, unknown>) => Record<string, unknown>;
31
+ /**
32
+ * Provision the search companions, then index one bounded page of the rows that
33
+ * predate each index — unless it is declared `staged: true`, which leaves the
34
+ * whole backfill to {@link backfillSqlSearchIndexes}.
35
+ *
36
+ * Idempotent (`CREATE … IF NOT EXISTS` throughout, and the backfill resumes
37
+ * from recorded progress).
38
+ */
39
+ declare const runSqlSearchMigrations: (exec: SqlCtxExec, schema: SchemaLike, dialect: SqlDialect) => Promise<void>;
40
+ /**
41
+ * Run every declared search index — including the `staged: true` ones the
42
+ * migration pass skips — through to completion. The entry point a host calls
43
+ * out-of-band after deploying a search index over a table too large to index a
44
+ * page at a time.
45
+ *
46
+ * Idempotent and resumable: an index recorded as complete is skipped, and an
47
+ * interrupted run picks up from its cursor.
48
+ */
49
+ declare const backfillSqlSearchIndexes: (exec: SqlCtxExec, schema: SchemaLike, dialect: SqlDialect) => Promise<void>;
14
50
  interface SqlCtxDbOptions {
15
51
  /**
16
- * Resolved request auth handed to `.serverDefault(fn)` column factories so
17
- * server-trusted columns (owner/tenant ids) stamp from the verified caller,
18
- * never the client. The generated worker passes the per-request identity;
19
- * absent it, server-trusted columns stamp the anonymous slice (`userId: null`).
20
- */
52
+ * Resolved request auth handed to `.serverDefault(fn)` column factories so
53
+ * server-trusted columns (owner/tenant ids) stamp from the verified caller,
54
+ * never the client. The generated worker passes the per-request identity;
55
+ * absent it, server-trusted columns stamp the anonymous slice (`userId: null`).
56
+ */
21
57
  auth?: ServerDefaultContextLike["auth"];
22
58
  /**
23
- * Opt into change-data-capture: when `true`, every committed write appends a
24
- * post-image to the `__cdc_log` table (created lazily alongside the other
25
- * companion tables). Backs CDC streaming export for `.global()` tables — the
26
- * log is for export/CDC consumers, NOT point-in-time recovery: D1's PITR is
27
- * the platform's own Time Travel (`wrangler d1 time-travel restore`), an
28
- * atomic restore, not a changelog replay. Leave undefined for zero-cost
29
- * legacy behaviour.
30
- */
59
+ * Opt into change-data-capture: when `true`, every committed write appends a
60
+ * post-image to the `__cdc_log` table (created lazily alongside the other
61
+ * companion tables). Backs CDC streaming export for `.global()` tables — the
62
+ * log is for export/CDC consumers, NOT point-in-time recovery: D1's PITR is
63
+ * the platform's own Time Travel (`wrangler d1 time-travel restore`), an
64
+ * atomic restore, not a changelog replay. Leave undefined for zero-cost
65
+ * legacy behaviour.
66
+ */
31
67
  cdc?: boolean;
68
+ /**
69
+ * How long `.global()` changelog entries are kept, in milliseconds. Absent
70
+ * (the default) means forever — the log grows for the life of the database.
71
+ *
72
+ * Opt-in for the same reason the shard-local windows are, and the reason is
73
+ * not caution: this log's streaming-export consumers hold opaque cursors
74
+ * issued outside this deployment, and nothing here can see where they sit.
75
+ * A default window would be a guess whose failure mode is silent data loss
76
+ * in someone's warehouse. A deployment that wants the log bounded states a
77
+ * window it knows covers its consumers; `.global()` shape pollers are then
78
+ * protected exactly, by the floor this read path reports rather than by the
79
+ * window. See `sweepSqlCdcRetention`.
80
+ *
81
+ * Time rather than rows because the log is SHARED: every shard in every
82
+ * region writes it, so a row count is not a bound any single consumer can
83
+ * reason about, while "older than N" is exactly what they compare their own
84
+ * lag against.
85
+ */
86
+ cdcRetentionMs?: number;
32
87
  clock?: () => number;
33
88
  /**
34
- * Cross-shard counter for **reverse cross-backend relations** — the `_count`
35
- * mirror of the `crossShardReader` option below.
36
- */
89
+ * Cross-shard counter for **reverse cross-backend relations** — the `_count`
90
+ * mirror of the `crossShardReader` option below.
91
+ */
37
92
  crossShardCounter?: DatabaseWriterLike["count"];
38
93
  /**
39
- * Optional cross-shard reader for **reverse cross-backend relations**: a
40
- * `.global()` (D1) parent loading a shard-local (`.shardBy()`/root) child.
41
- * Such a child's rows are partitioned across every shard DO, so the local D1
42
- * writer can't resolve it. When provided, the relation loader routes the
43
- * child's read through this (the host wires it to the Query Coordinator's
44
- * RLS-correct `fanOut`, with identity forwarded so each shard applies its own
45
- * RLS). Absent it, loading such a relation throws a clear "not supported"
46
- * error (legacy behaviour). The forward direction (shard-local parent →
47
- * global child) and same-backend relations never touch this.
48
- */
49
- crossShardReader?: DatabaseWriterLike["findMany"];
94
+ * Optional cross-shard reader for **reverse cross-backend relations**: a
95
+ * `.global()` (D1) parent loading a shard-local (`.shardBy()`/root) child.
96
+ * Such a child's rows are partitioned across every shard DO, so the local D1
97
+ * writer can't resolve it. When provided, the relation loader routes the
98
+ * child's read through this (the host wires it to the Query Coordinator's
99
+ * RLS-correct `fanOut`, with identity forwarded so each shard applies its own
100
+ * RLS). Absent it, loading such a relation throws a clear "not supported"
101
+ * error (legacy behaviour). The forward direction (shard-local parent →
102
+ * global child) and same-backend relations never touch this.
103
+ *
104
+ * Takes {@link CrossShardReadArgs}, not `QueryArgs`: the hop is JSON, so the
105
+ * RLS filters are handed over as data (see that type's docblock).
106
+ */
107
+ crossShardReader?: (table: string, args: CrossShardReadArgs) => Promise<QueryPage>;
50
108
  /**
51
- * The SQL dialect that shapes every statement (identifier quoting, value
52
- * encode/decode, column types, upserts, RETURNING vs affected-rows).
53
- * `@lunora/d1` passes its `sqliteDialect`; the PlanetScale/Hyperdrive backend
54
- * passes its Postgres/MySQL dialect. Required — the core is engine-blind.
55
- */
109
+ * The SQL dialect that shapes every statement (identifier quoting, value
110
+ * encode/decode, column types, upserts, RETURNING vs affected-rows).
111
+ * `@lunora/d1` passes its `sqliteDialect`; the PlanetScale/Hyperdrive backend
112
+ * passes its Postgres/MySQL dialect. Required — the core is engine-blind.
113
+ */
56
114
  dialect: SqlDialect;
57
115
  exec: SqlCtxExec;
58
116
  idGenerator?: () => string;
59
117
  /**
60
- * Ceiling on the number of child join keys a relation-crossing `where`
61
- * predicate may materialize before the semijoin pre-resolver fails closed
62
- * (`relation predicate … exceeding the N-key limit`). D1 has no EXISTS
63
- * push-down, so an overflow here can only fail closed — never truncate the
64
- * `IN (...)` and silently mis-match. Defaults to the pre-resolver's shared
65
- * key cap when omitted.
66
- */
118
+ * Ceiling on the number of child join keys a relation-crossing `where`
119
+ * predicate may materialize before the semijoin pre-resolver fails closed
120
+ * (`relation predicate … exceeding the N-key limit`). D1 has no EXISTS
121
+ * push-down, so an overflow here can only fail closed — never truncate the
122
+ * `IN (...)` and silently mis-match. Defaults to the pre-resolver's shared
123
+ * key cap when omitted.
124
+ */
67
125
  maxRelationKeys?: number;
68
126
  /**
69
- * Scheduler exposed to global-table trigger handlers as `ctx.scheduler`.
70
- * Absent it, `ctx.scheduler` is a stub that throws on use — pass one when
71
- * triggers on `.global()` tables need to enqueue follow-up work.
72
- */
127
+ * Scheduler exposed to global-table trigger handlers as `ctx.scheduler`.
128
+ * Absent it, `ctx.scheduler` is a stub that throws on use — pass one when
129
+ * triggers on `.global()` tables need to enqueue follow-up work.
130
+ */
73
131
  scheduler?: SchedulerLike;
74
132
  schema: SchemaLike;
75
133
  }
76
134
  /**
77
- * Decode a SELECTed row back into a document: `id` → `_id`, `_creationTime`
78
- * preserved, and every column run through the shared {@link sqliteDecode} so the
79
- * stored form is reversed back into its JS shape. Exported so the data-browser
80
- * (`introspect.ts`) and admin export/import paths share the exact same decode.
81
- *
82
- * The decode is engine-agnostic: every backend stores SQLite-shaped values
83
- * (boolean → 1/0, JSON → text, bigint → decimal string), and `sqliteDecode` is
84
- * robust to a driver returning either the stored string OR a natively-parsed
85
- * value (e.g. mysql2 returns JSON columns pre-parsed) — so the same decoder is
86
- * correct on SQLite, Postgres and MySQL.
87
- */
88
- declare const decodeGlobalRow: (definition: TableDefinitionLike, row: Record<string, unknown>) => Record<string, unknown>;
135
+ * Auto-provision every `.global()` table from the schema: `CREATE TABLE IF NOT
136
+ * EXISTS` with the physical `id`/`_creationTime` columns plus a typed column per
137
+ * declared field, then its secondary and `.unique()` indexes. This is the D1
138
+ * twin of `@lunora/do`'s `runShardMigrations` (which self-creates shard-local
139
+ * tables) — it makes the schema the single source of truth for global tables
140
+ * too, so a fresh database serves them without a hand-applied migration. The
141
+ * column set and dialect match exactly what this module reads and writes
142
+ * (`columnRef`, `serializeColumnValue`, `decodeGlobalRow`).
143
+ *
144
+ * Idempotent (`CREATE TABLE/INDEX IF NOT EXISTS`); additive only — it never
145
+ * drops or retypes an existing column, so destructive schema changes still need
146
+ * an explicit migration.
147
+ */
89
148
  declare const runSqlGlobalTableMigrations: (exec: SqlCtxExec, schema: SchemaLike, dialect: SqlDialect) => Promise<void>;
90
149
  /**
91
- * Materialize the `__agg_&lt;index>` companion tables for every declared
92
- * `aggregateIndex` on a global table. Global tables in Lunora ship their own
93
- * DDL — counter tables are opt-in so production hosts can decide where they
94
- * live. Tests and dev hosts can call this once after their schema migration to
95
- * unlock O(1) counts.
96
- *
97
- * Idempotent (`CREATE TABLE IF NOT EXISTS`).
98
- */
150
+ * Materialize the `__agg_<index>` companion tables for every declared
151
+ * `aggregateIndex` on a global table. Global tables in Lunora ship their own
152
+ * DDL — counter tables are opt-in so production hosts can decide where they
153
+ * live. Tests and dev hosts can call this once after their schema migration to
154
+ * unlock O(1) counts.
155
+ *
156
+ * Idempotent (`CREATE TABLE IF NOT EXISTS`).
157
+ */
99
158
  declare const runSqlAggregateMigrations: (exec: SqlCtxExec, schema: SchemaLike, dialect: SqlDialect) => Promise<void>;
100
159
  /**
101
- * Materialize the `__rank_&lt;index>` companion tables for every declared
102
- * `rankIndex` on a global table. Mirrors `runSqlAggregateMigrations` — same
103
- * opt-in pattern so production hosts decide whether to spend the DDL.
104
- *
105
- * Idempotent (`CREATE TABLE IF NOT EXISTS` + `createIndexIfNotExists`).
106
- */
160
+ * Materialize the `__rank_<index>` companion tables for every declared
161
+ * `rankIndex` on a global table. Mirrors `runSqlAggregateMigrations` — same
162
+ * opt-in pattern so production hosts decide whether to spend the DDL.
163
+ *
164
+ * Idempotent (`CREATE TABLE IF NOT EXISTS` + `createIndexIfNotExists`).
165
+ */
107
166
  declare const runSqlRankMigrations: (exec: SqlCtxExec, schema: SchemaLike, dialect: SqlDialect) => Promise<void>;
108
- /**
109
- * Materialize the `__fts_&lt;index>` FTS5 shadow tables for every declared
110
- * `.searchIndex()` on a global table. Mirrors `runSqlAggregateMigrations` — same
111
- * opt-in pattern so production hosts decide whether to spend the DDL. Only runs
112
- * on engines that ship FTS5 (D1 does; the `node:sqlite` test runner doesn't,
113
- * where `.search()` transparently falls back to a scan). `__text__` holds the
114
- * indexed field; `__id__` (UNINDEXED) joins back to the row.
115
- *
116
- * Idempotent (`CREATE VIRTUAL TABLE IF NOT EXISTS`).
117
- */
118
- declare const runSqlSearchMigrations: (exec: SqlCtxExec, schema: SchemaLike, dialect: SqlDialect) => Promise<void>;
119
- /** One change-data-capture entry: a committed mutation, in monotonic `seq` order. Mirrors the DO twin. */
120
- interface CdcChange {
121
- /** Post-image document for insert/update; absent for delete (the `id` identifies the removed row). */
122
- doc?: Record<string, unknown>;
123
- id: string;
124
- op: "delete" | "insert" | "update";
125
- /** Monotonic per-database cursor — strictly increasing, never reused. */
126
- seq: number;
127
- table: string;
128
- /** Wall-clock millis when the change committed (the ctx-db `clock`). */
129
- ts: number;
130
- }
131
167
  /** Create the `__cdc_log` table. Idempotent; only run when CDC is enabled. */
132
168
  declare const runSqlCdcMigration: (exec: SqlCtxExec, dialect: SqlDialect) => Promise<void>;
133
169
  /**
134
- * Read changelog entries newer than `sinceSeq` in commit order, up to `limit`
135
- * (clamped to [1, 10000]); plus the cursor to resume from.
136
- */
170
+ * Delete `.global()` changelog entries older than `retentionMs`, at most
171
+ * {@link GLOBAL_CDC_SWEEP_MAX_ROWS} per pass, and only if this writer wins the lease.
172
+ *
173
+ * **Time, not rows.** The shard-local twin bounds its log by row count because
174
+ * one shard owns it and a row count is a memory bound on that one object. This
175
+ * log is shared, so a row count means nothing to any individual consumer — but
176
+ * "older than N" is directly what every consumer needs to reason about, and it
177
+ * is the unit an operator can compare against their own connector's lag.
178
+ *
179
+ * **Which consumers this can strand, and what protects each:**
180
+ *
181
+ * - `.global()` shape pollers hold in-memory cursors this store cannot see. They
182
+ * are protected EXACTLY rather than approximately: {@link readSqlCdcChangedTables}
183
+ * reports the retained floor, and a poller below it treats the tick as "no
184
+ * visibility" and re-reads every shape. That is the same self-healing path a
185
+ * changelog error already takes, so a trimmed poller is slow for one tick, not
186
+ * wrong. No cursor registry, no assumption about how far behind a shard can be.
187
+ * - Streaming-export / warehouse consumers hold opaque cursors issued outside
188
+ * this deployment. Nothing here can see them, so nothing here guesses:
189
+ * {@link readSqlCdcChanges} refuses a page below the floor rather than serving
190
+ * the surviving tail, and retention stays OFF unless an operator states a
191
+ * window they know covers their connector.
192
+ */
193
+ declare const sweepSqlCdcRetention: (exec: SqlCtxExec, dialect: SqlDialect, retentionMs: number, now: number) => Promise<void>;
194
+ /** Oldest `seq` still retained in the `.global()` changelog, or `undefined` when it is empty. */
195
+ declare const readSqlCdcFloor: (exec: SqlCtxExec, dialect: SqlDialect) => Promise<number | undefined>;
196
+ /**
197
+ * Read changelog entries newer than `sinceSeq` in commit order, up to `limit`
198
+ * (clamped to [1, 10000]); plus the cursor to resume from.
199
+ */
137
200
  declare const readSqlCdcChanges: (exec: SqlCtxExec, options: {
138
201
  limit?: number;
139
202
  sinceSeq?: number;
@@ -141,9 +204,44 @@ declare const readSqlCdcChanges: (exec: SqlCtxExec, options: {
141
204
  changes: CdcChange[];
142
205
  cursor: number;
143
206
  }>;
144
- /** Drop changelog entries at or below a checkpointed `throughSeq` (retention). */
145
- declare const trimSqlCdcChanges: (exec: SqlCtxExec, throughSeq: number, dialect: SqlDialect) => Promise<void>;
207
+ /**
208
+ * Which tables the changelog recorded a write to after `sinceSeq`, plus the
209
+ * cursor to resume from. Metadata only — it reads no `doc`, so its cost is a
210
+ * grouped scan over an index range rather than the size of the documents in it.
211
+ *
212
+ * The `.global()` shape poll asks this once per tick for the whole shard, and a
213
+ * shape whose table is absent from the answer skips its membership read
214
+ * entirely.
215
+ *
216
+ * **One statement, and the cursor comes out of the same rows as the tables.**
217
+ * Reading the head separately — in either order — opens a window where a write
218
+ * commits between the two round trips and ends up absent from `tables` while
219
+ * sitting at or below the adopted `cursor`, which loses it for good. Deriving
220
+ * the cursor as the max `seq` actually returned closes that window by
221
+ * construction: the caller can never advance past a row this scan did not see.
222
+ *
223
+ * **What it does NOT close**, and the poll's resync interval is what covers it:
224
+ * Postgres and MySQL allocate `seq` from a sequence BEFORE commit, so a
225
+ * transaction holding a lower `seq` can commit after one holding a higher one.
226
+ * No single read can see the uncommitted row, so a change can land below a
227
+ * cursor already adopted. That change is invisible to the changelog probe until
228
+ * the next unconditional pass — bounded by `GLOBAL_SHAPE_RESYNC_MS`, which is
229
+ * the same bound already accepted for an out-of-band writer. D1 has no such
230
+ * window (single writer, and `withSession` gives both reads one snapshot).
231
+ */
232
+ declare const readSqlCdcChangedTables: (exec: SqlCtxExec, sinceSeq: number, dialect: SqlDialect, options?: {
233
+ cursorOnly?: boolean;
234
+ retained?: boolean;
235
+ }) => Promise<{
236
+ cursor: number;
237
+ floor?: number;
238
+ tables: string[];
239
+ }>;
146
240
  declare const createSqlCtxDb: (options: SqlCtxDbOptions) => DatabaseWriterLike;
241
+ /** Reserved table holding one backfill-progress row per search companion. */
242
+ declare const SEARCH_STATE_TABLE = "__lunora_search_state";
243
+ /** Create the progress table. Idempotent; runs alongside the companion DDL. */
244
+ declare const migrateSearchState: (exec: SqlCtxExec, dialect: SqlDialect) => Promise<void>;
147
245
  /** Map a JS value onto its SQLite storage form — SQLite has no boolean, so true/false → 1/0. */
148
246
  declare const sqliteEncode: (value: unknown) => unknown;
149
247
  /** Parse `raw` as JSON, returning `raw` unchanged when it is not valid JSON. */
@@ -151,24 +249,69 @@ declare const tryJsonParse: (raw: string) => unknown;
151
249
  /** Decode a `bigint` column: a decimal string back into a `BigInt`, else verbatim. */
152
250
  declare const decodeBigint: (raw: unknown) => unknown;
153
251
  /**
154
- * Resolve the *effective* storage kind of a column validator. Encoding keys off
155
- * the runtime value's JS type, so a `v.optional(inner)` column stores its
156
- * present value exactly as `inner` would. The validator's own `kind` is
157
- * `"optional"`, which hides that — unwrap to the inner validator's kind so the
158
- * decode reverses the real storage form. The inner validator is stashed on
159
- * `_meta.inner` by `@lunora/values`' `createValidator`.
160
- */
252
+ * Resolve the *effective* storage kind of a column validator: `v.optional(inner)`
253
+ * unwrapped to `inner`'s kind, since encoding keys off the runtime value's JS
254
+ * type and the validator's own `kind` of `"optional"` hides that.
255
+ *
256
+ * A thin alias over `shared/effective-kind`, which is where the rule lives so
257
+ * the DO row store applies the identical one — it reads the same validators and
258
+ * has the same failure mode, and two copies drifted the last time.
259
+ */
161
260
  declare const effectiveColumnKind: (validator: ValidatorLike) => string | undefined;
162
261
  /**
163
- * Inverse of {@link sqliteEncode}: map a SQLite storage value back onto its JS
164
- * form, driven by the field's effective validator `kind`:
165
- *
166
- * - `boolean`: 1/0 → true/false (SQLite has no boolean type).
167
- * - `bigint`: decimal string → `BigInt`.
168
- * - `object`/`array`/`record`: JSON string → parsed value.
169
- * - `union`/`any`: parsed back only when the stored string is a JSON non-scalar
170
- * (a scalar union member round-trips through SQLite's native column type).
171
- * - everything else (string/number/date/timestamp/id/literal): verbatim.
172
- */
262
+ * Inverse of {@link sqliteEncode}: map a SQLite storage value back onto its JS
263
+ * form, driven by the field's effective validator `kind`:
264
+ *
265
+ * - `boolean`: 1/0 → true/false (SQLite has no boolean type).
266
+ * - `bigint`: decimal string → `BigInt`.
267
+ * - `bytes`: normalizes any driver return shape to a genuine `ArrayBuffer` — a
268
+ * view (`Uint8Array`/`Buffer`/…) is sliced to its own byte window, a plain
269
+ * `ArrayBuffer` passes through. Required because `v.bytes()` validates
270
+ * `value instanceof ArrayBuffer` and different backends return different BLOB
271
+ * shapes (workerd D1 `ArrayBuffer`, node:sqlite `Uint8Array`, pg/mysql2 `Buffer`).
272
+ * - `object`/`array`/`record`: JSON string → parsed value.
273
+ * - `union`/`any`/`from`: parsed back only when the stored string is a JSON
274
+ * non-scalar (a scalar member round-trips through SQLite's native column type).
275
+ * `from` belongs to THIS group, not to `object`/`array`/`record`: an external
276
+ * Standard Schema can describe a string just as easily as an object, and
277
+ * {@link sqliteEncode} keys off the runtime JS type — so a `v.from(z.string())`
278
+ * column holding `"123"` is stored verbatim, and unconditional parsing would
279
+ * read it back as the NUMBER 123.
280
+ * CAVEAT: a union/any/from member is stored verbatim by {@link sqliteEncode}, so
281
+ * a legitimate *string* value that itself looks like JSON (`'{"a":1}'`, `'[1,2]'`)
282
+ * is ambiguous on read and decodes back to the parsed object/array, not the
283
+ * original string. This is inherent to sharing one TEXT column between a string
284
+ * and an object member; disambiguating would require a breaking storage-format
285
+ * change (tagging encoded non-scalars), so it is documented rather than fixed.
286
+ * - everything else (string/number/date/timestamp/id/literal): verbatim.
287
+ */
173
288
  declare const sqliteDecode: (raw: unknown, kind: string | undefined) => unknown;
174
- export { type SqlCtxDbOptions, type SqlCtxExec, type SqlDialect, type SqlRunResult, createSqlCtxDb, decodeBigint, decodeGlobalRow, effectiveColumnKind, readSqlCdcChanges, runSqlAggregateMigrations, runSqlCdcMigration, runSqlGlobalTableMigrations, runSqlRankMigrations, runSqlSearchMigrations, sqliteDecode, sqliteEncode, trimSqlCdcChanges, tryJsonParse };
289
+ export { SEARCH_STATE_TABLE,
290
+ /**
291
+ * `@lunora/sql-store` — the internal, dialect-parameterized SQL store core
292
+ * shared by Lunora's `.global()` table backends.
293
+ *
294
+ * One ORM implementation (`createSqlCtxDb`) drives any SQL engine through a
295
+ * `SqlDialect`: SQLite via `@lunora/d1`, and Postgres/MySQL (PlanetScale, Neon,
296
+ * any Hyperdrive-reachable database) via `@lunora/hyperdrive`. Reactivity is
297
+ * unaffected — the writer is injected as `globalDb` into `createShardCtxDb`,
298
+ * whose `broadcast` hook drives live queries regardless of engine.
299
+ *
300
+ * This package is internal: consumers depend on `@lunora/d1` or
301
+ * `@lunora/hyperdrive`, which assemble the concrete dialect and wrap the core.
302
+ */
303
+ type SqlCtxDbOptions,
304
+ /**
305
+ * `@lunora/sql-store` — the internal, dialect-parameterized SQL store core
306
+ * shared by Lunora's `.global()` table backends.
307
+ *
308
+ * One ORM implementation (`createSqlCtxDb`) drives any SQL engine through a
309
+ * `SqlDialect`: SQLite via `@lunora/d1`, and Postgres/MySQL (PlanetScale, Neon,
310
+ * any Hyperdrive-reachable database) via `@lunora/hyperdrive`. Reactivity is
311
+ * unaffected — the writer is injected as `globalDb` into `createShardCtxDb`,
312
+ * whose `broadcast` hook drives live queries regardless of engine.
313
+ *
314
+ * This package is internal: consumers depend on `@lunora/d1` or
315
+ * `@lunora/hyperdrive`, which assemble the concrete dialect and wrap the core.
316
+ */
317
+ type SqlCtxExec, type SqlDialect, type SqlRunResult, backfillSqlSearchIndexes, createSqlCtxDb, decodeBigint, decodeGlobalRow, effectiveColumnKind, migrateSearchState, readSqlCdcChangedTables, readSqlCdcChanges, readSqlCdcFloor, runSqlAggregateMigrations, runSqlCdcMigration, runSqlGlobalTableMigrations, runSqlRankMigrations, runSqlSearchMigrations, sqliteDecode, sqliteEncode, sweepSqlCdcRetention, tryJsonParse };
package/dist/index.mjs CHANGED
@@ -1,2 +1 @@
1
- export { createSqlCtxDb, decodeGlobalRow, readSqlCdcChanges, runSqlAggregateMigrations, runSqlCdcMigration, runSqlGlobalTableMigrations, runSqlRankMigrations, runSqlSearchMigrations, trimSqlCdcChanges } from './packem_shared/createSqlCtxDb-DYqDyPq8.mjs';
2
- export { decodeBigint, effectiveColumnKind, sqliteDecode, sqliteEncode, tryJsonParse } from './packem_shared/decodeBigint-Dedu92k4.mjs';
1
+ import{createSqlCtxDb as o,readSqlCdcChangedTables as a,readSqlCdcChanges as l,readSqlCdcFloor as t,runSqlAggregateMigrations as n,runSqlCdcMigration as i,runSqlGlobalTableMigrations as d,runSqlRankMigrations as S,sweepSqlCdcRetention as c}from"./packem_shared/createSqlCtxDb-Bn3b-5Wa.mjs";import{b as q,r as g}from"./packem_shared/ctx-db-search-DlItpO6A.mjs";import{SEARCH_STATE_TABLE as f,migrateSearchState as b}from"./packem_shared/SEARCH_STATE_TABLE-DA3LgCT0.mjs";import{decodeBigint as x,effectiveColumnKind as p,sqliteDecode as u,sqliteEncode as h,tryJsonParse as M}from"./packem_shared/decodeBigint-B4wY6fcF.mjs";import{decodeGlobalRow as A}from"./packem_shared/decodeGlobalRow-BKIjk_Zk.mjs";export{f as SEARCH_STATE_TABLE,q as backfillSqlSearchIndexes,o as createSqlCtxDb,x as decodeBigint,A as decodeGlobalRow,p as effectiveColumnKind,b as migrateSearchState,a as readSqlCdcChangedTables,l as readSqlCdcChanges,t as readSqlCdcFloor,n as runSqlAggregateMigrations,i as runSqlCdcMigration,d as runSqlGlobalTableMigrations,S as runSqlRankMigrations,g as runSqlSearchMigrations,u as sqliteDecode,h as sqliteEncode,c as sweepSqlCdcRetention,M as tryJsonParse};
@@ -0,0 +1 @@
1
+ import{sql as i}from"drizzle-orm";import{queryRun as s,queryAll as E}from"./decodeGlobalRow-BKIjk_Zk.mjs";const f="__lunora_search_state",l=async(r,e)=>{const{integer:t,key:o}=e.companionTypes,n=async()=>{try{await s(r,e,i`ALTER TABLE ${i.identifier(f)} ADD COLUMN ${i.identifier("profile")} ${i.raw(o)}`)}catch{}};await s(r,e,i`CREATE TABLE IF NOT EXISTS ${i.identifier(f)} (${i.identifier("companion")} ${i.raw(o)} PRIMARY KEY, ${i.identifier("cursor")} ${i.raw(o)}, ${i.identifier("done")} ${i.raw(t)} NOT NULL DEFAULT 0, ${i.identifier("profile")} ${i.raw(o)})`),await n()},p=r=>r===1||r===!0||r==="1",S=async(r,e,t)=>{const n=(await E(r,e,i`SELECT ${i.identifier("cursor")}, ${i.identifier("done")}, ${i.identifier("profile")} FROM ${i.identifier(f)} WHERE ${i.identifier("companion")} = ${t}`))[0];if(!n)return{cursor:void 0,done:!1,profile:void 0};const{cursor:a,profile:$}=n;return{cursor:typeof a=="string"?a:void 0,done:p(n.done),profile:typeof $=="string"?$:void 0}},A=async(r,e,t,o,n,a)=>{const $=o??null,d=i`UPDATE ${i.identifier(f)} SET ${i.identifier("cursor")} = ${$}, ${i.identifier("done")} = ${n?1:0}, ${i.identifier("profile")} = ${a} WHERE ${i.identifier("companion")} = ${t}`;if((await E(r,e,i`SELECT ${i.identifier("companion")} FROM ${i.identifier(f)} WHERE ${i.identifier("companion")} = ${t}`)).length>0){await s(r,e,d);return}try{await s(r,e,i`INSERT INTO ${i.identifier(f)} (${i.identifier("companion")}, ${i.identifier("cursor")}, ${i.identifier("done")}, ${i.identifier("profile")}) VALUES (${t}, ${$}, ${n?1:0}, ${a})`)}catch(c){if(!e.isUniqueViolation(c))throw c;await s(r,e,d)}};export{f as SEARCH_STATE_TABLE,l as migrateSearchState,S as readSearchBackfillState,A as writeSearchBackfillState};
@@ -0,0 +1 @@
1
+ import{b as S,c as s,a as i,r as l}from"./ctx-db-search-DlItpO6A.mjs";import"./SEARCH_STATE_TABLE-DA3LgCT0.mjs";import"./decodeGlobalRow-BKIjk_Zk.mjs";export{S as backfillSqlSearchIndexes,s as createSearchSync,i as runSqlSearch,l as runSqlSearchMigrations};
@@ -0,0 +1,7 @@
1
+ import{LunoraError as g}from"@lunora/errors";import{c as It,r as Ct,R as At,j as Lt,_ as Nt,M as kt,a as Mt,D as qt,y as Dt,I as Ot}from"./ctx-db-search-DlItpO6A.mjs";import{b as Xn}from"./ctx-db-search-DlItpO6A.mjs";import{literalInList as Bt,throwingScheduler as Ft,CDC_LOG_TABLE as K,cursorBelowRetainedFloor as xt,cdcTrimmedError as Wt,aggregateTableName as Q,rankTableName as ne,hasTrigger as Ut,runRowValidators as Me,assertFlatPredicate as Je,mergeWhere as D,sortColumnName as J,resolveRankPartition as Ze,encodePartitionKey as Ie,decodeCursor as et,RANK_TIEBREAK as ye,CountRlsUnsupportedError as tt,normalizeIdStructurally as Pt,assertValidClientId as jt,aggregateSqlFunction as _e,softDeleteScope as $e,compileWhereSql as he,normalizeOrderKeys as Ht,buildSeekWhere as Kt,resolveRelationPredicates as nt,resolveWith as it,relationHooks as ot,applySelect as rt,encodeCursor as Gt,NotFoundError as Vt,applyOnDelete as Yt,ConflictError as Ee,normalizeCountArgument as Xt,selectIndexForCount as zt,encodeAggregateKey as z,selectIndexForAggregate as Qt,readAggregateValue as qe,runTriggers as Jt,matchesRankStaticWhere as st,selectIndexForGroupBy as Zt,fanOutScalarCounts as en,matchesStaticWhere as De,foldAggregateTally as tn,coerceAggregateNumber as Oe,CDC_LOG_TABLE_SEQ_INDEX as nn,NotUniqueError as on}from"@lunora/shard-engine";import{sql as e}from"drizzle-orm";import{migrateSearchState as rn}from"./SEARCH_STATE_TABLE-DA3LgCT0.mjs";import{queryAll as I,queryRun as L,createIndexIfNotExists as Ce,decodeRow as ie,serializeColumnValue as H,columnRefSql as O,decodeRows as sn,forEachRowPaged as at,queryBatch as ct}from"./decodeGlobalRow-BKIjk_Zk.mjs";import{decodeGlobalRow as Qn}from"./decodeGlobalRow-BKIjk_Zk.mjs";import{effectiveColumnKind as Ae}from"./decodeBigint-B4wY6fcF.mjs";const an=(s,u)=>{if(s.size<u)return;const l=s.keys().next().value;l!==void 0&&s.delete(l)},dt=50,cn=(s,u={})=>{const l=u.maxBatch??dt,i=Number.isInteger(l)&&l>0?l:dt,h=new Map;let d=!1;const c=async(R,b)=>{const v=[...b.keys()];for(let N=0;N<v.length;N+=i){const M=v.slice(N,N+i);try{const x=await s(R,M);for(const G of M){const Y=x.get(G);for(const X of b.get(G)??[])X.resolve(Y)}}catch(x){for(const G of M)for(const Y of b.get(G)??[])Y.reject(x)}}},m=()=>{d=!1;const R=[...h.entries()];h.clear();for(const[b,v]of R)c(b,v).catch(()=>{})};return{async load(R,b){return new Promise((v,N)=>{let M=h.get(R);M||(M=new Map,h.set(R,M));const x=M.get(b);x?x.push({reject:N,resolve:v}):M.set(b,[{reject:N,resolve:v}]),d||(d=!0,queueMicrotask(m))})}}},dn=new Set(["_id","id"]),xe=(s,u,l)=>s==="postgres"?e`${u} IS NOT DISTINCT FROM ${l}`:s==="mysql"?e`${u} <=> ${l}`:e`${u} IS ${l}`,ln=s=>{const u=s.map(l=>e`${O(l.field)} ${e.raw(l.direction==="desc"?"DESC":"ASC")}`);return s.some(l=>dn.has(l.field))||u.push(e`${O("id")} ASC`),e.join(u,e`, `)},lt=50,We=s=>{const u=[];for(const[l,i]of Object.entries(s.shape)){const h=i._meta?.column;h&&u.push([l,h])}return u},un=(s,u,l)=>{const i={...u};for(const[h,d]of We(s)){if(d.serverDefault){i[h]=d.serverDefault({auth:l});continue}i[h]===void 0&&(d.defaultFn?i[h]=d.defaultFn():"defaultValue"in d&&(i[h]=d.defaultValue))}return i},ut=(s,u,l,i)=>{for(const[h,d]of We(s)){if(d.serverDefault){h in u&&(l[h]=d.serverDefault({auth:i}));continue}d.onUpdateFn&&!(h in u)&&(l[h]=d.onUpdateFn())}},fn=128,wn=()=>{const s=new Map;return{delete:u=>{s.delete(u)},get:u=>{const l=s.get(u);if(l!==void 0)return s.delete(u),s.set(u,l),l},set:(u,l)=>{s.has(u)?s.delete(u):an(s,fn),s.set(u,l)}}},_n=async(s,u,l,i,h)=>{const d=h.get(i);if(d!==void 0)return d;const c=Object.entries(l.tables).filter(([,R])=>R.shardMode===void 0||R.shardMode.kind==="global").map(([R])=>R),m=await Promise.all(c.map(async R=>({found:(await I(s,u,e`SELECT 1 FROM ${e.identifier(R)} WHERE ${e.identifier("id")} = ${i} LIMIT 1`)).length>0,tableName:R})));for(const R of m)if(R.found)return h.set(i,R.tableName),R.tableName},oe=s=>s==null?null:Number(s),$n=(s,u)=>u.map(l=>{const i={};for(const h of s)i[h]=l[h]??null;return{key:i,value:oe(l.value)}}),hn=(s,u,l)=>[...s.partitionBy??[],...s.sortBy.map(h=>h.field),...s.where?Object.keys(s.where):[]].every(h=>u[h]===l[h]),wt=s=>s.length===1?s[0]:e`(${e.join(s,e` AND `)})`,te=s=>e.join(s.map(u=>e`${e.identifier(u)}`),e`, `),re=s=>e.join(s.map(u=>e`${u}`),e`, `),En=(s,u,l,i,h)=>{const d=[];for(let c=0;c<l.length+1;c+=1){const m=l.slice(0,c).map(v=>xe(s,e`${e.identifier(v)}`,i[v])),R=l[c],b=u.sortBy[c];c<l.length&&R!==void 0&&b!==void 0?m.push(e`${e.identifier(R)} ${e.raw(b.direction==="desc"?">":"<")} ${i[R]}`):m.push(e`${e.identifier(ye)} < ${h}`),d.push(wt(m))}return d.length>0?e.join(d,e` OR `):void 0},yn=(s,u,l)=>{if(l.length!==u.length)return;const i=[];for(const[h,d]of u.entries()){const c=[];for(let m=0;m<h;m+=1){const R=u[m];R!==void 0&&c.push(xe(s,e`${e.identifier(R.column)}`,l[m]))}c.push(e`${e.identifier(d.column)} ${e.raw(d.direction==="desc"?"<":">")} ${l[h]}`),i.push(wt(c))}return e`(${e.join(i,e` OR `)})`},pn=(s,u)=>{if(s.sortBy.length===0)throw new g("INTERNAL",`rankIndex "${s.name}" requires at least one "sortBy" column for stable pagination`);const l=[{column:"__partition__",direction:"asc"}];for(const[i,h]of s.sortBy.entries())l.push({column:u[i]??J(i),direction:h.direction});return l.push({column:ye,direction:"asc"}),l},Rn=async(s,u,l,i,h)=>{const c=[];for(let v=0;v<h.length;v+=50)c.push(h.slice(v,v+50));const m=await Promise.all(c.map(async v=>I(s,u,e`SELECT * FROM ${e.identifier(i)} WHERE ${e.identifier("id")} IN (${re(v)})`))),R=new Map;for(const v of m)for(const N of v)R.set(N.id,N);const b=[];for(const v of h){const N=ie(l,R.get(v));N&&b.push(N)}return b},Tn=s=>{const u=JSON.stringify(s),l=new TextEncoder().encode(u);let i="";for(const h of l)i+=String.fromCodePoint(h);return btoa(i)},mn=(s,u)=>u.columnType(Ae(s)),Sn=(s,u,l)=>{const i=[];for(const[c,m]of Object.entries(u.shape)){if(!m._meta?.column)continue;const R=m._meta.column.notNull&&m.kind!=="optional"?" NOT NULL":"";i.push(e`${e.identifier(c)} ${e.raw(`${mn(m,l)}${R}`)}`)}const h=l.frameworkColumns().map(c=>e`${e.identifier(c.name)} ${e.raw(c.type)}`),d=h.length+i.length;if(l.maxTableColumns!==void 0&&d>l.maxTableColumns)throw new g("VALIDATION_ERROR",`@lunora/sql-store: global table "${s}" needs ${String(d)} columns, over this engine's ${String(l.maxTableColumns)}-column limit — split the table, or move the extra fields into one object field`);return e.join([...h,...i],e`, `)},gn=async(s,u,l,i)=>{const h=d=>{const c=O(d),m=l.shape[d],R=m&&i.indexKeyPrefix?i.indexKeyPrefix(Ae(m)):void 0;return R===void 0?c:e`${c}(${e.raw(String(R))})`};for(const d of l.indexes){const c=e.join(d.fields.map(m=>h(m)),e`, `);await Ce(s,i,{columns:c,name:`${u}_${d.name}`,table:u,unique:d.unique??!1})}for(const[d,c]of We(l))c.unique&&await Ce(s,i,{columns:h(d),name:`${u}_unique_${d}`,table:u,unique:!0})},vn=async(s,u,l)=>{for(const[i,h]of Object.entries(u.tables)){if(h.shardMode?.kind!=="global")continue;const d=Sn(i,h,l);await L(s,l,e`CREATE TABLE IF NOT EXISTS ${e.identifier(i)} (${d})`),await gn(s,i,h,l)}},bn=async(s,u,l)=>{const{integer:i,key:h,real:d}=l.companionTypes;for(const[c,m]of Object.entries(u.tables)){const R=m.aggregateIndexes;if(!(!R||R.length===0))for(const b of R){const v=Q(c,b.name);await L(s,l,e`CREATE TABLE IF NOT EXISTS ${e.identifier(v)} (${e.identifier("__key__")} ${e.raw(h)} PRIMARY KEY, ${e.identifier("__value__")} ${e.raw(d)}, ${e.identifier("__count__")} ${e.raw(i)} NOT NULL DEFAULT 0)`),l.name==="sqlite"&&((await I(s,l,e`PRAGMA table_info(${e.identifier(v)})`)).some(M=>M.name==="__count__")||await L(s,l,e`ALTER TABLE ${e.identifier(v)} ADD COLUMN ${e.identifier("__count__")} ${e.raw(i)} NOT NULL DEFAULT 0`))}}},Be=(s,u,l,i)=>{const h=s.name==="mysql"&&i?e`${e.identifier(u)}(191)`:e`${e.identifier(u)}`;return e`${h} ${e.raw(l)}`},In=(s,u,l)=>{const i=[Be(s,"__partition__","ASC",!0)];for(const[h,d]of u.sortBy.entries()){const c=l.shape[d.field],m=c!==void 0&&s.indexKeyPrefix?.(Ae(c))!==void 0;i.push(Be(s,J(h),d.direction==="desc"?"DESC":"ASC",m))}return i.push(Be(s,"__id__","ASC",!0)),i},Cn=(s,u,l)=>u.sortBy.map((i,h)=>{const d=l.shape[i.field],c=s.columnType(d?Ae(d):void 0);return e`${e.identifier(J(h))} ${e.raw(c)}`}),An=async(s,u,l)=>{const{key:i}=l.companionTypes;for(const[h,d]of Object.entries(u.tables)){const c=d.rankIndexes;if(!(!c||c.length===0))for(const m of c){const R=ne(h,m.name),b=Cn(l,m,d),v=b.length>0?e`, ${e.join(b,e`, `)}`:e``;await L(s,l,e`CREATE TABLE IF NOT EXISTS ${e.identifier(R)} (${e.identifier("__id__")} ${e.raw(i)} PRIMARY KEY, ${e.identifier("__partition__")} ${e.raw(i)} NOT NULL${v})`);const N=In(l,m,d),M=`${h}__rank_${m.name}__btree`;await Ce(s,l,{columns:e.join(N,e`, `),name:M,table:R,unique:!1})}}},Fe="__cdc_sweep",_t="global",Ln=6e4,Nn=1e4,kn=3e4;let ft=0;const Mn=async(s,u)=>{const{autoincrementPrimaryKey:l,key:i,real:h,text:d}=u.companionTypes;await L(s,u,e`CREATE TABLE IF NOT EXISTS ${e.identifier(K)} (${e.identifier("seq")} ${e.raw(l)}, ${e.identifier("ts")} ${e.raw(h)} NOT NULL, ${e.identifier("table")} ${e.raw(i)} NOT NULL, ${e.identifier("id")} ${e.raw(i)} NOT NULL, ${e.identifier("op")} ${e.raw(i)} NOT NULL, ${e.identifier("doc")} ${e.raw(d)})`);const c=u.indexKeyPrefix?.("string");await Ce(s,u,{columns:e`${c===void 0?e`${e.identifier("table")}`:e`${e.identifier("table")}(${e.raw(String(c))})`}, ${e.identifier("seq")}`,name:nn,table:K,unique:!1}),await L(s,u,e`CREATE TABLE IF NOT EXISTS ${e.identifier(Fe)} (${e.identifier("id")} ${e.raw(i)} NOT NULL PRIMARY KEY, ${e.identifier("lease_until")} ${e.raw(h)} NOT NULL)`),await L(s,u,e`INSERT INTO ${e.identifier(Fe)} (${e.identifier("id")}, ${e.identifier("lease_until")}) VALUES (${_t}, ${0}) ${u.name==="mysql"?e`ON DUPLICATE KEY UPDATE ${e.identifier("id")} = ${e.identifier("id")}`:e`ON CONFLICT(${e.identifier("id")}) DO NOTHING`}`)},qn=async(s,u,l)=>{const i=e`UPDATE ${e.identifier(Fe)} SET ${e.identifier("lease_until")} = ${l+Ln} WHERE ${e.identifier("id")} = ${_t} AND ${e.identifier("lease_until")} <= ${l}`;if(u.supportsReturning)return(await I(s,u,e`${i} RETURNING ${e.identifier("id")}`)).length>0;const h=await L(s,u,i);return(u.affectedRows?u.affectedRows(h??{rowsAffected:0}):0)>0},Dn=async(s,u,l,i)=>{if(!await qn(s,u,i))return;const h=i-l;await L(s,u,e`
2
+ DELETE FROM ${e.identifier(K)} WHERE ${e.identifier("seq")} IN (
3
+ SELECT ${e.identifier("seq")} FROM (
4
+ SELECT ${e.identifier("seq")} FROM ${e.identifier(K)} WHERE ${e.identifier("ts")} <= ${h} ORDER BY ${e.identifier("seq")} ASC LIMIT ${e.raw(String(Nn))}
5
+ ) AS ${e.identifier("expired")}
6
+ )
7
+ `)},$t=async(s,u)=>{const l=await I(s,u,e`SELECT MIN(${e.identifier("seq")}) AS ${e.identifier("seq")} FROM ${e.identifier(K)}`),i=Number(l[0]?.seq??Number.NaN);return Number.isFinite(i)?i:void 0},On=async(s,u,l,i,h,d,c)=>{await L(s,c,e`INSERT INTO ${e.identifier(K)} (${e.identifier("ts")}, ${e.identifier("table")}, ${e.identifier("id")}, ${e.identifier("op")}, ${e.identifier("doc")}) VALUES (${u}, ${l}, ${i}, ${h}, ${d===void 0?null:JSON.stringify(d)})`)},Kn=async(s,u,l)=>{const i=u.sinceSeq??0,h=Math.max(1,Math.min(u.limit??1e3,1e4)),d=await $t(s,l);if(d!==void 0&&xt(d,i))throw Wt(d,i,"global");const m=(await I(s,l,e`SELECT seq, ts, ${e.identifier("table")}, id, op, doc FROM ${e.identifier(K)} WHERE seq > ${i} ORDER BY seq ASC LIMIT ${e.raw(String(h))}`)).map(R=>{const{doc:b}=R,v={id:String(R.id),op:String(R.op),seq:Number(R.seq),table:String(R.table),ts:Number(R.ts)};return typeof b=="string"?{...v,doc:JSON.parse(b)}:v});return{changes:m,cursor:m.at(-1)?.seq??i}},Bn=async(s,u,l,i={})=>{if(i.cursorOnly){const R=await I(s,l,e`SELECT MAX(seq) AS seq FROM ${e.identifier(K)}`),b=Number(R[0]?.seq??u);return{cursor:Number.isFinite(b)?b:u,tables:[]}}const h=await I(s,l,e`SELECT ${e.identifier("table")} AS ${e.identifier("table")}, MAX(seq) AS seq FROM ${e.identifier(K)} WHERE seq > ${u} GROUP BY ${e.identifier("table")}`);let d=u;const c=[];for(const R of h){const b=Number(R.seq);Number.isFinite(b)&&b>d&&(d=b),c.push(String(R.table))}const m=i.retained===!0?await $t(s,l):void 0;return{cursor:d,tables:c,...m===void 0?{}:{floor:m}}},Gn=s=>{const{crossShardCounter:u,crossShardReader:l,exec:i,maxRelationKeys:h,schema:d}=s,{dialect:c}=s,{isUniqueViolation:m}=c,R={fieldRef:O,serialize:H,...c.name==="mysql"?{containsExpr:(t,n)=>e`LOCATE(${n}, ${t}) > 0`}:{},...c.name==="postgres"?{containsExpr:(t,n)=>e`strpos(${t}, ${n}) > 0`}:{},...c.name==="sqlite"?{}:{inList:Bt}},b=(t,n)=>xe(c.name,t,n),v=t=>{const n=te(t.columns),o=re(t.values),r=f=>c.name==="mysql"?e`VALUES(${e.identifier(f)})`:e`excluded.${e.identifier(f)}`,a=f=>c.name==="postgres"?e`${e.identifier(t.table)}.${e.identifier(f)}`:e`${e.identifier(f)}`,_=e.join(Object.entries(t.set(r,a)).map(([f,y])=>e`${e.identifier(f)} = ${y}`),e`, `),E=c.name==="mysql"?e`ON DUPLICATE KEY UPDATE ${_}`:e`ON CONFLICT(${e.identifier(t.conflictKey)}) DO UPDATE SET ${_}`;return e`INSERT INTO ${e.identifier(t.table)} (${n}) VALUES (${o}) ${E}`},N=s.clock??(()=>Date.now()),M=s.idGenerator??(()=>crypto.randomUUID()),x=s.cdc??!1,G=typeof s.cdcRetentionMs=="number"&&Number.isFinite(s.cdcRetentionMs)&&s.cdcRetentionMs>0?s.cdcRetentionMs:void 0,Y=s.auth??{identity:null,userId:null},X=async(t,n,o,r)=>{if(!x||(await On(i,N(),t,n,o,r,c),G===void 0))return;const a=N();if(!(a-ft<kn)){ft=a;try{await Dn(i,c,G,a)}catch{}}},ht=s.scheduler??Ft,Le=wn();let pe=0,Ne;const W=async()=>(Ne??=(async()=>{await vn(i,d,c),await bn(i,d,c),await An(i,d,c),await rn(i,c),await Ct(i,d,c),x&&await Mn(i,c)})().catch(t=>{throw Ne=void 0,t}),Ne),se=async(t,n)=>{await W();const o=await _n(i,c,d,t,Le);if(!(n!==void 0&&o!==n))return o},ae=new Map,Ue=async(t,n)=>{const o=Q(t,n);return(await I(i,c,c.tableExists(o))).length>0},Re=async(t,n)=>{const o=`${t}::${n.name}`,r=ae.get(o);if(r!==void 0)return r;if(!await Ue(t,n.name))return ae.set(o,!1),!1;const _=d.tables[t];if(!_)return ae.set(o,!1),!1;const E=n.by??[],f=new Map;await at(i,c,_,t,$=>{if(n.where&&!De($,n.where))return;const p=z(E,$);tn(f,p,n,$)});const y=Q(t,n.name);await L(i,c,e`DELETE FROM ${e.identifier(y)}`);const w=[];for(const[$,p]of f)w.push(e`INSERT INTO ${e.identifier(y)} (${e.identifier("__key__")}, ${e.identifier("__value__")}, ${e.identifier("__count__")}) VALUES (${$}, ${p.value}, ${p.count})`);return await ct(i,c,w),ae.set(o,!0),!0},Et=async(t,n,o)=>{const r=_e(n.op),a=n.field??"",_=[];for(const y of n.by??[]){const w=H(o[y]??null);_.push(w===null?e`${O(y)} IS NULL`:e`${O(y)} = ${w}`)}for(const[y,w]of Object.entries(n.where??{})){const $=w!==null&&typeof w=="object"&&!Array.isArray(w)?w.eq:w,p=H($);_.push(p===null?e`${O(y)} IS NULL`:e`${O(y)} = ${p}`)}const E=e`SELECT ${e.raw(r)}(${O(a)}) AS value FROM ${e.identifier(t)}`,f=await I(i,c,_.length>0?e`${E} WHERE ${e.join(_,e` AND `)}`:E);return oe(f[0]?.value)},yt=async(t,n)=>{await L(i,c,e`DELETE FROM ${e.identifier(t)} WHERE ${e.identifier("__key__")} = ${n} AND ${e.identifier("__count__")} <= 0`)},pt=async(t,n,o,r)=>{const a=Q(t,n.name),{op:_}=n,E=n.field??"",f=o&&(!n.where||De(o,n.where))?o:void 0,y=r&&(!n.where||De(r,n.where))?r:void 0;if(!(!f&&!y)){if(_==="count"||_==="sum"||_==="avg"){const w=new Set;for(const[$,p]of[[f,-1],[y,1]]){if(!$)continue;const S=_==="count"?1:Oe($[E]);if(S===void 0)continue;const T=z(n.by??[],$);w.add(T),await L(i,c,v({columns:["__key__","__value__","__count__"],conflictKey:"__key__",set:(A,C)=>({__count__:e`${C("__count__")} + ${A("__count__")}`,__value__:_==="count"?e`${C("__value__")} + ${A("__value__")}`:e`COALESCE(${C("__value__")}, 0) + ${A("__value__")}`}),table:a,values:[T,p*S,p]}))}for(const $ of w)await yt(a,$);return}if(f){const w=z(n.by??[],f),$=Oe(f[E]),S=(await I(i,c,e`SELECT ${e.identifier("__value__")} AS value, ${e.identifier("__count__")} AS count FROM ${e.identifier(a)} WHERE ${e.identifier("__key__")} = ${w}`))[0],T=oe(S?.value),A=(S?.count??0)-1;if(A<=0)await L(i,c,e`DELETE FROM ${e.identifier(a)} WHERE ${e.identifier("__key__")} = ${w}`);else if(S&&$!==void 0&&T!==null&&$===T){const C=await Et(t,n,f);await L(i,c,e`UPDATE ${e.identifier(a)} SET ${e.identifier("__value__")} = ${C}, ${e.identifier("__count__")} = ${A} WHERE ${e.identifier("__key__")} = ${w}`)}else await L(i,c,e`UPDATE ${e.identifier(a)} SET ${e.identifier("__count__")} = ${e.identifier("__count__")} - 1 WHERE ${e.identifier("__key__")} = ${w}`)}if(y){const w=z(n.by??[],y),$=Oe(y[E]);if($===void 0)await L(i,c,v({columns:["__key__","__value__","__count__"],conflictKey:"__key__",set:(p,S)=>({__count__:e`${S("__count__")} + 1`}),table:a,values:[w,null,1]}));else{const p=_==="min"?"MIN":"MAX";await L(i,c,v({columns:["__key__","__value__","__count__"],conflictKey:"__key__",set:(S,T)=>({__count__:e`${T("__count__")} + 1`,__value__:e`${e.raw(p)}(COALESCE(${T("__value__")}, ${S("__value__")}), ${S("__value__")})`}),table:a,values:[w,$,1]}))}}}},Te=async t=>{const n=d.tables[t]?.aggregateIndexes;if(!(!n||n.length===0))for(const o of n)await Re(t,o)},ce=async(t,n,o)=>{const r=d.tables[t]?.aggregateIndexes;if(!(!r||r.length===0))for(const a of r){const _=`${t}::${a.name}`;(ae.get(_)??await Ue(t,a.name))&&await pt(t,a,n,o)}},de=new Map,Pe=async(t,n)=>{const o=ne(t,n);return(await I(i,c,c.tableExists(o))).length>0},ke=async(t,n)=>{const o=`${t}::rank::${n.name}`,r=de.get(o);if(r!==void 0)return r;if(!await Pe(t,n.name))return de.set(o,!1),!1;const _=d.tables[t];if(!_)return de.set(o,!1),!1;const E=ne(t,n.name);await L(i,c,e`DELETE FROM ${e.identifier(E)}`);const f=n.sortBy.map((p,S)=>J(S)),y=te(["__id__","__partition__",...f]),w=[];await at(i,c,_,t,p=>{if(n.where&&!st(p,n.where))return;const S=Ie(n.partitionBy??[],p),T=n.sortBy.map(A=>H(p[A.field]??null));w.push([p._id,S,...T])});const $=w.map(p=>e`INSERT INTO ${e.identifier(E)} (${y}) VALUES (${re(p)})`);return await ct(i,c,$),de.set(o,!0),!0},me=async t=>{const n=d.tables[t]?.rankIndexes;if(!(!n||n.length===0))for(const o of n)await ke(t,o)},Z=async(t,n,o,r)=>{const a=d.tables[t],_=a?.rankIndexes;if(!_||_.length===0)return;const E=a?.softDeleteMode?.field,f=E===void 0||r===void 0?void 0:r[E],y=f!=null;for(const w of _){if(o!==void 0&&r!==void 0&&hn(w,o,r))continue;const $=`${t}::rank::${w.name}`;if(!(de.get($)??await Pe(t,w.name)))continue;const T=ne(t,w.name);if(o&&await L(i,c,e`DELETE FROM ${e.identifier(T)} WHERE ${e.identifier("__id__")} = ${n}`),r&&!y){if(w.where&&!st(r,w.where))continue;const A=w.sortBy.map((B,V)=>J(V)),C=te(["__id__","__partition__",...A]),F=Ie(w.partitionBy??[],r),k=w.sortBy.map(B=>H(r[B.field]??null));await L(i,c,e`INSERT INTO ${e.identifier(T)} (${C}) VALUES (${re([n,F,...k])})`)}}},le=It({dialect:c,exec:i,schema:d}),je=new Set;for(const[t,n]of Object.entries(d.tables))for(const o of Object.values(n.triggerMap??{}))je.add(`${t} ${o.timing} ${o.op}`);const P=(t,n,o)=>je.has(`${t} ${n} ${o}`);let He;const j=async(t,n,o)=>{if(pe+=1,pe>lt)throw pe-=1,new Ee(`trigger recursion exceeded ${String(lt)} levels on "${o.table}" — check for a self-triggering write`,"trigger");try{await Jt({ctx:He,event:o,op:n,schema:d,tableName:o.table,timing:t})}finally{pe-=1}},Rt=async(t,n)=>{try{await L(i,c,n)}catch(o){throw m(o)?new Ee(`unique constraint violation on "${t}"`,"unique"):o}},Se=async(t,n)=>(await I(i,c,e`SELECT * FROM ${e.identifier(t)} WHERE ${e.identifier("id")} = ${n}`))[0],ge=async(t,n,o,r)=>{if(r===void 0)return;const a=e.join(Object.keys(r).map(f=>b(e`${e.identifier(f)}`,r[f])),e` AND `),_=n==="UPDATE"?e`UPDATE ${e.identifier(t)} SET ${o} WHERE ${a}`:e`DELETE FROM ${e.identifier(t)} WHERE ${a}`,E=()=>{throw new Ee(`optimistic concurrency conflict on "${t}" — the row changed during this mutation; refetch and retry`,"occ")};try{if(c.supportsReturning)(await I(i,c,e`${_} RETURNING ${e.identifier("id")}`)).length===0&&E();else{const f=await L(i,c,_);(c.affectedRows?c.affectedRows(f??{rowsAffected:0}):0)===0&&E()}}catch(f){throw m(f)?new Ee(`unique constraint violation on "${t}"`,"unique"):f}},Tt=(t,n,o,r)=>{const a=Object.keys(t.shape);return{columns:["id","_creationTime",...a],values:[n,o,...a.map(_=>H(r[_]??null))]}},mt=async(t,n,o,r)=>{const a=Zt(n,o.op,o.field,r.by,r.where);if(!a||!await Re(t,a.index))return;const E=Q(t,a.index.name),f=Object.keys(a.partial);if(f.length===(a.index.by??[]).length&&f.length>0){const w=z(a.index.by??[],a.partial),$=await I(i,c,e`SELECT ${e.identifier("__value__")} AS value, ${e.identifier("__count__")} AS count FROM ${e.identifier(E)} WHERE ${e.identifier("__key__")} = ${w}`);if($.length===0)return[];const p=$[0];return[{key:{...a.partial},value:qe(o.op,{count:p.count,value:oe(p.value)})}]}return f.length>0?void 0:(await I(i,c,e`SELECT ${e.identifier("__key__")} AS key, ${e.identifier("__value__")} AS value, ${e.identifier("__count__")} AS count FROM ${e.identifier(E)}`)).map(w=>{const $=w;return{key:JSON.parse($.key),value:qe(o.op,{count:$.count,value:oe($.value)})}})},Ke=t=>{const n=d.tables[t]?.shardMode?.kind;return n==="shardBy"||n==="root"},Ge=t=>{throw new g("INTERNAL",`cross-backend relation: a global table cannot load the shard-local relation '${t}' (it spans every shard) — wire a cross-shard reader to support it`)},St=(t,n)=>{if(n?.relationMask!==void 0&&n.with!==void 0&&Object.keys(n.with).length>0)throw new g("MASK_UNSUPPORTED",`masking cannot follow a nested \`with\` across the cross-shard relation hop to "${t}" — the nested rows are hydrated on the serving shard, where the mask policy does not exist. Read the nested relation at its own (masked) call site instead.`);const o={};if(n?.relationBaseWhere)for(const r of Object.keys(d.tables)){const a=n.relationBaseWhere(r);a!==void 0&&(o[r]=a)}return{orderBy:n?.orderBy,relationPolicies:o,where:D(n?.baseWhere,n?.where),with:n?.with}},Ve=(t,n)=>Ke(t)?l?l(t,St(t,n)):Ge(t):U.findMany(t,n),ve=(t,n,o)=>nt(t,{fetcher:Ve,maxRelationKeys:h,relationBaseWhere:o,schema:d,tableName:n}),gt=cn(async(t,n)=>{const o=await I(i,c,e`SELECT * FROM ${e.identifier(t)} WHERE ${e.identifier("id")} IN (${re(n)})`),r=new Map;for(const a of o){const{id:_}=a;typeof _=="string"&&r.set(_,a)}return r}),U={async cdcChangedTables(t,n){if(x)try{return await W(),await Bn(i,t,c,{...n,retained:G!==void 0})}catch{return}},async aggregate(t,n){const o=d.tables[t];if(!o)throw new g("INTERNAL",`unknown table: ${t}`);if(await W(),_e(n.op),n.op==="count")return U.count(t,{baseWhere:n.baseWhere,relationBaseWhere:n.relationBaseWhere,restrictsCounts:n.restrictsCounts,where:n.where});if(!n.field)throw new g("INTERNAL",`aggregate(${t}, { op: "${n.op}" }): "field" is required for non-count reducers`);const r=$e(o.softDeleteMode,void 0),a=D(D(n.baseWhere,n.where),r),_=await ve(a,t,n.relationBaseWhere),E=_!==a;if(o.aggregateIndexes&&!n.baseWhere&&!E&&!r){const S=Qt(o.aggregateIndexes,n.op,n.field,n.where);if(S&&await Re(t,S.index)){const A=z(S.index.by??[],S.key),C=Q(t,S.index.name),k=(await I(i,c,e`SELECT ${e.identifier("__value__")} AS value, ${e.identifier("__count__")} AS count FROM ${e.identifier(C)} WHERE ${e.identifier("__key__")} = ${A}`))[0];return qe(n.op,k===void 0?void 0:{count:k.count,value:oe(k.value)})}}const f=he(_,R),y=e.raw(_e(n.op)),w=e`SELECT ${y}(${O(n.field)}) AS value FROM ${e.identifier(t)}`,p=(await I(i,c,f?e`${w} WHERE ${f}`:w))[0]?.value;return p==null?null:Number(p)},async count(t,n){const o=d.tables[t];if(!o)throw new g("INTERNAL",`unknown table: ${t}`);await W();const r=Xt(n);if(r.restrictsCounts)throw new tt(t);const a=$e(o.softDeleteMode,void 0),_=D(D(r.baseWhere,r.where),a),E=await ve(_,t,r.relationBaseWhere),f=E!==_;if(o.aggregateIndexes&&!r.baseWhere&&!f&&!a){const p=zt(o.aggregateIndexes,r.where);if(p&&await Re(t,p.index)){const T=z(p.index.by??[],p.key),A=Q(t,p.index.name),C=await I(i,c,e`SELECT ${e.identifier("__value__")} AS value FROM ${e.identifier(A)} WHERE ${e.identifier("__key__")} = ${T}`);return Number(C[0]?.value??0)}}const y=he(E,R),w=e`SELECT COUNT(*) AS count FROM ${e.identifier(t)}`,$=await I(i,c,y?e`${w} WHERE ${y}`:w);return Number($[0]?.count??0)},async delete(t,n,o){const r=await se(t,n);if(!r)return;const a=d.tables[r];if(!a)return;const _=await Se(r,t),E=ie(a,_),f=o?.hard===!0,y=!f&&a.softDeleteMode?a.softDeleteMode.field:void 0;if(!(y&&(!E||E[y]!==null&&E[y]!==void 0))){if(P(r,"before","delete")&&await j("before","delete",{id:t,op:"delete",previous:E??void 0,table:r}),await Yt({deletedId:t,deletedReference:w=>E?.[w],findHolders:async(w,$,p)=>{if(d.tables[w]?.shardMode?.kind==="shardBy")throw new g("INTERNAL",`cross-backend cascade from global '${r}' into shardBy '${w}' is not supported — would require Query Coordinator fan-out across shards`);return(await U.findMany(w,{includeDeleted:f,where:{[$]:p}})).page},onCascade:(w,$)=>U.delete($,void 0,o),onRestrict:w=>{throw new Ee(w,"restrict")},onSetNull:(w,$,p)=>U.patch($,{[p]:null}),schema:d,tableName:r}),await Te(r),await me(r),y&&E){const w={...E,[y]:N()},$=e.join(Object.keys(a.shape).map(p=>e`${e.identifier(p)} = ${H(w[p]??null)}`),e`, `);await ge(r,"UPDATE",$,_),await ce(r,E,w),await Z(r,t,E,void 0),await le(r,t,w,E),await X(r,t,"update",w),P(r,"after","delete")&&await j("after","delete",{id:t,op:"delete",previous:E,table:r});return}await ge(r,"DELETE",void 0,_),Le.delete(t),await ce(r,E??void 0,void 0),await Z(r,t,E??void 0,void 0),await le(r,t,void 0),await X(r,t,"delete"),P(r,"after","delete")&&await j("after","delete",{id:t,op:"delete",previous:E??void 0,table:r})}},async findFirst(t,n={}){return(await U.findMany(t,{...n,limit:1})).page[0]??null},async findFirstOrThrow(t,n={}){const o=await U.findFirst(t,n);if(o===null)throw new Vt(`findFirstOrThrow: no "${t}" document matched`);return o},async findMany(t,n={}){const o=d.tables[t];if(!o)throw new g("INTERNAL",`unknown table: ${t}`);await W();const r=Ht(n.orderBy),a=n.cursor?Kt(r,et(n.cursor)):void 0,_=Ve,E=async(k,B,V,be)=>{if(Ke(k))return u?en(u,k,B,V,be):Ge(k);const ue=d.tables[k];if(!ue)throw new g("INTERNAL",`unknown table: ${k}`);const ee=$e(ue.softDeleteMode,void 0),q={[B]:{in:V}},fe=D(D(q,be),ee),vt=await ve(fe,k,void 0),Ye=he(vt,R),Xe=O(B);let we=e`SELECT ${Xe} AS __fk__, COUNT(*) AS count FROM ${e.identifier(k)}`;Ye&&(we=e`${we} WHERE ${Ye}`),we=e`${we} GROUP BY ${Xe}`;const bt=await I(i,c,we),ze=new Map;for(const Qe of bt)ze.set(Qe.__fk__,Number(Qe.count??0));return ze};let f=D(n.baseWhere,n.where);f=D(f,$e(o.softDeleteMode,n.includeDeleted)),f=await nt(f,{fetcher:_,maxRelationKeys:h,relationBaseWhere:n.relationBaseWhere,schema:d,tableName:t}),a&&(f=f?{AND:[f,a]}:a);const y=he(f,R),w=ln(r);let $=e`SELECT * FROM ${e.identifier(t)}`;y&&($=e`${$} WHERE ${y}`),$=e`${$} ORDER BY ${w}`;const p=typeof n.limit=="number"?Math.max(0,Math.floor(n.limit)):void 0;p!==void 0&&($=e`${$} LIMIT ${e.raw(String(p+1))}`);const S=await I(i,c,$),T=sn(o,S);if(p===void 0)return n.with&&await it({fetcher:_,groupedCounter:E,parents:T,...ot(n),schema:d,tableName:t,with:n.with}),{continueCursor:null,isDone:!0,page:rt(T,n.select,n.with)};const A=T.length>p,C=A?T.slice(0,p):T,F=C.at(-1);return n.with&&await it({fetcher:_,groupedCounter:E,parents:C,...ot(n),schema:d,tableName:t,with:n.with}),{continueCursor:A&&F?Gt(F,r):null,isDone:!A,page:rt(C,n.select,n.with)}},async get(t,n){const o=await se(t,n);if(!o)return null;const r=d.tables[o];return r?ie(r,await gt.load(o,t)):null},async groupBy(t,n){const o=d.tables[t];if(!o)throw new g("INTERNAL",`unknown table: ${t}`);await W();const r=n.agg??{op:"count"};if(_e(r.op),r.op!=="count"&&!r.field)throw new g("INTERNAL",`groupBy(${t}, { agg: { op: "${r.op}" } }): "field" is required for non-count reducers`);const a=$e(o.softDeleteMode,void 0),_=D(D(n.baseWhere,n.where),a),E=await ve(_,t,n.relationBaseWhere),f=E!==_;if(o.aggregateIndexes&&!n.baseWhere&&!f&&!a){const T=await mt(t,o.aggregateIndexes,r,n);if(T!==void 0)return T}const y=he(E,R),w=n.by.map(T=>e`${O(T)} AS ${e.identifier(T)}`);if(r.op==="count")w.push(e`COUNT(*) AS value`);else{if(!r.field)throw new g("INTERNAL",`groupBy(${t}, { agg: { op: "${r.op}" } }): "field" is required for non-count reducers`);w.push(e`${e.raw(_e(r.op))}(${O(r.field)}) AS value`)}const $=e.join(n.by.map(T=>O(T)),e`, `);let p=e`SELECT ${e.join(w,e`, `)} FROM ${e.identifier(t)}`;y&&(p=e`${p} WHERE ${y}`),p=e`${p} GROUP BY ${$}`;const S=await I(i,c,p);return $n(n.by,S)},async insert(t,n,o){const r=d.tables[t];if(!r)throw new g("INTERNAL",`unknown table: ${t}`);await W();const a=un(r,n,Y);Me(r,a);let _,E=!0;o?.clientId!==void 0?(jt(o.clientId),_=o.clientId):o?.allowExplicitId&&typeof a._id=="string"?_=a._id:(_=M(),E=!1);const f=o?.allowExplicitId&&typeof a._creationTime=="number"?a._creationTime:N(),y={...a,_creationTime:f,_id:_};P(t,"before","insert")&&await j("before","insert",{doc:{...y},id:_,op:"insert",table:t}),await Te(t),await me(t);const{columns:w,values:$}=Tt(r,_,f,a);return await Rt(t,e`INSERT INTO ${e.identifier(t)} (${te(w)}) VALUES (${re($)})`),E&&Le.set(_,t),await ce(t,void 0,y),await Z(t,_,void 0,y),await le(t,_,y),await X(t,_,"insert",y),P(t,"after","insert")&&await j("after","insert",{doc:y,id:_,op:"insert",table:t}),_},normalizeId(t,n){return Pt(d,t,n)},async patch(t,n,o){const r=await se(t,o);if(!r)throw new g("INTERNAL",`document not found: ${t}`);const a=d.tables[r];if(!a)throw new g("INTERNAL",`document not found: ${t}`);const _=await Se(r,t),E=ie(a,_);if(!E)throw new g("INTERNAL",`document not found: ${t}`);const f={...E,...n,_id:t};ut(a,n,f,Y),Me(a,f,!0),P(r,"before","update")&&await j("before","update",{doc:{...f},id:t,op:"update",previous:E,table:r}),await Te(r),await me(r);const y=Object.keys(a.shape),w=e.join(y.map($=>e`${e.identifier($)} = ${H(f[$]??null)}`),e`, `);await ge(r,"UPDATE",w,_),await ce(r,E,f),await Z(r,t,E,f),await le(r,t,f,E),await X(r,t,"update",f),P(r,"after","update")&&await j("after","update",{doc:f,id:t,op:"update",previous:E,table:r})},async restore(t,n){const o=await se(t,n);if(!o)throw new g("INTERNAL",`document not found: ${t}`);const r=d.tables[o],a=r?.softDeleteMode?.field;if(!r||!a)throw new g("INTERNAL",`ctx.db.restore: table "${o}" is not a .softDelete() table`);const _=await Se(o,t),E=_?.[a]!==null&&_?.[a]!==void 0;if(await U.patch(t,{[a]:null},n),E){const f=ie(r,_);f!==null&&await Z(o,t,void 0,{...f,[a]:null})}},query(t){const n=d.tables[t];if(!n)throw new g("INTERNAL",`unknown table: ${t}`);const o="the legacy query()/withIndex() reader is not available on the D1 (global) backend; use findMany",r=[],a=f=>r.every(y=>y(f)),_=async(f,y)=>{await W();const w=r.length>0,$=await Mt(i,c,n,t,f,qt(w?void 0:y));if(!w)return y===void 0&&Dt($),$;const p=$.filter(S=>a(S));return y===void 0?p:p.slice(0,y)},E=f=>{const y={async*[Symbol.asyncIterator](){if(!f)throw new g("INTERNAL",o);yield*await _(f,void 0)},async collect(){if(!f)throw new g("INTERNAL",o);return _(f,void 0)},async collectWithScores(){throw new g("INTERNAL",`collectWithScores() is not supported on \`.global()\` tables (table "${t}") — relevance scores are not yet surfaced on this backend; use .collect() instead`)},filter(w){return r.push(w),y},async first(){if(!f)throw new g("INTERNAL",o);return(await _(f,1))[0]??null},order(){return y},async paginate(w){if(!f)throw new g("INTERNAL",o);const $=Nt(w);return kt(await _(f,Ot($)),$)},async take(w){if(!f)throw new g("INTERNAL",o);return _(f,w)},async unique(){if(!f)throw new g("INTERNAL",o);const w=await _(f,2);if(w.length>1)throw new on(`unique() on table "${t}" matched ${String(w.length)} documents; expected at most one`);return w[0]??null},withGeoIndex(){throw new g("INTERNAL",`geo indexes are not supported on \`.global()\` tables (table "${t}") — geospatial queries run only on sharded tables`)},withIndex(){throw new g("INTERNAL",o)},withSearchIndex(w,$){const p=(n.searchIndexes??[]).find(T=>T.name===w);if(!p)throw new g("INTERNAL",`unknown search index "${w}" on table "${t}"`);const S={definition:p,field:p.field,filters:[],hasQuery:!1,indexName:w,query:""};if($(At(S,t,Lt(p.language))),!S.hasQuery)throw new g("INTERNAL",`search index "${w}" on table "${t}" requires a .search(field, query) call`);return E(S)}};return y};return E(void 0)},async rank(t,n,o){const r=d.tables[t];if(!r)throw new g("INTERNAL",`unknown table: ${t}`);const a=r.rankIndexes?.find(B=>B.name===n);if(!a)throw new g("INTERNAL",`unknown rankIndex "${n}" on table "${t}"`);if(o.restrictsCounts)throw new tt(t);if(await W(),!await ke(t,a))return null;const E=typeof o.row=="string"?o.row:o.row._id;if(!E)return null;const f=ne(t,a.name),y=a.sortBy.map((B,V)=>J(V)),w=te(["__partition__",...y]),p=(await I(i,c,e`SELECT ${w} FROM ${e.identifier(f)} WHERE ${e.identifier("__id__")} = ${E}`))[0];if(!p)return null;const S=p.__partition__,T=D(o.baseWhere,o.where);Je(T,d,t,"rank");const A=Ze(a,T);if(A&&Ie(a.partitionBy??[],A)!==S)return null;const C=En(c.name,a,y,p,E),F=await I(i,c,e`SELECT COUNT(*) AS c FROM ${e.identifier(f)} WHERE ${e.identifier("__partition__")} = ${S}${C?e` AND (${C})`:e``}`),k=await I(i,c,e`SELECT COUNT(*) AS c FROM ${e.identifier(f)} WHERE ${e.identifier("__partition__")} = ${S}`);return{position:Number(F[0]?.c??0)+1,total:Number(k[0]?.c??0)}},async rankPage(t,n,o={}){Je(D(o.baseWhere,o.where),d,t,"rankPage");const r=d.tables[t];if(!r)throw new g("INTERNAL",`unknown table: ${t}`);const a=r.rankIndexes?.find(q=>q.name===n);if(!a)throw new g("INTERNAL",`unknown rankIndex "${n}" on table "${t}"`);if(await W(),!await ke(t,a))return{continueCursor:null,isDone:!0,page:[]};const E=ne(t,a.name),f=a.sortBy.map((q,fe)=>J(fe)),y=Math.max(1,Math.min(1e3,Math.floor(o.take??100))),w=D(o.baseWhere,o.where),$=Ze(a,w),p=pn(a,f),S=e.join(p.map(q=>e`${e.identifier(q.column)} ${e.raw(q.direction==="desc"?"DESC":"ASC")}`),e`, `),T=[];if($&&T.push(e`${e.identifier("__partition__")} = ${Ie(a.partitionBy??[],$)}`),o.cursor){const q=yn(c.name,p,et(o.cursor));q!==void 0&&T.push(q)}const A=te([ye,"__partition__",...f]);let C=e`SELECT ${A} FROM ${e.identifier(E)}`;T.length>0&&(C=e`${C} WHERE ${e.join(T,e` AND `)}`),C=e`${C} ORDER BY ${S} LIMIT ${e.raw(String(y+1))}`;const F=await I(i,c,C),k=F.length>y,B=k?F.slice(0,y):F,V=B.map(q=>q[ye]),be=await Rn(i,c,r,t,V);let ue=null;const ee=B.at(-1);if(k&&ee!==void 0){const q=[ee.__partition__,...f.map(fe=>ee[fe]),ee[ye]];ue=Tn(q)}return{continueCursor:ue,isDone:!k,page:be}},async replace(t,n,o,r){const a=await se(t,o);if(!a)throw new g("INTERNAL",`document not found: ${t}`);const _=d.tables[a];if(!_)throw new g("INTERNAL",`document not found: ${t}`);const E=await Se(a,t);if(E===void 0)throw new g("INTERNAL",`document not found: ${t}`);const y=Ut(d,a,"update")||(_.aggregateIndexes??[]).length>0||(_.rankIndexes??[]).length>0?ie(_,E)??void 0:void 0,w=r?.allowExplicitId&&typeof n._creationTime=="number"?n._creationTime:N(),$={...n,_creationTime:w,_id:t};ut(_,n,$,Y),Me(_,$),P(a,"before","update")&&await j("before","update",{doc:{...$},id:t,op:"update",previous:y,table:a}),await Te(a),await me(a);const p=Object.keys(_.shape),S=e.join([e`${e.identifier("_creationTime")} = ${w}`,...p.map(T=>e`${e.identifier(T)} = ${H($[T]??null)}`)],e`, `);await ge(a,"UPDATE",S,E),await ce(a,y,$),await Z(a,t,y,$),await le(a,t,$,y),await X(a,t,"update",$),P(a,"after","update")&&await j("after","update",{doc:$,id:t,op:"update",previous:y,table:a})}};return He={db:U,scheduler:ht},U};export{Xn as backfillSqlSearchIndexes,Gn as createSqlCtxDb,Qn as decodeGlobalRow,Bn as readSqlCdcChangedTables,Kn as readSqlCdcChanges,$t as readSqlCdcFloor,bn as runSqlAggregateMigrations,Mn as runSqlCdcMigration,vn as runSqlGlobalTableMigrations,An as runSqlRankMigrations,Ct as runSqlSearchMigrations,Dn as sweepSqlCdcRetention};
@@ -0,0 +1 @@
1
+ import{sql as t}from"drizzle-orm";import{migrateSearchState as Z,readSearchBackfillState as P,writeSearchBackfillState as I}from"./SEARCH_STATE_TABLE-DA3LgCT0.mjs";import{LunoraError as h}from"@lunora/errors";import{unionAll as ee}from"@lunora/shard-engine";import{queryRun as S,decodeRows as z,queryAll as L,queryBatch as ne,createIndexIfNotExists as U,columnRefSql as M,serializeColumnValue as te,forEachRowPaged as ie}from"./decodeGlobalRow-BKIjk_Zk.mjs";const re=["de","en","es","fr","it","nl","none","pt"],oe=e=>re.includes(e),se="a an and are as at be but by for if in into is it no not of on or such that the their then there these they this to was will with",ae="aber als am an auch auf aus bei bin bis bist da dass der den des dem die das denn dir du ein eine für hat ich im in ist mit nicht noch nur oder sich sie sind über und von vor war wie wir zu zum zur",ce="a al como con de del el en es la las lo los mas no o para pero por que se su sus un una uno y ya",ue="au aux avec ce ces dans de des du elle en et eux il je la le les leur lui ma mais me même mes moi mon ne nos notre nous on ou par pas pour qu que qui sa se ses son sur ta te tes toi ton tu un une vos votre vous y",fe="a ai al alla anche che chi ci coi col come con da dal degli dei del della di do e ed gli ha hai hanno i il in la le lo ma mi ne nei nel non o per più quale quanto se si sono su sul tra un una uno vi",de="aan al als bij dan dat de der deze die dit door een en er het hij ij in is je kan me men met mij na naar niet nog nu of om ons ook op over te tot uit van voor was wat we wij zij zijn zo",le="a ao aos as até com como da das de do dos e em entre era essa esse esta este eu foi há isso já mais mas me mesmo meu na nas no nos num numa o os ou para pela pelo por qual que quem se sem seu só sua também te tem um uma você",me=/[\u0300-\u036F]/gu,W=e=>e.normalize("NFD").replaceAll(me,"").normalize("NFC").toLowerCase(),g=e=>new Set(W(e).split(" ")),$e={de:g(ae),en:g(se),es:g(ce),fr:g(ue),it:g(fe),nl:g(de),none:new Set,pt:g(le)},he=2,we=256,j=new Map,y=e=>{const n=e!==void 0&&oe(e)?e:"none",i=j.get(n);if(i)return i;const r=$e[n],o=a=>{const c=(W(a).match(/[\p{L}\p{N}]+/gu)??[]).filter(u=>u.length<=we);return r.size===0?c:c.filter(u=>!r.has(u))},s={document:o,profile:`${n}-v${String(he)}`,query:a=>{const c=o(a);return c.filter((u,f)=>c.lastIndexOf(u)===f)}};return j.set(n,s),s},Se=(e,n)=>e.profile!==n?{cursor:void 0,finished:!1,wipe:e.cursor!==void 0||e.done}:{cursor:e.cursor,finished:e.done,wipe:!1},pe=(e,n)=>{if(!n.includes("."))return e[n];let i=e;for(const r of n.split(".")){if(i===null||typeof i!="object"||Array.isArray(i))return;i=i[r]}return i},v=(e,n)=>`${e}__fts_${n}`,Ee="__text__",l="__id__",T="__token__",D="__n__",ge=1e3,Q=(e,n)=>n.document(e),C=(e,n)=>pe(e,n),_e=e=>typeof e=="string"?e:e==null?"":typeof e=="number"||typeof e=="bigint"||typeof e=="boolean"?String(e):JSON.stringify(e)??"",ye=(e,n,i)=>e!==void 0&&n!==void 0&&C(e,i.field)===C(n,i.field),O=(e,n)=>Q(_e(C(e,n.field)),y(n.language)).slice(0,ge).join(" "),Te=(e,n)=>{const i=new Map;for(const r of Q(e,n))i.set(r,(i.get(r)??0)+1);return i},Ne=e=>{const n=new TextEncoder().encode(e);let i="";for(const r of n)i+=String.fromCodePoint(r);return btoa(i)},Ie=e=>{const n=atob(e),i=Uint8Array.from(n,r=>r.codePointAt(0)??0);return new TextDecoder().decode(i)},ve=()=>new h("BAD_REQUEST","invalid cursor"),B=16,F=8,w=1024,R=(e,n)=>n.query(e),Re=(e,n)=>{if(!n)return{exact:!0,lower:e,upper:e};const i=[...e].at(-1)??"",r=(i.codePointAt(0)??0)+1;if(r>=55296&&r<=57343||r>1114111)return{exact:!0,lower:e,upper:e};const o=e.slice(0,e.length-i.length);return{exact:!1,lower:e,upper:o+String.fromCodePoint(r)}},He=(e,n,i)=>{const r={eq:(o,s)=>{if(!e.definition.filterFields?.includes(o))throw new h("INTERNAL",`field "${o}" is not a filter field of search index "${e.indexName}" on table "${n}"`);if(e.filters.length>=F)throw new h("BAD_REQUEST",`search index "${e.indexName}" on table "${n}": at most ${String(F)} .eq() filters are supported per search query`);return e.filters.push({field:o,value:s}),r},search:(o,s)=>{const a=e;if(o!==a.definition.field)throw new h("INTERNAL",`search index "${a.indexName}" on table "${n}" indexes "${a.definition.field}", not "${o}"`);const c=R(s,i).length;if(c>B)throw new h("BAD_REQUEST",`search index "${a.indexName}" on table "${n}": at most ${String(B)} search terms are supported (got ${String(c)})`);return a.field=o,a.query=s,a.hasQuery=!0,r}};return r},Pe=e=>{if(e.length>w)throw new h("BAD_REQUEST",`more than ${String(w)} documents match this search — narrow it with filters or read it a page at a time with .paginate()`)},ze=e=>Math.min(e.offset+e.numItems+1,w),be=e=>Ne(`search:${String(e)}`),Ae=e=>{let n;try{n=Ie(e)}catch{return}if(!n.startsWith("search:"))return;const i=Number(n.slice(7));return Number.isInteger(i)&&i>=0?i:void 0},We=e=>{if(typeof e.endCursor=="string")throw new h("BAD_REQUEST","bounded (endCursor) pagination is not supported on search queries — relevance order has no stable range boundary");if(!Number.isFinite(e.numItems))throw new h("BAD_REQUEST",`search pagination needs a finite numItems, got ${String(e.numItems)}`);const n=Math.max(0,Math.floor(e.numItems)),i=e.cursor?Ae(e.cursor):0;if(i===void 0)throw ve();if(i+n>=w)throw new h("BAD_REQUEST",`search pagination reaches the ${String(w)}-document limit (offset ${String(i)} + ${String(n)} requested) — a page must end below the cap so the probe row that answers \`hasMore\` still fits: retry with numItems ${String(Math.max(1,w-i-1))} or fewer, or narrow the query or the filters instead`);return{numItems:n,offset:i}},Qe=(e,n)=>{const i=n.offset+n.numItems,r=n.numItems>0&&e.length>i;return{continueCursor:r?be(i):null,isDone:!r,page:e.slice(n.offset,i)}},Ge=e=>{if(e===void 0)return w+1;if(!Number.isFinite(e))return w;const n=Math.max(0,Math.floor(e));if(n>w)throw new h("BAD_REQUEST",`search returns at most ${String(w)} documents (asked for ${String(n)}) — narrow the query or paginate instead`);return n},b=async(e,n,i,r)=>{await S(e,n,t`DELETE FROM ${t.identifier(i)} WHERE ${t.identifier(l)} = ${r}`)},A=(e,n)=>e.name==="mysql"?t`${t.identifier(n)}(191)`:e.textPatternOperatorClass===void 0?t`${t.identifier(n)}`:t`${t.identifier(n)} ${t.raw(e.textPatternOperatorClass)}`,k=50,De=(e,n)=>n?t`${t.identifier(T)} LIKE ${`${e}%`}`:t`${t.identifier(T)} = ${e}`,G=(e,n)=>{const i=n.filters.map(r=>t`m.${M(r.field)} = ${te(r.value)}`);return e.softDeleteMode&&i.push(t`m.${M(e.softDeleteMode.field)} IS NULL`),i},V=async(e,n,i,r,o,s,a)=>{const c=G(i,o);let u=t`SELECT m.* FROM (${a}) s JOIN ${t.identifier(r)} m ON m.${t.identifier("id")} = s.${t.identifier(l)}`;return c.length>0&&(u=t`${u} WHERE ${t.join(c,t` AND `)}`),u=t`${u} ORDER BY s.${t.identifier("__score__")} DESC, m.${t.identifier("_creationTime")} DESC, m.${t.identifier("id")} ASC LIMIT ${t.raw(String(s))}`,z(i,await L(e,n,u))},Ce=async(e,n,i,r,o,s)=>{const a=R(o.query,y(o.definition.language));if(a.length===0)return[];const c=v(r,o.indexName),u=a.length-1,f=a.map((d,_)=>De(d,_===u)),m=t.join(f,t` OR `),$=f.map(d=>t`SUM(CASE WHEN ${d} THEN ${t.identifier(D)} ELSE 0 END)`),E=t.join($.map(d=>t`${d} > 0`),t` AND `),p=t`SELECT ${t.identifier(l)}, ${t.join($,t` + `)} AS ${t.identifier("__score__")} FROM ${t.identifier(c)} WHERE ${m} GROUP BY ${t.identifier(l)} HAVING ${E}`;return V(e,n,i,r,o,s,p)},Le=async(e,n,i,r,o,s)=>{const a=R(o.query,y(o.definition.language));if(a.length===0)return[];const c=v(r,o.indexName),u=`${c}__vocab`,f=a.length-1,m=a.map((p,d)=>{const _=Re(p,d===f),K=_.exact?t`${t.identifier("term")} = ${_.lower}`:t`${t.identifier("term")} >= ${_.lower} AND ${t.identifier("term")} < ${_.upper}`;return t`SELECT ${t.identifier("doc")}, ${t.raw(String(d))} AS ${t.identifier("__term__")}, COUNT(*) AS ${t.identifier("__n__")} FROM ${t.identifier(u)} WHERE ${K} GROUP BY ${t.identifier("doc")}`}),$=a.map((p,d)=>t`SUM(CASE WHEN u.${t.identifier("__term__")} = ${t.raw(String(d))} THEN u.${t.identifier("__n__")} ELSE 0 END)`),E=t`SELECT f.${t.identifier(l)} AS ${t.identifier(l)}, ${t.join($,t` + `)} AS ${t.identifier("__score__")} FROM (${ee(m)}) u JOIN ${t.identifier(c)} f ON f.rowid = u.${t.identifier("doc")} GROUP BY f.${t.identifier(l)} HAVING ${t.join($.map(p=>t`${p} > 0`),t` AND `)}`;return V(e,n,i,r,o,s,E)},Oe=async(e,n,i,r,o,s)=>{const a=n.nativeTextSearch,c=R(o.query,y(o.definition.language));if(!a||c.length===0)return[];const u=v(r,o.indexName),f=[a.matches(u,c),...G(i,o)],m=t`SELECT m.* FROM ${t.identifier(u)} JOIN ${t.identifier(r)} m ON m.${t.identifier("id")} = ${t.identifier(u)}.${t.identifier(l)} WHERE ${t.join(f,t` AND `)} ORDER BY ${a.rank(u,c)} DESC, m.${t.identifier("_creationTime")} DESC, m.${t.identifier("id")} ASC LIMIT ${t.raw(String(s))}`;return z(i,await L(e,n,m))},qe={ensureCompanion:async(e,n,i)=>{const{integer:r,key:o}=n.companionTypes;await S(e,n,t`CREATE TABLE IF NOT EXISTS ${t.identifier(i)} (${t.identifier(T)} ${t.raw(o)} NOT NULL, ${t.identifier(l)} ${t.raw(o)} NOT NULL, ${t.identifier(D)} ${t.raw(r)} NOT NULL)`),await U(e,n,{columns:t`${A(n,T)}, ${A(n,l)}`,name:`${i}__btree`,table:i,unique:!1}),await U(e,n,{columns:A(n,l),name:`${i}__by_id`,table:i,unique:!1})},indexDocument:async(e,n,i,r,o,s)=>{await b(e,n,i,r);const a=[...Te(O(o,s),y(s.language))],c=t.join([T,l,D].map(f=>t.identifier(f)),t`, `),u=[];for(let f=0;f<a.length;f+=k){const m=t.join(a.slice(f,f+k).map(([$,E])=>t`(${$}, ${r}, ${E})`),t`, `);u.push(t`INSERT INTO ${t.identifier(i)} (${c}) VALUES ${m}`)}await ne(e,n,u)},name:"inverted",runSearch:Ce},xe={ensureCompanion:async(e,n,i)=>{await S(e,n,t`CREATE VIRTUAL TABLE IF NOT EXISTS ${t.identifier(i)} USING fts5(${t.identifier(Ee)}, ${t.identifier(l)} UNINDEXED)`),await S(e,n,t`CREATE VIRTUAL TABLE IF NOT EXISTS ${t.identifier(`${i}__vocab`)} USING fts5vocab(${t.identifier(i)}, ${t.raw("instance")})`)},indexDocument:async(e,n,i,r,o,s)=>{await b(e,n,i,r),await S(e,n,t`INSERT INTO ${t.identifier(i)} (${t.identifier("__text__")}, ${t.identifier(l)}) VALUES (${O(o,s)}, ${r})`)},name:"fts5",runSearch:Le},Ue={ensureCompanion:async(e,n,i)=>{const r=n.nativeTextSearch;if(r){await S(e,n,r.createCompanion(i,n.companionTypes.key));for(const o of r.createIndexes(i))await S(e,n,o)}},indexDocument:async(e,n,i,r,o,s)=>{const a=n.nativeTextSearch;a&&(await b(e,n,i,r),await S(e,n,a.indexDocument(i,r,O(o,s))))},name:"native",runSearch:Oe},N=(e,n)=>e.strategy==="native"&&n.nativeTextSearch!==void 0?Ue:n.supportsFts5?xe:qe,J=(e,n)=>`${y(e.language).profile}/${N(e,n).name}`,q=function*(e){for(const[n,i]of Object.entries(e.tables)){const r=i.searchIndexes;if(!(i.shardMode!==void 0&&i.shardMode.kind!=="global"||!r))for(const o of r)yield[n,i,o]}},x=(e,n)=>v(e,n.name),Ve=async(e,n,i,r,o,s)=>N(o.definition,n).runSearch(e,n,i,r,o,s),H=200,X=async(e,n,i,r,o)=>{const s=x(r,o),a=N(o,n),c=J(o,n),u=Se(await P(e,n,s),c);if(u.finished)return!0;if(u.wipe&&(await S(e,n,t`DELETE FROM ${t.identifier(s)}`),await I(e,n,s,void 0,!1,c)),(await L(e,n,n.tableExists(r))).length===0)return await I(e,n,s,void 0,!0,c),!0;let m=0,$=u.cursor;await ie(e,n,i,r,async p=>{m+=1;const d=p._id;typeof d=="string"&&($=d,await a.indexDocument(e,n,s,d,p,o))},{after:u.cursor,limit:H});const E=m<H;return await I(e,n,s,$,E,c),E},Y=async(e,n,i)=>{await Z(e,i);for(const[r,,o]of q(n)){const s=x(r,o),a=J(o,i),c=await P(e,i,s);c.profile!==void 0&&c.profile!==a&&(await S(e,i,t`DROP TABLE IF EXISTS ${t.identifier(s)}`),await I(e,i,s,void 0,!1,a)),await N(o,i).ensureCompanion(e,i,s)}},Je=async(e,n,i)=>{await Y(e,n,i);for(const[r,o,s]of q(n))s.staged||await X(e,i,o,r,s)},Xe=async(e,n,i)=>{await Y(e,n,i);for(const[r,o,s]of q(n)){let a=!1;for(;!a;)a=await X(e,i,o,r,s)}},Ye=e=>{const{dialect:n,exec:i,schema:r}=e;return async(o,s,a,c)=>{const u=r.tables[o]?.searchIndexes;if(!(!u||u.length===0))for(const f of u){if(ye(c,a,f))continue;const m=x(o,f);if(a){await N(f,n).indexDocument(i,n,m,s,a,f);continue}await b(i,n,m,s)}}};export{Ge as D,ze as I,Qe as M,He as R,We as _,Ve as a,Xe as b,Ye as c,y as j,Je as r,Pe as y};
@@ -0,0 +1 @@
1
+ const r=t=>{if(t.kind!=="optional")return t.kind;const n=t._meta?.inner;return n?r(n):t.kind},f=t=>typeof t=="boolean"?t?1:0:t===null||typeof t=="string"||typeof t=="number"?t:typeof t=="bigint"?t.toString():t instanceof Uint8Array?t:t instanceof ArrayBuffer?new Uint8Array(t):JSON.stringify(t),e=t=>{try{return JSON.parse(t)}catch{return t}},i=t=>{if(typeof t!="string")return t;try{return BigInt(t)}catch{return t}},s=t=>r(t),c=(t,n)=>{if(t===null)return t;switch(n){case"any":case"from":case"union":return typeof t=="string"&&(t.startsWith("{")||t.startsWith("["))?e(t):t;case"array":case"object":case"record":return typeof t=="string"?e(t):t;case"bigint":return i(t);case"boolean":return t===0||t===1?t===1:t;case"bytes":return t instanceof ArrayBuffer?t:ArrayBuffer.isView(t)?new Uint8Array(t.buffer,t.byteOffset,t.byteLength).slice().buffer:t;default:return t}};export{i as decodeBigint,s as effectiveColumnKind,c as sqliteDecode,f as sqliteEncode,e as tryJsonParse};
@@ -0,0 +1 @@
1
+ import{renderSql as u}from"@lunora/shard-engine";import{sql as i}from"drizzle-orm";import{sqliteEncode as I,sqliteDecode as y,effectiveColumnKind as C}from"./decodeBigint-B4wY6fcF.mjs";const g=t=>t==="_id"||t==="id"?"id":t,A=t=>i`${i.identifier(g(t))}`,S=(t,n,e)=>{const{params:o,sql:r}=u(n.name,e);return t.all(r,o)},l=(t,n,e)=>{const{params:o,sql:r}=u(n.name,e);return t.run(r,o)},b=async(t,n,e)=>{if(e.length!==0){if(!t.batch){for(const o of e)await l(t,n,o);return}await t.batch(e.map(o=>u(n.name,o)))}},M=async(t,n,e)=>{const o=e.unique?i`UNIQUE `:i``;if(n.name==="mysql"){try{await l(t,n,i`CREATE ${o}INDEX ${i.identifier(e.name)} ON ${i.identifier(e.table)} (${e.columns})`)}catch(r){const a=r;if(a.errno!==1061&&a.code!=="ER_DUP_KEYNAME"&&a.code!==1061)throw r}return}await l(t,n,i`CREATE ${o}INDEX IF NOT EXISTS ${i.identifier(e.name)} ON ${i.identifier(e.table)} (${e.columns})`)},O=I,h=new WeakMap,T=t=>{let n=h.get(t);return n===void 0&&(n=Object.entries(t.shape).map(([e,o])=>[e,C(o)]),h.set(t,n)),n},p=(t,n)=>{const e={};for(const[o,r]of T(t)){const a=n[o];a!==void 0&&(e[o]=y(a,r))}return e._id=n.id,e._creationTime=n._creationTime,e},$=(t,n)=>n?p(t,n):null,D=(t,n)=>{const e=[];for(const o of n){const r=$(t,o);r&&e.push(r)}return e},w=500,K=async(t,n,e,o,r,a={})=>{let d=a.after,c=a.limit,f=!0;for(;f&&(c===void 0||c>0);){const m=c===void 0?w:Math.min(w,c),R=d===void 0?i``:i` WHERE ${i.identifier("id")} > ${d}`,s=await S(t,n,i`SELECT * FROM ${i.identifier(o)}${R} ORDER BY ${i.identifier("id")} ASC LIMIT ${i.raw(String(m))}`);for(const q of s){const E=$(e,q);E&&await r(E)}d=s.at(-1)?.id,f=s.length===m,c=c===void 0?void 0:c-s.length}};export{w as BACKFILL_BATCH_SIZE,A as columnRefSql,M as createIndexIfNotExists,p as decodeGlobalRow,$ as decodeRow,D as decodeRows,K as forEachRowPaged,g as physicalColumn,S as queryAll,b as queryBatch,l as queryRun,O as serializeColumnValue};