@eltjs/elt-sqlite 0.0.0-stage → 0.1.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.
Files changed (47) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +248 -2
  3. package/dist/computed-dataset.d.ts +22 -0
  4. package/dist/computed-dataset.js +230 -0
  5. package/dist/index.d.ts +13 -0
  6. package/dist/index.js +13 -0
  7. package/dist/open-destination.d.ts +3 -0
  8. package/dist/open-destination.js +14 -0
  9. package/dist/reserved.d.ts +7 -0
  10. package/dist/reserved.js +9 -0
  11. package/dist/sqlite-append-writer.d.ts +8 -0
  12. package/dist/sqlite-append-writer.js +10 -0
  13. package/dist/sqlite-catalog.d.ts +3 -0
  14. package/dist/sqlite-catalog.js +33 -0
  15. package/dist/sqlite-checkpoint-store.d.ts +9 -0
  16. package/dist/sqlite-checkpoint-store.js +84 -0
  17. package/dist/sqlite-column.d.ts +53 -0
  18. package/dist/sqlite-column.js +306 -0
  19. package/dist/sqlite-columns.d.ts +14 -0
  20. package/dist/sqlite-columns.js +118 -0
  21. package/dist/sqlite-deduplicating-writer.d.ts +21 -0
  22. package/dist/sqlite-deduplicating-writer.js +124 -0
  23. package/dist/sqlite-descriptions.d.ts +8 -0
  24. package/dist/sqlite-descriptions.js +37 -0
  25. package/dist/sqlite-destination.d.ts +19 -0
  26. package/dist/sqlite-destination.js +67 -0
  27. package/dist/sqlite-file-store.d.ts +17 -0
  28. package/dist/sqlite-file-store.js +82 -0
  29. package/dist/sqlite-identifiers.d.ts +8 -0
  30. package/dist/sqlite-identifiers.js +11 -0
  31. package/dist/sqlite-overwrite-writer.d.ts +9 -0
  32. package/dist/sqlite-overwrite-writer.js +13 -0
  33. package/dist/sqlite-passes.d.ts +39 -0
  34. package/dist/sqlite-passes.js +174 -0
  35. package/dist/sqlite-sync-history-schema.d.ts +8 -0
  36. package/dist/sqlite-sync-history-schema.js +94 -0
  37. package/dist/sqlite-sync-history.d.ts +12 -0
  38. package/dist/sqlite-sync-history.js +119 -0
  39. package/dist/sqlite-table.d.ts +15 -0
  40. package/dist/sqlite-table.js +83 -0
  41. package/dist/sqlite-views.d.ts +16 -0
  42. package/dist/sqlite-views.js +62 -0
  43. package/dist/sqlite-writer.d.ts +38 -0
  44. package/dist/sqlite-writer.js +483 -0
  45. package/dist/view.d.ts +20 -0
  46. package/dist/view.js +59 -0
  47. package/package.json +52 -4
