@feltdb/core 0.8.3 → 0.8.4

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 (59) hide show
  1. package/dist/cli/commands.js +4 -1
  2. package/dist/cli/provisioning-neutrality.js +79 -0
  3. package/dist/collection.d.ts +43 -1
  4. package/dist/collection.d.ts.map +1 -1
  5. package/dist/collection.js +192 -22
  6. package/dist/create/create.js +25 -21
  7. package/dist/create/managed-account.js +11 -0
  8. package/dist/create/package-versions.js +1 -1
  9. package/dist/create/server-source/crates/feltdb/src/equality_index.rs +595 -0
  10. package/dist/create/server-source/crates/feltdb/src/lib.rs +547 -115
  11. package/dist/create/server-source/crates/feltdb/src/phase1c3_acceptance.rs +11 -2
  12. package/dist/create/server-source/crates/feltdb/src/query_execution_diagnostics.rs +126 -0
  13. package/dist/create/server-source/crates/feltdb/src/state_contract.rs +292 -2
  14. package/dist/create/server-source/crates/feltdb/src/sync.rs +12 -0
  15. package/dist/create/server-source/crates/feltdb/src/workload_diagnostics.rs +443 -0
  16. package/dist/create/server-source/crates/feltdb/tests/pr34_query_collection.rs +233 -0
  17. package/dist/create/server-source/crates/feltdb/tests/pr35_equality_index.rs +892 -0
  18. package/dist/create/server-source/crates/feltdb-server/src/audit.rs +1137 -29
  19. package/dist/create/server-source/crates/feltdb-server/src/main.rs +474 -28
  20. package/dist/db.d.ts +33 -34
  21. package/dist/db.d.ts.map +1 -1
  22. package/dist/db.js +74 -20
  23. package/dist/deployment.d.ts +30 -0
  24. package/dist/deployment.d.ts.map +1 -0
  25. package/dist/deployment.js +130 -0
  26. package/dist/embedded-transaction.d.ts +22 -4
  27. package/dist/embedded-transaction.d.ts.map +1 -1
  28. package/dist/embedded-transaction.js +51 -5
  29. package/dist/feltdb.d.ts +14 -2
  30. package/dist/feltdb.d.ts.map +1 -1
  31. package/dist/file-db.js +1 -1
  32. package/dist/http-client.d.ts +14 -0
  33. package/dist/http-client.d.ts.map +1 -1
  34. package/dist/http-client.js +23 -5
  35. package/dist/http-db.d.ts +119 -1
  36. package/dist/http-db.d.ts.map +1 -1
  37. package/dist/http-db.js +346 -31
  38. package/dist/index-core.d.ts +2 -0
  39. package/dist/index-core.d.ts.map +1 -1
  40. package/dist/index-core.js +2 -0
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +9 -0
  43. package/dist/indexeddb-db.d.ts.map +1 -1
  44. package/dist/indexeddb-db.js +35 -21
  45. package/dist/managed-recovery.d.ts +192 -0
  46. package/dist/managed-recovery.d.ts.map +1 -0
  47. package/dist/managed-recovery.js +242 -0
  48. package/dist/memory-db.js +1 -1
  49. package/dist/studio-app/assets/{feltdb_wasm-DB8cX151.js → feltdb_wasm-CVQWgXO-.js} +1 -1
  50. package/dist/studio-app/assets/feltdb_wasm_bg-CNVpvaZV.wasm +0 -0
  51. package/dist/studio-app/assets/index-DwgNAIIX.js +29 -0
  52. package/dist/studio-app/index.html +1 -1
  53. package/dist/transaction.d.ts +30 -0
  54. package/dist/transaction.d.ts.map +1 -1
  55. package/dist/transaction.js +41 -0
  56. package/dist/wasm/feltdb_wasm_bg.wasm +0 -0
  57. package/package.json +1 -1
  58. package/dist/studio-app/assets/feltdb_wasm_bg-ClhDHp0S.wasm +0 -0
  59. package/dist/studio-app/assets/index-B0k4UAlI.js +0 -29
