@swirls/looms 0.0.1 → 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 (257) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/README.md +2 -0
  3. package/dist/actor/cell.d.ts +9 -4
  4. package/dist/actor/cell.js +7 -11
  5. package/dist/actor/host.d.ts +7 -3
  6. package/dist/actor/host.js +55 -32
  7. package/dist/actor/index.d.ts +1 -1
  8. package/dist/actor/index.js +1 -1
  9. package/dist/actor/routing.d.ts +12 -1
  10. package/dist/actor/routing.js +57 -51
  11. package/dist/agent/agent-session.d.ts +5 -0
  12. package/dist/agent/agent-session.js +270 -0
  13. package/dist/agent/definitions-store.d.ts +2 -3
  14. package/dist/agent/definitions-store.js +2 -4
  15. package/dist/agent/definitions.d.ts +46 -20
  16. package/dist/agent/definitions.js +36 -8
  17. package/dist/agent/effects.d.ts +359 -2
  18. package/dist/agent/effects.js +60 -34
  19. package/dist/agent/events.d.ts +175 -19
  20. package/dist/agent/events.js +45 -10
  21. package/dist/agent/index.d.ts +1 -0
  22. package/dist/agent/index.js +1 -0
  23. package/dist/agent/llm.d.ts +11 -2
  24. package/dist/agent/llm.js +13 -3
  25. package/dist/agent/module.d.ts +812 -78
  26. package/dist/agent/module.js +16 -4
  27. package/dist/agent/scope.d.ts +174 -18
  28. package/dist/agent/signals.d.ts +24 -1
  29. package/dist/agent/signals.js +77 -0
  30. package/dist/agent/threads.d.ts +3 -33
  31. package/dist/agent/threads.js +20 -25
  32. package/dist/agent/tool-schema.js +0 -1
  33. package/dist/agent/types.d.ts +49 -4
  34. package/dist/agent/types.js +5 -5
  35. package/dist/ai-vercel/index.d.ts +1 -0
  36. package/dist/ai-vercel/index.js +1 -0
  37. package/dist/ai-vercel/messages.js +22 -7
  38. package/dist/ai-vercel/vercel-evaluator.d.ts +23 -0
  39. package/dist/ai-vercel/vercel-evaluator.js +68 -0
  40. package/dist/ai-vercel/vercel-llm.js +64 -44
  41. package/dist/approval/effects.d.ts +89 -0
  42. package/dist/approval/module.d.ts +179 -29
  43. package/dist/approval/projections.d.ts +0 -14
  44. package/dist/approval/projections.js +20 -3
  45. package/dist/approval/signals.d.ts +2 -0
  46. package/dist/approval/signals.js +18 -2
  47. package/dist/cli/cli.js +2 -0
  48. package/dist/cli/commands.js +12 -16
  49. package/dist/client/client.d.ts +1195 -18
  50. package/dist/client/client.js +118 -120
  51. package/dist/client/index.d.ts +1 -1
  52. package/dist/client/index.js +1 -1
  53. package/dist/client/sse.d.ts +13 -0
  54. package/dist/client/sse.js +38 -44
  55. package/dist/cloudflare/alarm-scheduler.d.ts +12 -2
  56. package/dist/cloudflare/alarm-scheduler.js +34 -7
  57. package/dist/cloudflare/durable-object.d.ts +10 -2
  58. package/dist/cloudflare/durable-object.js +59 -28
  59. package/dist/cloudflare/event-store.d.ts +7 -2
  60. package/dist/cloudflare/event-store.js +8 -7
  61. package/dist/cloudflare/index.d.ts +2 -2
  62. package/dist/cloudflare/index.js +2 -2
  63. package/dist/cloudflare/router.js +13 -14
  64. package/dist/core/catalog.d.ts +3 -3
  65. package/dist/core/catalog.js +2 -5
  66. package/dist/core/coalesce.js +5 -3
  67. package/dist/core/compose.d.ts +2 -1
  68. package/dist/core/compose.js +18 -2
  69. package/dist/core/definition-store.d.ts +11 -0
  70. package/dist/core/definition-store.js +6 -0
  71. package/dist/core/effects.d.ts +127 -15
  72. package/dist/core/effects.js +89 -17
  73. package/dist/core/envelope.d.ts +27 -7
  74. package/dist/core/envelope.js +20 -6
  75. package/dist/core/event-codec.d.ts +4 -2
  76. package/dist/core/event-codec.js +10 -5
  77. package/dist/core/event-index.js +1 -3
  78. package/dist/core/fold.d.ts +3 -0
  79. package/dist/core/fold.js +171 -24
  80. package/dist/core/ids.d.ts +5 -0
  81. package/dist/core/ids.js +35 -2
  82. package/dist/core/index.d.ts +5 -1
  83. package/dist/core/index.js +5 -1
  84. package/dist/core/kind.d.ts +22 -0
  85. package/dist/core/kind.js +17 -0
  86. package/dist/core/limits.d.ts +34 -0
  87. package/dist/core/limits.js +46 -0
  88. package/dist/core/match.d.ts +1 -0
  89. package/dist/core/match.js +10 -1
  90. package/dist/core/module-scope.d.ts +22 -20
  91. package/dist/core/module-scope.js +38 -28
  92. package/dist/core/module.d.ts +16 -5
  93. package/dist/core/module.js +4 -0
  94. package/dist/core/projection.d.ts +13 -13
  95. package/dist/core/projection.js +9 -5
  96. package/dist/core/protocol.d.ts +68 -11
  97. package/dist/core/protocol.js +82 -11
  98. package/dist/core/replay.d.ts +312 -3
  99. package/dist/core/replay.js +11 -1
  100. package/dist/core/schema.d.ts +8 -19
  101. package/dist/core/schema.js +25 -80
  102. package/dist/core/serialize.js +2 -4
  103. package/dist/core/snapshot-store.d.ts +2 -1
  104. package/dist/core/snapshot-store.js +8 -2
  105. package/dist/core/snapshots.d.ts +5 -1
  106. package/dist/core/snapshots.js +10 -15
  107. package/dist/core/sqlite-store.js +56 -11
  108. package/dist/core/state.d.ts +142 -4
  109. package/dist/core/state.js +78 -0
  110. package/dist/core/store.d.ts +38 -3
  111. package/dist/core/store.js +77 -22
  112. package/dist/core/thread.d.ts +10 -9
  113. package/dist/core/types.d.ts +5 -8
  114. package/dist/core/types.js +28 -24
  115. package/dist/core/validate-event.js +14 -4
  116. package/dist/core/worker-callback.d.ts +17 -0
  117. package/dist/core/worker-callback.js +1 -0
  118. package/dist/debugger/components/event-stream.js +9 -10
  119. package/dist/debugger/events/protocol-summarize.js +26 -0
  120. package/dist/debugger/lib/event-meta.js +1 -2
  121. package/dist/evaluator/definitions-store.d.ts +11 -0
  122. package/dist/evaluator/definitions-store.js +8 -0
  123. package/dist/evaluator/definitions.d.ts +18 -0
  124. package/dist/evaluator/definitions.js +4 -0
  125. package/dist/evaluator/effects.d.ts +226 -0
  126. package/dist/evaluator/effects.js +132 -0
  127. package/dist/evaluator/evaluator.d.ts +24 -0
  128. package/dist/evaluator/evaluator.js +67 -0
  129. package/dist/evaluator/events.d.ts +59 -0
  130. package/dist/evaluator/events.js +21 -0
  131. package/dist/evaluator/index.d.ts +11 -0
  132. package/dist/evaluator/index.js +11 -0
  133. package/dist/evaluator/module.d.ts +606 -0
  134. package/dist/evaluator/module.js +25 -0
  135. package/dist/evaluator/projections.d.ts +50 -0
  136. package/dist/evaluator/projections.js +20 -0
  137. package/dist/evaluator/scope.d.ts +59 -0
  138. package/dist/evaluator/scope.js +7 -0
  139. package/dist/evaluator/signals.d.ts +3 -0
  140. package/dist/evaluator/signals.js +17 -0
  141. package/dist/evaluator/threads.d.ts +62 -0
  142. package/dist/evaluator/threads.js +117 -0
  143. package/dist/evaluator/types.d.ts +125 -0
  144. package/dist/evaluator/types.js +51 -0
  145. package/dist/projectors/delivery.d.ts +83 -0
  146. package/dist/projectors/delivery.js +182 -0
  147. package/dist/projectors/index-model.d.ts +1 -1
  148. package/dist/projectors/index-model.js +4 -6
  149. package/dist/projectors/index.d.ts +2 -0
  150. package/dist/projectors/index.js +2 -0
  151. package/dist/projectors/memory.d.ts +1 -1
  152. package/dist/projectors/memory.js +10 -8
  153. package/dist/projectors/postgres.js +44 -58
  154. package/dist/projectors/projector.d.ts +17 -0
  155. package/dist/projectors/projector.js +26 -1
  156. package/dist/projectors/sqlite-delivery.d.ts +4 -0
  157. package/dist/projectors/sqlite-delivery.js +147 -0
  158. package/dist/projectors/sqlite.js +23 -23
  159. package/dist/projectors/with-projectors.d.ts +7 -2
  160. package/dist/projectors/with-projectors.js +27 -28
  161. package/dist/react/notify.js +11 -8
  162. package/dist/react/react.d.ts +2 -3
  163. package/dist/react/react.js +14 -30
  164. package/dist/react/sse.d.ts +1 -1
  165. package/dist/react/sse.js +1 -1
  166. package/dist/react/store.d.ts +2 -2
  167. package/dist/react/store.js +109 -119
  168. package/dist/runtime/cancellation.d.ts +16 -0
  169. package/dist/runtime/cancellation.js +129 -0
  170. package/dist/runtime/deadline-transition-planner.d.ts +31 -0
  171. package/dist/runtime/deadline-transition-planner.js +139 -0
  172. package/dist/runtime/dispatch-effect.d.ts +3 -2
  173. package/dist/runtime/dispatch-effect.js +7 -13
  174. package/dist/runtime/errors.d.ts +51 -0
  175. package/dist/runtime/errors.js +39 -0
  176. package/dist/runtime/{runtime-helpers.d.ts → helpers.d.ts} +24 -4
  177. package/dist/runtime/{runtime-helpers.js → helpers.js} +56 -19
  178. package/dist/runtime/index.d.ts +3 -3
  179. package/dist/runtime/index.js +3 -3
  180. package/dist/runtime/ingress.d.ts +15 -0
  181. package/dist/runtime/ingress.js +42 -0
  182. package/dist/runtime/local-effect-executor.d.ts +21 -0
  183. package/dist/runtime/local-effect-executor.js +137 -0
  184. package/dist/runtime/looms.d.ts +22 -4
  185. package/dist/runtime/looms.js +54 -36
  186. package/dist/runtime/observer.d.ts +45 -0
  187. package/dist/runtime/observer.js +11 -0
  188. package/dist/runtime/repository.d.ts +55 -0
  189. package/dist/runtime/repository.js +278 -0
  190. package/dist/runtime/retry-policy.d.ts +16 -0
  191. package/dist/runtime/retry-policy.js +29 -0
  192. package/dist/runtime/run-execution-context.d.ts +30 -0
  193. package/dist/runtime/run-execution-context.js +56 -0
  194. package/dist/runtime/run-start.d.ts +4 -0
  195. package/dist/runtime/run-start.js +44 -0
  196. package/dist/runtime/runtime.d.ts +6 -87
  197. package/dist/runtime/runtime.js +233 -470
  198. package/dist/runtime/server/auth.d.ts +40 -0
  199. package/dist/runtime/server/auth.js +40 -0
  200. package/dist/runtime/server/fetch-handler.d.ts +14 -0
  201. package/dist/runtime/server/fetch-handler.js +51 -0
  202. package/dist/runtime/server/http.d.ts +37 -0
  203. package/dist/runtime/server/http.js +62 -0
  204. package/dist/runtime/server/route-table.d.ts +55 -0
  205. package/dist/runtime/server/route-table.js +74 -0
  206. package/dist/runtime/server/routes/effects.d.ts +3 -0
  207. package/dist/runtime/server/routes/effects.js +59 -0
  208. package/dist/runtime/server/routes/operations.d.ts +3 -0
  209. package/dist/runtime/server/routes/operations.js +14 -0
  210. package/dist/runtime/server/routes/runs.d.ts +3 -0
  211. package/dist/runtime/server/routes/runs.js +192 -0
  212. package/dist/runtime/server.d.ts +2 -8
  213. package/dist/runtime/server.js +4 -211
  214. package/dist/runtime/sse.d.ts +2 -1
  215. package/dist/runtime/sse.js +24 -17
  216. package/dist/runtime/types.d.ts +115 -0
  217. package/dist/runtime/types.js +1 -0
  218. package/dist/runtime/wake-engine.d.ts +20 -0
  219. package/dist/runtime/wake-engine.js +311 -0
  220. package/dist/runtime/wake-scheduler.d.ts +3 -4
  221. package/dist/runtime/wake-scheduler.js +25 -31
  222. package/dist/runtime/worker-callbacks.d.ts +16 -0
  223. package/dist/runtime/worker-callbacks.js +167 -0
  224. package/dist/runtime/worker-effect-dispatch.d.ts +36 -0
  225. package/dist/runtime/worker-effect-dispatch.js +126 -0
  226. package/dist/s2/config.d.ts +1 -1
  227. package/dist/s2/config.js +1 -1
  228. package/dist/s2/lite.js +70 -82
  229. package/dist/s2/pagination.d.ts +15 -0
  230. package/dist/s2/pagination.js +36 -0
  231. package/dist/s2/projector.js +6 -5
  232. package/dist/s2/snapshot-store.js +148 -163
  233. package/dist/s2/store.d.ts +3 -21
  234. package/dist/s2/store.js +71 -165
  235. package/dist/testing/index.d.ts +15 -0
  236. package/dist/testing/index.js +53 -16
  237. package/dist/workflow/definitions-store.d.ts +2 -3
  238. package/dist/workflow/definitions-store.js +2 -2
  239. package/dist/workflow/definitions.d.ts +78 -22
  240. package/dist/workflow/definitions.js +80 -14
  241. package/dist/workflow/effects.d.ts +359 -13
  242. package/dist/workflow/effects.js +214 -115
  243. package/dist/workflow/events.d.ts +149 -5
  244. package/dist/workflow/events.js +27 -5
  245. package/dist/workflow/graph.d.ts +56 -0
  246. package/dist/workflow/graph.js +207 -0
  247. package/dist/workflow/index.d.ts +1 -0
  248. package/dist/workflow/index.js +1 -0
  249. package/dist/workflow/module.d.ts +1047 -266
  250. package/dist/workflow/projections.d.ts +14 -12
  251. package/dist/workflow/projections.js +30 -8
  252. package/dist/workflow/scope.d.ts +146 -4
  253. package/dist/workflow/threads.d.ts +383 -155
  254. package/dist/workflow/threads.js +308 -110
  255. package/package.json +8 -2
  256. package/dist/runtime/events-api.d.ts +0 -4
  257. package/dist/runtime/events-api.js +0 -84
