effect-cf 0.35.0 → 0.37.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 CHANGED
@@ -1,5 +1,19 @@
1
1
  # effect-cf
2
2
 
3
+ ## 0.37.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#135](https://github.com/danieljvdm/effect-cf/pull/135) [`9dea62e`](https://github.com/danieljvdm/effect-cf/commit/9dea62e74818b7fc9da919e8bdbfcc49991aef36) Thanks [@danieljvdm](https://github.com/danieljvdm)! - Add `DurableObjectAlarm.transaction` to commit application storage and logical alarm schedule, replacement, or cancellation together in one native SQLite Durable Object transaction. Application queries can use the existing storage wrapper or a `SqlClient` from `@effect/sql-sqlite-do` backed by the same object. The callback receives transaction-only alarm mutations and preserves the caller's success value, typed errors, and service requirements.
8
+
9
+ Failures before commit roll back application rows, logical alarms, and native alarm reconciliation together. Interruption or a lost reply after commit does not undo committed state. Keep external effects outside the transaction and durably pre-arm a later wake before fallible external work; Cloudflare's native alarm retries remain bounded.
10
+
11
+ ## 0.36.0
12
+
13
+ ### Minor Changes
14
+
15
+ - [#132](https://github.com/danieljvdm/effect-cf/pull/132) [`d2e53a9`](https://github.com/danieljvdm/effect-cf/commit/d2e53a9fab38432a472c9f46a74b87cc89474103) Thanks [@danieljvdm](https://github.com/danieljvdm)! - Add the `effect-cf/mcp` entry point for serving an Effect AI `Toolkit` over MCP from a Worker fetch handler. `Mcp.fromToolkit(toolkit, { name, version, route })` wraps the MCP SDK's stateless Streamable HTTP handler (`createMcpHandler` from `@modelcontextprotocol/server`) together with the Worker edge concerns remote MCP deployments expect (CORS plus `Host` and `Origin` validation with localhost/`workers.dev` defaults): requests on the configured route (default `/mcp`) are answered by a fresh MCP server exposing every toolkit tool, and other requests fall through with `Option.none()` so the rest of the fetch handler (for example an `HttpApi` router) keeps serving them. Tool input/output schemas are derived from each tool's Effect `Schema`, tool calls run through the Worker's Effect runtime with the toolkit handler layer, declared tool failures become `isError` tool results, and invalid arguments are rejected by the tool's parameters schema. The `@modelcontextprotocol/server` package is a new optional peer dependency, needed only when importing `effect-cf/mcp`.
16
+
3
17
  ## 0.35.0
4
18
 
5
19
  ### Minor Changes
package/README.md CHANGED
@@ -47,6 +47,8 @@ Declare `SETTINGS` in `wrangler.jsonc` and pass `SettingsLive` to `Worker.make`.
47
47
 
48
48
  The [counter example](https://github.com/danieljvdm/effect-cf/tree/main/examples/counter) shows `DurableObject.Tag`, storage, a typed RPC call, and the complete Wrangler configuration.
49
49
 
50
+ For atomic application writes and alarm changes, see the [alarm transaction example](tests/fixtures/alarm-transaction-consumer.ts) and [API contract](src/DurableObjectAlarm.ts).
51
+
50
52
  ## Native RPC tracing
51
53
 
52
54
  `call()`, `scopedCall()`, and definition methods create one CLIENT span named `binding/method`, covering argument encoding, the native RPC wait, and success decoding. Raw `rpc()` retains Cloudflare's pipelined result without creating a span. Wrap its complete lifetime with `RpcTracing.withRpcClientSpan` when tracing raw calls.
@@ -1,5 +1,5 @@
1
1
  import { ComputerArtifactsClient } from "./ComputerArtifacts.mjs";
2
- import { t as DurableObjectState } from "./DurableObjectState-DpYdNZgT.mjs";
2
+ import { t as DurableObjectState } from "./DurableObjectState-CpgAmFqo.mjs";
3
3
  import { Context, Effect, Layer, Schema, Scope, Stream } from "effect";
4
4
  import { CommitView, GitClient, GitCloneOptions, GitCommitOptions, GitDiffOptions, GitFetchOptions, GitLogOptions, GitUpdateRefOptions } from "@cloudflare/computer/git";
5
5
  import { ExecSyncResult, RuntimeExecOptions, RuntimeGetOptions, RuntimeKillOptions, ShellValue, SkippedEntry, ThinkWorkspaceCompatibility, WorkspaceClient, WorkspaceRuntimeResult, WorkspaceRuntimeValue as WorkspaceRuntimeValue$1 } from "@cloudflare/computer";
@@ -1,4 +1,4 @@
1
- import { t as DurableObjectState } from "./DurableObjectState-DcqElJ4e.mjs";
1
+ import { t as DurableObjectState } from "./DurableObjectState-DLL0YgBd.mjs";
2
2
  import { fromClient } from "./ComputerArtifacts.mjs";
3
3
  import { t as lookup } from "./ComputerWorkspaceHostRegistry-CY5Fv4Ng.mjs";
4
4
  import { Context, Effect, Layer, Predicate, Schema, Stream } from "effect";
@@ -0,0 +1,244 @@
1
+ import { i as WorkerContextWaitUntilOptions } from "./Worker-Dl6_Pijl.mjs";
2
+ import { Cause, Context, Duration, Effect, Option, Schema } from "effect";
3
+ declare namespace DurableObjectStorage_d_exports {
4
+ export { DurableObjectStorage, DurableObjectTransaction, SchemaBackedSyncKvStorage, SqlCursor, SqlStorage, SqlStorageValue, StorageOperationError, SyncKvDefinition, SyncKvStorage, fromDurableObjectStorage };
5
+ }
6
+ type SqlStorageValue = globalThis.SqlStorageValue;
7
+ declare const StorageOperationError_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]; }>) => Cause.YieldableError & {
8
+ readonly _tag: "StorageOperationError";
9
+ } & Readonly<A>;
10
+ declare class StorageOperationError extends StorageOperationError_base<{
11
+ readonly operation: string;
12
+ readonly cause: unknown;
13
+ }> {
14
+ get message(): string;
15
+ }
16
+ type StorageEffect<A> = Effect.Effect<A, StorageOperationError>;
17
+ interface SqlCursor<T extends Record<string, SqlStorageValue>> {
18
+ next(): StorageEffect<{
19
+ done?: false;
20
+ value: T;
21
+ } | {
22
+ done: true;
23
+ value?: never;
24
+ }>;
25
+ toArray(): StorageEffect<Array<T>>;
26
+ one(): StorageEffect<T>;
27
+ raw<U extends Array<SqlStorageValue>>(): StorageEffect<IterableIterator<U>>;
28
+ readonly columnNames: Array<string>;
29
+ readonly rowsRead: StorageEffect<number>;
30
+ readonly rowsWritten: StorageEffect<number>;
31
+ }
32
+ interface SqlStorage {
33
+ exec<T extends Record<string, SqlStorageValue>>(query: string, ...bindings: Array<SqlStorageValue>): StorageEffect<SqlCursor<T>>;
34
+ readonly databaseSize: number;
35
+ }
36
+ interface SyncKvDefinition<Key, Value, EncodedValue> {
37
+ readonly key: Schema.Codec<Key, string>;
38
+ readonly value: Schema.Codec<Value, EncodedValue>;
39
+ }
40
+ /**
41
+ * Sync KV wrapper that transparently encodes keys/values through Effect Schema.
42
+ */
43
+ interface SchemaBackedSyncKvStorage<Key, Value> {
44
+ get(key: Key): Effect.Effect<Option.Option<Value>, unknown>;
45
+ put(key: Key, value: Value): Effect.Effect<void, unknown>;
46
+ delete(key: Key): Effect.Effect<boolean, unknown>;
47
+ list(options?: globalThis.SyncKvListOptions): Effect.Effect<Array<[Key, Value]>, unknown>;
48
+ }
49
+ interface SyncKvStorage {
50
+ get<T = unknown>(key: string): StorageEffect<T | undefined>;
51
+ put<T>(key: string, value: T): StorageEffect<void>;
52
+ delete(key: string): StorageEffect<boolean>;
53
+ list<T = unknown>(options?: globalThis.SyncKvListOptions): StorageEffect<Array<[string, T]>>;
54
+ schema<Key, Value, EncodedValue>(definition: SyncKvDefinition<Key, Value, EncodedValue>): SchemaBackedSyncKvStorage<Key, Value>;
55
+ }
56
+ /**
57
+ * Effect wrapper around Cloudflare transaction callbacks.
58
+ */
59
+ interface DurableObjectTransaction {
60
+ get<T = unknown>(key: string, options?: globalThis.DurableObjectGetOptions): StorageEffect<T | undefined>;
61
+ get<T = unknown>(keys: Array<string>, options?: globalThis.DurableObjectGetOptions): StorageEffect<Map<string, T>>;
62
+ list<T = unknown>(options?: globalThis.DurableObjectListOptions): StorageEffect<Map<string, T>>;
63
+ put<T>(key: string, value: T, options?: globalThis.DurableObjectPutOptions): StorageEffect<void>;
64
+ put<T>(entries: Record<string, T>, options?: globalThis.DurableObjectPutOptions): StorageEffect<void>;
65
+ delete(key: string, options?: globalThis.DurableObjectPutOptions): StorageEffect<boolean>;
66
+ delete(keys: Array<string>, options?: globalThis.DurableObjectPutOptions): StorageEffect<number>;
67
+ rollback(): StorageEffect<void>;
68
+ getAlarm(options?: globalThis.DurableObjectGetAlarmOptions): StorageEffect<number | null>;
69
+ setAlarm(scheduledTime: number | Date, options?: globalThis.DurableObjectSetAlarmOptions): StorageEffect<void>;
70
+ setAlarmAfter(delay: Duration.Input, options?: globalThis.DurableObjectSetAlarmOptions): StorageEffect<void>;
71
+ deleteAlarm(options?: globalThis.DurableObjectSetAlarmOptions): StorageEffect<void>;
72
+ }
73
+ interface DurableObjectStorage {
74
+ get<T = unknown>(key: string, options?: globalThis.DurableObjectGetOptions): StorageEffect<T | undefined>;
75
+ put<T>(key: string, value: T, options?: globalThis.DurableObjectPutOptions): StorageEffect<void>;
76
+ delete(key: string, options?: globalThis.DurableObjectPutOptions): StorageEffect<boolean>;
77
+ /**
78
+ * Deletes all stored data. On compatibility dates before 2026-02-24, Cloudflare
79
+ * documents that active alarms must be deleted separately with `deleteAlarm()`.
80
+ */
81
+ deleteAll(options?: globalThis.DurableObjectPutOptions): StorageEffect<void>;
82
+ getAlarm(options?: globalThis.DurableObjectGetAlarmOptions): StorageEffect<number | null>;
83
+ setAlarm(scheduledTime: number | Date, options?: globalThis.DurableObjectSetAlarmOptions): StorageEffect<void>;
84
+ setAlarmAfter(delay: Duration.Input, options?: globalThis.DurableObjectSetAlarmOptions): StorageEffect<void>;
85
+ deleteAlarm(options?: globalThis.DurableObjectSetAlarmOptions): StorageEffect<void>;
86
+ /**
87
+ * SQLite-backed Durable Object storage only.
88
+ *
89
+ * The callback Effect must complete synchronously. If it requires asynchronous
90
+ * work, use `transaction` instead so Cloudflare can run the async transaction
91
+ * callback under the platform's transaction contract.
92
+ */
93
+ transactionSync<A, E, R>(closure: () => Effect.Effect<A, E, R>): Effect.Effect<A, E | StorageOperationError, R>;
94
+ /**
95
+ * Runs an async transaction and exposes a typed transaction wrapper.
96
+ */
97
+ transaction<A, E, R>(closure: (txn: DurableObjectTransaction) => Effect.Effect<A, E, R>): Effect.Effect<A, E | StorageOperationError, R>;
98
+ /** Flushes pending writes to disk. */
99
+ sync(): StorageEffect<void>;
100
+ /** Requires SQLite-backed Durable Object storage. */
101
+ getCurrentBookmark(): StorageEffect<string>;
102
+ /** Requires SQLite-backed Durable Object storage. */
103
+ onNextSessionRestoreBookmark(bookmark: string): StorageEffect<string>;
104
+ readonly sql: SqlStorage;
105
+ readonly kv: SyncKvStorage;
106
+ }
107
+ declare const fromDurableObjectStorage: (storage: globalThis.DurableObjectStorage) => DurableObjectStorage;
108
+ declare namespace DurableObjectWebSocket_d_exports {
109
+ export { AcceptUpgradeOptions, AcceptedUpgrade, AttachmentInvalidPolicy, AttachmentRehydrateOptions, DurableWebSocket, DurableWebSocketAttachment, DurableWebSocketAttachmentError, DurableWebSocketCloseError, DurableWebSocketHandlers, DurableWebSocketSendData, DurableWebSocketSendError, RehydratedDurableWebSocket, acceptUpgrade, attachment, fromWebSocket, handlers };
110
+ }
111
+ type DurableWebSocketSendData = string | ArrayBuffer | ArrayBufferView;
112
+ declare const DurableWebSocketSendError_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 & {
113
+ readonly _tag: "DurableWebSocketSendError";
114
+ } & Readonly<A>;
115
+ declare class DurableWebSocketSendError extends DurableWebSocketSendError_base<{
116
+ readonly cause: unknown;
117
+ }> {
118
+ get message(): string;
119
+ }
120
+ declare const DurableWebSocketCloseError_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 & {
121
+ readonly _tag: "DurableWebSocketCloseError";
122
+ } & Readonly<A>;
123
+ declare class DurableWebSocketCloseError extends DurableWebSocketCloseError_base<{
124
+ readonly cause: unknown;
125
+ }> {
126
+ get message(): string;
127
+ }
128
+ declare const DurableWebSocketAttachmentError_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 & {
129
+ readonly _tag: "DurableWebSocketAttachmentError";
130
+ } & Readonly<A>;
131
+ /** Error raised when serializing or deserializing a websocket attachment fails. */
132
+ declare class DurableWebSocketAttachmentError extends DurableWebSocketAttachmentError_base<{
133
+ readonly operation: "serialize" | "deserialize";
134
+ readonly cause: unknown;
135
+ }> {
136
+ get message(): string;
137
+ }
138
+ interface DurableWebSocket<Attachment = unknown> {
139
+ readonly raw: WebSocket;
140
+ send(data: DurableWebSocketSendData): Effect.Effect<void, DurableWebSocketSendError>;
141
+ close(code?: number, reason?: string): Effect.Effect<void, DurableWebSocketCloseError>;
142
+ /** Serializes hibernation attachment metadata onto the socket. */
143
+ serializeAttachment<A extends Attachment = Attachment>(value: A): Effect.Effect<void, DurableWebSocketAttachmentError>;
144
+ /** Deserializes hibernation attachment metadata from the socket. */
145
+ readonly deserializeAttachment: Effect.Effect<unknown, DurableWebSocketAttachmentError>;
146
+ }
147
+ declare const fromWebSocket: <Attachment = unknown>(raw: WebSocket) => DurableWebSocket<Attachment>;
148
+ interface AcceptUpgradeOptions<Attachment = unknown> {
149
+ readonly tags?: ReadonlyArray<string> | undefined;
150
+ /** Optional attachment serialized onto the server socket. */
151
+ readonly attachment?: Attachment | undefined;
152
+ }
153
+ interface AcceptedUpgrade<Attachment = unknown> {
154
+ readonly client: WebSocket;
155
+ readonly server: DurableWebSocket<Attachment>;
156
+ readonly response: Response;
157
+ }
158
+ declare const acceptUpgrade: <Attachment = unknown>(options?: AcceptUpgradeOptions<Attachment> | undefined) => Effect.Effect<AcceptedUpgrade<Attachment>, DurableWebSocketAttachmentError, DurableObjectState>;
159
+ type AttachmentInvalidPolicy = "ignore" | "ignore-and-close" | "fail";
160
+ interface AttachmentRehydrateOptions {
161
+ readonly tag?: string | undefined;
162
+ readonly onInvalid?: AttachmentInvalidPolicy | undefined;
163
+ }
164
+ interface RehydratedDurableWebSocket<Attachment> {
165
+ readonly socket: DurableWebSocket<Attachment>;
166
+ readonly attachment: Attachment;
167
+ }
168
+ interface DurableWebSocketAttachment<Attachment, Encoded> {
169
+ serialize(socket: DurableWebSocket<unknown>, value: Attachment): Effect.Effect<void, DurableWebSocketAttachmentError>;
170
+ deserialize(socket: DurableWebSocket<unknown>): Effect.Effect<Option.Option<Attachment>, DurableWebSocketAttachmentError>;
171
+ rehydrate(options?: AttachmentRehydrateOptions): Effect.Effect<Array<RehydratedDurableWebSocket<Attachment>>, DurableWebSocketAttachmentError, DurableObjectState>;
172
+ readonly schema: Schema.Codec<Attachment, Encoded, never, never>;
173
+ }
174
+ declare const attachment: <const AttachmentSchema extends Schema.Codec<any, any, never, never>>(schema: AttachmentSchema) => DurableWebSocketAttachment<Schema.Schema.Type<AttachmentSchema>, Schema.Codec.Encoded<AttachmentSchema>>;
175
+ interface DurableWebSocketHandlers<R = never, E = unknown> {
176
+ readonly message?: (socket: DurableWebSocket, message: string | ArrayBuffer) => Effect.Effect<void, E, R>;
177
+ readonly close?: (socket: DurableWebSocket, code: number, reason: string, wasClean: boolean) => Effect.Effect<void, E, R>;
178
+ readonly error?: (socket: DurableWebSocket, cause: unknown) => Effect.Effect<void, E, R>;
179
+ }
180
+ declare const handlers: <R = never, E = unknown>(options: DurableWebSocketHandlers<R, E>) => {
181
+ webSocketMessage: ((socket: DurableWebSocket, message: string | ArrayBuffer) => Effect.Effect<void, E, R>) | undefined;
182
+ webSocketClose: ((socket: DurableWebSocket, code: number, reason: string, wasClean: boolean) => Effect.Effect<void, E, R>) | undefined;
183
+ webSocketError: ((socket: DurableWebSocket, cause: unknown) => Effect.Effect<void, E, R>) | undefined;
184
+ };
185
+ declare namespace DurableObjectState_d_exports {
186
+ export { DurableObjectState, DurableObjectStateService, fromDurableObjectState };
187
+ }
188
+ /**
189
+ * Effect-friendly wrapper around Cloudflare `DurableObjectState`.
190
+ */
191
+ interface DurableObjectStateService {
192
+ readonly raw: globalThis.DurableObjectState;
193
+ readonly id: globalThis.DurableObjectId;
194
+ readonly storage: DurableObjectStorage;
195
+ waitUntil(promise: Promise<unknown>): Effect.Effect<void>;
196
+ /**
197
+ * Runs a background Effect via Cloudflare's `waitUntil` with the caller's
198
+ * Effect context, mirroring `WorkerContext.waitUntil` failure modes:
199
+ * `"observe"` (default) logs or routes failures to `onFailure`, while
200
+ * `"propagate"` also rejects the native `waitUntil` promise.
201
+ */
202
+ waitUntil<A, E, R, R2 = never>(effect: Effect.Effect<A, E, R>, options?: WorkerContextWaitUntilOptions<E, R2>): Effect.Effect<void, never, R | R2>;
203
+ /**
204
+ * Runs an Effect inside Cloudflare's `blockConcurrencyWhile` gate.
205
+ *
206
+ * Cloudflare resets a Durable Object if the callback throws/rejects, or if the
207
+ * callback exceeds the platform timeout (currently documented as 30 seconds).
208
+ * This safe default resolves the callback with the Effect `Exit` for typed
209
+ * Effect failures and resumes that `Exit` afterward, so typed failures remain
210
+ * ordinary Effect failures instead of resetting the Durable Object. Defects
211
+ * and interruptions still reject the callback.
212
+ */
213
+ blockConcurrencyWhile<A, E, R>(effect: Effect.Effect<A, E, R>): Effect.Effect<A, E, R>;
214
+ /**
215
+ * Runs an Effect inside Cloudflare's `blockConcurrencyWhile` gate and lets
216
+ * Effect failures reject the callback.
217
+ *
218
+ * Use this only when Cloudflare's reset-on-throw behavior is intentional. The
219
+ * platform also resets the Durable Object if the callback exceeds the
220
+ * documented timeout (currently 30 seconds).
221
+ */
222
+ blockConcurrencyWhileOrReset<A, E, R>(effect: Effect.Effect<A, E, R>): Effect.Effect<A, E, R>;
223
+ acceptWebSocket(ws: DurableWebSocket, tags?: Array<string>): Effect.Effect<void>;
224
+ getWebSockets(tag?: string): Effect.Effect<Array<DurableWebSocket>>;
225
+ setWebSocketAutoResponse(pair?: WebSocketRequestResponsePair): Effect.Effect<void>;
226
+ getWebSocketAutoResponse: Effect.Effect<WebSocketRequestResponsePair | null>;
227
+ getWebSocketAutoResponseTimestamp(ws: DurableWebSocket): Effect.Effect<Date | null>;
228
+ setHibernatableWebSocketEventTimeout(timeoutMs?: number): Effect.Effect<void>;
229
+ getHibernatableWebSocketEventTimeout: Effect.Effect<number | null>;
230
+ getTags(ws: DurableWebSocket): Effect.Effect<Array<string>>;
231
+ /**
232
+ * Forcibly resets the Durable Object. Cloudflare logs an uncaught Error using
233
+ * the optional reason, and the method is not available in `wrangler dev` local
234
+ * development according to the Durable Object State docs. Set
235
+ * `retryAlarm: false` to prevent the current alarm invocation from retrying;
236
+ * this option has no effect outside an alarm handler.
237
+ */
238
+ abort(reason?: string, options?: globalThis.DurableObjectAbortOptions): Effect.Effect<void>;
239
+ }
240
+ declare const DurableObjectState_base: Context.ServiceClass<DurableObjectState, "effect-cf/DurableObjectState", DurableObjectStateService>;
241
+ declare class DurableObjectState extends DurableObjectState_base {}
242
+ declare const fromDurableObjectState: (state: globalThis.DurableObjectState) => DurableObjectStateService;
243
+ //#endregion
244
+ export { AcceptedUpgrade as a, DurableWebSocketAttachmentError as c, AcceptUpgradeOptions as i, DurableObjectStorage_d_exports as l, DurableObjectStateService as n, DurableObjectWebSocket_d_exports as o, DurableObjectState_d_exports as r, DurableWebSocket as s, DurableObjectState as t, StorageOperationError as u };
@@ -1,35 +1,7 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
2
  import { t as causeMessage } from "./ErrorMessage-Byx2OgNd.mjs";
