@swirls/looms 0.0.2 → 0.1.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 (256) hide show
  1. package/CHANGELOG.md +9 -2
  2. package/dist/actor/cell.d.ts +9 -4
  3. package/dist/actor/cell.js +7 -11
  4. package/dist/actor/host.d.ts +7 -3
  5. package/dist/actor/host.js +55 -32
  6. package/dist/actor/index.d.ts +1 -1
  7. package/dist/actor/index.js +1 -1
  8. package/dist/actor/routing.d.ts +12 -1
  9. package/dist/actor/routing.js +57 -51
  10. package/dist/agent/agent-session.d.ts +5 -0
  11. package/dist/agent/agent-session.js +270 -0
  12. package/dist/agent/definitions-store.d.ts +2 -3
  13. package/dist/agent/definitions-store.js +2 -4
  14. package/dist/agent/definitions.d.ts +46 -20
  15. package/dist/agent/definitions.js +36 -8
  16. package/dist/agent/effects.d.ts +359 -2
  17. package/dist/agent/effects.js +60 -34
  18. package/dist/agent/events.d.ts +175 -19
  19. package/dist/agent/events.js +45 -10
  20. package/dist/agent/index.d.ts +1 -0
  21. package/dist/agent/index.js +1 -0
  22. package/dist/agent/llm.d.ts +11 -2
  23. package/dist/agent/llm.js +13 -3
  24. package/dist/agent/module.d.ts +812 -78
  25. package/dist/agent/module.js +16 -4
  26. package/dist/agent/scope.d.ts +174 -18
  27. package/dist/agent/signals.d.ts +24 -1
  28. package/dist/agent/signals.js +77 -0
  29. package/dist/agent/threads.d.ts +3 -33
  30. package/dist/agent/threads.js +20 -25
  31. package/dist/agent/tool-schema.js +0 -1
  32. package/dist/agent/types.d.ts +49 -4
  33. package/dist/agent/types.js +5 -5
  34. package/dist/ai-vercel/index.d.ts +1 -0
  35. package/dist/ai-vercel/index.js +1 -0
  36. package/dist/ai-vercel/messages.js +22 -7
  37. package/dist/ai-vercel/vercel-evaluator.d.ts +23 -0
  38. package/dist/ai-vercel/vercel-evaluator.js +68 -0
  39. package/dist/ai-vercel/vercel-llm.js +64 -44
  40. package/dist/approval/effects.d.ts +89 -0
  41. package/dist/approval/module.d.ts +179 -29
  42. package/dist/approval/projections.d.ts +0 -14
  43. package/dist/approval/projections.js +20 -3
  44. package/dist/approval/signals.d.ts +2 -0
  45. package/dist/approval/signals.js +18 -2
  46. package/dist/cli/cli.js +2 -0
  47. package/dist/cli/commands.js +12 -16
  48. package/dist/client/client.d.ts +1195 -18
  49. package/dist/client/client.js +118 -120
  50. package/dist/client/index.d.ts +1 -1
  51. package/dist/client/index.js +1 -1
  52. package/dist/client/sse.d.ts +13 -0
  53. package/dist/client/sse.js +38 -44
  54. package/dist/cloudflare/alarm-scheduler.d.ts +12 -2
  55. package/dist/cloudflare/alarm-scheduler.js +34 -7
  56. package/dist/cloudflare/durable-object.d.ts +10 -2
  57. package/dist/cloudflare/durable-object.js +59 -28
  58. package/dist/cloudflare/event-store.d.ts +7 -2
  59. package/dist/cloudflare/event-store.js +8 -7
  60. package/dist/cloudflare/index.d.ts +2 -2
  61. package/dist/cloudflare/index.js +2 -2
  62. package/dist/cloudflare/router.js +13 -14
  63. package/dist/core/catalog.d.ts +3 -3
  64. package/dist/core/catalog.js +2 -5
  65. package/dist/core/coalesce.js +5 -3
  66. package/dist/core/compose.d.ts +2 -1
  67. package/dist/core/compose.js +18 -2
  68. package/dist/core/definition-store.d.ts +11 -0
  69. package/dist/core/definition-store.js +6 -0
  70. package/dist/core/effects.d.ts +127 -15
  71. package/dist/core/effects.js +89 -17
  72. package/dist/core/envelope.d.ts +27 -7
  73. package/dist/core/envelope.js +20 -6
  74. package/dist/core/event-codec.d.ts +4 -2
  75. package/dist/core/event-codec.js +10 -5
  76. package/dist/core/event-index.js +1 -3
  77. package/dist/core/fold.d.ts +3 -0
  78. package/dist/core/fold.js +171 -24
  79. package/dist/core/ids.d.ts +5 -0
  80. package/dist/core/ids.js +35 -2
  81. package/dist/core/index.d.ts +5 -1
  82. package/dist/core/index.js +5 -1
  83. package/dist/core/kind.d.ts +22 -0
  84. package/dist/core/kind.js +17 -0
  85. package/dist/core/limits.d.ts +34 -0
  86. package/dist/core/limits.js +46 -0
  87. package/dist/core/match.d.ts +1 -0
  88. package/dist/core/match.js +10 -1
  89. package/dist/core/module-scope.d.ts +22 -20
  90. package/dist/core/module-scope.js +38 -28
  91. package/dist/core/module.d.ts +16 -5
  92. package/dist/core/module.js +4 -0
  93. package/dist/core/projection.d.ts +13 -13
  94. package/dist/core/projection.js +9 -5
  95. package/dist/core/protocol.d.ts +68 -11
  96. package/dist/core/protocol.js +82 -11
  97. package/dist/core/replay.d.ts +312 -3
  98. package/dist/core/replay.js +11 -1
  99. package/dist/core/schema.d.ts +8 -19
  100. package/dist/core/schema.js +25 -80
  101. package/dist/core/serialize.js +2 -4
  102. package/dist/core/snapshot-store.d.ts +2 -1
  103. package/dist/core/snapshot-store.js +8 -2
  104. package/dist/core/snapshots.d.ts +5 -1
  105. package/dist/core/snapshots.js +10 -15
  106. package/dist/core/sqlite-store.js +56 -11
  107. package/dist/core/state.d.ts +142 -4
  108. package/dist/core/state.js +78 -0
  109. package/dist/core/store.d.ts +38 -3
  110. package/dist/core/store.js +77 -22
  111. package/dist/core/thread.d.ts +10 -9
  112. package/dist/core/types.d.ts +5 -8
  113. package/dist/core/types.js +28 -24
  114. package/dist/core/validate-event.js +14 -4
  115. package/dist/core/worker-callback.d.ts +17 -0
  116. package/dist/core/worker-callback.js +1 -0
  117. package/dist/debugger/components/event-stream.js +9 -10
  118. package/dist/debugger/events/protocol-summarize.js +26 -0
  119. package/dist/debugger/lib/event-meta.js +1 -2
  120. package/dist/evaluator/definitions-store.d.ts +11 -0
  121. package/dist/evaluator/definitions-store.js +8 -0
  122. package/dist/evaluator/definitions.d.ts +18 -0
  123. package/dist/evaluator/definitions.js +4 -0
  124. package/dist/evaluator/effects.d.ts +226 -0
  125. package/dist/evaluator/effects.js +132 -0
  126. package/dist/evaluator/evaluator.d.ts +24 -0
  127. package/dist/evaluator/evaluator.js +67 -0
  128. package/dist/evaluator/events.d.ts +59 -0
  129. package/dist/evaluator/events.js +21 -0
  130. package/dist/evaluator/index.d.ts +11 -0
  131. package/dist/evaluator/index.js +11 -0
  132. package/dist/evaluator/module.d.ts +606 -0
  133. package/dist/evaluator/module.js +25 -0
  134. package/dist/evaluator/projections.d.ts +50 -0
  135. package/dist/evaluator/projections.js +20 -0
  136. package/dist/evaluator/scope.d.ts +59 -0
  137. package/dist/evaluator/scope.js +7 -0
  138. package/dist/evaluator/signals.d.ts +3 -0
  139. package/dist/evaluator/signals.js +17 -0
  140. package/dist/evaluator/threads.d.ts +62 -0
  141. package/dist/evaluator/threads.js +117 -0
  142. package/dist/evaluator/types.d.ts +125 -0
  143. package/dist/evaluator/types.js +51 -0
  144. package/dist/projectors/delivery.d.ts +83 -0
  145. package/dist/projectors/delivery.js +182 -0
  146. package/dist/projectors/index-model.d.ts +1 -1
  147. package/dist/projectors/index-model.js +4 -6
  148. package/dist/projectors/index.d.ts +2 -0
  149. package/dist/projectors/index.js +2 -0
  150. package/dist/projectors/memory.d.ts +1 -1
  151. package/dist/projectors/memory.js +10 -8
  152. package/dist/projectors/postgres.js +44 -58
  153. package/dist/projectors/projector.d.ts +17 -0
  154. package/dist/projectors/projector.js +26 -1
  155. package/dist/projectors/sqlite-delivery.d.ts +4 -0
  156. package/dist/projectors/sqlite-delivery.js +147 -0
  157. package/dist/projectors/sqlite.js +23 -23
  158. package/dist/projectors/with-projectors.d.ts +7 -2
  159. package/dist/projectors/with-projectors.js +27 -28
  160. package/dist/react/notify.js +11 -8
  161. package/dist/react/react.d.ts +2 -3
  162. package/dist/react/react.js +14 -30
  163. package/dist/react/sse.d.ts +1 -1
  164. package/dist/react/sse.js +1 -1
  165. package/dist/react/store.d.ts +2 -2
  166. package/dist/react/store.js +109 -119
  167. package/dist/runtime/cancellation.d.ts +16 -0
  168. package/dist/runtime/cancellation.js +129 -0
  169. package/dist/runtime/deadline-transition-planner.d.ts +31 -0
  170. package/dist/runtime/deadline-transition-planner.js +139 -0
  171. package/dist/runtime/dispatch-effect.d.ts +3 -2
  172. package/dist/runtime/dispatch-effect.js +7 -13
  173. package/dist/runtime/errors.d.ts +51 -0
  174. package/dist/runtime/errors.js +39 -0
  175. package/dist/runtime/{runtime-helpers.d.ts → helpers.d.ts} +24 -4
  176. package/dist/runtime/{runtime-helpers.js → helpers.js} +56 -19
  177. package/dist/runtime/index.d.ts +3 -3
  178. package/dist/runtime/index.js +3 -3
  179. package/dist/runtime/ingress.d.ts +15 -0
  180. package/dist/runtime/ingress.js +42 -0
  181. package/dist/runtime/local-effect-executor.d.ts +21 -0
  182. package/dist/runtime/local-effect-executor.js +137 -0
  183. package/dist/runtime/looms.d.ts +22 -4
  184. package/dist/runtime/looms.js +54 -36
  185. package/dist/runtime/observer.d.ts +45 -0
  186. package/dist/runtime/observer.js +11 -0
  187. package/dist/runtime/repository.d.ts +55 -0
  188. package/dist/runtime/repository.js +278 -0
  189. package/dist/runtime/retry-policy.d.ts +16 -0
  190. package/dist/runtime/retry-policy.js +29 -0
  191. package/dist/runtime/run-execution-context.d.ts +30 -0
  192. package/dist/runtime/run-execution-context.js +56 -0
  193. package/dist/runtime/run-start.d.ts +4 -0
  194. package/dist/runtime/run-start.js +44 -0
  195. package/dist/runtime/runtime.d.ts +6 -87
  196. package/dist/runtime/runtime.js +233 -470
  197. package/dist/runtime/server/auth.d.ts +40 -0
  198. package/dist/runtime/server/auth.js +40 -0
  199. package/dist/runtime/server/fetch-handler.d.ts +14 -0
  200. package/dist/runtime/server/fetch-handler.js +51 -0
  201. package/dist/runtime/server/http.d.ts +37 -0
  202. package/dist/runtime/server/http.js +62 -0
  203. package/dist/runtime/server/route-table.d.ts +55 -0
  204. package/dist/runtime/server/route-table.js +74 -0
  205. package/dist/runtime/server/routes/effects.d.ts +3 -0
  206. package/dist/runtime/server/routes/effects.js +59 -0
  207. package/dist/runtime/server/routes/operations.d.ts +3 -0
  208. package/dist/runtime/server/routes/operations.js +14 -0
  209. package/dist/runtime/server/routes/runs.d.ts +3 -0
  210. package/dist/runtime/server/routes/runs.js +192 -0
  211. package/dist/runtime/server.d.ts +2 -8
  212. package/dist/runtime/server.js +4 -211
  213. package/dist/runtime/sse.d.ts +2 -1
  214. package/dist/runtime/sse.js +24 -17
  215. package/dist/runtime/types.d.ts +115 -0
  216. package/dist/runtime/types.js +1 -0
  217. package/dist/runtime/wake-engine.d.ts +20 -0
  218. package/dist/runtime/wake-engine.js +311 -0
  219. package/dist/runtime/wake-scheduler.d.ts +3 -4
  220. package/dist/runtime/wake-scheduler.js +25 -31
  221. package/dist/runtime/worker-callbacks.d.ts +16 -0
  222. package/dist/runtime/worker-callbacks.js +167 -0
  223. package/dist/runtime/worker-effect-dispatch.d.ts +36 -0
  224. package/dist/runtime/worker-effect-dispatch.js +126 -0
  225. package/dist/s2/config.d.ts +1 -1
  226. package/dist/s2/config.js +1 -1
  227. package/dist/s2/lite.js +70 -82
  228. package/dist/s2/pagination.d.ts +15 -0
  229. package/dist/s2/pagination.js +36 -0
  230. package/dist/s2/projector.js +6 -5
  231. package/dist/s2/snapshot-store.js +148 -163
  232. package/dist/s2/store.d.ts +3 -21
  233. package/dist/s2/store.js +71 -165
  234. package/dist/testing/index.d.ts +15 -0
  235. package/dist/testing/index.js +53 -16
  236. package/dist/workflow/definitions-store.d.ts +2 -3
  237. package/dist/workflow/definitions-store.js +2 -2
  238. package/dist/workflow/definitions.d.ts +78 -22
  239. package/dist/workflow/definitions.js +80 -14
  240. package/dist/workflow/effects.d.ts +453 -107
  241. package/dist/workflow/effects.js +214 -115
  242. package/dist/workflow/events.d.ts +149 -5
  243. package/dist/workflow/events.js +27 -5
  244. package/dist/workflow/graph.d.ts +56 -0
  245. package/dist/workflow/graph.js +207 -0
  246. package/dist/workflow/index.d.ts +1 -0
  247. package/dist/workflow/index.js +1 -0
  248. package/dist/workflow/module.d.ts +1047 -266
  249. package/dist/workflow/projections.d.ts +14 -12
  250. package/dist/workflow/projections.js +30 -8
  251. package/dist/workflow/scope.d.ts +146 -4
  252. package/dist/workflow/threads.d.ts +383 -155
  253. package/dist/workflow/threads.js +308 -110
  254. package/package.json +7 -2
  255. package/dist/runtime/events-api.d.ts +0 -4
  256. package/dist/runtime/events-api.js +0 -84
