@mark1russell7/sqlite 0.0.0-stage → 0.2.0
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 +21 -0
- package/README.md +187 -2
- package/build/database.d.ts +36 -0
- package/build/database.d.ts.map +1 -0
- package/build/database.js +47 -0
- package/build/database.js.map +1 -0
- package/build/event-log.d.ts +135 -0
- package/build/event-log.d.ts.map +1 -0
- package/build/event-log.js +201 -0
- package/build/event-log.js.map +1 -0
- package/build/faults.d.ts +41 -0
- package/build/faults.d.ts.map +1 -0
- package/build/faults.js +90 -0
- package/build/faults.js.map +1 -0
- package/build/index.d.ts +13 -0
- package/build/index.js +6 -0
- package/build/migrate.d.ts +57 -0
- package/build/migrate.d.ts.map +1 -0
- package/build/migrate.js +75 -0
- package/build/migrate.js.map +1 -0
- package/build/transaction.d.ts +30 -0
- package/build/transaction.d.ts.map +1 -0
- package/build/transaction.js +36 -0
- package/build/transaction.js.map +1 -0
- package/build/warning.d.ts +32 -0
- package/build/warning.d.ts.map +1 -0
- package/build/warning.js +49 -0
- package/build/warning.js.map +1 -0
- package/package.json +71 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event-log.js","names":[],"sources":["../src/event-log.ts"],"sourcesContent":["/**\n * A typed append-only log of events in SQLite. The log is a grow-only set of events with an order: an event that the log\n * has already changes nothing. The caller keeps a state, and the log keeps the file equal to it.\n *\n * @packageDocumentation\n */\nimport type { DatabaseSync } from \"node:sqlite\";\nimport { openDatabase, type OpenOptions } from \"./database.ts\";\nimport { migrate, SCHEMA_VERSIONS_TABLE, type Migration } from \"./migrate.ts\";\nimport { transaction } from \"./transaction.ts\";\n\n/** The fields that the log reads from each event. The log keeps the full event as JSON. */\nexport interface LogRecord {\n readonly id: string;\n readonly at: string;\n readonly type: string;\n}\n\n/**\n * This function accepts an event into the state of the caller, or refuses it. It gives a problem when it refuses the\n * event, and then it must not change the state.\n */\nexport type Accept<E, P> = (event: E) => P | undefined;\n\n/** This function tells if a new event is equal to the stored event with the same id. */\nexport type Equal<E> = (stored: E, event: E) => boolean;\n\n/**\n * The result of the append of one event.\n *\n * - `added`: the log did not have the id, `accept` accepted the event, and the log wrote it at the position `seq`.\n * - `duplicate`: the log has an equal event at the position `seq`. Nothing changed, and `accept` did not get the event.\n * - `conflict`: the log has a different event with the same id at the position `seq`. Nothing changed.\n * - `refused`: `accept` refused the event with the problem `problem`. Nothing changed.\n */\nexport type Appended<E, P> =\n | { readonly kind: \"added\"; readonly seq: number }\n | { readonly kind: \"duplicate\"; readonly seq: number }\n | { readonly kind: \"conflict\"; readonly seq: number; readonly stored: E }\n | { readonly kind: \"refused\"; readonly problem: P };\n\n/** One event of a log and its position. The first position is 1, and each new event gets a higher position. */\nexport interface LogEntry<E> {\n readonly seq: number;\n readonly event: E;\n}\n\n/** The options of a read of a log. */\nexport interface ReadOptions {\n /** This value is a position. The read gives only the events after it. The default is 0, so the read starts at the first event. */\n readonly after?: number | undefined;\n /** This value is a list of types. The read gives only the events of these types. The default is each type. */\n readonly types?: readonly string[] | undefined;\n /** This value is the highest count of events that the read gives. The default is no limit. */\n readonly limit?: number | undefined;\n}\n\n/** An open log. */\nexport interface EventLog<E extends LogRecord> {\n /** This function gives the events of the log, in the order of the log. */\n read(options?: ReadOptions): Generator<E>;\n /** This function gives the events of the log with their positions, in the order of the log. */\n entries(options?: ReadOptions): Generator<LogEntry<E>>;\n /** This function gives the event with an id and its position, or `undefined` when the log does not have the id. */\n get(id: string): LogEntry<E> | undefined;\n /** This function gives the position of the last event, or 0 for an empty log. A read after it gives only new events. */\n head(): number;\n /**\n * This function appends one event. An event that the log has already changes nothing. `accept` gets only an event with\n * a new id, before the write. The default `accept` accepts each event.\n */\n append<P = never>(event: E, accept?: Accept<E, P>): Appended<E, P>;\n /** This function does `append` for each event, in one transaction, and gives the result of each event in order. */\n appendAll<P = never>(events: Iterable<E>, accept?: Accept<E, P>): Appended<E, P>[];\n /** This function gives the count of events in the log. */\n count(): number;\n /**\n * This function makes a broken log usable again. It throws a `BrokenLogError` when the rollback of the failed write did\n * not end, and then the log stays broken. After it, the caller replays the log, so its state is equal to the file again.\n */\n recover(): void;\n}\n\n/** A log that owns its connection: `openEventLog` gives it. */\nexport interface EventLogFile<E extends LogRecord> extends EventLog<E> {\n /** This function closes the connection of the log. */\n close(): void;\n}\n\n/** The error of a call to a broken log. `stuck` is true when the rollback of the failed write did not end. */\nexport class BrokenLogError extends Error {\n override readonly name = \"BrokenLogError\";\n readonly stuck: boolean;\n\n constructor(message: string, stuck: boolean, cause: unknown) {\n super(message, { cause });\n this.stuck = stuck;\n }\n}\n\n/** The options of `eventLog`. */\nexport interface EventLogOptions<E> {\n /** This function changes a stored JSON value into an event. It throws on a value that is not an event. */\n readonly parse: (value: unknown) => E;\n /** This value is the name of the table of the log. The default is `events`. A file can have many logs in different tables. */\n readonly table?: string | undefined;\n /** This function tells if a new event is equal to the stored event with its id. The default compares the JSON text. */\n readonly equal?: Equal<E> | undefined;\n}\n\n/** The options of `openEventLog`: the options of the connection, and the equality of events. */\nexport interface EventLogFileOptions<E> extends OpenOptions {\n /** This function tells if a new event is equal to the stored event with its id. The default compares the JSON text. */\n readonly equal?: Equal<E> | undefined;\n}\n\n/** The steps of the schema of a log in a table. Do not change a step: add a new step. */\nfunction logSteps(table: string): readonly Migration[] {\n return [\n `CREATE TABLE ${table} (\n seq INTEGER PRIMARY KEY,\n id TEXT NOT NULL UNIQUE,\n at TEXT NOT NULL,\n type TEXT NOT NULL,\n body TEXT NOT NULL\n ) STRICT`,\n `CREATE INDEX ${table}_type ON ${table} (type, seq)`,\n ];\n}\n\n/** The schema version of a log: the `user_version` of a file of `openEventLog`, or the version of the part `event-log:<table>`. */\nexport const EVENT_LOG_SCHEMA_VERSION: number = logSteps(\"events\").length;\n\n/** The default name of the table of a log. */\nconst DEFAULT_TABLE = \"events\";\n\n/** This function throws when a name cannot be the table of a log. */\nfunction checkTable(table: string): void {\n if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(table) || table.toLowerCase().startsWith(\"sqlite_\") || table === SCHEMA_VERSIONS_TABLE) {\n throw new RangeError(`The table of a log must be a name of letters, digits and underscores, and not ${SCHEMA_VERSIONS_TABLE} or sqlite_*, not ${JSON.stringify(table)}.`);\n }\n}\n\n/** This function throws when a value is not a whole number at or above a lowest value. */\nfunction checkWhole(what: string, value: number, lowest: number): void {\n if (!Number.isInteger(value) || value < lowest) throw new RangeError(`${what} must be a whole number of ${String(lowest)} or more, not ${String(value)}.`);\n}\n\n/**\n * This function opens a log in a table of a connection of the caller. The log does not close the connection.\n *\n * The log keeps its schema version in the part `event-log:<table>` of the file (refer to `migrate`). Thus the app keeps its own\n * tables and its own steps in the same file. A call of the log in a transaction of the caller throws, and the log does not break.\n *\n * The state of the caller and the file stay equal: `accept` comes before the write. A write or an `accept` can fail with an\n * error. Then the transaction rolls back, and the log is broken. Each later call throws a `BrokenLogError` until `recover`.\n * The state of the caller can keep the events of the failed write, so the caller replays the log after `recover`.\n *\n * @param db - The connection. Set its pragmas with `openDatabase`.\n * @param options - The parse of the events, the table and the equality of events.\n */\nexport function eventLog<E extends LogRecord>(db: DatabaseSync, options: EventLogOptions<E>): EventLog<E> {\n const table = options.table ?? DEFAULT_TABLE;\n checkTable(table);\n migrate(db, logSteps(table), { part: `event-log:${table}`, name: `log ${table}` });\n return attach(db, table, options.parse, options.equal);\n}\n\n/**\n * This function opens a log in its own file, and makes the file when it is not there. `:memory:` gives a log with no file.\n *\n * The log owns the connection and the `user_version` of its file, and its table is `events`. A file of a newer schema\n * version gives an error. The rules of `eventLog` apply.\n *\n * @param path - The path of the file, or `:memory:`.\n * @param parse - The function that changes a stored JSON value into an event. It throws on a value that is not an event.\n * @param options - The pragmas of the connection, and the equality of events. The defaults are the defaults of `openDatabase`.\n */\nexport function openEventLog<E extends LogRecord>(path: string, parse: (value: unknown) => E, options: EventLogFileOptions<E> = {}): EventLogFile<E> {\n const db = openDatabase(path, options);\n try {\n migrate(db, logSteps(DEFAULT_TABLE), { name: \"log\" });\n const log = attach(db, DEFAULT_TABLE, parse, options.equal);\n return {\n ...log,\n close() {\n db.close();\n },\n };\n } catch (error) {\n db.close();\n throw error;\n }\n}\n\n/** This function makes the log object on a table with a current schema. */\nfunction attach<E extends LogRecord>(db: DatabaseSync, table: string, parse: (value: unknown) => E, equal: Equal<E> | undefined): EventLog<E> {\n const insert = db.prepare(`INSERT INTO ${table} (id, at, type, body) VALUES (?, ?, ?, ?)`);\n const byId = db.prepare(`SELECT seq, body FROM ${table} WHERE id = ?`);\n const readSql = `SELECT seq, body FROM ${table} WHERE seq > ? AND (? IS NULL OR type IN (SELECT value FROM json_each(?))) ORDER BY seq LIMIT ?`;\n /** The error of a failed write. A broken log refuses each call until `recover`. */\n let broken: unknown;\n /** The error of a rollback that did not end. A log with this error cannot recover. */\n let stuck: unknown;\n const onRollbackError = (error: unknown): void => {\n stuck = error;\n };\n\n function guard(): void {\n if (broken !== undefined) throw new BrokenLogError(\"The log is broken by an earlier error. Open it again and replay it.\", stuck !== undefined, broken);\n }\n\n function decode(row: { seq: number; body: string }): E {\n try {\n return parse(JSON.parse(row.body));\n } catch (error) {\n throw new Error(`The event at the position ${String(row.seq)} of the log is not valid.`, { cause: error });\n }\n }\n\n function write<P>(event: E, accept: Accept<E, P> | undefined): Appended<E, P> {\n const body = JSON.stringify(event);\n const row = byId.get(event.id) as { seq: number; body: string } | undefined;\n if (row !== undefined) {\n if (equal === undefined && row.body === body) return { kind: \"duplicate\", seq: row.seq };\n const stored = decode(row);\n if (equal?.(stored, event) === true) return { kind: \"duplicate\", seq: row.seq };\n return { kind: \"conflict\", seq: row.seq, stored };\n }\n const problem = accept?.(event);\n if (problem !== undefined) return { kind: \"refused\", problem };\n const result = insert.run(event.id, event.at, event.type, body);\n return { kind: \"added\", seq: Number(result.lastInsertRowid) };\n }\n\n /** This function does the work in one transaction. A failure after the `BEGIN` breaks the log. */\n function inTransaction<T>(work: () => T): T {\n guard();\n let began = false;\n try {\n return transaction(\n db,\n () => {\n began = true;\n return work();\n },\n { onRollbackError },\n );\n } catch (error) {\n if (began) broken = error;\n throw error;\n }\n }\n\n function* entries(options: ReadOptions = {}): Generator<LogEntry<E>> {\n guard();\n const after = options.after ?? 0;\n checkWhole(\"The position after\", after, 0);\n if (options.limit !== undefined) checkWhole(\"The limit of a read\", options.limit, 1);\n const types = options.types === undefined ? null : JSON.stringify(options.types);\n const rows = db.prepare(readSql).iterate(after, types, types, options.limit ?? -1) as Iterable<{ seq: number; body: string }>;\n for (const row of rows) yield { seq: row.seq, event: decode(row) };\n }\n\n return {\n *read(options) {\n for (const entry of entries(options)) yield entry.event;\n },\n entries,\n get(id) {\n guard();\n const row = byId.get(id) as { seq: number; body: string } | undefined;\n return row === undefined ? undefined : { seq: row.seq, event: decode(row) };\n },\n head() {\n guard();\n return (db.prepare(`SELECT COALESCE(MAX(seq), 0) AS head FROM ${table}`).get() as { head: number }).head;\n },\n append(event, accept) {\n return inTransaction(() => write(event, accept));\n },\n appendAll(events, accept) {\n return inTransaction(() => {\n const results = [];\n for (const event of events) results.push(write(event, accept));\n return results;\n });\n },\n count() {\n guard();\n return (db.prepare(`SELECT count(*) AS n FROM ${table}`).get() as { n: number }).n;\n },\n recover() {\n if (broken === undefined) return;\n if (stuck !== undefined || db.isTransaction) {\n throw new BrokenLogError(\"The rollback of the failed write did not end. Open the log again and replay it.\", true, stuck ?? broken);\n }\n broken = undefined;\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The faults of `node:sqlite`: the kind of each error and the action that it needs. A pure calculation from the result
|
|
3
|
+
* code of SQLite, so a caller decides with no text match on the message.
|
|
4
|
+
*
|
|
5
|
+
* @packageDocumentation
|
|
6
|
+
*/
|
|
7
|
+
/** The kind of a fault of SQLite, from its primary result code. */
|
|
8
|
+
export type SqliteFaultKind = "sql" | "internal" | "permission" | "aborted" | "busy" | "locked" | "no-memory" | "read-only" | "interrupted" | "io" | "corrupt" | "not-found" | "full" | "cannot-open" | "protocol" | "schema" | "too-big" | "constraint" | "mismatch" | "misuse" | "no-lfs" | "authorization" | "range" | "not-a-database" | "other";
|
|
9
|
+
/**
|
|
10
|
+
* The action that a fault needs.
|
|
11
|
+
*
|
|
12
|
+
* - `retry`: the fault can go away alone. Do the work again later, for example after a wait for a lock.
|
|
13
|
+
* - `quarantine`: the file is broken. Move it away, and make the data again from its source or from a copy.
|
|
14
|
+
* - `operator`: the computer needs a change that the program cannot do: disk space, access, a path or memory.
|
|
15
|
+
* - `bug`: the program used SQLite wrongly. The same work fails again.
|
|
16
|
+
*/
|
|
17
|
+
export type SqliteFaultAction = "retry" | "quarantine" | "operator" | "bug";
|
|
18
|
+
/** One fault of SQLite. */
|
|
19
|
+
export interface SqliteFault {
|
|
20
|
+
readonly kind: SqliteFaultKind;
|
|
21
|
+
readonly action: SqliteFaultAction;
|
|
22
|
+
/** This value is the primary result code of SQLite, for example 5 for `SQLITE_BUSY`. */
|
|
23
|
+
readonly code: number;
|
|
24
|
+
/** This value is the extended result code of SQLite, for example 1555 for `SQLITE_CONSTRAINT_PRIMARYKEY`. */
|
|
25
|
+
readonly extendedCode: number;
|
|
26
|
+
/** This value is the message of the error. */
|
|
27
|
+
readonly message: string;
|
|
28
|
+
/** This value is the error of SQLite. It can be the cause of the error that the caller got. */
|
|
29
|
+
readonly error: Error;
|
|
30
|
+
}
|
|
31
|
+
/** The action of each kind of fault. */
|
|
32
|
+
export declare const SQLITE_FAULT_ACTIONS: Readonly<Record<SqliteFaultKind, SqliteFaultAction>>;
|
|
33
|
+
/**
|
|
34
|
+
* This function gives the fault of SQLite in an error, or `undefined` when the error has none.
|
|
35
|
+
*
|
|
36
|
+
* It reads the error first, and then its chain of causes. Thus it finds the fault in the error of a broken log too.
|
|
37
|
+
*
|
|
38
|
+
* @param error - The error that the caller got.
|
|
39
|
+
*/
|
|
40
|
+
export declare function sqliteFault(error: unknown): SqliteFault | undefined;
|
|
41
|
+
//# sourceMappingURL=faults.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"faults.d.ts","names":[],"sources":["../src/faults.ts"],"mappings":";;;;;;;YAQY;;;;;;;;;YAmCA;;iBAGK;WACN,MAAM;WACN,QAAQ;;WAER;;WAEA;;WAEA;;WAEA,OAAO;;;qBAgCL,sBAAsB,SAAS,OAAO,iBAAiB;;;;;;;;wBA2CpD,YAAY,iBAAiB"}
|
package/build/faults.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// src/faults.ts
|
|
2
|
+
/** The kind of each primary result code of SQLite. */
|
|
3
|
+
const KINDS = {
|
|
4
|
+
1: "sql",
|
|
5
|
+
2: "internal",
|
|
6
|
+
3: "permission",
|
|
7
|
+
4: "aborted",
|
|
8
|
+
5: "busy",
|
|
9
|
+
6: "locked",
|
|
10
|
+
7: "no-memory",
|
|
11
|
+
8: "read-only",
|
|
12
|
+
9: "interrupted",
|
|
13
|
+
10: "io",
|
|
14
|
+
11: "corrupt",
|
|
15
|
+
12: "not-found",
|
|
16
|
+
13: "full",
|
|
17
|
+
14: "cannot-open",
|
|
18
|
+
15: "protocol",
|
|
19
|
+
17: "schema",
|
|
20
|
+
18: "too-big",
|
|
21
|
+
19: "constraint",
|
|
22
|
+
20: "mismatch",
|
|
23
|
+
21: "misuse",
|
|
24
|
+
22: "no-lfs",
|
|
25
|
+
23: "authorization",
|
|
26
|
+
25: "range",
|
|
27
|
+
26: "not-a-database"
|
|
28
|
+
};
|
|
29
|
+
/** The action of each kind of fault. */
|
|
30
|
+
const SQLITE_FAULT_ACTIONS = {
|
|
31
|
+
busy: "retry",
|
|
32
|
+
locked: "retry",
|
|
33
|
+
aborted: "retry",
|
|
34
|
+
interrupted: "retry",
|
|
35
|
+
protocol: "retry",
|
|
36
|
+
schema: "retry",
|
|
37
|
+
corrupt: "quarantine",
|
|
38
|
+
"not-a-database": "quarantine",
|
|
39
|
+
full: "operator",
|
|
40
|
+
io: "operator",
|
|
41
|
+
"read-only": "operator",
|
|
42
|
+
"cannot-open": "operator",
|
|
43
|
+
permission: "operator",
|
|
44
|
+
"no-memory": "operator",
|
|
45
|
+
"no-lfs": "operator",
|
|
46
|
+
other: "operator",
|
|
47
|
+
sql: "bug",
|
|
48
|
+
internal: "bug",
|
|
49
|
+
"not-found": "bug",
|
|
50
|
+
"too-big": "bug",
|
|
51
|
+
constraint: "bug",
|
|
52
|
+
mismatch: "bug",
|
|
53
|
+
misuse: "bug",
|
|
54
|
+
authorization: "bug",
|
|
55
|
+
range: "bug"
|
|
56
|
+
};
|
|
57
|
+
/** The longest chain of causes that `sqliteFault` reads. */
|
|
58
|
+
const MAX_DEPTH = 16;
|
|
59
|
+
/** This function tells if a value is an error of `node:sqlite`. */
|
|
60
|
+
function isSqliteError(value) {
|
|
61
|
+
return value instanceof Error && value.code === "ERR_SQLITE_ERROR" && typeof value.errcode === "number";
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* This function gives the fault of SQLite in an error, or `undefined` when the error has none.
|
|
65
|
+
*
|
|
66
|
+
* It reads the error first, and then its chain of causes. Thus it finds the fault in the error of a broken log too.
|
|
67
|
+
*
|
|
68
|
+
* @param error - The error that the caller got.
|
|
69
|
+
*/
|
|
70
|
+
function sqliteFault(error) {
|
|
71
|
+
let value = error;
|
|
72
|
+
for (let depth = 0; depth < MAX_DEPTH && value instanceof Error; depth++) {
|
|
73
|
+
if (isSqliteError(value)) {
|
|
74
|
+
const code = value.errcode & 255;
|
|
75
|
+
const kind = KINDS[code] ?? "other";
|
|
76
|
+
return {
|
|
77
|
+
kind,
|
|
78
|
+
action: SQLITE_FAULT_ACTIONS[kind],
|
|
79
|
+
code,
|
|
80
|
+
extendedCode: value.errcode,
|
|
81
|
+
message: value.message,
|
|
82
|
+
error: value
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
value = value.cause;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
export { SQLITE_FAULT_ACTIONS, sqliteFault };
|
|
89
|
+
|
|
90
|
+
//# sourceMappingURL=faults.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"faults.js","names":[],"sources":["../src/faults.ts"],"sourcesContent":["/**\n * The faults of `node:sqlite`: the kind of each error and the action that it needs. A pure calculation from the result\n * code of SQLite, so a caller decides with no text match on the message.\n *\n * @packageDocumentation\n */\n\n/** The kind of a fault of SQLite, from its primary result code. */\nexport type SqliteFaultKind =\n | \"sql\"\n | \"internal\"\n | \"permission\"\n | \"aborted\"\n | \"busy\"\n | \"locked\"\n | \"no-memory\"\n | \"read-only\"\n | \"interrupted\"\n | \"io\"\n | \"corrupt\"\n | \"not-found\"\n | \"full\"\n | \"cannot-open\"\n | \"protocol\"\n | \"schema\"\n | \"too-big\"\n | \"constraint\"\n | \"mismatch\"\n | \"misuse\"\n | \"no-lfs\"\n | \"authorization\"\n | \"range\"\n | \"not-a-database\"\n | \"other\";\n\n/**\n * The action that a fault needs.\n *\n * - `retry`: the fault can go away alone. Do the work again later, for example after a wait for a lock.\n * - `quarantine`: the file is broken. Move it away, and make the data again from its source or from a copy.\n * - `operator`: the computer needs a change that the program cannot do: disk space, access, a path or memory.\n * - `bug`: the program used SQLite wrongly. The same work fails again.\n */\nexport type SqliteFaultAction = \"retry\" | \"quarantine\" | \"operator\" | \"bug\";\n\n/** One fault of SQLite. */\nexport interface SqliteFault {\n readonly kind: SqliteFaultKind;\n readonly action: SqliteFaultAction;\n /** This value is the primary result code of SQLite, for example 5 for `SQLITE_BUSY`. */\n readonly code: number;\n /** This value is the extended result code of SQLite, for example 1555 for `SQLITE_CONSTRAINT_PRIMARYKEY`. */\n readonly extendedCode: number;\n /** This value is the message of the error. */\n readonly message: string;\n /** This value is the error of SQLite. It can be the cause of the error that the caller got. */\n readonly error: Error;\n}\n\n/** The kind of each primary result code of SQLite. */\nconst KINDS: Readonly<Record<number, SqliteFaultKind>> = {\n 1: \"sql\",\n 2: \"internal\",\n 3: \"permission\",\n 4: \"aborted\",\n 5: \"busy\",\n 6: \"locked\",\n 7: \"no-memory\",\n 8: \"read-only\",\n 9: \"interrupted\",\n 10: \"io\",\n 11: \"corrupt\",\n 12: \"not-found\",\n 13: \"full\",\n 14: \"cannot-open\",\n 15: \"protocol\",\n 17: \"schema\",\n 18: \"too-big\",\n 19: \"constraint\",\n 20: \"mismatch\",\n 21: \"misuse\",\n 22: \"no-lfs\",\n 23: \"authorization\",\n 25: \"range\",\n 26: \"not-a-database\",\n};\n\n/** The action of each kind of fault. */\nexport const SQLITE_FAULT_ACTIONS: Readonly<Record<SqliteFaultKind, SqliteFaultAction>> = {\n busy: \"retry\",\n locked: \"retry\",\n aborted: \"retry\",\n interrupted: \"retry\",\n protocol: \"retry\",\n schema: \"retry\",\n corrupt: \"quarantine\",\n \"not-a-database\": \"quarantine\",\n full: \"operator\",\n io: \"operator\",\n \"read-only\": \"operator\",\n \"cannot-open\": \"operator\",\n permission: \"operator\",\n \"no-memory\": \"operator\",\n \"no-lfs\": \"operator\",\n other: \"operator\",\n sql: \"bug\",\n internal: \"bug\",\n \"not-found\": \"bug\",\n \"too-big\": \"bug\",\n constraint: \"bug\",\n mismatch: \"bug\",\n misuse: \"bug\",\n authorization: \"bug\",\n range: \"bug\",\n};\n\n/** The longest chain of causes that `sqliteFault` reads. */\nconst MAX_DEPTH = 16;\n\n/** This function tells if a value is an error of `node:sqlite`. */\nfunction isSqliteError(value: unknown): value is Error & { readonly errcode: number } {\n return value instanceof Error && (value as { code?: unknown }).code === \"ERR_SQLITE_ERROR\" && typeof (value as { errcode?: unknown }).errcode === \"number\";\n}\n\n/**\n * This function gives the fault of SQLite in an error, or `undefined` when the error has none.\n *\n * It reads the error first, and then its chain of causes. Thus it finds the fault in the error of a broken log too.\n *\n * @param error - The error that the caller got.\n */\nexport function sqliteFault(error: unknown): SqliteFault | undefined {\n let value: unknown = error;\n for (let depth = 0; depth < MAX_DEPTH && value instanceof Error; depth++) {\n if (isSqliteError(value)) {\n const code = value.errcode & 0xff;\n const kind = KINDS[code] ?? \"other\";\n return { kind, action: SQLITE_FAULT_ACTIONS[kind], code, extendedCode: value.errcode, message: value.message, error: value };\n }\n value = value.cause;\n }\n return undefined;\n}\n"],"mappings":";;AA4DA,MAAM,QAAmD;CACvD,GAAG;CACH,GAAG;CACH,GAAG;CACH,GAAG;CACH,GAAG;CACH,GAAG;CACH,GAAG;CACH,GAAG;CACH,GAAG;CACH,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;AACN;;AAGA,MAAa,uBAA6E;CACxF,MAAM;CACN,QAAQ;CACR,SAAS;CACT,aAAa;CACb,UAAU;CACV,QAAQ;CACR,SAAS;CACT,kBAAkB;CAClB,MAAM;CACN,IAAI;CACJ,aAAa;CACb,eAAe;CACf,YAAY;CACZ,aAAa;CACb,UAAU;CACV,OAAO;CACP,KAAK;CACL,UAAU;CACV,aAAa;CACb,WAAW;CACX,YAAY;CACZ,UAAU;CACV,QAAQ;CACR,eAAe;CACf,OAAO;AACT;;AAGA,MAAM,YAAY;;AAGlB,SAAS,cAAc,OAA+D;CACpF,OAAO,iBAAiB,SAAU,MAA6B,SAAS,sBAAsB,OAAQ,MAAgC,YAAY;AACpJ;;;;;;;;AASA,SAAgB,YAAY,OAAyC;CACnE,IAAI,QAAiB;CACrB,KAAK,IAAI,QAAQ,GAAG,QAAQ,aAAa,iBAAiB,OAAO,SAAS;EACxE,IAAI,cAAc,KAAK,GAAG;GACxB,MAAM,OAAO,MAAM,UAAU;GAC7B,MAAM,OAAO,MAAM,SAAS;GAC5B,OAAO;IAAE;IAAM,QAAQ,qBAAqB;IAAO;IAAM,cAAc,MAAM;IAAS,SAAS,MAAM;IAAS,OAAO;GAAM;EAC7H;EACA,QAAQ,MAAM;CAChB;AAEF"}
|
package/build/index.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Databases on one computer through `node:sqlite`: the connection with its pragmas, the migrations by schema version and
|
|
3
|
+
* the write transaction. The package also has a typed append-only event log and the faults of SQLite. The entry
|
|
4
|
+
* `@mark1russell7/sqlite/warning` has the filter of the experimental warning.
|
|
5
|
+
*
|
|
6
|
+
* @packageDocumentation
|
|
7
|
+
*/
|
|
8
|
+
import { OpenOptions, Synchronous, openDatabase } from "./database.js";
|
|
9
|
+
import { MigrateOptions, Migrated, Migration, SCHEMA_VERSIONS_TABLE, migrate, schemaVersion, userVersion } from "./migrate.js";
|
|
10
|
+
import { TransactionDatabase, TransactionOptions, transaction } from "./transaction.js";
|
|
11
|
+
import { Accept, Appended, BrokenLogError, EVENT_LOG_SCHEMA_VERSION, Equal, EventLog, EventLogFile, EventLogFileOptions, EventLogOptions, LogEntry, LogRecord, ReadOptions, eventLog, openEventLog } from "./event-log.js";
|
|
12
|
+
import { SQLITE_FAULT_ACTIONS, SqliteFault, SqliteFaultAction, SqliteFaultKind, sqliteFault } from "./faults.js";
|
|
13
|
+
export { type Accept, type Appended, BrokenLogError, EVENT_LOG_SCHEMA_VERSION, type Equal, type EventLog, type EventLogFile, type EventLogFileOptions, type EventLogOptions, type LogEntry, type LogRecord, type MigrateOptions, type Migrated, type Migration, type OpenOptions, type ReadOptions, SCHEMA_VERSIONS_TABLE, SQLITE_FAULT_ACTIONS, type SqliteFault, type SqliteFaultAction, type SqliteFaultKind, type Synchronous, type TransactionDatabase, type TransactionOptions, eventLog, migrate, openDatabase, openEventLog, schemaVersion, sqliteFault, transaction, userVersion };
|
package/build/index.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { openDatabase } from "./database.js";
|
|
2
|
+
import { SCHEMA_VERSIONS_TABLE, migrate, schemaVersion, userVersion } from "./migrate.js";
|
|
3
|
+
import { transaction } from "./transaction.js";
|
|
4
|
+
import { BrokenLogError, EVENT_LOG_SCHEMA_VERSION, eventLog, openEventLog } from "./event-log.js";
|
|
5
|
+
import { SQLITE_FAULT_ACTIONS, sqliteFault } from "./faults.js";
|
|
6
|
+
export { BrokenLogError, EVENT_LOG_SCHEMA_VERSION, SCHEMA_VERSIONS_TABLE, SQLITE_FAULT_ACTIONS, eventLog, migrate, openDatabase, openEventLog, schemaVersion, sqliteFault, transaction, userVersion };
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The migrations of a schema. The version of a file is the count of steps that it has. The file keeps the version in the
|
|
3
|
+
* pragma `user_version`, or in the table `schema_versions` for each named part of the file.
|
|
4
|
+
*
|
|
5
|
+
* @packageDocumentation
|
|
6
|
+
*/
|
|
7
|
+
import { DatabaseSync } from "node:sqlite";
|
|
8
|
+
/**
|
|
9
|
+
* One step of a schema: SQL statements, or a function that changes the database. A step operates in the transaction of
|
|
10
|
+
* its migration, so it does not start a transaction.
|
|
11
|
+
*/
|
|
12
|
+
export type Migration = string | ((db: DatabaseSync) => void);
|
|
13
|
+
/** The table that keeps the version of each named part of a file. `migrate` makes it at the first step of a part. */
|
|
14
|
+
export declare const SCHEMA_VERSIONS_TABLE = "schema_versions";
|
|
15
|
+
/** The options of `migrate`. */
|
|
16
|
+
export interface MigrateOptions {
|
|
17
|
+
/**
|
|
18
|
+
* This value is the name of the database in the error for a newer file, for example `log`. The default is `database`,
|
|
19
|
+
* or `part <part>` for a part.
|
|
20
|
+
*/
|
|
21
|
+
readonly name?: string | undefined;
|
|
22
|
+
/**
|
|
23
|
+
* This value is the name of a part of the file, for example `event-log:events`. Each part has its own steps and its
|
|
24
|
+
* own version in the table `schema_versions`. Thus a library and the app keep their tables in one file. Without a part,
|
|
25
|
+
* the version is the `user_version` of the file.
|
|
26
|
+
*/
|
|
27
|
+
readonly part?: string | undefined;
|
|
28
|
+
}
|
|
29
|
+
/** The versions of a file, or of a part of a file, before and after `migrate`. */
|
|
30
|
+
export interface Migrated {
|
|
31
|
+
readonly from: number;
|
|
32
|
+
readonly to: number;
|
|
33
|
+
}
|
|
34
|
+
/** This function gives the pragma `user_version` of a database. */
|
|
35
|
+
export declare function userVersion(db: DatabaseSync): number;
|
|
36
|
+
/**
|
|
37
|
+
* This function gives the schema version of a database. Without a part, it is the `user_version` of the file. With a
|
|
38
|
+
* part, it is the version of that part in the table `schema_versions`, and 0 when the file does not have the part.
|
|
39
|
+
*
|
|
40
|
+
* @param db - The connection.
|
|
41
|
+
* @param part - The name of a part of the file.
|
|
42
|
+
*/
|
|
43
|
+
export declare function schemaVersion(db: DatabaseSync, part?: string): number;
|
|
44
|
+
/**
|
|
45
|
+
* This function does the steps that a database does not have, in order. The version of the file, or of its part, is the
|
|
46
|
+
* count of steps that it has.
|
|
47
|
+
*
|
|
48
|
+
* Each step is one `BEGIN IMMEDIATE` transaction with its new version. A step that fails rolls back alone, and the steps
|
|
49
|
+
* before it stay. In the transaction, the function reads the version again, so two processes can migrate one file at
|
|
50
|
+
* the same time. A file with more steps than `migrations` is from a newer version of the app, and the function throws.
|
|
51
|
+
*
|
|
52
|
+
* @param db - The connection.
|
|
53
|
+
* @param migrations - The steps of the schema. Do not change a step that a file can have: add a new step.
|
|
54
|
+
* @param options - The part of the file, and the name of the database in the errors.
|
|
55
|
+
*/
|
|
56
|
+
export declare function migrate(db: DatabaseSync, migrations: readonly Migration[], options?: MigrateOptions): Migrated;
|
|
57
|
+
//# sourceMappingURL=migrate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migrate.d.ts","names":[],"sources":["../src/migrate.ts"],"mappings":";;;;;;;;;;;YAaY,uBAAuB,IAAI;;qBAG1B;;iBAGI;;;;;WAKN;;;;;;WAMA;;;iBAIM;WACN;WACA;;;wBAIK,YAAY,IAAI;;;;;;;;wBAiBhB,cAAc,IAAI,cAAc;;;;;;;;;;;;;wBAkChC,QAAQ,IAAI,cAAc,qBAAqB,aAAa,UAAS,iBAAsB"}
|
package/build/migrate.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { transaction } from "./transaction.js";
|
|
2
|
+
// src/migrate.ts
|
|
3
|
+
/** The table that keeps the version of each named part of a file. `migrate` makes it at the first step of a part. */
|
|
4
|
+
const SCHEMA_VERSIONS_TABLE = "schema_versions";
|
|
5
|
+
/** This function gives the pragma `user_version` of a database. */
|
|
6
|
+
function userVersion(db) {
|
|
7
|
+
return db.prepare("PRAGMA user_version").get().user_version;
|
|
8
|
+
}
|
|
9
|
+
/** This function throws when the name of a part is empty. */
|
|
10
|
+
function checkPart(part) {
|
|
11
|
+
if (part !== void 0 && part.length === 0) throw new RangeError("The name of a part must not be empty.");
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* This function gives the schema version of a database. Without a part, it is the `user_version` of the file. With a
|
|
15
|
+
* part, it is the version of that part in the table `schema_versions`, and 0 when the file does not have the part.
|
|
16
|
+
*
|
|
17
|
+
* @param db - The connection.
|
|
18
|
+
* @param part - The name of a part of the file.
|
|
19
|
+
*/
|
|
20
|
+
function schemaVersion(db, part) {
|
|
21
|
+
checkPart(part);
|
|
22
|
+
if (part === void 0) return userVersion(db);
|
|
23
|
+
if (db.prepare("SELECT 1 AS found FROM sqlite_master WHERE type = 'table' AND name = ?").get("schema_versions") === void 0) return 0;
|
|
24
|
+
return db.prepare(`SELECT version FROM schema_versions WHERE part = ?`).get(part)?.version ?? 0;
|
|
25
|
+
}
|
|
26
|
+
/** This function writes the schema version of a database, or of a part of it. It operates in a transaction. */
|
|
27
|
+
function setSchemaVersion(db, part, version) {
|
|
28
|
+
if (part === void 0) {
|
|
29
|
+
db.exec(`PRAGMA user_version = ${String(version)}`);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
db.exec(`CREATE TABLE IF NOT EXISTS ${SCHEMA_VERSIONS_TABLE} (part TEXT PRIMARY KEY, version INTEGER NOT NULL) STRICT`);
|
|
33
|
+
db.prepare(`INSERT INTO ${SCHEMA_VERSIONS_TABLE} (part, version) VALUES (?, ?) ON CONFLICT (part) DO UPDATE SET version = excluded.version`).run(part, version);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* This function does the steps that a database does not have, in order. The version of the file, or of its part, is the
|
|
37
|
+
* count of steps that it has.
|
|
38
|
+
*
|
|
39
|
+
* Each step is one `BEGIN IMMEDIATE` transaction with its new version. A step that fails rolls back alone, and the steps
|
|
40
|
+
* before it stay. In the transaction, the function reads the version again, so two processes can migrate one file at
|
|
41
|
+
* the same time. A file with more steps than `migrations` is from a newer version of the app, and the function throws.
|
|
42
|
+
*
|
|
43
|
+
* @param db - The connection.
|
|
44
|
+
* @param migrations - The steps of the schema. Do not change a step that a file can have: add a new step.
|
|
45
|
+
* @param options - The part of the file, and the name of the database in the errors.
|
|
46
|
+
*/
|
|
47
|
+
function migrate(db, migrations, options = {}) {
|
|
48
|
+
const part = options.part;
|
|
49
|
+
checkPart(part);
|
|
50
|
+
const name = options.name ?? (part === void 0 ? "database" : `part ${part}`);
|
|
51
|
+
const known = migrations.length;
|
|
52
|
+
const refuseNewer = (version) => {
|
|
53
|
+
if (version > known) throw new Error(`The ${name} has the schema version ${String(version)}, and this code knows only ${String(known)}. Use a newer version of the app.`);
|
|
54
|
+
};
|
|
55
|
+
const from = schemaVersion(db, part);
|
|
56
|
+
refuseNewer(from);
|
|
57
|
+
let version = from;
|
|
58
|
+
while (version < known) version = transaction(db, () => {
|
|
59
|
+
const current = schemaVersion(db, part);
|
|
60
|
+
refuseNewer(current);
|
|
61
|
+
if (current === known) return current;
|
|
62
|
+
const step = migrations[current];
|
|
63
|
+
if (typeof step === "string") db.exec(step);
|
|
64
|
+
else step(db);
|
|
65
|
+
setSchemaVersion(db, part, current + 1);
|
|
66
|
+
return current + 1;
|
|
67
|
+
});
|
|
68
|
+
return {
|
|
69
|
+
from,
|
|
70
|
+
to: version
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
export { SCHEMA_VERSIONS_TABLE, migrate, schemaVersion, userVersion };
|
|
74
|
+
|
|
75
|
+
//# sourceMappingURL=migrate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migrate.js","names":[],"sources":["../src/migrate.ts"],"sourcesContent":["/**\n * The migrations of a schema. The version of a file is the count of steps that it has. The file keeps the version in the\n * pragma `user_version`, or in the table `schema_versions` for each named part of the file.\n *\n * @packageDocumentation\n */\nimport type { DatabaseSync } from \"node:sqlite\";\nimport { transaction } from \"./transaction.ts\";\n\n/**\n * One step of a schema: SQL statements, or a function that changes the database. A step operates in the transaction of\n * its migration, so it does not start a transaction.\n */\nexport type Migration = string | ((db: DatabaseSync) => void);\n\n/** The table that keeps the version of each named part of a file. `migrate` makes it at the first step of a part. */\nexport const SCHEMA_VERSIONS_TABLE = \"schema_versions\";\n\n/** The options of `migrate`. */\nexport interface MigrateOptions {\n /**\n * This value is the name of the database in the error for a newer file, for example `log`. The default is `database`,\n * or `part <part>` for a part.\n */\n readonly name?: string | undefined;\n /**\n * This value is the name of a part of the file, for example `event-log:events`. Each part has its own steps and its\n * own version in the table `schema_versions`. Thus a library and the app keep their tables in one file. Without a part,\n * the version is the `user_version` of the file.\n */\n readonly part?: string | undefined;\n}\n\n/** The versions of a file, or of a part of a file, before and after `migrate`. */\nexport interface Migrated {\n readonly from: number;\n readonly to: number;\n}\n\n/** This function gives the pragma `user_version` of a database. */\nexport function userVersion(db: DatabaseSync): number {\n const row = db.prepare(\"PRAGMA user_version\").get() as { user_version: number };\n return row.user_version;\n}\n\n/** This function throws when the name of a part is empty. */\nfunction checkPart(part: string | undefined): void {\n if (part !== undefined && part.length === 0) throw new RangeError(\"The name of a part must not be empty.\");\n}\n\n/**\n * This function gives the schema version of a database. Without a part, it is the `user_version` of the file. With a\n * part, it is the version of that part in the table `schema_versions`, and 0 when the file does not have the part.\n *\n * @param db - The connection.\n * @param part - The name of a part of the file.\n */\nexport function schemaVersion(db: DatabaseSync, part?: string): number {\n checkPart(part);\n if (part === undefined) return userVersion(db);\n const table = db.prepare(\"SELECT 1 AS found FROM sqlite_master WHERE type = 'table' AND name = ?\").get(SCHEMA_VERSIONS_TABLE);\n if (table === undefined) return 0;\n const row = db.prepare(`SELECT version FROM ${SCHEMA_VERSIONS_TABLE} WHERE part = ?`).get(part) as { version: number } | undefined;\n return row?.version ?? 0;\n}\n\n/** This function writes the schema version of a database, or of a part of it. It operates in a transaction. */\nfunction setSchemaVersion(db: DatabaseSync, part: string | undefined, version: number): void {\n if (part === undefined) {\n db.exec(`PRAGMA user_version = ${String(version)}`);\n return;\n }\n db.exec(`CREATE TABLE IF NOT EXISTS ${SCHEMA_VERSIONS_TABLE} (part TEXT PRIMARY KEY, version INTEGER NOT NULL) STRICT`);\n db.prepare(`INSERT INTO ${SCHEMA_VERSIONS_TABLE} (part, version) VALUES (?, ?) ON CONFLICT (part) DO UPDATE SET version = excluded.version`).run(\n part,\n version,\n );\n}\n\n/**\n * This function does the steps that a database does not have, in order. The version of the file, or of its part, is the\n * count of steps that it has.\n *\n * Each step is one `BEGIN IMMEDIATE` transaction with its new version. A step that fails rolls back alone, and the steps\n * before it stay. In the transaction, the function reads the version again, so two processes can migrate one file at\n * the same time. A file with more steps than `migrations` is from a newer version of the app, and the function throws.\n *\n * @param db - The connection.\n * @param migrations - The steps of the schema. Do not change a step that a file can have: add a new step.\n * @param options - The part of the file, and the name of the database in the errors.\n */\nexport function migrate(db: DatabaseSync, migrations: readonly Migration[], options: MigrateOptions = {}): Migrated {\n const part = options.part;\n checkPart(part);\n const name = options.name ?? (part === undefined ? \"database\" : `part ${part}`);\n const known = migrations.length;\n const refuseNewer = (version: number): void => {\n if (version > known) {\n throw new Error(`The ${name} has the schema version ${String(version)}, and this code knows only ${String(known)}. Use a newer version of the app.`);\n }\n };\n const from = schemaVersion(db, part);\n refuseNewer(from);\n let version = from;\n while (version < known) {\n version = transaction(db, () => {\n const current = schemaVersion(db, part);\n refuseNewer(current);\n if (current === known) return current;\n const step = migrations[current] as Migration;\n if (typeof step === \"string\") db.exec(step);\n else step(db);\n setSchemaVersion(db, part, current + 1);\n return current + 1;\n });\n }\n return { from, to: version };\n}\n"],"mappings":";;;AAgBA,MAAa,wBAAwB;;AAwBrC,SAAgB,YAAY,IAA0B;CAEpD,OADY,GAAG,QAAQ,qBAAqB,CAAC,CAAC,IACrC,CAAC,CAAC;AACb;;AAGA,SAAS,UAAU,MAAgC;CACjD,IAAI,SAAS,KAAA,KAAa,KAAK,WAAW,GAAG,MAAM,IAAI,WAAW,uCAAuC;AAC3G;;;;;;;;AASA,SAAgB,cAAc,IAAkB,MAAuB;CACrE,UAAU,IAAI;CACd,IAAI,SAAS,KAAA,GAAW,OAAO,YAAY,EAAE;CAE7C,IADc,GAAG,QAAQ,wEAAwE,CAAC,CAAC,IAAA,iBAC3F,MAAM,KAAA,GAAW,OAAO;CAEhC,OADY,GAAG,QAAQ,oDAA6D,CAAC,CAAC,IAAI,IACjF,CAAC,EAAE,WAAW;AACzB;;AAGA,SAAS,iBAAiB,IAAkB,MAA0B,SAAuB;CAC3F,IAAI,SAAS,KAAA,GAAW;EACtB,GAAG,KAAK,yBAAyB,OAAO,OAAO,GAAG;EAClD;CACF;CACA,GAAG,KAAK,8BAA8B,sBAAsB,0DAA0D;CACtH,GAAG,QAAQ,eAAe,sBAAsB,2FAA2F,CAAC,CAAC,IAC3I,MACA,OACF;AACF;;;;;;;;;;;;;AAcA,SAAgB,QAAQ,IAAkB,YAAkC,UAA0B,CAAC,GAAa;CAClH,MAAM,OAAO,QAAQ;CACrB,UAAU,IAAI;CACd,MAAM,OAAO,QAAQ,SAAS,SAAS,KAAA,IAAY,aAAa,QAAQ;CACxE,MAAM,QAAQ,WAAW;CACzB,MAAM,eAAe,YAA0B;EAC7C,IAAI,UAAU,OACZ,MAAM,IAAI,MAAM,OAAO,KAAK,0BAA0B,OAAO,OAAO,EAAE,6BAA6B,OAAO,KAAK,EAAE,kCAAkC;CAEvJ;CACA,MAAM,OAAO,cAAc,IAAI,IAAI;CACnC,YAAY,IAAI;CAChB,IAAI,UAAU;CACd,OAAO,UAAU,OACf,UAAU,YAAY,UAAU;EAC9B,MAAM,UAAU,cAAc,IAAI,IAAI;EACtC,YAAY,OAAO;EACnB,IAAI,YAAY,OAAO,OAAO;EAC9B,MAAM,OAAO,WAAW;EACxB,IAAI,OAAO,SAAS,UAAU,GAAG,KAAK,IAAI;OACrC,KAAK,EAAE;EACZ,iBAAiB,IAAI,MAAM,UAAU,CAAC;EACtC,OAAO,UAAU;CACnB,CAAC;CAEH,OAAO;EAAE;EAAM,IAAI;CAAQ;AAC7B"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The write transaction: `BEGIN IMMEDIATE`, the work, and `COMMIT`, or a `ROLLBACK` after an error.
|
|
3
|
+
*
|
|
4
|
+
* @packageDocumentation
|
|
5
|
+
*/
|
|
6
|
+
import { DatabaseSync } from "node:sqlite";
|
|
7
|
+
/** The part of a connection that a transaction uses. A test can give a fake. */
|
|
8
|
+
export type TransactionDatabase = Pick<DatabaseSync, "exec" | "isTransaction">;
|
|
9
|
+
/** The options of `transaction`. */
|
|
10
|
+
export interface TransactionOptions {
|
|
11
|
+
/**
|
|
12
|
+
* This function gets the error of a rollback that did not end. Then the connection can stay in the transaction, and
|
|
13
|
+
* each later `BEGIN` throws. The work error stays the error that `transaction` throws.
|
|
14
|
+
*/
|
|
15
|
+
readonly onRollbackError?: ((error: unknown) => void) | undefined;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* This function does the work in one write transaction, and gives the result of the work.
|
|
19
|
+
*
|
|
20
|
+
* `BEGIN IMMEDIATE` takes the write lock at the start, so a different writer cannot cause a failure in the middle of
|
|
21
|
+
* the work. The busy timeout of the connection sets the wait for that lock. When the work or the commit throws, the
|
|
22
|
+
* function rolls back and throws the same error. The work is synchronous: a work that gives a promise is an error.
|
|
23
|
+
* A call in a transaction throws, because SQLite refuses a `BEGIN` there.
|
|
24
|
+
*
|
|
25
|
+
* @param db - The connection.
|
|
26
|
+
* @param work - The work. Its writes are part of the transaction.
|
|
27
|
+
* @param options - The function that gets a failed rollback.
|
|
28
|
+
*/
|
|
29
|
+
export declare function transaction<T>(db: TransactionDatabase, work: () => T, options?: TransactionOptions): T;
|
|
30
|
+
//# sourceMappingURL=transaction.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transaction.d.ts","names":[],"sources":["../src/transaction.ts"],"mappings":";;;;;;;YAQY,sBAAsB,KAAK;;iBAGtB;;;;;WAKN,oBAAoB;;;;;;;;;;;;;;wBAoBf,YAAY,GAAG,IAAI,qBAAqB,YAAY,GAAG,UAAS,qBAA0B"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// src/transaction.ts
|
|
2
|
+
/** This function tells if a value is a promise or a different thenable. */
|
|
3
|
+
function isThenable(value) {
|
|
4
|
+
return (typeof value === "object" || typeof value === "function") && value !== null && typeof value.then === "function";
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* This function does the work in one write transaction, and gives the result of the work.
|
|
8
|
+
*
|
|
9
|
+
* `BEGIN IMMEDIATE` takes the write lock at the start, so a different writer cannot cause a failure in the middle of
|
|
10
|
+
* the work. The busy timeout of the connection sets the wait for that lock. When the work or the commit throws, the
|
|
11
|
+
* function rolls back and throws the same error. The work is synchronous: a work that gives a promise is an error.
|
|
12
|
+
* A call in a transaction throws, because SQLite refuses a `BEGIN` there.
|
|
13
|
+
*
|
|
14
|
+
* @param db - The connection.
|
|
15
|
+
* @param work - The work. Its writes are part of the transaction.
|
|
16
|
+
* @param options - The function that gets a failed rollback.
|
|
17
|
+
*/
|
|
18
|
+
function transaction(db, work, options = {}) {
|
|
19
|
+
db.exec("BEGIN IMMEDIATE");
|
|
20
|
+
try {
|
|
21
|
+
const result = work();
|
|
22
|
+
if (isThenable(result)) throw new TypeError("The work of a transaction must be synchronous. The transaction rolled back.");
|
|
23
|
+
db.exec("COMMIT");
|
|
24
|
+
return result;
|
|
25
|
+
} catch (error) {
|
|
26
|
+
try {
|
|
27
|
+
if (db.isTransaction) db.exec("ROLLBACK");
|
|
28
|
+
} catch (rollback) {
|
|
29
|
+
options.onRollbackError?.(rollback);
|
|
30
|
+
}
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export { transaction };
|
|
35
|
+
|
|
36
|
+
//# sourceMappingURL=transaction.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transaction.js","names":[],"sources":["../src/transaction.ts"],"sourcesContent":["/**\n * The write transaction: `BEGIN IMMEDIATE`, the work, and `COMMIT`, or a `ROLLBACK` after an error.\n *\n * @packageDocumentation\n */\nimport type { DatabaseSync } from \"node:sqlite\";\n\n/** The part of a connection that a transaction uses. A test can give a fake. */\nexport type TransactionDatabase = Pick<DatabaseSync, \"exec\" | \"isTransaction\">;\n\n/** The options of `transaction`. */\nexport interface TransactionOptions {\n /**\n * This function gets the error of a rollback that did not end. Then the connection can stay in the transaction, and\n * each later `BEGIN` throws. The work error stays the error that `transaction` throws.\n */\n readonly onRollbackError?: ((error: unknown) => void) | undefined;\n}\n\n/** This function tells if a value is a promise or a different thenable. */\nfunction isThenable(value: unknown): boolean {\n return (typeof value === \"object\" || typeof value === \"function\") && value !== null && typeof (value as { then?: unknown }).then === \"function\";\n}\n\n/**\n * This function does the work in one write transaction, and gives the result of the work.\n *\n * `BEGIN IMMEDIATE` takes the write lock at the start, so a different writer cannot cause a failure in the middle of\n * the work. The busy timeout of the connection sets the wait for that lock. When the work or the commit throws, the\n * function rolls back and throws the same error. The work is synchronous: a work that gives a promise is an error.\n * A call in a transaction throws, because SQLite refuses a `BEGIN` there.\n *\n * @param db - The connection.\n * @param work - The work. Its writes are part of the transaction.\n * @param options - The function that gets a failed rollback.\n */\nexport function transaction<T>(db: TransactionDatabase, work: () => T, options: TransactionOptions = {}): T {\n db.exec(\"BEGIN IMMEDIATE\");\n try {\n const result = work();\n if (isThenable(result)) {\n throw new TypeError(\"The work of a transaction must be synchronous. The transaction rolled back.\");\n }\n db.exec(\"COMMIT\");\n return result;\n } catch (error) {\n try {\n if (db.isTransaction) db.exec(\"ROLLBACK\");\n } catch (rollback) {\n options.onRollbackError?.(rollback);\n }\n throw error;\n }\n}\n"],"mappings":";;AAoBA,SAAS,WAAW,OAAyB;CAC3C,QAAQ,OAAO,UAAU,YAAY,OAAO,UAAU,eAAe,UAAU,QAAQ,OAAQ,MAA6B,SAAS;AACvI;;;;;;;;;;;;;AAcA,SAAgB,YAAe,IAAyB,MAAe,UAA8B,CAAC,GAAM;CAC1G,GAAG,KAAK,iBAAiB;CACzB,IAAI;EACF,MAAM,SAAS,KAAK;EACpB,IAAI,WAAW,MAAM,GACnB,MAAM,IAAI,UAAU,6EAA6E;EAEnG,GAAG,KAAK,QAAQ;EAChB,OAAO;CACT,SAAS,OAAO;EACd,IAAI;GACF,IAAI,GAAG,eAAe,GAAG,KAAK,UAAU;EAC1C,SAAS,UAAU;GACjB,QAAQ,kBAAkB,QAAQ;EACpC;EACA,MAAM;CACR;AACF"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A filter of the experimental warning of `node:sqlite`. Node.js 22 writes the warning to the standard error at the
|
|
3
|
+
* first load of `node:sqlite`. This file does not import `node:sqlite`, so a command loads it first.
|
|
4
|
+
*
|
|
5
|
+
* ```ts
|
|
6
|
+
* import { quietSqliteWarning } from "@mark1russell7/sqlite/warning";
|
|
7
|
+
*
|
|
8
|
+
* quietSqliteWarning();
|
|
9
|
+
* const { openDatabase } = await import("@mark1russell7/sqlite");
|
|
10
|
+
* ```
|
|
11
|
+
*
|
|
12
|
+
* @packageDocumentation
|
|
13
|
+
*/
|
|
14
|
+
/** The start of the text of the experimental warning of `node:sqlite`. */
|
|
15
|
+
export declare const SQLITE_WARNING = "SQLite is an experimental feature";
|
|
16
|
+
/** The part of `process` that the filter changes. A test can give a fake. */
|
|
17
|
+
export interface WarningTarget {
|
|
18
|
+
emitWarning(warning: string | Error, ...rest: unknown[]): void;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* This function tells if the arguments of `process.emitWarning` give the experimental warning of `node:sqlite`. The
|
|
22
|
+
* type of the warning is the second argument, the field `type` of the options, or the name of an `Error`.
|
|
23
|
+
*/
|
|
24
|
+
export declare function isSqliteWarning(warning: string | Error, ...rest: unknown[]): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* This function removes only the experimental warning of `node:sqlite` from `emitWarning`. Each other warning stays.
|
|
27
|
+
* Use it before the first import of `node:sqlite`. It gives a function that puts the earlier `emitWarning` back.
|
|
28
|
+
*
|
|
29
|
+
* @param target - The object with `emitWarning`. The default is `process`.
|
|
30
|
+
*/
|
|
31
|
+
export declare function quietSqliteWarning(target?: WarningTarget): () => void;
|
|
32
|
+
//# sourceMappingURL=warning.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"warning.d.ts","names":[],"sources":["../src/warning.ts"],"mappings":";;;;;;;;;;;;;;qBAea;;iBAGI;EACf,YAAY,kBAAkB,UAAU;;;;;;wBAO1B,gBAAgB,kBAAkB,UAAU;;;;;;;wBAgB5C,mBAAmB,SAAQ"}
|
package/build/warning.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// src/warning.ts
|
|
2
|
+
/**
|
|
3
|
+
* A filter of the experimental warning of `node:sqlite`. Node.js 22 writes the warning to the standard error at the
|
|
4
|
+
* first load of `node:sqlite`. This file does not import `node:sqlite`, so a command loads it first.
|
|
5
|
+
*
|
|
6
|
+
* ```ts
|
|
7
|
+
* import { quietSqliteWarning } from "@mark1russell7/sqlite/warning";
|
|
8
|
+
*
|
|
9
|
+
* quietSqliteWarning();
|
|
10
|
+
* const { openDatabase } = await import("@mark1russell7/sqlite");
|
|
11
|
+
* ```
|
|
12
|
+
*
|
|
13
|
+
* @packageDocumentation
|
|
14
|
+
*/
|
|
15
|
+
/** The start of the text of the experimental warning of `node:sqlite`. */
|
|
16
|
+
const SQLITE_WARNING = "SQLite is an experimental feature";
|
|
17
|
+
/**
|
|
18
|
+
* This function tells if the arguments of `process.emitWarning` give the experimental warning of `node:sqlite`. The
|
|
19
|
+
* type of the warning is the second argument, the field `type` of the options, or the name of an `Error`.
|
|
20
|
+
*/
|
|
21
|
+
function isSqliteWarning(warning, ...rest) {
|
|
22
|
+
const first = rest[0];
|
|
23
|
+
let kind;
|
|
24
|
+
if (typeof first === "string") kind = first;
|
|
25
|
+
else if (typeof first === "object" && first !== null && "type" in first) kind = first.type;
|
|
26
|
+
else if (warning instanceof Error) kind = warning.name;
|
|
27
|
+
const text = typeof warning === "string" ? warning : warning.message;
|
|
28
|
+
return kind === "ExperimentalWarning" && text.startsWith("SQLite is an experimental feature");
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* This function removes only the experimental warning of `node:sqlite` from `emitWarning`. Each other warning stays.
|
|
32
|
+
* Use it before the first import of `node:sqlite`. It gives a function that puts the earlier `emitWarning` back.
|
|
33
|
+
*
|
|
34
|
+
* @param target - The object with `emitWarning`. The default is `process`.
|
|
35
|
+
*/
|
|
36
|
+
function quietSqliteWarning(target = process) {
|
|
37
|
+
const earlier = target.emitWarning;
|
|
38
|
+
const filtered = (warning, ...rest) => {
|
|
39
|
+
if (isSqliteWarning(warning, ...rest)) return;
|
|
40
|
+
earlier.call(target, warning, ...rest);
|
|
41
|
+
};
|
|
42
|
+
target.emitWarning = filtered;
|
|
43
|
+
return () => {
|
|
44
|
+
if (target.emitWarning === filtered) target.emitWarning = earlier;
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
export { SQLITE_WARNING, isSqliteWarning, quietSqliteWarning };
|
|
48
|
+
|
|
49
|
+
//# sourceMappingURL=warning.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"warning.js","names":[],"sources":["../src/warning.ts"],"sourcesContent":["/**\n * A filter of the experimental warning of `node:sqlite`. Node.js 22 writes the warning to the standard error at the\n * first load of `node:sqlite`. This file does not import `node:sqlite`, so a command loads it first.\n *\n * ```ts\n * import { quietSqliteWarning } from \"@mark1russell7/sqlite/warning\";\n *\n * quietSqliteWarning();\n * const { openDatabase } = await import(\"@mark1russell7/sqlite\");\n * ```\n *\n * @packageDocumentation\n */\n\n/** The start of the text of the experimental warning of `node:sqlite`. */\nexport const SQLITE_WARNING = \"SQLite is an experimental feature\";\n\n/** The part of `process` that the filter changes. A test can give a fake. */\nexport interface WarningTarget {\n emitWarning(warning: string | Error, ...rest: unknown[]): void;\n}\n\n/**\n * This function tells if the arguments of `process.emitWarning` give the experimental warning of `node:sqlite`. The\n * type of the warning is the second argument, the field `type` of the options, or the name of an `Error`.\n */\nexport function isSqliteWarning(warning: string | Error, ...rest: unknown[]): boolean {\n const first = rest[0];\n let kind: unknown;\n if (typeof first === \"string\") kind = first;\n else if (typeof first === \"object\" && first !== null && \"type\" in first) kind = first.type;\n else if (warning instanceof Error) kind = warning.name;\n const text = typeof warning === \"string\" ? warning : warning.message;\n return kind === \"ExperimentalWarning\" && text.startsWith(SQLITE_WARNING);\n}\n\n/**\n * This function removes only the experimental warning of `node:sqlite` from `emitWarning`. Each other warning stays.\n * Use it before the first import of `node:sqlite`. It gives a function that puts the earlier `emitWarning` back.\n *\n * @param target - The object with `emitWarning`. The default is `process`.\n */\nexport function quietSqliteWarning(target: WarningTarget = process): () => void {\n const earlier = target.emitWarning;\n const filtered = (warning: string | Error, ...rest: unknown[]): void => {\n if (isSqliteWarning(warning, ...rest)) return;\n earlier.call(target, warning, ...rest);\n };\n target.emitWarning = filtered;\n return () => {\n if (target.emitWarning === filtered) target.emitWarning = earlier;\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;AAeA,MAAa,iBAAiB;;;;;AAW9B,SAAgB,gBAAgB,SAAyB,GAAG,MAA0B;CACpF,MAAM,QAAQ,KAAK;CACnB,IAAI;CACJ,IAAI,OAAO,UAAU,UAAU,OAAO;MACjC,IAAI,OAAO,UAAU,YAAY,UAAU,QAAQ,UAAU,OAAO,OAAO,MAAM;MACjF,IAAI,mBAAmB,OAAO,OAAO,QAAQ;CAClD,MAAM,OAAO,OAAO,YAAY,WAAW,UAAU,QAAQ;CAC7D,OAAO,SAAS,yBAAyB,KAAK,WAAA,mCAAyB;AACzE;;;;;;;AAQA,SAAgB,mBAAmB,SAAwB,SAAqB;CAC9E,MAAM,UAAU,OAAO;CACvB,MAAM,YAAY,SAAyB,GAAG,SAA0B;EACtE,IAAI,gBAAgB,SAAS,GAAG,IAAI,GAAG;EACvC,QAAQ,KAAK,QAAQ,SAAS,GAAG,IAAI;CACvC;CACA,OAAO,cAAc;CACrB,aAAa;EACX,IAAI,OAAO,gBAAgB,UAAU,OAAO,cAAc;CAC5D;AACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,74 @@
|
|
|
1
1
|
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/package",
|
|
2
3
|
"name": "@mark1russell7/sqlite",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
4
|
+
"version": "0.2.0",
|
|
5
|
+
"description": "Databases on one computer through node:sqlite: the pragmas, migrations by schema version, BEGIN IMMEDIATE transactions, a typed append-only event log as a grow-only set, the faults of SQLite and a filter of the experimental warning.",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"sqlite",
|
|
8
|
+
"node:sqlite",
|
|
9
|
+
"wal",
|
|
10
|
+
"migrations",
|
|
11
|
+
"user_version",
|
|
12
|
+
"transaction",
|
|
13
|
+
"event-log",
|
|
14
|
+
"append-only",
|
|
15
|
+
"event-sourcing",
|
|
16
|
+
"typescript",
|
|
17
|
+
"grow-only-set",
|
|
18
|
+
"crdt",
|
|
19
|
+
"error-codes"
|
|
20
|
+
],
|
|
21
|
+
"homepage": "https://github.com/mark1russell7/sqlite/tree/main/packages/sqlite#readme",
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://github.com/mark1russell7/sqlite/issues"
|
|
24
|
+
},
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "git+https://github.com/mark1russell7/sqlite.git",
|
|
28
|
+
"directory": "packages/sqlite"
|
|
29
|
+
},
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"author": "Mark Russell (https://github.com/mark1russell7)",
|
|
32
|
+
"type": "module",
|
|
33
|
+
"sideEffects": false,
|
|
34
|
+
"main": "./build/index.js",
|
|
35
|
+
"exports": {
|
|
36
|
+
".": {
|
|
37
|
+
"types": "./build/index.d.ts",
|
|
38
|
+
"import": "./build/index.js",
|
|
39
|
+
"default": "./build/index.js"
|
|
40
|
+
},
|
|
41
|
+
"./warning": {
|
|
42
|
+
"types": "./build/warning.d.ts",
|
|
43
|
+
"import": "./build/warning.js",
|
|
44
|
+
"default": "./build/warning.js"
|
|
45
|
+
},
|
|
46
|
+
"./package.json": "./package.json"
|
|
47
|
+
},
|
|
48
|
+
"files": [
|
|
49
|
+
"build"
|
|
50
|
+
],
|
|
51
|
+
"publishConfig": {
|
|
52
|
+
"access": "public"
|
|
53
|
+
},
|
|
54
|
+
"engines": {
|
|
55
|
+
"node": ">=22.16"
|
|
56
|
+
},
|
|
57
|
+
"devDependencies": {
|
|
58
|
+
"@mark1russell7/cue": "^2.0.0",
|
|
59
|
+
"@types/node": "^26.6.5",
|
|
60
|
+
"@vitest/coverage-v8": "^5.0.3",
|
|
61
|
+
"esbuild": "^0.28.2",
|
|
62
|
+
"fast-check": "^4.10.2",
|
|
63
|
+
"tsdown": "^0.23.7",
|
|
64
|
+
"typescript": "^7.0.2",
|
|
65
|
+
"vitest": "^5.0.3"
|
|
66
|
+
},
|
|
67
|
+
"scripts": {
|
|
68
|
+
"build": "tsdown",
|
|
69
|
+
"test": "vitest run",
|
|
70
|
+
"test:watch": "vitest",
|
|
71
|
+
"typecheck": "tsc -p tsconfig.json && tsc -p tsconfig.test.json"
|
|
72
|
+
},
|
|
73
|
+
"types": "./build/index.d.ts"
|
|
6
74
|
}
|