@irtio/schema 3.1.1 → 4.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/index.d.ts +32 -1
- package/dist/index.js +98 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -361,6 +361,17 @@ interface EntityOptions {
|
|
|
361
361
|
* Server behaviour, so it is part of the canonical form and the hash — unlike `predicted`.
|
|
362
362
|
*/
|
|
363
363
|
readonly perPlayer?: boolean;
|
|
364
|
+
/**
|
|
365
|
+
* M8 proposal 13: the fields of this per-player record that outlive the room.
|
|
366
|
+
*
|
|
367
|
+
* On a relay-with-state project a client that joins with a verified identity assertion has these
|
|
368
|
+
* fields hydrated from a per-(project, subject) blob, and they are written back when the player
|
|
369
|
+
* leaves. Anonymous joins get schema defaults and write nothing. Requires `perPlayer: true`, the
|
|
370
|
+
* named fields must exist, and their worst-case encoded size must fit `PERSIST_BLOB_BYTES`.
|
|
371
|
+
*
|
|
372
|
+
* Server behaviour — hydration shape — so it is part of the canonical form and the hash.
|
|
373
|
+
*/
|
|
374
|
+
readonly persist?: readonly string[];
|
|
364
375
|
/**
|
|
365
376
|
* D77: clients may `add` records to this collection (the adder becomes the owner, with the id
|
|
366
377
|
* it chose) and the owner may `remove` one. Off by default, which is what every collection
|
|
@@ -413,6 +424,21 @@ declare function entity<F extends Fields, const O extends EntityOptions>(fields:
|
|
|
413
424
|
/** Exactly one instance; same `serverOwned`/`visibility` options. */
|
|
414
425
|
declare function singleton<F extends Fields>(fields: F): SingletonDef<F, {}>;
|
|
415
426
|
declare function singleton<F extends Fields, const O extends EntityOptions>(fields: F, options: O): SingletonDef<F, O>;
|
|
427
|
+
/**
|
|
428
|
+
* The hard cap on one player's persistence blob, in encoded bytes.
|
|
429
|
+
*
|
|
430
|
+
* A compile that could exceed it is refused here rather than at the first leave that would have
|
|
431
|
+
* written an oversize blob: a cap discovered in production is a cap nobody can act on.
|
|
432
|
+
*/
|
|
433
|
+
declare const PERSIST_BLOB_BYTES: number;
|
|
434
|
+
/**
|
|
435
|
+
* The worst-case encoded size of `names` out of `fields`, in bytes.
|
|
436
|
+
*
|
|
437
|
+
* Static and deliberately pessimistic: every bound is the largest value the declared type can
|
|
438
|
+
* hold, so a schema that compiles can never write a blob over the cap. Mirrors `codec.ts`'s
|
|
439
|
+
* writers — LEB128 varints for list and string lengths, one presence byte for `.opt`.
|
|
440
|
+
*/
|
|
441
|
+
declare function maxEncodedBytes(fields: readonly FieldDesc[], names: readonly string[]): number;
|
|
416
442
|
type RpcDirection = 'server' | 'client';
|
|
417
443
|
interface RpcDef<Dir extends RpcDirection = RpcDirection, P extends Fields = Fields, R extends Fields | undefined = Fields | undefined> {
|
|
418
444
|
readonly kind: 'rpc';
|
|
@@ -484,6 +510,11 @@ interface CollectionDesc {
|
|
|
484
510
|
readonly physics: PhysicsDesc | undefined;
|
|
485
511
|
/** D77: one record per joined client, owned by them. In the hash. */
|
|
486
512
|
readonly perPlayer: boolean;
|
|
513
|
+
/**
|
|
514
|
+
* M8 proposal 13: the per-player fields that outlive the room, sorted, or `undefined` when none
|
|
515
|
+
* were declared. In the hash when present.
|
|
516
|
+
*/
|
|
517
|
+
readonly persist: readonly string[] | undefined;
|
|
487
518
|
/** D77: clients may add records here and their owner may remove them. In the hash. */
|
|
488
519
|
readonly clientCreate: boolean;
|
|
489
520
|
/** D77: the compiled transfer and leave rules. `undefined` when none were declared. In the hash. */
|
|
@@ -1258,4 +1289,4 @@ declare function diffSchemas(oldSchema: AnySchema, newSchema: AnySchema): Schema
|
|
|
1258
1289
|
*/
|
|
1259
1290
|
declare function breakingIsWireOnly(changes: readonly SchemaChange[]): boolean;
|
|
1260
1291
|
|
|
1261
|
-
export { type AddOptions, type AnyDef, type AnyMessageMap, type AnyRpc, type AnySchema, type AnyType, type Body2dState, type BodyFieldsOf, type BroadcastProxy, ByteReader, ByteWriter, CANONICAL_VERSION, type ClientCallProxy, type ClientImplementations, type ClientRpcs, type ClientState, type Collection, type CollectionDesc, type CollectionDirty, type DecodeSnapshotOptions, type DecodedSnapshot, type DeepReadonly, type DeepWritable, type DefMap, type Delta, type DeltaCollection, type DeltaOp, type DirtySet, EntityCollection, type EntityDef, type EntityOptions, type EntityPhysics, type FieldDesc, type FieldMask, type Fields, type FromCanonicalOptions, type GridDesc, type GridOptions, type Header, ID_MAX_BYTES, type Implementations, type Infer, type InferFields, type InitFields, type InitOf, type InstanceOf, type Kind, MAX_MESSAGES, MAX_USER_RPCS, type MessageChannels, type MessageDesc, type MessageMap, type MessageNames, type MessageValue, type MessagesOf, type OwnableKeys, type Owned, type OwnershipDesc, type OwnershipLeave, type OwnershipLeaveAction, type OwnershipOptions, type OwnershipPosition, type OwnershipTransfer, PHYSICS_BODY_CHANNELS, type PhysicsBodyChannel, type PhysicsBodyMap, type PhysicsDesc, type PhysicsKeys, type PlainState, RESERVED_COLLECTION_NAMES, RESERVED_RPC_NAMES, RESERVED_RPC_PREFIX, type ReadOptions, type ReadonlyBodyFields, type ReadonlyCollection, type RecordDirty, type RoleOf, type RpcDef, type RpcDesc, type RpcDirection, type RpcMap, type RpcParams, type RpcReturns, type RpcSpec, SERVER_OWNER, SINGLETON_ID, type Schema, type SchemaChange, type SchemaDefs, type SchemaIssue, type SchemaMessages, type SchemaOptions, type SchemaRoles, type SchemaRpc, type ServerCallProxy, type ServerMessageChannels, type ServerOwnedKeys, type ServerRpcs, type SingletonDef, type SnapshotOptions, type State, type Tracked, type Type, type TypeDesc, type ValueSink, type Visibility, type VisibleKeys, type WalkEvents, angleFrom2d, applyChannel2d, applyDelta, bool, breakingIsWireOnly, bytesEqual, canonicalOwnership, canonicalPhysics, canonicalType, channelOf2d, client, cloneValue, collectionDirty, compileOwnership, compilePhysics, computeDirty, createDirtySet, createFieldMask, createState, decodeDelta, decodeDeltaFrom, decodeFields, decodeSnapshot, defaultRecord, defaultValue, defineSchema, describeType, diffSchemas, encodeDelta, encodeFields, encodeSnapshot, entity, enumOf, estimateSize, f32, f64, filterDirty, frozenProxy, i32, isDirtyEmpty, isWhole, list, markAdd, markField, markOwner, markPath, markRemove, mergeDirty, mergeMask, normalizeRecord, normalizeValue, ownershipFromCanonical, parseHold, physicsFromCanonical, readValue, reconcileCollection, ref, schemaFromCanonical, server, sha256, sha256Hex, singleton, stableStringify, str, struct, toHex, track, u16, u32, u8, utf8Length, validateForDeploy, validateValue, walkDelta, walkSnapshot, writeValue };
|
|
1292
|
+
export { type AddOptions, type AnyDef, type AnyMessageMap, type AnyRpc, type AnySchema, type AnyType, type Body2dState, type BodyFieldsOf, type BroadcastProxy, ByteReader, ByteWriter, CANONICAL_VERSION, type ClientCallProxy, type ClientImplementations, type ClientRpcs, type ClientState, type Collection, type CollectionDesc, type CollectionDirty, type DecodeSnapshotOptions, type DecodedSnapshot, type DeepReadonly, type DeepWritable, type DefMap, type Delta, type DeltaCollection, type DeltaOp, type DirtySet, EntityCollection, type EntityDef, type EntityOptions, type EntityPhysics, type FieldDesc, type FieldMask, type Fields, type FromCanonicalOptions, type GridDesc, type GridOptions, type Header, ID_MAX_BYTES, type Implementations, type Infer, type InferFields, type InitFields, type InitOf, type InstanceOf, type Kind, MAX_MESSAGES, MAX_USER_RPCS, type MessageChannels, type MessageDesc, type MessageMap, type MessageNames, type MessageValue, type MessagesOf, type OwnableKeys, type Owned, type OwnershipDesc, type OwnershipLeave, type OwnershipLeaveAction, type OwnershipOptions, type OwnershipPosition, type OwnershipTransfer, PERSIST_BLOB_BYTES, PHYSICS_BODY_CHANNELS, type PhysicsBodyChannel, type PhysicsBodyMap, type PhysicsDesc, type PhysicsKeys, type PlainState, RESERVED_COLLECTION_NAMES, RESERVED_RPC_NAMES, RESERVED_RPC_PREFIX, type ReadOptions, type ReadonlyBodyFields, type ReadonlyCollection, type RecordDirty, type RoleOf, type RpcDef, type RpcDesc, type RpcDirection, type RpcMap, type RpcParams, type RpcReturns, type RpcSpec, SERVER_OWNER, SINGLETON_ID, type Schema, type SchemaChange, type SchemaDefs, type SchemaIssue, type SchemaMessages, type SchemaOptions, type SchemaRoles, type SchemaRpc, type ServerCallProxy, type ServerMessageChannels, type ServerOwnedKeys, type ServerRpcs, type SingletonDef, type SnapshotOptions, type State, type Tracked, type Type, type TypeDesc, type ValueSink, type Visibility, type VisibleKeys, type WalkEvents, angleFrom2d, applyChannel2d, applyDelta, bool, breakingIsWireOnly, bytesEqual, canonicalOwnership, canonicalPhysics, canonicalType, channelOf2d, client, cloneValue, collectionDirty, compileOwnership, compilePhysics, computeDirty, createDirtySet, createFieldMask, createState, decodeDelta, decodeDeltaFrom, decodeFields, decodeSnapshot, defaultRecord, defaultValue, defineSchema, describeType, diffSchemas, encodeDelta, encodeFields, encodeSnapshot, entity, enumOf, estimateSize, f32, f64, filterDirty, frozenProxy, i32, isDirtyEmpty, isWhole, list, markAdd, markField, markOwner, markPath, markRemove, maxEncodedBytes, mergeDirty, mergeMask, normalizeRecord, normalizeValue, ownershipFromCanonical, parseHold, physicsFromCanonical, readValue, reconcileCollection, ref, schemaFromCanonical, server, sha256, sha256Hex, singleton, stableStringify, str, struct, toHex, track, u16, u32, u8, utf8Length, validateForDeploy, validateValue, walkDelta, walkSnapshot, writeValue };
|
package/dist/index.js
CHANGED
|
@@ -704,6 +704,90 @@ function compileServerFields(name, kind, fields, o) {
|
|
|
704
704
|
}
|
|
705
705
|
return new Set([...out].sort());
|
|
706
706
|
}
|
|
707
|
+
var PERSIST_BLOB_BYTES = 4 * 1024;
|
|
708
|
+
function maxEncodedBytes(fields, names) {
|
|
709
|
+
const byName = new Map(fields.map((f) => [f.name, f]));
|
|
710
|
+
let total = 0;
|
|
711
|
+
for (const n of names) {
|
|
712
|
+
const f = byName.get(n);
|
|
713
|
+
if (f) total += worstCase(f.type);
|
|
714
|
+
}
|
|
715
|
+
return total;
|
|
716
|
+
}
|
|
717
|
+
function varintBytes(max) {
|
|
718
|
+
let bytes = 1;
|
|
719
|
+
let v = Math.max(0, Math.floor(max));
|
|
720
|
+
while (v > 127) {
|
|
721
|
+
v = Math.floor(v / 128);
|
|
722
|
+
bytes++;
|
|
723
|
+
}
|
|
724
|
+
return bytes;
|
|
725
|
+
}
|
|
726
|
+
function worstCase(d) {
|
|
727
|
+
const presence = d.opt ? 1 : 0;
|
|
728
|
+
switch (d.kind) {
|
|
729
|
+
case "bool":
|
|
730
|
+
case "u8":
|
|
731
|
+
case "enum":
|
|
732
|
+
return presence + 1;
|
|
733
|
+
case "u16":
|
|
734
|
+
return presence + 2;
|
|
735
|
+
case "u32":
|
|
736
|
+
case "i32":
|
|
737
|
+
case "f32":
|
|
738
|
+
return presence + 4;
|
|
739
|
+
case "f64":
|
|
740
|
+
return presence + 8;
|
|
741
|
+
case "str":
|
|
742
|
+
return presence + varintBytes(d.max) + d.max;
|
|
743
|
+
case "ref":
|
|
744
|
+
return presence + varintBytes(ID_MAX_BYTES) + ID_MAX_BYTES;
|
|
745
|
+
case "list":
|
|
746
|
+
return presence + varintBytes(d.max) + d.max * worstCase(d.item);
|
|
747
|
+
case "struct": {
|
|
748
|
+
let n = presence;
|
|
749
|
+
for (const fd of Object.values(d.fields)) n += worstCase(fd);
|
|
750
|
+
return n;
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
function compilePersist(name, fields, perPlayer, declared) {
|
|
755
|
+
if (declared === void 0) return void 0;
|
|
756
|
+
if (!Array.isArray(declared)) {
|
|
757
|
+
throw new Error(`${name}: persist must be an array of field names`);
|
|
758
|
+
}
|
|
759
|
+
if (!perPlayer) {
|
|
760
|
+
throw new Error(
|
|
761
|
+
`${name}: persist requires perPlayer \u2014 only a per-player record has a player to follow`
|
|
762
|
+
);
|
|
763
|
+
}
|
|
764
|
+
if (declared.length === 0) {
|
|
765
|
+
throw new Error(`${name}: persist is empty; leave it off instead`);
|
|
766
|
+
}
|
|
767
|
+
const known = new Map(fields.map((f) => [f.name, f]));
|
|
768
|
+
const out = [];
|
|
769
|
+
for (const field of declared) {
|
|
770
|
+
if (typeof field !== "string" || !known.has(field)) {
|
|
771
|
+
throw new Error(
|
|
772
|
+
`${name}: persist names ${JSON.stringify(field)}, which is not a field of ${name}`
|
|
773
|
+
);
|
|
774
|
+
}
|
|
775
|
+
if (out.includes(field)) throw new Error(`${name}: persist names ${field} twice`);
|
|
776
|
+
if (known.get(field).type.kind === "ref") {
|
|
777
|
+
throw new Error(
|
|
778
|
+
`${name}: persist names ${field}, an entity ref \u2014 a ref is an id inside one room and means nothing in the next one`
|
|
779
|
+
);
|
|
780
|
+
}
|
|
781
|
+
out.push(field);
|
|
782
|
+
}
|
|
783
|
+
const bytes = maxEncodedBytes(fields, out);
|
|
784
|
+
if (bytes > PERSIST_BLOB_BYTES) {
|
|
785
|
+
throw new Error(
|
|
786
|
+
`${name}: persist fields can encode up to ${bytes} bytes, over the 4 KB persistence cap`
|
|
787
|
+
);
|
|
788
|
+
}
|
|
789
|
+
return out.sort();
|
|
790
|
+
}
|
|
707
791
|
function checkFields(fields) {
|
|
708
792
|
const names = Object.keys(fields);
|
|
709
793
|
if (names.length === 0) throw new Error("entity/singleton needs at least one field");
|
|
@@ -762,6 +846,7 @@ function defineSchema(defs, options = {}) {
|
|
|
762
846
|
);
|
|
763
847
|
}
|
|
764
848
|
const ownership = o.ownership || perPlayer ? compileOwnership(name, def.kind, fields, o.ownership ?? {}, perPlayer) : void 0;
|
|
849
|
+
const persist = compilePersist(name, fields, perPlayer, o.persist);
|
|
765
850
|
const serverFields = compileServerFields(name, def.kind, fields, o);
|
|
766
851
|
return {
|
|
767
852
|
name,
|
|
@@ -772,6 +857,7 @@ function defineSchema(defs, options = {}) {
|
|
|
772
857
|
serverOwned: o.serverOwned === true,
|
|
773
858
|
serverFields,
|
|
774
859
|
perPlayer,
|
|
860
|
+
persist,
|
|
775
861
|
clientCreate: o.clientCreate === true,
|
|
776
862
|
ownership,
|
|
777
863
|
visibility: o.visibility ?? "all",
|
|
@@ -982,6 +1068,10 @@ function canonicalize(s) {
|
|
|
982
1068
|
// writes `upgrades` disagree about the world.
|
|
983
1069
|
serverFields: c.serverFields ? [...c.serverFields] : void 0,
|
|
984
1070
|
perPlayer: c.perPlayer ? true : void 0,
|
|
1071
|
+
// M8 proposal 13: the same only-when-declared rule again. In the hash when present because
|
|
1072
|
+
// it is hydration shape: a room that fills a field from a blob and a client that does not
|
|
1073
|
+
// expect it to be filled disagree about the world.
|
|
1074
|
+
persist: c.persist ? [...c.persist] : void 0,
|
|
985
1075
|
clientCreate: c.clientCreate ? true : void 0,
|
|
986
1076
|
ownership: c.ownership ? canonicalOwnership(c.ownership) : void 0
|
|
987
1077
|
// ---- end M6 lane K ----
|
|
@@ -1250,6 +1340,11 @@ function entityOptions(e, name) {
|
|
|
1250
1340
|
const physics = e.physics === void 0 || e.physics === null ? void 0 : physicsFromCanonical(`schemaFromCanonical: ${name}`, e.physics);
|
|
1251
1341
|
const grid = e.grid === void 0 || e.grid === null ? void 0 : gridFromCanonical(name, objectAt(e, "grid"));
|
|
1252
1342
|
const ownership = e.ownership === void 0 || e.ownership === null ? void 0 : ownershipFromCanonical(`schemaFromCanonical: ${name}`, e.ownership);
|
|
1343
|
+
const persist = e.persist === void 0 || e.persist === null ? void 0 : arrayAtRaw(e, "persist").map((f) => {
|
|
1344
|
+
if (typeof f !== "string")
|
|
1345
|
+
throw new Error(`schemaFromCanonical: ${name}: persist must be strings`);
|
|
1346
|
+
return f;
|
|
1347
|
+
});
|
|
1253
1348
|
const serverFields = e.serverFields === void 0 || e.serverFields === null ? void 0 : arrayAtRaw(e, "serverFields").map((f) => {
|
|
1254
1349
|
if (typeof f !== "string")
|
|
1255
1350
|
throw new Error(`schemaFromCanonical: ${name}: serverFields must be strings`);
|
|
@@ -1263,6 +1358,7 @@ function entityOptions(e, name) {
|
|
|
1263
1358
|
...physics !== void 0 ? { physics } : {},
|
|
1264
1359
|
// ---- M6 lane K: stateful relay ----
|
|
1265
1360
|
...e.perPlayer === true ? { perPlayer: true } : {},
|
|
1361
|
+
...persist !== void 0 ? { persist } : {},
|
|
1266
1362
|
...e.clientCreate === true ? { clientCreate: true } : {},
|
|
1267
1363
|
...ownership !== void 0 ? { ownership } : {},
|
|
1268
1364
|
// ---- end M6 lane K ----
|
|
@@ -3348,6 +3444,7 @@ export {
|
|
|
3348
3444
|
ID_MAX_BYTES,
|
|
3349
3445
|
MAX_MESSAGES,
|
|
3350
3446
|
MAX_USER_RPCS,
|
|
3447
|
+
PERSIST_BLOB_BYTES,
|
|
3351
3448
|
PHYSICS_BODY_CHANNELS,
|
|
3352
3449
|
RESERVED_COLLECTION_NAMES,
|
|
3353
3450
|
RESERVED_RPC_NAMES,
|
|
@@ -3401,6 +3498,7 @@ export {
|
|
|
3401
3498
|
markOwner,
|
|
3402
3499
|
markPath,
|
|
3403
3500
|
markRemove,
|
|
3501
|
+
maxEncodedBytes,
|
|
3404
3502
|
mergeDirty,
|
|
3405
3503
|
mergeMask,
|
|
3406
3504
|
normalizeRecord,
|