@crvouga/mockingbird-service-sqlite 1.2.0 → 1.3.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/CHANGELOG.md +17 -1
- package/README.md +49 -6
- package/dist/admin.d.ts +13 -0
- package/dist/admin.js +19180 -0
- package/dist/admin.js.map +7 -0
- package/dist/api/database.d.ts +37 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +270 -13
- package/dist/index.js.map +3 -3
- package/dist/runtime/options.d.ts +5 -0
- package/dist/socket/cli.d.ts +2 -0
- package/dist/socket/cli.js +16467 -0
- package/dist/socket/cli.js.map +7 -0
- package/dist/socket/index.d.ts +58 -0
- package/dist/socket/index.js +16486 -0
- package/dist/socket/index.js.map +7 -0
- package/dist/unstable.js.map +2 -2
- package/package.json +21 -3
package/dist/api/database.d.ts
CHANGED
|
@@ -2,10 +2,27 @@ import { type DatabaseOptions, type RandomMode } from "../runtime/index.js";
|
|
|
2
2
|
import type { BindValue, QueryRow } from "../types/value.js";
|
|
3
3
|
import { type Snapshot } from "./snapshot.js";
|
|
4
4
|
import { Statement } from "./statement.js";
|
|
5
|
+
/** A timeline checkpoint without its retained snapshot value. */
|
|
6
|
+
export interface HistoryCheckpoint {
|
|
7
|
+
id: string;
|
|
8
|
+
branch: string;
|
|
9
|
+
parent: string | null;
|
|
10
|
+
at: number;
|
|
11
|
+
}
|
|
12
|
+
/** Branch heads and checkpoints owned by the shared Timeline. */
|
|
13
|
+
export interface DatabaseHistory {
|
|
14
|
+
readonly size: number;
|
|
15
|
+
head(branch?: string): HistoryCheckpoint | undefined;
|
|
16
|
+
branches(): Readonly<Record<string, string>>;
|
|
17
|
+
checkpoints(): readonly HistoryCheckpoint[];
|
|
18
|
+
}
|
|
5
19
|
/** Additive time-travel controls for read APIs. */
|
|
6
20
|
export interface QueryOptions {
|
|
7
|
-
/**
|
|
8
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Execute against this immutable snapshot, or a timeline checkpoint id,
|
|
23
|
+
* instead of the live database.
|
|
24
|
+
*/
|
|
25
|
+
at?: Snapshot | string;
|
|
9
26
|
}
|
|
10
27
|
/**
|
|
11
28
|
* Pure TypeScript in-memory SQLite database.
|
|
@@ -34,6 +51,8 @@ export declare class Database {
|
|
|
34
51
|
private transactionSequence;
|
|
35
52
|
/** Depth of active {@link transaction} callbacks (not SQL BEGIN). */
|
|
36
53
|
private apiTransactionDepth;
|
|
54
|
+
private historyBranch;
|
|
55
|
+
private originId;
|
|
37
56
|
/**
|
|
38
57
|
* Create an empty in-memory database.
|
|
39
58
|
*
|
|
@@ -88,6 +107,19 @@ export declare class Database {
|
|
|
88
107
|
snapshot(): Snapshot;
|
|
89
108
|
/** Alias for {@link snapshot}, naming the value as a timeline checkpoint. */
|
|
90
109
|
checkpoint(): Snapshot;
|
|
110
|
+
/**
|
|
111
|
+
* Commit the live state onto `branch` (default: the branch this database follows).
|
|
112
|
+
* Does not switch the live database. Illegal inside a transaction.
|
|
113
|
+
*/
|
|
114
|
+
record(branch?: string): HistoryCheckpoint;
|
|
115
|
+
/** Point `name` at a checkpoint without copying rows or switching the live database. */
|
|
116
|
+
fork(name: string, at?: string): HistoryCheckpoint;
|
|
117
|
+
/** Move `branch` to `id` and install that checkpoint as the live database. */
|
|
118
|
+
checkout(id: string, branch?: string): HistoryCheckpoint;
|
|
119
|
+
/** Return the live database to the origin checkpoint on `main`. */
|
|
120
|
+
reset(): HistoryCheckpoint;
|
|
121
|
+
/** Checkpoint ids and branch heads. Values stay inside the timeline. */
|
|
122
|
+
get history(): DatabaseHistory;
|
|
91
123
|
/** Open a copy-on-write branch from `at`, or from the current state when omitted. */
|
|
92
124
|
branch(at?: Snapshot): Database;
|
|
93
125
|
/**
|
|
@@ -115,6 +147,9 @@ export declare class Database {
|
|
|
115
147
|
* @throws {SqliteError} If the database is closed.
|
|
116
148
|
*/
|
|
117
149
|
get totalChanges(): number;
|
|
150
|
+
private historyError;
|
|
151
|
+
private captureLive;
|
|
152
|
+
private install;
|
|
118
153
|
private prepareSingle;
|
|
119
154
|
}
|
|
120
155
|
export { Snapshot } from "./snapshot.js";
|
package/dist/index.d.ts
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
*
|
|
22
22
|
* @module
|
|
23
23
|
*/
|
|
24
|
-
export { Database, type DatabaseOptions, type QueryOptions, Snapshot } from "./api/database.js";
|
|
24
|
+
export { Database, type DatabaseHistory, type DatabaseOptions, type HistoryCheckpoint, type QueryOptions, Snapshot, } from "./api/database.js";
|
|
25
25
|
export { type RunResult, Statement } from "./api/statement.js";
|
|
26
26
|
export { type ErrorCategory, SqliteError } from "./errors/index.js";
|
|
27
27
|
export type { ResultSet } from "./executor/result.js";
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,164 @@
|
|
|
1
|
+
// ../../core/dist/timeline.js
|
|
2
|
+
var BRANCH_PATTERN = /^[A-Za-z0-9_.-]{1,64}$/;
|
|
3
|
+
var Timeline = class {
|
|
4
|
+
maxCheckpoints;
|
|
5
|
+
now;
|
|
6
|
+
makeId;
|
|
7
|
+
nodes = /* @__PURE__ */ new Map();
|
|
8
|
+
heads = /* @__PURE__ */ new Map();
|
|
9
|
+
/** Unreferenced nodes in the exact order they became collectible. */
|
|
10
|
+
evictable = /* @__PURE__ */ new Set();
|
|
11
|
+
/** Branch heads plus explicit retainers. Absent means zero. */
|
|
12
|
+
references = /* @__PURE__ */ new Map();
|
|
13
|
+
explicitPins = /* @__PURE__ */ new Map();
|
|
14
|
+
sequence = 0;
|
|
15
|
+
constructor(options = {}) {
|
|
16
|
+
const max = options.maxCheckpoints ?? 1e3;
|
|
17
|
+
if (!Number.isSafeInteger(max) || max < 1)
|
|
18
|
+
throw new RangeError("maxCheckpoints must be a positive integer");
|
|
19
|
+
this.maxCheckpoints = max;
|
|
20
|
+
this.now = options.now ?? (() => this.sequence);
|
|
21
|
+
this.makeId = options.id ?? ((sequence) => `cp_${sequence.toString(36).padStart(8, "0")}`);
|
|
22
|
+
}
|
|
23
|
+
/** Capture a new immutable value and move `branch` to it. */
|
|
24
|
+
commit(value, options = {}) {
|
|
25
|
+
const branch = options.branch ?? "main";
|
|
26
|
+
this.assertBranch(branch);
|
|
27
|
+
const parent = options.parent === void 0 ? this.heads.get(branch) ?? null : options.parent;
|
|
28
|
+
if (parent !== null && !this.nodes.has(parent))
|
|
29
|
+
throw new RangeError(`no checkpoint ${parent}`);
|
|
30
|
+
const id = this.makeId(++this.sequence);
|
|
31
|
+
if (this.nodes.has(id))
|
|
32
|
+
throw new RangeError(`duplicate checkpoint id ${id}`);
|
|
33
|
+
const checkpoint = Object.freeze({ id, branch, parent, at: this.now(), value });
|
|
34
|
+
this.nodes.set(id, checkpoint);
|
|
35
|
+
this.moveHead(branch, id);
|
|
36
|
+
this.collect(this.maxCheckpoints);
|
|
37
|
+
return checkpoint;
|
|
38
|
+
}
|
|
39
|
+
/** Create a branch pointer without copying its checkpoint value. */
|
|
40
|
+
fork(branch, options = {}) {
|
|
41
|
+
this.assertBranch(branch);
|
|
42
|
+
if (this.heads.has(branch))
|
|
43
|
+
throw new RangeError(`branch already exists: ${branch}`);
|
|
44
|
+
const from = options.from ?? this.heads.get("main");
|
|
45
|
+
if (from === void 0)
|
|
46
|
+
return void 0;
|
|
47
|
+
const checkpoint = this.get(from);
|
|
48
|
+
this.moveHead(branch, checkpoint.id);
|
|
49
|
+
return checkpoint;
|
|
50
|
+
}
|
|
51
|
+
/** Move a branch pointer to an existing checkpoint. */
|
|
52
|
+
checkout(branch, id) {
|
|
53
|
+
this.assertBranch(branch);
|
|
54
|
+
const checkpoint = this.get(id);
|
|
55
|
+
this.moveHead(branch, checkpoint.id);
|
|
56
|
+
return checkpoint;
|
|
57
|
+
}
|
|
58
|
+
get(id) {
|
|
59
|
+
const checkpoint = this.nodes.get(id);
|
|
60
|
+
if (!checkpoint)
|
|
61
|
+
throw new RangeError(`no checkpoint ${id}`);
|
|
62
|
+
return checkpoint;
|
|
63
|
+
}
|
|
64
|
+
head(branch = "main") {
|
|
65
|
+
const id = this.heads.get(branch);
|
|
66
|
+
return id === void 0 ? void 0 : this.get(id);
|
|
67
|
+
}
|
|
68
|
+
hasBranch(branch) {
|
|
69
|
+
return this.heads.has(branch);
|
|
70
|
+
}
|
|
71
|
+
branches() {
|
|
72
|
+
return Object.freeze(Object.fromEntries([...this.heads].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)));
|
|
73
|
+
}
|
|
74
|
+
checkpoints() {
|
|
75
|
+
return [...this.nodes.values()];
|
|
76
|
+
}
|
|
77
|
+
/** Number of retained checkpoints without allocating an array. */
|
|
78
|
+
get size() {
|
|
79
|
+
return this.nodes.size;
|
|
80
|
+
}
|
|
81
|
+
/** Pin a checkpoint independently of branch heads (used by compatibility snapshot handles). */
|
|
82
|
+
retain(id) {
|
|
83
|
+
const checkpoint = this.get(id);
|
|
84
|
+
this.explicitPins.set(id, (this.explicitPins.get(id) ?? 0) + 1);
|
|
85
|
+
this.addReference(id);
|
|
86
|
+
return checkpoint;
|
|
87
|
+
}
|
|
88
|
+
/** Release one explicit pin. Branch heads remain pinned until moved or deleted. */
|
|
89
|
+
release(id) {
|
|
90
|
+
if (!this.nodes.has(id))
|
|
91
|
+
return false;
|
|
92
|
+
const pins = this.explicitPins.get(id) ?? 0;
|
|
93
|
+
if (pins === 0)
|
|
94
|
+
return false;
|
|
95
|
+
if (pins === 1)
|
|
96
|
+
this.explicitPins.delete(id);
|
|
97
|
+
else
|
|
98
|
+
this.explicitPins.set(id, pins - 1);
|
|
99
|
+
this.removeReference(id);
|
|
100
|
+
this.collect(this.maxCheckpoints);
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
deleteBranch(branch) {
|
|
104
|
+
if (branch === "main")
|
|
105
|
+
throw new RangeError("cannot delete main branch");
|
|
106
|
+
const previous = this.heads.get(branch);
|
|
107
|
+
const deleted = this.heads.delete(branch);
|
|
108
|
+
if (previous !== void 0)
|
|
109
|
+
this.removeReference(previous);
|
|
110
|
+
this.collect(this.maxCheckpoints);
|
|
111
|
+
return deleted;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Deterministically discard oldest unpinned checkpoints. Collection is O(number removed):
|
|
115
|
+
* commits never scan pinned nodes or the retained history. Parents are metadata rather than a
|
|
116
|
+
* storage dependency, so a retained node remains usable after pruning.
|
|
117
|
+
*/
|
|
118
|
+
gc(max = this.maxCheckpoints) {
|
|
119
|
+
if (!Number.isSafeInteger(max) || max < 1)
|
|
120
|
+
throw new RangeError("max must be a positive integer");
|
|
121
|
+
const removed = [];
|
|
122
|
+
this.collect(max, removed);
|
|
123
|
+
return removed;
|
|
124
|
+
}
|
|
125
|
+
collect(max, removed) {
|
|
126
|
+
while (this.nodes.size > max && this.evictable.size > 0) {
|
|
127
|
+
const id = this.evictable.values().next().value;
|
|
128
|
+
this.evictable.delete(id);
|
|
129
|
+
this.nodes.delete(id);
|
|
130
|
+
removed?.push(id);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
moveHead(branch, id) {
|
|
134
|
+
const previous = this.heads.get(branch);
|
|
135
|
+
if (previous === id)
|
|
136
|
+
return;
|
|
137
|
+
if (previous !== void 0)
|
|
138
|
+
this.removeReference(previous);
|
|
139
|
+
this.heads.set(branch, id);
|
|
140
|
+
this.addReference(id);
|
|
141
|
+
}
|
|
142
|
+
addReference(id) {
|
|
143
|
+
this.references.set(id, (this.references.get(id) ?? 0) + 1);
|
|
144
|
+
this.evictable.delete(id);
|
|
145
|
+
}
|
|
146
|
+
removeReference(id) {
|
|
147
|
+
const next = (this.references.get(id) ?? 0) - 1;
|
|
148
|
+
if (next > 0)
|
|
149
|
+
this.references.set(id, next);
|
|
150
|
+
else {
|
|
151
|
+
this.references.delete(id);
|
|
152
|
+
if (this.nodes.has(id))
|
|
153
|
+
this.evictable.add(id);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
assertBranch(branch) {
|
|
157
|
+
if (!BRANCH_PATTERN.test(branch))
|
|
158
|
+
throw new RangeError(`branch must match ${BRANCH_PATTERN}`);
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
|
|
1
162
|
// src/errors/index.ts
|
|
2
163
|
var SqliteError = class extends Error {
|
|
3
164
|
/** Coarse error class (constraint vs syntax vs missing object, …). */
|
|
@@ -15800,6 +15961,10 @@ var Database = class {
|
|
|
15800
15961
|
transactionSequence = 0;
|
|
15801
15962
|
/** Depth of active {@link transaction} callbacks (not SQL BEGIN). */
|
|
15802
15963
|
apiTransactionDepth = 0;
|
|
15964
|
+
/** @internal Shared checkpoint DAG. Omitted from published declarations. */
|
|
15965
|
+
timeline;
|
|
15966
|
+
historyBranch = "main";
|
|
15967
|
+
originId = "";
|
|
15803
15968
|
/**
|
|
15804
15969
|
* Create an empty in-memory database.
|
|
15805
15970
|
*
|
|
@@ -15814,15 +15979,22 @@ var Database = class {
|
|
|
15814
15979
|
this.now = options.now;
|
|
15815
15980
|
this.state = options.state;
|
|
15816
15981
|
this.transactions = new TransactionManager(this.state, this.prng);
|
|
15817
|
-
|
|
15982
|
+
} else {
|
|
15983
|
+
this.seed = options.seed ?? DEFAULT_DATABASE_SEED;
|
|
15984
|
+
this.randomMode = options.random ?? "deterministic";
|
|
15985
|
+
this.systemClock = options.now === "system";
|
|
15986
|
+
this.prng = this.randomMode === "os" ? new OsEntropy() : new Prng(this.seed);
|
|
15987
|
+
this.now = resolveClock(options.now);
|
|
15988
|
+
this.state = new DatabaseState();
|
|
15989
|
+
this.transactions = new TransactionManager(this.state, this.prng);
|
|
15818
15990
|
}
|
|
15819
|
-
this.
|
|
15820
|
-
|
|
15821
|
-
|
|
15822
|
-
|
|
15823
|
-
|
|
15824
|
-
this.
|
|
15825
|
-
this.
|
|
15991
|
+
this.timeline = new Timeline({
|
|
15992
|
+
now: () => this.now().getTime(),
|
|
15993
|
+
...options.maxCheckpoints !== void 0 ? { maxCheckpoints: options.maxCheckpoints } : {}
|
|
15994
|
+
});
|
|
15995
|
+
const origin = this.timeline.commit(this.captureLive());
|
|
15996
|
+
this.originId = origin.id;
|
|
15997
|
+
this.timeline.retain(origin.id);
|
|
15826
15998
|
}
|
|
15827
15999
|
/**
|
|
15828
16000
|
* Execute SQL for its side effects (DDL/DML). Multiple statements are allowed.
|
|
@@ -15850,7 +16022,15 @@ var Database = class {
|
|
|
15850
16022
|
*/
|
|
15851
16023
|
query(sql, params = [], options = {}) {
|
|
15852
16024
|
this.assertOpen();
|
|
15853
|
-
|
|
16025
|
+
const at = options.at;
|
|
16026
|
+
if (typeof at === "string") {
|
|
16027
|
+
try {
|
|
16028
|
+
return this.timeline.get(at).value.open().query(sql, params);
|
|
16029
|
+
} catch (error) {
|
|
16030
|
+
this.historyError(error);
|
|
16031
|
+
}
|
|
16032
|
+
}
|
|
16033
|
+
if (at) return at.open().query(sql, params);
|
|
15854
16034
|
return this.prepareSingle(sql).all(...params);
|
|
15855
16035
|
}
|
|
15856
16036
|
/**
|
|
@@ -15912,15 +16092,77 @@ var Database = class {
|
|
|
15912
16092
|
* @throws {SqliteError} If the database is closed or a transaction is open.
|
|
15913
16093
|
*/
|
|
15914
16094
|
snapshot() {
|
|
16095
|
+
const point = this.record(this.historyBranch);
|
|
16096
|
+
return this.timeline.get(point.id).value;
|
|
16097
|
+
}
|
|
16098
|
+
/** Alias for {@link snapshot}, naming the value as a timeline checkpoint. */
|
|
16099
|
+
checkpoint() {
|
|
16100
|
+
return this.snapshot();
|
|
16101
|
+
}
|
|
16102
|
+
/**
|
|
16103
|
+
* Commit the live state onto `branch` (default: the branch this database follows).
|
|
16104
|
+
* Does not switch the live database. Illegal inside a transaction.
|
|
16105
|
+
*/
|
|
16106
|
+
record(branch = this.historyBranch) {
|
|
15915
16107
|
this.assertOpen();
|
|
15916
16108
|
if (this.transactions.inTransaction) {
|
|
15917
16109
|
throw new SqliteError("cannot snapshot during a transaction", "transaction");
|
|
15918
16110
|
}
|
|
15919
|
-
|
|
16111
|
+
try {
|
|
16112
|
+
return project(this.timeline.commit(this.captureLive(), { branch }));
|
|
16113
|
+
} catch (error) {
|
|
16114
|
+
this.historyError(error);
|
|
16115
|
+
}
|
|
15920
16116
|
}
|
|
15921
|
-
/**
|
|
15922
|
-
|
|
15923
|
-
|
|
16117
|
+
/** Point `name` at a checkpoint without copying rows or switching the live database. */
|
|
16118
|
+
fork(name, at) {
|
|
16119
|
+
this.assertOpen();
|
|
16120
|
+
try {
|
|
16121
|
+
const point = this.timeline.fork(name, at !== void 0 ? { from: at } : {});
|
|
16122
|
+
if (!point) throw new SqliteError("no checkpoint to fork from", "misuse");
|
|
16123
|
+
return project(point);
|
|
16124
|
+
} catch (error) {
|
|
16125
|
+
if (error instanceof SqliteError) throw error;
|
|
16126
|
+
this.historyError(error);
|
|
16127
|
+
}
|
|
16128
|
+
}
|
|
16129
|
+
/** Move `branch` to `id` and install that checkpoint as the live database. */
|
|
16130
|
+
checkout(id, branch = this.historyBranch) {
|
|
16131
|
+
this.assertOpen();
|
|
16132
|
+
if (this.transactions.inTransaction) {
|
|
16133
|
+
throw new SqliteError("cannot checkout during a transaction", "transaction");
|
|
16134
|
+
}
|
|
16135
|
+
try {
|
|
16136
|
+
const point = this.timeline.checkout(branch, id);
|
|
16137
|
+
this.historyBranch = branch;
|
|
16138
|
+
this.install(point.value);
|
|
16139
|
+
return project(point);
|
|
16140
|
+
} catch (error) {
|
|
16141
|
+
this.historyError(error);
|
|
16142
|
+
}
|
|
16143
|
+
}
|
|
16144
|
+
/** Return the live database to the origin checkpoint on `main`. */
|
|
16145
|
+
reset() {
|
|
16146
|
+
return this.checkout(this.originId, "main");
|
|
16147
|
+
}
|
|
16148
|
+
/** Checkpoint ids and branch heads. Values stay inside the timeline. */
|
|
16149
|
+
get history() {
|
|
16150
|
+
const timeline = this.timeline;
|
|
16151
|
+
return {
|
|
16152
|
+
get size() {
|
|
16153
|
+
return timeline.size;
|
|
16154
|
+
},
|
|
16155
|
+
head(branch) {
|
|
16156
|
+
const point = timeline.head(branch);
|
|
16157
|
+
return point ? project(point) : void 0;
|
|
16158
|
+
},
|
|
16159
|
+
branches() {
|
|
16160
|
+
return timeline.branches();
|
|
16161
|
+
},
|
|
16162
|
+
checkpoints() {
|
|
16163
|
+
return timeline.checkpoints().map(project);
|
|
16164
|
+
}
|
|
16165
|
+
};
|
|
15924
16166
|
}
|
|
15925
16167
|
/** Open a copy-on-write branch from `at`, or from the current state when omitted. */
|
|
15926
16168
|
branch(at = this.snapshot()) {
|
|
@@ -15976,6 +16218,18 @@ var Database = class {
|
|
|
15976
16218
|
assertOpen() {
|
|
15977
16219
|
if (this.closed) throw new SqliteError("Database is closed", "misuse");
|
|
15978
16220
|
}
|
|
16221
|
+
historyError(error) {
|
|
16222
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
16223
|
+
throw new SqliteError(message, "misuse");
|
|
16224
|
+
}
|
|
16225
|
+
captureLive() {
|
|
16226
|
+
return captureSnapshot(this.state, this.prng, this.now, this.seed, this.randomMode, this.systemClock);
|
|
16227
|
+
}
|
|
16228
|
+
install(snapshot) {
|
|
16229
|
+
this.state.replaceWith(snapshot.state.cloneShallow(), { adopt: true });
|
|
16230
|
+
this.prng.setState(snapshot.prngState);
|
|
16231
|
+
if (!this.systemClock) this.now = fixedClock(new Date(snapshot.nowMs));
|
|
16232
|
+
}
|
|
15979
16233
|
prepareSingle(sql) {
|
|
15980
16234
|
const units = parseUnits(sql);
|
|
15981
16235
|
if (units.length === 0) {
|
|
@@ -16005,6 +16259,9 @@ if (typeof disposeKey === "symbol") {
|
|
|
16005
16259
|
function isAdopted(value) {
|
|
16006
16260
|
return ADOPT in value;
|
|
16007
16261
|
}
|
|
16262
|
+
function project(point) {
|
|
16263
|
+
return { id: point.id, branch: point.branch, parent: point.parent, at: point.at };
|
|
16264
|
+
}
|
|
16008
16265
|
function createAdoptedDatabase(opts) {
|
|
16009
16266
|
return new Database({
|
|
16010
16267
|
[ADOPT]: true,
|