@lunora/sql-store 1.0.0-alpha.89 → 1.0.0-alpha.90

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/README.md CHANGED
@@ -83,7 +83,8 @@ seam from the root); consumers import everything from the root `@lunora/sql-stor
83
83
  - Migration runners (each takes `(exec, schema, dialect)`, or `(exec, dialect)`
84
84
  for CDC): `runSqlGlobalTableMigrations`, `runSqlAggregateMigrations`,
85
85
  `runSqlRankMigrations`, `runSqlSearchMigrations`, `runSqlCdcMigration`.
86
- - CDC log helpers: `readSqlCdcChanges`, `trimSqlCdcChanges`.
86
+ - CDC log helpers: `readSqlCdcChanges`, `readSqlCdcChangedTables`,
87
+ `readSqlCdcFloor`, `sweepSqlCdcRetention`.
87
88
  - Value codec building blocks a dialect reuses for `encode`/`decode`:
88
89
  `sqliteEncode`, `sqliteDecode`, `decodeBigint`, `tryJsonParse`,
89
90
  `effectiveColumnKind`.
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { TableDefinitionLike, SchemaLike, ServerDefaultContextLike, DatabaseWriterLike, CrossShardReadArgs, QueryPage, SchedulerLike, ValidatorLike } from '@lunora/shard-engine';
1
+ import { TableDefinitionLike, SchemaLike, ServerDefaultContextLike, DatabaseWriterLike, CrossShardReadArgs, QueryPage, SchedulerLike, CdcChange, ValidatorLike } from '@lunora/shard-engine';
2
2
  import { SqlRunResult, SqlDialect } from "./dialect.mjs";
3
3
  export type { SqlExec } from "./dialect.mjs";
4
4
  import 'drizzle-orm';
@@ -65,6 +65,25 @@ interface SqlCtxDbOptions {
65
65
  * legacy behaviour.
66
66
  */
67
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;
68
87
  clock?: () => number;
69
88
  /**
70
89
  * Cross-shard counter for **reverse cross-backend relations** — the `_count`
@@ -145,20 +164,35 @@ declare const runSqlAggregateMigrations: (exec: SqlCtxExec, schema: SchemaLike,
145
164
  * Idempotent (`CREATE TABLE IF NOT EXISTS` + `createIndexIfNotExists`).
146
165
  */
147
166
  declare const runSqlRankMigrations: (exec: SqlCtxExec, schema: SchemaLike, dialect: SqlDialect) => Promise<void>;
148
- /** One change-data-capture entry: a committed mutation, in monotonic `seq` order. Mirrors the DO twin. */
149
- interface CdcChange {
150
- /** Post-image document for insert/update; absent for delete (the `id` identifies the removed row). */
151
- doc?: Record<string, unknown>;
152
- id: string;
153
- op: "delete" | "insert" | "update";
154
- /** Monotonic per-database cursor — strictly increasing, never reused. */
155
- seq: number;
156
- table: string;
157
- /** Wall-clock millis when the change committed (the ctx-db `clock`). */
158
- ts: number;
159
- }
160
167
  /** Create the `__cdc_log` table. Idempotent; only run when CDC is enabled. */
161
168
  declare const runSqlCdcMigration: (exec: SqlCtxExec, dialect: SqlDialect) => Promise<void>;
169
+ /**
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>;
162
196
  /**
163
197
  * Read changelog entries newer than `sinceSeq` in commit order, up to `limit`
164
198
  * (clamped to [1, 10000]); plus the cursor to resume from.
@@ -170,8 +204,39 @@ declare const readSqlCdcChanges: (exec: SqlCtxExec, options: {
170
204
  changes: CdcChange[];
171
205
  cursor: number;
172
206
  }>;
173
- /** Drop changelog entries at or below a checkpointed `throughSeq` (retention). */
174
- 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
+ }>;
175
240
  declare const createSqlCtxDb: (options: SqlCtxDbOptions) => DatabaseWriterLike;
176
241
  /** Reserved table holding one backfill-progress row per search companion. */
177
242
  declare const SEARCH_STATE_TABLE = "__lunora_search_state";
@@ -249,4 +314,4 @@ type SqlCtxDbOptions,
249
314
  * This package is internal: consumers depend on `@lunora/d1` or
250
315
  * `@lunora/hyperdrive`, which assemble the concrete dialect and wrap the core.
251
316
  */
252
- type SqlCtxExec, type SqlDialect, type SqlRunResult, backfillSqlSearchIndexes, createSqlCtxDb, decodeBigint, decodeGlobalRow, effectiveColumnKind, migrateSearchState, readSqlCdcChanges, runSqlAggregateMigrations, runSqlCdcMigration, runSqlGlobalTableMigrations, runSqlRankMigrations, runSqlSearchMigrations, sqliteDecode, sqliteEncode, trimSqlCdcChanges, tryJsonParse };
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.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { TableDefinitionLike, SchemaLike, ServerDefaultContextLike, DatabaseWriterLike, CrossShardReadArgs, QueryPage, SchedulerLike, ValidatorLike } from '@lunora/shard-engine';
1
+ import { TableDefinitionLike, SchemaLike, ServerDefaultContextLike, DatabaseWriterLike, CrossShardReadArgs, QueryPage, SchedulerLike, CdcChange, ValidatorLike } from '@lunora/shard-engine';
2
2
  import { SqlRunResult, SqlDialect } from "./dialect.js";
3
3
  export type { SqlExec } from "./dialect.js";
4
4
  import 'drizzle-orm';
@@ -65,6 +65,25 @@ interface SqlCtxDbOptions {
65
65
  * legacy behaviour.
66
66
  */
67
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;
68
87
  clock?: () => number;
69
88
  /**
70
89
  * Cross-shard counter for **reverse cross-backend relations** — the `_count`
@@ -145,20 +164,35 @@ declare const runSqlAggregateMigrations: (exec: SqlCtxExec, schema: SchemaLike,
145
164
  * Idempotent (`CREATE TABLE IF NOT EXISTS` + `createIndexIfNotExists`).
146
165
  */
147
166
  declare const runSqlRankMigrations: (exec: SqlCtxExec, schema: SchemaLike, dialect: SqlDialect) => Promise<void>;
148
- /** One change-data-capture entry: a committed mutation, in monotonic `seq` order. Mirrors the DO twin. */
149
- interface CdcChange {
150
- /** Post-image document for insert/update; absent for delete (the `id` identifies the removed row). */
151
- doc?: Record<string, unknown>;
152
- id: string;
153
- op: "delete" | "insert" | "update";
154
- /** Monotonic per-database cursor — strictly increasing, never reused. */
155
- seq: number;
156
- table: string;
157
- /** Wall-clock millis when the change committed (the ctx-db `clock`). */
158
- ts: number;
159
- }
160
167
  /** Create the `__cdc_log` table. Idempotent; only run when CDC is enabled. */
161
168
  declare const runSqlCdcMigration: (exec: SqlCtxExec, dialect: SqlDialect) => Promise<void>;
169
+ /**
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>;
162
196
  /**
163
197
  * Read changelog entries newer than `sinceSeq` in commit order, up to `limit`
164
198
  * (clamped to [1, 10000]); plus the cursor to resume from.
@@ -170,8 +204,39 @@ declare const readSqlCdcChanges: (exec: SqlCtxExec, options: {
170
204
  changes: CdcChange[];
171
205
  cursor: number;
172
206
  }>;
173
- /** Drop changelog entries at or below a checkpointed `throughSeq` (retention). */
174
- 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
+ }>;
175
240
  declare const createSqlCtxDb: (options: SqlCtxDbOptions) => DatabaseWriterLike;
176
241
  /** Reserved table holding one backfill-progress row per search companion. */
177
242
  declare const SEARCH_STATE_TABLE = "__lunora_search_state";
@@ -249,4 +314,4 @@ type SqlCtxDbOptions,
249
314
  * This package is internal: consumers depend on `@lunora/d1` or
250
315
  * `@lunora/hyperdrive`, which assemble the concrete dialect and wrap the core.
251
316
  */
252
- type SqlCtxExec, type SqlDialect, type SqlRunResult, backfillSqlSearchIndexes, createSqlCtxDb, decodeBigint, decodeGlobalRow, effectiveColumnKind, migrateSearchState, readSqlCdcChanges, runSqlAggregateMigrations, runSqlCdcMigration, runSqlGlobalTableMigrations, runSqlRankMigrations, runSqlSearchMigrations, sqliteDecode, sqliteEncode, trimSqlCdcChanges, tryJsonParse };
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 +1 @@
1
- import{createSqlCtxDb as o,readSqlCdcChanges as a,runSqlAggregateMigrations as t,runSqlCdcMigration as i,runSqlGlobalTableMigrations as l,runSqlRankMigrations as n,trimSqlCdcChanges as S}from"./packem_shared/createSqlCtxDb-BUEDZFiZ.mjs";import{b as s,r as d}from"./packem_shared/ctx-db-search-DlItpO6A.mjs";import{SEARCH_STATE_TABLE as q,migrateSearchState as f}from"./packem_shared/SEARCH_STATE_TABLE-DA3LgCT0.mjs";import{decodeBigint as C,effectiveColumnKind as x,sqliteDecode as b,sqliteEncode as u,tryJsonParse as h}from"./packem_shared/decodeBigint-B4wY6fcF.mjs";import{decodeGlobalRow as M}from"./packem_shared/decodeGlobalRow-BKIjk_Zk.mjs";export{q as SEARCH_STATE_TABLE,s as backfillSqlSearchIndexes,o as createSqlCtxDb,C as decodeBigint,M as decodeGlobalRow,x as effectiveColumnKind,f as migrateSearchState,a as readSqlCdcChanges,t as runSqlAggregateMigrations,i as runSqlCdcMigration,l as runSqlGlobalTableMigrations,n as runSqlRankMigrations,d as runSqlSearchMigrations,b as sqliteDecode,u as sqliteEncode,S as trimSqlCdcChanges,h as tryJsonParse};
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,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};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/sql-store",
3
- "version": "1.0.0-alpha.89",
3
+ "version": "1.0.0-alpha.90",
4
4
  "description": "Internal dialect-parameterized SQL store core for Lunora .global() backends (D1, PlanetScale)",
5
5
  "keywords": [
6
6
  "cloudflare",
@@ -50,8 +50,8 @@
50
50
  "access": "public"
51
51
  },
52
52
  "dependencies": {
53
- "@lunora/errors": "1.0.0-alpha.22",
54
- "@lunora/shard-engine": "1.0.0-alpha.37",
53
+ "@lunora/errors": "1.0.0-alpha.23",
54
+ "@lunora/shard-engine": "1.0.0-alpha.38",
55
55
  "drizzle-orm": "^0.45.2"
56
56
  },
57
57
  "engines": {
@@ -1 +0,0 @@
1
- import{LunoraError as v}from"@lunora/errors";import{c as Tt,r as gt,R as vt,j as St,_ as It,M as bt,a as At,D as Ct,y as Lt,I as kt}from"./ctx-db-search-DlItpO6A.mjs";import{b as xn}from"./ctx-db-search-DlItpO6A.mjs";import{literalInList as Nt,throwingScheduler as Mt,aggregateTableName as z,rankTableName as ee,hasTrigger as Dt,runRowValidators as Ne,assertFlatPredicate as Je,mergeWhere as q,sortColumnName as X,resolveRankPartition as Qe,encodePartitionKey as Se,decodeCursor as Ze,RANK_TIEBREAK as he,CountRlsUnsupportedError as et,normalizeIdStructurally as qt,assertValidClientId as xt,aggregateSqlFunction as fe,softDeleteScope as we,compileWhereSql as _e,normalizeOrderKeys as Bt,buildSeekWhere as Ft,resolveRelationPredicates as tt,resolveWith as nt,relationHooks as ot,applySelect as it,encodeCursor as Ot,NotFoundError as Wt,applyOnDelete as Ut,ConflictError as $e,normalizeCountArgument as jt,selectIndexForCount as Pt,encodeAggregateKey as Y,selectIndexForAggregate as Kt,readAggregateValue as Me,runTriggers as Ht,matchesRankStaticWhere as rt,selectIndexForGroupBy as Vt,fanOutScalarCounts as Gt,matchesStaticWhere as De,foldAggregateTally as Yt,coerceAggregateNumber as qe,NotUniqueError as zt}from"@lunora/shard-engine";import{sql as e}from"drizzle-orm";import{migrateSearchState as Xt}from"./SEARCH_STATE_TABLE-DA3LgCT0.mjs";import{queryAll as A,queryRun as k,createIndexIfNotExists as Be,decodeRow as te,serializeColumnValue as H,columnRefSql as x,decodeRows as Jt,forEachRowPaged as st,queryBatch as at}from"./decodeGlobalRow-BKIjk_Zk.mjs";import{decodeGlobalRow as Fn}from"./decodeGlobalRow-BKIjk_Zk.mjs";import{effectiveColumnKind as Ie}from"./decodeBigint-B4wY6fcF.mjs";const Qt=(c,_)=>{if(c.size<_)return;const f=c.keys().next().value;f!==void 0&&c.delete(f)},ct=50,Zt=(c,_={})=>{const f=_.maxBatch??ct,r=Number.isInteger(f)&&f>0?f:ct,p=new Map;let l=!1;const a=async(m,I)=>{const S=[...I.keys()];for(let L=0;L<S.length;L+=r){const M=S.slice(L,L+r);try{const W=await c(m,M);for(const V of M){const U=W.get(V);for(const Ae of I.get(V)??[])Ae.resolve(U)}}catch(W){for(const V of M)for(const U of I.get(V)??[])U.reject(W)}}},R=()=>{l=!1;const m=[...p.entries()];p.clear();for(const[I,S]of m)a(I,S).catch(()=>{})};return{async load(m,I){return new Promise((S,L)=>{let M=p.get(m);M||(M=new Map,p.set(m,M));const W=M.get(I);W?W.push({reject:L,resolve:S}):M.set(I,[{reject:L,resolve:S}]),l||(l=!0,queueMicrotask(R))})}}},en=new Set(["_id","id"]),Fe=(c,_,f)=>c==="postgres"?e`${_} IS NOT DISTINCT FROM ${f}`:c==="mysql"?e`${_} <=> ${f}`:e`${_} IS ${f}`,tn=c=>{const _=c.map(f=>e`${x(f.field)} ${e.raw(f.direction==="desc"?"DESC":"ASC")}`);return c.some(f=>en.has(f.field))||_.push(e`${x("id")} ASC`),e.join(_,e`, `)},lt=50,Oe=c=>{const _=[];for(const[f,r]of Object.entries(c.shape)){const p=r._meta?.column;p&&_.push([f,p])}return _},nn=(c,_,f)=>{const r={..._};for(const[p,l]of Oe(c)){if(l.serverDefault){r[p]=l.serverDefault({auth:f});continue}r[p]===void 0&&(l.defaultFn?r[p]=l.defaultFn():"defaultValue"in l&&(r[p]=l.defaultValue))}return r},ut=(c,_,f,r)=>{for(const[p,l]of Oe(c)){if(l.serverDefault){p in _&&(f[p]=l.serverDefault({auth:r}));continue}l.onUpdateFn&&!(p in _)&&(f[p]=l.onUpdateFn())}},on=128,rn=()=>{const c=new Map;return{delete:_=>{c.delete(_)},get:_=>{const f=c.get(_);if(f!==void 0)return c.delete(_),c.set(_,f),f},set:(_,f)=>{c.has(_)?c.delete(_):Qt(c,on),c.set(_,f)}}},sn=async(c,_,f,r,p)=>{const l=p.get(r);if(l!==void 0)return l;const a=Object.entries(f.tables).filter(([,m])=>m.shardMode===void 0||m.shardMode.kind==="global").map(([m])=>m),R=await Promise.all(a.map(async m=>({found:(await A(c,_,e`SELECT 1 FROM ${e.identifier(m)} WHERE ${e.identifier("id")} = ${r} LIMIT 1`)).length>0,tableName:m})));for(const m of R)if(m.found)return p.set(r,m.tableName),m.tableName},ne=c=>c==null?null:Number(c),an=(c,_)=>_.map(f=>{const r={};for(const p of c)r[p]=f[p]??null;return{key:r,value:ne(f.value)}}),cn=(c,_,f)=>[...c.partitionBy??[],...c.sortBy.map(p=>p.field),...c.where?Object.keys(c.where):[]].every(p=>_[p]===f[p]),dt=c=>c.length===1?c[0]:e`(${e.join(c,e` AND `)})`,Z=c=>e.join(c.map(_=>e`${e.identifier(_)}`),e`, `),oe=c=>e.join(c.map(_=>e`${_}`),e`, `),ln=(c,_,f,r,p)=>{const l=[];for(let a=0;a<f.length+1;a+=1){const R=f.slice(0,a).map(S=>Fe(c,e`${e.identifier(S)}`,r[S])),m=f[a],I=_.sortBy[a];a<f.length&&m!==void 0&&I!==void 0?R.push(e`${e.identifier(m)} ${e.raw(I.direction==="desc"?">":"<")} ${r[m]}`):R.push(e`${e.identifier(he)} < ${p}`),l.push(dt(R))}return l.length>0?e.join(l,e` OR `):void 0},un=(c,_,f)=>{if(f.length!==_.length)return;const r=[];for(const[p,l]of _.entries()){const a=[];for(let R=0;R<p;R+=1){const m=_[R];m!==void 0&&a.push(Fe(c,e`${e.identifier(m.column)}`,f[R]))}a.push(e`${e.identifier(l.column)} ${e.raw(l.direction==="desc"?"<":">")} ${f[p]}`),r.push(dt(a))}return e`(${e.join(r,e` OR `)})`},dn=(c,_)=>{if(c.sortBy.length===0)throw new v("INTERNAL",`rankIndex "${c.name}" requires at least one "sortBy" column for stable pagination`);const f=[{column:"__partition__",direction:"asc"}];for(const[r,p]of c.sortBy.entries())f.push({column:_[r]??X(r),direction:p.direction});return f.push({column:he,direction:"asc"}),f},fn=async(c,_,f,r,p)=>{const a=[];for(let S=0;S<p.length;S+=50)a.push(p.slice(S,S+50));const R=await Promise.all(a.map(async S=>A(c,_,e`SELECT * FROM ${e.identifier(r)} WHERE ${e.identifier("id")} IN (${oe(S)})`))),m=new Map;for(const S of R)for(const L of S)m.set(L.id,L);const I=[];for(const S of p){const L=te(f,m.get(S));L&&I.push(L)}return I},wn=c=>{const _=JSON.stringify(c),f=new TextEncoder().encode(_);let r="";for(const p of f)r+=String.fromCodePoint(p);return btoa(r)},_n=(c,_)=>_.columnType(Ie(c)),$n=(c,_,f)=>{const r=[];for(const[a,R]of Object.entries(_.shape)){if(!R._meta?.column)continue;const m=R._meta.column.notNull&&R.kind!=="optional"?" NOT NULL":"";r.push(e`${e.identifier(a)} ${e.raw(`${_n(R,f)}${m}`)}`)}const p=f.frameworkColumns().map(a=>e`${e.identifier(a.name)} ${e.raw(a.type)}`),l=p.length+r.length;if(f.maxTableColumns!==void 0&&l>f.maxTableColumns)throw new v("VALIDATION_ERROR",`@lunora/sql-store: global table "${c}" needs ${String(l)} columns, over this engine's ${String(f.maxTableColumns)}-column limit — split the table, or move the extra fields into one object field`);return e.join([...p,...r],e`, `)},hn=async(c,_,f,r)=>{const p=l=>{const a=x(l),R=f.shape[l],m=R&&r.indexKeyPrefix?r.indexKeyPrefix(Ie(R)):void 0;return m===void 0?a:e`${a}(${e.raw(String(m))})`};for(const l of f.indexes){const a=e.join(l.fields.map(R=>p(R)),e`, `);await Be(c,r,{columns:a,name:`${_}_${l.name}`,table:_,unique:l.unique??!1})}for(const[l,a]of Oe(f))a.unique&&await Be(c,r,{columns:p(l),name:`${_}_unique_${l}`,table:_,unique:!0})},yn=async(c,_,f)=>{for(const[r,p]of Object.entries(_.tables)){if(p.shardMode?.kind!=="global")continue;const l=$n(r,p,f);await k(c,f,e`CREATE TABLE IF NOT EXISTS ${e.identifier(r)} (${l})`),await hn(c,r,p,f)}},En=async(c,_,f)=>{const{integer:r,key:p,real:l}=f.companionTypes;for(const[a,R]of Object.entries(_.tables)){const m=R.aggregateIndexes;if(!(!m||m.length===0))for(const I of m){const S=z(a,I.name);await k(c,f,e`CREATE TABLE IF NOT EXISTS ${e.identifier(S)} (${e.identifier("__key__")} ${e.raw(p)} PRIMARY KEY, ${e.identifier("__value__")} ${e.raw(l)}, ${e.identifier("__count__")} ${e.raw(r)} NOT NULL DEFAULT 0)`),f.name==="sqlite"&&((await A(c,f,e`PRAGMA table_info(${e.identifier(S)})`)).some(M=>M.name==="__count__")||await k(c,f,e`ALTER TABLE ${e.identifier(S)} ADD COLUMN ${e.identifier("__count__")} ${e.raw(r)} NOT NULL DEFAULT 0`))}}},xe=(c,_,f,r)=>{const p=c.name==="mysql"&&r?e`${e.identifier(_)}(191)`:e`${e.identifier(_)}`;return e`${p} ${e.raw(f)}`},pn=(c,_,f)=>{const r=[xe(c,"__partition__","ASC",!0)];for(const[p,l]of _.sortBy.entries()){const a=f.shape[l.field],R=a!==void 0&&c.indexKeyPrefix?.(Ie(a))!==void 0;r.push(xe(c,X(p),l.direction==="desc"?"DESC":"ASC",R))}return r.push(xe(c,"__id__","ASC",!0)),r},mn=(c,_,f)=>_.sortBy.map((r,p)=>{const l=f.shape[r.field],a=c.columnType(l?Ie(l):void 0);return e`${e.identifier(X(p))} ${e.raw(a)}`}),Rn=async(c,_,f)=>{const{key:r}=f.companionTypes;for(const[p,l]of Object.entries(_.tables)){const a=l.rankIndexes;if(!(!a||a.length===0))for(const R of a){const m=ee(p,R.name),I=mn(f,R,l),S=I.length>0?e`, ${e.join(I,e`, `)}`:e``;await k(c,f,e`CREATE TABLE IF NOT EXISTS ${e.identifier(m)} (${e.identifier("__id__")} ${e.raw(r)} PRIMARY KEY, ${e.identifier("__partition__")} ${e.raw(r)} NOT NULL${S})`);const L=pn(f,R,l),M=`${p}__rank_${R.name}__btree`;await Be(c,f,{columns:e.join(L,e`, `),name:M,table:m,unique:!1})}}},be="__cdc_log",Tn=async(c,_)=>{const{autoincrementPrimaryKey:f,key:r,real:p,text:l}=_.companionTypes;await k(c,_,e`CREATE TABLE IF NOT EXISTS ${e.identifier(be)} (${e.identifier("seq")} ${e.raw(f)}, ${e.identifier("ts")} ${e.raw(p)} NOT NULL, ${e.identifier("table")} ${e.raw(r)} NOT NULL, ${e.identifier("id")} ${e.raw(r)} NOT NULL, ${e.identifier("op")} ${e.raw(r)} NOT NULL, ${e.identifier("doc")} ${e.raw(l)})`)},gn=async(c,_,f,r,p,l,a)=>{await k(c,a,e`INSERT INTO ${e.identifier(be)} (${e.identifier("ts")}, ${e.identifier("table")}, ${e.identifier("id")}, ${e.identifier("op")}, ${e.identifier("doc")}) VALUES (${_}, ${f}, ${r}, ${p}, ${l===void 0?null:JSON.stringify(l)})`)},kn=async(c,_,f)=>{const r=_.sinceSeq??0,p=Math.max(1,Math.min(_.limit??1e3,1e4)),a=(await A(c,f,e`SELECT seq, ts, ${e.identifier("table")}, id, op, doc FROM ${e.identifier(be)} WHERE seq > ${r} ORDER BY seq ASC LIMIT ${e.raw(String(p))}`)).map(R=>{const{doc:m}=R,I={id:String(R.id),op:String(R.op),seq:Number(R.seq),table:String(R.table),ts:Number(R.ts)};return typeof m=="string"?{...I,doc:JSON.parse(m)}:I});return{changes:a,cursor:a.at(-1)?.seq??r}},Nn=async(c,_,f)=>{await k(c,f,e`DELETE FROM ${e.identifier(be)} WHERE ${e.identifier("seq")} <= ${_}`)},Mn=c=>{const{crossShardCounter:_,crossShardReader:f,exec:r,maxRelationKeys:p,schema:l}=c,{dialect:a}=c,{isUniqueViolation:R}=a,m={fieldRef:x,serialize:H,...a.name==="mysql"?{containsExpr:(t,n)=>e`LOCATE(${n}, ${t}) > 0`}:{},...a.name==="postgres"?{containsExpr:(t,n)=>e`strpos(${t}, ${n}) > 0`}:{},...a.name==="sqlite"?{}:{inList:Nt}},I=(t,n)=>Fe(a.name,t,n),S=t=>{const n=Z(t.columns),o=oe(t.values),i=u=>a.name==="mysql"?e`VALUES(${e.identifier(u)})`:e`excluded.${e.identifier(u)}`,s=u=>a.name==="postgres"?e`${e.identifier(t.table)}.${e.identifier(u)}`:e`${e.identifier(u)}`,w=e.join(Object.entries(t.set(i,s)).map(([u,y])=>e`${e.identifier(u)} = ${y}`),e`, `),h=a.name==="mysql"?e`ON DUPLICATE KEY UPDATE ${w}`:e`ON CONFLICT(${e.identifier(t.conflictKey)}) DO UPDATE SET ${w}`;return e`INSERT INTO ${e.identifier(t.table)} (${n}) VALUES (${o}) ${h}`},L=c.clock??(()=>Date.now()),M=c.idGenerator??(()=>crypto.randomUUID()),W=c.cdc??!1,V=c.auth??{identity:null,userId:null},U=async(t,n,o,i)=>{W&&await gn(r,L(),t,n,o,i,a)},Ae=c.scheduler??Mt,Ce=rn();let ye=0,Le;const j=async()=>(Le??=(async()=>{await yn(r,l,a),await En(r,l,a),await Rn(r,l,a),await Xt(r,a),await gt(r,l,a),W&&await Tn(r,a)})().catch(t=>{throw Le=void 0,t}),Le),ie=async(t,n)=>{await j();const o=await sn(r,a,l,t,Ce);if(!(n!==void 0&&o!==n))return o},re=new Map,We=async(t,n)=>{const o=z(t,n);return(await A(r,a,a.tableExists(o))).length>0},Ee=async(t,n)=>{const o=`${t}::${n.name}`,i=re.get(o);if(i!==void 0)return i;if(!await We(t,n.name))return re.set(o,!1),!1;const w=l.tables[t];if(!w)return re.set(o,!1),!1;const h=n.by??[],u=new Map;await st(r,a,w,t,$=>{if(n.where&&!De($,n.where))return;const E=Y(h,$);Yt(u,E,n,$)});const y=z(t,n.name);await k(r,a,e`DELETE FROM ${e.identifier(y)}`);const d=[];for(const[$,E]of u)d.push(e`INSERT INTO ${e.identifier(y)} (${e.identifier("__key__")}, ${e.identifier("__value__")}, ${e.identifier("__count__")}) VALUES (${$}, ${E.value}, ${E.count})`);return await at(r,a,d),re.set(o,!0),!0},ft=async(t,n,o)=>{const i=fe(n.op),s=n.field??"",w=[];for(const y of n.by??[]){const d=H(o[y]??null);w.push(d===null?e`${x(y)} IS NULL`:e`${x(y)} = ${d}`)}for(const[y,d]of Object.entries(n.where??{})){const $=d!==null&&typeof d=="object"&&!Array.isArray(d)?d.eq:d,E=H($);w.push(E===null?e`${x(y)} IS NULL`:e`${x(y)} = ${E}`)}const h=e`SELECT ${e.raw(i)}(${x(s)}) AS value FROM ${e.identifier(t)}`,u=await A(r,a,w.length>0?e`${h} WHERE ${e.join(w,e` AND `)}`:h);return ne(u[0]?.value)},wt=async(t,n)=>{await k(r,a,e`DELETE FROM ${e.identifier(t)} WHERE ${e.identifier("__key__")} = ${n} AND ${e.identifier("__count__")} <= 0`)},_t=async(t,n,o,i)=>{const s=z(t,n.name),{op:w}=n,h=n.field??"",u=o&&(!n.where||De(o,n.where))?o:void 0,y=i&&(!n.where||De(i,n.where))?i:void 0;if(!(!u&&!y)){if(w==="count"||w==="sum"||w==="avg"){const d=new Set;for(const[$,E]of[[u,-1],[y,1]]){if(!$)continue;const g=w==="count"?1:qe($[h]);if(g===void 0)continue;const T=Y(n.by??[],$);d.add(T),await k(r,a,S({columns:["__key__","__value__","__count__"],conflictKey:"__key__",set:(C,b)=>({__count__:e`${b("__count__")} + ${C("__count__")}`,__value__:w==="count"?e`${b("__value__")} + ${C("__value__")}`:e`COALESCE(${b("__value__")}, 0) + ${C("__value__")}`}),table:s,values:[T,E*g,E]}))}for(const $ of d)await wt(s,$);return}if(u){const d=Y(n.by??[],u),$=qe(u[h]),g=(await A(r,a,e`SELECT ${e.identifier("__value__")} AS value, ${e.identifier("__count__")} AS count FROM ${e.identifier(s)} WHERE ${e.identifier("__key__")} = ${d}`))[0],T=ne(g?.value),C=(g?.count??0)-1;if(C<=0)await k(r,a,e`DELETE FROM ${e.identifier(s)} WHERE ${e.identifier("__key__")} = ${d}`);else if(g&&$!==void 0&&T!==null&&$===T){const b=await ft(t,n,u);await k(r,a,e`UPDATE ${e.identifier(s)} SET ${e.identifier("__value__")} = ${b}, ${e.identifier("__count__")} = ${C} WHERE ${e.identifier("__key__")} = ${d}`)}else await k(r,a,e`UPDATE ${e.identifier(s)} SET ${e.identifier("__count__")} = ${e.identifier("__count__")} - 1 WHERE ${e.identifier("__key__")} = ${d}`)}if(y){const d=Y(n.by??[],y),$=qe(y[h]);if($===void 0)await k(r,a,S({columns:["__key__","__value__","__count__"],conflictKey:"__key__",set:(E,g)=>({__count__:e`${g("__count__")} + 1`}),table:s,values:[d,null,1]}));else{const E=w==="min"?"MIN":"MAX";await k(r,a,S({columns:["__key__","__value__","__count__"],conflictKey:"__key__",set:(g,T)=>({__count__:e`${T("__count__")} + 1`,__value__:e`${e.raw(E)}(COALESCE(${T("__value__")}, ${g("__value__")}), ${g("__value__")})`}),table:s,values:[d,$,1]}))}}}},pe=async t=>{const n=l.tables[t]?.aggregateIndexes;if(!(!n||n.length===0))for(const o of n)await Ee(t,o)},se=async(t,n,o)=>{const i=l.tables[t]?.aggregateIndexes;if(!(!i||i.length===0))for(const s of i){const w=`${t}::${s.name}`;(re.get(w)??await We(t,s.name))&&await _t(t,s,n,o)}},ae=new Map,Ue=async(t,n)=>{const o=ee(t,n);return(await A(r,a,a.tableExists(o))).length>0},ke=async(t,n)=>{const o=`${t}::rank::${n.name}`,i=ae.get(o);if(i!==void 0)return i;if(!await Ue(t,n.name))return ae.set(o,!1),!1;const w=l.tables[t];if(!w)return ae.set(o,!1),!1;const h=ee(t,n.name);await k(r,a,e`DELETE FROM ${e.identifier(h)}`);const u=n.sortBy.map((E,g)=>X(g)),y=Z(["__id__","__partition__",...u]),d=[];await st(r,a,w,t,E=>{if(n.where&&!rt(E,n.where))return;const g=Se(n.partitionBy??[],E),T=n.sortBy.map(C=>H(E[C.field]??null));d.push([E._id,g,...T])});const $=d.map(E=>e`INSERT INTO ${e.identifier(h)} (${y}) VALUES (${oe(E)})`);return await at(r,a,$),ae.set(o,!0),!0},me=async t=>{const n=l.tables[t]?.rankIndexes;if(!(!n||n.length===0))for(const o of n)await ke(t,o)},J=async(t,n,o,i)=>{const s=l.tables[t],w=s?.rankIndexes;if(!w||w.length===0)return;const h=s?.softDeleteMode?.field,u=h===void 0||i===void 0?void 0:i[h],y=u!=null;for(const d of w){if(o!==void 0&&i!==void 0&&cn(d,o,i))continue;const $=`${t}::rank::${d.name}`;if(!(ae.get($)??await Ue(t,d.name)))continue;const T=ee(t,d.name);if(o&&await k(r,a,e`DELETE FROM ${e.identifier(T)} WHERE ${e.identifier("__id__")} = ${n}`),i&&!y){if(d.where&&!rt(i,d.where))continue;const C=d.sortBy.map((B,G)=>X(G)),b=Z(["__id__","__partition__",...C]),F=Se(d.partitionBy??[],i),N=d.sortBy.map(B=>H(i[B.field]??null));await k(r,a,e`INSERT INTO ${e.identifier(T)} (${b}) VALUES (${oe([n,F,...N])})`)}}},ce=Tt({dialect:a,exec:r,schema:l}),je=new Set;for(const[t,n]of Object.entries(l.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 Pe;const K=async(t,n,o)=>{if(ye+=1,ye>lt)throw ye-=1,new $e(`trigger recursion exceeded ${String(lt)} levels on "${o.table}" — check for a self-triggering write`,"trigger");try{await Ht({ctx:Pe,event:o,op:n,schema:l,tableName:o.table,timing:t})}finally{ye-=1}},$t=async(t,n)=>{try{await k(r,a,n)}catch(o){throw R(o)?new $e(`unique constraint violation on "${t}"`,"unique"):o}},Re=async(t,n)=>(await A(r,a,e`SELECT * FROM ${e.identifier(t)} WHERE ${e.identifier("id")} = ${n}`))[0],Te=async(t,n,o,i)=>{if(i===void 0)return;const s=e.join(Object.keys(i).map(u=>I(e`${e.identifier(u)}`,i[u])),e` AND `),w=n==="UPDATE"?e`UPDATE ${e.identifier(t)} SET ${o} WHERE ${s}`:e`DELETE FROM ${e.identifier(t)} WHERE ${s}`,h=()=>{throw new $e(`optimistic concurrency conflict on "${t}" — the row changed during this mutation; refetch and retry`,"occ")};try{if(a.supportsReturning)(await A(r,a,e`${w} RETURNING ${e.identifier("id")}`)).length===0&&h();else{const u=await k(r,a,w);(a.affectedRows?a.affectedRows(u??{rowsAffected:0}):0)===0&&h()}}catch(u){throw R(u)?new $e(`unique constraint violation on "${t}"`,"unique"):u}},ht=(t,n,o,i)=>{const s=Object.keys(t.shape);return{columns:["id","_creationTime",...s],values:[n,o,...s.map(w=>H(i[w]??null))]}},yt=async(t,n,o,i)=>{const s=Vt(n,o.op,o.field,i.by,i.where);if(!s||!await Ee(t,s.index))return;const h=z(t,s.index.name),u=Object.keys(s.partial);if(u.length===(s.index.by??[]).length&&u.length>0){const d=Y(s.index.by??[],s.partial),$=await A(r,a,e`SELECT ${e.identifier("__value__")} AS value, ${e.identifier("__count__")} AS count FROM ${e.identifier(h)} WHERE ${e.identifier("__key__")} = ${d}`);if($.length===0)return[];const E=$[0];return[{key:{...s.partial},value:Me(o.op,{count:E.count,value:ne(E.value)})}]}return u.length>0?void 0:(await A(r,a,e`SELECT ${e.identifier("__key__")} AS key, ${e.identifier("__value__")} AS value, ${e.identifier("__count__")} AS count FROM ${e.identifier(h)}`)).map(d=>{const $=d;return{key:JSON.parse($.key),value:Me(o.op,{count:$.count,value:ne($.value)})}})},Ke=t=>{const n=l.tables[t]?.shardMode?.kind;return n==="shardBy"||n==="root"},He=t=>{throw new v("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`)},Et=(t,n)=>{if(n?.relationMask!==void 0&&n.with!==void 0&&Object.keys(n.with).length>0)throw new v("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 i of Object.keys(l.tables)){const s=n.relationBaseWhere(i);s!==void 0&&(o[i]=s)}return{orderBy:n?.orderBy,relationPolicies:o,where:q(n?.baseWhere,n?.where),with:n?.with}},Ve=(t,n)=>Ke(t)?f?f(t,Et(t,n)):He(t):O.findMany(t,n),ge=(t,n,o)=>tt(t,{fetcher:Ve,maxRelationKeys:p,relationBaseWhere:o,schema:l,tableName:n}),pt=Zt(async(t,n)=>{const o=await A(r,a,e`SELECT * FROM ${e.identifier(t)} WHERE ${e.identifier("id")} IN (${oe(n)})`),i=new Map;for(const s of o){const{id:w}=s;typeof w=="string"&&i.set(w,s)}return i}),O={async aggregate(t,n){const o=l.tables[t];if(!o)throw new v("INTERNAL",`unknown table: ${t}`);if(await j(),fe(n.op),n.op==="count")return O.count(t,{baseWhere:n.baseWhere,relationBaseWhere:n.relationBaseWhere,restrictsCounts:n.restrictsCounts,where:n.where});if(!n.field)throw new v("INTERNAL",`aggregate(${t}, { op: "${n.op}" }): "field" is required for non-count reducers`);const i=we(o.softDeleteMode,void 0),s=q(q(n.baseWhere,n.where),i),w=await ge(s,t,n.relationBaseWhere),h=w!==s;if(o.aggregateIndexes&&!n.baseWhere&&!h&&!i){const g=Kt(o.aggregateIndexes,n.op,n.field,n.where);if(g&&await Ee(t,g.index)){const C=Y(g.index.by??[],g.key),b=z(t,g.index.name),N=(await A(r,a,e`SELECT ${e.identifier("__value__")} AS value, ${e.identifier("__count__")} AS count FROM ${e.identifier(b)} WHERE ${e.identifier("__key__")} = ${C}`))[0];return Me(n.op,N===void 0?void 0:{count:N.count,value:ne(N.value)})}}const u=_e(w,m),y=e.raw(fe(n.op)),d=e`SELECT ${y}(${x(n.field)}) AS value FROM ${e.identifier(t)}`,E=(await A(r,a,u?e`${d} WHERE ${u}`:d))[0]?.value;return E==null?null:Number(E)},async count(t,n){const o=l.tables[t];if(!o)throw new v("INTERNAL",`unknown table: ${t}`);await j();const i=jt(n);if(i.restrictsCounts)throw new et(t);const s=we(o.softDeleteMode,void 0),w=q(q(i.baseWhere,i.where),s),h=await ge(w,t,i.relationBaseWhere),u=h!==w;if(o.aggregateIndexes&&!i.baseWhere&&!u&&!s){const E=Pt(o.aggregateIndexes,i.where);if(E&&await Ee(t,E.index)){const T=Y(E.index.by??[],E.key),C=z(t,E.index.name),b=await A(r,a,e`SELECT ${e.identifier("__value__")} AS value FROM ${e.identifier(C)} WHERE ${e.identifier("__key__")} = ${T}`);return Number(b[0]?.value??0)}}const y=_e(h,m),d=e`SELECT COUNT(*) AS count FROM ${e.identifier(t)}`,$=await A(r,a,y?e`${d} WHERE ${y}`:d);return Number($[0]?.count??0)},async delete(t,n,o){const i=await ie(t,n);if(!i)return;const s=l.tables[i];if(!s)return;const w=await Re(i,t),h=te(s,w),u=o?.hard===!0,y=!u&&s.softDeleteMode?s.softDeleteMode.field:void 0;if(!(y&&(!h||h[y]!==null&&h[y]!==void 0))){if(P(i,"before","delete")&&await K("before","delete",{id:t,op:"delete",previous:h??void 0,table:i}),await Ut({deletedId:t,deletedReference:d=>h?.[d],findHolders:async(d,$,E)=>{if(l.tables[d]?.shardMode?.kind==="shardBy")throw new v("INTERNAL",`cross-backend cascade from global '${i}' into shardBy '${d}' is not supported — would require Query Coordinator fan-out across shards`);return(await O.findMany(d,{includeDeleted:u,where:{[$]:E}})).page},onCascade:(d,$)=>O.delete($,void 0,o),onRestrict:d=>{throw new $e(d,"restrict")},onSetNull:(d,$,E)=>O.patch($,{[E]:null}),schema:l,tableName:i}),await pe(i),await me(i),y&&h){const d={...h,[y]:L()},$=e.join(Object.keys(s.shape).map(E=>e`${e.identifier(E)} = ${H(d[E]??null)}`),e`, `);await Te(i,"UPDATE",$,w),await se(i,h,d),await J(i,t,h,void 0),await ce(i,t,d,h),await U(i,t,"update",d),P(i,"after","delete")&&await K("after","delete",{id:t,op:"delete",previous:h,table:i});return}await Te(i,"DELETE",void 0,w),Ce.delete(t),await se(i,h??void 0,void 0),await J(i,t,h??void 0,void 0),await ce(i,t,void 0),await U(i,t,"delete"),P(i,"after","delete")&&await K("after","delete",{id:t,op:"delete",previous:h??void 0,table:i})}},async findFirst(t,n={}){return(await O.findMany(t,{...n,limit:1})).page[0]??null},async findFirstOrThrow(t,n={}){const o=await O.findFirst(t,n);if(o===null)throw new Wt(`findFirstOrThrow: no "${t}" document matched`);return o},async findMany(t,n={}){const o=l.tables[t];if(!o)throw new v("INTERNAL",`unknown table: ${t}`);await j();const i=Bt(n.orderBy),s=n.cursor?Ft(i,Ze(n.cursor)):void 0,w=Ve,h=async(N,B,G,ve)=>{if(Ke(N))return _?Gt(_,N,B,G,ve):He(N);const le=l.tables[N];if(!le)throw new v("INTERNAL",`unknown table: ${N}`);const Q=we(le.softDeleteMode,void 0),D={[B]:{in:G}},ue=q(q(D,ve),Q),mt=await ge(ue,N,void 0),Ge=_e(mt,m),Ye=x(B);let de=e`SELECT ${Ye} AS __fk__, COUNT(*) AS count FROM ${e.identifier(N)}`;Ge&&(de=e`${de} WHERE ${Ge}`),de=e`${de} GROUP BY ${Ye}`;const Rt=await A(r,a,de),ze=new Map;for(const Xe of Rt)ze.set(Xe.__fk__,Number(Xe.count??0));return ze};let u=q(n.baseWhere,n.where);u=q(u,we(o.softDeleteMode,n.includeDeleted)),u=await tt(u,{fetcher:w,maxRelationKeys:p,relationBaseWhere:n.relationBaseWhere,schema:l,tableName:t}),s&&(u=u?{AND:[u,s]}:s);const y=_e(u,m),d=tn(i);let $=e`SELECT * FROM ${e.identifier(t)}`;y&&($=e`${$} WHERE ${y}`),$=e`${$} ORDER BY ${d}`;const E=typeof n.limit=="number"?Math.max(0,Math.floor(n.limit)):void 0;E!==void 0&&($=e`${$} LIMIT ${e.raw(String(E+1))}`);const g=await A(r,a,$),T=Jt(o,g);if(E===void 0)return n.with&&await nt({fetcher:w,groupedCounter:h,parents:T,...ot(n),schema:l,tableName:t,with:n.with}),{continueCursor:null,isDone:!0,page:it(T,n.select,n.with)};const C=T.length>E,b=C?T.slice(0,E):T,F=b.at(-1);return n.with&&await nt({fetcher:w,groupedCounter:h,parents:b,...ot(n),schema:l,tableName:t,with:n.with}),{continueCursor:C&&F?Ot(F,i):null,isDone:!C,page:it(b,n.select,n.with)}},async get(t,n){const o=await ie(t,n);if(!o)return null;const i=l.tables[o];return i?te(i,await pt.load(o,t)):null},async groupBy(t,n){const o=l.tables[t];if(!o)throw new v("INTERNAL",`unknown table: ${t}`);await j();const i=n.agg??{op:"count"};if(fe(i.op),i.op!=="count"&&!i.field)throw new v("INTERNAL",`groupBy(${t}, { agg: { op: "${i.op}" } }): "field" is required for non-count reducers`);const s=we(o.softDeleteMode,void 0),w=q(q(n.baseWhere,n.where),s),h=await ge(w,t,n.relationBaseWhere),u=h!==w;if(o.aggregateIndexes&&!n.baseWhere&&!u&&!s){const T=await yt(t,o.aggregateIndexes,i,n);if(T!==void 0)return T}const y=_e(h,m),d=n.by.map(T=>e`${x(T)} AS ${e.identifier(T)}`);if(i.op==="count")d.push(e`COUNT(*) AS value`);else{if(!i.field)throw new v("INTERNAL",`groupBy(${t}, { agg: { op: "${i.op}" } }): "field" is required for non-count reducers`);d.push(e`${e.raw(fe(i.op))}(${x(i.field)}) AS value`)}const $=e.join(n.by.map(T=>x(T)),e`, `);let E=e`SELECT ${e.join(d,e`, `)} FROM ${e.identifier(t)}`;y&&(E=e`${E} WHERE ${y}`),E=e`${E} GROUP BY ${$}`;const g=await A(r,a,E);return an(n.by,g)},async insert(t,n,o){const i=l.tables[t];if(!i)throw new v("INTERNAL",`unknown table: ${t}`);await j();const s=nn(i,n,V);Ne(i,s);let w,h=!0;o?.clientId!==void 0?(xt(o.clientId),w=o.clientId):o?.allowExplicitId&&typeof s._id=="string"?w=s._id:(w=M(),h=!1);const u=o?.allowExplicitId&&typeof s._creationTime=="number"?s._creationTime:L(),y={...s,_creationTime:u,_id:w};P(t,"before","insert")&&await K("before","insert",{doc:{...y},id:w,op:"insert",table:t}),await pe(t),await me(t);const{columns:d,values:$}=ht(i,w,u,s);return await $t(t,e`INSERT INTO ${e.identifier(t)} (${Z(d)}) VALUES (${oe($)})`),h&&Ce.set(w,t),await se(t,void 0,y),await J(t,w,void 0,y),await ce(t,w,y),await U(t,w,"insert",y),P(t,"after","insert")&&await K("after","insert",{doc:y,id:w,op:"insert",table:t}),w},normalizeId(t,n){return qt(l,t,n)},async patch(t,n,o){const i=await ie(t,o);if(!i)throw new v("INTERNAL",`document not found: ${t}`);const s=l.tables[i];if(!s)throw new v("INTERNAL",`document not found: ${t}`);const w=await Re(i,t),h=te(s,w);if(!h)throw new v("INTERNAL",`document not found: ${t}`);const u={...h,...n,_id:t};ut(s,n,u,V),Ne(s,u,!0),P(i,"before","update")&&await K("before","update",{doc:{...u},id:t,op:"update",previous:h,table:i}),await pe(i),await me(i);const y=Object.keys(s.shape),d=e.join(y.map($=>e`${e.identifier($)} = ${H(u[$]??null)}`),e`, `);await Te(i,"UPDATE",d,w),await se(i,h,u),await J(i,t,h,u),await ce(i,t,u,h),await U(i,t,"update",u),P(i,"after","update")&&await K("after","update",{doc:u,id:t,op:"update",previous:h,table:i})},async restore(t,n){const o=await ie(t,n);if(!o)throw new v("INTERNAL",`document not found: ${t}`);const i=l.tables[o],s=i?.softDeleteMode?.field;if(!i||!s)throw new v("INTERNAL",`ctx.db.restore: table "${o}" is not a .softDelete() table`);const w=await Re(o,t),h=w?.[s]!==null&&w?.[s]!==void 0;if(await O.patch(t,{[s]:null},n),h){const u=te(i,w);u!==null&&await J(o,t,void 0,{...u,[s]:null})}},query(t){const n=l.tables[t];if(!n)throw new v("INTERNAL",`unknown table: ${t}`);const o="the legacy query()/withIndex() reader is not available on the D1 (global) backend; use findMany",i=[],s=u=>i.every(y=>y(u)),w=async(u,y)=>{await j();const d=i.length>0,$=await At(r,a,n,t,u,Ct(d?void 0:y));if(!d)return y===void 0&&Lt($),$;const E=$.filter(g=>s(g));return y===void 0?E:E.slice(0,y)},h=u=>{const y={async*[Symbol.asyncIterator](){if(!u)throw new v("INTERNAL",o);yield*await w(u,void 0)},async collect(){if(!u)throw new v("INTERNAL",o);return w(u,void 0)},async collectWithScores(){throw new v("INTERNAL",`collectWithScores() is not supported on \`.global()\` tables (table "${t}") — relevance scores are not yet surfaced on this backend; use .collect() instead`)},filter(d){return i.push(d),y},async first(){if(!u)throw new v("INTERNAL",o);return(await w(u,1))[0]??null},order(){return y},async paginate(d){if(!u)throw new v("INTERNAL",o);const $=It(d);return bt(await w(u,kt($)),$)},async take(d){if(!u)throw new v("INTERNAL",o);return w(u,d)},async unique(){if(!u)throw new v("INTERNAL",o);const d=await w(u,2);if(d.length>1)throw new zt(`unique() on table "${t}" matched ${String(d.length)} documents; expected at most one`);return d[0]??null},withGeoIndex(){throw new v("INTERNAL",`geo indexes are not supported on \`.global()\` tables (table "${t}") — geospatial queries run only on sharded tables`)},withIndex(){throw new v("INTERNAL",o)},withSearchIndex(d,$){const E=(n.searchIndexes??[]).find(T=>T.name===d);if(!E)throw new v("INTERNAL",`unknown search index "${d}" on table "${t}"`);const g={definition:E,field:E.field,filters:[],hasQuery:!1,indexName:d,query:""};if($(vt(g,t,St(E.language))),!g.hasQuery)throw new v("INTERNAL",`search index "${d}" on table "${t}" requires a .search(field, query) call`);return h(g)}};return y};return h(void 0)},async rank(t,n,o){const i=l.tables[t];if(!i)throw new v("INTERNAL",`unknown table: ${t}`);const s=i.rankIndexes?.find(B=>B.name===n);if(!s)throw new v("INTERNAL",`unknown rankIndex "${n}" on table "${t}"`);if(o.restrictsCounts)throw new et(t);if(await j(),!await ke(t,s))return null;const h=typeof o.row=="string"?o.row:o.row._id;if(!h)return null;const u=ee(t,s.name),y=s.sortBy.map((B,G)=>X(G)),d=Z(["__partition__",...y]),E=(await A(r,a,e`SELECT ${d} FROM ${e.identifier(u)} WHERE ${e.identifier("__id__")} = ${h}`))[0];if(!E)return null;const g=E.__partition__,T=q(o.baseWhere,o.where);Je(T,l,t,"rank");const C=Qe(s,T);if(C&&Se(s.partitionBy??[],C)!==g)return null;const b=ln(a.name,s,y,E,h),F=await A(r,a,e`SELECT COUNT(*) AS c FROM ${e.identifier(u)} WHERE ${e.identifier("__partition__")} = ${g}${b?e` AND (${b})`:e``}`),N=await A(r,a,e`SELECT COUNT(*) AS c FROM ${e.identifier(u)} WHERE ${e.identifier("__partition__")} = ${g}`);return{position:Number(F[0]?.c??0)+1,total:Number(N[0]?.c??0)}},async rankPage(t,n,o={}){Je(q(o.baseWhere,o.where),l,t,"rankPage");const i=l.tables[t];if(!i)throw new v("INTERNAL",`unknown table: ${t}`);const s=i.rankIndexes?.find(D=>D.name===n);if(!s)throw new v("INTERNAL",`unknown rankIndex "${n}" on table "${t}"`);if(await j(),!await ke(t,s))return{continueCursor:null,isDone:!0,page:[]};const h=ee(t,s.name),u=s.sortBy.map((D,ue)=>X(ue)),y=Math.max(1,Math.min(1e3,Math.floor(o.take??100))),d=q(o.baseWhere,o.where),$=Qe(s,d),E=dn(s,u),g=e.join(E.map(D=>e`${e.identifier(D.column)} ${e.raw(D.direction==="desc"?"DESC":"ASC")}`),e`, `),T=[];if($&&T.push(e`${e.identifier("__partition__")} = ${Se(s.partitionBy??[],$)}`),o.cursor){const D=un(a.name,E,Ze(o.cursor));D!==void 0&&T.push(D)}const C=Z([he,"__partition__",...u]);let b=e`SELECT ${C} FROM ${e.identifier(h)}`;T.length>0&&(b=e`${b} WHERE ${e.join(T,e` AND `)}`),b=e`${b} ORDER BY ${g} LIMIT ${e.raw(String(y+1))}`;const F=await A(r,a,b),N=F.length>y,B=N?F.slice(0,y):F,G=B.map(D=>D[he]),ve=await fn(r,a,i,t,G);let le=null;const Q=B.at(-1);if(N&&Q!==void 0){const D=[Q.__partition__,...u.map(ue=>Q[ue]),Q[he]];le=wn(D)}return{continueCursor:le,isDone:!N,page:ve}},async replace(t,n,o,i){const s=await ie(t,o);if(!s)throw new v("INTERNAL",`document not found: ${t}`);const w=l.tables[s];if(!w)throw new v("INTERNAL",`document not found: ${t}`);const h=await Re(s,t);if(h===void 0)throw new v("INTERNAL",`document not found: ${t}`);const y=Dt(l,s,"update")||(w.aggregateIndexes??[]).length>0||(w.rankIndexes??[]).length>0?te(w,h)??void 0:void 0,d=i?.allowExplicitId&&typeof n._creationTime=="number"?n._creationTime:L(),$={...n,_creationTime:d,_id:t};ut(w,n,$,V),Ne(w,$),P(s,"before","update")&&await K("before","update",{doc:{...$},id:t,op:"update",previous:y,table:s}),await pe(s),await me(s);const E=Object.keys(w.shape),g=e.join([e`${e.identifier("_creationTime")} = ${d}`,...E.map(T=>e`${e.identifier(T)} = ${H($[T]??null)}`)],e`, `);await Te(s,"UPDATE",g,h),await se(s,y,$),await J(s,t,y,$),await ce(s,t,$,y),await U(s,t,"update",$),P(s,"after","update")&&await K("after","update",{doc:$,id:t,op:"update",previous:y,table:s})}};return Pe={db:O,scheduler:Ae},O};export{xn as backfillSqlSearchIndexes,Mn as createSqlCtxDb,Fn as decodeGlobalRow,kn as readSqlCdcChanges,En as runSqlAggregateMigrations,Tn as runSqlCdcMigration,yn as runSqlGlobalTableMigrations,Rn as runSqlRankMigrations,gt as runSqlSearchMigrations,Nn as trimSqlCdcChanges};