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.
@@ -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.37.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 runDispatchAsync, n as defineMessages, o as spawnAsync, r as AsyncProcess, s as asyncify, t as defineActor } from "./define-actor-CZysIKH4.js";
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 LIB_VERSION } from "./version-DTvG9uAx.js";
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":";;;;;;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
+ {"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 { i as isProcess, t as defineActor } from "./define-actor-CZysIKH4.js";
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
- /** The name a served root is spawned under: what a visitor's handle on the far root
17
- * is called, since no frame about a root carries a name of its own. */
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) => call(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
- /** One function per announced name, on the surface whoever calls reads. */
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) => this.call(method, callArgs, to);
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
- * sender when it talks to a process of the far side's. A reference that names the
793
- * root it serves is told apart from the far side's root by it. */
794
- const ROOT_NAME = SERVED_ROOT_NAME;
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, ROOT_NAME, (id) => table.release(id));
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: ROOT_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(ROOT_NAME, parentName, parentId);
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 = name;
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
- parentName: fromName,
942
- parentIdName: fromName
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: SERVED_ROOT_NAME
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(name, null, null) });
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-CWWXL69y.js.map
1310
+ //# sourceMappingURL=json1-DZ8N0TuZ.js.map