effect-durable-agent-cloudflare 0.1.0-alpha.4 → 0.1.0-alpha.7
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/README.md +24 -4
- package/dist/durable-object.d.ts +18 -7
- package/dist/durable-object.js +193 -2
- package/dist/durable-object.js.map +1 -0
- package/dist/index.d.ts +5 -11
- package/dist/index.js +6 -25
- package/dist/openai.d.ts +18 -0
- package/dist/openai.js +59 -0
- package/dist/openai.js.map +1 -0
- package/dist/rpc.d.ts +16 -0
- package/dist/rpc.js +26 -0
- package/dist/rpc.js.map +1 -0
- package/dist/{durable-object-runtime-Dehm7PsO.d.ts → session-controller-BCsOxE22.d.ts} +132 -106
- package/dist/{durable-object-runtime-LgOJbsVm.js → session-controller-CMbW7vgf.js} +600 -423
- package/dist/session-controller-CMbW7vgf.js.map +1 -0
- package/dist/session-controller.d.ts +2 -0
- package/dist/session-controller.js +2 -0
- package/package.json +20 -15
- package/dist/chunk-BBjsoOtd.js +0 -27
- package/dist/durable-object-CjWWNyek.js +0 -194
- package/dist/durable-object-CjWWNyek.js.map +0 -1
- package/dist/durable-object-runtime-LgOJbsVm.js.map +0 -1
- package/dist/durable-object-runtime.d.ts +0 -2
- package/dist/durable-object-runtime.js +0 -2
- package/dist/index.js.map +0 -1
- package/dist/websocket-protocol.d.ts +0 -1
- package/dist/websocket-protocol.js +0 -10
- package/dist/websocket-protocol.js.map +0 -1
- /package/dist/{durable-object-storage.d.ts → storage.d.ts} +0 -0
- /package/dist/{durable-object-storage.js → storage.js} +0 -0
|
@@ -1,38 +1,25 @@
|
|
|
1
|
-
import { t as websocket_protocol_exports } from "./websocket-protocol.js";
|
|
2
1
|
import * as Effect from "effect/Effect";
|
|
3
2
|
import * as Schema from "effect/Schema";
|
|
4
3
|
import { DurableEventEnvelope, assistantMessageCommittedEventType, assistantMessageImportedEventType, assistantPartialCommittedEventType, commandAdmittedEventType, commandCancelledEventType, commandCompletedEventType, commandFailedEventType, commandStartedEventType, contextRebasedEventType, decodeUnknownEDADurableEventSync, effectDurableAgentNamespace, steeringMessageQueuedEventType, summaryCreatedEventType, systemMessageCommittedEventType, userMessageCommittedEventType } from "effect-durable-agent/types/events";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { OpenAiClient, OpenAiLanguageModel } from "@effect/ai-openai";
|
|
9
|
-
import * as Deferred from "effect/Deferred";
|
|
10
|
-
import * as Exit from "effect/Exit";
|
|
4
|
+
import { SequenceNumber, SessionId, SummaryId, durablePosition } from "effect-durable-agent/types/core";
|
|
5
|
+
import { EDATraceMetadata, makeRootEDATraceMetadata } from "effect-durable-agent/types/tracing";
|
|
6
|
+
import { annotateEdaSpan, committedBatchAttributes, compactSpanAttributes, toEdaExternalSpan } from "effect-durable-agent/services/tracing";
|
|
11
7
|
import * as Layer from "effect/Layer";
|
|
12
8
|
import * as ManagedRuntime from "effect/ManagedRuntime";
|
|
13
|
-
import * as Queue from "effect/Queue";
|
|
14
|
-
import * as Redacted from "effect/Redacted";
|
|
15
|
-
import "effect/Tracer";
|
|
16
|
-
import * as FetchHttpClient from "effect/unstable/http/FetchHttpClient";
|
|
17
|
-
import "effect/unstable/http/HttpBody";
|
|
18
|
-
import * as HttpClient from "effect/unstable/http/HttpClient";
|
|
19
|
-
import * as HttpClientError from "effect/unstable/http/HttpClientError";
|
|
20
|
-
import "effect/unstable/http/HttpClientRequest";
|
|
21
|
-
import * as HttpClientResponse from "effect/unstable/http/HttpClientResponse";
|
|
22
|
-
import "effect-durable-agent/services/compaction";
|
|
23
|
-
import { EDAKeepAlive } from "effect-durable-agent/services/keep-alive";
|
|
24
|
-
import "effect-durable-agent/services/prompt-projector";
|
|
25
|
-
import { EDAReducerRegistry } from "effect-durable-agent/services/reducer-registry";
|
|
26
9
|
import { EDARuntime } from "effect-durable-agent/services/runtime";
|
|
27
|
-
import { makeEDARuntimeLayer } from "effect-durable-agent/services/runtime-layer";
|
|
28
10
|
import { CommittedDurableEvent, EDASessionStore, EDASessionStoreError } from "effect-durable-agent/services/session-store";
|
|
29
|
-
import {
|
|
30
|
-
import { SubscriberLagged, SubscriberProtocolError, SubscriberSendFailed, runWebSocketSubscriber } from "effect-durable-agent/services/websocket-subscriber";
|
|
11
|
+
import { SinkCheckpointStore } from "effect-durable-agent/services/sink-checkpoint-store";
|
|
31
12
|
import * as Cause from "effect/Cause";
|
|
32
13
|
import * as Option from "effect/Option";
|
|
33
14
|
import * as Stream from "effect/Stream";
|
|
34
15
|
import { CompactionSummaryArtifact } from "effect-durable-agent/domain/context-projection";
|
|
35
|
-
import
|
|
16
|
+
import "effect-durable-agent/services/compaction";
|
|
17
|
+
import { EDAKeepAlive } from "effect-durable-agent/services/keep-alive";
|
|
18
|
+
import "effect-durable-agent/services/prompt-projector";
|
|
19
|
+
import { EDAReducerRegistry } from "effect-durable-agent/services/reducer-registry";
|
|
20
|
+
import { makeEDARuntimeLayer } from "effect-durable-agent/services/runtime-layer";
|
|
21
|
+
import { SessionEventObserver } from "effect-durable-agent/services/session-event-observer";
|
|
22
|
+
import { EDAWebSocketDeliveryCheckpoint, EDA_WEB_SOCKET_PING_MESSAGE, EDA_WEB_SOCKET_PONG_MESSAGE, EDA_WS_CLOSE_PROTOCOL_ERROR, EDA_WS_CLOSE_SEND_FAILED, SubscriberId, checkpointWebSocketDeliveryState, decodeEDAWebSocketClientMessage, defaultEDAWebSocketFlowControl, deliveryHelloFrame, encodeEDAWebSocketServerFrame, makeWebSocketDeliveryState, onClientAck, onDurableEvents, onEphemeralEvent, onHostSuppressedFrame, restoreWebSocketDeliveryState } from "effect-durable-agent/websocket";
|
|
36
23
|
/**
|
|
37
24
|
* Alarm-backed in-memory keepalive for a raw Durable Object host.
|
|
38
25
|
*
|
|
@@ -155,6 +142,78 @@ var DurableObjectKeepAlive = class {
|
|
|
155
142
|
}
|
|
156
143
|
};
|
|
157
144
|
//#endregion
|
|
145
|
+
//#region src/durable-object-sink-checkpoints.ts
|
|
146
|
+
/**
|
|
147
|
+
* Durable Object SQLite adapter for EDA sink checkpoints.
|
|
148
|
+
*
|
|
149
|
+
* Checkpoint SQL stays in the host layer for the same reason session storage SQL
|
|
150
|
+
* does: framework sink code should depend on checkpoint semantics, not table access.
|
|
151
|
+
*/
|
|
152
|
+
var DurableObjectSinkCheckpointStore = class DurableObjectSinkCheckpointStore {
|
|
153
|
+
/** Ensure the fixed `_eda_sink_cursors` table exists during host startup. */
|
|
154
|
+
static migrate = (storage) => migrateSinkCheckpointStore(storage);
|
|
155
|
+
/** Build the semantic checkpoint store over native DO SQLite. */
|
|
156
|
+
static make = (storage) => makeDurableObjectSinkCheckpointStore(storage);
|
|
157
|
+
static layer = (storage) => Layer.effect(SinkCheckpointStore, DurableObjectSinkCheckpointStore.make(storage));
|
|
158
|
+
};
|
|
159
|
+
/** Create the sink checkpoint table inside the current EDA storage schema. */
|
|
160
|
+
const migrateSinkCheckpointStore = (storage) => Effect.try({
|
|
161
|
+
try: () => {
|
|
162
|
+
storage.sql.exec(`
|
|
163
|
+
CREATE TABLE IF NOT EXISTS _eda_sink_cursors (
|
|
164
|
+
sink_name TEXT PRIMARY KEY,
|
|
165
|
+
after_seq INTEGER NOT NULL,
|
|
166
|
+
payload_json TEXT NOT NULL
|
|
167
|
+
)
|
|
168
|
+
`);
|
|
169
|
+
},
|
|
170
|
+
catch: (error) => sinkCheckpointError("migrating SinkCheckpointStore", error)
|
|
171
|
+
});
|
|
172
|
+
/** Interpret checkpoint load/save/commit with idempotent upserts. */
|
|
173
|
+
const makeDurableObjectSinkCheckpointStore = (storage) => Effect.gen(function* () {
|
|
174
|
+
yield* DurableObjectSinkCheckpointStore.migrate(storage);
|
|
175
|
+
const { sql } = storage;
|
|
176
|
+
return {
|
|
177
|
+
load: (sinkName) => Effect.try({
|
|
178
|
+
try: () => {
|
|
179
|
+
const row = sql.exec(`SELECT after_seq, payload_json FROM _eda_sink_cursors WHERE sink_name = ?`, sinkName).toArray()[0];
|
|
180
|
+
return row === void 0 ? {
|
|
181
|
+
afterSeq: SequenceNumber.make(0),
|
|
182
|
+
payload: void 0
|
|
183
|
+
} : {
|
|
184
|
+
afterSeq: SequenceNumber.make(row.after_seq),
|
|
185
|
+
payload: JSON.parse(row.payload_json)
|
|
186
|
+
};
|
|
187
|
+
},
|
|
188
|
+
catch: (error) => sinkCheckpointError("reading sink checkpoint", error)
|
|
189
|
+
}),
|
|
190
|
+
saveState: (sinkName, payload) => Effect.try({
|
|
191
|
+
try: () => {
|
|
192
|
+
sql.exec(`
|
|
193
|
+
INSERT INTO _eda_sink_cursors (sink_name, after_seq, payload_json)
|
|
194
|
+
VALUES (?, 0, ?)
|
|
195
|
+
ON CONFLICT(sink_name) DO UPDATE SET
|
|
196
|
+
payload_json = excluded.payload_json
|
|
197
|
+
`, sinkName, JSON.stringify(payload));
|
|
198
|
+
},
|
|
199
|
+
catch: (error) => sinkCheckpointError("saving sink checkpoint state", error)
|
|
200
|
+
}),
|
|
201
|
+
commit: (sinkName, afterSeq, payload) => Effect.try({
|
|
202
|
+
try: () => {
|
|
203
|
+
sql.exec(`
|
|
204
|
+
INSERT INTO _eda_sink_cursors (sink_name, after_seq, payload_json)
|
|
205
|
+
VALUES (?, ?, ?)
|
|
206
|
+
ON CONFLICT(sink_name) DO UPDATE SET
|
|
207
|
+
after_seq = excluded.after_seq,
|
|
208
|
+
payload_json = excluded.payload_json
|
|
209
|
+
`, sinkName, afterSeq, JSON.stringify(payload ?? { updatedAtMs: Date.now() }));
|
|
210
|
+
},
|
|
211
|
+
catch: (error) => sinkCheckpointError("committing sink checkpoint", error)
|
|
212
|
+
})
|
|
213
|
+
};
|
|
214
|
+
});
|
|
215
|
+
const sinkCheckpointError = (operation, cause) => new EDASessionStoreError({ message: cause instanceof Error ? `${operation}: ${cause.message}` : `${operation}: ${String(cause)}` });
|
|
216
|
+
//#endregion
|
|
158
217
|
//#region src/durable-object-store.ts
|
|
159
218
|
/** Safe payload ceiling below the Durable Object SQLite 2 MB value limit. */
|
|
160
219
|
/** Conservative JSON limit below the Durable Object SQLite per-value ceiling. */
|
|
@@ -776,422 +835,87 @@ const stringifyJsonForColumn = (value, column) => {
|
|
|
776
835
|
};
|
|
777
836
|
const durableStoreError = (operation, cause) => new EDASessionStoreError({ message: cause instanceof Error ? `${operation}: ${cause.message}` : `${operation}: ${String(cause)}` });
|
|
778
837
|
//#endregion
|
|
779
|
-
//#region src/
|
|
780
|
-
/**
|
|
781
|
-
|
|
782
|
-
*
|
|
783
|
-
* Checkpoint SQL stays in the host layer for the same reason session storage SQL
|
|
784
|
-
* does: framework sink code should depend on checkpoint semantics, not table access.
|
|
785
|
-
*/
|
|
786
|
-
var DurableObjectSinkCheckpointStore = class DurableObjectSinkCheckpointStore {
|
|
787
|
-
/** Ensure the fixed `_eda_sink_cursors` table exists during host startup. */
|
|
788
|
-
static migrate = (storage) => migrateSinkCheckpointStore(storage);
|
|
789
|
-
/** Build the semantic checkpoint store over native DO SQLite. */
|
|
790
|
-
static make = (storage) => makeDurableObjectSinkCheckpointStore(storage);
|
|
791
|
-
static layer = (storage) => Layer.effect(SinkCheckpointStore, DurableObjectSinkCheckpointStore.make(storage));
|
|
792
|
-
};
|
|
793
|
-
/** Create the sink checkpoint table inside the current EDA storage schema. */
|
|
794
|
-
const migrateSinkCheckpointStore = (storage) => Effect.try({
|
|
795
|
-
try: () => {
|
|
796
|
-
storage.sql.exec(`
|
|
797
|
-
CREATE TABLE IF NOT EXISTS _eda_sink_cursors (
|
|
798
|
-
sink_name TEXT PRIMARY KEY,
|
|
799
|
-
after_seq INTEGER NOT NULL,
|
|
800
|
-
payload_json TEXT NOT NULL
|
|
801
|
-
)
|
|
802
|
-
`);
|
|
803
|
-
},
|
|
804
|
-
catch: (error) => sinkCheckpointError("migrating SinkCheckpointStore", error)
|
|
805
|
-
});
|
|
806
|
-
/** Interpret checkpoint load/save/commit with idempotent upserts. */
|
|
807
|
-
const makeDurableObjectSinkCheckpointStore = (storage) => Effect.gen(function* () {
|
|
808
|
-
yield* DurableObjectSinkCheckpointStore.migrate(storage);
|
|
809
|
-
const { sql } = storage;
|
|
810
|
-
return {
|
|
811
|
-
load: (sinkName) => Effect.try({
|
|
812
|
-
try: () => {
|
|
813
|
-
const row = sql.exec(`SELECT after_seq, payload_json FROM _eda_sink_cursors WHERE sink_name = ?`, sinkName).toArray()[0];
|
|
814
|
-
return row === void 0 ? {
|
|
815
|
-
afterSeq: SequenceNumber.make(0),
|
|
816
|
-
payload: void 0
|
|
817
|
-
} : {
|
|
818
|
-
afterSeq: SequenceNumber.make(row.after_seq),
|
|
819
|
-
payload: JSON.parse(row.payload_json)
|
|
820
|
-
};
|
|
821
|
-
},
|
|
822
|
-
catch: (error) => sinkCheckpointError("reading sink checkpoint", error)
|
|
823
|
-
}),
|
|
824
|
-
saveState: (sinkName, payload) => Effect.try({
|
|
825
|
-
try: () => {
|
|
826
|
-
sql.exec(`
|
|
827
|
-
INSERT INTO _eda_sink_cursors (sink_name, after_seq, payload_json)
|
|
828
|
-
VALUES (?, 0, ?)
|
|
829
|
-
ON CONFLICT(sink_name) DO UPDATE SET
|
|
830
|
-
payload_json = excluded.payload_json
|
|
831
|
-
`, sinkName, JSON.stringify(payload));
|
|
832
|
-
},
|
|
833
|
-
catch: (error) => sinkCheckpointError("saving sink checkpoint state", error)
|
|
834
|
-
}),
|
|
835
|
-
commit: (sinkName, afterSeq, payload) => Effect.try({
|
|
836
|
-
try: () => {
|
|
837
|
-
sql.exec(`
|
|
838
|
-
INSERT INTO _eda_sink_cursors (sink_name, after_seq, payload_json)
|
|
839
|
-
VALUES (?, ?, ?)
|
|
840
|
-
ON CONFLICT(sink_name) DO UPDATE SET
|
|
841
|
-
after_seq = excluded.after_seq,
|
|
842
|
-
payload_json = excluded.payload_json
|
|
843
|
-
`, sinkName, afterSeq, JSON.stringify(payload ?? { updatedAtMs: Date.now() }));
|
|
844
|
-
},
|
|
845
|
-
catch: (error) => sinkCheckpointError("committing sink checkpoint", error)
|
|
846
|
-
})
|
|
847
|
-
};
|
|
848
|
-
});
|
|
849
|
-
const sinkCheckpointError = (operation, cause) => new EDASessionStoreError({ message: cause instanceof Error ? `${operation}: ${cause.message}` : `${operation}: ${String(cause)}` });
|
|
850
|
-
//#endregion
|
|
851
|
-
//#region src/durable-object-runtime.ts
|
|
852
|
-
const resolveRuntimeFactory = (value, sessionId) => typeof value === "function" ? value({ sessionId }) : value;
|
|
853
|
-
/**
|
|
854
|
-
* Compose the full EDA service graph for one raw Durable Object session.
|
|
855
|
-
*
|
|
856
|
-
* The layer is intentionally host-owned: tests can pass a fake model layer,
|
|
857
|
-
* while the Worker host can pass a provider-backed layer from Env.
|
|
858
|
-
*/
|
|
859
|
-
const makeEDADurableObjectRuntimeLayer = ({ config, compactionExecutorLayer, compactionPolicyLayer, keepAlive, modelLayer, promptProjectorLayer, reducers, sessionId, sinks, storage, toolkit, toolRegistry, tracer }) => {
|
|
860
|
-
const Store = DurableObjectSessionStore.layer({
|
|
861
|
-
sessionId,
|
|
862
|
-
storage
|
|
863
|
-
});
|
|
864
|
-
const KeepAlive = keepAlive === void 0 ? EDAKeepAlive.Noop : EDAKeepAlive.FromAcquire(() => keepAlive.acquire());
|
|
865
|
-
const SinkCheckpoints = DurableObjectSinkCheckpointStore.layer(storage);
|
|
838
|
+
//#region src/runtime/runtime-layer.ts
|
|
839
|
+
/** Compose the platform-neutral runtime with Cloudflare persistence and lifecycle adapters. */
|
|
840
|
+
const makeEDADurableObjectRuntimeLayer = ({ config, compactionExecutorLayer, compactionPolicyLayer, keepAlive, modelLayer, promptProjectorLayer, reducers, sessionEventObserverLayer, sessionId, sinks, storage, toolkit, toolRegistry, tracer }) => {
|
|
866
841
|
return makeEDARuntimeLayer({
|
|
867
842
|
config,
|
|
868
843
|
compactionExecutorLayer,
|
|
869
844
|
compactionPolicyLayer,
|
|
870
|
-
keepAliveLayer:
|
|
845
|
+
keepAliveLayer: keepAlive === void 0 ? EDAKeepAlive.Noop : EDAKeepAlive.FromAcquire(() => keepAlive.acquire()),
|
|
871
846
|
modelLayer,
|
|
872
847
|
promptProjectorLayer,
|
|
873
848
|
reducerRegistryLayer: EDAReducerRegistry.Live(reducers ?? []),
|
|
849
|
+
sessionEventObserverLayer,
|
|
874
850
|
sessionId,
|
|
875
|
-
sessionStoreLayer:
|
|
876
|
-
|
|
851
|
+
sessionStoreLayer: DurableObjectSessionStore.layer({
|
|
852
|
+
sessionId,
|
|
853
|
+
storage
|
|
854
|
+
}),
|
|
855
|
+
sinkCheckpointStoreLayer: DurableObjectSinkCheckpointStore.layer(storage),
|
|
877
856
|
sinks,
|
|
878
857
|
toolkit,
|
|
879
858
|
toolRegistry,
|
|
880
859
|
tracer
|
|
881
860
|
});
|
|
882
861
|
};
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
const baseHttpClient = options.aiGateway === void 0 ? FetchHttpClient.layer : Layer.succeed(HttpClient.HttpClient, makeCloudflareAiGatewayHttpClient(options.aiGateway));
|
|
888
|
-
const client = OpenAiClient.layer({
|
|
889
|
-
...apiKey === void 0 ? {} : { apiKey: Redacted.make(apiKey) },
|
|
890
|
-
...options.apiUrl === void 0 ? {} : { apiUrl: options.apiUrl }
|
|
891
|
-
}).pipe(Layer.provide(baseHttpClient));
|
|
892
|
-
return OpenAiLanguageModel.layer({
|
|
893
|
-
model: options.modelId,
|
|
894
|
-
...options.config === void 0 ? {} : { config: options.config }
|
|
895
|
-
}).pipe(Layer.provide(client));
|
|
896
|
-
};
|
|
897
|
-
const OPENAI_API_BASE_PATH_PREFIX = "/v1/";
|
|
898
|
-
const makeCloudflareAiGatewayHttpClient = (gateway) => HttpClient.make((request, url, signal) => Effect.tryPromise({
|
|
899
|
-
try: async () => {
|
|
900
|
-
const response = await gateway.run({
|
|
901
|
-
endpoint: openAiGatewayEndpointFromUrl(url),
|
|
902
|
-
headers: requestHeadersForGateway(request),
|
|
903
|
-
provider: "openai",
|
|
904
|
-
query: requestBodyForGateway(request.body)
|
|
905
|
-
}, { signal });
|
|
906
|
-
return HttpClientResponse.fromWeb(request, response);
|
|
907
|
-
},
|
|
908
|
-
catch: (cause) => new HttpClientError.HttpClientError({ reason: new HttpClientError.TransportError({
|
|
909
|
-
request,
|
|
910
|
-
cause,
|
|
911
|
-
description: "Cloudflare AI Gateway request failed"
|
|
912
|
-
}) })
|
|
913
|
-
}));
|
|
914
|
-
const openAiGatewayEndpointFromUrl = (url) => {
|
|
915
|
-
return `${url.pathname.startsWith(OPENAI_API_BASE_PATH_PREFIX) ? url.pathname.slice(4) : url.pathname.replace(/^\/+/, "")}${url.search}`;
|
|
916
|
-
};
|
|
917
|
-
const requestHeadersForGateway = (request) => Object.fromEntries(Object.entries(request.headers).filter((entry) => {
|
|
918
|
-
const [key, value] = entry;
|
|
919
|
-
return typeof value === "string" && key.toLowerCase() !== "host";
|
|
920
|
-
}));
|
|
921
|
-
const requestBodyForGateway = (body) => {
|
|
922
|
-
switch (body._tag) {
|
|
923
|
-
case "Empty": return;
|
|
924
|
-
case "Raw": return body.body;
|
|
925
|
-
case "Uint8Array": return JSON.parse(new TextDecoder().decode(body.body));
|
|
926
|
-
default: throw new Error(`Unsupported OpenAI request body for Cloudflare AI Gateway: ${body._tag}`);
|
|
927
|
-
}
|
|
928
|
-
};
|
|
929
|
-
const optionalNonEmptyString = (value) => {
|
|
930
|
-
const trimmed = value?.trim();
|
|
931
|
-
return trimmed === void 0 || trimmed.length === 0 ? void 0 : trimmed;
|
|
932
|
-
};
|
|
933
|
-
const hostSpanOptions = (trace, attributes) => ({
|
|
934
|
-
kind: "server",
|
|
935
|
-
attributes: compactSpanAttributes({
|
|
936
|
-
...trace.attributes,
|
|
937
|
-
...attributes
|
|
938
|
-
}),
|
|
939
|
-
links: trace.links.map((link) => ({
|
|
940
|
-
span: toEdaExternalSpan(link.context),
|
|
941
|
-
attributes: link.attributes
|
|
942
|
-
})),
|
|
943
|
-
...trace.parent === null ? {} : { parent: toEdaExternalSpan(trace.parent) }
|
|
944
|
-
});
|
|
945
|
-
/** Raw Durable Object host controller for one EDA session object. */
|
|
946
|
-
var EDASessionDurableObjectHost = class {
|
|
862
|
+
//#endregion
|
|
863
|
+
//#region src/runtime/session-runtime.ts
|
|
864
|
+
/** Owns the disposable ManagedRuntime and nothing about WebSocket transport. */
|
|
865
|
+
var EDASessionRuntime = class {
|
|
947
866
|
options;
|
|
948
867
|
keepAlive;
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
runtimePromise;
|
|
868
|
+
state;
|
|
869
|
+
building;
|
|
952
870
|
constructor(options) {
|
|
953
871
|
this.options = options;
|
|
954
872
|
this.keepAlive = options.keepAlive ?? new DurableObjectKeepAlive(options.storage, options.background);
|
|
955
873
|
}
|
|
956
|
-
/** Run idempotent host and event-store migrations. Safe from `blockConcurrencyWhile`. */
|
|
957
874
|
static migrate = (storage) => Effect.gen(function* () {
|
|
958
875
|
yield* DurableObjectSessionStore.migrate(storage);
|
|
959
876
|
yield* DurableObjectSinkCheckpointStore.migrate(storage);
|
|
960
877
|
});
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
const runtime = await this.getRuntime(input.sessionId);
|
|
964
|
-
const trace = input.trace ?? makeRootEDATraceMetadata();
|
|
965
|
-
return await runtime.runtime.runPromise(Effect.gen(function* () {
|
|
966
|
-
return yield* (yield* EDARuntime).submit(input.command);
|
|
967
|
-
}).pipe(Effect.withSpan("agent.command.submit", hostSpanOptions(trace, {
|
|
968
|
-
"eda.session.id": input.sessionId,
|
|
969
|
-
"eda.command.kind": input.command._tag,
|
|
970
|
-
"eda.command.id": input.command.commandId
|
|
971
|
-
}))));
|
|
878
|
+
isReady(sessionId) {
|
|
879
|
+
return this.state?.sessionId === sessionId;
|
|
972
880
|
}
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
const runtime = await this.getRuntime(input.sessionId);
|
|
976
|
-
const trace = input.trace ?? makeRootEDATraceMetadata();
|
|
977
|
-
return await runtime.runtime.runPromise(Effect.gen(function* () {
|
|
978
|
-
return yield* (yield* EDARuntime).submit(input.items);
|
|
979
|
-
}).pipe(Effect.withSpan("agent.command.submit", hostSpanOptions(trace, {
|
|
980
|
-
"eda.session.id": input.sessionId,
|
|
981
|
-
"eda.command.submittable_count": input.items.length,
|
|
982
|
-
"eda.command.count": input.items.filter((item) => "_tag" in item).length
|
|
983
|
-
}))));
|
|
984
|
-
}
|
|
985
|
-
/** Admit one command and wait for its terminal command boundary. */
|
|
986
|
-
async submitAndBlock(input) {
|
|
987
|
-
const runtime = await this.getRuntime(input.sessionId);
|
|
988
|
-
const trace = input.trace ?? makeRootEDATraceMetadata();
|
|
989
|
-
return await runtime.runtime.runPromise(Effect.gen(function* () {
|
|
990
|
-
return yield* (yield* EDARuntime).submitAndBlock(input.command);
|
|
991
|
-
}).pipe(Effect.withSpan("agent.command.submit.wait", hostSpanOptions(trace, {
|
|
992
|
-
"eda.session.id": input.sessionId,
|
|
993
|
-
"eda.command.kind": input.command._tag,
|
|
994
|
-
"eda.command.id": input.command.commandId
|
|
995
|
-
}))));
|
|
881
|
+
async prepare(sessionId) {
|
|
882
|
+
await this.get(sessionId);
|
|
996
883
|
}
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
return await runtime.runtime.runPromise(Effect.gen(function* () {
|
|
1002
|
-
return yield* (yield* EDARuntime).blockOnCommand(input.commandId, input.afterSeq ?? SequenceNumber.make(0));
|
|
1003
|
-
}).pipe(Effect.withSpan("agent.command.wait", hostSpanOptions(trace, {
|
|
1004
|
-
"eda.session.id": input.sessionId,
|
|
1005
|
-
"eda.command.id": input.commandId,
|
|
1006
|
-
"eda.seq.after": input.afterSeq
|
|
1007
|
-
}))));
|
|
1008
|
-
}
|
|
1009
|
-
/** Read the authoritative live session snapshot. */
|
|
1010
|
-
snapshot(input) {
|
|
1011
|
-
const trace = input.trace ?? makeRootEDATraceMetadata();
|
|
1012
|
-
return this.withRuntime(input.sessionId, (eda) => eda.snapshot().pipe(Effect.withSpan("agent.session.snapshot", hostSpanOptions(trace, { "eda.session.id": input.sessionId }))));
|
|
1013
|
-
}
|
|
1014
|
-
/** Read durable transcript messages in committed order. */
|
|
1015
|
-
messages(input) {
|
|
1016
|
-
const trace = input.trace ?? makeRootEDATraceMetadata();
|
|
1017
|
-
return this.withRuntime(input.sessionId, (eda) => eda.messages().pipe(Effect.withSpan("agent.messages.list", hostSpanOptions(trace, { "eda.session.id": input.sessionId }))));
|
|
1018
|
-
}
|
|
1019
|
-
/** Accept one WebSocket as the live event stream for this session. */
|
|
1020
|
-
async acceptEventWebSocket(input) {
|
|
1021
|
-
const attachment = websocket_protocol_exports.EDAWebSocketAttachment.make({
|
|
1022
|
-
kind: "eda-events-v1",
|
|
1023
|
-
sessionId: input.sessionId,
|
|
1024
|
-
subscriberId: websocket_protocol_exports.SubscriberId.make(crypto.randomUUID()),
|
|
1025
|
-
lastAckedSeq: input.afterSeq ?? SequenceNumber.make(0),
|
|
1026
|
-
trace: input.trace ?? makeRootEDATraceMetadata()
|
|
1027
|
-
});
|
|
1028
|
-
input.webSocket.serializeAttachment(attachment);
|
|
1029
|
-
await this.startEventWebSocket(input.webSocket, attachment);
|
|
1030
|
-
}
|
|
1031
|
-
/** Restore hibernated event WebSockets whose in-memory subscriber fibers disappeared. */
|
|
1032
|
-
async restoreEventWebSockets(webSockets) {
|
|
1033
|
-
for (const webSocket of webSockets) {
|
|
1034
|
-
if (this.eventSockets.has(webSocket)) continue;
|
|
1035
|
-
const decoded = await decodeWebSocketAttachment(webSocket.deserializeAttachment());
|
|
1036
|
-
if (decoded._tag === "Decoded") await this.startEventWebSocket(webSocket, decoded.attachment);
|
|
1037
|
-
else if (decoded._tag === "Malformed") closeWebSocket(webSocket, websocket_protocol_exports.EDA_WS_CLOSE_PROTOCOL_ERROR, "attachment");
|
|
1038
|
-
}
|
|
884
|
+
async run(sessionId, operation) {
|
|
885
|
+
return await (await this.get(sessionId)).runtime.runPromise(Effect.gen(function* () {
|
|
886
|
+
return yield* operation(yield* EDARuntime);
|
|
887
|
+
}));
|
|
1039
888
|
}
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
closeWebSocket(webSocket, websocket_protocol_exports.EDA_WS_CLOSE_PROTOCOL_ERROR, "protocol");
|
|
1045
|
-
return;
|
|
1046
|
-
}
|
|
1047
|
-
if (typeof message !== "string") {
|
|
1048
|
-
closeWebSocket(webSocket, websocket_protocol_exports.EDA_WS_CLOSE_PROTOCOL_ERROR, "binary");
|
|
1049
|
-
state.interrupt?.();
|
|
1050
|
-
return;
|
|
1051
|
-
}
|
|
1052
|
-
try {
|
|
1053
|
-
const frame = await Effect.runPromise((0, websocket_protocol_exports.decodeEDAWebSocketClientMessage)(message));
|
|
1054
|
-
await Effect.runPromise(Queue.offer(state.incoming, frame));
|
|
1055
|
-
} catch {
|
|
1056
|
-
closeWebSocket(webSocket, websocket_protocol_exports.EDA_WS_CLOSE_PROTOCOL_ERROR, "protocol");
|
|
1057
|
-
state.interrupt?.();
|
|
1058
|
-
}
|
|
889
|
+
async alarm(sessionId) {
|
|
890
|
+
const resolved = sessionId ?? this.state?.sessionId;
|
|
891
|
+
if (resolved !== void 0) await this.get(resolved);
|
|
892
|
+
await this.keepAlive.alarm();
|
|
1059
893
|
}
|
|
1060
|
-
|
|
1061
|
-
async webSocketClose(webSocket) {
|
|
1062
|
-
const state = this.eventSockets.get(webSocket);
|
|
1063
|
-
if (state === void 0) return;
|
|
1064
|
-
await Effect.runPromise(Deferred.succeed(state.closed, void 0).pipe(Effect.ignore));
|
|
1065
|
-
state.interrupt?.();
|
|
1066
|
-
this.eventSockets.delete(webSocket);
|
|
1067
|
-
}
|
|
1068
|
-
/** Interrupt subscriber work after a host WebSocket error. */
|
|
1069
|
-
async webSocketError(webSocket) {
|
|
1070
|
-
const state = this.eventSockets.get(webSocket);
|
|
1071
|
-
if (state === void 0) return;
|
|
1072
|
-
await Effect.runPromise(Deferred.succeed(state.closed, void 0).pipe(Effect.ignore));
|
|
1073
|
-
state.interrupt?.();
|
|
1074
|
-
this.eventSockets.delete(webSocket);
|
|
1075
|
-
}
|
|
1076
|
-
/** Dispose any live runtime before the host deletes its Durable Object storage. */
|
|
1077
|
-
async destroy(_input) {
|
|
894
|
+
async destroy() {
|
|
1078
895
|
await this.keepAlive.shutdown();
|
|
1079
896
|
await this.dispose();
|
|
1080
|
-
this.
|
|
1081
|
-
this.
|
|
897
|
+
this.state = void 0;
|
|
898
|
+
this.building = void 0;
|
|
1082
899
|
}
|
|
1083
|
-
/** Durable Object alarm hook: wake/recover the runtime and re-arm while EDA has active work. */
|
|
1084
|
-
async alarm(input) {
|
|
1085
|
-
const sessionId = input?.sessionId ?? this.runtimeState?.sessionId;
|
|
1086
|
-
if (sessionId !== void 0) await this.getRuntime(sessionId);
|
|
1087
|
-
await this.keepAlive.alarm();
|
|
1088
|
-
}
|
|
1089
|
-
/** Dispose the Effect runtime if the host wrapper is torn down by tests. */
|
|
1090
900
|
async dispose() {
|
|
1091
|
-
await this.
|
|
1092
|
-
}
|
|
1093
|
-
async withRuntime(sessionId, f) {
|
|
1094
|
-
return await (await this.getRuntime(sessionId)).runtime.runPromise(Effect.gen(function* () {
|
|
1095
|
-
return yield* f(yield* EDARuntime);
|
|
1096
|
-
}));
|
|
1097
|
-
}
|
|
1098
|
-
async ensureEventWebSocket(webSocket) {
|
|
1099
|
-
const existing = this.eventSockets.get(webSocket);
|
|
1100
|
-
if (existing !== void 0) return existing;
|
|
1101
|
-
const decoded = await decodeWebSocketAttachment(webSocket.deserializeAttachment());
|
|
1102
|
-
if (decoded._tag !== "Decoded") return;
|
|
1103
|
-
await this.startEventWebSocket(webSocket, decoded.attachment);
|
|
1104
|
-
return this.eventSockets.get(webSocket);
|
|
901
|
+
await this.state?.runtime.dispose();
|
|
1105
902
|
}
|
|
1106
|
-
async
|
|
1107
|
-
if (this.
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
...await runtime.runtime.runPromise(Effect.gen(function* () {
|
|
1111
|
-
return {
|
|
1112
|
-
incoming: yield* Queue.unbounded(),
|
|
1113
|
-
closed: yield* Deferred.make()
|
|
1114
|
-
};
|
|
1115
|
-
})),
|
|
1116
|
-
interrupt: void 0,
|
|
1117
|
-
lastAckedSeq: attachment.lastAckedSeq,
|
|
1118
|
-
sessionId: attachment.sessionId,
|
|
1119
|
-
subscriberId: attachment.subscriberId
|
|
1120
|
-
};
|
|
1121
|
-
this.eventSockets.set(webSocket, state);
|
|
1122
|
-
const send = (frame) => Effect.try({
|
|
1123
|
-
try: () => webSocket.send((0, websocket_protocol_exports.encodeEDAWebSocketServerFrame)(frame, this.options.webSocketProtocol)),
|
|
1124
|
-
catch: (cause) => new SubscriberSendFailed({
|
|
1125
|
-
subscriberId: state.subscriberId,
|
|
1126
|
-
cause
|
|
1127
|
-
})
|
|
1128
|
-
});
|
|
1129
|
-
const persistAck = (seq) => Effect.sync(() => {
|
|
1130
|
-
state.lastAckedSeq = seq;
|
|
1131
|
-
webSocket.serializeAttachment({
|
|
1132
|
-
kind: "eda-events-v1",
|
|
1133
|
-
sessionId: state.sessionId,
|
|
1134
|
-
subscriberId: state.subscriberId,
|
|
1135
|
-
lastAckedSeq: seq,
|
|
1136
|
-
trace: attachment.trace
|
|
1137
|
-
});
|
|
1138
|
-
});
|
|
1139
|
-
const closeForError = (error) => {
|
|
1140
|
-
if (error instanceof SubscriberLagged || isTagged(error, "SubscriberLagged")) {
|
|
1141
|
-
const lagged = error;
|
|
1142
|
-
return send(websocket_protocol_exports.EDAWebSocketLaggedFrame.make({
|
|
1143
|
-
_tag: "lagged",
|
|
1144
|
-
resumeSeq: lagged.lastAckedSeq,
|
|
1145
|
-
reason: lagged.reason
|
|
1146
|
-
})).pipe(Effect.ignore, Effect.andThen(Effect.sync(() => closeWebSocket(webSocket, websocket_protocol_exports.EDA_WS_CLOSE_LAGGED, (0, websocket_protocol_exports.laggedCloseReason)(lagged.lastAckedSeq)))));
|
|
1147
|
-
}
|
|
1148
|
-
if (error instanceof SubscriberProtocolError || isTagged(error, "SubscriberProtocolError")) {
|
|
1149
|
-
const protocolError = error;
|
|
1150
|
-
return send(websocket_protocol_exports.EDAWebSocketErrorFrame.make({
|
|
1151
|
-
_tag: "error",
|
|
1152
|
-
message: protocolError.message.slice(0, 120)
|
|
1153
|
-
})).pipe(Effect.ignore, Effect.andThen(Effect.sync(() => closeWebSocket(webSocket, websocket_protocol_exports.EDA_WS_CLOSE_PROTOCOL_ERROR, "protocol"))));
|
|
1154
|
-
}
|
|
1155
|
-
return Effect.sync(() => closeWebSocket(webSocket, websocket_protocol_exports.EDA_WS_CLOSE_SEND_FAILED, "send-failed"));
|
|
1156
|
-
};
|
|
1157
|
-
const program = Effect.scoped(runWebSocketSubscriber({
|
|
1158
|
-
subscriberId: state.subscriberId,
|
|
1159
|
-
resumeSeq: state.lastAckedSeq,
|
|
1160
|
-
policy: websocket_protocol_exports.defaultEDAWebSocketFlowControl,
|
|
1161
|
-
transport: {
|
|
1162
|
-
send,
|
|
1163
|
-
incoming: state.incoming,
|
|
1164
|
-
closed: state.closed,
|
|
1165
|
-
persistAck
|
|
1166
|
-
}
|
|
1167
|
-
}).pipe(Effect.catch((error) => closeForError(error)), Effect.ensuring(Effect.sync(() => {
|
|
1168
|
-
this.eventSockets.delete(webSocket);
|
|
1169
|
-
})), Effect.withSpan("agent.events.stream", hostSpanOptions(attachment.trace, {
|
|
1170
|
-
"eda.session.id": state.sessionId,
|
|
1171
|
-
"eda.subscriber.id": state.subscriberId,
|
|
1172
|
-
"eda.seq.after": state.lastAckedSeq
|
|
1173
|
-
}))));
|
|
1174
|
-
state.interrupt = runtime.runtime.runCallback(program, { onExit: (exit) => {
|
|
1175
|
-
this.eventSockets.delete(webSocket);
|
|
1176
|
-
if (Exit.isFailure(exit)) closeWebSocket(webSocket, websocket_protocol_exports.EDA_WS_CLOSE_SEND_FAILED, "subscriber-failed");
|
|
1177
|
-
} });
|
|
1178
|
-
}
|
|
1179
|
-
async getRuntime(sessionId) {
|
|
1180
|
-
if (this.runtimeState !== void 0) {
|
|
1181
|
-
if (this.runtimeState.sessionId !== sessionId) throw new Error(`EDASessionDurableObjectHost is scoped to session ${this.runtimeState.sessionId}; received ${sessionId}`);
|
|
1182
|
-
return this.runtimeState;
|
|
903
|
+
async get(sessionId) {
|
|
904
|
+
if (this.state !== void 0) {
|
|
905
|
+
if (this.state.sessionId !== sessionId) throw new Error(`EDA session runtime is scoped to session ${this.state.sessionId}; received ${sessionId}`);
|
|
906
|
+
return this.state;
|
|
1183
907
|
}
|
|
1184
908
|
this.keepAlive.restart();
|
|
1185
|
-
const
|
|
1186
|
-
this.
|
|
909
|
+
const building = this.building ?? this.build(sessionId);
|
|
910
|
+
this.building = building;
|
|
1187
911
|
try {
|
|
1188
|
-
return await
|
|
912
|
+
return await building;
|
|
1189
913
|
} catch (error) {
|
|
1190
|
-
if (this.
|
|
914
|
+
if (this.building === building) this.building = void 0;
|
|
1191
915
|
throw error;
|
|
1192
916
|
}
|
|
1193
917
|
}
|
|
1194
|
-
async
|
|
918
|
+
async build(sessionId) {
|
|
1195
919
|
const runtime = ManagedRuntime.make(makeEDADurableObjectRuntimeLayer({
|
|
1196
920
|
config: this.options.config,
|
|
1197
921
|
compactionExecutorLayer: this.options.compactionExecutorLayer,
|
|
@@ -1200,12 +924,13 @@ var EDASessionDurableObjectHost = class {
|
|
|
1200
924
|
modelLayer: this.options.modelLayer,
|
|
1201
925
|
promptProjectorLayer: this.options.promptProjectorLayer,
|
|
1202
926
|
reducers: this.options.reducers,
|
|
927
|
+
sessionEventObserverLayer: this.options.sessionEventObserverLayer,
|
|
1203
928
|
sessionId,
|
|
1204
929
|
sinks: this.options.sinks,
|
|
1205
930
|
storage: this.options.storage,
|
|
1206
|
-
toolkit:
|
|
1207
|
-
toolRegistry:
|
|
1208
|
-
tracer:
|
|
931
|
+
toolkit: resolveFactory(this.options.toolkit, sessionId),
|
|
932
|
+
toolRegistry: resolveFactory(this.options.toolRegistry, sessionId),
|
|
933
|
+
tracer: resolveFactory(this.options.tracer, sessionId)
|
|
1209
934
|
}));
|
|
1210
935
|
const state = {
|
|
1211
936
|
runtime,
|
|
@@ -1213,7 +938,7 @@ var EDASessionDurableObjectHost = class {
|
|
|
1213
938
|
};
|
|
1214
939
|
try {
|
|
1215
940
|
await runtime.context();
|
|
1216
|
-
this.
|
|
941
|
+
this.state = state;
|
|
1217
942
|
return state;
|
|
1218
943
|
} catch (error) {
|
|
1219
944
|
await runtime.dispose().catch(() => void 0);
|
|
@@ -1221,35 +946,487 @@ var EDASessionDurableObjectHost = class {
|
|
|
1221
946
|
}
|
|
1222
947
|
}
|
|
1223
948
|
};
|
|
949
|
+
const resolveFactory = (value, sessionId) => typeof value === "function" ? value({ sessionId }) : value;
|
|
950
|
+
//#endregion
|
|
951
|
+
//#region src/websocket/projection.ts
|
|
952
|
+
/** Stable non-empty identity persisted with an app WebSocket projection. */
|
|
953
|
+
const EDAWebSocketProjectionId = Schema.NonEmptyString.pipe(Schema.brand("EDAWebSocketProjectionId"));
|
|
954
|
+
//#endregion
|
|
955
|
+
//#region src/websocket/attachment.ts
|
|
956
|
+
/** Versioned state required to resume strict delivery after Durable Object hibernation. */
|
|
957
|
+
const EDAWebSocketAttachment = Schema.Struct({
|
|
958
|
+
kind: Schema.Literal("eda-events-v2"),
|
|
959
|
+
sessionId: SessionId,
|
|
960
|
+
subscriberId: SubscriberId,
|
|
961
|
+
trace: EDATraceMetadata,
|
|
962
|
+
delivery: EDAWebSocketDeliveryCheckpoint,
|
|
963
|
+
projection: Schema.optionalKey(Schema.Struct({
|
|
964
|
+
id: EDAWebSocketProjectionId,
|
|
965
|
+
state: Schema.Unknown
|
|
966
|
+
}))
|
|
967
|
+
});
|
|
968
|
+
/** Decode and classify an attachment read from Cloudflare's structured-clone boundary. */
|
|
1224
969
|
const decodeWebSocketAttachment = async (input) => {
|
|
1225
970
|
if (input === null || input === void 0) return { _tag: "Missing" };
|
|
1226
971
|
try {
|
|
1227
972
|
return {
|
|
1228
973
|
_tag: "Decoded",
|
|
1229
|
-
attachment: await Effect.runPromise((
|
|
974
|
+
attachment: await Effect.runPromise(Schema.decodeUnknownEffect(EDAWebSocketAttachment)(input))
|
|
1230
975
|
};
|
|
1231
976
|
} catch {
|
|
1232
977
|
return { _tag: "Malformed" };
|
|
1233
978
|
}
|
|
1234
979
|
};
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
980
|
+
/** Encode before serialization so schema transformations cannot leak into attachments. */
|
|
981
|
+
const encodeWebSocketAttachment = (attachment) => Schema.encodeSync(EDAWebSocketAttachment)(attachment);
|
|
982
|
+
//#endregion
|
|
983
|
+
//#region src/websocket/connection-manager.ts
|
|
984
|
+
/**
|
|
985
|
+
* Owns Cloudflare WebSockets, hibernation attachments, and interpretation of
|
|
986
|
+
* the platform-neutral delivery state machine.
|
|
987
|
+
*/
|
|
988
|
+
var EDAWebSocketConnectionManager = class {
|
|
989
|
+
options;
|
|
990
|
+
observerLayer = SessionEventObserver.FromHandler((event) => Effect.tryPromise(() => this.fanoutPublishedEvent(event)).pipe(Effect.catchCause((cause) => Effect.logError("EDA WebSocket event observer recovered from a host failure", { cause }))));
|
|
991
|
+
sockets = /* @__PURE__ */ new Map();
|
|
992
|
+
constructor(options) {
|
|
993
|
+
this.options = options;
|
|
994
|
+
}
|
|
995
|
+
resolveProjection(id) {
|
|
996
|
+
if (id === void 0) return void 0;
|
|
997
|
+
if (this.options.webSocketProjection?.id === id) return this.options.webSocketProjection;
|
|
998
|
+
}
|
|
999
|
+
async accept(input) {
|
|
1000
|
+
const subscriberId = SubscriberId.make(crypto.randomUUID());
|
|
1001
|
+
const delivery = makeWebSocketDeliveryState({
|
|
1002
|
+
subscriberId,
|
|
1003
|
+
resumeSeq: input.afterSeq,
|
|
1004
|
+
policy: defaultEDAWebSocketFlowControl
|
|
1005
|
+
});
|
|
1006
|
+
const state = {
|
|
1007
|
+
sessionId: input.sessionId,
|
|
1008
|
+
subscriberId,
|
|
1009
|
+
trace: input.trace,
|
|
1010
|
+
delivery,
|
|
1011
|
+
catchUpDeferred: false,
|
|
1012
|
+
head: input.afterSeq,
|
|
1013
|
+
queue: Promise.resolve(),
|
|
1014
|
+
...input.projectionState === void 0 ? {} : { projectionState: input.projectionState }
|
|
1015
|
+
};
|
|
1016
|
+
this.sockets.set(input.webSocket, state);
|
|
1017
|
+
if (!this.persist(input.webSocket, state)) return;
|
|
1018
|
+
if (!await this.send(input.webSocket, state, [deliveryHelloFrame(delivery)])) return;
|
|
1019
|
+
if (!await this.prepare(input.webSocket, state)) return;
|
|
1020
|
+
await this.enqueue(input.webSocket, state, async () => {
|
|
1021
|
+
await this.readAndDeliver(input.webSocket, state, input.afterSeq);
|
|
1022
|
+
});
|
|
1023
|
+
}
|
|
1024
|
+
async message(webSocket, message) {
|
|
1025
|
+
if (typeof message !== "string") {
|
|
1026
|
+
this.close(webSocket, EDA_WS_CLOSE_PROTOCOL_ERROR, "binary");
|
|
1027
|
+
return;
|
|
1028
|
+
}
|
|
1029
|
+
if (message === EDA_WEB_SOCKET_PING_MESSAGE) {
|
|
1030
|
+
try {
|
|
1031
|
+
webSocket.send(EDA_WEB_SOCKET_PONG_MESSAGE);
|
|
1032
|
+
} catch {
|
|
1033
|
+
this.close(webSocket, EDA_WS_CLOSE_SEND_FAILED, "send-failed");
|
|
1034
|
+
}
|
|
1035
|
+
return;
|
|
1036
|
+
}
|
|
1037
|
+
const state = await this.restore(webSocket);
|
|
1038
|
+
if (state === void 0) {
|
|
1039
|
+
this.close(webSocket, EDA_WS_CLOSE_PROTOCOL_ERROR, "protocol");
|
|
1040
|
+
return;
|
|
1041
|
+
}
|
|
1042
|
+
const frame = await Effect.runPromise(this.decodeClientMessage(message, state).pipe(Effect.option));
|
|
1043
|
+
if (frame._tag === "None") {
|
|
1044
|
+
this.close(webSocket, EDA_WS_CLOSE_PROTOCOL_ERROR, "protocol");
|
|
1045
|
+
return;
|
|
1046
|
+
}
|
|
1047
|
+
const clientFrame = frame.value;
|
|
1048
|
+
if (clientFrame._tag === "ping") return;
|
|
1049
|
+
if (!await this.prepare(webSocket, state)) return;
|
|
1050
|
+
await this.enqueue(webSocket, state, async () => {
|
|
1051
|
+
await this.interpret(webSocket, state, onClientAck(state.delivery, clientFrame, Date.now()));
|
|
1052
|
+
});
|
|
1053
|
+
}
|
|
1054
|
+
closed(webSocket) {
|
|
1055
|
+
this.sockets.delete(webSocket);
|
|
1056
|
+
}
|
|
1057
|
+
async flushDeferredCatchUp(sessionId) {
|
|
1058
|
+
const deliveries = [];
|
|
1059
|
+
for (const [webSocket, state] of this.sockets) {
|
|
1060
|
+
if (state.sessionId !== sessionId || !state.catchUpDeferred) continue;
|
|
1061
|
+
deliveries.push(this.enqueue(webSocket, state, async () => {
|
|
1062
|
+
if (!state.catchUpDeferred) return;
|
|
1063
|
+
state.catchUpDeferred = false;
|
|
1064
|
+
await this.readAndDeliver(webSocket, state, state.delivery.sentDurableThroughSeq);
|
|
1065
|
+
}));
|
|
1066
|
+
}
|
|
1067
|
+
await Promise.all(deliveries);
|
|
1068
|
+
}
|
|
1069
|
+
async fanoutPublishedEvent(event) {
|
|
1070
|
+
const webSockets = new Set([...this.options.getWebSockets?.() ?? [], ...this.sockets.keys()]);
|
|
1071
|
+
const deliveries = [];
|
|
1072
|
+
for (const webSocket of webSockets) {
|
|
1073
|
+
const state = await this.restore(webSocket);
|
|
1074
|
+
if (state === void 0) continue;
|
|
1075
|
+
deliveries.push(this.enqueue(webSocket, state, async () => {
|
|
1076
|
+
if (event.event.durability !== "durable") {
|
|
1077
|
+
await this.interpret(webSocket, state, onEphemeralEvent(state.delivery, event, Date.now()));
|
|
1078
|
+
return;
|
|
1079
|
+
}
|
|
1080
|
+
state.head = SequenceNumber.make(Math.max(state.head, event.position.seq));
|
|
1081
|
+
if (event.position.seq <= state.delivery.sentDurableThroughSeq) return;
|
|
1082
|
+
if (event.position.seq === state.delivery.sentDurableThroughSeq + 1) {
|
|
1083
|
+
await this.interpret(webSocket, state, onDurableEvents(state.delivery, [event], state.head, Date.now()));
|
|
1084
|
+
return;
|
|
1085
|
+
}
|
|
1086
|
+
if (!this.options.isSessionReady(state.sessionId)) {
|
|
1087
|
+
state.catchUpDeferred = true;
|
|
1088
|
+
return;
|
|
1089
|
+
}
|
|
1090
|
+
await this.readAndDeliver(webSocket, state, state.delivery.sentDurableThroughSeq);
|
|
1091
|
+
}));
|
|
1092
|
+
}
|
|
1093
|
+
await Promise.all(deliveries);
|
|
1094
|
+
}
|
|
1095
|
+
async restore(webSocket) {
|
|
1096
|
+
const existing = this.sockets.get(webSocket);
|
|
1097
|
+
if (existing !== void 0) return existing;
|
|
1098
|
+
const decoded = await decodeWebSocketAttachment(webSocket.deserializeAttachment());
|
|
1099
|
+
if (decoded._tag !== "Decoded") {
|
|
1100
|
+
this.close(webSocket, EDA_WS_CLOSE_PROTOCOL_ERROR, "protocol");
|
|
1101
|
+
return;
|
|
1102
|
+
}
|
|
1103
|
+
const attachment = decoded.attachment;
|
|
1104
|
+
const projectionState = attachment.projection === void 0 ? void 0 : await this.decodeProjectionState(attachment.projection.id, attachment.projection.state);
|
|
1105
|
+
if (attachment.projection !== void 0 && projectionState === void 0) {
|
|
1106
|
+
this.close(webSocket, EDA_WS_CLOSE_PROTOCOL_ERROR, "protocol");
|
|
1107
|
+
return;
|
|
1108
|
+
}
|
|
1109
|
+
const state = {
|
|
1110
|
+
sessionId: attachment.sessionId,
|
|
1111
|
+
subscriberId: attachment.subscriberId,
|
|
1112
|
+
trace: attachment.trace,
|
|
1113
|
+
delivery: restoreWebSocketDeliveryState({
|
|
1114
|
+
subscriberId: attachment.subscriberId,
|
|
1115
|
+
policy: defaultEDAWebSocketFlowControl,
|
|
1116
|
+
checkpoint: attachment.delivery
|
|
1117
|
+
}),
|
|
1118
|
+
catchUpDeferred: false,
|
|
1119
|
+
head: attachment.delivery.sentDurableThroughSeq,
|
|
1120
|
+
queue: Promise.resolve(),
|
|
1121
|
+
...projectionState === void 0 ? {} : { projectionState }
|
|
1122
|
+
};
|
|
1123
|
+
this.sockets.set(webSocket, state);
|
|
1124
|
+
return state;
|
|
1125
|
+
}
|
|
1126
|
+
async prepare(webSocket, state) {
|
|
1127
|
+
try {
|
|
1128
|
+
await this.options.prepareSession(state.sessionId);
|
|
1129
|
+
return this.sockets.get(webSocket) === state;
|
|
1130
|
+
} catch (error) {
|
|
1131
|
+
await Effect.runPromise(Effect.logError("EDA WebSocket session preparation failed", {
|
|
1132
|
+
error,
|
|
1133
|
+
subscriberId: state.subscriberId
|
|
1134
|
+
}));
|
|
1135
|
+
this.close(webSocket, EDA_WS_CLOSE_SEND_FAILED, "subscriber-failed");
|
|
1136
|
+
return false;
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
async interpret(webSocket, state, result) {
|
|
1140
|
+
state.delivery = result.state;
|
|
1141
|
+
for (const action of result.actions) switch (action._tag) {
|
|
1142
|
+
case "Persist":
|
|
1143
|
+
if (!this.persist(webSocket, state)) return false;
|
|
1144
|
+
break;
|
|
1145
|
+
case "Send":
|
|
1146
|
+
if (!await this.send(webSocket, state, action.frames)) return false;
|
|
1147
|
+
break;
|
|
1148
|
+
case "ReadEventPage":
|
|
1149
|
+
if (!await this.readAndDeliver(webSocket, state, action.afterSeq)) return false;
|
|
1150
|
+
break;
|
|
1151
|
+
case "Close":
|
|
1152
|
+
this.close(webSocket, action.code, action.reason);
|
|
1153
|
+
return false;
|
|
1154
|
+
}
|
|
1155
|
+
return true;
|
|
1156
|
+
}
|
|
1157
|
+
async readAndDeliver(webSocket, state, afterSeq) {
|
|
1158
|
+
const page = await this.options.readEventPage({
|
|
1159
|
+
afterSeq,
|
|
1160
|
+
limit: state.delivery.policy.maxInFlightFrames * state.delivery.policy.maxFrameEvents,
|
|
1161
|
+
sessionId: state.sessionId,
|
|
1162
|
+
subscriberId: state.subscriberId,
|
|
1163
|
+
trace: state.trace
|
|
1164
|
+
});
|
|
1165
|
+
state.head = SequenceNumber.make(Math.max(state.head, page.head));
|
|
1166
|
+
if (page.events.length === 0) return true;
|
|
1167
|
+
return await this.interpret(webSocket, state, onDurableEvents(state.delivery, page.events, state.head, Date.now()));
|
|
1168
|
+
}
|
|
1169
|
+
persist(webSocket, state) {
|
|
1170
|
+
try {
|
|
1171
|
+
const projection = state.projectionState === void 0 ? void 0 : this.options.webSocketProjection;
|
|
1172
|
+
if (state.projectionState !== void 0 && projection === void 0) {
|
|
1173
|
+
this.close(webSocket, EDA_WS_CLOSE_PROTOCOL_ERROR, "protocol");
|
|
1174
|
+
return false;
|
|
1175
|
+
}
|
|
1176
|
+
const attachment = EDAWebSocketAttachment.make({
|
|
1177
|
+
kind: "eda-events-v2",
|
|
1178
|
+
sessionId: state.sessionId,
|
|
1179
|
+
subscriberId: state.subscriberId,
|
|
1180
|
+
trace: state.trace,
|
|
1181
|
+
delivery: checkpointWebSocketDeliveryState(state.delivery),
|
|
1182
|
+
...state.projectionState === void 0 || projection === void 0 ? {} : { projection: {
|
|
1183
|
+
id: projection.id,
|
|
1184
|
+
state: projection.encodeState(state.projectionState)
|
|
1185
|
+
} }
|
|
1186
|
+
});
|
|
1187
|
+
webSocket.serializeAttachment(encodeWebSocketAttachment(attachment));
|
|
1188
|
+
return true;
|
|
1189
|
+
} catch {
|
|
1190
|
+
this.close(webSocket, EDA_WS_CLOSE_SEND_FAILED, "attachment-failed");
|
|
1191
|
+
return false;
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
send(webSocket, state, frames) {
|
|
1195
|
+
return this.sendPrepared(webSocket, state, frames);
|
|
1196
|
+
}
|
|
1197
|
+
async sendPrepared(webSocket, state, frames) {
|
|
1198
|
+
try {
|
|
1199
|
+
const encodedFrames = [];
|
|
1200
|
+
const followUpActions = [];
|
|
1201
|
+
for (const frame of frames) {
|
|
1202
|
+
const projected = state.projectionState === void 0 ? void 0 : this.options.webSocketProjection?.encodeServerFrame(frame, state.projectionState);
|
|
1203
|
+
if (state.projectionState !== void 0 && projected === void 0) {
|
|
1204
|
+
this.close(webSocket, EDA_WS_CLOSE_PROTOCOL_ERROR, "protocol");
|
|
1205
|
+
return false;
|
|
1206
|
+
}
|
|
1207
|
+
if (projected?._tag === "SuppressAndAck") {
|
|
1208
|
+
if (frame._tag !== "events") {
|
|
1209
|
+
this.close(webSocket, EDA_WS_CLOSE_PROTOCOL_ERROR, "protocol");
|
|
1210
|
+
return false;
|
|
1211
|
+
}
|
|
1212
|
+
state.projectionState = projected.state;
|
|
1213
|
+
const acknowledged = onHostSuppressedFrame(state.delivery, {
|
|
1214
|
+
durableThroughSeq: frame.durableThroughSeq,
|
|
1215
|
+
frameId: frame.frameId
|
|
1216
|
+
}, Date.now());
|
|
1217
|
+
state.delivery = acknowledged.state;
|
|
1218
|
+
followUpActions.push(...acknowledged.actions.filter((action) => action._tag !== "Persist"));
|
|
1219
|
+
continue;
|
|
1220
|
+
}
|
|
1221
|
+
if (projected !== void 0) state.projectionState = projected.state;
|
|
1222
|
+
const encoded = projected?._tag === "Send" ? projected.frame : encodeEDAWebSocketServerFrame(frame, this.options.webSocketProtocol);
|
|
1223
|
+
if (new TextEncoder().encode(encoded).byteLength > state.delivery.policy.maxFrameBytes) {
|
|
1224
|
+
this.close(webSocket, EDA_WS_CLOSE_PROTOCOL_ERROR, "frame-too-large");
|
|
1225
|
+
return false;
|
|
1226
|
+
}
|
|
1227
|
+
encodedFrames.push(encoded);
|
|
1228
|
+
}
|
|
1229
|
+
if (state.projectionState !== void 0 && !this.persist(webSocket, state)) return false;
|
|
1230
|
+
for (const encoded of encodedFrames) webSocket.send(encoded);
|
|
1231
|
+
if (followUpActions.length > 0) return await this.interpret(webSocket, state, {
|
|
1232
|
+
state: state.delivery,
|
|
1233
|
+
actions: coalesceFollowUpActions(followUpActions)
|
|
1234
|
+
});
|
|
1235
|
+
return true;
|
|
1236
|
+
} catch {
|
|
1237
|
+
this.close(webSocket, EDA_WS_CLOSE_SEND_FAILED, "send-failed");
|
|
1238
|
+
return false;
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
enqueue(webSocket, state, operation) {
|
|
1242
|
+
const run = async () => {
|
|
1243
|
+
try {
|
|
1244
|
+
await operation();
|
|
1245
|
+
} catch (error) {
|
|
1246
|
+
await Effect.runPromise(Effect.logError("EDA WebSocket operation failed", {
|
|
1247
|
+
error,
|
|
1248
|
+
subscriberId: state.subscriberId
|
|
1249
|
+
}));
|
|
1250
|
+
this.close(webSocket, EDA_WS_CLOSE_SEND_FAILED, "subscriber-failed");
|
|
1251
|
+
}
|
|
1252
|
+
};
|
|
1253
|
+
const next = state.queue.then(run);
|
|
1254
|
+
state.queue = next;
|
|
1255
|
+
return next;
|
|
1256
|
+
}
|
|
1257
|
+
close(webSocket, code, reason) {
|
|
1258
|
+
this.sockets.delete(webSocket);
|
|
1259
|
+
try {
|
|
1260
|
+
webSocket.close(code, reason);
|
|
1261
|
+
} catch {}
|
|
1262
|
+
}
|
|
1263
|
+
decodeClientMessage(message, state) {
|
|
1264
|
+
return state.projectionState === void 0 ? decodeEDAWebSocketClientMessage(message) : this.options.webSocketProjection?.decodeClientMessage(message) ?? Effect.fail("protocol");
|
|
1265
|
+
}
|
|
1266
|
+
async decodeProjectionState(id, encoded) {
|
|
1267
|
+
const projection = this.resolveProjection(id);
|
|
1268
|
+
if (projection === void 0) return void 0;
|
|
1269
|
+
const decoded = await Effect.runPromise(projection.decodeState(encoded).pipe(Effect.option));
|
|
1270
|
+
return decoded._tag === "Some" ? decoded.value : void 0;
|
|
1271
|
+
}
|
|
1239
1272
|
};
|
|
1240
|
-
const
|
|
1241
|
-
|
|
1242
|
-
const
|
|
1243
|
-
const
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1273
|
+
const coalesceFollowUpActions = (actions) => {
|
|
1274
|
+
let readAfterSeq;
|
|
1275
|
+
const retained = [];
|
|
1276
|
+
for (const action of actions) {
|
|
1277
|
+
if (action._tag !== "ReadEventPage") {
|
|
1278
|
+
retained.push(action);
|
|
1279
|
+
continue;
|
|
1280
|
+
}
|
|
1281
|
+
readAfterSeq = SequenceNumber.make(Math.max(readAfterSeq ?? 0, action.afterSeq));
|
|
1282
|
+
}
|
|
1283
|
+
return readAfterSeq === void 0 ? retained : [...retained, {
|
|
1284
|
+
_tag: "ReadEventPage",
|
|
1285
|
+
afterSeq: readAfterSeq
|
|
1286
|
+
}];
|
|
1287
|
+
};
|
|
1288
|
+
//#endregion
|
|
1289
|
+
//#region src/session-controller.ts
|
|
1290
|
+
/**
|
|
1291
|
+
* Public application-facing use cases for one EDA session.
|
|
1292
|
+
*
|
|
1293
|
+
* Runtime lifecycle belongs to EDASessionRuntime; WebSocket transport belongs
|
|
1294
|
+
* to EDAWebSocketConnectionManager. This class only coordinates them.
|
|
1295
|
+
*/
|
|
1296
|
+
var EDASessionController = class {
|
|
1297
|
+
runtime;
|
|
1298
|
+
webSockets;
|
|
1299
|
+
constructor(options) {
|
|
1300
|
+
this.webSockets = new EDAWebSocketConnectionManager({
|
|
1301
|
+
getWebSockets: options.getWebSockets,
|
|
1302
|
+
isSessionReady: (sessionId) => this.runtime.isReady(sessionId),
|
|
1303
|
+
prepareSession: (sessionId) => this.prepareSession(sessionId),
|
|
1304
|
+
readEventPage: async ({ afterSeq, limit, sessionId, subscriberId, trace }) => await this.runtime.run(sessionId, (eda) => eda.readEventPage(afterSeq, limit).pipe(Effect.withSpan("agent.events.page", hostSpanOptions(trace, {
|
|
1305
|
+
"eda.session.id": sessionId,
|
|
1306
|
+
"eda.subscriber.id": subscriberId,
|
|
1307
|
+
"eda.seq.after": afterSeq
|
|
1308
|
+
})))),
|
|
1309
|
+
webSocketProjection: options.webSocketProjection,
|
|
1310
|
+
webSocketProtocol: options.webSocketProtocol
|
|
1311
|
+
});
|
|
1312
|
+
this.runtime = new EDASessionRuntime({
|
|
1313
|
+
...options,
|
|
1314
|
+
sessionEventObserverLayer: this.webSockets.observerLayer
|
|
1315
|
+
});
|
|
1316
|
+
}
|
|
1317
|
+
static migrate = EDASessionRuntime.migrate;
|
|
1318
|
+
submit(input) {
|
|
1319
|
+
const trace = input.trace ?? makeRootEDATraceMetadata();
|
|
1320
|
+
return this.run(input.sessionId, (eda) => eda.submit(input.command).pipe(Effect.withSpan("agent.command.submit", hostSpanOptions(trace, {
|
|
1321
|
+
"eda.session.id": input.sessionId,
|
|
1322
|
+
"eda.command.kind": input.command._tag,
|
|
1323
|
+
"eda.command.id": input.command.commandId
|
|
1324
|
+
}))));
|
|
1325
|
+
}
|
|
1326
|
+
submitBatch(input) {
|
|
1327
|
+
const trace = input.trace ?? makeRootEDATraceMetadata();
|
|
1328
|
+
return this.run(input.sessionId, (eda) => eda.submit(input.items).pipe(Effect.withSpan("agent.command.submit", hostSpanOptions(trace, {
|
|
1329
|
+
"eda.session.id": input.sessionId,
|
|
1330
|
+
"eda.command.submittable_count": input.items.length
|
|
1331
|
+
}))));
|
|
1332
|
+
}
|
|
1333
|
+
submitAndBlock(input) {
|
|
1334
|
+
const trace = input.trace ?? makeRootEDATraceMetadata();
|
|
1335
|
+
return this.run(input.sessionId, (eda) => eda.submitAndBlock(input.command).pipe(Effect.withSpan("agent.command.submit.wait", hostSpanOptions(trace, {
|
|
1336
|
+
"eda.session.id": input.sessionId,
|
|
1337
|
+
"eda.command.kind": input.command._tag,
|
|
1338
|
+
"eda.command.id": input.command.commandId
|
|
1339
|
+
}))));
|
|
1340
|
+
}
|
|
1341
|
+
blockOnCommand(input) {
|
|
1342
|
+
const trace = input.trace ?? makeRootEDATraceMetadata();
|
|
1343
|
+
return this.run(input.sessionId, (eda) => eda.blockOnCommand(input.commandId, input.afterSeq ?? SequenceNumber.make(0)).pipe(Effect.withSpan("agent.command.wait", hostSpanOptions(trace, {
|
|
1344
|
+
"eda.session.id": input.sessionId,
|
|
1345
|
+
"eda.command.id": input.commandId,
|
|
1346
|
+
"eda.seq.after": input.afterSeq
|
|
1347
|
+
}))));
|
|
1348
|
+
}
|
|
1349
|
+
snapshot(input) {
|
|
1350
|
+
return this.query(input, (eda) => eda.snapshot(), "agent.session.snapshot");
|
|
1351
|
+
}
|
|
1352
|
+
messages(input) {
|
|
1353
|
+
return this.query(input, (eda) => eda.messages(), "agent.messages.list");
|
|
1354
|
+
}
|
|
1355
|
+
acceptEventWebSocket(input) {
|
|
1356
|
+
return this.acceptEventWebSocketUnhosted(input);
|
|
1357
|
+
}
|
|
1358
|
+
async acceptEventWebSocketUnhosted(input) {
|
|
1359
|
+
const prepared = await this.prepareEventWebSocket(input);
|
|
1360
|
+
await this.acceptPreparedEventWebSocket({
|
|
1361
|
+
...prepared,
|
|
1362
|
+
webSocket: input.webSocket
|
|
1363
|
+
});
|
|
1364
|
+
}
|
|
1365
|
+
async prepareEventWebSocket(input) {
|
|
1366
|
+
const projection = this.webSockets.resolveProjection(input.projectionId);
|
|
1367
|
+
if (input.projectionId !== void 0 && projection === void 0) throw new Error(`Unsupported EDA WebSocket projection: ${input.projectionId}`);
|
|
1368
|
+
const initialized = projection === void 0 ? void 0 : projection.initialize({
|
|
1369
|
+
...input.afterSeq === void 0 ? {} : { requestedAfterSeq: input.afterSeq },
|
|
1370
|
+
snapshot: await this.snapshot({
|
|
1371
|
+
sessionId: input.sessionId,
|
|
1372
|
+
trace: input.trace
|
|
1373
|
+
})
|
|
1374
|
+
});
|
|
1375
|
+
return {
|
|
1376
|
+
afterSeq: initialized?.afterSeq ?? input.afterSeq ?? SequenceNumber.make(0),
|
|
1377
|
+
sessionId: input.sessionId,
|
|
1378
|
+
trace: input.trace ?? makeRootEDATraceMetadata(),
|
|
1379
|
+
...initialized === void 0 ? {} : { projectionState: initialized.state }
|
|
1380
|
+
};
|
|
1381
|
+
}
|
|
1382
|
+
acceptPreparedEventWebSocket(input) {
|
|
1383
|
+
return this.webSockets.accept(input);
|
|
1384
|
+
}
|
|
1385
|
+
webSocketMessage(webSocket, message) {
|
|
1386
|
+
return this.webSockets.message(webSocket, message);
|
|
1387
|
+
}
|
|
1388
|
+
webSocketClose(webSocket) {
|
|
1389
|
+
this.webSockets.closed(webSocket);
|
|
1390
|
+
}
|
|
1391
|
+
webSocketError(webSocket) {
|
|
1392
|
+
this.webSockets.closed(webSocket);
|
|
1393
|
+
}
|
|
1394
|
+
async destroy(_input) {
|
|
1395
|
+
await this.runtime.destroy();
|
|
1396
|
+
}
|
|
1397
|
+
async alarm(input) {
|
|
1398
|
+
if (input !== void 0) await this.prepareSession(input.sessionId);
|
|
1399
|
+
await this.runtime.alarm(input?.sessionId);
|
|
1400
|
+
}
|
|
1401
|
+
dispose() {
|
|
1402
|
+
return this.runtime.dispose();
|
|
1403
|
+
}
|
|
1404
|
+
query(input, operation, spanName) {
|
|
1405
|
+
const trace = input.trace ?? makeRootEDATraceMetadata();
|
|
1406
|
+
return this.run(input.sessionId, (eda) => operation(eda).pipe(Effect.withSpan(spanName, hostSpanOptions(trace, { "eda.session.id": input.sessionId }))));
|
|
1407
|
+
}
|
|
1408
|
+
async prepareSession(sessionId) {
|
|
1409
|
+
await this.runtime.prepare(sessionId);
|
|
1410
|
+
await this.webSockets.flushDeferredCatchUp(sessionId);
|
|
1411
|
+
}
|
|
1412
|
+
async run(sessionId, operation) {
|
|
1413
|
+
await this.prepareSession(sessionId);
|
|
1414
|
+
return await this.runtime.run(sessionId, operation);
|
|
1415
|
+
}
|
|
1416
|
+
};
|
|
1417
|
+
const hostSpanOptions = (trace, attributes) => ({
|
|
1418
|
+
kind: "server",
|
|
1419
|
+
attributes: compactSpanAttributes({
|
|
1420
|
+
...trace.attributes,
|
|
1421
|
+
...attributes
|
|
1422
|
+
}),
|
|
1423
|
+
links: trace.links.map((link) => ({
|
|
1424
|
+
span: toEdaExternalSpan(link.context),
|
|
1425
|
+
attributes: link.attributes
|
|
1426
|
+
})),
|
|
1427
|
+
...trace.parent === null ? {} : { parent: toEdaExternalSpan(trace.parent) }
|
|
1428
|
+
});
|
|
1252
1429
|
//#endregion
|
|
1253
|
-
export {
|
|
1430
|
+
export { EDAWebSocketProjectionId as n, makeEDADurableObjectRuntimeLayer as r, EDASessionController as t };
|
|
1254
1431
|
|
|
1255
|
-
//# sourceMappingURL=
|
|
1432
|
+
//# sourceMappingURL=session-controller-CMbW7vgf.js.map
|