Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "@effection/effection",
"exports": "./mod.ts",
"license": "ISC",
"publish": { "include": ["lib", "mod.ts", "README.md"] },
"publish": { "include": ["lib", "mod.ts", "experimental.ts", "README.md"] },
"lock": false,
"tasks": {
"test": "deno test --allow-run --allow-read --allow-env --allow-ffi",
Expand All @@ -29,6 +29,10 @@
"tinyexec": "npm:tinyexec@1.0.1",
"https://deno.land/x/path_to_regexp@v6.2.1/index.ts": "npm:path-to-regexp@8.2.0"
},
"exports": {
".": "./mod.ts",
"./experimental": "./experimental.ts"
},
"nodeModulesDir": "auto",
"workspace": [
"./www"
Expand Down
7 changes: 7 additions & 0 deletions experimental.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import Scope from "./lib/api/scope.ts";
import Task from "./lib/api/task.ts";
import Main from "./lib/api/main.ts";

export * from "./lib/api.ts";

export const api = { Scope, Task, Main };
121 changes: 121 additions & 0 deletions lib/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// deno-lint-ignore-file ban-types
import { createContext } from "./context.ts";
import { useScope } from "./scope.ts";
import type { Api, Decorate, Operation, Scope } from "./types.ts";
import type { ScopeInternal } from "./scope-internal.ts";

export interface Middleware<TArgs extends unknown[], TReturn> {
(args: TArgs, next: (...args: TArgs) => TReturn): TReturn;
}

export function createApi<A extends {}>(name: string, core: A): Api<A> {
let fields = Object.keys(core) as (keyof A)[];

let context = createContext(`api::${name}`) as Api<A>["context"];

let api: Api<A> = {
core,
context,
lookup: (scope) => createHandle(api, scope),
decorate: (decorator, options = { at: "max" }) => ({
*[Symbol.iterator]() {
let scope = yield* useScope();
scope.decorate(api, decorator, options);
},
}),
operations: fields.reduce((sum, field) => {
if (typeof core[field] === "function") {
return Object.assign(sum, {
// deno-lint-ignore no-explicit-any
[field]: (...args: any[]) => ({
*[Symbol.iterator]() {
let scope = yield* useScope();
let handle = api.lookup(scope);
let handler = handle[field] as Function;
let target = handler(...args);
return isOperation(target) ? yield* target : target;
},
}),
});
} else {
return Object.assign(sum, {
[field]: {
*[Symbol.iterator]() {
let scope = yield* useScope();
let handle = api.lookup(scope);
let target = handle[field];
return isOperation(target) ? yield* target : target;
},
},
});
}
}, {} as Api<A>["operations"]),
};
return api;
}

function createHandle<A extends {}>(api: Api<A>, scope: Scope): A {
let handle = Object.create(api.core);
let $scope = scope as ScopeInternal;
for (let key of Object.keys(api.core) as Array<keyof A>) {
let dispatch = (args: unknown[], next: (...args: unknown[]) => unknown) => {
let { min, max } = $scope.reduce(api.context, (sum, current) => {
let min = current.min.flatMap((around) =>
around[key] ? [around[key]] : []
);
let max = current.max.flatMap((around) =>
around[key] ? [around[key]] : []
);

sum.min.push(...min);
sum.max.unshift(...max);

return sum;
}, {
min: [] as Decorate<A>[typeof key][],
max: [] as Decorate<A>[typeof key][],
});

let stack = combine(max.concat(min) as Middleware<unknown[], unknown>[]);
return stack(args, next);
};

if (typeof api.core[key] === "function") {
handle[key] = (...args: unknown[]) =>
dispatch(args, api.core[key] as (...args: unknown[]) => unknown);
} else {
Object.defineProperty(handle, key, {
enumerable: true,
get() {
return dispatch([], () => api.core[key]);
},
});
}
}
return handle;
}

function isOperation<T>(
target: Operation<T> | T,
): target is Operation<T> {
return target && !isNativeIterable(target) &&
typeof (target as Operation<T>)[Symbol.iterator] === "function";
}

function isNativeIterable(target: unknown): boolean {
return (
typeof target === "string" || Array.isArray(target) ||
target instanceof Map || target instanceof Set
);
}

function combine<TArgs extends unknown[], TReturn>(
middlewares: Middleware<TArgs, TReturn>[],
): Middleware<TArgs, TReturn> {
if (middlewares.length === 0) {
return (args, next) => next(...args);
}
return middlewares.reduceRight((sum, middleware) => (args, next) =>
middleware(args, (...args) => sum(args, next))
);
}
12 changes: 12 additions & 0 deletions lib/api/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { createApi } from "../api.ts";
import type { Api, Operation } from "../types.ts";

export interface Main {
main: (body: (args: string[]) => Operation<void>) => Promise<void>;
}

export default createApi<Main>("Main", {
main() {
throw new TypeError(`missing handler for "main()"`);
},
}) as Api<Main>;
22 changes: 22 additions & 0 deletions lib/api/scope.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { createApi } from "../api.ts";
import type { Api, Context, Operation, Scope } from "../types.ts";

export interface ScopeApi {
create(parent: Scope): [Scope, () => Operation<void>];
destroy(scope: Scope): Operation<void>;
set<T>(contexts: Record<string, unknown>, context: Context<T>, value: T): T;
delete<T>(contexts: Record<string, unknown>, context: Context<T>): boolean;
}

export default createApi<ScopeApi>("Scope", {
create() {
throw new TypeError(`no implementation for Scope.create()`);
},
*destroy() {},
set(contexts, context, value) {
return contexts[context.name] = value;
},
delete(contexts, context): boolean {
return delete contexts[context.name];
},
}) as Api<ScopeApi>;
10 changes: 10 additions & 0 deletions lib/api/task.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { createApi } from "../api.ts";
import type { Api, Operation } from "../types.ts";

export interface TaskApi {
run: <T>(operation: () => Operation<T>) => Operation<T>;
}

export default createApi("Task", {
run: (operation) => operation(),
}) as Api<TaskApi>;
8 changes: 6 additions & 2 deletions lib/coroutine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,23 +31,25 @@ export function createCoroutine<T>(
next(result) {
routine.data.exit((exitResult) => {
routine.data.exit = (didExit) => didExit(Ok());
let validator = scope.get(DelimiterContext)?.validator ?? valid;
reducer.reduce([
scope.expect(Priority),
routine,
exitResult.ok ? result : exitResult,
scope.expect(DelimiterContext).validator,
validator,
"next",
]);
});
},
return(result) {
routine.data.exit((exitResult) => {
routine.data.exit = (didExit) => didExit(Ok());
let validator = scope.get(DelimiterContext)?.validator ?? valid;
reducer.reduce([
scope.expect(Priority),
routine,
exitResult.ok ? result : exitResult,
scope.expect(DelimiterContext).validator,
validator,
"return",
]);
});
Expand All @@ -66,3 +68,5 @@ export function* useCoroutine(): Operation<Coroutine> {
},
}) as Coroutine;
}

const valid = () => true;
Loading
Loading