effect-cf 0.42.0 → 0.42.1
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 +8 -0
- package/dist/DurableObject.d.mts +4 -2
- package/dist/DurableObject.mjs +5 -1
- package/dist/DurableObjectSqlite.mjs +1 -1
- package/dist/internal/NativeCallback.mjs +5 -2
- package/package.json +1 -1
- package/src/DurableObject.ts +16 -3
- package/src/internal/NativeCallback.ts +13 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# effect-cf
|
|
2
2
|
|
|
3
|
+
## 0.42.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#171](https://github.com/danieljvdm/effect-cf/pull/171) [`06a0bfc`](https://github.com/danieljvdm/effect-cf/commit/06a0bfc1f750f5042e13f97b212c63ced5caa5e5) Thanks [@danieljvdm](https://github.com/danieljvdm)! - Prevent the first Durable Object event from overtaking an initialization hook's concurrency gate while its service layer is still building. Hooks that perform background setup without a concurrency gate continue to allow incoming events.
|
|
8
|
+
|
|
9
|
+
- [#170](https://github.com/danieljvdm/effect-cf/pull/170) [`4c41e8d`](https://github.com/danieljvdm/effect-cf/commit/4c41e8d648f340dbf43c5e2c17a850d967b7fa09) Thanks [@danieljvdm](https://github.com/danieljvdm)! - Prevent Durable Object storage transactions and concurrency-blocking callbacks from stalling when their Effect work yields while an earlier timer is blocked by the native input gate.
|
|
10
|
+
|
|
3
11
|
## 0.42.0
|
|
4
12
|
|
|
5
13
|
### Minor Changes
|
package/dist/DurableObject.d.mts
CHANGED
|
@@ -47,8 +47,10 @@ interface DurableObjectOptionsBase<RRuntime, REvent = never, EventLayerError = n
|
|
|
47
47
|
/**
|
|
48
48
|
* Effect run when Cloudflare loads this Durable Object instance into memory.
|
|
49
49
|
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
50
|
+
* The base layer is acquired before incoming events are admitted. The hook
|
|
51
|
+
* itself runs in the background; start it with
|
|
52
|
+
* `DurableObjectState.blockConcurrencyWhile` when incoming events should
|
|
53
|
+
* wait for setup to finish. Cloudflare may construct
|
|
52
54
|
* the same Durable Object id again after eviction or restart; use Durable
|
|
53
55
|
* Object storage if work must happen only once per id.
|
|
54
56
|
*/
|
package/dist/DurableObject.mjs
CHANGED
|
@@ -10,6 +10,7 @@ import { DurableObjectAlarm } from "./DurableObjectAlarm.mjs";
|
|
|
10
10
|
import { Tag, implement, method, native } from "./DurableObjectDefinition.mjs";
|
|
11
11
|
import * as Effect from "effect/Effect";
|
|
12
12
|
import * as Layer from "effect/Layer";
|
|
13
|
+
import * as Scheduler from "effect/Scheduler";
|
|
13
14
|
import { DurableObject } from "cloudflare:workers";
|
|
14
15
|
import * as Tracer from "effect/Tracer";
|
|
15
16
|
//#region src/DurableObject.ts
|
|
@@ -36,7 +37,10 @@ function make(layer, options = {}) {
|
|
|
36
37
|
const services = (registration?.layer ?? Layer.empty).pipe(Layer.provideMerge(baseServices));
|
|
37
38
|
this.runtime = makeEntrypointRuntime(layer, env, services);
|
|
38
39
|
const initialize = options.initialize;
|
|
39
|
-
if (initialize !== void 0) state.waitUntil(
|
|
40
|
+
if (initialize !== void 0) state.waitUntil(state.blockConcurrencyWhile(async () => {
|
|
41
|
+
await Effect.runPromise(this.runtime.contextEffect, { scheduler: new Scheduler.MixedScheduler("sync") });
|
|
42
|
+
state.waitUntil(this[RunSymbol](initialize, { eventLayer: false }));
|
|
43
|
+
}));
|
|
40
44
|
}
|
|
41
45
|
[RunSymbol](effect, runOptions = {}) {
|
|
42
46
|
const eventLayer = runOptions.eventLayer === false ? void 0 : options.eventLayer;
|
|
@@ -3,8 +3,8 @@ import { DurableObjectState } from "./DurableObjectState.mjs";
|
|
|
3
3
|
import * as Context from "effect/Context";
|
|
4
4
|
import * as Effect from "effect/Effect";
|
|
5
5
|
import * as Layer from "effect/Layer";
|
|
6
|
-
import { SqliteClient } from "@effect/sql-sqlite-do";
|
|
7
6
|
import * as Scheduler from "effect/Scheduler";
|
|
7
|
+
import { SqliteClient } from "@effect/sql-sqlite-do";
|
|
8
8
|
import * as Reactivity from "effect/unstable/reactivity/Reactivity";
|
|
9
9
|
import { SqlClient } from "effect/unstable/sql";
|
|
10
10
|
//#region src/DurableObjectSqlite.ts
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import * as Context from "effect/Context";
|
|
1
2
|
import * as Effect from "effect/Effect";
|
|
2
3
|
import "effect/Exit";
|
|
3
4
|
import * as FiberSet from "effect/FiberSet";
|
|
5
|
+
import * as Scheduler from "effect/Scheduler";
|
|
4
6
|
//#region src/internal/NativeCallback.ts
|
|
7
|
+
const callbackScheduler = new Scheduler.MixedScheduler("sync");
|
|
5
8
|
/**
|
|
6
9
|
* Runs an Effect callback owned by a native Promise operation.
|
|
7
10
|
*
|
|
@@ -10,7 +13,7 @@ import * as FiberSet from "effect/FiberSet";
|
|
|
10
13
|
* the native operation's settlement.
|
|
11
14
|
*/
|
|
12
15
|
const runNativeCallback = Effect.fnUntraced(function* (operation) {
|
|
13
|
-
const
|
|
16
|
+
const callbackContext = Context.add(yield* Effect.context(), Scheduler.Scheduler, callbackScheduler);
|
|
14
17
|
return yield* Effect.scoped(Effect.gen(function* () {
|
|
15
18
|
let nativePromise;
|
|
16
19
|
yield* Effect.addFinalizer(() => {
|
|
@@ -20,7 +23,7 @@ const runNativeCallback = Effect.fnUntraced(function* (operation) {
|
|
|
20
23
|
const runPromise = yield* FiberSet.makeRuntimePromise();
|
|
21
24
|
return yield* Effect.callback((resume) => {
|
|
22
25
|
try {
|
|
23
|
-
nativePromise = operation((effect) => runPromise(Effect.provideContext(Effect.exit(effect),
|
|
26
|
+
nativePromise = operation((effect) => runPromise(Effect.provideContext(Effect.exit(effect), callbackContext)));
|
|
24
27
|
} catch (cause) {
|
|
25
28
|
resume(Effect.fail(cause));
|
|
26
29
|
return;
|
package/package.json
CHANGED
package/src/DurableObject.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { DurableObject as CloudflareDurableObject } from "cloudflare:workers";
|
|
2
2
|
import * as Effect from "effect/Effect";
|
|
3
3
|
import * as Layer from "effect/Layer";
|
|
4
|
+
import * as Scheduler from "effect/Scheduler";
|
|
4
5
|
import * as Tracer from "effect/Tracer";
|
|
5
6
|
import type { ManagedRuntime, Scope } from "effect";
|
|
6
7
|
|
|
@@ -125,8 +126,10 @@ interface DurableObjectOptionsBase<
|
|
|
125
126
|
/**
|
|
126
127
|
* Effect run when Cloudflare loads this Durable Object instance into memory.
|
|
127
128
|
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
129
|
+
* The base layer is acquired before incoming events are admitted. The hook
|
|
130
|
+
* itself runs in the background; start it with
|
|
131
|
+
* `DurableObjectState.blockConcurrencyWhile` when incoming events should
|
|
132
|
+
* wait for setup to finish. Cloudflare may construct
|
|
130
133
|
* the same Durable Object id again after eviction or restart; use Durable
|
|
131
134
|
* Object storage if work must happen only once per id.
|
|
132
135
|
*/
|
|
@@ -298,7 +301,17 @@ export function make<
|
|
|
298
301
|
const initialize = options.initialize;
|
|
299
302
|
|
|
300
303
|
if (initialize !== undefined) {
|
|
301
|
-
state.waitUntil(
|
|
304
|
+
state.waitUntil(
|
|
305
|
+
state.blockConcurrencyWhile(async () => {
|
|
306
|
+
// Build services before admitting the first event, then let the
|
|
307
|
+
// hook synchronously install its own gate if it needs one. Its
|
|
308
|
+
// background work and failure reporting keep their waitUntil owner.
|
|
309
|
+
await Effect.runPromise(this.runtime.contextEffect, {
|
|
310
|
+
scheduler: new Scheduler.MixedScheduler("sync"),
|
|
311
|
+
});
|
|
312
|
+
state.waitUntil(this[RunSymbol](initialize, { eventLayer: false }));
|
|
313
|
+
}),
|
|
314
|
+
);
|
|
302
315
|
}
|
|
303
316
|
}
|
|
304
317
|
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
+
import * as Context from "effect/Context";
|
|
1
2
|
import * as Effect from "effect/Effect";
|
|
2
3
|
import * as Exit from "effect/Exit";
|
|
3
4
|
import * as FiberSet from "effect/FiberSet";
|
|
5
|
+
import * as Scheduler from "effect/Scheduler";
|
|
4
6
|
|
|
5
7
|
type RunCallback<A, E, R> = (effect: Effect.Effect<A, E, R>) => Promise<Exit.Exit<A, E>>;
|
|
6
8
|
|
|
9
|
+
const callbackScheduler = new Scheduler.MixedScheduler("sync");
|
|
10
|
+
|
|
7
11
|
/**
|
|
8
12
|
* Runs an Effect callback owned by a native Promise operation.
|
|
9
13
|
*
|
|
@@ -14,7 +18,14 @@ type RunCallback<A, E, R> = (effect: Effect.Effect<A, E, R>) => Promise<Exit.Exi
|
|
|
14
18
|
export const runNativeCallback = Effect.fnUntraced(function* <A, E, R, B>(
|
|
15
19
|
operation: (run: RunCallback<A, E, R>) => Promise<B>,
|
|
16
20
|
): Effect.fn.Return<B, unknown, R> {
|
|
17
|
-
|
|
21
|
+
// An older timer in the blocked parent input gate prevents later timers in
|
|
22
|
+
// this owner from running. Yield through microtasks while the native gate is
|
|
23
|
+
// held, including when the caller explicitly provided its own scheduler.
|
|
24
|
+
const callbackContext = Context.add(
|
|
25
|
+
yield* Effect.context<R>(),
|
|
26
|
+
Scheduler.Scheduler,
|
|
27
|
+
callbackScheduler,
|
|
28
|
+
);
|
|
18
29
|
|
|
19
30
|
return yield* Effect.scoped(
|
|
20
31
|
Effect.gen(function* () {
|
|
@@ -39,7 +50,7 @@ export const runNativeCallback = Effect.fnUntraced(function* <A, E, R, B>(
|
|
|
39
50
|
return yield* Effect.callback<B, unknown>((resume) => {
|
|
40
51
|
try {
|
|
41
52
|
nativePromise = operation((effect) =>
|
|
42
|
-
runPromise(Effect.provideContext(Effect.exit(effect),
|
|
53
|
+
runPromise(Effect.provideContext(Effect.exit(effect), callbackContext)),
|
|
43
54
|
);
|
|
44
55
|
} catch (cause) {
|
|
45
56
|
resume(Effect.fail(cause));
|