@smthrs/flow 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 +160 -0
- package/LICENSE +21 -0
- package/README.md +210 -2
- package/THIRD_PARTY_NOTICES.md +52 -0
- package/dist/cjs/Action/Action.d.ts +250 -0
- package/dist/cjs/Action/Action.d.ts.map +1 -0
- package/dist/cjs/Action/Action.js +42 -0
- package/dist/cjs/Action/Action.js.map +7 -0
- package/dist/cjs/Action/AttemptTimedOut.d.ts +33 -0
- package/dist/cjs/Action/AttemptTimedOut.d.ts.map +1 -0
- package/dist/cjs/Action/AttemptTimedOut.js +51 -0
- package/dist/cjs/Action/AttemptTimedOut.js.map +7 -0
- package/dist/cjs/Action/BoundaryMode.d.ts +29 -0
- package/dist/cjs/Action/BoundaryMode.d.ts.map +1 -0
- package/dist/cjs/Action/BoundaryMode.js +36 -0
- package/dist/cjs/Action/BoundaryMode.js.map +7 -0
- package/dist/cjs/Action/CacheEnvironment.d.ts +158 -0
- package/dist/cjs/Action/CacheEnvironment.d.ts.map +1 -0
- package/dist/cjs/Action/CacheEnvironment.js +57 -0
- package/dist/cjs/Action/CacheEnvironment.js.map +7 -0
- package/dist/cjs/Action/ConcurrentKeylessDispatch.d.ts +34 -0
- package/dist/cjs/Action/ConcurrentKeylessDispatch.d.ts.map +1 -0
- package/dist/cjs/Action/ConcurrentKeylessDispatch.js +46 -0
- package/dist/cjs/Action/ConcurrentKeylessDispatch.js.map +7 -0
- package/dist/cjs/Action/Context.d.ts +131 -0
- package/dist/cjs/Action/Context.d.ts.map +1 -0
- package/dist/cjs/Action/Context.js +68 -0
- package/dist/cjs/Action/Context.js.map +7 -0
- package/dist/cjs/Action/DuplicateImplementation.d.ts +20 -0
- package/dist/cjs/Action/DuplicateImplementation.d.ts.map +1 -0
- package/dist/cjs/Action/DuplicateImplementation.js +46 -0
- package/dist/cjs/Action/DuplicateImplementation.js.map +7 -0
- package/dist/cjs/Action/FileBoundary.d.ts +59 -0
- package/dist/cjs/Action/FileBoundary.d.ts.map +1 -0
- package/dist/cjs/Action/FileBoundary.js +81 -0
- package/dist/cjs/Action/FileBoundary.js.map +7 -0
- package/dist/cjs/Action/FileInput.d.ts +28 -0
- package/dist/cjs/Action/FileInput.d.ts.map +1 -0
- package/dist/cjs/Action/FileInput.js +42 -0
- package/dist/cjs/Action/FileInput.js.map +7 -0
- package/dist/cjs/Action/Filegroup.d.ts +16 -0
- package/dist/cjs/Action/Filegroup.d.ts.map +1 -0
- package/dist/cjs/Action/Filegroup.js +27 -0
- package/dist/cjs/Action/Filegroup.js.map +7 -0
- package/dist/cjs/Action/Glob.d.ts +8 -0
- package/dist/cjs/Action/Glob.d.ts.map +1 -0
- package/dist/cjs/Action/Glob.js +28 -0
- package/dist/cjs/Action/Glob.js.map +7 -0
- package/dist/cjs/Action/ImplementationVersionMismatch.d.ts +26 -0
- package/dist/cjs/Action/ImplementationVersionMismatch.d.ts.map +1 -0
- package/dist/cjs/Action/ImplementationVersionMismatch.js +45 -0
- package/dist/cjs/Action/ImplementationVersionMismatch.js.map +7 -0
- package/dist/cjs/Action/Implementations.d.ts +114 -0
- package/dist/cjs/Action/Implementations.d.ts.map +1 -0
- package/dist/cjs/Action/Implementations.js +65 -0
- package/dist/cjs/Action/Implementations.js.map +7 -0
- package/dist/cjs/Action/InfraInterrupt.d.ts +26 -0
- package/dist/cjs/Action/InfraInterrupt.d.ts.map +1 -0
- package/dist/cjs/Action/InfraInterrupt.js +46 -0
- package/dist/cjs/Action/InfraInterrupt.js.map +7 -0
- package/dist/cjs/Action/InfraInterruptRetriesExhausted.d.ts +33 -0
- package/dist/cjs/Action/InfraInterruptRetriesExhausted.d.ts.map +1 -0
- package/dist/cjs/Action/InfraInterruptRetriesExhausted.js +54 -0
- package/dist/cjs/Action/InfraInterruptRetriesExhausted.js.map +7 -0
- package/dist/cjs/Action/IrreversibleRetryRequiresIdempotencyKey.d.ts +26 -0
- package/dist/cjs/Action/IrreversibleRetryRequiresIdempotencyKey.d.ts.map +1 -0
- package/dist/cjs/Action/IrreversibleRetryRequiresIdempotencyKey.js +47 -0
- package/dist/cjs/Action/IrreversibleRetryRequiresIdempotencyKey.js.map +7 -0
- package/dist/cjs/Action/StepIdentity.d.ts +153 -0
- package/dist/cjs/Action/StepIdentity.d.ts.map +1 -0
- package/dist/cjs/Action/StepIdentity.js +75 -0
- package/dist/cjs/Action/StepIdentity.js.map +7 -0
- package/dist/cjs/Action/TreeArtifact.d.ts +8 -0
- package/dist/cjs/Action/TreeArtifact.d.ts.map +1 -0
- package/dist/cjs/Action/TreeArtifact.js +25 -0
- package/dist/cjs/Action/TreeArtifact.js.map +7 -0
- package/dist/cjs/Action/TypeId.d.ts +8 -0
- package/dist/cjs/Action/TypeId.d.ts.map +1 -0
- package/dist/cjs/Action/TypeId.js +25 -0
- package/dist/cjs/Action/TypeId.js.map +7 -0
- package/dist/cjs/Action/UncanonicalIdempotencyKey.d.ts +32 -0
- package/dist/cjs/Action/UncanonicalIdempotencyKey.d.ts.map +1 -0
- package/dist/cjs/Action/UncanonicalIdempotencyKey.js +51 -0
- package/dist/cjs/Action/UncanonicalIdempotencyKey.js.map +7 -0
- package/dist/cjs/Action/idempotencyKey.d.ts +21 -0
- package/dist/cjs/Action/idempotencyKey.d.ts.map +1 -0
- package/dist/cjs/Action/idempotencyKey.js +59 -0
- package/dist/cjs/Action/idempotencyKey.js.map +7 -0
- package/dist/cjs/Action/index.d.ts +29 -0
- package/dist/cjs/Action/index.d.ts.map +1 -0
- package/dist/cjs/Action/index.js +60 -0
- package/dist/cjs/Action/index.js.map +7 -0
- package/dist/cjs/Action/make.d.ts +128 -0
- package/dist/cjs/Action/make.d.ts.map +1 -0
- package/dist/cjs/Action/make.js +373 -0
- package/dist/cjs/Action/make.js.map +7 -0
- package/dist/cjs/Action/raceAll.d.ts +21 -0
- package/dist/cjs/Action/raceAll.d.ts.map +1 -0
- package/dist/cjs/Action/raceAll.js +46 -0
- package/dist/cjs/Action/raceAll.js.map +7 -0
- package/dist/cjs/Action/retry.d.ts +19 -0
- package/dist/cjs/Action/retry.d.ts.map +1 -0
- package/dist/cjs/Action/retry.js +75 -0
- package/dist/cjs/Action/retry.js.map +7 -0
- package/dist/cjs/Deadline.d.ts +105 -0
- package/dist/cjs/Deadline.d.ts.map +1 -0
- package/dist/cjs/Deadline.js +104 -0
- package/dist/cjs/Deadline.js.map +7 -0
- package/dist/cjs/DurableClock.d.ts +61 -0
- package/dist/cjs/DurableClock.d.ts.map +1 -0
- package/dist/cjs/DurableClock.js +90 -0
- package/dist/cjs/DurableClock.js.map +7 -0
- package/dist/cjs/DurableDeferred.d.ts +311 -0
- package/dist/cjs/DurableDeferred.d.ts.map +1 -0
- package/dist/cjs/DurableDeferred.js +322 -0
- package/dist/cjs/DurableDeferred.js.map +7 -0
- package/dist/cjs/DurableQueue.d.ts +160 -0
- package/dist/cjs/DurableQueue.d.ts.map +1 -0
- package/dist/cjs/DurableQueue.js +229 -0
- package/dist/cjs/DurableQueue.js.map +7 -0
- package/dist/cjs/ExternalJob.d.ts +179 -0
- package/dist/cjs/ExternalJob.d.ts.map +1 -0
- package/dist/cjs/ExternalJob.js +338 -0
- package/dist/cjs/ExternalJob.js.map +7 -0
- package/dist/cjs/Fault.d.ts +131 -0
- package/dist/cjs/Fault.d.ts.map +1 -0
- package/dist/cjs/Fault.js +157 -0
- package/dist/cjs/Fault.js.map +7 -0
- package/dist/cjs/Flow/Annotations.d.ts +245 -0
- package/dist/cjs/Flow/Annotations.d.ts.map +1 -0
- package/dist/cjs/Flow/Annotations.js +72 -0
- package/dist/cjs/Flow/Annotations.js.map +7 -0
- package/dist/cjs/Flow/CapabilityCeiling.d.ts +21 -0
- package/dist/cjs/Flow/CapabilityCeiling.d.ts.map +1 -0
- package/dist/cjs/Flow/CapabilityCeiling.js +49 -0
- package/dist/cjs/Flow/CapabilityCeiling.js.map +7 -0
- package/dist/cjs/Flow/DeadlineExceeded.d.ts +33 -0
- package/dist/cjs/Flow/DeadlineExceeded.d.ts.map +1 -0
- package/dist/cjs/Flow/DeadlineExceeded.js +52 -0
- package/dist/cjs/Flow/DeadlineExceeded.js.map +7 -0
- package/dist/cjs/Flow/ExecutionIdRequired.d.ts +25 -0
- package/dist/cjs/Flow/ExecutionIdRequired.d.ts.map +1 -0
- package/dist/cjs/Flow/ExecutionIdRequired.js +46 -0
- package/dist/cjs/Flow/ExecutionIdRequired.js.map +7 -0
- package/dist/cjs/Flow/ExecutionIds.d.ts +80 -0
- package/dist/cjs/Flow/ExecutionIds.d.ts.map +1 -0
- package/dist/cjs/Flow/ExecutionIds.js +66 -0
- package/dist/cjs/Flow/ExecutionIds.js.map +7 -0
- package/dist/cjs/Flow/Flow.d.ts +422 -0
- package/dist/cjs/Flow/Flow.d.ts.map +1 -0
- package/dist/cjs/Flow/Flow.js +17 -0
- package/dist/cjs/Flow/Flow.js.map +7 -0
- package/dist/cjs/Flow/MaxRoundsExceeded.d.ts +35 -0
- package/dist/cjs/Flow/MaxRoundsExceeded.d.ts.map +1 -0
- package/dist/cjs/Flow/MaxRoundsExceeded.js +50 -0
- package/dist/cjs/Flow/MaxRoundsExceeded.js.map +7 -0
- package/dist/cjs/Flow/Outcome.d.ts +162 -0
- package/dist/cjs/Flow/Outcome.d.ts.map +1 -0
- package/dist/cjs/Flow/Outcome.js +80 -0
- package/dist/cjs/Flow/Outcome.js.map +7 -0
- package/dist/cjs/Flow/Result.d.ts +186 -0
- package/dist/cjs/Flow/Result.d.ts.map +1 -0
- package/dist/cjs/Flow/Result.js +159 -0
- package/dist/cjs/Flow/Result.js.map +7 -0
- package/dist/cjs/Flow/Runtime.d.ts +98 -0
- package/dist/cjs/Flow/Runtime.d.ts.map +1 -0
- package/dist/cjs/Flow/Runtime.js +160 -0
- package/dist/cjs/Flow/Runtime.js.map +7 -0
- package/dist/cjs/Flow/TypeId.d.ts +8 -0
- package/dist/cjs/Flow/TypeId.d.ts.map +1 -0
- package/dist/cjs/Flow/TypeId.js +25 -0
- package/dist/cjs/Flow/TypeId.js.map +7 -0
- package/dist/cjs/Flow/index.d.ts +17 -0
- package/dist/cjs/Flow/index.d.ts.map +1 -0
- package/dist/cjs/Flow/index.js +29 -0
- package/dist/cjs/Flow/index.js.map +7 -0
- package/dist/cjs/Flow/internal.d.ts +34 -0
- package/dist/cjs/Flow/internal.d.ts.map +1 -0
- package/dist/cjs/Flow/internal.js +187 -0
- package/dist/cjs/Flow/internal.js.map +7 -0
- package/dist/cjs/Flow/make.d.ts +134 -0
- package/dist/cjs/Flow/make.d.ts.map +1 -0
- package/dist/cjs/Flow/make.js +104 -0
- package/dist/cjs/Flow/make.js.map +7 -0
- package/dist/cjs/FlowRuntime/CancelRequestFailed.d.ts +55 -0
- package/dist/cjs/FlowRuntime/CancelRequestFailed.d.ts.map +1 -0
- package/dist/cjs/FlowRuntime/CancelRequestFailed.js +51 -0
- package/dist/cjs/FlowRuntime/CancelRequestFailed.js.map +7 -0
- package/dist/cjs/FlowRuntime/ExecutionIdentityConflict.d.ts +23 -0
- package/dist/cjs/FlowRuntime/ExecutionIdentityConflict.d.ts.map +1 -0
- package/dist/cjs/FlowRuntime/ExecutionIdentityConflict.js +55 -0
- package/dist/cjs/FlowRuntime/ExecutionIdentityConflict.js.map +7 -0
- package/dist/cjs/FlowRuntime/FlowCycleDetected.d.ts +28 -0
- package/dist/cjs/FlowRuntime/FlowCycleDetected.d.ts.map +1 -0
- package/dist/cjs/FlowRuntime/FlowCycleDetected.js +49 -0
- package/dist/cjs/FlowRuntime/FlowCycleDetected.js.map +7 -0
- package/dist/cjs/FlowRuntime/FlowExecutionNotFound.d.ts +38 -0
- package/dist/cjs/FlowRuntime/FlowExecutionNotFound.d.ts.map +1 -0
- package/dist/cjs/FlowRuntime/FlowExecutionNotFound.js +53 -0
- package/dist/cjs/FlowRuntime/FlowExecutionNotFound.js.map +7 -0
- package/dist/cjs/FlowRuntime/FlowInstance.d.ts +115 -0
- package/dist/cjs/FlowRuntime/FlowInstance.d.ts.map +1 -0
- package/dist/cjs/FlowRuntime/FlowInstance.js +37 -0
- package/dist/cjs/FlowRuntime/FlowInstance.js.map +7 -0
- package/dist/cjs/FlowRuntime/FlowRuntime.d.ts +198 -0
- package/dist/cjs/FlowRuntime/FlowRuntime.d.ts.map +1 -0
- package/dist/cjs/FlowRuntime/FlowRuntime.js +37 -0
- package/dist/cjs/FlowRuntime/FlowRuntime.js.map +7 -0
- package/dist/cjs/FlowRuntime/NodeRecord.d.ts +175 -0
- package/dist/cjs/FlowRuntime/NodeRecord.d.ts.map +1 -0
- package/dist/cjs/FlowRuntime/NodeRecord.js +17 -0
- package/dist/cjs/FlowRuntime/NodeRecord.js.map +7 -0
- package/dist/cjs/FlowRuntime/WaitingAnnotation.d.ts +69 -0
- package/dist/cjs/FlowRuntime/WaitingAnnotation.d.ts.map +1 -0
- package/dist/cjs/FlowRuntime/WaitingAnnotation.js +53 -0
- package/dist/cjs/FlowRuntime/WaitingAnnotation.js.map +7 -0
- package/dist/cjs/FlowRuntime/index.d.ts +14 -0
- package/dist/cjs/FlowRuntime/index.d.ts.map +1 -0
- package/dist/cjs/FlowRuntime/index.js +26 -0
- package/dist/cjs/FlowRuntime/index.js.map +7 -0
- package/dist/cjs/Graph.d.ts +325 -0
- package/dist/cjs/Graph.d.ts.map +1 -0
- package/dist/cjs/Graph.js +1021 -0
- package/dist/cjs/Graph.js.map +7 -0
- package/dist/cjs/HumanTask.d.ts +440 -0
- package/dist/cjs/HumanTask.d.ts.map +1 -0
- package/dist/cjs/HumanTask.js +413 -0
- package/dist/cjs/HumanTask.js.map +7 -0
- package/dist/cjs/Interpreter.d.ts +222 -0
- package/dist/cjs/Interpreter.d.ts.map +1 -0
- package/dist/cjs/Interpreter.js +888 -0
- package/dist/cjs/Interpreter.js.map +7 -0
- package/dist/cjs/Poll.d.ts +238 -0
- package/dist/cjs/Poll.d.ts.map +1 -0
- package/dist/cjs/Poll.js +150 -0
- package/dist/cjs/Poll.js.map +7 -0
- package/dist/cjs/RetryPolicy.d.ts +273 -0
- package/dist/cjs/RetryPolicy.d.ts.map +1 -0
- package/dist/cjs/RetryPolicy.js +251 -0
- package/dist/cjs/RetryPolicy.js.map +7 -0
- package/dist/cjs/Sleep.d.ts +98 -0
- package/dist/cjs/Sleep.d.ts.map +1 -0
- package/dist/cjs/Sleep.js +133 -0
- package/dist/cjs/Sleep.js.map +7 -0
- package/dist/cjs/Stall.d.ts +173 -0
- package/dist/cjs/Stall.d.ts.map +1 -0
- package/dist/cjs/Stall.js +92 -0
- package/dist/cjs/Stall.js.map +7 -0
- package/dist/cjs/WaitFor.d.ts +109 -0
- package/dist/cjs/WaitFor.d.ts.map +1 -0
- package/dist/cjs/WaitFor.js +117 -0
- package/dist/cjs/WaitFor.js.map +7 -0
- package/dist/cjs/index.d.ts +107 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +69 -0
- package/dist/cjs/index.js.map +7 -0
- package/dist/cjs/internal/BoundedJson.d.ts +48 -0
- package/dist/cjs/internal/BoundedJson.d.ts.map +1 -0
- package/dist/cjs/internal/BoundedJson.js +96 -0
- package/dist/cjs/internal/BoundedJson.js.map +7 -0
- package/dist/cjs/internal/DeclarationSite.d.ts +135 -0
- package/dist/cjs/internal/DeclarationSite.d.ts.map +1 -0
- package/dist/cjs/internal/DeclarationSite.js +89 -0
- package/dist/cjs/internal/DeclarationSite.js.map +7 -0
- package/dist/cjs/internal/Declarations.d.ts +35 -0
- package/dist/cjs/internal/Declarations.d.ts.map +1 -0
- package/dist/cjs/internal/Declarations.js +50 -0
- package/dist/cjs/internal/Declarations.js.map +7 -0
- package/dist/cjs/internal/ExecutionMiddleware.d.ts +21 -0
- package/dist/cjs/internal/ExecutionMiddleware.d.ts.map +1 -0
- package/dist/cjs/internal/ExecutionMiddleware.js +39 -0
- package/dist/cjs/internal/ExecutionMiddleware.js.map +7 -0
- package/dist/cjs/internal/JsonSchemaSubset.d.ts +133 -0
- package/dist/cjs/internal/JsonSchemaSubset.d.ts.map +1 -0
- package/dist/cjs/internal/JsonSchemaSubset.js +229 -0
- package/dist/cjs/internal/JsonSchemaSubset.js.map +7 -0
- package/dist/cjs/internal/OutcomeMarker.d.ts +18 -0
- package/dist/cjs/internal/OutcomeMarker.d.ts.map +1 -0
- package/dist/cjs/internal/OutcomeMarker.js +27 -0
- package/dist/cjs/internal/OutcomeMarker.js.map +7 -0
- package/dist/cjs/package.json +1 -0
- package/dist/esm/Action/Action.d.ts +250 -0
- package/dist/esm/Action/Action.d.ts.map +1 -0
- package/dist/esm/Action/Action.js +30 -0
- package/dist/esm/Action/Action.js.map +1 -0
- package/dist/esm/Action/AttemptTimedOut.d.ts +33 -0
- package/dist/esm/Action/AttemptTimedOut.d.ts.map +1 -0
- package/dist/esm/Action/AttemptTimedOut.js +32 -0
- package/dist/esm/Action/AttemptTimedOut.js.map +1 -0
- package/dist/esm/Action/BoundaryMode.d.ts +29 -0
- package/dist/esm/Action/BoundaryMode.d.ts.map +1 -0
- package/dist/esm/Action/BoundaryMode.js +23 -0
- package/dist/esm/Action/BoundaryMode.js.map +1 -0
- package/dist/esm/Action/CacheEnvironment.d.ts +158 -0
- package/dist/esm/Action/CacheEnvironment.d.ts.map +1 -0
- package/dist/esm/Action/CacheEnvironment.js +130 -0
- package/dist/esm/Action/CacheEnvironment.js.map +1 -0
- package/dist/esm/Action/ConcurrentKeylessDispatch.d.ts +34 -0
- package/dist/esm/Action/ConcurrentKeylessDispatch.d.ts.map +1 -0
- package/dist/esm/Action/ConcurrentKeylessDispatch.js +33 -0
- package/dist/esm/Action/ConcurrentKeylessDispatch.js.map +1 -0
- package/dist/esm/Action/Context.d.ts +131 -0
- package/dist/esm/Action/Context.d.ts.map +1 -0
- package/dist/esm/Action/Context.js +99 -0
- package/dist/esm/Action/Context.js.map +1 -0
- package/dist/esm/Action/DuplicateImplementation.d.ts +20 -0
- package/dist/esm/Action/DuplicateImplementation.d.ts.map +1 -0
- package/dist/esm/Action/DuplicateImplementation.js +20 -0
- package/dist/esm/Action/DuplicateImplementation.js.map +1 -0
- package/dist/esm/Action/FileBoundary.d.ts +59 -0
- package/dist/esm/Action/FileBoundary.d.ts.map +1 -0
- package/dist/esm/Action/FileBoundary.js +60 -0
- package/dist/esm/Action/FileBoundary.js.map +1 -0
- package/dist/esm/Action/FileInput.d.ts +28 -0
- package/dist/esm/Action/FileInput.d.ts.map +1 -0
- package/dist/esm/Action/FileInput.js +32 -0
- package/dist/esm/Action/FileInput.js.map +1 -0
- package/dist/esm/Action/Filegroup.d.ts +16 -0
- package/dist/esm/Action/Filegroup.d.ts.map +1 -0
- package/dist/esm/Action/Filegroup.js +15 -0
- package/dist/esm/Action/Filegroup.js.map +1 -0
- package/dist/esm/Action/Glob.d.ts +8 -0
- package/dist/esm/Action/Glob.d.ts.map +1 -0
- package/dist/esm/Action/Glob.js +7 -0
- package/dist/esm/Action/Glob.js.map +1 -0
- package/dist/esm/Action/ImplementationVersionMismatch.d.ts +26 -0
- package/dist/esm/Action/ImplementationVersionMismatch.d.ts.map +1 -0
- package/dist/esm/Action/ImplementationVersionMismatch.js +24 -0
- package/dist/esm/Action/ImplementationVersionMismatch.js.map +1 -0
- package/dist/esm/Action/Implementations.d.ts +114 -0
- package/dist/esm/Action/Implementations.d.ts.map +1 -0
- package/dist/esm/Action/Implementations.js +94 -0
- package/dist/esm/Action/Implementations.js.map +1 -0
- package/dist/esm/Action/InfraInterrupt.d.ts +26 -0
- package/dist/esm/Action/InfraInterrupt.d.ts.map +1 -0
- package/dist/esm/Action/InfraInterrupt.js +25 -0
- package/dist/esm/Action/InfraInterrupt.js.map +1 -0
- package/dist/esm/Action/InfraInterruptRetriesExhausted.d.ts +33 -0
- package/dist/esm/Action/InfraInterruptRetriesExhausted.d.ts.map +1 -0
- package/dist/esm/Action/InfraInterruptRetriesExhausted.js +32 -0
- package/dist/esm/Action/InfraInterruptRetriesExhausted.js.map +1 -0
- package/dist/esm/Action/IrreversibleRetryRequiresIdempotencyKey.d.ts +26 -0
- package/dist/esm/Action/IrreversibleRetryRequiresIdempotencyKey.d.ts.map +1 -0
- package/dist/esm/Action/IrreversibleRetryRequiresIdempotencyKey.js +25 -0
- package/dist/esm/Action/IrreversibleRetryRequiresIdempotencyKey.js.map +1 -0
- package/dist/esm/Action/StepIdentity.d.ts +153 -0
- package/dist/esm/Action/StepIdentity.d.ts.map +1 -0
- package/dist/esm/Action/StepIdentity.js +139 -0
- package/dist/esm/Action/StepIdentity.js.map +1 -0
- package/dist/esm/Action/TreeArtifact.d.ts +8 -0
- package/dist/esm/Action/TreeArtifact.d.ts.map +1 -0
- package/dist/esm/Action/TreeArtifact.js +7 -0
- package/dist/esm/Action/TreeArtifact.js.map +1 -0
- package/dist/esm/Action/TypeId.d.ts +8 -0
- package/dist/esm/Action/TypeId.d.ts.map +1 -0
- package/dist/esm/Action/TypeId.js +9 -0
- package/dist/esm/Action/TypeId.js.map +1 -0
- package/dist/esm/Action/UncanonicalIdempotencyKey.d.ts +32 -0
- package/dist/esm/Action/UncanonicalIdempotencyKey.d.ts.map +1 -0
- package/dist/esm/Action/UncanonicalIdempotencyKey.js +31 -0
- package/dist/esm/Action/UncanonicalIdempotencyKey.js.map +1 -0
- package/dist/esm/Action/idempotencyKey.d.ts +21 -0
- package/dist/esm/Action/idempotencyKey.d.ts.map +1 -0
- package/dist/esm/Action/idempotencyKey.js +50 -0
- package/dist/esm/Action/idempotencyKey.js.map +1 -0
- package/dist/esm/Action/index.d.ts +29 -0
- package/dist/esm/Action/index.d.ts.map +1 -0
- package/dist/esm/Action/index.js +30 -0
- package/dist/esm/Action/index.js.map +1 -0
- package/dist/esm/Action/make.d.ts +128 -0
- package/dist/esm/Action/make.d.ts.map +1 -0
- package/dist/esm/Action/make.js +405 -0
- package/dist/esm/Action/make.js.map +1 -0
- package/dist/esm/Action/raceAll.d.ts +21 -0
- package/dist/esm/Action/raceAll.d.ts.map +1 -0
- package/dist/esm/Action/raceAll.js +20 -0
- package/dist/esm/Action/raceAll.js.map +1 -0
- package/dist/esm/Action/retry.d.ts +19 -0
- package/dist/esm/Action/retry.d.ts.map +1 -0
- package/dist/esm/Action/retry.js +72 -0
- package/dist/esm/Action/retry.js.map +1 -0
- package/dist/esm/Deadline.d.ts +105 -0
- package/dist/esm/Deadline.d.ts.map +1 -0
- package/dist/esm/Deadline.js +152 -0
- package/dist/esm/Deadline.js.map +1 -0
- package/dist/esm/DurableClock.d.ts +61 -0
- package/dist/esm/DurableClock.d.ts.map +1 -0
- package/dist/esm/DurableClock.js +75 -0
- package/dist/esm/DurableClock.js.map +1 -0
- package/dist/esm/DurableDeferred.d.ts +311 -0
- package/dist/esm/DurableDeferred.d.ts.map +1 -0
- package/dist/esm/DurableDeferred.js +357 -0
- package/dist/esm/DurableDeferred.js.map +1 -0
- package/dist/esm/DurableQueue.d.ts +160 -0
- package/dist/esm/DurableQueue.d.ts.map +1 -0
- package/dist/esm/DurableQueue.js +303 -0
- package/dist/esm/DurableQueue.js.map +1 -0
- package/dist/esm/ExternalJob.d.ts +179 -0
- package/dist/esm/ExternalJob.d.ts.map +1 -0
- package/dist/esm/ExternalJob.js +296 -0
- package/dist/esm/ExternalJob.js.map +1 -0
- package/dist/esm/Fault.d.ts +131 -0
- package/dist/esm/Fault.d.ts.map +1 -0
- package/dist/esm/Fault.js +198 -0
- package/dist/esm/Fault.js.map +1 -0
- package/dist/esm/Flow/Annotations.d.ts +245 -0
- package/dist/esm/Flow/Annotations.d.ts.map +1 -0
- package/dist/esm/Flow/Annotations.js +110 -0
- package/dist/esm/Flow/Annotations.js.map +1 -0
- package/dist/esm/Flow/CapabilityCeiling.d.ts +21 -0
- package/dist/esm/Flow/CapabilityCeiling.d.ts.map +1 -0
- package/dist/esm/Flow/CapabilityCeiling.js +27 -0
- package/dist/esm/Flow/CapabilityCeiling.js.map +1 -0
- package/dist/esm/Flow/DeadlineExceeded.d.ts +33 -0
- package/dist/esm/Flow/DeadlineExceeded.d.ts.map +1 -0
- package/dist/esm/Flow/DeadlineExceeded.js +34 -0
- package/dist/esm/Flow/DeadlineExceeded.js.map +1 -0
- package/dist/esm/Flow/ExecutionIdRequired.d.ts +25 -0
- package/dist/esm/Flow/ExecutionIdRequired.d.ts.map +1 -0
- package/dist/esm/Flow/ExecutionIdRequired.js +30 -0
- package/dist/esm/Flow/ExecutionIdRequired.js.map +1 -0
- package/dist/esm/Flow/ExecutionIds.d.ts +80 -0
- package/dist/esm/Flow/ExecutionIds.d.ts.map +1 -0
- package/dist/esm/Flow/ExecutionIds.js +91 -0
- package/dist/esm/Flow/ExecutionIds.js.map +1 -0
- package/dist/esm/Flow/Flow.d.ts +422 -0
- package/dist/esm/Flow/Flow.d.ts.map +1 -0
- package/dist/esm/Flow/Flow.js +3 -0
- package/dist/esm/Flow/Flow.js.map +1 -0
- package/dist/esm/Flow/MaxRoundsExceeded.d.ts +35 -0
- package/dist/esm/Flow/MaxRoundsExceeded.d.ts.map +1 -0
- package/dist/esm/Flow/MaxRoundsExceeded.js +34 -0
- package/dist/esm/Flow/MaxRoundsExceeded.js.map +1 -0
- package/dist/esm/Flow/Outcome.d.ts +162 -0
- package/dist/esm/Flow/Outcome.d.ts.map +1 -0
- package/dist/esm/Flow/Outcome.js +115 -0
- package/dist/esm/Flow/Outcome.js.map +1 -0
- package/dist/esm/Flow/Result.d.ts +186 -0
- package/dist/esm/Flow/Result.d.ts.map +1 -0
- package/dist/esm/Flow/Result.js +171 -0
- package/dist/esm/Flow/Result.js.map +1 -0
- package/dist/esm/Flow/Runtime.d.ts +98 -0
- package/dist/esm/Flow/Runtime.d.ts.map +1 -0
- package/dist/esm/Flow/Runtime.js +231 -0
- package/dist/esm/Flow/Runtime.js.map +1 -0
- package/dist/esm/Flow/TypeId.d.ts +8 -0
- package/dist/esm/Flow/TypeId.d.ts.map +1 -0
- package/dist/esm/Flow/TypeId.js +9 -0
- package/dist/esm/Flow/TypeId.js.map +1 -0
- package/dist/esm/Flow/index.d.ts +17 -0
- package/dist/esm/Flow/index.d.ts.map +1 -0
- package/dist/esm/Flow/index.js +18 -0
- package/dist/esm/Flow/index.js.map +1 -0
- package/dist/esm/Flow/internal.d.ts +34 -0
- package/dist/esm/Flow/internal.d.ts.map +1 -0
- package/dist/esm/Flow/internal.js +149 -0
- package/dist/esm/Flow/internal.js.map +1 -0
- package/dist/esm/Flow/make.d.ts +134 -0
- package/dist/esm/Flow/make.d.ts.map +1 -0
- package/dist/esm/Flow/make.js +106 -0
- package/dist/esm/Flow/make.js.map +1 -0
- package/dist/esm/FlowRuntime/CancelRequestFailed.d.ts +55 -0
- package/dist/esm/FlowRuntime/CancelRequestFailed.d.ts.map +1 -0
- package/dist/esm/FlowRuntime/CancelRequestFailed.js +53 -0
- package/dist/esm/FlowRuntime/CancelRequestFailed.js.map +1 -0
- package/dist/esm/FlowRuntime/ExecutionIdentityConflict.d.ts +23 -0
- package/dist/esm/FlowRuntime/ExecutionIdentityConflict.d.ts.map +1 -0
- package/dist/esm/FlowRuntime/ExecutionIdentityConflict.js +24 -0
- package/dist/esm/FlowRuntime/ExecutionIdentityConflict.js.map +1 -0
- package/dist/esm/FlowRuntime/FlowCycleDetected.d.ts +28 -0
- package/dist/esm/FlowRuntime/FlowCycleDetected.d.ts.map +1 -0
- package/dist/esm/FlowRuntime/FlowCycleDetected.js +35 -0
- package/dist/esm/FlowRuntime/FlowCycleDetected.js.map +1 -0
- package/dist/esm/FlowRuntime/FlowExecutionNotFound.d.ts +38 -0
- package/dist/esm/FlowRuntime/FlowExecutionNotFound.d.ts.map +1 -0
- package/dist/esm/FlowRuntime/FlowExecutionNotFound.js +40 -0
- package/dist/esm/FlowRuntime/FlowExecutionNotFound.js.map +1 -0
- package/dist/esm/FlowRuntime/FlowInstance.d.ts +115 -0
- package/dist/esm/FlowRuntime/FlowInstance.d.ts.map +1 -0
- package/dist/esm/FlowRuntime/FlowInstance.js +24 -0
- package/dist/esm/FlowRuntime/FlowInstance.js.map +1 -0
- package/dist/esm/FlowRuntime/FlowRuntime.d.ts +198 -0
- package/dist/esm/FlowRuntime/FlowRuntime.d.ts.map +1 -0
- package/dist/esm/FlowRuntime/FlowRuntime.js +34 -0
- package/dist/esm/FlowRuntime/FlowRuntime.js.map +1 -0
- package/dist/esm/FlowRuntime/NodeRecord.d.ts +175 -0
- package/dist/esm/FlowRuntime/NodeRecord.d.ts.map +1 -0
- package/dist/esm/FlowRuntime/NodeRecord.js +21 -0
- package/dist/esm/FlowRuntime/NodeRecord.js.map +1 -0
- package/dist/esm/FlowRuntime/WaitingAnnotation.d.ts +69 -0
- package/dist/esm/FlowRuntime/WaitingAnnotation.d.ts.map +1 -0
- package/dist/esm/FlowRuntime/WaitingAnnotation.js +53 -0
- package/dist/esm/FlowRuntime/WaitingAnnotation.js.map +1 -0
- package/dist/esm/FlowRuntime/index.d.ts +14 -0
- package/dist/esm/FlowRuntime/index.d.ts.map +1 -0
- package/dist/esm/FlowRuntime/index.js +15 -0
- package/dist/esm/FlowRuntime/index.js.map +1 -0
- package/dist/esm/Graph.d.ts +325 -0
- package/dist/esm/Graph.d.ts.map +1 -0
- package/dist/esm/Graph.js +1483 -0
- package/dist/esm/Graph.js.map +1 -0
- package/dist/esm/HumanTask.d.ts +440 -0
- package/dist/esm/HumanTask.d.ts.map +1 -0
- package/dist/esm/HumanTask.js +794 -0
- package/dist/esm/HumanTask.js.map +1 -0
- package/dist/esm/Interpreter.d.ts +222 -0
- package/dist/esm/Interpreter.d.ts.map +1 -0
- package/dist/esm/Interpreter.js +1269 -0
- package/dist/esm/Interpreter.js.map +1 -0
- package/dist/esm/Poll.d.ts +238 -0
- package/dist/esm/Poll.d.ts.map +1 -0
- package/dist/esm/Poll.js +282 -0
- package/dist/esm/Poll.js.map +1 -0
- package/dist/esm/RetryPolicy.d.ts +273 -0
- package/dist/esm/RetryPolicy.d.ts.map +1 -0
- package/dist/esm/RetryPolicy.js +429 -0
- package/dist/esm/RetryPolicy.js.map +1 -0
- package/dist/esm/Sleep.d.ts +98 -0
- package/dist/esm/Sleep.d.ts.map +1 -0
- package/dist/esm/Sleep.js +230 -0
- package/dist/esm/Sleep.js.map +1 -0
- package/dist/esm/Stall.d.ts +173 -0
- package/dist/esm/Stall.d.ts.map +1 -0
- package/dist/esm/Stall.js +138 -0
- package/dist/esm/Stall.js.map +1 -0
- package/dist/esm/WaitFor.d.ts +109 -0
- package/dist/esm/WaitFor.d.ts.map +1 -0
- package/dist/esm/WaitFor.js +184 -0
- package/dist/esm/WaitFor.js.map +1 -0
- package/dist/esm/index.d.ts +107 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +108 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/internal/BoundedJson.d.ts +48 -0
- package/dist/esm/internal/BoundedJson.d.ts.map +1 -0
- package/dist/esm/internal/BoundedJson.js +107 -0
- package/dist/esm/internal/BoundedJson.js.map +1 -0
- package/dist/esm/internal/DeclarationSite.d.ts +135 -0
- package/dist/esm/internal/DeclarationSite.d.ts.map +1 -0
- package/dist/esm/internal/DeclarationSite.js +196 -0
- package/dist/esm/internal/DeclarationSite.js.map +1 -0
- package/dist/esm/internal/Declarations.d.ts +35 -0
- package/dist/esm/internal/Declarations.d.ts.map +1 -0
- package/dist/esm/internal/Declarations.js +43 -0
- package/dist/esm/internal/Declarations.js.map +1 -0
- package/dist/esm/internal/ExecutionMiddleware.d.ts +21 -0
- package/dist/esm/internal/ExecutionMiddleware.d.ts.map +1 -0
- package/dist/esm/internal/ExecutionMiddleware.js +13 -0
- package/dist/esm/internal/ExecutionMiddleware.js.map +1 -0
- package/dist/esm/internal/JsonSchemaSubset.d.ts +133 -0
- package/dist/esm/internal/JsonSchemaSubset.d.ts.map +1 -0
- package/dist/esm/internal/JsonSchemaSubset.js +363 -0
- package/dist/esm/internal/JsonSchemaSubset.js.map +1 -0
- package/dist/esm/internal/OutcomeMarker.d.ts +18 -0
- package/dist/esm/internal/OutcomeMarker.d.ts.map +1 -0
- package/dist/esm/internal/OutcomeMarker.js +18 -0
- package/dist/esm/internal/OutcomeMarker.js.map +1 -0
- package/docs/README.md +150 -0
- package/docs/api.md +436 -0
- package/docs/concepts/bodies-and-plans.md +157 -0
- package/docs/concepts/execution-identity.md +142 -0
- package/docs/concepts/flows-and-actions.md +129 -0
- package/docs/concepts/suspension-and-replay.md +130 -0
- package/docs/concepts/the-runtime-port.md +110 -0
- package/docs/concepts/trampoline-rounds.md +151 -0
- package/docs/guides/ask-a-person.md +184 -0
- package/docs/guides/build-a-body.md +161 -0
- package/docs/guides/cancel-and-roll-back.md +112 -0
- package/docs/guides/implement-an-action.md +147 -0
- package/docs/guides/inspect-the-plan.md +154 -0
- package/docs/guides/poll-until-ready.md +167 -0
- package/docs/guides/queue-work-to-a-worker.md +122 -0
- package/docs/guides/retry-a-failing-action.md +243 -0
- package/docs/guides/reuse-a-recorded-result.md +122 -0
- package/docs/guides/run-a-child-flow.md +108 -0
- package/docs/guides/run-an-external-job.md +86 -0
- package/docs/guides/use-a-prompt.md +38 -0
- package/docs/guides/wait-for-a-deadline.md +141 -0
- package/docs/guides/wait-for-an-external-signal.md +151 -0
- package/docs/installation.md +82 -0
- package/docs/quickstart.md +155 -0
- package/docs/reference/flow.md +1831 -0
- package/docs/testing.md +157 -0
- package/docs/troubleshooting.md +286 -0
- package/package.json +327 -3
- package/src/Action/Action.ts +333 -0
- package/src/Action/AttemptTimedOut.ts +37 -0
- package/src/Action/BoundaryMode.ts +33 -0
- package/src/Action/CacheEnvironment.ts +170 -0
- package/src/Action/ConcurrentKeylessDispatch.ts +38 -0
- package/src/Action/Context.ts +156 -0
- package/src/Action/DuplicateImplementation.ts +24 -0
- package/src/Action/FileBoundary.ts +77 -0
- package/src/Action/FileInput.ts +42 -0
- package/src/Action/Filegroup.ts +16 -0
- package/src/Action/Glob.ts +8 -0
- package/src/Action/ImplementationVersionMismatch.ts +27 -0
- package/src/Action/Implementations.ts +149 -0
- package/src/Action/InfraInterrupt.ts +30 -0
- package/src/Action/InfraInterruptRetriesExhausted.ts +37 -0
- package/src/Action/IrreversibleRetryRequiresIdempotencyKey.ts +32 -0
- package/src/Action/StepIdentity.ts +219 -0
- package/src/Action/TreeArtifact.ts +8 -0
- package/src/Action/TypeId.ts +9 -0
- package/src/Action/UncanonicalIdempotencyKey.ts +36 -0
- package/src/Action/idempotencyKey.ts +67 -0
- package/src/Action/index.ts +31 -0
- package/src/Action/make.ts +657 -0
- package/src/Action/raceAll.ts +48 -0
- package/src/Action/retry.ts +92 -0
- package/src/Deadline.ts +185 -0
- package/src/DurableClock.ts +133 -0
- package/src/DurableDeferred.ts +753 -0
- package/src/DurableQueue.ts +519 -0
- package/src/ExternalJob.ts +432 -0
- package/src/Fault.ts +279 -0
- package/src/Flow/Annotations.ts +151 -0
- package/src/Flow/CapabilityCeiling.ts +37 -0
- package/src/Flow/DeadlineExceeded.ts +40 -0
- package/src/Flow/ExecutionIdRequired.ts +36 -0
- package/src/Flow/ExecutionIds.ts +133 -0
- package/src/Flow/Flow.ts +576 -0
- package/src/Flow/MaxRoundsExceeded.ts +39 -0
- package/src/Flow/Outcome.ts +179 -0
- package/src/Flow/Result.ts +284 -0
- package/src/Flow/Runtime.ts +309 -0
- package/src/Flow/TypeId.ts +9 -0
- package/src/Flow/index.ts +19 -0
- package/src/Flow/internal.ts +237 -0
- package/src/Flow/make.ts +279 -0
- package/src/FlowRuntime/CancelRequestFailed.ts +56 -0
- package/src/FlowRuntime/ExecutionIdentityConflict.ts +32 -0
- package/src/FlowRuntime/FlowCycleDetected.ts +41 -0
- package/src/FlowRuntime/FlowExecutionNotFound.ts +46 -0
- package/src/FlowRuntime/FlowInstance.ts +128 -0
- package/src/FlowRuntime/FlowRuntime.ts +333 -0
- package/src/FlowRuntime/NodeRecord.ts +183 -0
- package/src/FlowRuntime/WaitingAnnotation.ts +85 -0
- package/src/FlowRuntime/index.ts +17 -0
- package/src/Graph.ts +1862 -0
- package/src/HumanTask.ts +1003 -0
- package/src/Interpreter.ts +1593 -0
- package/src/Poll.ts +414 -0
- package/src/RetryPolicy.ts +550 -0
- package/src/Sleep.ts +267 -0
- package/src/Stall.ts +217 -0
- package/src/WaitFor.ts +228 -0
- package/src/index.ts +125 -0
- package/src/internal/BoundedJson.ts +109 -0
- package/src/internal/DeclarationSite.ts +214 -0
- package/src/internal/Declarations.ts +49 -0
- package/src/internal/ExecutionMiddleware.ts +29 -0
- package/src/internal/JsonSchemaSubset.ts +392 -0
- package/src/internal/OutcomeMarker.ts +19 -0
|
@@ -0,0 +1,1269 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The body interpreter: what a flow does when it runs.
|
|
3
|
+
*
|
|
4
|
+
* A `Flow` has no handler to register — its `body` IS the behavior, per
|
|
5
|
+
* `docs/concepts/flows-and-actions.md` — so something has to turn
|
|
6
|
+
* the graph that body describes into execution. That is this
|
|
7
|
+
* module. {@link layer} registers a flow with the runtime, and the
|
|
8
|
+
* handler it installs builds the graph with {@link module:Graph.build} and
|
|
9
|
+
* walks it: each node settles once, in dependency order, and the root's value
|
|
10
|
+
* is the flow's result.
|
|
11
|
+
*
|
|
12
|
+
* What each variant does is the run-time half of what the AST recorded. An
|
|
13
|
+
* `ActionCall` runs its declaration's implementation — looked up by tag in
|
|
14
|
+
* {@link module:Implementations.Implementations} before the walk starts — as
|
|
15
|
+
* the ordinary durable action `toLayer` built, so a node driven here takes
|
|
16
|
+
* the same attempt journal, retry policy, and tier as the same action called
|
|
17
|
+
* from a handler. Its invocation identity additionally folds this node's
|
|
18
|
+
* structural address, keeping concurrent graph sites replay-stable. A `Map`
|
|
19
|
+
* applies its deferred function to the real upstream value. A `Branch`
|
|
20
|
+
* evaluates its digested predicate on the real
|
|
21
|
+
* subject and settles ONLY the arm it took: the other arm is topology the plan
|
|
22
|
+
* shows and the run skipped, and it is reported as such rather than silently
|
|
23
|
+
* absent. An `All` joins its members by name, a `Succeed` yields its value, and
|
|
24
|
+
* an inline `FlowCall` was already flattened by graph building, so it settles
|
|
25
|
+
* with the body spliced beneath it. A `FlowCall` the author wrote as
|
|
26
|
+
* `.child(payload)` is the one node that is NOT flattened: it opens a real child
|
|
27
|
+
* execution under a deterministically derived id
|
|
28
|
+
* ({@link childExecutionId}), which is what gives it its own journal lineage and
|
|
29
|
+
* makes the parent's interruption and the child's suspension travel between
|
|
30
|
+
* them.
|
|
31
|
+
*
|
|
32
|
+
* Planned references are resolved the way the plan names them: a payload
|
|
33
|
+
* placeholder carries the `Ref` `{from, path}` its key material recorded, so
|
|
34
|
+
* `result.files` reads `files` off the settled value of the node that produced
|
|
35
|
+
* it.
|
|
36
|
+
*
|
|
37
|
+
* The walk is demand-driven from the root rather than a sweep over the node
|
|
38
|
+
* list, because dependency order puts BOTH branch arms before the branch that
|
|
39
|
+
* chooses between them, and executing an arm to discover it was not taken is
|
|
40
|
+
* exactly what static topology exists to avoid.
|
|
41
|
+
*
|
|
42
|
+
* @since 0.1.0
|
|
43
|
+
*/
|
|
44
|
+
import { isRecord } from "@smthrs/canonical/Record";
|
|
45
|
+
import * as CapabilitySet from "@smthrs/capability/CapabilitySet";
|
|
46
|
+
import { DerivedKey, digest } from "@smthrs/keys";
|
|
47
|
+
import { isFatalDiagnostic } from "@smthrs/plan/GraphBuildError";
|
|
48
|
+
import * as KeyMaterial from "@smthrs/plan/KeyMaterial";
|
|
49
|
+
import * as Node from "@smthrs/plan/Node";
|
|
50
|
+
import * as Planned from "@smthrs/plan/Planned";
|
|
51
|
+
import * as StepKey from "@smthrs/plan/StepKey";
|
|
52
|
+
import * as Cause from "effect/Cause";
|
|
53
|
+
import * as Context from "effect/Context";
|
|
54
|
+
import * as Deferred from "effect/Deferred";
|
|
55
|
+
import * as Effect from "effect/Effect";
|
|
56
|
+
import * as Exit from "effect/Exit";
|
|
57
|
+
import * as Layer from "effect/Layer";
|
|
58
|
+
import * as Option from "effect/Option";
|
|
59
|
+
import * as Predicate from "effect/Predicate";
|
|
60
|
+
import * as Schema from "effect/Schema";
|
|
61
|
+
import * as Scope from "effect/Scope";
|
|
62
|
+
import { Implementations, layerImplementations } from "./Action/Implementations.js";
|
|
63
|
+
import { DispatchReport, DispatchSite } from "./Action/StepIdentity.js";
|
|
64
|
+
import * as DurableDeferred from "./DurableDeferred.js";
|
|
65
|
+
import { attenuateCapabilities } from "./Flow/CapabilityCeiling.js";
|
|
66
|
+
import * as Outcome from "./Flow/Outcome.js";
|
|
67
|
+
import { Handoff } from "./Flow/Result.js";
|
|
68
|
+
import { suspend } from "./Flow/Runtime.js";
|
|
69
|
+
import { TypeId as FlowTypeId } from "./Flow/TypeId.js";
|
|
70
|
+
import { FlowInstance } from "./FlowRuntime/FlowInstance.js";
|
|
71
|
+
import { FlowRuntime } from "./FlowRuntime/FlowRuntime.js";
|
|
72
|
+
import { annotateWaiting } from "./FlowRuntime/WaitingAnnotation.js";
|
|
73
|
+
import * as Graph from "./Graph.js";
|
|
74
|
+
import * as BoundedJson from "./internal/BoundedJson.js";
|
|
75
|
+
import { ExecutionMiddleware } from "./internal/ExecutionMiddleware.js";
|
|
76
|
+
import { OutcomeValueTypeId } from "./internal/OutcomeMarker.js";
|
|
77
|
+
/**
|
|
78
|
+
* A graph the interpreter will not drive.
|
|
79
|
+
*
|
|
80
|
+
* Every code names something the run cannot recover from on its own: a graph
|
|
81
|
+
* whose topology is incomplete, an action with no implementation wired up, a
|
|
82
|
+
* call the interpreter does not execute, and a deferred function that did not
|
|
83
|
+
* survive serialization beside its AST. `missing_implementation_version`
|
|
84
|
+
* names a content-reusable action that lacks the canonical version contract;
|
|
85
|
+
* `implementation_version_mismatch` names a declaration and registry that
|
|
86
|
+
* disagree. Both are preflight failures, before action dispatch.
|
|
87
|
+
* `join_mismatch` names a race, any, or quorum whose journaled decision this
|
|
88
|
+
* plan's members can no longer reproduce.
|
|
89
|
+
*
|
|
90
|
+
* @category errors
|
|
91
|
+
* @since 0.1.0
|
|
92
|
+
*/
|
|
93
|
+
export class InterpreterError extends Schema.TaggedError()("@smthrs/flow/InterpreterError", {
|
|
94
|
+
code: Schema.Literals([
|
|
95
|
+
"incomplete_graph",
|
|
96
|
+
"duplicate_node_id",
|
|
97
|
+
"unresolved_action",
|
|
98
|
+
"implementation_version_mismatch",
|
|
99
|
+
"missing_implementation_version",
|
|
100
|
+
"unresolved_reference",
|
|
101
|
+
"unsupported_call",
|
|
102
|
+
"missing_operation",
|
|
103
|
+
"node_record_too_large",
|
|
104
|
+
"join_mismatch"
|
|
105
|
+
]),
|
|
106
|
+
flow: Schema.String,
|
|
107
|
+
node: Schema.String,
|
|
108
|
+
message: Schema.String
|
|
109
|
+
}) {
|
|
110
|
+
}
|
|
111
|
+
/** Reads one own data property without invoking an accessor or letting a proxy trap escape. */
|
|
112
|
+
const ownDataProperty = (value, key) => {
|
|
113
|
+
if (Object(value) !== value)
|
|
114
|
+
return undefined;
|
|
115
|
+
try {
|
|
116
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
117
|
+
return descriptor !== undefined && "value" in descriptor ? descriptor.value : undefined;
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
/**
|
|
124
|
+
* The execution id a `.child()` boundary runs its child under.
|
|
125
|
+
*
|
|
126
|
+
* DECIDED: the id is DERIVED from the parent
|
|
127
|
+
* execution and the child node's structural address, not minted. That is what
|
|
128
|
+
* makes a boundary at-most-once under replay, exactly as
|
|
129
|
+
* `docs/concepts/trampoline-rounds.md` requires of a round handoff: a
|
|
130
|
+
* parent that is re-driven re-derives the same id, so the engine lands on the
|
|
131
|
+
* child execution that already exists instead of starting a second copy of it.
|
|
132
|
+
* The canonical tuple includes the callee and a canonical payload digest, so
|
|
133
|
+
* delimiter splicing and a changed invocation cannot alias an earlier child.
|
|
134
|
+
* SHA-256 uses the same injected derivation services as the repository's
|
|
135
|
+
* other durable identities. The versioned key prefix is dropped because the
|
|
136
|
+
* child-id wire format is the bare digest. `digest` owns the
|
|
137
|
+
* prefix knowledge, so this package never guesses at a stored-key format.
|
|
138
|
+
*
|
|
139
|
+
* @since 0.1.0
|
|
140
|
+
* @category constructors
|
|
141
|
+
*/
|
|
142
|
+
export const childExecutionId = (parentExecutionId, nodeId, calleeTag, payload) => Effect.gen(function* () {
|
|
143
|
+
const payloadDigest = yield* Schema.decodeUnknownEffect(DerivedKey)(payload).pipe(Effect.orDie);
|
|
144
|
+
const tupleDigest = yield* Schema.decodeUnknownEffect(DerivedKey)([
|
|
145
|
+
parentExecutionId,
|
|
146
|
+
nodeId,
|
|
147
|
+
calleeTag,
|
|
148
|
+
payloadDigest
|
|
149
|
+
]).pipe(Effect.orDie);
|
|
150
|
+
return digest(tupleDigest);
|
|
151
|
+
});
|
|
152
|
+
/**
|
|
153
|
+
* The largest encoded page of a recorded plan.
|
|
154
|
+
*
|
|
155
|
+
* A journal entry has a byte bound and a projection clips an oversized one, so
|
|
156
|
+
* a graph is PAGED rather than truncated: whatever does not fit in the plan
|
|
157
|
+
* record follows as appended subgraph pages. The budget is deliberately well
|
|
158
|
+
* under the 16 KiB gateway limit on graph events. The runtime measures the
|
|
159
|
+
* encoded journal envelope; absent that hook, the complete node record is
|
|
160
|
+
* measured in UTF-8. A node too large for one page is a typed preflight refusal.
|
|
161
|
+
*
|
|
162
|
+
* @since 1.0.0
|
|
163
|
+
* @category constants
|
|
164
|
+
*/
|
|
165
|
+
export const maximumPageBytes = 12_000;
|
|
166
|
+
/**
|
|
167
|
+
* How many distinct step key digests one node's settlement names.
|
|
168
|
+
*
|
|
169
|
+
* A journal entry has a byte bound and a digest is 64 characters, so the list
|
|
170
|
+
* is capped rather than left to grow with whatever a node dispatched. The
|
|
171
|
+
* cap is far above what any node reaches today — an action node drives one
|
|
172
|
+
* dispatch and keeps one step key however many times it is retried — and a
|
|
173
|
+
* node past it names the dispatches it started with rather than none.
|
|
174
|
+
*
|
|
175
|
+
* @since 1.0.0
|
|
176
|
+
* @category constants
|
|
177
|
+
*/
|
|
178
|
+
export const maximumDispatchDigests = 16;
|
|
179
|
+
/** The declared tag a node dispatches, if it dispatches one. @private */
|
|
180
|
+
const actionTag = (ast) => ast._tag === "ActionCall" ? ast.action : ast._tag === "FlowCall" ? ast.flow : undefined;
|
|
181
|
+
/** What one node of a driven graph tells a monitor about itself. @private */
|
|
182
|
+
const summarize = (node) => {
|
|
183
|
+
const action = actionTag(node.ast);
|
|
184
|
+
return {
|
|
185
|
+
id: node.id,
|
|
186
|
+
kind: node.kind,
|
|
187
|
+
dependsOn: KeyMaterial.dependencies(node.draft.material),
|
|
188
|
+
tier: node.draft.material.kind,
|
|
189
|
+
effects: node.draft.effects,
|
|
190
|
+
...(action === undefined ? {} : { action }),
|
|
191
|
+
...(node.declaredAt === undefined ? {} : { declaredAt: node.declaredAt })
|
|
192
|
+
};
|
|
193
|
+
};
|
|
194
|
+
/**
|
|
195
|
+
* The graph, as the pages a journal can hold.
|
|
196
|
+
*
|
|
197
|
+
* Nothing about one node has to fit one page. A node's summary is seated on a
|
|
198
|
+
* page with no dependencies on it, and then its dependency list and the edges
|
|
199
|
+
* that END on it are spread over as many following pages as they need: a
|
|
200
|
+
* continuation page re-seats the same summary and carries the next disjoint
|
|
201
|
+
* slice of `dependsOn`, so a reader that unions `dependsOn` per node id and
|
|
202
|
+
* concatenates edges reassembles exactly the graph that was built. That is
|
|
203
|
+
* what lets an input-driven fan-in — `Node.all` over a caller-sized list — be
|
|
204
|
+
* recorded at any width instead of refused.
|
|
205
|
+
*
|
|
206
|
+
* A node's summary is seated no later than the first page naming one of its
|
|
207
|
+
* dependencies or one of its incoming edges, so an assembled PREFIX of pages
|
|
208
|
+
* never names an edge whose destination is unknown.
|
|
209
|
+
*
|
|
210
|
+
* The typed refusal is left for the one thing paging cannot divide: a node
|
|
211
|
+
* whose summary with NO dependencies on it still exceeds the budget.
|
|
212
|
+
*
|
|
213
|
+
* @private
|
|
214
|
+
*/
|
|
215
|
+
const planPages = (flow, graph, measure) => Effect.gen(function* () {
|
|
216
|
+
const incoming = new Map();
|
|
217
|
+
let edgeCount = 0;
|
|
218
|
+
for (const edge of Graph.edges(graph)) {
|
|
219
|
+
edgeCount += 1;
|
|
220
|
+
const summary = { from: edge.from, to: edge.to, reason: edge.reason };
|
|
221
|
+
const existing = incoming.get(edge.to);
|
|
222
|
+
if (existing === undefined)
|
|
223
|
+
incoming.set(edge.to, [summary]);
|
|
224
|
+
else
|
|
225
|
+
existing.push(summary);
|
|
226
|
+
}
|
|
227
|
+
const graphNodes = Graph.nodes(graph);
|
|
228
|
+
const pages = [];
|
|
229
|
+
let current = { nodes: [], edges: [] };
|
|
230
|
+
const record = (page, index, count) => ({
|
|
231
|
+
...(index === 0
|
|
232
|
+
? { _tag: "PlanRecorded", nodeCount: graphNodes.length }
|
|
233
|
+
: { _tag: "SubgraphAppended" }),
|
|
234
|
+
sourceId: `plan/0/${index}`,
|
|
235
|
+
flow,
|
|
236
|
+
generation: 0,
|
|
237
|
+
page: index,
|
|
238
|
+
pages: count,
|
|
239
|
+
...page
|
|
240
|
+
});
|
|
241
|
+
// Every page is started by a node seat or by an edge, and a node is seated
|
|
242
|
+
// once plus at most once per dependency that starts a continuation page,
|
|
243
|
+
// so the page count is at most nodes + dependencies + edges. Reserving
|
|
244
|
+
// that many digits is what makes replacing the reservation with the final
|
|
245
|
+
// count shrink an encoded envelope or leave it alone, never grow it.
|
|
246
|
+
const reserved = graphNodes.reduce((total, node) => total + KeyMaterial.dependencies(node.draft.material).length, graphNodes.length + edgeCount);
|
|
247
|
+
const fits = (page) => Effect.map(measure(record(page, pages.length, reserved)), (bytes) => bytes <= maximumPageBytes);
|
|
248
|
+
const flush = () => {
|
|
249
|
+
pages.push(current);
|
|
250
|
+
current = { nodes: [], edges: [] };
|
|
251
|
+
};
|
|
252
|
+
/**
|
|
253
|
+
* How many of the remaining items the page still holds.
|
|
254
|
+
*
|
|
255
|
+
* A page only grows as items are added to it, so the boundary is found by
|
|
256
|
+
* bisection: a thousand-way fan-in costs a logarithmic number of
|
|
257
|
+
* measurements per page rather than one per item. Taking none is the page
|
|
258
|
+
* as it stands, which was measured before anything was offered to it.
|
|
259
|
+
*/
|
|
260
|
+
const admits = (rest, grow) => Effect.gen(function* () {
|
|
261
|
+
if (yield* fits(grow(rest.length)))
|
|
262
|
+
return rest.length;
|
|
263
|
+
let low = 0;
|
|
264
|
+
let high = rest.length - 1;
|
|
265
|
+
while (low < high) {
|
|
266
|
+
const mid = low + Math.ceil((high - low) / 2);
|
|
267
|
+
if (yield* fits(grow(mid)))
|
|
268
|
+
low = mid;
|
|
269
|
+
else
|
|
270
|
+
high = mid - 1;
|
|
271
|
+
}
|
|
272
|
+
return low;
|
|
273
|
+
});
|
|
274
|
+
/**
|
|
275
|
+
* Puts every item somewhere, starting a page whenever the current one is
|
|
276
|
+
* full. `resume` prepares a freshly started page so what follows still
|
|
277
|
+
* says which node it belongs to, and a page that was just started and
|
|
278
|
+
* admits nothing is the end of what paging can do.
|
|
279
|
+
*/
|
|
280
|
+
const spread = (items, onto, resume, refusal) => Effect.gen(function* () {
|
|
281
|
+
let rest = items;
|
|
282
|
+
let started = false;
|
|
283
|
+
while (rest.length > 0) {
|
|
284
|
+
const take = yield* admits(rest, (count) => onto(current, rest.slice(0, count)));
|
|
285
|
+
if (take > 0) {
|
|
286
|
+
current = onto(current, rest.slice(0, take));
|
|
287
|
+
rest = rest.slice(take);
|
|
288
|
+
started = false;
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
if (started)
|
|
292
|
+
return yield* refusal();
|
|
293
|
+
flush();
|
|
294
|
+
current = resume(current);
|
|
295
|
+
started = true;
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
for (const node of graphNodes) {
|
|
299
|
+
const summary = summarize(node);
|
|
300
|
+
const arriving = incoming.get(node.id) ?? [];
|
|
301
|
+
// The summary alone first. What a page must hold whole is the node
|
|
302
|
+
// itself, and that is the only thing left to refuse.
|
|
303
|
+
const bare = { ...summary, dependsOn: [] };
|
|
304
|
+
const seat = (page) => ({ nodes: [...page.nodes, bare], edges: page.edges });
|
|
305
|
+
const tooLarge = () => new InterpreterError({
|
|
306
|
+
code: "node_record_too_large",
|
|
307
|
+
flow,
|
|
308
|
+
node: node.id,
|
|
309
|
+
message: `Node "${node.id}" and its encoded envelope exceed ${maximumPageBytes} bytes with no dependencies on it`
|
|
310
|
+
});
|
|
311
|
+
if (!(yield* fits(seat(current)))) {
|
|
312
|
+
if (current.nodes.length > 0 || current.edges.length > 0)
|
|
313
|
+
flush();
|
|
314
|
+
if (!(yield* fits(seat(current))))
|
|
315
|
+
return yield* tooLarge();
|
|
316
|
+
}
|
|
317
|
+
current = seat(current);
|
|
318
|
+
// Then its dependencies, onto the summary the page just seated, and then
|
|
319
|
+
// the edges that end on it. Either may run past this page onto the next.
|
|
320
|
+
yield* spread(summary.dependsOn, (page, taken) => {
|
|
321
|
+
const seated = page.nodes[page.nodes.length - 1];
|
|
322
|
+
return {
|
|
323
|
+
nodes: [...page.nodes.slice(0, -1), { ...seated, dependsOn: [...seated.dependsOn, ...taken] }],
|
|
324
|
+
edges: page.edges
|
|
325
|
+
};
|
|
326
|
+
}, seat, tooLarge);
|
|
327
|
+
yield* spread(arriving, (page, taken) => ({ nodes: page.nodes, edges: [...page.edges, ...taken] }), (page) => page, tooLarge);
|
|
328
|
+
}
|
|
329
|
+
pages.push(current);
|
|
330
|
+
return pages.map((page, index) => record(page, index, pages.length));
|
|
331
|
+
});
|
|
332
|
+
/**
|
|
333
|
+
* Interprets a flow body, or a bare node, against real values.
|
|
334
|
+
*
|
|
335
|
+
* The graph is built first and in full — planning is a pure function of the
|
|
336
|
+
* declarations and the payload, so the whole shape of the round is known before
|
|
337
|
+
* the first action runs — and then driven. The low-level default permits
|
|
338
|
+
* process-local callbacks; pass `callbackIdentity: "stable"` for reproducible
|
|
339
|
+
* callback identity. {@link layerWithImplementations} selects that policy by
|
|
340
|
+
* default.
|
|
341
|
+
*
|
|
342
|
+
* @since 0.1.0
|
|
343
|
+
* @category constructors
|
|
344
|
+
*/
|
|
345
|
+
export const interpret = (flowOrNode, payload, options = {}) => interpretWithPolicy(flowOrNode, payload, options, false);
|
|
346
|
+
/** The canonical composition requires versions before permitting content-key reuse. @private */
|
|
347
|
+
const interpretWithPolicy = (flowOrNode, payload, options, requireReusableVersions, registerReachable) => Effect.gen(function* () {
|
|
348
|
+
const table = yield* Implementations;
|
|
349
|
+
const name = "_tag" in flowOrNode ? flowOrNode._tag : "node";
|
|
350
|
+
const refuse = (code, node, message) => Effect.fail(new InterpreterError({ code, flow: name, node, message }));
|
|
351
|
+
const graph = yield* Effect.try({
|
|
352
|
+
try: () => Graph.build(flowOrNode, payload, options),
|
|
353
|
+
catch: (cause) => {
|
|
354
|
+
// Graph.build normalizes its own refusals to GraphBuildError, but the
|
|
355
|
+
// flow body runs inside this boundary and a body throws whatever it
|
|
356
|
+
// throws. Reading `node` and `message` off the value without checking
|
|
357
|
+
// built an InterpreterError its own schema rejects, and the
|
|
358
|
+
// constructor's "Schema validation failed" then replaced the reason
|
|
359
|
+
// the author needed. All three fields are read through own data
|
|
360
|
+
// descriptors and narrowed here so a plain Error from a body arrives
|
|
361
|
+
// as a typed failure that still says why. A body that threw produced no
|
|
362
|
+
// topology, so it reports `incomplete_graph`.
|
|
363
|
+
const refusalNode = ownDataProperty(cause, "node");
|
|
364
|
+
const refusalCode = ownDataProperty(cause, "code");
|
|
365
|
+
const refusalMessage = ownDataProperty(cause, "message");
|
|
366
|
+
const node = typeof refusalNode === "string" ? refusalNode : "";
|
|
367
|
+
const reported = typeof refusalMessage === "string" ? refusalMessage : "";
|
|
368
|
+
return new InterpreterError({
|
|
369
|
+
code: refusalCode === "duplicate_node" ? "duplicate_node_id" : "incomplete_graph",
|
|
370
|
+
flow: name,
|
|
371
|
+
node,
|
|
372
|
+
message: reported === ""
|
|
373
|
+
? `building the graph of flow ${name} threw ${typeof cause === "string" ? cause : BoundedJson.render(cause, 512)}`
|
|
374
|
+
: reported
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
});
|
|
378
|
+
const graphNodes = Graph.nodes(graph);
|
|
379
|
+
const byId = new Map(graphNodes.map((node) => [node.id, node]));
|
|
380
|
+
// Advisory diagnostics describe a graph that is exactly what will run, so
|
|
381
|
+
// they are reported by `Graph.diagnostics` and do not stop a run. A fatal
|
|
382
|
+
// one means the topology is not what the author wrote.
|
|
383
|
+
const fatal = graph.diagnostics.find(isFatalDiagnostic);
|
|
384
|
+
if (fatal !== undefined) {
|
|
385
|
+
return yield* refuse("incomplete_graph", fatal.node, `Graph of "${name}" is missing topology and cannot be driven: ${fatal.message}`);
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* The node records this walk writes, if the runtime keeps any.
|
|
389
|
+
*
|
|
390
|
+
* A runtime without the seam costs a comparison per node and nothing else:
|
|
391
|
+
* the record is built by the thunk only when something will hold it.
|
|
392
|
+
*/
|
|
393
|
+
const runtime = yield* FlowRuntime;
|
|
394
|
+
const recordNode = runtime.recordNode;
|
|
395
|
+
const record = recordNode === undefined
|
|
396
|
+
? (_) => Effect.void
|
|
397
|
+
: (make) => recordNode(make());
|
|
398
|
+
// The whole graph, before the first node runs. Planning is pure, so this
|
|
399
|
+
// is the complete declared ceiling of the round rather than a prefix of
|
|
400
|
+
// what happened to run, and a monitor can draw the round it is watching
|
|
401
|
+
// from the first record it receives.
|
|
402
|
+
if (recordNode !== undefined) {
|
|
403
|
+
const measure = runtime.nodeRecordBytes ??
|
|
404
|
+
((record) => Effect.succeed(new TextEncoder().encode(JSON.stringify(record)).byteLength));
|
|
405
|
+
// Validate every page before writing the first or dispatching any action.
|
|
406
|
+
const pages = yield* planPages(name, graph, measure);
|
|
407
|
+
yield* Effect.forEach(pages, (page) => recordNode(page), { discard: true });
|
|
408
|
+
}
|
|
409
|
+
// Everything the walk needs that the built graph can be asked for before it
|
|
410
|
+
// runs, is asked for here — so no such refusal can surface halfway through a
|
|
411
|
+
// body with the actions ahead of it already committed.
|
|
412
|
+
//
|
|
413
|
+
// A reference out of the graph is the first: a round may be PLANNED against
|
|
414
|
+
// a node an earlier generation settled — `Plan.append` exists for exactly
|
|
415
|
+
// that — but one interpretation settles one graph, so there is nothing to
|
|
416
|
+
// read it from. A missing implementation is the second, and it is a wiring
|
|
417
|
+
// error rather than a run-time contingency: every action the graph names
|
|
418
|
+
// is resolved up front, including the ones only an untaken branch arm would
|
|
419
|
+
// have reached, because the plan is the declared ceiling of what may run.
|
|
420
|
+
// Deferred operations must likewise survive beside every AST, even when
|
|
421
|
+
// the walk would skip their branch or catch arm.
|
|
422
|
+
const implementations = new Map();
|
|
423
|
+
const handoffDeclarations = new Map();
|
|
424
|
+
const childDeclarations = new Map();
|
|
425
|
+
for (const node of graphNodes) {
|
|
426
|
+
const ast = node.ast;
|
|
427
|
+
if (ast._tag === "Map" && Node.mapper(ast) === undefined) {
|
|
428
|
+
return yield* refuse("missing_operation", node.id, `Map at "${node.id}" lost its mapper.`);
|
|
429
|
+
}
|
|
430
|
+
if (ast._tag === "Branch" && Node.predicate(ast) === undefined) {
|
|
431
|
+
return yield* refuse("missing_operation", node.id, `Branch at "${node.id}" lost its predicate.`);
|
|
432
|
+
}
|
|
433
|
+
if (ast._tag === "Catch" && ast.filter !== undefined && Node.catchFilter(ast) === undefined) {
|
|
434
|
+
return yield* refuse("missing_operation", node.id, `Catch at "${node.id}" lost its schema filter.`);
|
|
435
|
+
}
|
|
436
|
+
for (const dependency of KeyMaterial.dependencies(node.draft.material)) {
|
|
437
|
+
if (byId.has(dependency))
|
|
438
|
+
continue;
|
|
439
|
+
return yield* refuse("unresolved_reference", node.id, `Node "${node.id}" reads "${dependency}", which this graph does not hold.`);
|
|
440
|
+
}
|
|
441
|
+
if (node.ast._tag === "FlowCall" && node.ast.mode === "handoff") {
|
|
442
|
+
const declaration = Node.declaration(node.ast);
|
|
443
|
+
if (!Predicate.hasProperty(declaration, FlowTypeId)) {
|
|
444
|
+
return yield* refuse("unsupported_call", node.id, `Handoff to flow "${node.ast.flow}" at "${node.id}" lost its declaration. ` +
|
|
445
|
+
"Build and interpret the authored node in the same process so its payload can be encoded.");
|
|
446
|
+
}
|
|
447
|
+
handoffDeclarations.set(node.id, declaration);
|
|
448
|
+
if (registerReachable !== undefined)
|
|
449
|
+
yield* registerReachable(declaration);
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
if (node.ast._tag === "FlowCall" && node.ast.mode === "boundary") {
|
|
453
|
+
// A boundary is one node here and a real execution underneath, so what
|
|
454
|
+
// it needs resolved up front is the callee itself: the declaration is
|
|
455
|
+
// what `execute` is called on, and losing it is a wiring error rather
|
|
456
|
+
// than a run-time contingency, exactly like an unimplemented action.
|
|
457
|
+
const declaration = Node.declaration(node.ast);
|
|
458
|
+
if (!Predicate.hasProperty(declaration, FlowTypeId)) {
|
|
459
|
+
return yield* refuse("unsupported_call", node.id, `Child boundary to flow "${node.ast.flow}" at "${node.id}" lost its declaration. ` +
|
|
460
|
+
"Build and interpret the authored node in the same process so the child can be executed.");
|
|
461
|
+
}
|
|
462
|
+
childDeclarations.set(node.id, declaration);
|
|
463
|
+
if (registerReachable !== undefined)
|
|
464
|
+
yield* registerReachable(declaration);
|
|
465
|
+
continue;
|
|
466
|
+
}
|
|
467
|
+
if (node.ast._tag !== "ActionCall")
|
|
468
|
+
continue;
|
|
469
|
+
const implementation = yield* table.get(node.ast.action);
|
|
470
|
+
if (Option.isNone(implementation)) {
|
|
471
|
+
return yield* refuse("unresolved_action", node.id, `Action "${node.ast.action}" has no implementation. ` +
|
|
472
|
+
`Provide ONE Action.layerImplementations under both ${node.ast.action}.toLayer(execute) and ` +
|
|
473
|
+
"this interpreter layer: an implementation files itself with the table that is in scope " +
|
|
474
|
+
"while IT is built, so a table merged beside it, or a second one built above it, is not the " +
|
|
475
|
+
"table this driver reads.");
|
|
476
|
+
}
|
|
477
|
+
const declaration = Node.declaration(node.ast);
|
|
478
|
+
const declaredVersion = ownDataProperty(declaration, "implementationVersion");
|
|
479
|
+
if (requireReusableVersions) {
|
|
480
|
+
const tier = ownDataProperty(declaration, "tier");
|
|
481
|
+
if (tier === undefined) {
|
|
482
|
+
return yield* refuse("incomplete_graph", node.id, `Action "${node.ast.action}" lost its declaration, so its implementation identity cannot be checked. ` +
|
|
483
|
+
"Build the canonical composition from the authored action nodes.");
|
|
484
|
+
}
|
|
485
|
+
if (tier === "sealed" && ownDataProperty(declaration, "idempotencyKey") !== undefined &&
|
|
486
|
+
declaredVersion === undefined) {
|
|
487
|
+
return yield* refuse("missing_implementation_version", node.id, `Sealed action "${node.ast.action}" declares an idempotency key and can reuse recorded content. ` +
|
|
488
|
+
"Declare implementationVersion on Action.make and attest the same version in toLayer.");
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
if (declaredVersion !== implementation.value.implementationVersion) {
|
|
492
|
+
return yield* refuse("implementation_version_mismatch", node.id, `Action "${node.ast.action}" does not match its registered implementationVersion. ` +
|
|
493
|
+
"Register the declared version and start a newly planned execution when its semantics change.");
|
|
494
|
+
}
|
|
495
|
+
implementations.set(node.ast.action, implementation.value);
|
|
496
|
+
}
|
|
497
|
+
// Graph.build also accepts authored references. Unlike Plan.compile, this
|
|
498
|
+
// entry point has not yet checked them for cycles: a self-reference would
|
|
499
|
+
// join its own in-flight deferred forever. Validate the entire dependency
|
|
500
|
+
// graph before dispatch, including branch arms the execution might skip.
|
|
501
|
+
const remaining = new Map();
|
|
502
|
+
const dependents = new Map();
|
|
503
|
+
const ready = [];
|
|
504
|
+
for (const node of graphNodes) {
|
|
505
|
+
const dependencies = KeyMaterial.dependencies(node.draft.material);
|
|
506
|
+
remaining.set(node.id, dependencies.length);
|
|
507
|
+
if (dependencies.length === 0)
|
|
508
|
+
ready.push(node.id);
|
|
509
|
+
for (const dependency of dependencies) {
|
|
510
|
+
const consumers = dependents.get(dependency);
|
|
511
|
+
if (consumers === undefined)
|
|
512
|
+
dependents.set(dependency, [node.id]);
|
|
513
|
+
else
|
|
514
|
+
consumers.push(node.id);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
for (let index = 0; index < ready.length; index++) {
|
|
518
|
+
for (const consumer of dependents.get(ready[index]) ?? []) {
|
|
519
|
+
const count = remaining.get(consumer) - 1;
|
|
520
|
+
remaining.set(consumer, count);
|
|
521
|
+
if (count === 0)
|
|
522
|
+
ready.push(consumer);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
if (ready.length !== graphNodes.length) {
|
|
526
|
+
const blocked = graphNodes.find((node) => remaining.get(node.id) > 0);
|
|
527
|
+
return yield* refuse("incomplete_graph", blocked.id, `Graph of "${name}" contains a dependency cycle blocking "${blocked.id}". ` +
|
|
528
|
+
"Use an acyclic reference, a child boundary, or a trampoline handoff.");
|
|
529
|
+
}
|
|
530
|
+
// The children a node settles with, in the order graph building recorded
|
|
531
|
+
// them: `first` then the arms of a branch, `first` then the continuation of
|
|
532
|
+
// a sequence, the members of a combination, the spliced body of an inline
|
|
533
|
+
// call. Payload references are NOT here — they are named by the key
|
|
534
|
+
// material, and settled from it.
|
|
535
|
+
const sources = new Map();
|
|
536
|
+
for (const edge of Graph.edges(graph)) {
|
|
537
|
+
if (edge.reason !== "value")
|
|
538
|
+
continue;
|
|
539
|
+
sources.set(edge.to, [...sources.get(edge.to) ?? [], edge.from]);
|
|
540
|
+
}
|
|
541
|
+
const settled = new Map();
|
|
542
|
+
const failed = new Map();
|
|
543
|
+
/**
|
|
544
|
+
* Projects a settled value along the property path a `Ref` recorded —
|
|
545
|
+
* through `StepKey.project`, the one projection semantics for the value
|
|
546
|
+
* channel. The scheduler digests exactly what this returns; resolving a
|
|
547
|
+
* `Ref` any other way here would let two executions that key identically
|
|
548
|
+
* consume different values.
|
|
549
|
+
*/
|
|
550
|
+
const project = StepKey.project;
|
|
551
|
+
/**
|
|
552
|
+
* Replaces every placeholder in a hydrated payload with what it stands for.
|
|
553
|
+
*
|
|
554
|
+
* The record is rebuilt with a null prototype and `defineProperty`, for the
|
|
555
|
+
* reason {@link module:Graph.build}'s cloners are: `output[key] = …` on an
|
|
556
|
+
* object literal routes `__proto__` through `Object.prototype`'s accessor,
|
|
557
|
+
* which drops an own `__proto__` field carrying a primitive and reparents
|
|
558
|
+
* the clone when it carries an object. A graph hydrates that field as data,
|
|
559
|
+
* so the value handed to an action has to keep it as data too.
|
|
560
|
+
*/
|
|
561
|
+
const resolve = (value) => {
|
|
562
|
+
const reference = Planned.reference(value);
|
|
563
|
+
if (reference !== undefined) {
|
|
564
|
+
return project(settled.has(reference.node) ? settled.get(reference.node) : failed.get(reference.node), reference.path);
|
|
565
|
+
}
|
|
566
|
+
if (Array.isArray(value))
|
|
567
|
+
return value.map((item) => resolve(item));
|
|
568
|
+
if (!isRecord(value))
|
|
569
|
+
return value;
|
|
570
|
+
const output = Object.create(null);
|
|
571
|
+
for (const key of Object.keys(value)) {
|
|
572
|
+
Object.defineProperty(output, key, {
|
|
573
|
+
configurable: true,
|
|
574
|
+
enumerable: true,
|
|
575
|
+
value: resolve(value[key]),
|
|
576
|
+
writable: true
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
const outcome = Object.getOwnPropertyDescriptor(value, OutcomeValueTypeId);
|
|
580
|
+
if (outcome !== undefined && "value" in outcome) {
|
|
581
|
+
Object.defineProperty(output, OutcomeValueTypeId, {
|
|
582
|
+
configurable: false,
|
|
583
|
+
enumerable: false,
|
|
584
|
+
value: outcome.value,
|
|
585
|
+
writable: false
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
return output;
|
|
589
|
+
};
|
|
590
|
+
/**
|
|
591
|
+
* The settlement currently in flight for a node, for as long as it is.
|
|
592
|
+
*
|
|
593
|
+
* `settled.has(id)` alone is not a memo once the walk runs concurrently: it
|
|
594
|
+
* answers "has this node FINISHED", and two demands that arrive while the
|
|
595
|
+
* node is still running both see `false` and both execute it. A diamond
|
|
596
|
+
* would then run its shared node twice, which for an action with effects is
|
|
597
|
+
* a correctness bug rather than a wasted cycle.
|
|
598
|
+
*
|
|
599
|
+
* The check-and-register below is the whole reason this is a `suspend` and
|
|
600
|
+
* not a generator: the callback runs synchronously, so no other fiber can
|
|
601
|
+
* observe the gap between the lookup and the insert.
|
|
602
|
+
*
|
|
603
|
+
* Every computation runs on a fiber forked into `nodes`, the
|
|
604
|
+
* interpretation's own scope, and every demand — the first included — is a
|
|
605
|
+
* join on the node's deferred. Ownership by the interpretation rather than
|
|
606
|
+
* by whichever demand arrived first is what makes a ref-diamond sound: a
|
|
607
|
+
* failing `All` interrupts its member JOINS, never a shared node's
|
|
608
|
+
* execution, so recovery arms above can still join the shared work; and
|
|
609
|
+
* when the walk itself ends, closing the scope interrupts whatever is
|
|
610
|
+
* still running, so an orphaned execution cannot outlive the
|
|
611
|
+
* interpretation or write a phantom success into `settled` afterwards
|
|
612
|
+
* (Skyframe's and `MemoMap`'s ownership model).
|
|
613
|
+
*/
|
|
614
|
+
const inFlight = new Map();
|
|
615
|
+
const nodes = yield* Scope.make();
|
|
616
|
+
/**
|
|
617
|
+
* The execution fiber of every node still running, so a join that settled
|
|
618
|
+
* early can interrupt the work of the members it no longer waits for.
|
|
619
|
+
*/
|
|
620
|
+
const executions = new Map();
|
|
621
|
+
/**
|
|
622
|
+
* The child execution id each `.child()` node opened. Interrupting the
|
|
623
|
+
* node's fiber only stops its join: a losing child is cancelled through
|
|
624
|
+
* the runtime, the same request a caller's cancellation makes.
|
|
625
|
+
*/
|
|
626
|
+
const childExecutions = new Map();
|
|
627
|
+
/**
|
|
628
|
+
* The nodes this walk has already settled a record for.
|
|
629
|
+
*
|
|
630
|
+
* Everything else is what the walk never reached, which is exactly the
|
|
631
|
+
* `skipped` set reported below — an untaken branch arm, or a dependent of
|
|
632
|
+
* something that failed.
|
|
633
|
+
*/
|
|
634
|
+
const reported = new Set();
|
|
635
|
+
const settleNode = (id) => Effect.suspend(() => {
|
|
636
|
+
if (settled.has(id))
|
|
637
|
+
return Effect.succeed(settled.get(id));
|
|
638
|
+
const waiting = inFlight.get(id);
|
|
639
|
+
// A later demand joins the execution rather than starting one.
|
|
640
|
+
if (waiting !== undefined)
|
|
641
|
+
return Deferred.await(waiting);
|
|
642
|
+
const deferred = Deferred.makeUnsafe();
|
|
643
|
+
inFlight.set(id, deferred);
|
|
644
|
+
const node = byId.get(id);
|
|
645
|
+
const action = actionTag(node.ast);
|
|
646
|
+
// What this node's dispatches did, reported by the engine underneath
|
|
647
|
+
// it: a node whose dispatches were ALL served from durable records
|
|
648
|
+
// rebuilt nothing, and says so. The digests name those dispatches, so
|
|
649
|
+
// the attempt rows the engine wrote under them belong to THIS node,
|
|
650
|
+
// and the highest attempt reported is the node's real attempt count.
|
|
651
|
+
// A `Set` keeps the digests unrepeated in report order: one dispatch
|
|
652
|
+
// retried three times keeps one step key and reports it three times.
|
|
653
|
+
const dispatches = { executed: 0, replayed: 0, attempts: 1, digests: new Set() };
|
|
654
|
+
const settleRecord = (outcome, settlement) => () => ({
|
|
655
|
+
_tag: "NodeSettled",
|
|
656
|
+
sourceId: `node/${id}/1/settled`,
|
|
657
|
+
nodeId: id,
|
|
658
|
+
outcome,
|
|
659
|
+
attempts: dispatches.attempts,
|
|
660
|
+
stepKeyDigests: [...dispatches.digests],
|
|
661
|
+
value: settlement.value,
|
|
662
|
+
...(action === undefined ? {} : { action })
|
|
663
|
+
});
|
|
664
|
+
const execution = record(() => ({
|
|
665
|
+
_tag: "NodeScheduled",
|
|
666
|
+
sourceId: `node/${id}/1`,
|
|
667
|
+
nodeId: id,
|
|
668
|
+
kind: node.kind,
|
|
669
|
+
attempt: 1,
|
|
670
|
+
...(action === undefined ? {} : { action })
|
|
671
|
+
})).pipe(Effect.andThen(compute(node).pipe(Effect.provideService(DispatchReport, DispatchReport.of({
|
|
672
|
+
dispatched: (dispatch) => Effect.sync(() => {
|
|
673
|
+
if (dispatch.outcome === "executed")
|
|
674
|
+
dispatches.executed = dispatches.executed + 1;
|
|
675
|
+
else
|
|
676
|
+
dispatches.replayed = dispatches.replayed + 1;
|
|
677
|
+
// Bounded where it is COLLECTED, not where it is
|
|
678
|
+
// written: a cap applied at the end still holds every
|
|
679
|
+
// digest in memory until then, and the point of the cap
|
|
680
|
+
// is that one node cannot grow without bound.
|
|
681
|
+
if (dispatch.stepKeyDigest !== undefined &&
|
|
682
|
+
dispatches.digests.size < maximumDispatchDigests) {
|
|
683
|
+
dispatches.digests.add(dispatch.stepKeyDigest);
|
|
684
|
+
}
|
|
685
|
+
if (dispatch.attempt !== undefined && dispatch.attempt > dispatches.attempts) {
|
|
686
|
+
dispatches.attempts = dispatch.attempt;
|
|
687
|
+
}
|
|
688
|
+
})
|
|
689
|
+
})))), Effect.tap((value) => Effect.sync(() => {
|
|
690
|
+
settled.set(id, value);
|
|
691
|
+
reported.add(id);
|
|
692
|
+
}).pipe(Effect.andThen(record(settleRecord(dispatches.executed === 0 && dispatches.replayed > 0 ? "clean" : "built", { value }))))), Effect.tapError((error) => Effect.sync(() => {
|
|
693
|
+
failed.set(id, error);
|
|
694
|
+
reported.add(id);
|
|
695
|
+
}).pipe(Effect.andThen(record(settleRecord("failed", { value: error }))))), Effect.onExit((exit) => {
|
|
696
|
+
// An interrupted execution is evicted, not memoized: current
|
|
697
|
+
// joiners observe the interruption, but a LATER demand — a
|
|
698
|
+
// recovery arm, a re-driven walk — re-executes the node instead
|
|
699
|
+
// of replaying an interrupt that says nothing about it.
|
|
700
|
+
if (Exit.isFailure(exit) && Cause.hasInterruptsOnly(exit.cause))
|
|
701
|
+
inFlight.delete(id);
|
|
702
|
+
executions.delete(id);
|
|
703
|
+
return Deferred.done(deferred, exit);
|
|
704
|
+
}));
|
|
705
|
+
// `startImmediately` keeps the first demand's latency identical to the
|
|
706
|
+
// old inline execution: the node runs synchronously until its first
|
|
707
|
+
// real suspension, so a body that parks does so before the driver's
|
|
708
|
+
// next tick — the same observable cadence a sequential walk had.
|
|
709
|
+
return Effect.forkIn(Effect.withFiber((fiber) => {
|
|
710
|
+
executions.set(id, fiber);
|
|
711
|
+
return execution;
|
|
712
|
+
}), nodes, { startImmediately: true }).pipe(Effect.andThen(Deferred.await(deferred)));
|
|
713
|
+
});
|
|
714
|
+
/**
|
|
715
|
+
* The nodes that read each node's value through their key material: the
|
|
716
|
+
* payload references and barriers a structural subtree does not show.
|
|
717
|
+
*/
|
|
718
|
+
const readers = new Map();
|
|
719
|
+
for (const node of graphNodes) {
|
|
720
|
+
for (const dependency of KeyMaterial.dependencies(node.draft.material)) {
|
|
721
|
+
readers.set(dependency, [...readers.get(dependency) ?? [], node.id]);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
/**
|
|
725
|
+
* The work of the members a join no longer waits for: their running node
|
|
726
|
+
* fibers and the child executions those nodes opened.
|
|
727
|
+
*
|
|
728
|
+
* A member's structural subtree is its own, so its running executions are
|
|
729
|
+
* interrupted; nothing else demands them again. A node something outside that
|
|
730
|
+
* subtree also reads is shared work the interpretation owns — the same
|
|
731
|
+
* ownership an `All` failure respects — and keeps running, together with
|
|
732
|
+
* everything inside the subtree it depends on.
|
|
733
|
+
*/
|
|
734
|
+
// Graph building gives every node exactly one structural parent and reads
|
|
735
|
+
// each child through that parent's key material, so the walk below never
|
|
736
|
+
// reaches a node twice and every node in a subtree has a reader.
|
|
737
|
+
const losingWork = (joinId, roots) => {
|
|
738
|
+
const subtree = new Set();
|
|
739
|
+
const pending = [...roots];
|
|
740
|
+
while (pending.length > 0) {
|
|
741
|
+
const id = pending.pop();
|
|
742
|
+
subtree.add(id);
|
|
743
|
+
pending.push(...sources.get(id) ?? []);
|
|
744
|
+
}
|
|
745
|
+
const kept = new Set();
|
|
746
|
+
const shared = [...subtree].filter((id) => readers.get(id).some((reader) => reader !== joinId && !subtree.has(reader)));
|
|
747
|
+
while (shared.length > 0) {
|
|
748
|
+
const id = shared.pop();
|
|
749
|
+
if (kept.has(id) || !subtree.has(id))
|
|
750
|
+
continue;
|
|
751
|
+
kept.add(id);
|
|
752
|
+
shared.push(...sources.get(id) ?? [], ...KeyMaterial.dependencies(byId.get(id).draft.material));
|
|
753
|
+
}
|
|
754
|
+
const fibers = [];
|
|
755
|
+
const childRuns = [];
|
|
756
|
+
for (const id of subtree) {
|
|
757
|
+
if (kept.has(id))
|
|
758
|
+
continue;
|
|
759
|
+
const fiber = executions.get(id);
|
|
760
|
+
if (fiber === undefined)
|
|
761
|
+
continue;
|
|
762
|
+
fibers.push(fiber);
|
|
763
|
+
const executionId = childExecutions.get(id);
|
|
764
|
+
if (executionId !== undefined)
|
|
765
|
+
childRuns.push({ node: id, executionId });
|
|
766
|
+
}
|
|
767
|
+
return { fibers, childRuns };
|
|
768
|
+
};
|
|
769
|
+
/**
|
|
770
|
+
* Cancels the child executions a join's losers opened, through the
|
|
771
|
+
* runtime: interrupting a node's fiber only stops its join, so a losing
|
|
772
|
+
* child is cancelled with the same request a caller's cancellation makes.
|
|
773
|
+
*/
|
|
774
|
+
const cancelChildren = (childRuns) => Effect.forEach(childRuns, ({ executionId, node }) => Effect.orDie(runtime.interrupt(childDeclarations.get(node), executionId)), { discard: true });
|
|
775
|
+
/**
|
|
776
|
+
* Settles a `Race` node: `race`, `any`, or `quorum`.
|
|
777
|
+
*
|
|
778
|
+
* The members that decided are journaled, in the order they decided,
|
|
779
|
+
* under a durable deferred named for the node before any loser is
|
|
780
|
+
* interrupted. A resumed walk that finds the record settles exactly those
|
|
781
|
+
* members — whose own dispatches replay from their durable records — and
|
|
782
|
+
* never demands the others, so it reaches the same verdict without
|
|
783
|
+
* running a loser again. A defect or an interpreter refusal in a member is
|
|
784
|
+
* not a decision: it propagates unjournaled, as it would from an `All`.
|
|
785
|
+
*
|
|
786
|
+
* The same record names the child executions the losers had opened, so a
|
|
787
|
+
* process that dies between the journal write and their cancellation does
|
|
788
|
+
* not orphan them: the resumed walk never demands a loser, and it cancels
|
|
789
|
+
* those children from the record instead (#2892). Cancellation is a
|
|
790
|
+
* first-writer-wins request, so repeating it on every replay is harmless.
|
|
791
|
+
*/
|
|
792
|
+
const join = (node, ast, children) => Effect.gen(function* () {
|
|
793
|
+
const members = Object.keys(ast.nodes);
|
|
794
|
+
const journal = DurableDeferred.make(`join/${node.id}`, { success: JoinDecision });
|
|
795
|
+
const instance = yield* FlowInstance;
|
|
796
|
+
// A join is not a wait: reading its record must not consume the
|
|
797
|
+
// waiting classification a parked sibling declared.
|
|
798
|
+
const waiting = instance.waiting;
|
|
799
|
+
const recorded = yield* runtime.deferredResult(journal);
|
|
800
|
+
instance.waiting = waiting;
|
|
801
|
+
if (Option.isSome(recorded) && Exit.isSuccess(recorded.value)) {
|
|
802
|
+
const record = recorded.value.value;
|
|
803
|
+
const decided = "members" in record ? record.members : record;
|
|
804
|
+
const opened = "members" in record ? record.children : [];
|
|
805
|
+
const indices = decided.map((member) => members.indexOf(member));
|
|
806
|
+
if (indices.some((index) => index < 0) || new Set(indices).size !== indices.length) {
|
|
807
|
+
return yield* refuse("join_mismatch", node.id, `Join at "${node.id}" journaled members ${JSON.stringify(decided)}, which it no longer holds.`);
|
|
808
|
+
}
|
|
809
|
+
const lost = opened.find((child) => !childDeclarations.has(child.node));
|
|
810
|
+
if (lost !== undefined) {
|
|
811
|
+
return yield* refuse("join_mismatch", node.id, `Join at "${node.id}" journaled a losing child at "${lost.node}", which the plan no longer holds.`);
|
|
812
|
+
}
|
|
813
|
+
yield* cancelChildren(opened);
|
|
814
|
+
// The losers are never demanded, so nothing of theirs starts again.
|
|
815
|
+
const exits = yield* Effect.forEach(indices, (index) => Effect.exit(settleNode(children[index])), {
|
|
816
|
+
concurrency: "unbounded"
|
|
817
|
+
});
|
|
818
|
+
// The verdict must close on the LAST journaled member, exactly as it
|
|
819
|
+
// did live: one that closes earlier, or never, is a different join.
|
|
820
|
+
const outcomes = [];
|
|
821
|
+
for (let position = 0; position < exits.length; position++) {
|
|
822
|
+
const exit = exits[position];
|
|
823
|
+
if (classify(exit) !== "decisive")
|
|
824
|
+
return yield* exit;
|
|
825
|
+
outcomes.push({ member: members[indices[position]], exit });
|
|
826
|
+
const verdict = decide(ast, members.length, outcomes);
|
|
827
|
+
if (verdict !== undefined && position === exits.length - 1)
|
|
828
|
+
return yield* verdict;
|
|
829
|
+
if (verdict !== undefined)
|
|
830
|
+
break;
|
|
831
|
+
}
|
|
832
|
+
return yield* refuse("join_mismatch", node.id, `Join at "${node.id}" replayed members ${JSON.stringify(decided)} without reaching its verdict.`);
|
|
833
|
+
}
|
|
834
|
+
const outcomes = [];
|
|
835
|
+
let verdict;
|
|
836
|
+
let journaled = false;
|
|
837
|
+
const over = Deferred.makeUnsafe();
|
|
838
|
+
const watch = (index) => Effect.exit(settleNode(children[index])).pipe(Effect.flatMap((exit) => Effect.sync(() => {
|
|
839
|
+
if (verdict !== undefined)
|
|
840
|
+
return;
|
|
841
|
+
const kind = classify(exit);
|
|
842
|
+
// An interrupted member is still undecided: it parked, or a
|
|
843
|
+
// join elsewhere cancelled shared work. The others may still
|
|
844
|
+
// decide without it.
|
|
845
|
+
if (kind === "pending")
|
|
846
|
+
return;
|
|
847
|
+
if (kind === "fatal") {
|
|
848
|
+
verdict = exit;
|
|
849
|
+
}
|
|
850
|
+
else {
|
|
851
|
+
outcomes.push({ member: members[index], exit });
|
|
852
|
+
verdict = decide(ast, members.length, outcomes);
|
|
853
|
+
journaled = verdict !== undefined;
|
|
854
|
+
}
|
|
855
|
+
if (verdict !== undefined)
|
|
856
|
+
Deferred.doneUnsafe(over, Exit.void);
|
|
857
|
+
})));
|
|
858
|
+
yield* Effect.raceFirst(Effect.forEach(children, (_, index) => watch(index), { concurrency: "unbounded", discard: true }), Deferred.await(over));
|
|
859
|
+
// Every member ended interrupted: the join cannot decide in this
|
|
860
|
+
// round, and the walk parks or ends with it.
|
|
861
|
+
if (verdict === undefined)
|
|
862
|
+
return yield* Effect.interrupt;
|
|
863
|
+
const decidedMembers = outcomes.map((outcome) => outcome.member);
|
|
864
|
+
yield* Effect.uninterruptible(Effect.gen(function* () {
|
|
865
|
+
const { childRuns, fibers } = losingWork(node.id, children.filter((_, index) => !decidedMembers.includes(members[index])));
|
|
866
|
+
if (journaled) {
|
|
867
|
+
yield* runtime.deferredDone(journal, {
|
|
868
|
+
flowName: instance.flow._tag,
|
|
869
|
+
executionId: instance.executionId,
|
|
870
|
+
deferredName: journal.name,
|
|
871
|
+
exit: Exit.succeed({ members: decidedMembers, children: childRuns })
|
|
872
|
+
});
|
|
873
|
+
}
|
|
874
|
+
// The interruption is requested, not awaited: a loser's finalizers
|
|
875
|
+
// may wait on work that only settles after the join returns, and
|
|
876
|
+
// the node scope awaits every fiber when the walk ends anyway.
|
|
877
|
+
yield* Effect.withFiber((current) => Effect.sync(() => {
|
|
878
|
+
for (const fiber of fibers)
|
|
879
|
+
fiber.interruptUnsafe(current.id);
|
|
880
|
+
}));
|
|
881
|
+
yield* cancelChildren(childRuns);
|
|
882
|
+
}));
|
|
883
|
+
return yield* verdict;
|
|
884
|
+
});
|
|
885
|
+
const compute = Effect.fnUntraced(function* (node) {
|
|
886
|
+
const children = sources.get(node.id) ?? [];
|
|
887
|
+
const ast = node.ast;
|
|
888
|
+
if (ast._tag === "AndThen" && ast.next !== undefined) {
|
|
889
|
+
// An explicit sequence is a success boundary for the WHOLE next
|
|
890
|
+
// subtree. Joining first and then concurrently lets a nested All,
|
|
891
|
+
// Map, or inline call start effects before its prerequisite settles.
|
|
892
|
+
// bindPlanned is distinct: it declares data dependencies and can
|
|
893
|
+
// expose independent work while the producer is still running.
|
|
894
|
+
yield* settleNode(children[0]);
|
|
895
|
+
return yield* settleNode(children[1]);
|
|
896
|
+
}
|
|
897
|
+
if (ast._tag === "Branch") {
|
|
898
|
+
// The predicate decides on the REAL value, and only the arm it chose
|
|
899
|
+
// is settled. Both arms are still in the plan; the untaken one is
|
|
900
|
+
// reported as skipped.
|
|
901
|
+
const decide = Node.predicate(ast);
|
|
902
|
+
const subject = yield* settleNode(children[0]);
|
|
903
|
+
return yield* settleNode(decide(subject) ? children[1] : children[2]);
|
|
904
|
+
}
|
|
905
|
+
if (ast._tag === "Catch") {
|
|
906
|
+
// DECIDED: catch observes only typed
|
|
907
|
+
// failures from ordinary protected execution. Interpreter refusals
|
|
908
|
+
// propagate unchanged. Effect defects and
|
|
909
|
+
// compensation failures remain outside this interpreter's error
|
|
910
|
+
// channel, so recovery cannot conceal a broken invariant or weaken
|
|
911
|
+
// withRollback's compensation guarantees.
|
|
912
|
+
const protectedId = children[0];
|
|
913
|
+
return yield* Effect.matchEffect(settleNode(protectedId), {
|
|
914
|
+
onFailure: (error) => {
|
|
915
|
+
if (Schema.is(InterpreterError)(error))
|
|
916
|
+
return Effect.fail(error);
|
|
917
|
+
const filter = Node.catchFilter(ast);
|
|
918
|
+
if (filter !== undefined && !Schema.is(filter)(error)) {
|
|
919
|
+
return Effect.fail(error);
|
|
920
|
+
}
|
|
921
|
+
// The failure arm's planned subject resolves through the
|
|
922
|
+
// protected node id. Filing the typed failure as that settlement
|
|
923
|
+
// also prevents its static failure edge from re-running the
|
|
924
|
+
// protected graph while the recovery arm is driven.
|
|
925
|
+
failed.set(protectedId, error);
|
|
926
|
+
return settleNode(children[1]);
|
|
927
|
+
},
|
|
928
|
+
onSuccess: Effect.succeed
|
|
929
|
+
});
|
|
930
|
+
}
|
|
931
|
+
if (ast._tag === "Race") {
|
|
932
|
+
// A join must not wait for every member, so its members are left
|
|
933
|
+
// out of the dependency barrier below; its other dependencies — the
|
|
934
|
+
// references and barriers it reads — still settle first.
|
|
935
|
+
const members = new Set(children);
|
|
936
|
+
yield* Effect.forEach(KeyMaterial.dependencies(node.draft.material).filter((dependency) => !members.has(dependency) && !failed.has(dependency)), settleNode, { concurrency: "unbounded", discard: true });
|
|
937
|
+
return yield* join(node, ast, children);
|
|
938
|
+
}
|
|
939
|
+
// Dependency order, from the key material: the same `Ref` and `Pending`
|
|
940
|
+
// inputs the plan turns into edges, settled before the node that reads
|
|
941
|
+
// them — and settled CONCURRENTLY, because independent dependencies are
|
|
942
|
+
// exactly what the scheduler admits under separate permits. Two
|
|
943
|
+
// execution surfaces disagreeing about concurrency is a correctness
|
|
944
|
+
// hazard, not just a latency one.
|
|
945
|
+
yield* Effect.forEach(KeyMaterial.dependencies(node.draft.material).filter((dependency) => !failed.has(dependency)), settleNode, { concurrency: "unbounded", discard: true });
|
|
946
|
+
switch (ast._tag) {
|
|
947
|
+
case "ActionCall":
|
|
948
|
+
// Resolved by the pre-pass above, which refuses the whole graph
|
|
949
|
+
// when an action it names has no implementation. The structural
|
|
950
|
+
// node address is durable dispatch identity, scoped to this one
|
|
951
|
+
// implementation call; it never comes from fiber arrival order.
|
|
952
|
+
return yield* implementations.get(ast.action).action(resolve(node.payload)).pipe(Effect.provideService(DispatchSite, node.id), attenuateCapabilities(node.capabilityCeilings));
|
|
953
|
+
case "Succeed":
|
|
954
|
+
return resolve(node.payload);
|
|
955
|
+
case "Fail":
|
|
956
|
+
// The typed error channel, from a constant the plan already holds.
|
|
957
|
+
// An enclosing catch observes it exactly as it observes an action's
|
|
958
|
+
// failure, and an uncaught one is the body's typed failure.
|
|
959
|
+
return yield* Effect.fail(resolve(node.payload));
|
|
960
|
+
case "Map": {
|
|
961
|
+
const transform = Node.mapper(ast);
|
|
962
|
+
return transform(yield* settleNode(children[0]));
|
|
963
|
+
}
|
|
964
|
+
case "All": {
|
|
965
|
+
// `All` is a combination, so its members settle concurrently — the
|
|
966
|
+
// same semantics `PlanScheduler` gives the same graph. Fail-fast
|
|
967
|
+
// matches `Effect.forEach`'s default and the scheduler's halt
|
|
968
|
+
// rule — with the ownership caveat: what a failure interrupts is
|
|
969
|
+
// each sibling's JOIN, not the node execution itself, which the
|
|
970
|
+
// interpretation owns and interrupts when the walk ends. A node
|
|
971
|
+
// whose execution was interrupted is evicted from the memo and
|
|
972
|
+
// records nothing, so it reports as skipped rather than as a
|
|
973
|
+
// phantom success.
|
|
974
|
+
const members = Object.keys(ast.nodes);
|
|
975
|
+
const values = yield* Effect.forEach(members, (_, index) => settleNode(children[index]), {
|
|
976
|
+
concurrency: "unbounded"
|
|
977
|
+
});
|
|
978
|
+
const joined = Object.create(null);
|
|
979
|
+
for (let index = 0; index < members.length; index++) {
|
|
980
|
+
Object.defineProperty(joined, members[index], {
|
|
981
|
+
configurable: true,
|
|
982
|
+
enumerable: true,
|
|
983
|
+
value: values[index],
|
|
984
|
+
writable: true
|
|
985
|
+
});
|
|
986
|
+
}
|
|
987
|
+
return joined;
|
|
988
|
+
}
|
|
989
|
+
case "AndThen":
|
|
990
|
+
return yield* settleNode(children[1]);
|
|
991
|
+
case "FlowCall": {
|
|
992
|
+
if (ast.mode === "handoff") {
|
|
993
|
+
const declaration = handoffDeclarations.get(node.id);
|
|
994
|
+
// DECIDED: a handoff target's schema
|
|
995
|
+
// services come from the context registration captured. A
|
|
996
|
+
// body's type cannot enumerate declarations hidden in its
|
|
997
|
+
// topology, so erase only that dynamic service parameter after
|
|
998
|
+
// resolving the concrete declaration here.
|
|
999
|
+
const payload = yield* Effect.flatMap(Effect.orDie(declaration.payloadSchema.makeEffect(resolve(node.payload))), (decoded) => Effect.orDie(Schema.encodeEffect(Schema.toCodecJson(declaration.payloadSchema))(decoded)));
|
|
1000
|
+
const ceiling = (yield* attenuateCapabilities(node.capabilityCeilings)(CapabilitySet.current)).groups;
|
|
1001
|
+
const outcome = {
|
|
1002
|
+
_tag: "To",
|
|
1003
|
+
flow: ast.flow,
|
|
1004
|
+
payload,
|
|
1005
|
+
...(ceiling.length === 0 ? {} : { capabilityCeilings: ceiling })
|
|
1006
|
+
};
|
|
1007
|
+
Object.defineProperty(outcome, OutcomeValueTypeId, {
|
|
1008
|
+
configurable: false,
|
|
1009
|
+
enumerable: false,
|
|
1010
|
+
value: "To",
|
|
1011
|
+
writable: false
|
|
1012
|
+
});
|
|
1013
|
+
return outcome;
|
|
1014
|
+
}
|
|
1015
|
+
if (ast.mode === "boundary") {
|
|
1016
|
+
// The real boundary of `docs/guides/run-a-child-flow.md`: an
|
|
1017
|
+
// ordinary child execution, opened through the same `execute` a
|
|
1018
|
+
// handler would call. The parent's `FlowInstance` is in scope, so
|
|
1019
|
+
// the engine records the lineage edge, interrupts the child with
|
|
1020
|
+
// the parent, and turns a suspended child into a suspended
|
|
1021
|
+
// parent — genuine nesting, not a spliced imitation of it. It is
|
|
1022
|
+
// deliberately NOT `Effect.scoped`: the interrupt link the engine
|
|
1023
|
+
// installs is a finalizer of the PARENT's scope, and closing a
|
|
1024
|
+
// scope of our own here would run it the moment the child
|
|
1025
|
+
// settled. The same dynamic schema-service erasure the handoff
|
|
1026
|
+
// above records applies to the cast: a body's type cannot
|
|
1027
|
+
// enumerate the declarations hidden in its topology.
|
|
1028
|
+
const declaration = childDeclarations.get(node.id);
|
|
1029
|
+
const instance = yield* FlowInstance;
|
|
1030
|
+
const childPayload = resolve(node.payload);
|
|
1031
|
+
const executionId = yield* childExecutionId(instance.executionId, node.id, declaration._tag, childPayload);
|
|
1032
|
+
childExecutions.set(node.id, executionId);
|
|
1033
|
+
return yield* declaration.execute(childPayload, {
|
|
1034
|
+
executionId
|
|
1035
|
+
}).pipe(attenuateCapabilities(node.capabilityCeilings));
|
|
1036
|
+
}
|
|
1037
|
+
const spliced = children[0];
|
|
1038
|
+
if (spliced === undefined) {
|
|
1039
|
+
return yield* refuse("unsupported_call", node.id, `Flow "${ast.flow}" is called at "${node.id}" as a leaf, which this interpreter does not drive. ` +
|
|
1040
|
+
"An inline .call() is spliced into the graph with the callee's body and driven with it, and " +
|
|
1041
|
+
"only a call that lost its declaration has no body to splice. Build and interpret the authored " +
|
|
1042
|
+
`node in the same process, or call it as ${ast.flow}.child(payload) to run it as its own execution.`);
|
|
1043
|
+
}
|
|
1044
|
+
return yield* settleNode(spliced);
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
});
|
|
1048
|
+
// Closing the node scope on every exit — value, failure, or the
|
|
1049
|
+
// interpretation's own interruption — is the fail-fast half of the
|
|
1050
|
+
// ownership model: whatever is still running when the walk ends is
|
|
1051
|
+
// interrupted before the interpretation reports, so no execution
|
|
1052
|
+
// outlives it.
|
|
1053
|
+
/**
|
|
1054
|
+
* Everything the walk never reached, recorded once the walk is over.
|
|
1055
|
+
*
|
|
1056
|
+
* Only a walk that ENDED reports this. An interrupted one is a run that
|
|
1057
|
+
* parked or was cancelled, and its nodes are not skipped — the resumed
|
|
1058
|
+
* walk settles them, and recording them as skipped first would leave the
|
|
1059
|
+
* journal claiming a node never ran that later did. A typed failure is
|
|
1060
|
+
* terminal for the round, so its stranded nodes are skipped exactly as a
|
|
1061
|
+
* completed walk's untaken branch arm is.
|
|
1062
|
+
*/
|
|
1063
|
+
const recordSkipped = (exit) => Exit.isFailure(exit) && Cause.hasInterruptsOnly(exit.cause) ? Effect.void : Effect.forEach(graphNodes.filter((node) => !reported.has(node.id)), (node) => {
|
|
1064
|
+
const action = actionTag(node.ast);
|
|
1065
|
+
return record(() => ({
|
|
1066
|
+
_tag: "NodeSettled",
|
|
1067
|
+
sourceId: `node/${node.id}/1/settled`,
|
|
1068
|
+
nodeId: node.id,
|
|
1069
|
+
outcome: "skipped",
|
|
1070
|
+
attempts: 0,
|
|
1071
|
+
// A node the walk never reached dispatched nothing, so it claims
|
|
1072
|
+
// no step key and there is no value it settled with.
|
|
1073
|
+
stepKeyDigests: [],
|
|
1074
|
+
...(action === undefined ? {} : { action })
|
|
1075
|
+
}));
|
|
1076
|
+
}, { discard: true });
|
|
1077
|
+
const value = yield* settleNode(options.root ?? "root").pipe(Effect.onExit((exit) => Scope.close(nodes, exit)), Effect.onExit(recordSkipped));
|
|
1078
|
+
return {
|
|
1079
|
+
value,
|
|
1080
|
+
settled,
|
|
1081
|
+
failed,
|
|
1082
|
+
skipped: Graph.nodes(graph).filter((node) => !settled.has(node.id) && !failed.has(node.id)).map((node) => node.id)
|
|
1083
|
+
};
|
|
1084
|
+
});
|
|
1085
|
+
/** A child execution a losing join member opened. @private */
|
|
1086
|
+
const JoinChild = Schema.Struct({ node: Schema.String, executionId: Schema.String });
|
|
1087
|
+
/**
|
|
1088
|
+
* The members a join decided on, in the order they decided, and the child
|
|
1089
|
+
* executions its losers had opened. A bare member list is the record written
|
|
1090
|
+
* before losing children were journaled; it names none, and still replays.
|
|
1091
|
+
*
|
|
1092
|
+
* @private
|
|
1093
|
+
*/
|
|
1094
|
+
const JoinDecision = Schema.Union([
|
|
1095
|
+
Schema.Struct({ members: Schema.Array(Schema.String), children: Schema.Array(JoinChild) }),
|
|
1096
|
+
Schema.Array(Schema.String)
|
|
1097
|
+
]);
|
|
1098
|
+
/**
|
|
1099
|
+
* How a member's settlement counts toward a join: a value or a typed failure
|
|
1100
|
+
* decides, an interruption is still undecided, and a defect or interpreter
|
|
1101
|
+
* refusal is fatal to the join.
|
|
1102
|
+
*
|
|
1103
|
+
* @private
|
|
1104
|
+
*/
|
|
1105
|
+
const classify = (exit) => {
|
|
1106
|
+
if (Exit.isSuccess(exit))
|
|
1107
|
+
return "decisive";
|
|
1108
|
+
if (Cause.hasInterruptsOnly(exit.cause))
|
|
1109
|
+
return "pending";
|
|
1110
|
+
const reasons = exit.cause.reasons;
|
|
1111
|
+
const typed = reasons.every((reason) => reason._tag !== "Die") &&
|
|
1112
|
+
reasons.some((reason) => reason._tag === "Fail") &&
|
|
1113
|
+
!reasons.some((reason) => reason._tag === "Fail" && Schema.is(InterpreterError)(reason.error));
|
|
1114
|
+
return typed ? "decisive" : "fatal";
|
|
1115
|
+
};
|
|
1116
|
+
/**
|
|
1117
|
+
* A join's verdict over the members that decided, in the order they decided,
|
|
1118
|
+
* or `undefined` while it is still open.
|
|
1119
|
+
*
|
|
1120
|
+
* `race` takes the first settlement. `any` and `quorum` succeed once `count`
|
|
1121
|
+
* members succeed, and fail with the failure that leaves too few members able
|
|
1122
|
+
* to: for `any`, that is the last one.
|
|
1123
|
+
*
|
|
1124
|
+
* @private
|
|
1125
|
+
*/
|
|
1126
|
+
const decide = (ast, width, outcomes) => {
|
|
1127
|
+
if (ast.mode === "race")
|
|
1128
|
+
return outcomes[0]?.exit;
|
|
1129
|
+
const successes = outcomes.filter((outcome) => Exit.isSuccess(outcome.exit));
|
|
1130
|
+
if (successes.length >= ast.count) {
|
|
1131
|
+
if (ast.mode === "any")
|
|
1132
|
+
return successes[0].exit;
|
|
1133
|
+
const joined = Object.create(null);
|
|
1134
|
+
for (const success of successes) {
|
|
1135
|
+
Object.defineProperty(joined, success.member, {
|
|
1136
|
+
configurable: true,
|
|
1137
|
+
enumerable: true,
|
|
1138
|
+
value: success.exit.value,
|
|
1139
|
+
writable: true
|
|
1140
|
+
});
|
|
1141
|
+
}
|
|
1142
|
+
return Exit.succeed(joined);
|
|
1143
|
+
}
|
|
1144
|
+
if (outcomes.length - successes.length > width - ast.count)
|
|
1145
|
+
return outcomes[outcomes.length - 1].exit;
|
|
1146
|
+
return undefined;
|
|
1147
|
+
};
|
|
1148
|
+
/**
|
|
1149
|
+
* Turns a body's root value into the settlement the engine acts on.
|
|
1150
|
+
*
|
|
1151
|
+
* Three of them exist, and only three
|
|
1152
|
+
* (`docs/concepts/trampoline-rounds.md`): `done(value)` is the answer, so the
|
|
1153
|
+
* value passes straight through; `Next.to(payload)` is the next round, recorded in the instance's
|
|
1154
|
+
* handoff slot so `Flow.intoResult` answers `Flow.Handoff`; and `park(reason)`
|
|
1155
|
+
* is a durable suspension, declared through the ordinary waiting vocabulary so
|
|
1156
|
+
* a durable driver parks the run under the flow's own reason and wake token
|
|
1157
|
+
* rather than the derived `timer`/`event` default. Anything else is an ordinary
|
|
1158
|
+
* value and is the answer as it stands, which is what keeps a body that never
|
|
1159
|
+
* heard of the trampoline working unchanged.
|
|
1160
|
+
*
|
|
1161
|
+
* @private
|
|
1162
|
+
*/
|
|
1163
|
+
const settleOutcome = (value) => {
|
|
1164
|
+
if (!Outcome.isOutcome(value))
|
|
1165
|
+
return Effect.succeed(value);
|
|
1166
|
+
switch (value._tag) {
|
|
1167
|
+
case "Done":
|
|
1168
|
+
return Effect.succeed(value.value);
|
|
1169
|
+
case "To":
|
|
1170
|
+
return Effect.gen(function* () {
|
|
1171
|
+
const instance = yield* FlowInstance;
|
|
1172
|
+
const current = yield* CapabilitySet.current;
|
|
1173
|
+
instance.handoff = new Handoff({
|
|
1174
|
+
flow: value.flow,
|
|
1175
|
+
payload: value.payload,
|
|
1176
|
+
capabilityCeilings: [...current.groups, ...(value.capabilityCeilings ?? [])]
|
|
1177
|
+
});
|
|
1178
|
+
});
|
|
1179
|
+
case "Park":
|
|
1180
|
+
return Effect.andThen(annotateWaiting(value.reason), Effect.flatMap(FlowInstance, suspend));
|
|
1181
|
+
}
|
|
1182
|
+
};
|
|
1183
|
+
/**
|
|
1184
|
+
* Registers a flow with the runtime, driven by its body.
|
|
1185
|
+
*
|
|
1186
|
+
* This is the only way a flow's behavior reaches the runtime, and the reason a
|
|
1187
|
+
* flow has no `toLayer`: a flow has one behavior and it is the body, so there
|
|
1188
|
+
* is no second, opaque one to attach. An Action is what carries an
|
|
1189
|
+
* implementation, and it attaches that implementation with its own `toLayer`.
|
|
1190
|
+
* Compose this beside the action implementation layers the body calls, over the
|
|
1191
|
+
* {@link module:Implementations.layerImplementations} table they file
|
|
1192
|
+
* themselves in — the table goes UNDER them, because filing happens while an
|
|
1193
|
+
* implementation layer is built:
|
|
1194
|
+
*
|
|
1195
|
+
* ```ts
|
|
1196
|
+
* Layer.mergeAll(Read.toLayer(read), Write.toLayer(write), Interpreter.layer(Pipeline)).pipe(
|
|
1197
|
+
* Layer.provideMerge(Action.layerImplementations)
|
|
1198
|
+
* )
|
|
1199
|
+
* ```
|
|
1200
|
+
*
|
|
1201
|
+
* It accepts a flow whatever its requirement channel says, and requires only
|
|
1202
|
+
* the table. Registering a body is not running one: the handler installed here
|
|
1203
|
+
* resolves each call by tag when the run reaches it, which is the same thing a
|
|
1204
|
+
* driver does for a plan read back out of a journal. `Flow.execute` is where
|
|
1205
|
+
* the requirements are asked for.
|
|
1206
|
+
*
|
|
1207
|
+
* This low-level registration keeps the `process-local` callback policy for
|
|
1208
|
+
* compatibility. Select `callbackIdentity: "stable"`, or use
|
|
1209
|
+
* {@link layerWithImplementations}, to refuse unstable callbacks before
|
|
1210
|
+
* dispatch. Without that selection it makes no reproducible-plan guarantee.
|
|
1211
|
+
*
|
|
1212
|
+
* @since 0.1.0
|
|
1213
|
+
* @category layers
|
|
1214
|
+
*/
|
|
1215
|
+
export const layer = (flow, options = {}) => makeLayer(flow, options, false);
|
|
1216
|
+
/** Registers one interpreter policy without changing the low-level compatibility surface. @private */
|
|
1217
|
+
const makeLayer = (flow, options, requireReusableVersions, reachable, ifAbsent = false) => Layer.effectDiscard(Effect.gen(function* () {
|
|
1218
|
+
const runtime = yield* FlowRuntime;
|
|
1219
|
+
const registered = reachable ?? new Set();
|
|
1220
|
+
if (registered.has(flow))
|
|
1221
|
+
return;
|
|
1222
|
+
// Lazy registration can be skipped by an independent live owner. Recheck
|
|
1223
|
+
// on later executions so releasing that owner permits fresh admission.
|
|
1224
|
+
if (!ifAbsent)
|
|
1225
|
+
registered.add(flow);
|
|
1226
|
+
const scope = yield* Effect.scope;
|
|
1227
|
+
const context = yield* Effect.context();
|
|
1228
|
+
const implementations = Context.get(context, Implementations);
|
|
1229
|
+
// Keep discovered declarations in the host registration scope, rather
|
|
1230
|
+
// than the executing parent's scope: handoffs outlive that parent.
|
|
1231
|
+
// Dynamic declarations erase their codec services, just like execute;
|
|
1232
|
+
// those services still come from the registering host's context.
|
|
1233
|
+
const registerReachable = (declaration) => Layer.buildWithScope(makeLayer(declaration, options, requireReusableVersions, registered, true), scope).pipe(Effect.provide(context), Effect.asVoid);
|
|
1234
|
+
const middleware = Context.get(flow.annotations, ExecutionMiddleware);
|
|
1235
|
+
yield* runtime.register(flow, ((payload) => {
|
|
1236
|
+
const body = Effect.flatMap(interpretWithPolicy(flow, payload, options, requireReusableVersions, registerReachable), (interpretation) => settleOutcome(interpretation.value));
|
|
1237
|
+
// Registration owns action resolution. A caller may supply a different
|
|
1238
|
+
// table when entering this flow as a child; retain dynamic execution
|
|
1239
|
+
// services while restoring this flow's registered implementations.
|
|
1240
|
+
return (middleware === undefined ? body : middleware.wrap(payload, body)).pipe(Effect.provideService(Implementations, implementations));
|
|
1241
|
+
}), { ifAbsent });
|
|
1242
|
+
}));
|
|
1243
|
+
/**
|
|
1244
|
+
* Composes one flow and its implementation layers around one isolated registry.
|
|
1245
|
+
*
|
|
1246
|
+
* The implementation layer must provide every action requirement carried by the
|
|
1247
|
+
* flow. Callback identity defaults to `stable`: raw callbacks are refused before
|
|
1248
|
+
* dispatch, with a diagnostic naming the callback and `Node.capture`. Captures
|
|
1249
|
+
* must cover semantic configuration and implementation versions, including
|
|
1250
|
+
* behavior reached through imports. Explicit `callbackIdentity: "process-local"`
|
|
1251
|
+
* is available for local experimentation, without a reproducible-plan claim.
|
|
1252
|
+
* Sealed actions with an idempotency key must declare `implementationVersion`
|
|
1253
|
+
* and register that exact version. This rule is independent of callback policy:
|
|
1254
|
+
* those declarations can reuse recorded content even if their code changes.
|
|
1255
|
+
* Keyless sealed actions use invocation identity; compensable and irreversible
|
|
1256
|
+
* actions do not use content-key caching. Their versions remain optional.
|
|
1257
|
+
* Versions must cover semantic handler and service changes; callback stability
|
|
1258
|
+
* cannot establish that declaration's completeness.
|
|
1259
|
+
*
|
|
1260
|
+
* The interpreter validates all named graph actions before dispatching any
|
|
1261
|
+
* of them. Conflicting registrations fail while the layer builds; use an explicit
|
|
1262
|
+
* `toLayer(handler, { override: true })` only for intentional scoped substitution.
|
|
1263
|
+
*
|
|
1264
|
+
* @category layers
|
|
1265
|
+
* @since 0.1.0
|
|
1266
|
+
*/
|
|
1267
|
+
export const layerWithImplementations = (flow, implementations, options = {}) => Layer.merge(implementations, makeLayer(flow, { ...options, callbackIdentity: options.callbackIdentity ?? "stable" }, true))
|
|
1268
|
+
.pipe(Layer.provide(Layer.fresh(layerImplementations)));
|
|
1269
|
+
//# sourceMappingURL=Interpreter.js.map
|