Initial work on novy-syndicate

This commit is contained in:
Tony Garnock-Jones 2021-02-17 20:57:15 +01:00
commit 84569b8c90
8 changed files with 483 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
*.d.ts
*.js
*.js.map
package-lock.json
node_modules/

115
actor.ts Normal file
View File

@ -0,0 +1,115 @@
import { Value } from 'preserves';
type Assertion = Value<Peer>;
type ActorId = number;
type ExitReason = null | { ok: true } | { ok: false, err: Error };
let nextActorId: ActorId = 0;
type AssertionHandle = object;
const assert = Symbol('assert');
const retract = Symbol('retract');
const message = Symbol('message');
type RestParameters<T extends (arg: any, ...args: any) => any> =
T extends (arg: any, ...args: infer P) => any ? P : never;
type Event =
| { type: typeof assert, args: RestParameters<Facet[typeof assert]> }
| { type: typeof retract, args: RestParameters<Facet[typeof retract]> }
| { type: typeof message, args: RestParameters<Facet[typeof message]> }
type Action = Event;
class Peer {
readonly actor: Actor;
readonly target: Facet;
constructor(actor: Actor, target: Facet) {
this.actor = actor;
this.target = target;
}
}
interface Facet {
[assert](turn: Turn, assertion: Assertion, handle: AssertionHandle): void;
[retract](turn: Turn, handle: AssertionHandle): void;
[message](turn: Turn, message: Assertion): void;
}
class Actor {
readonly id: ActorId = nextActorId++;
exitReason: ExitReason = null;
scheduleTurn(target: Facet, turn: Turn) {
queueMicrotask(() => {
if (this.alive) {
try {
const event = turn.event;
(target as any)[event.type](turn, ...event.args);
// ^ This is safe. Try replacing it with the following to see:
//
// switch (event.type) {
// case assert: target[event.type](turn, ...event.args); break;
// case retract: target[event.type](turn, ...event.args); break;
// case message: target[event.type](turn, ...event.args); break;
// }
turn.finish();
} catch (err) {
this.terminateWith({ ok: false, err });
}
}
});
}
get alive(): boolean {
return this.exitReason === null;
}
stop() {
this.terminateWith({ ok: true });
}
terminateWith(reason: Exclude<ExitReason, null>) {
if (this.alive) {
this.exitReason = reason;
// TODO cleanup
}
}
}
class Turn {
readonly recipient: Actor;
readonly event: Event;
readonly actions: Map<Actor, Map<Facet, Action[]>> = new Map();
constructor(recipient: Actor, event: Event) {
this.recipient = recipient;
this.event = event;
}
enqueueAction(peer: Peer, action: Action) {
let targetMap = this.actions.get(peer.actor);
if (targetMap === void 0) {
targetMap = new Map();
this.actions.set(peer.actor, targetMap);
}
let actions = targetMap.get(peer.target);
if (actions === void 0) {
actions = [];
targetMap.set(peer.target, actions);
}
actions.push(action);
}
finish() {
this.actions.forEach((targetMap, actor) => {
targetMap.forEach((actions, target) => {
actions.forEach(action => actor.scheduleTurn(target, new Turn(actor, action)));
});
});
}
}

143
i0.ts Normal file
View File