@@ -0,0 +1,230 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { Declaration } from '@zukhruf/sqlite-drift';
3
+ import { open } from '@zukhruf/sqlite-open';
4
+ import { transaction } from '@zukhruf/sqlite-transaction';
5
+ import { DerivedDataset, } from '@eltjs/elt';
6
+ import { busyTimeout, openDestination } from './open-destination.js';
7
+ import { SQLiteDestination } from './sqlite-destination.js';
8
+ import { publishSQLiteViews } from './sqlite-views.js';
9
+ import { View } from './view.js';
10
+ const quote = (name) => `"${name.replaceAll('"', '""')}"`;
11
+ // The columns the dataset keeps on every output row beside the rule's own.
12
+ const engineColumns = {
13
+ input_hash: 'SHA-256 of the input the row was made from; the row is made again when its input changes.',
14
+ rule: 'What made the row, such as the parser of its format or the model; the row is made again when that changes. unsupported: the rule does not handle the input.',
15
+ status: 'computed: the rule made the row. empty: the rule found nothing to keep, such as a photo without words. failed: the rule failed, error says why, and the refresh after the passes tries again, at most once for each pass.',
16
+ error: 'Why the rule failed. NULL unless status is failed.',
17
+ };
18
+ // Data computed on write from what the pipeline's copies load: a rule makes
19
+ // one output row for each input, such as the text of each stored file, as
20
+ // pgai's vectorizer keeps an embedding beside each row. The pipeline refreshes
21
+ // it as its copies commit; each refresh makes the rows whose input or rule
22
+ // changed and deletes the rows whose input is gone, and a refresh after the
23
+ // passes also tries failed rows again; the passes try each input at most
24
+ // once, so a failing rule is asked once a pass.
25
+ // A rule's failure on one input is that row's; it never fails the load.
26
+ // Readers query the view name, over the table raw_<name>, which a refresh
27
+ // replaces when its declaration changes, since it is rebuilt from the inputs.
28
+ export class ComputedDataset extends DerivedDataset {
29
+ name;
30
+ #into;
31
+ #description;
32
+ #from;
33
+ #rule;
34
+ #table;
35
+ #declaration;
36
+ // Every column of an output row, in table order.
37
+ #columns;
38
+ constructor({ into, name, description, from, compute, }) {
39
+ super();
40
+ this.name = name;
41
+ this.#into = into;
42
+ this.#description = description;
43
+ this.#from = Object.freeze([...from]);
44
+ this.#rule = compute;
45
+ this.#table = `raw_${name}`;
46
+ this.#columns = Object.freeze([
47
+ compute.key.name,
48
+ ...Object.keys(engineColumns),
49
+ ...Object.keys(compute.output),
50
+ ]);
51
+ const ruleColumns = Object.entries(compute.output).map(([column, { type }]) => `${quote(column)} ${type === 'blob' ? 'BLOB' : 'TEXT'}`);
52
+ this.#declaration = new Declaration([
53
+ `CREATE TABLE ${quote(this.#table)} (${[
54
+ `${quote(compute.key.name)} TEXT PRIMARY KEY`,
55
+ '"input_hash" TEXT NOT NULL',
56
+ '"rule" TEXT NOT NULL',
57
+ `"status" TEXT NOT NULL CHECK ("status" IN ('computed', 'empty', 'failed'))`,
58
+ '"error" TEXT',
59
+ ...ruleColumns,
60
+ ].join(', ')}) STRICT`,
61
+ ], { rows: 'replace' });
62
+ Object.freeze(this);
63
+ }
64
+ validate(connections) {
65
+ for (const target of this.#from)
66
+ if (!(target instanceof View) &&
67
+ this.#source(connections, target) === undefined)
68
+ throw new TypeError(`Computed dataset ${this.name} reads ${target.name}, which no copy of the pipeline loads`);
69
+ const made = connections
70
+ .filter(({ destination }) => this.#same(destination))
71
+ .flatMap(({ steps }) => steps)
72
+ .flatMap(({ to }) => [to.name, to.readerView])
73
+ .filter((name) => name !== undefined)
74
+ .map((name) => name.toLowerCase());
75
+ for (const name of [this.name, this.#table])
76
+ if (made.includes(name.toLowerCase()))
77
+ throw new TypeError(`Computed dataset ${this.name} needs ${name}, which a copy of the pipeline makes`);
78
+ }
79
+ async refresh({ connections, signal, trigger, }) {
80
+ const inputs = this.#inputs(connections);
81
+ using database = openDestination(this.#into.path);
82
+ const key = this.#rule.key.name;
83
+ const stored = transaction(database, () => {
84
+ this.#declaration.apply(database);
85
+ publishSQLiteViews(database, {
86
+ views: [
87
+ {
88
+ name: this.name,
89
+ description: this.#description,
90
+ columns: {
91
+ [key]: this.#rule.key.description,
92
+ ...engineColumns,
93
+ ...Object.fromEntries(Object.entries(this.#rule.output).map(([column, { description }]) => [column, description])),
94
+ },
95
+ query: `SELECT ${this.#columns.map(quote).join(', ')} FROM ${quote(this.#table)}`,
96
+ },
97
+ ],
98
+ });
99
+ const rows = new Map();
100
+ const remove = database.prepare(`DELETE FROM ${quote(this.#table)} WHERE ${quote(key)} = ?`);
101
+ for (const row of database
102
+ .prepare(`SELECT ${quote(key)} AS "key", "input_hash", "rule", "status" FROM ${quote(this.#table)}`)
103
+ .all()) {
104
+ const id = String(row.key);
105
+ if (!inputs.has(id))
106
+ remove.run(id);
107
+ else
108
+ rows.set(id, {
109
+ inputHash: String(row.input_hash),
110
+ rule: String(row.rule),
111
+ status: String(row.status),
112
+ });
113
+ }
114
+ return rows;
115
+ }, { mode: 'immediate' });
116
+ const write = database.prepare(`INSERT OR REPLACE INTO ${quote(this.#table)} (${this.#columns.map(quote).join(', ')}) VALUES (${this.#columns.map(() => '?').join(', ')})`);
117
+ const store = (id, inputHash, rule, status, error, values) => write.run(id, inputHash, rule, status, error, ...Object.keys(this.#rule.output).map((column) => values[column] ?? null));
118
+ const due = [];
119
+ for (const [id, input] of inputs) {
120
+ if (signal?.aborted)
121
+ return;
122
+ const inputHash = createHash('sha256')
123
+ .update(JSON.stringify(input))
124
+ .digest('hex');
125
+ const rule = await this.#rule.identity(input);
126
+ const before = stored.get(id);
127
+ // A rule that does not handle the input keeps what it made before.
128
+ if (rule === null) {
129
+ if (before?.status === 'computed')
130
+ continue;
131
+ if (before?.rule === 'unsupported' && before.inputHash === inputHash)
132
+ continue;
133
+ store(id, inputHash, 'unsupported', 'empty', null, {});
134
+ continue;
135
+ }
136
+ if (before !== undefined &&
137
+ before.inputHash === inputHash &&
138
+ before.rule === rule) {
139
+ // A failed input waits for the refresh after the passes.
140
+ if (before.status !== 'failed' || trigger.kind === 'commit')
141
+ continue;
142
+ }
143
+ // The passes try each input, as it is now, at most once: the refresh
144
+ // after them leaves what a commit refresh of the same passes tried.
145
+ const attempt = `${id}\0${inputHash}\0${rule}`;
146
+ if (trigger.tried.has(attempt))
147
+ continue;
148
+ if (trigger.kind === 'commit')
149
+ trigger.tried.add(attempt);
150
+ due.push({ id, input, inputHash, rule });
151
+ }
152
+ // A pool of calls, each taking the next batch once its own committed, so
153
+ // a slow call holds only its place, as a worker pool keeps its other
154
+ // workers busy. A call that fails stops the pool taking more.
155
+ const failures = [];
156
+ let next = 0;
157
+ await Promise.all(Array.from({ length: this.#rule.concurrency }, async () => {
158
+ while (failures.length === 0 && signal?.aborted !== true) {
159
+ const batch = due.slice(next, next + this.#rule.batch);
160
+ next += this.#rule.batch;
161
+ if (batch.length === 0)
162
+ return;
163
+ let results;
164
+ try {
165
+ results = await this.#rule.compute(batch.map(({ input }) => input), signal);
166
+ }
167
+ catch (error) {
168
+ failures.push(error);
169
+ return;
170
+ }
171
+ // Each call commits on its own, so readers see rows as they land.
172
+ transaction(database, () => {
173
+ batch.forEach(({ id, inputHash, rule }, index) => {
174
+ const result = results[index];
175
+ if (result === undefined || 'gone' in result)
176
+ return;
177
+ if ('value' in result)
178
+ store(id, inputHash, rule, 'computed', null, result.value);
179
+ else if ('error' in result)
180
+ store(id, inputHash, rule, 'failed', result.error, {});
181
+ else
182
+ store(id, inputHash, rule, 'empty', null, {});
183
+ });
184
+ }, { mode: 'immediate' });
185
+ }
186
+ }));
187
+ if (failures.length > 0)
188
+ throw failures[0];
189
+ }
190
+ // Each input by key, from the rows of each relation of from that exists
191
+ // yet: what the copies loaded, or what a view before this dataset gives. A
192
+ // key read twice keeps its first input.
193
+ #inputs(connections) {
194
+ const inputs = new Map();
195
+ for (const target of this.#from) {
196
+ const path = target instanceof View
197
+ ? target.into.path
198
+ : this.#source(connections, target);
199
+ if (path === undefined)
200
+ continue;
201
+ using database = open(path, { readOnly: true, timeout: busyTimeout });
202
+ const relation = target instanceof View
203
+ ? target.name
204
+ : (target.readerView ?? target.name);
205
+ if (database
206
+ .prepare('SELECT 1 FROM sqlite_schema WHERE lower("name") = lower(?)')
207
+ .get(relation) === undefined)
208
+ continue;
209
+ for (const row of database
210
+ .prepare(`SELECT * FROM ${quote(relation)}`)
211
+ .all()) {
212
+ const read = this.#rule.read(row);
213
+ if (read !== null && !inputs.has(read.key))
214
+ inputs.set(read.key, read.input);
215
+ }
216
+ }
217
+ return inputs;
218
+ }
219
+ // The file of the copy of the pipeline that loads target.
220
+ #source(connections, target) {
221
+ const connection = connections.find(({ steps }) => steps.some(({ to }) => to === target));
222
+ return connection?.destination instanceof SQLiteDestination
223
+ ? connection.destination.path
224
+ : undefined;
225
+ }
226
+ #same(destination) {
227
+ return (destination instanceof SQLiteDestination &&
228
+ destination.path === this.#into.path);
229
+ }
230
+ }
@@ -0,0 +1,13 @@
1
+ export { ComputedDataset } from './computed-dataset.ts';
2
+ export { openDestination } from './open-destination.ts';
3
+ export { installSQLiteCatalog } from './sqlite-catalog.ts';
4
+ export { SQLiteCheckpointStore } from './sqlite-checkpoint-store.ts';
5
+ export { SQLiteColumn } from './sqlite-column.ts';
6
+ export { SQLiteColumns } from './sqlite-columns.ts';
7
+ export { SQLiteDestination } from './sqlite-destination.ts';
8
+ export { type PassState, type PassStatus, readPassStatus, type StreamPassStatus, } from './sqlite-passes.ts';
9
+ export { SQLiteSyncHistory } from './sqlite-sync-history.ts';
10
+ export { SQLiteTable } from './sqlite-table.ts';
11
+ export { isReserved } from './reserved.ts';
12
+ export { publishSQLiteViews, type SQLiteView } from './sqlite-views.ts';
13
+ export { View } from './view.ts';
package/dist/index.js ADDED
@@ -0,0 +1,13 @@
1
+ export { ComputedDataset } from './computed-dataset.js';
2
+ export { openDestination } from './open-destination.js';
3
+ export { installSQLiteCatalog } from './sqlite-catalog.js';
4
+ export { SQLiteCheckpointStore } from './sqlite-checkpoint-store.js';
5
+ export { SQLiteColumn } from './sqlite-column.js';
6
+ export { SQLiteColumns } from './sqlite-columns.js';
7
+ export { SQLiteDestination } from './sqlite-destination.js';
8
+ export { readPassStatus, } from './sqlite-passes.js';
9
+ export { SQLiteSyncHistory } from './sqlite-sync-history.js';
10
+ export { SQLiteTable } from './sqlite-table.js';
11
+ export { isReserved } from './reserved.js';
12
+ export { publishSQLiteViews } from './sqlite-views.js';
13
+ export { View } from './view.js';
@@ -0,0 +1,3 @@
1
+ import type { DatabaseSync } from 'node:sqlite';
2
+ export declare const busyTimeout = 30000;
3
+ export declare function openDestination(path: string): DatabaseSync;
@@ -0,0 +1,14 @@
1
+ import { PersistentWriteAheadLog, open } from '@zukhruf/sqlite-open';
2
+ // How long a connection waits for the commit of another connection, in
3
+ // milliseconds.
4
+ export const busyTimeout = 30_000;
5
+ // Opens a connection that writes a SQLite file. Agents read the file with
6
+ // sqlite3 -readonly from a folder that they cannot write, so the -wal and
7
+ // -shm files must stay after the last close. The last connection that writes
8
+ // the file and closes decides this, so every such connection opens here.
9
+ export function openDestination(path) {
10
+ return open(path, {
11
+ journal: new PersistentWriteAheadLog(),
12
+ timeout: busyTimeout,
13
+ });
14
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Whether the destination and its sync history own a table, index or view of
3
+ * this name: each one whose name starts with `_elt_`, in any case, as SQLite
4
+ * compares names. No stream, view or computed dataset has such a name, so a
5
+ * reader that follows the data of a file skips these tables.
6
+ */
7
+ export declare function isReserved(name: string): boolean;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Whether the destination and its sync history own a table, index or view of
3
+ * this name: each one whose name starts with `_elt_`, in any case, as SQLite
4
+ * compares names. No stream, view or computed dataset has such a name, so a
5
+ * reader that follows the data of a file skips these tables.
6
+ */
7
+ export function isReserved(name) {
8
+ return /^_elt_/i.test(name);
9
+ }
@@ -0,0 +1,8 @@
1
+ import type { DatabaseSync } from 'node:sqlite';
2
+ import type { CopyConfiguration } from '@eltjs/elt';
3
+ import type { SQLiteTable } from './sqlite-table.ts';
4
+ import { SQLiteWriter } from './sqlite-writer.ts';
5
+ export declare class SQLiteAppendWriter extends SQLiteWriter {
6
+ constructor(configuration: CopyConfiguration, path: string, table: SQLiteTable);
7
+ protected merge(database: DatabaseSync, stage: string, loadedAt: string, into: string): void;
8
+ }
@@ -0,0 +1,10 @@
1
+ import { SQLiteWriter } from './sqlite-writer.js';
2
+ export class SQLiteAppendWriter extends SQLiteWriter {
3
+ constructor(configuration, path, table) {
4
+ super(configuration, path, table);
5
+ Object.freeze(this);
6
+ }
7
+ merge(database, stage, loadedAt, into) {
8
+ this.append(database, stage, loadedAt, into);
9
+ }
10
+ }
@@ -0,0 +1,3 @@
1
+ export declare function installSQLiteCatalog({ path }: {
2
+ path: string;
3
+ }): void;
@@ -0,0 +1,33 @@
1
+ import { transaction } from '@zukhruf/sqlite-transaction';
2
+ import { readerCatalog } from '@eltjs/elt';
3
+ import { openDestination } from './open-destination.js';
4
+ import { createDescriptions, descriptions } from './sqlite-descriptions.js';
5
+ import { publishSQLiteViews } from './sqlite-views.js';
6
+ // Lists what readers of one file can query: every described view and its
7
+ // columns, from what loads and publications wrote there; safe to repeat.
8
+ // Every column of a described view is described: loads refuse a reader view
9
+ // with an undescribed column, and publications describe exactly their columns.
10
+ export function installSQLiteCatalog({ path }) {
11
+ if (path === ':memory:')
12
+ throw new TypeError('A SQLite catalog requires a database file');
13
+ using database = openDestination(path);
14
+ transaction(database, () => {
15
+ createDescriptions(database);
16
+ publishSQLiteViews(database, {
17
+ views: [
18
+ {
19
+ ...readerCatalog,
20
+ query: `SELECT 'view' AS "kind", s."name" AS "name", NULL AS "data_type", d."description" AS "description"
21
+ FROM sqlite_schema s JOIN ${descriptions} d ON d."relation" = s."name" AND d."column" = ''
22
+ WHERE s."type" = 'view'
23
+ UNION ALL
24
+ SELECT 'column', s."name" || '.' || c."column", c."data_type", c."description"
25
+ FROM sqlite_schema s JOIN ${descriptions} d ON d."relation" = s."name" AND d."column" = ''
26
+ JOIN ${descriptions} c ON c."relation" = s."name" AND c."column" <> ''
27
+ WHERE s."type" = 'view'
28
+ ORDER BY 2`,
29
+ },
30
+ ],
31
+ });
32
+ }, { mode: 'immediate' });
33
+ }
@@ -0,0 +1,9 @@
1
+ import { type CheckpointSession, CheckpointStore } from '@eltjs/elt';
2
+ export declare class SQLiteCheckpointStore extends CheckpointStore {
3
+ #private;
4
+ readonly path: string;
5
+ constructor({ path }: {
6
+ path: string;
7
+ });
8
+ protected session<T>(ids: readonly string[], work: (session: CheckpointSession) => Promise<T>): Promise<T>;
9
+ }
@@ -0,0 +1,84 @@
1
+ import { chmodSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
3
+ import { Modes, Mutex } from '@zukhruf/mutex';
4
+ import { SqliteStore } from '@zukhruf/mutex/sqlite';
5
+ import { Declaration } from '@zukhruf/sqlite-drift';
6
+ import { open } from '@zukhruf/sqlite-open';
7
+ import { CheckpointStore } from '@eltjs/elt';
8
+ import { busyTimeout } from './open-destination.js';
9
+ const checkpoints = new Declaration([
10
+ 'CREATE TABLE checkpoints (id TEXT PRIMARY KEY NOT NULL, binding TEXT NOT NULL, reloading INTEGER NOT NULL CHECK (reloading IN (0, 1)), state TEXT NOT NULL) STRICT',
11
+ ], { rows: 'replace' });
12
+ export class SQLiteCheckpointStore extends CheckpointStore {
13
+ path;
14
+ // One key per replication, in a folder beside the state file. The kernel
15
+ // releases a key when the run holding it exits.
16
+ #locks;
17
+ constructor({ path }) {
18
+ super();
19
+ if (!path || path === ':memory:' || path.includes('\0'))
20
+ throw new TypeError('Checkpoints require a persistent SQLite file');
21
+ this.path = resolve(path);
22
+ // The lock never waits (skipIfBusy makes one attempt), and the write of
23
+ // its token retries only a busy file on Windows: real time, as no caller
24
+ // has another correct value.
25
+ this.#locks = new Mutex(new SqliteStore(`${this.path}.locks`, { clock: globalThis }));
26
+ Object.freeze(this);
27
+ }
28
+ // Holds each replication's key in turn, then works. A replication already
29
+ // running fails the run fast; others run in parallel.
30
+ async session(ids, work) {
31
+ const [id, ...rest] = ids;
32
+ if (id === undefined)
33
+ return this.#withFile(work);
34
+ const held = await this.#locks.acquire(id, () => this.session(rest, work), {
35
+ mode: Modes.skipIfBusy(),
36
+ });
37
+ if (!held.acquired)
38
+ throw new TypeError(`Checkpoint ${id} is in use by another run`);
39
+ return held.value;
40
+ }
41
+ // Each save commits on its own, so it is durable when it resolves.
42
+ async #withFile(work) {
43
+ // Runs of other replications write the same file.
44
+ using database = open(this.path, { timeout: busyTimeout });
45
+ // SQLite gives the -wal and -shm files the mode of the file when it makes
46
+ // them, at the first read after the open.
47
+ chmodSync(this.path, 0o600);
48
+ // A table another version declared otherwise is replaced with its
49
+ // checkpoints, so every replication starts over.
50
+ checkpoints.apply(database);
51
+ return await work({
52
+ read: async (id) => {
53
+ const saved = database
54
+ .prepare('SELECT binding, reloading FROM checkpoints WHERE id = ?')
55
+ .get(id);
56
+ return saved === undefined
57
+ ? undefined
58
+ : {
59
+ binding: String(saved.binding),
60
+ reloading: saved.reloading === 1,
61
+ };
62
+ },
63
+ state: async (id) => {
64
+ const saved = database
65
+ .prepare('SELECT state FROM checkpoints WHERE id = ?')
66
+ .get(id);
67
+ return saved === undefined ? undefined : String(saved.state);
68
+ },
69
+ save: async (id, { binding, reloading, state }) => {
70
+ database
71
+ .prepare('INSERT INTO checkpoints (id, binding, reloading, state) VALUES (?, ?, ?, ?) ON CONFLICT(id) DO UPDATE SET binding = excluded.binding, reloading = excluded.reloading, state = excluded.state')
72
+ .run(id, binding, reloading ? 1 : 0, state);
73
+ },
74
+ reloaded: async (id) => {
75
+ database
76
+ .prepare('UPDATE checkpoints SET reloading = 0 WHERE id = ?')
77
+ .run(id);
78
+ },
79
+ remove: async (id) => {
80
+ database.prepare('DELETE FROM checkpoints WHERE id = ?').run(id);
81
+ },
82
+ });
83
+ }
84
+ }
@@ -0,0 +1,53 @@
1
+ import type { SQLInputValue } from 'node:sqlite';
2
+ import type { DeclaredFormat, DocumentParser, FileReference } from '@eltjs/elt';
3
+ import { FileRead } from '@eltjs/elt';
4
+ declare const storageTypes: {
5
+ readonly text: 'TEXT';
6
+ readonly integer: 'INTEGER';
7
+ readonly int64: 'INTEGER';
8
+ readonly real: 'REAL';
9
+ readonly decimal: 'TEXT';
10
+ readonly blob: 'BLOB';
11
+ readonly boolean: 'INTEGER';
12
+ readonly date: 'TEXT';
13
+ readonly timestamp: 'TEXT';
14
+ readonly local_timestamp: 'TEXT';
15
+ readonly local_time: 'TEXT';
16
+ readonly object: 'TEXT';
17
+ };
18
+ type Kind = keyof typeof storageTypes;
19
+ type PlainKind = 'text' | 'integer' | 'real' | 'blob' | 'boolean' | 'date' | 'timestamp' | 'object';
20
+ export type SQLiteColumnType = PlainKind | DeclaredFormat;
21
+ export declare function canonical(kind: Kind, name: string, format?: DeclaredFormat): string;
22
+ export declare class SQLiteColumn {
23
+ #private;
24
+ readonly field: string;
25
+ readonly name: string;
26
+ readonly type: SQLiteColumnType;
27
+ readonly kind: Kind;
28
+ readonly isPrimaryKey: boolean;
29
+ readonly nullable: boolean;
30
+ readonly optional: boolean;
31
+ readonly array: boolean;
32
+ readonly fileRead?: FileRead;
33
+ constructor(field: string, type: SQLiteColumnType, options: {
34
+ nullable: boolean;
35
+ optional: boolean;
36
+ primaryKey: boolean;
37
+ array?: boolean;
38
+ fileRead?: FileRead;
39
+ name?: string;
40
+ });
41
+ primaryKey(): SQLiteColumn;
42
+ notNull(): SQLiteColumn;
43
+ from(file: FileReference): SQLiteColumn;
44
+ parse(parser: DocumentParser): SQLiteColumn;
45
+ named(name: string): SQLiteColumn;
46
+ get quotedName(): string;
47
+ get storesFile(): boolean;
48
+ get storageType(): string;
49
+ get dataType(): string;
50
+ get definition(): string;
51
+ encode(record: unknown): SQLInputValue;
52
+ }
53
+ export {};