@swirls/looms 0.4.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 (241) hide show
  1. package/CHANGELOG.md +55 -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/definitions-store.d.ts +1 -1
  11. package/dist/agent/definitions-store.js +1 -1
  12. package/dist/agent/definitions.d.ts +48 -0
  13. package/dist/agent/definitions.js +44 -0
  14. package/dist/agent/effects.d.ts +184 -0
  15. package/dist/agent/effects.js +172 -40
  16. package/dist/agent/events.d.ts +89 -0
  17. package/dist/agent/events.js +25 -0
  18. package/dist/agent/module.d.ts +412 -0
  19. package/dist/agent/module.js +5 -1
  20. package/dist/agent/scope.d.ts +88 -0
  21. package/dist/agent/threads.js +39 -3
  22. package/dist/agent/types.d.ts +4 -0
  23. package/dist/approval/effects.d.ts +40 -0
  24. package/dist/approval/module.d.ts +80 -0
  25. package/dist/client/client.d.ts +468 -119
  26. package/dist/client/client.js +214 -21
  27. package/dist/client/index.d.ts +1 -1
  28. package/dist/client/sse.d.ts +1 -1
  29. package/dist/client/sse.js +2 -2
  30. package/dist/cloudflare/durable-object.d.ts +66 -2
  31. package/dist/cloudflare/durable-object.js +96 -1
  32. package/dist/cloudflare/event-store.d.ts +7 -3
  33. package/dist/cloudflare/event-store.js +16 -3
  34. package/dist/cloudflare/index.d.ts +7 -1
  35. package/dist/cloudflare/index.js +7 -1
  36. package/dist/cloudflare/memory-queue.d.ts +18 -0
  37. package/dist/cloudflare/memory-queue.js +53 -0
  38. package/dist/cloudflare/queue-consumer.d.ts +52 -0
  39. package/dist/cloudflare/queue-consumer.js +102 -0
  40. package/dist/cloudflare/queue-message.d.ts +31 -0
  41. package/dist/cloudflare/queue-message.js +33 -0
  42. package/dist/cloudflare/queue-pull.d.ts +62 -0
  43. package/dist/cloudflare/queue-pull.js +130 -0
  44. package/dist/cloudflare/queue-worker.d.ts +39 -0
  45. package/dist/cloudflare/queue-worker.js +62 -0
  46. package/dist/cloudflare/r2-blob-store.d.ts +33 -0
  47. package/dist/cloudflare/r2-blob-store.js +60 -0
  48. package/dist/cloudflare/router.d.ts +4 -1
  49. package/dist/cloudflare/router.js +8 -3
  50. package/dist/cloudflare/websocket.d.ts +27 -0
  51. package/dist/cloudflare/websocket.js +215 -0
  52. package/dist/core/blob-offload.d.ts +21 -0
  53. package/dist/core/blob-offload.js +143 -0
  54. package/dist/core/blob-store.d.ts +45 -0
  55. package/dist/core/blob-store.js +164 -0
  56. package/dist/core/blobs.d.ts +37 -0
  57. package/dist/core/blobs.js +160 -0
  58. package/dist/core/definition-resolver.d.ts +40 -0
  59. package/dist/core/definition-resolver.js +129 -0
  60. package/dist/core/definition-store.d.ts +6 -0
  61. package/dist/core/definition-store.js +37 -1
  62. package/dist/core/effects.d.ts +86 -3
  63. package/dist/core/effects.js +99 -3
  64. package/dist/core/envelope.d.ts +9 -0
  65. package/dist/core/envelope.js +4 -0
  66. package/dist/core/event-codec.js +3 -0
  67. package/dist/core/fold.js +83 -1
  68. package/dist/core/ids.d.ts +2 -0
  69. package/dist/core/ids.js +4 -0
  70. package/dist/core/index.d.ts +8 -0
  71. package/dist/core/index.js +8 -0
  72. package/dist/core/launcher.d.ts +97 -0
  73. package/dist/core/launcher.js +67 -0
  74. package/dist/core/module-scope.d.ts +7 -3
  75. package/dist/core/module-scope.js +3 -0
  76. package/dist/core/module.d.ts +10 -0
  77. package/dist/core/projection.js +4 -0
  78. package/dist/core/protocol.d.ts +36 -1
  79. package/dist/core/protocol.js +35 -0
  80. package/dist/core/published.d.ts +2 -0
  81. package/dist/core/published.js +4 -1
  82. package/dist/core/replay.d.ts +96 -0
  83. package/dist/core/run-context.d.ts +33 -0
  84. package/dist/core/run-context.js +23 -0
  85. package/dist/core/sqlite-store.js +57 -5
  86. package/dist/core/state.d.ts +60 -0
  87. package/dist/core/state.js +19 -0
  88. package/dist/core/store.d.ts +6 -0
  89. package/dist/core/store.js +7 -0
  90. package/dist/core/subscription.d.ts +97 -0
  91. package/dist/core/subscription.js +224 -0
  92. package/dist/core/thread.d.ts +7 -0
  93. package/dist/core/thread.js +1 -0
  94. package/dist/core/trace.d.ts +21 -0
  95. package/dist/core/trace.js +45 -0
  96. package/dist/core/worker-callback.d.ts +25 -1
  97. package/dist/debugger/app/assets/{index-CgDzWJg7.css → index-B7GdT1UA.css} +1 -1
  98. package/dist/debugger/app/assets/index-C31SnCuN.js +22 -0
  99. package/dist/debugger/app/assets/routes-DPA3O5AF.js +47 -0
  100. package/dist/debugger/app/index.html +2 -2
  101. package/dist/debugger/components/event-inspector.d.ts +3 -1
  102. package/dist/debugger/components/event-inspector.js +25 -4
  103. package/dist/debugger/components/event-stream.d.ts +4 -1
  104. package/dist/debugger/components/event-stream.js +2 -2
  105. package/dist/debugger/components/run-tree.d.ts +5 -2
  106. package/dist/debugger/components/run-tree.js +10 -4
  107. package/dist/debugger/context.d.ts +8 -1
  108. package/dist/debugger/context.js +15 -3
  109. package/dist/debugger/events/protocol-summarize.js +18 -3
  110. package/dist/debugger/index.d.ts +1 -1
  111. package/dist/debugger/index.js +1 -1
  112. package/dist/debugger/shell/debugger-shell.js +1 -1
  113. package/dist/debugger/shell/events-pane.js +4 -3
  114. package/dist/debugger/styles.css +1 -0
  115. package/dist/evaluator/definitions-store.d.ts +3 -4
  116. package/dist/evaluator/definitions-store.js +3 -3
  117. package/dist/evaluator/definitions.d.ts +4 -1
  118. package/dist/evaluator/definitions.js +5 -2
  119. package/dist/evaluator/effects.d.ts +74 -33
  120. package/dist/evaluator/effects.js +6 -2
  121. package/dist/evaluator/module.d.ts +150 -66
  122. package/dist/evaluator/module.js +7 -3
  123. package/dist/evaluator/threads.d.ts +2 -0
  124. package/dist/evaluator/threads.js +4 -0
  125. package/dist/projectors/event-log.d.ts +87 -0
  126. package/dist/projectors/event-log.js +396 -0
  127. package/dist/projectors/index-model.d.ts +12 -0
  128. package/dist/projectors/index-model.js +12 -3
  129. package/dist/projectors/memory.js +7 -0
  130. package/dist/projectors/postgres.d.ts +1 -0
  131. package/dist/projectors/postgres.js +32 -15
  132. package/dist/projectors/schema.d.ts +5 -0
  133. package/dist/projectors/schema.js +26 -1
  134. package/dist/projectors/sqlite.js +47 -19
  135. package/dist/projectors/with-projectors.d.ts +7 -1
  136. package/dist/projectors/with-projectors.js +13 -3
  137. package/dist/react/materialize.js +5 -1
  138. package/dist/react/react.d.ts +10 -2
  139. package/dist/react/react.js +43 -19
  140. package/dist/react/store.d.ts +11 -2
  141. package/dist/react/store.js +104 -12
  142. package/dist/react/tables.d.ts +4 -1
  143. package/dist/runtime/cancellation.d.ts +2 -1
  144. package/dist/runtime/cancellation.js +35 -2
  145. package/dist/runtime/child-run.d.ts +10 -0
  146. package/dist/runtime/child-run.js +58 -0
  147. package/dist/runtime/deadline-transition-planner.js +11 -2
  148. package/dist/runtime/dispatch-effect.d.ts +2 -2
  149. package/dist/runtime/dispatch-effect.js +43 -19
  150. package/dist/runtime/errors.d.ts +14 -1
  151. package/dist/runtime/errors.js +10 -0
  152. package/dist/runtime/filesystem-blob-store.d.ts +6 -0
  153. package/dist/runtime/filesystem-blob-store.js +59 -0
  154. package/dist/runtime/helpers.d.ts +15 -6
  155. package/dist/runtime/helpers.js +46 -6
  156. package/dist/runtime/index.d.ts +4 -2
  157. package/dist/runtime/index.js +3 -1
  158. package/dist/runtime/local-effect-executor.d.ts +11 -2
  159. package/dist/runtime/local-effect-executor.js +80 -4
  160. package/dist/runtime/looms.d.ts +38 -4
  161. package/dist/runtime/looms.js +17 -3
  162. package/dist/runtime/repository.d.ts +5 -4
  163. package/dist/runtime/repository.js +5 -2
  164. package/dist/runtime/retry-policy.d.ts +5 -0
  165. package/dist/runtime/retry-policy.js +10 -2
  166. package/dist/runtime/run-start.d.ts +1 -1
  167. package/dist/runtime/run-start.js +10 -0
  168. package/dist/runtime/runtime.d.ts +2 -2
  169. package/dist/runtime/runtime.js +191 -68
  170. package/dist/runtime/server/auth.d.ts +3 -0
  171. package/dist/runtime/server/fetch-handler.d.ts +2 -2
  172. package/dist/runtime/server/fetch-handler.js +35 -4
  173. package/dist/runtime/server/grant.d.ts +49 -0
  174. package/dist/runtime/server/grant.js +177 -0
  175. package/dist/runtime/server/routes/effects.js +47 -5
  176. package/dist/runtime/server/routes/runs.js +6 -1
  177. package/dist/runtime/server/run-route.d.ts +18 -0
  178. package/dist/runtime/server/run-route.js +26 -0
  179. package/dist/runtime/server.d.ts +3 -1
  180. package/dist/runtime/server.js +3 -1
  181. package/dist/runtime/sse.js +26 -4
  182. package/dist/runtime/subscription.d.ts +8 -0
  183. package/dist/runtime/subscription.js +23 -0
  184. package/dist/runtime/types.d.ts +87 -8
  185. package/dist/runtime/wake-engine.d.ts +4 -2
  186. package/dist/runtime/wake-engine.js +57 -6
  187. package/dist/runtime/worker-callbacks.d.ts +12 -2
  188. package/dist/runtime/worker-callbacks.js +102 -72
  189. package/dist/runtime/worker-effect-dispatch.d.ts +14 -5
  190. package/dist/runtime/worker-effect-dispatch.js +84 -18
  191. package/dist/schedule/debugger/index.d.ts +4 -0
  192. package/dist/schedule/debugger/index.js +11 -0
  193. package/dist/schedule/debugger/schedule-view.d.ts +3 -0
  194. package/dist/schedule/debugger/schedule-view.js +68 -0
  195. package/dist/schedule/debugger/summarize.d.ts +2 -0
  196. package/dist/schedule/debugger/summarize.js +43 -0
  197. package/dist/schedule/definitions-store.d.ts +10 -0
  198. package/dist/schedule/definitions-store.js +8 -0
  199. package/dist/schedule/definitions.d.ts +57 -0
  200. package/dist/schedule/definitions.js +30 -0
  201. package/dist/schedule/effects.d.ts +756 -0
  202. package/dist/schedule/effects.js +244 -0
  203. package/dist/schedule/ensure.d.ts +55 -0
  204. package/dist/schedule/ensure.js +109 -0
  205. package/dist/schedule/events.d.ts +116 -0
  206. package/dist/schedule/events.js +49 -0
  207. package/dist/schedule/index.d.ts +11 -0
  208. package/dist/schedule/index.js +11 -0
  209. package/dist/schedule/module.d.ts +1474 -0
  210. package/dist/schedule/module.js +22 -0
  211. package/dist/schedule/projections.d.ts +55 -0
  212. package/dist/schedule/projections.js +88 -0
  213. package/dist/schedule/scope.d.ts +121 -0
  214. package/dist/schedule/scope.js +7 -0
  215. package/dist/schedule/signals.d.ts +25 -0
  216. package/dist/schedule/signals.js +33 -0
  217. package/dist/schedule/specs.d.ts +59 -0
  218. package/dist/schedule/specs.js +231 -0
  219. package/dist/schedule/threads.d.ts +25 -0
  220. package/dist/schedule/threads.js +259 -0
  221. package/dist/testing/corpus-module.d.ts +34 -0
  222. package/dist/testing/corpus-module.js +87 -0
  223. package/dist/testing/index.d.ts +2 -0
  224. package/dist/testing/index.js +5 -6
  225. package/dist/testing/replay-corpus.d.ts +22 -0
  226. package/dist/testing/replay-corpus.js +50 -0
  227. package/dist/workflow/definitions-store.d.ts +1 -1
  228. package/dist/workflow/definitions-store.js +1 -1
  229. package/dist/workflow/definitions.d.ts +14 -0
  230. package/dist/workflow/effects.d.ts +122 -0
  231. package/dist/workflow/effects.js +16 -6
  232. package/dist/workflow/events.d.ts +42 -0
  233. package/dist/workflow/events.js +4 -0
  234. package/dist/workflow/module.d.ts +345 -32
  235. package/dist/workflow/module.js +7 -3
  236. package/dist/workflow/scope.d.ts +42 -0
  237. package/dist/workflow/threads.d.ts +72 -0
  238. package/dist/workflow/threads.js +106 -38
  239. package/package.json +10 -1
  240. package/dist/debugger/app/assets/index-D--9esm9.js +0 -22
  241. package/dist/debugger/app/assets/routes-Dakrdusf.js +0 -47