@@ -1,10 +1,15 @@
1
+ import type { Effect } from 'effect';
1
2
  import type { EventStore } from '@swirls/looms/core';
2
- import { type Projector } from '@swirls/looms/projectors';
3
+ import { type Projector, type ProjectorDelivery } from '@swirls/looms/projectors';
3
4
  export interface DurableObjectStoreOptions {
4
5
  readonly projectors?: readonly Projector[];
6
+ readonly scheduleProjectorRetry?: (at: number) => Effect.Effect<void>;
7
+ }
8
+ export interface DurableObjectEventStore extends EventStore {
9
+ readonly projectorDelivery?: ProjectorDelivery;
5
10
  }
6
11
  /**
7
12
  * Creates an EventStore backed by a Durable Object's embedded SQLite database (`ctx.storage.sql`).
8
13
  * Optionally attaches asynchronous projectors (such as S2).
9
14
  */
10
- export declare function durableObjectEventStore(ctx: DurableObjectState, options?: DurableObjectStoreOptions): EventStore;
15
+ export declare function durableObjectEventStore(ctx: DurableObjectState, options?: DurableObjectStoreOptions): DurableObjectEventStore;
@@ -1,18 +1,19 @@
1
1
  import { sqliteEventStore, sqlStorageExec } from '@swirls/looms/core/sqlite-store';
