@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.
Files changed (83) hide show
  1. package/README.md +32 -0
  2. package/dist/agent-registry.js +1 -3
  3. package/dist/agent-runtime.js +8 -7
  4. package/dist/analytics-backend.js +3 -1
  5. package/dist/application-contract.js +1 -0
  6. package/dist/application-manifest.js +1 -0
  7. package/dist/artifact.js +2 -0
  8. package/dist/authorization.js +2 -0
  9. package/dist/bundle.js +2 -0
  10. package/dist/capability.js +1 -3
  11. package/dist/cell.js +9 -4
  12. package/dist/cli/commands.js +26 -2
  13. package/dist/cli/index.js +1 -1
  14. package/dist/collection.js +39 -31
  15. package/dist/create/package-versions.js +1 -1
  16. package/dist/create/server-source/crates/feltdb/src/bin/feltdb_node.rs +613 -27
  17. package/dist/create/server-source/crates/feltdb/src/causal_backlog_bound.rs +452 -0
  18. package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier.rs +208 -0
  19. package/dist/create/server-source/crates/feltdb/src/convergence.rs +16 -0
  20. package/dist/create/server-source/crates/feltdb/src/dedup_bound_investigation.rs +402 -0
  21. package/dist/create/server-source/crates/feltdb/src/distributed_transactions.rs +784 -24
  22. package/dist/create/server-source/crates/feltdb/src/durable_operation_identity.rs +418 -0
  23. package/dist/create/server-source/crates/feltdb/src/lib.rs +284 -0
  24. package/dist/create/server-source/crates/feltdb/src/replica_acknowledgements.rs +471 -0
  25. package/dist/create/server-source/crates/feltdb/src/replica_membership.rs +661 -0
  26. package/dist/create/server-source/crates/feltdb/src/tcp_transport.rs +72 -3
  27. package/dist/create/server-source/crates/feltdb/src/transaction_preconditions.rs +899 -0
  28. package/dist/create/server-source/crates/feltdb-server/src/main.rs +116 -4
  29. package/dist/db.d.ts +2 -2
  30. package/dist/db.d.ts.map +1 -1
  31. package/dist/db.js +48 -15
  32. package/dist/development-runtime-bridge.js +1 -1
  33. package/dist/distributed-indexing.js +7 -5
  34. package/dist/embedded-transaction.d.ts +9 -0
  35. package/dist/embedded-transaction.d.ts.map +1 -1
  36. package/dist/embedded-transaction.js +95 -3
  37. package/dist/feltdb.d.ts +16 -0
  38. package/dist/feltdb.d.ts.map +1 -1
  39. package/dist/file-db.d.ts.map +1 -1
  40. package/dist/file-db.js +9 -3
  41. package/dist/flowspec.js +2 -1
  42. package/dist/http-client.js +2 -0
  43. package/dist/http-db.d.ts +11 -0
  44. package/dist/http-db.d.ts.map +1 -1
  45. package/dist/http-db.js +32 -3
  46. package/dist/identity.js +1 -0
  47. package/dist/index-analytics.js +6 -7
  48. package/dist/index-backend.js +3 -3
  49. package/dist/index-dashboard.js +10 -13
  50. package/dist/index-manager.js +12 -11
  51. package/dist/index-monitoring.js +9 -4
  52. package/dist/index-store.js +2 -0
  53. package/dist/indexeddb-db.d.ts.map +1 -1
  54. package/dist/indexeddb-db.js +32 -23
  55. package/dist/memory-db.d.ts.map +1 -1
  56. package/dist/memory-db.js +8 -4
  57. package/dist/observe.js +2 -0
  58. package/dist/provider.js +2 -0
  59. package/dist/query-planner.js +2 -4
  60. package/dist/reactive-graph.js +6 -8
  61. package/dist/release.js +2 -0
  62. package/dist/sharding.js +11 -6
  63. package/dist/state-contract.js +3 -3
  64. package/dist/studio-app/assets/{feltdb_wasm-CJv3wHzi.js → feltdb_wasm-CD744e5D.js} +1 -1
  65. package/dist/studio-app/assets/feltdb_wasm_bg-CiIXhOLi.wasm +0 -0
  66. package/dist/studio-app/assets/index-DoROs8yx.js +28 -0
  67. package/dist/studio-app/index.html +1 -1
  68. package/dist/sync-contract.js +9 -2
  69. package/dist/telemetry.d.ts.map +1 -1
  70. package/dist/telemetry.js +32 -12
  71. package/dist/transaction.d.ts +127 -9
  72. package/dist/transaction.d.ts.map +1 -1
  73. package/dist/transaction.js +91 -5
  74. package/dist/wasm/feltdb_wasm_bg.wasm +0 -0
  75. package/dist/worker.js +2 -0
  76. package/dist/workload.js +2 -0
  77. package/dist/workspace/development-node.js +11 -10
  78. package/dist/workspace/investigation-lifecycle-manager.js +2 -0
  79. package/dist/workspace/investigation-supervisor.js +5 -3
  80. package/dist/workspace/workspace-connection.js +14 -5
  81. package/package.json +1 -1
  82. package/dist/studio-app/assets/feltdb_wasm_bg-C8TG8r2n.wasm +0 -0
  83. 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-DospFFYE.js"></script>
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>
@@ -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
  }
