effect-cf 0.50.0 → 0.52.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 +20 -0
- package/README.md +2 -0
- package/dist/DurableObjectAlarm.d.mts +68 -15
- package/dist/DurableObjectAlarm.mjs +261 -110
- package/dist/DurableObjectContainer.d.mts +3 -1
- package/dist/DurableObjectContainer.mjs +4 -2
- package/dist/DurableObjectRpcWebSocket.d.mts +2 -2
- package/dist/DurableObjectRpcWebSocket.mjs +2 -6
- package/dist/DurableObjectStorage.d.mts +2 -9
- package/dist/DurableObjectStorage.mjs +1 -14
- package/dist/DurableObjectWebSocket.d.mts +10 -2
- package/dist/DurableObjectWebSocket.mjs +18 -2
- package/dist/RpcTracing.mjs +1 -1
- package/package.json +1 -1
- package/src/DurableObjectAlarm.ts +536 -202
- package/src/DurableObjectContainer.ts +12 -1
- package/src/DurableObjectRpcWebSocket.ts +4 -14
- package/src/DurableObjectStorage.ts +2 -59
- package/src/DurableObjectWebSocket.ts +24 -0
|
@@ -102,6 +102,10 @@ export interface ContainerTcpPort {
|
|
|
102
102
|
readonly connect: (
|
|
103
103
|
...args: Parameters<globalThis.Fetcher["connect"]>
|
|
104
104
|
) => Effect.Effect<ReturnType<globalThis.Fetcher["connect"]>, ContainerError>;
|
|
105
|
+
/** Closes the socket when the scope ends, awaiting cleanup and preserving close failures as defects. */
|
|
106
|
+
readonly connectScoped: (
|
|
107
|
+
...args: Parameters<globalThis.Fetcher["connect"]>
|
|
108
|
+
) => Effect.Effect<ReturnType<globalThis.Fetcher["connect"]>, ContainerError, Scope.Scope>;
|
|
105
109
|
}
|
|
106
110
|
|
|
107
111
|
/** Direct container control inside a Durable Object; lifecycle policy belongs to the application. */
|
|
@@ -248,6 +252,9 @@ const fromTcpPort = (port: globalThis.Fetcher): ContainerTcpPort => {
|
|
|
248
252
|
});
|
|
249
253
|
});
|
|
250
254
|
|
|
255
|
+
const connect: ContainerTcpPort["connect"] = (...args) =>
|
|
256
|
+
attempt("getTcpPort.connect", () => port.connect(...args));
|
|
257
|
+
|
|
251
258
|
return {
|
|
252
259
|
raw: port,
|
|
253
260
|
fetch,
|
|
@@ -259,7 +266,11 @@ const fromTcpPort = (port: globalThis.Fetcher): ContainerTcpPort => {
|
|
|
259
266
|
|
|
260
267
|
return new Request(url, request);
|
|
261
268
|
}).pipe(Effect.flatMap((forwarded) => fetch(forwarded))),
|
|
262
|
-
connect
|
|
269
|
+
connect,
|
|
270
|
+
connectScoped: (...args) =>
|
|
271
|
+
Effect.acquireRelease(connect(...args), (socket) =>
|
|
272
|
+
attemptPromise("getTcpPort.close", () => socket.close()).pipe(Effect.orDie),
|
|
273
|
+
),
|
|
263
274
|
};
|
|
264
275
|
};
|
|
265
276
|
|
|
@@ -87,8 +87,8 @@ export interface LayerOptions {
|
|
|
87
87
|
readonly attachmentKey?: string | undefined;
|
|
88
88
|
/**
|
|
89
89
|
* Heartbeat behavior. `"auto-response"` (the default) installs Cloudflare's
|
|
90
|
-
* hibernation-safe text Ping/Pong response
|
|
91
|
-
*
|
|
90
|
+
* hibernation-safe text Ping/Pong response for the lifetime of RPC sockets,
|
|
91
|
+
* including pending operations. Keepalives never dispatch application code.
|
|
92
92
|
* `"passthrough"` leaves the Durable Object's existing auto-response
|
|
93
93
|
* configuration unchanged and makes the application responsible for waking a
|
|
94
94
|
* lost pending operation.
|
|
@@ -248,7 +248,6 @@ export const layer = (
|
|
|
248
248
|
readonly request: RpcMessage.RequestEncoded;
|
|
249
249
|
}> = [];
|
|
250
250
|
let nextClientId = 0;
|
|
251
|
-
let activeNonResumableRequestCount = 0;
|
|
252
251
|
let writeRequest:
|
|
253
252
|
| ((clientId: number, data: RpcMessage.FromClientEncoded) => Effect.Effect<void>)
|
|
254
253
|
| undefined;
|
|
@@ -383,8 +382,7 @@ export const layer = (
|
|
|
383
382
|
heartbeatPolicy,
|
|
384
383
|
);
|
|
385
384
|
|
|
386
|
-
const syncHeartbeat = () =>
|
|
387
|
-
heartbeat.setEnabled(connectionsBySocket.size > 0 && activeNonResumableRequestCount === 0);
|
|
385
|
+
const syncHeartbeat = () => heartbeat.setEnabled(connectionsBySocket.size > 0);
|
|
388
386
|
|
|
389
387
|
const persistConnection = Effect.fn("DurableObjectRpcWebSocket.persistConnection")(function* (
|
|
390
388
|
connection: RpcConnection,
|
|
@@ -430,7 +428,6 @@ export const layer = (
|
|
|
430
428
|
|
|
431
429
|
connectionsBySocket.delete(socket.raw);
|
|
432
430
|
connectionsById.delete(connection.id);
|
|
433
|
-
activeNonResumableRequestCount -= connection.nonResumableRequestIds.size;
|
|
434
431
|
connection.requestIds.clear();
|
|
435
432
|
connection.nonResumableRequestIds.clear();
|
|
436
433
|
connection.subscriptionsByRequestId.clear();
|
|
@@ -511,7 +508,6 @@ export const layer = (
|
|
|
511
508
|
} else {
|
|
512
509
|
connection.requestIds.add(request.id);
|
|
513
510
|
connection.nonResumableRequestIds.add(request.id);
|
|
514
|
-
activeNonResumableRequestCount++;
|
|
515
511
|
}
|
|
516
512
|
|
|
517
513
|
if (!(yield* persistOrReset(connection))) {
|
|
@@ -529,9 +525,7 @@ export const layer = (
|
|
|
529
525
|
preserveAckFilter = false,
|
|
530
526
|
) {
|
|
531
527
|
if (connection.requestIds.delete(requestId)) {
|
|
532
|
-
|
|
533
|
-
activeNonResumableRequestCount--;
|
|
534
|
-
}
|
|
528
|
+
connection.nonResumableRequestIds.delete(requestId);
|
|
535
529
|
|
|
536
530
|
const subscription = removeSubscription(connection, requestId);
|
|
537
531
|
|
|
@@ -557,10 +551,6 @@ export const layer = (
|
|
|
557
551
|
function* (connection: RpcConnection) {
|
|
558
552
|
const hadRequests = connection.requestIds.size > 0;
|
|
559
553
|
|
|
560
|
-
if (connection.requestIds.size > 0) {
|
|
561
|
-
activeNonResumableRequestCount -= connection.nonResumableRequestIds.size;
|
|
562
|
-
}
|
|
563
|
-
|
|
564
554
|
connection.requestIds.clear();
|
|
565
555
|
connection.nonResumableRequestIds.clear();
|
|
566
556
|
connection.subscriptionsByRequestId.clear();
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import * as Cause from "effect/Cause";
|
|
2
|
-
import * as Clock from "effect/Clock";
|
|
3
2
|
import * as Data from "effect/Data";
|
|
4
|
-
import * as Duration from "effect/Duration";
|
|
5
3
|
import * as Effect from "effect/Effect";
|
|
6
4
|
import * as Exit from "effect/Exit";
|
|
7
5
|
import * as Fiber from "effect/Fiber";
|
|
@@ -91,15 +89,6 @@ export interface DurableObjectTransaction {
|
|
|
91
89
|
delete(keys: Array<string>, options?: globalThis.DurableObjectPutOptions): StorageEffect<number>;
|
|
92
90
|
rollback(): StorageEffect<void>;
|
|
93
91
|
getAlarm(options?: globalThis.DurableObjectGetAlarmOptions): StorageEffect<number | null>;
|
|
94
|
-
setAlarm(
|
|
95
|
-
scheduledTime: number | Date,
|
|
96
|
-
options?: globalThis.DurableObjectSetAlarmOptions,
|
|
97
|
-
): StorageEffect<void>;
|
|
98
|
-
setAlarmAfter(
|
|
99
|
-
delay: Duration.Input,
|
|
100
|
-
options?: globalThis.DurableObjectSetAlarmOptions,
|
|
101
|
-
): StorageEffect<void>;
|
|
102
|
-
deleteAlarm(options?: globalThis.DurableObjectSetAlarmOptions): StorageEffect<void>;
|
|
103
92
|
}
|
|
104
93
|
|
|
105
94
|
export interface DurableObjectStorage {
|
|
@@ -110,20 +99,11 @@ export interface DurableObjectStorage {
|
|
|
110
99
|
put<T>(key: string, value: T, options?: globalThis.DurableObjectPutOptions): StorageEffect<void>;
|
|
111
100
|
delete(key: string, options?: globalThis.DurableObjectPutOptions): StorageEffect<boolean>;
|
|
112
101
|
/**
|
|
113
|
-
* Deletes all stored data
|
|
114
|
-
*
|
|
102
|
+
* Deletes all stored data, including logical schedules. Use the alarm scheduler
|
|
103
|
+
* to cancel individual alarms without deleting application data.
|
|
115
104
|
*/
|
|
116
105
|
deleteAll(options?: globalThis.DurableObjectPutOptions): StorageEffect<void>;
|
|
117
106
|
getAlarm(options?: globalThis.DurableObjectGetAlarmOptions): StorageEffect<number | null>;
|
|
118
|
-
setAlarm(
|
|
119
|
-
scheduledTime: number | Date,
|
|
120
|
-
options?: globalThis.DurableObjectSetAlarmOptions,
|
|
121
|
-
): StorageEffect<void>;
|
|
122
|
-
setAlarmAfter(
|
|
123
|
-
delay: Duration.Input,
|
|
124
|
-
options?: globalThis.DurableObjectSetAlarmOptions,
|
|
125
|
-
): StorageEffect<void>;
|
|
126
|
-
deleteAlarm(options?: globalThis.DurableObjectSetAlarmOptions): StorageEffect<void>;
|
|
127
107
|
/**
|
|
128
108
|
* SQLite-backed Durable Object storage only.
|
|
129
109
|
*
|
|
@@ -165,21 +145,6 @@ const tryStoragePromise = <A>(operation: string, evaluate: () => Promise<A>): St
|
|
|
165
145
|
catch: (cause) => storageError(operation, cause),
|
|
166
146
|
});
|
|
167
147
|
|
|
168
|
-
const setAlarmAfter = Effect.fn("DurableObjectStorage.setAlarmAfter")(function* (
|
|
169
|
-
operation: string,
|
|
170
|
-
setAlarm: (
|
|
171
|
-
scheduledTime: number,
|
|
172
|
-
options?: globalThis.DurableObjectSetAlarmOptions,
|
|
173
|
-
) => Promise<void>,
|
|
174
|
-
delay: Duration.Input,
|
|
175
|
-
options?: globalThis.DurableObjectSetAlarmOptions,
|
|
176
|
-
) {
|
|
177
|
-
const now = yield* Clock.currentTimeMillis;
|
|
178
|
-
const delayMillis = Math.ceil(Duration.toMillis(delay));
|
|
179
|
-
|
|
180
|
-
yield* tryStoragePromise(operation, () => setAlarm(now + delayMillis, options));
|
|
181
|
-
});
|
|
182
|
-
|
|
183
148
|
const fromSqlCursor = <T extends Record<string, SqlStorageValue>>(
|
|
184
149
|
cursor: globalThis.SqlStorageCursor<T>,
|
|
185
150
|
): SqlCursor<T> => ({
|
|
@@ -297,17 +262,6 @@ const fromDurableObjectTransaction = (
|
|
|
297
262
|
rollback: () => tryStorageSync("transaction.rollback", () => txn.rollback()),
|
|
298
263
|
getAlarm: (options?: globalThis.DurableObjectGetAlarmOptions) =>
|
|
299
264
|
tryStoragePromise("transaction.getAlarm", () => txn.getAlarm(options)),
|
|
300
|
-
setAlarm: (scheduledTime: number | Date, options?: globalThis.DurableObjectSetAlarmOptions) =>
|
|
301
|
-
tryStoragePromise("transaction.setAlarm", () => txn.setAlarm(scheduledTime, options)),
|
|
302
|
-
setAlarmAfter: (delay: Duration.Input, options?: globalThis.DurableObjectSetAlarmOptions) =>
|
|
303
|
-
setAlarmAfter(
|
|
304
|
-
"transaction.setAlarm",
|
|
305
|
-
(scheduledTime, options) => txn.setAlarm(scheduledTime, options),
|
|
306
|
-
delay,
|
|
307
|
-
options,
|
|
308
|
-
),
|
|
309
|
-
deleteAlarm: (options?: globalThis.DurableObjectSetAlarmOptions) =>
|
|
310
|
-
tryStoragePromise("transaction.deleteAlarm", () => txn.deleteAlarm(options)),
|
|
311
265
|
}) as DurableObjectTransaction;
|
|
312
266
|
|
|
313
267
|
export const fromDurableObjectStorage = (
|
|
@@ -323,17 +277,6 @@ export const fromDurableObjectStorage = (
|
|
|
323
277
|
tryStoragePromise("deleteAll", () => storage.deleteAll(options)),
|
|
324
278
|
getAlarm: (options?: globalThis.DurableObjectGetAlarmOptions) =>
|
|
325
279
|
tryStoragePromise("getAlarm", () => storage.getAlarm(options)),
|
|
326
|
-
setAlarm: (scheduledTime: number | Date, options?: globalThis.DurableObjectSetAlarmOptions) =>
|
|
327
|
-
tryStoragePromise("setAlarm", () => storage.setAlarm(scheduledTime, options)),
|
|
328
|
-
setAlarmAfter: (delay: Duration.Input, options?: globalThis.DurableObjectSetAlarmOptions) =>
|
|
329
|
-
setAlarmAfter(
|
|
330
|
-
"setAlarm",
|
|
331
|
-
(scheduledTime, options) => storage.setAlarm(scheduledTime, options),
|
|
332
|
-
delay,
|
|
333
|
-
options,
|
|
334
|
-
),
|
|
335
|
-
deleteAlarm: (options?: globalThis.DurableObjectSetAlarmOptions) =>
|
|
336
|
-
tryStoragePromise("deleteAlarm", () => storage.deleteAlarm(options)),
|
|
337
280
|
transactionSync: <A, E, R>(closure: () => Effect.Effect<A, E, R>) =>
|
|
338
281
|
Effect.context<R>().pipe(
|
|
339
282
|
Effect.flatMap((context) =>
|
|
@@ -7,6 +7,30 @@ import * as S from "effect/Schema";
|
|
|
7
7
|
import { DurableObjectState } from "./DurableObjectState";
|
|
8
8
|
import * as ErrorMessage from "./internal/ErrorMessage";
|
|
9
9
|
|
|
10
|
+
export const DEFAULT_KEEPALIVE_REQUEST = "effect-cf:ping";
|
|
11
|
+
export const DEFAULT_KEEPALIVE_RESPONSE = "effect-cf:pong";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Registers exact text keepalives with Cloudflare so they never invoke the object.
|
|
15
|
+
* The pair applies to all hibernating sockets on this object; install in its constructor.
|
|
16
|
+
* Clients send the request text and consume the response outside their application protocol.
|
|
17
|
+
*/
|
|
18
|
+
export const installKeepalive = Effect.fn("DurableObjectWebSocket.installKeepalive")(function* (
|
|
19
|
+
request = DEFAULT_KEEPALIVE_REQUEST,
|
|
20
|
+
response = DEFAULT_KEEPALIVE_RESPONSE,
|
|
21
|
+
) {
|
|
22
|
+
const state = yield* DurableObjectState;
|
|
23
|
+
const configured = yield* state.getWebSocketAutoResponse;
|
|
24
|
+
|
|
25
|
+
if (configured !== null && (configured.request !== request || configured.response !== response)) {
|
|
26
|
+
return yield* Effect.die(
|
|
27
|
+
new Error("Durable Object already has a different keepalive protocol"),
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
yield* state.setWebSocketAutoResponse(new WebSocketRequestResponsePair(request, response));
|
|
32
|
+
});
|
|
33
|
+
|
|
10
34
|
export type DurableWebSocketSendData = string | ArrayBuffer | ArrayBufferView;
|
|
11
35
|
|
|
12
36
|
export class DurableWebSocketSendError extends Data.TaggedError("DurableWebSocketSendError")<{
|