@irtio/runtime 1.0.0 → 3.0.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.
@@ -1,13 +1,8 @@
1
- import { L as LogLevel, T as TimelineDump, p as RoomStats, c as HostCall, d as HostCallResult, i as RoomCoreApi, n as RoomHost } from '../contract-UEKTud1Z.js';
1
+ import { L as LogLevel, t as RpcTraceDump, T as TimelineDump, r as RoomStats, c as HostCall, d as HostCallResult, k as RoomCoreApi, p as RoomHost } from '../contract-BoCNAPZT.js';
2
2
  import { ErrorCodeName, ProfileSnapshot, PlatformNotice } from '@irtio/protocol';
3
3
  import { NpcConfig, LeaveReason } from '@irtio/server';
4
4
  import '@irtio/schema';
5
5
 
6
- /**
7
- * Supervisor ↔ room-worker `postMessage` protocol. Every `Uint8Array` is transferred.
8
- * Keep it additive: deployed supervisors speak exactly this.
9
- */
10
-
11
6
  /**
12
7
  * One deployment in a wake-time migration chain. The worker imports each
13
8
  * `migrationUrl` and runs `up` inside its own sandbox — migrations are tenant code and never run
@@ -40,6 +35,11 @@ type ToWorker = {
40
35
  * existing supervisor already sends, and a room without it pays nothing.
41
36
  */
42
37
  profile?: boolean;
