@feltdb/core 0.6.13 → 0.7.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 +32 -0
- package/dist/agent-registry.js +1 -3
- package/dist/agent-runtime.js +8 -7
- package/dist/analytics-backend.js +3 -1
- package/dist/application-contract.js +1 -0
- package/dist/application-manifest.js +1 -0
- package/dist/artifact.js +2 -0
- package/dist/authorization.js +2 -0
- package/dist/bundle.js +2 -0
- package/dist/capability.js +1 -3
- package/dist/cell.js +9 -4
- package/dist/cli/commands.js +26 -2
- package/dist/cli/index.js +1 -1
- package/dist/collection.js +39 -31
- package/dist/create/package-versions.js +1 -1
- package/dist/create/server-source/crates/feltdb/src/bin/feltdb_node.rs +613 -27
- package/dist/create/server-source/crates/feltdb/src/causal_backlog_bound.rs +452 -0
- package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier.rs +208 -0
- package/dist/create/server-source/crates/feltdb/src/convergence.rs +16 -0
- package/dist/create/server-source/crates/feltdb/src/dedup_bound_investigation.rs +402 -0
- package/dist/create/server-source/crates/feltdb/src/distributed_transactions.rs +784 -24
- package/dist/create/server-source/crates/feltdb/src/durable_operation_identity.rs +418 -0
- package/dist/create/server-source/crates/feltdb/src/lib.rs +284 -0
- package/dist/create/server-source/crates/feltdb/src/replica_acknowledgements.rs +471 -0
- package/dist/create/server-source/crates/feltdb/src/replica_membership.rs +661 -0
- package/dist/create/server-source/crates/feltdb/src/tcp_transport.rs +72 -3
- package/dist/create/server-source/crates/feltdb/src/transaction_preconditions.rs +899 -0
- package/dist/create/server-source/crates/feltdb-server/src/main.rs +116 -4
- package/dist/db.d.ts +2 -2
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +48 -15
- package/dist/development-runtime-bridge.js +1 -1
- package/dist/distributed-indexing.js +7 -5
- package/dist/embedded-transaction.d.ts +9 -0
- package/dist/embedded-transaction.d.ts.map +1 -1
- package/dist/embedded-transaction.js +95 -3
- package/dist/feltdb.d.ts +16 -0
- package/dist/feltdb.d.ts.map +1 -1
- package/dist/file-db.d.ts.map +1 -1
- package/dist/file-db.js +9 -3
- package/dist/flowspec.js +2 -1
- package/dist/http-client.js +2 -0
- package/dist/http-db.d.ts +11 -0
- package/dist/http-db.d.ts.map +1 -1
- package/dist/http-db.js +32 -3
- package/dist/identity.js +1 -0
- package/dist/index-analytics.js +6 -7
- package/dist/index-backend.js +3 -3
- package/dist/index-dashboard.js +10 -13
- package/dist/index-manager.js +12 -11
- package/dist/index-monitoring.js +9 -4
- package/dist/index-store.js +2 -0
- package/dist/indexeddb-db.d.ts.map +1 -1
- package/dist/indexeddb-db.js +32 -23
- package/dist/memory-db.d.ts.map +1 -1
- package/dist/memory-db.js +8 -4
- package/dist/observe.js +2 -0
- package/dist/provider.js +2 -0
- package/dist/query-planner.js +2 -4
- package/dist/reactive-graph.js +6 -8
- package/dist/release.js +2 -0
- package/dist/sharding.js +11 -6
- package/dist/state-contract.js +3 -3
- package/dist/studio-app/assets/{feltdb_wasm-CJv3wHzi.js → feltdb_wasm-CD744e5D.js} +1 -1
- package/dist/studio-app/assets/feltdb_wasm_bg-CiIXhOLi.wasm +0 -0
- package/dist/studio-app/assets/index-DoROs8yx.js +28 -0
- package/dist/studio-app/index.html +1 -1
- package/dist/sync-contract.js +9 -2
- package/dist/telemetry.d.ts.map +1 -1
- package/dist/telemetry.js +32 -12
- package/dist/transaction.d.ts +127 -9
- package/dist/transaction.d.ts.map +1 -1
- package/dist/transaction.js +91 -5
- package/dist/wasm/feltdb_wasm_bg.wasm +0 -0
- package/dist/worker.js +2 -0
- package/dist/workload.js +2 -0
- package/dist/workspace/development-node.js +11 -10
- package/dist/workspace/investigation-lifecycle-manager.js +2 -0
- package/dist/workspace/investigation-supervisor.js +5 -3
- package/dist/workspace/workspace-connection.js +14 -5
- package/package.json +1 -1
- package/dist/studio-app/assets/feltdb_wasm_bg-C8TG8r2n.wasm +0 -0
- package/dist/studio-app/assets/index-DospFFYE.js +0 -28
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
<!doctype html><html lang="en"><head><meta charset="UTF-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#17211b"/><title>FeltDB Studio</title> <script type="module" crossorigin src="/assets/index-
|
|
1
|
+
<!doctype html><html lang="en"><head><meta charset="UTF-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#17211b"/><title>FeltDB Studio</title> <script type="module" crossorigin src="/assets/index-DoROs8yx.js"></script>
|
|
2
2
|
<link rel="stylesheet" crossorigin href="/assets/index-C1GWyazR.css">
|
|
3
3
|
</head><body><div id="root"></div></body></html>
|
package/dist/sync-contract.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
class StatusSignal {
|
|
2
|
+
value;
|
|
3
|
+
listeners = new Set();
|
|
2
4
|
constructor(value = 'offline') {
|
|
3
5
|
this.value = value;
|
|
4
|
-
this.listeners = new Set();
|
|
5
6
|
}
|
|
6
7
|
subscribe(listener) { this.listeners.add(listener); listener(this.value); return () => this.listeners.delete(listener); }
|
|
7
8
|
set(value) { this.value = value; for (const listener of this.listeners)
|
|
@@ -9,9 +10,15 @@ class StatusSignal {
|
|
|
9
10
|
get() { return this.value; }
|
|
10
11
|
}
|
|
11
12
|
export class SyncController {
|
|
13
|
+
config;
|
|
14
|
+
status = new StatusSignal();
|
|
15
|
+
fetcher;
|
|
16
|
+
sessionId;
|
|
17
|
+
cursor;
|
|
18
|
+
error;
|
|
19
|
+
timer;
|
|
12
20
|
constructor(config) {
|
|
13
21
|
this.config = config;
|
|
14
|
-
this.status = new StatusSignal();
|
|
15
22
|
this.fetcher = config.fetcher || fetch;
|
|
16
23
|
this.restore();
|
|
17
24
|
}
|
package/dist/telemetry.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"telemetry.d.ts","sourceRoot":"","sources":["../src/telemetry.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;
|
|
1
|
+
{"version":3,"file":"telemetry.d.ts","sourceRoot":"","sources":["../src/telemetry.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AA4CH;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAC1B,YAAY,GACZ,OAAO,GACP,OAAO,GACP,MAAM,GACN,iBAAiB,GACjB,qBAAqB,CAAC;AAE1B;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,kBAAkB,CAAC;IAC1B,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;CACnB;AA8CD;;;GAGG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAuB;IACvC,OAAO,CAAC,OAAO,CAAU;IACzB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAM;IAChC,OAAO,CAAC,UAAU,CAAwB;IAC1C,OAAO,CAAC,UAAU,CAA+B;IACjD,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,OAAO,CAA8B;gBAEjC,WAAW,GAAE,MAAgB;IAczC;;OAEG;IACH,IAAI,CAAC,SAAS,EAAE,kBAAkB,GAAG,IAAI;IA6BzC;;OAEG;YACW,KAAK;IAqBnB;;OAEG;IACH,OAAO,CAAC,aAAa;IAarB;;;OAGG;YACW,UAAU;IA6BxB;;OAEG;IACH,OAAO,CAAC,KAAK;IAkBb;;OAEG;IACH,MAAM,IAAI,IAAI;IAId;;OAEG;IACH,OAAO,IAAI,IAAI;IASf;;OAEG;IACH,SAAS,IAAI,OAAO;IAIpB;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;CAKlC;AAOD;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,eAAe,CAKxE;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,SAAS,EAAE,kBAAkB,GAAG,IAAI,CAEjE"}
|
package/dist/telemetry.js
CHANGED
|
@@ -8,14 +8,33 @@
|
|
|
8
8
|
const isBrowserRuntime = () => typeof window !== 'undefined' && typeof window.document !== 'undefined';
|
|
9
9
|
const environmentValue = (name) => typeof process !== 'undefined' ? process.env?.[name] : undefined;
|
|
10
10
|
const createUUID = () => {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
// Try using the modern crypto.randomUUID() API first
|
|
12
|
+
try {
|
|
13
|
+
const browserCrypto = globalThis.crypto;
|
|
14
|
+
if (typeof browserCrypto?.randomUUID === 'function') {
|
|
15
|
+
return browserCrypto.randomUUID();
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
catch (e) {
|
|
19
|
+
// If randomUUID exists but throws (due to browser security restrictions or unimplemented feature),
|
|
20
|
+
// fall through to the fallback implementation
|
|
21
|
+
}
|
|
22
|
+
// Fallback implementation using getRandomValues or Math.random()
|
|
14
23
|
const bytes = new Uint8Array(16);
|
|
24
|
+
const browserCrypto = globalThis.crypto;
|
|
15
25
|
if (typeof browserCrypto?.getRandomValues === 'function') {
|
|
16
|
-
|
|
26
|
+
try {
|
|
27
|
+
browserCrypto.getRandomValues(bytes);
|
|
28
|
+
}
|
|
29
|
+
catch (e) {
|
|
30
|
+
// If getRandomValues fails, fall back to Math.random()
|
|
31
|
+
for (let index = 0; index < bytes.length; index += 1) {
|
|
32
|
+
bytes[index] = Math.floor(Math.random() * 256);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
17
35
|
}
|
|
18
36
|
else {
|
|
37
|
+
// No crypto support at all - use Math.random()
|
|
19
38
|
for (let index = 0; index < bytes.length; index += 1) {
|
|
20
39
|
bytes[index] = Math.floor(Math.random() * 256);
|
|
21
40
|
}
|
|
@@ -30,10 +49,8 @@ const createUUID = () => {
|
|
|
30
49
|
* Generates and persists a unique, anonymous installation ID
|
|
31
50
|
*/
|
|
32
51
|
class InstallationIdentity {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
this.identityFile = null;
|
|
36
|
-
}
|
|
52
|
+
id = null;
|
|
53
|
+
identityFile = null;
|
|
37
54
|
/**
|
|
38
55
|
* Get or create installation ID
|
|
39
56
|
*/
|
|
@@ -72,11 +89,14 @@ class InstallationIdentity {
|
|
|
72
89
|
* Handles event collection and transport
|
|
73
90
|
*/
|
|
74
91
|
export class TelemetryClient {
|
|
92
|
+
identity;
|
|
93
|
+
enabled;
|
|
94
|
+
batchSize = 10;
|
|
95
|
+
eventQueue = [];
|
|
96
|
+
flushTimer = null;
|
|
97
|
+
coreVersion;
|
|
98
|
+
runtime = 'node';
|
|
75
99
|
constructor(coreVersion = '0.5.7') {
|
|
76
|
-
this.batchSize = 10;
|
|
77
|
-
this.eventQueue = [];
|
|
78
|
-
this.flushTimer = null;
|
|
79
|
-
this.runtime = 'node';
|
|
80
100
|
this.coreVersion = coreVersion;
|
|
81
101
|
this.identity = new InstallationIdentity();
|
|
82
102
|
// Check if telemetry is disabled
|
package/dist/transaction.d.ts
CHANGED
|
@@ -22,6 +22,50 @@
|
|
|
22
22
|
* a runtime that cannot provide that path refuses the transaction rather than
|
|
23
23
|
* silently degrading it into a sequence of independent writes.
|
|
24
24
|
*/
|
|
25
|
+
/**
|
|
26
|
+
* What must be true of one record for the transaction to commit.
|
|
27
|
+
*
|
|
28
|
+
* Evaluated by the authority inside the same atomic boundary as the writes.
|
|
29
|
+
* A client-side check cannot provide this: between the check and the write,
|
|
30
|
+
* another writer commits.
|
|
31
|
+
*
|
|
32
|
+
* Every predicate is optional and only a supplied one is checked. `version` is
|
|
33
|
+
* the record's `__version` -- the field you read back and pass to
|
|
34
|
+
* `updateIfVersion` -- and it is owned by the writer, so a transaction that
|
|
35
|
+
* wants to exclude its own successor must write an advanced `__version`.
|
|
36
|
+
*/
|
|
37
|
+
export interface AtomicTransactionPrecondition {
|
|
38
|
+
collection: string;
|
|
39
|
+
id: string;
|
|
40
|
+
/** The record must not exist. */
|
|
41
|
+
requireAbsent?: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* The record's current `__version`, spelled as `Collection.updateIfVersion`
|
|
44
|
+
* spells it.
|
|
45
|
+
*
|
|
46
|
+
* `ifVersion` and `expectedVersion` are the same predicate; `ifVersion` is
|
|
47
|
+
* the public spelling and `expectedVersion` is what goes on the wire.
|
|
48
|
+
* Supplying both with different values is an error rather than a silent
|
|
49
|
+
* preference, because a caller who wrote two different numbers did not mean
|
|
50
|
+
* either of them.
|
|
51
|
+
*/
|
|
52
|
+
ifVersion?: number;
|
|
53
|
+
/** The record's current `__version`. Wire spelling of `ifVersion`. */
|
|
54
|
+
expectedVersion?: number;
|
|
55
|
+
/** The record's authority epoch. */
|
|
56
|
+
expectedEpoch?: number;
|
|
57
|
+
/** The id of an unexpired lease the caller believes it holds. */
|
|
58
|
+
expectedLeaseId?: string;
|
|
59
|
+
}
|
|
60
|
+
/** Predicates that may be attached to a staged write. */
|
|
61
|
+
export interface AtomicTransactionGuard {
|
|
62
|
+
requireAbsent?: boolean;
|
|
63
|
+
/** Public spelling of `expectedVersion`; the two are one predicate. */
|
|
64
|
+
ifVersion?: number;
|
|
65
|
+
expectedVersion?: number;
|
|
66
|
+
expectedEpoch?: number;
|
|
67
|
+
expectedLeaseId?: string;
|
|
68
|
+
}
|
|
25
69
|
/** One staged operation. An absent value means delete. */
|
|
26
70
|
export interface AtomicTransactionOperationRequest {
|
|
27
71
|
collection: string;
|
|
@@ -29,10 +73,47 @@ export interface AtomicTransactionOperationRequest {
|
|
|
29
73
|
value?: Record<string, unknown>;
|
|
30
74
|
/** When set, the record must not already exist for the transaction to commit. */
|
|
31
75
|
requireAbsent?: boolean;
|
|
76
|
+
/** Public spelling of `expectedVersion`; the two are one predicate. */
|
|
77
|
+
ifVersion?: number;
|
|
78
|
+
/** Fence this write on the record's current `__version`. */
|
|
79
|
+
expectedVersion?: number;
|
|
80
|
+
expectedEpoch?: number;
|
|
81
|
+
expectedLeaseId?: string;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* A transaction described in full, rather than staged through a callback.
|
|
85
|
+
*
|
|
86
|
+
* The same request the builder produces, taking the same path to the same
|
|
87
|
+
* authority call. It exists because a caller that already knows every operation
|
|
88
|
+
* — because it read state, decided, and is now committing that decision — has
|
|
89
|
+
* nothing to stage.
|
|
90
|
+
*
|
|
91
|
+
* ```ts
|
|
92
|
+
* await db.transaction({
|
|
93
|
+
* transactionId,
|
|
94
|
+
* preconditions: [{ collection: 'sessions', id, ifVersion: version }],
|
|
95
|
+
* operations: [
|
|
96
|
+
* { collection: 'sessions', id, value: nextSession },
|
|
97
|
+
* { collection: 'changes', id: changeId, value: change },
|
|
98
|
+
* ],
|
|
99
|
+
* });
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
102
|
+
export interface AtomicTransactionDocument {
|
|
103
|
+
transactionId?: string;
|
|
104
|
+
preconditions?: AtomicTransactionPrecondition[];
|
|
105
|
+
operations: AtomicTransactionOperationRequest[];
|
|
32
106
|
}
|
|
33
107
|
export interface AtomicTransactionRequest {
|
|
34
108
|
transactionId: string;
|
|
35
109
|
operations: AtomicTransactionOperationRequest[];
|
|
110
|
+
/**
|
|
111
|
+
* Conditions on records the transaction need not write. Read/decide/write
|
|
112
|
+
* fencing routinely guards on a record it does not modify -- a session
|
|
113
|
+
* generation, an ownership epoch -- and expressing that as a no-op write
|
|
114
|
+
* would make the guard a mutation.
|
|
115
|
+
*/
|
|
116
|
+
preconditions?: AtomicTransactionPrecondition[];
|
|
36
117
|
}
|
|
37
118
|
export interface AtomicTransactionResult {
|
|
38
119
|
/** Durable identity of the transaction. Stable across restart and replay. */
|
|
@@ -46,9 +127,7 @@ export interface AtomicTransactionResult {
|
|
|
46
127
|
/** Staging surface for one collection inside a transaction. */
|
|
47
128
|
export interface AtomicTransactionCollection<T = unknown> {
|
|
48
129
|
/** Stage a write. */
|
|
49
|
-
set(id: string | number, value: T, options?:
|
|
50
|
-
requireAbsent?: boolean;
|
|
51
|
-
}): AtomicTransactionCollection<T>;
|
|
130
|
+
set(id: string | number, value: T, options?: AtomicTransactionGuard): AtomicTransactionCollection<T>;
|
|
52
131
|
/**
|
|
53
132
|
* Stage a write, in the argument order used by `Collection.insert`.
|
|
54
133
|
*
|
|
@@ -56,21 +135,27 @@ export interface AtomicTransactionCollection<T = unknown> {
|
|
|
56
135
|
* rest of the collection API does not have to change shape inside a
|
|
57
136
|
* transaction.
|
|
58
137
|
*/
|
|
59
|
-
insert(value: T, id: string | number, options?:
|
|
60
|
-
requireAbsent?: boolean;
|
|
61
|
-
}): AtomicTransactionCollection<T>;
|
|
138
|
+
insert(value: T, id: string | number, options?: AtomicTransactionGuard): AtomicTransactionCollection<T>;
|
|
62
139
|
/** Stage a delete. */
|
|
63
|
-
delete(id: string | number, options?:
|
|
64
|
-
requireAbsent?: boolean;
|
|
65
|
-
}): AtomicTransactionCollection<T>;
|
|
140
|
+
delete(id: string | number, options?: AtomicTransactionGuard): AtomicTransactionCollection<T>;
|
|
66
141
|
}
|
|
67
142
|
/** The handle passed to a transaction callback. */
|
|
68
143
|
export interface AtomicTransactionScope {
|
|
69
144
|
/** The transaction's durable identity, available while staging. */
|
|
70
145
|
readonly transactionId: string;
|
|
71
146
|
collection<T = unknown>(name: string): AtomicTransactionCollection<T>;
|
|
147
|
+
/**
|
|
148
|
+
* Require something of a record without writing it.
|
|
149
|
+
*
|
|
150
|
+
* ```ts
|
|
151
|
+
* tx.require({ collection: 'sessions', id: 's1', expectedVersion: 12 });
|
|
152
|
+
* ```
|
|
153
|
+
*/
|
|
154
|
+
require(condition: AtomicTransactionPrecondition): AtomicTransactionScope;
|
|
72
155
|
/** Operations staged so far. */
|
|
73
156
|
readonly operations: readonly AtomicTransactionOperationRequest[];
|
|
157
|
+
/** Preconditions staged so far. */
|
|
158
|
+
readonly preconditions: readonly AtomicTransactionPrecondition[];
|
|
74
159
|
}
|
|
75
160
|
export interface AtomicTransactionOptions {
|
|
76
161
|
/**
|
|
@@ -84,14 +169,47 @@ export declare class AtomicTransactionError extends Error {
|
|
|
84
169
|
readonly transactionId: string;
|
|
85
170
|
constructor(message: string, transactionId: string);
|
|
86
171
|
}
|
|
172
|
+
/** Which predicate failed, and what the authority actually holds. */
|
|
173
|
+
export interface ConditionalConflictDetail {
|
|
174
|
+
predicate: 'version' | 'epoch' | 'lease' | 'present' | 'missing';
|
|
175
|
+
collection: string;
|
|
176
|
+
key: string;
|
|
177
|
+
expected?: number | string;
|
|
178
|
+
actual?: number | string | null;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* A transaction was refused because a precondition did not hold.
|
|
182
|
+
*
|
|
183
|
+
* Distinct from every other failure a transaction can have, and carried as a
|
|
184
|
+
* type rather than a message: a conflict means re-read and decide again, while
|
|
185
|
+
* an authentication, network, validation or server error means something else
|
|
186
|
+
* entirely, and a caller must not have to parse prose to tell them apart.
|
|
187
|
+
*
|
|
188
|
+
* A conflict guarantees that **zero** operations committed.
|
|
189
|
+
*/
|
|
190
|
+
export declare class ConditionalConflictError extends AtomicTransactionError {
|
|
191
|
+
readonly code: "PRECONDITION_FAILED";
|
|
192
|
+
readonly failure?: ConditionalConflictDetail;
|
|
193
|
+
constructor(message: string, transactionId: string, failure?: ConditionalConflictDetail);
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Normalise a precondition to the single wire field.
|
|
197
|
+
*
|
|
198
|
+
* `ifVersion` is the public spelling and `expectedVersion` is the wire's. They
|
|
199
|
+
* are one predicate, so two different values for it is a caller error.
|
|
200
|
+
*/
|
|
201
|
+
export declare function normalisePrecondition(condition: AtomicTransactionPrecondition): AtomicTransactionPrecondition;
|
|
87
202
|
/** Collects operations without performing any I/O. */
|
|
88
203
|
export declare class AtomicTransactionBuilder implements AtomicTransactionScope {
|
|
89
204
|
readonly transactionId: string;
|
|
90
205
|
private readonly staged;
|
|
206
|
+
private readonly required;
|
|
91
207
|
private sealed;
|
|
92
208
|
constructor(transactionId?: string);
|
|
93
209
|
get operations(): readonly AtomicTransactionOperationRequest[];
|
|
94
210
|
collection<T = unknown>(name: string): AtomicTransactionCollection<T>;
|
|
211
|
+
get preconditions(): readonly AtomicTransactionPrecondition[];
|
|
212
|
+
require(condition: AtomicTransactionPrecondition): AtomicTransactionScope;
|
|
95
213
|
/** Freeze the builder and produce the request to send. */
|
|
96
214
|
seal(): AtomicTransactionRequest;
|
|
97
215
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transaction.d.ts","sourceRoot":"","sources":["../src/transaction.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,0DAA0D;AAC1D,MAAM,WAAW,iCAAiC;IAChD,UAAU,EAAE,MAAM,CAAC;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,iFAAiF;IACjF,aAAa,CAAC,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"transaction.d.ts","sourceRoot":"","sources":["../src/transaction.ts"],"names":[],"mappings":"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"}
|
package/dist/transaction.js
CHANGED
|
@@ -24,21 +24,58 @@
|
|
|
24
24
|
*/
|
|
25
25
|
/** Thrown when a transaction cannot be committed. Nothing was applied. */
|
|
26
26
|
export class AtomicTransactionError extends Error {
|
|
27
|
+
transactionId;
|
|
27
28
|
constructor(message, transactionId) {
|
|
28
29
|
super(message);
|
|
29
30
|
this.name = 'AtomicTransactionError';
|
|
30
31
|
this.transactionId = transactionId;
|
|
31
32
|
}
|
|
32
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* A transaction was refused because a precondition did not hold.
|
|
36
|
+
*
|
|
37
|
+
* Distinct from every other failure a transaction can have, and carried as a
|
|
38
|
+
* type rather than a message: a conflict means re-read and decide again, while
|
|
39
|
+
* an authentication, network, validation or server error means something else
|
|
40
|
+
* entirely, and a caller must not have to parse prose to tell them apart.
|
|
41
|
+
*
|
|
42
|
+
* A conflict guarantees that **zero** operations committed.
|
|
43
|
+
*/
|
|
44
|
+
export class ConditionalConflictError extends AtomicTransactionError {
|
|
45
|
+
code = 'PRECONDITION_FAILED';
|
|
46
|
+
failure;
|
|
47
|
+
constructor(message, transactionId, failure) {
|
|
48
|
+
super(message, transactionId);
|
|
49
|
+
this.name = 'ConditionalConflictError';
|
|
50
|
+
this.failure = failure;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Normalise a precondition to the single wire field.
|
|
55
|
+
*
|
|
56
|
+
* `ifVersion` is the public spelling and `expectedVersion` is the wire's. They
|
|
57
|
+
* are one predicate, so two different values for it is a caller error.
|
|
58
|
+
*/
|
|
59
|
+
export function normalisePrecondition(condition) {
|
|
60
|
+
const { ifVersion, expectedVersion, ...rest } = condition;
|
|
61
|
+
if (ifVersion !== undefined && expectedVersion !== undefined && ifVersion !== expectedVersion) {
|
|
62
|
+
throw new Error(`precondition on ${condition.collection}:${condition.id} supplies ifVersion `
|
|
63
|
+
+ `${ifVersion} and expectedVersion ${expectedVersion}; they are the same predicate`);
|
|
64
|
+
}
|
|
65
|
+
const version = ifVersion ?? expectedVersion;
|
|
66
|
+
return version === undefined ? { ...rest } : { ...rest, expectedVersion: version };
|
|
67
|
+
}
|
|
33
68
|
function generateTransactionId() {
|
|
34
69
|
const random = Math.random().toString(36).slice(2, 10);
|
|
35
70
|
return `tx_${Date.now().toString(36)}_${random}`;
|
|
36
71
|
}
|
|
37
72
|
/** Collects operations without performing any I/O. */
|
|
38
73
|
export class AtomicTransactionBuilder {
|
|
74
|
+
transactionId;
|
|
75
|
+
staged = [];
|
|
76
|
+
required = [];
|
|
77
|
+
sealed = false;
|
|
39
78
|
constructor(transactionId) {
|
|
40
|
-
this.staged = [];
|
|
41
|
-
this.sealed = false;
|
|
42
79
|
this.transactionId = transactionId || generateTransactionId();
|
|
43
80
|
}
|
|
44
81
|
get operations() {
|
|
@@ -47,7 +84,17 @@ export class AtomicTransactionBuilder {
|
|
|
47
84
|
collection(name) {
|
|
48
85
|
if (!name || !name.trim())
|
|
49
86
|
throw new Error('collection name is required');
|
|
50
|
-
const stage = (
|
|
87
|
+
const stage = (staged) => {
|
|
88
|
+
// Normalised here so that an `ifVersion` written on an operation cannot
|
|
89
|
+
// be silently ignored — a declared-but-dropped predicate is the defect
|
|
90
|
+
// this whole capability exists to remove.
|
|
91
|
+
const { ifVersion, expectedVersion, ...rest } = staged;
|
|
92
|
+
if (ifVersion !== undefined && expectedVersion !== undefined && ifVersion !== expectedVersion) {
|
|
93
|
+
throw new Error(`${staged.collection}:${staged.id} supplies ifVersion ${ifVersion} and expectedVersion `
|
|
94
|
+
+ `${expectedVersion}; they are the same predicate`);
|
|
95
|
+
}
|
|
96
|
+
const version = ifVersion ?? expectedVersion;
|
|
97
|
+
const operation = version === undefined ? { ...rest } : { ...rest, expectedVersion: version };
|
|
51
98
|
if (this.sealed) {
|
|
52
99
|
throw new Error('this transaction has already been committed; start a new one');
|
|
53
100
|
}
|
|
@@ -67,20 +114,59 @@ export class AtomicTransactionBuilder {
|
|
|
67
114
|
id: String(id),
|
|
68
115
|
value: value,
|
|
69
116
|
requireAbsent: options?.requireAbsent,
|
|
117
|
+
ifVersion: options?.ifVersion,
|
|
118
|
+
expectedVersion: options?.expectedVersion,
|
|
119
|
+
expectedEpoch: options?.expectedEpoch,
|
|
120
|
+
expectedLeaseId: options?.expectedLeaseId,
|
|
70
121
|
});
|
|
71
122
|
return surface;
|
|
72
123
|
},
|
|
73
124
|
insert: (value, id, options) => surface.set(id, value, options),
|
|
74
125
|
delete: (id, options) => {
|
|
75
|
-
stage({
|
|
126
|
+
stage({
|
|
127
|
+
collection: name,
|
|
128
|
+
id: String(id),
|
|
129
|
+
requireAbsent: options?.requireAbsent,
|
|
130
|
+
ifVersion: options?.ifVersion,
|
|
131
|
+
expectedVersion: options?.expectedVersion,
|
|
132
|
+
expectedEpoch: options?.expectedEpoch,
|
|
133
|
+
expectedLeaseId: options?.expectedLeaseId,
|
|
134
|
+
});
|
|
76
135
|
return surface;
|
|
77
136
|
},
|
|
78
137
|
};
|
|
79
138
|
return surface;
|
|
80
139
|
}
|
|
140
|
+
get preconditions() {
|
|
141
|
+
return this.required;
|
|
142
|
+
}
|
|
143
|
+
require(condition) {
|
|
144
|
+
if (this.sealed) {
|
|
145
|
+
throw new Error('this transaction has already been committed; start a new one');
|
|
146
|
+
}
|
|
147
|
+
if (!condition.collection?.trim() || !String(condition.id ?? '').trim()) {
|
|
148
|
+
throw new Error('a precondition requires a collection and an id');
|
|
149
|
+
}
|
|
150
|
+
const normalised = normalisePrecondition(condition);
|
|
151
|
+
const constrains = normalised.requireAbsent === true
|
|
152
|
+
|| normalised.expectedVersion !== undefined
|
|
153
|
+
|| normalised.expectedEpoch !== undefined
|
|
154
|
+
|| normalised.expectedLeaseId !== undefined;
|
|
155
|
+
if (!constrains) {
|
|
156
|
+
// Refused rather than ignored: a precondition that constrains nothing
|
|
157
|
+
// reads as protection and provides none.
|
|
158
|
+
throw new Error(`precondition on ${condition.collection}:${condition.id} constrains nothing`);
|
|
159
|
+
}
|
|
160
|
+
this.required.push({ ...normalised, id: String(condition.id) });
|
|
161
|
+
return this;
|
|
162
|
+
}
|
|
81
163
|
/** Freeze the builder and produce the request to send. */
|
|
82
164
|
seal() {
|
|
83
165
|
this.sealed = true;
|
|
84
|
-
return {
|
|
166
|
+
return {
|
|
167
|
+
transactionId: this.transactionId,
|
|
168
|
+
operations: [...this.staged],
|
|
169
|
+
preconditions: [...this.required],
|
|
170
|
+
};
|
|
85
171
|
}
|
|
86
172
|
}
|
|
Binary file
|
package/dist/worker.js
CHANGED
package/dist/workload.js
CHANGED
|
@@ -7,17 +7,18 @@
|
|
|
7
7
|
import { classifyWorkspaceActivity, evaluateVerification, transitionInvestigation, transitionRemediation, transitionVerification, } from './runtime-investigation.js';
|
|
8
8
|
import { createDevelopmentWorkspace, isValidWorkspaceId, } from './workspace-identity.js';
|
|
9
9
|
export class DevelopmentNode {
|
|
10
|
+
workspaces = new Map();
|
|
11
|
+
tasks = new Map();
|
|
12
|
+
codeChanges = new Map();
|
|
13
|
+
verificationResults = new Map();
|
|
14
|
+
runtimeObservations = new Map();
|
|
15
|
+
investigations = new Map();
|
|
16
|
+
remediationContracts = new Map();
|
|
17
|
+
workspaceActivities = new Map();
|
|
18
|
+
verificationAttempts = new Map();
|
|
19
|
+
isRunning = false;
|
|
20
|
+
dataDir;
|
|
10
21
|
constructor(config = {}) {
|
|
11
|
-
this.workspaces = new Map();
|
|
12
|
-
this.tasks = new Map();
|
|
13
|
-
this.codeChanges = new Map();
|
|
14
|
-
this.verificationResults = new Map();
|
|
15
|
-
this.runtimeObservations = new Map();
|
|
16
|
-
this.investigations = new Map();
|
|
17
|
-
this.remediationContracts = new Map();
|
|
18
|
-
this.workspaceActivities = new Map();
|
|
19
|
-
this.verificationAttempts = new Map();
|
|
20
|
-
this.isRunning = false;
|
|
21
22
|
this.dataDir = config.dataDir || '.feltdb';
|
|
22
23
|
}
|
|
23
24
|
async start() {
|
|
@@ -16,6 +16,8 @@ import path from 'node:path';
|
|
|
16
16
|
import { createRemediationContract, evaluateVerification, transitionInvestigation, transitionRemediation, transitionVerification, } from './runtime-investigation.js';
|
|
17
17
|
import { trackRemediation } from './git-identity.js';
|
|
18
18
|
export class InvestigationLifecycleManager {
|
|
19
|
+
workspaceDir;
|
|
20
|
+
investigationsDir;
|
|
19
21
|
constructor(workspaceDir) {
|
|
20
22
|
this.workspaceDir = workspaceDir;
|
|
21
23
|
this.investigationsDir = path.join(workspaceDir, '.feltdb', 'investigations');
|
|
@@ -21,9 +21,11 @@ import { captureRepositoryIdentity } from './git-identity.js';
|
|
|
21
21
|
import { agentRemediationHandoff } from './runtime-investigation.js';
|
|
22
22
|
import { connectDevelopmentWorkspace } from './workspace-connection.js';
|
|
23
23
|
export class RuntimeInvestigationSupervisor {
|
|
24
|
+
investigations;
|
|
25
|
+
options;
|
|
26
|
+
connection = null;
|
|
27
|
+
seenObservations = new Set();
|
|
24
28
|
constructor(options) {
|
|
25
|
-
this.connection = null;
|
|
26
|
-
this.seenObservations = new Set();
|
|
27
29
|
this.options = options;
|
|
28
30
|
this.investigations = new InvestigationLifecycleManager(options.projectDir);
|
|
29
31
|
// Observations already turned into investigations must not re-open on a
|
|
@@ -64,7 +66,7 @@ export class RuntimeInvestigationSupervisor {
|
|
|
64
66
|
* verification rather than a false VERIFIED.
|
|
65
67
|
*/
|
|
66
68
|
async awaitObservation(criterion, options = { after: 0 }) {
|
|
67
|
-
const deadline = Date.now() + (options.timeoutMs ??
|
|
69
|
+
const deadline = Date.now() + (options.timeoutMs ?? 10_000);
|
|
68
70
|
const interval = options.intervalMs ?? 100;
|
|
69
71
|
while (Date.now() < deadline) {
|
|
70
72
|
const match = (await this.observations()).find(observation => observation.timestamp > options.after
|
|
@@ -166,11 +166,19 @@ function generateClientId(clientType) {
|
|
|
166
166
|
* Provides methods to publish, subscribe, and query shared state.
|
|
167
167
|
*/
|
|
168
168
|
export class DevelopmentWorkspaceConnection {
|
|
169
|
+
workspaceId;
|
|
170
|
+
/** Canonical `feltdb dev` session identity, when supplied by pairing/session configuration. */
|
|
171
|
+
sessionId;
|
|
172
|
+
/** Canonical active runtime identity, when supplied by pairing/session configuration. */
|
|
173
|
+
runtimeInstanceId;
|
|
174
|
+
clientId;
|
|
175
|
+
clientType;
|
|
176
|
+
options;
|
|
177
|
+
subscriptions = new Map();
|
|
178
|
+
connectedAt = 0;
|
|
179
|
+
transport = null;
|
|
180
|
+
unsubscribeEvents = null;
|
|
169
181
|
constructor(workspaceId, clientId, clientType, options) {
|
|
170
|
-
this.subscriptions = new Map();
|
|
171
|
-
this.connectedAt = 0;
|
|
172
|
-
this.transport = null;
|
|
173
|
-
this.unsubscribeEvents = null;
|
|
174
182
|
this.workspaceId = workspaceId;
|
|
175
183
|
this.sessionId = options.sessionId;
|
|
176
184
|
this.runtimeInstanceId = options.runtimeInstanceId;
|
|
@@ -471,8 +479,9 @@ const ENTITY_COLLECTION = '_development_workspace_entities';
|
|
|
471
479
|
const EVENT_COLLECTION = '_development_workspace_events';
|
|
472
480
|
const CLIENT_COLLECTION = '_development_workspace_clients';
|
|
473
481
|
class HttpWorkspaceTransport {
|
|
482
|
+
db;
|
|
483
|
+
seenEvents = new Set();
|
|
474
484
|
constructor(endpoint, token) {
|
|
475
|
-
this.seenEvents = new Set();
|
|
476
485
|
this.db = new HttpJsDb({ url: endpoint, token });
|
|
477
486
|
}
|
|
478
487
|
async assertReachable() {
|
package/package.json
CHANGED
|
Binary file
|