@feltdb/core 0.6.9 → 0.6.11

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 (122) hide show
  1. package/dist/cli/application-lifecycle.js +112 -0
  2. package/dist/cli/application.js +10 -0
  3. package/dist/cli/commands.js +407 -119
  4. package/dist/cli/development-session.js +54 -0
  5. package/dist/cli/index.js +1 -1
  6. package/dist/cli/workspace-integration.js +146 -20
  7. package/dist/collection.d.ts +110 -2
  8. package/dist/collection.d.ts.map +1 -1
  9. package/dist/collection.js +228 -3
  10. package/dist/create/cli.js +4 -2
  11. package/dist/create/create.js +42 -6
  12. package/dist/create/frameworks.js +18 -0
  13. package/dist/create/package-versions.js +1 -1
  14. package/dist/create/server-source/crates/feltdb/src/bin/feltdb_node.rs +408 -178
  15. package/dist/create/server-source/crates/feltdb/src/lib.rs +49 -5
  16. package/dist/create/server-source/crates/feltdb/src/multi_operation_transaction.rs +659 -0
  17. package/dist/create/server-source/crates/feltdb/src/p1_application_atomicity.rs +285 -0
  18. package/dist/create/server-source/crates/feltdb/src/p1_atomicity_acceptance.rs +582 -0
  19. package/dist/create/server-source/crates/feltdb-server/src/main.rs +141 -0
  20. package/dist/create/workspace-initialization.js +4 -5
  21. package/dist/db.d.ts +63 -1
  22. package/dist/db.d.ts.map +1 -1
  23. package/dist/db.js +145 -4
  24. package/dist/development-runtime-bridge.d.ts +60 -0
  25. package/dist/development-runtime-bridge.d.ts.map +1 -0
  26. package/dist/development-runtime-bridge.js +171 -0
  27. package/dist/embedded-transaction.d.ts +77 -0
  28. package/dist/embedded-transaction.d.ts.map +1 -0
  29. package/dist/embedded-transaction.js +102 -0
  30. package/dist/feltdb.d.ts +49 -1
  31. package/dist/feltdb.d.ts.map +1 -1
  32. package/dist/file-db.d.ts +33 -0
  33. package/dist/file-db.d.ts.map +1 -1
  34. package/dist/file-db.js +128 -4
  35. package/dist/freshness.d.ts +119 -0
  36. package/dist/freshness.d.ts.map +1 -0
  37. package/dist/freshness.js +115 -0
  38. package/dist/http-db.d.ts +48 -0
  39. package/dist/http-db.d.ts.map +1 -1
  40. package/dist/http-db.js +77 -1
  41. package/dist/index-backend.d.ts +8 -0
  42. package/dist/index-backend.d.ts.map +1 -1
  43. package/dist/index-backend.js +13 -0
  44. package/dist/index-core.d.ts +80 -0
  45. package/dist/index-core.d.ts.map +1 -0
  46. package/dist/index-core.js +74 -0
  47. package/dist/index-manager.d.ts +11 -0
  48. package/dist/index-manager.d.ts.map +1 -1
  49. package/dist/index-manager.js +30 -13
  50. package/dist/index-types.d.ts +17 -0
  51. package/dist/index-types.d.ts.map +1 -1
  52. package/dist/index.browser.d.ts +14 -0
  53. package/dist/index.browser.d.ts.map +1 -0
  54. package/dist/index.browser.js +13 -0
  55. package/dist/index.d.ts +8 -59
  56. package/dist/index.d.ts.map +1 -1
  57. package/dist/index.js +11 -54
  58. package/dist/indexeddb-db.d.ts +32 -0
  59. package/dist/indexeddb-db.d.ts.map +1 -1
  60. package/dist/indexeddb-db.js +151 -1
  61. package/dist/memory-db.d.ts +26 -0
  62. package/dist/memory-db.d.ts.map +1 -1
  63. package/dist/memory-db.js +86 -0
  64. package/dist/runtime-registry.d.ts +32 -0
  65. package/dist/runtime-registry.d.ts.map +1 -0
  66. package/dist/runtime-registry.js +38 -0
  67. package/dist/studio/app.d.ts +2 -1
  68. package/dist/studio/app.d.ts.map +1 -1
  69. package/dist/studio/components/StateExplorer.d.ts +2 -1
  70. package/dist/studio/components/StateExplorer.d.ts.map +1 -1
  71. package/dist/studio/components/index.js +1 -1
  72. package/dist/studio/{components-9kDSWiGL.js → components-C5p2TfIU.js} +25 -14
  73. package/dist/studio/index.js +234 -160
  74. package/dist/studio-app/assets/{feltdb_wasm-B4wq4mqp.js → feltdb_wasm-CJv3wHzi.js} +1 -1
  75. package/dist/studio-app/assets/feltdb_wasm_bg-C8TG8r2n.wasm +0 -0
  76. package/dist/studio-app/assets/index-DospFFYE.js +28 -0
  77. package/dist/studio-app/index.html +1 -1
  78. package/dist/transaction.d.ts +98 -0
  79. package/dist/transaction.d.ts.map +1 -0
  80. package/dist/transaction.js +86 -0
  81. package/dist/wasm/feltdb_wasm_bg.wasm +0 -0
  82. package/dist/workspace/browser.d.ts +22 -0
  83. package/dist/workspace/browser.d.ts.map +1 -0
  84. package/dist/workspace/browser.js +17 -0
  85. package/dist/workspace/development-node.d.ts +16 -0
  86. package/dist/workspace/development-node.d.ts.map +1 -1
  87. package/dist/workspace/development-node.js +85 -1
  88. package/dist/workspace/git-identity.d.ts +35 -0
  89. package/dist/workspace/git-identity.d.ts.map +1 -0
  90. package/dist/workspace/git-identity.js +81 -0
  91. package/dist/workspace/index.d.ts +12 -2
  92. package/dist/workspace/index.d.ts.map +1 -1
  93. package/dist/workspace/index.js +7 -1
  94. package/dist/workspace/investigation-analysis.d.ts +47 -0
  95. package/dist/workspace/investigation-analysis.d.ts.map +1 -0
  96. package/dist/workspace/investigation-analysis.js +181 -0
  97. package/dist/workspace/investigation-lifecycle-manager.d.ts +88 -0
  98. package/dist/workspace/investigation-lifecycle-manager.d.ts.map +1 -0
  99. package/dist/workspace/investigation-lifecycle-manager.js +259 -0
  100. package/dist/workspace/investigation-supervisor.d.ts +100 -0
  101. package/dist/workspace/investigation-supervisor.d.ts.map +1 -0
  102. package/dist/workspace/investigation-supervisor.js +238 -0
  103. package/dist/workspace/runtime-investigation.d.ts +5 -1
  104. package/dist/workspace/runtime-investigation.d.ts.map +1 -1
  105. package/dist/workspace/runtime-investigation.js +50 -21
  106. package/dist/workspace/runtime-observation.d.ts +82 -0
  107. package/dist/workspace/runtime-observation.d.ts.map +1 -0
  108. package/dist/workspace/runtime-observation.js +170 -0
  109. package/dist/workspace/runtime-observer.d.ts +57 -0
  110. package/dist/workspace/runtime-observer.d.ts.map +1 -0
  111. package/dist/workspace/runtime-observer.js +272 -0
  112. package/dist/workspace/workspace-connection.d.ts.map +1 -1
  113. package/dist/workspace/workspace-connection.js +14 -5
  114. package/dist/workspace/workspace-identity.d.ts +1 -1
  115. package/dist/workspace/workspace-identity.d.ts.map +1 -1
  116. package/dist/workspace/workspace-identity.js +4 -4
  117. package/dist/workspace/workspace-types.d.ts +208 -0
  118. package/dist/workspace/workspace-types.d.ts.map +1 -1
  119. package/package.json +12 -4
  120. package/dist/create/server-source/crates/feltdb/src/phase_1c_three_node.rs +0 -523
  121. package/dist/studio-app/assets/feltdb_wasm_bg-Ceyi7l21.wasm +0 -0
  122. package/dist/studio-app/assets/index-BF45M757.js +0 -28