38
+ /**
39
+ * M7 pf4: run this room with an RPC trace ring. Absent means off — the shape every existing
40
+ * supervisor already sends — and a room without it pays nothing. `irtio dev --trace-rpc`.
41
+ */
42
+ traceRpc?: boolean;
43
43
  } | {
44
44
  t: 'join';
45
45
  clientId: string;
@@ -51,6 +51,9 @@ type ToWorker = {
51
51
  reconnecting?: boolean;
52
52
  /** D44: this join is a scripted NPC's loopback session. */
53
53
  npc?: boolean;
54
+ /** M8 lane 5: the verified subject, absent for a key join. See `JoinOptions.subject` — it is
55
+ * deliberately narrower than `playerId`, which always has a value. */
56
+ subject?: string;
54
57
  } | {
55
58
  t: 'leave';
56
59
  clientId: string;
@@ -63,12 +66,23 @@ type ToWorker = {
63
66
  clientId: string;
64
67
  bytes: Uint8Array;
65
68
  }
66
- /** `forSave` (D24) asks for the bytes only: no `onSleep`, no timers cleared, nothing
67
- * rejected — the room keeps running. Absent means hibernation, as it always did. */
69
+ /**
70
+ * `forSave` (D24) asks for the bytes only: no `onSleep`, no timers cleared, nothing rejected —
71
+ * the room keeps running. `false` is the departure: `onSleep` runs, timers are cleared, pending
72
+ * work is rejected, and the worker is about to be terminated.
73
+ *
74
+ * **Required, deliberately** (bugs.md #82). It was optional, defaulting to the departure, and
75
+ * the periodic `snapshotEveryMs` snapshot of a *running* event-mode room simply omitted it — so
76
+ * every such room ran `onSleep` and lost its armed timers every thirty seconds while it kept
77
+ * running. Only the caller knows which of the two it meant, so the protocol makes it say. Host
78
+ * and worker are one shipping unit (`@irtio/supervisor` is private and depends on
79
+ * `@irtio/runtime` by `workspace:*`, and this module is not on `@irtio/runtime`'s export map),
80
+ * so there is no mixed-version pair for the missing field to have to mean anything to.
81
+ */
68
82
  | {
69
83
  t: 'serialize';
70
84
  reqId: number;
71
- forSave?: boolean;
85
+ forSave: boolean;
72
86
  } | {
73
87
  t: 'inspect';
74
88
  reqId: number;
@@ -215,6 +229,17 @@ type FromWorker = {
215
229
  t: 'serialized';
216
230
  reqId: number;
217
231
  bytes: Uint8Array;
232
+ /**
233
+ * The earliest pending `room.setTimeout`/`setInterval` deadline the departure serialize
234
+ * just cleared, in **wall-clock ms** (`Date.now()`'s frame, translated in the worker the
235
+ * same way `setAlarm`'s `atMs` is — the room's own clock is `performance.now()` and means
236
+ * nothing to a supervisor that outlives the worker).
237
+ *
238
+ * Absent when the room had no timers armed, and always absent for `forSave`, which clears
239
+ * nothing and therefore loses nothing. The supervisor arms an internal wake for it so the
240
+ * room is up again by the time its timer would have fired.
241
+ */
242
+ nextTimerAt?: number;
218
243
  } | {
219
244
  t: 'inspected';
220
245
  reqId: number;
@@ -228,6 +253,13 @@ type FromWorker = {
228
253
  }[];
229
254
  /** Worker process RSS in bytes. */
230
255
  rss: number;
256
+ /**
257
+ * M7 pf4: the RPC trace and the per-client stamp-lag table, present only when this room was
258
+ * armed with `traceRpc`. It rides `inspected` rather than a message of its own because the
259
+ * dev page already polls `inspect` once a second and a second round trip would buy nothing
260
+ * — and because a new worker message is a wider change than an additive field on one.
261
+ */
262
+ rpcTrace?: RpcTraceDump;
231
263
  }
232
264
  /** D41: the recorder is armed. `ok: false` when there is no room core to arm. */
233
265
  | {
@@ -1,7 +1,8 @@
1
1
  import {
2
2
  migrateSnapshot
3
- } from "../chunk-YWDRE77H.js";
3
+ } from "../chunk-3BCTP4YS.js";
4
4
  import {
5
+ AdmissionError,
5
6
  RoomCore,
6
7
  RoomFullError,
7
8
  initMatter,
@@ -9,7 +10,7 @@ import {
9
10
  initRapier2d,
10
11
  onFirstRapierStep,
11
12
  rapierHasStepped
12
- } from "../chunk-LT46QFLW.js";
13
+ } from "../chunk-NPRIYYNM.js";
13
14
 
14
15
  // src/worker/index.ts
15
16
  import { getHeapStatistics } from "v8";
@@ -196,7 +197,8 @@ async function handleMessage(state, host, post, msg) {
196
197
  roomId: msg.roomId,
197
198
  ...msg.seed !== void 0 ? { seed: msg.seed } : {},
198
199
  ...msg.publicUrl !== void 0 ? { publicUrl: msg.publicUrl } : {},
199
- ...msg.profile === true ? { profile: true } : {}
200
+ ...msg.profile === true ? { profile: true } : {},
201
+ ...msg.traceRpc === true ? { traceRpc: true } : {}
200
202
  };
201
203
  const core = snapshot ? RoomCore.restore(def, snapshot, host, options) : new RoomCore(def, host, options);
202
204
  state.core = core;
@@ -259,7 +261,10 @@ async function handleMessage(state, host, post, msg) {
259
261
  ...msg.name !== void 0 ? { name: msg.name } : {},
260
262
  ...msg.playerId !== void 0 ? { playerId: msg.playerId } : {},
261
263
  ...msg.reconnecting !== void 0 ? { reconnecting: msg.reconnecting } : {},
262
- ...msg.npc === true ? { npc: true } : {}
264
+ ...msg.npc === true ? { npc: true } : {},
265
+ // M8 lane 5: absent for a key join, and absent is the answer `onAdmit` and `room.ban`
266
+ // both need to be able to see.
267
+ ...msg.subject !== void 0 ? { subject: msg.subject } : {}
263
268
  };
264
269
  const result = core.join(msg.clientId, joinOptions);
265
270
  const snapshot = ownBuffer(result.snapshot);
@@ -276,6 +281,13 @@ async function handleMessage(state, host, post, msg) {
276
281
  } catch (err) {
277
282
  if (err instanceof RoomFullError) {
278
283
  post({ t: "joinRejected", clientId: msg.clientId, code: "E_ROOM_FULL" });
284
+ } else if (err instanceof AdmissionError) {
285
+ post({
286
+ t: "joinRejected",
287
+ clientId: msg.clientId,
288
+ code: "E_ADMISSION",
289
+ reason: err.reason
290
+ });
279
291
  } else {
280
292
  post({
281
293
  t: "joinRejected",
@@ -302,8 +314,17 @@ async function handleMessage(state, host, post, msg) {
302
314
  case "serialize": {
303
315
  const core = state.core;
304
316
  if (!core) return;
305
- const bytes = ownBuffer(msg.forSave === true ? core.snapshot() : core.serialize());
306
- post({ t: "serialized", reqId: msg.reqId, bytes }, [bytes.buffer]);
317
+ const bytes = ownBuffer(msg.forSave ? core.snapshot() : core.serialize());
318
+ const timerAt = msg.forSave ? void 0 : core.lastSerializeTimerAt;
319
+ post(
320
+ {
321
+ t: "serialized",
322
+ reqId: msg.reqId,
323
+ bytes,
324
+ ...timerAt === void 0 ? {} : { nextTimerAt: Date.now() + (timerAt - performance.now()) }
325
+ },
326
+ [bytes.buffer]
327
+ );
307
328
  return;
308
329
  }
309
330
  case "inspect": {
@@ -316,7 +337,13 @@ async function handleMessage(state, host, post, msg) {
316
337
  tick: ins.tick,
317
338
  stateJson: ins.state,
318
339
  recent: ins.recent,
319
- rss: typeof process !== "undefined" ? process.memoryUsage().rss : 0
340
+ rss: typeof process !== "undefined" ? process.memoryUsage().rss : 0,
341
+ // M7 pf4: absent unless this room was armed, which is what makes `undefined` mean
342
+ // "nobody asked" rather than "nobody called".
343
+ ...(() => {
344
+ const trace = core.rpcTrace();
345
+ return trace === void 0 ? {} : { rpcTrace: trace };
346
+ })()
320
347
  });
321
348
  return;
322
349
  }
package/package.json CHANGED
@@ -1,8 +1,13 @@
1
1
  {
2
2
  "name": "@irtio/runtime",
3
- "version": "1.0.0",
3
+ "version": "3.0.0",
4
4
  "description": "irtio room runtime: RoomCore, worker_threads host, in-process test harness",
5
5
  "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/alex-irt/irtio.git",
9
+ "directory": "packages/runtime"
10
+ },
6
11
  "publishConfig": {
7
12
  "access": "public"
8
13
  },
@@ -31,9 +36,9 @@
31
36
  "@dimforge/rapier2d-compat": "0.20.0",
32
37
  "@dimforge/rapier3d-compat": "0.20.0",
33
38
  "matter-js": "0.20.0",
34
- "@irtio/protocol": "1.0.0",
35
- "@irtio/schema": "1.0.0",
36
- "@irtio/server": "1.0.0"
39
+ "@irtio/protocol": "3.0.0",
40
+ "@irtio/schema": "3.0.0",
41
+ "@irtio/server": "3.0.0"
37
42
  },
38
43
  "devDependencies": {
39
44
  "@types/matter-js": "0.20.2"