@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
package/dist/http-db.js CHANGED
@@ -1,17 +1,100 @@
1
1
  import { FeltDBServiceError } from './error-codes.js';
2
+ import { conditionalRefusal } from './transaction.js';
3
+ import { BoundedBackoff, SingleFlightValue, cancellableDelay, describeFailure, } from './managed-recovery.js';
4
+ /**
5
+ * A durable identity for one conditional write attempt.
6
+ *
7
+ * The authority deduplicates by transaction id, so a redelivered request
8
+ * cannot apply the same conditional write twice. Each *attempt* gets its own
9
+ * id, because a retry after a conflict is a new decision against fresh state
10
+ * and must be evaluated again rather than answered from the first attempt.
11
+ */
12
+ function conditionalTransactionId(purpose) {
13
+ const unique = typeof crypto?.randomUUID === 'function'
14
+ ? crypto.randomUUID()
15
+ : `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
16
+ return `${purpose}-${unique}`;
17
+ }
2
18
  /** Network adapter implementing the same collection runtime contract as WASM. */
3
19
  export class HttpJsDb {
4
20
  url;
5
21
  token;
6
22
  applicationId;
7
23
  environment;
8
- activeRevision;
9
24
  authWaiters = new Set();
25
+ /**
26
+ * Canonical revision discovery.
27
+ *
28
+ * This used to be `private activeRevision?: Promise<string>` assigned with
29
+ * `??=`, which cached the rejected promise a single timeout produced and
30
+ * poisoned the client for its whole life. It is a state machine now, and the
31
+ * state it refuses to keep is a failure.
32
+ */
33
+ revisionCache = new SingleFlightValue();
34
+ backoff;
35
+ requestTimeoutMs;
36
+ connectionState = 'closed';
37
+ availabilityState = 'unknown';
38
+ lastError;
39
+ lastSuccessAt;
40
+ nextRetryAt;
41
+ streamFailures = 0;
42
+ authorityFailures = 0;
10
43
  constructor(options) {
11
44
  this.url = options.url.replace(/\/$/, '');
12
45
  this.token = options.token || '';
13
46
  this.applicationId = options.applicationId;
14
47
  this.environment = options.environment || 'production';
48
+ this.requestTimeoutMs = Math.max(1, options.requestTimeoutMs ?? 30_000);
49
+ this.backoff = new BoundedBackoff(options.reconnect);
50
+ }
51
+ /**
52
+ * The operational view: connected, recovering, or unable to proceed.
53
+ *
54
+ * This does not replace the typed error a caller already receives. An
55
+ * application handles `FeltDBServiceError`; an operator reads this. Before
56
+ * PR39 the only signal for a swallowed failure was a `console.error` in the
57
+ * collection refresh path, which no program can act on.
58
+ */
59
+ health() {
60
+ const revision = this.revisionCache.snapshot();
61
+ const pending = this.nextRetryAt !== undefined ? this.nextRetryAt - Date.now() : undefined;
62
+ return {
63
+ connection_state: this.connectionState,
64
+ availability_state: this.availabilityState,
65
+ usable: this.availabilityState === 'available' && revision.state === 'cached',
66
+ recovery_possible: this.availabilityState !== 'closed'
67
+ && !['authentication', 'authorization', 'configuration'].includes(this.lastError?.failure_class ?? ''),
68
+ revision_state: revision.state,
69
+ ...(this.revisionCache.value !== undefined ? { revision_id: this.revisionCache.value } : {}),
70
+ consecutive_failures: this.authorityFailures,
71
+ ...(this.lastSuccessAt ?? revision.last_success_at
72
+ ? { last_success_at: this.lastSuccessAt ?? revision.last_success_at }
73
+ : {}),
74
+ ...(this.lastError ?? revision.last_failure
75
+ ? { last_error: this.lastError ?? revision.last_failure }
76
+ : {}),
77
+ ...(this.connectionState === 'backoff' && this.nextRetryAt !== undefined
78
+ ? {
79
+ next_retry_at: new Date(this.nextRetryAt).toISOString(),
80
+ next_retry_in_ms: Math.max(0, pending ?? 0),
81
+ }
82
+ : {}),
83
+ };
84
+ }
85
+ /** Record a failure for the operational surface, without swallowing it. */
86
+ recordFailure(error) {
87
+ const failure = describeFailure(error);
88
+ this.lastError = failure;
89
+ this.authorityFailures += 1;
90
+ this.availabilityState = failure.failure_class === 'closed' ? 'closed' : 'degraded';
91
+ return failure;
92
+ }
93
+ recordSuccess() {
94
+ this.availabilityState = 'available';
95
+ this.lastSuccessAt = new Date().toISOString();
96
+ this.lastError = undefined;
97
+ this.authorityFailures = 0;
15
98
  }
16
99
  headers(json = false) {
17
100
  return {
@@ -22,6 +105,10 @@ export class HttpJsDb {
22
105
  }
23
106
  updateToken(token) {
24
107
  this.token = token;
108
+ // Credentials changed, so a permanent authorization failure may no longer
109
+ // be permanent. The backoff returns to base for the same reason: the next
110
+ // attempt is against a different premise, not a repeat of the last one.
111
+ this.backoff.reset();
25
112
  for (const wake of this.authWaiters)
26
113
  wake();
27
114
  this.authWaiters.clear();
@@ -58,11 +145,38 @@ export class HttpJsDb {
58
145
  }
59
146
  return [key.slice(0, separator), key.slice(separator + 1)];
60
147
  }
148
+ /**
149
+ * The canonical revision for this application and environment.
150
+ *
151
+ * A success is cached for the life of the client, because a revision pointer
152
+ * the authority has already resolved does not change under a running client.
153
+ * Concurrent callers share one lookup. A failure is delivered to everyone
154
+ * waiting and cached by nobody, so the next operation tries again against
155
+ * whatever the authority has become.
156
+ *
157
+ * Nothing here retries on its own. That is the design, not an omission: the
158
+ * only automatic retry loop in this client is the event stream, and it is
159
+ * bounded and jittered. A revision lookup is re-attempted exactly when the
160
+ * application performs another operation, so a permanently misconfigured
161
+ * application produces the application's own call rate against the
162
+ * authority, never a client-generated storm on top of it.
163
+ */
61
164
  async revisionId() {
62
165
  if (!this.applicationId)
63
166
  throw new Error('applicationId is required for the canonical application service');
64
- this.activeRevision ??= this.application_request('/application').then(value => value.revision_id);
65
- return this.activeRevision;
167
+ return this.revisionCache.get(async () => {
168
+ const value = await this.application_request('/application');
169
+ if (!value?.revision_id) {
170
+ const error = new FeltDBServiceError('canonical application discovery returned no revision_id', 'VALIDATION_ERROR', undefined, 422);
171
+ this.recordFailure(error);
172
+ throw error;
173
+ }
174
+ return value.revision_id;
175
+ });
176
+ }
177
+ /** Discovery state for tests and diagnostics; never used to make decisions. */
178
+ revisionState() {
179
+ return this.revisionCache.state;
66
180
  }
67
181
  async canonical(path, payload) {
68
182
  const revisionId = await this.revisionId();
@@ -70,13 +184,13 @@ export class HttpJsDb {
70
184
  application_id: this.applicationId, revision_id: revisionId, environment: this.environment, ...payload,
71
185
  }) });
72
186
  }
73
- transactionDocument(revisionId, operations, transactionId = crypto.randomUUID()) {
187
+ transactionDocument(revisionId, operations, transactionId = crypto.randomUUID(), preconditions = []) {
74
188
  return { transaction_id: transactionId, tenant_id: '', application_id: this.applicationId, revision_id: revisionId, schema_version: 0,
75
- authorization: { subject: '', tenant_id: '', application_id: this.applicationId, revision_id: revisionId, capabilities: [] }, operations };
189
+ authorization: { subject: '', tenant_id: '', application_id: this.applicationId, revision_id: revisionId, capabilities: [] }, operations, preconditions };
76
190
  }
77
- async canonicalTransaction(operations, transactionId) {
191
+ async canonicalTransaction(operations, transactionId, preconditions = []) {
78
192
  const revisionId = await this.revisionId();
79
- return this.application_request('/transactions', { method: 'POST', body: JSON.stringify({ application_id: this.applicationId, revision_id: revisionId, environment: this.environment, transaction: this.transactionDocument(revisionId, operations, transactionId) }) });
193
+ return this.application_request('/transactions', { method: 'POST', body: JSON.stringify({ application_id: this.applicationId, revision_id: revisionId, environment: this.environment, transaction: this.transactionDocument(revisionId, operations, transactionId, preconditions) }) });
80
194
  }
81
195
  async get(key) {
82
196
  try {
@@ -193,13 +307,32 @@ export class HttpJsDb {
193
307
  */
194
308
  async commit_transaction(request) {
195
309
  if (this.applicationId) {
196
- if (request.preconditions?.length)
197
- throw new Error('record preconditions are not representable by the canonical transaction contract');
198
310
  const operations = request.operations.map(operation => ({
199
311
  kind: operation.value === undefined ? 'delete' : operation.requireAbsent ? 'insert' : 'update',
200
- collection: operation.collection, id: operation.id, value: operation.value, if_version: operation.expectedVersion,
312
+ collection: operation.collection, id: operation.id, value: operation.value,
313
+ }));
314
+ // Every record-level guard travels as a `record` precondition, which the
315
+ // authority evaluates inside the same lock as the writes and against the
316
+ // fields a caller can actually read back.
317
+ //
318
+ // `if_version` is deliberately not used for this. It fences the row's
319
+ // internal storage sequence, and a caller's `expectedVersion` is the
320
+ // document's `__version`: two different numbers that happen to share a
321
+ // name. Sending one as the other is a fence that compares the wrong
322
+ // counter, which is worse than no fence at all because it reads as one.
323
+ const guards = [
324
+ ...(request.preconditions ?? []),
325
+ ...request.operations.filter(operation => operation.expectedVersion !== undefined
326
+ || operation.expectedEpoch !== undefined
327
+ || operation.expectedLeaseId !== undefined),
328
+ ].map(guard => ({
329
+ kind: 'record', collection: guard.collection, id: guard.id,
330
+ require_absent: guard.requireAbsent ?? false,
331
+ expected_version: guard.expectedVersion,
332
+ expected_epoch: guard.expectedEpoch,
333
+ expected_lease_id: guard.expectedLeaseId,
201
334
  }));
202
- const result = await this.canonicalTransaction(operations, request.transactionId);
335
+ const result = await this.canonicalTransaction(operations, request.transactionId, guards);
203
336
  return { transactionId: result.transaction_id, baseRevision: result.base_revision, commitRevision: result.commit_revision, status: result.status, duplicate: result.duplicate, operations: operations.length, stateBefore: result.state_before, stateAfter: result.state_after };
204
337
  }
205
338
  const response = await fetch(`${this.url}/transactions`, {
@@ -228,7 +361,128 @@ export class HttpJsDb {
228
361
  const body = await response.json();
229
362
  return body;
230
363
  }
364
+ /**
365
+ * Atomically create a record only if the key is free.
366
+ *
367
+ * Routed through the authority's own transaction, not emulated with a read
368
+ * followed by a write: between a read and a write another writer commits,
369
+ * and the whole point of this call is that exactly one caller wins.
370
+ *
371
+ * A lost race is a value, not an exception -- `{ inserted: false }` with the
372
+ * record the winner wrote. Every other failure (authentication, schema,
373
+ * transport, a server error) is raised, because reporting one of those as
374
+ * "someone else got there first" would let a caller skip work it must do.
375
+ *
376
+ * One difference from the embedded runtimes is worth stating rather than
377
+ * discovering: the authority owns `__version` on a conditional create and
378
+ * stores 1, where an embedded runtime stores the caller's object verbatim.
379
+ * `Collection.putIfAbsent` supplies version 1 either way, so a collection
380
+ * caller sees no difference; a caller of this raw key/value surface does.
381
+ */
382
+ async putIfAbsent(key, value) {
383
+ const [collection, id] = this.splitKey(key);
384
+ const record = JSON.parse(value);
385
+ try {
386
+ await this.commit_transaction({
387
+ transactionId: conditionalTransactionId('put-if-absent'),
388
+ operations: [{ collection, id, value: record, requireAbsent: true }],
389
+ });
390
+ }
391
+ catch (error) {
392
+ if (!conditionalRefusal(error).conflict)
393
+ throw error;
394
+ const existing = await this.get(key);
395
+ if (!existing.success) {
396
+ throw new Error(existing.error || `could not read the record that won ${key}`);
397
+ }
398
+ if (existing.data === undefined) {
399
+ // The winner's record was removed between the refusal and this read.
400
+ // Reporting it as an existing record would hand back a value nobody
401
+ // wrote, so the ambiguity is raised instead of invented away.
402
+ throw new Error(`${key} was created and then removed while resolving a conditional create`);
403
+ }
404
+ return { inserted: false, value: existing.data };
405
+ }
406
+ // The authority assigns version 1 inside the commit boundary for a
407
+ // conditional create, so the committed record is known without a second
408
+ // request. See docs/architecture/transaction-version-contract.md.
409
+ return { inserted: true, value: JSON.stringify({ ...record, id, __version: 1 }) };
410
+ }
411
+ /**
412
+ * Compare-and-set through the managed authority's transaction surface.
413
+ *
414
+ * The managed runtime serves the canonical application contract and not the
415
+ * single-record `/cas` endpoint, so a conditional update reaches it as a
416
+ * one-operation transaction fenced on the record's `__version`. It is the
417
+ * same predicate, evaluated by the same authority, inside the same lock.
418
+ */
419
+ async managedCas(params) {
420
+ if (params.rejectIfDiverged) {
421
+ // Refused rather than dropped. A guard that is silently ignored reads as
422
+ // protection and provides none.
423
+ throw new Error('rejectIfDiverged is a replication predicate the canonical transaction contract cannot express');
424
+ }
425
+ const [collection, id] = this.splitKey(params.key);
426
+ const proposed = JSON.parse(params.value);
427
+ // The authority owns the next version. A caller-computed one must not be
428
+ // installable, so it never leaves here.
429
+ delete proposed.__version;
430
+ try {
431
+ await this.commit_transaction({
432
+ transactionId: conditionalTransactionId('update-if-version'),
433
+ operations: [{
434
+ collection, id, value: proposed,
435
+ expectedVersion: params.expectedVersion,
436
+ expectedEpoch: params.expectedEpoch,
437
+ expectedLeaseId: params.expectedLeaseId,
438
+ }],
439
+ });
440
+ }
441
+ catch (error) {
442
+ const refusal = conditionalRefusal(error);
443
+ if (!refusal.conflict)
444
+ throw error;
445
+ return { updated: false, ...(await this.observedConflict(params.key, refusal.failure)) };
446
+ }
447
+ const committed = { ...proposed, id, __version: params.expectedVersion + 1 };
448
+ return {
449
+ updated: true,
450
+ currentVersion: params.expectedVersion + 1,
451
+ currentEpoch: params.expectedEpoch,
452
+ item: committed,
453
+ };
454
+ }
455
+ /**
456
+ * What the authority actually holds after it refused a conditional update.
457
+ *
458
+ * The refusal usually names it, and that is the answer worth having because
459
+ * it was read inside the commit lock. When the failure does not carry a
460
+ * version -- the record was deleted, or an epoch or lease predicate failed --
461
+ * the record is re-read, which is what the caller would have to do anyway.
462
+ */
463
+ async observedConflict(key, failure) {
464
+ const conflictCode = failure?.predicate === 'version' ? 'VERSION_CONFLICT'
465
+ : failure?.predicate === 'epoch' ? 'AUTHORITY_CONFLICT'
466
+ : failure?.predicate === 'lease' ? 'LEASE_CONFLICT'
467
+ : failure?.predicate === 'missing' ? 'RECORD_MISSING'
468
+ : failure?.predicate === 'present' ? 'RECORD_PRESENT'
469
+ : undefined;
470
+ if (failure?.predicate === 'version' && typeof failure.actual === 'number') {
471
+ return { currentVersion: failure.actual, conflictCode };
472
+ }
473
+ const current = await this.get(key);
474
+ const record = current.success && current.data
475
+ ? JSON.parse(current.data)
476
+ : undefined;
477
+ return {
478
+ currentVersion: typeof record?.__version === 'number' ? record.__version : 0,
479
+ currentEpoch: record?.authority?.epoch,
480
+ conflictCode,
481
+ };
482
+ }
231
483
  async cas(params) {
484
+ if (this.applicationId)
485
+ return this.managedCas(params);
232
486
  try {
233
487
  const [collection, id] = this.splitKey(params.key);
234
488
  const parsed = JSON.parse(params.value);
@@ -337,11 +591,20 @@ export class HttpJsDb {
337
591
  const context = this.applicationId
338
592
  ? `${separator}application_id=${encodeURIComponent(this.applicationId)}&environment=${encodeURIComponent(this.environment)}`
339
593
  : '';
340
- const response = await fetch(`${this.url}/v1${path}${context}`, {
341
- ...init,
342
- signal: init.signal ?? AbortSignal.timeout(30_000),
343
- headers: { ...this.headers(init.body !== undefined), ...(init.headers || {}) },
344
- });
594
+ if (this.availabilityState === 'degraded')
595
+ this.availabilityState = 'recovering';
596
+ let response;
597
+ try {
598
+ response = await fetch(`${this.url}/v1${path}${context}`, {
599
+ ...init,
600
+ signal: init.signal ?? AbortSignal.timeout(this.requestTimeoutMs),
601
+ headers: { ...this.headers(init.body !== undefined), ...(init.headers || {}) },
602
+ });
603
+ }
604
+ catch (error) {
605
+ this.recordFailure(error);
606
+ throw error;
607
+ }
345
608
  const text = await response.text();
346
609
  let body;
347
610
  try {
@@ -351,34 +614,75 @@ export class HttpJsDb {
351
614
  body = { message: text };
352
615
  }
353
616
  if (!response.ok) {
354
- throw new FeltDBServiceError(body?.message || body?.error || `FeltDB service request failed: HTTP ${response.status}`, body?.code || body?.error || 'REQUEST_FAILED', body?.request_id, response.status);
617
+ const error = new FeltDBServiceError(body?.message || body?.error || `FeltDB service request failed: HTTP ${response.status}`, body?.code || body?.error || 'REQUEST_FAILED', body?.request_id, response.status);
618
+ this.recordFailure(error);
619
+ throw error;
355
620
  }
621
+ this.recordSuccess();
356
622
  return body;
357
623
  }
624
+ /**
625
+ * Subscribe to change notifications.
626
+ *
627
+ * The returned function is the only cancellation surface, and it has to do
628
+ * two things rather than one: abort the connection that may be open, and
629
+ * cancel the reconnect that may be pending. Cancelling only the first leaves
630
+ * a timer that wakes up after the caller has unsubscribed and opens a new
631
+ * connection to an authority nobody is listening to.
632
+ */
358
633
  subscribe_changes(callback) {
359
634
  const controller = new AbortController();
635
+ this.connectionState = 'connecting';
360
636
  void this.consumeEvents(controller, callback);
361
- return () => controller.abort();
637
+ return () => {
638
+ // `abort()` reaches both: the in-flight fetch through its signal, and
639
+ // the pending backoff through the listener `cancellableDelay` installs.
640
+ controller.abort();
641
+ this.connectionState = 'closed';
642
+ this.nextRetryAt = undefined;
643
+ };
362
644
  }
363
645
  async consumeEvents(controller, callback) {
364
646
  while (!controller.signal.aborted) {
365
647
  if (!this.token) {
648
+ // No credentials is not a transient transport failure and must not be
649
+ // retried on a timer. The loop waits for a token rather than spinning.
650
+ this.connectionState = 'failed';
651
+ this.availabilityState = 'degraded';
366
652
  await this.waitForAuthChange(controller.signal);
367
653
  continue;
368
654
  }
655
+ let connected = false;
656
+ let reader;
369
657
  try {
658
+ this.connectionState = 'connecting';
370
659
  const eventPath = this.applicationId ? `/v1/events?application_id=${encodeURIComponent(this.applicationId)}&environment=${encodeURIComponent(this.environment)}` : '/events';
371
660
  const response = await fetch(`${this.url}${eventPath}`, {
372
661
  headers: this.headers(),
373
662
  signal: controller.signal,
374
663
  });
375
664
  if (response.status === 401 || response.status === 403) {
665
+ // Permanent until something changes. Waiting for an auth change is
666
+ // the correct response; backing off and retrying the same rejected
667
+ // credentials is a request storm with extra steps.
668
+ this.recordFailure(new FeltDBServiceError(`Event stream rejected: HTTP ${response.status}`, response.status === 401 ? 'AUTHENTICATION_REQUIRED' : 'FORBIDDEN', undefined, response.status));
669
+ this.connectionState = 'failed';
670
+ this.availabilityState = 'degraded';
376
671
  await this.waitForAuthChange(controller.signal);
377
672
  continue;
378
673
  }
379
- if (!response.ok || !response.body)
380
- throw new Error(`Event stream failed: HTTP ${response.status}`);
381
- const reader = response.body.getReader();
674
+ if (!response.ok || !response.body) {
675
+ throw new FeltDBServiceError(`Event stream failed: HTTP ${response.status}`, 'SERVICE_UNAVAILABLE', undefined, response.status);
676
+ }
677
+ // The stream is open and readable. This is what "stable" means here,
678
+ // and it is what resets the backoff: a connection that was accepted
679
+ // and delivered is evidence the authority is serving again.
680
+ connected = true;
681
+ this.connectionState = 'connected';
682
+ this.streamFailures = 0;
683
+ this.recordSuccess();
684
+ this.backoff.reset();
685
+ reader = response.body.getReader();
382
686
  const decoder = new TextDecoder();
383
687
  let buffer = '';
384
688
  while (!controller.signal.aborted) {
@@ -400,19 +704,30 @@ export class HttpJsDb {
400
704
  }
401
705
  catch (error) {
402
706
  if (controller.signal.aborted)
403
- return;
707
+ break;
708
+ this.streamFailures += 1;
709
+ this.recordFailure(error);
710
+ }
711
+ finally {
712
+ // Release the body whether the stream ended, failed or was aborted.
713
+ if (reader)
714
+ await reader.cancel().catch(() => { });
404
715
  }
405
716
  if (controller.signal.aborted)
406
- return;
407
- // The retry timer must not keep a Node process alive on its own. An
408
- // authority that has shut down would otherwise stop its clients from
409
- // ever exiting, because this loop reconnects forever.
410
- await new Promise(resolve => {
411
- const timer = setTimeout(resolve, 1000);
412
- timer?.unref?.();
413
- controller.signal.addEventListener('abort', () => { clearTimeout(timer); resolve(); }, { once: true });
414
- });
717
+ break;
718
+ if (!connected) {
719
+ // Only a connection that never opened counts against the backoff. A
720
+ // stream that ran and then ended is a normal reconnect, not a failure.
721
+ this.streamFailures = Math.max(this.streamFailures, 1);
722
+ }
723
+ const delay = this.backoff.next();
724
+ this.connectionState = 'backoff';
725
+ this.nextRetryAt = Date.now() + delay;
726
+ await cancellableDelay(delay, controller.signal);
727
+ this.nextRetryAt = undefined;
415
728
  }
729
+ this.connectionState = 'closed';
730
+ this.nextRetryAt = undefined;
416
731
  }
417
732
  async failure(response) {
418
733
  let detail = `HTTP ${response.status}`;
@@ -9,6 +9,7 @@
9
9
  * Applications import `@feltdb/core`, not this module directly.
10
10
  */
11
11
  export * from './db.js';
12
+ export * from './deployment.js';
12
13
  export * from './collection.js';
13
14
  export * from './transaction.js';
14
15
  export * from './freshness.js';
@@ -40,6 +41,7 @@ export * from './state-contract.js';
40
41
  export * from './revision-recovery.js';
41
42
  export * from './operation-admission.js';
42
43
  export * from './error-codes.js';
44
+ export * from './managed-recovery.js';
43
45
  export * from './authorization.js';
44
46
  export * from './sync-contract.js';
45
47
  export * from './workload.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index-core.d.ts","sourceRoot":"","sources":["../src/index-core.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,cAAc,SAAS,CAAC;AACxB,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,WAAW,CAAC;AAC1B,cAAc,qBAAqB,CAAC;AACpC,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,YAAY,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAC9E,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,mBAAmB,CAAC;AAClC,cAAc,eAAe,CAAC;AAC9B,cAAc,2BAA2B,CAAC;AAC1C,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC;AACvC,cAAc,0BAA0B,CAAC;AACzC,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,iCAAiC,CAAC;AAChD,cAAc,2BAA2B,CAAC;AAC1C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,oBAAoB,CAAC;AACnC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,kBAAkB,CAAC;AACjC,cAAc,aAAa,CAAC;AAC5B,OAAO,EACL,eAAe,EACf,KAAK,OAAO,EACZ,KAAK,SAAS,IAAI,iBAAiB,EACnC,KAAK,QAAQ,IAAI,gBAAgB,GAClC,MAAM,eAAe,CAAC;AAEvB;;GAEG;AACH,cAAc,YAAY,CAAC;AAC3B,cAAc,wBAAwB,CAAC;AACvC,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AAEnC;;GAEG;AACH,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACnE,YAAY,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAEzE;;GAEG;AACH,YAAY,EACV,eAAe,EACf,cAAc,EACd,cAAc,EACd,kBAAkB,EAClB,kBAAkB,GACnB,MAAM,SAAS,CAAC;AAEjB;;;;GAIG;AACH,cAAc,wBAAwB,CAAC;AACvC,cAAc,iCAAiC,CAAC"}
1
+ {"version":3,"file":"index-core.d.ts","sourceRoot":"","sources":["../src/index-core.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,cAAc,SAAS,CAAC;AACxB,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,WAAW,CAAC;AAC1B,cAAc,qBAAqB,CAAC;AACpC,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,YAAY,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAC9E,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,mBAAmB,CAAC;AAClC,cAAc,eAAe,CAAC;AAC9B,cAAc,2BAA2B,CAAC;AAC1C,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC;AACvC,cAAc,0BAA0B,CAAC;AACzC,cAAc,kBAAkB,CAAC;AACjC,cAAc,uBAAuB,CAAC;AACtC,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,iCAAiC,CAAC;AAChD,cAAc,2BAA2B,CAAC;AAC1C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,oBAAoB,CAAC;AACnC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,kBAAkB,CAAC;AACjC,cAAc,aAAa,CAAC;AAC5B,OAAO,EACL,eAAe,EACf,KAAK,OAAO,EACZ,KAAK,SAAS,IAAI,iBAAiB,EACnC,KAAK,QAAQ,IAAI,gBAAgB,GAClC,MAAM,eAAe,CAAC;AAEvB;;GAEG;AACH,cAAc,YAAY,CAAC;AAC3B,cAAc,wBAAwB,CAAC;AACvC,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AAEnC;;GAEG;AACH,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACnE,YAAY,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAEzE;;GAEG;AACH,YAAY,EACV,eAAe,EACf,cAAc,EACd,cAAc,EACd,kBAAkB,EAClB,kBAAkB,GACnB,MAAM,SAAS,CAAC;AAEjB;;;;GAIG;AACH,cAAc,wBAAwB,CAAC;AACvC,cAAc,iCAAiC,CAAC"}
@@ -9,6 +9,7 @@
9
9
  * Applications import `@feltdb/core`, not this module directly.
10
10
  */
11
11
  export * from './db.js';
12
+ export * from './deployment.js';
12
13
  export * from './collection.js';
13
14
  export * from './transaction.js';
14
15
  export * from './freshness.js';
@@ -39,6 +40,7 @@ export * from './state-contract.js';
39
40
  export * from './revision-recovery.js';
40
41
  export * from './operation-admission.js';
41
42
  export * from './error-codes.js';
43
+ export * from './managed-recovery.js';
42
44
  export * from './authorization.js';
43
45
  export * from './sync-contract.js';
44
46
  export * from './workload.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAOH,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC;AAE7B;;;;;;GAMG;AACH,cAAc,sBAAsB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAeH,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC;AAE7B;;;;;;GAMG;AACH,cAAc,sBAAsB,CAAC"}
package/dist/index.js CHANGED
@@ -11,8 +11,17 @@
11
11
  * map, so that a bare `fs` import never enters a browser module graph.
12
12
  */
13
13
  import { registerRuntime } from './runtime-registry.js';
14
+ import { registerDeploymentConfigLoader } from './deployment.js';
14
15
  import { FileJsDb } from './file-db.js';
16
+ import * as nodeFs from 'fs';
17
+ import * as nodePath from 'path';
15
18
  registerRuntime('file', path => new FileJsDb(path));
19
+ registerDeploymentConfigLoader(() => {
20
+ const configPath = nodePath.join(process.cwd(), 'feltdb.config.json');
21
+ if (!nodeFs.existsSync(configPath))
22
+ return undefined;
23
+ return JSON.parse(nodeFs.readFileSync(configPath, 'utf8'));
24
+ });
16
25
  export * from './index-core.js';
17
26
  export * from './file-db.js';
18
27
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"indexeddb-db.d.ts","sourceRoot":"","sources":["../src/indexeddb-db.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"indexeddb-db.d.ts","sourceRoot":"","sources":["../src/indexeddb-db.ts"],"names":[],"mappings":"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"}