posipaki 0.37.0 → 0.38.0
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/define-actor-CCJy7LDp.js +189 -0
- package/dist/define-actor-CCJy7LDp.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/{json1-CWWXL69y.js → json1-DZ8N0TuZ.js} +213 -19
- package/dist/json1-DZ8N0TuZ.js.map +1 -0
- package/dist/plugins/tree-introspection.d.ts +22 -2
- package/dist/plugins/tree-introspection.d.ts.map +1 -1
- package/dist/plugins/tree-introspection.js +47 -4
- package/dist/plugins/tree-introspection.js.map +1 -1
- package/dist/{define-actor-CZysIKH4.js → process.async-CdQ_zpKp.js} +2 -186
- package/dist/process.async-CdQ_zpKp.js.map +1 -0
- package/dist/remote/index.d.ts +67 -1
- package/dist/remote/index.d.ts.map +1 -1
- package/dist/remote/index.js +1 -1
- package/dist/remote/node.js +2 -2
- package/dist/{version-DTvG9uAx.js → version-BnBkASor.js} +2 -2
- package/dist/{version-DTvG9uAx.js.map → version-BnBkASor.js.map} +1 -1
- package/package.json +1 -1
- package/dist/define-actor-CZysIKH4.js.map +0 -1
- package/dist/json1-CWWXL69y.js.map +0 -1
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { a as spawnAsync, i as runDispatchAsync } from "./process.async-CdQ_zpKp.js";
|
|
2
|
+
import { a as STOP_SENTINEL, i as PROPAGATE_SENTINEL, t as callHook } from "./hooks-CgXhmY4r.js";
|
|
3
|
+
//#region src/define-actor.ts
|
|
4
|
+
function defineMessages() {}
|
|
5
|
+
function hidePrivate(value) {
|
|
6
|
+
if (value && typeof value === "object" && "private" in value && "public" in value) return value.public;
|
|
7
|
+
return value;
|
|
8
|
+
}
|
|
9
|
+
function resolvePlugins(raw, parentPlugins, addPlugins) {
|
|
10
|
+
const resolved = !raw ? parentPlugins ? [...parentPlugins] : [] : Array.isArray(raw) ? [...raw] : raw(parentPlugins ?? []);
|
|
11
|
+
const combined = addPlugins ? [...resolved, ...addPlugins] : resolved;
|
|
12
|
+
const seen = /* @__PURE__ */ new Set();
|
|
13
|
+
return combined.filter((p) => {
|
|
14
|
+
const name = p.name;
|
|
15
|
+
if (!name) {
|
|
16
|
+
console.warn("posipaki: plugin has no name, dedup won't work");
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
if (seen.has(name)) return false;
|
|
20
|
+
seen.add(name);
|
|
21
|
+
return true;
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
async function assembleActor(config, plugins, addPlugins) {
|
|
25
|
+
let cur = config;
|
|
26
|
+
for (const p of plugins) try {
|
|
27
|
+
cur = await p(cur);
|
|
28
|
+
} catch (e) {
|
|
29
|
+
console.error(`[assembleActor] plugin "${p.name || "?"}" failed:`, e);
|
|
30
|
+
}
|
|
31
|
+
cur.resolvedPlugins = plugins;
|
|
32
|
+
if (addPlugins) cur.addPlugins = addPlugins;
|
|
33
|
+
return cur;
|
|
34
|
+
}
|
|
35
|
+
function defineActor(config) {
|
|
36
|
+
const actorCtxMap = /* @__PURE__ */ new Map();
|
|
37
|
+
function makeRuntime(assembly) {
|
|
38
|
+
return async function* (ctx, args) {
|
|
39
|
+
let done = false;
|
|
40
|
+
let exitReason;
|
|
41
|
+
let rawState = void 0;
|
|
42
|
+
const decorated = /* @__PURE__ */ new Map();
|
|
43
|
+
const self = {
|
|
44
|
+
ctx,
|
|
45
|
+
...assembly.methods || {},
|
|
46
|
+
...assembly.$decorate || {},
|
|
47
|
+
reflection: {},
|
|
48
|
+
state: rawState,
|
|
49
|
+
name: ctx.pname,
|
|
50
|
+
id: ctx.id,
|
|
51
|
+
async emit(msg) {
|
|
52
|
+
await callHook(assembly.onEmit, void 0, self, msg, {
|
|
53
|
+
fromName: ctx.pname,
|
|
54
|
+
fromId: ctx.id
|
|
55
|
+
});
|
|
56
|
+
ctx.toParent(msg);
|
|
57
|
+
},
|
|
58
|
+
agreeToStop() {
|
|
59
|
+
exitReason = "stopped";
|
|
60
|
+
done = true;
|
|
61
|
+
},
|
|
62
|
+
exit(reason) {
|
|
63
|
+
exitReason = reason;
|
|
64
|
+
done = true;
|
|
65
|
+
ctx.wake();
|
|
66
|
+
},
|
|
67
|
+
$child: {},
|
|
68
|
+
decorators: {},
|
|
69
|
+
fork: async (childActor, childArgs, forkOpts) => {
|
|
70
|
+
let child;
|
|
71
|
+
const childName = forkOpts?.name ?? childActor?.name ?? `child-${Object.keys(self.$child).length}`;
|
|
72
|
+
const treeName = `${ctx.pname}:${childName}`;
|
|
73
|
+
const childAddPlugins = [...assembly.addPlugins || [], ...forkOpts?.addPlugins || []];
|
|
74
|
+
child = await childActor.spawnAsChild(ctx, childArgs, {
|
|
75
|
+
name: treeName,
|
|
76
|
+
parentPlugins: assembly.resolvedPlugins || [],
|
|
77
|
+
...childAddPlugins.length ? { addPlugins: childAddPlugins } : {}
|
|
78
|
+
});
|
|
79
|
+
self.$child[child.pname] = child;
|
|
80
|
+
return child;
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
actorCtxMap.set(ctx.id, self);
|
|
84
|
+
/**
|
|
85
|
+
* Lifecycle hooks are the actor's own control flow: if one throws, the
|
|
86
|
+
* state machine is half-applied, so the error is fatal — `onError` still
|
|
87
|
+
* observes it (a logger logs it), but it cannot absorb it. The actor goes
|
|
88
|
+
* down and its parent still gets the EXIT, instead of staying up with
|
|
89
|
+
* nothing left to do.
|
|
90
|
+
*
|
|
91
|
+
* Message hooks and handlers are not forced either way: they are called
|
|
92
|
+
* with `onError` itself, and its *return value* decides — nothing means
|
|
93
|
+
* "handled, carry on" (the default, and what an observing logger
|
|
94
|
+
* returns), `propagateError()` means "not handled", and the actor goes
|
|
95
|
+
* down with the same EXIT.
|
|
96
|
+
*/
|
|
97
|
+
const hookErrorHandler = async (e) => {
|
|
98
|
+
try {
|
|
99
|
+
await assembly.onError?.call(self, e);
|
|
100
|
+
} catch {}
|
|
101
|
+
return PROPAGATE_SENTINEL;
|
|
102
|
+
};
|
|
103
|
+
ctx.afterExit = async () => {
|
|
104
|
+
await callHook(assembly.afterEnd, hookErrorHandler, self, exitReason);
|
|
105
|
+
};
|
|
106
|
+
for (const [k, v] of decorated) self[k] = v;
|
|
107
|
+
await callHook(assembly.beforeStart, hookErrorHandler, self);
|
|
108
|
+
if (assembly.setup) rawState = await assembly.setup.call(self, args);
|
|
109
|
+
else rawState = null;
|
|
110
|
+
self.state = rawState;
|
|
111
|
+
yield hidePrivate(rawState);
|
|
112
|
+
await callHook(assembly.afterStart, hookErrorHandler, self);
|
|
113
|
+
yield* runDispatchAsync(ctx.pname, async (stamped) => {
|
|
114
|
+
const [msg, sender] = stamped;
|
|
115
|
+
if (msg.type === "__ADVANCE__") return;
|
|
116
|
+
if (msg.type === "STOP") {
|
|
117
|
+
if (assembly.onStopRequested) await callHook(assembly.onStopRequested, hookErrorHandler, self);
|
|
118
|
+
else {
|
|
119
|
+
exitReason = "stopped";
|
|
120
|
+
done = true;
|
|
121
|
+
}
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (msg.type === "EXIT") {
|
|
125
|
+
const childName = sender.fromName;
|
|
126
|
+
if (childName && self.$child[childName]) delete self.$child[childName];
|
|
127
|
+
await callHook(assembly.onChildExit, hookErrorHandler, self, childName, msg);
|
|
128
|
+
const orphans = msg.orphans;
|
|
129
|
+
if (orphans && orphans.length > 0) for (const orphan of orphans) {
|
|
130
|
+
const decision = assembly.onOrphan ? await callHook(assembly.onOrphan, hookErrorHandler, self, orphan) : "force-stop";
|
|
131
|
+
if (decision === "adopt") {
|
|
132
|
+
ctx.adopt(orphan);
|
|
133
|
+
const idx = ctx.orphans.indexOf(orphan);
|
|
134
|
+
if (idx >= 0) ctx.orphans.splice(idx, 1);
|
|
135
|
+
self.$child[orphan.pname] = orphan;
|
|
136
|
+
} else if (decision === "force-stop") {
|
|
137
|
+
orphan.forceStop({ cascade: true });
|
|
138
|
+
const idx = ctx.orphans.indexOf(orphan);
|
|
139
|
+
if (idx >= 0) ctx.orphans.splice(idx, 1);
|
|
140
|
+
} else if (decision === "unparent") orphan.unparent();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
let hookStopped = false;
|
|
144
|
+
if (msg.type !== "STOP" && msg.type !== "EXIT" && assembly.onMessage) {
|
|
145
|
+
if (await callHook(assembly.onMessage, assembly.onError, self, msg, sender) === STOP_SENTINEL) hookStopped = true;
|
|
146
|
+
}
|
|
147
|
+
if (msg.type !== "STOP" && !hookStopped) {
|
|
148
|
+
const handler = (assembly.handlers || {})[msg.type] || assembly.onUnhandled;
|
|
149
|
+
await callHook(handler, assembly.onError, self, msg, sender);
|
|
150
|
+
}
|
|
151
|
+
}, () => done);
|
|
152
|
+
await callHook(assembly.beforeEnd, hookErrorHandler, self, exitReason);
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
function attachReflection(proc, reflectionMethods) {
|
|
156
|
+
const self = actorCtxMap.get(proc.id);
|
|
157
|
+
if (self) proc.$reflection = self.reflection;
|
|
158
|
+
if (!reflectionMethods) return;
|
|
159
|
+
const refl = proc.$reflection;
|
|
160
|
+
const written = Object.entries(reflectionMethods);
|
|
161
|
+
for (const [k, m] of written) refl[k] = async (...args) => await m.apply(self, args);
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
fn: makeRuntime(config),
|
|
165
|
+
name: config.name,
|
|
166
|
+
pvtPluginsRaw: config.plugins,
|
|
167
|
+
inMessages: config.inMessages,
|
|
168
|
+
outMessages: config.outMessages,
|
|
169
|
+
async spawn(args, opts) {
|
|
170
|
+
const assembly = await assembleActor(config, resolvePlugins(config.plugins, void 0, opts?.addPlugins), opts?.addPlugins);
|
|
171
|
+
const runtime = makeRuntime(assembly);
|
|
172
|
+
const proc = spawnAsync(runtime, opts?.name ?? assembly.name ?? "actor", opts?.toParent, opts?.parentName, opts?.parentId)(args);
|
|
173
|
+
attachReflection(proc, assembly.$reflectionMethods);
|
|
174
|
+
if (opts?.awaitReady !== false) await proc.ready();
|
|
175
|
+
return proc;
|
|
176
|
+
},
|
|
177
|
+
async spawnAsChild(ctx, args, opts) {
|
|
178
|
+
const assembly = await assembleActor(config, resolvePlugins(config.plugins, opts?.parentPlugins, opts?.addPlugins), opts?.addPlugins);
|
|
179
|
+
const runtime = makeRuntime(assembly);
|
|
180
|
+
const proc = ctx.fork(runtime, opts?.name ?? assembly.name ?? "child")(args);
|
|
181
|
+
attachReflection(proc, assembly.$reflectionMethods);
|
|
182
|
+
return proc;
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
//#endregion
|
|
187
|
+
export { defineMessages as n, defineActor as t };
|
|
188
|
+
|
|
189
|
+
//# sourceMappingURL=define-actor-CCJy7LDp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"define-actor-CCJy7LDp.js","names":[],"sources":["../src/define-actor.ts"],"sourcesContent":["// ── defineActor — high-level actor wrapper ───────────────────────────────────\n//\n// Compiles a declarative config into an AsyncProcessFn. Built on top of\n// the existing runDispatchAsync / spawnAsync primitives.\n//\nimport {\n type AsyncProcess,\n runDispatchAsync,\n spawnAsync,\n AnyProcess,\n INTERNAL_ADVANCE,\n} from \"./process.async.js\";\nimport type {\n WithSender,\n SenderInfo,\n AsyncProcessFn,\n Message,\n ExitMessage,\n ProcessCtx,\n} from \"./types.js\";\nimport type {\n ActorDefinition,\n ActorConfig,\n ActorContext,\n MethodOptions,\n ActorMessages,\n HandlerOptions,\n HandlerFn,\n ReflectionOptions,\n AnyConfig,\n HidePrivate,\n} from \"./actor-types.js\";\nimport { STOP_SENTINEL, PROPAGATE_SENTINEL } from \"./actor-types.js\";\nimport { ActorDecorated, type ActorPlugin, ActorReflection, callHook } from \"./hooks.js\";\n\nexport function defineMessages<OutMsg extends Message = Message>(): ActorMessages<OutMsg> {\n return undefined as unknown as ActorMessages<OutMsg>;\n}\n\nfunction hidePrivate<T>(value: T): HidePrivate<T> {\n if (value && typeof value === \"object\" && \"private\" in value && \"public\" in value) {\n return value.public as HidePrivate<T>;\n }\n return value as HidePrivate<T>;\n}\n\nfunction resolvePlugins(\n raw: ActorPlugin[] | ((parents: ActorPlugin[]) => ActorPlugin[]) | undefined,\n parentPlugins?: ActorPlugin[],\n addPlugins?: ActorPlugin[],\n): ActorPlugin[] {\n const resolved = !raw\n ? parentPlugins\n ? [...parentPlugins]\n : []\n : Array.isArray(raw)\n ? [...raw]\n : raw(parentPlugins ?? []);\n\n const combined = addPlugins ? [...resolved, ...addPlugins] : resolved;\n\n // Deduplicate by function name\n const seen = new Set<string>();\n return combined.filter((p) => {\n const name = p.name;\n if (!name) {\n console.warn(\"posipaki: plugin has no name, dedup won't work\");\n return true;\n }\n if (seen.has(name)) return false;\n seen.add(name);\n return true;\n });\n}\n\nasync function assembleActor<C>(\n config: C,\n plugins: ActorPlugin[],\n addPlugins?: ActorPlugin[],\n): Promise<C> {\n // Work with the default ActorPlugin type internally, cast back on return\n let cur = config as AnyConfig;\n for (const p of plugins) {\n try {\n cur = await p(cur);\n } catch (e: unknown) {\n console.error(`[assembleActor] plugin \"${p.name || \"?\"}\" failed:`, e);\n }\n }\n cur.resolvedPlugins = plugins;\n if (addPlugins) cur.addPlugins = addPlugins;\n return cur as C;\n}\n\nexport function defineActor<\n Args,\n InternalState,\n InMsg extends Message,\n OutMsg extends Message,\n Methods extends MethodOptions,\n Handlers extends HandlerOptions<InMsg>,\n ReflectionMethods extends ReflectionOptions,\n>(\n config: ActorConfig<Args, InternalState, InMsg, OutMsg, Methods, Handlers, ReflectionMethods>,\n): ActorDefinition<Args, InternalState, InMsg, OutMsg, ReflectionMethods> {\n const actorCtxMap = new Map<\n symbol,\n ActorContext<Args, InternalState, InMsg, OutMsg, Methods, Handlers, ReflectionMethods>\n >();\n\n function makeRuntime(\n assembly: ActorConfig<Args, InternalState, InMsg, OutMsg, Methods, Handlers, ReflectionMethods>,\n ): AsyncProcessFn<Args, HidePrivate<InternalState>, InMsg, OutMsg> {\n return async function* (\n ctx,\n args,\n ): AsyncGenerator<HidePrivate<InternalState | null>, void, WithSender<InMsg>> {\n let done = false;\n let exitReason: unknown;\n let rawState: InternalState = undefined as unknown as InternalState;\n\n const decorated = new Map();\n const self: ActorContext<\n Args,\n InternalState,\n InMsg,\n OutMsg,\n Methods,\n Handlers,\n ReflectionMethods & ActorReflection\n > = {\n ctx,\n ...((assembly.methods || {}) as Methods),\n ...((assembly.$decorate || {}) as ActorDecorated),\n reflection: {} as ReflectionMethods & ActorReflection,\n state: rawState,\n name: ctx.pname,\n id: ctx.id,\n async emit(msg: OutMsg) {\n await callHook(assembly.onEmit, undefined, self, msg, {\n fromName: ctx.pname,\n fromId: ctx.id,\n });\n ctx.toParent(msg);\n },\n agreeToStop() {\n exitReason = \"stopped\";\n done = true;\n },\n exit(reason: unknown) {\n exitReason = reason;\n done = true;\n // The loop only re-reads `done` when it is resumed: an idle actor is\n // parked at its dispatch yield and would sit there forever.\n ctx.wake();\n },\n $child: {} as Record<string, AnyProcess>,\n decorators: {},\n fork: async <A, S, IM extends Message, OM extends InMsg, R extends ReflectionOptions>(\n childActor: ActorDefinition<A, S, IM, OM, R>,\n childArgs?: A,\n forkOpts?: { name?: string; addPlugins?: ActorPlugin[] },\n ): Promise<AsyncProcess<A, HidePrivate<S>, IM, OM, R & ActorReflection>> => {\n let child: AsyncProcess<A, HidePrivate<S>, IM, OM, R & ActorReflection>;\n const childName =\n forkOpts?.name ?? childActor?.name ?? `child-${Object.keys(self.$child).length}`;\n const treeName = `${ctx.pname}:${childName}`;\n const childAddPlugins = [...(assembly.addPlugins || []), ...(forkOpts?.addPlugins || [])];\n child = await childActor.spawnAsChild(ctx, childArgs!, {\n name: treeName,\n parentPlugins: assembly.resolvedPlugins || [],\n ...(childAddPlugins.length ? { addPlugins: childAddPlugins } : {}),\n });\n self.$child[child.pname] = child as unknown as AnyProcess;\n return child;\n },\n };\n actorCtxMap.set(ctx.id, self);\n /**\n * Lifecycle hooks are the actor's own control flow: if one throws, the\n * state machine is half-applied, so the error is fatal — `onError` still\n * observes it (a logger logs it), but it cannot absorb it. The actor goes\n * down and its parent still gets the EXIT, instead of staying up with\n * nothing left to do.\n *\n * Message hooks and handlers are not forced either way: they are called\n * with `onError` itself, and its *return value* decides — nothing means\n * \"handled, carry on\" (the default, and what an observing logger\n * returns), `propagateError()` means \"not handled\", and the actor goes\n * down with the same EXIT.\n */\n const hookErrorHandler = async (e: unknown): Promise<unknown> => {\n try {\n await assembly.onError?.call(self, e);\n } catch {\n // An error in the error handler must not mask the original one.\n }\n return PROPAGATE_SENTINEL;\n };\n ctx.afterExit = async () => {\n await callHook(assembly.afterEnd, hookErrorHandler, self, exitReason);\n };\n\n for (const [k, v] of decorated) {\n (self as Record<string, unknown>)[k] = v;\n }\n\n await callHook(\n assembly.beforeStart,\n hookErrorHandler,\n self as ActorContext<\n Args,\n never, // state not set yet\n InMsg,\n OutMsg,\n Methods,\n Handlers,\n ReflectionMethods & ActorReflection\n >,\n );\n\n if (assembly.setup) {\n rawState = await assembly.setup.call(\n self as ActorContext<\n Args,\n never, // break inference cycle here\n InMsg,\n OutMsg,\n Methods,\n Handlers,\n ReflectionMethods & ActorReflection\n >,\n args,\n );\n } else {\n rawState = null as InternalState;\n }\n self.state = rawState;\n yield hidePrivate(rawState);\n await callHook(assembly.afterStart, hookErrorHandler, self);\n\n yield* runDispatchAsync<WithSender<InMsg | ExitMessage>>(\n ctx.pname,\n async (stamped) => {\n const [msg, sender] = stamped;\n if (msg.type === INTERNAL_ADVANCE) return; // framework traffic, not a message\n if (msg.type === \"STOP\") {\n if (assembly.onStopRequested) {\n await callHook(assembly.onStopRequested, hookErrorHandler, self);\n // Hook may call agreeToStop(). If not, actor keeps running.\n } else {\n exitReason = \"stopped\";\n done = true;\n }\n return;\n }\n if (msg.type === \"EXIT\") {\n // FIXME: process name are user-specified\n // and should not be used for indexing so\n // we don't get into name conflicts\n const childName = sender.fromName;\n if (childName && self.$child[childName]) {\n delete self.$child[childName];\n }\n await callHook(\n assembly.onChildExit,\n hookErrorHandler,\n self,\n childName,\n msg as ExitMessage,\n );\n // Orphan policy: a child that exits may leave still-running\n // grandchildren behind (see ctx-orphans / orphan-policy).\n const orphans = (msg as ExitMessage).orphans;\n if (orphans && orphans.length > 0) {\n for (const orphan of orphans) {\n // Default when no hook is defined: hard-kill the orphan.\n const decision = assembly.onOrphan\n ? await callHook(assembly.onOrphan, hookErrorHandler, self, orphan)\n : \"force-stop\";\n if (decision === \"adopt\") {\n ctx.adopt(orphan);\n const idx = ctx.orphans.indexOf(orphan);\n if (idx >= 0) ctx.orphans.splice(idx, 1);\n self.$child[orphan.pname] = orphan;\n } else if (decision === \"force-stop\") {\n orphan.forceStop({ cascade: true });\n const idx = ctx.orphans.indexOf(orphan);\n if (idx >= 0) ctx.orphans.splice(idx, 1);\n } else if (decision === \"unparent\") {\n orphan.unparent();\n }\n // 'leave' (or undefined) → do nothing; it propagates on exit.\n }\n }\n }\n let hookStopped = false;\n if (msg.type !== \"STOP\" && msg.type !== \"EXIT\" && assembly.onMessage) {\n const result = await callHook(\n assembly.onMessage,\n assembly.onError,\n self,\n msg as InMsg,\n sender,\n );\n if (result === STOP_SENTINEL) hookStopped = true;\n }\n if (msg.type !== \"STOP\" && !hookStopped) {\n const handler =\n ((assembly.handlers || ({} as Handlers))[\n msg.type as keyof Handlers\n ] as HandlerFn<InMsg>) || assembly.onUnhandled;\n\n await callHook(handler, assembly.onError, self, msg as InMsg, sender);\n }\n },\n () => done,\n );\n\n await callHook(assembly.beforeEnd, hookErrorHandler, self, exitReason);\n };\n }\n\n type ReflectableProcess = {\n id: symbol;\n $reflection: Record<string, Function>;\n };\n function attachReflection(\n proc: ReflectableProcess,\n reflectionMethods: ReflectionMethods | undefined,\n ): void {\n const self = actorCtxMap.get(proc.id);\n if (self) {\n // One object under two names: the surface the handle exposes as\n // `$reflection` and the one the actor's own hooks see as `reflection`.\n // Handing the same object to both is what lets a method be added after\n // assembly — the first caller that needs that is a remote peer saying\n // which methods it can answer.\n proc.$reflection = self.reflection as unknown as Record<string, Function>;\n }\n if (!reflectionMethods) return;\n const refl = proc.$reflection as Record<string, Function>;\n // The declared contract is `ReflectionMethod`, whose parameters are `never`\n // because nothing is called through it; this is the one place that calls\n // what it describes.\n const written = Object.entries(reflectionMethods) as Array<\n [string, (...args: unknown[]) => unknown]\n >;\n for (const [k, m] of written) {\n // A promise whatever the method was written as, so a caller reads the same\n // call the same way whether the actor is here or behind a wire.\n refl[k] = async (...args: unknown[]) => await m.apply(self, args);\n }\n }\n\n return {\n fn: makeRuntime(config),\n name: config.name,\n pvtPluginsRaw: config.plugins,\n inMessages: config.inMessages,\n outMessages: config.outMessages,\n async spawn(\n args: Args,\n opts?: {\n name?: string;\n toParent?: (msg: OutMsg, from: SenderInfo) => void;\n addPlugins?: ActorPlugin[];\n parentName?: string | null;\n parentId?: symbol | null;\n /**\n * Resolve only once the initial state is available. Default `true`:\n * a spawned process is safe to send to when `spawn()` returns. Pass\n * `false` for the raw handle — the caller then has to `await\n * proc.ready()` itself before sending anything it wants handled.\n */\n awaitReady?: boolean;\n },\n ): Promise<\n AsyncProcess<\n Args,\n HidePrivate<InternalState>,\n InMsg,\n OutMsg,\n ReflectionMethods & ActorReflection\n >\n > {\n const plugs = resolvePlugins(config.plugins, undefined, opts?.addPlugins);\n const assembly = await assembleActor(config, plugs, opts?.addPlugins);\n const runtime = makeRuntime(assembly);\n const proc = spawnAsync(\n runtime,\n opts?.name ?? assembly.name ?? \"actor\",\n opts?.toParent,\n opts?.parentName,\n opts?.parentId,\n )(args);\n attachReflection(proc, assembly.$reflectionMethods as ReflectionMethods);\n if (opts?.awaitReady !== false) await proc.ready();\n return proc as AsyncProcess<\n Args,\n HidePrivate<InternalState>,\n InMsg,\n OutMsg,\n ReflectionMethods & ActorReflection\n >;\n },\n async spawnAsChild(\n ctx: ProcessCtx<unknown, unknown, OutMsg, Message>,\n args: Args,\n opts?: {\n name?: string;\n parentPlugins?: ActorPlugin[];\n addPlugins?: ActorPlugin[];\n },\n ) {\n const plugs = resolvePlugins(config.plugins, opts?.parentPlugins, opts?.addPlugins);\n const assembly = await assembleActor(config, plugs, opts?.addPlugins);\n const runtime = makeRuntime(assembly);\n const proc = ctx.fork(runtime, opts?.name ?? assembly.name ?? \"child\")(args);\n attachReflection(proc, assembly.$reflectionMethods as ReflectionMethods);\n return proc as AsyncProcess<\n Args,\n HidePrivate<InternalState>,\n InMsg,\n OutMsg,\n ReflectionMethods & ActorReflection\n >;\n },\n };\n}\n"],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -20,7 +20,7 @@ declare function defineActor<Args, InternalState, InMsg extends Message, OutMsg
|
|
|
20
20
|
//#endregion
|
|
21
21
|
//#region src/version.d.ts
|
|
22
22
|
/** posipaki's release version. Must equal package.json's `version`. */
|
|
23
|
-
declare const LIB_VERSION = "0.
|
|
23
|
+
declare const LIB_VERSION = "0.38.0";
|
|
24
24
|
//#endregion
|
|
25
25
|
export { type ActorConfig, type ActorContext, type ActorDecorated, type ActorDefinition, type ActorPlugin, type ActorReflection, type AnyProcess, AsyncProcess, type AsyncProcessFn, type ExitMessage, type HandlerFn, type HandlerOptions, type HookResult, LIB_VERSION, type Message, type MethodOptions, type PluginTransform, Process, type ProcessCtx, type ProcessFn, type SenderInfo, type SenderOrigin, type SpawnedFrom, type StopMessage, type WithSender, type WithoutSender, asyncify, callHook, chainHook, defineActor, defineMessages, mergeConfigs, propagateError, runDispatch, runDispatchAsync, spawn, spawnAsync, stopPropagation };
|
|
26
26
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { i as runDispatch } from "./util-Brtfgv68.js";
|
|
2
|
-
import { a as
|
|
2
|
+
import { a as spawnAsync, i as runDispatchAsync, o as asyncify, t as AsyncProcess } from "./process.async-CdQ_zpKp.js";
|
|
3
3
|
import { n as chainHook, o as propagateError, r as mergeConfigs, s as stopPropagation, t as callHook } from "./hooks-CgXhmY4r.js";
|
|
4
|
-
import { t as
|
|
4
|
+
import { n as defineMessages, t as defineActor } from "./define-actor-CCJy7LDp.js";
|
|
5
|
+
import { t as LIB_VERSION } from "./version-BnBkASor.js";
|
|
5
6
|
//#region src/process.ts
|
|
6
7
|
/** @deprecated Use {@link spawnAsync} instead. */
|
|
7
8
|
function spawn(fn, pname, tp) {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/process.ts","../src/index.ts"],"sourcesContent":["import { AsyncProcess } from \"./process.async.js\";\nimport { asyncify } from \"./adapters.js\";\nimport type { Message, SenderInfo, ExitMessage, ProcessFn } from \"./types.js\";\n\nexport type { Message, ProcessFn, ProcessCtx } from \"./types.js\";\nexport { runDispatch } from \"./util.js\";\n\n/** @deprecated Use {@link spawnAsync} instead. */\nexport function spawn<A, S, IM extends Message = Message, OM extends Message = ExitMessage>(\n fn: ProcessFn<A, S, IM, OM>,\n pname: string,\n tp?: (m: OM, from: SenderInfo) => void,\n): (a: A) => Process<A, S, IM, OM> {\n return (a: A) => new Process(fn, pname, tp).start(a);\n}\n\n/** @deprecated Use {@link AsyncProcess} instead. Sync processes are\n * wrapped via asyncify internally; spawnAsync handles both. */\nclass Process<A, S, IM extends Message, OM extends Message> extends AsyncProcess<A, S, IM, OM, {}> {\n constructor(fn: ProcessFn<A, S, IM, OM>, pname: string, tp?: (m: OM, from: SenderInfo) => void) {\n super(asyncify(fn), pname, tp);\n }\n\n start(a: A) {\n super.start(a);\n return this;\n }\n tick(): Promise<void> {\n return super.pvtTick();\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 * @module\n */\n\nimport { Process, spawn } from \"./process.js\";\nimport { runDispatch } from \"./util.js\";\nimport { AsyncProcess, spawnAsync, runDispatchAsync } from \"./process.async.js\";\nimport { asyncify } from \"./adapters.js\";\n\nexport { Process, spawn, runDispatch };\nexport { AsyncProcess, spawnAsync, runDispatchAsync, asyncify };\nexport type { AnyProcess } from \"./process.async.js\";\n\nexport type {\n Message,\n WithSender,\n WithoutSender,\n ExitMessage,\n StopMessage,\n ProcessFn,\n ProcessCtx,\n AsyncProcessFn,\n SenderOrigin,\n SenderInfo,\n} from \"./types.js\";\n\nexport { defineActor, defineMessages } from \"./define-actor.js\";\nexport type {\n ActorDefinition,\n SpawnedFrom,\n ActorConfig,\n ActorContext,\n MethodOptions,\n HandlerOptions,\n HandlerFn,\n} from \"./actor-types.js\";\n\n// ── hooks ─────────────────────────────────────────────────────────────────\n\nexport { stopPropagation, propagateError, mergeConfigs, chainHook, callHook } from \"./hooks.js\";\nexport type {\n HookResult,\n ActorPlugin,\n ActorDecorated,\n ActorReflection,\n PluginTransform,\n} from \"./hooks.js\";\nexport { LIB_VERSION } from \"./version.js\";\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/process.ts","../src/index.ts"],"sourcesContent":["import { AsyncProcess } from \"./process.async.js\";\nimport { asyncify } from \"./adapters.js\";\nimport type { Message, SenderInfo, ExitMessage, ProcessFn } from \"./types.js\";\n\nexport type { Message, ProcessFn, ProcessCtx } from \"./types.js\";\nexport { runDispatch } from \"./util.js\";\n\n/** @deprecated Use {@link spawnAsync} instead. */\nexport function spawn<A, S, IM extends Message = Message, OM extends Message = ExitMessage>(\n fn: ProcessFn<A, S, IM, OM>,\n pname: string,\n tp?: (m: OM, from: SenderInfo) => void,\n): (a: A) => Process<A, S, IM, OM> {\n return (a: A) => new Process(fn, pname, tp).start(a);\n}\n\n/** @deprecated Use {@link AsyncProcess} instead. Sync processes are\n * wrapped via asyncify internally; spawnAsync handles both. */\nclass Process<A, S, IM extends Message, OM extends Message> extends AsyncProcess<A, S, IM, OM, {}> {\n constructor(fn: ProcessFn<A, S, IM, OM>, pname: string, tp?: (m: OM, from: SenderInfo) => void) {\n super(asyncify(fn), pname, tp);\n }\n\n start(a: A) {\n super.start(a);\n return this;\n }\n tick(): Promise<void> {\n return super.pvtTick();\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 * @module\n */\n\nimport { Process, spawn } from \"./process.js\";\nimport { runDispatch } from \"./util.js\";\nimport { AsyncProcess, spawnAsync, runDispatchAsync } from \"./process.async.js\";\nimport { asyncify } from \"./adapters.js\";\n\nexport { Process, spawn, runDispatch };\nexport { AsyncProcess, spawnAsync, runDispatchAsync, asyncify };\nexport type { AnyProcess } from \"./process.async.js\";\n\nexport type {\n Message,\n WithSender,\n WithoutSender,\n ExitMessage,\n StopMessage,\n ProcessFn,\n ProcessCtx,\n AsyncProcessFn,\n SenderOrigin,\n SenderInfo,\n} from \"./types.js\";\n\nexport { defineActor, defineMessages } from \"./define-actor.js\";\nexport type {\n ActorDefinition,\n SpawnedFrom,\n ActorConfig,\n ActorContext,\n MethodOptions,\n HandlerOptions,\n HandlerFn,\n} from \"./actor-types.js\";\n\n// ── hooks ─────────────────────────────────────────────────────────────────\n\nexport { stopPropagation, propagateError, mergeConfigs, chainHook, callHook } from \"./hooks.js\";\nexport type {\n HookResult,\n ActorPlugin,\n ActorDecorated,\n ActorReflection,\n PluginTransform,\n} from \"./hooks.js\";\nexport { LIB_VERSION } from \"./version.js\";\n"],"mappings":";;;;;;;AAQA,SAAgB,MACd,IACA,OACA,IACiC;CACjC,QAAQ,MAAS,IAAI,QAAQ,IAAI,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC;AACrD;;;AAIA,IAAM,UAAN,cAAoE,aAA+B;CACjG,YAAY,IAA6B,OAAe,IAAwC;EAC9F,MAAM,SAAS,EAAE,GAAG,OAAO,EAAE;CAC/B;CAEA,MAAM,GAAM;EACV,MAAM,MAAM,CAAC;EACb,OAAO;CACT;CACA,OAAsB;EACpB,OAAO,MAAM,QAAQ;CACvB;AACF"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { r as makeWaiter } from "./util-Brtfgv68.js";
|
|
2
|
-
import {
|
|
2
|
+
import { r as isProcess } from "./process.async-CdQ_zpKp.js";
|
|
3
3
|
import { s as stopPropagation } from "./hooks-CgXhmY4r.js";
|
|
4
|
+
import { t as defineActor } from "./define-actor-CCJy7LDp.js";
|
|
4
5
|
//#region src/remote/process-ref.ts
|
|
5
6
|
/** The key that marks a process reference inside an otherwise JSON value. */
|
|
6
7
|
const PROCESS_REF = "$p";
|
|
@@ -13,8 +14,10 @@ const PROCESS_REF = "$p";
|
|
|
13
14
|
function rootIdFor(parity) {
|
|
14
15
|
return parity === "odd" ? 1 : 0;
|
|
15
16
|
}
|
|
16
|
-
/**
|
|
17
|
-
*
|
|
17
|
+
/** What a served root is called when the client states no name for it. A client that
|
|
18
|
+
* states one — and the library's own does, in `$init` — is served under that name instead:
|
|
19
|
+
* the process across the seam is the one its proxy was forked as, and it should be called
|
|
20
|
+
* the same on both sides. This is the name for a connection that says nothing. */
|
|
18
21
|
const SERVED_ROOT_NAME = "remote";
|
|
19
22
|
/**
|
|
20
23
|
* The processes one connection knows, by the ids that name them. One table per
|
|
@@ -338,6 +341,42 @@ function jsonProblem(value) {
|
|
|
338
341
|
}
|
|
339
342
|
//#endregion
|
|
340
343
|
//#region src/remote/remote-process.ts
|
|
344
|
+
/**
|
|
345
|
+
* What a reference with nothing keeping it amounts to. It names a process this side can
|
|
346
|
+
* hear nothing from and ask nothing of, so it is not a process in any sense that matters
|
|
347
|
+
* here, and it is written as what it is wherever it turns up: a state somebody is reading,
|
|
348
|
+
* a log line, anything at all that stringifies.
|
|
349
|
+
*/
|
|
350
|
+
const UNSTABLE_REFERENCE = "UnstableReference";
|
|
351
|
+
/** What a process left behind when it ended. */
|
|
352
|
+
/**
|
|
353
|
+
* The references inside a value that crossed. A handle is the one thing in a frame that is
|
|
354
|
+
* not JSON, so wherever one sits, it came here on somebody else's frame and whoever is
|
|
355
|
+
* holding that value is holding it. Plain objects and arrays only: a value of another kind
|
|
356
|
+
* is not a shape this walks, and its references are not found.
|
|
357
|
+
*/
|
|
358
|
+
function refsInside(value) {
|
|
359
|
+
const found = [];
|
|
360
|
+
const seen = /* @__PURE__ */ new Set();
|
|
361
|
+
const walk = (node) => {
|
|
362
|
+
if (isRemoteProcess(node)) {
|
|
363
|
+
found.push(node);
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
if (typeof node !== "object" || node === null) return;
|
|
367
|
+
if (seen.has(node)) return;
|
|
368
|
+
seen.add(node);
|
|
369
|
+
if (Array.isArray(node)) {
|
|
370
|
+
for (const item of node) walk(item);
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
const proto = Object.getPrototypeOf(node);
|
|
374
|
+
if (proto !== Object.prototype && proto !== null) return;
|
|
375
|
+
for (const item of Object.values(node)) walk(item);
|
|
376
|
+
};
|
|
377
|
+
walk(value);
|
|
378
|
+
return found;
|
|
379
|
+
}
|
|
341
380
|
var RemoteProcess = class {
|
|
342
381
|
constructor(ref, send, fromName, letGo) {
|
|
343
382
|
this.state = null;
|
|
@@ -348,6 +387,10 @@ var RemoteProcess = class {
|
|
|
348
387
|
this.pvtExitWaiter = null;
|
|
349
388
|
this.pvtMessageSubs = [];
|
|
350
389
|
this.pvtStateSubs = [];
|
|
390
|
+
this.pvtReadyWaiters = [];
|
|
391
|
+
this.pvtRefCount = 0;
|
|
392
|
+
this.pvtOwnedRefs = [];
|
|
393
|
+
this.pvtTransientRefs = [];
|
|
351
394
|
this.pvtWanted = /* @__PURE__ */ new Set();
|
|
352
395
|
this.ref = ref;
|
|
353
396
|
this.pname = ref.pname;
|
|
@@ -378,6 +421,7 @@ var RemoteProcess = class {
|
|
|
378
421
|
if (!this.pvtConnected) return;
|
|
379
422
|
this.pvtConnected = false;
|
|
380
423
|
this.pvtStateSubs.forEach((fn) => fn());
|
|
424
|
+
this.pvtSettleReady();
|
|
381
425
|
this.pvtExitWaiter?.reject(/* @__PURE__ */ new Error(`${this.pname} cannot be reached: the connection is closed`));
|
|
382
426
|
}
|
|
383
427
|
/** Whether the process it names has ended, as far as this side has been told. */
|
|
@@ -398,6 +442,121 @@ var RemoteProcess = class {
|
|
|
398
442
|
if (why !== null) throw new Error(why);
|
|
399
443
|
}
|
|
400
444
|
/**
|
|
445
|
+
* Keep this reference: from here it is counted, which is what lets anything be asked of
|
|
446
|
+
* it. It returns the handle, so what is kept is written in one line —
|
|
447
|
+
* `this.pvtRemoteRefsHeld.push(ref.holdRef())` — and a table of them reads as what it is.
|
|
448
|
+
*/
|
|
449
|
+
holdRef() {
|
|
450
|
+
this.pvtRefCount += 1;
|
|
451
|
+
return this;
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Give the count back. Whoever gives the last one back lets the reference go, which is
|
|
455
|
+
* what tells the far side that nothing here wants to hear about the process again. A
|
|
456
|
+
* reference nobody ever kept is let go of by this too, which is how whoever receives one
|
|
457
|
+
* they do not want is rid of it.
|
|
458
|
+
*/
|
|
459
|
+
releaseRef() {
|
|
460
|
+
if (this.pvtRefCount > 0) this.pvtRefCount -= 1;
|
|
461
|
+
if (this.pvtRefCount > 0) return;
|
|
462
|
+
if (this.pvtWhyNotAskable() === null) {
|
|
463
|
+
this.release();
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
this.pvtDropBinding();
|
|
467
|
+
}
|
|
468
|
+
/** Let go of the binding here without asking the far side for anything: what the process
|
|
469
|
+
* was is over, or the wire that carried it is. */
|
|
470
|
+
pvtDropBinding() {
|
|
471
|
+
if (this.pvtReleased) return;
|
|
472
|
+
this.pvtReleased = true;
|
|
473
|
+
this.pvtLetGo?.(this.ref.id);
|
|
474
|
+
this.pvtSettleReady();
|
|
475
|
+
}
|
|
476
|
+
/** How many times this reference has been kept. */
|
|
477
|
+
refCount() {
|
|
478
|
+
return this.pvtRefCount;
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* What this is, as text: a handle nothing here keeps is an unstable reference and says
|
|
482
|
+
* so, and one that is kept says which process it names and by which id.
|
|
483
|
+
*/
|
|
484
|
+
[Symbol.for("nodejs.util.inspect.custom")]() {
|
|
485
|
+
return this.pvtRefCount === 0 ? UNSTABLE_REFERENCE : `RemoteProcess(${this.ref.id}: ${this.pname})`;
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* What this is, in JSON: the same reference the wire would carry, or the unstable thing
|
|
489
|
+
* it is when nothing keeps it. Written so that an unheld reference cannot be mistaken
|
|
490
|
+
* for a process that merely looks odd in a state, and so that one that is kept reads as
|
|
491
|
+
* the reference it is rather than as whatever fields it happens to have.
|
|
492
|
+
*/
|
|
493
|
+
toJSON() {
|
|
494
|
+
if (this.pvtRefCount === 0) return UNSTABLE_REFERENCE;
|
|
495
|
+
return { ["$p"]: {
|
|
496
|
+
id: this.ref.id,
|
|
497
|
+
pname: this.pname
|
|
498
|
+
} };
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* Remember the references inside a value that arrived here to be passed on. They are not
|
|
502
|
+
* counted — the receiver is the one to deal with them — and this is what keeps one from
|
|
503
|
+
* being nobody's for ever: when this handle is let go of, they are dropped with it.
|
|
504
|
+
*/
|
|
505
|
+
markTransient(value) {
|
|
506
|
+
for (const ref of refsInside(value)) {
|
|
507
|
+
if (ref.id === this.id || ref.pvtReleased) continue;
|
|
508
|
+
this.pvtDropTransient(ref);
|
|
509
|
+
if (this.pvtOwnedRefs.includes(ref) || this.pvtTransientRefs.includes(ref)) continue;
|
|
510
|
+
this.pvtTransientRefs.push(ref);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
/** Stop treating a reference as one that was only passing through. */
|
|
514
|
+
pvtDropTransient(ref) {
|
|
515
|
+
const at = this.pvtTransientRefs.indexOf(ref);
|
|
516
|
+
if (at >= 0) this.pvtTransientRefs.splice(at, 1);
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* Keep the references sitting in what this process holds. What it holds is here, so the
|
|
520
|
+
* thing keeping those references alive is this handle, and it holds them until it is let
|
|
521
|
+
* go of itself.
|
|
522
|
+
*/
|
|
523
|
+
pvtHoldOwned(value) {
|
|
524
|
+
for (const ref of refsInside(value)) {
|
|
525
|
+
if (ref.id === this.id || ref.pvtReleased) continue;
|
|
526
|
+
if (this.pvtOwnedRefs.includes(ref)) continue;
|
|
527
|
+
this.pvtDropTransient(ref);
|
|
528
|
+
this.pvtOwnedRefs.push(ref.holdRef());
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
/** Nothing is heard about a process nothing here keeps. A subscription, a tuning and a
|
|
532
|
+
* wait for the state are all ways of hearing, so they are where a reference has to have
|
|
533
|
+
* been kept first — and where the mistake says what to do about it. */
|
|
534
|
+
pvtRequireHeld(what) {
|
|
535
|
+
if (this.pvtRefCount > 0) return;
|
|
536
|
+
throw new Error(`${this.pname} is not held by anyone here: call holdRef() to keep this reference, or releaseRef() to let it go, before ${what}`);
|
|
537
|
+
}
|
|
538
|
+
/**
|
|
539
|
+
* Wait until what it holds is here.
|
|
540
|
+
*
|
|
541
|
+
* A handle starts out knowing nothing about the process: the far side says what a
|
|
542
|
+
* process holds when something over here asks to hear it, so the asking and the waiting
|
|
543
|
+
* are one act, and this is both. It settles with whatever has arrived when nothing more
|
|
544
|
+
* can: a process that has ended has said its last, and a connection that has gone has
|
|
545
|
+
* nothing left to say. Asking twice is asking about what is already here.
|
|
546
|
+
*/
|
|
547
|
+
ready() {
|
|
548
|
+
if (this.state !== null || this.pvtWhyNotAskable() !== null) return Promise.resolve();
|
|
549
|
+
this.pvtRequireHeld("waiting for what it holds");
|
|
550
|
+
this.pvtWant("state");
|
|
551
|
+
return new Promise((resolve) => this.pvtReadyWaiters.push(resolve));
|
|
552
|
+
}
|
|
553
|
+
/** Everyone waiting for what it holds has heard it, or never will. */
|
|
554
|
+
pvtSettleReady() {
|
|
555
|
+
const waiting = this.pvtReadyWaiters;
|
|
556
|
+
this.pvtReadyWaiters = [];
|
|
557
|
+
for (const resolve of waiting) resolve();
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
401
560
|
* Wait for the process to end. Its exit crosses back like everything else it
|
|
402
561
|
* does, and what it left behind is the answer.
|
|
403
562
|
*
|
|
@@ -433,8 +592,15 @@ var RemoteProcess = class {
|
|
|
433
592
|
release() {
|
|
434
593
|
this.pvtRequireAskable();
|
|
435
594
|
this.pvtReleased = true;
|
|
595
|
+
const owned = this.pvtOwnedRefs;
|
|
596
|
+
this.pvtOwnedRefs = [];
|
|
597
|
+
for (const ref of owned) if (!ref.pvtReleased) ref.releaseRef();
|
|
598
|
+
const transient = this.pvtTransientRefs;
|
|
599
|
+
this.pvtTransientRefs = [];
|
|
600
|
+
for (const ref of transient) if (!ref.pvtReleased && ref.pvtRefCount === 0) ref.release();
|
|
436
601
|
this.pvtLetGo?.(this.ref.id);
|
|
437
602
|
this.pvtSend({ $release: {} }, this.ref.id);
|
|
603
|
+
this.pvtSettleReady();
|
|
438
604
|
}
|
|
439
605
|
/** Stop feeding it messages. It is still there: `send` still reaches it. */
|
|
440
606
|
pause() {
|
|
@@ -458,6 +624,7 @@ var RemoteProcess = class {
|
|
|
458
624
|
*/
|
|
459
625
|
tune(kinds) {
|
|
460
626
|
this.pvtRequireAskable();
|
|
627
|
+
this.pvtRequireHeld("tuning what is heard about it");
|
|
461
628
|
this.pvtTune(kinds === "silent" ? [] : kinds);
|
|
462
629
|
}
|
|
463
630
|
/** Ask the far side to say exactly this much, and nothing when it is already
|
|
@@ -486,6 +653,7 @@ var RemoteProcess = class {
|
|
|
486
653
|
} }, this.ref.id);
|
|
487
654
|
}
|
|
488
655
|
subscribe(channel, cb) {
|
|
656
|
+
this.pvtRequireHeld(`subscribing to what it ${channel === "message" ? "says" : "holds"}`);
|
|
489
657
|
if (channel === "message") {
|
|
490
658
|
const fn = cb;
|
|
491
659
|
this.pvtMessageSubs.push(fn);
|
|
@@ -505,11 +673,14 @@ var RemoteProcess = class {
|
|
|
505
673
|
receiveState(state) {
|
|
506
674
|
if (this.pvtReleased) return;
|
|
507
675
|
this.state = Object.assign(this.state ?? {}, state);
|
|
676
|
+
this.pvtHoldOwned(state);
|
|
508
677
|
this.pvtStateSubs.forEach((fn) => fn());
|
|
678
|
+
this.pvtSettleReady();
|
|
509
679
|
}
|
|
510
680
|
/** A message it emitted. */
|
|
511
681
|
receiveMessage(msg, fromName) {
|
|
512
682
|
if (this.pvtReleased) return;
|
|
683
|
+
this.markTransient(msg);
|
|
513
684
|
this.pvtMessageSubs.forEach((fn) => fn(msg, fromName));
|
|
514
685
|
}
|
|
515
686
|
/** It has ended, and this is what it left behind. The last thing said about a
|
|
@@ -519,12 +690,17 @@ var RemoteProcess = class {
|
|
|
519
690
|
if (this.pvtExit !== null || this.pvtReleased) return;
|
|
520
691
|
this.pvtExit = exit;
|
|
521
692
|
this.pvtExitWaiter?.resolve(exit);
|
|
693
|
+
this.pvtSettleReady();
|
|
522
694
|
}
|
|
523
695
|
/** The methods it can answer, as functions that ask it for an answer. `call`
|
|
524
696
|
* is the connection's own: a handle knows what to ask for, not how a call is
|
|
525
697
|
* made or paired with its answer. */
|
|
526
698
|
receiveMethods(names, call) {
|
|
527
|
-
for (const name of names) this.$reflection[name] = (...args) =>
|
|
699
|
+
for (const name of names) this.$reflection[name] = async (...args) => {
|
|
700
|
+
const value = await call(name, args);
|
|
701
|
+
this.markTransient(value);
|
|
702
|
+
return value;
|
|
703
|
+
};
|
|
528
704
|
}
|
|
529
705
|
};
|
|
530
706
|
//#endregion
|
|
@@ -714,9 +890,20 @@ var CallSide = class {
|
|
|
714
890
|
});
|
|
715
891
|
});
|
|
716
892
|
}
|
|
717
|
-
/**
|
|
893
|
+
/**
|
|
894
|
+
* One function per announced name, on the surface whoever calls reads.
|
|
895
|
+
*
|
|
896
|
+
* An answer that carries a reference hands it to whoever asked, which makes it theirs to
|
|
897
|
+
* deal with: the handle that was asked is told about it, so that one nobody keeps is let
|
|
898
|
+
* go of with that handle rather than being nobody's for ever.
|
|
899
|
+
*/
|
|
718
900
|
install(surface, methods, to) {
|
|
719
|
-
for (const method of methods) surface[method] = (...callArgs) =>
|
|
901
|
+
for (const method of methods) surface[method] = async (...callArgs) => {
|
|
902
|
+
const value = await this.call(method, callArgs, to);
|
|
903
|
+
const owner = this.pvtTable.farHandleFor(to);
|
|
904
|
+
if (isRemoteProcess(owner)) owner.markTransient(value);
|
|
905
|
+
return value;
|
|
906
|
+
};
|
|
720
907
|
}
|
|
721
908
|
/**
|
|
722
909
|
* An answer to a call this side made: the call waiting under that `seq` is settled
|
|
@@ -788,10 +975,12 @@ async function answerCall(call, target, write, table) {
|
|
|
788
975
|
}
|
|
789
976
|
//#endregion
|
|
790
977
|
//#region src/remote/server.ts
|
|
791
|
-
/** The name the served actor is spawned under, and the one it answers to as a
|
|
792
|
-
*
|
|
793
|
-
*
|
|
794
|
-
|
|
978
|
+
/** The name the served actor is spawned under, and the one it answers to as a sender when
|
|
979
|
+
* it talks to a process of the far side's: what the client states in `$init`, since the
|
|
980
|
+
* process being served is the one its proxy was forked as over there. The fallback is for
|
|
981
|
+
* a connection that states none — an older client, or a hand-written `$init` — where the
|
|
982
|
+
* far side's name for its own end is all there is to go on. */
|
|
983
|
+
let rootName = SERVED_ROOT_NAME;
|
|
795
984
|
async function serveRemoteActor(actor, spawner) {
|
|
796
985
|
const channel = await spawner();
|
|
797
986
|
const table = new ProcessTable("even", (proc, id) => streams.crossed(proc, id));
|
|
@@ -818,7 +1007,7 @@ async function serveRemoteActor(actor, spawner) {
|
|
|
818
1007
|
if (held) return held;
|
|
819
1008
|
const known = table.farHandleFor(ref.id);
|
|
820
1009
|
if (isRemoteProcess(known)) return known;
|
|
821
|
-
const handle = new RemoteProcess(ref, sendTo,
|
|
1010
|
+
const handle = new RemoteProcess(ref, sendTo, rootName, (id) => table.release(id));
|
|
822
1011
|
table.bindFar(ref.id, handle);
|
|
823
1012
|
return handle;
|
|
824
1013
|
};
|
|
@@ -833,12 +1022,14 @@ async function serveRemoteActor(actor, spawner) {
|
|
|
833
1022
|
const initFrame = decodeFrame(waiting.shift(), handleFor);
|
|
834
1023
|
if (!isInit(initFrame)) throw new Error("serveRemoteActor: expected $init");
|
|
835
1024
|
const init = initFrame.$init;
|
|
1025
|
+
const stated = init.rootName;
|
|
1026
|
+
if (typeof stated === "string" && stated !== "") rootName = stated;
|
|
836
1027
|
const parentName = init.parentName ?? null;
|
|
837
1028
|
const parentIdName = init.parentIdName ?? null;
|
|
838
1029
|
const parentId = parentIdName ? Symbol.for(parentIdName) : null;
|
|
839
1030
|
const { parentName: _pn, parentIdName: _pid, ...initArgs } = init;
|
|
840
1031
|
const proc = await actor.spawn(initArgs, {
|
|
841
|
-
name:
|
|
1032
|
+
name: rootName,
|
|
842
1033
|
parentName,
|
|
843
1034
|
parentId
|
|
844
1035
|
});
|
|
@@ -881,7 +1072,7 @@ async function serveRemoteActor(actor, spawner) {
|
|
|
881
1072
|
}
|
|
882
1073
|
if (isStop(frame) || isPause(frame) || isResume(frame)) {
|
|
883
1074
|
if (isProcess(mine)) {
|
|
884
|
-
const sender = makeSender(
|
|
1075
|
+
const sender = makeSender(rootName, parentName, parentId);
|
|
885
1076
|
if (isStop(frame)) mine.stop({ from: sender });
|
|
886
1077
|
else if (isPause(frame)) mine.pause();
|
|
887
1078
|
else mine.resume();
|
|
@@ -923,7 +1114,8 @@ function remoteClient(name, spawner) {
|
|
|
923
1114
|
const root = this.ctx;
|
|
924
1115
|
table.bindRoot(root);
|
|
925
1116
|
const channel = await spawner(args);
|
|
926
|
-
const fromName =
|
|
1117
|
+
const fromName = this.ctx.pname;
|
|
1118
|
+
const parentName = this.ctx.parentName ?? this.ctx.pname;
|
|
927
1119
|
const streams = new ProcessStreams((frame, to) => sendTo(frame, to), table.rootId());
|
|
928
1120
|
/** Put an encoded frame on the wire, and then say what it numbered. A frame that
|
|
929
1121
|
* carried a reference has to be out first, or the far side hears about a process
|
|
@@ -938,8 +1130,9 @@ function remoteClient(name, spawner) {
|
|
|
938
1130
|
};
|
|
939
1131
|
sendTo({ $init: {
|
|
940
1132
|
...args,
|
|
941
|
-
|
|
942
|
-
|
|
1133
|
+
rootName: fromName,
|
|
1134
|
+
parentName,
|
|
1135
|
+
parentIdName: parentName
|
|
943
1136
|
} }, table.farRootId());
|
|
944
1137
|
let currentState = {};
|
|
945
1138
|
const calls = new CallSide(write, table);
|
|
@@ -969,9 +1162,10 @@ function remoteClient(name, spawner) {
|
|
|
969
1162
|
*/
|
|
970
1163
|
const farRoot = new RemoteProcess({
|
|
971
1164
|
id: table.farRootId(),
|
|
972
|
-
pname:
|
|
1165
|
+
pname: fromName
|
|
973
1166
|
}, sendTo, fromName, (id) => table.release(id));
|
|
974
1167
|
table.bindFar(table.farRootId(), farRoot);
|
|
1168
|
+
farRoot.holdRef();
|
|
975
1169
|
let started = null;
|
|
976
1170
|
const start = new Promise((resolve) => {
|
|
977
1171
|
started = resolve;
|
|
@@ -1040,7 +1234,7 @@ function remoteClient(name, spawner) {
|
|
|
1040
1234
|
}
|
|
1041
1235
|
if (isStop(frame) || isPause(frame) || isResume(frame)) {
|
|
1042
1236
|
if (isProcess(mine)) {
|
|
1043
|
-
if (isStop(frame)) mine.stop({ from: makeSender(
|
|
1237
|
+
if (isStop(frame)) mine.stop({ from: makeSender(fromName, null, null) });
|
|
1044
1238
|
else if (isPause(frame)) mine.pause();
|
|
1045
1239
|
else mine.resume();
|
|
1046
1240
|
}
|
|
@@ -1113,4 +1307,4 @@ function json1Channel(transport) {
|
|
|
1113
1307
|
//#endregion
|
|
1114
1308
|
export { isStop as _, remoteClient as a, rootIdFor as b, RemoteProcess as c, isMsg as d, isPause as f, isState as g, isResume as h, json1Channel as i, isExit as l, isRelease as m, decode as n, serveRemoteActor as o, isProto as p, encode as r, makeSender as s, VERSION as t, isInit as u, PROCESS_REF as v, isRemoteProcess as y };
|
|
1115
1309
|
|
|
1116
|
-
//# sourceMappingURL=json1-
|
|
1310
|
+
//# sourceMappingURL=json1-DZ8N0TuZ.js.map
|