@@ -0,0 +1,33 @@
1
+ import { Schema, type Effect } from 'effect';
2
+ import type { JsonValue } from './types';
3
+ /**
4
+ * Immutable labels for a run, such as tenant, project, or org.
5
+ * They are safe for every reader of the run: events, snapshots, summaries, and authorization.
6
+ */
7
+ export type RunAttributes = {
8
+ readonly [key: string]: string;
9
+ };
10
+ /**
11
+ * Bearer material for a run, such as an attenuated macaroon.
12
+ * It is stored beside the log and is never written to an event, snapshot, or HTTP response.
13
+ */
14
+ export type SecretContext = {
15
+ readonly [key: string]: string;
16
+ };
17
+ export declare const RunAttributesSchema: Schema.$Record<Schema.String, Schema.String>;
18
+ export declare const SecretContextSchema: Schema.$Record<Schema.String, Schema.String>;
19
+ export interface RunSecretStore {
20
+ readonly get: (runId: string) => Effect.Effect<SecretContext, Error>;
21
+ readonly put: (runId: string, secrets: SecretContext) => Effect.Effect<void, Error>;
22
+ }
23
+ export declare function hasEntries(record: {
24
+ readonly [key: string]: string;
25
+ }): boolean;
26
+ export declare function sameStringRecord(left: {
27
+ readonly [key: string]: string;
28
+ }, right: {
29
+ readonly [key: string]: string;
30
+ }): boolean;
31
+ /** Reads a string map from an event payload. Anything else becomes an empty map. */
32
+ export declare function readAttributes(value: JsonValue | undefined): RunAttributes;
33
+ export declare function readSecretContext(value: JsonValue): SecretContext;
@@ -0,0 +1,23 @@
1
+ import { Option, Schema } from 'effect';
2
+ export const RunAttributesSchema = Schema.Record(Schema.String, Schema.String);
3
+ export const SecretContextSchema = Schema.Record(Schema.String, Schema.String);
4
+ const decodeAttributes = Schema.decodeUnknownOption(RunAttributesSchema);
5
+ const decodeSecretContext = Schema.decodeUnknownOption(SecretContextSchema);
6
+ export function hasEntries(record) {
7
+ return Object.keys(record).length > 0;
8
+ }
9
+ export function sameStringRecord(left, right) {
10
+ const leftKeys = Object.keys(left).toSorted();
11
+ const rightKeys = Object.keys(right).toSorted();
12
+ if (leftKeys.length !== rightKeys.length) {
13
+ return false;
14
+ }
15
+ return leftKeys.every((key, index) => key === rightKeys[index] && left[key] === right[key]);
16
+ }
17
+ /** Reads a string map from an event payload. Anything else becomes an empty map. */
18
+ export function readAttributes(value) {
19
+ return value === undefined ? {} : Option.getOrElse(decodeAttributes(value), () => ({}));
20
+ }
21
+ export function readSecretContext(value) {
22
+ return Option.getOrElse(decodeSecretContext(value), () => ({}));
23
+ }
@@ -1,6 +1,7 @@
1
1
  import { Effect, Option, Queue, Schema, Stream } from 'effect';
