@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.
- package/CHANGELOG.md +55 -0
- package/dist/actor/cell.d.ts +21 -2
- package/dist/actor/cell.js +15 -3
- package/dist/actor/host.d.ts +10 -2
- package/dist/actor/host.js +27 -4
- package/dist/actor/index.d.ts +2 -1
- package/dist/actor/index.js +1 -1
- package/dist/actor/routing.d.ts +3 -0
- package/dist/actor/routing.js +13 -2
- package/dist/agent/definitions-store.d.ts +1 -1
- package/dist/agent/definitions-store.js +1 -1
- package/dist/agent/definitions.d.ts +48 -0
- package/dist/agent/definitions.js +44 -0
- package/dist/agent/effects.d.ts +184 -0
- package/dist/agent/effects.js +172 -40
- package/dist/agent/events.d.ts +89 -0
- package/dist/agent/events.js +25 -0
- package/dist/agent/module.d.ts +412 -0
- package/dist/agent/module.js +5 -1
- package/dist/agent/scope.d.ts +88 -0
- package/dist/agent/threads.js +39 -3
- package/dist/agent/types.d.ts +4 -0
- package/dist/approval/effects.d.ts +40 -0
- package/dist/approval/module.d.ts +80 -0
- package/dist/client/client.d.ts +468 -119
- package/dist/client/client.js +214 -21
- package/dist/client/index.d.ts +1 -1
- package/dist/client/sse.d.ts +1 -1
- package/dist/client/sse.js +2 -2
- package/dist/cloudflare/durable-object.d.ts +66 -2
- package/dist/cloudflare/durable-object.js +96 -1
- package/dist/cloudflare/event-store.d.ts +7 -3
- package/dist/cloudflare/event-store.js +16 -3
- package/dist/cloudflare/index.d.ts +7 -1
- package/dist/cloudflare/index.js +7 -1
- package/dist/cloudflare/memory-queue.d.ts +18 -0
- package/dist/cloudflare/memory-queue.js +53 -0
- package/dist/cloudflare/queue-consumer.d.ts +52 -0
- package/dist/cloudflare/queue-consumer.js +102 -0
- package/dist/cloudflare/queue-message.d.ts +31 -0
- package/dist/cloudflare/queue-message.js +33 -0
- package/dist/cloudflare/queue-pull.d.ts +62 -0
- package/dist/cloudflare/queue-pull.js +130 -0
- package/dist/cloudflare/queue-worker.d.ts +39 -0
- package/dist/cloudflare/queue-worker.js +62 -0
- package/dist/cloudflare/r2-blob-store.d.ts +33 -0
- package/dist/cloudflare/r2-blob-store.js +60 -0
- package/dist/cloudflare/router.d.ts +4 -1
- package/dist/cloudflare/router.js +8 -3
- package/dist/cloudflare/websocket.d.ts +27 -0
- package/dist/cloudflare/websocket.js +215 -0
- package/dist/core/blob-offload.d.ts +21 -0
- package/dist/core/blob-offload.js +143 -0
- package/dist/core/blob-store.d.ts +45 -0
- package/dist/core/blob-store.js +164 -0
- package/dist/core/blobs.d.ts +37 -0
- package/dist/core/blobs.js +160 -0
- package/dist/core/definition-resolver.d.ts +40 -0
- package/dist/core/definition-resolver.js +129 -0
- package/dist/core/definition-store.d.ts +6 -0
- package/dist/core/definition-store.js +37 -1
- package/dist/core/effects.d.ts +86 -3
- package/dist/core/effects.js +99 -3
- package/dist/core/envelope.d.ts +9 -0
- package/dist/core/envelope.js +4 -0
- package/dist/core/event-codec.js +3 -0
- package/dist/core/fold.js +83 -1
- package/dist/core/ids.d.ts +2 -0
- package/dist/core/ids.js +4 -0
- package/dist/core/index.d.ts +8 -0
- package/dist/core/index.js +8 -0
- package/dist/core/launcher.d.ts +97 -0
- package/dist/core/launcher.js +67 -0
- package/dist/core/module-scope.d.ts +7 -3
- package/dist/core/module-scope.js +3 -0
- package/dist/core/module.d.ts +10 -0
- package/dist/core/projection.js +4 -0
- package/dist/core/protocol.d.ts +36 -1
- package/dist/core/protocol.js +35 -0
- package/dist/core/published.d.ts +2 -0
- package/dist/core/published.js +4 -1
- package/dist/core/replay.d.ts +96 -0
- package/dist/core/run-context.d.ts +33 -0
- package/dist/core/run-context.js +23 -0
- package/dist/core/sqlite-store.js +57 -5
- package/dist/core/state.d.ts +60 -0
- package/dist/core/state.js +19 -0
- package/dist/core/store.d.ts +6 -0
- package/dist/core/store.js +7 -0
- package/dist/core/subscription.d.ts +97 -0
- package/dist/core/subscription.js +224 -0
- package/dist/core/thread.d.ts +7 -0
- package/dist/core/thread.js +1 -0
- package/dist/core/trace.d.ts +21 -0
- package/dist/core/trace.js +45 -0
- package/dist/core/worker-callback.d.ts +25 -1
- package/dist/debugger/app/assets/{index-CgDzWJg7.css → index-B7GdT1UA.css} +1 -1
- package/dist/debugger/app/assets/index-C31SnCuN.js +22 -0
- package/dist/debugger/app/assets/routes-DPA3O5AF.js +47 -0
- package/dist/debugger/app/index.html +2 -2
- package/dist/debugger/components/event-inspector.d.ts +3 -1
- package/dist/debugger/components/event-inspector.js +25 -4
- package/dist/debugger/components/event-stream.d.ts +4 -1
- package/dist/debugger/components/event-stream.js +2 -2
- package/dist/debugger/components/run-tree.d.ts +5 -2
- package/dist/debugger/components/run-tree.js +10 -4
- package/dist/debugger/context.d.ts +8 -1
- package/dist/debugger/context.js +15 -3
- package/dist/debugger/events/protocol-summarize.js +18 -3
- package/dist/debugger/index.d.ts +1 -1
- package/dist/debugger/index.js +1 -1
- package/dist/debugger/shell/debugger-shell.js +1 -1
- package/dist/debugger/shell/events-pane.js +4 -3
- package/dist/debugger/styles.css +1 -0
- package/dist/evaluator/definitions-store.d.ts +3 -4
- package/dist/evaluator/definitions-store.js +3 -3
- package/dist/evaluator/definitions.d.ts +4 -1
- package/dist/evaluator/definitions.js +5 -2
- package/dist/evaluator/effects.d.ts +74 -33
- package/dist/evaluator/effects.js +6 -2
- package/dist/evaluator/module.d.ts +150 -66
- package/dist/evaluator/module.js +7 -3
- package/dist/evaluator/threads.d.ts +2 -0
- package/dist/evaluator/threads.js +4 -0
- package/dist/projectors/event-log.d.ts +87 -0
- package/dist/projectors/event-log.js +396 -0
- package/dist/projectors/index-model.d.ts +12 -0
- package/dist/projectors/index-model.js +12 -3
- package/dist/projectors/memory.js +7 -0
- package/dist/projectors/postgres.d.ts +1 -0
- package/dist/projectors/postgres.js +32 -15
- package/dist/projectors/schema.d.ts +5 -0
- package/dist/projectors/schema.js +26 -1
- package/dist/projectors/sqlite.js +47 -19
- package/dist/projectors/with-projectors.d.ts +7 -1
- package/dist/projectors/with-projectors.js +13 -3
- package/dist/react/materialize.js +5 -1
- package/dist/react/react.d.ts +10 -2
- package/dist/react/react.js +43 -19
- package/dist/react/store.d.ts +11 -2
- package/dist/react/store.js +104 -12
- package/dist/react/tables.d.ts +4 -1
- package/dist/runtime/cancellation.d.ts +2 -1
- package/dist/runtime/cancellation.js +35 -2
- package/dist/runtime/child-run.d.ts +10 -0
- package/dist/runtime/child-run.js +58 -0
- package/dist/runtime/deadline-transition-planner.js +11 -2
- package/dist/runtime/dispatch-effect.d.ts +2 -2
- package/dist/runtime/dispatch-effect.js +43 -19
- package/dist/runtime/errors.d.ts +14 -1
- package/dist/runtime/errors.js +10 -0
- package/dist/runtime/filesystem-blob-store.d.ts +6 -0
- package/dist/runtime/filesystem-blob-store.js +59 -0
- package/dist/runtime/helpers.d.ts +15 -6
- package/dist/runtime/helpers.js +46 -6
- package/dist/runtime/index.d.ts +4 -2
- package/dist/runtime/index.js +3 -1
- package/dist/runtime/local-effect-executor.d.ts +11 -2
- package/dist/runtime/local-effect-executor.js +80 -4
- package/dist/runtime/looms.d.ts +38 -4
- package/dist/runtime/looms.js +17 -3
- package/dist/runtime/repository.d.ts +5 -4
- package/dist/runtime/repository.js +5 -2
- package/dist/runtime/retry-policy.d.ts +5 -0
- package/dist/runtime/retry-policy.js +10 -2
- package/dist/runtime/run-start.d.ts +1 -1
- package/dist/runtime/run-start.js +10 -0
- package/dist/runtime/runtime.d.ts +2 -2
- package/dist/runtime/runtime.js +191 -68
- package/dist/runtime/server/auth.d.ts +3 -0
- package/dist/runtime/server/fetch-handler.d.ts +2 -2
- package/dist/runtime/server/fetch-handler.js +35 -4
- package/dist/runtime/server/grant.d.ts +49 -0
- package/dist/runtime/server/grant.js +177 -0
- package/dist/runtime/server/routes/effects.js +47 -5
- package/dist/runtime/server/routes/runs.js +6 -1
- package/dist/runtime/server/run-route.d.ts +18 -0
- package/dist/runtime/server/run-route.js +26 -0
- package/dist/runtime/server.d.ts +3 -1
- package/dist/runtime/server.js +3 -1
- package/dist/runtime/sse.js +26 -4
- package/dist/runtime/subscription.d.ts +8 -0
- package/dist/runtime/subscription.js +23 -0
- package/dist/runtime/types.d.ts +87 -8
- package/dist/runtime/wake-engine.d.ts +4 -2
- package/dist/runtime/wake-engine.js +57 -6
- package/dist/runtime/worker-callbacks.d.ts +12 -2
- package/dist/runtime/worker-callbacks.js +102 -72
- package/dist/runtime/worker-effect-dispatch.d.ts +14 -5
- package/dist/runtime/worker-effect-dispatch.js +84 -18
- package/dist/schedule/debugger/index.d.ts +4 -0
- package/dist/schedule/debugger/index.js +11 -0
- package/dist/schedule/debugger/schedule-view.d.ts +3 -0
- package/dist/schedule/debugger/schedule-view.js +68 -0
- package/dist/schedule/debugger/summarize.d.ts +2 -0
- package/dist/schedule/debugger/summarize.js +43 -0
- package/dist/schedule/definitions-store.d.ts +10 -0
- package/dist/schedule/definitions-store.js +8 -0
- package/dist/schedule/definitions.d.ts +57 -0
- package/dist/schedule/definitions.js +30 -0
- package/dist/schedule/effects.d.ts +756 -0
- package/dist/schedule/effects.js +244 -0
- package/dist/schedule/ensure.d.ts +55 -0
- package/dist/schedule/ensure.js +109 -0
- package/dist/schedule/events.d.ts +116 -0
- package/dist/schedule/events.js +49 -0
- package/dist/schedule/index.d.ts +11 -0
- package/dist/schedule/index.js +11 -0
- package/dist/schedule/module.d.ts +1474 -0
- package/dist/schedule/module.js +22 -0
- package/dist/schedule/projections.d.ts +55 -0
- package/dist/schedule/projections.js +88 -0
- package/dist/schedule/scope.d.ts +121 -0
- package/dist/schedule/scope.js +7 -0
- package/dist/schedule/signals.d.ts +25 -0
- package/dist/schedule/signals.js +33 -0
- package/dist/schedule/specs.d.ts +59 -0
- package/dist/schedule/specs.js +231 -0
- package/dist/schedule/threads.d.ts +25 -0
- package/dist/schedule/threads.js +259 -0
- package/dist/testing/corpus-module.d.ts +34 -0
- package/dist/testing/corpus-module.js +87 -0
- package/dist/testing/index.d.ts +2 -0
- package/dist/testing/index.js +5 -6
- package/dist/testing/replay-corpus.d.ts +22 -0
- package/dist/testing/replay-corpus.js +50 -0
- package/dist/workflow/definitions-store.d.ts +1 -1
- package/dist/workflow/definitions-store.js +1 -1
- package/dist/workflow/definitions.d.ts +14 -0
- package/dist/workflow/effects.d.ts +122 -0
- package/dist/workflow/effects.js +16 -6
- package/dist/workflow/events.d.ts +42 -0
- package/dist/workflow/events.js +4 -0
- package/dist/workflow/module.d.ts +345 -32
- package/dist/workflow/module.js +7 -3
- package/dist/workflow/scope.d.ts +42 -0
- package/dist/workflow/threads.d.ts +72 -0
- package/dist/workflow/threads.js +106 -38
- package/package.json +10 -1
- package/dist/debugger/app/assets/index-D--9esm9.js +0 -22
- package/dist/debugger/app/assets/routes-Dakrdusf.js +0 -47
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Effect, Schema } from 'effect';
|
|
2
|
+
import { JsonValueSchema, } from '@swirls/looms/core';
|
|
2
3
|
import { EventInputBodySchema, provideStore, readJson } from '../http.js';
|
|
3
4
|
import { integer, oneOf, route } from '../route-table.js';
|
|
4
5
|
const WORKER_CALLBACK_KINDS = [
|
|
@@ -11,16 +12,46 @@ const WORKER_CALLBACK_KINDS = [
|
|
|
11
12
|
const WorkerCallbackBodySchema = Schema.Struct({
|
|
12
13
|
events: Schema.optional(Schema.Array(EventInputBodySchema)),
|
|
13
14
|
error: Schema.optional(Schema.String),
|
|
15
|
+
nonRetryable: Schema.optional(Schema.Boolean),
|
|
16
|
+
retryAfterMs: Schema.optional(Schema.Finite),
|
|
17
|
+
details: Schema.optional(JsonValueSchema),
|
|
14
18
|
});
|
|
15
19
|
function workerCallbackInput(params, body) {
|
|
16
20
|
const callback = { effectId: params.effectId, attempt: params.attempt };
|
|
17
21
|
switch (params.action) {
|
|
18
22
|
case 'complete':
|
|
19
23
|
return { ...callback, kind: 'complete', events: body.events ?? [] };
|
|
20
|
-
case 'fail':
|
|
21
|
-
|
|
22
|
-
|
|
24
|
+
case 'fail': {
|
|
25
|
+
if (body.error === undefined) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
const failure = {
|
|
29
|
+
...callback,
|
|
30
|
+
kind: 'fail',
|
|
31
|
+
error: body.error,
|
|
32
|
+
};
|
|
33
|
+
if (body.nonRetryable !== undefined) {
|
|
34
|
+
failure.nonRetryable = body.nonRetryable;
|
|
35
|
+
}
|
|
36
|
+
if (body.retryAfterMs !== undefined) {
|
|
37
|
+
failure.retryAfterMs = body.retryAfterMs;
|
|
38
|
+
}
|
|
39
|
+
return failure;
|
|
40
|
+
}
|
|
41
|
+
case 'heartbeat': {
|
|
42
|
+
const heartbeat = { ...callback, kind: 'heartbeat' };
|
|
43
|
+
if (body.details !== undefined) {
|
|
44
|
+
heartbeat.details = body.details;
|
|
45
|
+
}
|
|
46
|
+
return heartbeat;
|
|
47
|
+
}
|
|
48
|
+
case 'started':
|
|
49
|
+
case 'cancelled':
|
|
23
50
|
return { ...callback, kind: params.action };
|
|
51
|
+
default: {
|
|
52
|
+
const exhaustive = params.action;
|
|
53
|
+
return exhaustive;
|
|
54
|
+
}
|
|
24
55
|
}
|
|
25
56
|
}
|
|
26
57
|
export function effectRoutes({ runtime, store }) {
|
|
@@ -39,6 +70,17 @@ export function effectRoutes({ runtime, store }) {
|
|
|
39
70
|
access: 'operations',
|
|
40
71
|
handle: ({ params }) => provideStore(runtime.retryEffect(params.runId, params.effectId), store).pipe(Effect.map((state) => Response.json({ runId: params.runId, effectId: params.effectId, state }))),
|
|
41
72
|
}),
|
|
73
|
+
route({
|
|
74
|
+
method: 'POST',
|
|
75
|
+
path: '/runs/:runId/effects/:effectId/:attempt/secrets',
|
|
76
|
+
name: 'effects.workerSecrets',
|
|
77
|
+
access: 'worker',
|
|
78
|
+
params: { attempt: integer },
|
|
79
|
+
handle: ({ params }) => provideStore(runtime.workerSecrets(params.runId, {
|
|
80
|
+
effectId: params.effectId,
|
|
81
|
+
attempt: params.attempt,
|
|
82
|
+
}), store).pipe(Effect.map((result) => Response.json(result, { headers: { 'cache-control': 'no-store' } }))),
|
|
83
|
+
}),
|
|
42
84
|
route({
|
|
43
85
|
method: 'POST',
|
|
44
86
|
path: '/runs/:runId/effects/:effectId/:attempt/:action',
|
|
@@ -51,8 +93,8 @@ export function effectRoutes({ runtime, store }) {
|
|
|
51
93
|
if (!input) {
|
|
52
94
|
return Response.json({ error: 'error required' }, { status: 400 });
|
|
53
95
|
}
|
|
54
|
-
const
|
|
55
|
-
return Response.json(
|
|
96
|
+
const result = yield* provideStore(runtime.workerCallback(params.runId, input), store);
|
|
97
|
+
return Response.json(result);
|
|
56
98
|
}),
|
|
57
99
|
}),
|
|
58
100
|
];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Effect, Schema } from 'effect';
|
|
2
|
-
import { createRunId, EventStoreTag, JsonValueSchema, stringifyJson, } from '@swirls/looms/core';
|
|
2
|
+
import { createRunId, EventStoreTag, JsonValueSchema, RunAttributesSchema, readTraceHeaders, SecretContextSchema, stringifyJson, } from '@swirls/looms/core';
|
|
3
3
|
import { createEventStreamResponse } from '../../sse.js';
|
|
4
4
|
import { acceptsEventStream, EventInputBodySchema, extractIdempotencyKey, provideStore, readIntegerQuery, readJson, readOptionalJson, RequestQueryError, } from '../http.js';
|
|
5
5
|
import { route } from '../route-table.js';
|
|
@@ -11,6 +11,8 @@ const StartRunBodySchema = Schema.Struct({
|
|
|
11
11
|
input: Schema.optional(JsonValueSchema),
|
|
12
12
|
runId: Schema.optional(Schema.String),
|
|
13
13
|
idempotencyKey: Schema.optional(Schema.String),
|
|
14
|
+
attributes: Schema.optional(RunAttributesSchema),
|
|
15
|
+
secrets: Schema.optional(SecretContextSchema),
|
|
14
16
|
});
|
|
15
17
|
const SignalBodySchema = Schema.Struct({
|
|
16
18
|
events: Schema.optional(Schema.Array(EventInputBodySchema)),
|
|
@@ -108,6 +110,9 @@ export function runRoutes({ runtime, store }) {
|
|
|
108
110
|
input: body.input ?? null,
|
|
109
111
|
runId,
|
|
110
112
|
idempotencyKey,
|
|
113
|
+
attributes: body.attributes,
|
|
114
|
+
secrets: body.secrets,
|
|
115
|
+
trace: readTraceHeaders(req.headers),
|
|
111
116
|
};
|
|
112
117
|
if (wantsRunStream(req, url)) {
|
|
113
118
|
return streamStartRun(req, runtime, store, args);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Effect } from 'effect';
|
|
2
|
+
import { type EventStore, type RunAttributes } from '@swirls/looms/core';
|
|
3
|
+
import type { LoomsRuntime } from '../runtime';
|
|
4
|
+
import type { AuthorizedAccess, RouteInfo } from './auth';
|
|
5
|
+
/**
|
|
6
|
+
* Attributes are immutable and recorded on `runtime.run.started`, which is seq 1 of every
|
|
7
|
+
* untrimmed log, forks included. Only a trimmed log needs the snapshot-backed fold.
|
|
8
|
+
*/
|
|
9
|
+
export declare function readStoredAttributes(runtime: LoomsRuntime, store: EventStore, runId: string): Effect.Effect<RunAttributes, Error>;
|
|
10
|
+
/**
|
|
11
|
+
* What `authorize` sees for a request about one run: the run's attributes are included when it has
|
|
12
|
+
* any. Every path that serves a run, including a WebSocket upgrade, authorizes through this.
|
|
13
|
+
*/
|
|
14
|
+
export declare function runRouteInfo(runtime: LoomsRuntime, store: EventStore, route: {
|
|
15
|
+
readonly access: AuthorizedAccess;
|
|
16
|
+
readonly name: string;
|
|
17
|
+
readonly runId: string;
|
|
18
|
+
}): Effect.Effect<RouteInfo, Error>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Effect } from 'effect';
|
|
2
|
+
import { EventStoreTag, hasEntries, isJsonObject, readAttributes, } from '@swirls/looms/core';
|
|
3
|
+
const noAttributes = {};
|
|
4
|
+
/**
|
|
5
|
+
* Attributes are immutable and recorded on `runtime.run.started`, which is seq 1 of every
|
|
6
|
+
* untrimmed log, forks included. Only a trimmed log needs the snapshot-backed fold.
|
|
7
|
+
*/
|
|
8
|
+
export function readStoredAttributes(runtime, store, runId) {
|
|
9
|
+
const folded = runtime.getRun(runId).pipe(Effect.provideService(EventStoreTag, store), Effect.map((state) => state.attributes));
|
|
10
|
+
return store.read(runId, { fromSeq: 1, limit: 1 }).pipe(Effect.flatMap(([first]) => {
|
|
11
|
+
if (first === undefined) {
|
|
12
|
+
return Effect.succeed(noAttributes);
|
|
13
|
+
}
|
|
14
|
+
if (first.type === 'runtime.run.started' && isJsonObject(first.payload)) {
|
|
15
|
+
return Effect.succeed(readAttributes(first.payload.attributes));
|
|
16
|
+
}
|
|
17
|
+
return folded;
|
|
18
|
+
}), Effect.catch(() => folded));
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* What `authorize` sees for a request about one run: the run's attributes are included when it has
|
|
22
|
+
* any. Every path that serves a run, including a WebSocket upgrade, authorizes through this.
|
|
23
|
+
*/
|
|
24
|
+
export function runRouteInfo(runtime, store, route) {
|
|
25
|
+
return readStoredAttributes(runtime, store, route.runId).pipe(Effect.map((attributes) => (hasEntries(attributes) ? { ...route, attributes } : route)));
|
|
26
|
+
}
|
package/dist/runtime/server.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
export { createFetchHandler, handlesPath, type FetchHandlerOptions } from './server/fetch-handler';
|
|
2
2
|
export { isLoomsApiPath } from './server/http';
|
|
3
3
|
export { mountDebugger, type DebuggerMount } from './server/debugger-host';
|
|
4
|
-
export {
|
|
4
|
+
export { runRouteInfo } from './server/run-route';
|
|
5
|
+
export { authAllowed, authDenied, authResponse, bearerAuth, defaultAuthorize, type AuthDecision, type AuthorizedAccess, type Authorize, type BearerTokens, type RouteAccess, type RouteInfo, } from './server/auth';
|
|
6
|
+
export { grantAuth, issueRunGrant, grantSubprotocol, runGrantToken, verifyRunGrant, GrantError, type GrantAuthOptions, type GrantFailure, type RunGrant, } from './server/grant';
|
|
5
7
|
export interface RunningServer {
|
|
6
8
|
port: number;
|
|
7
9
|
stop: () => void;
|
package/dist/runtime/server.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
export { createFetchHandler, handlesPath } from './server/fetch-handler.js';
|
|
2
2
|
export { isLoomsApiPath } from './server/http.js';
|
|
3
3
|
export { mountDebugger } from './server/debugger-host.js';
|
|
4
|
-
export {
|
|
4
|
+
export { runRouteInfo } from './server/run-route.js';
|
|
5
|
+
export { authAllowed, authDenied, authResponse, bearerAuth, defaultAuthorize, } from './server/auth.js';
|
|
6
|
+
export { grantAuth, issueRunGrant, grantSubprotocol, runGrantToken, verifyRunGrant, GrantError, } from './server/grant.js';
|
|
5
7
|
export function serveHttp(fetchHandler, options) {
|
|
6
8
|
const port = options?.port ?? 8787;
|
|
7
9
|
const hostname = options?.hostname ?? '0.0.0.0';
|
package/dist/runtime/sse.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Effect, Fiber, Schedule, Stream } from 'effect';
|
|
2
|
-
import { encodeLoomsEvent,
|
|
2
|
+
import { encodeLoomsEvent, encodeSubscriptionFrame, } from '@swirls/looms/core';
|
|
3
|
+
import { readSubscriptionBounds } from './subscription.js';
|
|
3
4
|
function disableBunSocketTimeout(request) {
|
|
4
5
|
if (!request) {
|
|
5
6
|
return;
|
|
@@ -64,12 +65,33 @@ export function createEventStreamResponse(options) {
|
|
|
64
65
|
close();
|
|
65
66
|
}
|
|
66
67
|
};
|
|
67
|
-
subscriptionFiber = Effect.runFork(store.subscribe(runId, { fromSeq }).pipe(Stream.tap((event) => Effect.sync(() => {
|
|
68
|
-
write(`id: ${event.seq}\ndata: ${stringifyJson({ batch: [encodeLoomsEvent(event)] })}\n\n`);
|
|
69
|
-
})), Stream.runDrain));
|
|
70
68
|
// Send an immediate keepalive so bytes and headers flush over the wire immediately,
|
|
71
69
|
// preventing dev server proxies and Bun.serve from aborting quiet connections.
|
|
70
|
+
// It has to be enqueued before the subscription fiber, which writes hello synchronously.
|
|
72
71
|
write(': keepalive\n\n');
|
|
72
|
+
subscriptionFiber = Effect.runFork(Effect.gen(function* () {
|
|
73
|
+
const bounds = yield* readSubscriptionBounds(store, runId);
|
|
74
|
+
if (fromSeq < bounds.head) {
|
|
75
|
+
write(`data: ${encodeSubscriptionFrame({
|
|
76
|
+
type: 'resync',
|
|
77
|
+
head: bounds.head,
|
|
78
|
+
reason: 'trimmed',
|
|
79
|
+
})}\n\n`);
|
|
80
|
+
close();
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
write(`data: ${encodeSubscriptionFrame({
|
|
84
|
+
type: 'hello',
|
|
85
|
+
head: bounds.head,
|
|
86
|
+
tail: bounds.tail,
|
|
87
|
+
})}\n\n`);
|
|
88
|
+
yield* store.subscribe(runId, { fromSeq }).pipe(Stream.tap((event) => Effect.sync(() => {
|
|
89
|
+
write(`id: ${event.seq}\ndata: ${encodeSubscriptionFrame({
|
|
90
|
+
type: 'batch',
|
|
91
|
+
batch: [encodeLoomsEvent(event)],
|
|
92
|
+
})}\n\n`);
|
|
93
|
+
})), Stream.runDrain);
|
|
94
|
+
}));
|
|
73
95
|
heartbeatFiber = Effect.runFork(Effect.repeat(Effect.sleep(heartbeatMs).pipe(Effect.andThen(Effect.sync(() => write(': keepalive\n\n')))), Schedule.forever).pipe(Effect.asVoid));
|
|
74
96
|
const startResult = onStart?.(write, close);
|
|
75
97
|
const startEffect = Effect.isEffect(startResult)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Effect } from 'effect';
|
|
2
|
+
import { type EventStore, type EventStoreError, type SubscriptionOpening } from '@swirls/looms/core';
|
|
3
|
+
export declare function readSubscriptionBounds(store: EventStore, runId: string): Effect.Effect<{
|
|
4
|
+
readonly head: number;
|
|
5
|
+
readonly tail: number;
|
|
6
|
+
}, EventStoreError>;
|
|
7
|
+
/** Hello plus the retained events, or a resync when `fromSeq` is below the log head. */
|
|
8
|
+
export declare function loadSubscriptionOpening(store: EventStore, runId: string, fromSeq: number): Effect.Effect<SubscriptionOpening, EventStoreError>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Effect } from 'effect';
|
|
2
|
+
import { subscriptionReplay, } from '@swirls/looms/core';
|
|
3
|
+
export function readSubscriptionBounds(store, runId) {
|
|
4
|
+
if (store.bounds) {
|
|
5
|
+
return store.bounds(runId);
|
|
6
|
+
}
|
|
7
|
+
return store.tail(runId).pipe(Effect.map((tail) => ({ head: 1, tail })));
|
|
8
|
+
}
|
|
9
|
+
/** Hello plus the retained events, or a resync when `fromSeq` is below the log head. */
|
|
10
|
+
export function loadSubscriptionOpening(store, runId, fromSeq) {
|
|
11
|
+
return Effect.gen(function* () {
|
|
12
|
+
const bounds = yield* readSubscriptionBounds(store, runId);
|
|
13
|
+
const decision = subscriptionReplay(bounds.head, fromSeq);
|
|
14
|
+
if (decision.kind === 'resync') {
|
|
15
|
+
return { kind: 'resync', head: decision.head };
|
|
16
|
+
}
|
|
17
|
+
const events = yield* store.read(runId, {
|
|
18
|
+
fromSeq: decision.fromSeq,
|
|
19
|
+
});
|
|
20
|
+
const hello = { type: 'hello', head: bounds.head, tail: bounds.tail };
|
|
21
|
+
return { kind: 'open', hello, events };
|
|
22
|
+
});
|
|
23
|
+
}
|
package/dist/runtime/types.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Effect } from 'effect';
|
|
2
|
-
import type { AnyRuntimeModule, ComposedRegistry, EventEnvelope, EventInput, WorkerCallbackInput, EventStore, EventStoreAppendError, EventStoreError, EventStoreFencedError, EventStoreTag, EventStoreTrimCoverage, EventStoreTruncationError, JsonValue, ProjectionDefinition, ReplayStep, RunState, RunSummary, SnapshotStore, SnapshotStoreError } from '@swirls/looms/core';
|
|
2
|
+
import type { AnyRuntimeModule, ComposedRegistry, DefinitionResolver, EventEnvelope, EventInput, WorkerCallback, WorkerCallbackInput, DefinitionResolveError, WorkerCallbackResult, WorkerSecretsResult, EventStore, EventStoreAppendError, EventStoreError, EventStoreFencedError, EventStoreTag, EventStoreTrimCoverage, RunLauncher, RunLaunchSource, RunAttributes, SecretContext, TraceContext, EventStoreTruncationError, JsonValue, ProjectionDefinition, ReplayStep, RunState, RunSummary, SnapshotStore, SnapshotStoreError } from '@swirls/looms/core';
|
|
3
3
|
import type { EventStoreTrimmedError } from '@swirls/looms/core';
|
|
4
4
|
import type { DuplicateEffectDispatchError, MaxWakeIterationsError } from './errors';
|
|
5
5
|
import type { RuntimeObserver } from './observer';
|
|
@@ -7,6 +7,12 @@ import type { WakeScheduler } from './wake-scheduler';
|
|
|
7
7
|
export type WakeError = DuplicateEffectDispatchError | MaxWakeIterationsError | EventStoreTruncationError | EventStoreTrimmedError | EventStoreFencedError | EventStoreError | Error;
|
|
8
8
|
export interface CreateRuntimeOptions<TModules extends readonly AnyRuntimeModule[] = readonly AnyRuntimeModule[]> {
|
|
9
9
|
readonly modules: TModules;
|
|
10
|
+
/**
|
|
11
|
+
* Loads a definition that was not registered on a module. Registered definitions are checked
|
|
12
|
+
* first. Lookups are cached, including misses. Start fails with `UnknownDefinitionError`
|
|
13
|
+
* and writes no events when the definition is missing.
|
|
14
|
+
*/
|
|
15
|
+
readonly definitionResolver?: DefinitionResolver;
|
|
10
16
|
readonly store?: EventStore;
|
|
11
17
|
/**
|
|
12
18
|
* Durable events between mid-wake snapshots. Default 200; `0` disables.
|
|
@@ -15,6 +21,8 @@ export interface CreateRuntimeOptions<TModules extends readonly AnyRuntimeModule
|
|
|
15
21
|
*/
|
|
16
22
|
readonly snapshotEvery?: number;
|
|
17
23
|
readonly maxWakeIterations?: number;
|
|
24
|
+
/** Maximum concurrent in-flight worker effects dispatched per run (fairness guard). */
|
|
25
|
+
readonly maxInFlightWorkerEffects?: number;
|
|
18
26
|
/** Maximum active and queued mutation operations per run. Default 256. */
|
|
19
27
|
readonly maxPendingRunOperations?: number;
|
|
20
28
|
/** Defaults to the EventStore's attached store or an in-memory SnapshotStore. */
|
|
@@ -39,6 +47,13 @@ export interface CreateRuntimeOptions<TModules extends readonly AnyRuntimeModule
|
|
|
39
47
|
* Defaults to an in-process Effect fiber scheduler.
|
|
40
48
|
*/
|
|
41
49
|
readonly scheduler?: WakeScheduler;
|
|
50
|
+
/**
|
|
51
|
+
* Starts runs from effect handlers. Required for schedules on an actor or Durable Object host.
|
|
52
|
+
* Single-store `createLooms` installs one that launches into its own store.
|
|
53
|
+
*/
|
|
54
|
+
readonly launcher?: RunLauncher;
|
|
55
|
+
/** Install a launcher that starts runs in this runtime's store. `createLooms` sets this. */
|
|
56
|
+
readonly localLauncher?: boolean;
|
|
42
57
|
readonly worker?: EffectWorker;
|
|
43
58
|
/** Failure-isolated, framework-neutral operational event sink. */
|
|
44
59
|
readonly observer?: RuntimeObserver;
|
|
@@ -49,6 +64,24 @@ export interface EffectWorkerTask {
|
|
|
49
64
|
readonly attempt: number;
|
|
50
65
|
readonly type: string;
|
|
51
66
|
readonly input: JsonValue;
|
|
67
|
+
/** Progress from the previous attempt's last heartbeat, when it reported any. */
|
|
68
|
+
readonly details?: JsonValue;
|
|
69
|
+
/** The run's W3C trace context, for parenting the worker's spans. */
|
|
70
|
+
readonly trace?: TraceContext;
|
|
71
|
+
/** The attempt's deadlines from the effect's retry policy, when it sets any. */
|
|
72
|
+
readonly deadlines?: EffectWorkerDeadlines;
|
|
73
|
+
/**
|
|
74
|
+
* Present only on the in-memory dispatch. A transport that persists the task must leave this out
|
|
75
|
+
* and let the worker read the secret context through an authenticated callback.
|
|
76
|
+
*/
|
|
77
|
+
readonly secrets?: SecretContext;
|
|
78
|
+
}
|
|
79
|
+
/** Looms enforces these; a worker can use them to pace heartbeats or give up early. */
|
|
80
|
+
export interface EffectWorkerDeadlines {
|
|
81
|
+
/** Epoch ms. A `started` callback after this is stale. */
|
|
82
|
+
readonly scheduleToStartAt?: number;
|
|
83
|
+
readonly startToCloseTimeoutMs?: number;
|
|
84
|
+
readonly heartbeatTimeoutMs?: number;
|
|
52
85
|
}
|
|
53
86
|
export interface EffectWorker {
|
|
54
87
|
/** Effect types this worker claims, even when a local handler exists. */
|
|
@@ -57,6 +90,14 @@ export interface EffectWorker {
|
|
|
57
90
|
readonly canDispatch?: (task: EffectWorkerTask) => boolean;
|
|
58
91
|
dispatch(task: EffectWorkerTask): void | Promise<void>;
|
|
59
92
|
cancel?(task: EffectWorkerTask): void | Promise<void>;
|
|
93
|
+
/**
|
|
94
|
+
* What a rejected `dispatch` means. With `'unacknowledged'` (the default) the task may still have
|
|
95
|
+
* arrived, so the attempt waits for its schedule-to-start timeout or an operator. With
|
|
96
|
+
* `'acknowledged'` a rejection means the transport refused it, so the attempt fails under the
|
|
97
|
+
* retry policy. A transport can still lose an accepted message, so every effect such a worker
|
|
98
|
+
* claims must set `retry.scheduleToStartTimeoutMs`.
|
|
99
|
+
*/
|
|
100
|
+
readonly dispatchSemantics?: 'unacknowledged' | 'acknowledged';
|
|
60
101
|
}
|
|
61
102
|
export interface StartRunArgs {
|
|
62
103
|
kind: string;
|
|
@@ -66,6 +107,22 @@ export interface StartRunArgs {
|
|
|
66
107
|
runId?: string;
|
|
67
108
|
threadId?: string;
|
|
68
109
|
idempotencyKey?: string;
|
|
110
|
+
/** The effect that launched this run. The runtime sets it on launches made from effects. */
|
|
111
|
+
launchedBy?: RunLaunchSource;
|
|
112
|
+
/** Recorded on `runtime.run.started` to group related runs. */
|
|
113
|
+
correlationId?: string;
|
|
114
|
+
/** Immutable labels stored on the start event and the run summary. */
|
|
115
|
+
attributes?: RunAttributes;
|
|
116
|
+
/**
|
|
117
|
+
* Bearer material stored beside the log. A retry that sends a different context conflicts.
|
|
118
|
+
* Omitting it on a retry keeps the context already stored.
|
|
119
|
+
*/
|
|
120
|
+
secrets?: SecretContext;
|
|
121
|
+
/**
|
|
122
|
+
* The caller's W3C trace context, recorded on `runtime.run.started` when it is a valid version-00
|
|
123
|
+
* `traceparent`. It is not part of the start identity, so a retry from another trace is not a conflict.
|
|
124
|
+
*/
|
|
125
|
+
trace?: TraceContext;
|
|
69
126
|
}
|
|
70
127
|
export interface StartResult {
|
|
71
128
|
runId: string;
|
|
@@ -91,18 +148,32 @@ export interface LoomsRuntime<TModules extends readonly AnyRuntimeModule[] = rea
|
|
|
91
148
|
readonly modules: TModules;
|
|
92
149
|
readonly registry: ComposedRegistry;
|
|
93
150
|
startRun(args: StartRunArgs): Effect.Effect<StartResult, Error | EventStoreAppendError | EventStoreError, EventStoreTag>;
|
|
151
|
+
/**
|
|
152
|
+
* Commit a run's start, then schedule a wake instead of waiting for it. A duplicate start with
|
|
153
|
+
* the same identity is a no-op aside from the scheduled wake.
|
|
154
|
+
*/
|
|
155
|
+
launchRun(args: StartRunArgs): Effect.Effect<StartResult, Error | EventStoreAppendError | EventStoreError, EventStoreTag>;
|
|
94
156
|
signal(runId: string, events: ReadonlyArray<EventInput>, options?: {
|
|
95
157
|
idempotencyKey?: string;
|
|
158
|
+
coalesce?: boolean;
|
|
159
|
+
}): Effect.Effect<RunState, Error | EventStoreAppendError | EventStoreError, EventStoreTag>;
|
|
160
|
+
/**
|
|
161
|
+
* Append events the runtime itself produced for another run. Unlike `signal`, this does not
|
|
162
|
+
* reject reserved runtime event types or rewrite their origin.
|
|
163
|
+
*/
|
|
164
|
+
accept(runId: string, events: ReadonlyArray<EventInput>, options?: {
|
|
165
|
+
idempotencyKey?: string;
|
|
166
|
+
coalesce?: boolean;
|
|
96
167
|
}): Effect.Effect<RunState, Error | EventStoreAppendError | EventStoreError, EventStoreTag>;
|
|
97
168
|
wake(runId: string): Effect.Effect<RunState, WakeError, EventStoreTag>;
|
|
98
|
-
getRun(runId: string): Effect.Effect<RunState, EventStoreTruncationError | EventStoreTrimmedError | EventStoreError, EventStoreTag>;
|
|
169
|
+
getRun(runId: string): Effect.Effect<RunState, EventStoreTruncationError | EventStoreTrimmedError | EventStoreError | DefinitionResolveError, EventStoreTag>;
|
|
99
170
|
getEvents(runId: string, options?: {
|
|
100
171
|
fromSeq?: number;
|
|
101
172
|
limit?: number;
|
|
102
173
|
}): Effect.Effect<EventEnvelope[], EventStoreError, EventStoreTag>;
|
|
103
174
|
project<S>(runId: string, definition: ProjectionDefinition<S>): Effect.Effect<S, EventStoreError, EventStoreTag>;
|
|
104
175
|
replayTo(runId: string, seq: number): Effect.Effect<ReplayStep | null, EventStoreError, EventStoreTag>;
|
|
105
|
-
inspectRun(runId: string): Effect.Effect<RunOperationalStatus, EventStoreError | EventStoreTrimmedError | EventStoreTruncationError, EventStoreTag>;
|
|
176
|
+
inspectRun(runId: string): Effect.Effect<RunOperationalStatus, EventStoreError | EventStoreTrimmedError | EventStoreTruncationError | DefinitionResolveError, EventStoreTag>;
|
|
106
177
|
retryEffect(runId: string, effectId: string): Effect.Effect<RunState, Error | EventStoreAppendError | EventStoreError, EventStoreTag>;
|
|
107
178
|
cancel(runId: string, threadId?: string): Effect.Effect<RunState, Error | EventStoreAppendError | EventStoreError, EventStoreTag>;
|
|
108
179
|
/**
|
|
@@ -113,13 +184,21 @@ export interface LoomsRuntime<TModules extends readonly AnyRuntimeModule[] = rea
|
|
|
113
184
|
fork(sourceRunId: string, options?: {
|
|
114
185
|
runId?: string;
|
|
115
186
|
}): Effect.Effect<StartResult, Error | EventStoreAppendError | EventStoreError, EventStoreTag>;
|
|
116
|
-
/**
|
|
117
|
-
|
|
187
|
+
/**
|
|
188
|
+
* Applies a worker's report about one effect attempt. A report for another attempt, or one that
|
|
189
|
+
* arrives after the attempt settled, is `stale` and changes nothing.
|
|
190
|
+
*/
|
|
191
|
+
workerCallback(runId: string, input: WorkerCallbackInput): Effect.Effect<WorkerCallbackResult, Error | EventStoreAppendError | EventStoreError, EventStoreTag>;
|
|
192
|
+
/**
|
|
193
|
+
* The run's secret context for a worker attempt that has started and not settled. Any other
|
|
194
|
+
* attempt is `stale`, so a redelivered or superseded message never sees the secrets.
|
|
195
|
+
*/
|
|
196
|
+
workerSecrets(runId: string, callback: WorkerCallback): Effect.Effect<WorkerSecretsResult, Error | EventStoreTruncationError | EventStoreTrimmedError | EventStoreError, EventStoreTag>;
|
|
118
197
|
readonly listRuns: Effect.Effect<string[], EventStoreError, EventStoreTag>;
|
|
119
198
|
/** Indexed run rows. Folds a run only when its header has not been written yet. */
|
|
120
|
-
readonly listRunSummaries: Effect.Effect<readonly RunSummary[], EventStoreError | EventStoreTrimmedError | EventStoreTruncationError | SnapshotStoreError, EventStoreTag>;
|
|
121
|
-
readonly rescanTimers: Effect.Effect<number, EventStoreTruncationError | EventStoreTrimmedError | EventStoreError, EventStoreTag>;
|
|
199
|
+
readonly listRunSummaries: Effect.Effect<readonly RunSummary[], EventStoreError | EventStoreTrimmedError | EventStoreTruncationError | SnapshotStoreError | DefinitionResolveError, EventStoreTag>;
|
|
200
|
+
readonly rescanTimers: Effect.Effect<number, EventStoreTruncationError | EventStoreTrimmedError | EventStoreError | DefinitionResolveError, EventStoreTag>;
|
|
122
201
|
/** Reschedule every durable timer and effect deadline after restart or repair. */
|
|
123
|
-
readonly recoverDeadlines: Effect.Effect<number, EventStoreTruncationError | EventStoreTrimmedError | EventStoreError, EventStoreTag>;
|
|
202
|
+
readonly recoverDeadlines: Effect.Effect<number, EventStoreTruncationError | EventStoreTrimmedError | EventStoreError | DefinitionResolveError, EventStoreTag>;
|
|
124
203
|
readonly dispose: Effect.Effect<void>;
|
|
125
204
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Context, type ManagedRuntime } from 'effect';
|
|
2
|
-
import { type ComposedRegistry, type
|
|
2
|
+
import { type ComposedRegistry, type DefinitionResolver, type RunLauncher } from '@swirls/looms/core';
|
|
3
3
|
import { type RuntimeObserver } from './observer';
|
|
4
4
|
import type { RuntimeRepository } from './repository';
|
|
5
5
|
import type { RunExecutionContexts } from './run-execution-context';
|
|
@@ -8,13 +8,15 @@ import type { WakeScheduler } from './wake-scheduler';
|
|
|
8
8
|
export interface WakeEngineOptions {
|
|
9
9
|
readonly registry: ComposedRegistry;
|
|
10
10
|
readonly services: ManagedRuntime.ManagedRuntime<Context.Service.Any, never>;
|
|
11
|
-
readonly
|
|
11
|
+
readonly definitionResolver: DefinitionResolver;
|
|
12
12
|
readonly executionContexts: RunExecutionContexts;
|
|
13
13
|
readonly repository: RuntimeRepository;
|
|
14
14
|
readonly snapshotEvery: number;
|
|
15
15
|
readonly maxWakeIterations: number;
|
|
16
|
+
readonly maxInFlightWorkerEffects?: number;
|
|
16
17
|
readonly worker?: EffectWorker;
|
|
17
18
|
readonly observer?: RuntimeObserver;
|
|
18
19
|
readonly scheduler: () => WakeScheduler;
|
|
20
|
+
readonly launcher: () => RunLauncher;
|
|
19
21
|
}
|
|
20
22
|
export declare function createWakeEngine(options: WakeEngineOptions): LoomsRuntime['wake'];
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Clock, Effect, Exit, Option } from 'effect';
|
|
2
|
-
import { createEvent, EventStoreTag, foldRun, isRunTerminal, } from '@swirls/looms/core';
|
|
2
|
+
import { createEvent, deferredUntil, EventStoreTag, foldRun, isRunTerminal, } from '@swirls/looms/core';
|
|
3
|
+
import { childCloseCancellations, notifyLaunchingParent } from './child-run.js';
|
|
3
4
|
import { nextRuntimeDeadline, planDeadlineTransitions, planTerminalCancellationTransitions, } from './deadline-transition-planner.js';
|
|
4
5
|
import { DuplicateEffectDispatchError, MaxWakeIterationsError, RuntimeExecutionError, } from './errors.js';
|
|
5
6
|
import { cascadedCancellations, stripSeq, synthesizedThreadFailed, waitSatisfiedEvents, } from './helpers.js';
|
|
@@ -10,13 +11,36 @@ function currentTimeMillis() {
|
|
|
10
11
|
return Math.floor(performance.timeOrigin + performance.now());
|
|
11
12
|
}
|
|
12
13
|
export function createWakeEngine(options) {
|
|
13
|
-
const { registry, services,
|
|
14
|
+
const { registry, services, definitionResolver, executionContexts, repository, snapshotEvery, maxWakeIterations, } = options;
|
|
14
15
|
const { appendObserved, applyAppend, liveAppends, loadCursor, persistSnapshot } = repository;
|
|
15
16
|
const runCache = {
|
|
16
17
|
delete: repository.deleteCachedCursor,
|
|
17
18
|
set: repository.setCachedCursor,
|
|
18
19
|
};
|
|
19
20
|
const observe = (event) => notifyObserver(options.observer, event);
|
|
21
|
+
const closeLaunchedRuns = (store, runId, cursor) => Effect.gen(function* () {
|
|
22
|
+
let next = cursor;
|
|
23
|
+
const state = next.state;
|
|
24
|
+
if (isRunTerminal(state) && !state.childCloseSent && state.launchedRuns.length > 0) {
|
|
25
|
+
const requested = createEvent(runId, {
|
|
26
|
+
type: 'runtime.child.close.requested',
|
|
27
|
+
payload: { policy: 'terminate' },
|
|
28
|
+
threadId: state.rootThreadId,
|
|
29
|
+
origin: { type: 'system' },
|
|
30
|
+
idempotencyKey: `child-close:${runId}`,
|
|
31
|
+
});
|
|
32
|
+
const appended = yield* appendObserved(store, runId, stripSeq([requested]));
|
|
33
|
+
next = yield* applyAppend(store, runId, next, stripSeq([requested]), appended);
|
|
34
|
+
yield* Effect.forEach(state.launchedRuns, (child) => options.launcher().cancel(child.runId), {
|
|
35
|
+
concurrency: 1,
|
|
36
|
+
discard: true,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
if (isRunTerminal(next.state)) {
|
|
40
|
+
yield* notifyLaunchingParent(options.launcher(), runId, next.state);
|
|
41
|
+
}
|
|
42
|
+
return next;
|
|
43
|
+
});
|
|
20
44
|
return (runId) => Effect.gen(function* () {
|
|
21
45
|
observe({ type: 'run.wake', runId, at: currentTimeMillis() });
|
|
22
46
|
const executionContext = executionContexts.get(runId);
|
|
@@ -64,11 +88,18 @@ export function createWakeEngine(options) {
|
|
|
64
88
|
};
|
|
65
89
|
return yield* Effect.gen(function* () {
|
|
66
90
|
const store = yield* EventStoreTag;
|
|
91
|
+
const secrets = store.runSecrets ? yield* store.runSecrets.get(runId) : {};
|
|
67
92
|
const runInContext = Effect.runPromiseWith(yield* Effect.context());
|
|
68
93
|
executionContext.liveStore = store;
|
|
69
94
|
let finalState;
|
|
70
95
|
do {
|
|
71
96
|
let cursor = yield* loadCursor(store, runId);
|
|
97
|
+
const closeEvents = childCloseCancellations(cursor.state);
|
|
98
|
+
if (closeEvents.length > 0) {
|
|
99
|
+
const created = closeEvents.map((input) => createEvent(runId, input));
|
|
100
|
+
const appended = yield* appendObserved(store, runId, stripSeq(created));
|
|
101
|
+
cursor = yield* applyAppend(store, runId, cursor, stripSeq(created), appended);
|
|
102
|
+
}
|
|
72
103
|
if (isRunTerminal(cursor.state)) {
|
|
73
104
|
const now = yield* Clock.currentTimeMillis;
|
|
74
105
|
const events = planTerminalCancellationTransitions({
|
|
@@ -80,6 +111,7 @@ export function createWakeEngine(options) {
|
|
|
80
111
|
const appended = yield* appendObserved(store, runId, stripSeq(events));
|
|
81
112
|
cursor = yield* applyAppend(store, runId, cursor, stripSeq(events), appended);
|
|
82
113
|
}
|
|
114
|
+
cursor = yield* closeLaunchedRuns(store, runId, cursor);
|
|
83
115
|
const nextDeadline = nextRuntimeDeadline(cursor.state, now);
|
|
84
116
|
if (nextDeadline !== undefined) {
|
|
85
117
|
yield* options.scheduler().schedule(runId, nextDeadline);
|
|
@@ -144,6 +176,8 @@ export function createWakeEngine(options) {
|
|
|
144
176
|
registry,
|
|
145
177
|
worker: options.worker,
|
|
146
178
|
now,
|
|
179
|
+
maxInFlightWorkerEffects: options.maxInFlightWorkerEffects,
|
|
180
|
+
secrets,
|
|
147
181
|
});
|
|
148
182
|
if (workerPlans.length > 0) {
|
|
149
183
|
for (const workerPlan of workerPlans) {
|
|
@@ -151,7 +185,7 @@ export function createWakeEngine(options) {
|
|
|
151
185
|
const queuedAppend = yield* appendObserved(store, runId, stripSeq([workerPlan.queuedEvent]));
|
|
152
186
|
cursor = yield* applyAppend(store, runId, cursor, stripSeq([workerPlan.queuedEvent]), queuedAppend);
|
|
153
187
|
}
|
|
154
|
-
const dispatchEvent = yield* dispatchWorkerEffect(runId, workerPlan, options.worker);
|
|
188
|
+
const dispatchEvent = yield* dispatchWorkerEffect(runId, workerPlan, options.worker, now);
|
|
155
189
|
const dispatchAppend = yield* appendObserved(store, runId, stripSeq([dispatchEvent]));
|
|
156
190
|
cursor = yield* applyAppend(store, runId, cursor, stripSeq([dispatchEvent]), dispatchAppend);
|
|
157
191
|
}
|
|
@@ -162,7 +196,9 @@ export function createWakeEngine(options) {
|
|
|
162
196
|
: []));
|
|
163
197
|
const outstanding = state.outstandingEffects.filter((item) => {
|
|
164
198
|
const execution = state.effectExecutions[item.effectId];
|
|
165
|
-
|
|
199
|
+
const dueAt = deferredUntil(item.effect);
|
|
200
|
+
return !((dueAt !== undefined && dueAt > now + 5) ||
|
|
201
|
+
(cancellationTargets.has(item.threadId) && item.effect.type !== 'runtime.cancel') ||
|
|
166
202
|
isWorkerEffect({ runId, item, execution, registry, worker: options.worker }) ||
|
|
167
203
|
(execution?.status === 'retry_wait' &&
|
|
168
204
|
execution.nextAttemptAt !== null &&
|
|
@@ -213,12 +249,24 @@ export function createWakeEngine(options) {
|
|
|
213
249
|
const produced = yield* executeLocalEffects({
|
|
214
250
|
registry,
|
|
215
251
|
services,
|
|
216
|
-
|
|
252
|
+
definitionResolver,
|
|
217
253
|
executionContext,
|
|
218
254
|
liveAppends,
|
|
219
255
|
runInContext,
|
|
220
256
|
observe,
|
|
221
|
-
|
|
257
|
+
launcher: options.launcher(),
|
|
258
|
+
}, {
|
|
259
|
+
runId,
|
|
260
|
+
context: {
|
|
261
|
+
correlationId: cursor.state.startIdentity?.correlationId ?? runId,
|
|
262
|
+
attributes: cursor.state.attributes ?? {},
|
|
263
|
+
secrets,
|
|
264
|
+
trace: cursor.state.startIdentity?.trace ?? null,
|
|
265
|
+
},
|
|
266
|
+
outstanding,
|
|
267
|
+
attempts,
|
|
268
|
+
now,
|
|
269
|
+
});
|
|
222
270
|
if (produced.length === 0) {
|
|
223
271
|
break;
|
|
224
272
|
}
|
|
@@ -271,6 +319,9 @@ export function createWakeEngine(options) {
|
|
|
271
319
|
const appendResult = yield* appendObserved(store, runId, stripSeq([completed]));
|
|
272
320
|
cursor = yield* applyAppend(store, runId, cursor, stripSeq([completed]), appendResult);
|
|
273
321
|
}
|
|
322
|
+
if (isRunTerminal(cursor.state)) {
|
|
323
|
+
cursor = yield* closeLaunchedRuns(store, runId, cursor);
|
|
324
|
+
}
|
|
274
325
|
// Snapshot on park: the next wake (on any instance) starts from here.
|
|
275
326
|
cursor = yield* persistSnapshot(store, runId, cursor);
|
|
276
327
|
const nextDeadline = nextRuntimeDeadline(cursor.state, plannedAt);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Effect } from 'effect';
|
|
2
|
-
import { EventStoreTag, type ComposedRegistry, type
|
|
2
|
+
import { EventStoreTag, type ComposedRegistry, type WorkerCallback, type WorkerCallbackInput, type WorkerCallbackResult } from '@swirls/looms/core';
|
|
3
3
|
import type { RunIngress } from './ingress';
|
|
4
4
|
import { type RuntimeObserver } from './observer';
|
|
5
5
|
import type { RuntimeRepository } from './repository';
|
|
@@ -12,5 +12,15 @@ export interface WorkerCallbacksOptions {
|
|
|
12
12
|
readonly wake: LoomsRuntime['wake'];
|
|
13
13
|
}
|
|
14
14
|
export declare function createWorkerCallbacks(options: WorkerCallbacksOptions): {
|
|
15
|
-
workerCallback: (runId: string, callback: WorkerCallbackInput) => Effect.Effect<
|
|
15
|
+
workerCallback: (runId: string, callback: WorkerCallbackInput) => Effect.Effect<WorkerCallbackResult, import("@swirls/looms/core").DefinitionResolveError | import("./errors").DuplicateEffectDispatchError | Error | import("@swirls/looms/core").EventPayloadTooLargeError | import("@swirls/looms/core").EventStoreConflictError | import("@swirls/looms/core").EventStoreError | import("@swirls/looms/core").EventStoreFencedError | import("@swirls/looms/core").EventStoreTrimmedError | import("@swirls/looms/core").EventStoreTruncationError | import("./errors").MaxWakeIterationsError | import("./ingress").RunOverloadedError | import("./errors").RuntimeExecutionError, EventStoreTag>;
|
|
16
|
+
workerSecrets: (runId: string, callback: WorkerCallback) => Effect.Effect<{
|
|
17
|
+
status: "stale";
|
|
18
|
+
secrets?: undefined;
|
|
19
|
+
} | {
|
|
20
|
+
status: "cancelled";
|
|
21
|
+
secrets?: undefined;
|
|
22
|
+
} | {
|
|
23
|
+
status: "accepted";
|
|
24
|
+
secrets: import("@swirls/looms/core").SecretContext;
|
|
25
|
+
}, Error | import("@swirls/looms/core").EventStoreError, EventStoreTag>;
|
|
16
26
|
};
|