Skip to content

Commit bc757a0

Browse files
author
Taras Mankovski
committed
✨ Route reduction through api.Reducer middleware and export internals
Add api.Reducer as a middleware interception point for effect reduction, enabling external packages (e.g., @effectionx/durably) to intercept instruction processing without forking Effection. Changes: - lib/api.ts: Define ReducerApi interface and api.Reducer with createApi - lib/coroutine.ts: Route reduce calls through reducerApi.invoke() instead of directly calling ReducerContext, using routine.scope for correct middleware chain resolution - lib/scope-internal.ts: Install stock Reducer as base-layer api.Reducer middleware on the global scope - experimental.ts: Export InstructionQueue, Instruction type, and DelimiterContext for custom reducer implementations - lib/callcc.ts, lib/delimiter.ts, lib/each.ts, lib/future.ts, lib/scope-internal.ts: Add description labels to withResolvers() calls so custom reducers can distinguish infrastructure effects from user-facing effects
1 parent 9e6b35a commit bc757a0

9 files changed

Lines changed: 43 additions & 19 deletions

File tree

experimental.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,6 @@
11
export * from "./lib/api.ts";
2+
export {
3+
InstructionQueue,
4+
type Instruction,
5+
} from "./lib/reducer.ts";
6+
export { DelimiterContext } from "./lib/delimiter.ts";

lib/api.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import type { Api, Context, Operation, Scope } from "./types.ts";
33
import { createApiInternal } from "./api-internal.ts";
44
import type { ScopeInternal } from "./scope-internal.ts";
5+
import type { Instruction } from "./reducer.ts";
56

67
export function createApi<A extends {}>(name: string, core: A): Api<A> {
78
return createApiInternal(name, core);
@@ -18,9 +19,14 @@ interface MainApi {
1819
main(body: (args: string[]) => Operation<void>): Promise<void>;
1920
}
2021

22+
interface ReducerApi {
23+
reduce(instruction: Instruction): void;
24+
}
25+
2126
interface Apis {
2227
Scope: Api<ScopeApi>;
2328
Main: Api<MainApi>;
29+
Reducer: Api<ReducerApi>;
2430
}
2531

2632
export const api: Apis = {
@@ -41,4 +47,9 @@ export const api: Apis = {
4147
throw new TypeError(`missing handler for "main()"`);
4248
},
4349
}),
50+
Reducer: createApi<ReducerApi>("Reducer", {
51+
reduce() {
52+
throw new TypeError(`no handler for Reducer.reduce()`);
53+
},
54+
}),
4455
};

