@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.
- package/dist/{chunk-YWDRE77H.js → chunk-3BCTP4YS.js} +79 -6
- package/dist/{chunk-LT46QFLW.js → chunk-NPRIYYNM.js} +1951 -1009
- package/dist/{contract-UEKTud1Z.d.ts → contract-BoCNAPZT.d.ts} +146 -1
- package/dist/index.d.ts +6 -3
- package/dist/index.js +14 -2
- package/dist/{room-uhj3b-8E.d.ts → room-CGbiPxi3.d.ts} +311 -11
- package/dist/test/index.d.ts +20 -4
- package/dist/test/index.js +9 -4
- package/dist/worker/index.d.ts +41 -9
- package/dist/worker/index.js +34 -7
- package/package.json +9 -4
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
|
+
HOLD_ACTIONS,
|
|
2
3
|
parseHibernationBlob,
|
|
4
|
+
sameSchemaHash,
|
|
3
5
|
writeHibernationBlob
|
|
4
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-NPRIYYNM.js";
|
|
5
7
|
|
|
6
8
|
// src/migrate.ts
|
|
7
9
|
import { PRESENCE_COLLECTION, withBuiltins } from "@irtio/protocol";
|
|
@@ -24,8 +26,18 @@ function migrateSnapshot(blob, chain, options) {
|
|
|
24
26
|
}
|
|
25
27
|
const parsed = parseHibernationBlob(blob);
|
|
26
28
|
let schema = schemaFromCanonical(first.schemaJson);
|
|
27
|
-
let
|
|
28
|
-
|
|
29
|
+
let ext = withBuiltins(schema);
|
|
30
|
+
if (!sameSchemaHash(parsed.schemaHash8, ext.hash8)) {
|
|
31
|
+
throw new Error(
|
|
32
|
+
`migrateSnapshot: room ${options.roomId}: the snapshot was written under schema ${hex(parsed.schemaHash8)}, but the chain starts at v${first.version} whose schema is ${hex(ext.hash8)}. The migration chain does not begin at the deployment these bytes were written under \u2014 most likely the save predates the oldest retained deployment.`
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
let plain = decodeSnapshot(ext, parsed.snapshot, {
|
|
36
|
+
onNonFinite: (path, value) => options.log?.("warn", [
|
|
37
|
+
`migrateSnapshot: room ${options.roomId}: ${path} held ${value}; reset to its default`
|
|
38
|
+
])
|
|
39
|
+
}).state;
|
|
40
|
+
let state = toMigrationState(ext, plain);
|
|
29
41
|
delete state[PRESENCE_COLLECTION];
|
|
30
42
|
const applied = [];
|
|
31
43
|
for (let i = 1; i < chain.length; i++) {
|
|
@@ -51,7 +63,8 @@ function migrateSnapshot(blob, chain, options) {
|
|
|
51
63
|
}
|
|
52
64
|
schema = nextSchema;
|
|
53
65
|
}
|
|
54
|
-
|
|
66
|
+
ext = withBuiltins(schema);
|
|
67
|
+
plain = fromMigrationState(ext, state, last.version);
|
|
55
68
|
const bytes = writeHibernationBlob(
|
|
56
69
|
{
|
|
57
70
|
seed: parsed.seed,
|
|
@@ -60,9 +73,23 @@ function migrateSnapshot(blob, chain, options) {
|
|
|
60
73
|
mode: parsed.mode,
|
|
61
74
|
// Stamped with the *target* schema: these bytes are now that schema's, and a room waking on
|
|
62
75
|
// it must not see them as a mismatch.
|
|
63
|
-
schemaHash8:
|
|
76
|
+
schemaHash8: ext.hash8
|
|
64
77
|
},
|
|
65
|
-
encodeSnapshot(
|
|
78
|
+
encodeSnapshot(ext, plain, { tick: parsed.tick }),
|
|
79
|
+
// The physics section is dropped, and that is the D22 rule rather than an oversight: the world
|
|
80
|
+
// is rebuilt from post-transform schema state, because pose and velocity ARE schema fields and
|
|
81
|
+
// a migration is free to move them. Carrying the serialized world across would silently
|
|
82
|
+
// overwrite whatever the transform wrote — `physics-migrate.test.ts` pins both halves of that.
|
|
83
|
+
// What follows here is the *third* section, which had no such reason.
|
|
84
|
+
void 0,
|
|
85
|
+
// D77 holds were dropped the same way physics is, but by accident: `writeHibernationBlob` takes
|
|
86
|
+
// four arguments and this call passed two, so a migrating deploy silently emptied the hold map
|
|
87
|
+
// and nothing logged. A crate under `onLeave: { hold, then }` then woke with no hold, its
|
|
88
|
+
// deadline never fired, and the record stayed owned by a client id that never comes back —
|
|
89
|
+
// unwritable by anyone, forever. Unlike a physics world, a hold has no field layout in it, so
|
|
90
|
+
// it survives a schema change on its own terms; what it needs is re-validating against the
|
|
91
|
+
// schema it is about to wake under.
|
|
92
|
+
migrateHolds(parsed.holds, ext, options)
|
|
66
93
|
);
|
|
67
94
|
return {
|
|
68
95
|
bytes,
|
|
@@ -72,6 +99,52 @@ function migrateSnapshot(blob, chain, options) {
|
|
|
72
99
|
ms: performance.now() - startedAt
|
|
73
100
|
};
|
|
74
101
|
}
|
|
102
|
+
function hex(bytes) {
|
|
103
|
+
if (!bytes) return "(none \u2014 a pre-v3 blob carries no schema hash)";
|
|
104
|
+
return [...bytes].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
105
|
+
}
|
|
106
|
+
function migrateHolds(bytes, target, options) {
|
|
107
|
+
if (!bytes || bytes.length === 0) return void 0;
|
|
108
|
+
let parsed;
|
|
109
|
+
try {
|
|
110
|
+
parsed = JSON.parse(new TextDecoder().decode(bytes));
|
|
111
|
+
} catch (err) {
|
|
112
|
+
options.log?.("warn", ["migrate: hold deadlines did not decode; they are dropped:", err]);
|
|
113
|
+
return void 0;
|
|
114
|
+
}
|
|
115
|
+
if (!Array.isArray(parsed)) {
|
|
116
|
+
options.log?.("warn", ["migrate: hold deadlines were not an array; they are dropped"]);
|
|
117
|
+
return void 0;
|
|
118
|
+
}
|
|
119
|
+
const entities = new Map(
|
|
120
|
+
target.collections.map((c) => [c.name, c.kind])
|
|
121
|
+
);
|
|
122
|
+
const kept = [];
|
|
123
|
+
for (const h of parsed) {
|
|
124
|
+
if (typeof h?.collection !== "string" || typeof h.id !== "string" || typeof h.holder !== "string" || typeof h.deadlineMs !== "number") {
|
|
125
|
+
options.log?.("warn", ["migrate: dropping a hold that is not a hold record:", h]);
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
const kind = entities.get(h.collection);
|
|
129
|
+
if (kind !== "entity") {
|
|
130
|
+
options.log?.("warn", [
|
|
131
|
+
`migrate: dropping the hold on ${h.collection}.${h.id} \u2014`,
|
|
132
|
+
kind === void 0 ? "the target schema no longer declares that collection" : "that collection is no longer an entity collection"
|
|
133
|
+
]);
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
if (!HOLD_ACTIONS.has(h.then)) {
|
|
137
|
+
options.log?.("warn", [
|
|
138
|
+
`migrate: dropping the hold on ${h.collection}.${h.id} \u2014 unknown leave action`,
|
|
139
|
+
h.then
|
|
140
|
+
]);
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
kept.push(h);
|
|
144
|
+
}
|
|
145
|
+
if (kept.length === 0) return void 0;
|
|
146
|
+
return new TextEncoder().encode(JSON.stringify(kept));
|
|
147
|
+
}
|
|
75
148
|
function toMigrationState(schema, plain) {
|
|
76
149
|
const out = {};
|
|
77
150
|
for (const c of schema.collections) {
|