@ -0,0 +1,143 @@
type ValidSelector = string | number | symbol;
export type EventMessage<Selector extends ValidSelector, Args extends any[]> = {
selector: Selector,
args: Args,
};
export type RequestMessage<Selector extends ValidSelector, Args extends any[], Result> = {
selector: Selector,
args: Args,
callback: (result: Result) => void,
};
export type Message<Selector extends ValidSelector, Args extends any[], Result> =
void extends Result ? EventMessage<Selector, Args> : RequestMessage<Selector, Args, Result>;
// export type EventMessage<Selector extends ValidSelector, Args extends any[]> = {
// selector: Selector,
// args: Args,
// };
//
// export type RequestMessage<Selector extends ValidSelector, Args extends any[], Result> = {
// selector: Selector,
// args: Args,
// callback: (result: Result) => void,
// };
//
// export type Message<Selector extends ValidSelector, Args extends any[], Result> =
// | EventMessage<Selector, Args>
// | RequestMessage<Selector, Args, Result>;
//
// type Messages1<I, ContextArgs extends any[]> = {
// [K in keyof I]: (I[K] extends (...args: [...ContextArgs, ...infer P]) => infer Q
// ? (void extends Q ? EventMessage<K, P> : RequestMessage<K, P, Q>)
// : never);
// };
type Messages1<I, ContextArgs extends any[]> = {
[K in keyof I]: (I[K] extends (...args: [...ContextArgs, ...infer P]) => infer Q
? Message<K, P, Q>
: never);
};
// type Proj1<I, K extends keyof I> = I[K];
// type Proj<I> = Proj1<I, keyof I>;
// export type Messages<I, ContextArgs extends any[] = []> = Proj<Messages1<I, ContextArgs>>;
export type Messages<I, ContextArgs extends any[] = []> = Messages1<I, ContextArgs>[keyof I];
export type Methods<M extends { selector: ValidSelector }, ContextArgs extends any[] = []> = {
[S in M['selector']]: (
M extends RequestMessage<S, infer P, infer R> ? (...args: [...ContextArgs, ...P]) => R :
M extends EventMessage<S, infer P> ? (... args: [...ContextArgs, ...P]) => void :
never);
};
// interface I {
// m1(a: string, b: number): boolean;
// m2(): void;
// m3(n: number): void;
// m4(x: [string, string]): { k: string, j: string };
// m5(a: string, b: string[]): number;
// v: string;
// w: number;
// };
//
// const a = { a(): string { console.log('in a'); return 'hi'; }, b(): void { console.log('in b'); } };
// type A = typeof a;
// type A1 = Messages<A>;
// type A2 = Methods<A1>;
// const b: A2 = a;
// export function performRequest<S extends ValidSelector, A extends any[], R, ContextArgs extends any[] = []>(
// i: { [s in S]: (...args: [...ContextArgs, ...A]) => R },
// m: { selector: S, args: A, callback: (result: R) => void },
// ...ctxt: ContextArgs)
// : R
// {
// const r = i[m.selector](...ctxt, ... m.args);
// m.callback(r);
// return r;
// }
// export function performEvent<S extends ValidSelector, A extends any[], ContextArgs extends any[] = []>(
// i: { [s in S]: (...args: [...ContextArgs, ...A]) => void },
// m: { selector: S, args: A },
// ...ctxt: ContextArgs)
// : void
// {
// i[m.selector](...ctxt, ...m.args);
// }
// function send<S extends ValidSelector, A extends any[], R>(
// i: { [s in S]: (...args: A) => R },
// m: { selector: S, args: A, callback?: (result: R) => void })
// : R;
// function send<S extends ValidSelector, A extends any[], R>(
// i: { [s in S]: (...args: A) => void },
// m: { selector: S, args: A })
// : void;
// function send<S extends ValidSelector, A extends any[], R>(
// i: { [s in S]: (...args: A) => R },
// m: { selector: S, args: A, callback?: (result: R) => void })
// : R
// // function send<I extends {}, M extends Messages<I>>(i: I, m: M): [M, I] extends [RequestMessage<any, any, infer R>, Methods<M>] ? R : void
// {
// const r = i[m.selector](... m.args);
// m.callback?.(r);
// return r;
// }
export function perform<I extends Methods<M, ContextArgs>, S extends ValidSelector, M extends RequestMessage<S, any, any>, ContextArgs extends any[] = []>(i: I, m: M, ...ctxt: ContextArgs)
: [M, I] extends [RequestMessage<S, any, infer R>, Methods<M>] ? R : void;
export function perform<I extends Methods<M, ContextArgs>, S extends ValidSelector, M extends EventMessage<S, any>, ContextArgs extends any[] = []>(i: I, m: M, ...ctxt: ContextArgs): void;
export function perform<I extends Methods<M, ContextArgs>, S extends ValidSelector, M extends RequestMessage<S, any, any>, R, ContextArgs extends any[] = []>(i: I, m: M, ...ctxt: ContextArgs): R
{
const r = i[m.selector](...ctxt, ... m.args);
m.callback?.(r);
return r;
}
// function perform<I extends Methods<M>, S extends ValidSelector, M extends RequestMessage<S, any, any>>(i: I, m: M)
// : [M, I] extends [RequestMessage<S, any, infer R>, Methods<M>] ? R : void;
// function perform<I extends Methods<M>, S extends ValidSelector, M extends EventMessage<S, any>>(i: I, m: M): void;
// function perform<I extends Methods<M>, S extends ValidSelector, M extends RequestMessage<S, any, any>>(i: I, m: M): R
// {
// const r = i[m.selector](... m.args);
// m.callback?.(r);
// return r;
// }
// const aa = perform(a, { selector: 'a', args: [], callback: (_r: string) => {} });
// const bb = perform(a, { selector: 'b', args: [], callback: (_r: void) => { console.log('bb'); } });
// const bb2 = perform(a, { selector: 'b', args: [] });
// // perform({ a(): string { return 'hi' } }, { selector: 'a', args: [123], callback: (_r: string) => {} });
// type Q = Messages<I>;
// type M = Methods<Q>;
// type N = M;
// type R = Messages<Methods<Q>>;
// type S = R;
// const x: Q = { selector: 'm2', args: [] };

