posipaki 0.6.2 → 0.6.4
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/dist/index.d.ts +2 -3
- package/dist/index.js +165 -3
- package/dist/index.js.map +1 -1
- package/dist/pipe.d.ts +22 -0
- package/dist/pipe.d.ts.map +1 -0
- package/dist/pipe.js +49 -0
- package/dist/pipe.js.map +1 -0
- package/dist/process-BlUe6Luh.d.ts +100 -0
- package/dist/process-BlUe6Luh.d.ts.map +1 -0
- package/dist/supervisor.d.ts +45 -0
- package/dist/supervisor.d.ts.map +1 -0
- package/dist/supervisor.js +53 -0
- package/dist/supervisor.js.map +1 -0
- package/dist/util-5PSJsovA.js +64 -0
- package/dist/util-5PSJsovA.js.map +1 -0
- package/package.json +13 -15
- package/dist/process.d.ts +0 -47
- package/dist/process.js +0 -130
- package/dist/process.js.map +0 -1
- package/dist/util.d.ts +0 -23
- package/dist/util.js +0 -51
- package/dist/util.js.map +0 -1
- package/dist/xfetch.d.ts +0 -30
- package/dist/xfetch.js +0 -78
- package/dist/xfetch.js.map +0 -1
- package/src/index.ts +0 -4
- package/src/process.ts +0 -181
- package/src/util.ts +0 -86
- package/src/xfetch.ts +0 -103
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,2 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
export { Process, spawn, runDispatch, Message, ExitMessage, ProcessCtx, ProcessFn };
|
|
1
|
+
import { a as spawn, i as ProcessFn, n as Process, o as ExitMessage, r as ProcessCtx, s as runDispatch, t as Message } from "./process-BlUe6Luh.js";
|
|
2
|
+
export { type ExitMessage, type Message, Process, type ProcessCtx, type ProcessFn, runDispatch, spawn };
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,166 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { i as watchExit, n as makeWaiter, r as runDispatch, t as defer } from "./util-5PSJsovA.js";
|
|
2
|
+
//#region src/process.ts
|
|
3
|
+
/**
|
|
4
|
+
* Spawn a new process from a process function. Returns a curried
|
|
5
|
+
* function: call it with initial args to start execution.
|
|
6
|
+
*/
|
|
7
|
+
function spawn(fn, pname, toParent) {
|
|
8
|
+
return (args) => {
|
|
9
|
+
const process = new Process(fn, pname, toParent);
|
|
10
|
+
process.start(args);
|
|
11
|
+
return process;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
const noop = () => null;
|
|
15
|
+
/**
|
|
16
|
+
* A running process — an actor with message-passing, child processes,
|
|
17
|
+
* and observable state. Driven by a generator function that yields
|
|
18
|
+
* state snapshots and receives messages via `yield` expressions.
|
|
19
|
+
*/
|
|
20
|
+
var Process = class Process {
|
|
21
|
+
constructor(fn, pname, toParent) {
|
|
22
|
+
this._isPaused = false;
|
|
23
|
+
this.pgenerator = fn;
|
|
24
|
+
this.pname = pname;
|
|
25
|
+
this.toParent = toParent || noop;
|
|
26
|
+
this.id = Symbol(pname);
|
|
27
|
+
this.current = null;
|
|
28
|
+
this.state = null;
|
|
29
|
+
this.buffer = [];
|
|
30
|
+
this.nextTick = null;
|
|
31
|
+
this.children = [];
|
|
32
|
+
this.subscribers = [];
|
|
33
|
+
this.exitWaiter = makeWaiter();
|
|
34
|
+
}
|
|
35
|
+
/** Kick off the generator with initial arguments. */
|
|
36
|
+
start(arg0) {
|
|
37
|
+
const ctx = {
|
|
38
|
+
pname: this.pname,
|
|
39
|
+
fork: this.fork.bind(this),
|
|
40
|
+
send: this.send.bind(this),
|
|
41
|
+
toParent: this.toParent
|
|
42
|
+
};
|
|
43
|
+
const task = watchExit(this)(ctx, arg0);
|
|
44
|
+
this.current = task;
|
|
45
|
+
let ret = task.next();
|
|
46
|
+
this.state = ret.value || null;
|
|
47
|
+
this._eatResult(task.next());
|
|
48
|
+
}
|
|
49
|
+
/** Fork a child process. Children forward messages to this process
|
|
50
|
+
* and send `EXIT` when they terminate. */
|
|
51
|
+
fork(fn, pname) {
|
|
52
|
+
return (args) => {
|
|
53
|
+
const child = new Process(fn, pname, this.fromChild.bind(this));
|
|
54
|
+
this.children.push(child);
|
|
55
|
+
child.start(args);
|
|
56
|
+
return child;
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
_tick() {
|
|
60
|
+
if (!this.current) return;
|
|
61
|
+
let msg;
|
|
62
|
+
let ret = null;
|
|
63
|
+
while (msg = this.buffer.shift()) {
|
|
64
|
+
ret = this.current.next(msg);
|
|
65
|
+
if (ret.done) break;
|
|
66
|
+
}
|
|
67
|
+
this.notify();
|
|
68
|
+
this._eatResult(ret);
|
|
69
|
+
}
|
|
70
|
+
_eatResult(ret) {
|
|
71
|
+
if (ret && ret.done) this.exitWaiter.resolve();
|
|
72
|
+
}
|
|
73
|
+
/** Send a message to every child process (used for `STOP` propagation). */
|
|
74
|
+
toAllChildren(msg) {
|
|
75
|
+
this.children.forEach((p) => p.send(msg));
|
|
76
|
+
}
|
|
77
|
+
/** Send a message to this process. Messages are buffered and
|
|
78
|
+
* processed asynchronously via a microtask. */
|
|
79
|
+
send(msg) {
|
|
80
|
+
this.buffer.push(msg);
|
|
81
|
+
this._scheduleTick();
|
|
82
|
+
}
|
|
83
|
+
/** Synchronously flush the message buffer. Useful in tests. */
|
|
84
|
+
tick() {
|
|
85
|
+
this.nextTick?.flush();
|
|
86
|
+
this.nextTick = null;
|
|
87
|
+
}
|
|
88
|
+
_scheduleTick() {
|
|
89
|
+
if (this._isPaused) return;
|
|
90
|
+
this.nextTick?.cancel();
|
|
91
|
+
this.nextTick = defer(() => {
|
|
92
|
+
this.nextTick = null;
|
|
93
|
+
this._tick();
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
notify() {
|
|
97
|
+
this.subscribers.forEach((f) => f());
|
|
98
|
+
}
|
|
99
|
+
/** Whether any subscribers are watching for state changes. */
|
|
100
|
+
get isListenedTo() {
|
|
101
|
+
return this.subscribers.length > 0;
|
|
102
|
+
}
|
|
103
|
+
/** Subscribe to state changes. Returns an unsubscribe function. */
|
|
104
|
+
subscribe(f) {
|
|
105
|
+
this.subscribers.push(f);
|
|
106
|
+
return () => {
|
|
107
|
+
const idx = this.subscribers.indexOf(f);
|
|
108
|
+
if (idx < 0) return;
|
|
109
|
+
this.subscribers.splice(idx, 1);
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
/** Pause message processing. Incoming messages are buffered
|
|
113
|
+
* but not processed until {@link resume} is called. */
|
|
114
|
+
pause() {
|
|
115
|
+
this.nextTick?.cancel();
|
|
116
|
+
this.nextTick = null;
|
|
117
|
+
this._isPaused = true;
|
|
118
|
+
}
|
|
119
|
+
/** Resume processing after a {@link pause}. */
|
|
120
|
+
resume() {
|
|
121
|
+
this._isPaused = false;
|
|
122
|
+
this._scheduleTick();
|
|
123
|
+
}
|
|
124
|
+
/** Return a promise that resolves when the generator completes. */
|
|
125
|
+
wait() {
|
|
126
|
+
return this.exitWaiter.promise;
|
|
127
|
+
}
|
|
128
|
+
/** Handle a message forwarded from a child process. `EXIT` removes
|
|
129
|
+
* the child; all other messages are forwarded to `send`. */
|
|
130
|
+
fromChild(msg) {
|
|
131
|
+
if (msg.type === "EXIT") this.children = this.children.filter((p) => p.id !== msg.pid);
|
|
132
|
+
this.send(msg);
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
//#endregion
|
|
136
|
+
//#region src/index.ts
|
|
137
|
+
/**
|
|
138
|
+
* Posipaki — Erlang-inspired lightweight actor processes built on
|
|
139
|
+
* generator functions. Processes communicate via message-passing,
|
|
140
|
+
* can fork children, and expose their state reactively.
|
|
141
|
+
*
|
|
142
|
+
* ## Quick start
|
|
143
|
+
*
|
|
144
|
+
* ```ts
|
|
145
|
+
* import { spawn, runDispatch } from 'posipaki'
|
|
146
|
+
*
|
|
147
|
+
* function* hello({ pname }) {
|
|
148
|
+
* const state = { count: 0 }
|
|
149
|
+
* yield state
|
|
150
|
+
* yield* runDispatch(pname, (msg) => {
|
|
151
|
+
* if (msg.type === 'POKE') state.count++
|
|
152
|
+
* }, () => false)
|
|
153
|
+
* }
|
|
154
|
+
*
|
|
155
|
+
* const proc = spawn(hello, 'hello')(null)
|
|
156
|
+
* proc.send({ type: 'POKE' })
|
|
157
|
+
* proc.tick()
|
|
158
|
+
* console.log(proc.state.count) // 1
|
|
159
|
+
* ```
|
|
160
|
+
*
|
|
161
|
+
* @module
|
|
162
|
+
*/
|
|
163
|
+
//#endregion
|
|
164
|
+
export { Process, runDispatch, spawn };
|
|
165
|
+
|
|
4
166
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,EAAE,EAAE,KAAK,EAAkC,MAAM,WAAW,CAAC;AAC3E,OAAO,EAAe,WAAW,EAAE,MAAM,QAAQ,CAAC;AAElD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,EAA+C,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/process.ts","../src/index.ts"],"sourcesContent":["import {\n ExitMessage,\n watchExit,\n Waiter,\n defer,\n DeferredCall,\n makeWaiter,\n} from \"./util\";\n\n/**\n * Base message type. All messages must include a `type` field\n * for discrimination in reducers.\n */\nexport interface Message {\n type: string;\n}\n\ntype ProcessGenerator<ProcessState, InMessage> = Generator<\n ProcessState | null,\n void,\n InMessage\n>;\ntype Fork = <\n ChildArgs,\n ChildState,\n InMessage extends Message,\n OutMessage extends Message,\n>(\n fn: ProcessFn<ChildArgs, ChildState, InMessage, OutMessage>,\n pname: string,\n) => (args: ChildArgs) => Process<ChildArgs, ChildState, InMessage, OutMessage>;\n\n/**\n * A process generator function. Receives a {@link ProcessCtx} and\n * initial args, yields state (or `null`) at each step.\n */\nexport type ProcessFn<Args, State, InMessage, OutMessage> = (\n ctx: ProcessCtx<InMessage, OutMessage>,\n args: Args,\n) => ProcessGenerator<State, InMessage>;\ntype ProcessMessageCb<M> = (msg: M) => void;\n\n/**\n * Context injected into every running process. Provides the process\n * name, ability to fork children, and I/O channels.\n */\nexport type ProcessCtx<IM, OM> = {\n pname: string;\n fork: Fork;\n send: (msg: IM) => void;\n toParent: ProcessMessageCb<OM>;\n};\n\ntype NotifyFn = () => void;\ntype _UnsubscibeFn = () => void;\n\n/**\n * Spawn a new process from a process function. Returns a curried\n * function: call it with initial args to start execution.\n */\nexport function spawn<\n Args,\n State,\n InMessage extends Message = Message,\n OutMessage extends Message = ExitMessage,\n>(\n fn: ProcessFn<Args, State, InMessage, OutMessage>,\n pname: string,\n toParent?: ProcessMessageCb<OutMessage>,\n) {\n return (args: Args): Process<Args, State, InMessage, OutMessage> => {\n const process = new Process<Args, State, InMessage, OutMessage>(\n fn,\n pname,\n toParent,\n );\n process.start(args);\n return process;\n };\n}\nconst noop = () => null;\n\n/**\n * A running process — an actor with message-passing, child processes,\n * and observable state. Driven by a generator function that yields\n * state snapshots and receives messages via `yield` expressions.\n */\nclass Process<\n Args,\n State,\n InMessage extends Message,\n OutMessage extends Message,\n> {\n pgenerator: ProcessFn<Args, State, InMessage, OutMessage>;\n pname: string;\n toParent: ProcessMessageCb<OutMessage>;\n id: symbol;\n state: State | null;\n\n private current: ProcessGenerator<State, InMessage> | null;\n private buffer: Array<InMessage>;\n private nextTick: DeferredCall | null;\n private children: Array<Process<unknown, unknown, Message, Message>>;\n private subscribers: Array<NotifyFn>;\n private exitWaiter: Waiter;\n private _isPaused: boolean = false;\n\n constructor(\n fn: ProcessFn<Args, State, InMessage, OutMessage>,\n pname: string,\n toParent: ProcessMessageCb<OutMessage> | undefined,\n ) {\n this.pgenerator = fn;\n this.pname = pname;\n this.toParent = toParent || noop;\n this.id = Symbol(pname);\n\n this.current = null;\n this.state = null;\n this.buffer = [];\n this.nextTick = null;\n\n this.children = [];\n this.subscribers = [];\n this.exitWaiter = makeWaiter();\n }\n\n /** Kick off the generator with initial arguments. */\n start(arg0: Args) {\n const ctx: ProcessCtx<InMessage, OutMessage> = {\n pname: this.pname,\n fork: this.fork.bind(this),\n send: this.send.bind(this),\n toParent: this.toParent,\n };\n const task = watchExit<Args, State, InMessage, OutMessage>(this)(ctx, arg0);\n this.current = task;\n let ret = task.next();\n this.state = ret.value || null;\n this._eatResult(task.next());\n }\n\n /** Fork a child process. Children forward messages to this process\n * and send `EXIT` when they terminate. */\n fork<ChildArgs, ChildState, ChildIM extends Message, ChildOM extends Message>(\n fn: ProcessFn<ChildArgs, ChildState, ChildIM, ChildOM>,\n pname: string,\n ) {\n return (\n args: ChildArgs,\n ): Process<ChildArgs, ChildState, ChildIM, ChildOM> => {\n // not enough typescript power in this one\n const fromChild = this.fromChild.bind(this) as unknown;\n const child = new Process<ChildArgs, ChildState, ChildIM, ChildOM>(\n fn,\n pname,\n fromChild as ProcessMessageCb<ChildOM>,\n );\n // we don't keep track of what is happening down there\n this.children.push(\n child as unknown as Process<unknown, unknown, Message, Message>,\n );\n child.start(args);\n return child;\n };\n }\n\n _tick() {\n if (!this.current) {\n return;\n }\n let msg: InMessage | undefined;\n let ret: IteratorResult<State | null, void> | null = null;\n while ((msg = this.buffer.shift())) {\n ret = this.current.next(msg);\n if (ret.done) {\n break;\n }\n }\n this.notify();\n this._eatResult(ret);\n }\n\n _eatResult(ret: IteratorResult<State | null, void> | null) {\n if (ret && ret.done) {\n this.exitWaiter.resolve();\n }\n }\n\n /** Send a message to every child process (used for `STOP` propagation). */\n toAllChildren(msg: Message) {\n this.children.forEach((p) => p.send(msg));\n }\n\n /** Send a message to this process. Messages are buffered and\n * processed asynchronously via a microtask. */\n send(msg: InMessage) {\n this.buffer.push(msg);\n this._scheduleTick();\n }\n\n /** Synchronously flush the message buffer. Useful in tests. */\n tick() {\n this.nextTick?.flush();\n this.nextTick = null;\n }\n\n _scheduleTick() {\n if (this._isPaused) {\n return;\n }\n\n this.nextTick?.cancel();\n this.nextTick = defer(() => {\n this.nextTick = null;\n this._tick();\n });\n }\n\n notify() {\n this.subscribers.forEach((f) => f());\n }\n\n /** Whether any subscribers are watching for state changes. */\n get isListenedTo() {\n return this.subscribers.length > 0;\n }\n\n /** Subscribe to state changes. Returns an unsubscribe function. */\n subscribe(f: NotifyFn) {\n this.subscribers.push(f);\n return () => {\n const idx = this.subscribers.indexOf(f);\n if (idx < 0) {\n return;\n }\n this.subscribers.splice(idx, 1);\n };\n }\n\n /** Pause message processing. Incoming messages are buffered\n * but not processed until {@link resume} is called. */\n pause() {\n this.nextTick?.cancel();\n this.nextTick = null;\n this._isPaused = true;\n }\n\n /** Resume processing after a {@link pause}. */\n resume() {\n this._isPaused = false;\n this._scheduleTick();\n }\n\n /** Return a promise that resolves when the generator completes. */\n wait() {\n return this.exitWaiter.promise;\n }\n\n /** Handle a message forwarded from a child process. `EXIT` removes\n * the child; all other messages are forwarded to `send`. */\n fromChild(msg: InMessage) {\n if (msg.type === \"EXIT\") {\n this.children = this.children.filter(\n (p) => p.id !== (msg as unknown as ExitMessage).pid,\n );\n }\n this.send(msg);\n }\n}\n\nexport { Process };\n","/**\n * Posipaki — Erlang-inspired lightweight actor processes built on\n * generator functions. Processes communicate via message-passing,\n * can fork children, and expose their state reactively.\n *\n * ## Quick start\n *\n * ```ts\n * import { spawn, runDispatch } from 'posipaki'\n *\n * function* hello({ pname }) {\n * const state = { count: 0 }\n * yield state\n * yield* runDispatch(pname, (msg) => {\n * if (msg.type === 'POKE') state.count++\n * }, () => false)\n * }\n *\n * const proc = spawn(hello, 'hello')(null)\n * proc.send({ type: 'POKE' })\n * proc.tick()\n * console.log(proc.state.count) // 1\n * ```\n *\n * @module\n */\n\nimport { Process, spawn } from \"./process\";\nimport type { Message, ProcessCtx, ProcessFn } from \"./process\";\nimport { runDispatch } from \"./util\";\nimport type { ExitMessage } from \"./util\";\n\nexport { Process, spawn, runDispatch };\nexport type { Message, ExitMessage, ProcessCtx, ProcessFn };\n"],"mappings":";;;;;;AA4DA,SAAgB,MAMd,IACA,OACA,UACA;CACA,QAAQ,SAA4D;EAClE,MAAM,UAAU,IAAI,QAClB,IACA,OACA,QACF;EACA,QAAQ,MAAM,IAAI;EAClB,OAAO;CACT;AACF;AACA,MAAM,aAAa;;;;;;AAOnB,IAAM,UAAN,MAAM,QAKJ;CAeA,YACE,IACA,OACA,UACA;mBAN2B;EAO3B,KAAK,aAAa;EAClB,KAAK,QAAQ;EACb,KAAK,WAAW,YAAY;EAC5B,KAAK,KAAK,OAAO,KAAK;EAEtB,KAAK,UAAU;EACf,KAAK,QAAQ;EACb,KAAK,SAAS,CAAC;EACf,KAAK,WAAW;EAEhB,KAAK,WAAW,CAAC;EACjB,KAAK,cAAc,CAAC;EACpB,KAAK,aAAa,WAAW;CAC/B;;CAGA,MAAM,MAAY;EAChB,MAAM,MAAyC;GAC7C,OAAO,KAAK;GACZ,MAAM,KAAK,KAAK,KAAK,IAAI;GACzB,MAAM,KAAK,KAAK,KAAK,IAAI;GACzB,UAAU,KAAK;EACjB;EACA,MAAM,OAAO,UAA8C,IAAI,EAAE,KAAK,IAAI;EAC1E,KAAK,UAAU;EACf,IAAI,MAAM,KAAK,KAAK;EACpB,KAAK,QAAQ,IAAI,SAAS;EAC1B,KAAK,WAAW,KAAK,KAAK,CAAC;CAC7B;;;CAIA,KACE,IACA,OACA;EACA,QACE,SACqD;GAGrD,MAAM,QAAQ,IAAI,QAChB,IACA,OAHgB,KAAK,UAAU,KAAK,IAI5B,CACV;GAEA,KAAK,SAAS,KACZ,KACF;GACA,MAAM,MAAM,IAAI;GAChB,OAAO;EACT;CACF;CAEA,QAAQ;EACN,IAAI,CAAC,KAAK,SACR;EAEF,IAAI;EACJ,IAAI,MAAiD;EACrD,OAAQ,MAAM,KAAK,OAAO,MAAM,GAAI;GAClC,MAAM,KAAK,QAAQ,KAAK,GAAG;GAC3B,IAAI,IAAI,MACN;EAEJ;EACA,KAAK,OAAO;EACZ,KAAK,WAAW,GAAG;CACrB;CAEA,WAAW,KAAgD;EACzD,IAAI,OAAO,IAAI,MACb,KAAK,WAAW,QAAQ;CAE5B;;CAGA,cAAc,KAAc;EAC1B,KAAK,SAAS,SAAS,MAAM,EAAE,KAAK,GAAG,CAAC;CAC1C;;;CAIA,KAAK,KAAgB;EACnB,KAAK,OAAO,KAAK,GAAG;EACpB,KAAK,cAAc;CACrB;;CAGA,OAAO;EACL,KAAK,UAAU,MAAM;EACrB,KAAK,WAAW;CAClB;CAEA,gBAAgB;EACd,IAAI,KAAK,WACP;EAGF,KAAK,UAAU,OAAO;EACtB,KAAK,WAAW,YAAY;GAC1B,KAAK,WAAW;GAChB,KAAK,MAAM;EACb,CAAC;CACH;CAEA,SAAS;EACP,KAAK,YAAY,SAAS,MAAM,EAAE,CAAC;CACrC;;CAGA,IAAI,eAAe;EACjB,OAAO,KAAK,YAAY,SAAS;CACnC;;CAGA,UAAU,GAAa;EACrB,KAAK,YAAY,KAAK,CAAC;EACvB,aAAa;GACX,MAAM,MAAM,KAAK,YAAY,QAAQ,CAAC;GACtC,IAAI,MAAM,GACR;GAEF,KAAK,YAAY,OAAO,KAAK,CAAC;EAChC;CACF;;;CAIA,QAAQ;EACN,KAAK,UAAU,OAAO;EACtB,KAAK,WAAW;EAChB,KAAK,YAAY;CACnB;;CAGA,SAAS;EACP,KAAK,YAAY;EACjB,KAAK,cAAc;CACrB;;CAGA,OAAO;EACL,OAAO,KAAK,WAAW;CACzB;;;CAIA,UAAU,KAAgB;EACxB,IAAI,IAAI,SAAS,QACf,KAAK,WAAW,KAAK,SAAS,QAC3B,MAAM,EAAE,OAAQ,IAA+B,GAClD;EAEF,KAAK,KAAK,GAAG;CACf;AACF"}
|
package/dist/pipe.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { i as ProcessFn, o as ExitMessage, t as Message } from "./process-BlUe6Luh.js";
|
|
2
|
+
|
|
3
|
+
//#region src/pipe.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* State yielded by the pipe process. `params` starts as a copy of the
|
|
6
|
+
* initial args and is updated after each child exits by merging the
|
|
7
|
+
* child's `state.result` into it.
|
|
8
|
+
*/
|
|
9
|
+
interface PipeState<Params, Result> {
|
|
10
|
+
params: Params | null;
|
|
11
|
+
result: Result | null;
|
|
12
|
+
running: boolean;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Chain process functions so each runs after the previous one exits.
|
|
16
|
+
* The `.state.result` of each completed child is spread into the params
|
|
17
|
+
* of the next.
|
|
18
|
+
*/
|
|
19
|
+
declare function pipe<Params, Result>(fns: ProcessFn<Params, any, any, any>[]): ProcessFn<Params, PipeState<Params, Result>, Message, Message | ExitMessage>;
|
|
20
|
+
//#endregion
|
|
21
|
+
export { PipeState, pipe };
|
|
22
|
+
//# sourceMappingURL=pipe.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pipe.d.ts","names":[],"sources":["../src/pipe.ts"],"mappings":";;;AAWA;;;;;AAAA,UAAiB,SAAA;EACf,MAAA,EAAQ,MAAA;EACR,MAAA,EAAQ,MAAM;EACd,OAAA;AAAA;;;AAAO;AACR;;iBAOQ,IAAA,iBACP,GAAA,EAAK,SAAA,CAAU,MAAA,qBACd,SAAA,CACD,MAAA,EACA,SAAA,CAAU,MAAA,EAAQ,MAAA,GAClB,OAAA,EACA,OAAA,GAAU,WAAA"}
|
package/dist/pipe.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { r as runDispatch } from "./util-5PSJsovA.js";
|
|
2
|
+
//#region src/pipe.ts
|
|
3
|
+
/**
|
|
4
|
+
* Chain process functions so each runs after the previous one exits.
|
|
5
|
+
* The `.state.result` of each completed child is spread into the params
|
|
6
|
+
* of the next.
|
|
7
|
+
*/
|
|
8
|
+
function pipe(fns) {
|
|
9
|
+
return function* ({ pname, fork }, params) {
|
|
10
|
+
const state = {
|
|
11
|
+
params: { ...params },
|
|
12
|
+
result: null,
|
|
13
|
+
running: false
|
|
14
|
+
};
|
|
15
|
+
yield state;
|
|
16
|
+
const queue = [...fns];
|
|
17
|
+
let task;
|
|
18
|
+
function spawnNext() {
|
|
19
|
+
const fn = queue.shift();
|
|
20
|
+
task = fork(fn, `${pname} [${fn.name || "<anonymous>"}]`)(state.params);
|
|
21
|
+
}
|
|
22
|
+
function hasNext() {
|
|
23
|
+
return queue.length > 0;
|
|
24
|
+
}
|
|
25
|
+
spawnNext();
|
|
26
|
+
state.running = hasNext();
|
|
27
|
+
yield* runDispatch(pname, (msg) => {
|
|
28
|
+
if (msg.type === "STOP") {
|
|
29
|
+
state.params = null;
|
|
30
|
+
state.running = false;
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (msg.type === "EXIT" && msg.pid === task.id) if (hasNext()) {
|
|
34
|
+
state.params = {
|
|
35
|
+
...state.params,
|
|
36
|
+
...task.state?.result
|
|
37
|
+
};
|
|
38
|
+
spawnNext();
|
|
39
|
+
} else {
|
|
40
|
+
state.running = false;
|
|
41
|
+
state.result = task.state?.result;
|
|
42
|
+
}
|
|
43
|
+
}, () => !state.running, true);
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
//#endregion
|
|
47
|
+
export { pipe };
|
|
48
|
+
|
|
49
|
+
//# sourceMappingURL=pipe.js.map
|
package/dist/pipe.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pipe.js","names":[],"sources":["../src/pipe.ts"],"sourcesContent":["import { runDispatch } from \"./util.js\";\nimport type { ExitMessage } from \"./util.js\";\nimport type { ProcessFn, ProcessCtx } from \"./process.js\";\nimport { Process } from \"./process.js\";\nimport type { Message } from \"./process.js\";\n\n/**\n * State yielded by the pipe process. `params` starts as a copy of the\n * initial args and is updated after each child exits by merging the\n * child's `state.result` into it.\n */\nexport interface PipeState<Params, Result> {\n params: Params | null;\n result: Result | null;\n running: boolean;\n}\n\n/**\n * Chain process functions so each runs after the previous one exits.\n * The `.state.result` of each completed child is spread into the params\n * of the next.\n */\nfunction pipe<Params, Result>(\n fns: ProcessFn<Params, any, any, any>[],\n): ProcessFn<\n Params,\n PipeState<Params, Result>,\n Message,\n Message | ExitMessage\n> {\n return function* (\n { pname, fork }: ProcessCtx<Message, Message | ExitMessage>,\n params: Params,\n ) {\n const state: PipeState<Params, Result> = {\n params: { ...params },\n result: null,\n running: false,\n };\n yield state;\n\n const queue = [...fns];\n let task: Process<any, any, any, any>;\n\n function spawnNext(): void {\n const fn = queue.shift()!;\n task = fork(\n fn,\n `${pname} [${fn.name || \"<anonymous>\"}]`,\n )(state.params as Params);\n }\n\n function hasNext(): boolean {\n return queue.length > 0;\n }\n\n spawnNext();\n state.running = hasNext();\n\n yield* runDispatch<Message | ExitMessage>(\n pname,\n (msg) => {\n if (msg.type === \"STOP\") {\n state.params = null;\n state.running = false;\n return;\n }\n\n if (msg.type === \"EXIT\" && (msg as ExitMessage).pid === task.id) {\n if (hasNext()) {\n state.params = {\n ...state.params!,\n ...task.state?.result,\n } as Params;\n spawnNext();\n } else {\n state.running = false;\n state.result = task.state?.result as Result;\n }\n }\n },\n () => !state.running,\n true,\n );\n };\n}\n\nexport { pipe };\n"],"mappings":";;;;;;;AAsBA,SAAS,KACP,KAMA;CACA,OAAO,WACL,EAAE,OAAO,QACT,QACA;EACA,MAAM,QAAmC;GACvC,QAAQ,EAAE,GAAG,OAAO;GACpB,QAAQ;GACR,SAAS;EACX;EACA,MAAM;EAEN,MAAM,QAAQ,CAAC,GAAG,GAAG;EACrB,IAAI;EAEJ,SAAS,YAAkB;GACzB,MAAM,KAAK,MAAM,MAAM;GACvB,OAAO,KACL,IACA,GAAG,MAAM,IAAI,GAAG,QAAQ,cAAc,EACxC,EAAE,MAAM,MAAgB;EAC1B;EAEA,SAAS,UAAmB;GAC1B,OAAO,MAAM,SAAS;EACxB;EAEA,UAAU;EACV,MAAM,UAAU,QAAQ;EAExB,OAAO,YACL,QACC,QAAQ;GACP,IAAI,IAAI,SAAS,QAAQ;IACvB,MAAM,SAAS;IACf,MAAM,UAAU;IAChB;GACF;GAEA,IAAI,IAAI,SAAS,UAAW,IAAoB,QAAQ,KAAK,IAC3D,IAAI,QAAQ,GAAG;IACb,MAAM,SAAS;KACb,GAAG,MAAM;KACT,GAAG,KAAK,OAAO;IACjB;IACA,UAAU;GACZ,OAAO;IACL,MAAM,UAAU;IAChB,MAAM,SAAS,KAAK,OAAO;GAC7B;EAEJ,SACM,CAAC,MAAM,SACb,IACF;CACF;AACF"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
//#region src/util.d.ts
|
|
2
|
+
type ReducerClosure<M> = (msg: M) => void;
|
|
3
|
+
type ReadyFn = () => boolean;
|
|
4
|
+
/**
|
|
5
|
+
* Generator helper that loops, yielding `null` and feeding incoming
|
|
6
|
+
* messages to `fn` until `readyFn()` returns true. Used inside
|
|
7
|
+
* process generators to build the main message loop.
|
|
8
|
+
*/
|
|
9
|
+
declare function runDispatch<M>(name: string, fn: ReducerClosure<M>, readyFn?: ReadyFn, debugLevel?: boolean): Generator<null, void, M>;
|
|
10
|
+
/** Message emitted by a process to its parent when it terminates. */
|
|
11
|
+
type ExitMessage = {
|
|
12
|
+
type: 'EXIT';
|
|
13
|
+
pid: symbol;
|
|
14
|
+
};
|
|
15
|
+
//#endregion
|
|
16
|
+
//#region src/process.d.ts
|
|
17
|
+
/**
|
|
18
|
+
* Base message type. All messages must include a `type` field
|
|
19
|
+
* for discrimination in reducers.
|
|
20
|
+
*/
|
|
21
|
+
interface Message {
|
|
22
|
+
type: string;
|
|
23
|
+
}
|
|
24
|
+
type ProcessGenerator<ProcessState, InMessage> = Generator<ProcessState | null, void, InMessage>;
|
|
25
|
+
type Fork = <ChildArgs, ChildState, InMessage extends Message, OutMessage extends Message>(fn: ProcessFn<ChildArgs, ChildState, InMessage, OutMessage>, pname: string) => (args: ChildArgs) => Process<ChildArgs, ChildState, InMessage, OutMessage>;
|
|
26
|
+
/**
|
|
27
|
+
* A process generator function. Receives a {@link ProcessCtx} and
|
|
28
|
+
* initial args, yields state (or `null`) at each step.
|
|
29
|
+
*/
|
|
30
|
+
type ProcessFn<Args, State, InMessage, OutMessage> = (ctx: ProcessCtx<InMessage, OutMessage>, args: Args) => ProcessGenerator<State, InMessage>;
|
|
31
|
+
type ProcessMessageCb<M> = (msg: M) => void;
|
|
32
|
+
/**
|
|
33
|
+
* Context injected into every running process. Provides the process
|
|
34
|
+
* name, ability to fork children, and I/O channels.
|
|
35
|
+
*/
|
|
36
|
+
type ProcessCtx<IM, OM> = {
|
|
37
|
+
pname: string;
|
|
38
|
+
fork: Fork;
|
|
39
|
+
send: (msg: IM) => void;
|
|
40
|
+
toParent: ProcessMessageCb<OM>;
|
|
41
|
+
};
|
|
42
|
+
type NotifyFn = () => void;
|
|
43
|
+
/**
|
|
44
|
+
* Spawn a new process from a process function. Returns a curried
|
|
45
|
+
* function: call it with initial args to start execution.
|
|
46
|
+
*/
|
|
47
|
+
declare function spawn<Args, State, InMessage extends Message = Message, OutMessage extends Message = ExitMessage>(fn: ProcessFn<Args, State, InMessage, OutMessage>, pname: string, toParent?: ProcessMessageCb<OutMessage>): (args: Args) => Process<Args, State, InMessage, OutMessage>;
|
|
48
|
+
/**
|
|
49
|
+
* A running process — an actor with message-passing, child processes,
|
|
50
|
+
* and observable state. Driven by a generator function that yields
|
|
51
|
+
* state snapshots and receives messages via `yield` expressions.
|
|
52
|
+
*/
|
|
53
|
+
declare class Process<Args, State, InMessage extends Message, OutMessage extends Message> {
|
|
54
|
+
pgenerator: ProcessFn<Args, State, InMessage, OutMessage>;
|
|
55
|
+
pname: string;
|
|
56
|
+
toParent: ProcessMessageCb<OutMessage>;
|
|
57
|
+
id: symbol;
|
|
58
|
+
state: State | null;
|
|
59
|
+
private current;
|
|
60
|
+
private buffer;
|
|
61
|
+
private nextTick;
|
|
62
|
+
private children;
|
|
63
|
+
private subscribers;
|
|
64
|
+
private exitWaiter;
|
|
65
|
+
private _isPaused;
|
|
66
|
+
constructor(fn: ProcessFn<Args, State, InMessage, OutMessage>, pname: string, toParent: ProcessMessageCb<OutMessage> | undefined);
|
|
67
|
+
/** Kick off the generator with initial arguments. */
|
|
68
|
+
start(arg0: Args): void;
|
|
69
|
+
/** Fork a child process. Children forward messages to this process
|
|
70
|
+
* and send `EXIT` when they terminate. */
|
|
71
|
+
fork<ChildArgs, ChildState, ChildIM extends Message, ChildOM extends Message>(fn: ProcessFn<ChildArgs, ChildState, ChildIM, ChildOM>, pname: string): (args: ChildArgs) => Process<ChildArgs, ChildState, ChildIM, ChildOM>;
|
|
72
|
+
_tick(): void;
|
|
73
|
+
_eatResult(ret: IteratorResult<State | null, void> | null): void;
|
|
74
|
+
/** Send a message to every child process (used for `STOP` propagation). */
|
|
75
|
+
toAllChildren(msg: Message): void;
|
|
76
|
+
/** Send a message to this process. Messages are buffered and
|
|
77
|
+
* processed asynchronously via a microtask. */
|
|
78
|
+
send(msg: InMessage): void;
|
|
79
|
+
/** Synchronously flush the message buffer. Useful in tests. */
|
|
80
|
+
tick(): void;
|
|
81
|
+
_scheduleTick(): void;
|
|
82
|
+
notify(): void;
|
|
83
|
+
/** Whether any subscribers are watching for state changes. */
|
|
84
|
+
get isListenedTo(): boolean;
|
|
85
|
+
/** Subscribe to state changes. Returns an unsubscribe function. */
|
|
86
|
+
subscribe(f: NotifyFn): () => void;
|
|
87
|
+
/** Pause message processing. Incoming messages are buffered
|
|
88
|
+
* but not processed until {@link resume} is called. */
|
|
89
|
+
pause(): void;
|
|
90
|
+
/** Resume processing after a {@link pause}. */
|
|
91
|
+
resume(): void;
|
|
92
|
+
/** Return a promise that resolves when the generator completes. */
|
|
93
|
+
wait(): Promise<void>;
|
|
94
|
+
/** Handle a message forwarded from a child process. `EXIT` removes
|
|
95
|
+
* the child; all other messages are forwarded to `send`. */
|
|
96
|
+
fromChild(msg: InMessage): void;
|
|
97
|
+
}
|
|
98
|
+
//#endregion
|
|
99
|
+
export { spawn as a, ProcessFn as i, Process as n, ExitMessage as o, ProcessCtx as r, runDispatch as s, Message as t };
|
|
100
|
+
//# sourceMappingURL=process-BlUe6Luh.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"process-BlUe6Luh.d.ts","names":[],"sources":["../src/util.ts","../src/process.ts"],"mappings":";KASK,cAAA,OAAqB,GAAA,EAAK,CAAC;AAAA,KAC3B,OAAA;;;;;AAD2B;iBAStB,WAAA,IAAe,IAAA,UAAc,EAAA,EAAK,cAAA,CAAe,CAAA,GAAI,OAAA,GAAS,OAAA,EAAsB,UAAA,aAAsB,SAAA,aAAsB,CAAA;;KAU9H,WAAA;EACV,IAAA;EACA,GAAG;AAAA;;;AA7BmD;;;;AAAA,UCYvC,OAAA;EACf,IAAI;AAAA;AAAA,KAGD,gBAAA,4BAA4C,SAAA,CAC/C,YAAA,eAEA,SAAA;AAAA,KAEG,IAAA,6CAGe,OAAA,qBACC,OAAA,EAEnB,EAAA,EAAI,SAAA,CAAU,SAAA,EAAW,UAAA,EAAY,SAAA,EAAW,UAAA,GAChD,KAAA,cACI,IAAA,EAAM,SAAA,KAAc,OAAA,CAAQ,SAAA,EAAW,UAAA,EAAY,SAAA,EAAW,UAAA;ADrBpC;;;;AAAA,KC2BpB,SAAA,wCACV,GAAA,EAAK,UAAA,CAAW,SAAA,EAAW,UAAA,GAC3B,IAAA,EAAM,IAAA,KACH,gBAAA,CAAiB,KAAA,EAAO,SAAA;AAAA,KACxB,gBAAA,OAAuB,GAAA,EAAK,CAAC;;;;;KAMtB,UAAA;EACV,KAAA;EACA,IAAA,EAAM,IAAA;EACN,IAAA,GAAO,GAAA,EAAK,EAAA;EACZ,QAAA,EAAU,gBAAA,CAAiB,EAAA;AAAA;AAAA,KAGxB,QAAA;;;;;iBAOW,KAAA,gCAGI,OAAA,GAAU,OAAA,qBACT,OAAA,GAAU,WAAA,EAE7B,EAAA,EAAI,SAAA,CAAU,IAAA,EAAM,KAAA,EAAO,SAAA,EAAW,UAAA,GACtC,KAAA,UACA,QAAA,GAAW,gBAAA,CAAiB,UAAA,KAEpB,IAAA,EAAM,IAAA,KAAO,OAAA,CAAQ,IAAA,EAAM,KAAA,EAAO,SAAA,EAAW,UAAA;;;;;ADpDoF;cCqErI,OAAA,gCAGc,OAAA,qBACC,OAAA;EAEnB,UAAA,EAAY,SAAA,CAAU,IAAA,EAAM,KAAA,EAAO,SAAA,EAAW,UAAA;EAC9C,KAAA;EACA,QAAA,EAAU,gBAAA,CAAiB,UAAA;EAC3B,EAAA;EACA,KAAA,EAAO,KAAA;EAAA,QAEC,OAAA;EAAA,QACA,MAAA;EAAA,QACA,QAAA;EAAA,QACA,QAAA;EAAA,QACA,WAAA;EAAA,QACA,UAAA;EAAA,QACA,SAAA;cAGN,EAAA,EAAI,SAAA,CAAU,IAAA,EAAM,KAAA,EAAO,SAAA,EAAW,UAAA,GACtC,KAAA,UACA,QAAA,EAAU,gBAAA,CAAiB,UAAA;EA7FV;EA+GnB,KAAA,CAAM,IAAA,EAAM,IAAA;EA9GZ;;EA8HA,IAAA,wCAA4C,OAAA,kBAAyB,OAAA,EACnE,EAAA,EAAI,SAAA,CAAU,SAAA,EAAW,UAAA,EAAY,OAAA,EAAS,OAAA,GAC9C,KAAA,YAGE,IAAA,EAAM,SAAA,KACL,OAAA,CAAQ,SAAA,EAAW,UAAA,EAAY,OAAA,EAAS,OAAA;EAiB7C,KAAA;EAgBA,UAAA,CAAW,GAAA,EAAK,cAAA,CAAe,KAAA;EAtKX;EA6KpB,aAAA,CAAc,GAAA,EAAK,OAAA;EA7K4B;;EAmL/C,IAAA,CAAK,GAAA,EAAK,SAAA;EAhLD;EAsLT,IAAA;EAKA,aAAA;EAYA,MAAA;EArMO;EAAA,IA0MH,YAAA;EAtMe;EA2MnB,SAAA,CAAU,CAAA,EAAG,QAAA;EAzMY;;EAsNzB,KAAA;EAtNI;EA6NJ,MAAA;EA3NgC;EAiOhC,IAAA,IAAI,OAAA;EAjOmD;;EAuOvD,SAAA,CAAU,GAAA,EAAK,SAAA;AAAA"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { i as ProcessFn, n as Process, o as ExitMessage, r as ProcessCtx } from "./process-BlUe6Luh.js";
|
|
2
|
+
|
|
3
|
+
//#region src/supervisor.d.ts
|
|
4
|
+
type SupMsg = ExitMessage | RunMsg | StopMsg | ErrorMsg | OkMsg;
|
|
5
|
+
interface RunMsg {
|
|
6
|
+
type: "RUN";
|
|
7
|
+
fn: ProcessFn<any, any, any, any>;
|
|
8
|
+
args: any[];
|
|
9
|
+
pname: string;
|
|
10
|
+
}
|
|
11
|
+
interface StopMsg {
|
|
12
|
+
type: "STOP";
|
|
13
|
+
}
|
|
14
|
+
interface ErrorMsg {
|
|
15
|
+
type: "ERROR";
|
|
16
|
+
}
|
|
17
|
+
interface OkMsg {
|
|
18
|
+
type: "OK";
|
|
19
|
+
[key: string]: unknown;
|
|
20
|
+
}
|
|
21
|
+
interface SupervisorState {
|
|
22
|
+
processes: Process<any, any, any, any>[];
|
|
23
|
+
phase: "wait" | "running" | "stopping";
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* A supervisor process. Call `attach` to send it `RUN` messages —
|
|
27
|
+
* it will fork the given function as a child. The supervisor exits
|
|
28
|
+
* when it enters `'stopping'` phase (triggered by `ERROR` or `STOP`).
|
|
29
|
+
*
|
|
30
|
+
* @param wrap optional transformation applied to the state before
|
|
31
|
+
* each yield (e.g. `reactive` from Vue)
|
|
32
|
+
*/
|
|
33
|
+
declare function supervise({
|
|
34
|
+
pname,
|
|
35
|
+
toParent,
|
|
36
|
+
fork
|
|
37
|
+
}: ProcessCtx<SupMsg, SupMsg>, wrap?: (s: SupervisorState) => SupervisorState, debugLevel?: boolean): Generator<SupervisorState | null, void, SupMsg>;
|
|
38
|
+
/**
|
|
39
|
+
* Bind a process function to a supervisor. The returned function,
|
|
40
|
+
* when called, sends a `RUN` message so the supervisor forks the child.
|
|
41
|
+
*/
|
|
42
|
+
declare function attach<Args extends any[]>(supervisor: Process<any, any, SupMsg, SupMsg>, fn: ProcessFn<Args, any, any, any>, pname: string): (...args: Args) => void;
|
|
43
|
+
//#endregion
|
|
44
|
+
export { SupervisorState, attach, supervise };
|
|
45
|
+
//# sourceMappingURL=supervisor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"supervisor.d.ts","names":[],"sources":["../src/supervisor.ts"],"mappings":";;;KAMK,MAAA,GAAS,WAAA,GAAc,MAAA,GAAS,OAAA,GAAU,QAAA,GAAW,KAAA;AAAA,UAEhD,MAAA;EACR,IAAA;EACA,EAAA,EAAI,SAAS;EACb,IAAA;EACA,KAAA;AAAA;AAAA,UAGQ,OAAA;EACR,IAAI;AAAA;AAAA,UAGI,QAAA;EACR,IAAI;AAAA;AAAA,UAGI,KAAA;EACR,IAAA;EAAA,CACC,GAAW;AAAA;AAAA,UAKG,eAAA;EACf,SAAA,EAAW,OAAO;EAClB,KAAA;AAAA;;;;;;;;AApBK;iBAiCG,SAAA;EACN,KAAA;EAAO,QAAA;EAAU;AAAA,GAAQ,UAAA,CAAW,MAAA,EAAQ,MAAA,GAC9C,IAAA,IAAO,CAAA,EAAG,eAAA,KAAoB,eAAA,EAC9B,UAAA,aACC,SAAA,CAAU,eAAA,eAA8B,MAAA;;;;AAjCrC;iBAoEG,MAAA,qBACP,UAAA,EAAY,OAAA,WAAkB,MAAA,EAAQ,MAAA,GACtC,EAAA,EAAI,SAAA,CAAU,IAAA,kBACd,KAAA,eACK,IAAA,EAAM,IAAA"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { r as runDispatch } from "./util-5PSJsovA.js";
|
|
2
|
+
//#region src/supervisor.ts
|
|
3
|
+
/**
|
|
4
|
+
* A supervisor process. Call `attach` to send it `RUN` messages —
|
|
5
|
+
* it will fork the given function as a child. The supervisor exits
|
|
6
|
+
* when it enters `'stopping'` phase (triggered by `ERROR` or `STOP`).
|
|
7
|
+
*
|
|
8
|
+
* @param wrap optional transformation applied to the state before
|
|
9
|
+
* each yield (e.g. `reactive` from Vue)
|
|
10
|
+
*/
|
|
11
|
+
function* supervise({ pname, toParent, fork }, wrap = (a) => a, debugLevel = false) {
|
|
12
|
+
const state = wrap({
|
|
13
|
+
processes: [],
|
|
14
|
+
phase: "wait"
|
|
15
|
+
});
|
|
16
|
+
yield state;
|
|
17
|
+
yield* runDispatch(pname, (msg) => {
|
|
18
|
+
switch (msg.type) {
|
|
19
|
+
case "RUN":
|
|
20
|
+
fork(msg.fn, msg.pname)(...msg.args);
|
|
21
|
+
state.phase = "running";
|
|
22
|
+
break;
|
|
23
|
+
case "ERROR":
|
|
24
|
+
case "STOP":
|
|
25
|
+
state.phase = "stopping";
|
|
26
|
+
break;
|
|
27
|
+
case "EXIT":
|
|
28
|
+
state.processes = state.processes.filter((p) => p.id !== msg.pid);
|
|
29
|
+
break;
|
|
30
|
+
case "OK":
|
|
31
|
+
toParent(msg);
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
}, () => state.phase === "stopping", debugLevel);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Bind a process function to a supervisor. The returned function,
|
|
38
|
+
* when called, sends a `RUN` message so the supervisor forks the child.
|
|
39
|
+
*/
|
|
40
|
+
function attach(supervisor, fn, pname) {
|
|
41
|
+
return (...args) => {
|
|
42
|
+
supervisor.send({
|
|
43
|
+
type: "RUN",
|
|
44
|
+
fn,
|
|
45
|
+
args,
|
|
46
|
+
pname
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
//#endregion
|
|
51
|
+
export { attach, supervise };
|
|
52
|
+
|
|
53
|
+
//# sourceMappingURL=supervisor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"supervisor.js","names":[],"sources":["../src/supervisor.ts"],"sourcesContent":["import { runDispatch } from \"./util.js\";\nimport type { Process, ProcessFn, ProcessCtx } from \"./process.js\";\nimport type { ExitMessage } from \"./util.js\";\n\n// ---- messages ---------------------------------------------------------------\n\ntype SupMsg = ExitMessage | RunMsg | StopMsg | ErrorMsg | OkMsg;\n\ninterface RunMsg {\n type: \"RUN\";\n fn: ProcessFn<any, any, any, any>;\n args: any[];\n pname: string;\n}\n\ninterface StopMsg {\n type: \"STOP\";\n}\n\ninterface ErrorMsg {\n type: \"ERROR\";\n}\n\ninterface OkMsg {\n type: \"OK\";\n [key: string]: unknown;\n}\n\n// ---- state ------------------------------------------------------------------\n\nexport interface SupervisorState {\n processes: Process<any, any, any, any>[];\n phase: \"wait\" | \"running\" | \"stopping\";\n}\n\n// ---- supervise --------------------------------------------------------------\n\n/**\n * A supervisor process. Call `attach` to send it `RUN` messages —\n * it will fork the given function as a child. The supervisor exits\n * when it enters `'stopping'` phase (triggered by `ERROR` or `STOP`).\n *\n * @param wrap optional transformation applied to the state before\n * each yield (e.g. `reactive` from Vue)\n */\nfunction* supervise(\n { pname, toParent, fork }: ProcessCtx<SupMsg, SupMsg>,\n wrap: (s: SupervisorState) => SupervisorState = (a) => a,\n debugLevel = false,\n): Generator<SupervisorState | null, void, SupMsg> {\n const state = wrap({ processes: [], phase: \"wait\" });\n yield state;\n\n yield* runDispatch<SupMsg>(\n pname,\n (msg) => {\n switch (msg.type) {\n case \"RUN\":\n (fork as (...a: any[]) => any)(msg.fn, msg.pname)(...msg.args);\n state.phase = \"running\";\n break;\n case \"ERROR\":\n case \"STOP\":\n state.phase = \"stopping\";\n break;\n case \"EXIT\":\n state.processes = state.processes.filter((p) => p.id !== msg.pid);\n break;\n case \"OK\":\n toParent(msg);\n break;\n }\n },\n () => state.phase === \"stopping\",\n debugLevel,\n );\n}\n\n// ---- attach -----------------------------------------------------------------\n\n/**\n * Bind a process function to a supervisor. The returned function,\n * when called, sends a `RUN` message so the supervisor forks the child.\n */\nfunction attach<Args extends any[]>(\n supervisor: Process<any, any, SupMsg, SupMsg>,\n fn: ProcessFn<Args, any, any, any>,\n pname: string,\n): (...args: Args) => void {\n return (...args) => {\n supervisor.send({ type: \"RUN\", fn, args, pname } as SupMsg);\n };\n}\n\nexport { attach, supervise };\n"],"mappings":";;;;;;;;;;AA6CA,UAAU,UACR,EAAE,OAAO,UAAU,QACnB,QAAiD,MAAM,GACvD,aAAa,OACoC;CACjD,MAAM,QAAQ,KAAK;EAAE,WAAW,CAAC;EAAG,OAAO;CAAO,CAAC;CACnD,MAAM;CAEN,OAAO,YACL,QACC,QAAQ;EACP,QAAQ,IAAI,MAAZ;GACE,KAAK;IACH,KAA+B,IAAI,IAAI,IAAI,KAAK,EAAE,GAAG,IAAI,IAAI;IAC7D,MAAM,QAAQ;IACd;GACF,KAAK;GACL,KAAK;IACH,MAAM,QAAQ;IACd;GACF,KAAK;IACH,MAAM,YAAY,MAAM,UAAU,QAAQ,MAAM,EAAE,OAAO,IAAI,GAAG;IAChE;GACF,KAAK;IACH,SAAS,GAAG;IACZ;EACJ;CACF,SACM,MAAM,UAAU,YACtB,UACF;AACF;;;;;AAQA,SAAS,OACP,YACA,IACA,OACyB;CACzB,QAAQ,GAAG,SAAS;EAClB,WAAW,KAAK;GAAE,MAAM;GAAO;GAAI;GAAM;EAAM,CAAW;CAC5D;AACF"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
//#region src/util.ts
|
|
2
|
+
function debugLog(level, ...args) {
|
|
3
|
+
if (level) console.log(...args);
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Generator helper that loops, yielding `null` and feeding incoming
|
|
7
|
+
* messages to `fn` until `readyFn()` returns true. Used inside
|
|
8
|
+
* process generators to build the main message loop.
|
|
9
|
+
*/
|
|
10
|
+
function* runDispatch(name, fn, readyFn = () => false, debugLevel = false) {
|
|
11
|
+
let msg;
|
|
12
|
+
while (!readyFn()) {
|
|
13
|
+
msg = yield null;
|
|
14
|
+
debugLog(debugLevel, "msg", name, " <- ", msg);
|
|
15
|
+
fn(msg);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/** Wrap a process generator so that on completion it sends `STOP`
|
|
19
|
+
* to all children and `EXIT` to the parent. */
|
|
20
|
+
function watchExit(process) {
|
|
21
|
+
return function* (ctx, arg0) {
|
|
22
|
+
yield* process.pgenerator(ctx, arg0);
|
|
23
|
+
process.toAllChildren({ type: "STOP" });
|
|
24
|
+
process.toParent({
|
|
25
|
+
type: "EXIT",
|
|
26
|
+
pid: process.id
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/** Schedule a function to run on the next microtask. Returns an
|
|
31
|
+
* object with `cancel()` and `flush()` methods. */
|
|
32
|
+
function defer(fn) {
|
|
33
|
+
const g = globalThis;
|
|
34
|
+
function schedule(deferFn, cancelFn) {
|
|
35
|
+
const taskId = deferFn(fn);
|
|
36
|
+
return () => cancelFn(taskId);
|
|
37
|
+
}
|
|
38
|
+
let cancel;
|
|
39
|
+
if (g.setImmediate && g.clearImmediate) cancel = schedule(g.setImmediate, g.clearImmediate);
|
|
40
|
+
else if (g.requestIdleCallback && g.cancelIdleCallback) cancel = schedule(g.requestIdleCallback, g.cancelIdleCallback);
|
|
41
|
+
else cancel = schedule((f) => setTimeout(f, 0), clearTimeout);
|
|
42
|
+
function flush() {
|
|
43
|
+
cancel();
|
|
44
|
+
fn();
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
flush,
|
|
48
|
+
cancel
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/** Create a new {@link Waiter}. */
|
|
52
|
+
function makeWaiter() {
|
|
53
|
+
let resolve;
|
|
54
|
+
return {
|
|
55
|
+
promise: new Promise((_resolve) => {
|
|
56
|
+
resolve = _resolve;
|
|
57
|
+
}),
|
|
58
|
+
resolve
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
//#endregion
|
|
62
|
+
export { watchExit as i, makeWaiter as n, runDispatch as r, defer as t };
|
|
63
|
+
|
|
64
|
+
//# sourceMappingURL=util-5PSJsovA.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"util-5PSJsovA.js","names":[],"sources":["../src/util.ts"],"sourcesContent":["import type { Process } from './process.js';\nimport type { ProcessCtx, Message } from './process.js';\n\nfunction debugLog(level: boolean, ...args: Array<unknown>) {\n if (level) {\n console.log(...args);\n }\n}\n\ntype ReducerClosure<M> = (msg: M) => void;\ntype ReadyFn = () => boolean;\ntype NotifyFn = () => void;\n\n/**\n * Generator helper that loops, yielding `null` and feeding incoming\n * messages to `fn` until `readyFn()` returns true. Used inside\n * process generators to build the main message loop.\n */\nfunction* runDispatch<M>(name: string, fn : ReducerClosure<M>, readyFn: ReadyFn = ()=> false, debugLevel = false) : Generator<null, void, M> {\n let msg: M;\n while(!readyFn()) {\n msg = yield null;\n debugLog(debugLevel, 'msg', name, ' <- ', msg);\n fn(msg);\n }\n}\n\n/** Message emitted by a process to its parent when it terminates. */\nexport type ExitMessage = {\n type: 'EXIT';\n pid: symbol;\n};\n\n/** Wrap a process generator so that on completion it sends `STOP`\n * to all children and `EXIT` to the parent. */\nfunction watchExit<Args, State, InMessage extends Message, OutMessage extends (Message | ExitMessage)>(process: Process<Args, State, InMessage, OutMessage>) {\n return function* (ctx: ProcessCtx<InMessage, OutMessage>, arg0: Args) {\n yield* process.pgenerator(ctx, arg0);\n process.toAllChildren({ type: 'STOP' });\n process.toParent({ type: 'EXIT', pid: process.id} as OutMessage);\n }\n}\n\ntype DeferCb = () => void;\ntype Defer = (fn: DeferCb) => unknown;\ntype Cancel = (taskId: any)=> void;\ntype WindowGlobal = {\n setImmediate?: Defer,\n clearImmediate?: Cancel,\n requestIdleCallback?: Defer,\n cancelIdleCallback?: Cancel,\n setTimeout: Defer,\n clearTimeout: Cancel,\n};\n\n/** Handle to a scheduled (but not yet executed) callback. */\nexport type DeferredCall = {\n cancel: ()=> void,\n flush: () => void,\n};\n/** Schedule a function to run on the next microtask. Returns an\n * object with `cancel()` and `flush()` methods. */\nfunction defer(fn: DeferCb) : DeferredCall {\n const g = globalThis as WindowGlobal;\n function schedule(deferFn: Defer, cancelFn: Cancel) {\n const taskId = deferFn(fn);\n return ()=> cancelFn(taskId);\n }\n let cancel : ()=> void;\n if (g.setImmediate && g.clearImmediate) {\n cancel = schedule(g.setImmediate, g.clearImmediate);\n } else if (g.requestIdleCallback && g.cancelIdleCallback) {\n cancel = schedule(g.requestIdleCallback, g.cancelIdleCallback);\n } else {\n cancel = schedule((f) => setTimeout(f, 0), clearTimeout);\n }\n\n function flush() {\n cancel();\n fn();\n }\n\n return { flush, cancel };\n}\n/** A promise paired with its resolve function — used to signal\n * process completion. */\nexport type Waiter = {\n promise: Promise<void>;\n resolve: NotifyFn;\n};\n/** Create a new {@link Waiter}. */\nfunction makeWaiter() : Waiter {\n let resolve: unknown;\n let promise = new Promise<void>(_resolve => {\n resolve = _resolve;\n });\n return {promise, resolve: resolve as NotifyFn};\n}\n\nexport { runDispatch, watchExit, defer, makeWaiter };\n"],"mappings":";AAGA,SAAS,SAAS,OAAgB,GAAG,MAAsB;CACzD,IAAI,OACF,QAAQ,IAAI,GAAG,IAAI;AAEvB;;;;;;AAWA,UAAU,YAAe,MAAc,IAAwB,gBAAwB,OAAO,aAAa,OAAkC;CAC3I,IAAI;CACJ,OAAM,CAAC,QAAQ,GAAG;EAChB,MAAM,MAAM;EACZ,SAAS,YAAY,OAAO,MAAM,QAAQ,GAAG;EAC7C,GAAG,GAAG;CACR;AACF;;;AAUA,SAAS,UAA8F,SAAsD;CAC3J,OAAO,WAAW,KAAwC,MAAY;EACpE,OAAO,QAAQ,WAAW,KAAK,IAAI;EACnC,QAAQ,cAAc,EAAE,MAAM,OAAO,CAAC;EACtC,QAAQ,SAAS;GAAE,MAAM;GAAQ,KAAK,QAAQ;EAAE,CAAe;CACjE;AACF;;;AAqBA,SAAS,MAAM,IAA4B;CACzC,MAAM,IAAI;CACV,SAAS,SAAS,SAAgB,UAAkB;EAClD,MAAM,SAAS,QAAQ,EAAE;EACzB,aAAY,SAAS,MAAM;CAC7B;CACA,IAAI;CACJ,IAAI,EAAE,gBAAgB,EAAE,gBACtB,SAAS,SAAS,EAAE,cAAc,EAAE,cAAc;MAC7C,IAAI,EAAE,uBAAuB,EAAE,oBACpC,SAAS,SAAS,EAAE,qBAAqB,EAAE,kBAAkB;MAE7D,SAAS,UAAU,MAAM,WAAW,GAAG,CAAC,GAAG,YAAY;CAGzD,SAAS,QAAQ;EACf,OAAO;EACP,GAAG;CACL;CAEA,OAAO;EAAE;EAAO;CAAO;AACzB;;AAQA,SAAS,aAAsB;CAC7B,IAAI;CAIJ,OAAO;EAAC,SAAA,IAHU,SAAc,aAAY;GAC1C,UAAU;EACZ,CACc;EAAY;CAAmB;AAC/C"}
|
package/package.json
CHANGED
|
@@ -1,25 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "posipaki",
|
|
4
|
-
"version": "0.6.
|
|
4
|
+
"version": "0.6.4",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
7
|
-
"scripts": {
|
|
8
|
-
"build": "tsc",
|
|
9
|
-
"test": "jest"
|
|
10
|
-
},
|
|
11
7
|
"files": [
|
|
12
|
-
"dist
|
|
13
|
-
"dist/*map",
|
|
14
|
-
"dist/*d.ts",
|
|
15
|
-
"src/*ts"
|
|
8
|
+
"dist"
|
|
16
9
|
],
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsdown",
|
|
12
|
+
"test": "vitest run",
|
|
13
|
+
"test:watch": "vitest",
|
|
14
|
+
"lint": "oxlint"
|
|
15
|
+
},
|
|
17
16
|
"devDependencies": {
|
|
18
|
-
"@
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"typescript": "^5.1.3"
|
|
17
|
+
"@types/node": "^25.9.1",
|
|
18
|
+
"oxlint": "^1.67.0",
|
|
19
|
+
"tsdown": "^0.22.1",
|
|
20
|
+
"typescript": "^6.0.3",
|
|
21
|
+
"vitest": "^4.1.7"
|
|
24
22
|
}
|
|
25
23
|
}
|