@swirls/looms 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (253) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/dist/actor/cell.d.ts +21 -2
  3. package/dist/actor/cell.js +15 -3
  4. package/dist/actor/host.d.ts +10 -2
  5. package/dist/actor/host.js +27 -4
  6. package/dist/actor/index.d.ts +2 -1
  7. package/dist/actor/index.js +1 -1
  8. package/dist/actor/routing.d.ts +3 -0
  9. package/dist/actor/routing.js +13 -2
  10. package/dist/agent/debugger/agent-chat.js +3 -30
  11. package/dist/agent/debugger/streaming-text.d.ts +6 -0
  12. package/dist/agent/debugger/streaming-text.js +33 -0
  13. package/dist/agent/definitions-store.d.ts +1 -1
  14. package/dist/agent/definitions-store.js +1 -1
  15. package/dist/agent/definitions.d.ts +48 -0
  16. package/dist/agent/definitions.js +44 -0
  17. package/dist/agent/effects.d.ts +184 -0
  18. package/dist/agent/effects.js +172 -40
  19. package/dist/agent/events.d.ts +89 -0
  20. package/dist/agent/events.js +25 -0
  21. package/dist/agent/module.d.ts +819 -407
  22. package/dist/agent/module.js +5 -1
  23. package/dist/agent/scope.d.ts +88 -0
  24. package/dist/agent/threads.js +39 -3
  25. package/dist/agent/types.d.ts +4 -0
  26. package/dist/approval/effects.d.ts +40 -0
  27. package/dist/approval/module.d.ts +80 -0
  28. package/dist/client/client.d.ts +468 -119
  29. package/dist/client/client.js +214 -21
  30. package/dist/client/index.d.ts +1 -1
  31. package/dist/client/sse.d.ts +1 -1
  32. package/dist/client/sse.js +2 -2
  33. package/dist/cloudflare/durable-object.d.ts +66 -2
  34. package/dist/cloudflare/durable-object.js +96 -1
  35. package/dist/cloudflare/event-store.d.ts +7 -3
  36. package/dist/cloudflare/event-store.js +16 -3
  37. package/dist/cloudflare/index.d.ts +7 -1
  38. package/dist/cloudflare/index.js +7 -1
  39. package/dist/cloudflare/memory-queue.d.ts +18 -0
  40. package/dist/cloudflare/memory-queue.js +53 -0
  41. package/dist/cloudflare/queue-consumer.d.ts +52 -0
  42. package/dist/cloudflare/queue-consumer.js +102 -0
  43. package/dist/cloudflare/queue-message.d.ts +31 -0
  44. package/dist/cloudflare/queue-message.js +33 -0
  45. package/dist/cloudflare/queue-pull.d.ts +62 -0
  46. package/dist/cloudflare/queue-pull.js +130 -0
  47. package/dist/cloudflare/queue-worker.d.ts +39 -0
  48. package/dist/cloudflare/queue-worker.js +62 -0
  49. package/dist/cloudflare/r2-blob-store.d.ts +33 -0
  50. package/dist/cloudflare/r2-blob-store.js +60 -0
  51. package/dist/cloudflare/router.d.ts +4 -1
  52. package/dist/cloudflare/router.js +8 -3
  53. package/dist/cloudflare/websocket.d.ts +27 -0
  54. package/dist/cloudflare/websocket.js +215 -0
  55. package/dist/core/attempts.d.ts +8 -0
  56. package/dist/core/attempts.js +27 -0
  57. package/dist/core/blob-offload.d.ts +21 -0
  58. package/dist/core/blob-offload.js +143 -0
  59. package/dist/core/blob-store.d.ts +45 -0
  60. package/dist/core/blob-store.js +164 -0
  61. package/dist/core/blobs.d.ts +37 -0
  62. package/dist/core/blobs.js +160 -0
  63. package/dist/core/definition-resolver.d.ts +40 -0
  64. package/dist/core/definition-resolver.js +129 -0
  65. package/dist/core/definition-store.d.ts +6 -0
  66. package/dist/core/definition-store.js +37 -1
  67. package/dist/core/effects.d.ts +86 -3
  68. package/dist/core/effects.js +99 -3
  69. package/dist/core/envelope.d.ts +9 -0
  70. package/dist/core/envelope.js +4 -0
  71. package/dist/core/event-codec.js +3 -0
  72. package/dist/core/fold.js +83 -1
  73. package/dist/core/ids.d.ts +2 -0
  74. package/dist/core/ids.js +4 -0
  75. package/dist/core/index.d.ts +10 -0
  76. package/dist/core/index.js +9 -0
  77. package/dist/core/launcher.d.ts +97 -0
  78. package/dist/core/launcher.js +67 -0
  79. package/dist/core/module-scope.d.ts +7 -3
  80. package/dist/core/module-scope.js +3 -0
  81. package/dist/core/module.d.ts +10 -0
  82. package/dist/core/projection.d.ts +5 -4
  83. package/dist/core/projection.js +4 -0
  84. package/dist/core/protocol.d.ts +36 -1
  85. package/dist/core/protocol.js +35 -0
  86. package/dist/core/published.d.ts +2 -0
  87. package/dist/core/published.js +4 -1
  88. package/dist/{react → core}/register.d.ts +9 -8
  89. package/dist/core/replay.d.ts +96 -0
  90. package/dist/core/run-context.d.ts +33 -0
  91. package/dist/core/run-context.js +23 -0
  92. package/dist/core/sqlite-store.js +57 -5
  93. package/dist/core/state.d.ts +60 -0
  94. package/dist/core/state.js +19 -0
  95. package/dist/core/store.d.ts +6 -0
  96. package/dist/core/store.js +7 -0
  97. package/dist/core/subscription.d.ts +97 -0
  98. package/dist/core/subscription.js +224 -0
  99. package/dist/core/thread.d.ts +7 -0
  100. package/dist/core/thread.js +1 -0
  101. package/dist/core/trace.d.ts +21 -0
  102. package/dist/core/trace.js +45 -0
  103. package/dist/core/worker-callback.d.ts +25 -1
  104. package/dist/debugger/app/assets/{index-CgDzWJg7.css → index-B7GdT1UA.css} +1 -1
  105. package/dist/debugger/app/assets/index-C31SnCuN.js +22 -0
  106. package/dist/debugger/app/assets/routes-DPA3O5AF.js +47 -0
  107. package/dist/debugger/app/index.html +2 -2
  108. package/dist/debugger/components/event-inspector.d.ts +3 -1
  109. package/dist/debugger/components/event-inspector.js +25 -4
  110. package/dist/debugger/components/event-stream.d.ts +4 -1
  111. package/dist/debugger/components/event-stream.js +2 -2
  112. package/dist/debugger/components/run-tree.d.ts +5 -2
  113. package/dist/debugger/components/run-tree.js +10 -4
  114. package/dist/debugger/context.d.ts +8 -1
  115. package/dist/debugger/context.js +15 -3
  116. package/dist/debugger/events/protocol-summarize.js +18 -3
  117. package/dist/debugger/index.d.ts +1 -1
  118. package/dist/debugger/index.js +1 -1
  119. package/dist/debugger/shell/debugger-shell.js +1 -1
  120. package/dist/debugger/shell/events-pane.js +4 -3
  121. package/dist/debugger/styles.css +1 -0
  122. package/dist/evaluator/definitions-store.d.ts +3 -4
  123. package/dist/evaluator/definitions-store.js +3 -3
  124. package/dist/evaluator/definitions.d.ts +4 -1
  125. package/dist/evaluator/definitions.js +5 -2
  126. package/dist/evaluator/effects.d.ts +41 -0
  127. package/dist/evaluator/effects.js +6 -2
  128. package/dist/evaluator/module.d.ts +84 -0
  129. package/dist/evaluator/module.js +7 -3
  130. package/dist/evaluator/threads.d.ts +2 -0
  131. package/dist/evaluator/threads.js +4 -0
  132. package/dist/projectors/event-log.d.ts +87 -0
  133. package/dist/projectors/event-log.js +396 -0
  134. package/dist/projectors/index-model.d.ts +12 -0
  135. package/dist/projectors/index-model.js +12 -3
  136. package/dist/projectors/memory.js +7 -0
  137. package/dist/projectors/postgres.d.ts +1 -0
  138. package/dist/projectors/postgres.js +32 -15
  139. package/dist/projectors/schema.d.ts +5 -0
  140. package/dist/projectors/schema.js +26 -1
  141. package/dist/projectors/sqlite.js +47 -19
  142. package/dist/projectors/with-projectors.d.ts +7 -1
  143. package/dist/projectors/with-projectors.js +13 -3
  144. package/dist/react/derived.d.ts +26 -8
  145. package/dist/react/derived.js +23 -26
  146. package/dist/react/index.d.ts +3 -3
  147. package/dist/react/index.js +2 -2
  148. package/dist/react/materialize.js +5 -1
  149. package/dist/react/react.d.ts +16 -9
  150. package/dist/react/react.js +47 -23
  151. package/dist/react/store.d.ts +14 -6
  152. package/dist/react/store.js +104 -12
  153. package/dist/react/tables.d.ts +4 -1
  154. package/dist/runtime/cancellation.d.ts +2 -1
  155. package/dist/runtime/cancellation.js +35 -2
  156. package/dist/runtime/child-run.d.ts +10 -0
  157. package/dist/runtime/child-run.js +58 -0
  158. package/dist/runtime/deadline-transition-planner.js +11 -2
  159. package/dist/runtime/dispatch-effect.d.ts +2 -2
  160. package/dist/runtime/dispatch-effect.js +43 -19
  161. package/dist/runtime/errors.d.ts +14 -1
  162. package/dist/runtime/errors.js +10 -0
  163. package/dist/runtime/filesystem-blob-store.d.ts +6 -0
  164. package/dist/runtime/filesystem-blob-store.js +59 -0
  165. package/dist/runtime/helpers.d.ts +15 -6
  166. package/dist/runtime/helpers.js +46 -6
  167. package/dist/runtime/index.d.ts +4 -2
  168. package/dist/runtime/index.js +3 -1
  169. package/dist/runtime/local-effect-executor.d.ts +11 -2
  170. package/dist/runtime/local-effect-executor.js +80 -4
  171. package/dist/runtime/looms.d.ts +38 -4
  172. package/dist/runtime/looms.js +17 -3
  173. package/dist/runtime/repository.d.ts +5 -4
  174. package/dist/runtime/repository.js +5 -2
  175. package/dist/runtime/retry-policy.d.ts +5 -0
  176. package/dist/runtime/retry-policy.js +10 -2
  177. package/dist/runtime/run-start.d.ts +1 -1
  178. package/dist/runtime/run-start.js +10 -0
  179. package/dist/runtime/runtime.d.ts +2 -2
  180. package/dist/runtime/runtime.js +191 -68
  181. package/dist/runtime/server/auth.d.ts +3 -0
  182. package/dist/runtime/server/fetch-handler.d.ts +2 -2
  183. package/dist/runtime/server/fetch-handler.js +35 -4
  184. package/dist/runtime/server/grant.d.ts +49 -0
  185. package/dist/runtime/server/grant.js +177 -0
  186. package/dist/runtime/server/routes/effects.js +47 -5
  187. package/dist/runtime/server/routes/runs.js +6 -1
  188. package/dist/runtime/server/run-route.d.ts +18 -0
  189. package/dist/runtime/server/run-route.js +26 -0
  190. package/dist/runtime/server.d.ts +3 -1
  191. package/dist/runtime/server.js +3 -1
  192. package/dist/runtime/sse.js +26 -4
  193. package/dist/runtime/subscription.d.ts +8 -0
  194. package/dist/runtime/subscription.js +23 -0
  195. package/dist/runtime/types.d.ts +87 -8
  196. package/dist/runtime/wake-engine.d.ts +4 -2
  197. package/dist/runtime/wake-engine.js +57 -6
  198. package/dist/runtime/worker-callbacks.d.ts +12 -2
  199. package/dist/runtime/worker-callbacks.js +102 -72
  200. package/dist/runtime/worker-effect-dispatch.d.ts +14 -5
  201. package/dist/runtime/worker-effect-dispatch.js +84 -18
  202. package/dist/schedule/debugger/index.d.ts +4 -0
  203. package/dist/schedule/debugger/index.js +11 -0
  204. package/dist/schedule/debugger/schedule-view.d.ts +3 -0
  205. package/dist/schedule/debugger/schedule-view.js +68 -0
  206. package/dist/schedule/debugger/summarize.d.ts +2 -0
  207. package/dist/schedule/debugger/summarize.js +43 -0
  208. package/dist/schedule/definitions-store.d.ts +10 -0
  209. package/dist/schedule/definitions-store.js +8 -0
  210. package/dist/schedule/definitions.d.ts +57 -0
  211. package/dist/schedule/definitions.js +30 -0
  212. package/dist/schedule/effects.d.ts +756 -0
  213. package/dist/schedule/effects.js +244 -0
  214. package/dist/schedule/ensure.d.ts +55 -0
  215. package/dist/schedule/ensure.js +109 -0
  216. package/dist/schedule/events.d.ts +116 -0
  217. package/dist/schedule/events.js +49 -0
  218. package/dist/schedule/index.d.ts +11 -0
  219. package/dist/schedule/index.js +11 -0
  220. package/dist/schedule/module.d.ts +1474 -0
  221. package/dist/schedule/module.js +22 -0
  222. package/dist/schedule/projections.d.ts +55 -0
  223. package/dist/schedule/projections.js +88 -0
  224. package/dist/schedule/scope.d.ts +121 -0
  225. package/dist/schedule/scope.js +7 -0
  226. package/dist/schedule/signals.d.ts +25 -0
  227. package/dist/schedule/signals.js +33 -0
  228. package/dist/schedule/specs.d.ts +59 -0
  229. package/dist/schedule/specs.js +231 -0
  230. package/dist/schedule/threads.d.ts +25 -0
  231. package/dist/schedule/threads.js +259 -0
  232. package/dist/testing/corpus-module.d.ts +34 -0
  233. package/dist/testing/corpus-module.js +87 -0
  234. package/dist/testing/index.d.ts +2 -0
  235. package/dist/testing/index.js +5 -6
  236. package/dist/testing/replay-corpus.d.ts +22 -0
  237. package/dist/testing/replay-corpus.js +50 -0
  238. package/dist/workflow/definitions-store.d.ts +1 -1
  239. package/dist/workflow/definitions-store.js +1 -1
  240. package/dist/workflow/definitions.d.ts +14 -0
  241. package/dist/workflow/effects.d.ts +122 -0
  242. package/dist/workflow/effects.js +16 -6
  243. package/dist/workflow/events.d.ts +42 -0
  244. package/dist/workflow/events.js +4 -0
  245. package/dist/workflow/module.d.ts +345 -32
  246. package/dist/workflow/module.js +7 -3
  247. package/dist/workflow/scope.d.ts +42 -0
  248. package/dist/workflow/threads.d.ts +72 -0
  249. package/dist/workflow/threads.js +106 -38
  250. package/package.json +11 -2
  251. package/dist/debugger/app/assets/index-BevQYa-U.js +0 -22
  252. package/dist/debugger/app/assets/routes-B17Om93i.js +0 -47
  253. /package/dist/{react → core}/register.js +0 -0
