dbgate-sqlite-dumper 0.1.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.
@@ -0,0 +1,140 @@
1
+ // src/utils/errors.ts
2
+ var SqliteDumperError = class extends Error {
3
+ code;
4
+ constructor(code, message, options) {
5
+ super(message, options);
6
+ this.name = "SqliteDumperError";
7
+ this.code = code;
8
+ }
9
+ };
10
+ var OperationCancelledError = class extends SqliteDumperError {
11
+ constructor(message = "The operation was cancelled") {
12
+ super("operation-cancelled", message);
13
+ this.name = "OperationCancelledError";
14
+ }
15
+ };
16
+ function throwIfAborted(signal) {
17
+ if (signal?.aborted) {
18
+ throw new OperationCancelledError();
19
+ }
20
+ }
21
+
22
+ // src/better-sqlite3.ts
23
+ function toDriverParameters(parameters) {
24
+ return (parameters ?? []).map(
25
+ (value) => value instanceof Uint8Array && !Buffer.isBuffer(value) ? Buffer.from(value.buffer, value.byteOffset, value.byteLength) : value
26
+ );
27
+ }
28
+ function prepare(database, sql) {
29
+ const statement = database.prepare(sql);
30
+ statement.safeIntegers(true);
31
+ return statement;
32
+ }
33
+ function isMultipleStatementsError(error) {
34
+ return error instanceof RangeError && /more than one statement/i.test(error.message);
35
+ }
36
+ function fromBetterSqlite3(database) {
37
+ const connection = {
38
+ async query(query, signal) {
39
+ throwIfAborted(signal);
40
+ const statement = prepare(database, query.sql);
41
+ const parameters = toDriverParameters(query.parameters);
42
+ if (statement.reader) {
43
+ const rows = statement.all(...parameters);
44
+ return { rows, columns: statement.columns().map((column) => column.name) };
45
+ }
46
+ statement.run(...parameters);
47
+ return { rows: [] };
48
+ },
49
+ stream(query, options) {
50
+ const signal = options?.signal;
51
+ return {
52
+ async *[Symbol.asyncIterator]() {
53
+ throwIfAborted(signal);
54
+ const statement = prepare(database, query.sql);
55
+ if (!statement.reader) {
56
+ statement.run(...toDriverParameters(query.parameters));
57
+ return;
58
+ }
59
+ const iterator = statement.iterate(
60
+ ...toDriverParameters(query.parameters)
61
+ );
62
+ try {
63
+ for (const row of iterator) {
64
+ if (signal?.aborted) {
65
+ throw new OperationCancelledError();
66
+ }
67
+ yield row;
68
+ }
69
+ } finally {
70
+ iterator.return?.();
71
+ }
72
+ }
73
+ };
74
+ },
75
+ async execute(sql, signal) {
76
+ throwIfAborted(signal);
77
+ let statement;
78
+ try {
79
+ statement = prepare(database, sql);
80
+ } catch (error) {
81
+ if (!isMultipleStatementsError(error)) {
82
+ throw error;
83
+ }
84
+ const before = totalChanges(database);
85
+ database.exec(sql);
86
+ return { changes: totalChanges(database) - before };
87
+ }
88
+ if (statement.reader) {
89
+ for (const _row of statement.iterate()) {
90
+ }
91
+ return { changes: 0 };
92
+ }
93
+ const info = statement.run();
94
+ return { changes: Number(info.changes) };
95
+ },
96
+ describeError(error) {
97
+ if (!(error instanceof Error)) {
98
+ return void 0;
99
+ }
100
+ const code = error.code;
101
+ return {
102
+ ...typeof code === "string" ? { code } : {},
103
+ message: error.message
104
+ };
105
+ },
106
+ isInTransaction() {
107
+ return database.inTransaction;
108
+ },
109
+ async setDefensive(enabled) {
110
+ database.unsafeMode(!enabled);
111
+ },
112
+ async cancel() {
113
+ }
114
+ };
115
+ return connection;
116
+ }
117
+ function totalChanges(database) {
118
+ return Number(database.prepare("SELECT total_changes()").pluck().get());
119
+ }
120
+ async function connectBetterSqlite3(filename, options) {
121
+ const module = await import("better-sqlite3");
122
+ const DatabaseConstructor = module.default ?? module;
123
+ const database = new DatabaseConstructor(filename, options);
124
+ let closed = false;
125
+ return {
126
+ connection: fromBetterSqlite3(database),
127
+ database,
128
+ close: async () => {
129
+ if (!closed) {
130
+ closed = true;
131
+ database.close();
132
+ }
133
+ }
134
+ };
135
+ }
136
+ export {
137
+ connectBetterSqlite3,
138
+ fromBetterSqlite3
139
+ };
140
+ //# sourceMappingURL=better-sqlite3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils/errors.ts","../src/better-sqlite3.ts"],"sourcesContent":["/** Base class for every error this package throws intentionally. */\r\nexport class SqliteDumperError extends Error {\r\n readonly code: string;\r\n\r\n constructor(code: string, message: string, options?: { cause?: unknown }) {\r\n super(message, options);\r\n this.name = 'SqliteDumperError';\r\n this.code = code;\r\n }\r\n}\r\n\r\n/** Thrown when an operation stops because its `AbortSignal` was triggered. */\r\nexport class OperationCancelledError extends SqliteDumperError {\r\n constructor(message = 'The operation was cancelled') {\r\n super('operation-cancelled', message);\r\n this.name = 'OperationCancelledError';\r\n }\r\n}\r\n\r\nexport function throwIfAborted(signal: AbortSignal | undefined): void {\r\n if (signal?.aborted) {\r\n throw new OperationCancelledError();\r\n }\r\n}\r\n\r\n/**\r\n * True for both cancellation shapes this package can observe: its own\r\n * {@link OperationCancelledError} and the `DOMException` an `AbortSignal`\r\n * (or a Node stream aborted through one) raises.\r\n */\r\nexport function isAbortError(error: unknown): boolean {\r\n return (\r\n (error instanceof DOMException && error.name === 'AbortError') ||\r\n (error instanceof Error && error.name === 'OperationCancelledError')\r\n );\r\n}\r\n","/**\r\n * Optional adapter for the `better-sqlite3` package.\r\n *\r\n * Wraps a caller-owned `better-sqlite3` `Database` as a\r\n * {@link SqliteConnection}. This module is never imported by the core\r\n * package; `better-sqlite3` is an optional peer dependency and is only\r\n * resolved when a consumer imports `dbgate-sqlite-dumper/better-sqlite3`\r\n * themselves.\r\n *\r\n * `better-sqlite3` is the driver DbGate's own SQLite plugin uses, which is\r\n * why it is the bundled adapter. Any other driver works through the same\r\n * {@link SqliteConnection} interface.\r\n */\r\nimport type BetterSqlite3 from 'better-sqlite3';\r\nimport type {\r\n SqliteConnection,\r\n SqliteErrorInfo,\r\n SqliteExecResult,\r\n SqliteParameterValue,\r\n SqliteQuery,\r\n SqliteQueryResult,\r\n SqliteRow,\r\n SqliteStreamOptions,\r\n} from './connection/types.js';\r\nimport { OperationCancelledError, throwIfAborted } from './utils/errors.js';\r\n\r\ntype Database = BetterSqlite3.Database;\r\ntype Statement = BetterSqlite3.Statement<unknown[], unknown>;\r\n\r\nfunction toDriverParameters(parameters: readonly SqliteParameterValue[] | undefined): unknown[] {\r\n return (parameters ?? []).map(value =>\r\n value instanceof Uint8Array && !Buffer.isBuffer(value)\r\n ? Buffer.from(value.buffer, value.byteOffset, value.byteLength)\r\n : value,\r\n );\r\n}\r\n\r\n/**\r\n * Prepares a statement with 64-bit integers returned as `bigint`, so no\r\n * catalog value is ever rounded. (Row data does not depend on this: the core\r\n * reads it through SQL that renders numbers as text — see `valueQuery.ts`.)\r\n */\r\nfunction prepare(database: Database, sql: string): Statement {\r\n const statement = database.prepare(sql) as Statement;\r\n statement.safeIntegers(true);\r\n return statement;\r\n}\r\n\r\nfunction isMultipleStatementsError(error: unknown): boolean {\r\n return error instanceof RangeError && /more than one statement/i.test(error.message);\r\n}\r\n\r\n/**\r\n * Wraps an open `better-sqlite3` database. The database is **borrowed**:\r\n * this package never closes it.\r\n *\r\n * `better-sqlite3` is synchronous, which shapes three behaviours:\r\n *\r\n * - `stream()` steps the statement one row at a time with `iterate()`,\r\n * yielding between rows, so a table of any size streams in constant\r\n * memory. While a stream is open the database cannot run another\r\n * statement — this package never asks it to.\r\n * - `cancel()` cannot interrupt a statement mid-step. Cancellation takes\r\n * effect between rows and between statements, which for a dump or a\r\n * restore (each statement is short) is prompt.\r\n * - `setDefensive()` is implemented with `unsafeMode()`, which is how\r\n * `better-sqlite3` exposes `SQLITE_DBCONFIG_DEFENSIVE`. (Unsafe mode also\r\n * relaxes the driver's own guard against writing while iterating; this\r\n * package never does that.)\r\n */\r\nexport function fromBetterSqlite3(database: Database): SqliteConnection {\r\n const connection: SqliteConnection = {\r\n async query<Row extends SqliteRow = SqliteRow>(\r\n query: SqliteQuery,\r\n signal?: AbortSignal,\r\n ): Promise<SqliteQueryResult<Row>> {\r\n throwIfAborted(signal);\r\n const statement = prepare(database, query.sql);\r\n const parameters = toDriverParameters(query.parameters);\r\n if (statement.reader) {\r\n const rows = statement.all(...parameters) as Row[];\r\n return { rows, columns: statement.columns().map(column => column.name) };\r\n }\r\n statement.run(...parameters);\r\n return { rows: [] };\r\n },\r\n\r\n stream<Row extends SqliteRow = SqliteRow>(\r\n query: SqliteQuery,\r\n options?: SqliteStreamOptions,\r\n ): AsyncIterable<Row> {\r\n const signal = options?.signal;\r\n return {\r\n async *[Symbol.asyncIterator](): AsyncGenerator<Row> {\r\n throwIfAborted(signal);\r\n const statement = prepare(database, query.sql);\r\n if (!statement.reader) {\r\n statement.run(...toDriverParameters(query.parameters));\r\n return;\r\n }\r\n const iterator = statement.iterate(\r\n ...toDriverParameters(query.parameters),\r\n ) as IterableIterator<Row>;\r\n try {\r\n for (const row of iterator) {\r\n if (signal?.aborted) {\r\n throw new OperationCancelledError();\r\n }\r\n yield row;\r\n }\r\n } finally {\r\n // Finalizes the statement even when the consumer stops early,\r\n // which is what frees the database for the next statement.\r\n iterator.return?.();\r\n }\r\n },\r\n };\r\n },\r\n\r\n async execute(sql: string, signal?: AbortSignal): Promise<SqliteExecResult> {\r\n throwIfAborted(signal);\r\n let statement: Statement;\r\n try {\r\n statement = prepare(database, sql);\r\n } catch (error) {\r\n if (!isMultipleStatementsError(error)) {\r\n throw error;\r\n }\r\n // Text the shell would run as several statements at once. Rare in a\r\n // restore (the parser splits statements), but valid input.\r\n const before = totalChanges(database);\r\n database.exec(sql);\r\n return { changes: totalChanges(database) - before };\r\n }\r\n if (statement.reader) {\r\n // A script's own SELECT or PRAGMA query: step it to completion, as\r\n // the shell would, without keeping the rows.\r\n for (const _row of statement.iterate()) {\r\n // discard\r\n }\r\n return { changes: 0 };\r\n }\r\n const info = statement.run();\r\n return { changes: Number(info.changes) };\r\n },\r\n\r\n describeError(error: unknown): SqliteErrorInfo | undefined {\r\n if (!(error instanceof Error)) {\r\n return undefined;\r\n }\r\n const code = (error as { code?: unknown }).code;\r\n return {\r\n ...(typeof code === 'string' ? { code } : {}),\r\n message: error.message,\r\n };\r\n },\r\n\r\n isInTransaction(): boolean {\r\n return database.inTransaction;\r\n },\r\n\r\n async setDefensive(enabled: boolean): Promise<void> {\r\n database.unsafeMode(!enabled);\r\n },\r\n\r\n async cancel(): Promise<void> {\r\n // better-sqlite3 runs each statement synchronously to completion;\r\n // there is nothing in flight to interrupt.\r\n },\r\n };\r\n return connection;\r\n}\r\n\r\nfunction totalChanges(database: Database): number {\r\n return Number(database.prepare('SELECT total_changes()').pluck().get());\r\n}\r\n\r\nexport interface ConnectedBetterSqlite3 {\r\n readonly connection: SqliteConnection;\r\n /** The underlying `better-sqlite3` database, for anything the adapter does not cover. */\r\n readonly database: Database;\r\n /** Closes the database. Idempotent. */\r\n close(): Promise<void>;\r\n}\r\n\r\n/**\r\n * Opens a database file with `better-sqlite3` and wraps it. Unlike\r\n * {@link fromBetterSqlite3}, the returned `close()` owns the handle.\r\n *\r\n * `better-sqlite3` is loaded lazily, so importing this module does not\r\n * require the optional peer dependency to be installed until this function\r\n * is actually called.\r\n */\r\nexport async function connectBetterSqlite3(\r\n filename: string,\r\n options?: BetterSqlite3.Options,\r\n): Promise<ConnectedBetterSqlite3> {\r\n const module = await import('better-sqlite3');\r\n const DatabaseConstructor = (module.default ?? module) as unknown as new (\r\n filename: string,\r\n options?: BetterSqlite3.Options,\r\n ) => Database;\r\n const database = new DatabaseConstructor(filename, options);\r\n let closed = false;\r\n return {\r\n connection: fromBetterSqlite3(database),\r\n database,\r\n close: async () => {\r\n if (!closed) {\r\n closed = true;\r\n database.close();\r\n }\r\n },\r\n };\r\n}\r\n"],"mappings":"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","names":[]}