things-api 0.11.0 → 0.13.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/README.md +16 -5
- package/dist/audit/schema.d.ts +7 -0
- package/dist/audit/schema.js.map +1 -1
- package/dist/cli/canonical-ref.d.ts +21 -0
- package/dist/cli/canonical-ref.js +23 -0
- package/dist/cli/canonical-ref.js.map +1 -0
- package/dist/cli/commands/area.d.ts +18 -11
- package/dist/cli/commands/area.js +81 -77
- package/dist/cli/commands/area.js.map +1 -1
- package/dist/cli/commands/doctor.d.ts +1 -2
- package/dist/cli/commands/doctor.js +11 -1
- package/dist/cli/commands/doctor.js.map +1 -1
- package/dist/cli/commands/install-skill.d.ts +19 -4
- package/dist/cli/commands/install-skill.js +130 -52
- package/dist/cli/commands/install-skill.js.map +1 -1
- package/dist/cli/commands/mcp.js +21 -1
- package/dist/cli/commands/mcp.js.map +1 -1
- package/dist/cli/commands/project.d.ts +1 -0
- package/dist/cli/commands/project.js +92 -48
- package/dist/cli/commands/project.js.map +1 -1
- package/dist/cli/commands/reads.d.ts +5 -1
- package/dist/cli/commands/reads.js +110 -31
- package/dist/cli/commands/reads.js.map +1 -1
- package/dist/cli/commands/setup.js +11 -4
- package/dist/cli/commands/setup.js.map +1 -1
- package/dist/cli/commands/show.js +75 -36
- package/dist/cli/commands/show.js.map +1 -1
- package/dist/cli/commands/todo.js +2 -2
- package/dist/cli/commands/todo.js.map +1 -1
- package/dist/cli/commands/writes.js +795 -144
- package/dist/cli/commands/writes.js.map +1 -1
- package/dist/cli/did-you-mean.d.ts +14 -3
- package/dist/cli/did-you-mean.js +44 -7
- package/dist/cli/did-you-mean.js.map +1 -1
- package/dist/cli/dry-run.d.ts +55 -0
- package/dist/cli/dry-run.js +92 -0
- package/dist/cli/dry-run.js.map +1 -0
- package/dist/cli/glyphs.d.ts +16 -2
- package/dist/cli/glyphs.js +60 -12
- package/dist/cli/glyphs.js.map +1 -1
- package/dist/cli/help.js +69 -13
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/main.js +8 -1
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/move-hint.d.ts +3 -2
- package/dist/cli/move-hint.js +2 -2
- package/dist/cli/move-hint.js.map +1 -1
- package/dist/cli/read-driver.d.ts +53 -5
- package/dist/cli/read-driver.js +82 -15
- package/dist/cli/read-driver.js.map +1 -1
- package/dist/cli/ref-render.d.ts +14 -0
- package/dist/cli/ref-render.js +9 -0
- package/dist/cli/ref-render.js.map +1 -0
- package/dist/cli/render.d.ts +40 -14
- package/dist/cli/render.js +128 -55
- package/dist/cli/render.js.map +1 -1
- package/dist/cli/resolve-invocation.d.ts +7 -5
- package/dist/cli/resolve-invocation.js +16 -7
- package/dist/cli/resolve-invocation.js.map +1 -1
- package/dist/cli/skill-check.d.ts +12 -6
- package/dist/cli/skill-check.js +31 -14
- package/dist/cli/skill-check.js.map +1 -1
- package/dist/cli/skill.d.ts +8 -0
- package/dist/cli/skill.js +10 -0
- package/dist/cli/skill.js.map +1 -1
- package/dist/cli/verb-hint.js +1 -1
- package/dist/cli/verb-hint.js.map +1 -1
- package/dist/client.d.ts +158 -26
- package/dist/client.js +254 -38
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts +66 -2
- package/dist/config.js +120 -12
- package/dist/config.js.map +1 -1
- package/dist/contracts.d.ts +187 -28
- package/dist/contracts.js +22 -1
- package/dist/contracts.js.map +1 -1
- package/dist/db/baselines/db-v26.js +4 -1
- package/dist/db/baselines/db-v26.js.map +1 -1
- package/dist/diagnose.d.ts +34 -2
- package/dist/diagnose.js +36 -1
- package/dist/diagnose.js.map +1 -1
- package/dist/index.d.ts +21 -8
- package/dist/index.js +26 -3
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.d.ts +9 -0
- package/dist/mcp/server.js +790 -322
- package/dist/mcp/server.js.map +1 -1
- package/dist/model/entities.d.ts +57 -4
- package/dist/model/entities.js.map +1 -1
- package/dist/model/mappers.d.ts +2 -0
- package/dist/model/mappers.js +44 -2
- package/dist/model/mappers.js.map +1 -1
- package/dist/model/recurrence.d.ts +8 -1
- package/dist/model/recurrence.js.map +1 -1
- package/dist/model/serialize.js +15 -3
- package/dist/model/serialize.js.map +1 -1
- package/dist/read/area-filter.d.ts +76 -0
- package/dist/read/area-filter.js +58 -0
- package/dist/read/area-filter.js.map +1 -0
- package/dist/read/area-view.d.ts +22 -11
- package/dist/read/area-view.js +54 -28
- package/dist/read/area-view.js.map +1 -1
- package/dist/read/detail.js +15 -8
- package/dist/read/detail.js.map +1 -1
- package/dist/read/predicates.d.ts +18 -0
- package/dist/read/predicates.js +19 -0
- package/dist/read/predicates.js.map +1 -1
- package/dist/read/project-view.d.ts +91 -15
- package/dist/read/project-view.js +135 -37
- package/dist/read/project-view.js.map +1 -1
- package/dist/read/pseudo-area.d.ts +29 -0
- package/dist/read/pseudo-area.js +27 -0
- package/dist/read/pseudo-area.js.map +1 -0
- package/dist/read/queries.d.ts +175 -19
- package/dist/read/queries.js +376 -32
- package/dist/read/queries.js.map +1 -1
- package/dist/read/scope.d.ts +126 -0
- package/dist/read/scope.js +161 -0
- package/dist/read/scope.js.map +1 -0
- package/dist/read/search-rank.d.ts +2 -5
- package/dist/read/shape.d.ts +181 -0
- package/dist/read/shape.js +737 -0
- package/dist/read/shape.js.map +1 -0
- package/dist/read/show-target.d.ts +9 -1
- package/dist/read/show-target.js +77 -17
- package/dist/read/show-target.js.map +1 -1
- package/dist/read/stage.d.ts +199 -0
- package/dist/read/stage.js +125 -0
- package/dist/read/stage.js.map +1 -0
- package/dist/read/truncation.d.ts +7 -12
- package/dist/read/truncation.js +56 -21
- package/dist/read/truncation.js.map +1 -1
- package/dist/read/views.d.ts +68 -16
- package/dist/read/views.js +206 -52
- package/dist/read/views.js.map +1 -1
- package/dist/surface-copy.d.ts +16 -0
- package/dist/surface-copy.js +16 -0
- package/dist/surface-copy.js.map +1 -1
- package/dist/write/batch.d.ts +49 -10
- package/dist/write/batch.js +423 -71
- package/dist/write/batch.js.map +1 -1
- package/dist/write/commands.js +268 -55
- package/dist/write/commands.js.map +1 -1
- package/dist/write/guards.d.ts +1 -1
- package/dist/write/guards.js +71 -10
- package/dist/write/guards.js.map +1 -1
- package/dist/write/heading.d.ts +10 -1
- package/dist/write/heading.js +35 -5
- package/dist/write/heading.js.map +1 -1
- package/dist/write/make-repeating-project.d.ts +2 -2
- package/dist/write/make-repeating-project.js +9 -9
- package/dist/write/make-repeating-project.js.map +1 -1
- package/dist/write/move.d.ts +134 -0
- package/dist/write/move.js +1874 -0
- package/dist/write/move.js.map +1 -0
- package/dist/write/operations.d.ts +105 -19
- package/dist/write/operations.js +56 -7
- package/dist/write/operations.js.map +1 -1
- package/dist/write/opid.d.ts +31 -0
- package/dist/write/opid.js +30 -0
- package/dist/write/opid.js.map +1 -0
- package/dist/write/pipeline.d.ts +70 -3
- package/dist/write/pipeline.js +198 -33
- package/dist/write/pipeline.js.map +1 -1
- package/dist/write/pre-state.d.ts +172 -8
- package/dist/write/pre-state.js +467 -25
- package/dist/write/pre-state.js.map +1 -1
- package/dist/write/reorder.d.ts +23 -3
- package/dist/write/reorder.js +2106 -86
- package/dist/write/reorder.js.map +1 -1
- package/dist/write/reversibility.js +19 -7
- package/dist/write/reversibility.js.map +1 -1
- package/dist/write/scope-guard.d.ts +31 -0
- package/dist/write/scope-guard.js +162 -0
- package/dist/write/scope-guard.js.map +1 -0
- package/dist/write/undo.js +120 -13
- package/dist/write/undo.js.map +1 -1
- package/dist/write/vectors/applescript.js +16 -7
- package/dist/write/vectors/applescript.js.map +1 -1
- package/dist/write/vectors/shortcuts.js +1 -1
- package/dist/write/vectors/shortcuts.js.map +1 -1
- package/dist/write/vectors/simulator.js +83 -27
- package/dist/write/vectors/simulator.js.map +1 -1
- package/dist/write/vectors/types.d.ts +11 -0
- package/dist/write/vectors/ui-certification.d.ts +3 -3
- package/dist/write/vectors/ui-certification.js +41 -5
- package/dist/write/vectors/ui-certification.js.map +1 -1
- package/dist/write/vectors/ui-drag.d.ts +21 -1
- package/dist/write/vectors/ui-drag.js +138 -27
- package/dist/write/vectors/ui-drag.js.map +1 -1
- package/dist/write/vectors/ui-recipes.d.ts +21 -1
- package/dist/write/vectors/ui-recipes.js +166 -7
- package/dist/write/vectors/ui-recipes.js.map +1 -1
- package/dist/write/vectors/ui.d.ts +25 -1
- package/dist/write/vectors/ui.js +142 -33
- package/dist/write/vectors/ui.js.map +1 -1
- package/dist/write/verify/delta.d.ts +17 -1
- package/dist/write/verify/delta.js +106 -29
- package/dist/write/verify/delta.js.map +1 -1
- package/package.json +7 -1
- package/schema/envelope.schema.json +397 -0
- package/skills/things-cli/SKILL.md +36 -17
- package/skills/things-cli/references/banner.md +35 -0
- package/skills/things-cli/references/contracts.md +32 -9
- package/skills/things-cli/references/errors.md +57 -0
- package/skills/things-cli/references/gui.md +1 -1
- package/skills/things-cli/references/model.md +18 -7
- package/skills/things-cli/references/ordering.md +88 -0
package/dist/surface-copy.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"surface-copy.js","sourceRoot":"","sources":["../src/surface-copy.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,yCAAyC;AACzC,MAAM,CAAC,MAAM,WAAW,GAAG,kDAAkD,CAAC;AAE9E,gEAAgE;AAChE,MAAM,CAAC,MAAM,WAAW,GAAG,YAAY,CAAC;AAExC,6CAA6C;AAC7C,MAAM,CAAC,MAAM,eAAe,GAAG,iBAAiB,CAAC;AAEjD,uDAAuD;AACvD,MAAM,CAAC,MAAM,UAAU,GAAG,qBAAqB,CAAC;AAEhD;;;;GAIG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,oEAAoE,CAAC;AACjG,MAAM,CAAC,MAAM,YAAY,GAAG,+DAA+D,CAAC;AAE5F,sEAAsE;AACtE,MAAM,CAAC,MAAM,kBAAkB,GAAG,EAAE,CAAC;AAErC,2EAA2E;AAC3E,MAAM,CAAC,MAAM,UAAU,GAAG,kCAAkC,kBAAkB,GAAG,CAAC;AAElF,6DAA6D;AAC7D,MAAM,CAAC,MAAM,QAAQ,GAAG,oCAAoC,CAAC;AAE7D,2EAA2E;AAC3E,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,CAAC;AAEvC,8EAA8E;AAC9E,MAAM,CAAC,MAAM,kBAAkB,GAAG,EAAE,CAAC;AAErC,+DAA+D;AAC/D,MAAM,CAAC,MAAM,eAAe,GAAG,mGAAmG,kBAAkB,gCAAgC,CAAC;AAErL,+DAA+D;AAC/D,MAAM,CAAC,MAAM,kBAAkB,GAAG,4DAA4D,qBAAqB,sCAAsC,CAAC;AAE1J,kFAAkF;AAClF,MAAM,CAAC,MAAM,gBAAgB,GAAG,oDAAoD,CAAC;AAErF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,eAAe,GAC1B,qJAAqJ,CAAC;AAExJ;;;;;;;;GAQG;AACH,MAAM,UAAU,cAAc,CAAC,MAAsD;IACnF,QAAQ,MAAM,CAAC,MAAM,EAAE,CAAC;QACtB,KAAK,IAAI;YACP,OAAO,EAAE,CAAC;QACZ,KAAK,OAAO;YACV,OAAO;gBACL,wEAAwE;oBACtE,8CAA8C;aACjD,CAAC;QACJ,KAAK,iBAAiB;YACpB,OAAO;gBACL,6EAA6E;oBAC3E,8CAA8C;aACjD,CAAC;IACN,CAAC;AACH,CAAC"}
|
|
1
|
+
{"version":3,"file":"surface-copy.js","sourceRoot":"","sources":["../src/surface-copy.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,yCAAyC;AACzC,MAAM,CAAC,MAAM,WAAW,GAAG,kDAAkD,CAAC;AAE9E,gEAAgE;AAChE,MAAM,CAAC,MAAM,WAAW,GAAG,YAAY,CAAC;AAExC,6CAA6C;AAC7C,MAAM,CAAC,MAAM,eAAe,GAAG,iBAAiB,CAAC;AAEjD,uDAAuD;AACvD,MAAM,CAAC,MAAM,UAAU,GAAG,qBAAqB,CAAC;AAEhD;;;;GAIG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,oEAAoE,CAAC;AACjG,MAAM,CAAC,MAAM,YAAY,GAAG,+DAA+D,CAAC;AAE5F,sEAAsE;AACtE,MAAM,CAAC,MAAM,kBAAkB,GAAG,EAAE,CAAC;AAErC,2EAA2E;AAC3E,MAAM,CAAC,MAAM,UAAU,GAAG,kCAAkC,kBAAkB,GAAG,CAAC;AAElF,6DAA6D;AAC7D,MAAM,CAAC,MAAM,QAAQ,GAAG,oCAAoC,CAAC;AAE7D;;;;;GAKG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,sDAAsD,CAAC;AAEhF,2EAA2E;AAC3E,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,CAAC;AAEvC,8EAA8E;AAC9E,MAAM,CAAC,MAAM,kBAAkB,GAAG,EAAE,CAAC;AAErC,+DAA+D;AAC/D,MAAM,CAAC,MAAM,eAAe,GAAG,mGAAmG,kBAAkB,gCAAgC,CAAC;AAErL,+DAA+D;AAC/D,MAAM,CAAC,MAAM,kBAAkB,GAAG,4DAA4D,qBAAqB,sCAAsC,CAAC;AAE1J,kFAAkF;AAClF,MAAM,CAAC,MAAM,gBAAgB,GAAG,oDAAoD,CAAC;AAErF,oFAAoF;AACpF,MAAM,CAAC,MAAM,gBAAgB,GAAG,sEAAsE,UAAU,yCAAyC,CAAC;AAE1J;;;;;GAKG;AACH,MAAM,CAAC,MAAM,eAAe,GAC1B,qJAAqJ,CAAC;AAExJ;;;;;GAKG;AACH,MAAM,CAAC,MAAM,aAAa,GACxB,obAAob,CAAC;AAEvb;;;;;;;;GAQG;AACH,MAAM,UAAU,cAAc,CAAC,MAAsD;IACnF,QAAQ,MAAM,CAAC,MAAM,EAAE,CAAC;QACtB,KAAK,IAAI;YACP,OAAO,EAAE,CAAC;QACZ,KAAK,OAAO;YACV,OAAO;gBACL,wEAAwE;oBACtE,8CAA8C;aACjD,CAAC;QACJ,KAAK,iBAAiB;YACpB,OAAO;gBACL,6EAA6E;oBAC3E,8CAA8C;aACjD,CAAC;IACN,CAAC;AACH,CAAC"}
|
package/dist/write/batch.d.ts
CHANGED
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Batch mutations: N ops through the SAME pipeline as single mutations —
|
|
3
|
-
* every op individually pre-read, guarded, verified, and audited. The wins
|
|
4
|
-
* are amortization (one process, one DB handle, one config load) and a
|
|
5
|
-
* per-op result stream; there is deliberately NO transactional semantics
|
|
6
|
-
* (the app's surfaces have none to offer). Ops run SEQUENTIALLY: the
|
|
7
|
-
* mutation lock serializes them anyway, and create-probe verification must
|
|
8
|
-
* never race.
|
|
9
|
-
*/
|
|
10
1
|
import { type OperationKind } from "./operations.ts";
|
|
11
2
|
import { type WriteDeps, type WriteOptions } from "./pipeline.ts";
|
|
12
3
|
import { type ReorderResult } from "./reorder.ts";
|
|
@@ -14,12 +5,36 @@ import { type ReorderResult } from "./reorder.ts";
|
|
|
14
5
|
export interface BatchOp {
|
|
15
6
|
op: OperationKind;
|
|
16
7
|
params: Record<string, unknown>;
|
|
8
|
+
/**
|
|
9
|
+
* Client handle for the uuid this op CREATES — a later line references it as
|
|
10
|
+
* `"$tempId"` (or `"$tempId.instance"` / `"$tempId.replaced"`) in any
|
|
11
|
+
* ref-accepting param. Valid only on uuid-minting ops (never `tag.add` — tags
|
|
12
|
+
* have no uuid; reference a tag by its title). `[A-Za-z0-9_-]{1,32}`; unique
|
|
13
|
+
* within one batch.
|
|
14
|
+
*/
|
|
15
|
+
tempId?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Client idempotency id: a resubmitted batch whose line carries the same id
|
|
18
|
+
* as a prior successful change is skipped (reported already-applied) instead
|
|
19
|
+
* of re-created. `[A-Za-z0-9_-]{1,64}`.
|
|
20
|
+
*/
|
|
21
|
+
opId?: string;
|
|
17
22
|
/** Per-op acknowledgements/overrides (a safe subset of WriteOptions). */
|
|
18
23
|
options?: {
|
|
19
24
|
acknowledgeChecklistReset?: boolean;
|
|
20
25
|
acknowledgeProjectReopen?: boolean;
|
|
21
26
|
dangerouslyPermanent?: boolean;
|
|
22
27
|
acknowledgeTagSubtree?: boolean;
|
|
28
|
+
/** Delete a non-empty area together with its contents (area.delete). */
|
|
29
|
+
allowNonEmptyArea?: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Acknowledge a GUI-driven op (make-repeating, convert-to-project, …) — the
|
|
32
|
+
* second of its two keys. Required for any ui-drive op (several of which are
|
|
33
|
+
* uuid-minting, so tempId-eligible), or the per-leg H-UI-DRIVE gate blocks it.
|
|
34
|
+
*/
|
|
35
|
+
dangerouslyDriveGui?: boolean;
|
|
36
|
+
/** Create any missing tag (mkdir-p for parent/child) instead of failing on an unknown tag. */
|
|
37
|
+
createTags?: boolean;
|
|
23
38
|
vector?: WriteOptions["vector"];
|
|
24
39
|
verifyTimeoutMs?: number;
|
|
25
40
|
maxDisruption?: WriteOptions["maxDisruption"];
|
|
@@ -33,11 +48,35 @@ export type BatchItemOutcome = ReorderResult | {
|
|
|
33
48
|
kind: "skipped";
|
|
34
49
|
op: string;
|
|
35
50
|
detail: string;
|
|
51
|
+
} | {
|
|
52
|
+
/** An idempotency-id match: the op was already applied by an earlier submission. */
|
|
53
|
+
kind: "already-applied";
|
|
54
|
+
op: string;
|
|
55
|
+
uuid: string;
|
|
56
|
+
detail: string;
|
|
36
57
|
};
|
|
37
58
|
export interface BatchItemResult {
|
|
38
59
|
index: number;
|
|
39
60
|
op: string;
|
|
40
61
|
outcome: BatchItemOutcome;
|
|
62
|
+
/** Echoed when the op declared a temp id (ADDITIVE). */
|
|
63
|
+
tempId?: string;
|
|
64
|
+
/** The uuid bound to this op's temp id, once the leg minted one (ADDITIVE). */
|
|
65
|
+
boundUuid?: string;
|
|
66
|
+
/** Echoed when the op carried a client idempotency id (ADDITIVE). */
|
|
67
|
+
opId?: string;
|
|
68
|
+
}
|
|
69
|
+
/** The whole-batch result: the per-op stream plus the batch-level additions. */
|
|
70
|
+
export interface BatchResult {
|
|
71
|
+
results: BatchItemResult[];
|
|
72
|
+
/** Every temp id that bound a uuid → that uuid (ADDITIVE; empty when none declared). */
|
|
73
|
+
tempIdMapping: Record<string, string>;
|
|
74
|
+
/**
|
|
75
|
+
* The batch-level undo token — pass it to `things undo --txn <token>` to
|
|
76
|
+
* reverse the WHOLE submission as one unit. Absent for a dry-run, an
|
|
77
|
+
* all-rejected batch, or one where no leg reached the pipeline.
|
|
78
|
+
*/
|
|
79
|
+
undoToken?: string;
|
|
41
80
|
}
|
|
42
81
|
export interface BatchOptions {
|
|
43
82
|
/** Stop at the first non-ok outcome; remaining ops report kind "skipped". */
|
|
@@ -48,4 +87,4 @@ export interface BatchOptions {
|
|
|
48
87
|
}
|
|
49
88
|
/** True when an outcome should be treated as a failure for --fail-fast/exit. */
|
|
50
89
|
export declare function outcomeFailed(outcome: BatchItemOutcome): boolean;
|
|
51
|
-
export declare function runBatch(deps: WriteDeps, ops: BatchOp[], options?: BatchOptions, onResult?: (result: BatchItemResult) => void): Promise<
|
|
90
|
+
export declare function runBatch(deps: WriteDeps, ops: BatchOp[], options?: BatchOptions, onResult?: (result: BatchItemResult) => void): Promise<BatchResult>;
|
package/dist/write/batch.js
CHANGED
|
@@ -1,100 +1,452 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Batch mutations: N ops through the SAME pipeline as single mutations —
|
|
3
|
-
* every op individually pre-read, guarded, verified, and audited. The wins
|
|
4
|
-
* are amortization (one process, one DB handle, one config load) and a
|
|
5
|
-
* per-op result stream; there is deliberately NO transactional semantics
|
|
6
|
-
* (the app's surfaces have none to offer). Ops run SEQUENTIALLY: the
|
|
7
|
-
* mutation lock serializes them anyway, and create-probe verification must
|
|
8
|
-
* never race.
|
|
9
|
-
*/
|
|
10
1
|
import { OPERATION_KINDS } from "./operations.js";
|
|
11
|
-
import {
|
|
2
|
+
import { findAppliedOpId, OP_ID_RE } from "./opid.js";
|
|
3
|
+
import { fingerprintLabel, runMutation } from "./pipeline.js";
|
|
12
4
|
import { runReorder } from "./reorder.js";
|
|
5
|
+
import { readAuditRecords } from "./undo.js";
|
|
13
6
|
const KNOWN_OPS = new Set(OPERATION_KINDS);
|
|
7
|
+
/**
|
|
8
|
+
* Ops that MINT a uuid, so may declare a `tempId` — the ratified rule is
|
|
9
|
+
* "anything that creates a new uuid". NB: `tag.add` is deliberately absent (tags
|
|
10
|
+
* have no uuid — identity is the title).
|
|
11
|
+
*/
|
|
12
|
+
const UUID_MINTING_OPS = new Set([
|
|
13
|
+
"todo.add",
|
|
14
|
+
"todo.add-logged",
|
|
15
|
+
"project.add",
|
|
16
|
+
"project.add-repeating",
|
|
17
|
+
"area.add",
|
|
18
|
+
"project.add-heading",
|
|
19
|
+
"todo.duplicate",
|
|
20
|
+
"project.duplicate",
|
|
21
|
+
"todo.make-repeating",
|
|
22
|
+
"project.make-repeating",
|
|
23
|
+
"todo.convert-to-project",
|
|
24
|
+
"project.promote-heading",
|
|
25
|
+
]);
|
|
26
|
+
const TEMP_ID_RE = /^[A-Za-z0-9_-]{1,32}$/;
|
|
27
|
+
/**
|
|
28
|
+
* Param keys whose values are REFERENCES (uuid / name / container) — the only
|
|
29
|
+
* places a `"$temp"` handle is resolved. A value starting with `$` anywhere
|
|
30
|
+
* else (a title, notes) is a literal, never a ref. Container refs nest the id
|
|
31
|
+
* under `.uuid`; `uuids` is a list.
|
|
32
|
+
*/
|
|
33
|
+
const REF_KEYS = new Set([
|
|
34
|
+
"uuid",
|
|
35
|
+
"uuids",
|
|
36
|
+
"target",
|
|
37
|
+
"before",
|
|
38
|
+
"after",
|
|
39
|
+
"project",
|
|
40
|
+
"area",
|
|
41
|
+
"container",
|
|
42
|
+
"heading",
|
|
43
|
+
"headings",
|
|
44
|
+
]);
|
|
14
45
|
/** True when an outcome should be treated as a failure for --fail-fast/exit. */
|
|
15
46
|
export function outcomeFailed(outcome) {
|
|
16
|
-
return outcome.kind !== "ok" && outcome.kind !== "dry-run";
|
|
47
|
+
return outcome.kind !== "ok" && outcome.kind !== "dry-run" && outcome.kind !== "already-applied";
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Pre-flight scan of every declared `tempId`, BEFORE any leg runs. Returns the
|
|
51
|
+
* name→line index of valid declarations and, per offending line, the usage
|
|
52
|
+
* detail. Any declaration error rejects the WHOLE batch (nothing executes) —
|
|
53
|
+
* a temp-id script with a bad/duplicate/misplaced handle is a structural error,
|
|
54
|
+
* like a torn JSONL line.
|
|
55
|
+
*/
|
|
56
|
+
function validateDeclarations(ops) {
|
|
57
|
+
const declIndex = new Map();
|
|
58
|
+
const errors = new Map();
|
|
59
|
+
for (let i = 0; i < ops.length; i++) {
|
|
60
|
+
const entry = ops[i];
|
|
61
|
+
if (typeof entry !== "object" || entry === null)
|
|
62
|
+
continue;
|
|
63
|
+
const tempId = entry.tempId;
|
|
64
|
+
if (tempId === undefined)
|
|
65
|
+
continue;
|
|
66
|
+
if (entry.op === "tag.add") {
|
|
67
|
+
errors.set(i, 'tempId is not valid on "tag.add": a tag has no uuid to bind — reference a tag by its title instead');
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (!UUID_MINTING_OPS.has(entry.op)) {
|
|
71
|
+
errors.set(i, `tempId is only valid on an op that creates something (e.g. todo.add, project.add, project.add-heading) — not "${entry.op}"`);
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (typeof tempId !== "string" || !TEMP_ID_RE.test(tempId)) {
|
|
75
|
+
errors.set(i, "tempId must match [A-Za-z0-9_-] and be 1–32 characters");
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
const prior = declIndex.get(tempId);
|
|
79
|
+
if (prior !== undefined) {
|
|
80
|
+
errors.set(i, `duplicate tempId "${tempId}" (already declared on line ${prior + 1})`);
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
declIndex.set(tempId, i);
|
|
84
|
+
}
|
|
85
|
+
return { declIndex, errors };
|
|
86
|
+
}
|
|
87
|
+
/** Parse a `"$name"` / `"$name.instance"` token into its handle + accessor. */
|
|
88
|
+
function parseRef(value) {
|
|
89
|
+
const body = value.slice(1); // drop the leading "$"
|
|
90
|
+
const dot = body.indexOf(".");
|
|
91
|
+
if (dot < 0)
|
|
92
|
+
return { name: body };
|
|
93
|
+
return { name: body.slice(0, dot), accessor: body.slice(dot + 1) };
|
|
94
|
+
}
|
|
95
|
+
/** Resolve one `$`-token to a uuid, or a fail-closed usage detail. */
|
|
96
|
+
function resolveRef(value, declIndex, bindings, currentIndex) {
|
|
97
|
+
const { name, accessor } = parseRef(value);
|
|
98
|
+
const declaredAt = declIndex.get(name);
|
|
99
|
+
if (declaredAt === undefined) {
|
|
100
|
+
return {
|
|
101
|
+
ok: false,
|
|
102
|
+
detail: `unresolved-temp-ref: "${value}" names no tempId declared in this batch`,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
const bound = bindings.get(name);
|
|
106
|
+
if (bound === undefined) {
|
|
107
|
+
if (declaredAt > currentIndex) {
|
|
108
|
+
return {
|
|
109
|
+
ok: false,
|
|
110
|
+
detail: `unresolved-temp-ref: "${value}" is a forward reference — tempId "${name}" is declared later (line ${declaredAt + 1})`,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
ok: false,
|
|
115
|
+
detail: `unresolved-temp-ref: tempId "${name}" bound nothing (its op failed or was skipped) — cannot resolve "${value}"`,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
if (accessor === undefined)
|
|
119
|
+
return { ok: true, uuid: bound.primary };
|
|
120
|
+
if (accessor === "instance") {
|
|
121
|
+
if (bound.instance === null) {
|
|
122
|
+
return { ok: false, detail: `"${value}" has no spawned-instance uuid to resolve` };
|
|
123
|
+
}
|
|
124
|
+
return { ok: true, uuid: bound.instance };
|
|
125
|
+
}
|
|
126
|
+
if (accessor === "replaced") {
|
|
127
|
+
if (bound.replaced === null) {
|
|
128
|
+
return { ok: false, detail: `"${value}" has no replaced uuid to resolve` };
|
|
129
|
+
}
|
|
130
|
+
return { ok: true, uuid: bound.replaced };
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
ok: false,
|
|
134
|
+
detail: `"${value}" uses an unknown accessor ".${accessor}" (only .instance and .replaced exist)`,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
/** True for a string that must be treated as a temp ref (leading `$`). */
|
|
138
|
+
function isRef(value) {
|
|
139
|
+
return typeof value === "string" && value.startsWith("$");
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Resolve every `$`-reference in a line's ref-accepting params against the
|
|
143
|
+
* current bindings. Returns rewritten params, or the FIRST fail-closed detail
|
|
144
|
+
* (that line becomes invalid; independent later lines still run).
|
|
145
|
+
*/
|
|
146
|
+
function resolveRefs(params, declIndex, bindings, currentIndex) {
|
|
147
|
+
const out = { ...params };
|
|
148
|
+
const resolveOne = (value) => resolveRef(value, declIndex, bindings, currentIndex);
|
|
149
|
+
for (const key of Object.keys(out)) {
|
|
150
|
+
if (!REF_KEYS.has(key))
|
|
151
|
+
continue;
|
|
152
|
+
const value = out[key];
|
|
153
|
+
if (isRef(value)) {
|
|
154
|
+
const r = resolveOne(value);
|
|
155
|
+
if (!r.ok)
|
|
156
|
+
return r;
|
|
157
|
+
out[key] = r.uuid;
|
|
158
|
+
}
|
|
159
|
+
else if (Array.isArray(value)) {
|
|
160
|
+
const next = [...value];
|
|
161
|
+
for (let i = 0; i < next.length; i++) {
|
|
162
|
+
if (isRef(next[i])) {
|
|
163
|
+
const r = resolveOne(next[i]);
|
|
164
|
+
if (!r.ok)
|
|
165
|
+
return r;
|
|
166
|
+
next[i] = r.uuid;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
out[key] = next;
|
|
170
|
+
}
|
|
171
|
+
else if (typeof value === "object" && value !== null) {
|
|
172
|
+
// Container ref: resolve a `$` uuid sub-field, leave title/others alone.
|
|
173
|
+
const ref = value;
|
|
174
|
+
if (isRef(ref["uuid"])) {
|
|
175
|
+
const r = resolveOne(ref["uuid"]);
|
|
176
|
+
if (!r.ok)
|
|
177
|
+
return r;
|
|
178
|
+
out[key] = { ...ref, uuid: r.uuid };
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return { ok: true, params: out };
|
|
183
|
+
}
|
|
184
|
+
/** Does any line use a `$`-reference in a ref-accepting param? (dry-run gate) */
|
|
185
|
+
function usesRef(params) {
|
|
186
|
+
for (const key of Object.keys(params)) {
|
|
187
|
+
if (!REF_KEYS.has(key))
|
|
188
|
+
continue;
|
|
189
|
+
const value = params[key];
|
|
190
|
+
if (isRef(value))
|
|
191
|
+
return true;
|
|
192
|
+
if (Array.isArray(value) && value.some(isRef))
|
|
193
|
+
return true;
|
|
194
|
+
if (typeof value === "object" &&
|
|
195
|
+
value !== null &&
|
|
196
|
+
isRef(value["uuid"]))
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
/** Bind a leg's discovered uuids to its temp id, once it verifies ok. */
|
|
202
|
+
function bindingFromResult(result) {
|
|
203
|
+
if (typeof result.uuid !== "string")
|
|
204
|
+
return null;
|
|
205
|
+
return {
|
|
206
|
+
primary: result.uuid,
|
|
207
|
+
instance: result.repeating?.instanceUuid ?? null,
|
|
208
|
+
replaced: result.repeating?.replacedUuid ?? null,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
/** Write the batch-level summary audit record (role "summary"; its undoToken is the batch token). */
|
|
212
|
+
function auditBatchSummary(deps, startedAt, txnId, legCount, tempIdMapping, actor) {
|
|
213
|
+
const fp = deps.fingerprint();
|
|
214
|
+
const hasMapping = Object.keys(tempIdMapping).length > 0;
|
|
215
|
+
const record = {
|
|
216
|
+
v: 1,
|
|
217
|
+
ts: startedAt.toISOString(),
|
|
218
|
+
actor,
|
|
219
|
+
host: deps.config.host,
|
|
220
|
+
op: "batch",
|
|
221
|
+
uuid: null,
|
|
222
|
+
vector: null,
|
|
223
|
+
disruption: null,
|
|
224
|
+
invocation: `batch ×${legCount}`,
|
|
225
|
+
txn: { id: txnId, role: "summary" },
|
|
226
|
+
requested: { legCount, ...(hasMapping && { tempIdMapping }) },
|
|
227
|
+
pre: null,
|
|
228
|
+
observed: hasMapping ? { tempIdMapping } : null,
|
|
229
|
+
result: "ok",
|
|
230
|
+
verify: null,
|
|
231
|
+
durationMs: (deps.now?.() ?? new Date()).getTime() - startedAt.getTime(),
|
|
232
|
+
env: {
|
|
233
|
+
pkg: deps.pkgVersion ?? "0.0.1",
|
|
234
|
+
dbVersion: fp.observation.databaseVersion,
|
|
235
|
+
fingerprint: fingerprintLabel(fp, deps.config),
|
|
236
|
+
},
|
|
237
|
+
};
|
|
238
|
+
deps.audit.append(record);
|
|
17
239
|
}
|
|
18
240
|
export async function runBatch(deps, ops, options = {}, onResult) {
|
|
241
|
+
const startedAt = deps.now?.() ?? new Date();
|
|
242
|
+
const actor = options.actor ?? deps.config.actor;
|
|
243
|
+
const bindings = new Map();
|
|
244
|
+
const tempIdMapping = {};
|
|
245
|
+
// Pre-flight: validate every temp-id declaration BEFORE any leg runs.
|
|
246
|
+
const { declIndex, errors: declErrors } = validateDeclarations(ops);
|
|
247
|
+
if (declErrors.size > 0) {
|
|
248
|
+
const results = ops.map((entry, index) => {
|
|
249
|
+
const op = String(entry?.op);
|
|
250
|
+
const detail = declErrors.get(index);
|
|
251
|
+
const outcome = detail !== undefined
|
|
252
|
+
? { kind: "invalid", op, detail }
|
|
253
|
+
: {
|
|
254
|
+
kind: "skipped",
|
|
255
|
+
op,
|
|
256
|
+
detail: "not run — the batch has a temp-id declaration error (see the invalid line)",
|
|
257
|
+
};
|
|
258
|
+
const result = { index, op, outcome };
|
|
259
|
+
onResult?.(result);
|
|
260
|
+
return result;
|
|
261
|
+
});
|
|
262
|
+
return { results, tempIdMapping };
|
|
263
|
+
}
|
|
264
|
+
// opId idempotency reads the recent trail ONCE, before executing.
|
|
265
|
+
const usesOpId = ops.some((o) => typeof o === "object" && o !== null && o.opId !== undefined);
|
|
266
|
+
const priorRecords = usesOpId && deps.auditDirPath !== undefined ? readAuditRecords(deps.auditDirPath) : [];
|
|
267
|
+
const txnId = `txn-batch-${startedAt.getTime().toString(36)}-${process.pid.toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
19
268
|
const results = [];
|
|
20
269
|
let halted = false;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
270
|
+
let legsDispatched = 0;
|
|
271
|
+
/** Bind (or rebind) a temp id to a discovered uuid so later `$refs` resolve. */
|
|
272
|
+
const bind = (tempId, binding) => {
|
|
273
|
+
bindings.set(tempId, binding);
|
|
274
|
+
tempIdMapping[tempId] = binding.primary;
|
|
275
|
+
};
|
|
276
|
+
/** Compute one line's outcome + bound uuid; mutates bindings/dispatch counter. */
|
|
277
|
+
const runLine = async (entry, index, tempId, opId) => {
|
|
278
|
+
if (typeof entry !== "object" || entry === null || typeof entry.op !== "string") {
|
|
279
|
+
return {
|
|
280
|
+
outcome: {
|
|
281
|
+
kind: "invalid",
|
|
282
|
+
op: String(entry?.op),
|
|
283
|
+
detail: "each op needs {op, params}",
|
|
284
|
+
},
|
|
29
285
|
};
|
|
30
286
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
287
|
+
if (!KNOWN_OPS.has(entry.op)) {
|
|
288
|
+
return {
|
|
289
|
+
outcome: {
|
|
290
|
+
kind: "invalid",
|
|
291
|
+
op: entry.op,
|
|
292
|
+
detail: `unknown op "${entry.op}" — see \`things capabilities\``,
|
|
293
|
+
},
|
|
36
294
|
};
|
|
37
295
|
}
|
|
38
|
-
|
|
39
|
-
outcome
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
296
|
+
if (typeof entry.params !== "object" || entry.params === null) {
|
|
297
|
+
return { outcome: { kind: "invalid", op: entry.op, detail: "params must be an object" } };
|
|
298
|
+
}
|
|
299
|
+
if (opId !== undefined && !OP_ID_RE.test(opId)) {
|
|
300
|
+
return {
|
|
301
|
+
outcome: {
|
|
302
|
+
kind: "invalid",
|
|
303
|
+
op: entry.op,
|
|
304
|
+
detail: "opId must match [A-Za-z0-9_-] and be 1–64 characters",
|
|
305
|
+
},
|
|
43
306
|
};
|
|
44
307
|
}
|
|
45
|
-
|
|
46
|
-
|
|
308
|
+
// opId idempotency: an earlier submission already applied this line — skip,
|
|
309
|
+
// report already-applied with the recorded uuid, and rebind it so later
|
|
310
|
+
// $refs still resolve.
|
|
311
|
+
if (opId !== undefined) {
|
|
312
|
+
const applied = findAppliedOpId(priorRecords, opId, startedAt);
|
|
313
|
+
if (applied !== undefined) {
|
|
314
|
+
const uuid = applied.uuid ?? "";
|
|
315
|
+
if (tempId !== undefined && uuid !== "")
|
|
316
|
+
bind(tempId, { primary: uuid, instance: null, replaced: null });
|
|
317
|
+
return {
|
|
318
|
+
outcome: {
|
|
319
|
+
kind: "already-applied",
|
|
320
|
+
op: entry.op,
|
|
321
|
+
uuid,
|
|
322
|
+
detail: "already applied by an earlier submission (matching opId in the change history) — not re-run",
|
|
323
|
+
},
|
|
324
|
+
...(tempId !== undefined && uuid !== "" && { boundUuid: uuid }),
|
|
325
|
+
};
|
|
326
|
+
}
|
|
47
327
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}),
|
|
59
|
-
...(entry.options?.acknowledgeTagSubtree !== undefined && {
|
|
60
|
-
acknowledgeTagSubtree: entry.options.acknowledgeTagSubtree,
|
|
61
|
-
}),
|
|
62
|
-
...(entry.options?.vector !== undefined && { vector: entry.options.vector }),
|
|
63
|
-
...(entry.options?.verifyTimeoutMs !== undefined && {
|
|
64
|
-
verifyTimeoutMs: entry.options.verifyTimeoutMs,
|
|
65
|
-
}),
|
|
66
|
-
...(entry.options?.maxDisruption !== undefined && {
|
|
67
|
-
maxDisruption: entry.options.maxDisruption,
|
|
68
|
-
}),
|
|
69
|
-
...(options.dryRun === true && { dryRun: true }),
|
|
70
|
-
...(options.actor !== undefined && { actor: options.actor }),
|
|
328
|
+
// Temp refs resolve only during real execution — nothing is created in a
|
|
329
|
+
// dry-run to bind them, so preview a ref-using line as skipped (not a false
|
|
330
|
+
// failure).
|
|
331
|
+
if (options.dryRun === true && usesRef(entry.params)) {
|
|
332
|
+
return {
|
|
333
|
+
outcome: {
|
|
334
|
+
kind: "skipped",
|
|
335
|
+
op: entry.op,
|
|
336
|
+
detail: "not previewed — temp-references resolve only during real execution (not --dry-run)",
|
|
337
|
+
},
|
|
71
338
|
};
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
339
|
+
}
|
|
340
|
+
const resolved = resolveRefs(entry.params, declIndex, bindings, index);
|
|
341
|
+
if (!resolved.ok) {
|
|
342
|
+
return { outcome: { kind: "invalid", op: entry.op, detail: resolved.detail } };
|
|
343
|
+
}
|
|
344
|
+
const writeOptions = {
|
|
345
|
+
...(entry.options?.acknowledgeChecklistReset !== undefined && {
|
|
346
|
+
acknowledgeChecklistReset: entry.options.acknowledgeChecklistReset,
|
|
347
|
+
}),
|
|
348
|
+
...(entry.options?.acknowledgeProjectReopen !== undefined && {
|
|
349
|
+
acknowledgeProjectReopen: entry.options.acknowledgeProjectReopen,
|
|
350
|
+
}),
|
|
351
|
+
...(entry.options?.dangerouslyPermanent !== undefined && {
|
|
352
|
+
dangerouslyPermanent: entry.options.dangerouslyPermanent,
|
|
353
|
+
}),
|
|
354
|
+
...(entry.options?.acknowledgeTagSubtree !== undefined && {
|
|
355
|
+
acknowledgeTagSubtree: entry.options.acknowledgeTagSubtree,
|
|
356
|
+
}),
|
|
357
|
+
...(entry.options?.allowNonEmptyArea !== undefined && {
|
|
358
|
+
allowNonEmptyArea: entry.options.allowNonEmptyArea,
|
|
359
|
+
}),
|
|
360
|
+
...(entry.options?.dangerouslyDriveGui !== undefined && {
|
|
361
|
+
dangerouslyDriveGui: entry.options.dangerouslyDriveGui,
|
|
362
|
+
}),
|
|
363
|
+
...(entry.options?.createTags !== undefined && { createTags: entry.options.createTags }),
|
|
364
|
+
...(entry.options?.vector !== undefined && { vector: entry.options.vector }),
|
|
365
|
+
...(entry.options?.verifyTimeoutMs !== undefined && {
|
|
366
|
+
verifyTimeoutMs: entry.options.verifyTimeoutMs,
|
|
367
|
+
}),
|
|
368
|
+
...(entry.options?.maxDisruption !== undefined && {
|
|
369
|
+
maxDisruption: entry.options.maxDisruption,
|
|
370
|
+
}),
|
|
371
|
+
...(options.dryRun === true && { dryRun: true }),
|
|
372
|
+
...(opId !== undefined && { opId }),
|
|
373
|
+
// Group every leg under one txn so the batch summary's undo token replays
|
|
374
|
+
// the whole submission (dry-run legs are not recorded, so no txn).
|
|
375
|
+
...(options.dryRun !== true && { txn: { id: txnId, role: "leg" } }),
|
|
376
|
+
actor,
|
|
377
|
+
};
|
|
378
|
+
try {
|
|
379
|
+
// Params arrive as parsed JSON; the pipeline's pre-read + guards are the
|
|
380
|
+
// runtime validators (loud on bad shapes), same as single ops.
|
|
381
|
+
const result = entry.op === "reorder"
|
|
382
|
+
? // batch ops run sequentially by design: the mutation lock serializes them and create-probe verification must never race
|
|
383
|
+
await runReorder(deps, resolved.params, writeOptions)
|
|
384
|
+
: // same sequencing requirement as the reorder branch above; batch is a
|
|
385
|
+
// consumer entry point, so a consumer `when` normalizes to the zone
|
|
386
|
+
await runMutation(deps, entry.op, resolved.params, { ...writeOptions, normalizeWhen: true });
|
|
387
|
+
if (options.dryRun !== true)
|
|
388
|
+
legsDispatched += 1;
|
|
389
|
+
// A leg binds its temp id ONLY when it verified ok — a failed leg
|
|
390
|
+
// (including a rule-mismatch that surfaces discovery uuids in its error)
|
|
391
|
+
// binds nothing, so later refs to it fail fast.
|
|
392
|
+
let boundUuid;
|
|
393
|
+
if (result.kind === "ok" && tempId !== undefined) {
|
|
394
|
+
const binding = bindingFromResult(result);
|
|
395
|
+
if (binding !== null) {
|
|
396
|
+
bind(tempId, binding);
|
|
397
|
+
boundUuid = binding.primary;
|
|
398
|
+
}
|
|
82
399
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
400
|
+
return { outcome: result, ...(boundUuid !== undefined && { boundUuid }) };
|
|
401
|
+
}
|
|
402
|
+
catch (err) {
|
|
403
|
+
// Param-shape errors (exclusive combos etc.) surface per-op, not fatally.
|
|
404
|
+
return {
|
|
405
|
+
outcome: {
|
|
86
406
|
kind: "invalid",
|
|
87
407
|
op: entry.op,
|
|
88
408
|
detail: err instanceof Error ? err.message : String(err),
|
|
89
|
-
}
|
|
90
|
-
}
|
|
409
|
+
},
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
for (let index = 0; index < ops.length; index++) {
|
|
414
|
+
const entry = ops[index];
|
|
415
|
+
const tempId = typeof entry === "object" && entry !== null ? entry.tempId : undefined;
|
|
416
|
+
const opId = typeof entry === "object" && entry !== null ? entry.opId : undefined;
|
|
417
|
+
let outcome;
|
|
418
|
+
let boundUuid;
|
|
419
|
+
if (halted) {
|
|
420
|
+
outcome = {
|
|
421
|
+
kind: "skipped",
|
|
422
|
+
op: String(entry?.op),
|
|
423
|
+
detail: "skipped after earlier failure (--fail-fast)",
|
|
424
|
+
};
|
|
91
425
|
}
|
|
92
|
-
|
|
426
|
+
else {
|
|
427
|
+
({ outcome, boundUuid } = await runLine(entry, index, tempId, opId));
|
|
428
|
+
}
|
|
429
|
+
const result = {
|
|
430
|
+
index,
|
|
431
|
+
op: String(entry?.op),
|
|
432
|
+
outcome,
|
|
433
|
+
...(tempId !== undefined && { tempId }),
|
|
434
|
+
...(boundUuid !== undefined && { boundUuid }),
|
|
435
|
+
...(opId !== undefined && { opId }),
|
|
436
|
+
};
|
|
93
437
|
results.push(result);
|
|
94
438
|
onResult?.(result);
|
|
95
439
|
if (options.failFast === true && !halted && outcomeFailed(outcome))
|
|
96
440
|
halted = true;
|
|
97
441
|
}
|
|
98
|
-
|
|
442
|
+
// Write the batch summary (and mint the batch undo token) when at least one
|
|
443
|
+
// leg actually reached the pipeline. Skipped for dry-run and all-rejected
|
|
444
|
+
// batches — there is nothing to undo.
|
|
445
|
+
let undoToken;
|
|
446
|
+
if (options.dryRun !== true && legsDispatched > 0) {
|
|
447
|
+
auditBatchSummary(deps, startedAt, txnId, legsDispatched, tempIdMapping, actor);
|
|
448
|
+
undoToken = txnId;
|
|
449
|
+
}
|
|
450
|
+
return { results, tempIdMapping, ...(undoToken !== undefined && { undoToken }) };
|
|
99
451
|
}
|
|
100
452
|
//# sourceMappingURL=batch.js.map
|