@lunora/sql-store 0.0.0 → 1.0.0-alpha.1
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/LICENSE.md +105 -0
- package/__assets__/package-og.svg +14 -0
- package/dist/dialect.d.mts +82 -0
- package/dist/dialect.d.ts +82 -0
- package/dist/dialect.mjs +1 -0
- package/dist/index.d.mts +174 -0
- package/dist/index.d.ts +174 -0
- package/dist/index.mjs +2 -0
- package/dist/packem_shared/createSqlCtxDb-CQFYyQLP.mjs +1700 -0
- package/dist/packem_shared/sqliteEncode-Dedu92k4.mjs +69 -0
- package/package.json +46 -18
|
@@ -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 `(<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 `(<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 };
|
package/dist/dialect.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
package/dist/index.d.mts
ADDED
|
@@ -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_<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_<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_<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.d.ts
ADDED
|
@@ -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_<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_<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_<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-CQFYyQLP.mjs';
|
|
2
|
+
export { decodeBigint, effectiveColumnKind, sqliteDecode, sqliteEncode, tryJsonParse } from './packem_shared/sqliteEncode-Dedu92k4.mjs';
|