@@ -1 +1 @@
1
- {"version":3,"file":"telemetry.d.ts","sourceRoot":"","sources":["../src/telemetry.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAyBH;;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"}
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
- const browserCrypto = globalThis.crypto;
12
- if (typeof browserCrypto?.randomUUID === 'function')
13
- return browserCrypto.randomUUID();
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
- browserCrypto.getRandomValues(bytes);
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
- constructor() {
34
- this.id = null;
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
@@ -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;CACzB;AAED,MAAM,WAAW,wBAAwB;IACvC,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,iCAAiC,EAAE,CAAC;CACjD;AAED,MAAM,WAAW,uBAAuB;IACtC,6EAA6E;IAC7E,aAAa,EAAE,MAAM,CAAC;IACtB,0EAA0E;IAC1E,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,+DAA+D;AAC/D,MAAM,WAAW,2BAA2B,CAAC,CAAC,GAAG,OAAO;IACtD,qBAAqB;IACrB,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE;QAAE,aAAa,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,2BAA2B,CAAC,CAAC,CAAC,CAAC;IAC1G;;;;;;OAMG;IACH,MAAM,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,aAAa,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,2BAA2B,CAAC,CAAC,CAAC,CAAC;IAC7G,sBAAsB;IACtB,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,aAAa,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,2BAA2B,CAAC,CAAC,CAAC,CAAC;CACpG;AAED,mDAAmD;AACnD,MAAM,WAAW,sBAAsB;IACrC,mEAAmE;IACnE,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,UAAU,CAAC,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,2BAA2B,CAAC,CAAC,CAAC,CAAC;IACtE,gCAAgC;IAChC,QAAQ,CAAC,UAAU,EAAE,SAAS,iCAAiC,EAAE,CAAC;CACnE;AAED,MAAM,WAAW,wBAAwB;IACvC;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,0EAA0E;AAC1E,qBAAa,sBAAuB,SAAQ,KAAK;IAC/C,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;gBACnB,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM;CAKnD;AAOD,sDAAsD;AACtD,qBAAa,wBAAyB,YAAW,sBAAsB;IACrE,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA2C;IAClE,OAAO,CAAC,MAAM,CAAS;gBAEX,aAAa,CAAC,EAAE,MAAM;IAIlC,IAAI,UAAU,IAAI,SAAS,iCAAiC,EAAE,CAE7D;IAED,UAAU,CAAC,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,2BAA2B,CAAC,CAAC,CAAC;IAuCrE,0DAA0D;IAC1D,IAAI,IAAI,wBAAwB;CAIjC"}
1
+ {"version":3,"file":"transaction.d.ts","sourceRoot":"","sources":["../src/transaction.ts"],"names":[],"mappings":"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"}
@@ -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 = (operation) => {
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({ collection: name, id: String(id), requireAbsent: options?.requireAbsent });
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 { transactionId: this.transactionId, operations: [...this.staged] };
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
@@ -1,4 +1,6 @@
1
1
  class MeshTransport {
2
+ baseUrl;
3
+ fetcher;
2
4
  constructor(baseUrl = '', fetcher = fetch) {
3
5
  this.baseUrl = baseUrl;
4
6
  this.fetcher = fetcher;
package/dist/workload.js CHANGED
@@ -1,4 +1,6 @@
1
1
  export class WorkloadClient {
2
+ baseUrl;
3
+ fetcher;
2
4
  constructor(baseUrl = '', fetcher = fetch) {
3
5
  this.baseUrl = baseUrl;
4
6
  this.fetcher = fetcher;
@@ -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 ?? 10000);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@feltdb/core",
3
- "version": "0.6.13",
3
+ "version": "0.7.0",
4
4
  "description": "FeltDB Core - Application-facing database with Browser, Local, and Server runtimes. Durable state, reactive APIs.",
5
5
  "license": "MIT",
6
6
  "type": "module",