@smthrs/engine-store 0.0.0-stage → 1.0.0-rc.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +247 -0
- package/LICENSE +21 -0
- package/README.md +146 -2
- package/dist/cjs/ArtifactGc.d.ts +235 -0
- package/dist/cjs/ArtifactGc.d.ts.map +1 -0
- package/dist/cjs/ArtifactGc.js +183 -0
- package/dist/cjs/ArtifactGc.js.map +7 -0
- package/dist/cjs/ArtifactSync.d.ts +164 -0
- package/dist/cjs/ArtifactSync.d.ts.map +1 -0
- package/dist/cjs/ArtifactSync.js +144 -0
- package/dist/cjs/ArtifactSync.js.map +7 -0
- package/dist/cjs/CacheSync.d.ts +112 -0
- package/dist/cjs/CacheSync.d.ts.map +1 -0
- package/dist/cjs/CacheSync.js +64 -0
- package/dist/cjs/CacheSync.js.map +7 -0
- package/dist/cjs/DeferredClockFold.d.ts +354 -0
- package/dist/cjs/DeferredClockFold.d.ts.map +1 -0
- package/dist/cjs/DeferredClockFold.js +335 -0
- package/dist/cjs/DeferredClockFold.js.map +7 -0
- package/dist/cjs/DisasterRecovery.d.ts +431 -0
- package/dist/cjs/DisasterRecovery.d.ts.map +1 -0
- package/dist/cjs/DisasterRecovery.js +453 -0
- package/dist/cjs/DisasterRecovery.js.map +7 -0
- package/dist/cjs/DurableEngineState.d.ts +776 -0
- package/dist/cjs/DurableEngineState.d.ts.map +1 -0
- package/dist/cjs/DurableEngineState.js +1536 -0
- package/dist/cjs/DurableEngineState.js.map +7 -0
- package/dist/cjs/EngineStore.d.ts +158 -0
- package/dist/cjs/EngineStore.d.ts.map +1 -0
- package/dist/cjs/EngineStore.js +398 -0
- package/dist/cjs/EngineStore.js.map +7 -0
- package/dist/cjs/EngineStoreMetrics.d.ts +276 -0
- package/dist/cjs/EngineStoreMetrics.d.ts.map +1 -0
- package/dist/cjs/EngineStoreMetrics.js +135 -0
- package/dist/cjs/EngineStoreMetrics.js.map +7 -0
- package/dist/cjs/Errors.d.ts +22 -0
- package/dist/cjs/Errors.d.ts.map +1 -0
- package/dist/cjs/Errors.js +39 -0
- package/dist/cjs/Errors.js.map +7 -0
- package/dist/cjs/EventTypes.d.ts +27 -0
- package/dist/cjs/EventTypes.d.ts.map +1 -0
- package/dist/cjs/EventTypes.js +37 -0
- package/dist/cjs/EventTypes.js.map +7 -0
- package/dist/cjs/ExecutionFacts.d.ts +59 -0
- package/dist/cjs/ExecutionFacts.d.ts.map +1 -0
- package/dist/cjs/ExecutionFacts.js +123 -0
- package/dist/cjs/ExecutionFacts.js.map +7 -0
- package/dist/cjs/ExecutionSnapshot.d.ts +117 -0
- package/dist/cjs/ExecutionSnapshot.d.ts.map +1 -0
- package/dist/cjs/ExecutionSnapshot.js +111 -0
- package/dist/cjs/ExecutionSnapshot.js.map +7 -0
- package/dist/cjs/Inconsistency.d.ts +154 -0
- package/dist/cjs/Inconsistency.d.ts.map +1 -0
- package/dist/cjs/Inconsistency.js +135 -0
- package/dist/cjs/Inconsistency.js.map +7 -0
- package/dist/cjs/Migrations.d.ts +56 -0
- package/dist/cjs/Migrations.d.ts.map +1 -0
- package/dist/cjs/Migrations.js +78 -0
- package/dist/cjs/Migrations.js.map +7 -0
- package/dist/cjs/OwnerIdentity.d.ts +91 -0
- package/dist/cjs/OwnerIdentity.d.ts.map +1 -0
- package/dist/cjs/OwnerIdentity.js +65 -0
- package/dist/cjs/OwnerIdentity.js.map +7 -0
- package/dist/cjs/PlanInputStore.d.ts +110 -0
- package/dist/cjs/PlanInputStore.d.ts.map +1 -0
- package/dist/cjs/PlanInputStore.js +224 -0
- package/dist/cjs/PlanInputStore.js.map +7 -0
- package/dist/cjs/PlanMergeStore.d.ts +113 -0
- package/dist/cjs/PlanMergeStore.d.ts.map +1 -0
- package/dist/cjs/PlanMergeStore.js +255 -0
- package/dist/cjs/PlanMergeStore.js.map +7 -0
- package/dist/cjs/PlanScheduler.d.ts +363 -0
- package/dist/cjs/PlanScheduler.d.ts.map +1 -0
- package/dist/cjs/PlanScheduler.js +1090 -0
- package/dist/cjs/PlanScheduler.js.map +7 -0
- package/dist/cjs/Reconciliation.d.ts +175 -0
- package/dist/cjs/Reconciliation.d.ts.map +1 -0
- package/dist/cjs/Reconciliation.js +119 -0
- package/dist/cjs/Reconciliation.js.map +7 -0
- package/dist/cjs/ReplayOnly.d.ts +91 -0
- package/dist/cjs/ReplayOnly.d.ts.map +1 -0
- package/dist/cjs/ReplayOnly.js +52 -0
- package/dist/cjs/ReplayOnly.js.map +7 -0
- package/dist/cjs/Retention.d.ts +113 -0
- package/dist/cjs/Retention.d.ts.map +1 -0
- package/dist/cjs/Retention.js +99 -0
- package/dist/cjs/Retention.js.map +7 -0
- package/dist/cjs/RunCatalogRead.d.ts +17 -0
- package/dist/cjs/RunCatalogRead.d.ts.map +1 -0
- package/dist/cjs/RunCatalogRead.js +33 -0
- package/dist/cjs/RunCatalogRead.js.map +7 -0
- package/dist/cjs/RunChangeFeed.d.ts +75 -0
- package/dist/cjs/RunChangeFeed.d.ts.map +1 -0
- package/dist/cjs/RunChangeFeed.js +96 -0
- package/dist/cjs/RunChangeFeed.js.map +7 -0
- package/dist/cjs/RunState.d.ts +116 -0
- package/dist/cjs/RunState.d.ts.map +1 -0
- package/dist/cjs/RunState.js +76 -0
- package/dist/cjs/RunState.js.map +7 -0
- package/dist/cjs/Selection.d.ts +495 -0
- package/dist/cjs/Selection.d.ts.map +1 -0
- package/dist/cjs/Selection.js +288 -0
- package/dist/cjs/Selection.js.map +7 -0
- package/dist/cjs/SelectionStore.d.ts +141 -0
- package/dist/cjs/SelectionStore.d.ts.map +1 -0
- package/dist/cjs/SelectionStore.js +229 -0
- package/dist/cjs/SelectionStore.js.map +7 -0
- package/dist/cjs/StepBoundary.d.ts +501 -0
- package/dist/cjs/StepBoundary.d.ts.map +1 -0
- package/dist/cjs/StepBoundary.js +619 -0
- package/dist/cjs/StepBoundary.js.map +7 -0
- package/dist/cjs/StepCacheFold.d.ts +148 -0
- package/dist/cjs/StepCacheFold.d.ts.map +1 -0
- package/dist/cjs/StepCacheFold.js +131 -0
- package/dist/cjs/StepCacheFold.js.map +7 -0
- package/dist/cjs/StepSandbox.d.ts +78 -0
- package/dist/cjs/StepSandbox.d.ts.map +1 -0
- package/dist/cjs/StepSandbox.js +75 -0
- package/dist/cjs/StepSandbox.js.map +7 -0
- package/dist/cjs/WakeBus.d.ts +116 -0
- package/dist/cjs/WakeBus.d.ts.map +1 -0
- package/dist/cjs/WakeBus.js +89 -0
- package/dist/cjs/WakeBus.js.map +7 -0
- package/dist/cjs/WorkspaceSandbox.d.ts +619 -0
- package/dist/cjs/WorkspaceSandbox.d.ts.map +1 -0
- package/dist/cjs/WorkspaceSandbox.js +711 -0
- package/dist/cjs/WorkspaceSandbox.js.map +7 -0
- package/dist/cjs/index.d.ts +157 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +95 -0
- package/dist/cjs/index.js.map +7 -0
- package/dist/cjs/internal/ActionPersistence.d.ts +276 -0
- package/dist/cjs/internal/ActionPersistence.d.ts.map +1 -0
- package/dist/cjs/internal/ActionPersistence.js +1395 -0
- package/dist/cjs/internal/ActionPersistence.js.map +7 -0
- package/dist/cjs/internal/ArtifactRoots.d.ts +38 -0
- package/dist/cjs/internal/ArtifactRoots.d.ts.map +1 -0
- package/dist/cjs/internal/ArtifactRoots.js +89 -0
- package/dist/cjs/internal/ArtifactRoots.js.map +7 -0
- package/dist/cjs/internal/AttemptAdmission.d.ts +53 -0
- package/dist/cjs/internal/AttemptAdmission.d.ts.map +1 -0
- package/dist/cjs/internal/AttemptAdmission.js +52 -0
- package/dist/cjs/internal/AttemptAdmission.js.map +7 -0
- package/dist/cjs/internal/AttemptLifecycle.d.ts +245 -0
- package/dist/cjs/internal/AttemptLifecycle.d.ts.map +1 -0
- package/dist/cjs/internal/AttemptLifecycle.js +112 -0
- package/dist/cjs/internal/AttemptLifecycle.js.map +7 -0
- package/dist/cjs/internal/AttemptProbe.d.ts +53 -0
- package/dist/cjs/internal/AttemptProbe.d.ts.map +1 -0
- package/dist/cjs/internal/AttemptProbe.js +66 -0
- package/dist/cjs/internal/AttemptProbe.js.map +7 -0
- package/dist/cjs/internal/CacheAdmission.d.ts +79 -0
- package/dist/cjs/internal/CacheAdmission.d.ts.map +1 -0
- package/dist/cjs/internal/CacheAdmission.js +71 -0
- package/dist/cjs/internal/CacheAdmission.js.map +7 -0
- package/dist/cjs/internal/CacheAgeHistory.d.ts +27 -0
- package/dist/cjs/internal/CacheAgeHistory.d.ts.map +1 -0
- package/dist/cjs/internal/CacheAgeHistory.js +61 -0
- package/dist/cjs/internal/CacheAgeHistory.js.map +7 -0
- package/dist/cjs/internal/CacheAgeVerdicts.d.ts +15 -0
- package/dist/cjs/internal/CacheAgeVerdicts.d.ts.map +1 -0
- package/dist/cjs/internal/CacheAgeVerdicts.js +73 -0
- package/dist/cjs/internal/CacheAgeVerdicts.js.map +7 -0
- package/dist/cjs/internal/CacheOutputPolicy.d.ts +24 -0
- package/dist/cjs/internal/CacheOutputPolicy.d.ts.map +1 -0
- package/dist/cjs/internal/CacheOutputPolicy.js +75 -0
- package/dist/cjs/internal/CacheOutputPolicy.js.map +7 -0
- package/dist/cjs/internal/CachePublication.d.ts +97 -0
- package/dist/cjs/internal/CachePublication.d.ts.map +1 -0
- package/dist/cjs/internal/CachePublication.js +80 -0
- package/dist/cjs/internal/CachePublication.js.map +7 -0
- package/dist/cjs/internal/CallFacts.d.ts +15 -0
- package/dist/cjs/internal/CallFacts.d.ts.map +1 -0
- package/dist/cjs/internal/CallFacts.js +97 -0
- package/dist/cjs/internal/CallFacts.js.map +7 -0
- package/dist/cjs/internal/CopiedRecord.d.ts +61 -0
- package/dist/cjs/internal/CopiedRecord.d.ts.map +1 -0
- package/dist/cjs/internal/CopiedRecord.js +134 -0
- package/dist/cjs/internal/CopiedRecord.js.map +7 -0
- package/dist/cjs/internal/DeferredPersistence.d.ts +108 -0
- package/dist/cjs/internal/DeferredPersistence.d.ts.map +1 -0
- package/dist/cjs/internal/DeferredPersistence.js +270 -0
- package/dist/cjs/internal/DeferredPersistence.js.map +7 -0
- package/dist/cjs/internal/EffectRecords.d.ts +92 -0
- package/dist/cjs/internal/EffectRecords.d.ts.map +1 -0
- package/dist/cjs/internal/EffectRecords.js +63 -0
- package/dist/cjs/internal/EffectRecords.js.map +7 -0
- package/dist/cjs/internal/EngineJj.d.ts +30 -0
- package/dist/cjs/internal/EngineJj.d.ts.map +1 -0
- package/dist/cjs/internal/EngineJj.js +42 -0
- package/dist/cjs/internal/EngineJj.js.map +7 -0
- package/dist/cjs/internal/EngineStateSchema.d.ts +68 -0
- package/dist/cjs/internal/EngineStateSchema.d.ts.map +1 -0
- package/dist/cjs/internal/EngineStateSchema.js +58 -0
- package/dist/cjs/internal/EngineStateSchema.js.map +7 -0
- package/dist/cjs/internal/ExecutionSnapshotRead.d.ts +162 -0
- package/dist/cjs/internal/ExecutionSnapshotRead.d.ts.map +1 -0
- package/dist/cjs/internal/ExecutionSnapshotRead.js +201 -0
- package/dist/cjs/internal/ExecutionSnapshotRead.js.map +7 -0
- package/dist/cjs/internal/ExecutionSnapshotRelations.d.ts +39 -0
- package/dist/cjs/internal/ExecutionSnapshotRelations.d.ts.map +1 -0
- package/dist/cjs/internal/ExecutionSnapshotRelations.js +105 -0
- package/dist/cjs/internal/ExecutionSnapshotRelations.js.map +7 -0
- package/dist/cjs/internal/ExitEncoding.d.ts +180 -0
- package/dist/cjs/internal/ExitEncoding.d.ts.map +1 -0
- package/dist/cjs/internal/ExitEncoding.js +236 -0
- package/dist/cjs/internal/ExitEncoding.js.map +7 -0
- package/dist/cjs/internal/FileBoundarySnapshot.d.ts +14 -0
- package/dist/cjs/internal/FileBoundarySnapshot.d.ts.map +1 -0
- package/dist/cjs/internal/FileBoundarySnapshot.js +51 -0
- package/dist/cjs/internal/FileBoundarySnapshot.js.map +7 -0
- package/dist/cjs/internal/FileEnumeration.d.ts +127 -0
- package/dist/cjs/internal/FileEnumeration.d.ts.map +1 -0
- package/dist/cjs/internal/FileEnumeration.js +194 -0
- package/dist/cjs/internal/FileEnumeration.js.map +7 -0
- package/dist/cjs/internal/HostReflection.d.ts +80 -0
- package/dist/cjs/internal/HostReflection.d.ts.map +1 -0
- package/dist/cjs/internal/HostReflection.js +43 -0
- package/dist/cjs/internal/HostReflection.js.map +7 -0
- package/dist/cjs/internal/JournalRecords.d.ts +325 -0
- package/dist/cjs/internal/JournalRecords.d.ts.map +1 -0
- package/dist/cjs/internal/JournalRecords.js +122 -0
- package/dist/cjs/internal/JournalRecords.js.map +7 -0
- package/dist/cjs/internal/NodeJournal.d.ts +157 -0
- package/dist/cjs/internal/NodeJournal.d.ts.map +1 -0
- package/dist/cjs/internal/NodeJournal.js +192 -0
- package/dist/cjs/internal/NodeJournal.js.map +7 -0
- package/dist/cjs/internal/Ordering.d.ts +14 -0
- package/dist/cjs/internal/Ordering.d.ts.map +1 -0
- package/dist/cjs/internal/Ordering.js +25 -0
- package/dist/cjs/internal/Ordering.js.map +7 -0
- package/dist/cjs/internal/PlanInputs.d.ts +14 -0
- package/dist/cjs/internal/PlanInputs.d.ts.map +1 -0
- package/dist/cjs/internal/PlanInputs.js +78 -0
- package/dist/cjs/internal/PlanInputs.js.map +7 -0
- package/dist/cjs/internal/PlanMerges.d.ts +178 -0
- package/dist/cjs/internal/PlanMerges.d.ts.map +1 -0
- package/dist/cjs/internal/PlanMerges.js +125 -0
- package/dist/cjs/internal/PlanMerges.js.map +7 -0
- package/dist/cjs/internal/ProvenanceSlot.d.ts +30 -0
- package/dist/cjs/internal/ProvenanceSlot.d.ts.map +1 -0
- package/dist/cjs/internal/ProvenanceSlot.js +53 -0
- package/dist/cjs/internal/ProvenanceSlot.js.map +7 -0
- package/dist/cjs/internal/ResultEnvelope.d.ts +88 -0
- package/dist/cjs/internal/ResultEnvelope.d.ts.map +1 -0
- package/dist/cjs/internal/ResultEnvelope.js +90 -0
- package/dist/cjs/internal/ResultEnvelope.js.map +7 -0
- package/dist/cjs/internal/RetentionOps.d.ts +414 -0
- package/dist/cjs/internal/RetentionOps.d.ts.map +1 -0
- package/dist/cjs/internal/RetentionOps.js +354 -0
- package/dist/cjs/internal/RetentionOps.js.map +7 -0
- package/dist/cjs/internal/RunCatalogOps.d.ts +118 -0
- package/dist/cjs/internal/RunCatalogOps.d.ts.map +1 -0
- package/dist/cjs/internal/RunCatalogOps.js +85 -0
- package/dist/cjs/internal/RunCatalogOps.js.map +7 -0
- package/dist/cjs/internal/RunCoordinator.d.ts +78 -0
- package/dist/cjs/internal/RunCoordinator.d.ts.map +1 -0
- package/dist/cjs/internal/RunCoordinator.js +123 -0
- package/dist/cjs/internal/RunCoordinator.js.map +7 -0
- package/dist/cjs/internal/RunDriver.d.ts +192 -0
- package/dist/cjs/internal/RunDriver.d.ts.map +1 -0
- package/dist/cjs/internal/RunDriver.js +1586 -0
- package/dist/cjs/internal/RunDriver.js.map +7 -0
- package/dist/cjs/internal/RunLifecycle.d.ts +22 -0
- package/dist/cjs/internal/RunLifecycle.d.ts.map +1 -0
- package/dist/cjs/internal/RunLifecycle.js +63 -0
- package/dist/cjs/internal/RunLifecycle.js.map +7 -0
- package/dist/cjs/internal/RunListing.d.ts +78 -0
- package/dist/cjs/internal/RunListing.d.ts.map +1 -0
- package/dist/cjs/internal/RunListing.js +171 -0
- package/dist/cjs/internal/RunListing.js.map +7 -0
- package/dist/cjs/internal/RunListingError.d.ts +35 -0
- package/dist/cjs/internal/RunListingError.d.ts.map +1 -0
- package/dist/cjs/internal/RunListingError.js +47 -0
- package/dist/cjs/internal/RunListingError.js.map +7 -0
- package/dist/cjs/internal/RuntimeGraph.d.ts +73 -0
- package/dist/cjs/internal/RuntimeGraph.d.ts.map +1 -0
- package/dist/cjs/internal/RuntimeGraph.js +180 -0
- package/dist/cjs/internal/RuntimeGraph.js.map +7 -0
- package/dist/cjs/internal/SandboxedExecution.d.ts +80 -0
- package/dist/cjs/internal/SandboxedExecution.d.ts.map +1 -0
- package/dist/cjs/internal/SandboxedExecution.js +104 -0
- package/dist/cjs/internal/SandboxedExecution.js.map +7 -0
- package/dist/cjs/internal/StateTransaction.d.ts +44 -0
- package/dist/cjs/internal/StateTransaction.d.ts.map +1 -0
- package/dist/cjs/internal/StateTransaction.js +52 -0
- package/dist/cjs/internal/StateTransaction.js.map +7 -0
- package/dist/cjs/internal/StepFacts.d.ts +14 -0
- package/dist/cjs/internal/StepFacts.d.ts.map +1 -0
- package/dist/cjs/internal/StepFacts.js +58 -0
- package/dist/cjs/internal/StepFacts.js.map +7 -0
- package/dist/cjs/internal/TypedEvents.d.ts +14 -0
- package/dist/cjs/internal/TypedEvents.d.ts.map +1 -0
- package/dist/cjs/internal/TypedEvents.js +37 -0
- package/dist/cjs/internal/TypedEvents.js.map +7 -0
- package/dist/cjs/migrations/0001_initial.d.ts +17 -0
- package/dist/cjs/migrations/0001_initial.d.ts.map +1 -0
- package/dist/cjs/migrations/0001_initial.js +61 -0
- package/dist/cjs/migrations/0001_initial.js.map +7 -0
- package/dist/cjs/migrations/0002_selection_store.d.ts +15 -0
- package/dist/cjs/migrations/0002_selection_store.d.ts.map +1 -0
- package/dist/cjs/migrations/0002_selection_store.js +56 -0
- package/dist/cjs/migrations/0002_selection_store.js.map +7 -0
- package/dist/cjs/migrations/0003_plan_inputs.d.ts +13 -0
- package/dist/cjs/migrations/0003_plan_inputs.d.ts.map +1 -0
- package/dist/cjs/migrations/0003_plan_inputs.js +90 -0
- package/dist/cjs/migrations/0003_plan_inputs.js.map +7 -0
- package/dist/cjs/migrations/0004_plan_environment.d.ts +14 -0
- package/dist/cjs/migrations/0004_plan_environment.d.ts.map +1 -0
- package/dist/cjs/migrations/0004_plan_environment.js +56 -0
- package/dist/cjs/migrations/0004_plan_environment.js.map +7 -0
- package/dist/cjs/migrations/0005_plan_merges.d.ts +14 -0
- package/dist/cjs/migrations/0005_plan_merges.d.ts.map +1 -0
- package/dist/cjs/migrations/0005_plan_merges.js +82 -0
- package/dist/cjs/migrations/0005_plan_merges.js.map +7 -0
- package/dist/cjs/migrations/0006_execution_listing.d.ts +15 -0
- package/dist/cjs/migrations/0006_execution_listing.d.ts.map +1 -0
- package/dist/cjs/migrations/0006_execution_listing.js +101 -0
- package/dist/cjs/migrations/0006_execution_listing.js.map +7 -0
- package/dist/cjs/migrations/0007_run_parent_sequence.d.ts +15 -0
- package/dist/cjs/migrations/0007_run_parent_sequence.d.ts.map +1 -0
- package/dist/cjs/migrations/0007_run_parent_sequence.js +40 -0
- package/dist/cjs/migrations/0007_run_parent_sequence.js.map +7 -0
- package/dist/cjs/migrations/0008_deferred_consumption.d.ts +16 -0
- package/dist/cjs/migrations/0008_deferred_consumption.d.ts.map +1 -0
- package/dist/cjs/migrations/0008_deferred_consumption.js +45 -0
- package/dist/cjs/migrations/0008_deferred_consumption.js.map +7 -0
- package/dist/cjs/migrations/0009_deferred_clock_facts.d.ts +24 -0
- package/dist/cjs/migrations/0009_deferred_clock_facts.d.ts.map +1 -0
- package/dist/cjs/migrations/0009_deferred_clock_facts.js +141 -0
- package/dist/cjs/migrations/0009_deferred_clock_facts.js.map +7 -0
- package/dist/cjs/migrations/0010_pending_cancellation.d.ts +15 -0
- package/dist/cjs/migrations/0010_pending_cancellation.d.ts.map +1 -0
- package/dist/cjs/migrations/0010_pending_cancellation.js +41 -0
- package/dist/cjs/migrations/0010_pending_cancellation.js.map +7 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/test/TestStores.d.ts +89 -0
- package/dist/cjs/test/TestStores.d.ts.map +1 -0
- package/dist/cjs/test/TestStores.js +98 -0
- package/dist/cjs/test/TestStores.js.map +7 -0
- package/dist/esm/ArtifactGc.d.ts +235 -0
- package/dist/esm/ArtifactGc.d.ts.map +1 -0
- package/dist/esm/ArtifactGc.js +268 -0
- package/dist/esm/ArtifactGc.js.map +1 -0
- package/dist/esm/ArtifactSync.d.ts +164 -0
- package/dist/esm/ArtifactSync.d.ts.map +1 -0
- package/dist/esm/ArtifactSync.js +176 -0
- package/dist/esm/ArtifactSync.js.map +1 -0
- package/dist/esm/CacheSync.d.ts +112 -0
- package/dist/esm/CacheSync.d.ts.map +1 -0
- package/dist/esm/CacheSync.js +94 -0
- package/dist/esm/CacheSync.js.map +1 -0
- package/dist/esm/DeferredClockFold.d.ts +354 -0
- package/dist/esm/DeferredClockFold.d.ts.map +1 -0
- package/dist/esm/DeferredClockFold.js +472 -0
- package/dist/esm/DeferredClockFold.js.map +1 -0
- package/dist/esm/DisasterRecovery.d.ts +431 -0
- package/dist/esm/DisasterRecovery.d.ts.map +1 -0
- package/dist/esm/DisasterRecovery.js +523 -0
- package/dist/esm/DisasterRecovery.js.map +1 -0
- package/dist/esm/DurableEngineState.d.ts +776 -0
- package/dist/esm/DurableEngineState.d.ts.map +1 -0
- package/dist/esm/DurableEngineState.js +1648 -0
- package/dist/esm/DurableEngineState.js.map +1 -0
- package/dist/esm/EngineStore.d.ts +158 -0
- package/dist/esm/EngineStore.d.ts.map +1 -0
- package/dist/esm/EngineStore.js +403 -0
- package/dist/esm/EngineStore.js.map +1 -0
- package/dist/esm/EngineStoreMetrics.d.ts +276 -0
- package/dist/esm/EngineStoreMetrics.d.ts.map +1 -0
- package/dist/esm/EngineStoreMetrics.js +260 -0
- package/dist/esm/EngineStoreMetrics.js.map +1 -0
- package/dist/esm/Errors.d.ts +22 -0
- package/dist/esm/Errors.d.ts.map +1 -0
- package/dist/esm/Errors.js +22 -0
- package/dist/esm/Errors.js.map +1 -0
- package/dist/esm/EventTypes.d.ts +27 -0
- package/dist/esm/EventTypes.d.ts.map +1 -0
- package/dist/esm/EventTypes.js +27 -0
- package/dist/esm/EventTypes.js.map +1 -0
- package/dist/esm/ExecutionFacts.d.ts +59 -0
- package/dist/esm/ExecutionFacts.d.ts.map +1 -0
- package/dist/esm/ExecutionFacts.js +96 -0
- package/dist/esm/ExecutionFacts.js.map +1 -0
- package/dist/esm/ExecutionSnapshot.d.ts +117 -0
- package/dist/esm/ExecutionSnapshot.d.ts.map +1 -0
- package/dist/esm/ExecutionSnapshot.js +95 -0
- package/dist/esm/ExecutionSnapshot.js.map +1 -0
- package/dist/esm/Inconsistency.d.ts +154 -0
- package/dist/esm/Inconsistency.d.ts.map +1 -0
- package/dist/esm/Inconsistency.js +133 -0
- package/dist/esm/Inconsistency.js.map +1 -0
- package/dist/esm/Migrations.d.ts +56 -0
- package/dist/esm/Migrations.d.ts.map +1 -0
- package/dist/esm/Migrations.js +91 -0
- package/dist/esm/Migrations.js.map +1 -0
- package/dist/esm/OwnerIdentity.d.ts +91 -0
- package/dist/esm/OwnerIdentity.d.ts.map +1 -0
- package/dist/esm/OwnerIdentity.js +99 -0
- package/dist/esm/OwnerIdentity.js.map +1 -0
- package/dist/esm/PlanInputStore.d.ts +110 -0
- package/dist/esm/PlanInputStore.d.ts.map +1 -0
- package/dist/esm/PlanInputStore.js +207 -0
- package/dist/esm/PlanInputStore.js.map +1 -0
- package/dist/esm/PlanMergeStore.d.ts +113 -0
- package/dist/esm/PlanMergeStore.d.ts.map +1 -0
- package/dist/esm/PlanMergeStore.js +244 -0
- package/dist/esm/PlanMergeStore.js.map +1 -0
- package/dist/esm/PlanScheduler.d.ts +363 -0
- package/dist/esm/PlanScheduler.d.ts.map +1 -0
- package/dist/esm/PlanScheduler.js +1321 -0
- package/dist/esm/PlanScheduler.js.map +1 -0
- package/dist/esm/Reconciliation.d.ts +175 -0
- package/dist/esm/Reconciliation.d.ts.map +1 -0
- package/dist/esm/Reconciliation.js +175 -0
- package/dist/esm/Reconciliation.js.map +1 -0
- package/dist/esm/ReplayOnly.d.ts +91 -0
- package/dist/esm/ReplayOnly.d.ts.map +1 -0
- package/dist/esm/ReplayOnly.js +59 -0
- package/dist/esm/ReplayOnly.js.map +1 -0
- package/dist/esm/Retention.d.ts +113 -0
- package/dist/esm/Retention.d.ts.map +1 -0
- package/dist/esm/Retention.js +139 -0
- package/dist/esm/Retention.js.map +1 -0
- package/dist/esm/RunCatalogRead.d.ts +17 -0
- package/dist/esm/RunCatalogRead.d.ts.map +1 -0
- package/dist/esm/RunCatalogRead.js +17 -0
- package/dist/esm/RunCatalogRead.js.map +1 -0
- package/dist/esm/RunChangeFeed.d.ts +75 -0
- package/dist/esm/RunChangeFeed.d.ts.map +1 -0
- package/dist/esm/RunChangeFeed.js +81 -0
- package/dist/esm/RunChangeFeed.js.map +1 -0
- package/dist/esm/RunState.d.ts +116 -0
- package/dist/esm/RunState.d.ts.map +1 -0
- package/dist/esm/RunState.js +109 -0
- package/dist/esm/RunState.js.map +1 -0
- package/dist/esm/Selection.d.ts +495 -0
- package/dist/esm/Selection.d.ts.map +1 -0
- package/dist/esm/Selection.js +482 -0
- package/dist/esm/Selection.js.map +1 -0
- package/dist/esm/SelectionStore.d.ts +141 -0
- package/dist/esm/SelectionStore.d.ts.map +1 -0
- package/dist/esm/SelectionStore.js +215 -0
- package/dist/esm/SelectionStore.js.map +1 -0
- package/dist/esm/StepBoundary.d.ts +501 -0
- package/dist/esm/StepBoundary.d.ts.map +1 -0
- package/dist/esm/StepBoundary.js +919 -0
- package/dist/esm/StepBoundary.js.map +1 -0
- package/dist/esm/StepCacheFold.d.ts +148 -0
- package/dist/esm/StepCacheFold.d.ts.map +1 -0
- package/dist/esm/StepCacheFold.js +201 -0
- package/dist/esm/StepCacheFold.js.map +1 -0
- package/dist/esm/StepSandbox.d.ts +78 -0
- package/dist/esm/StepSandbox.d.ts.map +1 -0
- package/dist/esm/StepSandbox.js +70 -0
- package/dist/esm/StepSandbox.js.map +1 -0
- package/dist/esm/WakeBus.d.ts +116 -0
- package/dist/esm/WakeBus.d.ts.map +1 -0
- package/dist/esm/WakeBus.js +121 -0
- package/dist/esm/WakeBus.js.map +1 -0
- package/dist/esm/WorkspaceSandbox.d.ts +619 -0
- package/dist/esm/WorkspaceSandbox.d.ts.map +1 -0
- package/dist/esm/WorkspaceSandbox.js +985 -0
- package/dist/esm/WorkspaceSandbox.js.map +1 -0
- package/dist/esm/index.d.ts +157 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +157 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/internal/ActionPersistence.d.ts +276 -0
- package/dist/esm/internal/ActionPersistence.d.ts.map +1 -0
- package/dist/esm/internal/ActionPersistence.js +2306 -0
- package/dist/esm/internal/ActionPersistence.js.map +1 -0
- package/dist/esm/internal/ArtifactRoots.d.ts +38 -0
- package/dist/esm/internal/ArtifactRoots.d.ts.map +1 -0
- package/dist/esm/internal/ArtifactRoots.js +76 -0
- package/dist/esm/internal/ArtifactRoots.js.map +1 -0
- package/dist/esm/internal/AttemptAdmission.d.ts +53 -0
- package/dist/esm/internal/AttemptAdmission.d.ts.map +1 -0
- package/dist/esm/internal/AttemptAdmission.js +52 -0
- package/dist/esm/internal/AttemptAdmission.js.map +1 -0
- package/dist/esm/internal/AttemptLifecycle.d.ts +245 -0
- package/dist/esm/internal/AttemptLifecycle.d.ts.map +1 -0
- package/dist/esm/internal/AttemptLifecycle.js +104 -0
- package/dist/esm/internal/AttemptLifecycle.js.map +1 -0
- package/dist/esm/internal/AttemptProbe.d.ts +53 -0
- package/dist/esm/internal/AttemptProbe.d.ts.map +1 -0
- package/dist/esm/internal/AttemptProbe.js +83 -0
- package/dist/esm/internal/AttemptProbe.js.map +1 -0
- package/dist/esm/internal/CacheAdmission.d.ts +79 -0
- package/dist/esm/internal/CacheAdmission.d.ts.map +1 -0
- package/dist/esm/internal/CacheAdmission.js +72 -0
- package/dist/esm/internal/CacheAdmission.js.map +1 -0
- package/dist/esm/internal/CacheAgeHistory.d.ts +27 -0
- package/dist/esm/internal/CacheAgeHistory.d.ts.map +1 -0
- package/dist/esm/internal/CacheAgeHistory.js +49 -0
- package/dist/esm/internal/CacheAgeHistory.js.map +1 -0
- package/dist/esm/internal/CacheAgeVerdicts.d.ts +15 -0
- package/dist/esm/internal/CacheAgeVerdicts.d.ts.map +1 -0
- package/dist/esm/internal/CacheAgeVerdicts.js +54 -0
- package/dist/esm/internal/CacheAgeVerdicts.js.map +1 -0
- package/dist/esm/internal/CacheOutputPolicy.d.ts +24 -0
- package/dist/esm/internal/CacheOutputPolicy.d.ts.map +1 -0
- package/dist/esm/internal/CacheOutputPolicy.js +73 -0
- package/dist/esm/internal/CacheOutputPolicy.js.map +1 -0
- package/dist/esm/internal/CachePublication.d.ts +97 -0
- package/dist/esm/internal/CachePublication.d.ts.map +1 -0
- package/dist/esm/internal/CachePublication.js +116 -0
- package/dist/esm/internal/CachePublication.js.map +1 -0
- package/dist/esm/internal/CallFacts.d.ts +15 -0
- package/dist/esm/internal/CallFacts.d.ts.map +1 -0
- package/dist/esm/internal/CallFacts.js +83 -0
- package/dist/esm/internal/CallFacts.js.map +1 -0
- package/dist/esm/internal/CopiedRecord.d.ts +61 -0
- package/dist/esm/internal/CopiedRecord.d.ts.map +1 -0
- package/dist/esm/internal/CopiedRecord.js +165 -0
- package/dist/esm/internal/CopiedRecord.js.map +1 -0
- package/dist/esm/internal/DeferredPersistence.d.ts +108 -0
- package/dist/esm/internal/DeferredPersistence.d.ts.map +1 -0
- package/dist/esm/internal/DeferredPersistence.js +240 -0
- package/dist/esm/internal/DeferredPersistence.js.map +1 -0
- package/dist/esm/internal/EffectRecords.d.ts +92 -0
- package/dist/esm/internal/EffectRecords.d.ts.map +1 -0
- package/dist/esm/internal/EffectRecords.js +94 -0
- package/dist/esm/internal/EffectRecords.js.map +1 -0
- package/dist/esm/internal/EngineJj.d.ts +30 -0
- package/dist/esm/internal/EngineJj.d.ts.map +1 -0
- package/dist/esm/internal/EngineJj.js +29 -0
- package/dist/esm/internal/EngineJj.js.map +1 -0
- package/dist/esm/internal/EngineStateSchema.d.ts +68 -0
- package/dist/esm/internal/EngineStateSchema.d.ts.map +1 -0
- package/dist/esm/internal/EngineStateSchema.js +53 -0
- package/dist/esm/internal/EngineStateSchema.js.map +1 -0
- package/dist/esm/internal/ExecutionSnapshotRead.d.ts +162 -0
- package/dist/esm/internal/ExecutionSnapshotRead.d.ts.map +1 -0
- package/dist/esm/internal/ExecutionSnapshotRead.js +218 -0
- package/dist/esm/internal/ExecutionSnapshotRead.js.map +1 -0
- package/dist/esm/internal/ExecutionSnapshotRelations.d.ts +39 -0
- package/dist/esm/internal/ExecutionSnapshotRelations.d.ts.map +1 -0
- package/dist/esm/internal/ExecutionSnapshotRelations.js +81 -0
- package/dist/esm/internal/ExecutionSnapshotRelations.js.map +1 -0
- package/dist/esm/internal/ExitEncoding.d.ts +180 -0
- package/dist/esm/internal/ExitEncoding.d.ts.map +1 -0
- package/dist/esm/internal/ExitEncoding.js +361 -0
- package/dist/esm/internal/ExitEncoding.js.map +1 -0
- package/dist/esm/internal/FileBoundarySnapshot.d.ts +14 -0
- package/dist/esm/internal/FileBoundarySnapshot.d.ts.map +1 -0
- package/dist/esm/internal/FileBoundarySnapshot.js +29 -0
- package/dist/esm/internal/FileBoundarySnapshot.js.map +1 -0
- package/dist/esm/internal/FileEnumeration.d.ts +127 -0
- package/dist/esm/internal/FileEnumeration.d.ts.map +1 -0
- package/dist/esm/internal/FileEnumeration.js +236 -0
- package/dist/esm/internal/FileEnumeration.js.map +1 -0
- package/dist/esm/internal/HostReflection.d.ts +80 -0
- package/dist/esm/internal/HostReflection.d.ts.map +1 -0
- package/dist/esm/internal/HostReflection.js +98 -0
- package/dist/esm/internal/HostReflection.js.map +1 -0
- package/dist/esm/internal/JournalRecords.d.ts +325 -0
- package/dist/esm/internal/JournalRecords.d.ts.map +1 -0
- package/dist/esm/internal/JournalRecords.js +306 -0
- package/dist/esm/internal/JournalRecords.js.map +1 -0
- package/dist/esm/internal/NodeJournal.d.ts +157 -0
- package/dist/esm/internal/NodeJournal.d.ts.map +1 -0
- package/dist/esm/internal/NodeJournal.js +298 -0
- package/dist/esm/internal/NodeJournal.js.map +1 -0
- package/dist/esm/internal/Ordering.d.ts +14 -0
- package/dist/esm/internal/Ordering.d.ts.map +1 -0
- package/dist/esm/internal/Ordering.js +14 -0
- package/dist/esm/internal/Ordering.js.map +1 -0
- package/dist/esm/internal/PlanInputs.d.ts +14 -0
- package/dist/esm/internal/PlanInputs.d.ts.map +1 -0
- package/dist/esm/internal/PlanInputs.js +57 -0
- package/dist/esm/internal/PlanInputs.js.map +1 -0
- package/dist/esm/internal/PlanMerges.d.ts +178 -0
- package/dist/esm/internal/PlanMerges.d.ts.map +1 -0
- package/dist/esm/internal/PlanMerges.js +113 -0
- package/dist/esm/internal/PlanMerges.js.map +1 -0
- package/dist/esm/internal/ProvenanceSlot.d.ts +30 -0
- package/dist/esm/internal/ProvenanceSlot.d.ts.map +1 -0
- package/dist/esm/internal/ProvenanceSlot.js +39 -0
- package/dist/esm/internal/ProvenanceSlot.js.map +1 -0
- package/dist/esm/internal/ResultEnvelope.d.ts +88 -0
- package/dist/esm/internal/ResultEnvelope.d.ts.map +1 -0
- package/dist/esm/internal/ResultEnvelope.js +83 -0
- package/dist/esm/internal/ResultEnvelope.js.map +1 -0
- package/dist/esm/internal/RetentionOps.d.ts +414 -0
- package/dist/esm/internal/RetentionOps.d.ts.map +1 -0
- package/dist/esm/internal/RetentionOps.js +576 -0
- package/dist/esm/internal/RetentionOps.js.map +1 -0
- package/dist/esm/internal/RunCatalogOps.d.ts +118 -0
- package/dist/esm/internal/RunCatalogOps.d.ts.map +1 -0
- package/dist/esm/internal/RunCatalogOps.js +120 -0
- package/dist/esm/internal/RunCatalogOps.js.map +1 -0
- package/dist/esm/internal/RunCoordinator.d.ts +78 -0
- package/dist/esm/internal/RunCoordinator.d.ts.map +1 -0
- package/dist/esm/internal/RunCoordinator.js +115 -0
- package/dist/esm/internal/RunCoordinator.js.map +1 -0
- package/dist/esm/internal/RunDriver.d.ts +192 -0
- package/dist/esm/internal/RunDriver.d.ts.map +1 -0
- package/dist/esm/internal/RunDriver.js +2334 -0
- package/dist/esm/internal/RunDriver.js.map +1 -0
- package/dist/esm/internal/RunLifecycle.d.ts +22 -0
- package/dist/esm/internal/RunLifecycle.d.ts.map +1 -0
- package/dist/esm/internal/RunLifecycle.js +49 -0
- package/dist/esm/internal/RunLifecycle.js.map +1 -0
- package/dist/esm/internal/RunListing.d.ts +78 -0
- package/dist/esm/internal/RunListing.d.ts.map +1 -0
- package/dist/esm/internal/RunListing.js +146 -0
- package/dist/esm/internal/RunListing.js.map +1 -0
- package/dist/esm/internal/RunListingError.d.ts +35 -0
- package/dist/esm/internal/RunListingError.d.ts.map +1 -0
- package/dist/esm/internal/RunListingError.js +27 -0
- package/dist/esm/internal/RunListingError.js.map +1 -0
- package/dist/esm/internal/RuntimeGraph.d.ts +73 -0
- package/dist/esm/internal/RuntimeGraph.d.ts.map +1 -0
- package/dist/esm/internal/RuntimeGraph.js +182 -0
- package/dist/esm/internal/RuntimeGraph.js.map +1 -0
- package/dist/esm/internal/SandboxedExecution.d.ts +80 -0
- package/dist/esm/internal/SandboxedExecution.d.ts.map +1 -0
- package/dist/esm/internal/SandboxedExecution.js +107 -0
- package/dist/esm/internal/SandboxedExecution.js.map +1 -0
- package/dist/esm/internal/StateTransaction.d.ts +44 -0
- package/dist/esm/internal/StateTransaction.d.ts.map +1 -0
- package/dist/esm/internal/StateTransaction.js +54 -0
- package/dist/esm/internal/StateTransaction.js.map +1 -0
- package/dist/esm/internal/StepFacts.d.ts +14 -0
- package/dist/esm/internal/StepFacts.d.ts.map +1 -0
- package/dist/esm/internal/StepFacts.js +35 -0
- package/dist/esm/internal/StepFacts.js.map +1 -0
- package/dist/esm/internal/TypedEvents.d.ts +14 -0
- package/dist/esm/internal/TypedEvents.d.ts.map +1 -0
- package/dist/esm/internal/TypedEvents.js +14 -0
- package/dist/esm/internal/TypedEvents.js.map +1 -0
- package/dist/esm/migrations/0001_initial.d.ts +17 -0
- package/dist/esm/migrations/0001_initial.d.ts.map +1 -0
- package/dist/esm/migrations/0001_initial.js +41 -0
- package/dist/esm/migrations/0001_initial.js.map +1 -0
- package/dist/esm/migrations/0002_selection_store.d.ts +15 -0
- package/dist/esm/migrations/0002_selection_store.d.ts.map +1 -0
- package/dist/esm/migrations/0002_selection_store.js +34 -0
- package/dist/esm/migrations/0002_selection_store.js.map +1 -0
- package/dist/esm/migrations/0003_plan_inputs.d.ts +13 -0
- package/dist/esm/migrations/0003_plan_inputs.d.ts.map +1 -0
- package/dist/esm/migrations/0003_plan_inputs.js +70 -0
- package/dist/esm/migrations/0003_plan_inputs.js.map +1 -0
- package/dist/esm/migrations/0004_plan_environment.d.ts +14 -0
- package/dist/esm/migrations/0004_plan_environment.d.ts.map +1 -0
- package/dist/esm/migrations/0004_plan_environment.js +33 -0
- package/dist/esm/migrations/0004_plan_environment.js.map +1 -0
- package/dist/esm/migrations/0005_plan_merges.d.ts +14 -0
- package/dist/esm/migrations/0005_plan_merges.d.ts.map +1 -0
- package/dist/esm/migrations/0005_plan_merges.js +59 -0
- package/dist/esm/migrations/0005_plan_merges.js.map +1 -0
- package/dist/esm/migrations/0006_execution_listing.d.ts +15 -0
- package/dist/esm/migrations/0006_execution_listing.d.ts.map +1 -0
- package/dist/esm/migrations/0006_execution_listing.js +81 -0
- package/dist/esm/migrations/0006_execution_listing.js.map +1 -0
- package/dist/esm/migrations/0007_run_parent_sequence.d.ts +15 -0
- package/dist/esm/migrations/0007_run_parent_sequence.d.ts.map +1 -0
- package/dist/esm/migrations/0007_run_parent_sequence.js +18 -0
- package/dist/esm/migrations/0007_run_parent_sequence.js.map +1 -0
- package/dist/esm/migrations/0008_deferred_consumption.d.ts +16 -0
- package/dist/esm/migrations/0008_deferred_consumption.d.ts.map +1 -0
- package/dist/esm/migrations/0008_deferred_consumption.js +24 -0
- package/dist/esm/migrations/0008_deferred_consumption.js.map +1 -0
- package/dist/esm/migrations/0009_deferred_clock_facts.d.ts +24 -0
- package/dist/esm/migrations/0009_deferred_clock_facts.d.ts.map +1 -0
- package/dist/esm/migrations/0009_deferred_clock_facts.js +127 -0
- package/dist/esm/migrations/0009_deferred_clock_facts.js.map +1 -0
- package/dist/esm/migrations/0010_pending_cancellation.d.ts +15 -0
- package/dist/esm/migrations/0010_pending_cancellation.d.ts.map +1 -0
- package/dist/esm/migrations/0010_pending_cancellation.js +19 -0
- package/dist/esm/migrations/0010_pending_cancellation.js.map +1 -0
- package/dist/esm/test/TestStores.d.ts +89 -0
- package/dist/esm/test/TestStores.d.ts.map +1 -0
- package/dist/esm/test/TestStores.js +103 -0
- package/dist/esm/test/TestStores.js.map +1 -0
- package/docs/README.md +113 -0
- package/docs/api.md +1494 -0
- package/docs/concepts/attempts-and-replay.md +149 -0
- package/docs/concepts/cache-admission.md +265 -0
- package/docs/concepts/call-facts.md +86 -0
- package/docs/concepts/durable-waits.md +175 -0
- package/docs/concepts/execution-facts.md +90 -0
- package/docs/concepts/ownership-and-fencing.md +128 -0
- package/docs/concepts/step-boundaries.md +148 -0
- package/docs/concepts/workspace-transactions.md +199 -0
- package/docs/event-types.md +14 -0
- package/docs/guides/back-up-and-restore.md +164 -0
- package/docs/guides/collect-unreferenced-artifacts.md +128 -0
- package/docs/guides/compose-a-durable-engine.md +146 -0
- package/docs/guides/coordinate-two-processes.md +120 -0
- package/docs/guides/defer-work-with-selection.md +171 -0
- package/docs/guides/delete-old-run-history.md +154 -0
- package/docs/guides/drive-a-plan.md +314 -0
- package/docs/guides/observe-engine-metrics.md +98 -0
- package/docs/guides/observe-executions.md +187 -0
- package/docs/guides/reclaim-runs-from-a-dead-host.md +133 -0
- package/docs/guides/share-a-cache-across-machines.md +134 -0
- package/docs/guides/testing.md +123 -0
- package/docs/installation.md +102 -0
- package/docs/quickstart.md +215 -0
- package/docs/troubleshooting.md +347 -0
- package/package.json +438 -3
- package/src/ArtifactGc.ts +408 -0
- package/src/ArtifactSync.ts +278 -0
- package/src/CacheSync.ts +131 -0
- package/src/DeferredClockFold.ts +586 -0
- package/src/DisasterRecovery.ts +888 -0
- package/src/DurableEngineState.ts +2656 -0
- package/src/EngineStore.ts +656 -0
- package/src/EngineStoreMetrics.ts +331 -0
- package/src/Errors.ts +28 -0
- package/src/EventTypes.ts +27 -0
- package/src/ExecutionFacts.ts +125 -0
- package/src/ExecutionSnapshot.ts +186 -0
- package/src/Inconsistency.ts +253 -0
- package/src/Migrations.ts +95 -0
- package/src/OwnerIdentity.ts +125 -0
- package/src/PlanInputStore.ts +292 -0
- package/src/PlanMergeStore.ts +330 -0
- package/src/PlanScheduler.ts +1806 -0
- package/src/Reconciliation.ts +224 -0
- package/src/ReplayOnly.ts +95 -0
- package/src/Retention.ts +214 -0
- package/src/RunCatalogRead.ts +27 -0
- package/src/RunChangeFeed.ts +125 -0
- package/src/RunState.ts +137 -0
- package/src/Selection.ts +683 -0
- package/src/SelectionStore.ts +353 -0
- package/src/StepBoundary.ts +1165 -0
- package/src/StepCacheFold.ts +244 -0
- package/src/StepSandbox.ts +110 -0
- package/src/WakeBus.ts +167 -0
- package/src/WorkspaceSandbox.ts +1604 -0
- package/src/index.ts +185 -0
- package/src/internal/ActionPersistence.ts +2687 -0
- package/src/internal/ArtifactRoots.ts +92 -0
- package/src/internal/AttemptAdmission.ts +76 -0
- package/src/internal/AttemptLifecycle.ts +163 -0
- package/src/internal/AttemptProbe.ts +90 -0
- package/src/internal/CacheAdmission.ts +133 -0
- package/src/internal/CacheAgeHistory.ts +58 -0
- package/src/internal/CacheAgeVerdicts.ts +61 -0
- package/src/internal/CacheOutputPolicy.ts +82 -0
- package/src/internal/CachePublication.ts +153 -0
- package/src/internal/CallFacts.ts +87 -0
- package/src/internal/CopiedRecord.ts +201 -0
- package/src/internal/DeferredPersistence.ts +459 -0
- package/src/internal/EffectRecords.ts +136 -0
- package/src/internal/EngineJj.ts +30 -0
- package/src/internal/EngineStateSchema.ts +92 -0
- package/src/internal/ExecutionSnapshotRead.ts +300 -0
- package/src/internal/ExecutionSnapshotRelations.ts +121 -0
- package/src/internal/ExitEncoding.ts +453 -0
- package/src/internal/FileBoundarySnapshot.ts +39 -0
- package/src/internal/FileEnumeration.ts +334 -0
- package/src/internal/HostReflection.ts +128 -0
- package/src/internal/JournalRecords.ts +396 -0
- package/src/internal/NodeJournal.ts +340 -0
- package/src/internal/Ordering.ts +14 -0
- package/src/internal/PlanInputs.ts +62 -0
- package/src/internal/PlanMerges.ts +123 -0
- package/src/internal/ProvenanceSlot.ts +52 -0
- package/src/internal/ResultEnvelope.ts +118 -0
- package/src/internal/RetentionOps.ts +865 -0
- package/src/internal/RunCatalogOps.ts +174 -0
- package/src/internal/RunCoordinator.ts +215 -0
- package/src/internal/RunDriver.ts +3154 -0
- package/src/internal/RunLifecycle.ts +53 -0
- package/src/internal/RunListing.ts +210 -0
- package/src/internal/RunListingError.ts +37 -0
- package/src/internal/RuntimeGraph.ts +253 -0
- package/src/internal/SandboxedExecution.ts +165 -0
- package/src/internal/StateTransaction.ts +71 -0
- package/src/internal/StepFacts.ts +36 -0
- package/src/internal/TypedEvents.ts +29 -0
- package/src/migrations/0001_initial.ts +51 -0
- package/src/migrations/0002_selection_store.ts +38 -0
- package/src/migrations/0003_plan_inputs.ts +75 -0
- package/src/migrations/0004_plan_environment.ts +34 -0
- package/src/migrations/0005_plan_merges.ts +60 -0
- package/src/migrations/0006_execution_listing.ts +86 -0
- package/src/migrations/0007_run_parent_sequence.ts +19 -0
- package/src/migrations/0008_deferred_consumption.ts +27 -0
- package/src/migrations/0009_deferred_clock_facts.ts +163 -0
- package/src/migrations/0010_pending_cancellation.ts +20 -0
- package/src/test/TestStores.ts +145 -0
|
@@ -0,0 +1,1648 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable deferred-completion and clock-deadline state used by the flow
|
|
3
|
+
* engine adapter.
|
|
4
|
+
*
|
|
5
|
+
* A parked run keeps the `suspended` status the run store admits; there is
|
|
6
|
+
* no `waiting` status. The waiting taxonomy lives beside it on the run row
|
|
7
|
+
* in the `waiting_reason`, `waiting_wake_at_ms`, `waiting_token`, and
|
|
8
|
+
* `waiting_request` columns, written by `park` and cleared by `wake`. See
|
|
9
|
+
* `docs/concepts/durable-waits.md`.
|
|
10
|
+
*
|
|
11
|
+
* @since 0.1.0
|
|
12
|
+
*/
|
|
13
|
+
import * as Dialect from "@smthrs/database/Dialect";
|
|
14
|
+
import { afterCommit, DatabaseError, DurableWriter } from "@smthrs/database/DurableWriter";
|
|
15
|
+
import { contentDigest } from "@smthrs/journal/JournalEvent";
|
|
16
|
+
import * as Cause from "effect/Cause";
|
|
17
|
+
import * as Context from "effect/Context";
|
|
18
|
+
import * as Effect from "effect/Effect";
|
|
19
|
+
import * as Exit from "effect/Exit";
|
|
20
|
+
import * as Layer from "effect/Layer";
|
|
21
|
+
import * as Option from "effect/Option";
|
|
22
|
+
import * as Result from "effect/Result";
|
|
23
|
+
import * as Schema from "effect/Schema";
|
|
24
|
+
import * as Semaphore from "effect/Semaphore";
|
|
25
|
+
import * as SqlClient from "effect/unstable/sql/SqlClient";
|
|
26
|
+
import * as EngineStateSchema from "./internal/EngineStateSchema.js";
|
|
27
|
+
import { compareText } from "./internal/Ordering.js";
|
|
28
|
+
const pendingCancellationLimit = (limit) => Number.isSafeInteger(limit) && limit >= 0
|
|
29
|
+
? Effect.void
|
|
30
|
+
: Effect.die(new RangeError("Pending cancellation limit must be a non-negative safe integer"));
|
|
31
|
+
/** JSON text carrying an arbitrary decoded value. */
|
|
32
|
+
const UnknownFromJsonString = Schema.fromJsonString(Schema.Unknown);
|
|
33
|
+
/** @private */
|
|
34
|
+
const NonNegativeSafeInt = Schema.Int.check(Schema.isGreaterThanOrEqualTo(0));
|
|
35
|
+
/**
|
|
36
|
+
* The durable address of a deferred result.
|
|
37
|
+
*
|
|
38
|
+
* @since 0.1.0
|
|
39
|
+
* @category models
|
|
40
|
+
*/
|
|
41
|
+
export const DeferredAddress = Schema.Struct({
|
|
42
|
+
flowName: Schema.NonEmptyString,
|
|
43
|
+
executionId: Schema.NonEmptyString,
|
|
44
|
+
deferredName: Schema.NonEmptyString
|
|
45
|
+
});
|
|
46
|
+
/**
|
|
47
|
+
* The first durable completion recorded for a deferred.
|
|
48
|
+
*
|
|
49
|
+
* Correlation data remains opaque at this boundary. A future external-event
|
|
50
|
+
* layer owns its schema and authorization policy.
|
|
51
|
+
*
|
|
52
|
+
* @since 0.1.0
|
|
53
|
+
* @category models
|
|
54
|
+
*/
|
|
55
|
+
export const DeferredRow = Schema.Struct({
|
|
56
|
+
...DeferredAddress.fields,
|
|
57
|
+
exit: Schema.Unknown,
|
|
58
|
+
metadata: Schema.optionalKey(Schema.Unknown),
|
|
59
|
+
completedAtMs: NonNegativeSafeInt
|
|
60
|
+
});
|
|
61
|
+
/**
|
|
62
|
+
* The durable address of a clock.
|
|
63
|
+
*
|
|
64
|
+
* @since 0.1.0
|
|
65
|
+
* @category models
|
|
66
|
+
*/
|
|
67
|
+
export const ClockAddress = Schema.Struct({
|
|
68
|
+
flowName: Schema.NonEmptyString,
|
|
69
|
+
executionId: Schema.NonEmptyString,
|
|
70
|
+
clockName: Schema.NonEmptyString
|
|
71
|
+
});
|
|
72
|
+
/**
|
|
73
|
+
* A durable absolute clock deadline.
|
|
74
|
+
*
|
|
75
|
+
* @since 0.1.0
|
|
76
|
+
* @category models
|
|
77
|
+
*/
|
|
78
|
+
export const ClockRow = Schema.Struct({
|
|
79
|
+
...ClockAddress.fields,
|
|
80
|
+
deferredName: Schema.NonEmptyString,
|
|
81
|
+
dueAtMs: NonNegativeSafeInt,
|
|
82
|
+
completedAtMs: Schema.NullOr(NonNegativeSafeInt)
|
|
83
|
+
});
|
|
84
|
+
/**
|
|
85
|
+
* The core wait-reason vocabulary a supervisor understands for wake policy.
|
|
86
|
+
*
|
|
87
|
+
* - `approval` — parked until a human (or policy) approves; woken by token.
|
|
88
|
+
* - `event` — parked on an external event / deferred completion.
|
|
89
|
+
* - `timer` — parked until `wakeAt`; woken by the durable clock sweep.
|
|
90
|
+
* - `quota` — parked on provider quota; woken at `wakeAt` when known.
|
|
91
|
+
* - `released` — the owning process released the run without settling it
|
|
92
|
+
* (shutdown, heartbeat self-interrupt; issue #39). A released row has no
|
|
93
|
+
* held lease and no `wakeAt`: any supervisor/sweeper MUST scan for this
|
|
94
|
+
* reason explicitly and re-drive the run through the claim path, or
|
|
95
|
+
* released runs are stranded forever (issue #67).
|
|
96
|
+
* - `quarantine` — parked because a SUCCEEDED attempt row's recorded
|
|
97
|
+
* evidence measured corrupt under the strict verdict (issue #171). The
|
|
98
|
+
* row cannot be evicted/re-executed like a cache row (its side effects
|
|
99
|
+
* already ran), so the corrupt boundary evidence is quarantined off the
|
|
100
|
+
* row while its durable outcome stays intact. No sweeper auto-wakes this
|
|
101
|
+
* reason: the strict integrity verdict remains visible until an explicit
|
|
102
|
+
* resume, which completes from the recorded outcome without re-execution.
|
|
103
|
+
*
|
|
104
|
+
* Left open (`string & {}`) so a plugin can park on a reason the core
|
|
105
|
+
* taxonomy has not named yet — the store persists whatever it is given
|
|
106
|
+
* rather than rejecting unknown reasons.
|
|
107
|
+
*
|
|
108
|
+
* @since 0.1.0
|
|
109
|
+
* @category models
|
|
110
|
+
*/
|
|
111
|
+
export const WaitingReason = Schema.NonEmptyString;
|
|
112
|
+
/**
|
|
113
|
+
* The payload recorded when a run parks.
|
|
114
|
+
*
|
|
115
|
+
* `reason` and `wakeAt` earn columns because a supervisor sweeper queries
|
|
116
|
+
* them (`WHERE waiting_reason = 'quota' AND waiting_wake_at_ms <= ?`);
|
|
117
|
+
* `token` is compare-and-swap/lookup material a wake handler matches
|
|
118
|
+
* against.
|
|
119
|
+
*
|
|
120
|
+
* @since 0.1.0
|
|
121
|
+
* @category models
|
|
122
|
+
*/
|
|
123
|
+
export const Waiting = Schema.Struct({
|
|
124
|
+
reason: WaitingReason,
|
|
125
|
+
wakeAt: Schema.optional(NonNegativeSafeInt),
|
|
126
|
+
token: Schema.optional(Schema.NonEmptyString),
|
|
127
|
+
/**
|
|
128
|
+
* What the wait declared about itself: for a `HumanTask` park, the kind of
|
|
129
|
+
* answer it wants, the prompt, and the attempt budget.
|
|
130
|
+
*
|
|
131
|
+
* `reason` and `token` say that a person owes the run an answer and which
|
|
132
|
+
* wait point holds it. Neither says what was asked, and the question lives
|
|
133
|
+
* in an action payload of an execution that has released its owner, so a
|
|
134
|
+
* reader had nothing to render but the wait's own name. This is the question
|
|
135
|
+
* itself, as the JSON TEXT the declaring flow wrote, stored and cleared with
|
|
136
|
+
* the other waiting columns.
|
|
137
|
+
*/
|
|
138
|
+
request: Schema.optional(Schema.String)
|
|
139
|
+
});
|
|
140
|
+
/**
|
|
141
|
+
* A decoded waiting row for a parked run.
|
|
142
|
+
*
|
|
143
|
+
* @since 0.1.0
|
|
144
|
+
* @category models
|
|
145
|
+
*/
|
|
146
|
+
export const WaitingRow = Schema.Struct({
|
|
147
|
+
runId: Schema.NonEmptyString,
|
|
148
|
+
reason: WaitingReason,
|
|
149
|
+
wakeAt: Schema.NullOr(NonNegativeSafeInt),
|
|
150
|
+
token: Schema.NullOr(Schema.NonEmptyString),
|
|
151
|
+
/** The question {@link Waiting} declared, absent on a park that declared none. */
|
|
152
|
+
request: Schema.optionalKey(Schema.Json)
|
|
153
|
+
});
|
|
154
|
+
/**
|
|
155
|
+
* A durable parent edge in the run DAG.
|
|
156
|
+
*
|
|
157
|
+
* The run row's `state_json` carries only the first (creating) parent; a
|
|
158
|
+
* diamond gives a run a second parent the row cannot express. Cycle
|
|
159
|
+
* detection must see every edge from every owner process and across
|
|
160
|
+
* restarts (issues #40/#41), so edges are persisted here. `seq` is a
|
|
161
|
+
* store-global insertion order used only to list a run's parents oldest
|
|
162
|
+
* first (and to make the cycle walk deterministic); it no longer arbitrates
|
|
163
|
+
* anything, so duplicate values would be harmless (issues #54/#66).
|
|
164
|
+
*
|
|
165
|
+
* @since 0.1.0
|
|
166
|
+
* @category models
|
|
167
|
+
*/
|
|
168
|
+
export const RunParentEdge = Schema.Struct({
|
|
169
|
+
childId: Schema.NonEmptyString,
|
|
170
|
+
parentId: Schema.NonEmptyString,
|
|
171
|
+
seq: NonNegativeSafeInt
|
|
172
|
+
});
|
|
173
|
+
/**
|
|
174
|
+
* The surviving attempt rows of one action key: the earliest surviving
|
|
175
|
+
* row's start time and the highest attempt number contiguous from it.
|
|
176
|
+
*
|
|
177
|
+
* @since 0.1.0
|
|
178
|
+
* @category models
|
|
179
|
+
*/
|
|
180
|
+
export const AttemptSurvivors = Schema.Struct({
|
|
181
|
+
/**
|
|
182
|
+
* The lowest surviving attempt number. Anything above 1 means the prune
|
|
183
|
+
* job removed a leading run of attempts; the engine store applies the
|
|
184
|
+
* pruned-prefix tolerance to it so both survivor paths agree (issue #96).
|
|
185
|
+
*/
|
|
186
|
+
earliestAttempt: NonNegativeSafeInt,
|
|
187
|
+
earliestStartedAtMs: NonNegativeSafeInt,
|
|
188
|
+
latest: NonNegativeSafeInt
|
|
189
|
+
});
|
|
190
|
+
/**
|
|
191
|
+
* Recording a parent edge would close a cycle in the run DAG.
|
|
192
|
+
*
|
|
193
|
+
* Raised from inside the same transaction that inserted the edge, which is
|
|
194
|
+
* rolled back: a rejected edge leaves no durable trace anywhere (issues
|
|
195
|
+
* #54/#55/#56). `path` lists execution ids from the child back to itself
|
|
196
|
+
* through its would-be ancestors, matching the engine's
|
|
197
|
+
* `FlowCycleDetected.path` shape.
|
|
198
|
+
*
|
|
199
|
+
* @since 0.1.0
|
|
200
|
+
* @category errors
|
|
201
|
+
*/
|
|
202
|
+
export class RunParentCycleError extends Schema.TaggedError()("@smthrs/engine-store/RunParentCycleError", {
|
|
203
|
+
path: Schema.Array(Schema.String)
|
|
204
|
+
}) {
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* A durable engine-state invariant broke: a defect (a bug), never a typed
|
|
208
|
+
* failure.
|
|
209
|
+
*
|
|
210
|
+
* `value_not_serializable` and `value_not_decodable` name the JSON column
|
|
211
|
+
* (`field`) whose value could not be written or read back.
|
|
212
|
+
* `deferred_completion_missing` and `run_parent_edge_missing` mean a row a
|
|
213
|
+
* first-writer transaction just lost to was gone when read.
|
|
214
|
+
*
|
|
215
|
+
* @since 1.0.0
|
|
216
|
+
* @category errors
|
|
217
|
+
*/
|
|
218
|
+
export class EngineStateFault extends Schema.TaggedError()("@smthrs/engine-store/EngineStateFault", {
|
|
219
|
+
reason: Schema.Literals([
|
|
220
|
+
"value_not_serializable",
|
|
221
|
+
"value_not_decodable",
|
|
222
|
+
"deferred_completion_missing",
|
|
223
|
+
"run_parent_edge_missing"
|
|
224
|
+
]),
|
|
225
|
+
field: Schema.optional(Schema.String),
|
|
226
|
+
cause: Schema.optional(Schema.Defect())
|
|
227
|
+
}) {
|
|
228
|
+
get message() {
|
|
229
|
+
switch (this.reason) {
|
|
230
|
+
case "value_not_serializable":
|
|
231
|
+
return `${this.field} must be JSON-serializable`;
|
|
232
|
+
case "value_not_decodable":
|
|
233
|
+
return `could not decode ${this.field}`;
|
|
234
|
+
case "deferred_completion_missing":
|
|
235
|
+
return "deferred completion disappeared during first-writer transaction";
|
|
236
|
+
case "run_parent_edge_missing":
|
|
237
|
+
return "run parent edge disappeared during first-writer transaction";
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Service tag for durable deferred completions and absolute clock deadlines.
|
|
243
|
+
*
|
|
244
|
+
* @since 0.1.0
|
|
245
|
+
* @category services
|
|
246
|
+
*/
|
|
247
|
+
export class DurableEngineState extends Context.Service()("@smthrs/engine-store/DurableEngineState") {
|
|
248
|
+
}
|
|
249
|
+
/** @private */
|
|
250
|
+
const deferredKey = (address) => JSON.stringify([address.flowName, address.executionId, address.deferredName]);
|
|
251
|
+
/** @private */
|
|
252
|
+
const clockKey = (address) => JSON.stringify([address.flowName, address.executionId, address.clockName]);
|
|
253
|
+
/** @private */
|
|
254
|
+
const DeferredDatabaseRow = Schema.Struct({
|
|
255
|
+
...DeferredAddress.fields,
|
|
256
|
+
exitJson: Schema.String,
|
|
257
|
+
metadataJson: Schema.NullOr(Schema.String),
|
|
258
|
+
completedAtMs: NonNegativeSafeInt
|
|
259
|
+
});
|
|
260
|
+
/** @private */
|
|
261
|
+
const ClockDatabaseRow = Schema.Struct({
|
|
262
|
+
...ClockAddress.fields,
|
|
263
|
+
deferredName: Schema.NonEmptyString,
|
|
264
|
+
dueAtMs: NonNegativeSafeInt,
|
|
265
|
+
completedAtMs: Schema.NullOr(NonNegativeSafeInt)
|
|
266
|
+
});
|
|
267
|
+
/** @private */
|
|
268
|
+
const WaitingDatabaseRow = Schema.Struct({
|
|
269
|
+
runId: Schema.NonEmptyString,
|
|
270
|
+
waitingReason: WaitingReason,
|
|
271
|
+
waitingWakeAtMs: Schema.NullOr(NonNegativeSafeInt),
|
|
272
|
+
waitingToken: Schema.NullOr(Schema.NonEmptyString),
|
|
273
|
+
/** Absent rather than null on a database written before `0004_waiting_request`. */
|
|
274
|
+
waitingRequest: Schema.optional(Schema.NullOr(Schema.String))
|
|
275
|
+
});
|
|
276
|
+
/**
|
|
277
|
+
* The declared question of a waiting row, or absence.
|
|
278
|
+
*
|
|
279
|
+
* The column is the wait's own JSON, so text that does not parse is text the
|
|
280
|
+
* writer never wrote: it is reported as no declared question rather than
|
|
281
|
+
* failing the read of a park that is otherwise intact.
|
|
282
|
+
*/
|
|
283
|
+
const decodeWaitingRequest = (value) => {
|
|
284
|
+
if (value === null || value === undefined)
|
|
285
|
+
return {};
|
|
286
|
+
try {
|
|
287
|
+
return { request: JSON.parse(value) };
|
|
288
|
+
}
|
|
289
|
+
catch {
|
|
290
|
+
return {};
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
const decodeWaitingRow = (input) => decodeWaitingRowResult(input).pipe(Effect.orDie);
|
|
294
|
+
/** {@link decodeWaitingRow} with its parse failure left in the error channel. */
|
|
295
|
+
const decodeWaitingRowResult = (input) => Schema.decodeUnknownEffect(WaitingDatabaseRow)(input).pipe(Effect.map((row) => ({
|
|
296
|
+
runId: row.runId,
|
|
297
|
+
reason: row.waitingReason,
|
|
298
|
+
wakeAt: row.waitingWakeAtMs,
|
|
299
|
+
token: row.waitingToken,
|
|
300
|
+
...decodeWaitingRequest(row.waitingRequest)
|
|
301
|
+
})));
|
|
302
|
+
/** @private */
|
|
303
|
+
const RunParentDatabaseRow = Schema.Struct({
|
|
304
|
+
childId: Schema.NonEmptyString,
|
|
305
|
+
parentId: Schema.NonEmptyString,
|
|
306
|
+
seq: NonNegativeSafeInt
|
|
307
|
+
});
|
|
308
|
+
const decodeRunParentEdge = (input) => decodeRunParentEdgeResult(input).pipe(Effect.orDie);
|
|
309
|
+
/** {@link decodeRunParentEdge} with its parse failure left in the error channel. */
|
|
310
|
+
const decodeRunParentEdgeResult = (input) => Schema.decodeUnknownEffect(RunParentDatabaseRow)(input);
|
|
311
|
+
/**
|
|
312
|
+
* Walks the parent chain upward from `parentId` over the durable edges,
|
|
313
|
+
* looking for `childId`. Returns the cycle path the new `(childId,
|
|
314
|
+
* parentId)` edge would close — ids from the child back to itself, matching
|
|
315
|
+
* the engine's `FlowCycleDetected.path` shape — or `undefined` when the
|
|
316
|
+
* edge is safe. A repeated id (pre-existing corrupt cycle written outside
|
|
317
|
+
* this API) terminates that branch instead of looping forever.
|
|
318
|
+
*/
|
|
319
|
+
const findCyclePath = (childId, parentId, parentsOf) => Effect.suspend(() => {
|
|
320
|
+
if (parentId === childId) {
|
|
321
|
+
return Effect.succeed([childId]);
|
|
322
|
+
}
|
|
323
|
+
const seen = new Set([parentId]);
|
|
324
|
+
const walk = (current, chain) => Effect.gen(function* () {
|
|
325
|
+
const parents = yield* parentsOf(current);
|
|
326
|
+
for (const parent of parents) {
|
|
327
|
+
if (parent === childId)
|
|
328
|
+
return [...chain, parent].reverse();
|
|
329
|
+
if (seen.has(parent))
|
|
330
|
+
continue;
|
|
331
|
+
seen.add(parent);
|
|
332
|
+
const found = yield* walk(parent, [...chain, parent]);
|
|
333
|
+
if (found !== undefined)
|
|
334
|
+
return found;
|
|
335
|
+
}
|
|
336
|
+
return undefined;
|
|
337
|
+
});
|
|
338
|
+
return walk(parentId, [parentId]);
|
|
339
|
+
});
|
|
340
|
+
/** @private */
|
|
341
|
+
const DeferredAddressDatabaseRow = DeferredAddress;
|
|
342
|
+
/** The digests of a stored completion row's encoded text. */
|
|
343
|
+
const deferredDigest = (row) => ({
|
|
344
|
+
exit: contentDigest(row.exitJson),
|
|
345
|
+
metadata: row.metadataJson === null ? null : contentDigest(row.metadataJson)
|
|
346
|
+
});
|
|
347
|
+
/** The digests of a memory completion row, over the text the SQL store would keep. */
|
|
348
|
+
const memoryDigest = (row) => Effect.gen(function* () {
|
|
349
|
+
const exitJson = yield* encodeJson(row.exit, "exit");
|
|
350
|
+
const metadataJson = row.metadata === undefined ? null : yield* encodeJson(row.metadata, "metadata");
|
|
351
|
+
return deferredDigest({ exitJson, metadataJson });
|
|
352
|
+
});
|
|
353
|
+
const encodeJson = (value, field) => Schema.encodeEffect(UnknownFromJsonString)(value).pipe(Effect.mapError((cause) => new EngineStateFault({ reason: "value_not_serializable", field, cause })), Effect.orDie);
|
|
354
|
+
const decodeJson = (value, field) => Schema.decodeUnknownEffect(UnknownFromJsonString)(value).pipe(Effect.mapError((cause) => new EngineStateFault({ reason: "value_not_decodable", field, cause })), Effect.orDie);
|
|
355
|
+
/**
|
|
356
|
+
* Rehydrates Effect's JSON representation after a storage round-trip.
|
|
357
|
+
*
|
|
358
|
+
* `Exit` and `Cause` deliberately serialize as plain JSON objects. Leaving a
|
|
359
|
+
* failure in that form is unsafe: the success branch happens to be rebuilt by
|
|
360
|
+
* `Exit.map`, while a plain failure is not an Exit and the deferred's schema
|
|
361
|
+
* rejects it after restart. Unknown non-Exit payloads remain untouched for
|
|
362
|
+
* the generic durable-state contract.
|
|
363
|
+
*/
|
|
364
|
+
const restoreJsonExit = (value) => {
|
|
365
|
+
if (typeof value !== "object" || value === null)
|
|
366
|
+
return value;
|
|
367
|
+
const record = value;
|
|
368
|
+
if (record["_id"] !== "Exit")
|
|
369
|
+
return value;
|
|
370
|
+
if (record["_tag"] === "Success")
|
|
371
|
+
return Exit.succeed(record["value"]);
|
|
372
|
+
if (record["_tag"] !== "Failure")
|
|
373
|
+
return value;
|
|
374
|
+
const cause = record["cause"];
|
|
375
|
+
if (typeof cause !== "object" || cause === null)
|
|
376
|
+
return value;
|
|
377
|
+
const failures = cause["failures"];
|
|
378
|
+
if (!Array.isArray(failures))
|
|
379
|
+
return value;
|
|
380
|
+
const reasons = [];
|
|
381
|
+
for (const failure of failures) {
|
|
382
|
+
if (typeof failure !== "object" || failure === null)
|
|
383
|
+
return value;
|
|
384
|
+
const reason = failure;
|
|
385
|
+
switch (reason["_tag"]) {
|
|
386
|
+
case "Fail":
|
|
387
|
+
reasons.push(Cause.makeFailReason(reason["error"]));
|
|
388
|
+
break;
|
|
389
|
+
case "Die":
|
|
390
|
+
reasons.push(Cause.makeDieReason(reason["defect"]));
|
|
391
|
+
break;
|
|
392
|
+
case "Interrupt":
|
|
393
|
+
reasons.push(Cause.makeInterruptReason(typeof reason["fiberId"] === "number" ? reason["fiberId"] : undefined));
|
|
394
|
+
break;
|
|
395
|
+
default:
|
|
396
|
+
return value;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
return Exit.failCause(Cause.fromReasons(reasons));
|
|
400
|
+
};
|
|
401
|
+
const decodeDeferredRow = (input) => Schema.decodeUnknownEffect(DeferredDatabaseRow)(input).pipe(Effect.orDie, Effect.flatMap((row) => Effect.all({
|
|
402
|
+
exit: decodeJson(row.exitJson, "exit_json").pipe(Effect.map(restoreJsonExit)),
|
|
403
|
+
metadata: row.metadataJson === null
|
|
404
|
+
? Effect.succeed(undefined)
|
|
405
|
+
: decodeJson(row.metadataJson, "metadata_json")
|
|
406
|
+
}).pipe(Effect.map(({ exit, metadata }) => ({
|
|
407
|
+
flowName: row.flowName,
|
|
408
|
+
executionId: row.executionId,
|
|
409
|
+
deferredName: row.deferredName,
|
|
410
|
+
exit,
|
|
411
|
+
...(metadata === undefined ? {} : { metadata }),
|
|
412
|
+
completedAtMs: row.completedAtMs
|
|
413
|
+
})))));
|
|
414
|
+
const decodeClockRow = (input) => decodeClockRowResult(input).pipe(Effect.orDie);
|
|
415
|
+
/** {@link decodeClockRow} with its parse failure left in the error channel. */
|
|
416
|
+
const decodeClockRowResult = (input) => Schema.decodeUnknownEffect(ClockDatabaseRow)(input).pipe(Effect.map((row) => ({
|
|
417
|
+
flowName: row.flowName,
|
|
418
|
+
executionId: row.executionId,
|
|
419
|
+
clockName: row.clockName,
|
|
420
|
+
deferredName: row.deferredName,
|
|
421
|
+
dueAtMs: row.dueAtMs,
|
|
422
|
+
completedAtMs: row.completedAtMs
|
|
423
|
+
})));
|
|
424
|
+
/** The primary key of a clock row, read defensively off a row that may not decode. */
|
|
425
|
+
const clockRowKey = (row) => JSON.stringify([row["flowName"], row["executionId"], row["clockName"]]);
|
|
426
|
+
/** The primary key of a deferred completion, read the same way. */
|
|
427
|
+
const deferredAddressRowKey = (row) => JSON.stringify([row["flowName"], row["executionId"], row["deferredName"]]);
|
|
428
|
+
/** The primary key of a run-parent edge, read the same way. */
|
|
429
|
+
const runParentRowKey = (row) => JSON.stringify([row["childId"], row["parentId"]]);
|
|
430
|
+
/**
|
|
431
|
+
* The deepest nesting {@link Service.waitingTree} walks.
|
|
432
|
+
*
|
|
433
|
+
* A run tree is authored, not generated: `agent/run` → `coding/request` →
|
|
434
|
+
* `coding/Request` → `coding/PrepareWithWiki` → `coding/PreparePlan` is five,
|
|
435
|
+
* and the deepest composition in this repository is well inside this. The cap
|
|
436
|
+
* exists so a tree whose edges were corrupted outside the engine costs a
|
|
437
|
+
* bounded read rather than the whole table.
|
|
438
|
+
*
|
|
439
|
+
* @since 1.0.0
|
|
440
|
+
* @category constants
|
|
441
|
+
*/
|
|
442
|
+
export const waitingTreeMaxDepth = 64;
|
|
443
|
+
/** The primary key of a parked run's waiting payload, read the same way. */
|
|
444
|
+
const waitingRowKey = (row) => JSON.stringify([row["runId"]]);
|
|
445
|
+
/**
|
|
446
|
+
* Decodes the rows one durable-state sweep read, skipping — with a
|
|
447
|
+
* storage-integrity warning naming the row's key — any row that will not
|
|
448
|
+
* decode.
|
|
449
|
+
*
|
|
450
|
+
* Decoding the batch through `Effect.orDie` made one malformed row fatal for
|
|
451
|
+
* every row beside it: `completedDeferreds` and `pendingClocks` are what
|
|
452
|
+
* `register` sweeps, so a single unreadable row anywhere in a flow's history
|
|
453
|
+
* killed every registration of that flow, and no run of it ever resumed again.
|
|
454
|
+
* The blast radius of a corrupt row must be that row. The key is logged
|
|
455
|
+
* because it is what an operator needs to find and repair it, and it is read
|
|
456
|
+
* field by field off the raw row so a row that failed to decode can still name
|
|
457
|
+
* itself.
|
|
458
|
+
*
|
|
459
|
+
* Every enumeration goes through here for that reason, not just the two the
|
|
460
|
+
* outage named: `dueClocks`, `waitingRuns`, `runParents`, and `runChildren`
|
|
461
|
+
* each had their own version of it — no timer in the store fires, no parked
|
|
462
|
+
* run is ever swept, no cycle check can read a run's ancestry, no
|
|
463
|
+
* cancellation reaches a child.
|
|
464
|
+
*
|
|
465
|
+
* The point reads (`deferred`, `clock`, `waiting`) deliberately stay on
|
|
466
|
+
* `Effect.orDie`. They answer a question about ONE row, and their callers
|
|
467
|
+
* read "no row" as "this never happened": silently reporting that for a
|
|
468
|
+
* completion or deadline that is durably recorded but unreadable would
|
|
469
|
+
* re-run work whose side effects already ran. There the corrupt row IS the
|
|
470
|
+
* blast radius, so dying is the correct-sized failure.
|
|
471
|
+
*/
|
|
472
|
+
const decodeSweep = (rows, kind, keyOf, decode) => Effect.gen(function* () {
|
|
473
|
+
const decoded = [];
|
|
474
|
+
for (const row of rows) {
|
|
475
|
+
const result = yield* Effect.result(decode(row));
|
|
476
|
+
if (Result.isSuccess(result)) {
|
|
477
|
+
decoded.push(result.success);
|
|
478
|
+
continue;
|
|
479
|
+
}
|
|
480
|
+
yield* Effect.logWarning(`engine-store: skipping a malformed ${kind} row ${keyOf(row)}`, result.failure);
|
|
481
|
+
}
|
|
482
|
+
return decoded;
|
|
483
|
+
});
|
|
484
|
+
/**
|
|
485
|
+
* Demotes a write boundary's storage failures to defects, reason by reason.
|
|
486
|
+
*
|
|
487
|
+
* `transaction` declares the BODY's error type, and the shared writer adds
|
|
488
|
+
* `DatabaseError` to it. A sole storage failure is already a defect; a
|
|
489
|
+
* composite one used to be cast back through the declared channel, which let
|
|
490
|
+
* a raw `DatabaseError` surface as a typed failure of a type that never
|
|
491
|
+
* admits one. Re-channelling keeps the error object, its annotations, the
|
|
492
|
+
* other reasons and the interruptions intact while making the declared type
|
|
493
|
+
* true. Every other reason is passed through UNCHANGED rather than rebuilt,
|
|
494
|
+
* so a body failure keeps its own identity and spans.
|
|
495
|
+
*/
|
|
496
|
+
const dieOnDatabaseError = (cause) => Cause.fromReasons(cause.reasons.map((reason) => Cause.isFailReason(reason) && reason.error instanceof DatabaseError
|
|
497
|
+
? Cause.makeDieReason(reason.error).annotate(Cause.reasonAnnotations(reason))
|
|
498
|
+
// Narrowing a generic union by `instanceof` is beyond the checker, but
|
|
499
|
+
// the guard above is the proof: what is left is a defect, an
|
|
500
|
+
// interruption, or a failure whose error is not a `DatabaseError`.
|
|
501
|
+
: reason));
|
|
502
|
+
/**
|
|
503
|
+
* Constructs the database-backed durable-state implementation.
|
|
504
|
+
*
|
|
505
|
+
* Clock creation is fenced against the current run owner. Deferred completion
|
|
506
|
+
* and clock firing are external trigger admissions protected by first-writer
|
|
507
|
+
* and compare-and-set semantics; execution remains claim-gated by `RunStore`.
|
|
508
|
+
*
|
|
509
|
+
* @since 0.1.0
|
|
510
|
+
* @category constructors
|
|
511
|
+
*/
|
|
512
|
+
export const make = Effect.gen(function* () {
|
|
513
|
+
const sql = yield* Effect.service(SqlClient.SqlClient);
|
|
514
|
+
const writer = yield* DurableWriter;
|
|
515
|
+
// Engine-store-owned storage created outside the journal migration
|
|
516
|
+
// (issues #40/#41/#79/#81). The statements, their rationale, and
|
|
517
|
+
// the dialects each is known to accept live in one machine-readable
|
|
518
|
+
// inventory so the pg-porting plan cannot omit them (issue #92).
|
|
519
|
+
yield* EngineStateSchema.apply(sql, writer);
|
|
520
|
+
const selectDeferred = (address) => sql `
|
|
521
|
+
SELECT
|
|
522
|
+
flow_name AS "flowName",
|
|
523
|
+
execution_id AS "executionId",
|
|
524
|
+
deferred_name AS "deferredName",
|
|
525
|
+
exit_json AS "exitJson",
|
|
526
|
+
metadata_json AS "metadataJson",
|
|
527
|
+
completed_at_ms AS "completedAtMs"
|
|
528
|
+
FROM flows_deferred_completions
|
|
529
|
+
WHERE flow_name = ${address.flowName}
|
|
530
|
+
AND execution_id = ${address.executionId}
|
|
531
|
+
AND deferred_name = ${address.deferredName}
|
|
532
|
+
`;
|
|
533
|
+
const selectClock = (address) => sql `
|
|
534
|
+
SELECT
|
|
535
|
+
flow_name AS "flowName",
|
|
536
|
+
execution_id AS "executionId",
|
|
537
|
+
clock_name AS "clockName",
|
|
538
|
+
deferred_name AS "deferredName",
|
|
539
|
+
due_at_ms AS "dueAtMs",
|
|
540
|
+
completed_at_ms AS "completedAtMs"
|
|
541
|
+
FROM flows_clock_deadlines
|
|
542
|
+
WHERE flow_name = ${address.flowName}
|
|
543
|
+
AND execution_id = ${address.executionId}
|
|
544
|
+
AND clock_name = ${address.clockName}
|
|
545
|
+
`;
|
|
546
|
+
const deferred = Effect.fn("DurableEngineState.deferred")((address) => selectDeferred(address).pipe(Effect.orDie, Effect.flatMap((rows) => rows[0] === undefined
|
|
547
|
+
? Effect.succeedNone
|
|
548
|
+
: Effect.map(decodeDeferredRow(rows[0]), Option.some))));
|
|
549
|
+
const consumeDeferred = Effect.fn("DurableEngineState.consumeDeferred")((address, consumedAtMs) => writer.write(sql `
|
|
550
|
+
UPDATE flows_deferred_completions
|
|
551
|
+
SET consumed_at_ms = ${consumedAtMs}
|
|
552
|
+
WHERE flow_name = ${address.flowName}
|
|
553
|
+
AND execution_id = ${address.executionId}
|
|
554
|
+
AND deferred_name = ${address.deferredName}
|
|
555
|
+
AND consumed_at_ms IS NULL
|
|
556
|
+
RETURNING flow_name AS "flowName"
|
|
557
|
+
`).pipe(Effect.orDie, Effect.map((rows) => rows.length > 0)));
|
|
558
|
+
const completeDeferred = Effect.fn("DurableEngineState.completeDeferred")((row) => Effect.gen(function* () {
|
|
559
|
+
const exitJson = yield* encodeJson(row.exit, "exit");
|
|
560
|
+
const metadataJson = row.metadata === undefined
|
|
561
|
+
? null
|
|
562
|
+
: yield* encodeJson(row.metadata, "metadata");
|
|
563
|
+
return yield* writer.write(Effect.gen(function* () {
|
|
564
|
+
const inserted = yield* sql `
|
|
565
|
+
INSERT INTO flows_deferred_completions (
|
|
566
|
+
flow_name,
|
|
567
|
+
execution_id,
|
|
568
|
+
deferred_name,
|
|
569
|
+
exit_json,
|
|
570
|
+
metadata_json,
|
|
571
|
+
completed_at_ms
|
|
572
|
+
) VALUES (
|
|
573
|
+
${row.flowName},
|
|
574
|
+
${row.executionId},
|
|
575
|
+
${row.deferredName},
|
|
576
|
+
${exitJson},
|
|
577
|
+
${metadataJson},
|
|
578
|
+
${row.completedAtMs}
|
|
579
|
+
)
|
|
580
|
+
ON CONFLICT (flow_name, execution_id, deferred_name) DO NOTHING
|
|
581
|
+
RETURNING
|
|
582
|
+
flow_name AS "flowName",
|
|
583
|
+
execution_id AS "executionId",
|
|
584
|
+
deferred_name AS "deferredName",
|
|
585
|
+
exit_json AS "exitJson",
|
|
586
|
+
metadata_json AS "metadataJson",
|
|
587
|
+
completed_at_ms AS "completedAtMs"
|
|
588
|
+
`;
|
|
589
|
+
if (inserted[0] !== undefined) {
|
|
590
|
+
return {
|
|
591
|
+
_tag: "Completed",
|
|
592
|
+
row: yield* decodeDeferredRow(inserted[0]),
|
|
593
|
+
digest: deferredDigest(inserted[0])
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
const existing = yield* selectDeferred(row);
|
|
597
|
+
if (existing[0] === undefined) {
|
|
598
|
+
return yield* Effect.die(new EngineStateFault({ reason: "deferred_completion_missing" }));
|
|
599
|
+
}
|
|
600
|
+
return {
|
|
601
|
+
_tag: "Existing",
|
|
602
|
+
row: yield* decodeDeferredRow(existing[0]),
|
|
603
|
+
digest: deferredDigest(existing[0])
|
|
604
|
+
};
|
|
605
|
+
})).pipe(Effect.orDie);
|
|
606
|
+
}));
|
|
607
|
+
const clock = Effect.fn("DurableEngineState.clock")((address) => selectClock(address).pipe(Effect.orDie, Effect.flatMap((rows) => rows[0] === undefined
|
|
608
|
+
? Effect.succeedNone
|
|
609
|
+
: Effect.map(decodeClockRow(rows[0]), Option.some))));
|
|
610
|
+
const scheduleClock = Effect.fn("DurableEngineState.scheduleClock")((row, owner) => writer.write(Effect.gen(function* () {
|
|
611
|
+
const inserted = yield* sql `
|
|
612
|
+
INSERT INTO flows_clock_deadlines (
|
|
613
|
+
flow_name,
|
|
614
|
+
execution_id,
|
|
615
|
+
clock_name,
|
|
616
|
+
deferred_name,
|
|
617
|
+
due_at_ms,
|
|
618
|
+
completed_at_ms
|
|
619
|
+
)
|
|
620
|
+
SELECT
|
|
621
|
+
${row.flowName},
|
|
622
|
+
${row.executionId},
|
|
623
|
+
${row.clockName},
|
|
624
|
+
${row.deferredName},
|
|
625
|
+
${row.dueAtMs},
|
|
626
|
+
${row.completedAtMs}
|
|
627
|
+
WHERE EXISTS (
|
|
628
|
+
SELECT 1
|
|
629
|
+
FROM flows_runs
|
|
630
|
+
WHERE run_id = ${row.executionId}
|
|
631
|
+
AND status = 'running'
|
|
632
|
+
AND owner_host_id = ${owner.hostId}
|
|
633
|
+
AND owner_pid = ${owner.pid}
|
|
634
|
+
AND owner_nonce = ${owner.nonce}
|
|
635
|
+
)
|
|
636
|
+
ON CONFLICT (flow_name, execution_id, clock_name) DO NOTHING
|
|
637
|
+
RETURNING
|
|
638
|
+
flow_name AS "flowName",
|
|
639
|
+
execution_id AS "executionId",
|
|
640
|
+
clock_name AS "clockName",
|
|
641
|
+
deferred_name AS "deferredName",
|
|
642
|
+
due_at_ms AS "dueAtMs",
|
|
643
|
+
completed_at_ms AS "completedAtMs"
|
|
644
|
+
`;
|
|
645
|
+
if (inserted[0] !== undefined) {
|
|
646
|
+
return {
|
|
647
|
+
_tag: "Scheduled",
|
|
648
|
+
row: yield* decodeClockRow(inserted[0])
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
const existing = yield* selectClock(row);
|
|
652
|
+
if (existing[0] !== undefined) {
|
|
653
|
+
return {
|
|
654
|
+
_tag: "Existing",
|
|
655
|
+
row: yield* decodeClockRow(existing[0])
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
return yield* Effect.interrupt;
|
|
659
|
+
})).pipe(Effect.orDie));
|
|
660
|
+
const completeClock = Effect.fn("DurableEngineState.completeClock")((address, completedAtMs) => writer.write(Effect.gen(function* () {
|
|
661
|
+
const updated = yield* sql `
|
|
662
|
+
UPDATE flows_clock_deadlines
|
|
663
|
+
SET completed_at_ms = ${completedAtMs}
|
|
664
|
+
WHERE flow_name = ${address.flowName}
|
|
665
|
+
AND execution_id = ${address.executionId}
|
|
666
|
+
AND clock_name = ${address.clockName}
|
|
667
|
+
AND completed_at_ms IS NULL
|
|
668
|
+
RETURNING
|
|
669
|
+
flow_name AS "flowName",
|
|
670
|
+
execution_id AS "executionId",
|
|
671
|
+
clock_name AS "clockName",
|
|
672
|
+
deferred_name AS "deferredName",
|
|
673
|
+
due_at_ms AS "dueAtMs",
|
|
674
|
+
completed_at_ms AS "completedAtMs"
|
|
675
|
+
`;
|
|
676
|
+
if (updated[0] !== undefined) {
|
|
677
|
+
return {
|
|
678
|
+
_tag: "Completed",
|
|
679
|
+
row: yield* decodeClockRow(updated[0])
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
const existing = yield* selectClock(address);
|
|
683
|
+
return existing[0] === undefined
|
|
684
|
+
? { _tag: "NotFound" }
|
|
685
|
+
: {
|
|
686
|
+
_tag: "AlreadyCompleted",
|
|
687
|
+
row: yield* decodeClockRow(existing[0])
|
|
688
|
+
};
|
|
689
|
+
})).pipe(Effect.orDie));
|
|
690
|
+
const dueClocks = Effect.fn("DurableEngineState.dueClocks")((nowMs) => sql `
|
|
691
|
+
SELECT
|
|
692
|
+
flow_name AS "flowName",
|
|
693
|
+
execution_id AS "executionId",
|
|
694
|
+
clock_name AS "clockName",
|
|
695
|
+
deferred_name AS "deferredName",
|
|
696
|
+
due_at_ms AS "dueAtMs",
|
|
697
|
+
completed_at_ms AS "completedAtMs"
|
|
698
|
+
FROM flows_clock_deadlines
|
|
699
|
+
WHERE completed_at_ms IS NULL
|
|
700
|
+
AND due_at_ms <= ${nowMs}
|
|
701
|
+
ORDER BY due_at_ms, execution_id, clock_name
|
|
702
|
+
`.pipe(Effect.orDie,
|
|
703
|
+
// This is the whole store's due-timer sweep: dying on one unreadable
|
|
704
|
+
// deadline stopped every timer in every flow from firing.
|
|
705
|
+
Effect.flatMap((rows) => decodeSweep(rows, "clock deadline", clockRowKey, decodeClockRowResult))));
|
|
706
|
+
const pendingClocks = Effect.fn("DurableEngineState.pendingClocks")((scope) => (scope.executionId !== undefined
|
|
707
|
+
? sql `
|
|
708
|
+
SELECT
|
|
709
|
+
flow_name AS "flowName",
|
|
710
|
+
execution_id AS "executionId",
|
|
711
|
+
clock_name AS "clockName",
|
|
712
|
+
deferred_name AS "deferredName",
|
|
713
|
+
due_at_ms AS "dueAtMs",
|
|
714
|
+
completed_at_ms AS "completedAtMs"
|
|
715
|
+
FROM flows_clock_deadlines
|
|
716
|
+
WHERE completed_at_ms IS NULL
|
|
717
|
+
AND execution_id = ${scope.executionId}
|
|
718
|
+
AND ${scope.flowName === undefined ? sql `1 = 1` : sql `flow_name = ${scope.flowName}`}
|
|
719
|
+
AND EXISTS (
|
|
720
|
+
SELECT 1 FROM flows_runs
|
|
721
|
+
WHERE flows_runs.run_id = flows_clock_deadlines.execution_id
|
|
722
|
+
AND flows_runs.status NOT IN ('completed', 'failed', 'cancelled')
|
|
723
|
+
)
|
|
724
|
+
ORDER BY due_at_ms, execution_id, clock_name
|
|
725
|
+
`
|
|
726
|
+
: scope.flowName !== undefined
|
|
727
|
+
? sql `
|
|
728
|
+
SELECT
|
|
729
|
+
flow_name AS "flowName",
|
|
730
|
+
execution_id AS "executionId",
|
|
731
|
+
clock_name AS "clockName",
|
|
732
|
+
deferred_name AS "deferredName",
|
|
733
|
+
due_at_ms AS "dueAtMs",
|
|
734
|
+
completed_at_ms AS "completedAtMs"
|
|
735
|
+
FROM flows_clock_deadlines
|
|
736
|
+
WHERE completed_at_ms IS NULL
|
|
737
|
+
AND flow_name = ${scope.flowName}
|
|
738
|
+
AND EXISTS (
|
|
739
|
+
SELECT 1 FROM flows_runs
|
|
740
|
+
WHERE flows_runs.run_id = flows_clock_deadlines.execution_id
|
|
741
|
+
AND flows_runs.status NOT IN ('completed', 'failed', 'cancelled')
|
|
742
|
+
)
|
|
743
|
+
ORDER BY due_at_ms, execution_id, clock_name
|
|
744
|
+
`
|
|
745
|
+
: sql `
|
|
746
|
+
SELECT
|
|
747
|
+
flow_name AS "flowName",
|
|
748
|
+
execution_id AS "executionId",
|
|
749
|
+
clock_name AS "clockName",
|
|
750
|
+
deferred_name AS "deferredName",
|
|
751
|
+
due_at_ms AS "dueAtMs",
|
|
752
|
+
completed_at_ms AS "completedAtMs"
|
|
753
|
+
FROM flows_clock_deadlines
|
|
754
|
+
WHERE completed_at_ms IS NULL
|
|
755
|
+
AND EXISTS (
|
|
756
|
+
SELECT 1 FROM flows_runs
|
|
757
|
+
WHERE flows_runs.run_id = flows_clock_deadlines.execution_id
|
|
758
|
+
AND flows_runs.status NOT IN ('completed', 'failed', 'cancelled')
|
|
759
|
+
)
|
|
760
|
+
ORDER BY due_at_ms, execution_id, clock_name
|
|
761
|
+
`).pipe(Effect.orDie,
|
|
762
|
+
// A row that will not decode is a storage-integrity event about that
|
|
763
|
+
// row. Dying here made one of them fatal for the whole sweep, and so
|
|
764
|
+
// for `register` itself (issue B-08).
|
|
765
|
+
Effect.flatMap((rows) => decodeSweep(rows, "clock deadline", clockRowKey, decodeClockRowResult))));
|
|
766
|
+
const completedDeferreds = Effect.fn("DurableEngineState.completedDeferreds")((flowName) => sql `
|
|
767
|
+
SELECT
|
|
768
|
+
flow_name AS "flowName",
|
|
769
|
+
execution_id AS "executionId",
|
|
770
|
+
deferred_name AS "deferredName"
|
|
771
|
+
FROM flows_deferred_completions
|
|
772
|
+
WHERE flow_name = ${flowName}
|
|
773
|
+
AND consumed_at_ms IS NULL
|
|
774
|
+
AND EXISTS (
|
|
775
|
+
SELECT 1 FROM flows_runs
|
|
776
|
+
WHERE flows_runs.run_id = flows_deferred_completions.execution_id
|
|
777
|
+
AND flows_runs.status NOT IN ('completed', 'failed', 'cancelled')
|
|
778
|
+
)
|
|
779
|
+
ORDER BY execution_id, deferred_name
|
|
780
|
+
`.pipe(Effect.orDie, Effect.flatMap((rows) => decodeSweep(rows, "deferred completion", deferredAddressRowKey, (row) => Schema.decodeUnknownEffect(DeferredAddressDatabaseRow)(row)))));
|
|
781
|
+
const completeRunClocks = Effect.fn("DurableEngineState.completeRunClocks")((executionId, completedAtMs) => writer.write(sql `
|
|
782
|
+
UPDATE flows_clock_deadlines
|
|
783
|
+
SET completed_at_ms = ${completedAtMs}
|
|
784
|
+
WHERE execution_id = ${executionId}
|
|
785
|
+
AND completed_at_ms IS NULL
|
|
786
|
+
RETURNING
|
|
787
|
+
flow_name AS "flowName",
|
|
788
|
+
execution_id AS "executionId",
|
|
789
|
+
clock_name AS "clockName",
|
|
790
|
+
deferred_name AS "deferredName",
|
|
791
|
+
due_at_ms AS "dueAtMs",
|
|
792
|
+
completed_at_ms AS "completedAtMs"
|
|
793
|
+
`).pipe(Effect.orDie, Effect.flatMap((rows) => Effect.forEach(rows, decodeClockRow)), Effect.map((rows) => [...rows].sort((left, right) => compareText(left.flowName, right.flowName) || compareText(left.clockName, right.clockName)))));
|
|
794
|
+
const selectWaiting = (runId) => sql `
|
|
795
|
+
SELECT
|
|
796
|
+
run_id AS "runId",
|
|
797
|
+
waiting_reason AS "waitingReason",
|
|
798
|
+
waiting_wake_at_ms AS "waitingWakeAtMs",
|
|
799
|
+
waiting_token AS "waitingToken",
|
|
800
|
+
waiting_request AS "waitingRequest"
|
|
801
|
+
FROM flows_runs
|
|
802
|
+
WHERE run_id = ${runId}
|
|
803
|
+
AND waiting_reason IS NOT NULL
|
|
804
|
+
`;
|
|
805
|
+
const park = Effect.fn("DurableEngineState.park")((runId, waiting, owner) => writer.write(Effect.gen(function* () {
|
|
806
|
+
const updated = yield* sql `
|
|
807
|
+
UPDATE flows_runs
|
|
808
|
+
SET
|
|
809
|
+
waiting_reason = ${waiting.reason},
|
|
810
|
+
waiting_wake_at_ms = ${waiting.wakeAt ?? null},
|
|
811
|
+
waiting_token = ${waiting.token ?? null},
|
|
812
|
+
waiting_request = ${waiting.request ?? null}
|
|
813
|
+
WHERE run_id = ${runId}
|
|
814
|
+
AND owner_host_id = ${owner.hostId}
|
|
815
|
+
AND owner_pid = ${owner.pid}
|
|
816
|
+
AND owner_nonce = ${owner.nonce}
|
|
817
|
+
RETURNING
|
|
818
|
+
run_id AS "runId",
|
|
819
|
+
waiting_reason AS "waitingReason",
|
|
820
|
+
waiting_wake_at_ms AS "waitingWakeAtMs",
|
|
821
|
+
waiting_token AS "waitingToken",
|
|
822
|
+
waiting_request AS "waitingRequest"
|
|
823
|
+
`;
|
|
824
|
+
if (updated[0] === undefined) {
|
|
825
|
+
return { _tag: "NotFound" };
|
|
826
|
+
}
|
|
827
|
+
return { _tag: "Parked", row: yield* decodeWaitingRow(updated[0]) };
|
|
828
|
+
})).pipe(Effect.orDie));
|
|
829
|
+
const wake = Effect.fn("DurableEngineState.wake")((runId) => writer.write(Effect.gen(function* () {
|
|
830
|
+
const before = yield* selectWaiting(runId);
|
|
831
|
+
if (before[0] === undefined) {
|
|
832
|
+
const existing = yield* sql `
|
|
833
|
+
SELECT run_id AS "runId" FROM flows_runs WHERE run_id = ${runId}
|
|
834
|
+
`;
|
|
835
|
+
return existing[0] === undefined ? { _tag: "NotFound" } : { _tag: "NotWaiting" };
|
|
836
|
+
}
|
|
837
|
+
const row = yield* decodeWaitingRow(before[0]);
|
|
838
|
+
yield* sql `
|
|
839
|
+
UPDATE flows_runs
|
|
840
|
+
SET
|
|
841
|
+
waiting_reason = NULL,
|
|
842
|
+
waiting_wake_at_ms = NULL,
|
|
843
|
+
waiting_token = NULL,
|
|
844
|
+
waiting_request = NULL
|
|
845
|
+
WHERE run_id = ${runId}
|
|
846
|
+
AND waiting_reason IS NOT NULL
|
|
847
|
+
`;
|
|
848
|
+
return { _tag: "Woken", row };
|
|
849
|
+
})).pipe(Effect.orDie));
|
|
850
|
+
const waiting = Effect.fn("DurableEngineState.waiting")((runId) => selectWaiting(runId).pipe(Effect.orDie, Effect.flatMap((rows) => rows[0] === undefined
|
|
851
|
+
? Effect.succeedNone
|
|
852
|
+
: Effect.map(decodeWaitingRow(rows[0]), Option.some))));
|
|
853
|
+
/**
|
|
854
|
+
* Every open wait in one run tree, the named run's own first.
|
|
855
|
+
*
|
|
856
|
+
* Walk the authoritative spawn and trampoline edges. UNION visits each
|
|
857
|
+
* (run, depth) pair once; the depth cap bounds even externally corrupted
|
|
858
|
+
* cycles. Grouping by run afterwards collapses unequal-depth DAG paths
|
|
859
|
+
* and orders each wait at its shortest attached distance from the root.
|
|
860
|
+
* Correlated edge lookups seek each parent's indexes instead of building
|
|
861
|
+
* an edge set from the database's entire execution history.
|
|
862
|
+
*/
|
|
863
|
+
const waitingTree = Effect.fn("DurableEngineState.waitingTree")((runId, options) => sql `
|
|
864
|
+
WITH RECURSIVE tree(run_id, depth) AS (
|
|
865
|
+
SELECT run_id, 0 FROM flows_runs WHERE run_id = ${runId}
|
|
866
|
+
UNION
|
|
867
|
+
SELECT child.run_id, tree.depth + 1
|
|
868
|
+
FROM tree
|
|
869
|
+
JOIN flows_runs child ON child.run_id IN (
|
|
870
|
+
SELECT child_id FROM flows_run_parents WHERE parent_id = tree.run_id
|
|
871
|
+
UNION ALL
|
|
872
|
+
SELECT run_id FROM flows_runs WHERE parent_run_id = tree.run_id
|
|
873
|
+
)
|
|
874
|
+
WHERE tree.depth < ${waitingTreeMaxDepth}
|
|
875
|
+
AND (${options?.detached === "include" ? 1 : 0} = 1
|
|
876
|
+
OR child.parent_run_id = tree.run_id
|
|
877
|
+
OR COALESCE(${Dialect.jsonText(sql, sql `child.state_json`, "$.onParentExit")}, 'cancel') <> 'detach')
|
|
878
|
+
), reachable(run_id, depth) AS (
|
|
879
|
+
SELECT run_id, MIN(depth) FROM tree GROUP BY run_id
|
|
880
|
+
)
|
|
881
|
+
SELECT
|
|
882
|
+
parked.run_id AS "runId",
|
|
883
|
+
parked.waiting_reason AS "waitingReason",
|
|
884
|
+
parked.waiting_wake_at_ms AS "waitingWakeAtMs",
|
|
885
|
+
parked.waiting_token AS "waitingToken",
|
|
886
|
+
parked.waiting_request AS "waitingRequest"
|
|
887
|
+
FROM reachable
|
|
888
|
+
JOIN flows_runs parked ON parked.run_id = reachable.run_id
|
|
889
|
+
WHERE parked.waiting_reason IS NOT NULL
|
|
890
|
+
AND parked.status NOT IN ('completed', 'failed', 'cancelled')
|
|
891
|
+
ORDER BY reachable.depth, parked.created_at_ms, parked.run_id
|
|
892
|
+
`.pipe(Effect.orDie, Effect.flatMap((rows) => decodeSweep(rows, "waiting run", waitingRowKey, decodeWaitingRowResult))));
|
|
893
|
+
const waitingRuns = Effect.fn("DurableEngineState.waitingRuns")((filter) => {
|
|
894
|
+
// `1 = 0` short-circuits the cancel predicate away when the caller did
|
|
895
|
+
// not ask for it, so the reason/wake filters (and the 0004 index) stay
|
|
896
|
+
// the leading predicates (issue #68).
|
|
897
|
+
const cancelRequestedOnly = filter?.cancelRequested === true ? 1 : 0;
|
|
898
|
+
return (filter?.reason !== undefined && filter.dueBeforeMs !== undefined
|
|
899
|
+
? sql `
|
|
900
|
+
SELECT
|
|
901
|
+
run_id AS "runId",
|
|
902
|
+
waiting_reason AS "waitingReason",
|
|
903
|
+
waiting_wake_at_ms AS "waitingWakeAtMs",
|
|
904
|
+
waiting_token AS "waitingToken",
|
|
905
|
+
waiting_request AS "waitingRequest"
|
|
906
|
+
FROM flows_runs
|
|
907
|
+
WHERE waiting_reason = ${filter.reason}
|
|
908
|
+
AND waiting_wake_at_ms IS NOT NULL
|
|
909
|
+
AND waiting_wake_at_ms <= ${filter.dueBeforeMs}
|
|
910
|
+
AND (${cancelRequestedOnly} = 0 OR cancel_requested_at_ms IS NOT NULL)
|
|
911
|
+
AND status NOT IN ('completed', 'failed', 'cancelled')
|
|
912
|
+
ORDER BY waiting_wake_at_ms, run_id
|
|
913
|
+
`
|
|
914
|
+
: filter?.reason !== undefined
|
|
915
|
+
? sql `
|
|
916
|
+
SELECT
|
|
917
|
+
run_id AS "runId",
|
|
918
|
+
waiting_reason AS "waitingReason",
|
|
919
|
+
waiting_wake_at_ms AS "waitingWakeAtMs",
|
|
920
|
+
waiting_token AS "waitingToken",
|
|
921
|
+
waiting_request AS "waitingRequest"
|
|
922
|
+
FROM flows_runs
|
|
923
|
+
WHERE waiting_reason = ${filter.reason}
|
|
924
|
+
AND (${cancelRequestedOnly} = 0 OR cancel_requested_at_ms IS NOT NULL)
|
|
925
|
+
AND status NOT IN ('completed', 'failed', 'cancelled')
|
|
926
|
+
ORDER BY waiting_wake_at_ms IS NULL, waiting_wake_at_ms, run_id
|
|
927
|
+
`
|
|
928
|
+
: filter?.dueBeforeMs !== undefined
|
|
929
|
+
? sql `
|
|
930
|
+
SELECT
|
|
931
|
+
run_id AS "runId",
|
|
932
|
+
waiting_reason AS "waitingReason",
|
|
933
|
+
waiting_wake_at_ms AS "waitingWakeAtMs",
|
|
934
|
+
waiting_token AS "waitingToken",
|
|
935
|
+
waiting_request AS "waitingRequest"
|
|
936
|
+
FROM flows_runs
|
|
937
|
+
WHERE waiting_reason IS NOT NULL
|
|
938
|
+
AND waiting_wake_at_ms IS NOT NULL
|
|
939
|
+
AND waiting_wake_at_ms <= ${filter.dueBeforeMs}
|
|
940
|
+
AND (${cancelRequestedOnly} = 0 OR cancel_requested_at_ms IS NOT NULL)
|
|
941
|
+
AND status NOT IN ('completed', 'failed', 'cancelled')
|
|
942
|
+
ORDER BY waiting_wake_at_ms, run_id
|
|
943
|
+
`
|
|
944
|
+
: sql `
|
|
945
|
+
SELECT
|
|
946
|
+
run_id AS "runId",
|
|
947
|
+
waiting_reason AS "waitingReason",
|
|
948
|
+
waiting_wake_at_ms AS "waitingWakeAtMs",
|
|
949
|
+
waiting_token AS "waitingToken",
|
|
950
|
+
waiting_request AS "waitingRequest"
|
|
951
|
+
FROM flows_runs
|
|
952
|
+
WHERE waiting_reason IS NOT NULL
|
|
953
|
+
AND (${cancelRequestedOnly} = 0 OR cancel_requested_at_ms IS NOT NULL)
|
|
954
|
+
AND status NOT IN ('completed', 'failed', 'cancelled')
|
|
955
|
+
ORDER BY waiting_wake_at_ms IS NULL, waiting_wake_at_ms, run_id
|
|
956
|
+
`).pipe(Effect.orDie,
|
|
957
|
+
// The parked-run sweeper's only enumeration: one unreadable waiting
|
|
958
|
+
// payload stranded every parked run, including the ones whose
|
|
959
|
+
// cancellation had already been requested.
|
|
960
|
+
Effect.flatMap((rows) => decodeSweep(rows, "waiting run", waitingRowKey, decodeWaitingRowResult)));
|
|
961
|
+
});
|
|
962
|
+
const pendingRows = (flowName, limit, after, through, descending = false, eligibleBeforeMs) => sql `
|
|
963
|
+
SELECT run_id AS "runId", created_at_ms AS "createdAtMs" FROM flows_runs
|
|
964
|
+
${Dialect.isPostgres(sql) ? sql `` : sql `INDEXED BY flows_runs_listing_3`}
|
|
965
|
+
WHERE status = 'pending'
|
|
966
|
+
AND ${eligibleBeforeMs === undefined ? sql `TRUE` : sql `created_at_ms < ${eligibleBeforeMs}`}
|
|
967
|
+
AND execution_flow = ${flowName}
|
|
968
|
+
AND ${after === undefined ? sql `TRUE` : sql `(
|
|
969
|
+
created_at_ms > ${after.createdAtMs}
|
|
970
|
+
OR (created_at_ms = ${after.createdAtMs} AND run_id > ${after.runId})
|
|
971
|
+
)`}
|
|
972
|
+
AND ${through === undefined ? sql `TRUE` : sql `(
|
|
973
|
+
created_at_ms < ${through.createdAtMs}
|
|
974
|
+
OR (created_at_ms = ${through.createdAtMs} AND run_id <= ${through.runId})
|
|
975
|
+
)`}
|
|
976
|
+
ORDER BY ${descending ? sql `created_at_ms DESC, run_id DESC` : sql `created_at_ms, run_id`}
|
|
977
|
+
LIMIT ${limit ?? (Dialect.isPostgres(sql) ? null : -1)}
|
|
978
|
+
`.pipe(Effect.orDie, Effect.map((rows) => rows.map((row) => ({ runId: row.runId, createdAtMs: Number(row.createdAtMs) }))));
|
|
979
|
+
const pendingRuns = Effect.fn("DurableEngineState.pendingRuns")((flowName, limit, after, through, eligibleBeforeMs) => pendingRows(flowName, limit, after, through, false, eligibleBeforeMs));
|
|
980
|
+
const pendingRunTail = Effect.fn("DurableEngineState.pendingRunTail")((flowName, eligibleBeforeMs) => pendingRows(flowName, 1, undefined, undefined, true, eligibleBeforeMs).pipe(Effect.map((rows) => Option.fromNullishOr(rows[0]))));
|
|
981
|
+
const pendingCancellationRows = (limit, after, through, descending = false) => pendingCancellationLimit(limit).pipe(Effect.andThen(sql `
|
|
982
|
+
SELECT run_id AS "runId", created_at_ms AS "createdAtMs" FROM flows_runs
|
|
983
|
+
${Dialect.isPostgres(sql) ? sql `` : sql `INDEXED BY flows_runs_pending_cancel_idx`}
|
|
984
|
+
WHERE status = 'pending' AND cancel_requested_at_ms IS NOT NULL
|
|
985
|
+
AND ${after === undefined ? sql `TRUE` : sql `(created_at_ms, run_id) > (${after.createdAtMs}, ${after.runId})`}
|
|
986
|
+
AND ${through === undefined ? sql `TRUE` : sql `(created_at_ms, run_id) <= (${through.createdAtMs}, ${through.runId})`}
|
|
987
|
+
ORDER BY ${descending ? sql `created_at_ms DESC, run_id DESC` : sql `created_at_ms, run_id`}
|
|
988
|
+
LIMIT ${limit}
|
|
989
|
+
`.pipe(Effect.orDie, Effect.map((rows) => rows.map((row) => ({ runId: row.runId, createdAtMs: Number(row.createdAtMs) }))))));
|
|
990
|
+
const pendingCancellationRuns = Effect.fn("DurableEngineState.pendingCancellationRuns")((limit, after, through) => pendingCancellationRows(limit, after, through));
|
|
991
|
+
const pendingCancellationTail = Effect.fn("DurableEngineState.pendingCancellationTail")(() => pendingCancellationRows(1, undefined, undefined, true).pipe(Effect.map((rows) => Option.fromNullishOr(rows[0]))));
|
|
992
|
+
const staleRunningRuns = Effect.fn("DurableEngineState.staleRunningRuns")((staleBeforeMs, limit) =>
|
|
993
|
+
// `LIMIT -1` is SQLite's explicit "no limit"; the partial index created
|
|
994
|
+
// in `make` serves the predicate, so the per-tick sweep is an index
|
|
995
|
+
// range read instead of a full table scan (issue #79).
|
|
996
|
+
sql `
|
|
997
|
+
SELECT run_id AS "runId", heartbeat_at_ms AS "heartbeatAtMs"
|
|
998
|
+
FROM flows_runs
|
|
999
|
+
WHERE status = 'running'
|
|
1000
|
+
AND heartbeat_at_ms IS NOT NULL
|
|
1001
|
+
AND heartbeat_at_ms < ${staleBeforeMs}
|
|
1002
|
+
ORDER BY heartbeat_at_ms, run_id
|
|
1003
|
+
LIMIT ${limit ?? (Dialect.isPostgres(sql) ? null : -1)}
|
|
1004
|
+
`.pipe(Effect.orDie, Effect.map((rows) => rows.map((row) => ({ runId: String(row.runId), heartbeatAtMs: Number(row.heartbeatAtMs) })))));
|
|
1005
|
+
/**
|
|
1006
|
+
* One ordered range read replaces the engine store's per-attempt point
|
|
1007
|
+
* probes (issue #77): a fresh key costs one empty SELECT instead of 32
|
|
1008
|
+
* sequential gets, and a resumed key one SELECT instead of one per
|
|
1009
|
+
* surviving attempt. The earliest surviving row is the durable retry
|
|
1010
|
+
* origin (issue #69) and the highest attempt contiguous from it the
|
|
1011
|
+
* resumed counter (issue #59). This read is deliberately unbounded; the
|
|
1012
|
+
* pruned-prefix tolerance that makes it agree with the point-probe
|
|
1013
|
+
* fallback is applied once, by the caller, in
|
|
1014
|
+
* `internal/AttemptProbe.probeAttempts` (issue #96).
|
|
1015
|
+
*/
|
|
1016
|
+
const attemptSurvivors = Effect.fn("DurableEngineState.attemptSurvivors")((runId, stepKeyDigest) => sql `
|
|
1017
|
+
SELECT attempt AS "attempt", started_at_ms AS "startedAtMs"
|
|
1018
|
+
FROM flows_attempts
|
|
1019
|
+
WHERE run_id = ${runId}
|
|
1020
|
+
AND step_key_digest = ${stepKeyDigest}
|
|
1021
|
+
ORDER BY attempt
|
|
1022
|
+
`.pipe(Effect.orDie, Effect.map((rows) => {
|
|
1023
|
+
const first = rows[0];
|
|
1024
|
+
if (first === undefined)
|
|
1025
|
+
return Option.none();
|
|
1026
|
+
let latest = Number(first.attempt);
|
|
1027
|
+
for (let index = 1; index < rows.length; index++) {
|
|
1028
|
+
const next = Number(rows[index].attempt);
|
|
1029
|
+
if (next !== latest + 1)
|
|
1030
|
+
break;
|
|
1031
|
+
latest = next;
|
|
1032
|
+
}
|
|
1033
|
+
return Option.some({
|
|
1034
|
+
earliestAttempt: Number(first.attempt),
|
|
1035
|
+
earliestStartedAtMs: Number(first.startedAtMs),
|
|
1036
|
+
latest
|
|
1037
|
+
});
|
|
1038
|
+
})));
|
|
1039
|
+
const selectRunParent = (childId, parentId) => sql `
|
|
1040
|
+
SELECT
|
|
1041
|
+
child_id AS "childId",
|
|
1042
|
+
parent_id AS "parentId",
|
|
1043
|
+
seq AS "seq"
|
|
1044
|
+
FROM flows_run_parents
|
|
1045
|
+
WHERE child_id = ${childId}
|
|
1046
|
+
AND parent_id = ${parentId}
|
|
1047
|
+
`;
|
|
1048
|
+
const parentIdsOf = (id) => sql `
|
|
1049
|
+
SELECT
|
|
1050
|
+
child_id AS "childId",
|
|
1051
|
+
parent_id AS "parentId",
|
|
1052
|
+
seq AS "seq"
|
|
1053
|
+
FROM flows_run_parents
|
|
1054
|
+
WHERE child_id = ${id}
|
|
1055
|
+
ORDER BY seq
|
|
1056
|
+
`.pipe(Effect.orDie, Effect.map((rows) => rows.map((row) => String(row.parentId))));
|
|
1057
|
+
const recordRunParent = Effect.fn("DurableEngineState.recordRunParent")((childId, parentId) => writer.write(Effect.gen(function* () {
|
|
1058
|
+
// MAX(seq)+1 and the insert share one write transaction, so the
|
|
1059
|
+
// assigned seq is a total insertion order over the store.
|
|
1060
|
+
const inserted = yield* sql `
|
|
1061
|
+
INSERT INTO flows_run_parents (child_id, parent_id, seq)
|
|
1062
|
+
SELECT ${childId}, ${parentId}, COALESCE(MAX(seq), 0) + 1
|
|
1063
|
+
FROM flows_run_parents
|
|
1064
|
+
WHERE TRUE
|
|
1065
|
+
ON CONFLICT (child_id, parent_id) DO NOTHING
|
|
1066
|
+
RETURNING
|
|
1067
|
+
child_id AS "childId",
|
|
1068
|
+
parent_id AS "parentId",
|
|
1069
|
+
seq AS "seq"
|
|
1070
|
+
`;
|
|
1071
|
+
if (inserted[0] !== undefined) {
|
|
1072
|
+
// The cycle check shares the insert's transaction: a hit fails the
|
|
1073
|
+
// effect, rolling the insert back, so a rejected edge is never
|
|
1074
|
+
// durable and there is no check-then-record window for another
|
|
1075
|
+
// writer to slip through (issues #54/#55/#56). The insert
|
|
1076
|
+
// precedes the walk, so the transaction holds the write lock
|
|
1077
|
+
// while walking: under `DurableWriter.write`'s documented contract —
|
|
1078
|
+
// write transactions are mutually serialized (issue #74) — of two
|
|
1079
|
+
// edges that jointly close a cycle, exactly the later one fails.
|
|
1080
|
+
// This is a contract requirement, not a SQLite artifact; a
|
|
1081
|
+
// backend that ran writes at READ COMMITTED would break it and is
|
|
1082
|
+
// excluded by the DurableWriter contract.
|
|
1083
|
+
const cycle = yield* findCyclePath(childId, parentId, parentIdsOf);
|
|
1084
|
+
if (cycle !== undefined) {
|
|
1085
|
+
return yield* Effect.fail(new RunParentCycleError({ path: cycle }));
|
|
1086
|
+
}
|
|
1087
|
+
return {
|
|
1088
|
+
_tag: "Recorded",
|
|
1089
|
+
edge: yield* decodeRunParentEdge(inserted[0])
|
|
1090
|
+
};
|
|
1091
|
+
}
|
|
1092
|
+
const existing = yield* selectRunParent(childId, parentId);
|
|
1093
|
+
if (existing[0] === undefined) {
|
|
1094
|
+
return yield* Effect.die(new EngineStateFault({ reason: "run_parent_edge_missing" }));
|
|
1095
|
+
}
|
|
1096
|
+
return {
|
|
1097
|
+
_tag: "Existing",
|
|
1098
|
+
edge: yield* decodeRunParentEdge(existing[0])
|
|
1099
|
+
};
|
|
1100
|
+
})).pipe(Effect.catch((error) => error instanceof RunParentCycleError ? Effect.fail(error) : Effect.die(error))));
|
|
1101
|
+
const removeRunParentsForRun = Effect.fn("DurableEngineState.removeRunParentsForRun")((runId) => writer.write(sql `
|
|
1102
|
+
DELETE FROM flows_run_parents
|
|
1103
|
+
WHERE child_id = ${runId}
|
|
1104
|
+
OR parent_id = ${runId}
|
|
1105
|
+
`).pipe(Effect.orDie, Effect.asVoid));
|
|
1106
|
+
// Keep state-first ordering. This gate and DurableWriter's shared permit are
|
|
1107
|
+
// both taken at the caller's interruptibility, so an interruptible caller can
|
|
1108
|
+
// still be cancelled while queued, including behind a journal-only write that
|
|
1109
|
+
// skips this gate.
|
|
1110
|
+
// A nested SQL transaction already owns the connection and bypasses this gate.
|
|
1111
|
+
const transactionGate = Semaphore.makeUnsafe(1);
|
|
1112
|
+
const transaction = (effect) => Effect.flatMap(Effect.serviceOption(sql.transactionService), (enclosing) => {
|
|
1113
|
+
const write = (body) => writer.write(body).pipe(Effect.catchCause((cause) => {
|
|
1114
|
+
// Only a sole storage failure is normalized. A composite keeps every
|
|
1115
|
+
// reason for the caller to diagnose, and its storage failures are
|
|
1116
|
+
// demoted to defects one by one rather than cast through the
|
|
1117
|
+
// declared error type: `transaction` promises the BODY's failures,
|
|
1118
|
+
// so a `DatabaseError` arriving as a typed failure would be a lie
|
|
1119
|
+
// the compiler could not catch. Defect identity is unchanged.
|
|
1120
|
+
if (cause.reasons.length === 1) {
|
|
1121
|
+
const reason = cause.reasons[0];
|
|
1122
|
+
if (Cause.isFailReason(reason) && reason.error instanceof DatabaseError) {
|
|
1123
|
+
return Effect.die(reason.error);
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
return Effect.failCause(dieOnDatabaseError(cause));
|
|
1127
|
+
}));
|
|
1128
|
+
if (Option.isSome(enclosing))
|
|
1129
|
+
return write(effect);
|
|
1130
|
+
return Effect.uninterruptibleMask((restore) => Effect.gen(function* () {
|
|
1131
|
+
yield* restore(transactionGate.take(1));
|
|
1132
|
+
let released = false;
|
|
1133
|
+
const release = Effect.suspend(() => {
|
|
1134
|
+
if (released)
|
|
1135
|
+
return Effect.void;
|
|
1136
|
+
released = true;
|
|
1137
|
+
return transactionGate.release(1).pipe(Effect.asVoid);
|
|
1138
|
+
});
|
|
1139
|
+
return yield* restore(write(Effect.gen(function* () {
|
|
1140
|
+
// Release at COMMIT, before publication can read through another
|
|
1141
|
+
// transaction. A failed write instead releases in ensuring below.
|
|
1142
|
+
yield* afterCommit(release, sql);
|
|
1143
|
+
return yield* effect;
|
|
1144
|
+
}))).pipe(Effect.ensuring(release));
|
|
1145
|
+
}));
|
|
1146
|
+
});
|
|
1147
|
+
const runParents = Effect.fn("DurableEngineState.runParents")((childId) => sql `
|
|
1148
|
+
SELECT
|
|
1149
|
+
child_id AS "childId",
|
|
1150
|
+
parent_id AS "parentId",
|
|
1151
|
+
seq AS "seq"
|
|
1152
|
+
FROM flows_run_parents
|
|
1153
|
+
WHERE child_id = ${childId}
|
|
1154
|
+
ORDER BY seq
|
|
1155
|
+
`.pipe(Effect.orDie,
|
|
1156
|
+
// One unreadable edge made a run's whole ancestry unreadable, so no
|
|
1157
|
+
// subflow under it could be linked at all.
|
|
1158
|
+
Effect.flatMap((rows) => decodeSweep(rows, "run parent", runParentRowKey, decodeRunParentEdgeResult))));
|
|
1159
|
+
const runChildren = Effect.fn("DurableEngineState.runChildren")((parentId) => sql `
|
|
1160
|
+
SELECT
|
|
1161
|
+
child_id AS "childId",
|
|
1162
|
+
parent_id AS "parentId",
|
|
1163
|
+
seq AS "seq"
|
|
1164
|
+
FROM flows_run_parents
|
|
1165
|
+
WHERE parent_id = ${parentId}
|
|
1166
|
+
ORDER BY seq
|
|
1167
|
+
`.pipe(Effect.orDie,
|
|
1168
|
+
// The cancellation cascade's only cross-process way to find a run's
|
|
1169
|
+
// children: one unreadable sibling edge left every child running after
|
|
1170
|
+
// its parent was cancelled.
|
|
1171
|
+
Effect.flatMap((rows) => decodeSweep(rows, "run parent", runParentRowKey, decodeRunParentEdgeResult))));
|
|
1172
|
+
return DurableEngineState.of({
|
|
1173
|
+
deferred,
|
|
1174
|
+
consumeDeferred,
|
|
1175
|
+
completeDeferred,
|
|
1176
|
+
clock,
|
|
1177
|
+
scheduleClock,
|
|
1178
|
+
completeClock,
|
|
1179
|
+
completeRunClocks,
|
|
1180
|
+
dueClocks,
|
|
1181
|
+
pendingClocks,
|
|
1182
|
+
completedDeferreds,
|
|
1183
|
+
park,
|
|
1184
|
+
wake,
|
|
1185
|
+
waiting,
|
|
1186
|
+
waitingTree,
|
|
1187
|
+
waitingRuns,
|
|
1188
|
+
pendingRuns,
|
|
1189
|
+
pendingRunTail,
|
|
1190
|
+
pendingCancellationRuns,
|
|
1191
|
+
pendingCancellationTail,
|
|
1192
|
+
staleRunningRuns,
|
|
1193
|
+
attemptSurvivors,
|
|
1194
|
+
recordRunParent,
|
|
1195
|
+
removeRunParentsForRun,
|
|
1196
|
+
runParents,
|
|
1197
|
+
runChildren,
|
|
1198
|
+
transaction
|
|
1199
|
+
});
|
|
1200
|
+
});
|
|
1201
|
+
/**
|
|
1202
|
+
* Provides database-backed durable engine state.
|
|
1203
|
+
*
|
|
1204
|
+
* @since 0.1.0
|
|
1205
|
+
* @category layers
|
|
1206
|
+
*/
|
|
1207
|
+
export const layer = Layer.effect(DurableEngineState, make);
|
|
1208
|
+
/**
|
|
1209
|
+
* Constructs a deterministic in-memory durable-state implementation.
|
|
1210
|
+
*
|
|
1211
|
+
* The returned service can be shared by multiple fresh engine instances in a
|
|
1212
|
+
* test to model process restart over the same storage. With `runs` supplied
|
|
1213
|
+
* it enforces the same ownership fences as the SQL implementation and is
|
|
1214
|
+
* held to the same contract suite
|
|
1215
|
+
* (`test/contract/DurableEngineStateContract.ts`).
|
|
1216
|
+
*
|
|
1217
|
+
* @since 0.1.0
|
|
1218
|
+
* @category constructors
|
|
1219
|
+
*/
|
|
1220
|
+
export const makeMemory = (options = {}) => {
|
|
1221
|
+
const deferreds = new Map();
|
|
1222
|
+
const consumedDeferreds = new Map();
|
|
1223
|
+
const clocks = new Map();
|
|
1224
|
+
const waitingRows = new Map();
|
|
1225
|
+
// childId -> (parentId -> seq); `parentSeq` mirrors the SQL MAX(seq)+1.
|
|
1226
|
+
const parentEdges = new Map();
|
|
1227
|
+
let parentSeq = 0;
|
|
1228
|
+
const snapshotDeferred = (row) => Schema.decodeUnknownEffect(DeferredRow)(row).pipe(Effect.orDie, Effect.flatMap((decoded) => Effect.all({
|
|
1229
|
+
exitJson: encodeJson(decoded.exit, "exit"),
|
|
1230
|
+
metadataJson: decoded.metadata === undefined
|
|
1231
|
+
? Effect.succeed(null)
|
|
1232
|
+
: encodeJson(decoded.metadata, "metadata")
|
|
1233
|
+
}).pipe(Effect.flatMap(({ exitJson, metadataJson }) => decodeDeferredRow({
|
|
1234
|
+
flowName: decoded.flowName,
|
|
1235
|
+
executionId: decoded.executionId,
|
|
1236
|
+
deferredName: decoded.deferredName,
|
|
1237
|
+
exitJson,
|
|
1238
|
+
metadataJson,
|
|
1239
|
+
completedAtMs: decoded.completedAtMs
|
|
1240
|
+
})))));
|
|
1241
|
+
const snapshotClock = (row) => Schema.decodeUnknownEffect(ClockRow)(row).pipe(Effect.orDie, Effect.map((decoded) => ({ ...decoded })));
|
|
1242
|
+
const snapshotWaiting = (row) => ({ ...row });
|
|
1243
|
+
/** The synchronous twin of `findCyclePath` over the in-memory edge map. */
|
|
1244
|
+
const findCyclePathSync = (childId, parentId) => {
|
|
1245
|
+
if (parentId === childId)
|
|
1246
|
+
return [childId];
|
|
1247
|
+
const seen = new Set([parentId]);
|
|
1248
|
+
const walk = (current, chain) => {
|
|
1249
|
+
for (const parent of parentEdges.get(current)?.keys() ?? []) {
|
|
1250
|
+
if (parent === childId)
|
|
1251
|
+
return [...chain, parent].reverse();
|
|
1252
|
+
if (seen.has(parent))
|
|
1253
|
+
continue;
|
|
1254
|
+
seen.add(parent);
|
|
1255
|
+
const found = walk(parent, [...chain, parent]);
|
|
1256
|
+
if (found !== undefined)
|
|
1257
|
+
return found;
|
|
1258
|
+
}
|
|
1259
|
+
return undefined;
|
|
1260
|
+
};
|
|
1261
|
+
return walk(parentId, [parentId]);
|
|
1262
|
+
};
|
|
1263
|
+
const sameOwner = (left, right) => left.hostId === right.hostId && left.pid === right.pid && left.nonce === right.nonce;
|
|
1264
|
+
/** Mirrors the SQL `flows_runs` owner-match predicate for a run. */
|
|
1265
|
+
const runView = (runId, presented) => {
|
|
1266
|
+
if (options.runs === undefined) {
|
|
1267
|
+
return { exists: true, running: true, owned: true };
|
|
1268
|
+
}
|
|
1269
|
+
const view = options.runs(runId);
|
|
1270
|
+
if (Option.isNone(view)) {
|
|
1271
|
+
return { exists: false, running: false, owned: false };
|
|
1272
|
+
}
|
|
1273
|
+
const owned = presented !== undefined &&
|
|
1274
|
+
view.value.owner !== null &&
|
|
1275
|
+
sameOwner(view.value.owner, presented);
|
|
1276
|
+
return { exists: true, running: view.value.status === "running", owned };
|
|
1277
|
+
};
|
|
1278
|
+
/**
|
|
1279
|
+
* Mirrors the SQL sweeps' run-status join: a row whose run has settled is
|
|
1280
|
+
* never swept, and `Option.none()` from a supplied `runs` view means the run
|
|
1281
|
+
* does not exist, so its orphaned engine-state rows stay hidden. The
|
|
1282
|
+
* permissive default (no `runs` view) treats every run as live because a
|
|
1283
|
+
* standalone memory store has no run table to consult.
|
|
1284
|
+
*/
|
|
1285
|
+
const isLive = (runId) => {
|
|
1286
|
+
if (options.runs === undefined)
|
|
1287
|
+
return true;
|
|
1288
|
+
const view = options.runs(runId);
|
|
1289
|
+
return Option.isSome(view) && (view.value.status === "pending" ||
|
|
1290
|
+
view.value.status === "running" ||
|
|
1291
|
+
view.value.status === "suspended");
|
|
1292
|
+
};
|
|
1293
|
+
const pendingMemoryRows = (flowName, after, through, eligibleBeforeMs, cancellationOnly = false) => Array.from(options.listRuns?.() ?? [])
|
|
1294
|
+
.filter(([runId, view]) => view.status === "pending" && (flowName === undefined || view.flowName === flowName) &&
|
|
1295
|
+
(!cancellationOnly || view.cancelRequestedAtMs != null) &&
|
|
1296
|
+
(eligibleBeforeMs === undefined || (view.createdAtMs ?? 0) < eligibleBeforeMs) &&
|
|
1297
|
+
(after === undefined || (view.createdAtMs ?? 0) > after.createdAtMs ||
|
|
1298
|
+
((view.createdAtMs ?? 0) === after.createdAtMs && compareText(runId, after.runId) > 0)) &&
|
|
1299
|
+
(through === undefined || (view.createdAtMs ?? 0) < through.createdAtMs ||
|
|
1300
|
+
((view.createdAtMs ?? 0) === through.createdAtMs && compareText(runId, through.runId) <= 0)))
|
|
1301
|
+
.sort(([leftId, left], [rightId, right]) => (left.createdAtMs ?? 0) - (right.createdAtMs ?? 0) || compareText(leftId, rightId))
|
|
1302
|
+
.map(([runId, view]) => ({ runId, createdAtMs: view.createdAtMs ?? 0 }));
|
|
1303
|
+
const unguarded = {
|
|
1304
|
+
deferred: Effect.fn("DurableEngineState.deferred")((address) => Effect.gen(function* () {
|
|
1305
|
+
const row = deferreds.get(deferredKey(address));
|
|
1306
|
+
return row === undefined ? Option.none() : Option.some(yield* snapshotDeferred(row));
|
|
1307
|
+
})),
|
|
1308
|
+
consumeDeferred: Effect.fn("DurableEngineState.consumeDeferred")((address, consumedAtMs) => Effect.sync(() => {
|
|
1309
|
+
const key = deferredKey(address);
|
|
1310
|
+
if (!deferreds.has(key) || consumedDeferreds.has(key))
|
|
1311
|
+
return false;
|
|
1312
|
+
consumedDeferreds.set(key, consumedAtMs);
|
|
1313
|
+
return true;
|
|
1314
|
+
})),
|
|
1315
|
+
completeDeferred: Effect.fn("DurableEngineState.completeDeferred")((row) => Effect.gen(function* () {
|
|
1316
|
+
const stored = yield* snapshotDeferred(row);
|
|
1317
|
+
const key = deferredKey(stored);
|
|
1318
|
+
const existing = deferreds.get(key);
|
|
1319
|
+
if (existing !== undefined) {
|
|
1320
|
+
return {
|
|
1321
|
+
_tag: "Existing",
|
|
1322
|
+
row: yield* snapshotDeferred(existing),
|
|
1323
|
+
digest: yield* memoryDigest(existing)
|
|
1324
|
+
};
|
|
1325
|
+
}
|
|
1326
|
+
deferreds.set(key, stored);
|
|
1327
|
+
return {
|
|
1328
|
+
_tag: "Completed",
|
|
1329
|
+
row: yield* snapshotDeferred(stored),
|
|
1330
|
+
digest: yield* memoryDigest(stored)
|
|
1331
|
+
};
|
|
1332
|
+
})),
|
|
1333
|
+
clock: Effect.fn("DurableEngineState.clock")((address) => Effect.gen(function* () {
|
|
1334
|
+
const row = clocks.get(clockKey(address));
|
|
1335
|
+
return row === undefined ? Option.none() : Option.some(yield* snapshotClock(row));
|
|
1336
|
+
})),
|
|
1337
|
+
scheduleClock: Effect.fn("DurableEngineState.scheduleClock")((row, owner) => Effect.gen(function* () {
|
|
1338
|
+
// Mirrors the SQL fence: creation requires the presented owner to
|
|
1339
|
+
// currently run the execution; a lost fence surfaces as
|
|
1340
|
+
// self-interruption, an existing row wins regardless.
|
|
1341
|
+
const stored = yield* snapshotClock(row);
|
|
1342
|
+
const key = clockKey(stored);
|
|
1343
|
+
const existing = clocks.get(key);
|
|
1344
|
+
if (existing !== undefined) {
|
|
1345
|
+
return { _tag: "Existing", row: yield* snapshotClock(existing) };
|
|
1346
|
+
}
|
|
1347
|
+
const view = runView(stored.executionId, owner);
|
|
1348
|
+
if (!view.exists || !view.running || !view.owned) {
|
|
1349
|
+
return yield* Effect.interrupt;
|
|
1350
|
+
}
|
|
1351
|
+
clocks.set(key, stored);
|
|
1352
|
+
return { _tag: "Scheduled", row: yield* snapshotClock(stored) };
|
|
1353
|
+
})),
|
|
1354
|
+
completeClock: Effect.fn("DurableEngineState.completeClock")((address, completedAtMs) => Effect.sync(() => {
|
|
1355
|
+
const key = clockKey(address);
|
|
1356
|
+
const existing = clocks.get(key);
|
|
1357
|
+
if (existing === undefined) {
|
|
1358
|
+
return { _tag: "NotFound" };
|
|
1359
|
+
}
|
|
1360
|
+
if (existing.completedAtMs !== null) {
|
|
1361
|
+
return { _tag: "AlreadyCompleted", row: { ...existing } };
|
|
1362
|
+
}
|
|
1363
|
+
const row = { ...existing, completedAtMs };
|
|
1364
|
+
clocks.set(key, row);
|
|
1365
|
+
return { _tag: "Completed", row: { ...row } };
|
|
1366
|
+
})),
|
|
1367
|
+
dueClocks: Effect.fn("DurableEngineState.dueClocks")((nowMs) => Effect.forEach(Array.from(clocks.values())
|
|
1368
|
+
.filter((row) => row.completedAtMs === null && row.dueAtMs <= nowMs)
|
|
1369
|
+
.sort((left, right) => left.dueAtMs - right.dueAtMs ||
|
|
1370
|
+
compareText(left.executionId, right.executionId) ||
|
|
1371
|
+
compareText(left.clockName, right.clockName)), snapshotClock)),
|
|
1372
|
+
completeRunClocks: Effect.fn("DurableEngineState.completeRunClocks")((executionId, completedAtMs) => Effect.sync(() => {
|
|
1373
|
+
const completed = [];
|
|
1374
|
+
for (const [key, row] of clocks) {
|
|
1375
|
+
if (row.executionId !== executionId || row.completedAtMs !== null)
|
|
1376
|
+
continue;
|
|
1377
|
+
const next = { ...row, completedAtMs };
|
|
1378
|
+
clocks.set(key, next);
|
|
1379
|
+
completed.push({ ...next });
|
|
1380
|
+
}
|
|
1381
|
+
return completed.sort((left, right) => compareText(left.flowName, right.flowName) || compareText(left.clockName, right.clockName));
|
|
1382
|
+
})),
|
|
1383
|
+
pendingClocks: Effect.fn("DurableEngineState.pendingClocks")((scope) => Effect.flatMap(Effect.sync(() => Array.from(clocks.values())
|
|
1384
|
+
.filter((row) => row.completedAtMs === null &&
|
|
1385
|
+
isLive(row.executionId) &&
|
|
1386
|
+
(scope.executionId === undefined || row.executionId === scope.executionId) &&
|
|
1387
|
+
(scope.flowName === undefined || row.flowName === scope.flowName))
|
|
1388
|
+
.sort((left, right) => left.dueAtMs - right.dueAtMs ||
|
|
1389
|
+
compareText(left.executionId, right.executionId) ||
|
|
1390
|
+
compareText(left.clockName, right.clockName))), (rows) => Effect.forEach(rows, snapshotClock))),
|
|
1391
|
+
completedDeferreds: Effect.fn("DurableEngineState.completedDeferreds")((flowName) => Effect.sync(() => Array.from(deferreds.values())
|
|
1392
|
+
.filter((row) => row.flowName === flowName && isLive(row.executionId) && !consumedDeferreds.has(deferredKey(row)))
|
|
1393
|
+
.map(({ flowName, executionId, deferredName }) => ({
|
|
1394
|
+
flowName,
|
|
1395
|
+
executionId,
|
|
1396
|
+
deferredName
|
|
1397
|
+
}))
|
|
1398
|
+
.sort((left, right) => compareText(left.executionId, right.executionId) ||
|
|
1399
|
+
compareText(left.deferredName, right.deferredName)))),
|
|
1400
|
+
park: Effect.fn("DurableEngineState.park")((runId, waitingPayload, owner) => Effect.sync(() => {
|
|
1401
|
+
// Mirrors the SQL fence: only the current owner of an existing run
|
|
1402
|
+
// may park it; anything else reports NotFound, exactly like the
|
|
1403
|
+
// owner-guarded UPDATE matching no row.
|
|
1404
|
+
const view = runView(runId, owner);
|
|
1405
|
+
if (!view.exists || !view.owned) {
|
|
1406
|
+
return { _tag: "NotFound" };
|
|
1407
|
+
}
|
|
1408
|
+
// The declared question is stored as JSON TEXT and read back decoded,
|
|
1409
|
+
// exactly as the column and its SELECT do it.
|
|
1410
|
+
const row = {
|
|
1411
|
+
runId,
|
|
1412
|
+
reason: waitingPayload.reason,
|
|
1413
|
+
wakeAt: waitingPayload.wakeAt ?? null,
|
|
1414
|
+
token: waitingPayload.token ?? null,
|
|
1415
|
+
...decodeWaitingRequest(waitingPayload.request)
|
|
1416
|
+
};
|
|
1417
|
+
waitingRows.set(runId, row);
|
|
1418
|
+
return { _tag: "Parked", row: snapshotWaiting(row) };
|
|
1419
|
+
})),
|
|
1420
|
+
wake: Effect.fn("DurableEngineState.wake")((runId) => Effect.sync(() => {
|
|
1421
|
+
const row = waitingRows.get(runId);
|
|
1422
|
+
if (row === undefined) {
|
|
1423
|
+
// Mirrors SQL: an unknown run is NotFound, an existing unparked
|
|
1424
|
+
// run is NotWaiting.
|
|
1425
|
+
return runView(runId, undefined).exists
|
|
1426
|
+
? { _tag: "NotWaiting" }
|
|
1427
|
+
: { _tag: "NotFound" };
|
|
1428
|
+
}
|
|
1429
|
+
waitingRows.delete(runId);
|
|
1430
|
+
return { _tag: "Woken", row: snapshotWaiting(row) };
|
|
1431
|
+
})),
|
|
1432
|
+
waiting: Effect.fn("DurableEngineState.waiting")((runId) => Effect.sync(() => {
|
|
1433
|
+
const row = waitingRows.get(runId);
|
|
1434
|
+
return row === undefined ? Option.none() : Option.some(snapshotWaiting(row));
|
|
1435
|
+
})),
|
|
1436
|
+
waitingTree: Effect.fn("DurableEngineState.waitingTree")((runId, walk) => Effect.sync(() => {
|
|
1437
|
+
// Breadth-first over recorded spawn edges: the named run first, then
|
|
1438
|
+
// each generation beneath it. This memory view does not model the
|
|
1439
|
+
// parent_run_id trampoline links that the SQL walk also follows.
|
|
1440
|
+
const found = [];
|
|
1441
|
+
const seen = new Set([runId]);
|
|
1442
|
+
let frontier = [runId];
|
|
1443
|
+
for (let depth = 0; depth <= waitingTreeMaxDepth && frontier.length > 0; depth++) {
|
|
1444
|
+
const next = [];
|
|
1445
|
+
for (const current of frontier) {
|
|
1446
|
+
const row = waitingRows.get(current);
|
|
1447
|
+
if (row !== undefined && isLive(current))
|
|
1448
|
+
found.push(snapshotWaiting(row));
|
|
1449
|
+
for (const [childId, parents] of parentEdges) {
|
|
1450
|
+
if (!parents.has(current) || seen.has(childId))
|
|
1451
|
+
continue;
|
|
1452
|
+
// The SQL walk skips a detached subtree; so does this one, for a
|
|
1453
|
+
// view that records the policy. A view that does not describes an
|
|
1454
|
+
// attached child, which is the default.
|
|
1455
|
+
const view = options.runs?.(childId);
|
|
1456
|
+
if (walk?.detached !== "include" && view !== undefined && Option.isSome(view) &&
|
|
1457
|
+
view.value.onParentExit === "detach")
|
|
1458
|
+
continue;
|
|
1459
|
+
seen.add(childId);
|
|
1460
|
+
next.push(childId);
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
frontier = next;
|
|
1464
|
+
}
|
|
1465
|
+
return found;
|
|
1466
|
+
})),
|
|
1467
|
+
waitingRuns: Effect.fn("DurableEngineState.waitingRuns")((filter) => Effect.sync(() => Array.from(waitingRows.values())
|
|
1468
|
+
// Mirrors the SQL status predicate: a terminally closed run's
|
|
1469
|
+
// stale waiting row never surfaces to a sweeper (issue #28). The
|
|
1470
|
+
// permissive default (no `runs` view) treats every run as live.
|
|
1471
|
+
.filter((row) => isLive(row.runId))
|
|
1472
|
+
.filter((row) => filter?.reason === undefined || row.reason === filter.reason)
|
|
1473
|
+
.filter((row) => filter?.dueBeforeMs === undefined ||
|
|
1474
|
+
(row.wakeAt !== null && row.wakeAt <= filter.dueBeforeMs))
|
|
1475
|
+
// Mirrors the SQL cancel predicate over
|
|
1476
|
+
// `flows_runs.cancel_requested_at_ms` (issue #68). Without a
|
|
1477
|
+
// `runs` view the flag is unknowable, so the query stays
|
|
1478
|
+
// permissive and the sweeper's own per-row guard decides.
|
|
1479
|
+
.filter((row) => {
|
|
1480
|
+
if (filter?.cancelRequested !== true)
|
|
1481
|
+
return true;
|
|
1482
|
+
if (options.runs === undefined)
|
|
1483
|
+
return true;
|
|
1484
|
+
const view = options.runs(row.runId);
|
|
1485
|
+
return Option.isSome(view) && view.value.cancelRequestedAtMs != null;
|
|
1486
|
+
})
|
|
1487
|
+
.sort((left, right) => Number(left.wakeAt === null) - Number(right.wakeAt === null) ||
|
|
1488
|
+
(left.wakeAt ?? 0) - (right.wakeAt ?? 0) ||
|
|
1489
|
+
compareText(left.runId, right.runId))
|
|
1490
|
+
.map(snapshotWaiting))),
|
|
1491
|
+
pendingRuns: Effect.fn("DurableEngineState.pendingRuns")((flowName, limit, after, through, eligibleBeforeMs) => Effect.sync(() => {
|
|
1492
|
+
const ordered = pendingMemoryRows(flowName, after, through, eligibleBeforeMs);
|
|
1493
|
+
return limit === undefined ? ordered : ordered.slice(0, limit);
|
|
1494
|
+
})),
|
|
1495
|
+
pendingRunTail: Effect.fn("DurableEngineState.pendingRunTail")((flowName, eligibleBeforeMs) => Effect.sync(() => Option.fromNullishOr(pendingMemoryRows(flowName, undefined, undefined, eligibleBeforeMs).at(-1)))),
|
|
1496
|
+
pendingCancellationRuns: Effect.fn("DurableEngineState.pendingCancellationRuns")((limit, after, through) => pendingCancellationLimit(limit).pipe(Effect.andThen(Effect.sync(() => pendingMemoryRows(undefined, after, through, undefined, true).slice(0, limit))))),
|
|
1497
|
+
pendingCancellationTail: Effect.fn("DurableEngineState.pendingCancellationTail")(() => Effect.sync(() => Option.fromNullishOr(pendingMemoryRows(undefined, undefined, undefined, undefined, true).at(-1)))),
|
|
1498
|
+
staleRunningRuns: Effect.fn("DurableEngineState.staleRunningRuns")((staleBeforeMs, limit) => Effect.sync(() => {
|
|
1499
|
+
// Mirrors the SQL scan of `flows_runs`: without an enumerator there
|
|
1500
|
+
// is nothing to scan, so no stale rows exist.
|
|
1501
|
+
if (options.listRuns === undefined)
|
|
1502
|
+
return [];
|
|
1503
|
+
const stale = [];
|
|
1504
|
+
for (const [runId, view] of options.listRuns()) {
|
|
1505
|
+
const heartbeatAtMs = view.heartbeatAtMs;
|
|
1506
|
+
if (view.status === "running" &&
|
|
1507
|
+
typeof heartbeatAtMs === "number" &&
|
|
1508
|
+
heartbeatAtMs < staleBeforeMs) {
|
|
1509
|
+
stale.push({ runId, heartbeatAtMs });
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
const ordered = stale.sort((left, right) => left.heartbeatAtMs - right.heartbeatAtMs ||
|
|
1513
|
+
compareText(left.runId, right.runId));
|
|
1514
|
+
// Mirrors the SQL LIMIT: oldest heartbeats first, capped per sweep
|
|
1515
|
+
// (issue #79).
|
|
1516
|
+
return limit === undefined ? ordered : ordered.slice(0, limit);
|
|
1517
|
+
})),
|
|
1518
|
+
recordRunParent: Effect.fn("DurableEngineState.recordRunParent")((childId, parentId) =>
|
|
1519
|
+
// A single synchronous step mirrors the SQL transaction: the cycle
|
|
1520
|
+
// check and the insert are atomic — no fiber can interleave between
|
|
1521
|
+
// them, and a rejected edge is never observable (issues #54/#55/#56).
|
|
1522
|
+
Effect.suspend(() => {
|
|
1523
|
+
const parents = parentEdges.get(childId) ?? new Map();
|
|
1524
|
+
const existing = parents.get(parentId);
|
|
1525
|
+
if (existing !== undefined) {
|
|
1526
|
+
return Effect.succeed({
|
|
1527
|
+
_tag: "Existing",
|
|
1528
|
+
edge: { childId, parentId, seq: existing }
|
|
1529
|
+
});
|
|
1530
|
+
}
|
|
1531
|
+
const cycle = findCyclePathSync(childId, parentId);
|
|
1532
|
+
if (cycle !== undefined) {
|
|
1533
|
+
return Effect.fail(new RunParentCycleError({ path: cycle }));
|
|
1534
|
+
}
|
|
1535
|
+
parentSeq += 1;
|
|
1536
|
+
parents.set(parentId, parentSeq);
|
|
1537
|
+
parentEdges.set(childId, parents);
|
|
1538
|
+
return Effect.succeed({
|
|
1539
|
+
_tag: "Recorded",
|
|
1540
|
+
edge: { childId, parentId, seq: parentSeq }
|
|
1541
|
+
});
|
|
1542
|
+
})),
|
|
1543
|
+
removeRunParentsForRun: Effect.fn("DurableEngineState.removeRunParentsForRun")((runId) => Effect.sync(() => {
|
|
1544
|
+
parentEdges.delete(runId);
|
|
1545
|
+
for (const [childId, parents] of parentEdges) {
|
|
1546
|
+
parents.delete(runId);
|
|
1547
|
+
if (parents.size === 0)
|
|
1548
|
+
parentEdges.delete(childId);
|
|
1549
|
+
}
|
|
1550
|
+
})),
|
|
1551
|
+
runParents: Effect.fn("DurableEngineState.runParents")((childId) => Effect.sync(() => Array.from(parentEdges.get(childId) ?? [])
|
|
1552
|
+
.map(([parentId, seq]) => ({ childId, parentId, seq }))
|
|
1553
|
+
.sort((left, right) => left.seq - right.seq))),
|
|
1554
|
+
runChildren: Effect.fn("DurableEngineState.runChildren")((parentId) => Effect.sync(() => {
|
|
1555
|
+
const edges = [];
|
|
1556
|
+
for (const [childId, parents] of parentEdges) {
|
|
1557
|
+
const seq = parents.get(parentId);
|
|
1558
|
+
if (seq !== undefined)
|
|
1559
|
+
edges.push({ childId, parentId, seq });
|
|
1560
|
+
}
|
|
1561
|
+
return edges.sort((left, right) => left.seq - right.seq);
|
|
1562
|
+
}))
|
|
1563
|
+
};
|
|
1564
|
+
const gate = Semaphore.makeUnsafe(1);
|
|
1565
|
+
const transactionDepth = new Map();
|
|
1566
|
+
const guard = (effect) => Effect.flatMap(Effect.fiberId, (fiberId) => (transactionDepth.get(fiberId) ?? 0) > 0 ? effect : gate.withPermit(effect));
|
|
1567
|
+
const restoreMap = (target, source) => {
|
|
1568
|
+
target.clear();
|
|
1569
|
+
for (const [key, value] of source)
|
|
1570
|
+
target.set(key, value);
|
|
1571
|
+
};
|
|
1572
|
+
const transaction = (effect, options) => Effect.flatMap(Effect.fiberId, (fiberId) => Effect.uninterruptibleMask((restore) => Effect.gen(function* () {
|
|
1573
|
+
const nested = (transactionDepth.get(fiberId) ?? 0) > 0;
|
|
1574
|
+
if (!nested)
|
|
1575
|
+
yield* restore(gate.take(1));
|
|
1576
|
+
const before = {
|
|
1577
|
+
deferreds: new Map(deferreds),
|
|
1578
|
+
consumedDeferreds: new Map(consumedDeferreds),
|
|
1579
|
+
clocks: new Map(clocks),
|
|
1580
|
+
waitingRows: new Map(waitingRows),
|
|
1581
|
+
parentEdges: new Map([...parentEdges].map(([child, parents]) => [child, new Map(parents)])),
|
|
1582
|
+
parentSeq
|
|
1583
|
+
};
|
|
1584
|
+
transactionDepth.set(fiberId, (transactionDepth.get(fiberId) ?? 0) + 1);
|
|
1585
|
+
let committed = false;
|
|
1586
|
+
let closed = false;
|
|
1587
|
+
const close = Effect.suspend(() => {
|
|
1588
|
+
if (closed)
|
|
1589
|
+
return Effect.void;
|
|
1590
|
+
closed = true;
|
|
1591
|
+
const depth = transactionDepth.get(fiberId) - 1;
|
|
1592
|
+
if (depth === 0)
|
|
1593
|
+
transactionDepth.delete(fiberId);
|
|
1594
|
+
else
|
|
1595
|
+
transactionDepth.set(fiberId, depth);
|
|
1596
|
+
return nested ? Effect.void : gate.release(1).pipe(Effect.asVoid);
|
|
1597
|
+
});
|
|
1598
|
+
const commit = Effect.sync(() => {
|
|
1599
|
+
committed = true;
|
|
1600
|
+
}).pipe(Effect.andThen(close));
|
|
1601
|
+
return yield* Effect.sync(() => options?.onCommit(commit)).pipe(Effect.andThen(restore(effect)), Effect.catchCause((cause) => Effect.sync(() => {
|
|
1602
|
+
if (committed)
|
|
1603
|
+
return;
|
|
1604
|
+
restoreMap(deferreds, before.deferreds);
|
|
1605
|
+
restoreMap(consumedDeferreds, before.consumedDeferreds);
|
|
1606
|
+
restoreMap(clocks, before.clocks);
|
|
1607
|
+
restoreMap(waitingRows, before.waitingRows);
|
|
1608
|
+
restoreMap(parentEdges, before.parentEdges);
|
|
1609
|
+
parentSeq = before.parentSeq;
|
|
1610
|
+
}).pipe(Effect.andThen(Effect.failCause(cause)))), Effect.ensuring(close));
|
|
1611
|
+
})));
|
|
1612
|
+
return DurableEngineState.of({
|
|
1613
|
+
deferred: (address) => guard(unguarded.deferred(address)),
|
|
1614
|
+
consumeDeferred: (address, consumedAtMs) => guard(unguarded.consumeDeferred(address, consumedAtMs)),
|
|
1615
|
+
completeDeferred: (row) => guard(unguarded.completeDeferred(row)),
|
|
1616
|
+
clock: (address) => guard(unguarded.clock(address)),
|
|
1617
|
+
scheduleClock: (row, owner) => guard(unguarded.scheduleClock(row, owner)),
|
|
1618
|
+
completeClock: (address, completedAtMs) => guard(unguarded.completeClock(address, completedAtMs)),
|
|
1619
|
+
dueClocks: (nowMs) => guard(unguarded.dueClocks(nowMs)),
|
|
1620
|
+
completeRunClocks: (executionId, completedAtMs) => guard(unguarded.completeRunClocks(executionId, completedAtMs)),
|
|
1621
|
+
pendingClocks: (scope) => guard(unguarded.pendingClocks(scope)),
|
|
1622
|
+
completedDeferreds: (flowName) => guard(unguarded.completedDeferreds(flowName)),
|
|
1623
|
+
park: (runId, waiting, owner) => guard(unguarded.park(runId, waiting, owner)),
|
|
1624
|
+
wake: (runId) => guard(unguarded.wake(runId)),
|
|
1625
|
+
waiting: (runId) => guard(unguarded.waiting(runId)),
|
|
1626
|
+
waitingTree: (runId, options) => guard(unguarded.waitingTree(runId, options)),
|
|
1627
|
+
waitingRuns: (filter) => guard(unguarded.waitingRuns(filter)),
|
|
1628
|
+
pendingRuns: (flowName, limit, after, through, eligibleBeforeMs) => guard(unguarded.pendingRuns(flowName, limit, after, through, eligibleBeforeMs)),
|
|
1629
|
+
pendingRunTail: (flowName, eligibleBeforeMs) => guard(unguarded.pendingRunTail(flowName, eligibleBeforeMs)),
|
|
1630
|
+
pendingCancellationRuns: (limit, after, through) => guard(unguarded.pendingCancellationRuns(limit, after, through)),
|
|
1631
|
+
pendingCancellationTail: () => guard(unguarded.pendingCancellationTail()),
|
|
1632
|
+
staleRunningRuns: (staleBeforeMs, limit) => guard(unguarded.staleRunningRuns(staleBeforeMs, limit)),
|
|
1633
|
+
attemptSurvivors: undefined,
|
|
1634
|
+
recordRunParent: (childId, parentId) => guard(unguarded.recordRunParent(childId, parentId)),
|
|
1635
|
+
removeRunParentsForRun: (runId) => guard(unguarded.removeRunParentsForRun(runId)),
|
|
1636
|
+
runParents: (childId) => guard(unguarded.runParents(childId)),
|
|
1637
|
+
runChildren: (parentId) => guard(unguarded.runChildren(parentId)),
|
|
1638
|
+
transaction
|
|
1639
|
+
});
|
|
1640
|
+
};
|
|
1641
|
+
/**
|
|
1642
|
+
* Provides deterministic in-memory durable engine state.
|
|
1643
|
+
*
|
|
1644
|
+
* @since 0.1.0
|
|
1645
|
+
* @category layers
|
|
1646
|
+
*/
|
|
1647
|
+
export const layerMemory = Layer.sync(DurableEngineState, makeMemory);
|
|
1648
|
+
//# sourceMappingURL=DurableEngineState.js.map
|