@@ -1,13 +1,40 @@
1
1
  import { Data, Effect, Layer, Predicate } from 'effect';
2
- import { asJson, createEvent, definitionKey, descendantThreadIds, isTerminalStatus, makeMemorySnapshotStore, matchingWaits, snapshotStoreOf, validateEventInput, validateEventsEffect, validateInputEffect, withSnapshotStore, InvalidEventError, } from '@swirls/looms/core';
3
- export function workerTask(runId, item, attempt) {
4
- return {
2
+ import { asJson, createEvent, definitionKey, descendantThreadIds, isTerminalStatus, makeMemorySnapshotStore, matchingWaits, snapshotStoreOf, validateEventInput, validateEventsEffect, validateInputEffect, withSnapshotStore, hasEntries, InvalidEventError, } from '@swirls/looms/core';
3
+ export function workerTask(runId, item, attempt, extras = {}) {
4
+ const task = {
5
5
  runId,
6
6
  effectId: item.effectId,
7
7
  attempt,
8
8
  type: item.effect.type,
9
9
  input: 'input' in item.effect ? item.effect.input : null,
10
10
  };
11
+ if (extras.details != null) {
12
+ task.details = extras.details;
13
+ }
14
+ if (extras.secrets && hasEntries(extras.secrets)) {
15
+ task.secrets = extras.secrets;
16
+ }
17
+ if (extras.trace) {
18
+ task.trace = extras.trace;
19
+ }
20
+ const deadlines = workerDeadlines(extras);
21
+ if (deadlines) {
22
+ task.deadlines = deadlines;
23
+ }
24
+ return task;
25
+ }
26
+ function workerDeadlines(extras) {
27
+ const deadlines = {};
28
+ if (extras.scheduleToStartAt != null) {
29
+ deadlines.scheduleToStartAt = extras.scheduleToStartAt;
30
+ }
31
+ if (extras.retry?.startToCloseTimeoutMs) {
32
+ deadlines.startToCloseTimeoutMs = extras.retry.startToCloseTimeoutMs;
33
+ }
34
+ if (extras.retry?.heartbeatTimeoutMs) {
35
+ deadlines.heartbeatTimeoutMs = extras.retry.heartbeatTimeoutMs;
36
+ }
37
+ return Object.keys(deadlines).length > 0 ? deadlines : undefined;
11
38
  }
12
39
  export class UnknownDefinitionError extends Data.TaggedError('UnknownDefinitionError') {
13
40
  constructor(kind, definitionName, definitionVersion) {
@@ -105,15 +132,16 @@ export function groupOutstanding(items) {
105
132
  */
106
133
  export function threadStartedEvents(definitions, args) {
107
134
  return Effect.gen(function* () {
108
- const def = definitions.get(definitionKey(args.kind, args.definitionName, args.definitionVersion));
135
+ const def = yield* definitions.resolve(args.kind, args.definitionName, args.definitionVersion);
109
136
  if (!def) {
110
137
  return yield* new UnknownDefinitionError(args.kind, args.definitionName, args.definitionVersion);
111
138
  }
112
139
  const raw = args.input ?? null;
113
140
  let startedInput = raw;
114
141
  let validationError;
115
- if (def?.input) {
116
- const validated = yield* validateInputEffect(def.input, raw).pipe(Effect.match({
142
+ const input = schemaInputOf(def);
143
+ if (input) {
144
+ const validated = yield* validateInputEffect(input, raw).pipe(Effect.match({
117
145
  onFailure: (error) => ({ ok: false, error: error.message }),
118
146
  onSuccess: (value) => ({ ok: true, value }),
119
147
  }));
@@ -150,6 +178,18 @@ export function threadStartedEvents(definitions, args) {
150
178
  ];
151
179
  });
152
180
  }
181
+ function schemaInputOf(definition) {
182
+ if (!('input' in definition)) {
183
+ return undefined;
184
+ }
185
+ return isSchemaInput(definition.input) ? definition.input : undefined;
186
+ }
187
+ function isSchemaInput(value) {
188
+ if (value === null || (!Predicate.isObject(value) && !Predicate.isFunction(value))) {
189
+ return false;
190
+ }
191
+ return '~standard' in value || 'ast' in value;
192
+ }
153
193
  export function synthesizedThreadFailed(before, after, written) {
154
194
  const out = [];
155
195
  for (const [threadId, thread] of Object.entries(after.threads)) {
@@ -1,5 +1,7 @@
1
- export { createRuntime, createTimeoutScheduler, DuplicateEffectDispatchError, ForkConflictError, MaxWakeIterationsError, RunNotFinishedError, RunNotFoundError, RunOverloadedError, RunTerminalError, StartRunConflictError, type CreateRuntimeOptions, type EffectWorker, type EffectWorkerTask, type LoomsRuntime, type RunOperationalStatus, type RegisteredDefinition, type StartRunArgs, type WakeError, type WakeScheduler, type WorkerCallback, type WorkerCallbackInput, } from './runtime';
1
+ export { createRuntime, createTimeoutScheduler, DuplicateEffectDispatchError, ForkConflictError, MaxWakeIterationsError, RunNotFinishedError, RunNotFoundError, RunOverloadedError, RunTerminalError, StartRunConflictError, type CreateRuntimeOptions, type EffectWorker, type EffectWorkerDeadlines, type EffectWorkerTask, type LoomsRuntime, type RunOperationalStatus, type RegisteredDefinition, type StartRunArgs, type WakeError, type WakeScheduler, type WorkerCallback, type WorkerCallbackInput, type WorkerCallbackResult, } from './runtime';
2
2
  export { createLooms, type Looms, type CreateLoomsOptions, type StartResult } from './looms';
3
- export { serveHttp, createFetchHandler, isLoomsApiPath, type FetchHandlerOptions, authAllowed, authDenied, bearerAuth, defaultAuthorize, type AuthDecision, type AuthorizedAccess, type Authorize, type BearerTokens, type RouteAccess, type RouteInfo, type RunningServer, type LoomsFetchResult, } from './server';
3
+ export { loadSubscriptionOpening, readSubscriptionBounds } from './subscription';
4
+ export { serveHttp, createFetchHandler, isLoomsApiPath, type FetchHandlerOptions, authAllowed, authDenied, authResponse, bearerAuth, defaultAuthorize, grantAuth, issueRunGrant, grantSubprotocol, runGrantToken, verifyRunGrant, GrantError, type GrantAuthOptions, type GrantFailure, type RunGrant, runRouteInfo, type AuthDecision, type AuthorizedAccess, type Authorize, type BearerTokens, type RouteAccess, type RouteInfo, type RunningServer, type LoomsFetchResult, } from './server';
5
+ export { filesystemBlobStore } from './filesystem-blob-store';
4
6
  export { createEventStreamResponse, type EventStreamOptions } from './sse';
5
7
  export { notifyObserver, type RuntimeObservation, type RuntimeObserver } from './observer';
@@ -1,5 +1,7 @@
1
1
  export { createRuntime, createTimeoutScheduler, DuplicateEffectDispatchError, ForkConflictError, MaxWakeIterationsError, RunNotFinishedError, RunNotFoundError, RunOverloadedError, RunTerminalError, StartRunConflictError, } from './runtime.js';
2
2
  export { createLooms } from './looms.js';
3
- export { serveHttp, createFetchHandler, isLoomsApiPath, authAllowed, authDenied, bearerAuth, defaultAuthorize, } from './server.js';
3
+ export { loadSubscriptionOpening, readSubscriptionBounds } from './subscription.js';
4
+ export { serveHttp, createFetchHandler, isLoomsApiPath, authAllowed, authDenied, authResponse, bearerAuth, defaultAuthorize, grantAuth, issueRunGrant, grantSubprotocol, runGrantToken, verifyRunGrant, GrantError, runRouteInfo, } from './server.js';
5
+ export { filesystemBlobStore } from './filesystem-blob-store.js';
4
6
  export { createEventStreamResponse } from './sse.js';
5
7
  export { notifyObserver } from './observer.js';
@@ -1,19 +1,28 @@
1
1
  import { Effect, type Context, type ManagedRuntime } from 'effect';
2
- import { type ComposedRegistry, type EventEnvelope, type OutstandingEffect, type RegisteredDefinition } from '@swirls/looms/core';
2
+ import { type ComposedRegistry, type EventEnvelope, type DefinitionResolver, type OutstandingEffect, type RunAttributes, type RunLauncher, type SecretContext, type TraceContext } from '@swirls/looms/core';
3
3
  import type { RuntimeObserver } from './observer';
4
4
  import type { RuntimeRepository } from './repository';
5
5
  import type { RunExecutionContext } from './run-execution-context';
6
6
  export interface LocalEffectExecutorOptions {
7
7
  readonly registry: ComposedRegistry;
8
8
  readonly services: ManagedRuntime.ManagedRuntime<Context.Service.Any, never>;
9
- readonly definitions: ReadonlyMap<string, RegisteredDefinition>;
9
+ readonly definitionResolver: DefinitionResolver;
10
10
  readonly executionContext: RunExecutionContext;
11
11
  readonly liveAppends: RuntimeRepository['liveAppends'];
12
12
  readonly runInContext: <A, E>(effect: Effect.Effect<A, E>) => Promise<A>;
13
13
  readonly observe: (event: Parameters<RuntimeObserver['observe']>[0]) => void;
14
+ readonly launcher: RunLauncher;
15
+ }
16
+ /** Run-level values every effect sees. Runs the effects launch inherit them unless the launch overrides them. */
17
+ export interface RunContext {
18
+ readonly correlationId: string;
19
+ readonly attributes: RunAttributes;
20
+ readonly secrets: SecretContext;
21
+ readonly trace: TraceContext | null;
14
22
  }
15
23
  export interface ExecuteLocalEffectsInput {
16
24
  readonly runId: string;
25
+ readonly context: RunContext;
17
26
  readonly outstanding: ReadonlyArray<OutstandingEffect>;
18
27
  readonly attempts: ReadonlyMap<string, number>;
19
28
  readonly now: number;
@@ -1,9 +1,64 @@
1
1
  import { Effect, Option, Predicate } from 'effect';
2
- import { createEvent, validateEventInput, withdrawnError, } from '@swirls/looms/core';
2
+ import { createEvent, createRunId, DEFAULT_DEFINITION_VERSION, validateEventInput, withdrawnError, } from '@swirls/looms/core';
3
3
  import { dispatchEffect } from './dispatch-effect.js';
4
4
  import { RuntimeExecutionError } from './errors.js';
5
5
  import { createEffectFailedEvent, createLiveEvent, groupOutstanding, materializeEffectOutcome, } from './helpers.js';
6
6
  import { planAttemptFailure } from './retry-policy.js';
7
+ function runLaunchedPayload(args, targetRunId, effectId) {
8
+ const definitionVersion = args.definitionVersion ?? DEFAULT_DEFINITION_VERSION;
9
+ const base = {
10
+ runId: targetRunId,
11
+ kind: args.kind,
12
+ definitionName: args.definitionName,
13
+ definitionVersion,
14
+ effectId,
15
+ };
16
+ if (args.close === undefined) {
17
+ return base;
18
+ }
19
+ return { ...base, close: args.close };
20
+ }
21
+ /**
22
+ * Stamps every launch with the effect that made it, replacing anything the handler passed, and
23
+ * records a `runtime.run.launched` event for each launch the target accepts.
24
+ */
25
+ function launcherFor(launcher, runId, context, item, launched) {
26
+ const launchedBy = {
27
+ runId,
28
+ threadId: item.threadId,
29
+ effectId: item.effectId,
30
+ causationId: item.causingEventId,
31
+ causationSeq: item.causingSeq,
32
+ };
33
+ return {
34
+ launch: (args) => {
35
+ const targetRunId = args.runId ?? createRunId();
36
+ return launcher
37
+ .launch({
38
+ ...args,
39
+ runId: targetRunId,
40
+ launchedBy,
41
+ correlationId: context.correlationId,
42
+ attributes: args.attributes ?? context.attributes,
43
+ secrets: args.secrets ?? context.secrets,
44
+ trace: args.trace ?? context.trace ?? undefined,
45
+ })
46
+ .pipe(Effect.tap(() => Effect.sync(() => {
47
+ launched.push(createEvent(runId, {
48
+ type: 'runtime.run.launched',
49
+ payload: runLaunchedPayload(args, targetRunId, item.effectId),
50
+ threadId: item.threadId,
51
+ causationId: item.causingEventId,
52
+ correlationId: context.correlationId,
53
+ origin: { type: 'thread', threadId: item.threadId },
54
+ }));
55
+ })));
56
+ },
57
+ status: launcher.status,
58
+ signal: (targetRunId, events, options) => launcher.signal(targetRunId, events, options),
59
+ cancel: (targetRunId) => launcher.cancel(targetRunId),
60
+ };
61
+ }
7
62
  function makeAbortController() {
8
63
  return new AbortController();
9
64
  }
@@ -20,7 +75,7 @@ function abortion(signal) {
20
75
  });
21
76
  }
22
77
  export function executeLocalEffects(options, input) {
23
- const { registry, services, definitions, executionContext, liveAppends, runInContext, observe } = options;
78
+ const { registry, services, definitionResolver, executionContext, liveAppends, runInContext, observe, launcher, } = options;
24
79
  return Effect.forEach(groupOutstanding(input.outstanding), (groupKey) => Effect.gen(function* () {
25
80
  const group = input.outstanding.filter((item) => `${item.threadId}:${item.causingSeq}` === groupKey);
26
81
  const produced = [];
@@ -43,11 +98,15 @@ export function executeLocalEffects(options, input) {
43
98
  threadId: item.threadId,
44
99
  });
45
100
  const retry = registry.effects.get(item.effect.type)?.retry;
46
- const dispatched = dispatchEffect(registry, services, definitions, item.effect, {
101
+ const launched = [];
102
+ const dispatched = dispatchEffect(registry, services, definitionResolver, launcherFor(launcher, input.runId, input.context, item, launched), item.effect, {
47
103
  effectId: item.effectId,
48
104
  runId: input.runId,
49
105
  threadId: item.threadId,
50
106
  causingEventId: item.causingEventId,
107
+ attributes: input.context.attributes,
108
+ secrets: input.context.secrets,
109
+ trace: input.context.trace,
51
110
  signal: controller.signal,
52
111
  emit: appendLive,
53
112
  }).pipe(Effect.map((outcomes) => ({ _tag: 'completed', outcomes })), Effect.raceFirst(abortion(controller.signal)));
@@ -56,6 +115,8 @@ export function executeLocalEffects(options, input) {
56
115
  : dispatched).pipe(Effect.ensuring(Effect.sync(() => {
57
116
  executionContext.activeEffects.delete(item.effectId);
58
117
  })));
118
+ // A launch the target accepted happened even if the attempt was later aborted or failed.
119
+ produced.push(...launched);
59
120
  if (settled._tag === 'aborted') {
60
121
  continue;
61
122
  }
@@ -94,7 +155,19 @@ export function executeLocalEffects(options, input) {
94
155
  continue;
95
156
  }
96
157
  const { outcomes } = settled;
97
- if (outcomes.length === 0) {
158
+ const sideEffect = item.effect.type === 'runtime.launch' || item.effect.type === 'runtime.cancelRun';
159
+ if (outcomes.length === 0 && sideEffect) {
160
+ const attempt = input.attempts.get(item.effectId) ?? 1;
161
+ produced.push(createEvent(input.runId, {
162
+ type: 'runtime.effect.completed',
163
+ payload: { effectId: item.effectId, attempt },
164
+ threadId: item.threadId,
165
+ effectId: item.effectId,
166
+ causationId: item.causingEventId,
167
+ origin: { type: 'system' },
168
+ }));
169
+ }
170
+ else if (outcomes.length === 0) {
98
171
  produced.push(createEffectFailedEvent(input.runId, item, 'empty-outcome'));
99
172
  }
100
173
  for (const outcome of outcomes) {
@@ -102,6 +175,7 @@ export function executeLocalEffects(options, input) {
102
175
  const attempt = input.attempts.get(item.effectId) ?? 1;
103
176
  const payload = Predicate.isObject(outcome.payload) ? outcome.payload : {};
104
177
  const error = Predicate.isString(payload.error) ? payload.error : 'effect failed';
178
+ const retryAfterMs = payload.retryAfterMs;
105
179
  const failure = planAttemptFailure({
106
180
  runId: input.runId,
107
181
  item,
@@ -110,6 +184,8 @@ export function executeLocalEffects(options, input) {
110
184
  now: input.now,
111
185
  retry,
112
186
  origin: { type: 'system' },
187
+ nonRetryable: payload.nonRetryable === true,
188
+ retryAfterMs: Predicate.isNumber(retryAfterMs) ? retryAfterMs : undefined,
113
189
  });
114
190
  if (failure.retrying) {
115
191
  observe({
@@ -1,10 +1,15 @@
1
- import { type AnyRuntimeModule, type DebuggerHost, type DefinitionInput, type DefinitionRef, type EventEnvelope, type EventInput, type EventStore, type EventStoreTrimCoverage, type JsonValue, type SnapshotStore, type ProjectionDefinition, type ReplayStep, type RunState } from '@swirls/looms/core';
1
+ import { type AnyRuntimeModule, type DebuggerHost, type DefinitionInput, type DefinitionRef, type DefinitionResolver, type EventEnvelope, type EventInput, type BlobStore, type EventStore, type EventStoreTrimCoverage, type JsonValue, type SnapshotStore, type ProjectionDefinition, type ReplayStep, type RunLauncher, type RunState } from '@swirls/looms/core';
2
2
  import type { RuntimeObserver } from './observer';
3
- import { type EffectWorker, type LoomsRuntime, type RunOperationalStatus, type WakeScheduler, type WorkerCallbackInput } from './runtime';
3
+ import { type EffectWorker, type LoomsRuntime, type RunOperationalStatus, type StartRunArgs, type WakeScheduler, type WorkerCallbackInput, type WorkerCallbackResult } from './runtime';
4
4
  import { type Authorize, type RunningServer } from './server';
5
5
  export interface CreateLoomsOptions {
6
6
  /** Runtime modules to enable. Pass `agent()`, `workflow()`, `approval()`, and/or your own. */
7
7
  readonly modules?: readonly AnyRuntimeModule[];
8
+ /**
9
+ * Loads a definition that was not registered on a module. Registered definitions are checked
10
+ * first. A run pins the resolved version on its start event.
11
+ */
12
+ readonly definitionResolver?: DefinitionResolver;
8
13
  readonly store?: EventStore | Promise<EventStore> | (() => Promise<EventStore>);
9
14
  readonly serve?: boolean | {
10
15
  port?: number;
@@ -17,6 +22,11 @@ export interface CreateLoomsOptions {
17
22
  readonly snapshotEvery?: number;
18
23
  /** Defaults to the EventStore's attached store (S2) or an in-memory SnapshotStore. */
19
24
  readonly snapshotStore?: SnapshotStore;
25
+ /**
26
+ * Content-addressed store for JSON values that would exceed the 1 MiB event limit or the
27
+ * 8 MiB snapshot limit. Reads resolve references before folds, projections, and callers see them.
28
+ */
29
+ readonly blobStore?: BlobStore;
20
30
  readonly trimAfterSnapshot?: {
21
31
  keepSnapshots: number;
22
32
  coverage?: (runId: string) => EventStoreTrimCoverage | Promise<EventStoreTrimCoverage>;
@@ -27,7 +37,11 @@ export interface CreateLoomsOptions {
27
37
  readonly rescanTimers?: boolean;
28
38
  /** Pluggable wake scheduler for timer waits. Defaults to an in-process Effect fiber scheduler. */
29
39
  readonly scheduler?: WakeScheduler;
40
+ /** Starts runs from effect handlers. Defaults to launching into this runtime's own store. */
41
+ readonly launcher?: RunLauncher;
30
42
  readonly worker?: EffectWorker;
43
+ /** Maximum concurrent in-flight worker effects dispatched per run (fairness guard). */
44
+ readonly maxInFlightWorkerEffects?: number;
31
45
  /** Failure-isolated runtime observation sink. */
32
46
  readonly observer?: RuntimeObserver;
33
47
  /**
@@ -35,7 +49,8 @@ export interface CreateLoomsOptions {
35
49
  * and operations routes return 503; `bearerAuth({ worker, operations })` enables them.
36
50
  *
37
51
  * A mounted debugger is authorized as `{ access: 'read', name: 'debugger' }`. Bearer tokens on
38
- * reads block browser navigation, so hosts that need auth should use a cookie or session policy.
52
+ * reads block browser navigation. `grantAuth` covers a browser EventSource or WebSocket that can
53
+ * only send a query parameter or a subprotocol.
39
54
  */
40
55
  readonly authorize?: Authorize;
41
56
  /**
@@ -61,6 +76,9 @@ export interface Looms {
61
76
  runId?: string;
62
77
  threadId?: string;
63
78
  idempotencyKey?: string;
79
+ attributes?: StartRunArgs['attributes'];
80
+ secrets?: StartRunArgs['secrets'];
81
+ trace?: StartRunArgs['trace'];
64
82
  }): Promise<StartResult>;
65
83
  /** Start a run by `{ kind, definitionName }` when you only have names (HTTP bodies, CLIs). */
66
84
  startRun(args: {
@@ -71,6 +89,22 @@ export interface Looms {
71
89
  runId?: string;
72
90
  threadId?: string;
73
91
  idempotencyKey?: string;
92
+ attributes?: StartRunArgs['attributes'];
93
+ secrets?: StartRunArgs['secrets'];
94
+ trace?: StartRunArgs['trace'];
95
+ }): Promise<StartResult>;
96
+ /** Commit a run's start and schedule a wake without waiting for it. */
97
+ launchRun(args: {
98
+ kind: string;
99
+ definitionName: string;
100
+ definitionVersion?: string;
101
+ input?: JsonValue;
102
+ runId?: string;
103
+ threadId?: string;
104
+ idempotencyKey?: string;
105
+ attributes?: StartRunArgs['attributes'];
106
+ secrets?: StartRunArgs['secrets'];
107
+ trace?: StartRunArgs['trace'];
74
108
  }): Promise<StartResult>;
75
109
  getRun(runId: string): Promise<RunState>;
76
110
  getEvents(runId: string, options?: {
@@ -93,7 +127,7 @@ export interface Looms {
93
127
  fork(sourceRunId: string, options?: {
94
128
  runId?: string;
95
129
  }): Promise<StartResult>;
96
- workerCallback(runId: string, input: WorkerCallbackInput): Promise<RunState>;
130
+ workerCallback(runId: string, input: WorkerCallbackInput): Promise<WorkerCallbackResult>;
97
131
  fetch(req: Request): Promise<Response | null>;
98
132
  serve(options?: {
99
133
  port?: number;
@@ -1,5 +1,5 @@
1
1
  import { Data, Effect, Predicate } from 'effect';
2
- import { EventStoreTag, makeMemoryEventStore, snapshotStoreOf, SnapshotStoreTag, } from '@swirls/looms/core';
2
+ import { EventStoreTag, makeMemoryEventStore, makeMemorySnapshotStore, snapshotStoreOf, withBlobOffload, SnapshotStoreTag, } from '@swirls/looms/core';
3
3
  import { createRuntime, } from './runtime.js';
4
4
  import { createFetchHandler, handlesPath, mountDebugger, serveHttp, } from './server.js';
5
5
  class LoomsInitializationError extends Data.TaggedError('LoomsInitializationError') {
@@ -20,7 +20,7 @@ export function createLooms(options = {}) {
20
20
  if (!initPromise) {
21
21
  initPromise = Effect.runPromise(Effect.gen(function* () {
22
22
  const configuredStore = options.store;
23
- const store = !configuredStore
23
+ let store = !configuredStore
24
24
  ? yield* makeMemoryEventStore
25
25
  : Predicate.isFunction(configuredStore)
26
26
  ? yield* Effect.tryPromise({
@@ -33,18 +33,28 @@ export function createLooms(options = {}) {
33
33
  catch: (cause) => new LoomsInitializationError(cause),
34
34
  })
35
35
  : configuredStore;
36
+ if (options.blobStore) {
37
+ const snapshots = options.snapshotStore ?? snapshotStoreOf(store) ?? (yield* makeMemorySnapshotStore);
38
+ store = withBlobOffload(store, options.blobStore, { snapshots });
39
+ }
36
40
  const runtime = createRuntime({
37
41
  modules,
38
42
  store,
39
43
  maxWakeIterations: options.maxWakeIterations,
40
44
  maxPendingRunOperations: options.maxPendingRunOperations,
41
45
  snapshotEvery: options.snapshotEvery,
42
- snapshotStore: options.snapshotStore ?? snapshotStoreOf(store),
46
+ snapshotStore: options.blobStore
47
+ ? snapshotStoreOf(store)
48
+ : (options.snapshotStore ?? snapshotStoreOf(store)),
43
49
  trimAfterSnapshot: options.trimAfterSnapshot,
44
50
  runCacheSize: options.runCacheSize,
45
51
  scheduler: options.scheduler,
52
+ launcher: options.launcher,
53
+ localLauncher: options.launcher === undefined,
46
54
  worker: options.worker,
55
+ maxInFlightWorkerEffects: options.maxInFlightWorkerEffects,
47
56
  observer: options.observer,
57
+ definitionResolver: options.definitionResolver,
48
58
  });
49
59
  if (options.rescanTimers !== false) {
50
60
  yield* runtime.rescanTimers.pipe(Effect.provideService(EventStoreTag, store), Effect.tapError(Effect.logError), Effect.ignore);
@@ -76,6 +86,7 @@ export function createLooms(options = {}) {
76
86
  },
77
87
  ready: () => getInit().then(({ store, runtime }) => ({ store, runtime })),
78
88
  startRun: (args) => runEffect((i) => i.runtime.startRun(args)),
89
+ launchRun: (args) => runEffect((i) => i.runtime.launchRun(args)),
79
90
  start: (definition, input, startOpts) => runEffect((i) => i.runtime.startRun({
80
91
  kind: definition.kind,
81
92
  definitionName: definition.name,
@@ -85,6 +96,9 @@ export function createLooms(options = {}) {
85
96
  runId: startOpts?.runId,
86
97
  threadId: startOpts?.threadId,
87
98
  idempotencyKey: startOpts?.idempotencyKey,
99
+ attributes: startOpts?.attributes,
100
+ secrets: startOpts?.secrets,
101
+ trace: startOpts?.trace,
88
102
  })),
89
103
  getRun: (runId) => runEffect((i) => i.runtime.getRun(runId)),
90
104
  getEvents: (runId, opts) => runEffect((i) => i.runtime.getEvents(runId, opts)),
@@ -1,5 +1,5 @@
1
1
  import { Effect } from 'effect';
2
- import { EventStoreTrimmedError, type AppendableEvent, type AppendResult, type ComposedRegistry, type EventEnvelope, type EventStore, type EventStoreAppendError, type EventStoreError, type EventStoreTrimCoverage, type EventStoreTruncationError, type RunCursor, type SnapshotStore } from '@swirls/looms/core';
2
+ import { EventStoreTrimmedError, type AppendableEvent, type AppendResult, type ComposedRegistry, type DefinitionResolveError, type DefinitionResolver, type EventEnvelope, type EventStore, type EventStoreAppendError, type EventStoreError, type EventStoreTrimCoverage, type EventStoreTruncationError, type RunCursor, type SnapshotStore } from '@swirls/looms/core';
3
3
  import { RuntimeExecutionError } from './errors';
4
4
  import { type RunCursorCache } from './helpers';
5
5
  import { type RuntimeObserver } from './observer';
@@ -35,19 +35,20 @@ export interface RuntimeRepositoryOptions {
35
35
  readonly runCache: RunCursorCache;
36
36
  readonly executionContexts: RunExecutionContexts;
37
37
  readonly observer?: RuntimeObserver;
38
+ readonly definitionResolver: DefinitionResolver;
38
39
  }
39
40
  export declare function createRuntimeRepository(options: RuntimeRepositoryOptions): {
40
41
  appendObserved: (store: EventStore, runId: string, events: ReadonlyArray<AppendableEvent>, appendOptions?: Parameters<EventStore['append']>[2]) => Effect.Effect<AppendResult, EventStoreAppendError>;
41
- commit: <A, E, R>(store: EventStore, runId: string, plan: (cursor: RunCursor) => Effect.Effect<CommitPlan<A>, E, R>) => Effect.Effect<CommitResult<A>, E | RuntimeExecutionError | EventStoreAppendError | EventStoreTruncationError | EventStoreTrimmedError | EventStoreError, R>;
42
+ commit: <A, E, R>(store: EventStore, runId: string, plan: (cursor: RunCursor) => Effect.Effect<CommitPlan<A>, E, R>) => Effect.Effect<CommitResult<A>, E | RuntimeExecutionError | EventStoreAppendError | EventStoreTruncationError | EventStoreTrimmedError | EventStoreError | DefinitionResolveError, R>;
42
43
  liveAppends: {
43
44
  push: (key: string, event: AppendableEvent) => Effect.Effect<void, LiveAppendError | EventStoreAppendError, never>;
44
45
  drain: (key: string) => Effect.Effect<void>;
45
46
  clear: (key?: string) => void;
46
47
  size: () => number;
47
48
  };
48
- loadCursor: (store: EventStore, runId: string) => Effect.Effect<RunCursor, EventStoreTruncationError | EventStoreTrimmedError | EventStoreError>;
49
+ loadCursor: (store: EventStore, runId: string) => Effect.Effect<RunCursor, EventStoreTruncationError | EventStoreTrimmedError | EventStoreError | DefinitionResolveError>;
49
50
  persistSnapshot: (store: EventStore, runId: string, cursor: RunCursor) => Effect.Effect<RunCursor>;
50
- applyAppend: (store: EventStore, runId: string, cursor: RunCursor, batch: ReadonlyArray<AppendableEvent>, appendResult: AppendResult, commandRecords?: number) => Effect.Effect<RunCursor, EventStoreTruncationError | EventStoreTrimmedError | EventStoreError>;
51
+ applyAppend: (store: EventStore, runId: string, cursor: RunCursor, batch: ReadonlyArray<AppendableEvent>, appendResult: AppendResult, commandRecords?: number) => Effect.Effect<RunCursor, EventStoreTruncationError | EventStoreTrimmedError | EventStoreError | DefinitionResolveError>;
51
52
  deleteCachedCursor: (runId: string) => void;
52
53
  setCachedCursor: (runId: string, cursor: RunCursor) => void;
53
54
  };
@@ -1,5 +1,5 @@
1
1
  import { Clock, Data, Effect, Option } from 'effect';
2
- import { advanceCursor, assignSequences, createCoalescingAppender, emptyCursor, EventStoreTrimmedError, hashRunState, summarizeRun, trimEventStoreSafely, } from '@swirls/looms/core';
2
+ import { advanceCursor, assignSequences, createCoalescingAppender, emptyCursor, EventStoreTrimmedError, hashRunState, primePinnedDefinitions, summarizeRun, trimEventStoreSafely, } from '@swirls/looms/core';
3
3
  import { RuntimeExecutionError } from './errors.js';
4
4
  import { stripSeq } from './helpers.js';
5
5
  import { notifyObserver } from './observer.js';
@@ -35,7 +35,7 @@ function readRemaining(store, runId, events, expectedTail) {
35
35
  });
36
36
  }
37
37
  export function createRuntimeRepository(options) {
38
- const { registry, snapshotStore, trimAfterSnapshot, keepSnapshots, runCache, executionContexts } = options;
38
+ const { registry, snapshotStore, trimAfterSnapshot, keepSnapshots, runCache, executionContexts, definitionResolver, } = options;
39
39
  const headerFingerprints = new Map();
40
40
  const observe = (event) => notifyObserver(options.observer, event);
41
41
  const rememberHeader = (state) => {
@@ -99,6 +99,7 @@ export function createRuntimeRepository(options) {
99
99
  }
100
100
  }
101
101
  delta = yield* readRemaining(store, runId, delta, expectedTail);
102
+ yield* primePinnedDefinitions(definitionResolver, delta);
102
103
  return advanceCursor(cursor, delta, registry);
103
104
  });
104
105
  const loadCursorCold = (store, runId) => Effect.gen(function* () {
@@ -155,6 +156,7 @@ export function createRuntimeRepository(options) {
155
156
  if (delta.length > 0) {
156
157
  delta = yield* readRemaining(store, runId, delta);
157
158
  }
159
+ yield* primePinnedDefinitions(definitionResolver, delta);
158
160
  return advanceCursor(cursor, delta, registry);
159
161
  });
160
162
  const loadCursor = (store, runId) => Effect.gen(function* () {
@@ -249,6 +251,7 @@ export function createRuntimeRepository(options) {
249
251
  next = yield* catchUp(store, runId, cursor, first.seq - 1);
250
252
  }
251
253
  }
254
+ yield* primePinnedDefinitions(definitionResolver, echo);
252
255
  next = advanceCursor(next, echo, registry);
253
256
  executionContexts.get(runId).liveCount = 0;
254
257
  if (next.seq !== appendResult.tail) {
@@ -1,5 +1,7 @@
1
1
  import { type EventEnvelope, type EventOrigin, type OutstandingEffect, type RetryPolicy } from '@swirls/looms/core';
2
2
  export declare function retryBackoff(retry: RetryPolicy, attempt: number): number;
3
+ /** Policy backoff, extended when the failed attempt asked to wait longer. */
4
+ export declare function retryDelay(retry: RetryPolicy, attempt: number, retryAfterMs?: number): number;
3
5
  export interface AttemptFailurePlan {
4
6
  readonly event: EventEnvelope;
5
7
  readonly retrying: boolean;
@@ -13,4 +15,7 @@ export declare function planAttemptFailure(input: {
13
15
  readonly now: number;
14
16
  readonly retry: RetryPolicy | undefined;
15
17
  readonly origin: EventOrigin;
18
+ /** Customer/domain failures must not be retried even when attempts remain. */
19
+ readonly nonRetryable?: boolean;
20
+ readonly retryAfterMs?: number;
16
21
  }): AttemptFailurePlan;
@@ -5,10 +5,18 @@ export function retryBackoff(retry, attempt) {
5
5
  const multiplier = Math.max(1, retry.backoffMultiplier ?? 2);
6
6
  return Math.min(base * multiplier ** Math.max(0, attempt - 1), retry.maxBackoffMs ?? Number.MAX_SAFE_INTEGER);
7
7
  }
8
+ /** Policy backoff, extended when the failed attempt asked to wait longer. */
9
+ export function retryDelay(retry, attempt, retryAfterMs) {
10
+ const backoff = retryBackoff(retry, attempt);
11
+ if (retryAfterMs === undefined || !Number.isFinite(retryAfterMs) || retryAfterMs < 0) {
12
+ return backoff;
13
+ }
14
+ return Math.max(backoff, retryAfterMs);
15
+ }
8
16
  /** Schedule the next attempt when the retry policy allows one, otherwise fail the effect. */
9
17
  export function planAttemptFailure(input) {
10
18
  const { runId, item, attempt, error, retry } = input;
11
- if (!retry || attempt >= retry.maxAttempts) {
19
+ if (input.nonRetryable || !retry || attempt >= retry.maxAttempts) {
12
20
  return { event: createEffectFailedEvent(runId, item, error), retrying: false };
13
21
  }
14
22
  return {
@@ -18,7 +26,7 @@ export function planAttemptFailure(input) {
18
26
  payload: {
19
27
  effectId: item.effectId,
20
28
  attempt,
21
- nextAttemptAt: input.now + retryBackoff(retry, attempt),
29
+ nextAttemptAt: input.now + retryDelay(retry, attempt, input.retryAfterMs),
22
30
  error,
23
31
  },
24
32
  threadId: item.threadId,
@@ -1,4 +1,4 @@
1
1
  import { Effect } from 'effect';
2
- import type { RunState } from '@swirls/looms/core';
2
+ import { type RunState } from '@swirls/looms/core';
3
3
  import { RuntimeExecutionError, StartRunConflictError, type RunStartIdentity } from './errors';
4
4
  export declare function assertSameDurableStart(runId: string, state: RunState, requested: RunStartIdentity): Effect.Effect<string, RuntimeExecutionError | StartRunConflictError>;
@@ -1,4 +1,5 @@
1
1
  import { Effect, Predicate } from 'effect';
2
+ import { sameStringRecord } from '@swirls/looms/core';
2
3
  import { RuntimeExecutionError, StartRunConflictError } from './errors.js';
3
4
  function canonicalJson(value) {
4
5
  if (Array.isArray(value)) {
@@ -36,6 +37,15 @@ export function assertSameDurableStart(runId, state, requested) {
36
37
  if (existing.idempotencyKey !== requested.idempotencyKey) {
37
38
  mismatches.push('idempotencyKey');
38
39
  }
40
+ if (canonicalJson(existing.launchedBy ?? null) !== canonicalJson(requested.launchedBy ?? null)) {
41
+ mismatches.push('launchedBy');
42
+ }
43
+ if (existing.correlationId !== requested.correlationId) {
44
+ mismatches.push('correlationId');
45
+ }
46
+ if (!sameStringRecord(existing.attributes ?? {}, requested.attributes)) {
47
+ mismatches.push('attributes');
48
+ }
39
49
  return mismatches.length > 0
40
50
  ? Effect.fail(new StartRunConflictError(runId, requested, existing, mismatches))
41
51
  : Effect.succeed(existing.rootThreadId);
@@ -4,7 +4,7 @@ export type { WakeScheduler } from './wake-scheduler';
4
4
  export { createTimeoutScheduler } from './wake-scheduler';
5
5
  export { DuplicateEffectDispatchError, ForkConflictError, MaxWakeIterationsError, RunNotFinishedError, RunNotFoundError, RunTerminalError, StartRunConflictError, } from './errors';
6
6
  export { RunOverloadedError } from './ingress';
7
- export type { CreateRuntimeOptions, EffectWorker, EffectWorkerTask, LoomsRuntime, RunOperationalStatus, StartResult, StartRunArgs, WakeError, } from './types';
8
- export type { WorkerCallback, WorkerCallbackInput } from '@swirls/looms/core';
7
+ export type { CreateRuntimeOptions, EffectWorker, EffectWorkerDeadlines, EffectWorkerTask, LoomsRuntime, RunOperationalStatus, StartResult, StartRunArgs, WakeError, } from './types';
8
+ export type { WorkerCallback, WorkerCallbackInput, WorkerCallbackResult } from '@swirls/looms/core';
9
9
  import type { CreateRuntimeOptions, LoomsRuntime } from './types';
10
10
  export declare function createRuntime<const TModules extends readonly AnyRuntimeModule[]>(options: CreateRuntimeOptions<TModules>): LoomsRuntime<TModules>;