@fadhilp/stateql 0.10.1 → 0.11.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/README.md +111 -58
- package/dist/src/connection.d.ts +7 -1
- package/dist/src/connection.js +117 -12
- package/dist/src/migrations.js +120 -0
- package/dist/src/response-data.js +1 -0
- package/dist/src/stateql.d.ts +2 -0
- package/dist/src/stateql.js +154 -56
- package/dist/src/store.d.ts +7 -1
- package/dist/src/store.js +94 -66
- package/dist/src/types.d.ts +9 -1
- package/package.json +1 -1
package/dist/src/store.js
CHANGED
|
@@ -53,14 +53,22 @@ export class StateStore {
|
|
|
53
53
|
this.closed = true;
|
|
54
54
|
this.db.close();
|
|
55
55
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
56
|
+
randomId(prefix) {
|
|
57
|
+
return `${prefix}_${randomBase32Id()}`;
|
|
58
|
+
}
|
|
59
|
+
insertWithRandomId(prefix, table, insert) {
|
|
60
|
+
for (let attempt = 0; attempt < 64; attempt++) {
|
|
61
|
+
const id = this.randomId(prefix);
|
|
62
|
+
try {
|
|
63
|
+
insert(id);
|
|
64
|
+
return id;
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
if (!isIdCollision(error, table))
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
throw new Error(`Could not allocate a unique ${prefix} ID.`);
|
|
64
72
|
}
|
|
65
73
|
ensureSession(name = "default") {
|
|
66
74
|
const existing = this.getSessionByName(name);
|
|
@@ -87,12 +95,13 @@ export class StateStore {
|
|
|
87
95
|
.get(name);
|
|
88
96
|
const created = !row;
|
|
89
97
|
if (!row) {
|
|
90
|
-
const id = this.
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
98
|
+
const id = this.insertWithRandomId("s", "sessions", (candidate) => {
|
|
99
|
+
this.db
|
|
100
|
+
.prepare(`INSERT INTO sessions
|
|
101
|
+
(id, name, status, created_at, updated_at)
|
|
102
|
+
VALUES (?, ?, 'active', ?, ?)`)
|
|
103
|
+
.run(candidate, name, timestamp, timestamp);
|
|
104
|
+
});
|
|
96
105
|
row = { id, status: "active" };
|
|
97
106
|
}
|
|
98
107
|
else if (row.status !== "active") {
|
|
@@ -238,15 +247,15 @@ export class StateStore {
|
|
|
238
247
|
const timestamp = this.now().toISOString();
|
|
239
248
|
this.db
|
|
240
249
|
.prepare(`INSERT INTO profiles
|
|
241
|
-
(name, target, secret_env, credential_ref, read_only, created_at, updated_at)
|
|
242
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)`)
|
|
243
|
-
.run(input.name, input.target ?? null, input.secretEnv ?? null, input.credentialRef ?? null, input.readOnly ? 1 : 0, timestamp, timestamp);
|
|
250
|
+
(name, target, secret_env, credential_ref, password_ref, read_only, created_at, updated_at)
|
|
251
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
252
|
+
.run(input.name, input.target ?? null, input.secretEnv ?? null, input.credentialRef ?? null, input.passwordRef ?? null, input.readOnly ? 1 : 0, timestamp, timestamp);
|
|
244
253
|
return this.getProfile(input.name);
|
|
245
254
|
}
|
|
246
255
|
updateProfile(input) {
|
|
247
256
|
const result = this.db.prepare(`UPDATE profiles
|
|
248
|
-
SET target = ?, secret_env = ?, credential_ref = ?, read_only = ?, updated_at = ?
|
|
249
|
-
WHERE name = ?`).run(input.target, input.secretEnv, input.credentialRef, input.readOnly ? 1 : 0, this.now().toISOString(), input.name);
|
|
257
|
+
SET target = ?, secret_env = ?, credential_ref = ?, password_ref = ?, read_only = ?, updated_at = ?
|
|
258
|
+
WHERE name = ?`).run(input.target, input.secretEnv, input.credentialRef, input.passwordRef, input.readOnly ? 1 : 0, this.now().toISOString(), input.name);
|
|
250
259
|
return Number(result.changes) === 1 ? this.getProfile(input.name) : undefined;
|
|
251
260
|
}
|
|
252
261
|
getProfile(name) {
|
|
@@ -278,14 +287,15 @@ export class StateStore {
|
|
|
278
287
|
this.db.exec("COMMIT");
|
|
279
288
|
return undefined;
|
|
280
289
|
}
|
|
281
|
-
const id = this.nextId("conn");
|
|
282
290
|
const timestamp = this.now().toISOString();
|
|
283
|
-
this.
|
|
284
|
-
.
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
291
|
+
const id = this.insertWithRandomId("conn", "connections", (candidate) => {
|
|
292
|
+
this.db
|
|
293
|
+
.prepare(`INSERT INTO connections
|
|
294
|
+
(id, session_id, name, driver, database_name, source, secret_env,
|
|
295
|
+
credential_ref, password_ref, read_only, version, created_at)
|
|
296
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, ?)`)
|
|
297
|
+
.run(candidate, input.sessionId, input.name, input.driver, input.databaseName, input.source, input.secretEnv ?? null, input.credentialRef ?? null, input.passwordRef ?? null, input.readOnly ? 1 : 0, timestamp);
|
|
298
|
+
});
|
|
289
299
|
this.allocateConnectionAlias(id);
|
|
290
300
|
this.db
|
|
291
301
|
.prepare(`UPDATE sessions
|
|
@@ -362,16 +372,17 @@ export class StateStore {
|
|
|
362
372
|
return `sv_${row.version}`;
|
|
363
373
|
}
|
|
364
374
|
saveResult(input) {
|
|
365
|
-
const id = this.nextId("q");
|
|
366
375
|
this.db.exec("BEGIN IMMEDIATE");
|
|
367
376
|
try {
|
|
368
|
-
this.
|
|
369
|
-
.
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
377
|
+
const id = this.insertWithRandomId("q", "results", (candidate) => {
|
|
378
|
+
this.db
|
|
379
|
+
.prepare(`INSERT INTO results
|
|
380
|
+
(id, session_id, connection_id, fingerprint, sql, parameters,
|
|
381
|
+
rows_json, columns_json, row_count, state_version, state_signature,
|
|
382
|
+
state_confidence, expires_at, created_at)
|
|
383
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
384
|
+
.run(candidate, input.sessionId, input.connectionId, input.fingerprint, input.sql, JSON.stringify(toJsonSafe(input.parameters)), JSON.stringify(toJsonSafe(input.rows)), JSON.stringify(input.columns), input.rows.length, input.stateVersion, input.stateSignature, input.stateConfidence, input.expiresAt, this.now().toISOString());
|
|
385
|
+
});
|
|
375
386
|
this.enforceResultQuota(id);
|
|
376
387
|
this.allocateGeneratedAlias(input.sessionId, id);
|
|
377
388
|
const result = this.getResult(id);
|
|
@@ -466,14 +477,15 @@ export class StateStore {
|
|
|
466
477
|
}
|
|
467
478
|
}
|
|
468
479
|
saveOperation(input) {
|
|
469
|
-
const id = this.
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
480
|
+
const id = this.insertWithRandomId("op", "operations", (candidate) => {
|
|
481
|
+
this.db
|
|
482
|
+
.prepare(`INSERT INTO operations
|
|
483
|
+
(id, session_id, actor_id, connection_id, fingerprint, sql, parameters,
|
|
484
|
+
statement_type, affected_rows, status, transaction_id, replay_of,
|
|
485
|
+
idempotency_key, state_version_before, state_version_after, created_at)
|
|
486
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
487
|
+
.run(candidate, input.sessionId, input.actorId, input.connectionId, input.fingerprint, input.sql, JSON.stringify(toJsonSafe(input.parameters)), input.statementType, input.affectedRows ?? null, input.status, input.transactionId ?? null, input.replayOf ?? null, input.idempotencyKey ?? null, input.stateVersionBefore, input.stateVersionAfter ?? null, this.now().toISOString());
|
|
488
|
+
});
|
|
477
489
|
return this.getOperation(id);
|
|
478
490
|
}
|
|
479
491
|
getOperation(id) {
|
|
@@ -584,7 +596,6 @@ export class StateStore {
|
|
|
584
596
|
.run(operationId);
|
|
585
597
|
}
|
|
586
598
|
createTransaction(input) {
|
|
587
|
-
const id = this.nextId("tx");
|
|
588
599
|
const timestamp = this.now().toISOString();
|
|
589
600
|
this.db.exec("BEGIN IMMEDIATE");
|
|
590
601
|
try {
|
|
@@ -604,12 +615,14 @@ export class StateStore {
|
|
|
604
615
|
this.db.exec("COMMIT");
|
|
605
616
|
return undefined;
|
|
606
617
|
}
|
|
607
|
-
this.
|
|
608
|
-
.
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
618
|
+
const id = this.insertWithRandomId("tx", "transactions", (candidate) => {
|
|
619
|
+
this.db
|
|
620
|
+
.prepare(`INSERT INTO transactions
|
|
621
|
+
(id, session_id, owner_actor_id, connection_id, state,
|
|
622
|
+
isolation_level, start_version, created_at)
|
|
623
|
+
VALUES (?, ?, ?, ?, 'active', ?, ?, ?)`)
|
|
624
|
+
.run(candidate, input.sessionId, input.actorId, input.connectionId, input.isolation, `sv_${eligible.version}`, timestamp);
|
|
625
|
+
});
|
|
613
626
|
this.db
|
|
614
627
|
.prepare(`UPDATE sessions
|
|
615
628
|
SET active_transaction_id = ?, updated_at = ?
|
|
@@ -630,7 +643,7 @@ export class StateStore {
|
|
|
630
643
|
}
|
|
631
644
|
transactionOperations(transactionId) {
|
|
632
645
|
return this.db
|
|
633
|
-
.prepare("SELECT * FROM operations WHERE transaction_id = ? ORDER BY created_at")
|
|
646
|
+
.prepare("SELECT * FROM operations WHERE transaction_id = ? ORDER BY created_at, rowid")
|
|
634
647
|
.all(transactionId);
|
|
635
648
|
}
|
|
636
649
|
validatedTransactionOperations(transactionId) {
|
|
@@ -804,14 +817,15 @@ export class StateStore {
|
|
|
804
817
|
}
|
|
805
818
|
}
|
|
806
819
|
savePlan(input) {
|
|
807
|
-
const id = this.
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
820
|
+
const id = this.insertWithRandomId("p", "plans", (candidate) => {
|
|
821
|
+
this.db
|
|
822
|
+
.prepare(`INSERT INTO plans
|
|
823
|
+
(id, session_id, owner_actor_id, connection_id, sql, parameters,
|
|
824
|
+
statement_type, state_version, state_signature, destructive,
|
|
825
|
+
allow_unbounded, allow_destructive, expires_at, created_at)
|
|
826
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
827
|
+
.run(candidate, input.sessionId, input.ownerActorId, input.connectionId, input.sql, JSON.stringify(toJsonSafe(input.parameters)), input.statementType, input.stateVersion, input.stateSignature, input.destructive ? 1 : 0, input.allowUnbounded ? 1 : 0, input.allowDestructive ? 1 : 0, input.expiresAt, this.now().toISOString());
|
|
828
|
+
});
|
|
815
829
|
return this.getPlan(id);
|
|
816
830
|
}
|
|
817
831
|
getPlan(id) {
|
|
@@ -876,13 +890,17 @@ export class StateStore {
|
|
|
876
890
|
}
|
|
877
891
|
}
|
|
878
892
|
addHistory(input) {
|
|
879
|
-
const
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
893
|
+
const insert = (id) => {
|
|
894
|
+
this.db
|
|
895
|
+
.prepare(`INSERT INTO history
|
|
896
|
+
(id, timestamp, session_id, actor_id, origin, category, internal, command, sql, target, handle,
|
|
897
|
+
executed, cached, success, error_code)
|
|
898
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
899
|
+
.run(id, this.now().toISOString(), input.sessionId, input.actorId, input.origin ?? "legacy", input.category ?? "management", input.internal ? 1 : 0, input.command, boundedHistorySql(input.sql), input.target?.slice(0, 1024) ?? null, input.handle ?? null, input.executed ? 1 : 0, input.cached ? 1 : 0, input.success ? 1 : 0, input.errorCode ?? null);
|
|
900
|
+
};
|
|
901
|
+
const id = input.id ?? this.insertWithRandomId("cmd", "history", insert);
|
|
902
|
+
if (input.id !== undefined)
|
|
903
|
+
insert(id);
|
|
886
904
|
this.db
|
|
887
905
|
.prepare(`DELETE FROM history
|
|
888
906
|
WHERE rowid IN (
|
|
@@ -1120,8 +1138,18 @@ function outcomeJson(outcome) {
|
|
|
1120
1138
|
return outcome === undefined ? null : JSON.stringify(toJsonSafe(outcome));
|
|
1121
1139
|
}
|
|
1122
1140
|
function randomBase32Alias() {
|
|
1141
|
+
return randomBase32(10);
|
|
1142
|
+
}
|
|
1143
|
+
function randomBase32Id() {
|
|
1144
|
+
return randomBase32(26);
|
|
1145
|
+
}
|
|
1146
|
+
function randomBase32(length) {
|
|
1123
1147
|
const alphabet = "abcdefghijklmnopqrstuvwxyz234567";
|
|
1124
|
-
return [...randomBytes(
|
|
1148
|
+
return [...randomBytes(length)].map((value) => alphabet[value & 31]).join("");
|
|
1149
|
+
}
|
|
1150
|
+
function isIdCollision(error, table) {
|
|
1151
|
+
return error instanceof Error &&
|
|
1152
|
+
error.message.includes(`UNIQUE constraint failed: ${table}.id`);
|
|
1125
1153
|
}
|
|
1126
1154
|
function boundedHistorySql(sql) {
|
|
1127
1155
|
if (sql === undefined)
|
package/dist/src/types.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ export interface CommandExecutionContext {
|
|
|
11
11
|
}
|
|
12
12
|
export type CredentialAccess = "read" | "write";
|
|
13
13
|
export type CredentialOperation = "connect" | "query" | "inspect" | "plan" | "exec" | "apply" | "transaction.commit";
|
|
14
|
-
export type CredentialSource = "secret_env" | "credential_ref";
|
|
14
|
+
export type CredentialSource = "secret_env" | "credential_ref" | "password_ref";
|
|
15
15
|
interface CredentialRequestBase {
|
|
16
16
|
reference: string;
|
|
17
17
|
actorId: string;
|
|
@@ -39,6 +39,9 @@ export type CredentialRequest = CredentialRequestBase & ({
|
|
|
39
39
|
source?: "secret_env";
|
|
40
40
|
} | {
|
|
41
41
|
source: "credential_ref";
|
|
42
|
+
} | {
|
|
43
|
+
source: "password_ref";
|
|
44
|
+
target: string;
|
|
42
45
|
});
|
|
43
46
|
export type CredentialResolver = (request: CredentialRequest) => string | undefined | Promise<string | undefined>;
|
|
44
47
|
export type StateConfidence = "authoritative" | "transaction_snapshot" | "database_reported" | "local" | "ttl_based" | "unknown";
|
|
@@ -300,16 +303,19 @@ export interface ConnectOptions extends ExecutionOptions {
|
|
|
300
303
|
secretEnv?: string;
|
|
301
304
|
profile?: string;
|
|
302
305
|
credentialRef?: string;
|
|
306
|
+
passwordRef?: string;
|
|
303
307
|
}
|
|
304
308
|
export interface ProfileOptions {
|
|
305
309
|
readOnly?: boolean;
|
|
306
310
|
secretEnv?: string;
|
|
307
311
|
credentialRef?: string;
|
|
312
|
+
passwordRef?: string;
|
|
308
313
|
}
|
|
309
314
|
export interface ProfileUpdateOptions {
|
|
310
315
|
target?: string | null;
|
|
311
316
|
secretEnv?: string | null;
|
|
312
317
|
credentialRef?: string | null;
|
|
318
|
+
passwordRef?: string | null;
|
|
313
319
|
readOnly?: boolean;
|
|
314
320
|
}
|
|
315
321
|
export interface RedisQueryOptions extends ExecutionOptions {
|
|
@@ -353,6 +359,7 @@ export interface BatchCommand {
|
|
|
353
359
|
read_only?: boolean;
|
|
354
360
|
secret_env?: string;
|
|
355
361
|
credential_ref?: string;
|
|
362
|
+
password_ref?: string | null;
|
|
356
363
|
profile?: string;
|
|
357
364
|
replay?: boolean;
|
|
358
365
|
idempotency_key?: string;
|
|
@@ -398,6 +405,7 @@ export interface ProfileData {
|
|
|
398
405
|
target: string | null;
|
|
399
406
|
secret_env: string | null;
|
|
400
407
|
credential_ref: string | null;
|
|
408
|
+
password_ref: string | null;
|
|
401
409
|
read_only: boolean;
|
|
402
410
|
}
|
|
403
411
|
export interface ProfilesData {
|