@@ -1,5 +1,5 @@
1
1
  import { Schema } from 'effect';
2
- import type { JsonValue } from './types';
2
+ import { type JsonValue } from './types';
3
3
  export declare const JsonValueSchema: Schema.Codec<Schema.Json, Schema.Json, never, never>;
4
4
  export declare const EventOriginSchema: Schema.Union<readonly [Schema.Struct<{
5
5
  readonly type: Schema.Literal<"thread">;
@@ -14,8 +14,8 @@ export type EventOrigin = Schema.Schema.Type<typeof EventOriginSchema>;
14
14
  export declare const EventEnvelopeSchema: Schema.Struct<{
15
15
  readonly id: Schema.String;
16
16
  readonly runId: Schema.String;
17
- readonly seq: Schema.Number;
18
- readonly ts: Schema.Number;
17
+ readonly seq: Schema.Finite;
18
+ readonly ts: Schema.Finite;
19
19
  readonly type: Schema.String;
20
20
  readonly payload: Schema.Codec<Schema.Json, Schema.Json, never, never>;
21
21
  readonly threadId: Schema.NullOr<Schema.String>;
@@ -35,6 +35,28 @@ export declare const EventEnvelopeSchema: Schema.Struct<{
35
35
  readonly ephemeral: Schema.optional<Schema.Boolean>;
36
36
  readonly idempotencyKey: Schema.optional<Schema.NullOr<Schema.String>>;
37
37
  }>;
38
+ export declare const EventInputSchema: Schema.Struct<{
39
+ readonly type: Schema.String;
40
+ readonly payload: Schema.Codec<Schema.Json, Schema.Json, never, never>;
41
+ readonly threadId: Schema.optional<Schema.NullOr<Schema.String>>;
42
+ readonly parentThreadId: Schema.optional<Schema.NullOr<Schema.String>>;
43
+ readonly causationId: Schema.optional<Schema.NullOr<Schema.String>>;
44
+ readonly correlationId: Schema.optional<Schema.NullOr<Schema.String>>;
45
+ readonly effectId: Schema.optional<Schema.NullOr<Schema.String>>;
46
+ readonly origin: Schema.optional<Schema.Union<readonly [Schema.Struct<{
47
+ readonly type: Schema.Literal<"thread">;
48
+ readonly threadId: Schema.String;
49
+ }>, Schema.Struct<{
50
+ readonly type: Schema.Literal<"external">;
51
+ readonly actorId: Schema.optional<Schema.String>;
52
+ }>, Schema.Struct<{
53
+ readonly type: Schema.Literal<"system">;
54
+ }>]>>;
55
+ readonly ephemeral: Schema.optional<Schema.Boolean>;
56
+ readonly id: Schema.optional<Schema.String>;
57
+ readonly ts: Schema.optional<Schema.Finite>;
58
+ readonly idempotencyKey: Schema.optional<Schema.NullOr<Schema.String>>;
59
+ }>;
38
60
  export type EventEnvelope<TType extends string = string, TPayload = JsonValue> = {
39
61
  id: string;
40
62
  runId: string;
@@ -71,9 +93,7 @@ export type AppendableEvent = Omit<EventEnvelope, 'seq'> & {
71
93
  };
72
94
  export declare function payloadAsJson(payload: JsonValue): JsonValue;
73
95
  export declare function withAssignedSeq(partial: AppendableEvent, runId: string, seq: number): EventEnvelope;
74
- export declare function createEvent<TType extends string>(runId: string, input: Omit<EventInput, 'type'> & {
75
- type: TType;
76
- }, options?: {
96
+ export declare function createEvent<TType extends string, TPayload = JsonValue>(runId: string, input: EventInput<TType, TPayload>, options?: {
77
97
  seq?: number;
78
- }): EventEnvelope<TType>;
98
+ }): EventEnvelope<TType, TPayload>;
79
99
  export declare function fromWireEvent(wire: Schema.Schema.Type<typeof EventEnvelopeSchema>): EventEnvelope;
@@ -1,5 +1,6 @@
1
- import { Schema } from 'effect';
1
+ import { DateTime, Schema } from 'effect';
2
2
  import { createEventId } from './ids.js';
3
+ import { cleanUndefined } from './types.js';
3
4
  export const JsonValueSchema = Schema.Json;
4
5
  export const EventOriginSchema = Schema.Union([
5
6
  Schema.Struct({
@@ -17,8 +18,8 @@ export const EventOriginSchema = Schema.Union([
17
18
  export const EventEnvelopeSchema = Schema.Struct({
18
19
  id: Schema.String,
19
20
  runId: Schema.String,
20
- seq: Schema.Number,
21
- ts: Schema.Number,
21
+ seq: Schema.Finite,
22
+ ts: Schema.Finite,
22
23
  type: Schema.String,
23
24
  payload: JsonValueSchema,
24
25
  threadId: Schema.NullOr(Schema.String),
@@ -30,9 +31,22 @@ export const EventEnvelopeSchema = Schema.Struct({
30
31
  ephemeral: Schema.optional(Schema.Boolean),
31
32
  idempotencyKey: Schema.optional(Schema.NullOr(Schema.String)),
32
33
  });
34
+ export const EventInputSchema = Schema.Struct({
35
+ type: Schema.String,
36
+ payload: JsonValueSchema,
37
+ threadId: Schema.optional(Schema.NullOr(Schema.String)),
38
+ parentThreadId: Schema.optional(Schema.NullOr(Schema.String)),
39
+ causationId: Schema.optional(Schema.NullOr(Schema.String)),
40
+ correlationId: Schema.optional(Schema.NullOr(Schema.String)),
41
+ effectId: Schema.optional(Schema.NullOr(Schema.String)),
42
+ origin: Schema.optional(EventOriginSchema),
43
+ ephemeral: Schema.optional(Schema.Boolean),
44
+ id: Schema.optional(Schema.String),
45
+ ts: Schema.optional(Schema.Finite),
46
+ idempotencyKey: Schema.optional(Schema.NullOr(Schema.String)),
47
+ });
33
48
  export function payloadAsJson(payload) {
34
- // SAFETY: round-trip through JSON yields JsonValue.
35
- return JSON.parse(JSON.stringify(payload));
49
+ return cleanUndefined(payload);
36
50
  }
37
51
  export function withAssignedSeq(partial, runId, seq) {
38
52
  return { ...partial, runId, seq };
@@ -44,7 +58,7 @@ export function createEvent(runId, input, options) {
44
58
  id: input.id ?? createEventId(),
45
59
  runId,
46
60
  seq: options?.seq ?? 0,
47
- ts: input.ts ?? Date.now(),
61
+ ts: input.ts ?? DateTime.toEpochMillis(DateTime.nowUnsafe()),
48
62
  type: input.type,
49
63
  payload: input.payload,
50
64
  threadId,
@@ -2,7 +2,9 @@ import type { AppendableEvent, EventEnvelope } from './envelope';
2
2
  import type { JsonValue } from './types';
3
3
  /** JSON-safe event envelope for SSE and pull responses. */
4
4
  export type EncodedLoomsEvent = EventEnvelope;
5
+ type EventWireInput = JsonValue | Partial<EncodedLoomsEvent>;
5
6
  export declare function encodeLoomsEvent(event: EventEnvelope): EncodedLoomsEvent;
6
7
  export declare function encodeAppendableEvent(event: AppendableEvent): EncodedLoomsEvent;
7
- export declare function decodeLoomsEvent(raw: JsonValue | EncodedLoomsEvent, runId: string): EventEnvelope;
8
- export declare function decodeAppendableEvent(raw: JsonValue | EncodedLoomsEvent, runId: string): AppendableEvent | undefined;
8
+ export declare function decodeLoomsEvent(raw: EventWireInput, runId: string): EventEnvelope;
9
+ export declare function decodeAppendableEvent(raw: EventWireInput, runId: string): AppendableEvent | undefined;
10
+ export {};
@@ -1,6 +1,7 @@
1
- import { Predicate } from 'effect';
1
+ import { DateTime, Predicate, Schema } from 'effect';
2
2
  import { createEvent, payloadAsJson } from './envelope.js';
3
3
  import { createEventId } from './ids.js';
4
+ const decodeJsonValue = Schema.decodeUnknownSync(Schema.Json);
4
5
  export function encodeLoomsEvent(event) {
5
6
  return {
6
7
  ...event,
@@ -12,7 +13,7 @@ export function encodeAppendableEvent(event) {
12
13
  id: event.id ?? createEventId(),
13
14
  runId: event.runId,
14
15
  seq: event.seq ?? 0,
15
- ts: event.ts ?? Date.now(),
16
+ ts: event.ts ?? DateTime.toEpochMillis(DateTime.nowUnsafe()),
16
17
  type: event.type,
17
18
  payload: payloadAsJson(event.payload),
18
19
  threadId: event.threadId ?? null,
@@ -25,6 +26,9 @@ export function encodeAppendableEvent(event) {
25
26
  };
26
27
  }
27
28
  function readOrigin(raw) {
29
+ if (!Predicate.isReadonlyObject(raw)) {
30
+ return { type: 'system' };
31
+ }
28
32
  if ('type' in raw && raw.type === 'thread') {
29
33
  const threadId = 'threadId' in raw && Predicate.isString(raw.threadId) ? raw.threadId : '';
30
34
  return { type: 'thread', threadId };
@@ -41,10 +45,11 @@ export function decodeLoomsEvent(raw, runId) {
41
45
  }
42
46
  if ('type' in raw && Predicate.isString(raw.type)) {
43
47
  const rawPayload = 'payload' in raw ? raw.payload : {};
44
- // SAFETY: payload is JSON-compatible.
45
- const payload = rawPayload;
48
+ const payload = decodeJsonValue(rawPayload);
46
49
  const id = 'id' in raw && Predicate.isString(raw.id) ? raw.id : createEventId();
47
- const ts = 'ts' in raw && Predicate.isNumber(raw.ts) ? raw.ts : Date.now();
50
+ const ts = 'ts' in raw && Predicate.isNumber(raw.ts)
51
+ ? raw.ts
52
+ : DateTime.toEpochMillis(DateTime.nowUnsafe());
48
53
  const seq = 'seq' in raw && Predicate.isNumber(raw.seq) ? raw.seq : 0;
49
54
  const ephemeral = 'ephemeral' in raw && Predicate.isBoolean(raw.ephemeral) ? raw.ephemeral : false;
50
55
  const itemRunId = 'runId' in raw && Predicate.isString(raw.runId) ? raw.runId : runId;
@@ -1,4 +1,3 @@
1
- const EMPTY_EVENTS = Object.freeze([]);
2
1
  /** Thread bucket key used by run-tree counts (`threadId` or `'run'` for run-scoped). */
3
2
  export function eventThreadKey(event) {
4
3
  return event.threadId ?? 'run';
@@ -60,8 +59,7 @@ export class EventIndex {
60
59
  }
61
60
  getByThread(threadId) {
62
61
  this.publish();
63
- // SAFETY: EMPTY_EVENTS is an empty frozen array compatible with any TEvent[].
64
- return this.byThreadSnapshots.get(threadId) ?? EMPTY_EVENTS;
62
+ return this.byThreadSnapshots.get(threadId) ?? [];
65
63
  }
66
64
  getBySeq(seq) {
67
65
  return this.bySeqMap.get(seq);
@@ -1,10 +1,13 @@
1
1
  import { Stream, type Effect } from 'effect';
2
+ import { type RuntimeEffect } from './effects';
2
3
  import type { EventEnvelope } from './envelope';
3
4
  import type { RunState } from './state';
4
5
  import { type ThreadDefinition } from './thread';
5
6
  export interface FoldRegistry {
6
7
  readonly threads: ReadonlyMap<string, ThreadDefinition>;
7
8
  }
9
+ /** The id an effect keeps across folds when it names itself by tag, wait id, or child thread. */
10
+ export declare function stableEffectId(threadId: string, effect: RuntimeEffect): string | undefined;
8
11
  export declare function foldEvent(state: RunState, event: EventEnvelope, registry: FoldRegistry): RunState;
9
12
  export declare function foldRun(events: readonly EventEnvelope[], registry: FoldRegistry, options?: {
10
13
  runId?: string;
package/dist/core/fold.js CHANGED
@@ -1,8 +1,10 @@
1
- import { Predicate, Stream } from 'effect';
2
- import { isWithdrawnError } from './effects.js';
1
+ import { Option, Predicate, Schema, Stream } from 'effect';
2
+ import { isWithdrawnError, WaitConditionSchema } from './effects.js';
3
3
  import { createEffectId } from './ids.js';
4
+ import { DEFAULT_DEFINITION_VERSION } from './module.js';
4
5
  import { emptyRunState } from './state.js';
5
6
  import { isTerminalStatus } from './thread.js';
7
+ const decodeWaitCondition = Schema.decodeUnknownOption(WaitConditionSchema);
6
8
  function payloadObject(event) {
7
9
  const payload = event.payload;
8
10
  if (payload === null || Array.isArray(payload)) {
@@ -22,9 +24,11 @@ function cloneState(state) {
22
24
  runId: state.runId,
23
25
  status: state.status,
24
26
  rootThreadId: state.rootThreadId,
27
+ startIdentity: state.startIdentity,
25
28
  threads: { ...state.threads },
26
29
  waits: { ...state.waits },
27
30
  outstandingEffects: [...state.outstandingEffects],
31
+ effectExecutions: { ...state.effectExecutions },
28
32
  completedEffectIds: state.completedEffectIds ? [...state.completedEffectIds] : [],
29
33
  processedIdempotencyKeys: state.processedIdempotencyKeys
30
34
  ? [...state.processedIdempotencyKeys]
@@ -33,9 +37,11 @@ function cloneState(state) {
33
37
  }
34
38
  function completeEffect(state, effectId) {
35
39
  const completed = state.completedEffectIds ?? [];
40
+ const { [effectId]: _completedExecution, ...effectExecutions } = state.effectExecutions ?? {};
36
41
  return {
37
42
  ...state,
38
43
  outstandingEffects: state.outstandingEffects.filter((item) => item.effectId !== effectId),
44
+ effectExecutions,
39
45
  completedEffectIds: completed.includes(effectId) ? completed : [...completed, effectId],
40
46
  };
41
47
  }
@@ -75,18 +81,41 @@ function applyProtocol(state, event, registry) {
75
81
  const payload = payloadObject(event);
76
82
  switch (event.type) {
77
83
  case 'runtime.run.started': {
78
- const rootThreadId = readString(payload, 'rootThreadId') ?? null;
79
- return { ...state, runId: event.runId, rootThreadId, status: 'running' };
84
+ const rootThreadId = readString(payload, 'rootThreadId');
85
+ const kind = readString(payload, 'kind');
86
+ const definitionName = readString(payload, 'definitionName');
87
+ const definitionVersion = readString(payload, 'definitionVersion') ?? DEFAULT_DEFINITION_VERSION;
88
+ const startIdentity = rootThreadId && kind && definitionName && definitionVersion
89
+ ? {
90
+ kind,
91
+ definitionName,
92
+ definitionVersion,
93
+ input: payload.input ?? null,
94
+ requestedThreadId: readString(payload, 'requestedThreadId') ?? null,
95
+ idempotencyKey: event.idempotencyKey ?? null,
96
+ rootThreadId,
97
+ }
98
+ : null;
99
+ return {
100
+ ...state,
101
+ runId: event.runId,
102
+ rootThreadId: rootThreadId ?? null,
103
+ startIdentity,
104
+ status: 'running',
105
+ };
80
106
  }
81
107
  case 'runtime.run.completed': {
82
108
  const error = payload.error;
83
109
  const status = Predicate.isString(error) && error.length > 0 ? 'failed' : 'completed';
84
110
  return { ...state, status };
85
111
  }
112
+ case 'runtime.run.cancelled':
113
+ return { ...state, status: 'cancelled' };
86
114
  case 'runtime.thread.started': {
87
115
  const threadId = readString(payload, 'threadId') ?? event.threadId;
88
116
  const kind = readString(payload, 'kind');
89
117
  const definitionName = readString(payload, 'definitionName');
118
+ const definitionVersion = readString(payload, 'definitionVersion') ?? DEFAULT_DEFINITION_VERSION;
90
119
  if (!threadId || !kind || !definitionName) {
91
120
  return state;
92
121
  }
@@ -98,6 +127,7 @@ function applyProtocol(state, event, registry) {
98
127
  threadId,
99
128
  kind,
100
129
  definitionName,
130
+ definitionVersion,
101
131
  parentThreadId,
102
132
  status: definition ? 'running' : 'failed',
103
133
  input,
@@ -109,6 +139,7 @@ function applyProtocol(state, event, registry) {
109
139
  threadId,
110
140
  parentThreadId,
111
141
  definitionName,
142
+ definitionVersion,
112
143
  input,
113
144
  })
114
145
  : {},
@@ -156,7 +187,7 @@ function applyProtocol(state, event, registry) {
156
187
  return state;
157
188
  }
158
189
  const existing = state.threads[threadId];
159
- if (!existing) {
190
+ if (!existing || isTerminalStatus(existing.status)) {
160
191
  return state;
161
192
  }
162
193
  return dropThreadWork(putThread(state, {
@@ -165,6 +196,111 @@ function applyProtocol(state, event, registry) {
165
196
  error: readString(payload, 'reason') ?? existing.error,
166
197
  }), threadId);
167
198
  }
199
+ case 'runtime.effect.attempt.started': {
200
+ const effectId = readString(payload, 'effectId');
201
+ const attempt = payload.attempt;
202
+ if (!effectId || !Predicate.isNumber(attempt)) {
203
+ return state;
204
+ }
205
+ return {
206
+ ...state,
207
+ effectExecutions: {
208
+ ...state.effectExecutions,
209
+ [effectId]: {
210
+ effectId,
211
+ attempt,
212
+ status: 'running',
213
+ nextAttemptAt: null,
214
+ deadlineAt: null,
215
+ startedAt: event.ts,
216
+ lastHeartbeatAt: null,
217
+ lastError: null,
218
+ },
219
+ },
220
+ };
221
+ }
222
+ case 'runtime.effect.queued':
223
+ case 'runtime.effect.dispatched':
224
+ case 'runtime.effect.worker.started':
225
+ case 'runtime.effect.heartbeat':
226
+ case 'runtime.effect.cancel.requested':
227
+ case 'runtime.effect.timed_out':
228
+ case 'runtime.effect.ambiguous': {
229
+ const effectId = readString(payload, 'effectId');
230
+ const attempt = payload.attempt;
231
+ if (!effectId || !Predicate.isNumber(attempt)) {
232
+ return state;
233
+ }
234
+ const status = event.type === 'runtime.effect.queued'
235
+ ? 'queued'
236
+ : event.type === 'runtime.effect.dispatched'
237
+ ? 'dispatched'
238
+ : event.type === 'runtime.effect.worker.started'
239
+ ? 'started'
240
+ : event.type === 'runtime.effect.heartbeat'
241
+ ? 'heartbeat'
242
+ : event.type === 'runtime.effect.cancel.requested'
243
+ ? 'cancel_requested'
244
+ : event.type === 'runtime.effect.timed_out'
245
+ ? 'timed_out'
246
+ : 'ambiguous';
247
+ const deadlineAt = payload.deadlineAt;
248
+ const heartbeatAt = payload.heartbeatAt;
249
+ const previous = state.effectExecutions[effectId];
250
+ const sameAttempt = previous?.attempt === attempt;
251
+ return {
252
+ ...state,
253
+ effectExecutions: {
254
+ ...state.effectExecutions,
255
+ [effectId]: {
256
+ effectId,
257
+ attempt,
258
+ status,
259
+ nextAttemptAt: null,
260
+ deadlineAt: Predicate.isNumber(deadlineAt) ? deadlineAt : null,
261
+ startedAt: event.type === 'runtime.effect.worker.started'
262
+ ? event.ts
263
+ : sameAttempt
264
+ ? previous.startedAt
265
+ : null,
266
+ lastHeartbeatAt: Predicate.isNumber(heartbeatAt)
267
+ ? heartbeatAt
268
+ : sameAttempt
269
+ ? previous.lastHeartbeatAt
270
+ : null,
271
+ lastError: readString(payload, 'error') ?? null,
272
+ },
273
+ },
274
+ };
275
+ }
276
+ case 'runtime.effect.cancelled': {
277
+ const effectId = readString(payload, 'effectId');
278
+ return effectId ? completeEffect(state, effectId) : state;
279
+ }
280
+ case 'runtime.effect.retry.scheduled': {
281
+ const effectId = readString(payload, 'effectId');
282
+ const attempt = payload.attempt;
283
+ const nextAttemptAt = payload.nextAttemptAt;
284
+ if (!effectId || !Predicate.isNumber(attempt) || !Predicate.isNumber(nextAttemptAt)) {
285
+ return state;
286
+ }
287
+ return {
288
+ ...state,
289
+ effectExecutions: {
290
+ ...state.effectExecutions,
291
+ [effectId]: {
292
+ effectId,
293
+ attempt,
294
+ status: 'retry_wait',
295
+ nextAttemptAt,
296
+ deadlineAt: nextAttemptAt,
297
+ startedAt: null,
298
+ lastHeartbeatAt: null,
299
+ lastError: readString(payload, 'error') ?? 'effect failed',
300
+ },
301
+ },
302
+ };
303
+ }
168
304
  case 'runtime.wait.registered': {
169
305
  const waitId = readString(payload, 'waitId');
170
306
  const threadId = readString(payload, 'threadId') ?? event.threadId;
@@ -172,11 +308,14 @@ function applyProtocol(state, event, registry) {
172
308
  if (!waitId || !threadId || !on || !Predicate.isObject(on)) {
173
309
  return state;
174
310
  }
175
- // SAFETY: wait.registered payload.on is a WaitCondition written by the runtime.
311
+ const waitCondition = decodeWaitCondition(on);
312
+ if (Option.isNone(waitCondition)) {
313
+ return state;
314
+ }
176
315
  const record = {
177
316
  waitId,
178
317
  threadId,
179
- on: on,
318
+ on: waitCondition.value,
180
319
  };
181
320
  if (payload.tag !== undefined) {
182
321
  record.tag = payload.tag;
@@ -195,6 +334,16 @@ function applyProtocol(state, event, registry) {
195
334
  }
196
335
  const existingWait = state.waits[waitId];
197
336
  let next = removeWait(state, waitId);
337
+ const existingTag = Predicate.isObject(existingWait?.tag) ? existingWait.tag : undefined;
338
+ const raceId = existingTag && Predicate.isString(existingTag.raceId) ? existingTag.raceId : undefined;
339
+ if (raceId) {
340
+ for (const sibling of Object.values(next.waits)) {
341
+ const siblingTag = Predicate.isObject(sibling.tag) ? sibling.tag : undefined;
342
+ if (siblingTag?.raceId === raceId) {
343
+ next = removeWait(next, sibling.waitId);
344
+ }
345
+ }
346
+ }
198
347
  const threadId = existingWait?.threadId ?? event.threadId;
199
348
  if (!threadId) {
200
349
  return next;
@@ -206,16 +355,19 @@ function applyProtocol(state, event, registry) {
206
355
  return next;
207
356
  }
208
357
  case 'runtime.snapshot.taken':
358
+ case 'runtime.thread.cancel.requested':
209
359
  case 'runtime.timer.set':
210
360
  case 'runtime.timer.fired':
211
361
  case 'runtime.effect.failed':
362
+ case 'runtime.effect.completed':
212
363
  case 'runtime.signal.received':
213
364
  return state;
214
365
  default:
215
366
  return state;
216
367
  }
217
368
  }
218
- function deriveEffectId(threadId, causingSeq, index, effect, existingId) {
369
+ /** The id an effect keeps across folds when it names itself by tag, wait id, or child thread. */
370
+ export function stableEffectId(threadId, effect) {
219
371
  if ('tag' in effect && Predicate.isString(effect.tag)) {
220
372
  return createEffectId(threadId, effect.tag);
221
373
  }
@@ -225,29 +377,21 @@ function deriveEffectId(threadId, causingSeq, index, effect, existingId) {
225
377
  if ('childThreadId' in effect && Predicate.isString(effect.childThreadId)) {
226
378
  return createEffectId(threadId, `spawn_${effect.childThreadId}`);
227
379
  }
228
- if (existingId) {
229
- return existingId;
380
+ if (effect.type === 'runtime.cancel' &&
381
+ 'threadId' in effect &&
382
+ Predicate.isString(effect.threadId)) {
383
+ return createEffectId(threadId, `cancel_${effect.threadId}`);
230
384
  }
231
- return createEffectId(threadId, causingSeq, index);
385
+ return undefined;
232
386
  }
233
387
  function mapEffects(threadId, event, effects, completed, existingForThread) {
234
388
  const remainingExisting = [...existingForThread];
235
389
  return effects
236
390
  .map((effect, index) => {
237
- const matchIndex = remainingExisting.findIndex((item) => {
238
- if ('tag' in effect && Predicate.isString(effect.tag)) {
239
- return item.effectId === createEffectId(threadId, effect.tag);
240
- }
241
- if ('waitId' in effect && Predicate.isString(effect.waitId)) {
242
- return item.effectId === createEffectId(threadId, effect.waitId);
243
- }
244
- if ('childThreadId' in effect && Predicate.isString(effect.childThreadId)) {
245
- return item.effectId === createEffectId(threadId, `spawn_${effect.childThreadId}`);
246
- }
247
- return item.effect.type === effect.type;
248
- });
391
+ const stableId = stableEffectId(threadId, effect);
392
+ const matchIndex = remainingExisting.findIndex((item) => stableId === undefined ? item.effect.type === effect.type : item.effectId === stableId);
249
393
  const existing = matchIndex >= 0 ? remainingExisting.splice(matchIndex, 1)[0] : undefined;
250
- const effectId = deriveEffectId(threadId, event.seq, index, effect, existing?.effectId);
394
+ const effectId = stableId ?? existing?.effectId ?? createEffectId(threadId, event.seq, index);
251
395
  return (existing ?? {
252
396
  effectId,
253
397
  threadId,
@@ -285,6 +429,9 @@ function deliver(state, event, registry) {
285
429
  if (!record) {
286
430
  return state;
287
431
  }
432
+ if (isTerminalStatus(record.status)) {
433
+ return state;
434
+ }
288
435
  const definition = registry.threads.get(record.kind);
289
436
  if (!definition) {
290
437
  return state;
@@ -1,6 +1,11 @@
1
1
  export declare function createEventId(): string;
2
2
  export declare function createRunId(): string;
3
3
  export declare function createThreadId(): string;
4
+ /**
5
+ * A UUID-shaped id derived only from `parts`, so a reducer that replays the same events names the same
6
+ * child thread every time.
7
+ */
8
+ export declare function deterministicThreadId(...parts: readonly (string | number)[]): string;
4
9
  export declare function createWaitId(scope?: string, tag?: string | number): string;
5
10
  export declare function createEffectId(threadId: string, causingSeqOrTag: number | string, index?: number): string;
6
11
  export declare function parseEffectId(effectId: string): {
package/dist/core/ids.js CHANGED
@@ -1,8 +1,12 @@
1
- import { Predicate } from 'effect';
1
+ import { DateTime, Predicate } from 'effect';
2
2
  let counter = 0;
3
3
  function token() {
4
4
  counter += 1;
5
- return `${Date.now().toString(36)}_${counter.toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
5
+ const timestamp = DateTime.toEpochMillis(DateTime.nowUnsafe());
6
+ // Synchronous ID factories need process-independent entropy without an Effect runtime.
7
+ // oxlint-disable-next-line effecttsgo/crypto-random-uuid
8
+ const entropy = globalThis.crypto.randomUUID().replaceAll('-', '');
9
+ return `${timestamp.toString(36)}_${counter.toString(36)}_${entropy}`;
6
10
  }
7
11
  export function createEventId() {
8
12
  return `evt_${token()}`;
@@ -13,6 +17,35 @@ export function createRunId() {
13
17
  export function createThreadId() {
14
18
  return `thr_${token()}`;
15
19
  }
20
+ function fnv1a32(value, seed) {
21
+ let hash = seed >>> 0;
22
+ for (let index = 0; index < value.length; index++) {
23
+ hash ^= value.charCodeAt(index);
24
+ hash = Math.imul(hash, 0x01000193);
25
+ }
26
+ return (hash >>> 0).toString(16).padStart(8, '0');
27
+ }
28
+ /**
29
+ * A UUID-shaped id derived only from `parts`, so a reducer that replays the same events names the same
30
+ * child thread every time.
31
+ */
32
+ export function deterministicThreadId(...parts) {
33
+ const source = parts.join(':');
34
+ let reversed = '';
35
+ for (let index = source.length - 1; index >= 0; index--) {
36
+ reversed += source.charAt(index);
37
+ }
38
+ const hex = [
39
+ fnv1a32(source, 0x811c9dc5),
40
+ fnv1a32(source, 0x9e3779b9),
41
+ fnv1a32(reversed, 0x85ebca6b),
42
+ fnv1a32(reversed, 0xc2b2ae35),
43
+ ].join('');
44
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-5${hex.slice(13, 16)}-${((Number.parseInt(hex.slice(16, 18), 16) & 0x3f) |
45
+ 0x80)
46
+ .toString(16)
47
+ .padStart(2, '0')}${hex.slice(18, 20)}-${hex.slice(20)}`;
48
+ }
16
49
  export function createWaitId(scope, tag) {
17
50
  if (scope !== undefined && tag !== undefined) {
18
51
  return `wait_${scope}_${tag}`;
@@ -1,6 +1,7 @@
1
1
  export type * from './types';
2
- export { asJson, cleanUndefined, fromJsonStruct, isJsonNumber, isJsonObject, isJsonString, } from './types';
2
+ export { asJson, cleanUndefined, isJsonNumber, isJsonObject, isJsonString, stringifyJson, stringifyJsonPretty, } from './types';
3
3
  export * from './ids';
4
+ export * from './worker-callback';
4
5
  export * from './schema';
5
6
  export * from './validate-event';
6
7
  export * from './envelope';
@@ -14,6 +15,7 @@ export * from './fold';
14
15
  export * from './replay';
15
16
  export * from './projection';
16
17
  export * from './module';
18
+ export * from './kind';
17
19
  export * from './module-scope';
18
20
  export * from './compose';
19
21
  export * from './store';
@@ -24,3 +26,5 @@ export * from './event-codec';
24
26
  export * from './serialize';
25
27
  export * from './coalesce';
26
28
  export * from './event-index';
29
+ export * from './limits';
30
+ export * from './definition-store';
@@ -1,5 +1,6 @@
1
- export { asJson, cleanUndefined, fromJsonStruct, isJsonNumber, isJsonObject, isJsonString, } from './types.js';
1
+ export { asJson, cleanUndefined, isJsonNumber, isJsonObject, isJsonString, stringifyJson, stringifyJsonPretty, } from './types.js';
2
2
  export * from './ids.js';
3
+ export * from './worker-callback.js';
3
4
  export * from './schema.js';
4
5
  export * from './validate-event.js';
5
6
  export * from './envelope.js';
@@ -13,6 +14,7 @@ export * from './fold.js';
13
14
  export * from './replay.js';
14
15
  export * from './projection.js';
15
16
  export * from './module.js';
17
+ export * from './kind.js';
16
18
  export * from './module-scope.js';
17
19
  export * from './compose.js';
18
20
  export * from './store.js';
@@ -23,3 +25,5 @@ export * from './event-codec.js';
23
25
  export * from './serialize.js';
24
26
  export * from './coalesce.js';
25
27
  export * from './event-index.js';
28
+ export * from './limits.js';
29
+ export * from './definition-store.js';
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Stamp a startable `{ kind, name }` definition.
3
+ *
4
+ * `defineAgent` and `defineWorkflow` are specialized helpers for those kinds.
5
+ * Custom thread types should use `createKind` / `defineKind` instead of
6
+ * assembling the object by hand.
7
+ */
8
+ export declare function defineKind<TKind extends string, TDef extends {
9
+ readonly name: string;
10
+ }>(kind: TKind, def: TDef): TDef & {
11
+ readonly kind: TKind;
12
+ };
13
+ export interface KindBuilder<TKind extends string> {
14
+ readonly kind: TKind;
15
+ define<TDef extends {
16
+ readonly name: string;
17
+ }>(def: TDef): TDef & {
18
+ readonly kind: TKind;
19
+ };
20
+ }
21
+ /** Bind `define` to one kind so each custom type has a builder, like `defineAgent`. */
22
+ export declare function createKind<TKind extends string>(kind: TKind): KindBuilder<TKind>;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Stamp a startable `{ kind, name }` definition.
3
+ *
4
+ * `defineAgent` and `defineWorkflow` are specialized helpers for those kinds.
5
+ * Custom thread types should use `createKind` / `defineKind` instead of
6
+ * assembling the object by hand.
7
+ */
8
+ export function defineKind(kind, def) {
9
+ return { ...def, kind };
10
+ }
11
+ /** Bind `define` to one kind so each custom type has a builder, like `defineAgent`. */
12
+ export function createKind(kind) {
13
+ return {
14
+ kind,
15
+ define: (def) => defineKind(kind, def),
16
+ };
17
+ }