87
i2.ts Normal file
View File

@ -0,0 +1,87 @@
type ValidSelector = string | number | symbol
export type Message<Selector extends ValidSelector, Args extends any[], Result> =
Args extends never[]
? { selector: Selector, args: [], callback: (result: Result) => void }
: { selector: Selector, args: Args, callback: (result: Result) => void }
type MessagesProduct<I> = {
[K in keyof I]: (I[K] extends (...args: infer P) => infer Q ? Message<K, P, Q> : never);
}
export type Messages<I> = MessagesProduct<I>[keyof I]
export type Methods<M extends { selector: ValidSelector }> = {
[S in M['selector']]: (
M extends Message<S, infer P, infer R> ? (...args: P) => R :
never);
}
export function perform<I extends Methods<M>,
S extends ValidSelector,
M extends Message<S, any, any>>(
i: I,
m: M): void
{
m.callback(i[m.selector](... m.args));
}
//---------------------------------------------------------------------------
interface I {
m1(a: string, b: number): boolean;
m2(): void;
m3(n: number): void;
m4(x: [string, string]): { k: string, j: string };
m5(a: string, b: string[]): number;
v: string;
w: number;
}
type M = Messages<I>
// type M =
// | Message<"m1", [a: string, b: number], boolean>
// | Message<"m2", [], void>
// | Message<"m3", [n: number], void>
// | Message<"m4", [x: [string, string]], { k: string; j: string }>
// | Message<"m5", [a: string, b: string[]], number>
// type M =
// | { selector: "m1", args: [a: string, b: number], callback: (result: boolean) => void }
// | { selector: "m2", args: [], callback: (result: void) => void }
// | { selector: "m3", args: [n: number], callback: (result: void) => void }
// | { selector: "m4", args: [x: [string, string]], callback: (result: { k: string; j: string }) => void }
// | { selector: "m5", args: [a: string, b: string[]], callback: (result: number) => void }
// type M =
// | { selector: "m1", args: [string, number], callback: (result: boolean) => void }
// | { selector: "m2", args: [], callback: (result: void) => void }
// | { selector: "m3", args: [number], callback: (result: void) => void }
// | { selector: "m4", args: [[string, string]], callback: (result: { k: string; j: string }) => void }
// | { selector: "m5", args: [string, string[]], callback: (result: number) => void }
type I2 = Methods<M>
// type I2 = {
// m1: (a: string, b: number) => boolean;
// m2: () => void;
// m3: (n: number) => void;
// m4: (x: [string, string]) => { k: string; j: string };
// m5: (a: string, b: string[]) => number;
// };
type X = I2
const a = {
a(): string { console.log('in a'); return 'hi'; },
b(): void { console.log('in b'); },
c(x: number): number { console.log('in c:', x); return x * 2; },
v(x: number, y: string, z: boolean): string { return `x ${x} y ${y} z ${z}`; },
};
type A = typeof a;
type A1 = Messages<A>;
type A2 = Methods<A1>;
const b: A2 = a;
const aa = perform(a, { selector: 'a', args: [], callback: (_r: string) => {} });
const bb = perform(a, { selector: 'b', args: [], callback: (_r: void) => { console.log('bb'); } });
const cc = perform(a, { selector: 'c', args: [123], callback: (r: number) => { console.log('cc', r); } });
const vv = perform(a, { selector: 'v', args: [123, 'hi', true], callback: (r: string) => { console.log('vv', r); } });
perform({ a(): string { return 'hi' } }, { selector: 'a', args: [123], callback: (_r: string) => { console.log('x'); } });

67
interfaces.ts Normal file
View File

