@fadhilp/stateql 0.1.2 → 0.2.2
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/README.md +47 -20
- package/dist/src/adapters.d.ts +15 -1
- package/dist/src/adapters.js +631 -181
- package/dist/src/cli.js +16 -3
- package/dist/src/connection.d.ts +10 -0
- package/dist/src/connection.js +51 -0
- package/dist/src/errors.js +6 -2
- package/dist/src/filter.d.ts +14 -0
- package/dist/src/filter.js +256 -0
- package/dist/src/index.d.ts +1 -1
- package/dist/src/response-data.d.ts +8 -0
- package/dist/src/response-data.js +66 -0
- package/dist/src/sql.js +8 -2
- package/dist/src/sqlite-process.d.ts +1 -0
- package/dist/src/sqlite-process.js +240 -0
- package/dist/src/stateql.d.ts +15 -5
- package/dist/src/stateql.js +189 -397
- package/dist/src/store.d.ts +2 -0
- package/dist/src/store.js +37 -2
- package/dist/src/types.d.ts +56 -6
- package/package.json +5 -2
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { existsSync, statSync } from "node:fs";
|
|
2
|
+
import { DatabaseSync } from "node:sqlite";
|
|
3
|
+
import { hash, parseJson, toJsonSafe } from "./util.js";
|
|
4
|
+
let db;
|
|
5
|
+
let source;
|
|
6
|
+
let readOnly = true;
|
|
7
|
+
process.on("message", (request) => {
|
|
8
|
+
let response;
|
|
9
|
+
try {
|
|
10
|
+
initialize(request);
|
|
11
|
+
response = { id: request.id, result: execute(request) };
|
|
12
|
+
}
|
|
13
|
+
catch (error) {
|
|
14
|
+
response = {
|
|
15
|
+
id: request.id,
|
|
16
|
+
error: {
|
|
17
|
+
message: errorText(error),
|
|
18
|
+
...(error instanceof SQLiteBatchError
|
|
19
|
+
? { outcomeUnknown: error.outcomeUnknown }
|
|
20
|
+
: {}),
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
process.send?.(response);
|
|
25
|
+
});
|
|
26
|
+
process.on("disconnect", () => {
|
|
27
|
+
close();
|
|
28
|
+
process.exit(0);
|
|
29
|
+
});
|
|
30
|
+
process.on("exit", close);
|
|
31
|
+
function initialize(request) {
|
|
32
|
+
if (!db) {
|
|
33
|
+
source = request.source;
|
|
34
|
+
readOnly = request.readOnly;
|
|
35
|
+
db = new DatabaseSync(source, {
|
|
36
|
+
readOnly,
|
|
37
|
+
enableForeignKeyConstraints: true,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
db.exec(`PRAGMA busy_timeout = ${Math.max(1, request.busyTimeoutMs)}`);
|
|
41
|
+
}
|
|
42
|
+
function execute(request) {
|
|
43
|
+
const database = db;
|
|
44
|
+
switch (request.operation) {
|
|
45
|
+
case "read": {
|
|
46
|
+
const [sql, params] = request.args;
|
|
47
|
+
const statement = database.prepare(sql);
|
|
48
|
+
const rows = bindAll(statement, params);
|
|
49
|
+
return {
|
|
50
|
+
rows: toJsonSafe(rows),
|
|
51
|
+
columns: statement.columns().map((column) => ({
|
|
52
|
+
name: column.name,
|
|
53
|
+
type: column.type?.toLowerCase() ?? inferType(rows, column.name),
|
|
54
|
+
})),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
case "write": {
|
|
58
|
+
if (readOnly)
|
|
59
|
+
throw new Error("Connection is read-only.");
|
|
60
|
+
const [sql, params] = request.args;
|
|
61
|
+
try {
|
|
62
|
+
database.exec("BEGIN");
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
throw new SQLiteBatchError(errorText(error), false);
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
const result = bindRun(database.prepare(sql), params);
|
|
69
|
+
database.exec("COMMIT");
|
|
70
|
+
return { affectedRows: Number(result.changes) };
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
try {
|
|
74
|
+
database.exec("ROLLBACK");
|
|
75
|
+
throw new SQLiteBatchError(errorText(error), false);
|
|
76
|
+
}
|
|
77
|
+
catch (rollbackError) {
|
|
78
|
+
if (rollbackError instanceof SQLiteBatchError)
|
|
79
|
+
throw rollbackError;
|
|
80
|
+
throw new SQLiteBatchError(errorText(error), true);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
case "writeBatch": {
|
|
85
|
+
if (readOnly)
|
|
86
|
+
throw new Error("Connection is read-only.");
|
|
87
|
+
const [operations, isolation] = request.args;
|
|
88
|
+
if (isolation !== "serializable") {
|
|
89
|
+
throw new Error(`SQLite does not support isolation level "${isolation}".`);
|
|
90
|
+
}
|
|
91
|
+
const results = [];
|
|
92
|
+
try {
|
|
93
|
+
database.exec("BEGIN");
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
throw new SQLiteBatchError(errorText(error), false);
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
for (const operation of operations) {
|
|
100
|
+
const result = bindRun(database.prepare(operation.sql), parseJson(operation.parameters, []));
|
|
101
|
+
results.push({ affectedRows: Number(result.changes) });
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
try {
|
|
106
|
+
database.exec("ROLLBACK");
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
throw new SQLiteBatchError(errorText(error), true);
|
|
110
|
+
}
|
|
111
|
+
throw new SQLiteBatchError(errorText(error), false);
|
|
112
|
+
}
|
|
113
|
+
try {
|
|
114
|
+
database.exec("COMMIT");
|
|
115
|
+
return results;
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
try {
|
|
119
|
+
database.exec("ROLLBACK");
|
|
120
|
+
throw new SQLiteBatchError(errorText(error), false);
|
|
121
|
+
}
|
|
122
|
+
catch (rollbackError) {
|
|
123
|
+
if (rollbackError instanceof SQLiteBatchError)
|
|
124
|
+
throw rollbackError;
|
|
125
|
+
throw new SQLiteBatchError(errorText(error), true);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
case "signature": {
|
|
130
|
+
if (source === ":memory:")
|
|
131
|
+
return "memory";
|
|
132
|
+
const stats = statSync(source, { bigint: true });
|
|
133
|
+
const walPath = `${source}-wal`;
|
|
134
|
+
const wal = existsSync(walPath)
|
|
135
|
+
? statSync(walPath, { bigint: true })
|
|
136
|
+
: undefined;
|
|
137
|
+
return hash({
|
|
138
|
+
size: stats.size.toString(),
|
|
139
|
+
modified: stats.mtimeNs.toString(),
|
|
140
|
+
walSize: wal?.size.toString() ?? "0",
|
|
141
|
+
walModified: wal?.mtimeNs.toString() ?? "0",
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
case "inspect": {
|
|
145
|
+
const [kind, table] = request.args;
|
|
146
|
+
return inspect(database, kind, table);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
function inspect(database, kind, table) {
|
|
151
|
+
if (kind === "schema") {
|
|
152
|
+
const tables = database
|
|
153
|
+
.prepare(`SELECT name, type
|
|
154
|
+
FROM sqlite_master
|
|
155
|
+
WHERE type IN ('table', 'view') AND name NOT LIKE 'sqlite_%'
|
|
156
|
+
ORDER BY name`)
|
|
157
|
+
.all();
|
|
158
|
+
return { schema: "main", tables };
|
|
159
|
+
}
|
|
160
|
+
if (!table)
|
|
161
|
+
throw new Error(`Table is required for inspect ${kind}.`);
|
|
162
|
+
const quoted = quoteSqliteLiteral(table);
|
|
163
|
+
const columns = database
|
|
164
|
+
.prepare(`PRAGMA table_info(${quoted})`)
|
|
165
|
+
.all();
|
|
166
|
+
if (columns.length === 0)
|
|
167
|
+
throw new Error(`Table "${table}" was not found.`);
|
|
168
|
+
const indexes = database
|
|
169
|
+
.prepare(`PRAGMA index_list(${quoted})`)
|
|
170
|
+
.all();
|
|
171
|
+
const foreignKeys = database
|
|
172
|
+
.prepare(`PRAGMA foreign_key_list(${quoted})`)
|
|
173
|
+
.all();
|
|
174
|
+
if (kind === "columns")
|
|
175
|
+
return { table, columns };
|
|
176
|
+
if (kind === "indexes")
|
|
177
|
+
return { table, indexes };
|
|
178
|
+
if (kind === "constraints") {
|
|
179
|
+
return {
|
|
180
|
+
table,
|
|
181
|
+
primary_key: columns
|
|
182
|
+
.filter((column) => Number(column.pk) > 0)
|
|
183
|
+
.map((column) => column.name),
|
|
184
|
+
foreign_keys: foreignKeys,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
if (kind !== "table")
|
|
188
|
+
throw new Error(`Unknown inspection kind "${kind}".`);
|
|
189
|
+
return {
|
|
190
|
+
table,
|
|
191
|
+
schema: "main",
|
|
192
|
+
columns: columns.map((column) => ({
|
|
193
|
+
name: column.name,
|
|
194
|
+
type: String(column.type).toLowerCase(),
|
|
195
|
+
nullable: column.notnull === 0 && Number(column.pk) === 0,
|
|
196
|
+
primary_key: Number(column.pk) > 0,
|
|
197
|
+
})),
|
|
198
|
+
indexes: indexes.length,
|
|
199
|
+
foreign_keys: foreignKeys.length,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
function bindAll(statement, params) {
|
|
203
|
+
if (Array.isArray(params))
|
|
204
|
+
return statement.all(...params);
|
|
205
|
+
return statement.all(params);
|
|
206
|
+
}
|
|
207
|
+
function bindRun(statement, params) {
|
|
208
|
+
if (Array.isArray(params))
|
|
209
|
+
return statement.run(...params);
|
|
210
|
+
return statement.run(params);
|
|
211
|
+
}
|
|
212
|
+
function inferType(rows, name) {
|
|
213
|
+
const value = rows.find((row) => row[name] !== null)?.[name];
|
|
214
|
+
if (value === undefined)
|
|
215
|
+
return "unknown";
|
|
216
|
+
if (Buffer.isBuffer(value))
|
|
217
|
+
return "binary";
|
|
218
|
+
return typeof value;
|
|
219
|
+
}
|
|
220
|
+
function quoteSqliteLiteral(value) {
|
|
221
|
+
return `'${value.replaceAll("'", "''")}'`;
|
|
222
|
+
}
|
|
223
|
+
function close() {
|
|
224
|
+
try {
|
|
225
|
+
db?.close();
|
|
226
|
+
}
|
|
227
|
+
catch {
|
|
228
|
+
// Process teardown closes the database handle.
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
function errorText(error) {
|
|
232
|
+
return error instanceof Error ? error.message : String(error);
|
|
233
|
+
}
|
|
234
|
+
class SQLiteBatchError extends Error {
|
|
235
|
+
outcomeUnknown;
|
|
236
|
+
constructor(message, outcomeUnknown) {
|
|
237
|
+
super(message);
|
|
238
|
+
this.outcomeUnknown = outcomeUnknown;
|
|
239
|
+
}
|
|
240
|
+
}
|
package/dist/src/stateql.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { BatchCommand, BatchOptions, ConnectOptions, ExecOptions, FilterOptions, PlanOptions, ProfileOptions, QueryOptions, Response, RowsOptions, StateQLOptions } from "./types.js";
|
|
1
|
+
import type { BatchCommand, BatchOptions, ConnectOptions, ExecOptions, ExecutionOptions, FilterOptions, HistoryEntry, PlanOptions, ProfileOptions, QueryOptions, Response, RowsOptions, StateQLOptions, StateQLSnapshot } from "./types.js";
|
|
2
2
|
export declare class StateQL {
|
|
3
3
|
private readonly store;
|
|
4
4
|
private readonly sessionName;
|
|
@@ -7,6 +7,9 @@ export declare class StateQL {
|
|
|
7
7
|
private readonly resultTtlSeconds;
|
|
8
8
|
private readonly maxCellCharacters;
|
|
9
9
|
private readonly maxResultRows;
|
|
10
|
+
private readonly maxResultBytes;
|
|
11
|
+
private readonly timeoutMs;
|
|
12
|
+
private readonly signal?;
|
|
10
13
|
private readonly now;
|
|
11
14
|
constructor(options?: StateQLOptions);
|
|
12
15
|
close(): void;
|
|
@@ -16,6 +19,9 @@ export declare class StateQL {
|
|
|
16
19
|
showProfile(name: string): Promise<Response<unknown>>;
|
|
17
20
|
removeProfile(name: string): Promise<Response<unknown>>;
|
|
18
21
|
disconnect(): Promise<Response<unknown>>;
|
|
22
|
+
snapshot(options?: {
|
|
23
|
+
historyLimit?: number;
|
|
24
|
+
}): StateQLSnapshot;
|
|
19
25
|
status(): Promise<Response<unknown>>;
|
|
20
26
|
startSession(name: string): Promise<Response<unknown>>;
|
|
21
27
|
listSessions(): Promise<Response<unknown>>;
|
|
@@ -34,21 +40,25 @@ export declare class StateQL {
|
|
|
34
40
|
receipt(id: string): Promise<Response<unknown>>;
|
|
35
41
|
beginTransaction(isolation?: string): Promise<Response<unknown>>;
|
|
36
42
|
transactionStatus(id?: string): Promise<Response<unknown>>;
|
|
37
|
-
commitTransaction(id?: string): Promise<Response<unknown>>;
|
|
43
|
+
commitTransaction(id?: string, options?: ExecutionOptions): Promise<Response<unknown>>;
|
|
38
44
|
rollbackTransaction(id?: string): Promise<Response<unknown>>;
|
|
39
|
-
inspect(kind: string, table?: string): Promise<Response<unknown>>;
|
|
45
|
+
inspect(kind: string, table?: string, options?: ExecutionOptions): Promise<Response<unknown>>;
|
|
40
46
|
plan(sql: string, options?: PlanOptions): Promise<Response<unknown>>;
|
|
41
|
-
apply(planId: string): Promise<Response<unknown>>;
|
|
42
|
-
history(limit?: number): Promise<Response<
|
|
47
|
+
apply(planId: string, options?: ExecutionOptions): Promise<Response<unknown>>;
|
|
48
|
+
history(limit?: number): Promise<Response<{
|
|
49
|
+
history: HistoryEntry[];
|
|
50
|
+
}>>;
|
|
43
51
|
capabilities(): Promise<Response<unknown>>;
|
|
44
52
|
executeCommand(command: BatchCommand): Promise<Response<unknown>>;
|
|
45
53
|
batch(commands: Iterable<BatchCommand> | AsyncIterable<BatchCommand>, options?: BatchOptions): AsyncGenerator<Response<unknown>>;
|
|
46
54
|
private performExec;
|
|
47
55
|
private batchFailure;
|
|
48
56
|
private withResult;
|
|
57
|
+
private rejectDuringStagedTransaction;
|
|
49
58
|
private requireResult;
|
|
50
59
|
private requireConnection;
|
|
51
60
|
private requireActiveTransaction;
|
|
61
|
+
private executionContext;
|
|
52
62
|
private resultData;
|
|
53
63
|
private cacheValid;
|
|
54
64
|
private run;
|