3
- import { Cause, Clock, Context, Data, Duration, Effect, Exit, Fiber, FiberSet, Option, Predicate, Result, Schema } from "effect";
4
- //#region src/internal/NativeCallback.ts
5
- /**
6
- * Runs an Effect callback owned by a native Promise operation.
7
- *
8
- * The callback keeps the caller's context, while a private scope owns its
9
- * fiber. Closing that scope interrupts and joins the callback before awaiting
10
- * the native operation's settlement.
11
- */
12
- const runNativeCallback = Effect.fnUntraced(function* (operation) {
13
- const callerContext = yield* Effect.context();
14
- return yield* Effect.scoped(Effect.gen(function* () {
15
- let nativePromise;
16
- yield* Effect.addFinalizer(() => {
17
- const promise = nativePromise;
18
- return promise === void 0 ? Effect.void : Effect.promise(() => promise.then(() => void 0, () => void 0));
19
- });
20
- const runPromise = yield* FiberSet.makeRuntimePromise();
21
- return yield* Effect.callback((resume) => {
22
- try {
23
- nativePromise = operation((effect) => runPromise(Effect.provideContext(Effect.exit(effect), callerContext)));
24
- } catch (cause) {
25
- resume(Effect.fail(cause));
26
- return;
27
- }
28
- nativePromise.then((value) => resume(Effect.succeed(value)), (cause) => resume(Effect.fail(cause)));
29
- });
30
- }));
31
- });
32
- //#endregion
3
+ import { n as makeWaitUntilScheduler, r as runNativeCallback } from "./WorkerContext-Yobnfw-K.mjs";
4
+ import { Cause, Clock, Context, Data, Duration, Effect, Exit, Fiber, Option, Predicate, Result, Schema } from "effect";
33
5
  //#region src/DurableObjectStorage.ts
