@astrosheep/keiyaku 2.9.11 → 2.9.13
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 +79 -85
- package/build/.tsbuildinfo +1 -1
- package/build/agents/call-terms.js +1 -0
- package/build/agents/providers/claude-agent-sdk/adapter.js +17 -4
- package/build/cli/commands/akuma/akuma/handler.js +2 -1
- package/build/cli/commands/akuma/akuma/meta.js +4 -3
- package/build/cli/commands/akuma/catalog.js +2 -0
- package/build/cli/commands/akuma/list/handler.js +1 -1
- package/build/cli/commands/akuma/list/meta-list.js +12 -0
- package/build/cli/commands/akuma/list/meta-ls.js +2 -2
- package/build/cli/commands/akuma.js +5 -0
- package/build/cli/commands/contract/amend/meta.js +4 -3
- package/build/cli/commands/contract/arc/meta.js +2 -0
- package/build/cli/commands/contract/audit/handler.js +28 -2
- package/build/cli/commands/contract/audit/meta.js +4 -3
- package/build/cli/commands/contract/bind/meta.js +4 -4
- package/build/cli/commands/contract/petition/handler.js +2 -1
- package/build/cli/commands/contract/petition/meta.js +2 -2
- package/build/cli/commands/contract/renew/handler.js +44 -3
- package/build/cli/commands/contract/renew/meta.js +5 -4
- package/build/cli/commands/metadata.js +14 -10
- package/build/cli/commands/projection/call/handler.js +1 -1
- package/build/cli/commands/projection/call/meta.js +1 -1
- package/build/cli/commands/projection/catalog.js +2 -2
- package/build/cli/commands/projection/{revive → fork}/handler.js +10 -5
- package/build/cli/commands/projection/fork/meta.js +12 -0
- package/build/cli/commands/projection/history/handler.js +46 -2
- package/build/cli/commands/projection/history/meta.js +3 -3
- package/build/cli/commands/projection/kill/handler.js +4 -5
- package/build/cli/commands/projection/kill/meta.js +2 -2
- package/build/cli/commands/projection/status/handler.js +29 -13
- package/build/cli/commands/projection/status/meta.js +14 -3
- package/build/cli/commands/projection/tell/handler.js +8 -4
- package/build/cli/commands/projection/tell/meta.js +1 -1
- package/build/cli/commands/projection/wait/handler.js +45 -20
- package/build/cli/commands/projection/wait/meta.js +1 -1
- package/build/cli/commands/shared.js +2 -2
- package/build/cli/commands/system/catalog.js +2 -0
- package/build/cli/commands/task/add/handler.js +28 -12
- package/build/cli/commands/task/add/meta.js +18 -6
- package/build/cli/commands/task/catalog.js +4 -0
- package/build/cli/commands/task/compose/handler.js +18 -0
- package/build/cli/commands/task/compose/meta.js +10 -0
- package/build/cli/commands/task/doctor/handler.js +2 -3
- package/build/cli/commands/task/doctor/meta.js +2 -2
- package/build/cli/commands/task/done/meta.js +2 -2
- package/build/cli/commands/task/drop/handler.js +7 -2
- package/build/cli/commands/task/drop/meta.js +6 -3
- package/build/cli/commands/task/hold/meta.js +2 -2
- package/build/cli/commands/task/log/meta.js +2 -2
- package/build/cli/commands/task/ls/meta.js +2 -2
- package/build/cli/commands/task/note/handler.js +6 -0
- package/build/cli/commands/task/note/meta.js +11 -0
- package/build/cli/commands/task/resume/meta.js +2 -2
- package/build/cli/commands/task/shared.js +29 -15
- package/build/cli/commands/task/show/meta.js +2 -2
- package/build/cli/commands/task/start/meta.js +2 -2
- package/build/cli/commands/task/stop/meta.js +2 -2
- package/build/cli/commands/task/update/meta.js +3 -2
- package/build/cli/commands/verification/handler.js +43 -0
- package/build/cli/commands/verification/meta.js +13 -0
- package/build/cli/completion.js +16 -13
- package/build/cli/flags.js +48 -12
- package/build/cli/help.js +29 -30
- package/build/cli/index.js +14 -8
- package/build/cli/parse-flags.js +60 -21
- package/build/cli/parse-metadata.js +10 -4
- package/build/cli/parse-selectors.js +22 -4
- package/build/cli/parse.js +43 -30
- package/build/cli/projection-address.js +7 -7
- package/build/cli/render/arc.js +12 -3
- package/build/cli/render/audit.js +2 -2
- package/build/cli/render/call.js +4 -4
- package/build/cli/render/kanshi.js +122 -85
- package/build/cli/render/misc.js +2 -2
- package/build/cli/render/petition.js +16 -1
- package/build/cli/render/projection-history.js +17 -4
- package/build/cli/render/shared.js +5 -5
- package/build/cli/render/status-indicator.js +47 -0
- package/build/cli/render/status.js +106 -76
- package/build/cli/render/success-response.js +20 -5
- package/build/cli/render/task.js +33 -5
- package/build/cli/render/tell.js +10 -4
- package/build/cli/render/tool-command-normalization.js +137 -0
- package/build/cli/render/tool-presentation.js +2 -1
- package/build/cli/render/verification.js +23 -0
- package/build/cli/render/wait.js +101 -33
- package/build/cli/types.js +2 -1
- package/build/config/akuma-loader.js +21 -1
- package/build/config/settings/knobs.js +7 -1
- package/build/config/settings/schema.js +10 -5
- package/build/core/addressing.js +3 -3
- package/build/core/amend.js +102 -17
- package/build/core/arc.js +4 -4
- package/build/core/audit/candidate.js +10 -18
- package/build/core/audit/coordinates.js +12 -17
- package/build/core/audit/evidence.js +6 -4
- package/build/core/audit/facade.js +3 -3
- package/build/core/audit/report.js +1 -1
- package/build/core/bind-reconciliation.js +42 -23
- package/build/core/bind.js +182 -81
- package/build/core/call/call.js +6 -7
- package/build/core/call/context.js +46 -33
- package/build/core/call/execution.js +21 -6
- package/build/core/call/prompt.js +11 -42
- package/build/core/contract-carrier-runtime.js +209 -105
- package/build/core/contract-carrier.js +156 -73
- package/build/core/contract-view.js +43 -6
- package/build/core/contract.js +2 -1
- package/build/core/derived-replay.js +168 -35
- package/build/core/draft.js +60 -27
- package/build/core/forfeit.js +1 -1
- package/build/core/lifecycle-history.js +13 -0
- package/build/core/lifecycle-recovery.js +32 -27
- package/build/core/lifecycle-runner.js +14 -14
- package/build/core/log.js +1 -1
- package/build/core/outcome-base.js +2 -2
- package/build/core/projection/akuma-name.js +43 -0
- package/build/core/projection/generation/database.js +141 -79
- package/build/core/projection/generation/ledger.js +91 -0
- package/build/core/projection/generation/model.js +19 -14
- package/build/core/projection/generation/projection-generation-continuation.js +1 -1
- package/build/core/projection/generation/projection-generation-doorbell.js +88 -0
- package/build/core/projection/generation/projection-generation-execution.js +24 -0
- package/build/core/projection/generation/projection-generation-identity.js +4 -0
- package/build/core/projection/generation/projection-generation-launcher.js +72 -138
- package/build/core/projection/generation/projection-generation-process.js +16 -15
- package/build/core/projection/generation/projection-generation-runner.js +132 -76
- package/build/core/projection/generation/projection-generation-runtime.js +10 -61
- package/build/core/projection/generation/protocol.js +68 -0
- package/build/core/projection/generation/store.js +17 -7
- package/build/core/projection/generation/transitions.js +24 -14
- package/build/core/projection/index.js +5 -5
- package/build/core/projection/projection-core.js +6 -2
- package/build/core/projection/projection-execution-observer.js +1 -1
- package/build/core/projection/projection-history.js +8 -8
- package/build/core/projection/projection-kill.js +20 -10
- package/build/core/projection/projection-life-observer.js +8 -8
- package/build/core/projection/projection-mint.js +6 -8
- package/build/core/projection/projection-status-observation.js +77 -8
- package/build/core/projection/projection-status.js +151 -4
- package/build/core/projection/projection-wait.js +48 -29
- package/build/core/projection/projection-wake.js +28 -88
- package/build/core/projection/tell/launch-store.js +1 -1
- package/build/core/projection/tell/store.js +1 -1
- package/build/core/renew-build.js +204 -74
- package/build/core/renew-plan.js +34 -9
- package/build/core/renew-rewrite.js +56 -15
- package/build/core/renew.js +39 -35
- package/build/core/repository-ledger/accepted-fold-read.js +6 -3
- package/build/core/repository-ledger/claim-evidence.js +1 -1
- package/build/core/repository-ledger/codec.js +11 -5
- package/build/core/repository-ledger/current-state-store.js +464 -349
- package/build/core/repository-ledger/fold-repository.js +5 -6
- package/build/core/repository-ledger/identity.js +60 -0
- package/build/core/repository-ledger/inventory.js +3 -3
- package/build/core/repository-ledger/read-model.js +57 -9
- package/build/core/repository-ledger/write-transaction.js +24 -17
- package/build/core/run-control/connection-bound-close.js +41 -0
- package/build/core/run-control/detached-lease-bootstrap.js +208 -0
- package/build/core/run-control/detached-process.js +14 -0
- package/build/core/run-control/index.js +6 -0
- package/build/core/{process-group.js → run-control/process-tree.js} +1 -1
- package/build/core/run-control/runner-lease.js +172 -0
- package/build/core/run-control/sqlite-process-lifetime-lock.js +174 -0
- package/build/core/seal.js +44 -32
- package/build/core/settlement/claim-delivery.js +22 -9
- package/build/core/settlement/claim.js +8 -6
- package/build/core/settlement/petition-claim-gates.js +9 -8
- package/build/core/settlement/petition-head-guard.js +5 -6
- package/build/core/settlement/petition-preview.js +21 -5
- package/build/core/settlement/petition.js +51 -17
- package/build/core/settlement/settlement.js +20 -15
- package/build/core/settlement/verdict.js +2 -2
- package/build/core/settlement/verification.js +492 -364
- package/build/core/status/board.js +184 -75
- package/build/core/status/drift.js +8 -10
- package/build/core/task/board.js +1 -0
- package/build/core/task/commands.js +25 -0
- package/build/core/task/compose.js +328 -0
- package/build/core/task/document.js +102 -39
- package/build/core/task/index.js +4 -3
- package/build/core/task/settlement-git.js +70 -30
- package/build/core/task/settlement-policy.js +27 -14
- package/build/core/task/source-board.js +1 -0
- package/build/core/task/task-bind-preparation.js +59 -9
- package/build/core/task/task-contract.js +2 -2
- package/build/core/task/task-git-store.js +1 -0
- package/build/core/task/task-store-repository.js +2 -0
- package/build/core/task/task.js +2 -2
- package/build/core/transcripts.js +4 -4
- package/build/core/verification-declaration.js +95 -0
- package/build/core/worktree-bootstrap.js +1 -1
- package/build/core/worktree-path.js +7 -11
- package/build/flow-error.js +15 -2
- package/build/generated/version.js +2 -2
- package/build/git/branches.js +41 -70
- package/build/git/commits.js +92 -21
- package/build/git/core.js +9 -161
- package/build/git/diff/preview.js +4 -4
- package/build/git/diff/read.js +4 -4
- package/build/git/diff/structured.js +5 -5
- package/build/git/process.js +229 -0
- package/build/git/refs.js +30 -5
- package/build/git/staging.js +39 -115
- package/build/git/streaming-batch.js +9 -16
- package/build/git/worktree.js +104 -91
- package/build/index.js +7 -0
- package/package.json +2 -2
- package/skills/keiyaku-akuma/SKILL.md +27 -21
- package/skills/keiyaku-task/SKILL.md +61 -5
- package/skills/keiyaku-workflow/SKILL.md +81 -26
- package/build/cli/commands/projection/revive/meta.js +0 -12
- package/build/core/projection/projection-life-protocol.js +0 -115
- package/build/core/projection/projection-runner-lock.js +0 -382
- package/build/core/projection/tell/database.js +0 -127
- package/build/core/settlement/verification-coordination.js +0 -305
- package/build/core/settlement/verification-supervisor.js +0 -275
|
@@ -5,7 +5,7 @@ import { resolveAcceptedLifecycleTransaction } from "./repository-ledger/accepte
|
|
|
5
5
|
import { reconcileCommittedMutation, } from "./lifecycle-recovery.js";
|
|
6
6
|
function coordinateObject(pins, target) {
|
|
7
7
|
return {
|
|
8
|
-
ledgerHead: pins.
|
|
8
|
+
ledgerHead: pins.repositoryLedgerHead,
|
|
9
9
|
targetRef: pins.target.ref,
|
|
10
10
|
targetHead: pins.target.head,
|
|
11
11
|
deliveryRef: pins.delivery.ref,
|
|
@@ -20,7 +20,7 @@ function sameCoordinates(admission, session) {
|
|
|
20
20
|
}
|
|
21
21
|
function validateReplayCoordinate(session, coordinate) {
|
|
22
22
|
if (coordinate.state === "bind-aligned") {
|
|
23
|
-
const currentHead = session.
|
|
23
|
+
const currentHead = session.pins.repositoryLedgerHead;
|
|
24
24
|
if (coordinate.authoritativeHead !== currentHead) {
|
|
25
25
|
return {
|
|
26
26
|
code: "LIFECYCLE_REPLAY_COORDINATES_CHANGED",
|
|
@@ -55,7 +55,7 @@ function validateReplayCoordinate(session, coordinate) {
|
|
|
55
55
|
},
|
|
56
56
|
current: {
|
|
57
57
|
status: resolved.status,
|
|
58
|
-
authoritativeHead: session.
|
|
58
|
+
authoritativeHead: session.pins.repositoryLedgerHead,
|
|
59
59
|
},
|
|
60
60
|
};
|
|
61
61
|
}
|
|
@@ -115,12 +115,12 @@ export async function runLifecycle(plan) {
|
|
|
115
115
|
};
|
|
116
116
|
}
|
|
117
117
|
const attempt = await withContractCarrierSession({ cwd: plan.cwd, contractId: plan.contractId, mode: "observe-only", signal: plan.signal }, async (session) => {
|
|
118
|
-
observedHead = session.
|
|
118
|
+
observedHead = session.pins.repositoryLedgerHead;
|
|
119
119
|
const replayRefusal = validateReplayCoordinate(session, reconciliation.coordinate);
|
|
120
120
|
if (replayRefusal) {
|
|
121
121
|
return { kind: "refused", outcome: refusedLifecycleFacts({
|
|
122
122
|
contractId: plan.contractId,
|
|
123
|
-
authoritativeHead: session.
|
|
123
|
+
authoritativeHead: session.pins.repositoryLedgerHead,
|
|
124
124
|
...surfaceCoordinateRefusal(plan.mutation, replayRefusal),
|
|
125
125
|
}) };
|
|
126
126
|
}
|
|
@@ -130,13 +130,13 @@ export async function runLifecycle(plan) {
|
|
|
130
130
|
}
|
|
131
131
|
catch (error) {
|
|
132
132
|
if (error instanceof FlowError && !isCancellation(error)) {
|
|
133
|
-
return { kind: "refused", outcome: refusedLifecycleFacts({ contractId: plan.contractId, authoritativeHead: session.
|
|
133
|
+
return { kind: "refused", outcome: refusedLifecycleFacts({ contractId: plan.contractId, authoritativeHead: session.pins.repositoryLedgerHead, ...flowRefusal(error) }) };
|
|
134
134
|
}
|
|
135
135
|
throw error;
|
|
136
136
|
}
|
|
137
137
|
return {
|
|
138
138
|
kind: "admitted",
|
|
139
|
-
ledgerHead: session.
|
|
139
|
+
ledgerHead: session.pins.repositoryLedgerHead,
|
|
140
140
|
lifecycle: session.lifecycle,
|
|
141
141
|
repository: session.repository,
|
|
142
142
|
pins: session.pins,
|
|
@@ -172,20 +172,20 @@ export async function runLifecycle(plan) {
|
|
|
172
172
|
throw error;
|
|
173
173
|
}
|
|
174
174
|
try {
|
|
175
|
-
return await withContractCarrierSession({ cwd: plan.cwd, contractId: plan.contractId, mode: "mutation", signal: plan.signal, ...(plan.renewDeliveryProjector ? { renewDeliveryProjector: plan.renewDeliveryProjector } : {}), ...(plan.terminalWorkspaceCleanup ? { terminalWorkspaceCleanup: plan.terminalWorkspaceCleanup } : {}) }, async (session) => {
|
|
176
|
-
observedHead = session.
|
|
175
|
+
return await withContractCarrierSession({ cwd: plan.cwd, contractId: plan.contractId, mode: "mutation", signal: plan.signal, ...(plan.renewDeliveryProjector ? { renewDeliveryProjector: plan.renewDeliveryProjector } : {}), ...(plan.terminalWorkspaceCleanup ? { terminalWorkspaceCleanup: plan.terminalWorkspaceCleanup } : {}), ...(plan.transactionRunner ? { transactionRunner: plan.transactionRunner } : {}) }, async (session) => {
|
|
176
|
+
observedHead = session.pins.repositoryLedgerHead;
|
|
177
177
|
const replayRefusal = validateReplayCoordinate(session, admission.replay);
|
|
178
178
|
if (replayRefusal) {
|
|
179
179
|
return { kind: "noncommitted", outcome: refusedLifecycleFacts({
|
|
180
180
|
contractId: plan.contractId,
|
|
181
|
-
authoritativeHead: session.
|
|
181
|
+
authoritativeHead: session.pins.repositoryLedgerHead,
|
|
182
182
|
...surfaceCoordinateRefusal(plan.mutation, replayRefusal),
|
|
183
183
|
}) };
|
|
184
184
|
}
|
|
185
185
|
if (!sameCoordinates(admission, session)) {
|
|
186
186
|
return { kind: "noncommitted", outcome: refusedLifecycleFacts({
|
|
187
187
|
contractId: plan.contractId,
|
|
188
|
-
authoritativeHead: session.
|
|
188
|
+
authoritativeHead: session.pins.repositoryLedgerHead,
|
|
189
189
|
code: "LIFECYCLE_COORDINATES_CHANGED",
|
|
190
190
|
message: "contract coordinates changed during lifecycle preparation",
|
|
191
191
|
pinned: coordinateObject(admission.pins, admission.target),
|
|
@@ -202,7 +202,7 @@ export async function runLifecycle(plan) {
|
|
|
202
202
|
refusal = flowRefusal(error);
|
|
203
203
|
}
|
|
204
204
|
if (refusal) {
|
|
205
|
-
return { kind: "noncommitted", outcome: refusedLifecycleFacts({ contractId: plan.contractId, authoritativeHead: session.
|
|
205
|
+
return { kind: "noncommitted", outcome: refusedLifecycleFacts({ contractId: plan.contractId, authoritativeHead: session.pins.repositoryLedgerHead, ...refusal }) };
|
|
206
206
|
}
|
|
207
207
|
const effectSession = Object.freeze({
|
|
208
208
|
stableRoot: session.stableRoot,
|
|
@@ -239,7 +239,7 @@ export async function runLifecycle(plan) {
|
|
|
239
239
|
if (error instanceof FlowError && !isCancellation(error)) {
|
|
240
240
|
return { kind: "noncommitted", outcome: refusedLifecycleFacts({
|
|
241
241
|
contractId: plan.contractId,
|
|
242
|
-
authoritativeHead: session.
|
|
242
|
+
authoritativeHead: session.pins.repositoryLedgerHead,
|
|
243
243
|
...flowRefusal(error),
|
|
244
244
|
}) };
|
|
245
245
|
}
|
|
@@ -274,7 +274,7 @@ export async function runLifecycle(plan) {
|
|
|
274
274
|
kind: "noncommitted",
|
|
275
275
|
outcome: precommitLifecycleFacts({
|
|
276
276
|
contractId: plan.contractId,
|
|
277
|
-
authoritativeHead: session.
|
|
277
|
+
authoritativeHead: session.pins.repositoryLedgerHead,
|
|
278
278
|
journal: session.journal,
|
|
279
279
|
cancellationObserved: session.cancellationObserved(),
|
|
280
280
|
}),
|
package/build/core/log.js
CHANGED
|
@@ -44,7 +44,7 @@ export async function readContractLog(input) {
|
|
|
44
44
|
at: entry.at,
|
|
45
45
|
actor: entry.actor,
|
|
46
46
|
})),
|
|
47
|
-
markdown: renderCurrentContractView(model),
|
|
47
|
+
markdown: renderCurrentContractView(model, read.observedLedgerHead),
|
|
48
48
|
...(warning ? { amendmentWarning: warning } : {}),
|
|
49
49
|
};
|
|
50
50
|
}
|
|
@@ -88,14 +88,14 @@ export class EffectJournal {
|
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
90
|
export function carrierLifecycleEffect(carrier) {
|
|
91
|
-
const idempotencyKey = `carrier:${carrier.
|
|
91
|
+
const idempotencyKey = `carrier:${carrier.contractHead}`;
|
|
92
92
|
if (carrier.status === "failed") {
|
|
93
93
|
return { effect: "carrier-projection", idempotencyKey, status: "failed", cause: { code: "CARRIER_MATERIALIZATION_FAILED", message: carrier.cause } };
|
|
94
94
|
}
|
|
95
95
|
if (carrier.status === "target-unavailable") {
|
|
96
96
|
return { effect: "carrier-projection", idempotencyKey, status: "failed", cause: { code: "CARRIER_TARGET_UNAVAILABLE", message: carrier.reason } };
|
|
97
97
|
}
|
|
98
|
-
return { effect: "carrier-projection", idempotencyKey, status: "succeeded", receipt: {
|
|
98
|
+
return { effect: "carrier-projection", idempotencyKey, status: "succeeded", receipt: { contractHead: carrier.contractHead, status: carrier.status } };
|
|
99
99
|
}
|
|
100
100
|
export function committedLifecycleFacts(input) {
|
|
101
101
|
return {
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
const WINDOWS_RESERVED_NAME = /^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\..*)?$/i;
|
|
3
|
+
const FORBIDDEN_CODE_POINT = /[\u0000-\u001f\u007f-\u009f\uD800-\uDFFF]/u;
|
|
4
|
+
const FORBIDDEN_PORTABLE_PATH_CHARACTER = /[\\/<>:"|?*]/u;
|
|
5
|
+
const MAX_AKUMA_NAME_BYTES = 255;
|
|
6
|
+
export function canonicalizeAkumaName(value) {
|
|
7
|
+
return value.normalize("NFC");
|
|
8
|
+
}
|
|
9
|
+
export function akumaNameProblem(value) {
|
|
10
|
+
if (value.length === 0)
|
|
11
|
+
return "must not be empty.";
|
|
12
|
+
if (value !== canonicalizeAkumaName(value))
|
|
13
|
+
return "must use Unicode NFC normalization.";
|
|
14
|
+
if (value === "." || value === ".." || value.startsWith(".")) {
|
|
15
|
+
return "must not be a dot path or hidden filesystem name.";
|
|
16
|
+
}
|
|
17
|
+
if (FORBIDDEN_CODE_POINT.test(value))
|
|
18
|
+
return "must not contain control characters or unpaired surrogates.";
|
|
19
|
+
if (/\p{White_Space}/u.test(value))
|
|
20
|
+
return "must not contain whitespace.";
|
|
21
|
+
if (FORBIDDEN_PORTABLE_PATH_CHARACTER.test(value))
|
|
22
|
+
return "must not contain portable filesystem separators or reserved punctuation.";
|
|
23
|
+
if (/[. ]$/u.test(value))
|
|
24
|
+
return "must not end with a dot or space.";
|
|
25
|
+
if (WINDOWS_RESERVED_NAME.test(value))
|
|
26
|
+
return "must not be a reserved Windows filename.";
|
|
27
|
+
if (Buffer.byteLength(value, "utf8") > MAX_AKUMA_NAME_BYTES) {
|
|
28
|
+
return `must contain at most ${MAX_AKUMA_NAME_BYTES} UTF-8 bytes.`;
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Akuma identity is exact NFC text. Case, variation selectors, ZWJ sequences,
|
|
34
|
+
* and visually confusable code points are intentionally not folded.
|
|
35
|
+
*/
|
|
36
|
+
export const akumaNameSchema = z.string().superRefine((value, ctx) => {
|
|
37
|
+
const problem = akumaNameProblem(value);
|
|
38
|
+
if (problem !== null)
|
|
39
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, message: problem });
|
|
40
|
+
});
|
|
41
|
+
export function isAkumaName(value) {
|
|
42
|
+
return akumaNameProblem(value) === null;
|
|
43
|
+
}
|
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { DatabaseSync } from "node:sqlite";
|
|
4
|
+
import { ProjectionStateError } from "../../atomic-publish.js";
|
|
4
5
|
import { configureHeartBusyTimeout } from "../heart.js";
|
|
5
6
|
import { ProjectionGenerationStoreError, parseStoredRow, validateHistory, } from "./model.js";
|
|
7
|
+
import { ProjectionGenerationLedger } from "./ledger.js";
|
|
6
8
|
export const PROJECTION_GENERATION_STORE_FILE = "heart";
|
|
7
|
-
export const PROJECTION_GENERATION_SCHEMA_VERSION =
|
|
8
|
-
const
|
|
9
|
+
export const PROJECTION_GENERATION_SCHEMA_VERSION = 6;
|
|
10
|
+
const GENERATION_SCHEMA_SQL = `
|
|
9
11
|
CREATE TABLE generation_schema (
|
|
10
12
|
singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
|
|
11
13
|
version INTEGER NOT NULL CHECK (version = ${PROJECTION_GENERATION_SCHEMA_VERSION})
|
|
12
14
|
) STRICT
|
|
13
15
|
`;
|
|
14
|
-
const
|
|
16
|
+
const GENERATION_RECORD_SQL = `
|
|
15
17
|
CREATE TABLE generation_records (
|
|
16
18
|
seq INTEGER PRIMARY KEY CHECK (seq > 0),
|
|
17
|
-
|
|
19
|
+
activity_kind TEXT NOT NULL CHECK (activity_kind IN ('launch', 'adoption', 'kill-intent', 'interrupt-intent', 'verdict')),
|
|
18
20
|
execution_id TEXT NOT NULL CHECK (length(trim(execution_id)) > 0),
|
|
19
21
|
facts_json TEXT NOT NULL CHECK (json_valid(facts_json))
|
|
20
22
|
) STRICT
|
|
@@ -92,6 +94,34 @@ const TELL_LAUNCH_COORDINATE_TABLE_SQL = `
|
|
|
92
94
|
) STRICT
|
|
93
95
|
`;
|
|
94
96
|
const closedDatabases = new WeakSet();
|
|
97
|
+
function projectionHeartPath(projectionDirectory) {
|
|
98
|
+
return path.join(projectionDirectory, PROJECTION_GENERATION_STORE_FILE);
|
|
99
|
+
}
|
|
100
|
+
function openHeartConnection(databasePath, readOnly, foreignKeys = false) {
|
|
101
|
+
const database = new DatabaseSync(databasePath, {
|
|
102
|
+
readOnly,
|
|
103
|
+
...(foreignKeys ? { enableForeignKeyConstraints: true } : {}),
|
|
104
|
+
enableDoubleQuotedStringLiterals: false,
|
|
105
|
+
allowExtension: false,
|
|
106
|
+
});
|
|
107
|
+
try {
|
|
108
|
+
configureHeartBusyTimeout(database);
|
|
109
|
+
if (!readOnly) {
|
|
110
|
+
database.exec("PRAGMA journal_mode = DELETE");
|
|
111
|
+
database.exec("PRAGMA synchronous = FULL");
|
|
112
|
+
database.exec("PRAGMA foreign_keys = ON");
|
|
113
|
+
database.exec("PRAGMA trusted_schema = OFF");
|
|
114
|
+
}
|
|
115
|
+
return database;
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
try {
|
|
119
|
+
database.close();
|
|
120
|
+
}
|
|
121
|
+
catch { /* preserve configuration failure */ }
|
|
122
|
+
throw error;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
95
125
|
/** Classify only proved missing schema evidence as historical incompatibility. */
|
|
96
126
|
export function projectionGenerationStoreInspectionError(databasePath, error) {
|
|
97
127
|
if (error instanceof ProjectionGenerationStoreError)
|
|
@@ -153,14 +183,16 @@ function validateSchemaShape(database) {
|
|
|
153
183
|
throw new ProjectionGenerationStoreError(`projection generation schema contains unexpected objects: ${signature || "none"}`, { observation: "incompatible" });
|
|
154
184
|
}
|
|
155
185
|
const metadata = database.prepare("SELECT singleton, version FROM generation_schema").all();
|
|
156
|
-
if (metadata.length !== 1
|
|
186
|
+
if (metadata.length !== 1
|
|
187
|
+
|| Number(metadata[0].singleton) !== 1
|
|
188
|
+
|| Number(metadata[0].version) !== PROJECTION_GENERATION_SCHEMA_VERSION) {
|
|
157
189
|
throw new ProjectionGenerationStoreError("projection generation schema metadata is malformed", { observation: "incompatible" });
|
|
158
190
|
}
|
|
159
|
-
const
|
|
160
|
-
const
|
|
191
|
+
const generationColumns = database.prepare("PRAGMA table_info(generation_records)").all();
|
|
192
|
+
const generationSignature = generationColumns
|
|
161
193
|
.map((column) => `${column.name}:${column.type}:${Number(column.notnull)}:${Number(column.pk)}`)
|
|
162
194
|
.join(",");
|
|
163
|
-
if (
|
|
195
|
+
if (generationSignature !== "seq:INTEGER:0:1,activity_kind:TEXT:1:0,execution_id:TEXT:1:0,facts_json:TEXT:1:0") {
|
|
164
196
|
throw new ProjectionGenerationStoreError("projection generation record schema is malformed", { observation: "incompatible" });
|
|
165
197
|
}
|
|
166
198
|
const identityColumns = database.prepare("PRAGMA table_info(projection_identity)").all();
|
|
@@ -228,8 +260,10 @@ function initializeOrValidateSchema(database) {
|
|
|
228
260
|
ORDER BY name
|
|
229
261
|
`).all();
|
|
230
262
|
if (version === 0 && objects.length === 0) {
|
|
231
|
-
database.exec(
|
|
232
|
-
database.exec(
|
|
263
|
+
database.exec(GENERATION_SCHEMA_SQL);
|
|
264
|
+
database.exec(GENERATION_RECORD_SQL);
|
|
265
|
+
database.prepare("INSERT INTO generation_schema (singleton, version) VALUES (1, ?)")
|
|
266
|
+
.run(PROJECTION_GENERATION_SCHEMA_VERSION);
|
|
233
267
|
database.exec(IDENTITY_TABLE_SQL);
|
|
234
268
|
database.exec(TELL_TABLE_SQL);
|
|
235
269
|
database.exec(TELL_RECEIPT_TABLE_SQL);
|
|
@@ -237,8 +271,6 @@ function initializeOrValidateSchema(database) {
|
|
|
237
271
|
database.exec(TELL_LAUNCH_BATCH_TABLE_SQL);
|
|
238
272
|
database.exec(TELL_LAUNCH_ADMISSION_TABLE_SQL);
|
|
239
273
|
database.exec(TELL_LAUNCH_COORDINATE_TABLE_SQL);
|
|
240
|
-
database.prepare("INSERT INTO generation_schema (singleton, version) VALUES (1, ?)")
|
|
241
|
-
.run(PROJECTION_GENERATION_SCHEMA_VERSION);
|
|
242
274
|
database.exec(`PRAGMA user_version = ${PROJECTION_GENERATION_SCHEMA_VERSION}`);
|
|
243
275
|
}
|
|
244
276
|
else if (version !== PROJECTION_GENERATION_SCHEMA_VERSION) {
|
|
@@ -256,22 +288,10 @@ export function openMutableDatabase(projectionDirectory) {
|
|
|
256
288
|
const stat = fs.statSync(projectionDirectory);
|
|
257
289
|
if (!stat.isDirectory())
|
|
258
290
|
throw new ProjectionGenerationStoreError("projection generation store parent must be a directory");
|
|
259
|
-
const databasePath =
|
|
291
|
+
const databasePath = projectionHeartPath(projectionDirectory);
|
|
260
292
|
let database;
|
|
261
293
|
try {
|
|
262
|
-
database =
|
|
263
|
-
enableForeignKeyConstraints: true,
|
|
264
|
-
enableDoubleQuotedStringLiterals: false,
|
|
265
|
-
allowExtension: false,
|
|
266
|
-
});
|
|
267
|
-
configureHeartBusyTimeout(database);
|
|
268
|
-
// Rollback journal keeps observers genuinely read-only. A read-only WAL
|
|
269
|
-
// connection creates/updates -wal and -shm coordination files, while
|
|
270
|
-
// immutable WAL reads can miss concurrently committed facts.
|
|
271
|
-
database.exec("PRAGMA journal_mode = DELETE");
|
|
272
|
-
database.exec("PRAGMA synchronous = FULL");
|
|
273
|
-
database.exec("PRAGMA foreign_keys = ON");
|
|
274
|
-
database.exec("PRAGMA trusted_schema = OFF");
|
|
294
|
+
database = openHeartConnection(databasePath, false, true);
|
|
275
295
|
}
|
|
276
296
|
catch (error) {
|
|
277
297
|
try {
|
|
@@ -282,7 +302,7 @@ export function openMutableDatabase(projectionDirectory) {
|
|
|
282
302
|
}
|
|
283
303
|
try {
|
|
284
304
|
initializeOrValidateSchema(database);
|
|
285
|
-
readValidatedHistory(database);
|
|
305
|
+
readValidatedHistory(database, false);
|
|
286
306
|
return { databasePath, database };
|
|
287
307
|
}
|
|
288
308
|
catch (error) {
|
|
@@ -296,7 +316,7 @@ export function openReadOnlyDatabase(projectionDirectory) {
|
|
|
296
316
|
const stat = fs.statSync(projectionDirectory);
|
|
297
317
|
if (!stat.isDirectory())
|
|
298
318
|
throw new ProjectionGenerationStoreError("projection generation store parent must be a directory");
|
|
299
|
-
const databasePath =
|
|
319
|
+
const databasePath = projectionHeartPath(projectionDirectory);
|
|
300
320
|
try {
|
|
301
321
|
if (!fs.lstatSync(databasePath).isFile()) {
|
|
302
322
|
throw new ProjectionGenerationStoreError("projection generation store must be a regular file", { observation: "incompatible" });
|
|
@@ -307,14 +327,9 @@ export function openReadOnlyDatabase(projectionDirectory) {
|
|
|
307
327
|
}
|
|
308
328
|
let database;
|
|
309
329
|
try {
|
|
310
|
-
database =
|
|
311
|
-
readOnly: true,
|
|
312
|
-
enableDoubleQuotedStringLiterals: false,
|
|
313
|
-
allowExtension: false,
|
|
314
|
-
});
|
|
315
|
-
configureHeartBusyTimeout(database);
|
|
330
|
+
database = openHeartConnection(databasePath, true);
|
|
316
331
|
validateSchemaShape(database);
|
|
317
|
-
readValidatedHistory(database);
|
|
332
|
+
readValidatedHistory(database, true);
|
|
318
333
|
return { databasePath, database };
|
|
319
334
|
}
|
|
320
335
|
catch (error) {
|
|
@@ -325,13 +340,14 @@ export function openReadOnlyDatabase(projectionDirectory) {
|
|
|
325
340
|
throw projectionGenerationStoreReadError(databasePath, error);
|
|
326
341
|
}
|
|
327
342
|
}
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
const { database } = openReadOnlyDatabase(projectionDirectory);
|
|
343
|
+
function withProjectionHeartOperation(open, mode, operation) {
|
|
344
|
+
const database = open();
|
|
331
345
|
try {
|
|
332
|
-
|
|
346
|
+
if (mode === "read")
|
|
347
|
+
return operation(database);
|
|
348
|
+
database.exec(mode === "snapshot" ? "BEGIN" : "BEGIN IMMEDIATE");
|
|
333
349
|
try {
|
|
334
|
-
const result =
|
|
350
|
+
const result = operation(database);
|
|
335
351
|
database.exec("COMMIT");
|
|
336
352
|
return result;
|
|
337
353
|
}
|
|
@@ -344,64 +360,110 @@ export function withGenerationReadSnapshot(projectionDirectory, read) {
|
|
|
344
360
|
closeDatabase(database);
|
|
345
361
|
}
|
|
346
362
|
}
|
|
363
|
+
/** Read the validated heart through one consistent SQLite snapshot. */
|
|
364
|
+
export function withGenerationReadSnapshot(projectionDirectory, read) {
|
|
365
|
+
return withProjectionHeartOperation(() => openReadOnlyDatabase(projectionDirectory).database, "snapshot", read);
|
|
366
|
+
}
|
|
367
|
+
function openProjectionHeartDatabase(projectionDirectory, readOnly) {
|
|
368
|
+
const databasePath = projectionHeartPath(projectionDirectory);
|
|
369
|
+
if (!fs.lstatSync(databasePath).isFile()) {
|
|
370
|
+
throw new ProjectionStateError("projection heart must be a regular file");
|
|
371
|
+
}
|
|
372
|
+
return openHeartConnection(databasePath, readOnly, true);
|
|
373
|
+
}
|
|
374
|
+
export function withProjectionHeartRead(projectionDirectory, read) {
|
|
375
|
+
return withProjectionHeartOperation(() => openProjectionHeartDatabase(projectionDirectory, true), "read", read);
|
|
376
|
+
}
|
|
377
|
+
export function withProjectionHeartReadSnapshot(projectionDirectory, read) {
|
|
378
|
+
return withProjectionHeartOperation(() => openProjectionHeartDatabase(projectionDirectory, true), "snapshot", read);
|
|
379
|
+
}
|
|
380
|
+
export function withProjectionHeartWrite(projectionDirectory, write) {
|
|
381
|
+
return withProjectionHeartOperation(() => openProjectionHeartDatabase(projectionDirectory, false), "write", write);
|
|
382
|
+
}
|
|
383
|
+
export function openProjectionTellObserver(projectionDirectory) {
|
|
384
|
+
const database = openProjectionHeartDatabase(projectionDirectory, true);
|
|
385
|
+
let closed = false;
|
|
386
|
+
let observedDataVersion;
|
|
387
|
+
const assertOpen = () => {
|
|
388
|
+
if (closed)
|
|
389
|
+
throw new ProjectionStateError("projection tell observer is closed");
|
|
390
|
+
};
|
|
391
|
+
const readDataVersion = () => {
|
|
392
|
+
assertOpen();
|
|
393
|
+
const row = database.prepare("PRAGMA data_version").get();
|
|
394
|
+
const raw = row?.data_version;
|
|
395
|
+
const value = typeof raw === "bigint" ? Number(raw) : raw;
|
|
396
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
|
|
397
|
+
throw new ProjectionStateError("PRAGMA data_version returned an invalid value");
|
|
398
|
+
}
|
|
399
|
+
return value;
|
|
400
|
+
};
|
|
401
|
+
return {
|
|
402
|
+
hasDataVersionChanged() {
|
|
403
|
+
const current = readDataVersion();
|
|
404
|
+
if (current === observedDataVersion)
|
|
405
|
+
return false;
|
|
406
|
+
observedDataVersion = current;
|
|
407
|
+
return true;
|
|
408
|
+
},
|
|
409
|
+
hasInbox() {
|
|
410
|
+
assertOpen();
|
|
411
|
+
return database.prepare(`
|
|
412
|
+
SELECT 1
|
|
413
|
+
FROM projection_tells
|
|
414
|
+
WHERE state = 'inbox'
|
|
415
|
+
LIMIT 1
|
|
416
|
+
`).get() !== undefined;
|
|
417
|
+
},
|
|
418
|
+
close() {
|
|
419
|
+
if (closed)
|
|
420
|
+
return;
|
|
421
|
+
closed = true;
|
|
422
|
+
closeDatabase(database);
|
|
423
|
+
},
|
|
424
|
+
};
|
|
425
|
+
}
|
|
347
426
|
export function assertDatabaseOpen(database) {
|
|
348
427
|
if (closedDatabases.has(database))
|
|
349
428
|
throw new ProjectionGenerationStoreError("projection generation store is closed");
|
|
350
429
|
}
|
|
351
|
-
export function
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
430
|
+
export function connectionBoundGenerationStore(database, readOnly) {
|
|
431
|
+
return new ProjectionGenerationLedger(database, readOnly);
|
|
432
|
+
}
|
|
433
|
+
export function readValidatedHistory(database, readOnly) {
|
|
434
|
+
const records = connectionBoundGenerationStore(database, readOnly).readRecords().map((record) => parseStoredRow({
|
|
435
|
+
seq: record.seq,
|
|
436
|
+
kind: record.kind,
|
|
437
|
+
execution_id: record.executionId,
|
|
438
|
+
facts_json: JSON.stringify(record.facts),
|
|
439
|
+
}));
|
|
358
440
|
return { records, current: validateHistory(records) };
|
|
359
441
|
}
|
|
360
442
|
export function appendRecord(database, kind, executionId, facts, seq) {
|
|
361
|
-
const
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
443
|
+
const record = connectionBoundGenerationStore(database, false)
|
|
444
|
+
.appendRecord(kind, executionId, facts, seq);
|
|
445
|
+
return {
|
|
446
|
+
seq: record.seq,
|
|
447
|
+
kind: record.kind,
|
|
448
|
+
executionId: record.executionId,
|
|
449
|
+
facts: record.facts,
|
|
450
|
+
};
|
|
367
451
|
}
|
|
368
452
|
export function writeTransition(database, readOnly, run) {
|
|
369
453
|
assertDatabaseOpen(database);
|
|
370
|
-
|
|
371
|
-
throw new ProjectionGenerationStoreError("projection generation store is read-only");
|
|
372
|
-
}
|
|
373
|
-
if (database.isTransaction)
|
|
374
|
-
return run();
|
|
375
|
-
database.exec("BEGIN IMMEDIATE");
|
|
376
|
-
try {
|
|
377
|
-
const result = run();
|
|
378
|
-
database.exec("COMMIT");
|
|
379
|
-
return result;
|
|
380
|
-
}
|
|
381
|
-
catch (error) {
|
|
382
|
-
rollbackQuietly(database);
|
|
383
|
-
throw error;
|
|
384
|
-
}
|
|
454
|
+
return connectionBoundGenerationStore(database, readOnly).write(run);
|
|
385
455
|
}
|
|
386
456
|
export function beginGenerationWriteTransaction(database, readOnly) {
|
|
387
457
|
assertDatabaseOpen(database);
|
|
388
|
-
|
|
389
|
-
throw new ProjectionGenerationStoreError("projection generation store is read-only");
|
|
390
|
-
if (database.isTransaction)
|
|
391
|
-
throw new ProjectionGenerationStoreError("projection generation write transaction is already open");
|
|
392
|
-
database.exec("BEGIN IMMEDIATE");
|
|
458
|
+
connectionBoundGenerationStore(database, readOnly).beginWriteTransaction();
|
|
393
459
|
}
|
|
394
460
|
export function commitGenerationWriteTransaction(database) {
|
|
395
461
|
assertDatabaseOpen(database);
|
|
396
|
-
|
|
397
|
-
throw new ProjectionGenerationStoreError("projection generation write transaction is not open");
|
|
398
|
-
database.exec("COMMIT");
|
|
462
|
+
connectionBoundGenerationStore(database, false).commitWriteTransaction();
|
|
399
463
|
}
|
|
400
464
|
export function rollbackGenerationWriteTransaction(database) {
|
|
401
465
|
assertDatabaseOpen(database);
|
|
402
|
-
|
|
403
|
-
return;
|
|
404
|
-
rollbackQuietly(database);
|
|
466
|
+
connectionBoundGenerationStore(database, false).rollbackWriteTransaction();
|
|
405
467
|
}
|
|
406
468
|
export function closeDatabase(database) {
|
|
407
469
|
if (closedDatabases.has(database))
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { assertExecutionId, assertPositiveSafeInteger, cloneFacts, ProjectionGenerationStoreError, } from "./model.js";
|
|
2
|
+
const RECORD_KINDS = new Set([
|
|
3
|
+
"launch", "adoption", "kill-intent", "interrupt-intent", "verdict",
|
|
4
|
+
]);
|
|
5
|
+
/** The projection-owned persistence adapter for generation facts in heart. */
|
|
6
|
+
export class ProjectionGenerationLedger {
|
|
7
|
+
#database;
|
|
8
|
+
#readOnly;
|
|
9
|
+
constructor(database, readOnly) {
|
|
10
|
+
this.#database = database;
|
|
11
|
+
this.#readOnly = readOnly;
|
|
12
|
+
}
|
|
13
|
+
readRecords() {
|
|
14
|
+
const rows = this.#database.prepare(`
|
|
15
|
+
SELECT seq, activity_kind, execution_id, facts_json
|
|
16
|
+
FROM generation_records
|
|
17
|
+
ORDER BY seq
|
|
18
|
+
`).all();
|
|
19
|
+
return rows.map((row) => {
|
|
20
|
+
const seq = assertPositiveSafeInteger(Number(row.seq), "generation row seq");
|
|
21
|
+
if (typeof row.activity_kind !== "string" || !RECORD_KINDS.has(row.activity_kind)) {
|
|
22
|
+
throw new ProjectionGenerationStoreError(`generation row ${seq} has unknown kind '${String(row.activity_kind)}'`);
|
|
23
|
+
}
|
|
24
|
+
if (typeof row.execution_id !== "string")
|
|
25
|
+
throw new ProjectionGenerationStoreError(`generation row ${seq} has invalid execution_id`);
|
|
26
|
+
const executionId = assertExecutionId(row.execution_id);
|
|
27
|
+
if (typeof row.facts_json !== "string")
|
|
28
|
+
throw new ProjectionGenerationStoreError(`generation row ${seq} facts_json must be text`);
|
|
29
|
+
let facts;
|
|
30
|
+
try {
|
|
31
|
+
facts = cloneFacts(JSON.parse(row.facts_json), `generation row ${seq} facts`);
|
|
32
|
+
}
|
|
33
|
+
catch (cause) {
|
|
34
|
+
if (cause instanceof Error && cause.message.startsWith(`generation row ${seq}`))
|
|
35
|
+
throw cause;
|
|
36
|
+
throw new ProjectionGenerationStoreError(`generation row ${seq} facts_json is invalid JSON`, { cause });
|
|
37
|
+
}
|
|
38
|
+
return { seq, kind: row.activity_kind, executionId, facts };
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
appendRecord(kind, executionId, facts, seq) {
|
|
42
|
+
if (this.#readOnly)
|
|
43
|
+
throw new ProjectionGenerationStoreError("projection generation store is read-only");
|
|
44
|
+
const encoded = JSON.stringify(facts);
|
|
45
|
+
this.#database.prepare(`
|
|
46
|
+
INSERT INTO generation_records (seq, activity_kind, execution_id, facts_json)
|
|
47
|
+
VALUES (?, ?, ?, ?)
|
|
48
|
+
`).run(seq, kind, executionId, encoded);
|
|
49
|
+
return { seq, kind, executionId, facts: JSON.parse(encoded) };
|
|
50
|
+
}
|
|
51
|
+
write(run) {
|
|
52
|
+
if (this.#readOnly)
|
|
53
|
+
throw new ProjectionGenerationStoreError("projection generation store is read-only");
|
|
54
|
+
if (this.#database.isTransaction)
|
|
55
|
+
return run();
|
|
56
|
+
this.#database.exec("BEGIN IMMEDIATE");
|
|
57
|
+
try {
|
|
58
|
+
const result = run();
|
|
59
|
+
this.#database.exec("COMMIT");
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
this.rollback();
|
|
64
|
+
throw error;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
beginWriteTransaction() {
|
|
68
|
+
if (this.#readOnly)
|
|
69
|
+
throw new ProjectionGenerationStoreError("projection generation store is read-only");
|
|
70
|
+
if (this.#database.isTransaction)
|
|
71
|
+
throw new ProjectionGenerationStoreError("projection generation write transaction is already open");
|
|
72
|
+
this.#database.exec("BEGIN IMMEDIATE");
|
|
73
|
+
}
|
|
74
|
+
commitWriteTransaction() {
|
|
75
|
+
if (!this.#database.isTransaction)
|
|
76
|
+
throw new ProjectionGenerationStoreError("projection generation write transaction is not open");
|
|
77
|
+
this.#database.exec("COMMIT");
|
|
78
|
+
}
|
|
79
|
+
rollbackWriteTransaction() {
|
|
80
|
+
if (this.#database.isTransaction)
|
|
81
|
+
this.rollback();
|
|
82
|
+
}
|
|
83
|
+
rollback() {
|
|
84
|
+
try {
|
|
85
|
+
this.#database.exec("ROLLBACK");
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
// Preserve the failure that caused rollback.
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|