2
2
  import { EventEnvelopeSchema, fromWireEvent, withAssignedSeq } from './envelope.js';
3
3
  import { assertEventByteLimits, assertSnapshotByteLimit, EventPayloadTooLargeError, SnapshotPayloadTooLargeError, } from './limits.js';
4
+ import { RunAttributesSchema } from './run-context.js';
4
5
  import { SnapshotStoreError, withSnapshotStore, } from './snapshot-store.js';
5
6
  import { RunStateSchema } from './state.js';
6
7
  import { EventStoreConflictError, EventStoreError, EventStoreFencedError, } from './store.js';
@@ -40,7 +41,13 @@ CREATE TABLE IF NOT EXISTS looms_run_headers (
40
41
  run_id TEXT PRIMARY KEY,
41
42
  status TEXT NOT NULL,
42
43
  kind TEXT,
43
- definition_name TEXT
44
+ definition_name TEXT,
45
+ attributes TEXT NOT NULL DEFAULT '{}'
46
+ );
47
+
48
+ CREATE TABLE IF NOT EXISTS looms_run_secrets (
49
+ run_id TEXT PRIMARY KEY,
50
+ body TEXT NOT NULL
44
51
  );
45
52
  `.trim();
46
53
  const StreamRowSchema = Schema.Struct({
@@ -117,6 +124,13 @@ function initSchema(exec) {
117
124
  exec.run(sql);
118
125
  }
119
126
  }
127
+ // Databases created before attributes were indexed already have the headers table.
128
+ try {
129
+ exec.run(`ALTER TABLE looms_run_headers ADD COLUMN attributes TEXT NOT NULL DEFAULT '{}'`);
130
+ }
131
+ catch {
132
+ // The column is already present.
133
+ }
120
134
  }
121
135
  function parseEvent(body) {
122
136
  return fromWireEvent(decodeEventBody(body));
@@ -136,14 +150,20 @@ const HeaderRowSchema = Schema.Struct({
136
150
  status: RunStateSchema.fields.status,
137
151
  kind: Schema.NullOr(Schema.String),
138
152
  definition_name: Schema.NullOr(Schema.String),
153
+ attributes: Schema.String,
139
154
  });
140
155
  const decodeHeaderRow = Schema.decodeUnknownSync(HeaderRowSchema);
156
+ const encodeAttributes = Schema.encodeSync(Schema.fromJsonString(RunAttributesSchema));
157
+ const encodeSecrets = Schema.encodeSync(Schema.fromJsonString(Schema.Record(Schema.String, Schema.String)));
158
+ const decodeAttributesColumn = Schema.decodeUnknownOption(Schema.fromJsonString(RunAttributesSchema));
159
+ const decodeSecretsColumn = Schema.decodeUnknownOption(Schema.fromJsonString(Schema.Record(Schema.String, Schema.String)));
141
160
  function headerFromRow(row) {
142
161
  return {
143
162
  runId: row.run_id,
144
163
  status: row.status,
145
164
  kind: row.kind,
146
165
  definitionName: row.definition_name,
166
+ attributes: Option.getOrElse(decodeAttributesColumn(row.attributes), () => ({})),
147
167
  };
148
168
  }
149
169
  function createSqliteSnapshotStore(exec) {
@@ -199,23 +219,54 @@ function createSqliteSnapshotStore(exec) {
199
219
  }),
200
220
  saveHeader: (header) => Effect.try({
201
221
  try: () => {
202
- exec.run(`INSERT INTO looms_run_headers (run_id, status, kind, definition_name)
203
- VALUES (?, ?, ?, ?)
222
+ exec.run(`INSERT INTO looms_run_headers (run_id, status, kind, definition_name, attributes)
223
+ VALUES (?, ?, ?, ?, ?)
204
224
  ON CONFLICT (run_id) DO UPDATE SET
205
225
  status = excluded.status,
206
226
  kind = excluded.kind,
207
- definition_name = excluded.definition_name`, [header.runId, header.status, header.kind, header.definitionName]);
227
+ definition_name = excluded.definition_name,
228
+ attributes = excluded.attributes`, [
229
+ header.runId,
230
+ header.status,
231
+ header.kind,
232
+ header.definitionName,
233
+ encodeAttributes(header.attributes),
234
+ ]);
208
235
  },
209
236
  catch: (cause) => new SnapshotStoreError(`Failed to save run header for ${header.runId}`, cause),
210
237
  }),
211
238
  listHeaders: Effect.try({
212
239
  try: () => exec
213
- .rows('SELECT run_id, status, kind, definition_name FROM looms_run_headers')
240
+ .rows('SELECT run_id, status, kind, definition_name, attributes FROM looms_run_headers')
214
241
  .map((row) => headerFromRow(decodeHeaderRow(row))),
215
242
  catch: (cause) => new SnapshotStoreError('Failed to list run headers', cause),
216
243
  }),
217
244
  };
218
245
  }
246
+ const SecretRowSchema = Schema.Struct({ body: Schema.String });
247
+ const decodeSecretRow = Schema.decodeUnknownSync(SecretRowSchema);
248
+ function createSqliteSecrets(exec) {
249
+ return {
250
+ get: (runId) => Effect.try({
251
+ try: () => {
252
+ const [row] = exec.rows('SELECT body FROM looms_run_secrets WHERE run_id = ?', [runId]);
253
+ if (row === undefined) {
254
+ return {};
255
+ }
256
+ return Option.getOrElse(decodeSecretsColumn(decodeSecretRow(row).body), () => ({}));
257
+ },
258
+ catch: (cause) => new EventStoreError(`Failed to read secret context for ${runId}`, cause),
259
+ }),
260
+ put: (runId, secrets) => Effect.try({
261
+ try: () => {
262
+ exec.run(`INSERT INTO looms_run_secrets (run_id, body)
263
+ VALUES (?, ?)
264
+ ON CONFLICT (run_id) DO UPDATE SET body = excluded.body`, [runId, encodeSecrets(secrets)]);
265
+ },
266
+ catch: (cause) => new EventStoreError(`Failed to store secret context for ${runId}`, cause),
267
+ }),
268
+ };
269
+ }
219
270
  function createSqliteEventStore(exec) {
220
271
  const waiters = new Map();
221
272
  const getStream = (runId) => {
@@ -398,6 +449,7 @@ function createSqliteEventStore(exec) {
398
449
  .map((row) => decodeRunIdRow(row).run_id),
399
450
  catch: (cause) => new EventStoreError('listRuns failed', cause),
400
451
  }),
452
+ runSecrets: createSqliteSecrets(exec),
401
453
  };
402
454
  return service;
403
455
  }
@@ -1,5 +1,8 @@
1
1
  import { Schema } from 'effect';
2
2
  import { type RuntimeEffect, type WaitCondition } from './effects';
3
+ import { type ChildClose, type RunLaunchSource } from './launcher';
4
+ import { type RunAttributes } from './run-context';
5
+ import { type TraceContext } from './trace';
3
6
  import type { JsonValue, ThreadStatus, RunStatus } from './types';
4
7
  export interface ThreadRecord {
5
8
  threadId: string;
@@ -35,6 +38,8 @@ export interface EffectExecutionRecord {
35
38
  /** When the current attempt started executing, locally or on a worker. */
36
39
  startedAt: number | null;
37
40
  lastHeartbeatAt: number | null;
41
+ /** Progress reported by the latest heartbeat. Survives retries so the next attempt can resume. */
42
+ heartbeatDetails: JsonValue | null;
38
43
  lastError: string | null;
39
44
  }
40
45
  export interface RunStartIdentity {
@@ -45,18 +50,29 @@ export interface RunStartIdentity {
45
50
  requestedThreadId: string | null;
46
51
  idempotencyKey: string | null;
47
52
  rootThreadId: string;
53
+ launchedBy: RunLaunchSource | null;
54
+ correlationId: string | null;
55
+ attributes: RunAttributes;
56
+ /** The caller's W3C trace context. Effects, workers, and launched runs inherit it. */
57
+ trace: TraceContext | null;
48
58
  }
49
59
  /** The run and last sequence a fork copied its history from. */
50
60
  export interface RunForkSource {
51
61
  runId: string;
52
62
  seq: number;
53
63
  }
64
+ export interface LaunchedRun {
65
+ runId: string;
66
+ close: ChildClose;
67
+ }
54
68
  export interface RunState {
55
69
  runId: string;
56
70
  status: RunStatus;
57
71
  rootThreadId: string | null;
58
72
  startIdentity: RunStartIdentity | null;
59
73
  forkedFrom: RunForkSource | null;
74
+ /** Immutable labels copied from `runtime.run.started`. Safe for every reader of the run. */
75
+ attributes: RunAttributes;
60
76
  threads: {
61
77
  [threadId: string]: ThreadRecord;
62
78
  };
@@ -69,6 +85,12 @@ export interface RunState {
69
85
  };
70
86
  completedEffectIds: readonly string[];
71
87
  processedIdempotencyKeys: readonly string[];
88
+ /** Child runs this run launched and has not yet seen settle. */
89
+ launchedRuns: readonly LaunchedRun[];
90
+ /** The run is cancelling and will not finish until `wait` children settle. */
91
+ closeWaiting: boolean;
92
+ /** Child runs were already asked to stop because this run closed. */
93
+ childCloseSent: boolean;
72
94
  }
73
95
  export declare const RunStateSchema: Schema.Struct<{
74
96
  readonly runId: Schema.String;
@@ -82,7 +104,21 @@ export declare const RunStateSchema: Schema.Struct<{
82
104
  readonly requestedThreadId: Schema.NullOr<Schema.String>;
83
105
  readonly idempotencyKey: Schema.NullOr<Schema.String>;
84
106
  readonly rootThreadId: Schema.String;
107
+ readonly launchedBy: Schema.withDecodingDefaultKey<Schema.NullOr<Schema.Struct<{
108
+ readonly runId: Schema.String;
109
+ readonly threadId: Schema.String;
110
+ readonly effectId: Schema.String;
111
+ readonly causationId: Schema.String;
112
+ readonly causationSeq: Schema.Finite;
113
+ }>>, never>;
114
+ readonly correlationId: Schema.withDecodingDefaultKey<Schema.NullOr<Schema.String>, never>;
115
+ readonly attributes: Schema.withDecodingDefaultKey<Schema.$Record<Schema.String, Schema.String>, never>;
116
+ readonly trace: Schema.withDecodingDefaultKey<Schema.NullOr<Schema.Struct<{
117
+ readonly traceparent: Schema.String;
118
+ readonly tracestate: Schema.optional<Schema.String>;
119
+ }>>, never>;
85
120
  }>>, never>;
121
+ readonly attributes: Schema.withDecodingDefaultKey<Schema.$Record<Schema.String, Schema.String>, never>;
86
122
  readonly forkedFrom: Schema.withDecodingDefaultKey<Schema.NullOr<Schema.Struct<{
87
123
  readonly runId: Schema.String;
88
124
  readonly seq: Schema.Finite;
@@ -150,6 +186,9 @@ export declare const RunStateSchema: Schema.Struct<{
150
186
  readonly actorId: Schema.optional<Schema.String>;
151
187
  }>, Schema.Struct<{
152
188
  readonly type: Schema.Literal<"system">;
189
+ }>, Schema.Struct<{
190
+ readonly type: Schema.Literal<"run">;
191
+ readonly runId: Schema.String;
153
192
  }>]>>;
154
193
  readonly ephemeral: Schema.optional<Schema.Boolean>;
155
194
  readonly id: Schema.optional<Schema.String>;
@@ -167,10 +206,24 @@ export declare const RunStateSchema: Schema.Struct<{
167
206
  readonly type: Schema.Literal<"runtime.cancel">;
168
207
  readonly threadId: Schema.String;
169
208
  readonly tag: Schema.optional<Schema.String>;
209
+ }>, Schema.Struct<{
210
+ readonly type: Schema.Literal<"runtime.launch">;
211
+ readonly runId: Schema.String;
212
+ readonly kind: Schema.String;
213
+ readonly definitionName: Schema.String;
214
+ readonly definitionVersion: Schema.withDecodingDefaultKey<Schema.String, never>;
215
+ readonly input: Schema.Codec<Schema.Json, Schema.Json, never, never>;
216
+ readonly close: Schema.withDecodingDefaultKey<Schema.Union<readonly [Schema.Literal<"terminate">, Schema.Literal<"wait">]>, never>;
217
+ readonly tag: Schema.optional<Schema.String>;
218
+ }>, Schema.Struct<{
219
+ readonly type: Schema.Literal<"runtime.cancelRun">;
220
+ readonly runId: Schema.String;
221
+ readonly tag: Schema.optional<Schema.String>;
170
222
  }>, Schema.Struct<{
171
223
  readonly type: Schema.String;
172
224
  readonly input: Schema.Codec<Schema.Json, Schema.Json, never, never>;
173
225
  readonly tag: Schema.optional<Schema.String>;
226
+ readonly at: Schema.optional<Schema.Finite>;
174
227
  }>]>;
175
228
  }>>;
176
229
  readonly effectExecutions: Schema.withDecodingDefaultKey<Schema.$Record<Schema.String, Schema.Struct<{
@@ -181,10 +234,17 @@ export declare const RunStateSchema: Schema.Struct<{
181
234
  readonly deadlineAt: Schema.withDecodingDefaultKey<Schema.NullOr<Schema.Finite>, never>;
182
235
  readonly startedAt: Schema.withDecodingDefaultKey<Schema.NullOr<Schema.Finite>, never>;
183
236
  readonly lastHeartbeatAt: Schema.withDecodingDefaultKey<Schema.NullOr<Schema.Finite>, never>;
237
+ readonly heartbeatDetails: Schema.withDecodingDefaultKey<Schema.NullOr<Schema.Codec<Schema.Json, Schema.Json, never, never>>, never>;
184
238
  readonly lastError: Schema.NullOr<Schema.String>;
185
239
  }>>, never>;
186
240
  readonly completedEffectIds: Schema.withDecodingDefaultKey<Schema.$Array<Schema.String>, never>;
187
241
  readonly processedIdempotencyKeys: Schema.withDecodingDefaultKey<Schema.$Array<Schema.String>, never>;
242
+ readonly launchedRuns: Schema.withDecodingDefaultKey<Schema.$Array<Schema.Struct<{
243
+ readonly runId: Schema.String;
244
+ readonly close: Schema.Union<readonly [Schema.Literal<"terminate">, Schema.Literal<"wait">]>;
245
+ }>>, never>;
246
+ readonly closeWaiting: Schema.withDecodingDefaultKey<Schema.Boolean, never>;
247
+ readonly childCloseSent: Schema.withDecodingDefaultKey<Schema.Boolean, never>;
188
248
  }>;
189
249
  export declare function emptyRunState(runId: string): RunState;
190
250
  export declare function isRunTerminal(state: RunState): boolean;
@@ -1,8 +1,11 @@
1
1
  import { Effect, Predicate, Schema } from 'effect';
2
2
  import { RuntimeEffectSchema, WaitConditionSchema, } from './effects.js';
3
3
  import { JsonValueSchema } from './envelope.js';
4
+ import { RunLaunchSourceSchema } from './launcher.js';
4
5
  import { DEFAULT_DEFINITION_VERSION } from './module.js';
6
+ import { RunAttributesSchema } from './run-context.js';
5
7
  import { isTerminalStatus } from './thread.js';
8
+ import { TraceContextSchema } from './trace.js';
6
9
  export const RunStateSchema = Schema.Struct({
7
10
  runId: Schema.String,
8
11
  status: Schema.Union([
@@ -20,7 +23,12 @@ export const RunStateSchema = Schema.Struct({
20
23
  requestedThreadId: Schema.NullOr(Schema.String),
21
24
  idempotencyKey: Schema.NullOr(Schema.String),
22
25
  rootThreadId: Schema.String,
26
+ launchedBy: Schema.NullOr(RunLaunchSourceSchema).pipe(Schema.withDecodingDefaultKey(Effect.succeed(null))),
27
+ correlationId: Schema.NullOr(Schema.String).pipe(Schema.withDecodingDefaultKey(Effect.succeed(null))),
28
+ attributes: RunAttributesSchema.pipe(Schema.withDecodingDefaultKey(Effect.succeed({}))),
29
+ trace: Schema.NullOr(TraceContextSchema).pipe(Schema.withDecodingDefaultKey(Effect.succeed(null))),
23
30
  })).pipe(Schema.withDecodingDefaultKey(Effect.succeed(null))),
31
+ attributes: RunAttributesSchema.pipe(Schema.withDecodingDefaultKey(Effect.succeed({}))),
24
32
  forkedFrom: Schema.NullOr(Schema.Struct({ runId: Schema.String, seq: Schema.Finite })).pipe(Schema.withDecodingDefaultKey(Effect.succeed(null))),
25
33
  threads: Schema.Record(Schema.String, Schema.Struct({
26
34
  threadId: Schema.String,
@@ -71,10 +79,17 @@ export const RunStateSchema = Schema.Struct({
71
79
  deadlineAt: Schema.NullOr(Schema.Finite).pipe(Schema.withDecodingDefaultKey(Effect.succeed(null))),
72
80
  startedAt: Schema.NullOr(Schema.Finite).pipe(Schema.withDecodingDefaultKey(Effect.succeed(null))),
73
81
  lastHeartbeatAt: Schema.NullOr(Schema.Finite).pipe(Schema.withDecodingDefaultKey(Effect.succeed(null))),
82
+ heartbeatDetails: Schema.NullOr(JsonValueSchema).pipe(Schema.withDecodingDefaultKey(Effect.succeed(null))),
74
83
  lastError: Schema.NullOr(Schema.String),
75
84
  })).pipe(Schema.withDecodingDefaultKey(Effect.succeed({}))),
76
85
  completedEffectIds: Schema.Array(Schema.String).pipe(Schema.withDecodingDefaultKey(Effect.succeed([]))),
77
86
  processedIdempotencyKeys: Schema.Array(Schema.String).pipe(Schema.withDecodingDefaultKey(Effect.succeed([]))),
87
+ launchedRuns: Schema.Array(Schema.Struct({
88
+ runId: Schema.String,
89
+ close: Schema.Union([Schema.Literal('terminate'), Schema.Literal('wait')]),
90
+ })).pipe(Schema.withDecodingDefaultKey(Effect.succeed([]))),
91
+ closeWaiting: Schema.Boolean.pipe(Schema.withDecodingDefaultKey(Effect.succeed(false))),
92
+ childCloseSent: Schema.Boolean.pipe(Schema.withDecodingDefaultKey(Effect.succeed(false))),
78
93
  });
79
94
  export function emptyRunState(runId) {
80
95
  return {
@@ -83,12 +98,16 @@ export function emptyRunState(runId) {
83
98
  rootThreadId: null,
84
99
  startIdentity: null,
85
100
  forkedFrom: null,
101
+ attributes: {},
86
102
  threads: {},
87
103
  waits: {},
88
104
  outstandingEffects: [],
89
105
  effectExecutions: {},
90
106
  completedEffectIds: [],
91
107
  processedIdempotencyKeys: [],
108
+ launchedRuns: [],
109
+ closeWaiting: false,
110
+ childCloseSent: false,
92
111
  };
93
112
  }
94
113
  export function isRunTerminal(state) {
@@ -1,6 +1,7 @@
1
1
  import { Context, Effect, Layer, Stream } from 'effect';
2
2
  import { type AppendableEvent, type EventEnvelope } from './envelope';
3
3
  import { EventPayloadTooLargeError } from './limits';
4
+ import { type RunSecretStore } from './run-context';
4
5
  declare const EventStoreError_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 & {
5
6
  readonly _tag: "EventStoreError";
6
7
  } & Readonly<A>;
@@ -165,6 +166,11 @@ export interface EventStore {
165
166
  fromSeq?: number;
166
167
  }) => Stream.Stream<EventEnvelope, EventStoreError>;
167
168
  readonly listRuns: Effect.Effect<string[], EventStoreError>;
169
+ /**
170
+ * Run-scoped bearer material. Absent on stores that cannot keep it out of the event log.
171
+ * A start that supplies secrets fails closed when this is missing.
172
+ */
173
+ readonly runSecrets?: RunSecretStore;
168
174
  }
169
175
  /**
170
176
  * Utility to read an EventStore as a Stream.Stream, using `store.readStream` if implemented
@@ -1,6 +1,7 @@
1
1
  import { Context, Data, Effect, Layer, Option, Queue, Ref, Stream } from 'effect';
2
2
  import { withAssignedSeq } from './envelope.js';
3
3
  import { assertEventByteLimits, EventPayloadTooLargeError } from './limits.js';
4
+ import { readSecretContext } from './run-context.js';
4
5
  export class EventStoreError extends Data.TaggedError('EventStoreError') {
5
6
  conflict;
6
7
  cause;
@@ -117,6 +118,11 @@ export class EventStoreTag extends Context.Service()('looms/EventStore') {
117
118
  }
118
119
  export const makeMemoryEventStore = Effect.gen(function* () {
119
120
  const logs = yield* Ref.make(new Map());
121
+ const secrets = yield* Ref.make(new Map());
122
+ const runSecrets = {
123
+ get: (runId) => Ref.get(secrets).pipe(Effect.map((map) => readSecretContext(map.get(runId) ?? {}))),
124
+ put: (runId, value) => Ref.update(secrets, (map) => new Map(map).set(runId, value)),
125
+ };
120
126
  const getOrCreate = (map, runId) => {
121
127
  const existing = map.get(runId);
122
128
  if (existing) {
@@ -278,6 +284,7 @@ export const makeMemoryEventStore = Effect.gen(function* () {
278
284
  }));
279
285
  })),
280
286
  listRuns: Ref.get(logs).pipe(Effect.map((map) => [...map.keys()])),
287
+ runSecrets,
281
288
  };
282
289
  return service;
283
290
  });
@@ -0,0 +1,97 @@
1
+ import type { EventEnvelope } from './envelope';
2
+ import { type JsonValue } from './types';
3
+ /** A socket is closed with this code when the client must resume from `head`. */
4
+ export declare const SUBSCRIPTION_RESYNC_CODE = 4000;
5
+ /** Close a socket that has fallen this far behind instead of buffering without bound. */
6
+ export declare const SUBSCRIPTION_BACKPRESSURE_BYTES: number;
7
+ export type SubscriptionTransport = 'websocket' | 'sse' | 'auto';
8
+ export type SubscriptionResyncReason = 'trimmed' | 'backpressure';
9
+ export interface SubscriptionHello {
10
+ readonly type: 'hello';
11
+ readonly head: number;
12
+ readonly tail: number;
13
+ }
14
+ export interface SubscriptionBatch {
15
+ readonly type: 'batch';
16
+ readonly batch: readonly JsonValue[];
17
+ }
18
+ export interface SubscriptionResync {
19
+ readonly type: 'resync';
20
+ readonly head: number;
21
+ readonly reason: SubscriptionResyncReason;
22
+ }
23
+ export type SubscriptionFrame = SubscriptionHello | SubscriptionBatch | SubscriptionResync;
24
+ export interface SubscriptionFilter {
25
+ readonly threadId: string | null;
26
+ readonly includeEphemeral: boolean;
27
+ }
28
+ /** State hibernated with a Durable Object WebSocket. Writes stay on HTTP. */
29
+ export interface SubscriptionAttachment {
30
+ readonly principal: string | null;
31
+ readonly cursor: number;
32
+ readonly threadId: string | null;
33
+ readonly includeEphemeral: boolean;
34
+ }
35
+ export interface SubscriptionSender {
36
+ send(data: string): void;
37
+ close(code: number, reason: string): void;
38
+ readonly bufferedAmount: number;
39
+ }
40
+ export interface AttachedSubscriptionSocket extends SubscriptionSender {
41
+ attachment(): SubscriptionAttachment | undefined;
42
+ setAttachment(attachment: SubscriptionAttachment): void;
43
+ }
44
+ export interface SubscriptionPushOptions {
45
+ readonly bufferLimitBytes?: number;
46
+ readonly head?: number;
47
+ }
48
+ export type SubscriptionOpening = {
49
+ readonly kind: 'resync';
50
+ readonly head: number;
51
+ } | {
52
+ readonly kind: 'open';
53
+ readonly hello: SubscriptionHello;
54
+ readonly events: readonly EventEnvelope[];
55
+ };
56
+ export type SubscriptionRead = {
57
+ readonly kind: 'events';
58
+ readonly events: readonly EventEnvelope[];
59
+ } | {
60
+ readonly kind: 'hello';
61
+ readonly head: number;
62
+ readonly tail: number;
63
+ } | {
64
+ readonly kind: 'resync';
65
+ readonly head: number;
66
+ readonly reason: SubscriptionResyncReason;
67
+ } | {
68
+ readonly kind: 'ignore';
69
+ };
70
+ export declare function encodeSubscriptionFrame(frame: SubscriptionFrame): string;
71
+ export declare function decodeSubscriptionFrame(data: string): SubscriptionFrame | undefined;
72
+ /** One cursor for SSE and WebSocket. `Last-Event-ID` is the last seen seq. */
73
+ export declare function subscriptionFromSeq(url: URL, lastEventId: string | null): {
74
+ readonly ok: true;
75
+ readonly fromSeq: number;
76
+ } | {
77
+ readonly ok: false;
78
+ readonly message: string;
79
+ };
80
+ export declare function subscriptionFilter(url: URL): SubscriptionFilter;
81
+ export declare function subscriptionReplay(head: number, fromSeq: number): {
82
+ readonly kind: 'resync';
83
+ readonly head: number;
84
+ } | {
85
+ readonly kind: 'play';
86
+ readonly fromSeq: number;
87
+ };
88
+ export declare function eventMatchesSubscription(event: EventEnvelope, filter: SubscriptionFilter): boolean;
89
+ export declare function attachmentFromFilter(filter: SubscriptionFilter, fromSeq: number, principal: string | null): SubscriptionAttachment;
90
+ export declare function readSubscriptionData(data: string, runId: string): SubscriptionRead;
91
+ /**
92
+ * Sends the events this attachment still needs. Returns the advanced attachment,
93
+ * or `undefined` after the socket is closed for resync.
94
+ */
95
+ export declare function pushSubscriptionEvents(socket: SubscriptionSender, events: readonly EventEnvelope[], attachment: SubscriptionAttachment, options?: SubscriptionPushOptions): SubscriptionAttachment | undefined;
96
+ export declare function openSubscription(socket: SubscriptionSender, attachment: SubscriptionAttachment, opening: SubscriptionOpening, options?: SubscriptionPushOptions): SubscriptionAttachment | undefined;
97
+ export declare function fanoutSubscription(sockets: readonly AttachedSubscriptionSocket[], events: readonly EventEnvelope[], options?: SubscriptionPushOptions): void;