34
6
  var DurableObjectStorage_exports = /* @__PURE__ */ __exportAll({
35
7
  StorageOperationError: () => StorageOperationError,
@@ -279,35 +251,6 @@ const handlers = (options) => ({
279
251
  webSocketError: options.error
280
252
  });
281
253
  //#endregion
282
- //#region src/internal/WorkerContext.ts
283
- const causeError = (cause) => {
284
- const squashed = Cause.squash(cause);
285
- return squashed instanceof Error ? squashed : new Error(Cause.pretty(cause), { cause: squashed });
286
- };
287
- const failureHandler = (label, cause, options) => Effect.suspend(() => options?.onFailure?.(cause) ?? Effect.logError(`${label} failed`, Cause.pretty(cause))).pipe(Effect.catchCause((handlerCause) => Effect.logError(`${label} failure handler failed`, Cause.pretty(cause), Cause.pretty(handlerCause))));
288
- const makeWaitUntilScheduler = (label, register, runPromiseExit = (effect) => Effect.runPromiseExit(effect)) => {
289
- return (effect, options, mode) => Effect.context().pipe(Effect.flatMap((context) => Effect.sync(() => {
290
- const runHandler = (cause) => runPromiseExit(Effect.provideContext(Effect.scoped(failureHandler(label, cause, options)), context)).then((exit) => {
291
- if (Exit.isFailure(exit)) console.error(`${label} failure handler failed`, Cause.pretty(exit.cause));
292
- });
293
- register(runPromiseExit(Effect.provideContext(Effect.scoped(effect), context)).then(async (exit) => {
294
- if (Exit.isSuccess(exit)) return;
295
- await runHandler(exit.cause);
296
- if (mode === "propagate") throw causeError(exit.cause);
297
- }));
298
- })));
299
- };
300
- const fromExecutionContext = (ctx, runPromiseExit = (effect) => Effect.runPromiseExit(effect)) => {
301
- const schedule = makeWaitUntilScheduler("WorkerContext.waitUntil", (promise) => ctx.waitUntil(promise), runPromiseExit);
302
- return {
303
- raw: ctx,
304
- waitUntil: (effect, options) => schedule(effect, options, options?.mode ?? "observe"),
305
- waitUntilPropagating: (effect, options) => schedule(effect, options, "propagate"),
306
- passThroughOnException: Effect.sync(() => ctx.passThroughOnException()),
307
- abort: (reason) => Effect.sync(() => ctx.abort(reason))
308
- };
309
- };
310
- //#endregion
311
254
  //#region src/DurableObjectState.ts
312
255
  var DurableObjectState_exports = /* @__PURE__ */ __exportAll({
313
256
  DurableObjectState: () => DurableObjectState,
@@ -345,4 +288,4 @@ const runExitPreservingTypedFailures = async (run, effect) => {
345
288
  return exit;
346
289
  };
347
290
  //#endregion
348
- export { DurableObjectWebSocket_exports as a, DurableObjectStorage_exports as c, fromExecutionContext as i, runNativeCallback as l, DurableObjectState_exports as n, DurableWebSocketAttachmentError as o, fromDurableObjectState as r, fromWebSocket as s, DurableObjectState as t };
291
+ export { DurableWebSocketAttachmentError as a, StorageOperationError as c, DurableObjectWebSocket_exports as i, DurableObjectState_exports as n, fromWebSocket as o, fromDurableObjectState as r, DurableObjectStorage_exports as s, DurableObjectState as t };
package/dist/Mcp.d.mts ADDED
@@ -0,0 +1,122 @@
1
+ import { n as NativeRequest } from "./Worker-Dl6_Pijl.mjs";
2
+ import { Effect, Option } from "effect";
3
+ import { CreateMcpHandlerOptions, ServerOptions } from "@modelcontextprotocol/server";
4
+ import { Tool, Toolkit } from "effect/unstable/ai";
5
+ //#region src/Mcp.d.ts
6
+ /**
7
+ * CORS headers applied to MCP responses.
8
+ */
9
+ interface CorsOptions {
10
+ readonly origin?: string;
11
+ readonly methods?: string;
12
+ readonly headers?: string;
13
+ readonly maxAge?: number;
14
+ readonly exposeHeaders?: string;
15
+ }
16
+ /**
17
+ * Options for {@link fromToolkit}.
18
+ *
19
+ * Extends the MCP SDK `createMcpHandler` options (`legacy`, `responseMode`,
20
+ * `onerror`, ...) with the server identity advertised to MCP clients and the
21
+ * Worker edge options (`route`, `corsOptions`, `allowedHostnames`,
22
+ * `allowedOriginHostnames`).
23
+ */
24
+ interface FromToolkitOptions extends CreateMcpHandlerOptions {
25
+ /** Server name advertised in the MCP implementation info. */
26
+ readonly name: string;
27
+ /** Server version advertised in the MCP implementation info. */
28
+ readonly version: string;
29
+ /** Optional human-readable server title advertised to MCP clients. */
30
+ readonly title?: string;
31
+ /**
32
+ * Options passed to the underlying MCP SDK `McpServer` (capabilities,
33
+ * instructions, ...).
34
+ */
35
+ readonly serverOptions?: ServerOptions;
36
+ /** Pathname served by the MCP handler. @default "/mcp" */
37
+ readonly route?: string;
38
+ /** CORS headers applied to every MCP response. Pass `false` to disable. */
39
+ readonly corsOptions?: CorsOptions | false;
40
+ /**
41
+ * Restrict `Host` headers to these hostnames. Localhost and `workers.dev`
42
+ * endpoints receive matching defaults; custom domains rely on Cloudflare
43
+ * routing unless this option is set.
44
+ */
45
+ readonly allowedHostnames?: ReadonlyArray<string>;
46
+ /**
47
+ * Restrict present browser `Origin` headers to these hostnames. Requests
48
+ * without an Origin (including non-browser MCP clients) remain valid. The
49
+ * default includes localhost-class Origins, the endpoint's `workers.dev`
50
+ * hostname, and a concrete `corsOptions.origin` hostname. Pass `"*"` only
51
+ * when equivalent Origin validation runs in trusted middleware upstream.
52
+ */
53
+ readonly allowedOriginHostnames?: ReadonlyArray<string> | "*";
54
+ }
55
+ /**
56
+ * Services required to execute the tools of a toolkit: the tool handlers plus
57
+ * every service those handlers and their parameter/result codecs need.
58
+ */
59
+ type ToolkitServices<Tools extends Record<string, Tool.Any>> = Tool.HandlersFor<Tools> | Tool.HandlerServices<Tools[keyof Tools]>;
60
+ /**
61
+ * Context required by the Effect returned from {@link fromToolkit}: the native
62
+ * request provided by `Worker.make` plus the toolkit services.
63
+ */
64
+ type FromToolkitContext<Tools extends Record<string, Tool.Any>> = NativeRequest | ToolkitServices<Tools>;
65
+ /**
66
+ * Creates an Effect that serves a toolkit over MCP with the MCP SDK's
67
+ * stateless Streamable HTTP handler (`createMcpHandler` from
68
+ * `@modelcontextprotocol/server`), plus the Worker edge concerns Cloudflare's
69
+ * remote MCP deployments expect: CORS, `Host` header validation, and browser
70
+ * `Origin` validation.
71
+ *
72
+ * **Details**
73
+ *
74
+ * Yield the returned Effect inside a `Worker.make` fetch handler before other
75
+ * routing. When the request pathname matches `route` (default `"/mcp"`), the
76
+ * request is served by a fresh MCP server exposing every tool in the toolkit
77
+ * and the Effect succeeds with `Option.some(response)`. Any other pathname
78
+ * succeeds with `Option.none()` so the request can fall through to the rest of
79
+ * the fetch handler.
80
+ *
81
+ * Tool calls run through the caller's Effect context: provide the toolkit
82
+ * handlers (`toolkit.toLayer(...)`) and their services in the Worker layer or
83
+ * event layer. Tool parameters are decoded and results encoded with each
84
+ * tool's Effect `Schema`; parameter validation failures become MCP
85
+ * invalid-params errors, declared tool failures become tool results with
86
+ * `isError: true`, and unexpected errors are logged and reported to the client
87
+ * as an internal tool error.
88
+ *
89
+ * In-flight tool calls are awaited before the HTTP response is returned, so
90
+ * tool handlers always run to completion inside the fetch event, including on
91
+ * transport lanes that stream results into the response body.
92
+ *
93
+ * **Example**
94
+ *
95
+ * ```ts
96
+ * import { Effect, Layer, Option } from "effect"
97
+ * import { HttpRouter } from "effect/unstable/http"
98
+ * import { Worker } from "effect-cf"
99
+ * import * as Mcp from "effect-cf/mcp"
100
+ *
101
+ * const mcp = Mcp.fromToolkit(ActivityToolkit, {
102
+ * name: "strava-ingestor",
103
+ * version: "1.0.0",
104
+ * route: "/mcp",
105
+ * })
106
+ *
107
+ * const fetch = Effect.gen(function* () {
108
+ * const response = yield* mcp
109
+ * if (Option.isSome(response)) return response.value
110
+ * const router = yield* HttpRouter.HttpRouter
111
+ * return yield* router.asHttpEffect()
112
+ * })
113
+ *
114
+ * export default Worker.make(
115
+ * Layer.mergeAll(ApplicationLive, ActivityToolkitLive),
116
+ * { fetch },
117
+ * )
118
+ * ```
119
+ */
120
+ declare const fromToolkit: <Tools extends Record<string, Tool.Any>>(toolkit: Toolkit.Toolkit<Tools>, options: FromToolkitOptions) => Effect.Effect<Option.Option<Response>, unknown, FromToolkitContext<Tools>>;
121
+ //#endregion
122
+ export { CorsOptions, FromToolkitContext, FromToolkitOptions, ToolkitServices, fromToolkit };