@nicia-ai/typegraph 0.67.1 → 0.68.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/dist/graph-merge/index.cjs +520 -2
- package/dist/graph-merge/index.cjs.map +1 -1
- package/dist/graph-merge/index.d.cts +298 -1
- package/dist/graph-merge/index.d.ts +298 -1
- package/dist/graph-merge/index.js +507 -3
- package/dist/graph-merge/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# @nicia-ai/typegraph
|
|
2
2
|
|
|
3
|
+
## 0.68.0
|
|
4
|
+
|
|
5
|
+
### Highlights
|
|
6
|
+
|
|
7
|
+
TypeGraph 0.68 adds atomic operations to durable graph-merge branches. `operateDurableBranch()` lets a durable host commit an opaque graph mutation and immutable evidence in one host transaction, so a process can recover and deliver committed work after a crash without inventing a second coordination protocol. Canonical request digests make retries exact: the same idempotency key replays its evidence, while a changed mutation or metadata payload conflicts without another write.
|
|
8
|
+
|
|
9
|
+
The new evidence lifecycle is inspectable and bounded. Applications can read or page committed evidence, mark delivery monotonically, and ask whether any evidence remains undelivered. TypeGraph validates every host-returned outcome and preserves a typed destruction fence until downstream delivery is complete; unsupported hosts execute no mutation, and existing durable strategies remain valid without the optional capability.
|
|
10
|
+
|
|
11
|
+
### Upgrade notes
|
|
12
|
+
|
|
13
|
+
- Existing `DurableWorkingCopyStrategy` implementations require no changes unless they opt into `operations`. To opt in, commit the host mutation and its immutable evidence in one transaction, attest the descriptor's sealed origin, enforce exact idempotency replay and digest conflicts, and serialize operations against destruction.
|
|
14
|
+
- Treat only `applied` and `replayed` outcomes from `operateDurableBranch()` as committed. An `unsupported` outcome guarantees that the host ran no mutation SQL; do not recreate the atomic guarantee with callbacks or a separate evidence write.
|
|
15
|
+
- Deliver committed evidence with `scanDurableOperations()` or `getDurableOperation()`, then call `markDurableOperationDelivered()` only after the downstream transaction commits. A first application must remain undelivered, and `destroyDurableBranch()` refuses while any evidence is undelivered.
|
|
16
|
+
|
|
17
|
+
### Minor Changes
|
|
18
|
+
|
|
19
|
+
- [#731](https://github.com/nicia-ai/typegraph/pull/731) [`f8e800b`](https://github.com/nicia-ai/typegraph/commit/f8e800bca25f7168747624d1ab4dea525ec736c0) Thanks [@pdlug](https://github.com/pdlug)! - Add atomic durable-branch operations. A `DurableWorkingCopyStrategy` may now expose an optional `operations` capability that commits an opaque host mutation and its immutable evidence in one host transaction, keyed by idempotency. New public orchestrators `operateDurableBranch()`, `getDurableOperation()`, `scanDurableOperations()`, `markDurableOperationDelivered()`, and `durableBranchHasUndeliveredEvidence()` wrap it, with typed `DurableOperationError` subclasses for conflicts, unsupported capabilities, malformed evidence, and the undelivered-evidence destroy fence.
|
|
20
|
+
|
|
3
21
|
## 0.67.1
|
|
4
22
|
|
|
5
23
|
### Patch Changes
|
|
@@ -40,7 +40,7 @@ var chunkXPSPDFKO_cjs = require('../chunk-XPSPDFKO.cjs');
|
|
|
40
40
|
require('../chunk-HAHNRASW.cjs');
|
|
41
41
|
var chunkHQU2SE74_cjs = require('../chunk-HQU2SE74.cjs');
|
|
42
42
|
var chunkXUO3KQFO_cjs = require('../chunk-XUO3KQFO.cjs');
|
|
43
|
-
require('../chunk-JLSXOBLQ.cjs');
|
|
43
|
+
var chunkJLSXOBLQ_cjs = require('../chunk-JLSXOBLQ.cjs');
|
|
44
44
|
var chunkNYWU6CBZ_cjs = require('../chunk-NYWU6CBZ.cjs');
|
|
45
45
|
require('../chunk-OZ3ZU7Q2.cjs');
|
|
46
46
|
var chunkBXHAPCQG_cjs = require('../chunk-BXHAPCQG.cjs');
|
|
@@ -135,7 +135,13 @@ var MERGE_ERROR_CODES = {
|
|
|
135
135
|
candidateSource: "GRAPH_MERGE_CANDIDATE_SOURCE",
|
|
136
136
|
evidence: "GRAPH_MERGE_EVIDENCE",
|
|
137
137
|
candidateWriteSet: "GRAPH_MERGE_CANDIDATE_WRITE_SET",
|
|
138
|
-
review: "GRAPH_MERGE_REVIEW"
|
|
138
|
+
review: "GRAPH_MERGE_REVIEW",
|
|
139
|
+
operation: "GRAPH_MERGE_OPERATION",
|
|
140
|
+
operationRequest: "GRAPH_MERGE_OPERATION_REQUEST",
|
|
141
|
+
operationConflict: "GRAPH_MERGE_OPERATION_CONFLICT",
|
|
142
|
+
operationUnsupported: "GRAPH_MERGE_OPERATION_UNSUPPORTED",
|
|
143
|
+
operationEvidence: "GRAPH_MERGE_OPERATION_EVIDENCE",
|
|
144
|
+
operationUndelivered: "GRAPH_MERGE_OPERATION_UNDELIVERED"
|
|
139
145
|
};
|
|
140
146
|
function toTypeGraphErrorOptions(category, options) {
|
|
141
147
|
return {
|
|
@@ -342,6 +348,51 @@ var MatchEvidenceError = class extends MergeError {
|
|
|
342
348
|
this.name = "MatchEvidenceError";
|
|
343
349
|
}
|
|
344
350
|
};
|
|
351
|
+
var DurableOperationError = class extends MergeError {
|
|
352
|
+
code = MERGE_ERROR_CODES.operation;
|
|
353
|
+
constructor(message, options = {}) {
|
|
354
|
+
super(message, options);
|
|
355
|
+
this.name = "DurableOperationError";
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
var DurableOperationRequestError = class extends DurableOperationError {
|
|
359
|
+
static errorCategory = "user";
|
|
360
|
+
code = MERGE_ERROR_CODES.operationRequest;
|
|
361
|
+
constructor(message, options = {}) {
|
|
362
|
+
super(message, options);
|
|
363
|
+
this.name = "DurableOperationRequestError";
|
|
364
|
+
}
|
|
365
|
+
};
|
|
366
|
+
var DurableOperationConflictError = class extends DurableOperationError {
|
|
367
|
+
static errorCategory = "constraint";
|
|
368
|
+
code = MERGE_ERROR_CODES.operationConflict;
|
|
369
|
+
constructor(message, options = {}) {
|
|
370
|
+
super(message, options);
|
|
371
|
+
this.name = "DurableOperationConflictError";
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
var DurableOperationUnsupportedError = class extends DurableOperationError {
|
|
375
|
+
static errorCategory = "user";
|
|
376
|
+
code = MERGE_ERROR_CODES.operationUnsupported;
|
|
377
|
+
constructor(message, options = {}) {
|
|
378
|
+
super(message, options);
|
|
379
|
+
this.name = "DurableOperationUnsupportedError";
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
var DurableOperationEvidenceError = class extends DurableOperationError {
|
|
383
|
+
code = MERGE_ERROR_CODES.operationEvidence;
|
|
384
|
+
constructor(message, options = {}) {
|
|
385
|
+
super(message, options);
|
|
386
|
+
this.name = "DurableOperationEvidenceError";
|
|
387
|
+
}
|
|
388
|
+
};
|
|
389
|
+
var DurableEvidenceUndeliveredError = class extends BranchError {
|
|
390
|
+
code = MERGE_ERROR_CODES.operationUndelivered;
|
|
391
|
+
constructor(message, options = {}) {
|
|
392
|
+
super(message, options);
|
|
393
|
+
this.name = "DurableEvidenceUndeliveredError";
|
|
394
|
+
}
|
|
395
|
+
};
|
|
345
396
|
function describeCause(cause) {
|
|
346
397
|
return cause instanceof Error ? cause.message : String(cause);
|
|
347
398
|
}
|
|
@@ -10479,6 +10530,7 @@ async function destroyDurableBranch(descriptor, strategy) {
|
|
|
10479
10530
|
);
|
|
10480
10531
|
return chunkHQU2SE74_cjs.ok(void 0);
|
|
10481
10532
|
} catch (error) {
|
|
10533
|
+
if (error instanceof DurableEvidenceUndeliveredError) return chunkHQU2SE74_cjs.err(error);
|
|
10482
10534
|
return chunkHQU2SE74_cjs.err(
|
|
10483
10535
|
new BranchError(
|
|
10484
10536
|
`Failed to destroy durable working copy for branch "${descriptor.branchId}": ${describeCause(error)}`,
|
|
@@ -10733,6 +10785,458 @@ async function applyDurableMergePlan(args) {
|
|
|
10733
10785
|
);
|
|
10734
10786
|
}
|
|
10735
10787
|
|
|
10788
|
+
// src/graph-merge/durable-operation.ts
|
|
10789
|
+
var DURABLE_OPERATION_SCAN_DEFAULT_LIMIT = 100;
|
|
10790
|
+
var DURABLE_OPERATION_SCAN_MAX_LIMIT = 1e3;
|
|
10791
|
+
var OPERATION_DIGEST_BYTE_LENGTH = 16;
|
|
10792
|
+
async function computeDurableOperationDigest(request) {
|
|
10793
|
+
const canonical = canonicalValueKey({
|
|
10794
|
+
metadata: request.metadata,
|
|
10795
|
+
mutation: request.mutation
|
|
10796
|
+
});
|
|
10797
|
+
return chunkZY4GXIVN_cjs.sha256Hex(canonical, OPERATION_DIGEST_BYTE_LENGTH);
|
|
10798
|
+
}
|
|
10799
|
+
function requireDescriptorOwner(descriptor, strategy) {
|
|
10800
|
+
const refusal = durableDescriptorRefusal(descriptor, strategy);
|
|
10801
|
+
if (refusal !== void 0) {
|
|
10802
|
+
return {
|
|
10803
|
+
ok: false,
|
|
10804
|
+
error: new DurableOperationRequestError(
|
|
10805
|
+
"Durable operation descriptor validation failed.",
|
|
10806
|
+
{ cause: refusal }
|
|
10807
|
+
)
|
|
10808
|
+
};
|
|
10809
|
+
}
|
|
10810
|
+
return { ok: true, origin: durableOriginOfDescriptor(descriptor) };
|
|
10811
|
+
}
|
|
10812
|
+
async function normalizeOperationRequest(request) {
|
|
10813
|
+
const rawRequest = request;
|
|
10814
|
+
if (typeof rawRequest !== "object" || rawRequest === null || Array.isArray(rawRequest)) {
|
|
10815
|
+
return chunkHQU2SE74_cjs.err(
|
|
10816
|
+
new DurableOperationRequestError(
|
|
10817
|
+
"Durable operation request must be a JSON object."
|
|
10818
|
+
)
|
|
10819
|
+
);
|
|
10820
|
+
}
|
|
10821
|
+
if (typeof request.idempotencyKey !== "string" || request.idempotencyKey.length === 0) {
|
|
10822
|
+
return chunkHQU2SE74_cjs.err(
|
|
10823
|
+
new DurableOperationRequestError(
|
|
10824
|
+
"Durable operation request is malformed: idempotencyKey must be a non-empty string.",
|
|
10825
|
+
{ details: { idempotencyKey: request.idempotencyKey } }
|
|
10826
|
+
)
|
|
10827
|
+
);
|
|
10828
|
+
}
|
|
10829
|
+
try {
|
|
10830
|
+
chunkJLSXOBLQ_cjs.assertJsonValue(request.metadata, "metadata", "Durable operation");
|
|
10831
|
+
chunkJLSXOBLQ_cjs.assertJsonValue(request.mutation, "mutation", "Durable operation");
|
|
10832
|
+
} catch (error) {
|
|
10833
|
+
return chunkHQU2SE74_cjs.err(
|
|
10834
|
+
new DurableOperationRequestError(
|
|
10835
|
+
`Durable operation request is not JSON-safe: ${describeCause(error)}`,
|
|
10836
|
+
{ cause: error, details: { idempotencyKey: request.idempotencyKey } }
|
|
10837
|
+
)
|
|
10838
|
+
);
|
|
10839
|
+
}
|
|
10840
|
+
return chunkHQU2SE74_cjs.ok({
|
|
10841
|
+
idempotencyKey: request.idempotencyKey,
|
|
10842
|
+
metadata: request.metadata,
|
|
10843
|
+
mutation: request.mutation,
|
|
10844
|
+
operationDigest: await computeDurableOperationDigest(request)
|
|
10845
|
+
});
|
|
10846
|
+
}
|
|
10847
|
+
async function normalizeStoredEvidence(evidence, expectedIdempotencyKey) {
|
|
10848
|
+
if (typeof evidence !== "object" || evidence === null || Array.isArray(evidence)) {
|
|
10849
|
+
return chunkHQU2SE74_cjs.err(
|
|
10850
|
+
new DurableOperationEvidenceError(
|
|
10851
|
+
"Durable operation evidence must be a JSON object.",
|
|
10852
|
+
{ details: { idempotencyKey: expectedIdempotencyKey } }
|
|
10853
|
+
)
|
|
10854
|
+
);
|
|
10855
|
+
}
|
|
10856
|
+
const record = evidence;
|
|
10857
|
+
if (typeof record["idempotencyKey"] !== "string" || record["idempotencyKey"].length === 0) {
|
|
10858
|
+
return chunkHQU2SE74_cjs.err(
|
|
10859
|
+
new DurableOperationEvidenceError(
|
|
10860
|
+
"Durable operation evidence needs a non-empty idempotency key.",
|
|
10861
|
+
{ details: { expectedKey: expectedIdempotencyKey } }
|
|
10862
|
+
)
|
|
10863
|
+
);
|
|
10864
|
+
}
|
|
10865
|
+
if (expectedIdempotencyKey !== void 0 && record["idempotencyKey"] !== expectedIdempotencyKey) {
|
|
10866
|
+
return chunkHQU2SE74_cjs.err(
|
|
10867
|
+
new DurableOperationEvidenceError(
|
|
10868
|
+
"Durable operation evidence does not carry the requested idempotency key.",
|
|
10869
|
+
{
|
|
10870
|
+
details: {
|
|
10871
|
+
expectedKey: expectedIdempotencyKey,
|
|
10872
|
+
receivedKey: record["idempotencyKey"]
|
|
10873
|
+
}
|
|
10874
|
+
}
|
|
10875
|
+
)
|
|
10876
|
+
);
|
|
10877
|
+
}
|
|
10878
|
+
if (typeof record["operationDigest"] !== "string" || record["operationDigest"].length === 0) {
|
|
10879
|
+
return chunkHQU2SE74_cjs.err(
|
|
10880
|
+
new DurableOperationEvidenceError(
|
|
10881
|
+
"Durable operation evidence needs a non-empty operation digest.",
|
|
10882
|
+
{ details: { idempotencyKey: record["idempotencyKey"] } }
|
|
10883
|
+
)
|
|
10884
|
+
);
|
|
10885
|
+
}
|
|
10886
|
+
try {
|
|
10887
|
+
chunkJLSXOBLQ_cjs.assertJsonValue(
|
|
10888
|
+
record["metadata"],
|
|
10889
|
+
"metadata",
|
|
10890
|
+
"Durable operation evidence"
|
|
10891
|
+
);
|
|
10892
|
+
chunkJLSXOBLQ_cjs.assertJsonValue(
|
|
10893
|
+
record["mutation"],
|
|
10894
|
+
"mutation",
|
|
10895
|
+
"Durable operation evidence"
|
|
10896
|
+
);
|
|
10897
|
+
} catch (error) {
|
|
10898
|
+
return chunkHQU2SE74_cjs.err(
|
|
10899
|
+
new DurableOperationEvidenceError(
|
|
10900
|
+
`Durable operation evidence content is not JSON-safe: ${describeCause(error)}`,
|
|
10901
|
+
{ cause: error, details: { idempotencyKey: record["idempotencyKey"] } }
|
|
10902
|
+
)
|
|
10903
|
+
);
|
|
10904
|
+
}
|
|
10905
|
+
const refusal = validateCoordinates(record["before"], record, "before") ?? validateCoordinates(record["after"], record, "after") ?? (typeof record["delivered"] === "boolean" ? void 0 : new DurableOperationEvidenceError(
|
|
10906
|
+
"Durable operation evidence is missing its delivered flag.",
|
|
10907
|
+
{ details: { idempotencyKey: record["idempotencyKey"] } }
|
|
10908
|
+
));
|
|
10909
|
+
if (refusal !== void 0) return chunkHQU2SE74_cjs.err(refusal);
|
|
10910
|
+
const normalized = evidence;
|
|
10911
|
+
const canonicalDigest = await computeDurableOperationDigest(normalized);
|
|
10912
|
+
if (normalized.operationDigest !== canonicalDigest) {
|
|
10913
|
+
return chunkHQU2SE74_cjs.err(
|
|
10914
|
+
new DurableOperationEvidenceError(
|
|
10915
|
+
"Durable operation evidence digest disagrees with its canonical content.",
|
|
10916
|
+
{
|
|
10917
|
+
details: {
|
|
10918
|
+
idempotencyKey: normalized.idempotencyKey,
|
|
10919
|
+
expectedDigest: canonicalDigest,
|
|
10920
|
+
receivedDigest: normalized.operationDigest
|
|
10921
|
+
}
|
|
10922
|
+
}
|
|
10923
|
+
)
|
|
10924
|
+
);
|
|
10925
|
+
}
|
|
10926
|
+
return chunkHQU2SE74_cjs.ok(normalized);
|
|
10927
|
+
}
|
|
10928
|
+
async function validateEvidenceForOperation(evidence, expected) {
|
|
10929
|
+
const normalized = await normalizeStoredEvidence(
|
|
10930
|
+
evidence,
|
|
10931
|
+
expected.idempotencyKey
|
|
10932
|
+
);
|
|
10933
|
+
if (chunkHQU2SE74_cjs.isErr(normalized)) return normalized;
|
|
10934
|
+
if (normalized.data.operationDigest !== expected.operationDigest) {
|
|
10935
|
+
return chunkHQU2SE74_cjs.err(
|
|
10936
|
+
new DurableOperationEvidenceError(
|
|
10937
|
+
"Durable operation evidence digest disagrees with the canonical request digest.",
|
|
10938
|
+
{
|
|
10939
|
+
details: {
|
|
10940
|
+
idempotencyKey: expected.idempotencyKey,
|
|
10941
|
+
expectedDigest: expected.operationDigest,
|
|
10942
|
+
receivedDigest: normalized.data.operationDigest
|
|
10943
|
+
}
|
|
10944
|
+
}
|
|
10945
|
+
)
|
|
10946
|
+
);
|
|
10947
|
+
}
|
|
10948
|
+
if (canonicalValueKey(normalized.data.metadata) !== canonicalValueKey(expected.metadata) || canonicalValueKey(normalized.data.mutation) !== canonicalValueKey(expected.mutation)) {
|
|
10949
|
+
return chunkHQU2SE74_cjs.err(
|
|
10950
|
+
new DurableOperationEvidenceError(
|
|
10951
|
+
"Durable operation evidence does not echo the canonical request content.",
|
|
10952
|
+
{ details: { idempotencyKey: expected.idempotencyKey } }
|
|
10953
|
+
)
|
|
10954
|
+
);
|
|
10955
|
+
}
|
|
10956
|
+
return normalized;
|
|
10957
|
+
}
|
|
10958
|
+
var DURABLE_OPERATION_UNSUPPORTED_DIMENSIONS = /* @__PURE__ */ new Set([
|
|
10959
|
+
"atomicMutation",
|
|
10960
|
+
"evidenceStore",
|
|
10961
|
+
"host"
|
|
10962
|
+
]);
|
|
10963
|
+
function isDurableOperationUnsupportedDimension(value) {
|
|
10964
|
+
return typeof value === "string" && DURABLE_OPERATION_UNSUPPORTED_DIMENSIONS.has(
|
|
10965
|
+
value
|
|
10966
|
+
);
|
|
10967
|
+
}
|
|
10968
|
+
async function normalizeOperationOutcome(outcome, expected) {
|
|
10969
|
+
if (typeof outcome !== "object" || outcome === null || Array.isArray(outcome)) {
|
|
10970
|
+
return chunkHQU2SE74_cjs.err(
|
|
10971
|
+
new DurableOperationEvidenceError(
|
|
10972
|
+
"Durable operation host returned a malformed outcome envelope.",
|
|
10973
|
+
{ details: { idempotencyKey: expected.idempotencyKey } }
|
|
10974
|
+
)
|
|
10975
|
+
);
|
|
10976
|
+
}
|
|
10977
|
+
const record = outcome;
|
|
10978
|
+
const outcomeKind = record["outcome"];
|
|
10979
|
+
if (outcomeKind === "unsupported") {
|
|
10980
|
+
const dimensions = record["dimensions"];
|
|
10981
|
+
const normalizedDimensions = Array.isArray(dimensions) ? dimensions.filter(
|
|
10982
|
+
(dimension) => isDurableOperationUnsupportedDimension(dimension)
|
|
10983
|
+
) : [];
|
|
10984
|
+
const [firstDimension, ...remainingDimensions] = normalizedDimensions;
|
|
10985
|
+
if (!Array.isArray(dimensions) || firstDimension === void 0 || normalizedDimensions.length !== dimensions.length || new Set(normalizedDimensions).size !== normalizedDimensions.length) {
|
|
10986
|
+
return chunkHQU2SE74_cjs.err(
|
|
10987
|
+
new DurableOperationEvidenceError(
|
|
10988
|
+
"Durable operation host returned malformed unsupported dimensions.",
|
|
10989
|
+
{ details: { idempotencyKey: expected.idempotencyKey } }
|
|
10990
|
+
)
|
|
10991
|
+
);
|
|
10992
|
+
}
|
|
10993
|
+
return chunkHQU2SE74_cjs.ok({
|
|
10994
|
+
outcome: "unsupported",
|
|
10995
|
+
dimensions: [firstDimension, ...remainingDimensions]
|
|
10996
|
+
});
|
|
10997
|
+
}
|
|
10998
|
+
if (outcomeKind !== "applied" && outcomeKind !== "replayed") {
|
|
10999
|
+
return chunkHQU2SE74_cjs.err(
|
|
11000
|
+
new DurableOperationEvidenceError(
|
|
11001
|
+
"Durable operation host returned an unknown outcome.",
|
|
11002
|
+
{
|
|
11003
|
+
details: {
|
|
11004
|
+
idempotencyKey: expected.idempotencyKey,
|
|
11005
|
+
outcome: outcomeKind
|
|
11006
|
+
}
|
|
11007
|
+
}
|
|
11008
|
+
)
|
|
11009
|
+
);
|
|
11010
|
+
}
|
|
11011
|
+
const evidence = await validateEvidenceForOperation(
|
|
11012
|
+
record["evidence"],
|
|
11013
|
+
expected
|
|
11014
|
+
);
|
|
11015
|
+
if (chunkHQU2SE74_cjs.isErr(evidence)) return evidence;
|
|
11016
|
+
if (outcomeKind === "applied" && evidence.data.delivered) {
|
|
11017
|
+
return chunkHQU2SE74_cjs.err(
|
|
11018
|
+
new DurableOperationEvidenceError(
|
|
11019
|
+
"Newly applied durable operation evidence must be undelivered.",
|
|
11020
|
+
{ details: { idempotencyKey: expected.idempotencyKey } }
|
|
11021
|
+
)
|
|
11022
|
+
);
|
|
11023
|
+
}
|
|
11024
|
+
return chunkHQU2SE74_cjs.ok({ outcome: outcomeKind, evidence: evidence.data });
|
|
11025
|
+
}
|
|
11026
|
+
function validateCoordinates(coordinates, evidence, side) {
|
|
11027
|
+
const idempotencyKey = evidence["idempotencyKey"];
|
|
11028
|
+
if (typeof coordinates !== "object" || coordinates === null || Array.isArray(coordinates)) {
|
|
11029
|
+
return new DurableOperationEvidenceError(
|
|
11030
|
+
`Durable operation evidence ${side} coordinates are malformed.`,
|
|
11031
|
+
{ details: { idempotencyKey, side } }
|
|
11032
|
+
);
|
|
11033
|
+
}
|
|
11034
|
+
const record = coordinates;
|
|
11035
|
+
if (typeof record["base"] !== "string" || record["base"].length === 0) {
|
|
11036
|
+
return new DurableOperationEvidenceError(
|
|
11037
|
+
`Durable operation evidence ${side} coordinates need a non-empty base.`,
|
|
11038
|
+
{ details: { idempotencyKey, side } }
|
|
11039
|
+
);
|
|
11040
|
+
}
|
|
11041
|
+
if (record["revision"] !== void 0 && (typeof record["revision"] !== "string" || record["revision"].length === 0)) {
|
|
11042
|
+
return new DurableOperationEvidenceError(
|
|
11043
|
+
`Durable operation evidence ${side} coordinates revision must be a non-empty string.`,
|
|
11044
|
+
{ details: { idempotencyKey, side } }
|
|
11045
|
+
);
|
|
11046
|
+
}
|
|
11047
|
+
return void 0;
|
|
11048
|
+
}
|
|
11049
|
+
function unsupportedError(member, strategyType) {
|
|
11050
|
+
return new DurableOperationUnsupportedError(
|
|
11051
|
+
`Durable strategy "${strategyType}" does not provide the "${member}" operation capability.`,
|
|
11052
|
+
{
|
|
11053
|
+
details: { strategyType, member },
|
|
11054
|
+
suggestion: "Use a strategy whose `operations` capability provides atomic mutation-plus-evidence and evidence access; TypeGraph never emulates the atomic guarantee."
|
|
11055
|
+
}
|
|
11056
|
+
);
|
|
11057
|
+
}
|
|
11058
|
+
async function operateDurableBranch(descriptor, strategy, request) {
|
|
11059
|
+
const owner = requireDescriptorOwner(descriptor, strategy);
|
|
11060
|
+
if (!owner.ok) return chunkHQU2SE74_cjs.err(owner.error);
|
|
11061
|
+
const normalized = await normalizeOperationRequest(request);
|
|
11062
|
+
if (chunkHQU2SE74_cjs.isErr(normalized)) return normalized;
|
|
11063
|
+
if (strategy.operations === void 0) {
|
|
11064
|
+
return chunkHQU2SE74_cjs.ok({
|
|
11065
|
+
outcome: "unsupported",
|
|
11066
|
+
dimensions: ["atomicMutation"]
|
|
11067
|
+
});
|
|
11068
|
+
}
|
|
11069
|
+
try {
|
|
11070
|
+
const outcome = await strategy.operations.operate({
|
|
11071
|
+
descriptor: descriptor.store,
|
|
11072
|
+
expectedOrigin: owner.origin,
|
|
11073
|
+
request: normalized.data
|
|
11074
|
+
});
|
|
11075
|
+
return await normalizeOperationOutcome(outcome, normalized.data);
|
|
11076
|
+
} catch (error) {
|
|
11077
|
+
return chunkHQU2SE74_cjs.err(
|
|
11078
|
+
error instanceof DurableOperationError ? error : new DurableOperationError(
|
|
11079
|
+
`Durable operation failed: ${describeCause(error)}`,
|
|
11080
|
+
{
|
|
11081
|
+
cause: error,
|
|
11082
|
+
details: { idempotencyKey: request.idempotencyKey }
|
|
11083
|
+
}
|
|
11084
|
+
)
|
|
11085
|
+
);
|
|
11086
|
+
}
|
|
11087
|
+
}
|
|
11088
|
+
async function getDurableOperation(descriptor, strategy, idempotencyKey) {
|
|
11089
|
+
const owner = requireDescriptorOwner(descriptor, strategy);
|
|
11090
|
+
if (!owner.ok) return chunkHQU2SE74_cjs.err(owner.error);
|
|
11091
|
+
if (strategy.operations === void 0) {
|
|
11092
|
+
return chunkHQU2SE74_cjs.err(unsupportedError("get", strategy.type));
|
|
11093
|
+
}
|
|
11094
|
+
try {
|
|
11095
|
+
const evidence = await strategy.operations.get({
|
|
11096
|
+
descriptor: descriptor.store,
|
|
11097
|
+
expectedOrigin: owner.origin,
|
|
11098
|
+
idempotencyKey
|
|
11099
|
+
});
|
|
11100
|
+
if (evidence === void 0) return chunkHQU2SE74_cjs.ok(void 0);
|
|
11101
|
+
return await normalizeStoredEvidence(evidence, idempotencyKey);
|
|
11102
|
+
} catch (error) {
|
|
11103
|
+
return chunkHQU2SE74_cjs.err(
|
|
11104
|
+
error instanceof DurableOperationError ? error : new DurableOperationError(
|
|
11105
|
+
`Failed to read durable operation "${idempotencyKey}": ${describeCause(error)}`,
|
|
11106
|
+
{ cause: error, details: { idempotencyKey } }
|
|
11107
|
+
)
|
|
11108
|
+
);
|
|
11109
|
+
}
|
|
11110
|
+
}
|
|
11111
|
+
async function scanDurableOperations(descriptor, strategy, options = {}) {
|
|
11112
|
+
const owner = requireDescriptorOwner(descriptor, strategy);
|
|
11113
|
+
if (!owner.ok) return chunkHQU2SE74_cjs.err(owner.error);
|
|
11114
|
+
const limit = options.limit ?? DURABLE_OPERATION_SCAN_DEFAULT_LIMIT;
|
|
11115
|
+
if (!Number.isInteger(limit) || limit < 1) {
|
|
11116
|
+
return chunkHQU2SE74_cjs.err(
|
|
11117
|
+
new DurableOperationRequestError(
|
|
11118
|
+
"Durable operation scan limit must be a positive integer.",
|
|
11119
|
+
{ details: { limit } }
|
|
11120
|
+
)
|
|
11121
|
+
);
|
|
11122
|
+
}
|
|
11123
|
+
if (limit > DURABLE_OPERATION_SCAN_MAX_LIMIT) {
|
|
11124
|
+
return chunkHQU2SE74_cjs.err(
|
|
11125
|
+
new DurableOperationRequestError(
|
|
11126
|
+
`Durable operation scan limit ${limit} exceeds the maximum of ${DURABLE_OPERATION_SCAN_MAX_LIMIT}.`,
|
|
11127
|
+
{
|
|
11128
|
+
details: { limit, max: DURABLE_OPERATION_SCAN_MAX_LIMIT },
|
|
11129
|
+
suggestion: `Request at most ${DURABLE_OPERATION_SCAN_MAX_LIMIT} operations per page and page with the returned cursor.`
|
|
11130
|
+
}
|
|
11131
|
+
)
|
|
11132
|
+
);
|
|
11133
|
+
}
|
|
11134
|
+
if (strategy.operations === void 0) {
|
|
11135
|
+
return chunkHQU2SE74_cjs.err(unsupportedError("scan", strategy.type));
|
|
11136
|
+
}
|
|
11137
|
+
try {
|
|
11138
|
+
const rawPage = await strategy.operations.scan({
|
|
11139
|
+
descriptor: descriptor.store,
|
|
11140
|
+
expectedOrigin: owner.origin,
|
|
11141
|
+
after: options.after,
|
|
11142
|
+
limit
|
|
11143
|
+
});
|
|
11144
|
+
if (typeof rawPage !== "object" || rawPage === null || Array.isArray(rawPage)) {
|
|
11145
|
+
return chunkHQU2SE74_cjs.err(
|
|
11146
|
+
new DurableOperationEvidenceError(
|
|
11147
|
+
"Durable operation scan returned a malformed page.",
|
|
11148
|
+
{ details: { limit } }
|
|
11149
|
+
)
|
|
11150
|
+
);
|
|
11151
|
+
}
|
|
11152
|
+
const page = rawPage;
|
|
11153
|
+
const rawOperations = page["operations"];
|
|
11154
|
+
const cursor = page["cursor"];
|
|
11155
|
+
if (!Array.isArray(rawOperations) || rawOperations.length > limit || cursor !== void 0 && (typeof cursor !== "string" || cursor.length === 0)) {
|
|
11156
|
+
return chunkHQU2SE74_cjs.err(
|
|
11157
|
+
new DurableOperationEvidenceError(
|
|
11158
|
+
"Durable operation scan returned a malformed page.",
|
|
11159
|
+
{ details: { limit } }
|
|
11160
|
+
)
|
|
11161
|
+
);
|
|
11162
|
+
}
|
|
11163
|
+
const operations = [];
|
|
11164
|
+
for (const evidence of rawOperations) {
|
|
11165
|
+
const normalized = await normalizeStoredEvidence(evidence);
|
|
11166
|
+
if (chunkHQU2SE74_cjs.isErr(normalized)) return normalized;
|
|
11167
|
+
operations.push(normalized.data);
|
|
11168
|
+
}
|
|
11169
|
+
return chunkHQU2SE74_cjs.ok(
|
|
11170
|
+
cursor === void 0 ? { operations } : { operations, cursor }
|
|
11171
|
+
);
|
|
11172
|
+
} catch (error) {
|
|
11173
|
+
return chunkHQU2SE74_cjs.err(
|
|
11174
|
+
error instanceof DurableOperationError ? error : new DurableOperationError(
|
|
11175
|
+
`Failed to scan durable operations: ${describeCause(error)}`,
|
|
11176
|
+
{ cause: error, details: { limit } }
|
|
11177
|
+
)
|
|
11178
|
+
);
|
|
11179
|
+
}
|
|
11180
|
+
}
|
|
11181
|
+
async function markDurableOperationDelivered(descriptor, strategy, idempotencyKey) {
|
|
11182
|
+
const owner = requireDescriptorOwner(descriptor, strategy);
|
|
11183
|
+
if (!owner.ok) return chunkHQU2SE74_cjs.err(owner.error);
|
|
11184
|
+
if (strategy.operations === void 0) {
|
|
11185
|
+
return chunkHQU2SE74_cjs.err(unsupportedError("markDelivered", strategy.type));
|
|
11186
|
+
}
|
|
11187
|
+
try {
|
|
11188
|
+
const evidence = await strategy.operations.markDelivered({
|
|
11189
|
+
descriptor: descriptor.store,
|
|
11190
|
+
expectedOrigin: owner.origin,
|
|
11191
|
+
idempotencyKey
|
|
11192
|
+
});
|
|
11193
|
+
if (evidence === void 0) return chunkHQU2SE74_cjs.ok(void 0);
|
|
11194
|
+
const normalized = await normalizeStoredEvidence(evidence, idempotencyKey);
|
|
11195
|
+
if (chunkHQU2SE74_cjs.isErr(normalized)) return normalized;
|
|
11196
|
+
if (!normalized.data.delivered) {
|
|
11197
|
+
return chunkHQU2SE74_cjs.err(
|
|
11198
|
+
new DurableOperationEvidenceError(
|
|
11199
|
+
"Durable operation delivery marking returned undelivered evidence.",
|
|
11200
|
+
{ details: { idempotencyKey } }
|
|
11201
|
+
)
|
|
11202
|
+
);
|
|
11203
|
+
}
|
|
11204
|
+
return normalized;
|
|
11205
|
+
} catch (error) {
|
|
11206
|
+
return chunkHQU2SE74_cjs.err(
|
|
11207
|
+
error instanceof DurableOperationError ? error : new DurableOperationError(
|
|
11208
|
+
`Failed to mark durable operation "${idempotencyKey}" delivered: ${describeCause(error)}`,
|
|
11209
|
+
{ cause: error, details: { idempotencyKey } }
|
|
11210
|
+
)
|
|
11211
|
+
);
|
|
11212
|
+
}
|
|
11213
|
+
}
|
|
11214
|
+
async function durableBranchHasUndeliveredEvidence(descriptor, strategy) {
|
|
11215
|
+
const owner = requireDescriptorOwner(descriptor, strategy);
|
|
11216
|
+
if (!owner.ok) return chunkHQU2SE74_cjs.err(owner.error);
|
|
11217
|
+
if (strategy.operations === void 0) {
|
|
11218
|
+
return chunkHQU2SE74_cjs.err(unsupportedError("hasUndelivered", strategy.type));
|
|
11219
|
+
}
|
|
11220
|
+
try {
|
|
11221
|
+
const hasUndelivered = await strategy.operations.hasUndelivered({
|
|
11222
|
+
descriptor: descriptor.store,
|
|
11223
|
+
expectedOrigin: owner.origin
|
|
11224
|
+
});
|
|
11225
|
+
return typeof hasUndelivered === "boolean" ? chunkHQU2SE74_cjs.ok(hasUndelivered) : chunkHQU2SE74_cjs.err(
|
|
11226
|
+
new DurableOperationEvidenceError(
|
|
11227
|
+
"Durable operation undelivered query returned a non-boolean value."
|
|
11228
|
+
)
|
|
11229
|
+
);
|
|
11230
|
+
} catch (error) {
|
|
11231
|
+
return chunkHQU2SE74_cjs.err(
|
|
11232
|
+
error instanceof DurableOperationError ? error : new DurableOperationError(
|
|
11233
|
+
`Failed to query undelivered durable evidence: ${describeCause(error)}`,
|
|
11234
|
+
{ cause: error }
|
|
11235
|
+
)
|
|
11236
|
+
);
|
|
11237
|
+
}
|
|
11238
|
+
}
|
|
11239
|
+
|
|
10736
11240
|
// src/graph-merge/evolution-branch.ts
|
|
10737
11241
|
async function branchForEvolution(store, plan, makeBackend, options) {
|
|
10738
11242
|
try {
|
|
@@ -10769,6 +11273,14 @@ exports.CandidateSourceError = CandidateSourceError;
|
|
|
10769
11273
|
exports.CandidateWriteSetError = CandidateWriteSetError;
|
|
10770
11274
|
exports.CandidateWriteSetSchema = CandidateWriteSetSchema;
|
|
10771
11275
|
exports.CandidateWriteSetTargetSchema = CandidateWriteSetTargetSchema;
|
|
11276
|
+
exports.DURABLE_OPERATION_SCAN_DEFAULT_LIMIT = DURABLE_OPERATION_SCAN_DEFAULT_LIMIT;
|
|
11277
|
+
exports.DURABLE_OPERATION_SCAN_MAX_LIMIT = DURABLE_OPERATION_SCAN_MAX_LIMIT;
|
|
11278
|
+
exports.DurableEvidenceUndeliveredError = DurableEvidenceUndeliveredError;
|
|
11279
|
+
exports.DurableOperationConflictError = DurableOperationConflictError;
|
|
11280
|
+
exports.DurableOperationError = DurableOperationError;
|
|
11281
|
+
exports.DurableOperationEvidenceError = DurableOperationEvidenceError;
|
|
11282
|
+
exports.DurableOperationRequestError = DurableOperationRequestError;
|
|
11283
|
+
exports.DurableOperationUnsupportedError = DurableOperationUnsupportedError;
|
|
10772
11284
|
exports.IdentityMergeConflictError = IdentityMergeConflictError;
|
|
10773
11285
|
exports.InvalidMergeOptionsError = InvalidMergeOptionsError;
|
|
10774
11286
|
exports.InvalidMergePlanError = InvalidMergePlanError;
|
|
@@ -10804,13 +11316,18 @@ exports.captureCandidateWriteSetTarget = captureCandidateWriteSetTarget;
|
|
|
10804
11316
|
exports.captureCandidateWriteSetTargetForEvolution = captureCandidateWriteSetTargetForEvolution;
|
|
10805
11317
|
exports.cloneWorkingCopyStrategy = cloneWorkingCopyStrategy;
|
|
10806
11318
|
exports.computeBaseVersion = computeBaseVersion;
|
|
11319
|
+
exports.computeDurableOperationDigest = computeDurableOperationDigest;
|
|
10807
11320
|
exports.destroyDurableBranch = destroyDurableBranch;
|
|
11321
|
+
exports.durableBranchHasUndeliveredEvidence = durableBranchHasUndeliveredEvidence;
|
|
10808
11322
|
exports.forkedWorkingCopyStrategy = forkedWorkingCopyStrategy;
|
|
11323
|
+
exports.getDurableOperation = getDurableOperation;
|
|
10809
11324
|
exports.ingestionBranch = ingestionBranch;
|
|
11325
|
+
exports.markDurableOperationDelivered = markDurableOperationDelivered;
|
|
10810
11326
|
exports.merge = merge;
|
|
10811
11327
|
exports.mergeIncremental = mergeIncremental;
|
|
10812
11328
|
exports.normalizeMergeOptions = normalizeMergeOptions;
|
|
10813
11329
|
exports.openProvenanceStore = openProvenanceStore;
|
|
11330
|
+
exports.operateDurableBranch = operateDurableBranch;
|
|
10814
11331
|
exports.persistProvenanceRecords = persistProvenanceRecords;
|
|
10815
11332
|
exports.planCandidateWriteSet = planCandidateWriteSet;
|
|
10816
11333
|
exports.planCandidateWriteSetForEvolution = planCandidateWriteSetForEvolution;
|
|
@@ -10822,5 +11339,6 @@ exports.provenanceGraphId = provenanceGraphId;
|
|
|
10822
11339
|
exports.readProvenance = readProvenance;
|
|
10823
11340
|
exports.reopenDurableBranch = reopenDurableBranch;
|
|
10824
11341
|
exports.revalidateCandidateWriteSetReview = revalidateCandidateWriteSetReview;
|
|
11342
|
+
exports.scanDurableOperations = scanDurableOperations;
|
|
10825
11343
|
//# sourceMappingURL=index.cjs.map
|
|
10826
11344
|
//# sourceMappingURL=index.cjs.map
|