lib/callcc.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export function* callcc<T>(
1111
reject: (error: Error) => Operation<void>,
1212
) => Operation<void>,
1313
): Operation<T> {
14-
let result = withResolvers<Result<T>>();
14+
let result = withResolvers<Result<T>>("await callcc");
1515

1616
let resolve = lift((value: T) => result.resolve(Ok(value)));
1717

lib/coroutine.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
import { api as effection } from "./api.ts";
12
import { Priority } from "./contexts.ts";
23
import { DelimiterContext } from "./delimiter.ts";
3-
import { ReducerContext } from "./reducer.ts";
44
import { Ok } from "./result.ts";
55
import type { Coroutine, Operation, Scope } from "./types.ts";
66

7+
const reducerApi = effection.Reducer;
8+
79
export interface CoroutineOptions<T> {
810
scope: Scope;
911
operation(): Operation<T>;
@@ -12,8 +14,6 @@ export interface CoroutineOptions<T> {
1214
export function createCoroutine<T>(
1315
{ operation, scope }: CoroutineOptions<T>,
1416
): Coroutine<T> {
15-
let reducer = scope.expect(ReducerContext);
16-
1717
let iterator: Coroutine<T>["data"]["iterator"] | undefined = undefined;
1818

1919
let routine = {
@@ -31,25 +31,25 @@ export function createCoroutine<T>(
3131
next(result) {
3232
routine.data.exit((exitResult) => {
3333
routine.data.exit = (didExit) => didExit(Ok());
34-
reducer.reduce([
35-
scope.expect(Priority),
34+
reducerApi.invoke(routine.scope, "reduce", [[
35+
routine.scope.expect(Priority),
3636
routine,
3737
exitResult.ok ? result : exitResult,
38-
scope.expect(DelimiterContext).validator,
38+
routine.scope.expect(DelimiterContext).validator,
3939
"next",
40-
]);
40+
]]);
4141
});
4242
},
4343
return(result) {
4444
routine.data.exit((exitResult) => {
4545
routine.data.exit = (didExit) => didExit(Ok());
46-
reducer.reduce([
47-
scope.expect(Priority),
46+
reducerApi.invoke(routine.scope, "reduce", [[
47+
routine.scope.expect(Priority),
4848
routine,
4949
exitResult.ok ? result : exitResult,
50-
scope.expect(DelimiterContext).validator,
50+
routine.scope.expect(DelimiterContext).validator,
5151
"return",
52-
]);
52+
]]);
5353
});
5454
},
5555
} as Coroutine<T>;

lib/delimiter.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export class Delimiter<T>
1010
implements Operation<Maybe<Result<T>>>, ErrorBoundary {
1111
level = 0;
1212
finalized = false;
13-
future = withResolvers<Maybe<Result<T>>>();
13+
future = withResolvers<Maybe<Result<T>>>("await delimiter");
1414
computed = false;
1515
routine?: Coroutine;
1616
outcome?: Maybe<Result<T>>;

lib/each.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ export function each<T>(stream: Stream<T, unknown>): Operation<Iterable<T>> {
3737
scope.set(EachStack, []);
3838
}
3939

40-
let done = withResolvers<void>();
41-
let cxt = withResolvers<EachLoop<T>>();
40+
let done = withResolvers<void>("await each done");
41+
let cxt = withResolvers<EachLoop<T>>("await each context");
4242

4343
yield* spawn(function* () {
4444
let subscription = yield* stream;

lib/future.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export interface FutureWithResolvers<T> {
99
}
1010
export function createFuture<T>(): FutureWithResolvers<T> {
1111
let promise = lazyPromiseWithResolvers<T>();
12-
let operation = withResolvers<T>();
12+
let operation = withResolvers<T>("await future");
1313

1414
let resolve = (value: T) => {
1515
promise.resolve(value);

lib/reducer.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,15 +58,15 @@ export class Reducer {
5858
};
5959
}
6060

61-
type Instruction = [
61+
export type Instruction = [
6262
number,
6363
Coroutine<unknown>,
6464
Result<unknown>,
6565
() => boolean,
6666
"return" | "next",
6767
];
6868

69-
class InstructionQueue extends PriorityQueue<Instruction> {
69+
export class InstructionQueue extends PriorityQueue<Instruction> {
7070
enqueue(instruction: Instruction): void {
7171
let [priority] = instruction;
7272
this.push(priority, instruction);

lib/scope-internal.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import type { ApiInternal } from "./api-internal.ts";
22
import { api as effection } from "./api.ts";
33
import { Children, Priority } from "./contexts.ts";
4+
import { Reducer } from "./reducer.ts";
45
import { Err, Ok, unbox } from "./result.ts";
56
import { createTask } from "./task.ts";
67
import type { Context, Operation, Scope, Task } from "./types.ts";
78
import { type WithResolvers, withResolvers } from "./with-resolvers.ts";
89

910
const api = effection.Scope;
11+
const reducerApi = effection.Reducer;
1012

1113
export function createScopeInternal(
1214
parent?: Scope,
@@ -18,6 +20,12 @@ export function createScopeInternal(
1820
return buildScopeInternal(parent);
1921
},
2022
}, { at: "min" });
23+
let defaultReducer = new Reducer();
24+
global.around(reducerApi, {
25+
reduce([instruction]) {
26+
defaultReducer.reduce(instruction);
27+
},
28+
}, { at: "min" });
2129
return [global, destroy] as const;
2230
} else {
2331
return api.invoke(parent, "create", [parent]) as [
@@ -133,7 +141,7 @@ export function buildScopeInternal(
133141
if (destruction) {
134142
return yield* destruction.operation;
135143
}
136-
destruction = withResolvers<void>();
144+
destruction = withResolvers<void>("await destruction");
137145
parent?.expect(Children).delete(scope);
138146
unbind();
139147
let outcome = Ok();

0 commit comments

Comments
 (0)