2
- import { withProjectors } from '@swirls/looms/projectors';
2
+ import { createProjectorDelivery, sqliteProjectorCursorStore, withProjectors, } from '@swirls/looms/projectors';
3
3
  /**
4
4
  * Creates an EventStore backed by a Durable Object's embedded SQLite database (`ctx.storage.sql`).
5
5
  * Optionally attaches asynchronous projectors (such as S2).
6
6
  */
7
7
  export function durableObjectEventStore(ctx, options = {}) {
8
- const store = sqliteEventStore({
9
- exec: sqlStorageExec(ctx.storage.sql, {
10
- // DO SQLite rejects BEGIN/COMMIT/ROLLBACK; use the storage transaction API.
11
- transaction: (fn) => ctx.storage.transactionSync(fn),
12
- }),
8
+ const exec = sqlStorageExec(ctx.storage.sql, {
9
+ // DO SQLite rejects BEGIN/COMMIT/ROLLBACK; use the storage transaction API.
10
+ transaction: (fn) => ctx.storage.transactionSync(fn),
13
11
  });
12
+ const store = sqliteEventStore({ exec });
14
13
  if (options.projectors && options.projectors.length > 0) {
15
- return withProjectors(store, options.projectors);
14
+ const delivery = createProjectorDelivery(store, options.projectors, sqliteProjectorCursorStore(exec), { scheduleRetry: options.scheduleProjectorRetry });
15
+ const wrapped = withProjectors(store, options.projectors, { delivery });
16
+ return Object.assign(wrapped, { projectorDelivery: delivery });
16
17
  }
17
18
  return store;
18
19
  }
@@ -1,4 +1,4 @@
1
- export { alarmScheduler, type AlarmStorage } from './alarm-scheduler';
2
- export { durableObjectEventStore, type DurableObjectStoreOptions } from './event-store';
1
+ export { durableObjectAlarms, type AlarmStorage, type DurableObjectAlarms } from './alarm-scheduler';
2
+ export { durableObjectEventStore, type DurableObjectEventStore, type DurableObjectStoreOptions, } from './event-store';
3
3
  export { LoomsDurableObject, type LoomsDurableObjectConfig } from './durable-object';
4
4
  export { routeToDurableObject, type DurableObjectNamespaceLike, type DurableObjectStubLike, } from './router';
@@ -1,4 +1,4 @@
1
- export { alarmScheduler } from './alarm-scheduler.js';
2
- export { durableObjectEventStore } from './event-store.js';
1
+ export { durableObjectAlarms } from './alarm-scheduler.js';
2
+ export { durableObjectEventStore, } from './event-store.js';
3
3
  export { LoomsDurableObject } from './durable-object.js';
4
4
  export { routeToDurableObject, } from './router.js';
@@ -1,26 +1,25 @@
1
- import { resolveRunTarget } from '@swirls/looms/actor';
1
+ import { resolveRunTarget, RunTargetError } from '@swirls/looms/actor';
2
2
  /**
3
3
  * Routes incoming HTTP requests to the appropriate Durable Object actor cell.
4
4
  * Handles `/health` locally, returns 501 for global `GET /runs`, and forwards
5
5
  * all other run and event-stream requests to `namespace.getByName(runId)`.
6
6
  */
7
- export async function routeToDurableObject(namespace, req) {
7
+ export function routeToDurableObject(namespace, req) {
8
8
  const url = new URL(req.url);
9
9
  if (url.pathname === '/health') {
10
- return Response.json({ ok: true });
10
+ return Promise.resolve(Response.json({ ok: true }));
11
11
  }
12
12
  if (req.method === 'GET' && url.pathname === '/runs') {
13
- return Response.json({
13
+ return Promise.resolve(Response.json({
14
14
  error: 'Global run listing is not supported in the Durable Object backend without a global index',
15
- }, { status: 501 });
16
- }
17
- try {
18
- const { runId, request } = await resolveRunTarget(req);
19
- const stub = namespace.getByName(runId);
20
- return stub.fetch(request);
21
- }
22
- catch {
23
- // Paths without a resolvable runId (e.g. GET /) are not handled by this router.
24
- return null;
15
+ }, { status: 501 }));
25
16
  }
17
+ return resolveRunTarget(req)
18
+ .then(({ runId, request }) => namespace.getByName(runId).fetch(request))
19
+ .catch((error) => {
20
+ if (error instanceof RunTargetError) {
21
+ return null;
22
+ }
23
+ return Promise.reject(error);
24
+ });
26
25
  }
@@ -1,7 +1,7 @@
1
1
  import type { StandardSchemaV1 } from '@standard-schema/spec';
2
2
  import type { Schema } from 'effect';
3
3
  import { type EventEnvelope, type EventInput, type TypedEvent } from './envelope';
4
- import { type JsonValue } from './types';
4
+ import type { JsonValue } from './types';
5
5
  declare const catalogPayloadBrand: unique symbol;
6
6
  /** Type-only catalog entry. Carries a payload type without a runtime schema. */
7
7
  export type CatalogEventSpec<TPayload = object> = {
@@ -13,11 +13,11 @@ export type CatalogEventSpec<TPayload = object> = {
13
13
  * `payload<{ amount: number }>()` is a compile-time marker — not a cast of `{}`.
14
14
  */
15
15
  export declare function payload<TPayload>(): CatalogEventSpec<TPayload>;
16
- export type CatalogEntry = CatalogEventSpec | Schema.Top | StandardSchemaV1;
16
+ export type CatalogEntry = CatalogEventSpec | Schema.ConstraintDecoder<unknown> | StandardSchemaV1;
17
17
  export type CatalogEntries = {
18
18
  readonly [key: string]: CatalogEntry;
19
19
  };
20
- export type InferPayload<T> = T extends CatalogEventSpec<infer P> ? P : T extends Schema.Top ? Schema.Schema.Type<T> : T extends StandardSchemaV1<infer _In, infer Out> ? Out : JsonValue;
20
+ export type InferPayload<T> = T extends CatalogEventSpec<infer P> ? P : T extends Schema.ConstraintDecoder<unknown> ? Schema.Schema.Type<T> : T extends StandardSchemaV1<infer _In, infer Out> ? Out : JsonValue;
21
21
  export interface EventCatalog<TNamespace extends string = string, TEntries extends CatalogEntries = CatalogEntries> {
22
22
  readonly namespace: TNamespace;
23
23
  readonly entries: TEntries;
@@ -1,5 +1,4 @@
1
1
  import { createEvent } from './envelope.js';
2
- import { asJson } from './types.js';
3
2
  /**
4
3
  * Declare a catalog event's payload type when you do not have a Zod / Standard Schema.
5
4
  *
@@ -17,19 +16,17 @@ export function defineEventCatalog(namespace, entries) {
17
16
  entries,
18
17
  event(key, eventPayload, meta) {
19
18
  const { runId, ...rest } = meta;
20
- // SAFETY: createEvent returns EventEnvelope with inferred payload type.
21
19
  return createEvent(runId, {
22
20
  ...rest,
23
21
  type: eventType(namespace, key),
24
- payload: asJson(eventPayload),
22
+ payload: eventPayload,
25
23
  });
26
24
  },
27
25
  input(key, eventPayload, meta) {
28
26
  return {
29
27
  ...meta,
30
28
  type: eventType(namespace, key),
31
- // SAFETY: asJson returns typed payload for catalog event.
32
- payload: asJson(eventPayload),
29
+ payload: eventPayload,
33
30
  };
34
31
  },
35
32
  };
@@ -90,7 +90,8 @@ export function createCoalescingAppender(flush, options) {
90
90
  }
91
91
  default: {
92
92
  const exhaustiveCheck = taken;
93
- return exhaustiveCheck;
93
+ void exhaustiveCheck;
94
+ return;
94
95
  }
95
96
  }
96
97
  }
@@ -135,11 +136,12 @@ export function createCoalescingAppender(flush, options) {
135
136
  if (result.pending > maxPending) {
136
137
  yield* Deferred.await(result.deferred);
137
138
  }
138
- return;
139
+ return undefined;
139
140
  }
140
141
  default: {
141
142
  const exhaustiveCheck = result;
142
- return exhaustiveCheck;
143
+ void exhaustiveCheck;
144
+ return undefined;
143
145
  }
144
146
  }
145
147
  });
@@ -3,7 +3,7 @@ import type { CatalogEvent, EventCatalog } from './catalog';
3
3
  import type { EffectDefinition } from './effects';
4
4
  import type { EventEnvelope } from './envelope';
5
5
  import type { FoldRegistry } from './fold';
6
- import type { AnyRuntimeModule, EffectMiddleware, RuntimeModule, RegisteredDefinition } from './module';
6
+ import type { AnyRuntimeModule, EffectMiddleware, RuntimeModule, RegisteredDefinition, SignalInterruptRule } from './module';
7
7
  import type { ProjectionDefinition } from './projection';
8
8
  import { protocolCatalog } from './protocol';
9
9
  import type { ThreadDefinition } from './thread';
@@ -22,6 +22,7 @@ export interface ComposedRegistry extends FoldRegistry {
22
22
  readonly projections: ReadonlyMap<string, ProjectionDefinition>;
23
23
  readonly catalogs: readonly EventCatalog[];
24
24
  readonly middleware: readonly EffectMiddleware[];
25
+ readonly interruptRules: readonly SignalInterruptRule[];
25
26
  }
26
27
  export declare function composeModules(modules: readonly AnyRuntimeModule[]): ComposedRegistry;
27
28
  export declare function composeModulesEffect(modules: readonly AnyRuntimeModule[]): Effect.Effect<ComposedRegistry, ModuleCompositionError>;
@@ -1,4 +1,5 @@
1
1
  import { Data, Effect } from 'effect';
2
+ import { DEFAULT_DEFINITION_VERSION, definitionKey } from './module.js';
2
3
  import { protocolCatalog } from './protocol.js';
3
4
  export class ModuleCompositionError extends Data.TaggedError('ModuleCompositionError') {
4
5
  constructor(text) {
@@ -14,6 +15,7 @@ export function composeModules(modules) {
14
15
  const effects = new Map();
15
16
  const projections = new Map();
16
17
  const middleware = [];
18
+ const interruptRules = [];
17
19
  const catalogs = [protocolCatalog];
18
20
  for (const module of modules) {
19
21
  if (namespaces.has(module.namespace)) {
@@ -41,7 +43,8 @@ export function composeModules(modules) {
41
43
  }
42
44
  }
43
45
  for (const definition of module.definitions ?? []) {
44
- const key = `${definition.kind}:${definition.name}`;
46
+ const version = definition.version ?? DEFAULT_DEFINITION_VERSION;
47
+ const key = definitionKey(definition.kind, definition.name, version);
45
48
  if (definitionKeys.has(key)) {
46
49
  throw new ModuleCompositionError(`Duplicate definition: ${key}`);
47
50
  }
@@ -52,6 +55,7 @@ export function composeModules(modules) {
52
55
  definitions.push({
53
56
  kind: definition.kind,
54
57
  name: definition.name,
58
+ version,
55
59
  input: definition.input,
56
60
  value: definition,
57
61
  });
@@ -76,6 +80,9 @@ export function composeModules(modules) {
76
80
  if (module.middleware) {
77
81
  middleware.push(...module.middleware);
78
82
  }
83
+ if (module.interruptsOnSignal) {
84
+ interruptRules.push(module.interruptsOnSignal);
85
+ }
79
86
  if (module.projections) {
80
87
  for (const definition of Object.values(module.projections)) {
81
88
  if (projections.has(definition.name)) {
@@ -85,7 +92,16 @@ export function composeModules(modules) {
85
92
  }
86
93
  }
87
94
  }
88
- return { definitions, modules, threads, effects, projections, catalogs, middleware };
95
+ return {
96
+ definitions,
97
+ modules,
98
+ threads,
99
+ effects,
100
+ projections,
101
+ catalogs,
102
+ middleware,
103
+ interruptRules,
104
+ };
89
105
  }
90
106
  export function composeModulesEffect(modules) {
91
107
  return Effect.try({
@@ -0,0 +1,11 @@
1
+ export interface VersionedDefinition {
2
+ readonly kind: string;
3
+ readonly name: string;
4
+ readonly version: string;
5
+ }
6
+ /** Lookup of one definition kind by name and version. */
7
+ export interface DefinitionStore<TDefinition> {
8
+ get(name: string, version: string): TDefinition | undefined;
9
+ }
10
+ /** Indexes definitions of one kind by `definitionKey`. */
11
+ export declare function makeDefinitionStore<TDefinition extends VersionedDefinition>(kind: TDefinition['kind'], definitions: ReadonlyArray<TDefinition>): DefinitionStore<TDefinition>;
@@ -0,0 +1,6 @@
1
+ import { definitionKey } from './module.js';
2
+ /** Indexes definitions of one kind by `definitionKey`. */
3
+ export function makeDefinitionStore(kind, definitions) {
4
+ const byKey = new Map(definitions.map((definition) => [definitionKey(definition.kind, definition.name, definition.version), definition]));
5
+ return { get: (name, version) => byKey.get(definitionKey(kind, name, version)) };
6
+ }
@@ -1,6 +1,6 @@
1
- import { Effect } from 'effect';
2
- import type { EventInput } from './envelope';
3
- import { type InferDefinedSchema, type SchemaInput } from './schema';
1
+ import { Effect, Schema, type Context } from 'effect';
2
+ import { type EventInput } from './envelope';
3
+ import { type InferDefinedSchema, type InvalidInputError, type SchemaInput } from './schema';
4
4
  import { type JsonValue } from './types';
5
5
  export type WaitOnEvent = {
6
6
  type: string | readonly string[];
@@ -15,6 +15,7 @@ export type SpawnEffect = {
15
15
  childThreadId: string;
16
16
  kind: string;
17
17
  definitionName: string;
18
+ definitionVersion: string;
18
19
  input: JsonValue;
19
20
  };
20
21
  export type WaitEffect = {
@@ -26,6 +27,8 @@ export type WaitEffect = {
26
27
  export type EmitEffect<E extends EventInput = EventInput> = {
27
28
  type: 'runtime.emit';
28
29
  event: E;
30
+ /** Stable identity, so the emit survives reordering of the thread's effects. */
31
+ tag?: string;
29
32
  };
30
33
  export type CompleteEffect = {
31
34
  type: 'runtime.complete';
@@ -38,11 +41,17 @@ export type FailEffect = {
38
41
  export type CancelEffect = {
39
42
  type: 'runtime.cancel';
40
43
  threadId: string;
44
+ tag?: string;
41
45
  };
42
46
  export interface RetryPolicy {
43
47
  readonly maxAttempts: number;
44
48
  readonly backoffMs?: number;
45
49
  readonly maxBackoffMs?: number;
50
+ readonly backoffMultiplier?: number;
51
+ readonly scheduleToStartTimeoutMs?: number;
52
+ readonly startToCloseTimeoutMs?: number;
53
+ readonly heartbeatTimeoutMs?: number;
54
+ readonly cancellationTimeoutMs?: number;
46
55
  }
47
56
  export type InvokeEffect = {
48
57
  type: string;
@@ -51,6 +60,69 @@ export type InvokeEffect = {
51
60
  };
52
61
  export type PrimitiveEffect = SpawnEffect | WaitEffect | EmitEffect | CompleteEffect | FailEffect | CancelEffect;
53
62
  export type RuntimeEffect = PrimitiveEffect | InvokeEffect;
63
+ export declare const WaitConditionSchema: Schema.Union<readonly [Schema.Struct<{
64
+ readonly type: Schema.Union<readonly [Schema.String, Schema.$Array<Schema.String>]>;
65
+ readonly match: Schema.optional<Schema.Codec<Schema.Json, Schema.Json, never, never>>;
66
+ }>, Schema.Struct<{
67
+ readonly timerAt: Schema.Finite;
68
+ }>]>;
69
+ export declare const RuntimeEffectSchema: Schema.Union<readonly [Schema.Struct<{
70
+ readonly type: Schema.Literal<"runtime.spawn">;
71
+ readonly childThreadId: Schema.String;
72
+ readonly kind: Schema.String;
73
+ readonly definitionName: Schema.String;
74
+ readonly definitionVersion: Schema.withDecodingDefaultKey<Schema.String, never>;
75
+ readonly input: Schema.Codec<Schema.Json, Schema.Json, never, never>;
76
+ }>, Schema.Struct<{
77
+ readonly type: Schema.Literal<"runtime.wait">;
78
+ readonly waitId: Schema.String;
79
+ readonly on: Schema.Union<readonly [Schema.Struct<{
80
+ readonly type: Schema.Union<readonly [Schema.String, Schema.$Array<Schema.String>]>;
81
+ readonly match: Schema.optional<Schema.Codec<Schema.Json, Schema.Json, never, never>>;
82
+ }>, Schema.Struct<{
83
+ readonly timerAt: Schema.Finite;
84
+ }>]>;
85
+ readonly tag: Schema.optional<Schema.Codec<Schema.Json, Schema.Json, never, never>>;
86
+ }>, Schema.Struct<{
87
+ readonly type: Schema.Literal<"runtime.emit">;
88
+ readonly event: Schema.Struct<{
89
+ readonly type: Schema.String;
90
+ readonly payload: Schema.Codec<Schema.Json, Schema.Json, never, never>;
91
+ readonly threadId: Schema.optional<Schema.NullOr<Schema.String>>;
92
+ readonly parentThreadId: Schema.optional<Schema.NullOr<Schema.String>>;
93
+ readonly causationId: Schema.optional<Schema.NullOr<Schema.String>>;
94
+ readonly correlationId: Schema.optional<Schema.NullOr<Schema.String>>;
95
+ readonly effectId: Schema.optional<Schema.NullOr<Schema.String>>;
96
+ readonly origin: Schema.optional<Schema.Union<readonly [Schema.Struct<{
97
+ readonly type: Schema.Literal<"thread">;
98
+ readonly threadId: Schema.String;
99
+ }>, Schema.Struct<{
100
+ readonly type: Schema.Literal<"external">;
101
+ readonly actorId: Schema.optional<Schema.String>;
102
+ }>, Schema.Struct<{
103
+ readonly type: Schema.Literal<"system">;
104
+ }>]>>;
105
+ readonly ephemeral: Schema.optional<Schema.Boolean>;
106
+ readonly id: Schema.optional<Schema.String>;
107
+ readonly ts: Schema.optional<Schema.Finite>;
108
+ readonly idempotencyKey: Schema.optional<Schema.NullOr<Schema.String>>;
109
+ }>;
110
+ readonly tag: Schema.optional<Schema.String>;
111
+ }>, Schema.Struct<{
112
+ readonly type: Schema.Literal<"runtime.complete">;
113
+ readonly output: Schema.Codec<Schema.Json, Schema.Json, never, never>;
114
+ }>, Schema.Struct<{
115
+ readonly type: Schema.Literal<"runtime.fail">;
116
+ readonly error: Schema.String;
117
+ }>, Schema.Struct<{
118
+ readonly type: Schema.Literal<"runtime.cancel">;
119
+ readonly threadId: Schema.String;
120
+ readonly tag: Schema.optional<Schema.String>;
121
+ }>, Schema.Struct<{
122
+ readonly type: Schema.String;
123
+ readonly input: Schema.Codec<Schema.Json, Schema.Json, never, never>;
124
+ readonly tag: Schema.optional<Schema.String>;
125
+ }>]>;
54
126
  export declare function isPrimitiveEffect(effect: RuntimeEffect): effect is PrimitiveEffect;
55
127
  export declare function isWaitOnTimer(on: WaitCondition): on is WaitOnTimer;
56
128
  export declare function isWaitOnEvent(on: WaitCondition): on is WaitOnEvent;
@@ -62,6 +134,7 @@ export interface EffectContext {
62
134
  readonly runId: string;
63
135
  readonly threadId: string;
64
136
  readonly causingEventId: string;
137
+ readonly signal: AbortSignal;
65
138
  emit(event: EventInput): Promise<void>;
66
139
  }
67
140
  export interface ScopedEffectContext<E extends EventInput = EventInput> {
@@ -69,32 +142,60 @@ export interface ScopedEffectContext<E extends EventInput = EventInput> {
69
142
  readonly runId: string;
70
143
  readonly threadId: string;
71
144
  readonly causingEventId: string;
145
+ readonly signal: AbortSignal;
72
146
  emit(event: E): Promise<void>;
73
147
  }
74
148
  export type EffectInputSchema = SchemaInput;
75
- export type EffectHandlerResult<R = never, E extends EventInput = EventInput> = ReadonlyArray<E> | Promise<ReadonlyArray<E>> | Effect.Effect<ReadonlyArray<E>, Error, R>;
76
- export interface EffectDefinition<TInput = JsonValue, R = any, E extends EventInput = EventInput> {
149
+ declare const EffectHandlerError_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 & {
150
+ readonly _tag: "EffectHandlerError";
151
+ } & Readonly<A>;
152
+ export declare class EffectHandlerError extends EffectHandlerError_base<{
153
+ readonly cause: unknown;
154
+ readonly message: string;
155
+ }> {
156
+ constructor(cause: unknown);
157
+ }
158
+ export type EffectHandlerResult<R = never, E extends EventInput = EventInput, HandlerError = never> = ReadonlyArray<E> | Promise<ReadonlyArray<E>> | Effect.Effect<ReadonlyArray<E>, HandlerError, R>;
159
+ declare const effectInputType: unique symbol;
160
+ export type EffectExecution = 'local' | 'worker';
161
+ export interface EffectDefinition<TInput = JsonValue, R = Context.Service.Any, E extends EventInput = EventInput> {
77
162
  readonly type: string;
78
163
  readonly input?: SchemaInput;
79
164
  readonly retry?: RetryPolicy;
80
- execute(input: TInput, ctx: EffectContext): Effect.Effect<ReadonlyArray<E>, Error, R>;
165
+ /** Forces where the effect runs. When omitted, the runtime decides from `hasHandler` and the configured worker. */
166
+ readonly execution?: EffectExecution;
167
+ /** Whether `execute` runs a user handler. Without one, the effect can only complete through a worker. */
168
+ readonly hasHandler: boolean;
169
+ readonly [effectInputType]?: (input: TInput) => TInput;
170
+ execute(input: JsonValue, ctx: EffectContext): Effect.Effect<ReadonlyArray<E>, InvalidInputError | EffectHandlerError, R>;
81
171
  }
82
172
  /**
83
- * Define an effect handler. A run's actor cell executes each effect at most
84
- * once while it is alive. If the cell dies mid-execution, the replacement
85
- * actor may re-run the handler (at-least-once across failover). Keep handlers
173
+ * Define an effect. With `execute`, a run's actor cell runs the handler at
174
+ * most once while it is alive; if the cell dies mid-execution, the replacement
175
+ * actor may re-run it (at-least-once across failover). Keep handlers
86
176
  * idempotent when they have external side effects.
177
+ *
178
+ * Omit `execute` to declare an effect that only an `EffectWorker` completes.
87
179
  */
88
- export declare function defineEffect<TSchema = undefined, TInput = InferDefinedSchema<TSchema>, R = never, E extends EventInput = EventInput>(def: {
180
+ export declare function defineEffect<TSchema extends SchemaInput, R = never, E extends EventInput = EventInput, HandlerError = never>(def: {
181
+ type: string;
182
+ input: TSchema;
183
+ retry?: RetryPolicy;
184
+ execution?: EffectExecution;
185
+ execute?: (input: InferDefinedSchema<TSchema>, ctx: EffectContext) => EffectHandlerResult<R, E, HandlerError>;
186
+ }): EffectDefinition<InferDefinedSchema<TSchema>, R, E>;
187
+ export declare function defineEffect<R = never, E extends EventInput = EventInput, HandlerError = never>(def: {
89
188
  type: string;
90
- input?: TSchema;
189
+ input?: undefined;
91
190
  retry?: RetryPolicy;
92
- execute: (input: TInput, ctx: EffectContext) => EffectHandlerResult<R, E>;
93
- }): EffectDefinition<TInput, R, E>;
191
+ execution?: EffectExecution;
192
+ execute?: (input: JsonValue, ctx: EffectContext) => EffectHandlerResult<R, E, HandlerError>;
193
+ }): EffectDefinition<JsonValue, R, E>;
94
194
  export declare function spawn(args: {
95
195
  childThreadId?: string;
96
196
  kind: string;
97
197
  definitionName: string;
198
+ definitionVersion?: string;
98
199
  input: JsonValue;
99
200
  }): SpawnEffect;
100
201
  export declare function wait(args: {
@@ -102,9 +203,20 @@ export declare function wait(args: {
102
203
  on: WaitCondition;
103
204
  tag?: JsonValue;
104
205
  }): WaitEffect;
105
- export declare function emit<E extends EventInput = EventInput>(event: E): EmitEffect<E>;
206
+ export interface WaitOutcome {
207
+ readonly result: JsonValue | null;
208
+ readonly error: string | null;
209
+ }
210
+ /**
211
+ * Reads the outcome of a satisfied wait. The wait fails when the satisfying event's payload has an
212
+ * `error`, or when the waiter put an `error` on the wait's tag (such as a timeout that loses a
213
+ * race). Otherwise the result is the payload's `output`, else the whole payload.
214
+ */
215
+ export declare function waitOutcome(satisfied: JsonValue | undefined, tag: JsonValue | undefined, fallback?: JsonValue | null): WaitOutcome;
216
+ export declare function emit<E extends EventInput = EventInput>(event: E, tag?: string): EmitEffect<E>;
106
217
  export declare function complete(output: JsonValue): CompleteEffect;
107
218
  export declare function fail(error: string): FailEffect;
108
- export declare function cancel(threadId: string): CancelEffect;
219
+ export declare function cancel(threadId: string, tag?: string): CancelEffect;
109
220
  export declare function invoke<TInput>(definition: EffectDefinition<TInput, any, any>, input: TInput, tag?: string): InvokeEffect;
110
221
  export declare function invoke(type: string, input: JsonValue, tag?: string): InvokeEffect;
222
+ export {};
@@ -1,6 +1,54 @@
1
- import { Effect, Predicate } from 'effect';
2
- import { validateInputEffect } from './schema.js';
1
+ import { Data, Effect, Predicate, Schema } from 'effect';
2
+ import { EventInputSchema } from './envelope.js';
3
+ import { DEFAULT_DEFINITION_VERSION } from './module.js';
4
+ import { validateInputEffect, } from './schema.js';
3
5
  import { asJson } from './types.js';
6
+ export const WaitConditionSchema = Schema.Union([
7
+ Schema.Struct({
8
+ type: Schema.Union([Schema.String, Schema.Array(Schema.String)]),
9
+ match: Schema.optional(Schema.Json),
10
+ }),
11
+ Schema.Struct({ timerAt: Schema.Finite }),
12
+ ]);
13
+ export const RuntimeEffectSchema = Schema.Union([
14
+ Schema.Struct({
15
+ type: Schema.Literal('runtime.spawn'),
16
+ childThreadId: Schema.String,
17
+ kind: Schema.String,
18
+ definitionName: Schema.String,
19
+ definitionVersion: Schema.String.pipe(Schema.withDecodingDefaultKey(Effect.succeed(DEFAULT_DEFINITION_VERSION))),
20
+ input: Schema.Json,
21
+ }),
22
+ Schema.Struct({
23
+ type: Schema.Literal('runtime.wait'),
24
+ waitId: Schema.String,
25
+ on: WaitConditionSchema,
26
+ tag: Schema.optional(Schema.Json),
27
+ }),
28
+ Schema.Struct({
29
+ type: Schema.Literal('runtime.emit'),
30
+ event: EventInputSchema,
31
+ tag: Schema.optional(Schema.String),
32
+ }),
33
+ Schema.Struct({
34
+ type: Schema.Literal('runtime.complete'),
35
+ output: Schema.Json,
36
+ }),
37
+ Schema.Struct({
38
+ type: Schema.Literal('runtime.fail'),
39
+ error: Schema.String,
40
+ }),
41
+ Schema.Struct({
42
+ type: Schema.Literal('runtime.cancel'),
43
+ threadId: Schema.String,
44
+ tag: Schema.optional(Schema.String),
45
+ }),
46
+ Schema.Struct({
47
+ type: Schema.String,
48
+ input: Schema.Json,
49
+ tag: Schema.optional(Schema.String),
50
+ }),
51
+ ]);
4
52
  export function isPrimitiveEffect(effect) {
5
53
  switch (effect.type) {
6
54
  case 'runtime.spawn':
@@ -27,35 +75,40 @@ export function withdrawnError(siblingEffectId) {
27
75
  export function isWithdrawnError(error) {
28
76
  return error.startsWith(WITHDRAWN_ERROR_PREFIX);
29
77
  }
78
+ export class EffectHandlerError extends Data.TaggedError('EffectHandlerError') {
79
+ constructor(cause) {
80
+ super({
81
+ cause,
82
+ message: cause instanceof Error ? cause.message : String(cause),
83
+ });
84
+ this.name = 'EffectHandlerError';
85
+ }
86
+ }
30
87
  function liftHandlerResult(result) {
31
88
  if (Effect.isEffect(result)) {
32
- return result;
89
+ return result.pipe(Effect.mapError((cause) => new EffectHandlerError(cause)));
33
90
  }
34
91
  if (result instanceof Promise) {
35
92
  return Effect.tryPromise({
36
93
  try: () => result,
37
- catch: (cause) => (cause instanceof Error ? cause : new Error(String(cause))),
94
+ catch: (cause) => new EffectHandlerError(cause),
38
95
  });
39
96
  }
40
97
  return Effect.succeed(result);
41
98
  }
42
- /**
43
- * Define an effect handler. A run's actor cell executes each effect at most
44
- * once while it is alive. If the cell dies mid-execution, the replacement
45
- * actor may re-run the handler (at-least-once across failover). Keep handlers
46
- * idempotent when they have external side effects.
47
- */
48
99
  export function defineEffect(def) {
49
100
  const schema = def.input;
50
- // SAFETY: execute validates raw JsonValue against the optional schema before the typed handler.
101
+ const handler = def.execute;
102
+ const noOutput = [];
51
103
  return {
52
104
  type: def.type,
53
105
  input: schema,
54
106
  retry: def.retry,
107
+ execution: def.execution,
108
+ hasHandler: handler !== undefined,
55
109
  execute: (raw, ctx) => Effect.gen(function* () {
56
110
  const input = schema ? yield* validateInputEffect(schema, raw) : raw;
57
- // SAFETY: input has been validated against schema or is unconstrained raw input
58
- return yield* liftHandlerResult(def.execute(input, ctx));
111
+ return handler ? yield* liftHandlerResult(handler(input, ctx)) : noOutput;
59
112
  }),
60
113
  };
61
114
  }
@@ -66,6 +119,7 @@ export function spawn(args) {
66
119
  childThreadId,
67
120
  kind: args.kind,
68
121
  definitionName: args.definitionName,
122
+ definitionVersion: args.definitionVersion ?? DEFAULT_DEFINITION_VERSION,
69
123
  input: args.input,
70
124
  };
71
125
  }
@@ -80,8 +134,24 @@ export function wait(args) {
80
134
  }
81
135
  return effect;
82
136
  }
83
- export function emit(event) {
84
- return { type: 'runtime.emit', event };
137
+ /**
138
+ * Reads the outcome of a satisfied wait. The wait fails when the satisfying event's payload has an
139
+ * `error`, or when the waiter put an `error` on the wait's tag (such as a timeout that loses a
140
+ * race). Otherwise the result is the payload's `output`, else the whole payload.
141
+ */
142
+ export function waitOutcome(satisfied, tag, fallback = null) {
143
+ const payload = Predicate.isObject(satisfied) && Predicate.isObject(satisfied.payload) ? satisfied.payload : {};
144
+ const tagError = Predicate.isObject(tag) && Predicate.isString(tag.error) ? tag.error : null;
145
+ const error = Predicate.isString(payload.error) ? payload.error : tagError;
146
+ if (error !== null) {
147
+ return { result: null, error };
148
+ }
149
+ const result = payload.output ?? (Predicate.isObject(satisfied) ? satisfied.payload : fallback);
150
+ // SAFETY: wait results are persisted JSON event payloads.
151
+ return { result: asJson(result) ?? null, error: null };
152
+ }
153
+ export function emit(event, tag) {
154
+ return tag === undefined ? { type: 'runtime.emit', event } : { type: 'runtime.emit', event, tag };
85
155
  }
86
156
  export function complete(output) {
87
157
  return { type: 'runtime.complete', output };
@@ -89,8 +159,10 @@ export function complete(output) {
89
159
  export function fail(error) {
90
160
  return { type: 'runtime.fail', error };
91
161
  }
92
- export function cancel(threadId) {
93
- return { type: 'runtime.cancel', threadId };
162
+ export function cancel(threadId, tag) {
163
+ return tag === undefined
164
+ ? { type: 'runtime.cancel', threadId }
165
+ : { type: 'runtime.cancel', threadId, tag };
94
166
  }
95
167
  export function invoke(typeOrDef, input, tag) {
96
168
  const type = Predicate.isString(typeOrDef) ? typeOrDef : typeOrDef.type;