@@ -1,3 +1,5 @@
1
+ import { FreshnessContractViolation } from './freshness.js';
2
+ import { decideTransaction, embeddedResult, EmbeddedTransactionRejected, } from './embedded-transaction.js';
1
3
  /** Durable browser runtime. Every mutation resolves only after its IndexedDB transaction commits. */
2
4
  export class IndexedDbJsDb {
3
5
  constructor(namespace) {
@@ -30,13 +32,16 @@ export class IndexedDbJsDb {
30
32
  }
31
33
  open(name) {
32
34
  return new Promise((resolve, reject) => {
33
- const request = indexedDB.open(name, 1);
35
+ const request = indexedDB.open(name, 2);
34
36
  request.onupgradeneeded = () => {
35
37
  const db = request.result;
36
38
  if (!db.objectStoreNames.contains('rows'))
37
39
  db.createObjectStore('rows');
38
40
  if (!db.objectStoreNames.contains('changes'))
39
41
  db.createObjectStore('changes', { keyPath: 'sequence', autoIncrement: true });
42
+ // Committed transaction ids, so redelivery after a reload is a no-op.
43
+ if (!db.objectStoreNames.contains('transactions'))
44
+ db.createObjectStore('transactions');
40
45
  };
41
46
  request.onsuccess = () => resolve(request.result);
42
47
  request.onerror = () => reject(request.error ?? new Error('open IndexedDB failed'));
@@ -91,6 +96,107 @@ export class IndexedDbJsDb {
91
96
  insert(key, value) { return this.mutate(key, JSON.parse(value), 'put'); }
92
97
  update(key, value) { return this.mutate(key, JSON.parse(value), 'put'); }
93
98
  delete(key) { return this.mutate(key, undefined, 'delete'); }
99
+ /**
100
+ * Commit several operations atomically and durably.
101
+ *
102
+ * Reads, precondition checks, and writes all happen inside a single
103
+ * IndexedDB readwrite transaction, which is the browser's own atomic commit
104
+ * boundary. If any step fails the transaction aborts and nothing is written,
105
+ * and the promise resolves only after the transaction has committed.
106
+ */
107
+ async commit_transaction(request) {
108
+ const db = await this.database;
109
+ const before = this.sequence;
110
+ // Structural problems — no operations, the same record written twice, an
111
+ // invalid name — do not depend on stored state, so they are settled before
112
+ // opening a transaction. Without this an empty transaction would open a
113
+ // transaction that reads nothing and therefore never reaches a decision.
114
+ const structural = decideTransaction(request, { exists: () => false, hasApplied: () => false });
115
+ if (structural.kind === 'reject')
116
+ throw new EmbeddedTransactionRejected(structural.reason);
117
+ // Applied transaction ids live in their own store so identity survives a
118
+ // page reload the same way records do.
119
+ const alreadyApplied = await this.request('transactions', store => store.get(request.transactionId));
120
+ if (alreadyApplied !== undefined) {
121
+ return embeddedResult(request, true, before, this.sequence);
122
+ }
123
+ const plan = await new Promise((resolve, reject) => {
124
+ let transaction;
125
+ try {
126
+ transaction = db.transaction(['rows', 'changes', 'transactions'], 'readwrite', { durability: 'strict' });
127
+ }
128
+ catch {
129
+ transaction = db.transaction(['rows', 'changes', 'transactions'], 'readwrite');
130
+ }
131
+ const rows = transaction.objectStore('rows');
132
+ const changes = transaction.objectStore('changes');
133
+ const transactions = transaction.objectStore('transactions');
134
+ // Read every touched record inside the transaction, so the precondition
135
+ // check and the write cannot be separated by another writer.
136
+ const present = new Map();
137
+ let pending = request.operations.length;
138
+ let decided = null;
139
+ const finish = () => {
140
+ const plan = decideTransaction(request, {
141
+ exists: key => present.get(key) === true,
142
+ hasApplied: () => false,
143
+ });
144
+ decided = plan;
145
+ if (plan.kind !== 'apply') {
146
+ transaction.abort();
147
+ return;
148
+ }
149
+ const timestamp = Date.now();
150
+ for (const mutation of plan.mutations) {
151
+ if (mutation.value === undefined)
152
+ rows.delete(mutation.key);
153
+ else
154
+ rows.put(mutation.value, mutation.key);
155
+ changes.add({
156
+ collection: mutation.collection,
157
+ key: mutation.key,
158
+ type: mutation.value === undefined ? 'delete' : 'put',
159
+ value: mutation.value,
160
+ timestamp,
161
+ origin: this.origin,
162
+ id: `${this.origin}:${timestamp}:${Math.random().toString(36).slice(2)}`,
163
+ });
164
+ }
165
+ transactions.put({ transactionId: request.transactionId, committedAt: timestamp }, request.transactionId);
166
+ };
167
+ for (const operation of request.operations) {
168
+ const key = `${operation.collection}:${operation.id}`;
169
+ const lookup = rows.getKey(key);
170
+ lookup.onsuccess = () => {
171
+ present.set(key, lookup.result !== undefined);
172
+ pending -= 1;
173
+ if (pending === 0)
174
+ finish();
175
+ };
176
+ lookup.onerror = () => reject(lookup.error ?? new Error('IndexedDB read failed'));
177
+ }
178
+ transaction.oncomplete = () => resolve(decided ?? { kind: 'reject', reason: 'transaction did not decide' });
179
+ transaction.onabort = () => {
180
+ if (decided && decided.kind !== 'apply')
181
+ resolve(decided);
182
+ else
183
+ reject(transaction.error ?? new Error('IndexedDB transaction aborted'));
184
+ };
185
+ transaction.onerror = () => {
186
+ if (decided && decided.kind !== 'apply')
187
+ return;
188
+ reject(transaction.error ?? new Error('IndexedDB transaction failed'));
189
+ };
190
+ });
191
+ if (plan.kind === 'duplicate')
192
+ return embeddedResult(request, true, before, this.sequence);
193
+ if (plan.kind === 'reject')
194
+ throw new EmbeddedTransactionRejected(plan.reason);
195
+ this.sequence += plan.mutations.length;
196
+ for (const mutation of plan.mutations)
197
+ this.announceLocalChange(mutation.collection, this.sequence);
198
+ return embeddedResult(request, false, before, this.sequence);
199
+ }
94
200
  async cas(params) {
95
201
  try {
96
202
  const db = await this.database;
@@ -342,6 +448,50 @@ export class IndexedDbJsDb {
342
448
  acknowledge_peer_operations() { return { success: true }; }
343
449
  instance_id() { return this.origin; }
344
450
  get_sequence() { return this.sequence; }
451
+ /**
452
+ * IndexedDB is the serialization authority for this database.
453
+ *
454
+ * The `changes` store is `autoIncrement`, so its key is assigned by the
455
+ * storage engine inside the same readwrite transaction that writes the record,
456
+ * and the browser serializes readwrite transactions on an object store across
457
+ * tabs. A second tab's commit therefore advances the same durable counter,
458
+ * with no coordination code of our own.
459
+ *
460
+ * Note what is *not* used here: `this.sequence` counts what this handle wrote
461
+ * and would miss every other tab. The durable key is the authoritative value.
462
+ */
463
+ freshness() {
464
+ if (this.peers.size > 0) {
465
+ return {
466
+ domain: 'replicated',
467
+ validation: 'refresh',
468
+ invalidation: true,
469
+ reason: 'sync peers order operations causally, so the local autoIncrement key is not authoritative across replicas',
470
+ };
471
+ }
472
+ return {
473
+ domain: 'local-first',
474
+ validation: 'revision',
475
+ invalidation: true,
476
+ revisionScope: this.origin,
477
+ };
478
+ }
479
+ /**
480
+ * The highest durable `changes` key.
481
+ *
482
+ * Read from the store rather than from memory so a write committed by another
483
+ * tab is included. An empty store is revision 0, which is a real value: it
484
+ * means nothing has been committed, and a cache built at 0 is still current.
485
+ */
486
+ async revision() {
487
+ const capability = this.freshness();
488
+ if (capability.validation !== 'revision') {
489
+ throw new FreshnessContractViolation(`browser runtime: ${capability.reason}`);
490
+ }
491
+ const key = await this.request('changes', store => store.openKeyCursor(null, 'prev'));
492
+ const cursor = key;
493
+ return { value: cursor ? Number(cursor.key) : 0, scope: this.origin };
494
+ }
345
495
  async audit_events() { return (await this.request('changes', store => store.getAll())).map((value, index) => ({ ...value, sequence: value.sequence ?? index + 1 })); }
346
496
  async export_operations(sinceSequence) { return (await this.audit_events()).filter(value => (value.sequence ?? 0) > sinceSequence); }
347
497
  async apply_remote_operations(operations) {
@@ -1,4 +1,6 @@
1
1
  import type { JsDb } from './feltdb.js';
2
+ import { type FreshnessCapability, type Revision } from './freshness.js';
3
+ import type { AtomicTransactionRequest, AtomicTransactionResult } from './transaction.js';
2
4
  interface JsResult {
3
5
  success: boolean;
4
6
  data?: string;
@@ -20,9 +22,23 @@ export declare class MemoryJsDb implements JsDb {
20
22
  private readonly events;
21
23
  private sequence;
22
24
  private readonly origin;
25
+ private readonly revisionState;
23
26
  private readonly peers;
27
+ private readonly applied;
24
28
  constructor(namespace: string);
25
29
  private event;
30
+ /**
31
+ * Commit several operations atomically.
32
+ *
33
+ * The plan is decided against current state before anything is written, and
34
+ * the writes then happen with no `await` between them, so no other caller
35
+ * can observe a partially applied transaction.
36
+ *
37
+ * This runtime is atomic but not durable: nothing survives process exit.
38
+ * That is a property of an in-memory store, not a weaker transaction
39
+ * contract.
40
+ */
41
+ commit_transaction(request: AtomicTransactionRequest): Promise<AtomicTransactionResult>;
26
42
  insert(key: string, value: string): JsResult;
27
43
  update(key: string, value: string): JsResult;
28
44
  get(key: string): JsResult;
@@ -52,6 +68,16 @@ export declare class MemoryJsDb implements JsDb {
52
68
  get_pending_for_peer(): JsResult;
53
69
  instance_id(): string;
54
70
  get_sequence(): number;
71
+ /**
72
+ * One process, one event loop, one shared namespace record.
73
+ *
74
+ * Nothing outside this process can write these rows, and every handle on the
75
+ * namespace advances the same counter, so the counter is authoritative for
76
+ * the domain it represents. That stops being true the moment replication
77
+ * touches the namespace.
78
+ */
79
+ freshness(): FreshnessCapability;
80
+ revision(): Promise<Revision>;
55
81
  audit_events(): PortableEvent[];
56
82
  export_operations(sinceSequence: number): PortableEvent[];
57
83
  apply_remote_operations(operations: PortableEvent[]): {
@@ -1 +1 @@
1
- {"version":3,"file":"memory-db.d.ts","sourceRoot":"","sources":["../src/memory-db.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAExC,UAAU,QAAQ;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,UAAU,aAAa;IAAG,QAAQ,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,KAAK,GAAG,QAAQ,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE;AAIrK,mEAAmE;AACnE,qBAAa,UAAW,YAAW,IAAI;IACrC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAuB;IAC5C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAuB;IAC9C,OAAO,CAAC,QAAQ,CAAK;IACrB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAqB;gBAE/B,SAAS,EAAE,MAAM;IAU7B,OAAO,CAAC,KAAK;IAMb,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,QAAQ;IAU5C,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,QAAQ;IAO5C,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,QAAQ;IAO1B,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,QAAQ;IAM7B,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,QAAQ,EAAE,OAAO,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAiBtF,GAAG,CAAC,MAAM,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,CAAC;QAAC,aAAa,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,cAAc,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAiC1K,KAAK,CAAC,UAAU,EAAE,MAAM,GAAG,QAAQ;IAQnC,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,QAAQ;IAIpD,UAAU,IAAI,QAAQ;IAItB,SAAS,IAAI,QAAQ;IAIrB,QAAQ,IAAI,QAAQ;IACpB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ;IACvC,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ;IAC1C,2BAA2B,IAAI,QAAQ;IACvC,oBAAoB,IAAI,QAAQ;IAChC,WAAW,IAAI,MAAM;IACrB,YAAY,IAAI,MAAM;IACtB,YAAY;IACZ,iBAAiB,CAAC,aAAa,EAAE,MAAM;IACvC,uBAAuB,CAAC,UAAU,EAAE,aAAa,EAAE;;;;CAapD"}
1
+ {"version":3,"file":"memory-db.d.ts","sourceRoot":"","sources":["../src/memory-db.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAA8B,KAAK,mBAAmB,EAAE,KAAK,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AACrG,OAAO,KAAK,EAAE,wBAAwB,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAO1F,UAAU,QAAQ;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,UAAU,aAAa;IAAG,QAAQ,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,KAAK,GAAG,QAAQ,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE;AA6BrK,mEAAmE;AACnE,qBAAa,UAAW,YAAW,IAAI;IACrC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAuB;IAC5C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAuB;IAC9C,OAAO,CAAC,QAAQ,CAAK;IACrB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAoB;IAClD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAqB;IAC3C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;gBAEjC,SAAS,EAAE,MAAM;IAW7B,OAAO,CAAC,KAAK;IAOb;;;;;;;;;;OAUG;IACG,kBAAkB,CAAC,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC,uBAAuB,CAAC;IAuB7F,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,QAAQ;IAU5C,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,QAAQ;IAO5C,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,QAAQ;IAO1B,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,QAAQ;IAM7B,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,QAAQ,EAAE,OAAO,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAiBtF,GAAG,CAAC,MAAM,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,CAAC;QAAC,aAAa,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,cAAc,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAiC1K,KAAK,CAAC,UAAU,EAAE,MAAM,GAAG,QAAQ;IAQnC,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,QAAQ;IAIpD,UAAU,IAAI,QAAQ;IAItB,SAAS,IAAI,QAAQ;IAIrB,QAAQ,IAAI,QAAQ;IACpB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ;IACvC,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ;IAC1C,2BAA2B,IAAI,QAAQ;IACvC,oBAAoB,IAAI,QAAQ;IAChC,WAAW,IAAI,MAAM;IACrB,YAAY,IAAI,MAAM;IAEtB;;;;;;;OAOG;IACH,SAAS,IAAI,mBAAmB;IAiB1B,QAAQ,IAAI,OAAO,CAAC,QAAQ,CAAC;IAQnC,YAAY;IACZ,iBAAiB,CAAC,aAAa,EAAE,MAAM;IACvC,uBAAuB,CAAC,UAAU,EAAE,aAAa,EAAE;;;;CAmBpD"}
package/dist/memory-db.js CHANGED
@@ -1,23 +1,71 @@
1
+ import { FreshnessContractViolation } from './freshness.js';
2
+ import { decideTransaction, embeddedResult, EmbeddedTransactionRejected, } from './embedded-transaction.js';
1
3
  const namespaces = new Map();
4
+ const revisions = new Map();
5
+ function namespaceRevision(namespace) {
6
+ let revision = revisions.get(namespace);
7
+ if (!revision) {
8
+ revision = { value: 0, replicated: false };
9
+ revisions.set(namespace, revision);
10
+ }
11
+ return revision;
12
+ }
2
13
  /** In-process runtime used by the synchronous createFeltDB API. */
3
14
  export class MemoryJsDb {
4
15
  constructor(namespace) {
5
16
  this.events = [];
6
17
  this.sequence = 0;
7
18
  this.peers = new Set();
19
+ this.applied = new Set();
8
20
  let rows = namespaces.get(namespace);
9
21
  if (!rows) {
10
22
  rows = new Map();
11
23
  namespaces.set(namespace, rows);
12
24
  }
13
25
  this.rows = rows;
26
+ this.revisionState = namespaceRevision(namespace);
14
27
  this.origin = `memory:${namespace}`;
15
28
  }
16
29
  event(collection, key, type, value) {
17
30
  this.sequence += 1;
31
+ this.revisionState.value += 1;
18
32
  const timestamp = Date.now();
19
33
  this.events.push({ sequence: this.sequence, collection, key, type, value, timestamp, origin: this.origin, id: `${this.origin}:${timestamp}:${this.sequence}` });
20
34
  }
35
+ /**
36
+ * Commit several operations atomically.
37
+ *
38
+ * The plan is decided against current state before anything is written, and
39
+ * the writes then happen with no `await` between them, so no other caller
40
+ * can observe a partially applied transaction.
41
+ *
42
+ * This runtime is atomic but not durable: nothing survives process exit.
43
+ * That is a property of an in-memory store, not a weaker transaction
44
+ * contract.
45
+ */
46
+ async commit_transaction(request) {
47
+ const before = this.sequence;
48
+ const plan = decideTransaction(request, {
49
+ exists: key => this.rows.has(key),
50
+ hasApplied: transactionId => this.applied.has(transactionId),
51
+ });
52
+ if (plan.kind === 'duplicate')
53
+ return embeddedResult(request, true, before, this.sequence);
54
+ if (plan.kind === 'reject')
55
+ throw new EmbeddedTransactionRejected(plan.reason);
56
+ for (const mutation of plan.mutations) {
57
+ if (mutation.value === undefined) {
58
+ this.rows.delete(mutation.key);
59
+ this.event(mutation.collection, mutation.key, 'delete');
60
+ }
61
+ else {
62
+ this.rows.set(mutation.key, mutation.value);
63
+ this.event(mutation.collection, mutation.key, 'put', mutation.value);
64
+ }
65
+ }
66
+ this.applied.add(request.transactionId);
67
+ return embeddedResult(request, false, before, this.sequence);
68
+ }
21
69
  insert(key, value) {
22
70
  try {
23
71
  this.rows.set(key, JSON.parse(value));
@@ -110,9 +158,47 @@ export class MemoryJsDb {
110
158
  get_pending_for_peer() { return { success: true, data: '[]' }; }
111
159
  instance_id() { return this.origin; }
112
160
  get_sequence() { return this.sequence; }
161
+ /**
162
+ * One process, one event loop, one shared namespace record.
163
+ *
164
+ * Nothing outside this process can write these rows, and every handle on the
165
+ * namespace advances the same counter, so the counter is authoritative for
166
+ * the domain it represents. That stops being true the moment replication
167
+ * touches the namespace.
168
+ */
169
+ freshness() {
170
+ if (this.revisionState.replicated || this.peers.size > 0) {
171
+ return {
172
+ domain: 'replicated',
173
+ validation: 'refresh',
174
+ invalidation: false,
175
+ reason: 'remote operations order causally across replicas, so a local counter is not an authoritative revision',
176
+ };
177
+ }
178
+ return {
179
+ domain: 'single-process',
180
+ validation: 'revision',
181
+ invalidation: false,
182
+ revisionScope: this.origin,
183
+ };
184
+ }
185
+ async revision() {
186
+ const capability = this.freshness();
187
+ if (capability.validation !== 'revision') {
188
+ throw new FreshnessContractViolation(`memory runtime: ${capability.reason}`);
189
+ }
190
+ return { value: this.revisionState.value, scope: this.origin };
191
+ }
113
192
  audit_events() { return [...this.events]; }
114
193
  export_operations(sinceSequence) { return this.events.filter(event => event.sequence > sinceSequence); }
115
194
  apply_remote_operations(operations) {
195
+ // Once state can arrive from another replica this namespace is causally
196
+ // ordered, and the local counter stops being an authoritative revision.
197
+ // Latched rather than derived from the current call, because the loss of
198
+ // authority is permanent for this namespace: a later empty batch does not
199
+ // restore it.
200
+ if (operations.length > 0)
201
+ this.revisionState.replicated = true;
116
202
  let applied = 0, ignored = 0;
117
203
  for (const operation of operations) {
118
204
  if (this.events.some(event => event.id === operation.id)) {
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Registry for storage runtimes that only exist on some platforms.
3
+ *
4
+ * `createFeltDB` is synchronous, so it cannot `await import()` a runtime on
5
+ * demand. Instead, a platform entry point registers the runtimes it can
6
+ * provide, and `createFeltDB` looks them up.
7
+ *
8
+ * This exists for one reason: the file runtime imports Node's `fs` and `path`.
9
+ * A static import of it anywhere in the public entry's module graph makes the
10
+ * whole package unloadable in a browser, because a browser cannot resolve a
11
+ * bare `fs` specifier. Keeping that import out of the shared graph is what
12
+ * lets a browser application import `@feltdb/core` at all.
13
+ */
14
+ import type { JsDb } from './feltdb.js';
15
+ /** Builds a runtime from the option that selected it. */
16
+ export type RuntimeFactory = (target: string) => JsDb;
17
+ /**
18
+ * Make a runtime available to `createFeltDB`.
19
+ *
20
+ * Called by a platform entry point for the runtimes that platform supports.
21
+ */
22
+ export declare function registerRuntime(name: string, factory: RuntimeFactory): void;
23
+ export declare function resolveRuntime(name: string): RuntimeFactory | undefined;
24
+ /**
25
+ * Build a runtime, or explain precisely why this platform cannot.
26
+ *
27
+ * The message names the alternative rather than reporting a missing module,
28
+ * because "cannot resolve fs" is not an actionable error for someone who just
29
+ * wants a database in a browser.
30
+ */
31
+ export declare function requireRuntime(name: string, target: string, guidance: string): JsDb;
32
+ //# sourceMappingURL=runtime-registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime-registry.d.ts","sourceRoot":"","sources":["../src/runtime-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAExC,yDAAyD;AACzD,MAAM,MAAM,cAAc,GAAG,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;AAItD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,GAAG,IAAI,CAE3E;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAEvE;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAInF"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Registry for storage runtimes that only exist on some platforms.
3
+ *
4
+ * `createFeltDB` is synchronous, so it cannot `await import()` a runtime on
5
+ * demand. Instead, a platform entry point registers the runtimes it can
6
+ * provide, and `createFeltDB` looks them up.
7
+ *
8
+ * This exists for one reason: the file runtime imports Node's `fs` and `path`.
9
+ * A static import of it anywhere in the public entry's module graph makes the
10
+ * whole package unloadable in a browser, because a browser cannot resolve a
11
+ * bare `fs` specifier. Keeping that import out of the shared graph is what
12
+ * lets a browser application import `@feltdb/core` at all.
13
+ */
14
+ const runtimes = new Map();
15
+ /**
16
+ * Make a runtime available to `createFeltDB`.
17
+ *
18
+ * Called by a platform entry point for the runtimes that platform supports.
19
+ */
20
+ export function registerRuntime(name, factory) {
21
+ runtimes.set(name, factory);
22
+ }
23
+ export function resolveRuntime(name) {
24
+ return runtimes.get(name);
25
+ }
26
+ /**
27
+ * Build a runtime, or explain precisely why this platform cannot.
28
+ *
29
+ * The message names the alternative rather than reporting a missing module,
30
+ * because "cannot resolve fs" is not an actionable error for someone who just
31
+ * wants a database in a browser.
32
+ */
33
+ export function requireRuntime(name, target, guidance) {
34
+ const factory = resolveRuntime(name);
35
+ if (!factory)
36
+ throw new Error(guidance);
37
+ return factory(target);
38
+ }
@@ -7,8 +7,9 @@ export interface StudioAppProps {
7
7
  namespace?: string;
8
8
  deploymentRuntime?: string;
9
9
  applicationUrl?: string;
10
+ developmentSession?: Record<string, string>;
10
11
  onConnect?: (url: string, token: string) => void;
11
12
  }
12
- export declare function StudioApp({ db, remoteUrl, token, namespace, deploymentRuntime, applicationUrl, onConnect }: StudioAppProps): React.JSX.Element;
13
+ export declare function StudioApp({ db, remoteUrl, token, namespace, deploymentRuntime, applicationUrl, developmentSession, onConnect }: StudioAppProps): React.JSX.Element;
13
14
  export default StudioApp;
14
15
  //# sourceMappingURL=app.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.tsx"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAA8B,MAAM,OAAO,CAAC;AAmBnD,OAAO,EAAgC,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAC/E,OAAO,WAAW,CAAC;AAEnB,MAAM,WAAW,cAAc;IAC7B,EAAE,CAAC,EAAE,YAAY,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CAClD;AAED,wBAAgB,SAAS,CAAC,EAAE,EAAE,EAAE,SAAc,EAAE,KAAU,EAAE,SAAqB,EAAE,iBAA6B,EAAE,cAAmB,EAAE,SAAS,EAAE,EAAE,cAAc,qBAgGjK;AAED,eAAe,SAAS,CAAC"}
1
+ {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.tsx"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAA8B,MAAM,OAAO,CAAC;AAmBnD,OAAO,EAAgC,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAC/E,OAAO,WAAW,CAAC;AAEnB,MAAM,WAAW,cAAc;IAC7B,EAAE,CAAC,EAAE,YAAY,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5C,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CAClD;AAED,wBAAgB,SAAS,CAAC,EAAE,EAAE,EAAE,SAAc,EAAE,KAAU,EAAE,SAAqB,EAAE,iBAA6B,EAAE,cAAmB,EAAE,kBAAkB,EAAE,SAAS,EAAE,EAAE,cAAc,qBA6IrL;AAED,eAAe,SAAS,CAAC"}
@@ -5,7 +5,8 @@ export interface StateExplorerProps {
5
5
  model?: FlowSpec | null;
6
6
  namespace?: string;
7
7
  applicationUrl?: string;
8
+ developmentSession?: Record<string, string>;
8
9
  }
9
- export declare function StateExplorer({ db, model, namespace, applicationUrl }: StateExplorerProps): React.JSX.Element;
10
+ export declare function StateExplorer({ db, model, namespace, applicationUrl, developmentSession }: StateExplorerProps): React.JSX.Element;
10
11
  export default StateExplorer;
11
12
  //# sourceMappingURL=StateExplorer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"StateExplorer.d.ts","sourceRoot":"","sources":["../../src/components/StateExplorer.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAsC,MAAM,OAAO,CAAC;AAC3D,OAAO,KAAK,EAAkB,QAAQ,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAG3E,MAAM,WAAW,kBAAkB;IAAG,EAAE,CAAC,EAAE,YAAY,CAAC;IAAC,KAAK,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,cAAc,CAAC,EAAE,MAAM,CAAA;CAAE;AAoB/H,wBAAgB,aAAa,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,SAAqB,EAAE,cAAmB,EAAE,EAAE,kBAAkB,qBAuB1G;AACD,eAAe,aAAa,CAAC"}
1
+ {"version":3,"file":"StateExplorer.d.ts","sourceRoot":"","sources":["../../src/components/StateExplorer.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAsC,MAAM,OAAO,CAAC;AAC3D,OAAO,KAAK,EAAkB,QAAQ,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAG3E,MAAM,WAAW,kBAAkB;IAAG,EAAE,CAAC,EAAE,YAAY,CAAC;IAAC,KAAK,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE;AAoB5K,wBAAgB,aAAa,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,SAAqB,EAAE,cAAmB,EAAE,kBAAkB,EAAE,EAAE,kBAAkB,qBA0B9H;AACD,eAAe,aAAa,CAAC"}
@@ -1,4 +1,4 @@
1
- import { a as e, c as t, d as n, f as r, i, l as a, m as o, n as s, o as c, p as l, r as u, s as d, t as f, u as p, x as m } from "../components-9kDSWiGL.js";
1
+ import { a as e, c as t, d as n, f as r, i, l as a, m as o, n as s, o as c, p as l, r as u, s as d, t as f, u as p, x as m } from "../components-C5p2TfIU.js";
2
2
  import { t as h } from "../KeyManagementPanel-DZuSeWBK.js";
3
3
  import { ManagedInstancePanel as g } from "./ManagedInstancePanel.js";
4
4
  export { c as AgentExplorer, f as ApplicationDesigner, p as CapabilityExplorer, t as ConflictExplorer, a as ExecutionViewer, s as GlobalSearch, i as HealthCenter, h as KeyManagementPanel, g as ManagedInstancePanel, r as OperationsExplorer, m as OverviewDashboard, n as PeerMap, e as ProvenanceViewer, l as ReferenceExplorer, u as SettingsPanel, o as StateExplorer, d as WorkflowVisualizer };
@@ -281,22 +281,27 @@ function C({ db: e, definition: t, namespace: n, bridgedItems: r }) {
281
281
  ]
282
282
  });
283
283
  }
284
- function w({ db: e, model: t, namespace: r = "default", applicationUrl: o = "" }) {
285
- let l = i(null), [u, d] = a(null);
284
+ function w({ db: e, model: t, namespace: r = "default", applicationUrl: o = "", developmentSession: l }) {
285
+ let u = i(null), [d, f] = a(null);
286
286
  return n(() => {
287
287
  if (!o) return;
288
- let e = new URL(o).origin, t = () => l.current?.contentWindow?.postMessage({
289
- type: "feltdb:studio:read",
290
- requestId: "live"
291
- }, e), n = (n) => {
292
- n.origin === e && (n.data?.type === "feltdb:studio:ready" && t(), n.data?.type === "feltdb:studio:data" && d(n.data.records));
288
+ let e = new URL(o).origin, t = () => {
289
+ l?.sessionId && u.current?.contentWindow?.postMessage({
290
+ type: "feltdb:dev-session",
291
+ session: l
292
+ }, e), u.current?.contentWindow?.postMessage({
293
+ type: "feltdb:studio:read",
294
+ requestId: "live"
295
+ }, e);
296
+ }, n = (n) => {
297
+ n.origin === e && (n.data?.type === "feltdb:studio:ready" && t(), n.data?.type === "feltdb:studio:data" && f(n.data.records));
293
298
  };
294
299
  window.addEventListener("message", n);
295
300
  let r = window.setInterval(t, 1500);
296
301
  return () => {
297
302
  window.removeEventListener("message", n), window.clearInterval(r);
298
303
  };
299
- }, [o]), /* @__PURE__ */ c("div", {
304
+ }, [o, l]), /* @__PURE__ */ c("div", {
300
305
  className: "studio-model-page",
301
306
  children: [
302
307
  /* @__PURE__ */ c("header", { children: [
@@ -308,14 +313,20 @@ function w({ db: e, model: t, namespace: r = "default", applicationUrl: o = "" }
308
313
  /* @__PURE__ */ s("p", { children: "Live application records grouped by collection. Select a record to inspect every field." })
309
314
  ] }),
310
315
  o && /* @__PURE__ */ s("iframe", {
311
- ref: l,
316
+ ref: u,
312
317
  className: "studio-data-bridge",
313
318
  src: o,
314
319
  title: "FeltDB application data bridge",
315
- onLoad: () => l.current?.contentWindow?.postMessage({
316
- type: "feltdb:studio:read",
317
- requestId: "initial"
318
- }, new URL(o).origin)
320
+ onLoad: () => {
321
+ let e = new URL(o).origin;
322
+ l?.sessionId && u.current?.contentWindow?.postMessage({
323
+ type: "feltdb:dev-session",
324
+ session: l
325
+ }, e), u.current?.contentWindow?.postMessage({
326
+ type: "feltdb:studio:read",
327
+ requestId: "initial"
328
+ }, e);
329
+ }
319
330
  }),
320
331
  /* @__PURE__ */ c("div", {
321
332
  className: "model-summary",
@@ -331,7 +342,7 @@ function w({ db: e, model: t, namespace: r = "default", applicationUrl: o = "" }
331
342
  db: e ?? null,
332
343
  definition: t,
333
344
  namespace: r,
334
- bridgedItems: u?.[t.name]
345
+ bridgedItems: d?.[t.name]
335
346
  }, t.name)), !t?.collections.length && /* @__PURE__ */ c("div", {
336
347
  className: "model-empty",
337
348
  children: [