effect-cf 0.26.0 → 0.28.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 +16 -0
- package/README.md +8 -6
- package/dist/{Artifacts-DcoXcRP5.d.mts → Artifacts-DnHQA-Ax.d.mts} +42 -25
- package/dist/Artifacts-rq3hUKQE.mjs +257 -0
- package/dist/{Binding-COCtoJXa.d.mts → Binding-xJJJPmiD.d.mts} +6 -5
- package/dist/ComputerArtifacts.d.mts +55 -0
- package/dist/ComputerArtifacts.mjs +95 -0
- package/dist/ComputerWorkspace.d.mts +279 -0
- package/dist/ComputerWorkspace.mjs +577 -0
- package/dist/ComputerWorkspaceHost.d.mts +1 -1
- package/dist/ComputerWorkspaceHost.mjs +18 -16
- package/dist/{WorkflowBinding-D5iWzCUa.d.mts → DurableObjectState-B3uFY1eM.d.mts} +90 -141
- package/dist/{DurableObjectState-CEh0DVw7.mjs → DurableObjectState-BjkK2kwH.mjs} +4 -49
- package/dist/ErrorMessage-BM0K-Nik.mjs +46 -0
- package/dist/{Hyperdrive-6p59yyVk.d.mts → Hyperdrive-z-9fFz15.d.mts} +2 -2
- package/dist/HyperdrivePg.d.mts +2 -2
- package/dist/Vitest.d.mts +6 -4
- package/dist/Vitest.mjs +2 -1
- package/dist/WorkflowBinding-wZ-yGHow.d.mts +74 -0
- package/dist/index.d.mts +92 -398
- package/dist/index.mjs +323 -1105
- package/package.json +12 -1
- package/src/AiGateway.ts +62 -7
- package/src/AnalyticsEngine.ts +17 -13
- package/src/Artifacts.ts +111 -47
- package/src/Binding.ts +34 -23
- package/src/BrowserRendering.ts +72 -33
- package/src/CloudflareOtlp.ts +8 -5
- package/src/ComputerArtifacts.ts +17 -9
- package/src/ComputerWorkspace.ts +202 -98
- package/src/ComputerWorkspaceHost.ts +55 -27
- package/src/ContainerNamespace.ts +8 -9
- package/src/D1.ts +8 -14
- package/src/DurableObject.ts +27 -11
- package/src/DurableObjectAlarm.ts +17 -5
- package/src/DurableObjectDefinition.ts +89 -72
- package/src/DurableObjectNamespace.ts +26 -12
- package/src/DurableObjectRpcWebSocket.ts +9 -10
- package/src/DurableObjectState.ts +1 -0
- package/src/DurableObjectStorage.ts +5 -2
- package/src/DurableObjectWebSocket.ts +4 -1
- package/src/Email.ts +26 -25
- package/src/Hyperdrive.ts +4 -10
- package/src/Images.ts +12 -14
- package/src/Kv.ts +15 -17
- package/src/Queue.ts +2 -1
- package/src/QueueBinding.ts +7 -12
- package/src/QueueDefinition.ts +7 -2
- package/src/R2.ts +33 -24
- package/src/Rpc.ts +15 -21
- package/src/RpcDefinition.ts +33 -38
- package/src/ServiceBinding.ts +26 -8
- package/src/Vectorize.ts +45 -18
- package/src/Vitest.ts +9 -3
- package/src/WebTransport.ts +11 -5
- package/src/Worker.ts +91 -21
- package/src/WorkerDefinition.ts +143 -63
- package/src/WorkersAi.ts +50 -28
- package/src/Workflow.ts +66 -13
- package/src/WorkflowBinding.ts +18 -25
- package/src/WorkflowDefinition.ts +4 -2
- package/src/index.ts +0 -2
- package/src/internal/Entrypoint.ts +29 -13
- package/src/internal/ErrorMessage.ts +7 -6
- package/src/internal/RpcInvocation.ts +34 -11
- package/src/internal/Runtime.ts +33 -22
- package/src/internal/WorkerContext.ts +3 -3
|
@@ -125,32 +125,45 @@ const toWorkspaceOptions = (config: ComputerWorkspaceHostConfig): WorkspaceOptio
|
|
|
125
125
|
);
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
|
|
128
|
+
// SAFETY: Cloudflare's native DurableObjectStorage implements the narrower storage methods used
|
|
129
|
+
// by Computer; the union also accepts Computer's structural test/storage contract directly.
|
|
130
|
+
const options: WorkspaceOptions = {
|
|
129
131
|
storage: config.storage as DurableObjectStorageLike,
|
|
130
132
|
git: config.git ?? createGitClient(),
|
|
131
|
-
...(config.gitIdentity === undefined ? {} : { defaultGitIdentity: config.gitIdentity }),
|
|
132
|
-
...(backends.length === 0 ? {} : { backends }),
|
|
133
|
-
...(config.sessionId === undefined ? {} : { sessionId: config.sessionId }),
|
|
134
|
-
...(config.mounts === undefined ? {} : { mounts: config.mounts }),
|
|
135
|
-
...(config.observer === undefined ? {} : { observer: config.observer }),
|
|
136
|
-
...(config.retryScheduler === undefined ? {} : { retryScheduler: config.retryScheduler }),
|
|
137
|
-
...(config.retry === undefined ? {} : { retry: config.retry }),
|
|
138
|
-
...(config.assets === undefined ? {} : { assets: config.assets }),
|
|
139
|
-
...(config.artifacts === undefined
|
|
140
|
-
? {}
|
|
141
|
-
: {
|
|
142
|
-
artifacts: {
|
|
143
|
-
binding: config.artifacts.binding as unknown as NonNullable<
|
|
144
|
-
WorkspaceOptions["artifacts"]
|
|
145
|
-
>["binding"],
|
|
146
|
-
...(config.artifacts.sessionId === undefined
|
|
147
|
-
? {}
|
|
148
|
-
: { sessionId: config.artifacts.sessionId }),
|
|
149
|
-
},
|
|
150
|
-
}),
|
|
151
|
-
...(config.now === undefined ? {} : { now: config.now }),
|
|
152
|
-
...(config.useThink === undefined ? {} : { useThink: config.useThink }),
|
|
153
133
|
};
|
|
134
|
+
|
|
135
|
+
if (config.gitIdentity !== undefined) options.defaultGitIdentity = config.gitIdentity;
|
|
136
|
+
if (backends.length !== 0) options.backends = backends;
|
|
137
|
+
if (config.sessionId !== undefined) options.sessionId = config.sessionId;
|
|
138
|
+
if (config.mounts !== undefined) options.mounts = config.mounts;
|
|
139
|
+
if (config.observer !== undefined) options.observer = config.observer;
|
|
140
|
+
if (config.retryScheduler !== undefined) options.retryScheduler = config.retryScheduler;
|
|
141
|
+
if (config.retry !== undefined) options.retry = config.retry;
|
|
142
|
+
if (config.assets !== undefined) options.assets = config.assets;
|
|
143
|
+
|
|
144
|
+
if (config.artifacts !== undefined) {
|
|
145
|
+
const erasedArtifactsBinding: unknown = config.artifacts.binding;
|
|
146
|
+
// SAFETY: the local binding intentionally models the same Cloudflare Artifacts RPC surface;
|
|
147
|
+
// its readonly result arrays are valid runtime inputs to Computer, whose dependency declaration
|
|
148
|
+
// unnecessarily requires mutable arrays.
|
|
149
|
+
const artifactsBinding = erasedArtifactsBinding as NonNullable<
|
|
150
|
+
WorkspaceOptions["artifacts"]
|
|
151
|
+
>["binding"];
|
|
152
|
+
const artifacts: NonNullable<WorkspaceOptions["artifacts"]> = {
|
|
153
|
+
binding: artifactsBinding,
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
if (config.artifacts.sessionId !== undefined) {
|
|
157
|
+
artifacts.sessionId = config.artifacts.sessionId;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
options.artifacts = artifacts;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (config.now !== undefined) options.now = config.now;
|
|
164
|
+
if (config.useThink !== undefined) options.useThink = config.useThink;
|
|
165
|
+
|
|
166
|
+
return options;
|
|
154
167
|
};
|
|
155
168
|
|
|
156
169
|
// Cloudflare constructs Durable Objects with `(state, env)`. Keeping the
|
|
@@ -185,11 +198,13 @@ export const withComputerWorkspace = <TBase extends DurableObjectConstructor>(
|
|
|
185
198
|
env: ConstructorParameters<TBase>[1],
|
|
186
199
|
) => ComputerWorkspaceHostConfig,
|
|
187
200
|
): WithWorkspaceCtor<TBase> => {
|
|
188
|
-
const CapturedBase = class extends
|
|
201
|
+
const CapturedBase = class extends Base {
|
|
189
202
|
readonly [HostArguments]: CapturedHostArguments;
|
|
190
203
|
|
|
191
204
|
constructor(...args: Array<any>) {
|
|
192
205
|
super(...args);
|
|
206
|
+
// SAFETY: Cloudflare constructs Durable Objects with DurableObjectState as constructor
|
|
207
|
+
// argument zero; the open base tuple retains arbitrary trailing constructor arguments.
|
|
193
208
|
this[HostArguments] = {
|
|
194
209
|
state: args[0] as globalThis.DurableObjectState,
|
|
195
210
|
env: args[1],
|
|
@@ -197,20 +212,33 @@ export const withComputerWorkspace = <TBase extends DurableObjectConstructor>(
|
|
|
197
212
|
}
|
|
198
213
|
};
|
|
199
214
|
|
|
200
|
-
const
|
|
215
|
+
const workspaceConstructor: unknown = withWorkspace(CapturedBase, (self) => {
|
|
216
|
+
// SAFETY: CapturedBase initializes this private symbol for every constructed instance before
|
|
217
|
+
// withWorkspace evaluates its options callback.
|
|
201
218
|
const { env, state } = (self as HostArgumentsInstance)[HostArguments];
|
|
202
219
|
|
|
203
220
|
return toWorkspaceOptions(
|
|
221
|
+
// SAFETY: CapturedBase is an identity subclass of Base, so its instance retains Base's full
|
|
222
|
+
// public instance contract. The captured environment is constructor argument one.
|
|
204
223
|
config(self as InstanceType<TBase>, state, env as ConstructorParameters<TBase>[1]),
|
|
205
224
|
);
|
|
206
|
-
})
|
|
225
|
+
});
|
|
226
|
+
// SAFETY: withWorkspace returns a constructable identity subclass; this local constructor view
|
|
227
|
+
// intentionally hides its generic intersection so TypeScript can extend it.
|
|
228
|
+
const WithWorkspace = workspaceConstructor as DurableObjectConstructor;
|
|
207
229
|
|
|
208
230
|
class WithComputerWorkspace extends WithWorkspace {
|
|
209
231
|
constructor(...args: Array<any>) {
|
|
210
232
|
super(...args);
|
|
233
|
+
// SAFETY: Cloudflare supplies DurableObjectState at constructor argument zero, and
|
|
234
|
+
// withWorkspace adds the WorkspaceHandle methods implemented by this instance.
|
|
211
235
|
HostRegistry.register(args[0] as globalThis.DurableObjectState, this as WorkspaceHandle);
|
|
212
236
|
}
|
|
213
237
|
}
|
|
214
238
|
|
|
215
|
-
|
|
239
|
+
const erasedConstructor: unknown = WithComputerWorkspace;
|
|
240
|
+
|
|
241
|
+
// SAFETY: WithComputerWorkspace only adds registration to the constructor produced by
|
|
242
|
+
// withWorkspace, preserving Base's constructor and instance members plus the workspace host.
|
|
243
|
+
return erasedConstructor as WithWorkspaceCtor<TBase>;
|
|
216
244
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Context, Data, Effect, Schema as S, type Layer } from "effect";
|
|
1
|
+
import { Context, Data, Effect, Predicate, Schema as S, type Layer } from "effect";
|
|
2
2
|
|
|
3
3
|
import * as Binding from "./Binding";
|
|
4
4
|
import type { WorkerEnvironment } from "./Environment";
|
|
@@ -344,10 +344,10 @@ const makeInstanceClient = <Stub extends ContainerStub>(
|
|
|
344
344
|
};
|
|
345
345
|
};
|
|
346
346
|
|
|
347
|
-
export const isContainerNamespaceResource = (
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
347
|
+
export const isContainerNamespaceResource = <Candidate>(
|
|
348
|
+
value: Candidate,
|
|
349
|
+
): value is Candidate & ContainerNamespaceResource =>
|
|
350
|
+
Predicate.hasProperty(value, "getByName") && Predicate.isFunction(value.getByName);
|
|
351
351
|
|
|
352
352
|
export const makeClient =
|
|
353
353
|
(definition: ContainerNamespaceDefinition) =>
|
|
@@ -363,7 +363,7 @@ export const makeClient =
|
|
|
363
363
|
makeInstanceClient(
|
|
364
364
|
definition,
|
|
365
365
|
name,
|
|
366
|
-
// getByName has a single call signature, so its return type is
|
|
366
|
+
// SAFETY: getByName has a single call signature, so its return type is
|
|
367
367
|
// exactly the namespace's stub type.
|
|
368
368
|
namespace.getByName(name, options) as ContainerStubOf<Namespace>,
|
|
369
369
|
),
|
|
@@ -422,9 +422,7 @@ export const layer = <Self, Namespace extends ContainerNamespaceResource>(
|
|
|
422
422
|
Binding.layer(
|
|
423
423
|
tag,
|
|
424
424
|
definition.binding,
|
|
425
|
-
|
|
426
|
-
// asserted by the consumer's binding declaration, like env typing.
|
|
427
|
-
isContainerNamespaceResource as (value: unknown) => value is Namespace,
|
|
425
|
+
(value): value is Namespace => isContainerNamespaceResource(value),
|
|
428
426
|
makeClient(definition),
|
|
429
427
|
{
|
|
430
428
|
expected: expectedContainerNamespace,
|
|
@@ -499,6 +497,7 @@ export const Tag =
|
|
|
499
497
|
|
|
500
498
|
const rawUnsafe = Effect.flatMap(tag, (namespace) => namespace.rawUnsafe);
|
|
501
499
|
|
|
500
|
+
// SAFETY: the assigned namespace helpers exactly implement TagClass for this service tag.
|
|
502
501
|
return Object.assign(tag, {
|
|
503
502
|
id,
|
|
504
503
|
layer: makeLayer,
|
package/src/D1.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { D1Client } from "@effect/sql-d1";
|
|
2
|
-
import { Effect, Layer, type Config } from "effect";
|
|
2
|
+
import { Effect, Layer, Predicate, type Config } from "effect";
|
|
3
3
|
import type { SqlClient } from "effect/unstable/sql";
|
|
4
4
|
|
|
5
5
|
import * as Binding from "./Binding";
|
|
@@ -28,19 +28,13 @@ export interface D1Service<Id extends string> {
|
|
|
28
28
|
};
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
const isD1Database = (value:
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
return (
|
|
39
|
-
typeof resource.prepare === "function" &&
|
|
40
|
-
typeof resource.batch === "function" &&
|
|
41
|
-
typeof resource.exec === "function"
|
|
42
|
-
);
|
|
43
|
-
};
|
|
31
|
+
const isD1Database = <Candidate>(value: Candidate): value is Candidate & D1Database =>
|
|
32
|
+
Predicate.hasProperty(value, "prepare") &&
|
|
33
|
+
Predicate.isFunction(value.prepare) &&
|
|
34
|
+
Predicate.hasProperty(value, "batch") &&
|
|
35
|
+
Predicate.isFunction(value.batch) &&
|
|
36
|
+
Predicate.hasProperty(value, "exec") &&
|
|
37
|
+
Predicate.isFunction(value.exec);
|
|
44
38
|
|
|
45
39
|
/**
|
|
46
40
|
* Creates a typed D1 service tag plus Effect helpers.
|
package/src/DurableObject.ts
CHANGED
|
@@ -52,7 +52,7 @@ export type DurableObjectRpc<ROut> = Record<
|
|
|
52
52
|
(...args: Array<any>) => DurableObjectHandler<ROut>
|
|
53
53
|
>;
|
|
54
54
|
|
|
55
|
-
export type
|
|
55
|
+
export type DurableObjectRpcApi<Rpc extends DurableObjectRpc<ROut>, ROut> = {
|
|
56
56
|
readonly [Key in keyof Rpc]: Rpc[Key] extends (
|
|
57
57
|
...args: infer Args
|
|
58
58
|
) => Effect.Effect<infer A, unknown, HandlerContext<ROut>>
|
|
@@ -66,7 +66,7 @@ export type RpcHandlers<ROut, Api> = {
|
|
|
66
66
|
: Key extends string
|
|
67
67
|
? [Api[Key]] extends [never]
|
|
68
68
|
? never
|
|
69
|
-
: Api[Key] extends (...args: Array<any>) => Promise<
|
|
69
|
+
: Api[Key] extends (...args: Array<any>) => Promise<any>
|
|
70
70
|
? Key
|
|
71
71
|
: never
|
|
72
72
|
: never]: Api[Key] extends (...args: infer Args) => Promise<infer A>
|
|
@@ -142,7 +142,7 @@ export interface DurableObjectOptions<
|
|
|
142
142
|
) => Effect.Effect<void, unknown, HandlerContext<RRuntime | REvent>>;
|
|
143
143
|
readonly webSocketError?: (
|
|
144
144
|
socket: DurableWebSocket,
|
|
145
|
-
|
|
145
|
+
cause: unknown,
|
|
146
146
|
) => Effect.Effect<void, unknown, HandlerContext<RRuntime | REvent>>;
|
|
147
147
|
}
|
|
148
148
|
|
|
@@ -152,7 +152,7 @@ export interface DurableObjectOptions<
|
|
|
152
152
|
export type DurableObjectClass<Rpc extends DurableObjectRpc<ROut>, ROut> = new (
|
|
153
153
|
state: globalThis.DurableObjectState,
|
|
154
154
|
env: WorkerEnv,
|
|
155
|
-
) => CloudflareDurableObject<WorkerEnv> &
|
|
155
|
+
) => CloudflareDurableObject<WorkerEnv> & DurableObjectRpcApi<Rpc, ROut>;
|
|
156
156
|
|
|
157
157
|
/**
|
|
158
158
|
* Creates a Durable Object class backed by a single managed Effect runtime.
|
|
@@ -190,11 +190,24 @@ export const make = <
|
|
|
190
190
|
effect: Effect.Effect<A, E, HandlerContext<ROut | REvent>>,
|
|
191
191
|
runOptions: RunOptions = {},
|
|
192
192
|
): Promise<A> {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
193
|
+
const eventLayer = runOptions.eventLayer === false ? undefined : options.eventLayer;
|
|
194
|
+
|
|
195
|
+
if (eventLayer === undefined) {
|
|
196
|
+
// SAFETY: event-layer bypass is used only for initialize; otherwise an absent layer means REvent is never.
|
|
197
|
+
return Runtime.runEventPromise(
|
|
198
|
+
this.runtime,
|
|
199
|
+
effect as Effect.Effect<A, E, HandlerContext<ROut>>,
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return Runtime.runEventPromise<
|
|
204
|
+
A,
|
|
205
|
+
E,
|
|
206
|
+
RuntimeContext<ROut>,
|
|
207
|
+
REvent,
|
|
208
|
+
EventLayerError,
|
|
209
|
+
LayerError
|
|
210
|
+
>(this.runtime, effect, eventLayer);
|
|
198
211
|
}
|
|
199
212
|
|
|
200
213
|
fetch(request: Request): Promise<Response> {
|
|
@@ -242,9 +255,9 @@ export const make = <
|
|
|
242
255
|
}
|
|
243
256
|
}
|
|
244
257
|
|
|
245
|
-
webSocketError(socket: WebSocket,
|
|
258
|
+
webSocketError(socket: WebSocket, cause: unknown): Promise<void> | void {
|
|
246
259
|
if (options.webSocketError !== undefined) {
|
|
247
|
-
return this[RunSymbol](options.webSocketError(fromWebSocket(socket),
|
|
260
|
+
return this[RunSymbol](options.webSocketError(fromWebSocket(socket), cause));
|
|
248
261
|
}
|
|
249
262
|
}
|
|
250
263
|
}
|
|
@@ -280,3 +293,6 @@ export type {
|
|
|
280
293
|
} from "./DurableObjectDefinition";
|
|
281
294
|
|
|
282
295
|
export { implement, method, Tag } from "./DurableObjectDefinition";
|
|
296
|
+
|
|
297
|
+
// Preserve the original public type export while using a domain-role name internally.
|
|
298
|
+
export type { DurableObjectRpcApi as "DurableObjectRpcShape" };
|
|
@@ -1,4 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
Clock,
|
|
3
|
+
Context,
|
|
4
|
+
Data,
|
|
5
|
+
DateTime,
|
|
6
|
+
Duration,
|
|
7
|
+
Effect,
|
|
8
|
+
Exit,
|
|
9
|
+
Layer,
|
|
10
|
+
Predicate,
|
|
11
|
+
Schema as S,
|
|
12
|
+
} from "effect";
|
|
2
13
|
|
|
3
14
|
import { DurableObjectState } from "./DurableObjectState";
|
|
4
15
|
import type { SqlStorageValue, StorageOperationError } from "./DurableObjectStorage";
|
|
@@ -442,10 +453,10 @@ const getFailureRetryDelay = (options: ProcessDueAlarmsOptions<unknown, unknown>
|
|
|
442
453
|
);
|
|
443
454
|
|
|
444
455
|
const getFailureActionMode = (action: ProcessDueAlarmsFailureAction) =>
|
|
445
|
-
|
|
456
|
+
Predicate.isString(action) ? action : action.mode;
|
|
446
457
|
|
|
447
458
|
const getFailureActionRetryDelay = (action: ProcessDueAlarmsFailureAction) =>
|
|
448
|
-
|
|
459
|
+
Predicate.isString(action) || action.mode !== "retry" ? undefined : action.retryFailedAfter;
|
|
449
460
|
|
|
450
461
|
export const processDue = <R = never, E = never, OnFailureR = never, OnFailureE = never>(
|
|
451
462
|
handle: ProcessDueAlarmsHandler<R, E>,
|
|
@@ -497,8 +508,7 @@ export type DefinedAlarmHandlers<Definitions extends AlarmDefinitions, R = never
|
|
|
497
508
|
|
|
498
509
|
const isAlarmDefinitionConfig = (
|
|
499
510
|
definition: AlarmDefinitionEntry,
|
|
500
|
-
): definition is AlarmDefinitionConfig =>
|
|
501
|
-
typeof definition === "object" && definition !== null && "payload" in definition;
|
|
511
|
+
): definition is AlarmDefinitionConfig => Predicate.isObject(definition) && "payload" in definition;
|
|
502
512
|
|
|
503
513
|
const getAlarmDefinitionSchema = (definition: AlarmDefinitionEntry) =>
|
|
504
514
|
isAlarmDefinitionConfig(definition) ? definition.payload : definition;
|
|
@@ -534,6 +544,7 @@ export const define = <const Definitions extends AlarmDefinitions>(definitions:
|
|
|
534
544
|
}
|
|
535
545
|
|
|
536
546
|
const schema = getAlarmDefinitionSchema(definition);
|
|
547
|
+
// SAFETY: schema is selected from the same tagged definition used to select its handler.
|
|
537
548
|
const payload = yield* (
|
|
538
549
|
S.decodeUnknownEffect(schema)(event.payload) as Effect.Effect<
|
|
539
550
|
AlarmDefinitionPayload<Definitions[keyof Definitions & string]>,
|
|
@@ -550,6 +561,7 @@ export const define = <const Definitions extends AlarmDefinitions>(definitions:
|
|
|
550
561
|
);
|
|
551
562
|
const handler = handlers[event.tag];
|
|
552
563
|
|
|
564
|
+
// SAFETY: event.tag indexes the matching definition and handler, whose payload schema was decoded above.
|
|
553
565
|
yield* handler({ ...event, payload } as never);
|
|
554
566
|
}),
|
|
555
567
|
{
|
|
@@ -9,12 +9,8 @@ import type * as Rpc from "./Rpc";
|
|
|
9
9
|
import * as RpcDefinition from "./RpcDefinition";
|
|
10
10
|
import type { WorkerEnvironment } from "./Environment";
|
|
11
11
|
|
|
12
|
-
/**
|
|
13
|
-
|
|
14
|
-
* these internal invocations erase the binding client's generic argument
|
|
15
|
-
* tuples instead of instantiating them at `never`.
|
|
16
|
-
*/
|
|
17
|
-
type UnsafeInvoke<E> = (...args: ReadonlyArray<unknown>) => Effect.Effect<unknown, E>;
|
|
12
|
+
/** Internal dynamic invocation used only before the public client contract is restored. */
|
|
13
|
+
type ErasedInvoke<E> = (...args: ReadonlyArray<unknown>) => Effect.Effect<unknown, E>;
|
|
18
14
|
|
|
19
15
|
export type ServiceFreeSchema = S.Codec<any, any, never, never>;
|
|
20
16
|
|
|
@@ -49,7 +45,7 @@ export namespace Method {
|
|
|
49
45
|
|
|
50
46
|
export type Success<Self extends Any> = S.Schema.Type<Self["success"]>;
|
|
51
47
|
|
|
52
|
-
export type EncodedSuccess<
|
|
48
|
+
export type EncodedSuccess<_Self extends Any> = S.Json;
|
|
53
49
|
}
|
|
54
50
|
|
|
55
51
|
export type Methods = Record<string, Method.Any>;
|
|
@@ -57,9 +53,12 @@ export type Methods = Record<string, Method.Any>;
|
|
|
57
53
|
/**
|
|
58
54
|
* RPC contract for a Durable Object class.
|
|
59
55
|
*/
|
|
60
|
-
export interface Definition<
|
|
56
|
+
export interface Definition<
|
|
57
|
+
Id extends string = string,
|
|
58
|
+
MethodDefinitions extends Methods = Methods,
|
|
59
|
+
> {
|
|
61
60
|
readonly id: Id;
|
|
62
|
-
readonly methods:
|
|
61
|
+
readonly methods: MethodDefinitions;
|
|
63
62
|
}
|
|
64
63
|
|
|
65
64
|
export namespace Definition {
|
|
@@ -74,8 +73,8 @@ export type ReservedMethodName =
|
|
|
74
73
|
| "webSocketClose"
|
|
75
74
|
| "webSocketError";
|
|
76
75
|
|
|
77
|
-
export type NoReservedMethods<
|
|
78
|
-
Extract<keyof
|
|
76
|
+
export type NoReservedMethods<MethodDefinitions extends Methods> =
|
|
77
|
+
Extract<keyof MethodDefinitions, ReservedMethodName> extends never ? MethodDefinitions : never;
|
|
79
78
|
|
|
80
79
|
const reservedMethodNames = new Set<string>([
|
|
81
80
|
"constructor",
|
|
@@ -113,6 +112,10 @@ type BoundaryHandlers<ROut, Self extends Definition.Any> = {
|
|
|
113
112
|
) => DurableObjectHandler<ROut, Method.EncodedSuccess<Self["methods"][Key]>>;
|
|
114
113
|
};
|
|
115
114
|
|
|
115
|
+
type MutableBoundaryHandlers<ROut, Self extends Definition.Any> = {
|
|
116
|
+
-readonly [Key in keyof BoundaryHandlers<ROut, Self>]: BoundaryHandlers<ROut, Self>[Key];
|
|
117
|
+
};
|
|
118
|
+
|
|
116
119
|
/**
|
|
117
120
|
* Durable Object constructor options for a specific RPC definition.
|
|
118
121
|
*/
|
|
@@ -137,26 +140,30 @@ export type LayerOptions = {
|
|
|
137
140
|
readonly binding: string;
|
|
138
141
|
};
|
|
139
142
|
|
|
140
|
-
export type TagClass<
|
|
143
|
+
export type TagClass<
|
|
144
|
+
Self,
|
|
145
|
+
Id extends string,
|
|
146
|
+
MethodDefinitions extends Methods,
|
|
147
|
+
> = Context.ServiceClass<
|
|
141
148
|
Self,
|
|
142
149
|
`effect-cf/DurableObject/${Id}`,
|
|
143
150
|
DurableObjectNamespace.DurableObjectNamespaceEffectClient<
|
|
144
|
-
Api<Definition<Id,
|
|
145
|
-
Definition<Id,
|
|
151
|
+
Api<Definition<Id, MethodDefinitions>>,
|
|
152
|
+
Definition<Id, MethodDefinitions>
|
|
146
153
|
>
|
|
147
154
|
> &
|
|
148
155
|
DurableObjectNamespace.DurableObjectNamespaceStaticClient<
|
|
149
156
|
Self,
|
|
150
|
-
Api<Definition<Id,
|
|
151
|
-
Definition<Id,
|
|
157
|
+
Api<Definition<Id, MethodDefinitions>>,
|
|
158
|
+
Definition<Id, MethodDefinitions>
|
|
152
159
|
> & {
|
|
153
160
|
readonly id: Id;
|
|
154
|
-
readonly methods:
|
|
161
|
+
readonly methods: MethodDefinitions;
|
|
155
162
|
readonly make: <ROut, LayerError, REvent = never, EventLayerError = never>(
|
|
156
163
|
layer: Layer.Layer<ROut, LayerError, DurableObjectState | WorkerEnvironment>,
|
|
157
|
-
options: Options<ROut, Definition<Id,
|
|
164
|
+
options: Options<ROut, Definition<Id, MethodDefinitions>, REvent, EventLayerError>,
|
|
158
165
|
) => DurableObjectEntrypoint.DurableObjectClass<
|
|
159
|
-
Handlers<ROut | REvent, Definition<Id,
|
|
166
|
+
Handlers<ROut | REvent, Definition<Id, MethodDefinitions>>,
|
|
160
167
|
ROut | REvent
|
|
161
168
|
>;
|
|
162
169
|
readonly layer: (
|
|
@@ -171,18 +178,19 @@ export type TagClass<Self, Id extends string, MethodsShape extends Methods> = Co
|
|
|
171
178
|
/**
|
|
172
179
|
* Defines a single RPC method schema in a Durable Object definition.
|
|
173
180
|
*/
|
|
174
|
-
export
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
<
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
181
|
+
export function method<Success extends ServiceFreeSchema>(definition: {
|
|
182
|
+
readonly success: Success;
|
|
183
|
+
}): Method<readonly [], Success>;
|
|
184
|
+
export function method<
|
|
185
|
+
const Args extends ReadonlyArray<ServiceFreeSchema>,
|
|
186
|
+
Success extends ServiceFreeSchema,
|
|
187
|
+
>(definition: { readonly args: Args; readonly success: Success }): Method<Args, Success>;
|
|
188
|
+
export function method(definition: {
|
|
189
|
+
readonly args?: ReadonlyArray<ServiceFreeSchema>;
|
|
190
|
+
readonly success: ServiceFreeSchema;
|
|
191
|
+
}): Method {
|
|
192
|
+
return RpcDefinition.method(definition);
|
|
193
|
+
}
|
|
186
194
|
|
|
187
195
|
/**
|
|
188
196
|
* Creates a Durable Object RPC definition plus implementation/binding helpers.
|
|
@@ -197,11 +205,11 @@ export const method = RpcDefinition.method as {
|
|
|
197
205
|
* });
|
|
198
206
|
* ```
|
|
199
207
|
*/
|
|
200
|
-
const makeDefinition = <Id extends string, const
|
|
208
|
+
const makeDefinition = <Id extends string, const MethodDefinitions extends Methods>(
|
|
201
209
|
id: Id,
|
|
202
|
-
methods:
|
|
210
|
+
methods: MethodDefinitions & NoReservedMethods<MethodDefinitions>,
|
|
203
211
|
) => {
|
|
204
|
-
type SelfDefinition = Definition<Id,
|
|
212
|
+
type SelfDefinition = Definition<Id, MethodDefinitions>;
|
|
205
213
|
RpcDefinition.assertNoReservedMethods("Durable Object", methods, reservedMethodNames);
|
|
206
214
|
const definition: SelfDefinition = RpcDefinition.make(id, methods);
|
|
207
215
|
|
|
@@ -217,24 +225,20 @@ const makeDefinition = <Id extends string, const MethodsShape extends Methods>(
|
|
|
217
225
|
});
|
|
218
226
|
};
|
|
219
227
|
|
|
220
|
-
export const make = <Id extends string, const
|
|
228
|
+
export const make = <Id extends string, const MethodDefinitions extends Methods>(
|
|
221
229
|
id: Id,
|
|
222
|
-
methods:
|
|
223
|
-
) =>
|
|
224
|
-
Tag<Definition<Id, MethodsShape>>()<Id, MethodsShape>(
|
|
225
|
-
id,
|
|
226
|
-
methods as MethodsShape & NoReservedMethods<MethodsShape>,
|
|
227
|
-
);
|
|
230
|
+
methods: MethodDefinitions & NoReservedMethods<MethodDefinitions>,
|
|
231
|
+
) => Tag<Definition<Id, MethodDefinitions>>()<Id, MethodDefinitions>(id, methods);
|
|
228
232
|
|
|
229
233
|
export const Tag =
|
|
230
234
|
<Self>() =>
|
|
231
|
-
<Id extends string, const
|
|
235
|
+
<Id extends string, const MethodDefinitions extends Methods>(
|
|
232
236
|
id: Id,
|
|
233
|
-
methods:
|
|
234
|
-
) => {
|
|
235
|
-
const definition = makeDefinition<Id,
|
|
237
|
+
methods: MethodDefinitions & NoReservedMethods<MethodDefinitions>,
|
|
238
|
+
): TagClass<Self, Id, MethodDefinitions> => {
|
|
239
|
+
const definition = makeDefinition<Id, MethodDefinitions>(id, methods);
|
|
236
240
|
|
|
237
|
-
type SelfDefinition = Definition<Id,
|
|
241
|
+
type SelfDefinition = Definition<Id, MethodDefinitions>;
|
|
238
242
|
type ClientApi = Api<SelfDefinition>;
|
|
239
243
|
const serviceKey: `effect-cf/DurableObject/${Id}` = `effect-cf/DurableObject/${id}`;
|
|
240
244
|
const tag = Context.Service<
|
|
@@ -315,46 +319,50 @@ export const Tag =
|
|
|
315
319
|
return yield* namespace.fetch(stub, input, init);
|
|
316
320
|
});
|
|
317
321
|
|
|
318
|
-
const rpc = Effect.fn("DurableObject.rpc")(function* <
|
|
322
|
+
const rpc = Effect.fn("DurableObject.rpc")(function* <MethodName extends keyof ClientApi>(
|
|
319
323
|
stub: DurableObjectNamespace.DurableObjectStubClient<ClientApi>,
|
|
320
|
-
|
|
321
|
-
...args: ClientApi[
|
|
324
|
+
methodName: MethodName,
|
|
325
|
+
...args: ClientApi[MethodName] extends (...args: infer Args) => Promise<any> ? Args : never
|
|
322
326
|
) {
|
|
323
327
|
const namespace = yield* tag;
|
|
328
|
+
// SAFETY: ClientApi derives every key and argument tuple from the definition. This internal
|
|
329
|
+
// erased view only bypasses TypeScript's expansion of Rpc.Provider's callable intersection;
|
|
330
|
+
// TagClass restores the exact generic return contract before this helper is exported.
|
|
331
|
+
const invokeRpc = namespace.rpc as ErasedInvoke<DurableObjectNamespace.DurableObjectRpcError>;
|
|
324
332
|
|
|
325
|
-
return yield* (
|
|
326
|
-
stub,
|
|
327
|
-
method,
|
|
328
|
-
...args,
|
|
329
|
-
);
|
|
333
|
+
return yield* invokeRpc(stub, methodName, ...args);
|
|
330
334
|
});
|
|
331
335
|
|
|
332
|
-
const call = Effect.fn("DurableObject.call")(function* <
|
|
336
|
+
const call = Effect.fn("DurableObject.call")(function* <MethodName extends keyof ClientApi>(
|
|
333
337
|
stub: DurableObjectNamespace.DurableObjectStubClient<ClientApi>,
|
|
334
|
-
|
|
335
|
-
...args: ClientApi[
|
|
338
|
+
methodName: MethodName,
|
|
339
|
+
...args: ClientApi[MethodName] extends (...args: infer Args) => Promise<any> ? Args : never
|
|
336
340
|
) {
|
|
337
341
|
const namespace = yield* tag;
|
|
342
|
+
// SAFETY: ClientApi derives every key and argument tuple from the definition. This internal
|
|
343
|
+
// erased view only bypasses TypeScript's expansion of Rpc.Provider's callable intersection;
|
|
344
|
+
// TagClass restores the exact generic return contract before this helper is exported.
|
|
345
|
+
const invokeCall =
|
|
346
|
+
namespace.call as ErasedInvoke<DurableObjectNamespace.DurableObjectRpcError>;
|
|
338
347
|
|
|
339
|
-
return yield* (
|
|
340
|
-
stub,
|
|
341
|
-
method,
|
|
342
|
-
...args,
|
|
343
|
-
);
|
|
348
|
+
return yield* invokeCall(stub, methodName, ...args);
|
|
344
349
|
});
|
|
345
350
|
|
|
346
351
|
const scopedCall = Effect.fn("DurableObject.scopedCall")(function* <
|
|
347
|
-
|
|
352
|
+
MethodName extends keyof ClientApi,
|
|
348
353
|
>(
|
|
349
354
|
stub: DurableObjectNamespace.DurableObjectStubClient<ClientApi>,
|
|
350
|
-
|
|
351
|
-
...args: ClientApi[
|
|
355
|
+
methodName: MethodName,
|
|
356
|
+
...args: ClientApi[MethodName] extends (...args: infer Args) => Promise<any> ? Args : never
|
|
352
357
|
) {
|
|
353
358
|
const namespace = yield* tag;
|
|
359
|
+
// SAFETY: ClientApi derives every key and argument tuple from the definition. This internal
|
|
360
|
+
// erased view only bypasses TypeScript's expansion of Rpc.Provider's callable intersection;
|
|
361
|
+
// TagClass restores the exact generic return contract before this helper is exported.
|
|
362
|
+
const invokeScopedCall =
|
|
363
|
+
namespace.scopedCall as ErasedInvoke<DurableObjectNamespace.DurableObjectRpcError>;
|
|
354
364
|
|
|
355
|
-
return yield* (
|
|
356
|
-
namespace.scopedCall as UnsafeInvoke<DurableObjectNamespace.DurableObjectRpcError>
|
|
357
|
-
)(stub, method, ...args);
|
|
365
|
+
return yield* invokeScopedCall(stub, methodName, ...args);
|
|
358
366
|
});
|
|
359
367
|
|
|
360
368
|
const rawUnsafe = Effect.fnUntraced(function* () {
|
|
@@ -366,6 +374,8 @@ export const Tag =
|
|
|
366
374
|
const directMethods = DurableObjectNamespace.makeDirectMethods<Self, ClientApi, SelfDefinition>(
|
|
367
375
|
definition,
|
|
368
376
|
{
|
|
377
|
+
// SAFETY: direct methods call this helper only with method names and tuples obtained from
|
|
378
|
+
// the same definition; the public direct-method contract restores the result type.
|
|
369
379
|
call: call as never,
|
|
370
380
|
fetch,
|
|
371
381
|
get,
|
|
@@ -373,7 +383,7 @@ export const Tag =
|
|
|
373
383
|
},
|
|
374
384
|
);
|
|
375
385
|
|
|
376
|
-
|
|
386
|
+
const service: unknown = Object.assign(tag, directMethods, {
|
|
377
387
|
id: definition.id,
|
|
378
388
|
methods: definition.methods,
|
|
379
389
|
make: definition.make,
|
|
@@ -389,7 +399,12 @@ export const Tag =
|
|
|
389
399
|
call,
|
|
390
400
|
scopedCall,
|
|
391
401
|
rawUnsafe,
|
|
392
|
-
})
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
// SAFETY: every assigned member above is derived from the same definition and service tag;
|
|
405
|
+
// the erased internal invocation results are exposed only through TagClass's schema-derived
|
|
406
|
+
// client contract.
|
|
407
|
+
return service as TagClass<Self, Id, MethodDefinitions>;
|
|
393
408
|
};
|
|
394
409
|
|
|
395
410
|
export const DurableObject = Tag;
|
|
@@ -398,8 +413,10 @@ const wrapHandlers = <ROut, const Self extends Definition.Any>(
|
|
|
398
413
|
definition: Self,
|
|
399
414
|
handlers: Handlers<ROut, Self>,
|
|
400
415
|
): BoundaryHandlers<ROut, Self> => {
|
|
401
|
-
const wrapped
|
|
416
|
+
const wrapped: MutableBoundaryHandlers<ROut, Self> = Object.create(Object.prototype);
|
|
402
417
|
|
|
418
|
+
// SAFETY: definition.methods owns the complete method-name domain for Self; Object.keys only
|
|
419
|
+
// erases that key information and cannot introduce additional names.
|
|
403
420
|
for (const key of Object.keys(definition.methods) as Array<
|
|
404
421
|
RpcDefinition.Definition.MethodNames<Self>
|
|
405
422
|
>) {
|
|
@@ -414,7 +431,7 @@ const wrapHandlers = <ROut, const Self extends Definition.Any>(
|
|
|
414
431
|
});
|
|
415
432
|
}
|
|
416
433
|
|
|
417
|
-
return wrapped
|
|
434
|
+
return wrapped;
|
|
418
435
|
};
|
|
419
436
|
|
|
420
437
|
/**
|