@swirls/looms 0.2.0 → 0.4.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 +31 -0
- package/dist/actor/host.js +4 -6
- package/dist/actor/index.d.ts +1 -1
- package/dist/actor/index.js +1 -1
- package/dist/actor/routing.d.ts +5 -0
- package/dist/actor/routing.js +14 -0
- package/dist/agent/agent-session.js +1 -0
- package/dist/agent/debugger/agent-chat.js +45 -38
- package/dist/agent/debugger/streaming-text.d.ts +6 -0
- package/dist/agent/debugger/streaming-text.js +33 -0
- package/dist/agent/debugger/summarize.js +2 -0
- package/dist/agent/effects.d.ts +30 -0
- package/dist/agent/events.d.ts +2 -0
- package/dist/agent/events.js +2 -0
- package/dist/agent/llm.js +1 -8
- package/dist/agent/module.d.ts +230 -160
- package/dist/agent/module.js +11 -7
- package/dist/agent/projections.d.ts +5 -0
- package/dist/agent/projections.js +43 -2
- package/dist/agent/scope.d.ts +2 -0
- package/dist/agent/signals.d.ts +5 -0
- package/dist/agent/signals.js +7 -0
- package/dist/agent/threads.js +35 -21
- package/dist/agent/transcript.d.ts +7 -0
- package/dist/agent/transcript.js +19 -0
- package/dist/agent/types.d.ts +10 -0
- package/dist/agent/types.js +4 -0
- package/dist/approval/debugger/approvals-view.js +5 -3
- package/dist/approval/effects.d.ts +12 -0
- package/dist/approval/module.d.ts +24 -0
- package/dist/client/client.d.ts +292 -0
- package/dist/client/client.js +17 -1
- package/dist/cloudflare/router.d.ts +1 -1
- package/dist/cloudflare/router.js +6 -8
- package/dist/core/attempts.d.ts +8 -0
- package/dist/core/attempts.js +27 -0
- package/dist/core/effects.d.ts +7 -0
- package/dist/core/effects.js +1 -0
- package/dist/core/fold.js +12 -6
- package/dist/core/fork.d.ts +35 -0
- package/dist/core/fork.js +129 -0
- package/dist/core/index.d.ts +3 -0
- package/dist/core/index.js +2 -0
- package/dist/core/module-scope.d.ts +1 -0
- package/dist/core/module.d.ts +2 -2
- package/dist/core/projection.d.ts +5 -4
- package/dist/core/projection.js +1 -0
- package/dist/core/protocol.d.ts +10 -1
- package/dist/core/protocol.js +11 -0
- package/dist/{react → core}/register.d.ts +9 -8
- package/dist/core/replay.d.ts +8 -0
- package/dist/core/snapshots.js +3 -17
- package/dist/core/state.d.ts +14 -0
- package/dist/core/state.js +25 -2
- package/dist/core/store.d.ts +1 -0
- package/dist/debugger/app/assets/index-CgDzWJg7.css +2 -0
- package/dist/debugger/app/assets/index-D--9esm9.js +22 -0
- package/dist/debugger/app/assets/routes-Dakrdusf.js +47 -0
- package/dist/debugger/app/index.html +2 -2
- package/dist/debugger/components/composer.d.ts +5 -2
- package/dist/debugger/components/composer.js +5 -4
- package/dist/debugger/components/run-tree.js +2 -5
- package/dist/debugger/events/catalog.js +1 -3
- package/dist/debugger/events/protocol-summarize.js +7 -0
- package/dist/debugger/lib/event-counts.d.ts +2 -2
- package/dist/debugger/lib/event-counts.js +5 -9
- package/dist/debugger/shell/debugger-shell.js +1 -1
- package/dist/debugger/shell/run-controls.d.ts +2 -1
- package/dist/debugger/shell/run-controls.js +40 -13
- package/dist/debugger/shell/run-search.js +1 -3
- package/dist/evaluator/effects.d.ts +45 -33
- package/dist/evaluator/module.d.ts +90 -66
- package/dist/projectors/index-model.d.ts +13 -7
- package/dist/projectors/index-model.js +21 -26
- package/dist/projectors/memory.js +19 -11
- package/dist/projectors/postgres.js +20 -19
- package/dist/projectors/schema.js +20 -18
- package/dist/projectors/sqlite.js +32 -27
- package/dist/react/derived.d.ts +26 -8
- package/dist/react/derived.js +23 -26
- package/dist/react/index.d.ts +3 -3
- package/dist/react/index.js +2 -2
- package/dist/react/materialize.js +16 -11
- package/dist/react/react.d.ts +12 -9
- package/dist/react/react.js +9 -6
- package/dist/react/store.d.ts +4 -5
- package/dist/react/tables.d.ts +3 -2
- package/dist/runtime/cancellation.js +14 -27
- package/dist/runtime/errors.d.ts +39 -0
- package/dist/runtime/errors.js +32 -0
- package/dist/runtime/helpers.d.ts +2 -0
- package/dist/runtime/helpers.js +23 -1
- package/dist/runtime/index.d.ts +1 -1
- package/dist/runtime/index.js +1 -1
- package/dist/runtime/looms.d.ts +4 -0
- package/dist/runtime/looms.js +1 -0
- package/dist/runtime/run-start.js +1 -3
- package/dist/runtime/runtime.d.ts +1 -1
- package/dist/runtime/runtime.js +45 -18
- package/dist/runtime/server/http.d.ts +2 -0
- package/dist/runtime/server/http.js +17 -2
- package/dist/runtime/server/routes/runs.js +35 -2
- package/dist/runtime/types.d.ts +8 -0
- package/dist/runtime/wake-engine.js +5 -2
- package/dist/s2/snapshot-store.js +2 -6
- package/dist/workflow/effects.d.ts +24 -0
- package/dist/workflow/effects.js +1 -0
- package/dist/workflow/module.d.ts +48 -0
- package/package.json +2 -2
- package/dist/debugger/app/assets/index-B3Ah8WAE.css +0 -2
- package/dist/debugger/app/assets/index-Qb371t_O.js +0 -22
- package/dist/debugger/app/assets/routes-CjtEHgPQ.js +0 -47
- /package/dist/{react → core}/register.js +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isJsonObject, isJsonString } from '@swirls/looms/core';
|
|
1
|
+
import { completedRunStatus, isJsonObject, isJsonString, } from '@swirls/looms/core';
|
|
2
2
|
import { emptyTables } from './tables.js';
|
|
3
3
|
function payloadObject(event) {
|
|
4
4
|
return isJsonObject(event.payload) ? event.payload : {};
|
|
@@ -7,6 +7,16 @@ function readString(obj, key) {
|
|
|
7
7
|
const value = obj[key];
|
|
8
8
|
return isJsonString(value) ? value : undefined;
|
|
9
9
|
}
|
|
10
|
+
function setRunStatus(tables, runId, status) {
|
|
11
|
+
const existing = tables.runs.get(runId);
|
|
12
|
+
tables.runs.set(runId, {
|
|
13
|
+
runId,
|
|
14
|
+
status,
|
|
15
|
+
rootThreadId: existing?.rootThreadId ?? null,
|
|
16
|
+
kind: existing?.kind ?? null,
|
|
17
|
+
definitionName: existing?.definitionName ?? null,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
10
20
|
export function materializeEvents(events, from = emptyTables()) {
|
|
11
21
|
const tables = {
|
|
12
22
|
runs: from.runs,
|
|
@@ -28,17 +38,12 @@ export function materializeEvents(events, from = emptyTables()) {
|
|
|
28
38
|
tables.runs.set(event.runId, next);
|
|
29
39
|
break;
|
|
30
40
|
}
|
|
31
|
-
case 'runtime.run.completed':
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
rootThreadId: existing?.rootThreadId ?? null,
|
|
37
|
-
kind: existing?.kind ?? null,
|
|
38
|
-
definitionName: existing?.definitionName ?? null,
|
|
39
|
-
});
|
|
41
|
+
case 'runtime.run.completed':
|
|
42
|
+
setRunStatus(tables, event.runId, completedRunStatus(payload));
|
|
43
|
+
break;
|
|
44
|
+
case 'runtime.run.cancelled':
|
|
45
|
+
setRunStatus(tables, event.runId, 'cancelled');
|
|
40
46
|
break;
|
|
41
|
-
}
|
|
42
47
|
case 'runtime.thread.started': {
|
|
43
48
|
const threadId = readString(payload, 'threadId') ?? event.threadId;
|
|
44
49
|
const kind = readString(payload, 'kind');
|
package/dist/react/react.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { type ReactNode } from 'react';
|
|
2
|
-
import { type ProjectionDefinition, type ThreadTree } from '@swirls/looms/core';
|
|
3
|
-
import {
|
|
4
|
-
import type { AnyEventEnvelope, RegisteredEvent } from './register';
|
|
2
|
+
import { type AnyEventEnvelope, type ProjectionDefinition, type RegisteredEvent, type RunStatus, type ThreadTree } from '@swirls/looms/core';
|
|
3
|
+
import { defineLiveFold, foldLive, type LiveFoldDefinition } from './derived';
|
|
5
4
|
import { createLoomsStore, type CoalesceOption, type LoomsClientStore, type LoomsConnectionStatus } from './store';
|
|
6
5
|
export interface LoomsProviderProps {
|
|
7
6
|
children: ReactNode;
|
|
@@ -30,7 +29,11 @@ export declare function useProjection<S, TEvent extends AnyEventEnvelope = Regis
|
|
|
30
29
|
/** Subscribe to arbitrary store derivations with an equality check. */
|
|
31
30
|
export declare function useRunSelector<Selected, TEvent extends AnyEventEnvelope = RegisteredEvent>(store: LoomsClientStore<TEvent>, selector: (store: LoomsClientStore<TEvent>) => Selected, isEqual?: (a: Selected, b: Selected) => boolean): Selected;
|
|
32
31
|
export interface RunSummary {
|
|
33
|
-
readonly status:
|
|
32
|
+
readonly status: RunStatus;
|
|
33
|
+
/** True once the run is completed, failed, or cancelled and accepts no new signals. */
|
|
34
|
+
readonly finished: boolean;
|
|
35
|
+
/** True when the run failed or was cancelled, so a fork can continue it from where it stopped. */
|
|
36
|
+
readonly resumable: boolean;
|
|
34
37
|
readonly kind: string | undefined;
|
|
35
38
|
readonly definitionName: string | undefined;
|
|
36
39
|
readonly rootThreadId: string | undefined;
|
|
@@ -51,10 +54,10 @@ export declare function useEventCounts<TEvent extends AnyEventEnvelope = Registe
|
|
|
51
54
|
export declare function useEventBySeq<TEvent extends AnyEventEnvelope = RegisteredEvent>(store: LoomsClientStore<TEvent>, seq: number | undefined): TEvent | undefined;
|
|
52
55
|
/** Subscribe to the latest event of a given type. */
|
|
53
56
|
export declare function useLatestEvent<TEvent extends AnyEventEnvelope = RegisteredEvent>(store: LoomsClientStore<TEvent>, type: string): TEvent | undefined;
|
|
54
|
-
/** Subscribe to a
|
|
55
|
-
export declare function
|
|
57
|
+
/** Subscribe to a live fold, which includes ephemeral events. */
|
|
58
|
+
export declare function useLiveFold<S, TEvent extends AnyEventEnvelope = RegisteredEvent>(store: LoomsClientStore<TEvent>, fold: LiveFoldDefinition<S, TEvent>): S;
|
|
56
59
|
export { createLoomsStore };
|
|
57
|
-
export {
|
|
60
|
+
export { defineLiveFold, foldLive };
|
|
58
61
|
export type { LoomsClientStore, LoomsClientStoreOptions, LoomsConnectionStatus, LoomsStoreQuery, CoalesceOption, StoreListener, } from './store';
|
|
59
|
-
export type {
|
|
60
|
-
export type { LoomsRegister, RegisteredEvent, AnyEventEnvelope } from '
|
|
62
|
+
export type { LiveFoldDefinition } from './derived';
|
|
63
|
+
export type { LoomsRegister, RegisteredEvent, AnyEventEnvelope } from '@swirls/looms/core';
|
package/dist/react/react.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Effect, Fiber } from 'effect';
|
|
2
2
|
import { createContext, createElement, useCallback, useContext, useEffect, useMemo, useRef, useSyncExternalStore, } from 'react';
|
|
3
|
-
import { threadTree, toThreadTree } from '@swirls/looms/core';
|
|
4
|
-
import {
|
|
3
|
+
import { isTerminalStatus, threadTree, toThreadTree, } from '@swirls/looms/core';
|
|
4
|
+
import { defineLiveFold, foldLive } from './derived.js';
|
|
5
5
|
import { createLoomsStore, } from './store.js';
|
|
6
6
|
const LoomsContext = createContext({ endpoint: '' });
|
|
7
7
|
const GRACE_MS = 5_000;
|
|
@@ -125,8 +125,11 @@ function shallowEqualSummary(a, b) {
|
|
|
125
125
|
export function useRunSummary(store) {
|
|
126
126
|
const selector = useCallback((s) => {
|
|
127
127
|
const run = s.getState().runs.get(s.storeId);
|
|
128
|
+
const status = run?.status ?? 'running';
|
|
128
129
|
return {
|
|
129
|
-
status
|
|
130
|
+
status,
|
|
131
|
+
finished: isTerminalStatus(status),
|
|
132
|
+
resumable: status === 'failed' || status === 'cancelled',
|
|
130
133
|
kind: run?.kind ?? undefined,
|
|
131
134
|
definitionName: run?.definitionName ?? undefined,
|
|
132
135
|
rootThreadId: run?.rootThreadId ?? undefined,
|
|
@@ -162,10 +165,10 @@ export function useLatestEvent(store, type) {
|
|
|
162
165
|
const selector = useCallback((s) => s.query.latest(type), [type]);
|
|
163
166
|
return useRunSelector(store, selector);
|
|
164
167
|
}
|
|
165
|
-
/** Subscribe to a
|
|
166
|
-
export function
|
|
168
|
+
/** Subscribe to a live fold, which includes ephemeral events. */
|
|
169
|
+
export function useLiveFold(store, fold) {
|
|
167
170
|
const getSnapshot = useCallback(() => store.fold(fold), [store, fold]);
|
|
168
171
|
return useStoreSnapshot(store, getSnapshot);
|
|
169
172
|
}
|
|
170
173
|
export { createLoomsStore };
|
|
171
|
-
export {
|
|
174
|
+
export { defineLiveFold, foldLive };
|
package/dist/react/store.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { type EventInput, type ProjectionDefinition } from '@swirls/looms/core';
|
|
2
|
-
import { type
|
|
1
|
+
import { type AnyEventEnvelope, type EventInput, type ProjectionDefinition, type RegisteredEvent } from '@swirls/looms/core';
|
|
2
|
+
import { type LiveFoldDefinition } from './derived';
|
|
3
3
|
import { type CoalesceOption } from './notify';
|
|
4
|
-
import type { AnyEventEnvelope, RegisteredEvent } from './register';
|
|
5
4
|
import { type MaterializedTables } from './tables';
|
|
6
5
|
export type { CoalesceOption } from './notify';
|
|
7
6
|
/** Fired after the store version bumps. Re-read via `events()` / `getState()` / `query`. */
|
|
@@ -30,7 +29,7 @@ export interface LoomsStoreQuery<TEvent extends AnyEventEnvelope = RegisteredEve
|
|
|
30
29
|
bySeq: (seq: number) => TEvent | undefined;
|
|
31
30
|
startedAt: () => number | undefined;
|
|
32
31
|
latest: (type: string) => TEvent | undefined;
|
|
33
|
-
fold: <S>(def:
|
|
32
|
+
fold: <S>(def: LiveFoldDefinition<S, TEvent>) => S;
|
|
34
33
|
}
|
|
35
34
|
export interface LoomsClientStore<TEvent extends AnyEventEnvelope = RegisteredEvent> {
|
|
36
35
|
storeId: string;
|
|
@@ -43,7 +42,7 @@ export interface LoomsClientStore<TEvent extends AnyEventEnvelope = RegisteredEv
|
|
|
43
42
|
query: LoomsStoreQuery<TEvent>;
|
|
44
43
|
subscribe: (listener: StoreListener) => () => void;
|
|
45
44
|
project<S>(definition: ProjectionDefinition<S>): S;
|
|
46
|
-
fold<S>(definition:
|
|
45
|
+
fold<S>(definition: LiveFoldDefinition<S, TEvent>): S;
|
|
47
46
|
flush: () => void;
|
|
48
47
|
commit: (event: EventInput | readonly EventInput[]) => Promise<void>;
|
|
49
48
|
sync: () => Promise<void>;
|
package/dist/react/tables.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import type { RunStatus, ThreadStatus } from '@swirls/looms/core';
|
|
1
2
|
export interface RunRow {
|
|
2
3
|
runId: string;
|
|
3
|
-
status:
|
|
4
|
+
status: RunStatus;
|
|
4
5
|
rootThreadId: string | null;
|
|
5
6
|
kind: string | null;
|
|
6
7
|
definitionName: string | null;
|
|
@@ -11,7 +12,7 @@ export interface ThreadRow {
|
|
|
11
12
|
kind: string;
|
|
12
13
|
definitionName: string;
|
|
13
14
|
parentThreadId: string | null;
|
|
14
|
-
status:
|
|
15
|
+
status: ThreadStatus;
|
|
15
16
|
}
|
|
16
17
|
/** Materialized run/thread rows. Event log lives only in `store.events()` / EventIndex. */
|
|
17
18
|
export interface MaterializedTables {
|
|
@@ -1,29 +1,15 @@
|
|
|
1
1
|
import { Clock, Effect } from 'effect';
|
|
2
|
-
import { createEvent, EventStoreTag, foldRun, } from '@swirls/looms/core';
|
|
2
|
+
import { createEvent, descendantThreadIds, EventStoreTag, foldRun, } from '@swirls/looms/core';
|
|
3
3
|
import { RuntimeExecutionError } from './errors.js';
|
|
4
4
|
import { waitSatisfiedEvents, workerTask } from './helpers.js';
|
|
5
5
|
import { notifyObserver } from './observer.js';
|
|
6
6
|
import { isWorkerEffect } from './worker-effect-dispatch.js';
|
|
7
|
-
function descendantThreadIds(state, targetThreadId) {
|
|
8
|
-
return Object.values(state.threads)
|
|
9
|
-
.filter((record) => {
|
|
10
|
-
let parentThreadId = record.parentThreadId;
|
|
11
|
-
while (parentThreadId) {
|
|
12
|
-
if (parentThreadId === targetThreadId) {
|
|
13
|
-
return true;
|
|
14
|
-
}
|
|
15
|
-
parentThreadId = state.threads[parentThreadId]?.parentThreadId ?? null;
|
|
16
|
-
}
|
|
17
|
-
return false;
|
|
18
|
-
})
|
|
19
|
-
.map((record) => record.threadId);
|
|
20
|
-
}
|
|
21
7
|
/**
|
|
22
8
|
* Cancel a thread and its descendants. Worker attempts in flight are asked to stop and must
|
|
23
9
|
* acknowledge; attempts waiting to retry were never handed out and are cancelled outright.
|
|
24
10
|
*/
|
|
25
11
|
function planCancellation(input) {
|
|
26
|
-
const { runId, state, registry, now } = input;
|
|
12
|
+
const { runId, seq, state, registry, now } = input;
|
|
27
13
|
const targetThreadId = input.threadId ?? state.rootThreadId ?? '';
|
|
28
14
|
const idempotencyKey = `cancel:${targetThreadId}`;
|
|
29
15
|
if (state.processedIdempotencyKeys.includes(idempotencyKey)) {
|
|
@@ -65,18 +51,18 @@ function planCancellation(input) {
|
|
|
65
51
|
idempotencyKey,
|
|
66
52
|
}));
|
|
67
53
|
}
|
|
68
|
-
const threadEvents = [
|
|
69
|
-
...descendants.map((_, index) => descendants[descendants.length - 1 - index]),
|
|
70
|
-
targetThreadId,
|
|
71
|
-
]
|
|
54
|
+
const threadEvents = [...descendants.toReversed(), targetThreadId]
|
|
72
55
|
.filter((threadId) => threadId.length > 0)
|
|
73
|
-
.map((threadId) =>
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
56
|
+
.map((threadId) => {
|
|
57
|
+
const payload = { threadId, reason: 'cancelled' };
|
|
58
|
+
return createEvent(runId, {
|
|
59
|
+
type: 'runtime.thread.cancelled',
|
|
60
|
+
payload: threadId === targetThreadId ? { ...payload, resumeSeq: seq } : payload,
|
|
61
|
+
threadId,
|
|
62
|
+
origin: { type: 'external' },
|
|
63
|
+
idempotencyKey,
|
|
64
|
+
});
|
|
65
|
+
});
|
|
80
66
|
const batch = [...effectEvents, ...threadEvents];
|
|
81
67
|
const folded = foldRun(batch, registry, { runId, initial: state });
|
|
82
68
|
const satisfied = waitSatisfiedEvents(folded, batch).map((event) => createEvent(runId, event));
|
|
@@ -91,6 +77,7 @@ export function createCancellation(options) {
|
|
|
91
77
|
return yield* repository.commit(store, runId, (current) => {
|
|
92
78
|
const plan = planCancellation({
|
|
93
79
|
runId,
|
|
80
|
+
seq: current.seq,
|
|
94
81
|
state: current.state,
|
|
95
82
|
threadId,
|
|
96
83
|
registry,
|
package/dist/runtime/errors.d.ts
CHANGED
|
@@ -19,6 +19,45 @@ export declare class StartRunConflictError extends StartRunConflictError_base<{
|
|
|
19
19
|
}> {
|
|
20
20
|
constructor(runId: string, requested: RunStartIdentity, existing: DurableRunStartIdentity, mismatches: readonly (keyof RunStartIdentity)[]);
|
|
21
21
|
}
|
|
22
|
+
declare const RunTerminalError_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 & {
|
|
23
|
+
readonly _tag: "RunTerminalError";
|
|
24
|
+
} & Readonly<A>;
|
|
25
|
+
export declare class RunTerminalError extends RunTerminalError_base<{
|
|
26
|
+
readonly runId: string;
|
|
27
|
+
readonly status: string;
|
|
28
|
+
readonly message: string;
|
|
29
|
+
}> {
|
|
30
|
+
constructor(runId: string, status: string);
|
|
31
|
+
}
|
|
32
|
+
declare const RunNotFoundError_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 & {
|
|
33
|
+
readonly _tag: "RunNotFoundError";
|
|
34
|
+
} & Readonly<A>;
|
|
35
|
+
export declare class RunNotFoundError extends RunNotFoundError_base<{
|
|
36
|
+
readonly runId: string;
|
|
37
|
+
readonly message: string;
|
|
38
|
+
}> {
|
|
39
|
+
constructor(runId: string);
|
|
40
|
+
}
|
|
41
|
+
declare const RunNotFinishedError_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 & {
|
|
42
|
+
readonly _tag: "RunNotFinishedError";
|
|
43
|
+
} & Readonly<A>;
|
|
44
|
+
export declare class RunNotFinishedError extends RunNotFinishedError_base<{
|
|
45
|
+
readonly runId: string;
|
|
46
|
+
readonly status: string;
|
|
47
|
+
readonly message: string;
|
|
48
|
+
}> {
|
|
49
|
+
constructor(runId: string, status: string);
|
|
50
|
+
}
|
|
51
|
+
declare const ForkConflictError_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 & {
|
|
52
|
+
readonly _tag: "ForkConflictError";
|
|
53
|
+
} & Readonly<A>;
|
|
54
|
+
export declare class ForkConflictError extends ForkConflictError_base<{
|
|
55
|
+
readonly runId: string;
|
|
56
|
+
readonly sourceRunId: string;
|
|
57
|
+
readonly message: string;
|
|
58
|
+
}> {
|
|
59
|
+
constructor(runId: string, sourceRunId: string);
|
|
60
|
+
}
|
|
22
61
|
declare const DuplicateEffectDispatchError_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 & {
|
|
23
62
|
readonly _tag: "DuplicateEffectDispatchError";
|
|
24
63
|
} & Readonly<A>;
|
package/dist/runtime/errors.js
CHANGED
|
@@ -11,6 +11,38 @@ export class StartRunConflictError extends Data.TaggedError('StartRunConflictErr
|
|
|
11
11
|
this.name = 'StartRunConflictError';
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
|
+
export class RunTerminalError extends Data.TaggedError('RunTerminalError') {
|
|
15
|
+
constructor(runId, status) {
|
|
16
|
+
super({ runId, status, message: `Run "${runId}" is ${status} and accepts no new events` });
|
|
17
|
+
this.name = 'RunTerminalError';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export class RunNotFoundError extends Data.TaggedError('RunNotFoundError') {
|
|
21
|
+
constructor(runId) {
|
|
22
|
+
super({ runId, message: `Run "${runId}" does not exist` });
|
|
23
|
+
this.name = 'RunNotFoundError';
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export class RunNotFinishedError extends Data.TaggedError('RunNotFinishedError') {
|
|
27
|
+
constructor(runId, status) {
|
|
28
|
+
super({
|
|
29
|
+
runId,
|
|
30
|
+
status,
|
|
31
|
+
message: `Run "${runId}" is ${status}; only finished runs can be forked`,
|
|
32
|
+
});
|
|
33
|
+
this.name = 'RunNotFinishedError';
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export class ForkConflictError extends Data.TaggedError('ForkConflictError') {
|
|
37
|
+
constructor(runId, sourceRunId) {
|
|
38
|
+
super({
|
|
39
|
+
runId,
|
|
40
|
+
sourceRunId,
|
|
41
|
+
message: `Run "${runId}" already exists and is not a fork of "${sourceRunId}"`,
|
|
42
|
+
});
|
|
43
|
+
this.name = 'ForkConflictError';
|
|
44
|
+
}
|
|
45
|
+
}
|
|
14
46
|
export class DuplicateEffectDispatchError extends Data.TaggedError('DuplicateEffectDispatchError') {
|
|
15
47
|
constructor(runId, effectId, threadId) {
|
|
16
48
|
super({
|
|
@@ -43,6 +43,8 @@ export declare function threadStartedEvents(definitions: ReadonlyMap<string, Reg
|
|
|
43
43
|
parentThreadId: string | null;
|
|
44
44
|
}): Effect.Effect<ReadonlyArray<EventInput>, UnknownDefinitionError>;
|
|
45
45
|
export declare function synthesizedThreadFailed(before: RunState, after: RunState, written: readonly EventEnvelope[]): EventInput[];
|
|
46
|
+
/** Cancel whatever is still live below each thread `written` cancels, deepest first. */
|
|
47
|
+
export declare function cascadedCancellations(state: RunState, written: readonly EventEnvelope[]): EventInput[];
|
|
46
48
|
export declare function waitSatisfiedEvents(state: RunState, events: readonly EventEnvelope[]): EventInput[];
|
|
47
49
|
export declare function createEffectFailedEvent(runId: string, item: {
|
|
48
50
|
effectId: string;
|
package/dist/runtime/helpers.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Data, Effect, Layer, Predicate } from 'effect';
|
|
2
|
-
import { asJson, createEvent, definitionKey, makeMemorySnapshotStore, matchingWaits, snapshotStoreOf, validateEventInput, validateEventsEffect, validateInputEffect, withSnapshotStore, InvalidEventError, } from '@swirls/looms/core';
|
|
2
|
+
import { asJson, createEvent, definitionKey, descendantThreadIds, isTerminalStatus, makeMemorySnapshotStore, matchingWaits, snapshotStoreOf, validateEventInput, validateEventsEffect, validateInputEffect, withSnapshotStore, InvalidEventError, } from '@swirls/looms/core';
|
|
3
3
|
export function workerTask(runId, item, attempt) {
|
|
4
4
|
return {
|
|
5
5
|
runId,
|
|
@@ -180,6 +180,28 @@ export function synthesizedThreadFailed(before, after, written) {
|
|
|
180
180
|
}
|
|
181
181
|
return out;
|
|
182
182
|
}
|
|
183
|
+
/** Cancel whatever is still live below each thread `written` cancels, deepest first. */
|
|
184
|
+
export function cascadedCancellations(state, written) {
|
|
185
|
+
const cancelled = written.flatMap((event) => {
|
|
186
|
+
if (event.type !== 'runtime.thread.cancelled') {
|
|
187
|
+
return [];
|
|
188
|
+
}
|
|
189
|
+
const payload = Predicate.isObject(event.payload) ? event.payload : {};
|
|
190
|
+
const threadId = Predicate.isString(payload.threadId) ? payload.threadId : event.threadId;
|
|
191
|
+
return threadId ? [threadId] : [];
|
|
192
|
+
});
|
|
193
|
+
const live = new Set(cancelled
|
|
194
|
+
.flatMap((threadId) => descendantThreadIds(state, threadId))
|
|
195
|
+
.filter((threadId) => {
|
|
196
|
+
const record = state.threads[threadId];
|
|
197
|
+
return record !== undefined && !isTerminalStatus(record.status);
|
|
198
|
+
}));
|
|
199
|
+
return [...live].toReversed().map((threadId) => ({
|
|
200
|
+
type: 'runtime.thread.cancelled',
|
|
201
|
+
payload: { threadId, reason: 'cancelled' },
|
|
202
|
+
threadId,
|
|
203
|
+
}));
|
|
204
|
+
}
|
|
183
205
|
export function waitSatisfiedEvents(state, events) {
|
|
184
206
|
const produced = [];
|
|
185
207
|
const remaining = { ...state.waits };
|
package/dist/runtime/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { createRuntime, createTimeoutScheduler, DuplicateEffectDispatchError, MaxWakeIterationsError, RunOverloadedError, StartRunConflictError, type CreateRuntimeOptions, type EffectWorker, type EffectWorkerTask, type LoomsRuntime, type RunOperationalStatus, type RegisteredDefinition, type StartRunArgs, type WakeError, type WakeScheduler, type WorkerCallback, type WorkerCallbackInput, } from './runtime';
|
|
1
|
+
export { createRuntime, createTimeoutScheduler, DuplicateEffectDispatchError, ForkConflictError, MaxWakeIterationsError, RunNotFinishedError, RunNotFoundError, RunOverloadedError, RunTerminalError, StartRunConflictError, type CreateRuntimeOptions, type EffectWorker, type EffectWorkerTask, type LoomsRuntime, type RunOperationalStatus, type RegisteredDefinition, type StartRunArgs, type WakeError, type WakeScheduler, type WorkerCallback, type WorkerCallbackInput, } from './runtime';
|
|
2
2
|
export { createLooms, type Looms, type CreateLoomsOptions, type StartResult } from './looms';
|
|
3
3
|
export { serveHttp, createFetchHandler, isLoomsApiPath, type FetchHandlerOptions, authAllowed, authDenied, bearerAuth, defaultAuthorize, type AuthDecision, type AuthorizedAccess, type Authorize, type BearerTokens, type RouteAccess, type RouteInfo, type RunningServer, type LoomsFetchResult, } from './server';
|
|
4
4
|
export { createEventStreamResponse, type EventStreamOptions } from './sse';
|
package/dist/runtime/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { createRuntime, createTimeoutScheduler, DuplicateEffectDispatchError, MaxWakeIterationsError, RunOverloadedError, StartRunConflictError, } from './runtime.js';
|
|
1
|
+
export { createRuntime, createTimeoutScheduler, DuplicateEffectDispatchError, ForkConflictError, MaxWakeIterationsError, RunNotFinishedError, RunNotFoundError, RunOverloadedError, RunTerminalError, StartRunConflictError, } from './runtime.js';
|
|
2
2
|
export { createLooms } from './looms.js';
|
|
3
3
|
export { serveHttp, createFetchHandler, isLoomsApiPath, authAllowed, authDenied, bearerAuth, defaultAuthorize, } from './server.js';
|
|
4
4
|
export { createEventStreamResponse } from './sse.js';
|
package/dist/runtime/looms.d.ts
CHANGED
|
@@ -89,6 +89,10 @@ export interface Looms {
|
|
|
89
89
|
project<S>(runId: string, definition: ProjectionDefinition<S>): Promise<S>;
|
|
90
90
|
replayTo(runId: string, seq: number): Promise<ReplayStep | null>;
|
|
91
91
|
cancel(runId: string, threadId?: string): Promise<RunState>;
|
|
92
|
+
/** Start a new run that continues where a finished run ended. */
|
|
93
|
+
fork(sourceRunId: string, options?: {
|
|
94
|
+
runId?: string;
|
|
95
|
+
}): Promise<StartResult>;
|
|
92
96
|
workerCallback(runId: string, input: WorkerCallbackInput): Promise<RunState>;
|
|
93
97
|
fetch(req: Request): Promise<Response | null>;
|
|
94
98
|
serve(options?: {
|
package/dist/runtime/looms.js
CHANGED
|
@@ -98,6 +98,7 @@ export function createLooms(options = {}) {
|
|
|
98
98
|
project: (runId, definition) => runEffect((i) => i.runtime.project(runId, definition)),
|
|
99
99
|
replayTo: (runId, seq) => runEffect((i) => i.runtime.replayTo(runId, seq)),
|
|
100
100
|
cancel: (runId, threadId) => runEffect((i) => i.runtime.cancel(runId, threadId)),
|
|
101
|
+
fork: (sourceRunId, forkOpts) => runEffect((i) => i.runtime.fork(sourceRunId, forkOpts)),
|
|
101
102
|
workerCallback: (runId, input) => runEffect((i) => i.runtime.workerCallback(runId, input)),
|
|
102
103
|
fetch: (req) => {
|
|
103
104
|
if (!handlesPath(new URL(req.url).pathname, debuggerMount)) {
|
|
@@ -5,9 +5,7 @@ function canonicalJson(value) {
|
|
|
5
5
|
return `[${value.map(canonicalJson).join(',')}]`;
|
|
6
6
|
}
|
|
7
7
|
if (Predicate.isObject(value)) {
|
|
8
|
-
const keys = Object.keys(value);
|
|
9
|
-
// oxlint-disable-next-line unicorn/no-array-sort
|
|
10
|
-
keys.sort();
|
|
8
|
+
const keys = Object.keys(value).toSorted();
|
|
11
9
|
return `{${keys
|
|
12
10
|
.map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key] ?? null)}`)
|
|
13
11
|
.join(',')}}`;
|
|
@@ -2,7 +2,7 @@ import { type AnyRuntimeModule } from '@swirls/looms/core';
|
|
|
2
2
|
export type { RegisteredDefinition } from '@swirls/looms/core';
|
|
3
3
|
export type { WakeScheduler } from './wake-scheduler';
|
|
4
4
|
export { createTimeoutScheduler } from './wake-scheduler';
|
|
5
|
-
export { DuplicateEffectDispatchError, MaxWakeIterationsError, StartRunConflictError, } from './errors';
|
|
5
|
+
export { DuplicateEffectDispatchError, ForkConflictError, MaxWakeIterationsError, RunNotFinishedError, RunNotFoundError, RunTerminalError, StartRunConflictError, } from './errors';
|
|
6
6
|
export { RunOverloadedError } from './ingress';
|
|
7
7
|
export type { CreateRuntimeOptions, EffectWorker, EffectWorkerTask, LoomsRuntime, RunOperationalStatus, StartResult, StartRunArgs, WakeError, } from './types';
|
|
8
8
|
export type { WorkerCallback, WorkerCallbackInput } from '@swirls/looms/core';
|
package/dist/runtime/runtime.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Clock, Effect, ManagedRuntime, Predicate } from 'effect';
|
|
2
|
-
import { composeModules, createEvent, createThreadId, createRunId, DEFAULT_DEFINITION_VERSION, DEFAULT_SNAPSHOT_EVERY, definitionKey, EventStoreTag, foldRun, project, replayTo, summarizeRun, } from '@swirls/looms/core';
|
|
2
|
+
import { composeModules, createEvent, createThreadId, createRunId, DEFAULT_DEFINITION_VERSION, DEFAULT_SNAPSHOT_EVERY, definitionKey, descendantThreadIds, EventStoreTag, foldRun, isRunTerminal, planFork, project, replayTo, summarizeRun, } from '@swirls/looms/core';
|
|
3
3
|
import { createCancellation } from './cancellation.js';
|
|
4
4
|
import { nextRuntimeDeadline } from './deadline-transition-planner.js';
|
|
5
|
-
import { RuntimeExecutionError } from './errors.js';
|
|
5
|
+
import { ForkConflictError, RunNotFinishedError, RunNotFoundError, RunTerminalError, RuntimeExecutionError, } from './errors.js';
|
|
6
6
|
import { admitExternalEvents, moduleServices, resolveSnapshotStore, RunCursorCache, threadStartedEvents, UnknownDefinitionError, validateAndCreateEvents, waitSatisfiedEvents, } from './helpers.js';
|
|
7
7
|
import { createRunIngress } from './ingress.js';
|
|
8
8
|
import { notifyObserver } from './observer.js';
|
|
@@ -13,26 +13,21 @@ import { createWakeEngine } from './wake-engine.js';
|
|
|
13
13
|
import { createTimeoutScheduler } from './wake-scheduler.js';
|
|
14
14
|
import { createWorkerCallbacks } from './worker-callbacks.js';
|
|
15
15
|
export { createTimeoutScheduler } from './wake-scheduler.js';
|
|
16
|
-
export { DuplicateEffectDispatchError, MaxWakeIterationsError, StartRunConflictError, } from './errors.js';
|
|
16
|
+
export { DuplicateEffectDispatchError, ForkConflictError, MaxWakeIterationsError, RunNotFinishedError, RunNotFoundError, RunTerminalError, StartRunConflictError, } from './errors.js';
|
|
17
17
|
export { RunOverloadedError } from './ingress.js';
|
|
18
18
|
function currentTimeMillis() {
|
|
19
19
|
return Math.floor(performance.timeOrigin + performance.now());
|
|
20
20
|
}
|
|
21
|
-
/** Threads whose in-process effects an admitted signal interrupts. */
|
|
22
|
-
function interruptedThreadIds(events, rules) {
|
|
23
|
-
const
|
|
24
|
-
for (const event of events) {
|
|
21
|
+
/** Threads whose in-process effects an admitted signal interrupts: each named one and its subtree. */
|
|
22
|
+
function interruptedThreadIds(events, rules, state) {
|
|
23
|
+
const named = events.flatMap((event) => {
|
|
25
24
|
const payload = Predicate.isObject(event.payload) ? event.payload : {};
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
return threadIds;
|
|
25
|
+
const cancelled = event.type === 'runtime.thread.cancel.requested' && Predicate.isString(payload.threadId)
|
|
26
|
+
? [payload.threadId]
|
|
27
|
+
: [];
|
|
28
|
+
return [...cancelled, ...rules.flatMap((rule) => rule(event))];
|
|
29
|
+
});
|
|
30
|
+
return new Set(named.flatMap((threadId) => [threadId, ...descendantThreadIds(state, threadId)]));
|
|
36
31
|
}
|
|
37
32
|
const DEFAULT_RUN_CACHE_SIZE = 0;
|
|
38
33
|
const RESCAN_CONCURRENCY = 16;
|
|
@@ -83,6 +78,9 @@ export function createRuntime(options) {
|
|
|
83
78
|
...input,
|
|
84
79
|
idempotencyKey: input.idempotencyKey ?? signalKey,
|
|
85
80
|
})), { origin: { type: 'external' } });
|
|
81
|
+
if (isRunTerminal(current.state)) {
|
|
82
|
+
return yield* new RunTerminalError(runId, current.state.status);
|
|
83
|
+
}
|
|
86
84
|
const folded = foldRun(batch, registry, { runId, initial: current.state });
|
|
87
85
|
const satisfiedEvents = waitSatisfiedEvents(folded, batch).map((input) => createEvent(runId, input));
|
|
88
86
|
return {
|
|
@@ -92,7 +90,7 @@ export function createRuntime(options) {
|
|
|
92
90
|
};
|
|
93
91
|
}));
|
|
94
92
|
if (committed.append) {
|
|
95
|
-
executionContexts.abort(runId, interruptedThreadIds(events, registry.interruptRules), 'Effect interrupted');
|
|
93
|
+
executionContexts.abort(runId, interruptedThreadIds(events, registry.interruptRules, committed.value), 'Effect interrupted');
|
|
96
94
|
}
|
|
97
95
|
return committed.value;
|
|
98
96
|
});
|
|
@@ -295,6 +293,35 @@ export function createRuntime(options) {
|
|
|
295
293
|
},
|
|
296
294
|
signal: (runId, events, signalOpts) => admitExternalEvents(events).pipe(Effect.flatMap((admitted) => withRunIngress(runId, signalAdmitted(runId, admitted, signalOpts))), Effect.andThen(runtime.wake(runId))),
|
|
297
295
|
cancel: cancellation,
|
|
296
|
+
fork: (sourceRunId, forkOptions) => {
|
|
297
|
+
const runId = forkOptions?.runId ?? createRunId();
|
|
298
|
+
return withRunIngress(runId, Effect.gen(function* () {
|
|
299
|
+
const store = yield* EventStoreTag;
|
|
300
|
+
const source = yield* loadCursor(store, sourceRunId);
|
|
301
|
+
if (source.seq === 0) {
|
|
302
|
+
return yield* new RunNotFoundError(sourceRunId);
|
|
303
|
+
}
|
|
304
|
+
if (!isRunTerminal(source.state)) {
|
|
305
|
+
return yield* new RunNotFinishedError(sourceRunId, source.state.status);
|
|
306
|
+
}
|
|
307
|
+
const plan = yield* planFork({
|
|
308
|
+
sourceRunId,
|
|
309
|
+
runId,
|
|
310
|
+
events: yield* store.read(sourceRunId),
|
|
311
|
+
registry,
|
|
312
|
+
});
|
|
313
|
+
// Appending at tail 0 numbers the copied events 1..n, the seqs they had in the source.
|
|
314
|
+
yield* commit(store, runId, (existing) => {
|
|
315
|
+
if (existing.seq === 0) {
|
|
316
|
+
return Effect.succeed({ events: plan.events, value: undefined });
|
|
317
|
+
}
|
|
318
|
+
return existing.state.forkedFrom?.runId === sourceRunId
|
|
319
|
+
? Effect.succeed({ events: [], value: undefined })
|
|
320
|
+
: Effect.fail(new ForkConflictError(runId, sourceRunId));
|
|
321
|
+
});
|
|
322
|
+
return plan.rootThreadId;
|
|
323
|
+
})).pipe(Effect.flatMap((threadId) => runtime.wake(runId).pipe(Effect.map((state) => ({ runId, threadId, state })))));
|
|
324
|
+
},
|
|
298
325
|
wake: wakeEngine,
|
|
299
326
|
dispose: Effect.sync(() => scheduler.dispose?.()).pipe(Effect.andThen(services.disposeEffect)),
|
|
300
327
|
};
|
|
@@ -29,6 +29,8 @@ export declare const EventInputBodySchema: Schema.Struct<{
|
|
|
29
29
|
export declare function isLoomsApiPath(path: string): boolean;
|
|
30
30
|
export declare function provideStore<A, E>(effect: Effect.Effect<A, E, EventStoreTag>, store: EventStore): Effect.Effect<A, E>;
|
|
31
31
|
export declare const readJson: (req: Request) => Effect.Effect<JsonValue, RequestBodyError>;
|
|
32
|
+
/** `readJson` for routes whose body is optional: a request without one reads as `{}`. */
|
|
33
|
+
export declare const readOptionalJson: (req: Request) => Effect.Effect<JsonValue, RequestBodyError>;
|
|
32
34
|
export declare function extractIdempotencyKey(req: Request, bodyKey?: string): string | undefined;
|
|
33
35
|
export declare function acceptsEventStream(req: Request): boolean;
|
|
34
36
|
export declare function readIntegerQuery(url: URL, name: string, options: {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Data, Effect, Schema } from 'effect';
|
|
2
|
-
import { EventStoreTag, InvalidEventError, InvalidInputError, JsonValueSchema, } from '@swirls/looms/core';
|
|
3
|
-
import { RunOverloadedError, StartRunConflictError } from '../runtime.js';
|
|
2
|
+
import { EventStoreTag, ForkSourceError, InvalidEventError, InvalidInputError, JsonValueSchema, } from '@swirls/looms/core';
|
|
3
|
+
import { ForkConflictError, RunNotFinishedError, RunNotFoundError, RunOverloadedError, RunTerminalError, StartRunConflictError, } from '../runtime.js';
|
|
4
4
|
export class RequestBodyError extends Data.TaggedError('RequestBodyError') {
|
|
5
5
|
constructor(cause) {
|
|
6
6
|
super({ cause, message: 'Invalid JSON request body' });
|
|
@@ -25,6 +25,10 @@ export const readJson = (req) => Effect.tryPromise({
|
|
|
25
25
|
try: () => req.json(),
|
|
26
26
|
catch: (cause) => new RequestBodyError(cause),
|
|
27
27
|
}).pipe(Effect.flatMap(Schema.decodeUnknownEffect(JsonValueSchema)), Effect.mapError((cause) => cause instanceof RequestBodyError ? cause : new RequestBodyError(cause)));
|
|
28
|
+
/** `readJson` for routes whose body is optional: a request without one reads as `{}`. */
|
|
29
|
+
export const readOptionalJson = (req) => req.body === null || req.headers.get('content-length') === '0'
|
|
30
|
+
? Effect.succeed({})
|
|
31
|
+
: readJson(req);
|
|
28
32
|
export function extractIdempotencyKey(req, bodyKey) {
|
|
29
33
|
return (bodyKey ??
|
|
30
34
|
req.headers.get('idempotency-key') ??
|
|
@@ -59,8 +63,19 @@ export function toErrorResponse(err) {
|
|
|
59
63
|
if (err instanceof RunOverloadedError) {
|
|
60
64
|
return Response.json({ error: err.message }, { status: 429 });
|
|
61
65
|
}
|
|
66
|
+
if (err instanceof RunNotFoundError) {
|
|
67
|
+
return Response.json({ error: err.message }, { status: 404 });
|
|
68
|
+
}
|
|
69
|
+
if (err instanceof RunTerminalError) {
|
|
70
|
+
return Response.json({ error: err.message, status: err.status }, { status: 409 });
|
|
71
|
+
}
|
|
62
72
|
if (err instanceof StartRunConflictError) {
|
|
63
73
|
return Response.json({ error: err.message, mismatches: err.mismatches }, { status: 409 });
|
|
64
74
|
}
|
|
75
|
+
if (err instanceof RunNotFinishedError ||
|
|
76
|
+
err instanceof ForkConflictError ||
|
|
77
|
+
err instanceof ForkSourceError) {
|
|
78
|
+
return Response.json({ error: err.message }, { status: 409 });
|
|
79
|
+
}
|
|
65
80
|
return Response.json({ error: err.message }, { status: 500 });
|
|
66
81
|
}
|