@feltdb/core 0.8.2 → 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.
- package/dist/cli/commands.js +4 -1
- package/dist/cli/provisioning-neutrality.js +79 -0
- package/dist/collection.d.ts +43 -1
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +192 -22
- package/dist/create/create.js +25 -21
- package/dist/create/managed-account.js +11 -0
- package/dist/create/package-versions.js +1 -1
- package/dist/create/server-source/crates/feltdb/src/equality_index.rs +595 -0
- package/dist/create/server-source/crates/feltdb/src/lib.rs +547 -115
- package/dist/create/server-source/crates/feltdb/src/phase1c3_acceptance.rs +11 -2
- package/dist/create/server-source/crates/feltdb/src/query_execution_diagnostics.rs +126 -0
- package/dist/create/server-source/crates/feltdb/src/state_contract.rs +292 -2
- package/dist/create/server-source/crates/feltdb/src/sync.rs +12 -0
- package/dist/create/server-source/crates/feltdb/src/workload_diagnostics.rs +443 -0
- package/dist/create/server-source/crates/feltdb/tests/pr34_query_collection.rs +233 -0
- package/dist/create/server-source/crates/feltdb/tests/pr35_equality_index.rs +892 -0
- package/dist/create/server-source/crates/feltdb-server/src/audit.rs +1137 -29
- package/dist/create/server-source/crates/feltdb-server/src/main.rs +474 -28
- package/dist/db.d.ts +33 -34
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +74 -20
- package/dist/deployment.d.ts +30 -0
- package/dist/deployment.d.ts.map +1 -0
- package/dist/deployment.js +130 -0
- package/dist/embedded-transaction.d.ts +22 -4
- package/dist/embedded-transaction.d.ts.map +1 -1
- package/dist/embedded-transaction.js +51 -5
- package/dist/feltdb.d.ts +14 -2
- package/dist/feltdb.d.ts.map +1 -1
- package/dist/file-db.d.ts +8 -15
- package/dist/file-db.d.ts.map +1 -1
- package/dist/file-db.js +234 -130
- package/dist/http-client.d.ts +14 -0
- package/dist/http-client.d.ts.map +1 -1
- package/dist/http-client.js +23 -5
- package/dist/http-db.d.ts +119 -1
- package/dist/http-db.d.ts.map +1 -1
- package/dist/http-db.js +346 -31
- package/dist/index-core.d.ts +2 -0
- package/dist/index-core.d.ts.map +1 -1
- package/dist/index-core.js +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -0
- package/dist/indexeddb-db.d.ts.map +1 -1
- package/dist/indexeddb-db.js +35 -21
- package/dist/managed-recovery.d.ts +192 -0
- package/dist/managed-recovery.d.ts.map +1 -0
- package/dist/managed-recovery.js +242 -0
- package/dist/memory-db.js +1 -1
- package/dist/studio-app/assets/{feltdb_wasm-DB8cX151.js → feltdb_wasm-CVQWgXO-.js} +1 -1
- package/dist/studio-app/assets/feltdb_wasm_bg-CNVpvaZV.wasm +0 -0
- package/dist/studio-app/assets/index-DwgNAIIX.js +29 -0
- package/dist/studio-app/index.html +1 -1
- package/dist/transaction.d.ts +30 -0
- package/dist/transaction.d.ts.map +1 -1
- package/dist/transaction.js +41 -0
- package/dist/wasm/feltdb_wasm_bg.wasm +0 -0
- package/package.json +1 -1
- package/dist/studio-app/assets/feltdb_wasm_bg-ClhDHp0S.wasm +0 -0
- 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
|
-
|
|
65
|
-
|
|
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,
|
|
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
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
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
|
-
|
|
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 () =>
|
|
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
|
|
381
|
-
|
|
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
|
-
|
|
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
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
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}`;
|
package/dist/index-core.d.ts
CHANGED
|
@@ -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';
|
package/dist/index-core.d.ts.map
CHANGED
|
@@ -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"}
|
package/dist/index-core.js
CHANGED
|
@@ -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';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;
|
|
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":"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;AAQ1F,UAAU,QAAQ;IAAG,OAAO,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE;AACtE,UAAU,YAAY;IAAG,QAAQ,CAAC,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;AACrK,KAAK,cAAc,GAAG,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;AAEnD,qGAAqG;AACrG,qBAAa,aAAc,YAAW,IAAI;IACxC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAuB;IAChD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAmB;IAC5C,OAAO,CAAC,QAAQ,CAAK;IACrB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAqB;IAC3C,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAsD;IACtF,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,oBAAoB,CAAK;IACjC,OAAO,CAAC,iBAAiB,CAAS;IAClC,OAAO,CAAC,eAAe,CAAC,CAAgC;IACxD,OAAO,CAAC,sBAAsB,CAAS;IACvC,OAAO,CAAC,sBAAsB,CAAS;IACvC,OAAO,CAAC,0BAA0B,CAAwD;IAC1F,OAAO,CAAC,QAAQ,CAAC,wBAAwB,CAAqB;gBAElD,SAAS,EAAE,MAAM;IAO7B,OAAO,CAAC,IAAI;IAgBZ,OAAO,CAAC,QAAQ;YAMF,MAAM;IA8BpB,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;IACrD,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;IACrD,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;IAEtC;;;;;;;OAOG;IACG,kBAAkB,CAAC,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"indexeddb-db.d.ts","sourceRoot":"","sources":["../src/indexeddb-db.ts"],"names":[],"mappings":"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"}
|