@tiangong-lca/cli 0.1.1 → 0.1.2
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/README.md +26 -6
- package/dist/src/batch.d.ts +6 -0
- package/dist/src/batch.js +6 -0
- package/dist/src/batch.js.map +1 -0
- package/dist/src/cli.d.ts +161 -0
- package/dist/src/command-spec.d.ts +90 -0
- package/dist/src/command-spec.js +400 -0
- package/dist/src/command-spec.js.map +1 -0
- package/dist/src/lib/artifacts.d.ts +9 -0
- package/dist/src/lib/auth-identity-receipt.d.ts +160 -0
- package/dist/src/lib/batch/attempt-recovery.d.ts +12 -0
- package/dist/src/lib/batch/attempt-recovery.js +183 -0
- package/dist/src/lib/batch/attempt-recovery.js.map +1 -0
- package/dist/src/lib/batch/canonical-contracts.d.ts +21 -0
- package/dist/src/lib/batch/canonical-contracts.js +180 -0
- package/dist/src/lib/batch/canonical-contracts.js.map +1 -0
- package/dist/src/lib/batch/engine.d.ts +2 -0
- package/dist/src/lib/batch/engine.js +294 -0
- package/dist/src/lib/batch/engine.js.map +1 -0
- package/dist/src/lib/batch/errors.d.ts +51 -0
- package/dist/src/lib/batch/errors.js +81 -0
- package/dist/src/lib/batch/errors.js.map +1 -0
- package/dist/src/lib/batch/item-projection.d.ts +10 -0
- package/dist/src/lib/batch/item-projection.js +118 -0
- package/dist/src/lib/batch/item-projection.js.map +1 -0
- package/dist/src/lib/batch/run-lock.d.ts +4 -0
- package/dist/src/lib/batch/run-lock.js +155 -0
- package/dist/src/lib/batch/run-lock.js.map +1 -0
- package/dist/src/lib/batch/scheduler-runtime.d.ts +29 -0
- package/dist/src/lib/batch/scheduler-runtime.js +114 -0
- package/dist/src/lib/batch/scheduler-runtime.js.map +1 -0
- package/dist/src/lib/batch/types.d.ts +183 -0
- package/dist/src/lib/batch/types.js +2 -0
- package/dist/src/lib/batch/types.js.map +1 -0
- package/dist/src/lib/dataset-author.d.ts +39 -0
- package/dist/src/lib/dataset-bilingual.d.ts +165 -0
- package/dist/src/lib/dataset-classification.d.ts +279 -0
- package/dist/src/lib/dataset-command.d.ts +53 -0
- package/dist/src/lib/dataset-contract.d.ts +82 -0
- package/dist/src/lib/dataset-curation-queue.d.ts +219 -0
- package/dist/src/lib/dataset-evidence-search.d.ts +134 -0
- package/dist/src/lib/dataset-import-lca.d.ts +136 -0
- package/dist/src/lib/dataset-local.d.ts +23 -0
- package/dist/src/lib/dataset-maintenance-alias-request.d.ts +12 -0
- package/dist/src/lib/dataset-maintenance-alias-rewrite.d.ts +111 -0
- package/dist/src/lib/dataset-maintenance-apply.d.ts +458 -0
- package/dist/src/lib/dataset-maintenance-clear-account.d.ts +161 -0
- package/dist/src/lib/dataset-maintenance-contract.d.ts +317 -0
- package/dist/src/lib/dataset-maintenance-derivatives.d.ts +48 -0
- package/dist/src/lib/dataset-maintenance-flow-identity-approval-claim.d.ts +50 -0
- package/dist/src/lib/dataset-maintenance-flow-identity-capture.d.ts +110 -0
- package/dist/src/lib/dataset-maintenance-flow-identity-command.d.ts +15 -0
- package/dist/src/lib/dataset-maintenance-flow-identity-contract.d.ts +387 -0
- package/dist/src/lib/dataset-maintenance-flow-identity-execution-contract.d.ts +456 -0
- package/dist/src/lib/dataset-maintenance-flow-identity-freeze.d.ts +99 -0
- package/dist/src/lib/dataset-maintenance-flow-identity-plan.d.ts +85 -0
- package/dist/src/lib/dataset-maintenance-flow-identity-recovery.d.ts +268 -0
- package/dist/src/lib/dataset-maintenance-flow-identity-run.d.ts +123 -0
- package/dist/src/lib/dataset-maintenance-flow-identity-seal.d.ts +50 -0
- package/dist/src/lib/dataset-maintenance-flow-identity-verify.d.ts +85 -0
- package/dist/src/lib/dataset-maintenance-flow-identity-wire.d.ts +32 -0
- package/dist/src/lib/dataset-maintenance-pagination.d.ts +58 -0
- package/dist/src/lib/dataset-maintenance-plan.d.ts +40 -0
- package/dist/src/lib/dataset-maintenance-protected-artifacts.d.ts +16 -0
- package/dist/src/lib/dataset-maintenance-protected-before.d.ts +71 -0
- package/dist/src/lib/dataset-maintenance-protected-contract.d.ts +385 -0
- package/dist/src/lib/dataset-maintenance-protected-freeze.d.ts +148 -0
- package/dist/src/lib/dataset-maintenance-protected-preparation.d.ts +76 -0
- package/dist/src/lib/dataset-maintenance-protected-run.d.ts +252 -0
- package/dist/src/lib/dataset-maintenance-protected-seal.d.ts +93 -0
- package/dist/src/lib/dataset-maintenance-protected-toolchain.d.ts +31 -0
- package/dist/src/lib/dataset-maintenance-protected-verify.d.ts +99 -0
- package/dist/src/lib/dataset-maintenance-remote.d.ts +212 -0
- package/dist/src/lib/dataset-maintenance-support-validation.d.ts +18 -0
- package/dist/src/lib/dataset-maintenance-verify.d.ts +102 -0
- package/dist/src/lib/dataset-patch.d.ts +193 -0
- package/dist/src/lib/dataset-references-rewrite.d.ts +77 -0
- package/dist/src/lib/dataset-remote-refresh.d.ts +64 -0
- package/dist/src/lib/dataset-remote-verify.d.ts +168 -0
- package/dist/src/lib/dataset-save-draft-run.d.ts +283 -0
- package/dist/src/lib/dataset-save-draft-run.js +54 -21
- package/dist/src/lib/dataset-save-draft-run.js.map +1 -1
- package/dist/src/lib/dataset-source-upload-attachments.d.ts +109 -0
- package/dist/src/lib/dataset-validate.d.ts +62 -0
- package/dist/src/lib/dotenv.d.ts +6 -0
- package/dist/src/lib/env.d.ts +59 -0
- package/dist/src/lib/errors.d.ts +18 -0
- package/dist/src/lib/flow-build-alias-map.d.ts +80 -0
- package/dist/src/lib/flow-fetch-rows.d.ts +70 -0
- package/dist/src/lib/flow-get.d.ts +35 -0
- package/dist/src/lib/flow-governance.d.ts +22 -0
- package/dist/src/lib/flow-list.d.ts +57 -0
- package/dist/src/lib/flow-materialize-decisions.d.ts +67 -0
- package/dist/src/lib/flow-payload-validation.d.ts +32 -0
- package/dist/src/lib/flow-publish-reviewed-data.d.ts +262 -0
- package/dist/src/lib/flow-publish-version.d.ts +193 -0
- package/dist/src/lib/flow-qa.d.ts +289 -0
- package/dist/src/lib/flow-read.d.ts +70 -0
- package/dist/src/lib/flow-regen-product.d.ts +431 -0
- package/dist/src/lib/flow-remediate.d.ts +133 -0
- package/dist/src/lib/http.d.ts +25 -0
- package/dist/src/lib/identity-preflight.d.ts +235 -0
- package/dist/src/lib/io.d.ts +2 -0
- package/dist/src/lib/kb-search.d.ts +23 -0
- package/dist/src/lib/lca-release.d.ts +105 -0
- package/dist/src/lib/lifecyclemodel-auto-build.d.ts +223 -0
- package/dist/src/lib/lifecyclemodel-bundle-save.d.ts +71 -0
- package/dist/src/lib/lifecyclemodel-graph.d.ts +91 -0
- package/dist/src/lib/lifecyclemodel-orchestrate.d.ts +349 -0
- package/dist/src/lib/lifecyclemodel-publish-build.d.ts +77 -0
- package/dist/src/lib/lifecyclemodel-publish-resulting-process.d.ts +27 -0
- package/dist/src/lib/lifecyclemodel-qa.d.ts +206 -0
- package/dist/src/lib/lifecyclemodel-resulting-process.d.ts +204 -0
- package/dist/src/lib/lifecyclemodel-save-draft-run.d.ts +96 -0
- package/dist/src/lib/lifecyclemodel-validate-build.d.ts +78 -0
- package/dist/src/lib/llm.d.ts +51 -0
- package/dist/src/lib/package-version.d.ts +3 -0
- package/dist/src/lib/process-auto-build.d.ts +168 -0
- package/dist/src/lib/process-batch-build.d.ts +97 -0
- package/dist/src/lib/process-dedup-review.d.ts +253 -0
- package/dist/src/lib/process-flow-build-plan.d.ts +108 -0
- package/dist/src/lib/process-get.d.ts +26 -0
- package/dist/src/lib/process-list.d.ts +87 -0
- package/dist/src/lib/process-payload-validation.d.ts +27 -0
- package/dist/src/lib/process-publish-build.d.ts +148 -0
- package/dist/src/lib/process-qa.d.ts +243 -0
- package/dist/src/lib/process-refresh-references.d.ts +179 -0
- package/dist/src/lib/process-required-fields.d.ts +142 -0
- package/dist/src/lib/process-resume-build.d.ts +77 -0
- package/dist/src/lib/process-save-draft-run.d.ts +72 -0
- package/dist/src/lib/process-save-draft.d.ts +53 -0
- package/dist/src/lib/process-scope-statistics.d.ts +210 -0
- package/dist/src/lib/process-verify-rows.d.ts +71 -0
- package/dist/src/lib/publish.d.ts +219 -0
- package/dist/src/lib/remote.d.ts +19 -0
- package/dist/src/lib/run.d.ts +60 -0
- package/dist/src/lib/runtime-rulesets.d.ts +29 -0
- package/dist/src/lib/state-lock.d.ts +25 -0
- package/dist/src/lib/state-lock.js +23 -1
- package/dist/src/lib/state-lock.js.map +1 -1
- package/dist/src/lib/supabase-client.d.ts +47 -0
- package/dist/src/lib/supabase-data-api-contract.d.ts +304 -0
- package/dist/src/lib/supabase-data-api-replay.d.ts +74 -0
- package/dist/src/lib/supabase-json-ordered-write.d.ts +65 -0
- package/dist/src/lib/supabase-rest.d.ts +26 -0
- package/dist/src/lib/supabase-session.d.ts +115 -0
- package/dist/src/lib/tidas-languages.d.ts +3 -0
- package/dist/src/lib/tidas-sdk-package-validator.d.ts +111 -0
- package/dist/src/lib/tidas-sdk-validation.d.ts +42 -0
- package/dist/src/lib/unstructured.d.ts +32 -0
- package/dist/src/lib/user-api-key.d.ts +16 -0
- package/dist/src/lib/validation.d.ts +82 -0
- package/dist/src/main.d.ts +4 -0
- package/package.json +13 -2
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { MAX_NODE_TIMER_DELAY_MS } from './canonical-contracts.js';
|
|
2
|
+
import { BatchContractError, BatchMutationReplayError } from './errors.js';
|
|
3
|
+
import { failedResult, freezeResult, readClock } from './scheduler-runtime.js';
|
|
4
|
+
export async function executeClaim(input) {
|
|
5
|
+
const { options, index, itemContract, exclusiveKey, resumeItem, clock, sleep, emitter } = input;
|
|
6
|
+
const itemId = itemContract.item_id;
|
|
7
|
+
const item = options.items[index];
|
|
8
|
+
const resumed = resumeItem?.state === 'attempted';
|
|
9
|
+
const baseAttempts = resumed ? resumeItem.attempts : 0;
|
|
10
|
+
if (resumed && options.mode === 'mutation') {
|
|
11
|
+
const error = new BatchMutationReplayError(itemId);
|
|
12
|
+
if (!options.recoverMutation) {
|
|
13
|
+
return failedResult(item, itemContract, index, baseAttempts, true, true, clock, error, exclusiveKey);
|
|
14
|
+
}
|
|
15
|
+
return recoverMutation({
|
|
16
|
+
options,
|
|
17
|
+
item,
|
|
18
|
+
itemContract,
|
|
19
|
+
exclusiveKey,
|
|
20
|
+
index,
|
|
21
|
+
attempts: baseAttempts,
|
|
22
|
+
error,
|
|
23
|
+
source: 'resume_incomplete',
|
|
24
|
+
resumed: true,
|
|
25
|
+
clock,
|
|
26
|
+
emitter,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
const maxAttempts = options.retry?.maxAttempts ?? 1;
|
|
30
|
+
for (let invocationAttempt = 1;; invocationAttempt += 1) {
|
|
31
|
+
const attempt = baseAttempts + invocationAttempt;
|
|
32
|
+
const context = {
|
|
33
|
+
item,
|
|
34
|
+
item_id: itemId,
|
|
35
|
+
item_contract: itemContract,
|
|
36
|
+
exclusive_key: exclusiveKey,
|
|
37
|
+
input_index: index,
|
|
38
|
+
attempt,
|
|
39
|
+
mode: options.mode,
|
|
40
|
+
};
|
|
41
|
+
await emitter.emit('attempt_started', { item_id: itemId, input_index: index, attempt });
|
|
42
|
+
try {
|
|
43
|
+
const value = await options.execute(context);
|
|
44
|
+
await emitter.emit('attempt_succeeded', { item_id: itemId, input_index: index, attempt });
|
|
45
|
+
return freezeResult({
|
|
46
|
+
item,
|
|
47
|
+
item_id: itemId,
|
|
48
|
+
item_contract: itemContract,
|
|
49
|
+
exclusive_key: exclusiveKey,
|
|
50
|
+
input_index: index,
|
|
51
|
+
attempts: attempt,
|
|
52
|
+
attempt_consumed: true,
|
|
53
|
+
resumed,
|
|
54
|
+
completed_at_ms: readClock(clock),
|
|
55
|
+
status: 'succeeded',
|
|
56
|
+
value,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
await emitter.emit('attempt_failed', { item_id: itemId, input_index: index, attempt });
|
|
61
|
+
if (options.mode === 'mutation') {
|
|
62
|
+
if (!options.recoverMutation) {
|
|
63
|
+
return failedResult(item, itemContract, index, attempt, true, resumed, clock, error, exclusiveKey);
|
|
64
|
+
}
|
|
65
|
+
return recoverMutation({
|
|
66
|
+
options,
|
|
67
|
+
item,
|
|
68
|
+
itemContract,
|
|
69
|
+
exclusiveKey,
|
|
70
|
+
index,
|
|
71
|
+
attempts: attempt,
|
|
72
|
+
error,
|
|
73
|
+
source: 'execution_error',
|
|
74
|
+
resumed,
|
|
75
|
+
clock,
|
|
76
|
+
emitter,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
if (!options.retry || invocationAttempt >= maxAttempts) {
|
|
80
|
+
return failedResult(item, itemContract, index, attempt, true, resumed, clock, error, exclusiveKey);
|
|
81
|
+
}
|
|
82
|
+
const retryContext = { ...context, error };
|
|
83
|
+
let retry = false;
|
|
84
|
+
try {
|
|
85
|
+
retry = await options.retry.shouldRetry(retryContext);
|
|
86
|
+
}
|
|
87
|
+
catch (classificationError) {
|
|
88
|
+
return failedResult(item, itemContract, index, attempt, true, resumed, clock, classificationError, exclusiveKey);
|
|
89
|
+
}
|
|
90
|
+
if (!retry) {
|
|
91
|
+
return failedResult(item, itemContract, index, attempt, true, resumed, clock, error, exclusiveKey);
|
|
92
|
+
}
|
|
93
|
+
let requestedDelay;
|
|
94
|
+
try {
|
|
95
|
+
requestedDelay = await options.retry.delayMs(retryContext);
|
|
96
|
+
if (!Number.isSafeInteger(requestedDelay) ||
|
|
97
|
+
requestedDelay < 0 ||
|
|
98
|
+
requestedDelay > MAX_NODE_TIMER_DELAY_MS) {
|
|
99
|
+
throw new BatchContractError(`Batch retry delay must be a non-negative safe integer no greater than the maximum supported timer delay (${MAX_NODE_TIMER_DELAY_MS} ms).`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
catch (delayError) {
|
|
103
|
+
return failedResult(item, itemContract, index, attempt, true, resumed, clock, delayError, exclusiveKey);
|
|
104
|
+
}
|
|
105
|
+
const delay = Math.min(requestedDelay, options.retry.maxDelayMs);
|
|
106
|
+
await emitter.emit('retry_scheduled', {
|
|
107
|
+
item_id: itemId,
|
|
108
|
+
input_index: index,
|
|
109
|
+
attempt,
|
|
110
|
+
delay_ms: delay,
|
|
111
|
+
});
|
|
112
|
+
try {
|
|
113
|
+
await sleep(delay);
|
|
114
|
+
}
|
|
115
|
+
catch (sleepError) {
|
|
116
|
+
return failedResult(item, itemContract, index, attempt, true, resumed, clock, sleepError, exclusiveKey);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
async function recoverMutation(input) {
|
|
122
|
+
const { options, item, itemContract, exclusiveKey, index, attempts, error, source, resumed, clock, emitter, } = input;
|
|
123
|
+
const itemId = itemContract.item_id;
|
|
124
|
+
await emitter.emit('recovery_started', {
|
|
125
|
+
item_id: itemId,
|
|
126
|
+
input_index: index,
|
|
127
|
+
attempt: attempts,
|
|
128
|
+
source,
|
|
129
|
+
});
|
|
130
|
+
try {
|
|
131
|
+
const recovery = await options.recoverMutation({
|
|
132
|
+
item,
|
|
133
|
+
item_id: itemId,
|
|
134
|
+
item_contract: itemContract,
|
|
135
|
+
exclusive_key: exclusiveKey,
|
|
136
|
+
input_index: index,
|
|
137
|
+
attempts,
|
|
138
|
+
error,
|
|
139
|
+
source,
|
|
140
|
+
});
|
|
141
|
+
if (recovery.status === 'recovered') {
|
|
142
|
+
await emitter.emit('recovery_succeeded', {
|
|
143
|
+
item_id: itemId,
|
|
144
|
+
input_index: index,
|
|
145
|
+
attempt: attempts,
|
|
146
|
+
source,
|
|
147
|
+
});
|
|
148
|
+
return freezeResult({
|
|
149
|
+
item,
|
|
150
|
+
item_id: itemId,
|
|
151
|
+
item_contract: itemContract,
|
|
152
|
+
exclusive_key: exclusiveKey,
|
|
153
|
+
input_index: index,
|
|
154
|
+
attempts,
|
|
155
|
+
attempt_consumed: true,
|
|
156
|
+
resumed,
|
|
157
|
+
completed_at_ms: readClock(clock),
|
|
158
|
+
status: 'recovered',
|
|
159
|
+
value: recovery.value,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
if (recovery.status !== 'unresolved') {
|
|
163
|
+
throw new BatchContractError('Mutation recovery returned an unsupported status.');
|
|
164
|
+
}
|
|
165
|
+
await emitter.emit('recovery_failed', {
|
|
166
|
+
item_id: itemId,
|
|
167
|
+
input_index: index,
|
|
168
|
+
attempt: attempts,
|
|
169
|
+
source,
|
|
170
|
+
});
|
|
171
|
+
return failedResult(item, itemContract, index, attempts, true, resumed, clock, recovery.error ?? error, exclusiveKey);
|
|
172
|
+
}
|
|
173
|
+
catch (recoveryError) {
|
|
174
|
+
await emitter.emit('recovery_failed', {
|
|
175
|
+
item_id: itemId,
|
|
176
|
+
input_index: index,
|
|
177
|
+
attempt: attempts,
|
|
178
|
+
source,
|
|
179
|
+
});
|
|
180
|
+
return failedResult(item, itemContract, index, attempts, true, resumed, clock, recoveryError, exclusiveKey);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
//# sourceMappingURL=attempt-recovery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attempt-recovery.js","sourceRoot":"","sources":["../../../../src/lib/batch/attempt-recovery.ts"],"names":[],"mappings":"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","sourcesContent":["import { MAX_NODE_TIMER_DELAY_MS } from './canonical-contracts.js';\nimport { BatchContractError, BatchMutationReplayError } from './errors.js';\nimport { failedResult, freezeResult, readClock, type EventEmitter } from './scheduler-runtime.js';\nimport type {\n BatchClock,\n BatchExecutionContext,\n BatchItemContract,\n BatchItemResult,\n BatchJsonValue,\n BatchRecoverySource,\n BatchResumeItem,\n BatchRetryContext,\n BatchSleep,\n RunBoundedBatchOptions,\n} from './types.js';\n\nexport async function executeClaim<\n TInput,\n TOutput,\n TIdentity extends BatchJsonValue,\n TExclusiveKey extends string,\n>(input: {\n options: RunBoundedBatchOptions<TInput, TOutput, TIdentity, TExclusiveKey>;\n index: number;\n itemContract: BatchItemContract;\n exclusiveKey: TExclusiveKey | null;\n resumeItem: BatchResumeItem<TOutput> | undefined;\n clock: BatchClock;\n sleep: BatchSleep;\n emitter: EventEmitter;\n}): Promise<BatchItemResult<TInput, TOutput>> {\n const { options, index, itemContract, exclusiveKey, resumeItem, clock, sleep, emitter } = input;\n const itemId = itemContract.item_id;\n const item = options.items[index]!;\n const resumed = resumeItem?.state === 'attempted';\n const baseAttempts = resumed ? resumeItem.attempts : 0;\n if (resumed && options.mode === 'mutation') {\n const error = new BatchMutationReplayError(itemId);\n if (!options.recoverMutation) {\n return failedResult(\n item,\n itemContract,\n index,\n baseAttempts,\n true,\n true,\n clock,\n error,\n exclusiveKey,\n );\n }\n return recoverMutation({\n options,\n item,\n itemContract,\n exclusiveKey,\n index,\n attempts: baseAttempts,\n error,\n source: 'resume_incomplete',\n resumed: true,\n clock,\n emitter,\n });\n }\n\n const maxAttempts = options.retry?.maxAttempts ?? 1;\n for (let invocationAttempt = 1; ; invocationAttempt += 1) {\n const attempt = baseAttempts + invocationAttempt;\n const context: BatchExecutionContext<TInput, TExclusiveKey> = {\n item,\n item_id: itemId,\n item_contract: itemContract,\n exclusive_key: exclusiveKey,\n input_index: index,\n attempt,\n mode: options.mode,\n };\n await emitter.emit('attempt_started', { item_id: itemId, input_index: index, attempt });\n try {\n const value = await options.execute(context);\n await emitter.emit('attempt_succeeded', { item_id: itemId, input_index: index, attempt });\n return freezeResult({\n item,\n item_id: itemId,\n item_contract: itemContract,\n exclusive_key: exclusiveKey,\n input_index: index,\n attempts: attempt,\n attempt_consumed: true,\n resumed,\n completed_at_ms: readClock(clock),\n status: 'succeeded',\n value,\n });\n } catch (error) {\n await emitter.emit('attempt_failed', { item_id: itemId, input_index: index, attempt });\n if (options.mode === 'mutation') {\n if (!options.recoverMutation) {\n return failedResult(\n item,\n itemContract,\n index,\n attempt,\n true,\n resumed,\n clock,\n error,\n exclusiveKey,\n );\n }\n return recoverMutation({\n options,\n item,\n itemContract,\n exclusiveKey,\n index,\n attempts: attempt,\n error,\n source: 'execution_error',\n resumed,\n clock,\n emitter,\n });\n }\n if (!options.retry || invocationAttempt >= maxAttempts) {\n return failedResult(\n item,\n itemContract,\n index,\n attempt,\n true,\n resumed,\n clock,\n error,\n exclusiveKey,\n );\n }\n const retryContext: BatchRetryContext<TInput, TExclusiveKey> = { ...context, error };\n let retry = false;\n try {\n retry = await options.retry.shouldRetry(retryContext);\n } catch (classificationError) {\n return failedResult(\n item,\n itemContract,\n index,\n attempt,\n true,\n resumed,\n clock,\n classificationError,\n exclusiveKey,\n );\n }\n if (!retry) {\n return failedResult(\n item,\n itemContract,\n index,\n attempt,\n true,\n resumed,\n clock,\n error,\n exclusiveKey,\n );\n }\n let requestedDelay: number;\n try {\n requestedDelay = await options.retry.delayMs(retryContext);\n if (\n !Number.isSafeInteger(requestedDelay) ||\n requestedDelay < 0 ||\n requestedDelay > MAX_NODE_TIMER_DELAY_MS\n ) {\n throw new BatchContractError(\n `Batch retry delay must be a non-negative safe integer no greater than the maximum supported timer delay (${MAX_NODE_TIMER_DELAY_MS} ms).`,\n );\n }\n } catch (delayError) {\n return failedResult(\n item,\n itemContract,\n index,\n attempt,\n true,\n resumed,\n clock,\n delayError,\n exclusiveKey,\n );\n }\n const delay = Math.min(requestedDelay, options.retry.maxDelayMs);\n await emitter.emit('retry_scheduled', {\n item_id: itemId,\n input_index: index,\n attempt,\n delay_ms: delay,\n });\n try {\n await sleep(delay);\n } catch (sleepError) {\n return failedResult(\n item,\n itemContract,\n index,\n attempt,\n true,\n resumed,\n clock,\n sleepError,\n exclusiveKey,\n );\n }\n }\n }\n}\n\nasync function recoverMutation<\n TInput,\n TOutput,\n TIdentity extends BatchJsonValue,\n TExclusiveKey extends string,\n>(input: {\n options: RunBoundedBatchOptions<TInput, TOutput, TIdentity, TExclusiveKey>;\n item: TInput;\n itemContract: BatchItemContract;\n exclusiveKey: TExclusiveKey | null;\n index: number;\n attempts: number;\n error: unknown;\n source: BatchRecoverySource;\n resumed: boolean;\n clock: BatchClock;\n emitter: EventEmitter;\n}): Promise<BatchItemResult<TInput, TOutput>> {\n const {\n options,\n item,\n itemContract,\n exclusiveKey,\n index,\n attempts,\n error,\n source,\n resumed,\n clock,\n emitter,\n } = input;\n const itemId = itemContract.item_id;\n await emitter.emit('recovery_started', {\n item_id: itemId,\n input_index: index,\n attempt: attempts,\n source,\n });\n try {\n const recovery = await options.recoverMutation!({\n item,\n item_id: itemId,\n item_contract: itemContract,\n exclusive_key: exclusiveKey,\n input_index: index,\n attempts,\n error,\n source,\n });\n if (recovery.status === 'recovered') {\n await emitter.emit('recovery_succeeded', {\n item_id: itemId,\n input_index: index,\n attempt: attempts,\n source,\n });\n return freezeResult({\n item,\n item_id: itemId,\n item_contract: itemContract,\n exclusive_key: exclusiveKey,\n input_index: index,\n attempts,\n attempt_consumed: true,\n resumed,\n completed_at_ms: readClock(clock),\n status: 'recovered',\n value: recovery.value,\n });\n }\n if (recovery.status !== 'unresolved') {\n throw new BatchContractError('Mutation recovery returned an unsupported status.');\n }\n await emitter.emit('recovery_failed', {\n item_id: itemId,\n input_index: index,\n attempt: attempts,\n source,\n });\n return failedResult(\n item,\n itemContract,\n index,\n attempts,\n true,\n resumed,\n clock,\n recovery.error ?? error,\n exclusiveKey,\n );\n } catch (recoveryError) {\n await emitter.emit('recovery_failed', {\n item_id: itemId,\n input_index: index,\n attempt: attempts,\n source,\n });\n return failedResult(\n item,\n itemContract,\n index,\n attempts,\n true,\n resumed,\n clock,\n recoveryError,\n exclusiveKey,\n );\n }\n}\n"]}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { BatchContract, BatchItemContract, BatchJsonValue, CreateBatchContractOptions, CreateBatchItemContractOptions } from './types.js';
|
|
2
|
+
export declare const MAX_NODE_TIMER_DELAY_MS = 2147483647;
|
|
3
|
+
export declare function canonicalBatchJson(value: BatchJsonValue): string;
|
|
4
|
+
export declare function sha256BatchBytes(value: string | Uint8Array): string;
|
|
5
|
+
export declare function sha256BatchJson(value: BatchJsonValue): string;
|
|
6
|
+
export declare function createBatchContract<TIdentity extends BatchJsonValue>(options: CreateBatchContractOptions<TIdentity>): BatchContract<TIdentity>;
|
|
7
|
+
export declare function createBatchItemContract(options: CreateBatchItemContractOptions): BatchItemContract;
|
|
8
|
+
export declare function parseBatchItemContract(value: unknown): BatchItemContract;
|
|
9
|
+
export declare function assertBatchItemContractMatches(expectedValue: unknown, actualValue: unknown): BatchItemContract;
|
|
10
|
+
export declare function parseBatchContract<TIdentity extends BatchJsonValue = BatchJsonValue>(value: unknown): BatchContract<TIdentity>;
|
|
11
|
+
export declare function assertBatchContractMatches<TIdentity extends BatchJsonValue>(expectedValue: unknown, actualValue: unknown): BatchContract<TIdentity>;
|
|
12
|
+
export declare function canonicalizeBatchValue(value: unknown, ancestors: Set<object>): BatchJsonValue;
|
|
13
|
+
export declare function freezeBatchJson<T extends BatchJsonValue>(value: T): T;
|
|
14
|
+
export declare function isRecord(value: unknown): value is Record<string, unknown>;
|
|
15
|
+
export declare function parseBatchRunPath(value: unknown): string;
|
|
16
|
+
export declare function parseBatchRunLockToken(value: unknown, label: string): string;
|
|
17
|
+
export declare function assertBatchTimerDelaySupported(value: number | undefined, label: string): void;
|
|
18
|
+
export declare function hasExactKeys(value: Record<string, unknown>, expectedKeys: readonly string[]): boolean;
|
|
19
|
+
export declare function parseItemIdentity(value: unknown, location: number | 'contract'): string;
|
|
20
|
+
export declare function assertUniqueItemIds(itemIds: readonly string[]): void;
|
|
21
|
+
export declare function batchItemContractsMatch(left: BatchItemContract, right: BatchItemContract): boolean;
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { BatchContractError, BatchItemResumeContractError } from './errors.js';
|
|
4
|
+
const SHA256_PATTERN = /^[a-f0-9]{64}$/u;
|
|
5
|
+
export const MAX_NODE_TIMER_DELAY_MS = 2_147_483_647;
|
|
6
|
+
export function canonicalBatchJson(value) {
|
|
7
|
+
return JSON.stringify(canonicalizeBatchValue(value, new Set()));
|
|
8
|
+
}
|
|
9
|
+
export function sha256BatchBytes(value) {
|
|
10
|
+
return createHash('sha256').update(value).digest('hex');
|
|
11
|
+
}
|
|
12
|
+
export function sha256BatchJson(value) {
|
|
13
|
+
return sha256BatchBytes(canonicalBatchJson(value));
|
|
14
|
+
}
|
|
15
|
+
export function createBatchContract(options) {
|
|
16
|
+
const identity = freezeBatchJson(canonicalizeBatchValue(options.identity, new Set()));
|
|
17
|
+
return Object.freeze({
|
|
18
|
+
identity,
|
|
19
|
+
content_sha256: sha256BatchJson(options.content),
|
|
20
|
+
policy_sha256: sha256BatchJson(options.policy),
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
export function createBatchItemContract(options) {
|
|
24
|
+
const itemId = parseItemIdentity(options.item_id, 'contract');
|
|
25
|
+
return Object.freeze({
|
|
26
|
+
item_id: itemId,
|
|
27
|
+
content_sha256: sha256BatchJson(options.content),
|
|
28
|
+
policy_sha256: sha256BatchJson(options.policy),
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
export function parseBatchItemContract(value) {
|
|
32
|
+
if (!isRecord(value) ||
|
|
33
|
+
typeof value.item_id !== 'string' ||
|
|
34
|
+
typeof value.content_sha256 !== 'string' ||
|
|
35
|
+
!SHA256_PATTERN.test(value.content_sha256) ||
|
|
36
|
+
typeof value.policy_sha256 !== 'string' ||
|
|
37
|
+
!SHA256_PATTERN.test(value.policy_sha256)) {
|
|
38
|
+
throw new BatchContractError('Batch item contract requires item_id plus valid content and policy SHA-256 values.');
|
|
39
|
+
}
|
|
40
|
+
return Object.freeze({
|
|
41
|
+
item_id: parseItemIdentity(value.item_id, 'contract'),
|
|
42
|
+
content_sha256: value.content_sha256,
|
|
43
|
+
policy_sha256: value.policy_sha256,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
export function assertBatchItemContractMatches(expectedValue, actualValue) {
|
|
47
|
+
const expected = parseBatchItemContract(expectedValue);
|
|
48
|
+
const actual = parseBatchItemContract(actualValue);
|
|
49
|
+
if (!batchItemContractsMatch(expected, actual)) {
|
|
50
|
+
throw new BatchItemResumeContractError(expected.item_id);
|
|
51
|
+
}
|
|
52
|
+
return actual;
|
|
53
|
+
}
|
|
54
|
+
export function parseBatchContract(value) {
|
|
55
|
+
if (!isRecord(value) || !hasExactKeys(value, ['content_sha256', 'identity', 'policy_sha256'])) {
|
|
56
|
+
throw new BatchContractError('Batch contract must contain exact identity, content_sha256, and policy_sha256 keys.');
|
|
57
|
+
}
|
|
58
|
+
let identity;
|
|
59
|
+
try {
|
|
60
|
+
identity = freezeBatchJson(canonicalizeBatchValue(value.identity, new Set()));
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
throw new BatchContractError('Batch contract identity must be canonical JSON data.', {
|
|
64
|
+
cause: error,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
if (typeof value.content_sha256 !== 'string' ||
|
|
68
|
+
!SHA256_PATTERN.test(value.content_sha256) ||
|
|
69
|
+
typeof value.policy_sha256 !== 'string' ||
|
|
70
|
+
!SHA256_PATTERN.test(value.policy_sha256)) {
|
|
71
|
+
throw new BatchContractError('Batch contract content and policy SHA-256 values are malformed.');
|
|
72
|
+
}
|
|
73
|
+
return Object.freeze({
|
|
74
|
+
identity,
|
|
75
|
+
content_sha256: value.content_sha256,
|
|
76
|
+
policy_sha256: value.policy_sha256,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
export function assertBatchContractMatches(expectedValue, actualValue) {
|
|
80
|
+
const expected = parseBatchContract(expectedValue);
|
|
81
|
+
const actual = parseBatchContract(actualValue);
|
|
82
|
+
if (canonicalBatchJson(expected.identity) !== canonicalBatchJson(actual.identity) ||
|
|
83
|
+
expected.content_sha256 !== actual.content_sha256 ||
|
|
84
|
+
expected.policy_sha256 !== actual.policy_sha256) {
|
|
85
|
+
throw new BatchContractError('Batch resume requires an exact identity, content SHA-256, and policy SHA-256 match.');
|
|
86
|
+
}
|
|
87
|
+
return actual;
|
|
88
|
+
}
|
|
89
|
+
export function canonicalizeBatchValue(value, ancestors) {
|
|
90
|
+
if (value === null || typeof value === 'string' || typeof value === 'boolean')
|
|
91
|
+
return value;
|
|
92
|
+
if (typeof value === 'number') {
|
|
93
|
+
if (!Number.isFinite(value)) {
|
|
94
|
+
throw new BatchContractError('Canonical batch JSON rejects non-finite numbers.');
|
|
95
|
+
}
|
|
96
|
+
return value;
|
|
97
|
+
}
|
|
98
|
+
if (typeof value !== 'object') {
|
|
99
|
+
throw new BatchContractError('Canonical batch JSON accepts JSON data only.');
|
|
100
|
+
}
|
|
101
|
+
if (ancestors.has(value)) {
|
|
102
|
+
throw new BatchContractError('Canonical batch JSON rejects cyclic data.');
|
|
103
|
+
}
|
|
104
|
+
ancestors.add(value);
|
|
105
|
+
try {
|
|
106
|
+
if (Array.isArray(value)) {
|
|
107
|
+
return value.map((entry) => canonicalizeBatchValue(entry, ancestors));
|
|
108
|
+
}
|
|
109
|
+
const prototype = Object.getPrototypeOf(value);
|
|
110
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
111
|
+
throw new BatchContractError('Canonical batch JSON accepts plain objects only.');
|
|
112
|
+
}
|
|
113
|
+
return Object.fromEntries(Object.keys(value)
|
|
114
|
+
.sort()
|
|
115
|
+
.map((key) => [
|
|
116
|
+
key,
|
|
117
|
+
canonicalizeBatchValue(value[key], ancestors),
|
|
118
|
+
]));
|
|
119
|
+
}
|
|
120
|
+
finally {
|
|
121
|
+
ancestors.delete(value);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
export function freezeBatchJson(value) {
|
|
125
|
+
if (value !== null && typeof value === 'object') {
|
|
126
|
+
for (const child of Object.values(value))
|
|
127
|
+
freezeBatchJson(child);
|
|
128
|
+
Object.freeze(value);
|
|
129
|
+
}
|
|
130
|
+
return value;
|
|
131
|
+
}
|
|
132
|
+
export function isRecord(value) {
|
|
133
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
134
|
+
}
|
|
135
|
+
export function parseBatchRunPath(value) {
|
|
136
|
+
if (typeof value !== 'string' || value.length === 0 || /[\0\r\n]/u.test(value)) {
|
|
137
|
+
throw new BatchContractError('Batch runPath must be a non-empty single-line path.');
|
|
138
|
+
}
|
|
139
|
+
return path.resolve(value);
|
|
140
|
+
}
|
|
141
|
+
export function parseBatchRunLockToken(value, label) {
|
|
142
|
+
if (typeof value !== 'string' || value.length === 0 || /[\0\r\n]/u.test(value)) {
|
|
143
|
+
throw new BatchContractError(`Batch run-lock ${label} must be a non-empty single-line string.`);
|
|
144
|
+
}
|
|
145
|
+
return value;
|
|
146
|
+
}
|
|
147
|
+
export function assertBatchTimerDelaySupported(value, label) {
|
|
148
|
+
if (value === undefined)
|
|
149
|
+
return;
|
|
150
|
+
if (!Number.isSafeInteger(value) || value < 0) {
|
|
151
|
+
throw new BatchContractError(`Batch ${label} must be a non-negative safe integer.`);
|
|
152
|
+
}
|
|
153
|
+
if (value > MAX_NODE_TIMER_DELAY_MS) {
|
|
154
|
+
throw new BatchContractError(`Batch ${label} exceeds the maximum supported timer delay (${MAX_NODE_TIMER_DELAY_MS} ms).`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
export function hasExactKeys(value, expectedKeys) {
|
|
158
|
+
const actual = Object.keys(value).sort();
|
|
159
|
+
const expected = [...expectedKeys].sort();
|
|
160
|
+
return actual.length === expected.length && actual.every((key, index) => key === expected[index]);
|
|
161
|
+
}
|
|
162
|
+
export function parseItemIdentity(value, location) {
|
|
163
|
+
if (typeof value !== 'string' || value.length === 0 || /[\0\r\n]/u.test(value)) {
|
|
164
|
+
throw new BatchContractError(location === 'contract'
|
|
165
|
+
? 'Batch item contract identity must be a non-empty single-line string.'
|
|
166
|
+
: `Batch item identity at input index ${location} must be a non-empty single-line string.`);
|
|
167
|
+
}
|
|
168
|
+
return value;
|
|
169
|
+
}
|
|
170
|
+
export function assertUniqueItemIds(itemIds) {
|
|
171
|
+
if (new Set(itemIds).size !== itemIds.length) {
|
|
172
|
+
throw new BatchContractError('Batch item identities must be unique.');
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
export function batchItemContractsMatch(left, right) {
|
|
176
|
+
return (left.item_id === right.item_id &&
|
|
177
|
+
left.content_sha256 === right.content_sha256 &&
|
|
178
|
+
left.policy_sha256 === right.policy_sha256);
|
|
179
|
+
}
|
|
180
|
+
//# sourceMappingURL=canonical-contracts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canonical-contracts.js","sourceRoot":"","sources":["../../../../src/lib/batch/canonical-contracts.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from 'node:crypto';\nimport path from 'node:path';\n\nimport { BatchContractError, BatchItemResumeContractError } from './errors.js';\nimport type {\n BatchContract,\n BatchItemContract,\n BatchJsonValue,\n CreateBatchContractOptions,\n CreateBatchItemContractOptions,\n} from './types.js';\n\nconst SHA256_PATTERN = /^[a-f0-9]{64}$/u;\nexport const MAX_NODE_TIMER_DELAY_MS = 2_147_483_647;\n\nexport function canonicalBatchJson(value: BatchJsonValue): string {\n return JSON.stringify(canonicalizeBatchValue(value, new Set<object>()));\n}\n\nexport function sha256BatchBytes(value: string | Uint8Array): string {\n return createHash('sha256').update(value).digest('hex');\n}\n\nexport function sha256BatchJson(value: BatchJsonValue): string {\n return sha256BatchBytes(canonicalBatchJson(value));\n}\n\nexport function createBatchContract<TIdentity extends BatchJsonValue>(\n options: CreateBatchContractOptions<TIdentity>,\n): BatchContract<TIdentity> {\n const identity = freezeBatchJson(\n canonicalizeBatchValue(options.identity, new Set<object>()),\n ) as TIdentity;\n return Object.freeze({\n identity,\n content_sha256: sha256BatchJson(options.content),\n policy_sha256: sha256BatchJson(options.policy),\n });\n}\n\nexport function createBatchItemContract(\n options: CreateBatchItemContractOptions,\n): BatchItemContract {\n const itemId = parseItemIdentity(options.item_id, 'contract');\n return Object.freeze({\n item_id: itemId,\n content_sha256: sha256BatchJson(options.content),\n policy_sha256: sha256BatchJson(options.policy),\n });\n}\n\nexport function parseBatchItemContract(value: unknown): BatchItemContract {\n if (\n !isRecord(value) ||\n typeof value.item_id !== 'string' ||\n typeof value.content_sha256 !== 'string' ||\n !SHA256_PATTERN.test(value.content_sha256) ||\n typeof value.policy_sha256 !== 'string' ||\n !SHA256_PATTERN.test(value.policy_sha256)\n ) {\n throw new BatchContractError(\n 'Batch item contract requires item_id plus valid content and policy SHA-256 values.',\n );\n }\n return Object.freeze({\n item_id: parseItemIdentity(value.item_id, 'contract'),\n content_sha256: value.content_sha256,\n policy_sha256: value.policy_sha256,\n });\n}\n\nexport function assertBatchItemContractMatches(\n expectedValue: unknown,\n actualValue: unknown,\n): BatchItemContract {\n const expected = parseBatchItemContract(expectedValue);\n const actual = parseBatchItemContract(actualValue);\n if (!batchItemContractsMatch(expected, actual)) {\n throw new BatchItemResumeContractError(expected.item_id);\n }\n return actual;\n}\n\nexport function parseBatchContract<TIdentity extends BatchJsonValue = BatchJsonValue>(\n value: unknown,\n): BatchContract<TIdentity> {\n if (!isRecord(value) || !hasExactKeys(value, ['content_sha256', 'identity', 'policy_sha256'])) {\n throw new BatchContractError(\n 'Batch contract must contain exact identity, content_sha256, and policy_sha256 keys.',\n );\n }\n let identity: TIdentity;\n try {\n identity = freezeBatchJson(\n canonicalizeBatchValue(value.identity, new Set<object>()),\n ) as TIdentity;\n } catch (error) {\n throw new BatchContractError('Batch contract identity must be canonical JSON data.', {\n cause: error,\n });\n }\n if (\n typeof value.content_sha256 !== 'string' ||\n !SHA256_PATTERN.test(value.content_sha256) ||\n typeof value.policy_sha256 !== 'string' ||\n !SHA256_PATTERN.test(value.policy_sha256)\n ) {\n throw new BatchContractError('Batch contract content and policy SHA-256 values are malformed.');\n }\n return Object.freeze({\n identity,\n content_sha256: value.content_sha256,\n policy_sha256: value.policy_sha256,\n });\n}\n\nexport function assertBatchContractMatches<TIdentity extends BatchJsonValue>(\n expectedValue: unknown,\n actualValue: unknown,\n): BatchContract<TIdentity> {\n const expected = parseBatchContract<TIdentity>(expectedValue);\n const actual = parseBatchContract<TIdentity>(actualValue);\n if (\n canonicalBatchJson(expected.identity) !== canonicalBatchJson(actual.identity) ||\n expected.content_sha256 !== actual.content_sha256 ||\n expected.policy_sha256 !== actual.policy_sha256\n ) {\n throw new BatchContractError(\n 'Batch resume requires an exact identity, content SHA-256, and policy SHA-256 match.',\n );\n }\n return actual;\n}\n\nexport function canonicalizeBatchValue(value: unknown, ancestors: Set<object>): BatchJsonValue {\n if (value === null || typeof value === 'string' || typeof value === 'boolean') return value;\n if (typeof value === 'number') {\n if (!Number.isFinite(value)) {\n throw new BatchContractError('Canonical batch JSON rejects non-finite numbers.');\n }\n return value;\n }\n if (typeof value !== 'object') {\n throw new BatchContractError('Canonical batch JSON accepts JSON data only.');\n }\n if (ancestors.has(value)) {\n throw new BatchContractError('Canonical batch JSON rejects cyclic data.');\n }\n ancestors.add(value);\n try {\n if (Array.isArray(value)) {\n return value.map((entry) => canonicalizeBatchValue(entry, ancestors));\n }\n const prototype = Object.getPrototypeOf(value);\n if (prototype !== Object.prototype && prototype !== null) {\n throw new BatchContractError('Canonical batch JSON accepts plain objects only.');\n }\n return Object.fromEntries(\n Object.keys(value)\n .sort()\n .map((key) => [\n key,\n canonicalizeBatchValue((value as Record<string, unknown>)[key], ancestors),\n ]),\n );\n } finally {\n ancestors.delete(value);\n }\n}\n\nexport function freezeBatchJson<T extends BatchJsonValue>(value: T): T {\n if (value !== null && typeof value === 'object') {\n for (const child of Object.values(value)) freezeBatchJson(child);\n Object.freeze(value);\n }\n return value;\n}\n\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nexport function parseBatchRunPath(value: unknown): string {\n if (typeof value !== 'string' || value.length === 0 || /[\\0\\r\\n]/u.test(value)) {\n throw new BatchContractError('Batch runPath must be a non-empty single-line path.');\n }\n return path.resolve(value);\n}\n\nexport function parseBatchRunLockToken(value: unknown, label: string): string {\n if (typeof value !== 'string' || value.length === 0 || /[\\0\\r\\n]/u.test(value)) {\n throw new BatchContractError(`Batch run-lock ${label} must be a non-empty single-line string.`);\n }\n return value;\n}\n\nexport function assertBatchTimerDelaySupported(value: number | undefined, label: string): void {\n if (value === undefined) return;\n if (!Number.isSafeInteger(value) || value < 0) {\n throw new BatchContractError(`Batch ${label} must be a non-negative safe integer.`);\n }\n if (value > MAX_NODE_TIMER_DELAY_MS) {\n throw new BatchContractError(\n `Batch ${label} exceeds the maximum supported timer delay (${MAX_NODE_TIMER_DELAY_MS} ms).`,\n );\n }\n}\n\nexport function hasExactKeys(\n value: Record<string, unknown>,\n expectedKeys: readonly string[],\n): boolean {\n const actual = Object.keys(value).sort();\n const expected = [...expectedKeys].sort();\n return actual.length === expected.length && actual.every((key, index) => key === expected[index]);\n}\n\nexport function parseItemIdentity(value: unknown, location: number | 'contract'): string {\n if (typeof value !== 'string' || value.length === 0 || /[\\0\\r\\n]/u.test(value)) {\n throw new BatchContractError(\n location === 'contract'\n ? 'Batch item contract identity must be a non-empty single-line string.'\n : `Batch item identity at input index ${location} must be a non-empty single-line string.`,\n );\n }\n return value;\n}\n\nexport function assertUniqueItemIds(itemIds: readonly string[]): void {\n if (new Set(itemIds).size !== itemIds.length) {\n throw new BatchContractError('Batch item identities must be unique.');\n }\n}\n\nexport function batchItemContractsMatch(\n left: BatchItemContract,\n right: BatchItemContract,\n): boolean {\n return (\n left.item_id === right.item_id &&\n left.content_sha256 === right.content_sha256 &&\n left.policy_sha256 === right.policy_sha256\n );\n}\n"]}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import type { BatchJsonValue, BatchRunResult, RunBoundedBatchOptions } from './types.js';
|
|
2
|
+
export declare function runBoundedBatch<TInput, TOutput, TIdentity extends BatchJsonValue, TExclusiveKey extends string = string>(options: RunBoundedBatchOptions<TInput, TOutput, TIdentity, TExclusiveKey>): Promise<BatchRunResult<TInput, TOutput, TIdentity>>;
|