@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,6 +1,8 @@
1
1
  import { Effect, Schema, type Context } from 'effect';
2
2
  import { type EventInput } from './envelope';
3
+ import type { RunAttributes, SecretContext } from './run-context';
3
4
  import { type InferDefinedSchema, type InvalidInputError, type SchemaInput } from './schema';
5
+ import type { TraceContext } from './trace';
4
6
  import { type JsonValue } from './types';
5
7
  export type WaitOnEvent = {
6
8
  type: string | readonly string[];
@@ -43,6 +45,21 @@ export type CancelEffect = {
43
45
  threadId: string;
44
46
  tag?: string;
45
47
  };
48
+ export type LaunchEffect = {
49
+ type: 'runtime.launch';
50
+ runId: string;
51
+ kind: string;
52
+ definitionName: string;
53
+ definitionVersion: string;
54
+ input: JsonValue;
55
+ close: 'terminate' | 'wait';
56
+ tag?: string;
57
+ };
58
+ export type CancelRunEffect = {
59
+ type: 'runtime.cancelRun';
60
+ runId: string;
61
+ tag?: string;
62
+ };
46
63
  export interface RetryPolicy {
47
64
  readonly maxAttempts: number;
48
65
  readonly backoffMs?: number;
@@ -57,8 +74,10 @@ export type InvokeEffect = {
57
74
  type: string;
58
75
  input: JsonValue;
59
76
  tag?: string;
77
+ /** Epoch milliseconds. The host waits until this instant before running the effect. */
78
+ at?: number;
60
79
  };
61
- export type PrimitiveEffect = SpawnEffect | WaitEffect | EmitEffect | CompleteEffect | FailEffect | CancelEffect;
80
+ export type PrimitiveEffect = SpawnEffect | WaitEffect | EmitEffect | CompleteEffect | FailEffect | CancelEffect | LaunchEffect | CancelRunEffect;
62
81
  export type RuntimeEffect = PrimitiveEffect | InvokeEffect;
63
82
  export declare const WaitConditionSchema: Schema.Union<readonly [Schema.Struct<{
64
83
  readonly type: Schema.Union<readonly [Schema.String, Schema.$Array<Schema.String>]>;
@@ -101,6 +120,9 @@ export declare const RuntimeEffectSchema: Schema.Union<readonly [Schema.Struct<{
101
120
  readonly actorId: Schema.optional<Schema.String>;
102
121
  }>, Schema.Struct<{
103
122
  readonly type: Schema.Literal<"system">;
123
+ }>, Schema.Struct<{
124
+ readonly type: Schema.Literal<"run">;
125
+ readonly runId: Schema.String;
104
126
  }>]>>;
105
127
  readonly ephemeral: Schema.optional<Schema.Boolean>;
106
128
  readonly id: Schema.optional<Schema.String>;
@@ -118,14 +140,30 @@ export declare const RuntimeEffectSchema: Schema.Union<readonly [Schema.Struct<{
118
140
  readonly type: Schema.Literal<"runtime.cancel">;
119
141
  readonly threadId: Schema.String;
120
142
  readonly tag: Schema.optional<Schema.String>;
143
+ }>, Schema.Struct<{
144
+ readonly type: Schema.Literal<"runtime.launch">;
145
+ readonly runId: Schema.String;
146
+ readonly kind: Schema.String;
147
+ readonly definitionName: Schema.String;
148
+ readonly definitionVersion: Schema.withDecodingDefaultKey<Schema.String, never>;
149
+ readonly input: Schema.Codec<Schema.Json, Schema.Json, never, never>;
150
+ readonly close: Schema.withDecodingDefaultKey<Schema.Union<readonly [Schema.Literal<"terminate">, Schema.Literal<"wait">]>, never>;
151
+ readonly tag: Schema.optional<Schema.String>;
152
+ }>, Schema.Struct<{
153
+ readonly type: Schema.Literal<"runtime.cancelRun">;
154
+ readonly runId: Schema.String;
155
+ readonly tag: Schema.optional<Schema.String>;
121
156
  }>, Schema.Struct<{
122
157
  readonly type: Schema.String;
123
158
  readonly input: Schema.Codec<Schema.Json, Schema.Json, never, never>;
124
159
  readonly tag: Schema.optional<Schema.String>;
160
+ readonly at: Schema.optional<Schema.Finite>;
125
161
  }>]>;
126
162
  export declare function isPrimitiveEffect(effect: RuntimeEffect): effect is PrimitiveEffect;
127
163
  export declare function isWaitOnTimer(on: WaitCondition): on is WaitOnTimer;
128
164
  export declare function isWaitOnEvent(on: WaitCondition): on is WaitOnEvent;
165
+ /** When a deferred invoke becomes runnable, or undefined for effects that run immediately. */
166
+ export declare function deferredUntil(effect: RuntimeEffect): number | undefined;
129
167
  export declare const WITHDRAWN_ERROR_PREFIX = "withdrawn:";
130
168
  export declare function withdrawnError(siblingEffectId: string): string;
131
169
  export declare function isWithdrawnError(error: string): boolean;
@@ -135,6 +173,12 @@ export interface EffectContext {
135
173
  readonly threadId: string;
136
174
  readonly causingEventId: string;
137
175
  readonly signal: AbortSignal;
176
+ /** Labels safe for every reader of the run. */
177
+ readonly attributes: RunAttributes;
178
+ /** Bearer material for this run. Never copy it into an emitted event. */
179
+ readonly secrets: SecretContext;
180
+ /** The W3C trace context of the caller that started the run, to parent spans the handler starts. */
181
+ readonly trace: TraceContext | null;
138
182
  emit(event: EventInput): Promise<void>;
139
183
  }
140
184
  export interface ScopedEffectContext<E extends EventInput = EventInput> {
@@ -143,6 +187,9 @@ export interface ScopedEffectContext<E extends EventInput = EventInput> {
143
187
  readonly threadId: string;
144
188
  readonly causingEventId: string;
145
189
  readonly signal: AbortSignal;
190
+ readonly attributes: RunAttributes;
191
+ readonly secrets: SecretContext;
192
+ readonly trace: TraceContext | null;
146
193
  emit(event: E): Promise<void>;
147
194
  }
148
195
  export type EffectInputSchema = SchemaInput;
@@ -155,6 +202,28 @@ export declare class EffectHandlerError extends EffectHandlerError_base<{
155
202
  }> {
156
203
  constructor(cause: unknown);
157
204
  }
205
+ export interface EffectFailureOptions {
206
+ readonly error: string;
207
+ /** Fail the attempt even when the retry policy has attempts remaining. */
208
+ readonly nonRetryable?: boolean;
209
+ /** Wait at least this long before the next attempt. Does not shorten the policy backoff. */
210
+ readonly retryAfterMs?: number;
211
+ }
212
+ declare const EffectFailure_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
213
+ readonly _tag: "EffectFailure";
214
+ } & Readonly<A>;
215
+ /** A handler's deliberate attempt failure, distinct from an unexpected thrown error. */
216
+ export declare class EffectFailure extends EffectFailure_base<{
217
+ readonly error: string;
218
+ readonly nonRetryable: boolean;
219
+ readonly retryAfterMs?: number;
220
+ }> {
221
+ constructor(options: EffectFailureOptions);
222
+ }
223
+ /** Fail the current attempt. The retry policy decides whether another attempt is scheduled. */
224
+ export declare function failEffect(options: EffectFailureOptions): Effect.Effect<never, EffectFailure>;
225
+ /** Unwrap a handler failure, including one wrapped by `EffectHandlerError`. */
226
+ export declare function readEffectFailure(cause: unknown): EffectFailure | null;
158
227
  export type EffectHandlerResult<R = never, E extends EventInput = EventInput, HandlerError = never> = ReadonlyArray<E> | Promise<ReadonlyArray<E>> | Effect.Effect<ReadonlyArray<E>, HandlerError, R>;
159
228
  declare const effectInputType: unique symbol;
160
229
  export type EffectExecution = 'local' | 'worker';
@@ -224,6 +293,20 @@ export declare function emit<E extends EventInput = EventInput>(event: E, tag?:
224
293
  export declare function complete(output: JsonValue): CompleteEffect;
225
294
  export declare function fail(error: string): FailEffect;
226
295
  export declare function cancel(threadId: string, tag?: string): CancelEffect;
227
- export declare function invoke<TInput>(definition: EffectDefinition<TInput, any, any>, input: TInput, tag?: string): InvokeEffect;
228
- export declare function invoke(type: string, input: JsonValue, tag?: string): InvokeEffect;
296
+ export declare function launch(args: {
297
+ runId: string;
298
+ kind: string;
299
+ definitionName: string;
300
+ definitionVersion?: string;
301
+ input: JsonValue;
302
+ close?: 'terminate' | 'wait';
303
+ tag?: string;
304
+ }): LaunchEffect;
305
+ export declare function cancelRun(runId: string, tag?: string): CancelRunEffect;
306
+ export interface InvokeOptions {
307
+ /** Wait until this epoch millisecond before the host runs the effect. */
308
+ readonly at?: number;
309
+ }
310
+ export declare function invoke<TInput>(definition: EffectDefinition<TInput, any, any>, input: TInput, tag?: string, options?: InvokeOptions): InvokeEffect;
311
+ export declare function invoke(type: string, input: JsonValue, tag?: string, options?: InvokeOptions): InvokeEffect;
229
312
  export {};
@@ -43,10 +43,26 @@ export const RuntimeEffectSchema = Schema.Union([
43
43
  threadId: Schema.String,
44
44
  tag: Schema.optional(Schema.String),
45
45
  }),
46
+ Schema.Struct({
47
+ type: Schema.Literal('runtime.launch'),
48
+ runId: Schema.String,
49
+ kind: Schema.String,
50
+ definitionName: Schema.String,
51
+ definitionVersion: Schema.String.pipe(Schema.withDecodingDefaultKey(Effect.succeed(DEFAULT_DEFINITION_VERSION))),
52
+ input: Schema.Json,
53
+ close: Schema.Union([Schema.Literal('terminate'), Schema.Literal('wait')]).pipe(Schema.withDecodingDefaultKey(Effect.succeed('terminate'))),
54
+ tag: Schema.optional(Schema.String),
55
+ }),
56
+ Schema.Struct({
57
+ type: Schema.Literal('runtime.cancelRun'),
58
+ runId: Schema.String,
59
+ tag: Schema.optional(Schema.String),
60
+ }),
46
61
  Schema.Struct({
47
62
  type: Schema.String,
48
63
  input: Schema.Json,
49
64
  tag: Schema.optional(Schema.String),
65
+ at: Schema.optional(Schema.Finite),
50
66
  }),
51
67
  ]);
52
68
  export function isPrimitiveEffect(effect) {
@@ -57,6 +73,8 @@ export function isPrimitiveEffect(effect) {
57
73
  case 'runtime.complete':
58
74
  case 'runtime.fail':
59
75
  case 'runtime.cancel':
76
+ case 'runtime.launch':
77
+ case 'runtime.cancelRun':
60
78
  return true;
61
79
  default:
62
80
  return false;
@@ -68,6 +86,13 @@ export function isWaitOnTimer(on) {
68
86
  export function isWaitOnEvent(on) {
69
87
  return 'type' in on;
70
88
  }
89
+ /** When a deferred invoke becomes runnable, or undefined for effects that run immediately. */
90
+ export function deferredUntil(effect) {
91
+ if (isPrimitiveEffect(effect) || effect.at === undefined) {
92
+ return undefined;
93
+ }
94
+ return effect.at;
95
+ }
71
96
  export const WITHDRAWN_ERROR_PREFIX = 'withdrawn:';
72
97
  export function withdrawnError(siblingEffectId) {
73
98
  return `${WITHDRAWN_ERROR_PREFIX} sibling effect ${siblingEffectId} failed`;
@@ -84,6 +109,34 @@ export class EffectHandlerError extends Data.TaggedError('EffectHandlerError') {
84
109
  this.name = 'EffectHandlerError';
85
110
  }
86
111
  }
112
+ /** A handler's deliberate attempt failure, distinct from an unexpected thrown error. */
113
+ export class EffectFailure extends Data.TaggedError('EffectFailure') {
114
+ constructor(options) {
115
+ const error = options.error;
116
+ const nonRetryable = options.nonRetryable === true;
117
+ if (options.retryAfterMs === undefined) {
118
+ super({ error, nonRetryable });
119
+ }
120
+ else {
121
+ super({ error, nonRetryable, retryAfterMs: options.retryAfterMs });
122
+ }
123
+ this.name = 'EffectFailure';
124
+ }
125
+ }
126
+ /** Fail the current attempt. The retry policy decides whether another attempt is scheduled. */
127
+ export function failEffect(options) {
128
+ return Effect.fail(new EffectFailure(options));
129
+ }
130
+ /** Unwrap a handler failure, including one wrapped by `EffectHandlerError`. */
131
+ export function readEffectFailure(cause) {
132
+ if (cause instanceof EffectFailure) {
133
+ return cause;
134
+ }
135
+ if (cause instanceof EffectHandlerError) {
136
+ return readEffectFailure(cause.cause);
137
+ }
138
+ return null;
139
+ }
87
140
  function liftHandlerResult(result) {
88
141
  if (Effect.isEffect(result)) {
89
142
  return result.pipe(Effect.mapError((cause) => new EffectHandlerError(cause)));
@@ -96,6 +149,14 @@ function liftHandlerResult(result) {
96
149
  }
97
150
  return Effect.succeed(result);
98
151
  }
152
+ function invokeHandler(handler, input, ctx) {
153
+ try {
154
+ return liftHandlerResult(handler(input, ctx));
155
+ }
156
+ catch (cause) {
157
+ return Effect.fail(new EffectHandlerError(cause));
158
+ }
159
+ }
99
160
  export function defineEffect(def) {
100
161
  const schema = def.input;
101
162
  const handler = def.execute;
@@ -109,7 +170,11 @@ export function defineEffect(def) {
109
170
  deterministic: def.deterministic,
110
171
  execute: (raw, ctx) => Effect.gen(function* () {
111
172
  const input = schema ? yield* validateInputEffect(schema, raw) : raw;
112
- return handler ? yield* liftHandlerResult(handler(input, ctx)) : noOutput;
173
+ if (!handler) {
174
+ return noOutput;
175
+ }
176
+ // invokeHandler turns a synchronous throw into EffectHandlerError.
177
+ return yield* invokeHandler(handler, input, ctx);
113
178
  }),
114
179
  };
115
180
  }
@@ -165,8 +230,39 @@ export function cancel(threadId, tag) {
165
230
  ? { type: 'runtime.cancel', threadId }
166
231
  : { type: 'runtime.cancel', threadId, tag };
167
232
  }
168
- export function invoke(typeOrDef, input, tag) {
233
+ export function launch(args) {
234
+ const effect = {
235
+ type: 'runtime.launch',
236
+ runId: args.runId,
237
+ kind: args.kind,
238
+ definitionName: args.definitionName,
239
+ definitionVersion: args.definitionVersion ?? DEFAULT_DEFINITION_VERSION,
240
+ input: args.input,
241
+ close: args.close ?? 'terminate',
242
+ };
243
+ if (args.tag !== undefined) {
244
+ effect.tag = args.tag;
245
+ }
246
+ return effect;
247
+ }
248
+ export function cancelRun(runId, tag) {
249
+ return tag === undefined
250
+ ? { type: 'runtime.cancelRun', runId }
251
+ : { type: 'runtime.cancelRun', runId, tag };
252
+ }
253
+ export function invoke(typeOrDef, input, tag, options) {
169
254
  const type = Predicate.isString(typeOrDef) ? typeOrDef : typeOrDef.type;
170
255
  const jsonInput = asJson(input);
171
- return tag !== undefined ? { type, input: jsonInput, tag } : { type, input: jsonInput };
256
+ const at = options?.at;
257
+ if (at !== undefined && !Number.isFinite(at)) {
258
+ throw new Error('invoke at must be a finite epoch timestamp');
259
+ }
260
+ const effect = { type, input: jsonInput };
261
+ if (tag !== undefined) {
262
+ effect.tag = tag;
263
+ }
264
+ if (at !== undefined) {
265
+ effect.at = at;
266
+ }
267
+ return effect;
172
268
  }
@@ -9,6 +9,9 @@ export declare const EventOriginSchema: Schema.Union<readonly [Schema.Struct<{
9
9
  readonly actorId: Schema.optional<Schema.String>;
10
10
  }>, Schema.Struct<{
11
11
  readonly type: Schema.Literal<"system">;
12
+ }>, Schema.Struct<{
13
+ readonly type: Schema.Literal<"run">;
14
+ readonly runId: Schema.String;
12
15
  }>]>;
13
16
  export type EventOrigin = Schema.Schema.Type<typeof EventOriginSchema>;
14
17
  export declare const EventEnvelopeSchema: Schema.Struct<{
@@ -31,6 +34,9 @@ export declare const EventEnvelopeSchema: Schema.Struct<{
31
34
  readonly actorId: Schema.optional<Schema.String>;
32
35
  }>, Schema.Struct<{
33
36
  readonly type: Schema.Literal<"system">;
37
+ }>, Schema.Struct<{
38
+ readonly type: Schema.Literal<"run">;
39
+ readonly runId: Schema.String;
34
40
  }>]>;
35
41
  readonly ephemeral: Schema.optional<Schema.Boolean>;
36
42
  readonly idempotencyKey: Schema.optional<Schema.NullOr<Schema.String>>;
@@ -51,6 +57,9 @@ export declare const EventInputSchema: Schema.Struct<{
51
57
  readonly actorId: Schema.optional<Schema.String>;
52
58
  }>, Schema.Struct<{
53
59
  readonly type: Schema.Literal<"system">;
60
+ }>, Schema.Struct<{
61
+ readonly type: Schema.Literal<"run">;
62
+ readonly runId: Schema.String;
54
63
  }>]>>;
55
64
  readonly ephemeral: Schema.optional<Schema.Boolean>;
56
65
  readonly id: Schema.optional<Schema.String>;
@@ -14,6 +14,10 @@ export const EventOriginSchema = Schema.Union([
14
14
  Schema.Struct({
15
15
  type: Schema.Literal('system'),
16
16
  }),
17
+ Schema.Struct({
18
+ type: Schema.Literal('run'),
19
+ runId: Schema.String,
20
+ }),
17
21
  ]);
18
22
  export const EventEnvelopeSchema = Schema.Struct({
19
23
  id: Schema.String,
@@ -37,6 +37,9 @@ function readOrigin(raw) {
37
37
  const actorId = 'actorId' in raw && Predicate.isString(raw.actorId) ? raw.actorId : undefined;
38
38
  return actorId ? { type: 'external', actorId } : { type: 'external' };
39
39
  }
40
+ if ('type' in raw && raw.type === 'run' && 'runId' in raw && Predicate.isString(raw.runId)) {
41
+ return { type: 'run', runId: raw.runId };
42
+ }
40
43
  return { type: 'system' };
41
44
  }
42
45
  export function decodeLoomsEvent(raw, runId) {
package/dist/core/fold.js CHANGED
@@ -1,9 +1,12 @@
1
1
  import { Option, Predicate, Schema, Stream } from 'effect';
2
2
  import { isWithdrawnError, WaitConditionSchema } from './effects.js';
3
3
  import { createEffectId } from './ids.js';
4
+ import { readRunLaunchSource } from './launcher.js';
4
5
  import { DEFAULT_DEFINITION_VERSION } from './module.js';
6
+ import { readAttributes } from './run-context.js';
5
7
  import { completedRunStatus, emptyRunState } from './state.js';
6
8
  import { isTerminalStatus } from './thread.js';
9
+ import { readStoredTrace } from './trace.js';
7
10
  const decodeWaitCondition = Schema.decodeUnknownOption(WaitConditionSchema);
8
11
  function payloadObject(event) {
9
12
  const payload = event.payload;
@@ -19,6 +22,20 @@ function readString(obj, key) {
19
22
  const value = obj[key];
20
23
  return Predicate.isString(value) ? value : undefined;
21
24
  }
25
+ /** Heartbeat progress already recorded for this effect, if any. */
26
+ function priorHeartbeatDetails(state, effectId) {
27
+ return state.effectExecutions[effectId]?.heartbeatDetails ?? null;
28
+ }
29
+ /**
30
+ * A heartbeat that carries `details` replaces the saved progress. Every other execution event
31
+ * keeps it, including the retry that starts the next attempt.
32
+ */
33
+ function nextHeartbeatDetails(state, effectId, event, payload) {
34
+ if (event.type === 'runtime.effect.heartbeat' && Object.hasOwn(payload, 'details')) {
35
+ return payload.details ?? null;
36
+ }
37
+ return priorHeartbeatDetails(state, effectId);
38
+ }
22
39
  function cloneState(state) {
23
40
  return {
24
41
  runId: state.runId,
@@ -34,6 +51,10 @@ function cloneState(state) {
34
51
  processedIdempotencyKeys: state.processedIdempotencyKeys
35
52
  ? [...state.processedIdempotencyKeys]
36
53
  : [],
54
+ launchedRuns: state.launchedRuns ? [...state.launchedRuns] : [],
55
+ closeWaiting: state.closeWaiting ?? false,
56
+ childCloseSent: state.childCloseSent ?? false,
57
+ attributes: state.attributes ?? {},
37
58
  };
38
59
  }
39
60
  function completeEffect(state, effectId) {
@@ -95,6 +116,10 @@ function applyProtocol(state, event, registry) {
95
116
  requestedThreadId: readString(payload, 'requestedThreadId') ?? null,
96
117
  idempotencyKey: event.idempotencyKey ?? null,
97
118
  rootThreadId,
119
+ launchedBy: readRunLaunchSource(payload.launchedBy),
120
+ correlationId: event.correlationId ?? null,
121
+ attributes: readAttributes(payload.attributes),
122
+ trace: readStoredTrace(payload.trace),
98
123
  }
99
124
  : null;
100
125
  return {
@@ -102,6 +127,7 @@ function applyProtocol(state, event, registry) {
102
127
  runId: event.runId,
103
128
  rootThreadId: rootThreadId ?? null,
104
129
  startIdentity,
130
+ attributes: startIdentity?.attributes ?? {},
105
131
  status: 'running',
106
132
  };
107
133
  }
@@ -220,6 +246,7 @@ function applyProtocol(state, event, registry) {
220
246
  deadlineAt: null,
221
247
  startedAt: event.ts,
222
248
  lastHeartbeatAt: null,
249
+ heartbeatDetails: priorHeartbeatDetails(state, effectId),
223
250
  lastError: null,
224
251
  },
225
252
  },
@@ -274,6 +301,7 @@ function applyProtocol(state, event, registry) {
274
301
  : sameAttempt
275
302
  ? previous.lastHeartbeatAt
276
303
  : null,
304
+ heartbeatDetails: nextHeartbeatDetails(state, effectId, event, payload),
277
305
  lastError: readString(payload, 'error') ?? null,
278
306
  },
279
307
  },
@@ -302,6 +330,7 @@ function applyProtocol(state, event, registry) {
302
330
  deadlineAt: nextAttemptAt,
303
331
  startedAt: null,
304
332
  lastHeartbeatAt: null,
333
+ heartbeatDetails: priorHeartbeatDetails(state, effectId),
305
334
  lastError: readString(payload, 'error') ?? 'effect failed',
306
335
  },
307
336
  },
@@ -360,6 +389,36 @@ function applyProtocol(state, event, registry) {
360
389
  }
361
390
  return next;
362
391
  }
392
+ case 'runtime.run.launched': {
393
+ const childRunId = readString(payload, 'runId');
394
+ if (!childRunId || state.launchedRuns.some((run) => run.runId === childRunId)) {
395
+ return state;
396
+ }
397
+ const close = readString(payload, 'close');
398
+ if (close !== 'terminate' && close !== 'wait') {
399
+ return state;
400
+ }
401
+ return {
402
+ ...state,
403
+ launchedRuns: [...state.launchedRuns, { runId: childRunId, close }],
404
+ };
405
+ }
406
+ case 'runtime.child.settled': {
407
+ const childRunId = readString(payload, 'runId');
408
+ if (!childRunId) {
409
+ return state;
410
+ }
411
+ return {
412
+ ...state,
413
+ launchedRuns: state.launchedRuns.filter((run) => run.runId !== childRunId),
414
+ };
415
+ }
416
+ case 'runtime.child.close.requested':
417
+ return {
418
+ ...state,
419
+ childCloseSent: true,
420
+ closeWaiting: readString(payload, 'policy') === 'wait' ? true : state.closeWaiting,
421
+ };
363
422
  case 'runtime.snapshot.taken':
364
423
  case 'runtime.thread.cancel.requested':
365
424
  case 'runtime.timer.set':
@@ -388,6 +447,14 @@ export function stableEffectId(threadId, effect) {
388
447
  Predicate.isString(effect.threadId)) {
389
448
  return createEffectId(threadId, `cancel_${effect.threadId}`);
390
449
  }
450
+ if (effect.type === 'runtime.launch' && 'runId' in effect && Predicate.isString(effect.runId)) {
451
+ return createEffectId(threadId, `launch_${effect.runId}`);
452
+ }
453
+ if (effect.type === 'runtime.cancelRun' &&
454
+ 'runId' in effect &&
455
+ Predicate.isString(effect.runId)) {
456
+ return createEffectId(threadId, `cancel_${effect.runId}`);
457
+ }
391
458
  return undefined;
392
459
  }
393
460
  function mapEffects(threadId, event, effects, completed, existingForThread) {
@@ -427,7 +494,7 @@ function reconcileEffects(state, threadId, event, effects) {
427
494
  };
428
495
  }
429
496
  function deliver(state, event, registry) {
430
- const threadId = event.threadId;
497
+ const threadId = event.threadId ?? (event.origin.type === 'external' ? state.rootThreadId : null);
431
498
  if (!threadId) {
432
499
  return state;
433
500
  }
@@ -451,8 +518,23 @@ function deliver(state, event, registry) {
451
518
  const enabled = definition.effects(nextState, ctx);
452
519
  let next = putThread(state, { ...record, state: nextState });
453
520
  next = reconcileEffects(next, threadId, event, enabled);
521
+ if (definition.forgetsCompletedEffects) {
522
+ next = forgetCompletedEffects(next, threadId, enabled);
523
+ }
454
524
  return next;
455
525
  }
526
+ /** Drop completed effect ids this thread no longer returns from `effects()`. */
527
+ function forgetCompletedEffects(state, threadId, enabled) {
528
+ const enabledIds = new Set(enabled.flatMap((effect) => {
529
+ const effectId = stableEffectId(threadId, effect);
530
+ return effectId === undefined ? [] : [effectId];
531
+ }));
532
+ const prefix = `${threadId}:`;
533
+ const completedEffectIds = state.completedEffectIds.filter((effectId) => !effectId.startsWith(prefix) || enabledIds.has(effectId));
534
+ return completedEffectIds.length === state.completedEffectIds.length
535
+ ? state
536
+ : { ...state, completedEffectIds };
537
+ }
456
538
  function failUnhandledEffect(state, event, effectIdsBeforeDeliver) {
457
539
  if (event.type !== 'runtime.effect.failed') {
458
540
  return state;
@@ -1,6 +1,8 @@
1
1
  export declare function createEventId(): string;
2
2
  export declare function createRunId(): string;
3
3
  export declare function createThreadId(): string;
4
+ /** A stable `run_` id derived from `parts`, so a retry launches the same child run. */
5
+ export declare function deterministicRunId(...parts: readonly (string | number)[]): string;
4
6
  /**
5
7
  * A UUID-shaped id derived only from `parts`, so a reducer that replays the same events names the same
6
8
  * child thread every time.
package/dist/core/ids.js CHANGED
@@ -25,6 +25,10 @@ function fnv1a32(value, seed) {
25
25
  }
26
26
  return (hash >>> 0).toString(16).padStart(8, '0');
27
27
  }
28
+ /** A stable `run_` id derived from `parts`, so a retry launches the same child run. */
29
+ export function deterministicRunId(...parts) {
30
+ return `run_${deterministicThreadId(...parts).replaceAll('-', '')}`;
31
+ }
28
32
  /**
29
33
  * A UUID-shaped id derived only from `parts`, so a reducer that replays the same events names the same
30
34
  * child thread every time.
@@ -8,6 +8,7 @@ export * from './envelope';
8
8
  export * from './catalog';
9
9
  export * from './effects';
10
10
  export * from './protocol';
11
+ export * from './subscription';
11
12
  export * from './thread';
12
13
  export * from './state';
13
14
  export * from './match';
@@ -15,6 +16,8 @@ export * from './fold';
15
16
  export * from './replay';
16
17
  export * from './fork';
17
18
  export * from './projection';
19
+ export * from './attempts';
20
+ export type * from './register';
18
21
  export * from './module';
19
22
  export * from './kind';
20
23
  export * from './module-scope';
@@ -28,6 +31,13 @@ export * from './serialize';
28
31
  export * from './coalesce';
29
32
  export * from './event-index';
30
33
  export * from './limits';
34
+ export * from './blob-store';
35
+ export * from './blobs';
36
+ export * from './blob-offload';
31
37
  export * from './definition-store';
38
+ export * from './definition-resolver';
39
+ export * from './launcher';
40
+ export * from './trace';
41
+ export * from './run-context';
32
42
  export * from './published';
33
43
  export type { DebuggerHost, DebuggerRequest } from './debugger-host';
@@ -7,6 +7,7 @@ export * from './envelope.js';
7
7
  export * from './catalog.js';
8
8
  export * from './effects.js';
9
9
  export * from './protocol.js';
10
+ export * from './subscription.js';
10
11
  export * from './thread.js';
11
12
  export * from './state.js';
12
13
  export * from './match.js';
@@ -14,6 +15,7 @@ export * from './fold.js';
14
15
  export * from './replay.js';
15
16
  export * from './fork.js';
16
17
  export * from './projection.js';
18
+ export * from './attempts.js';
17
19
  export * from './module.js';
18
20
  export * from './kind.js';
19
21
  export * from './module-scope.js';
@@ -27,5 +29,12 @@ export * from './serialize.js';
27
29
  export * from './coalesce.js';
28
30
  export * from './event-index.js';
29
31
  export * from './limits.js';
32
+ export * from './blob-store.js';
33
+ export * from './blobs.js';
34
+ export * from './blob-offload.js';
30
35
  export * from './definition-store.js';
36
+ export * from './definition-resolver.js';
37
+ export * from './launcher.js';
38
+ export * from './trace.js';
39
+ export * from './run-context.js';
31
40
  export * from './published.js';