@lunora/sql-store 0.0.0 → 1.0.0-alpha.10

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.
@@ -0,0 +1,82 @@
1
+ import { SQL } from 'drizzle-orm';
2
+ /** Outcome of a non-`SELECT` statement: the row count it changed (for MySQL OCC, which lacks `RETURNING`). */
3
+ interface SqlRunResult {
4
+ /** Rows the statement inserted/updated/deleted. `0` when the engine can't report it. */
5
+ rowsAffected: number;
6
+ }
7
+ /**
8
+ * The async SQL surface the store core consumes. Satisfied by a
9
+ * `D1Session`/`D1Client` (D1), a `node:sqlite` adapter (tests), or a
10
+ * Hyperdrive-backed `postgres`/`pg`/`mysql2` driver (PlanetScale).
11
+ *
12
+ * `all` runs a row-returning statement (incl. `... RETURNING ...`); `run` runs a
13
+ * write and reports `rowsAffected` — the matched-rows count that drives the
14
+ * MySQL optimistic-concurrency guard (which has no `RETURNING`).
15
+ *
16
+ * Note: companion writes (aggregate/rank/FTS/CDC) run as separate sequential
17
+ * statements after the row write on every engine, so they share D1's
18
+ * at-least-once caveat — there is no cross-statement transaction here.
19
+ */
20
+ interface SqlExec {
21
+ all: (sql: string, params: ReadonlyArray<unknown>) => Promise<Record<string, unknown>[]>;
22
+ run: (sql: string, params: ReadonlyArray<unknown>) => Promise<SqlRunResult>;
23
+ }
24
+ /** Everything engine-specific the store core needs. One value per engine; `sqliteDialect` (in `@lunora/d1`) is the reference, Postgres/MySQL live in `@lunora/hyperdrive/global`. */
25
+ interface SqlDialect {
26
+ /** Affected-rows extractor for the OCC fallback when `supportsReturning` is false (MySQL). */
27
+ affectedRows?: (result: SqlRunResult) => number;
28
+ /**
29
+ * Storage SQL column type for a validator `kind`. SQLite affinity
30
+ * (`TEXT`/`INTEGER`/`REAL`/`BLOB`); Postgres `TEXT`/`DOUBLE PRECISION`/
31
+ * `BOOLEAN`/`JSONB`/`BYTEA`; MySQL `VARCHAR(255)`/`TEXT`/`DOUBLE`/
32
+ * `TINYINT(1)`/`JSON`/`LONGBLOB`.
33
+ */
34
+ columnType: (kind: string | undefined) => string;
35
+ /**
36
+ * Engine SQL types for the **internal companion tables** (aggregate / rank /
37
+ * CDC), which are built from raw SQL types, not validator kinds. SQLite uses
38
+ * `TEXT`/`REAL`/`INTEGER`/`BLOB` and `INTEGER PRIMARY KEY AUTOINCREMENT`;
39
+ * Postgres `TEXT`/`DOUBLE PRECISION`/`INTEGER`/`BYTEA` + `BIGSERIAL`; MySQL
40
+ * needs a bounded `VARCHAR` key, `DOUBLE`, `LONGBLOB`, `AUTO_INCREMENT`.
41
+ */
42
+ companionTypes: {
43
+ autoincrementPrimaryKey: string;
44
+ integer: string;
45
+ key: string;
46
+ real: string;
47
+ text: string;
48
+ };
49
+ /** Map a stored value back to its JS form, by effective validator `kind` (inverse of `encode`). */
50
+ decode: (value: unknown, kind: string | undefined) => unknown;
51
+ /** Map a JS value to its bound storage form (boolean→1/0, bigint→string, object→JSON on SQLite; mostly native on PG). */
52
+ encode: (value: unknown) => unknown;
53
+ /** The framework columns every global table carries — the `id` primary key and `_creationTime` — as `{ name, type }` so the DDL builder can quote each name through the engine's dialect. */
54
+ frameworkColumns: () => ReadonlyArray<{
55
+ name: string;
56
+ type: string;
57
+ }>;
58
+ /**
59
+ * Optional: the key-prefix length an indexed column of this `kind` needs.
60
+ * MySQL/InnoDB can't index a `TEXT`/`LONGTEXT`/`BLOB` column without a prefix
61
+ * (the store appends `(&lt;n>)` to the column reference); SQLite/Postgres index
62
+ * text columns directly and omit this hook (or return `undefined`). `kind` is
63
+ * the column's effective validator kind.
64
+ */
65
+ indexKeyPrefix?: (kind: string | undefined) => number | undefined;
66
+ /** True when an `error` thrown by a write is a UNIQUE-constraint breach (mapped to a 409 ConflictError). */
67
+ isUniqueViolation: (error: unknown) => boolean;
68
+ /** A short engine tag for diagnostics/branching (`"sqlite" | "postgres" | "mysql"`). The store core selects drizzle's matching dialect for rendering off this. */
69
+ readonly name: "mysql" | "postgres" | "sqlite";
70
+ /** True when the engine supports `UPDATE/DELETE ... RETURNING` (SQLite/PG yes, MySQL no → use `affectedRows`). */
71
+ supportsReturning: boolean;
72
+ /**
73
+ * The catalog probe for whether a physical `table` exists — backs the opt-in
74
+ * companion-table (`__agg_`/`__rank_`) existence checks. Returns a drizzle
75
+ * {@link SQL} (a non-empty result ⇒ the table exists) so it renders through
76
+ * the same per-engine path as every other statement, never a hand-built
77
+ * placeholder string. SQLite reads `sqlite_master`; Postgres/MySQL read
78
+ * `information_schema.tables`.
79
+ */
80
+ tableExists: (table: string) => SQL;
81
+ }
82
+ export { SqlDialect, SqlExec, SqlRunResult };
@@ -0,0 +1,82 @@
1
+ import { SQL } from 'drizzle-orm';
2
+ /** Outcome of a non-`SELECT` statement: the row count it changed (for MySQL OCC, which lacks `RETURNING`). */
3
+ interface SqlRunResult {
4
+ /** Rows the statement inserted/updated/deleted. `0` when the engine can't report it. */
5
+ rowsAffected: number;
6
+ }
7
+ /**
8
+ * The async SQL surface the store core consumes. Satisfied by a
9
+ * `D1Session`/`D1Client` (D1), a `node:sqlite` adapter (tests), or a
10
+ * Hyperdrive-backed `postgres`/`pg`/`mysql2` driver (PlanetScale).
11
+ *
12
+ * `all` runs a row-returning statement (incl. `... RETURNING ...`); `run` runs a
13
+ * write and reports `rowsAffected` — the matched-rows count that drives the
14
+ * MySQL optimistic-concurrency guard (which has no `RETURNING`).
15
+ *
16
+ * Note: companion writes (aggregate/rank/FTS/CDC) run as separate sequential
17
+ * statements after the row write on every engine, so they share D1's
18
+ * at-least-once caveat — there is no cross-statement transaction here.
19
+ */
20
+ interface SqlExec {
21
+ all: (sql: string, params: ReadonlyArray<unknown>) => Promise<Record<string, unknown>[]>;
22
+ run: (sql: string, params: ReadonlyArray<unknown>) => Promise<SqlRunResult>;
23
+ }
24
+ /** Everything engine-specific the store core needs. One value per engine; `sqliteDialect` (in `@lunora/d1`) is the reference, Postgres/MySQL live in `@lunora/hyperdrive/global`. */
25
+ interface SqlDialect {
26
+ /** Affected-rows extractor for the OCC fallback when `supportsReturning` is false (MySQL). */
27
+ affectedRows?: (result: SqlRunResult) => number;
28
+ /**
29
+ * Storage SQL column type for a validator `kind`. SQLite affinity
30
+ * (`TEXT`/`INTEGER`/`REAL`/`BLOB`); Postgres `TEXT`/`DOUBLE PRECISION`/
31
+ * `BOOLEAN`/`JSONB`/`BYTEA`; MySQL `VARCHAR(255)`/`TEXT`/`DOUBLE`/
32
+ * `TINYINT(1)`/`JSON`/`LONGBLOB`.
33
+ */
34
+ columnType: (kind: string | undefined) => string;
35
+ /**
36
+ * Engine SQL types for the **internal companion tables** (aggregate / rank /
37
+ * CDC), which are built from raw SQL types, not validator kinds. SQLite uses
38
+ * `TEXT`/`REAL`/`INTEGER`/`BLOB` and `INTEGER PRIMARY KEY AUTOINCREMENT`;
39
+ * Postgres `TEXT`/`DOUBLE PRECISION`/`INTEGER`/`BYTEA` + `BIGSERIAL`; MySQL
40
+ * needs a bounded `VARCHAR` key, `DOUBLE`, `LONGBLOB`, `AUTO_INCREMENT`.
41
+ */
42
+ companionTypes: {
43
+ autoincrementPrimaryKey: string;
44
+ integer: string;
45
+ key: string;
46
+ real: string;
47
+ text: string;
48
+ };
49
+ /** Map a stored value back to its JS form, by effective validator `kind` (inverse of `encode`). */
50
+ decode: (value: unknown, kind: string | undefined) => unknown;
51
+ /** Map a JS value to its bound storage form (boolean→1/0, bigint→string, object→JSON on SQLite; mostly native on PG). */
52
+ encode: (value: unknown) => unknown;
53
+ /** The framework columns every global table carries — the `id` primary key and `_creationTime` — as `{ name, type }` so the DDL builder can quote each name through the engine's dialect. */
54
+ frameworkColumns: () => ReadonlyArray<{
55
+ name: string;
56
+ type: string;
57
+ }>;
58
+ /**
59
+ * Optional: the key-prefix length an indexed column of this `kind` needs.
60
+ * MySQL/InnoDB can't index a `TEXT`/`LONGTEXT`/`BLOB` column without a prefix
61
+ * (the store appends `(&lt;n>)` to the column reference); SQLite/Postgres index
62
+ * text columns directly and omit this hook (or return `undefined`). `kind` is
63
+ * the column's effective validator kind.
64
+ */
65
+ indexKeyPrefix?: (kind: string | undefined) => number | undefined;
66
+ /** True when an `error` thrown by a write is a UNIQUE-constraint breach (mapped to a 409 ConflictError). */
67
+ isUniqueViolation: (error: unknown) => boolean;
68
+ /** A short engine tag for diagnostics/branching (`"sqlite" | "postgres" | "mysql"`). The store core selects drizzle's matching dialect for rendering off this. */
69
+ readonly name: "mysql" | "postgres" | "sqlite";
70
+ /** True when the engine supports `UPDATE/DELETE ... RETURNING` (SQLite/PG yes, MySQL no → use `affectedRows`). */
71
+ supportsReturning: boolean;
72
+ /**
73
+ * The catalog probe for whether a physical `table` exists — backs the opt-in
74
+ * companion-table (`__agg_`/`__rank_`) existence checks. Returns a drizzle
75
+ * {@link SQL} (a non-empty result ⇒ the table exists) so it renders through
76
+ * the same per-engine path as every other statement, never a hand-built
77
+ * placeholder string. SQLite reads `sqlite_master`; Postgres/MySQL read
78
+ * `information_schema.tables`.
79
+ */
80
+ tableExists: (table: string) => SQL;
81
+ }
82
+ export { SqlDialect, SqlExec, SqlRunResult };
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,174 @@
1
+ import { ServerDefaultContextLike, DatabaseWriterLike, SchedulerLike, SchemaLike, TableDefinitionLike, ValidatorLike } from '@lunora/do';
2
+ import { SqlDialect, SqlRunResult } from "./dialect.mjs";
3
+ export type { SqlExec } from "./dialect.mjs";
4
+ import 'drizzle-orm';
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
+ */
10
+ interface SqlCtxExec {
11
+ all: (sql: string, parameters: ReadonlyArray<unknown>) => Promise<Record<string, unknown>[]>;
12
+ run: (sql: string, parameters: ReadonlyArray<unknown>) => Promise<SqlRunResult | void>;
13
+ }
14
+ interface SqlCtxDbOptions {
15
+ /**
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
+ */
21
+ auth?: ServerDefaultContextLike["auth"];
22
+ /**
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
+ */
31
+ cdc?: boolean;
32
+ clock?: () => number;
33
+ /**
34
+ * Cross-shard counter for **reverse cross-backend relations** — the `_count`
35
+ * mirror of the `crossShardReader` option below.
36
+ */
37
+ crossShardCounter?: DatabaseWriterLike["count"];
38
+ /**
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"];
50
+ /**
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
+ */
56
+ dialect: SqlDialect;
57
+ exec: SqlCtxExec;
58
+ idGenerator?: () => string;
59
+ /**
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
+ */
67
+ maxRelationKeys?: number;
68
+ /**
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
+ */
73
+ scheduler?: SchedulerLike;
74
+ schema: SchemaLike;
75
+ }
76
+ /**
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>;
89
+ declare const runSqlGlobalTableMigrations: (exec: SqlCtxExec, schema: SchemaLike, dialect: SqlDialect) => Promise<void>;
90
+ /**
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
+ */
99
+ declare const runSqlAggregateMigrations: (exec: SqlCtxExec, schema: SchemaLike, dialect: SqlDialect) => Promise<void>;
100
+ /**
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
+ */
107
+ 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
+ /** Create the `__cdc_log` table. Idempotent; only run when CDC is enabled. */
132
+ declare const runSqlCdcMigration: (exec: SqlCtxExec, dialect: SqlDialect) => Promise<void>;
133
+ /**
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
+ */
137
+ declare const readSqlCdcChanges: (exec: SqlCtxExec, options: {
138
+ limit?: number;
139
+ sinceSeq?: number;
140
+ }, dialect: SqlDialect) => Promise<{
141
+ changes: CdcChange[];
142
+ cursor: number;
143
+ }>;
144
+ /** Drop changelog entries at or below a checkpointed `throughSeq` (retention). */
145
+ declare const trimSqlCdcChanges: (exec: SqlCtxExec, throughSeq: number, dialect: SqlDialect) => Promise<void>;
146
+ declare const createSqlCtxDb: (options: SqlCtxDbOptions) => DatabaseWriterLike;
147
+ /** Map a JS value onto its SQLite storage form — SQLite has no boolean, so true/false → 1/0. */
148
+ declare const sqliteEncode: (value: unknown) => unknown;
149
+ /** Parse `raw` as JSON, returning `raw` unchanged when it is not valid JSON. */
150
+ declare const tryJsonParse: (raw: string) => unknown;
151
+ /** Decode a `bigint` column: a decimal string back into a `BigInt`, else verbatim. */
152
+ declare const decodeBigint: (raw: unknown) => unknown;
153
+ /**
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
+ */
161
+ declare const effectiveColumnKind: (validator: ValidatorLike) => string | undefined;
162
+ /**
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
+ */
173
+ 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 };
@@ -0,0 +1,174 @@
1
+ import { ServerDefaultContextLike, DatabaseWriterLike, SchedulerLike, SchemaLike, TableDefinitionLike, ValidatorLike } from '@lunora/do';
2
+ import { SqlDialect, SqlRunResult } from "./dialect.js";
3
+ export type { SqlExec } from "./dialect.js";
4
+ import 'drizzle-orm';
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
+ */
10
+ interface SqlCtxExec {
11
+ all: (sql: string, parameters: ReadonlyArray<unknown>) => Promise<Record<string, unknown>[]>;
12
+ run: (sql: string, parameters: ReadonlyArray<unknown>) => Promise<SqlRunResult | void>;
13
+ }
14
+ interface SqlCtxDbOptions {
15
+ /**
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
+ */
21
+ auth?: ServerDefaultContextLike["auth"];
22
+ /**
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
+ */
31
+ cdc?: boolean;
32
+ clock?: () => number;
33
+ /**
34
+ * Cross-shard counter for **reverse cross-backend relations** — the `_count`
35
+ * mirror of the `crossShardReader` option below.
36
+ */
37
+ crossShardCounter?: DatabaseWriterLike["count"];
38
+ /**
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"];
50
+ /**
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
+ */
56
+ dialect: SqlDialect;
57
+ exec: SqlCtxExec;
58
+ idGenerator?: () => string;
59
+ /**
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
+ */
67
+ maxRelationKeys?: number;
68
+ /**
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
+ */
73
+ scheduler?: SchedulerLike;
74
+ schema: SchemaLike;
75
+ }
76
+ /**
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>;
89
+ declare const runSqlGlobalTableMigrations: (exec: SqlCtxExec, schema: SchemaLike, dialect: SqlDialect) => Promise<void>;
90
+ /**
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
+ */
99
+ declare const runSqlAggregateMigrations: (exec: SqlCtxExec, schema: SchemaLike, dialect: SqlDialect) => Promise<void>;
100
+ /**
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
+ */
107
+ 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
+ /** Create the `__cdc_log` table. Idempotent; only run when CDC is enabled. */
132
+ declare const runSqlCdcMigration: (exec: SqlCtxExec, dialect: SqlDialect) => Promise<void>;
133
+ /**
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
+ */
137
+ declare const readSqlCdcChanges: (exec: SqlCtxExec, options: {
138
+ limit?: number;
139
+ sinceSeq?: number;
140
+ }, dialect: SqlDialect) => Promise<{
141
+ changes: CdcChange[];
142
+ cursor: number;
143
+ }>;
144
+ /** Drop changelog entries at or below a checkpointed `throughSeq` (retention). */
145
+ declare const trimSqlCdcChanges: (exec: SqlCtxExec, throughSeq: number, dialect: SqlDialect) => Promise<void>;
146
+ declare const createSqlCtxDb: (options: SqlCtxDbOptions) => DatabaseWriterLike;
147
+ /** Map a JS value onto its SQLite storage form — SQLite has no boolean, so true/false → 1/0. */
148
+ declare const sqliteEncode: (value: unknown) => unknown;
149
+ /** Parse `raw` as JSON, returning `raw` unchanged when it is not valid JSON. */
150
+ declare const tryJsonParse: (raw: string) => unknown;
151
+ /** Decode a `bigint` column: a decimal string back into a `BigInt`, else verbatim. */
152
+ declare const decodeBigint: (raw: unknown) => unknown;
153
+ /**
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
+ */
161
+ declare const effectiveColumnKind: (validator: ValidatorLike) => string | undefined;
162
+ /**
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
+ */
173
+ 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 };
package/dist/index.mjs ADDED
@@ -0,0 +1,2 @@
1
+ export { createSqlCtxDb, decodeGlobalRow, readSqlCdcChanges, runSqlAggregateMigrations, runSqlCdcMigration, runSqlGlobalTableMigrations, runSqlRankMigrations, runSqlSearchMigrations, trimSqlCdcChanges } from './packem_shared/createSqlCtxDb-BlGq23Wp.mjs';
2
+ export { decodeBigint, effectiveColumnKind, sqliteDecode, sqliteEncode, tryJsonParse } from './packem_shared/decodeBigint-Dedu92k4.mjs';