@colyseus/schema 4.0.20 → 5.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/README.md +2 -0
- package/build/Metadata.d.ts +55 -2
- package/build/Reflection.d.ts +24 -30
- package/build/Schema.d.ts +70 -9
- package/build/annotations.d.ts +56 -13
- package/build/codegen/cli.cjs +84 -67
- package/build/codegen/cli.cjs.map +1 -1
- package/build/decoder/DecodeOperation.d.ts +48 -5
- package/build/decoder/Decoder.d.ts +2 -2
- package/build/decoder/strategy/Callbacks.d.ts +1 -1
- package/build/encoder/ChangeRecorder.d.ts +107 -0
- package/build/encoder/ChangeTree.d.ts +218 -69
- package/build/encoder/EncodeDescriptor.d.ts +63 -0
- package/build/encoder/EncodeOperation.d.ts +25 -2
- package/build/encoder/Encoder.d.ts +59 -3
- package/build/encoder/MapJournal.d.ts +62 -0
- package/build/encoder/RefIdAllocator.d.ts +35 -0
- package/build/encoder/Root.d.ts +94 -13
- package/build/encoder/StateView.d.ts +116 -8
- package/build/encoder/changeTree/inheritedFlags.d.ts +34 -0
- package/build/encoder/changeTree/liveIteration.d.ts +3 -0
- package/build/encoder/changeTree/parentChain.d.ts +24 -0
- package/build/encoder/changeTree/treeAttachment.d.ts +13 -0
- package/build/encoder/streaming.d.ts +73 -0
- package/build/encoder/subscriptions.d.ts +25 -0
- package/build/index.cjs +5202 -1552
- package/build/index.cjs.map +1 -1
- package/build/index.d.ts +7 -3
- package/build/index.js +5202 -1552
- package/build/index.mjs +5193 -1552
- package/build/index.mjs.map +1 -1
- package/build/input/InputDecoder.d.ts +32 -0
- package/build/input/InputEncoder.d.ts +117 -0
- package/build/input/index.cjs +7429 -0
- package/build/input/index.cjs.map +1 -0
- package/build/input/index.d.ts +3 -0
- package/build/input/index.mjs +7426 -0
- package/build/input/index.mjs.map +1 -0
- package/build/types/HelperTypes.d.ts +22 -8
- package/build/types/TypeContext.d.ts +9 -0
- package/build/types/builder.d.ts +162 -0
- package/build/types/custom/ArraySchema.d.ts +25 -4
- package/build/types/custom/CollectionSchema.d.ts +30 -2
- package/build/types/custom/MapSchema.d.ts +52 -3
- package/build/types/custom/SetSchema.d.ts +32 -2
- package/build/types/custom/StreamSchema.d.ts +114 -0
- package/build/types/symbols.d.ts +48 -5
- package/package.json +9 -3
- package/src/Metadata.ts +258 -31
- package/src/Reflection.ts +15 -13
- package/src/Schema.ts +176 -134
- package/src/annotations.ts +308 -236
- package/src/bench_bloat.ts +173 -0
- package/src/bench_decode.ts +221 -0
- package/src/bench_decode_mem.ts +165 -0
- package/src/bench_encode.ts +108 -0
- package/src/bench_init.ts +150 -0
- package/src/bench_static.ts +109 -0
- package/src/bench_stream.ts +295 -0
- package/src/bench_view_cmp.ts +142 -0
- package/src/codegen/languages/csharp.ts +0 -24
- package/src/codegen/parser.ts +83 -61
- package/src/decoder/DecodeOperation.ts +168 -63
- package/src/decoder/Decoder.ts +20 -10
- package/src/decoder/ReferenceTracker.ts +4 -0
- package/src/decoder/strategy/Callbacks.ts +30 -26
- package/src/decoder/strategy/getDecoderStateCallbacks.ts +16 -13
- package/src/encoder/ChangeRecorder.ts +276 -0
- package/src/encoder/ChangeTree.ts +674 -519
- package/src/encoder/EncodeDescriptor.ts +213 -0
- package/src/encoder/EncodeOperation.ts +107 -65
- package/src/encoder/Encoder.ts +630 -119
- package/src/encoder/MapJournal.ts +124 -0
- package/src/encoder/RefIdAllocator.ts +68 -0
- package/src/encoder/Root.ts +247 -120
- package/src/encoder/StateView.ts +592 -121
- package/src/encoder/changeTree/inheritedFlags.ts +217 -0
- package/src/encoder/changeTree/liveIteration.ts +74 -0
- package/src/encoder/changeTree/parentChain.ts +131 -0
- package/src/encoder/changeTree/treeAttachment.ts +171 -0
- package/src/encoder/streaming.ts +232 -0
- package/src/encoder/subscriptions.ts +71 -0
- package/src/index.ts +15 -3
- package/src/input/InputDecoder.ts +57 -0
- package/src/input/InputEncoder.ts +303 -0
- package/src/input/index.ts +3 -0
- package/src/types/HelperTypes.ts +21 -9
- package/src/types/TypeContext.ts +14 -2
- package/src/types/builder.ts +285 -0
- package/src/types/custom/ArraySchema.ts +210 -197
- package/src/types/custom/CollectionSchema.ts +115 -35
- package/src/types/custom/MapSchema.ts +162 -58
- package/src/types/custom/SetSchema.ts +128 -39
- package/src/types/custom/StreamSchema.ts +310 -0
- package/src/types/symbols.ts +54 -6
- package/src/utils.ts +4 -6
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import { OPERATION } from "../encoding/spec.js";
|
|
2
|
+
import { $numFields, $values, $changes } from "../types/symbols.js";
|
|
3
|
+
import { encode } from "../encoding/encode.js";
|
|
4
|
+
import { Encoder } from "../encoder/Encoder.js";
|
|
5
|
+
import { getEncodeDescriptor, type EncodeDescriptor } from "../encoder/EncodeDescriptor.js";
|
|
6
|
+
import type { Schema } from "../Schema.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Delivery-channel hint. Controls the wire layout:
|
|
10
|
+
* - `"reliable"`: single input per packet, no framing — bytes are
|
|
11
|
+
* wire-compatible with the standard {@link Decoder}.
|
|
12
|
+
* - `"unreliable"`: ring buffer of the last `historySize` inputs packed
|
|
13
|
+
* into one packet, each prefixed with a varint length. Gives the
|
|
14
|
+
* receiver redundancy against dropped packets. Use
|
|
15
|
+
* {@link InputDecoder.decodeAll} on the receiving end.
|
|
16
|
+
*/
|
|
17
|
+
export type InputMode = "reliable" | "unreliable";
|
|
18
|
+
|
|
19
|
+
export interface InputEncoderOptions {
|
|
20
|
+
/** Defaults to `"reliable"`. */
|
|
21
|
+
mode?: InputMode;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Unreliable-mode only. Number of past inputs to pack into each
|
|
25
|
+
* packet as redundancy against drops. Default: 3. Ignored in
|
|
26
|
+
* reliable mode (always exactly one input per packet).
|
|
27
|
+
*/
|
|
28
|
+
historySize?: number;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* When `true`, `encode()` emits only fields that changed since the
|
|
32
|
+
* previous call. First call (or first after `reset()`) still emits
|
|
33
|
+
* a full snapshot since there's no baseline to diff against.
|
|
34
|
+
*
|
|
35
|
+
* **Reliable mode:** returns an empty `Uint8Array` when nothing
|
|
36
|
+
* changed — caller can skip sending.
|
|
37
|
+
*
|
|
38
|
+
* **Unreliable mode:** no-change ticks don't push a new ring slot
|
|
39
|
+
* (avoids bloating the ring with empties); the existing ring is
|
|
40
|
+
* still re-emitted for redundancy. Wire ops use absolute values so
|
|
41
|
+
* cross-packet re-application is per-field idempotent.
|
|
42
|
+
*
|
|
43
|
+
* Decoder side is unchanged in either case: `(index|ADD)` wire ops
|
|
44
|
+
* apply to the bound instance; fields absent from a packet stay at
|
|
45
|
+
* their previously decoded value.
|
|
46
|
+
*/
|
|
47
|
+
delta?: boolean;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Override the reliable-mode output buffer. Default: 256 bytes,
|
|
51
|
+
* auto-grown on overflow.
|
|
52
|
+
*/
|
|
53
|
+
buffer?: Uint8Array;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const DEFAULT_SLOT_SIZE = 256;
|
|
57
|
+
const LENGTH_PREFIX_WORST_CASE = 5; // varint max for a uint32 length.
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Bound single-struct encoder for client→server input packets. Holds a
|
|
61
|
+
* reference to a Schema instance and produces wire-compatible bytes.
|
|
62
|
+
*
|
|
63
|
+
* **Reliable mode** emits one snapshot per `encode()`. The bytes decode
|
|
64
|
+
* cleanly through the standard {@link Decoder}.
|
|
65
|
+
*
|
|
66
|
+
* **Unreliable mode** pushes each snapshot onto a ring buffer of size
|
|
67
|
+
* `historySize` and emits the last N snapshots in one packet, each
|
|
68
|
+
* framed with a varint length prefix. Use {@link InputDecoder.decodeAll}
|
|
69
|
+
* to walk the framed packet on the receiving end.
|
|
70
|
+
*
|
|
71
|
+
* **Delta** (`delta: true`, any mode) wraps a standard {@link Encoder}
|
|
72
|
+
* and emits only fields that changed since the last call, via the
|
|
73
|
+
* setter-populated ChangeTree dirty set.
|
|
74
|
+
*
|
|
75
|
+
* Flat primitive fields only. Nested Schema / collection fields throw
|
|
76
|
+
* at construction.
|
|
77
|
+
*/
|
|
78
|
+
export class InputEncoder<T extends Schema = any> {
|
|
79
|
+
readonly instance: T;
|
|
80
|
+
readonly mode: InputMode;
|
|
81
|
+
readonly historySize: number;
|
|
82
|
+
readonly delta: boolean;
|
|
83
|
+
|
|
84
|
+
private readonly _desc: EncodeDescriptor;
|
|
85
|
+
private readonly _numFields: number;
|
|
86
|
+
|
|
87
|
+
// Reliable-mode output + its iterator. Auto-grown on overflow, so
|
|
88
|
+
// not `readonly` — `_encodeFull()` may swap in a larger buffer.
|
|
89
|
+
private _buffer: Uint8Array;
|
|
90
|
+
private readonly _it = { offset: 0 };
|
|
91
|
+
|
|
92
|
+
// Unreliable-mode ring state. `_outBuffer` is where the concatenated
|
|
93
|
+
// packet lives; `_slots` / `_slotLens` hold each snapshot's bytes.
|
|
94
|
+
private _slots?: Uint8Array[];
|
|
95
|
+
private readonly _slotLens?: number[];
|
|
96
|
+
private _slotHead: number = 0;
|
|
97
|
+
private _slotCount: number = 0;
|
|
98
|
+
private _outBuffer?: Uint8Array;
|
|
99
|
+
|
|
100
|
+
// Delta-mode delegate. Setter `$track` calls populate its
|
|
101
|
+
// ChangeTree; `encode()` drains dirty fields into its buffer.
|
|
102
|
+
private readonly _encoder?: Encoder<T>;
|
|
103
|
+
|
|
104
|
+
constructor(instance: T, options: InputEncoderOptions = {}) {
|
|
105
|
+
this.instance = instance;
|
|
106
|
+
this.mode = options.mode ?? "reliable";
|
|
107
|
+
this.delta = options.delta ?? false;
|
|
108
|
+
this.historySize = this.mode === "unreliable"
|
|
109
|
+
? Math.max(1, options.historySize ?? 3)
|
|
110
|
+
: 1;
|
|
111
|
+
|
|
112
|
+
// Resolve + validate schema metadata up front.
|
|
113
|
+
this._desc = getEncodeDescriptor(instance);
|
|
114
|
+
const numFields = this._desc.metadata?.[$numFields];
|
|
115
|
+
if (numFields === undefined) {
|
|
116
|
+
throw new Error(`InputEncoder: '${instance.constructor.name}' has no fields`);
|
|
117
|
+
}
|
|
118
|
+
this._numFields = numFields;
|
|
119
|
+
for (let i = 0; i <= numFields; i++) {
|
|
120
|
+
if (this._desc.names[i] !== undefined && this._desc.encoders[i] === undefined) {
|
|
121
|
+
throw new Error(
|
|
122
|
+
`InputEncoder: non-primitive field '${this._desc.names[i]}' on '${instance.constructor.name}' is not supported. Use Encoder for state containing refs/collections.`
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
this._buffer = options.buffer ?? new Uint8Array(DEFAULT_SLOT_SIZE);
|
|
128
|
+
|
|
129
|
+
if (this.mode === "unreliable") {
|
|
130
|
+
this._slots = new Array(this.historySize);
|
|
131
|
+
this._slotLens = new Array(this.historySize).fill(0);
|
|
132
|
+
for (let i = 0; i < this.historySize; i++) {
|
|
133
|
+
this._slots[i] = new Uint8Array(DEFAULT_SLOT_SIZE);
|
|
134
|
+
}
|
|
135
|
+
this._outBuffer = new Uint8Array(
|
|
136
|
+
(DEFAULT_SLOT_SIZE + LENGTH_PREFIX_WORST_CASE) * this.historySize,
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (this.delta) {
|
|
141
|
+
// Delegate to the standard Encoder — it attaches a Root and
|
|
142
|
+
// drains the setter-populated dirty set on each encode().
|
|
143
|
+
// Tracking stays enabled so setters keep populating it.
|
|
144
|
+
this._encoder = new Encoder<T>(instance);
|
|
145
|
+
} else {
|
|
146
|
+
// Full mode reads `$values` directly; setter-side tracking
|
|
147
|
+
// is pure overhead, so pause it.
|
|
148
|
+
instance.pauseTracking();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Encode the bound instance. Returns a subarray of an internal
|
|
154
|
+
* buffer — copy if retaining across calls.
|
|
155
|
+
*
|
|
156
|
+
* Output shape by configuration:
|
|
157
|
+
* - `reliable` + full: one snapshot's worth of bytes.
|
|
158
|
+
* - `reliable` + delta: only changed fields, or empty when nothing
|
|
159
|
+
* changed.
|
|
160
|
+
* - `unreliable` + full: ring of last `historySize` snapshots,
|
|
161
|
+
* length-framed per slot.
|
|
162
|
+
* - `unreliable` + delta: ring of last `historySize` deltas. No-
|
|
163
|
+
* change ticks don't push a new slot but still re-emit the ring.
|
|
164
|
+
* Empty only until the first change has been pushed.
|
|
165
|
+
*
|
|
166
|
+
* Buffers auto-grow on overflow; a one-time `console.warn` is
|
|
167
|
+
* emitted the first time it happens.
|
|
168
|
+
*/
|
|
169
|
+
encode(): Uint8Array {
|
|
170
|
+
const blob = this.delta ? this._produceDelta() : this._produceFull();
|
|
171
|
+
return this.mode === "reliable" ? blob : this._pushAndEmitRing(blob);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Reset the encoder's internal state:
|
|
176
|
+
* - Unreliable mode: drops the ring buffer.
|
|
177
|
+
* - Delta mode: re-marks every currently populated field as dirty,
|
|
178
|
+
* so the next `encode()` emits a fresh full snapshot.
|
|
179
|
+
*
|
|
180
|
+
* Useful on disconnect / reconnect / scene transitions.
|
|
181
|
+
*/
|
|
182
|
+
reset(): void {
|
|
183
|
+
this._slotHead = 0;
|
|
184
|
+
this._slotCount = 0;
|
|
185
|
+
if (this.delta) {
|
|
186
|
+
this._encoder!.discardChanges();
|
|
187
|
+
const tree = (this.instance as any)[$changes];
|
|
188
|
+
const values = (this.instance as any)[$values];
|
|
189
|
+
for (let i = 0; i <= this._numFields; i++) {
|
|
190
|
+
if (values[i] === undefined || values[i] === null) continue;
|
|
191
|
+
tree.markDirty(i);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// ────────────────────────────────────────────────────────────────────
|
|
197
|
+
// Blob producers — return a single snapshot (or delta) of the
|
|
198
|
+
// current instance. Caller routes by mode.
|
|
199
|
+
// ────────────────────────────────────────────────────────────────────
|
|
200
|
+
|
|
201
|
+
/** Write every populated primitive field into `_buffer`. */
|
|
202
|
+
private _produceFull(): Uint8Array {
|
|
203
|
+
let buf = this._buffer;
|
|
204
|
+
const it = this._it;
|
|
205
|
+
this._writeFields(buf, it);
|
|
206
|
+
if (it.offset > buf.byteLength) {
|
|
207
|
+
buf = this._buffer = InputEncoder._grow(buf, it.offset, "reliable encode");
|
|
208
|
+
this._writeFields(buf, it);
|
|
209
|
+
}
|
|
210
|
+
return buf.subarray(0, it.offset);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** Delegate to the wrapped Encoder, then clear its dirty set. */
|
|
214
|
+
private _produceDelta(): Uint8Array {
|
|
215
|
+
const bytes = this._encoder!.encode();
|
|
216
|
+
this._encoder!.discardChanges();
|
|
217
|
+
return bytes;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/** Emit every populated field as `(index|ADD)` + value. */
|
|
221
|
+
private _writeFields(buf: Uint8Array, it: { offset: number }): void {
|
|
222
|
+
const values = (this.instance as any)[$values];
|
|
223
|
+
const encoders = this._desc.encoders;
|
|
224
|
+
it.offset = 0;
|
|
225
|
+
for (let i = 0; i <= this._numFields; i++) {
|
|
226
|
+
const value = values[i];
|
|
227
|
+
if (value === undefined || value === null) continue;
|
|
228
|
+
buf[it.offset++] = (i | OPERATION.ADD) & 255;
|
|
229
|
+
(encoders[i] as (b: Uint8Array, v: any, it: any) => void)(buf, value, it);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// ────────────────────────────────────────────────────────────────────
|
|
234
|
+
// Ring — push blob to the current slot, concat (oldest → newest)
|
|
235
|
+
// into the output buffer, return the framed packet.
|
|
236
|
+
// ────────────────────────────────────────────────────────────────────
|
|
237
|
+
|
|
238
|
+
private _pushAndEmitRing(blob: Uint8Array): Uint8Array {
|
|
239
|
+
// Empty blob = no-change delta tick. Skip the push but re-emit
|
|
240
|
+
// the existing ring (redundancy). Empty ring → empty output.
|
|
241
|
+
if (blob.length === 0) {
|
|
242
|
+
if (this._slotCount === 0) return this._outBuffer!.subarray(0, 0);
|
|
243
|
+
return this._emitRing();
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Copy blob into the current slot, growing it if needed.
|
|
247
|
+
let slot = this._slots![this._slotHead];
|
|
248
|
+
if (blob.length > slot.byteLength) {
|
|
249
|
+
slot = this._slots![this._slotHead] = InputEncoder._grow(
|
|
250
|
+
slot, blob.length, "unreliable ring slot",
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
slot.set(blob);
|
|
254
|
+
this._slotLens![this._slotHead] = blob.length;
|
|
255
|
+
this._slotHead = (this._slotHead + 1) % this.historySize;
|
|
256
|
+
if (this._slotCount < this.historySize) this._slotCount++;
|
|
257
|
+
|
|
258
|
+
return this._emitRing();
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
private _emitRing(): Uint8Array {
|
|
262
|
+
// Upper bound: sum of slot byte counts + per-slot varint length.
|
|
263
|
+
let needed = 0;
|
|
264
|
+
for (let i = 0; i < this._slotCount; i++) {
|
|
265
|
+
needed += this._slotLens![i] + LENGTH_PREFIX_WORST_CASE;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
let out = this._outBuffer!;
|
|
269
|
+
if (needed > out.byteLength) {
|
|
270
|
+
out = this._outBuffer = InputEncoder._grow(out, needed, "unreliable output packet");
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const outIt = { offset: 0 };
|
|
274
|
+
const oldest = (this._slotHead - this._slotCount + this.historySize) % this.historySize;
|
|
275
|
+
for (let i = 0; i < this._slotCount; i++) {
|
|
276
|
+
const idx = (oldest + i) % this.historySize;
|
|
277
|
+
const len = this._slotLens![idx];
|
|
278
|
+
encode.number(out, len, outIt);
|
|
279
|
+
out.set(this._slots![idx].subarray(0, len), outIt.offset);
|
|
280
|
+
outIt.offset += len;
|
|
281
|
+
}
|
|
282
|
+
return out.subarray(0, outIt.offset);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// ────────────────────────────────────────────────────────────────────
|
|
286
|
+
// Buffer growth. Uint8Array writes past `byteLength` silently drop
|
|
287
|
+
// but `it.offset` still advances — callers detect overflow via the
|
|
288
|
+
// `offset > byteLength` check and re-encode into the grown buffer.
|
|
289
|
+
// ────────────────────────────────────────────────────────────────────
|
|
290
|
+
|
|
291
|
+
private static _warned = false;
|
|
292
|
+
private static _grow(buf: Uint8Array, needed: number, where: string): Uint8Array {
|
|
293
|
+
const newSize = Math.max(needed, buf.byteLength * 2);
|
|
294
|
+
if (!InputEncoder._warned) {
|
|
295
|
+
InputEncoder._warned = true;
|
|
296
|
+
console.warn(
|
|
297
|
+
`@colyseus/schema/input: InputEncoder buffer overflow in ${where}. ` +
|
|
298
|
+
`Growing to ${newSize} bytes. Pass a larger { buffer } option to avoid this at runtime.`
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
return new Uint8Array(newSize);
|
|
302
|
+
}
|
|
303
|
+
}
|
package/src/types/HelperTypes.ts
CHANGED
|
@@ -4,6 +4,8 @@ import type { ArraySchema } from "./custom/ArraySchema.js";
|
|
|
4
4
|
import type { CollectionSchema } from "./custom/CollectionSchema.js";
|
|
5
5
|
import type { MapSchema } from "./custom/MapSchema.js";
|
|
6
6
|
import type { SetSchema } from "./custom/SetSchema.js";
|
|
7
|
+
import type { StreamSchema } from "./custom/StreamSchema.js";
|
|
8
|
+
import type { FieldBuilder } from "./builder.js";
|
|
7
9
|
|
|
8
10
|
export type Constructor<T = {}> = new (...args: any[]) => T;
|
|
9
11
|
|
|
@@ -20,8 +22,11 @@ export interface Collection<K = any, V = any, IT = V> {
|
|
|
20
22
|
entries(): IterableIterator<[K, V]>;
|
|
21
23
|
}
|
|
22
24
|
|
|
23
|
-
export type InferValueType<T
|
|
24
|
-
|
|
25
|
+
export type InferValueType<T> =
|
|
26
|
+
// FieldBuilder<V> unwraps to V (used by the zod-style schema() API)
|
|
27
|
+
T extends FieldBuilder<infer V> ? V
|
|
28
|
+
|
|
29
|
+
: T extends "string" ? string
|
|
25
30
|
: T extends "number" ? number
|
|
26
31
|
: T extends "int8" ? number
|
|
27
32
|
: T extends "uint8" ? number
|
|
@@ -42,6 +47,8 @@ export type InferValueType<T extends DefinitionType> =
|
|
|
42
47
|
: T extends { type: { map: infer ChildType } } ? (ChildType extends Record<string | number, string | number> ? MapSchema<ChildType[keyof ChildType]> : MapSchema<ChildType>) // TS ENUM
|
|
43
48
|
: T extends { type: { set: infer ChildType } } ? (ChildType extends Record<string | number, string | number> ? SetSchema<ChildType[keyof ChildType]> : SetSchema<ChildType>) // TS ENUM
|
|
44
49
|
: T extends { type: { collection: infer ChildType } } ? (ChildType extends Record<string | number, string | number> ? CollectionSchema<ChildType[keyof ChildType]> : CollectionSchema<ChildType>) // TS ENUM
|
|
50
|
+
: T extends { type: { stream: infer ChildType extends Constructor } } ? StreamSchema<InstanceType<ChildType>>
|
|
51
|
+
: T extends { type: { stream: infer ChildType } } ? StreamSchema<ChildType>
|
|
45
52
|
: T extends { type: infer ChildType } ? (ChildType extends Record<string | number, string | number> ? ChildType[keyof ChildType] : ChildType) // TS ENUM
|
|
46
53
|
|
|
47
54
|
// Handle direct array patterns
|
|
@@ -63,6 +70,9 @@ export type InferValueType<T extends DefinitionType> =
|
|
|
63
70
|
: T extends { collection: infer ChildType extends RawPrimitiveType } ? CollectionSchema<InferValueType<ChildType>> // primitive types
|
|
64
71
|
: T extends { collection: infer ChildType } ? (ChildType extends Record<string | number, string | number> ? CollectionSchema<ChildType[keyof ChildType]> : CollectionSchema<ChildType>) // TS ENUM
|
|
65
72
|
|
|
73
|
+
: T extends { stream: infer ChildType extends Constructor } ? StreamSchema<InstanceType<ChildType>>
|
|
74
|
+
: T extends { stream: infer ChildType } ? StreamSchema<ChildType>
|
|
75
|
+
|
|
66
76
|
// Handle direct types
|
|
67
77
|
: T extends Constructor ? InstanceType<T>
|
|
68
78
|
: T extends Record<string | number, string | number> ? T[keyof T] // TS ENUM
|
|
@@ -70,10 +80,12 @@ export type InferValueType<T extends DefinitionType> =
|
|
|
70
80
|
|
|
71
81
|
: never;
|
|
72
82
|
|
|
73
|
-
export type InferSchemaInstanceType<T
|
|
74
|
-
[K in keyof T]: T[K] extends
|
|
75
|
-
?
|
|
76
|
-
:
|
|
83
|
+
export type InferSchemaInstanceType<T> = {
|
|
84
|
+
[K in keyof T]: T[K] extends FieldBuilder<any>
|
|
85
|
+
? InferValueType<T[K]>
|
|
86
|
+
: T[K] extends (...args: any[]) => any
|
|
87
|
+
? (T[K] extends new (...args: any[]) => any ? InferValueType<T[K]> : T[K])
|
|
88
|
+
: InferValueType<T[K]>
|
|
77
89
|
} & Schema;
|
|
78
90
|
|
|
79
91
|
export type NonFunctionProps<T> = Omit<T, {
|
|
@@ -95,8 +107,8 @@ export type NonFunctionNonPrimitivePropNames<T> = {
|
|
|
95
107
|
// Helper to recursively convert Schema instances to their JSON representation
|
|
96
108
|
type ToJSONValue<U> = U extends Schema ? ToJSON<U> : PrimitiveStringToType<U>;
|
|
97
109
|
|
|
98
|
-
export type ToJSON<T> = {
|
|
99
|
-
[K in keyof T
|
|
110
|
+
export type ToJSON<T> = NonFunctionProps<{
|
|
111
|
+
[K in keyof T]:
|
|
100
112
|
T[K] extends MapSchema<infer U> ? Record<string, ToJSONValue<U>>
|
|
101
113
|
: T[K] extends Map<string, infer U> ? Record<string, ToJSONValue<U>>
|
|
102
114
|
: T[K] extends ArraySchema<infer U> ? ToJSONValue<U>[]
|
|
@@ -104,7 +116,7 @@ export type ToJSON<T> = {
|
|
|
104
116
|
: T[K] extends CollectionSchema<infer U> ? ToJSONValue<U>[]
|
|
105
117
|
: T[K] extends Schema ? ToJSON<T[K]>
|
|
106
118
|
: T[K]
|
|
107
|
-
}
|
|
119
|
+
}>;
|
|
108
120
|
|
|
109
121
|
// Helper type to check if T is exactly 'never' (meaning no InitProps was provided)
|
|
110
122
|
export type IsNever<T> = [T] extends [never] ? true : false;
|
package/src/types/TypeContext.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Metadata } from "../Metadata.js";
|
|
2
2
|
import { Schema } from "../Schema.js";
|
|
3
|
-
import { $viewFieldIndexes } from "./symbols.js";
|
|
3
|
+
import { $streamFieldIndexes, $viewFieldIndexes } from "./symbols.js";
|
|
4
4
|
|
|
5
5
|
export class TypeContext {
|
|
6
6
|
types: { [id: number]: typeof Schema; } = {};
|
|
@@ -8,6 +8,15 @@ export class TypeContext {
|
|
|
8
8
|
|
|
9
9
|
hasFilters: boolean = false;
|
|
10
10
|
parentFiltered: {[typeIdAndParentIndex: string]: boolean} = {};
|
|
11
|
+
/**
|
|
12
|
+
* True iff `parentFiltered` has at least one entry. Flipped on by
|
|
13
|
+
* `registerFilteredByParent` and read in `checkInheritedFlags` as a
|
|
14
|
+
* cheap gate to skip the string-keyed `parentFiltered[key]` lookup
|
|
15
|
+
* when no class has registered filter inheritance via ancestry — the
|
|
16
|
+
* common case when @view tags exist only on sibling fields, not
|
|
17
|
+
* along any attachment chain.
|
|
18
|
+
*/
|
|
19
|
+
hasParentFilteredEntries: boolean = false;
|
|
11
20
|
|
|
12
21
|
/**
|
|
13
22
|
* For inheritance support
|
|
@@ -100,7 +109,9 @@ export class TypeContext {
|
|
|
100
109
|
const metadata: Metadata = (klass[Symbol.metadata] ??= {} as Metadata);
|
|
101
110
|
|
|
102
111
|
// if any schema/field has filters, mark "context" as having filters.
|
|
103
|
-
|
|
112
|
+
// Stream fields are always view-scoped — treat like @view tags for
|
|
113
|
+
// filter inheritance.
|
|
114
|
+
if (metadata[$viewFieldIndexes] || metadata[$streamFieldIndexes]) {
|
|
104
115
|
this.hasFilters = true;
|
|
105
116
|
}
|
|
106
117
|
|
|
@@ -142,6 +153,7 @@ export class TypeContext {
|
|
|
142
153
|
|
|
143
154
|
key += `-${parentIndex}`;
|
|
144
155
|
this.parentFiltered[key] = true;
|
|
156
|
+
this.hasParentFilteredEntries = true;
|
|
145
157
|
}
|
|
146
158
|
|
|
147
159
|
debug() {
|