@ -0,0 +1,67 @@
// This Tuple type (and tuple() function) is a hack to induce
// TypeScript to infer tuple types rather than array types. (Source:
// https://github.com/microsoft/TypeScript/issues/27179#issuecomment-422606990)
//
// Without it, [123, 'hi', true] will often get the type (string |
// number | boolean)[] instead of [number, string, boolean].
//
export type Tuple = any[] | [any];
export const tuple = <A extends Tuple>(... args: A) => args;
// Type ValidSelector captures TypeScript's notion of a valid object
// property name.
//
export type ValidSelector = string | number | symbol;
export type EventMessage<Selector extends ValidSelector, Args extends any[]> =
{ selector: Selector, args: Args };
export type RequestMessage<Selector extends ValidSelector, Args extends any[], Result extends Exclude<any, void>> =
{ selector: Selector, args: Args, callback: (result: Result) => void };
export type Message<Selector extends ValidSelector, Args extends any[], Result> =
void extends Result ? EventMessage<Selector, Args> : RequestMessage<Selector, Args, Result>;
// Function message() is needed for similar reasons to tuple() above:
// to help TypeScript infer the correct literal type for the selector
// (as well as the arguments).
//
export const message = <S extends ValidSelector, A extends Tuple, R>(m: Message<S, A, R>) => m;
type MessagesProduct<I, ContextArgs extends any[]> = {
[K in keyof I]: (I[K] extends (...args: [...ContextArgs, ...infer P]) => infer Q
? Message<K, P, Q>
: never);
};
export type Messages<I, ContextArgs extends any[] = []> = MessagesProduct<I, ContextArgs>[keyof I];
export type Methods<M extends { selector: ValidSelector }, ContextArgs extends any[] = []> = {
[S in M['selector']]: (
M extends RequestMessage<S, infer P, infer R>
? (void extends R ? never : (...args: [...ContextArgs, ...P]) => R)
: (M extends EventMessage<S, infer P>
? (...args: [...ContextArgs, ...P]) => void
: never));
};
export function perform<I extends Methods<M, ContextArgs>,
S extends ValidSelector,
M extends RequestMessage<S, Tuple, any>,
ContextArgs extends any[] = []>
(i: I, m: M, ...ctxt: ContextArgs): (M extends RequestMessage<S, Tuple, infer R> ? R : never);
export function perform<I extends Methods<M, ContextArgs>,
S extends ValidSelector,
M extends EventMessage<S, Tuple>,
ContextArgs extends any[] = []>
(i: I, m: M, ...ctxt: ContextArgs): void;
export function perform<I extends Methods<M, ContextArgs>,
S extends ValidSelector,
M extends RequestMessage<S, Tuple, any>,
ContextArgs extends any[] = []>
(i: I, m: M, ...ctxt: ContextArgs): any
{
const r = i[m.selector](...ctxt, ... m.args);
m.callback?.(r);
return r;
}

40
interfaces_test.ts Normal file
View File

@ -0,0 +1,40 @@
import { tuple, message, Methods, Messages, perform } from './interfaces.js';
const m = message({
selector: 'b',
args: [123, 'hi', true],
callback: (result: number) => console.log('result:', result),
});
const m2 = message({
selector: 'c',
args: [],
// callback: (result: void) => console.log('result:', result),
});
type X = Methods<typeof m | typeof m2, [string]>;
type Y = Messages<X, [string]>;
perform({
a(ctxt: string, x: number): string { return `${ctxt}(${x + 1})`; },
b(ctxt: string, y: number): number { return y * 2; },
z(ctxt: string, ...v: number[]) { return 3; },
v(ctxt: string, x: number, y: string, z: boolean): string {
console.log('in v');
return `ctxt ${ctxt} x ${x} y ${y} z ${z}`;
},
w(ctxt: string, m: [string, number]) { console.log('w', ctxt, m); },
w2(ctxt: string, x: number, ...m: [string, number][]) { console.log('w2', ctxt, x, m); },
},
// {
// selector: 'w2',
// args: [99, tuple('hi', 123), tuple('hi', 123)],
// callback: (result: void) => console.log('result:', result)
// },
{
selector: 'v',
args: [123, 'hi', true],
callback: (result: string) => console.log('result:', result)
},
'C');

8
package.json Normal file
View File

@ -0,0 +1,8 @@
{
"devDependencies": {
"typescript": "^4.1.5"
},
"dependencies": {
"preserves": "^0.5.2"
}
}

18
tsconfig.json Normal file
View File

@ -0,0 +1,18 @@
{
"compilerOptions": {
"target": "es2017",
"lib": ["es2017", "dom"],
"declaration": true,
"baseUrl": ".",
"rootDir": ".",
"outDir": ".",
"declarationDir": ".",
"esModuleInterop": true,
"moduleResolution": "node",
"module": "commonjs",
"sourceMap": true,
"strict": true
},
"include": ["**/*.ts"],
"exclude": []
}