@@ -293,8 +293,11 @@ async function handleAi(args) {
293
293
  if (stored) {
294
294
  const client = proposalService();
295
295
  const readiness = await client.status(stored.id);
296
+ // Coded like every other refusal this command can produce
297
+ // (SOURCE_SYNC_CONFLICT, REPOSITORY_AUTHORITY_REQUIRED), so a caller can
298
+ // tell a stale proposal from a failed apply without reading the prose.
296
299
  if (readiness.blockers.length)
297
- throw new Error(`Cannot apply Proposal ${stored.id}. Proposal is stale: ${readiness.blockers.join(', ')}. Proposal contract: ${stored.base_contract_hash}. Current contract: ${readiness.current_contract_hash}. Regenerate the Proposal before applying.`);
300
+ throw new Error(`STALE_PROPOSAL: Cannot apply Proposal ${stored.id}. Proposal is stale: ${readiness.blockers.join(', ')}. Proposal contract: ${stored.base_contract_hash}. Current contract: ${readiness.current_contract_hash}. Regenerate the Proposal before applying.`);
298
301
  if (!stored.preview_id)
299
302
  throw new Error(`Cannot apply Proposal ${stored.id}. A current preview is required.`);
300
303
  await client.getPreview(stored.preview_id);
@@ -0,0 +1,79 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import crypto from 'node:crypto';
4
+ function canonicalMode(input) {
5
+ const value = (input.mode || input.deploymentMode || input.runtime || (input.url || input.endpoint ? 'remote' : 'browser')).toLowerCase();
6
+ if (value === 'browser')
7
+ return 'browser';
8
+ if (value === 'local' || value === 'node')
9
+ return 'local';
10
+ if (value === 'remote' || value === 'self-hosted' || value === 'managed')
11
+ return 'remote';
12
+ throw new Error(`Unsupported FeltDB provisioning mode "${value}"`);
13
+ }
14
+ function sortRecord(record) {
15
+ if (!record)
16
+ return undefined;
17
+ return Object.fromEntries(Object.entries(record).sort(([a], [b]) => a.localeCompare(b)));
18
+ }
19
+ export function canonicalizeProvisioningConfiguration(input) {
20
+ const mode = canonicalMode(input);
21
+ const credentialReference = input.credentialReference?.trim();
22
+ const hasToken = !!input.token;
23
+ return {
24
+ deployment: {
25
+ mode,
26
+ endpoint: input.endpoint || input.url || undefined,
27
+ localDataPath: input.localDataPath || input.path || undefined,
28
+ },
29
+ application: {
30
+ id: input.applicationId || undefined,
31
+ namespace: input.namespace || undefined,
32
+ environment: input.environment || undefined,
33
+ },
34
+ credentials: {
35
+ present: hasToken || !!credentialReference,
36
+ type: credentialReference ? 'reference' : hasToken ? 'token' : 'none',
37
+ reference: credentialReference || undefined,
38
+ },
39
+ capabilities: sortRecord(input.capabilities),
40
+ };
41
+ }
42
+ export function provisioningConfigurationsEquivalent(left, right) {
43
+ return JSON.stringify(canonicalizeProvisioningConfiguration(left)) === JSON.stringify(canonicalizeProvisioningConfiguration(right));
44
+ }
45
+ export function provisioningConfigurationHash(input) {
46
+ const canonical = canonicalizeProvisioningConfiguration({ ...input, token: input.token ? '<present>' : undefined });
47
+ return crypto.createHash('sha256').update(JSON.stringify(canonical)).digest('hex');
48
+ }
49
+ export function writeDeploymentConfiguration(projectDir, input) {
50
+ const canonical = canonicalizeProvisioningConfiguration(input);
51
+ fs.mkdirSync(projectDir, { recursive: true });
52
+ fs.mkdirSync(path.join(projectDir, '.feltdb'), { recursive: true });
53
+ fs.writeFileSync(path.join(projectDir, 'feltdb.config.json'), `${JSON.stringify({
54
+ mode: canonical.deployment.mode,
55
+ namespace: canonical.application.namespace,
56
+ environment: canonical.application.environment,
57
+ applicationId: canonical.application.id,
58
+ url: canonical.deployment.endpoint,
59
+ path: canonical.deployment.localDataPath,
60
+ capabilities: canonical.capabilities || {},
61
+ }, null, 2)}\n`);
62
+ const envLines = [
63
+ '# Generated by feltdb provisioning. Do not commit secrets.',
64
+ `FELTDB_MODE=${canonical.deployment.mode}`,
65
+ canonical.deployment.endpoint ? `FELTDB_URL=${canonical.deployment.endpoint}` : undefined,
66
+ canonical.deployment.localDataPath ? `FELTDB_PATH=${canonical.deployment.localDataPath}` : undefined,
67
+ canonical.application.id ? `FELTDB_APPLICATION_ID=${canonical.application.id}` : undefined,
68
+ canonical.application.namespace ? `FELTDB_NAMESPACE=${canonical.application.namespace}` : undefined,
69
+ canonical.application.environment ? `FELTDB_ENVIRONMENT=${canonical.application.environment}` : undefined,
70
+ canonical.credentials.type === 'reference' && canonical.credentials.reference ? `FELTDB_CREDENTIAL_REFERENCE=${canonical.credentials.reference}` : undefined,
71
+ '',
72
+ ].filter((line) => line !== undefined);
73
+ fs.writeFileSync(path.join(projectDir, '.env.local'), envLines.join('\n'), { mode: 0o600 });
74
+ fs.writeFileSync(path.join(projectDir, '.feltdb', 'provisioning.json'), `${JSON.stringify({
75
+ ...canonical,
76
+ configurationHash: provisioningConfigurationHash(input),
77
+ }, null, 2)}\n`, { mode: 0o600 });
78
+ return canonical;
79
+ }
@@ -97,6 +97,8 @@ export declare class Collection<T> {
97
97
  * Returns cached results (live-updated).
98
98
  */
99
99
  all(): Promise<T[]>;
100
+ /** List records through the canonical application collection API. */
101
+ list(): Promise<T[]>;
100
102
  /**
101
103
  * Find records whose fields match the supplied query.
102
104
  *
@@ -112,6 +114,8 @@ export declare class Collection<T> {
112
114
  * `lastQueryPlan()` reports which path ran.
113
115
  */
114
116
  find(query?: Partial<T>): Promise<T[]>;
117
+ /** Query records by field equality through the canonical application API. */
118
+ query(query?: Partial<T>): Promise<T[]>;
115
119
  /**
116
120
  * How the most recent `find` was answered.
117
121
  *
@@ -171,6 +175,8 @@ export declare class Collection<T> {
171
175
  * Automatically initializes __version to 1 for durable atomic transitions.
172
176
  */
173
177
  insert(data: Partial<T>, id?: string | number): Promise<string>;
178
+ /** Put a record through the canonical application collection API. */
179
+ put(data: Partial<T>, id?: string | number): Promise<string>;
174
180
  /**
175
181
  * Update a record in this collection.
176
182
  */
@@ -182,6 +188,10 @@ export declare class Collection<T> {
182
188
  * The version check and update happen atomically at the backend boundary,
183
189
  * ensuring exactly one writer succeeds when multiple writers race.
184
190
  *
191
+ * A runtime without a dedicated compare-and-set carries the same fence
192
+ * through its atomic transaction, so this means the same thing on the file
193
+ * runtime as it does against a managed authority.
194
+ *
185
195
  * @param id Record identifier
186
196
  * @param expectedVersion The version you observed when you read this record
187
197
  * @param updates Fields to update (does not include __version; version is auto-incremented)
@@ -217,9 +227,41 @@ export declare class Collection<T> {
217
227
  * Check if a record exists.
218
228
  */
219
229
  exists(id: string | number): Promise<boolean>;
230
+ /**
231
+ * Split a storage key back into the collection and record it addresses.
232
+ *
233
+ * The transaction contract is expressed in collections and ids rather than
234
+ * in storage keys, so a helper that holds a key has to name the record the
235
+ * same way the authority does.
236
+ */
237
+ private addressOf;
238
+ /**
239
+ * The runtime's atomic transaction, used as the conditional-create surface.
240
+ *
241
+ * Returns the same shape a runtime's own `putIfAbsent` returns, so the caller
242
+ * above cannot tell which route it took. A refusal that is a lost race is
243
+ * resolved by reading the record the winner wrote; anything else -- a
244
+ * missing capability, authentication, schema, transport, a server error --
245
+ * is raised.
246
+ */
247
+ private putIfAbsentThroughTransaction;
248
+ /**
249
+ * The runtime's atomic transaction, used as the version-fenced update surface.
250
+ *
251
+ * Returns the same shape a runtime's own `cas` returns. The authority owns
252
+ * the next version, so the proposed value carries none.
253
+ */
254
+ private updateIfVersionThroughTransaction;
220
255
  /**
221
256
  * Atomically insert a record only if the key does not already exist.
222
- * Process-safe across independent Node.js processes.
257
+ *
258
+ * Process-safe on the file runtime and across clients against an authority:
259
+ * the condition is evaluated by the runtime inside its own commit boundary,
260
+ * never by this method. A runtime without a dedicated conditional create
261
+ * uses its atomic transaction, so this means the same thing everywhere.
262
+ *
263
+ * The created record starts at `__version: 1`, so it can be fenced by
264
+ * `updateIfVersion` immediately.
223
265
  *
224
266
  * @returns {inserted: true, value: newRecord} if this call created the record
225
267
  * @returns {inserted: false, value: existingRecord} if record already existed
@@ -1 +1 @@
1
- {"version":3,"file":"collection.d.ts","sourceRoot":"","sources":["../src/collection.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"collection.d.ts","sourceRoot":"","sources":["../src/collection.ts"],"names":[],"mappings":"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"}
@@ -9,8 +9,23 @@
9
9
  */
10
10
  import { describeFreshness, isCacheCurrent, readRevision, } from './freshness.js';
11
11
  import { getReactiveDependencyGraph } from './reactive-graph.js';
12
+ import { conditionalRefusal } from './transaction.js';
12
13
  import { IndexBackend } from './index-backend.js';
13
14
  import { IndexStore } from './index-store.js';
15
+ /**
16
+ * A durable identity for one conditional write attempt.
17
+ *
18
+ * The authority deduplicates by transaction id, so a redelivered request
19
+ * cannot apply the same conditional write twice. Each *attempt* gets its own
20
+ * id: a retry after a conflict is a new decision against fresh state and has
21
+ * to be evaluated again rather than answered from the first attempt.
22
+ */
23
+ function conditionalTransactionId(purpose) {
24
+ const unique = typeof globalThis.crypto?.randomUUID === 'function'
25
+ ? globalThis.crypto.randomUUID()
26
+ : `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
27
+ return `${purpose}-${unique}`;
28
+ }
14
29
  /**
15
30
  * A live collection that automatically updates when underlying data changes.
16
31
  * Represents application state, not a one-time query result.
@@ -96,6 +111,10 @@ export class Collection {
96
111
  await this.refresh();
97
112
  return [...this.cache];
98
113
  }
114
+ /** List records through the canonical application collection API. */
115
+ async list() {
116
+ return this.all();
117
+ }
99
118
  /**
100
119
  * Find records whose fields match the supplied query.
101
120
  *
@@ -142,6 +161,10 @@ export class Collection {
142
161
  };
143
162
  return found;
144
163
  }
164
+ /** Query records by field equality through the canonical application API. */
165
+ async query(query = {}) {
166
+ return this.find(query);
167
+ }
145
168
  /**
146
169
  * How the most recent `find` was answered.
147
170
  *
@@ -325,6 +348,10 @@ export class Collection {
325
348
  await graph.emitChange(this.name, change);
326
349
  return String(recordId);
327
350
  }
351
+ /** Put a record through the canonical application collection API. */
352
+ async put(data, id) {
353
+ return this.insert(data, id);
354
+ }
328
355
  /**
329
356
  * Update a record in this collection.
330
357
  */
@@ -361,6 +388,10 @@ export class Collection {
361
388
  * The version check and update happen atomically at the backend boundary,
362
389
  * ensuring exactly one writer succeeds when multiple writers race.
363
390
  *
391
+ * A runtime without a dedicated compare-and-set carries the same fence
392
+ * through its atomic transaction, so this means the same thing on the file
393
+ * runtime as it does against a managed authority.
394
+ *
364
395
  * @param id Record identifier
365
396
  * @param expectedVersion The version you observed when you read this record
366
397
  * @param updates Fields to update (does not include __version; version is auto-incremented)
@@ -394,21 +425,32 @@ export class Collection {
394
425
  delete cleanUpdates.__version;
395
426
  const updated = { ...current, ...cleanUpdates };
396
427
  const key = `${this.name}:${id}`;
397
- // Use CAS for atomic version-checked update
398
- if (!this.db.cas) {
399
- throw new Error(`updateIfVersion is not supported by this FeltDB runtime. ` +
400
- `Only FileJsDb (Node.js) and HTTP/Server backends support atomic version-checked updates.`);
401
- }
402
- // Delegate authoritative version and epoch check to the backend
403
- // The backend will atomically verify both predicates
404
- const casResult = await this.db.cas({
405
- key,
406
- expectedVersion,
407
- expectedEpoch,
408
- expectedLeaseId,
409
- rejectIfDiverged,
410
- value: JSON.stringify(updated),
411
- });
428
+ // Delegate the authoritative version and epoch check to the runtime, which
429
+ // evaluates both predicates inside its own commit boundary.
430
+ //
431
+ // A runtime that offers no single-record CAS but does offer an atomic
432
+ // transaction is not a runtime without compare-and-set: the transaction
433
+ // carries the same fence to the same authority. Routing through it is what
434
+ // keeps this helper's meaning the same everywhere, instead of making it a
435
+ // file-runtime privilege. Nothing here emulates CAS with separate reads
436
+ // and writes, because that would reintroduce the race the fence exists to
437
+ // remove.
438
+ const casResult = this.db.cas
439
+ ? await this.db.cas({
440
+ key,
441
+ expectedVersion,
442
+ expectedEpoch,
443
+ expectedLeaseId,
444
+ rejectIfDiverged,
445
+ value: JSON.stringify(updated),
446
+ })
447
+ : await this.updateIfVersionThroughTransaction(key, {
448
+ expectedVersion,
449
+ expectedEpoch,
450
+ expectedLeaseId,
451
+ rejectIfDiverged,
452
+ value: updated,
453
+ });
412
454
  if (!casResult.updated) {
413
455
  // Another writer won the race
414
456
  return {
@@ -478,24 +520,152 @@ export class Collection {
478
520
  const item = await this.get(id);
479
521
  return item !== null;
480
522
  }
523
+ /**
524
+ * Split a storage key back into the collection and record it addresses.
525
+ *
526
+ * The transaction contract is expressed in collections and ids rather than
527
+ * in storage keys, so a helper that holds a key has to name the record the
528
+ * same way the authority does.
529
+ */
530
+ addressOf(key) {
531
+ const separator = key.indexOf(':');
532
+ return { collection: key.slice(0, separator), id: key.slice(separator + 1) };
533
+ }
534
+ /**
535
+ * The runtime's atomic transaction, used as the conditional-create surface.
536
+ *
537
+ * Returns the same shape a runtime's own `putIfAbsent` returns, so the caller
538
+ * above cannot tell which route it took. A refusal that is a lost race is
539
+ * resolved by reading the record the winner wrote; anything else -- a
540
+ * missing capability, authentication, schema, transport, a server error --
541
+ * is raised.
542
+ */
543
+ async putIfAbsentThroughTransaction(key, value) {
544
+ const commit = this.db.commit_transaction;
545
+ if (!commit) {
546
+ throw new Error('putIfAbsent is not supported by this FeltDB runtime. It provides neither durable '
547
+ + 'atomic conditional creation nor an atomic transaction to carry the condition, and '
548
+ + 'FeltDB will not emulate one with a read followed by a write.');
549
+ }
550
+ const { collection, id } = this.addressOf(key);
551
+ try {
552
+ await commit.call(this.db, {
553
+ transactionId: conditionalTransactionId('put-if-absent'),
554
+ operations: [{ collection, id, value, requireAbsent: true }],
555
+ });
556
+ }
557
+ catch (error) {
558
+ if (!conditionalRefusal(error).conflict)
559
+ throw error;
560
+ const existing = await this.db.get(key);
561
+ if (!existing.success) {
562
+ throw new Error(existing.error || `could not read the record that won ${key}`);
563
+ }
564
+ if (!existing.data) {
565
+ throw new Error(`${key} was created and then removed while resolving a conditional create`);
566
+ }
567
+ return { inserted: false, value: existing.data };
568
+ }
569
+ // The authority assigns version 1 to a conditional create inside the
570
+ // commit boundary, which is the version this call already supplied.
571
+ return { inserted: true, value: JSON.stringify({ ...value, __version: 1 }) };
572
+ }
573
+ /**
574
+ * The runtime's atomic transaction, used as the version-fenced update surface.
575
+ *
576
+ * Returns the same shape a runtime's own `cas` returns. The authority owns
577
+ * the next version, so the proposed value carries none.
578
+ */
579
+ async updateIfVersionThroughTransaction(key, fence) {
580
+ const commit = this.db.commit_transaction;
581
+ if (!commit) {
582
+ throw new Error('updateIfVersion is not supported by this FeltDB runtime. It provides neither atomic '
583
+ + 'version-checked update nor an atomic transaction to carry the condition, and FeltDB '
584
+ + 'will not emulate one with a read followed by a write.');
585
+ }
586
+ if (fence.rejectIfDiverged) {
587
+ throw new Error('rejectIfDiverged is a replication predicate this runtime\'s transaction contract cannot '
588
+ + 'express, so it is refused rather than silently dropped.');
589
+ }
590
+ const { collection, id } = this.addressOf(key);
591
+ const proposed = { ...fence.value };
592
+ delete proposed.__version;
593
+ try {
594
+ await commit.call(this.db, {
595
+ transactionId: conditionalTransactionId('update-if-version'),
596
+ operations: [{
597
+ collection, id, value: proposed,
598
+ expectedVersion: fence.expectedVersion,
599
+ expectedEpoch: fence.expectedEpoch,
600
+ expectedLeaseId: fence.expectedLeaseId,
601
+ }],
602
+ });
603
+ }
604
+ catch (error) {
605
+ const refusal = conditionalRefusal(error);
606
+ if (!refusal.conflict)
607
+ throw error;
608
+ const failure = refusal.failure;
609
+ const conflictCode = failure?.predicate === 'version' ? 'VERSION_CONFLICT'
610
+ : failure?.predicate === 'epoch' ? 'AUTHORITY_CONFLICT'
611
+ : failure?.predicate === 'lease' ? 'LEASE_CONFLICT'
612
+ : failure?.predicate === 'missing' ? 'RECORD_MISSING'
613
+ : undefined;
614
+ if (failure?.predicate === 'version' && typeof failure.actual === 'number') {
615
+ return { updated: false, currentVersion: failure.actual, conflictCode };
616
+ }
617
+ // The refusal did not name a version, so the authority is asked for the
618
+ // one it holds -- which is what the caller would have to do next anyway.
619
+ const current = await this.db.get(key);
620
+ const record = current.success && current.data
621
+ ? JSON.parse(current.data)
622
+ : undefined;
623
+ return {
624
+ updated: false,
625
+ currentVersion: typeof record?.__version === 'number' ? record.__version : 0,
626
+ currentEpoch: record?.authority?.epoch,
627
+ conflictCode,
628
+ };
629
+ }
630
+ const nextVersion = fence.expectedVersion + 1;
631
+ return {
632
+ updated: true,
633
+ currentVersion: nextVersion,
634
+ currentEpoch: fence.expectedEpoch,
635
+ item: { ...proposed, id, __version: nextVersion },
636
+ };
637
+ }
481
638
  /**
482
639
  * Atomically insert a record only if the key does not already exist.
483
- * Process-safe across independent Node.js processes.
640
+ *
641
+ * Process-safe on the file runtime and across clients against an authority:
642
+ * the condition is evaluated by the runtime inside its own commit boundary,
643
+ * never by this method. A runtime without a dedicated conditional create
644
+ * uses its atomic transaction, so this means the same thing everywhere.
645
+ *
646
+ * The created record starts at `__version: 1`, so it can be fenced by
647
+ * `updateIfVersion` immediately.
484
648
  *
485
649
  * @returns {inserted: true, value: newRecord} if this call created the record
486
650
  * @returns {inserted: false, value: existingRecord} if record already existed
487
651
  */
488
652
  async putIfAbsent(id, data) {
489
653
  const key = `${this.name}:${id}`;
654
+ // Version 1 is supplied here for the same reason `insert` supplies it: a
655
+ // record with no version could never be conditionally updated, which would
656
+ // make atomic creation useless for exactly the lifecycles that need it.
490
657
  const stored = typeof data === 'object' && data !== null
491
- ? { ...data, id }
658
+ ? { ...data, id, __version: 1 }
492
659
  : data;
493
660
  const stringValue = JSON.stringify(stored);
494
- if (!this.db.putIfAbsent) {
495
- throw new Error(`putIfAbsent is not supported by this FeltDB runtime. ` +
496
- `Only FileJsDb (Node.js) supports durable atomic conditional creation.`);
497
- }
498
- const result = await this.db.putIfAbsent(key, stringValue);
661
+ // A runtime that cannot create conditionally by itself but can commit an
662
+ // atomic transaction has the capability under a different name, and the
663
+ // transaction carries the same `requireAbsent` predicate to the same
664
+ // authority. See `updateIfVersion` for why this is a route and not an
665
+ // emulation.
666
+ const result = this.db.putIfAbsent
667
+ ? await this.db.putIfAbsent(key, stringValue)
668
+ : await this.putIfAbsentThroughTransaction(key, stored);
499
669
  // Emit change through reactive dependency graph if this was a new insert
500
670
  if (result.inserted) {
501
671
  const change = {
@@ -79,8 +79,10 @@ export async function createProject(options) {
79
79
  }
80
80
  fs.writeFileSync(path.join(projectDir, 'package.json'), JSON.stringify(packageJson, null, 2));
81
81
  // Create feltdb.config.json
82
+ const deploymentMode = runtime === 'browser' ? 'browser' : runtime === 'node' ? 'local' : 'remote';
82
83
  const feltdbConfig = {
83
84
  namespace: applicationName,
85
+ mode: deploymentMode,
84
86
  runtime,
85
87
  storage: runtime === 'browser' ? 'indexeddb' : runtime === 'managed' ? 'managed' : 'durable',
86
88
  distributed,
@@ -195,13 +197,6 @@ ${hasAgents ? ` agent WorkspaceAssistant {
195
197
  }
196
198
  fs.writeFileSync(path.join(projectDir, 'tsconfig.json'), JSON.stringify(tsconfig, null, 2));
197
199
  // Create main application files
198
- const runtimeOptions = runtime === 'browser'
199
- ? "{ namespace: import.meta.env.VITE_FELTDB_NAMESPACE || '" + applicationName + "', browser: true }"
200
- : runtime === 'managed'
201
- ? "{ namespace: '" + applicationName + "', server: { url: import.meta.env.VITE_FELTDB_MANAGED_URL || '', applicationId: import.meta.env.VITE_FELTDB_MANAGED_APPLICATION_ID, environment: 'production' } }"
202
- : runtime === 'self-hosted'
203
- ? "{ namespace: import.meta.env.VITE_FELTDB_NAMESPACE || '" + applicationName + "', server: { url: import.meta.env.VITE_FELTDB_URL || 'http://localhost:7700', token: import.meta.env.VITE_FELTDB_API_KEY || '' } }"
204
- : "{ namespace: import.meta.env.VITE_FELTDB_NAMESPACE || '" + applicationName + "', memory: true }";
205
200
  const feltdbTs = `import { configureDevelopmentRuntimeBridge, createFeltDB } from '@feltdb/core';
206
201
 
207
202
  configureDevelopmentRuntimeBridge({
@@ -214,7 +209,7 @@ configureDevelopmentRuntimeBridge({
214
209
  applicationUrl: import.meta.env.VITE_FELTDB_APPLICATION_URL,
215
210
  });
216
211
 
217
- export const db = createFeltDB(${runtimeOptions});
212
+ export const db = createFeltDB();
218
213
 
219
214
  // Collections
220
215
  export const projects = db.collection('Project');
@@ -1198,7 +1193,7 @@ root.render(
1198
1193
  }
1199
1194
  fs.writeFileSync(path.join(projectDir, 'feltdb.flow'), fs.readFileSync(path.join(canonicalTemplate, 'feltdb.flow'), 'utf8').replace('app FeltDBStarter', `app ${appName}`));
1200
1195
  const databasePath = path.join(srcDir, 'feltdb.ts');
1201
- fs.writeFileSync(databasePath, fs.readFileSync(databasePath, 'utf8').replace(/export const managedRuntime[\s\S]*?\n\} : \{ namespace: import\.meta\.env\.VITE_FELTDB_NAMESPACE \|\| 'feltdb-starter', browser: true \}\);/, `export const managedRuntime = ${runtime === 'managed'};\nexport const db = createFeltDB(${runtimeOptions});`));
1196
+ fs.writeFileSync(databasePath, fs.readFileSync(databasePath, 'utf8').replace(/export const managedRuntime[\s\S]*?\n\} : \{ namespace: import\.meta\.env\.VITE_FELTDB_NAMESPACE \|\| 'feltdb-starter', browser: true \}\);/, `export const managedRuntime = ${runtime === 'managed'};\nexport const db = createFeltDB();`));
1202
1197
  }
1203
1198
  }
1204
1199
  else {
@@ -1234,18 +1229,27 @@ main().catch(console.error);
1234
1229
  const envExample = `# FeltDB Configuration
1235
1230
  # Copy this file to .env.local and update the values
1236
1231
 
1237
- # API key for authenticated self-hosted browser access
1238
- VITE_FELTDB_API_KEY=
1239
-
1240
- # FeltDB Server URL (self-hosted or managed runtime)
1241
- VITE_FELTDB_URL=http://localhost:7700
1242
-
1243
- # Managed example: https://runtime.your-app.feltdb.com
1244
- VITE_FELTDB_MANAGED_URL=
1245
- VITE_FELTDB_MANAGED_APPLICATION_ID=
1246
- # Server/control-plane only. Never rename with a VITE_ prefix.
1247
- FELTDB_MANAGED_CONTROL_API_KEY=
1248
- FELTDB_TOKEN=
1232
+ # Canonical application-plane deployment configuration consumed by createFeltDB()
1233
+ # Supported public modes: browser, local, remote
1234
+ VITE_FELTDB_MODE=${deploymentMode}
1235
+ VITE_FELTDB_NAMESPACE=${applicationName}
1236
+ VITE_FELTDB_ENVIRONMENT=development
1237
+
1238
+ # FeltDB Server URL (self-hosted or managed runtime)
1239
+ VITE_FELTDB_URL=http://localhost:7700
1240
+
1241
+ # Optional credential for authenticated remote browser access
1242
+ VITE_FELTDB_TOKEN=
1243
+ # Legacy alias accepted by older tooling
1244
+ VITE_FELTDB_API_KEY=
1245
+
1246
+ # Managed example: https://runtime.your-app.feltdb.com
1247
+ VITE_FELTDB_MANAGED_URL=
1248
+ VITE_FELTDB_MANAGED_APPLICATION_ID=
1249
+ VITE_FELTDB_APPLICATION_ID=
1250
+ # Server/control-plane only. Never rename with a VITE_ prefix.
1251
+ FELTDB_MANAGED_CONTROL_API_KEY=
1252
+ FELTDB_TOKEN=
1249
1253
 
1250
1254
  # Override the default self-hosted container image
1251
1255
  # By default the server image is built locally from the bundled source.
@@ -100,6 +100,17 @@ export async function configureManagedAccount(options) {
100
100
  }
101
101
  const environment = [
102
102
  '# Generated by create-feltdb managed setup. Do not commit this file.',
103
+ 'FELTDB_MODE=remote',
104
+ `FELTDB_URL=${apiUrl}`,
105
+ `FELTDB_APPLICATION_ID=${application.id}`,
106
+ `FELTDB_NAMESPACE=${application.namespace || options.namespace}`,
107
+ 'FELTDB_ENVIRONMENT=production',
108
+ '# Browser-safe deployment identity aliases.',
109
+ 'VITE_FELTDB_MODE=remote',
110
+ `VITE_FELTDB_URL=${apiUrl}`,
111
+ `VITE_FELTDB_APPLICATION_ID=${application.id}`,
112
+ `VITE_FELTDB_NAMESPACE=${application.namespace || options.namespace}`,
113
+ 'VITE_FELTDB_ENVIRONMENT=production',
103
114
  `VITE_FELTDB_MANAGED_URL=${apiUrl}`,
104
115
  `VITE_FELTDB_MANAGED_APPLICATION_ID=${application.id}`,
105
116
  '# Control-plane credential. Never prefix this with VITE_ or expose it to browser code.',
@@ -1,4 +1,4 @@
1
1
  // One release train keeps generated applications installable. The repository
2
2
  // validation script checks these values against every workspace manifest.
3
- export const FELTDB_PACKAGE_VERSION = '0.8.3';
3
+ export const FELTDB_PACKAGE_VERSION = '0.8.4';
4
4
  export const feltdbPackageRange = `^${FELTDB_PACKAGE_VERSION}`;