@lsmsgpack/client 0.0.0-stage → 2026.10.2901

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,738 @@
1
+ // Reads application/x-lsmsgpack payloads (the schema, or a reference to it, followed by the body) into complete objects:
2
+ // every property of the schema entry is set, the ones the writer left out (nil) get their default (of the schema or the type), the ones missing after the end of an array
3
+ // keep what the class sets (plain objects: the default).
4
+ // An object that came with a type id gets its type name as the property $type (as System.Text.Json names it): the encoder writes it back as the type id.
5
+ import { MissingSchemaError, MsgPackError } from './errors.js';
6
+ import { MsgPackExtension, MsgPackReader } from './reader.js';
7
+ import { nameFunction } from './names.js';
8
+ import { AnyType, Schema } from './schema.js';
9
+ import { toHex } from './sha256.js';
10
+ import { convertDecimal, dateOnlyText, decodeDecimal, EmptyGuid, guidString, MinDateTimeMs, offsetText, timeOnlyText, timeSpanText } from './values.js';
11
+ let codeAllowed = true; // false once new Function failed (Content-Security-Policy without 'unsafe-eval')
12
+ /** The property that holds the type name of an object read with a type id, and that the encoder writes as the type id. */
13
+ export const TypeProperty = '$type';
14
+ /**
15
+ * The generated create and fill of a plan (see DecodeOptions.generateCode), undefined when code cannot be generated. Instances of classes only get the fill (create is new).
16
+ * The fill has the read functions of the properties as constants: each call site then always calls the same function (reads[t](r) was one call site for all of them).
17
+ */
18
+ function generate(names, reads, literals, nil) {
19
+ if (!codeAllowed || names.length === 0 || names.length > 512 || names.includes('__proto__') || names.includes(TypeProperty))
20
+ return undefined; // an object literal would set the prototype, or have $type twice
21
+ const literal = names.map((n, t) => `${JSON.stringify(n)}: v[${t}]`).join(', ');
22
+ let fill = `const ${names.map((_, t) => `read${t} = reads[${t}]`).join(', ')};\nreturn function fill(r, o, n) {\nconst b = r.bytes;\n`;
23
+ names.forEach((n, t) => {
24
+ // a nil is the value the writer left out: already there in an object made with the defaults, set on an instance of a class (its constructor set its own)
25
+ const left = nil === undefined ? 'r.pos++;' : `{ r.pos++; o[${JSON.stringify(n)}] = nil(${t}); }`;
26
+ fill += `if (n === ${t}) return;\nif (b[r.pos] === 0xc0) ${left} else o[${JSON.stringify(n)}] = read${t}(r);\n`;
27
+ });
28
+ fill += `for (let k = ${names.length}; k < n; k++) r.value();\n};`;
29
+ try {
30
+ const fillFn = new Function('reads', 'nil', fill)(reads, nil);
31
+ if (!literals)
32
+ return { fill: fillFn };
33
+ return {
34
+ create: new Function('v', `return { ${literal} };`),
35
+ createTyped: new Function('v', 't', `return { ${JSON.stringify(TypeProperty)}: t, ${literal} };`), // $type first, as System.Text.Json writes it
36
+ fill: fillFn,
37
+ };
38
+ }
39
+ catch {
40
+ codeAllowed = false;
41
+ return undefined;
42
+ }
43
+ }
44
+ /** A value of generateReadArray that is still to be made (a value the data did not have: null is a value). */
45
+ const Unread = Object.freeze({});
46
+ /**
47
+ * Generates the reading of an object of the array layout: the values into variables first (the defaults where the data left them out), then the object at once,
48
+ * so its properties are written once (a literal, or for a model a constructor function with the class's prototype: new of the class would make its initial values
49
+ * only to have them replaced). The defaults that are made per object (makers) are made after the values, as Plan.complete does.
50
+ * A nil is the value the writer left out (nils, nilMakers: the schema's default or the type's), a value missing after the end of the array keeps the default (for a model: what its class sets).
51
+ * @param order the properties in the order the object gets them: an index of names, or an Extra
52
+ * @param proto the prototype of the model's class, undefined for a plain object
53
+ */
54
+ function generateReadArray(names, reads, defaults, makers, nils, nilMakers, order, proto) {
55
+ const count = names.length;
56
+ if (!codeAllowed || count === 0 || count > 512 || names.includes('__proto__') || names.includes(TypeProperty) || order.some((p) => typeof p !== 'number' && p.name === '__proto__'))
57
+ return undefined;
58
+ const v = names.map((_, t) => `v${t}`);
59
+ defaults = defaults.map((d, t) => makers[t] === undefined ? d : Unread);
60
+ let code = `const ${names.map((_, t) => `read${t} = reads[${t}], d${t} = defaults[${t}], m${t} = makers[${t}], z${t} = nils[${t}], zm${t} = nilMakers[${t}]`).join(', ')};\n`;
61
+ let result;
62
+ if (proto === undefined)
63
+ result = `{ ${order.map((p) => `${JSON.stringify(names[p])}: v${p}`).join(', ')} }`;
64
+ else {
65
+ code += `const extras = order.filter((p) => typeof p !== 'number');\n`;
66
+ let extra = 0;
67
+ code += `function Model(${v.join(', ')}) {\n${order.map((p) => typeof p === 'number'
68
+ ? `this[${JSON.stringify(names[p])}] = v${p};\n`
69
+ : `this[${JSON.stringify(p.name)}] = ${p.make === undefined ? `extras[${extra++}].value` : `extras[${extra++}].make()`};\n`).join('')}}\nModel.prototype = proto;\n`;
70
+ result = `new Model(${v.join(', ')})`;
71
+ }
72
+ code += `return function readArray(r, n) {\nconst b = r.bytes;\nlet ${names.map((_, t) => `v${t} = d${t}`).join(', ')};\nvalues: {\n`;
73
+ names.forEach((_, t) => {
74
+ const nil = nilMakers[t] === undefined ? `v${t} = z${t};` : `v${t} = zm${t}();`;
75
+ code += `if (n === ${t}) break values;\nif (b[r.pos] === 0xc0) { r.pos++; ${nil} } else v${t} = read${t}(r);\n`;
76
+ });
77
+ code += `for (let k = ${count}; k < n; k++) r.value();\n}\n`;
78
+ makers.forEach((m, t) => {
79
+ if (m !== undefined)
80
+ code += `if (v${t} === unread) v${t} = m${t}();\n`;
81
+ });
82
+ code += `return ${result};\n};`;
83
+ try {
84
+ return new Function('reads', 'defaults', 'makers', 'nils', 'nilMakers', 'order', 'proto', 'unread', code)(reads, defaults, makers, nils, nilMakers, order, proto, Unread);
85
+ }
86
+ catch {
87
+ codeAllowed = false;
88
+ return undefined;
89
+ }
90
+ }
91
+ /** The initial value of a field of a model's class (probe: an instance), and how to make it for each object when it is not a primitive. */
92
+ function initialOf(cls, probe, name) {
93
+ const value = probe[name];
94
+ if (value === null || (typeof value !== 'object' && typeof value !== 'function'))
95
+ return { value };
96
+ if (value instanceof Date) {
97
+ const time = value.getTime();
98
+ return { value: null, make: () => new Date(time) };
99
+ }
100
+ if (Array.isArray(value) && value.length === 0 && Object.getPrototypeOf(value) === Array.prototype)
101
+ return { value: null, make: () => [] };
102
+ return { value: null, make: () => new cls()[name] }; // e.g. a nested object: as the class makes it
103
+ }
104
+ /** Sets a property, also one named __proto__ (as JSON.parse does: an own property, not the prototype). */
105
+ export function setOwn(o, name, value) {
106
+ if (name === '__proto__')
107
+ Object.defineProperty(o, name, { value, writable: true, enumerable: true, configurable: true });
108
+ else
109
+ o[name] = value;
110
+ }
111
+ /** How to read the values of one schema entry. */
112
+ class Plan {
113
+ /** Makes what create and fill use, once the properties are known: the template, and with generateCode their code. */
114
+ prepare(generateCode) {
115
+ if (this.cls === undefined) {
116
+ const o = {};
117
+ for (let t = 0; t < this.names.length; t++)
118
+ setOwn(o, this.names[t], this.values[t]);
119
+ this.template = o;
120
+ }
121
+ if (!generateCode)
122
+ return;
123
+ const code = generate(this.names, this.reads, this.cls === undefined, this.cls === undefined ? undefined : (t) => this.nilValue(t));
124
+ this.createFn = code?.create;
125
+ this.createTypedFn = code?.createTyped;
126
+ this.fillFn = code?.fill;
127
+ this.readArrayFn = this.generateReadArray();
128
+ }
129
+ /** readArrayFn for plain objects and the models (not for DecodeOptions.classes: their constructors may do more than set fields). */
130
+ generateReadArray() {
131
+ const names = this.names;
132
+ if (this.cls === undefined)
133
+ return generateReadArray(names, this.reads, this.values, this.makers, this.values, this.makers, names.map((_, t) => t), undefined);
134
+ if (!this.model)
135
+ return undefined;
136
+ // The generated classes only initialize their fields: the same fields with the same values in the same order, without new of the class.
137
+ const cls = this.cls, probe = new cls();
138
+ const defaults = this.values.slice(), makers = this.makers.slice(), order = [];
139
+ for (const name of Object.keys(probe)) {
140
+ const t = this.indexOf(name), initial = initialOf(cls, probe, name);
141
+ if (t < 0)
142
+ order.push({ name, ...initial });
143
+ else {
144
+ order.push(t);
145
+ defaults[t] = initial.value;
146
+ makers[t] = initial.make;
147
+ }
148
+ }
149
+ for (let t = 0; t < this.missing.length; t++)
150
+ order.push(this.missing[t]); // after the class's fields, as create sets them
151
+ return generateReadArray(names, this.reads, defaults, makers, this.values, this.makers, order, cls.prototype);
152
+ }
153
+ /** The value of a nil in the array layout: what the writer left out (the default of the schema or of the type), not what a class sets. */
154
+ nilValue(t) {
155
+ const make = this.makers[t];
156
+ return make === undefined ? this.values[t] : make();
157
+ }
158
+ /**
159
+ * @param names the names of the properties in JavaScript (DecodeOptions.propertyNames)
160
+ * @param cls the class of the entry (a model, or one of DecodeOptions.classes)
161
+ * @param model the class is a model: it has $type on its prototype
162
+ */
163
+ constructor(entry, names, cls, model = false) {
164
+ this.entry = entry;
165
+ this.cls = cls;
166
+ this.model = model;
167
+ /** The properties the class does not initialize (they get the defaults of the schema: e.g. a property the server has and the models do not have yet). */
168
+ this.missing = [];
169
+ const count = entry.properties.length;
170
+ this.names = names;
171
+ this.reads = new Array(count);
172
+ this.values = new Array(count);
173
+ this.makers = new Array(count);
174
+ this.isObject = entry.content === undefined;
175
+ if (cls !== undefined) {
176
+ const probe = new cls(); // the same fields for every instance
177
+ for (let t = 0; t < count; t++)
178
+ if (probe[this.names[t]] === undefined)
179
+ this.missing.push(t);
180
+ }
181
+ }
182
+ /** A new object with every property at its default (null for the ones that are made, see complete), in the order of the schema. */
183
+ create() {
184
+ const names = this.names, values = this.values;
185
+ if (this.createFn !== undefined)
186
+ return this.createFn(values, null);
187
+ if (this.template !== undefined)
188
+ return { ...this.template }; // own properties, also one named __proto__
189
+ const o = new this.cls(); // new rather than a copy of one instance: the initial values (Date, arrays, nested objects) are not shared
190
+ const missing = this.missing;
191
+ for (let t = 0; t < missing.length; t++)
192
+ setOwn(o, names[missing[t]], values[missing[t]]);
193
+ return o;
194
+ }
195
+ /** create for an object that came with a type id: with its type name in $type (the first property; on instances of your classes after their own). */
196
+ createTyped() {
197
+ const typeName = this.entry.name;
198
+ if (typeName === null)
199
+ return this.create(); // the root entry of a described schema (type ids never refer to it) or a nameless entry
200
+ if (this.createTypedFn !== undefined)
201
+ return this.createTypedFn(this.values, typeName);
202
+ if (this.template !== undefined)
203
+ return { [TypeProperty]: typeName, ...this.template };
204
+ const o = this.create();
205
+ if (!this.model)
206
+ o[TypeProperty] = typeName; // a model has it on its prototype
207
+ return o;
208
+ }
209
+ /** Makes the defaults the data did not replace (a Date or struct is never null, so null means not read). */
210
+ complete(o) {
211
+ const made = this.made;
212
+ if (made === undefined)
213
+ return o;
214
+ for (let t = 0; t < made.length; t++) {
215
+ const name = this.names[made[t]];
216
+ if (o[name] === null)
217
+ o[name] = this.makers[made[t]]();
218
+ }
219
+ return o;
220
+ }
221
+ /** A new object with every default made (e.g. the default of a struct). */
222
+ createComplete() {
223
+ return this.complete(this.create());
224
+ }
225
+ indexOf(name) {
226
+ if (this._byName === undefined) {
227
+ this._byName = new Map();
228
+ for (let t = this.names.length - 1; t >= 0; t--)
229
+ this._byName.set(this.names[t], t);
230
+ }
231
+ return this._byName.get(name) ?? -1;
232
+ }
233
+ /** The index of a property by the name the writer gave it (a key of ObjectLayout.Map without the schema): the .NET name. */
234
+ indexOfWriterName(name) {
235
+ const properties = this.entry.properties;
236
+ if (this._byWriterName === undefined) {
237
+ this._byWriterName = new Map();
238
+ for (let t = properties.length - 1; t >= 0; t--)
239
+ this._byWriterName.set(properties[t].name, t);
240
+ }
241
+ return this._byWriterName.get(name) ?? -1;
242
+ }
243
+ }
244
+ /** The plans of one schema for one set of options, made when first used. */
245
+ class Plans {
246
+ constructor(schema, options) {
247
+ this.schema = schema;
248
+ this.options = options;
249
+ this.inline = new Map();
250
+ this.field = nameFunction(options.propertyNames);
251
+ this.keyName = this.field('Key');
252
+ this.valueName = this.field('Value');
253
+ this.plans = new Array(schema.entries.length);
254
+ this.root = schema.root === undefined ? readDynamic : this.compile(schema.root);
255
+ }
256
+ plan(index) {
257
+ const plan = this.plans[index];
258
+ if (plan !== undefined)
259
+ return plan;
260
+ if (!Number.isInteger(index) || index < 0 || index >= this.plans.length)
261
+ throw new MsgPackError(`The type id ${index} is not an entry of the schema (${this.plans.length} entries).`);
262
+ return this.build(index);
263
+ }
264
+ /** The plan of an entry by index, or of a struct described inline. */
265
+ planOf(entry) {
266
+ if (typeof entry === 'number')
267
+ return this.plan(entry);
268
+ const plan = this.inline.get(entry);
269
+ return plan ?? this.build(-1, entry);
270
+ }
271
+ build(index, entry = this.schema.entries[index]) {
272
+ const model = entry.name === null ? undefined : this.options.models?.byName(entry.name);
273
+ const plan = new Plan(entry, entry.properties.map((p) => this.field(p.name)), model?.ctor ?? (entry.name === null ? undefined : this.options.classes?.[entry.name]), model !== undefined);
274
+ if (index >= 0)
275
+ this.plans[index] = plan; // before the properties: they may refer to this entry
276
+ else
277
+ this.inline.set(entry, plan);
278
+ entry.properties.forEach((p, t) => {
279
+ const type = p.type ?? model?.byName.get(p.name) ?? AnyType; // a plain schema has names only: the models know the types
280
+ plan.reads[t] = this.compile(type);
281
+ const d = p.hasDefault ? this.convertDefault(p.default, type) : this.defaultOf(type);
282
+ plan.values[t] = d.make === undefined ? d.value : null;
283
+ plan.makers[t] = d.make;
284
+ if (d.make !== undefined && (plan.cls === undefined || plan.missing.includes(t)))
285
+ (plan.made ?? (plan.made = [])).push(t);
286
+ });
287
+ if (entry.content !== undefined)
288
+ plan.content = this.compile(entry.content);
289
+ plan.prepare(this.options.generateCode);
290
+ return plan;
291
+ }
292
+ /** A read function for values of the type (each handles nil and values wrapped with a type id). */
293
+ compile(type) {
294
+ switch (type.k) {
295
+ case 'kind': return this.kind(type.kind);
296
+ case 'nullable': return this.compile(type.of);
297
+ case 'object': {
298
+ const entry = type.entry;
299
+ let plan; // not when compiled: the entry may refer to itself
300
+ return (r) => r.readObject(plan ?? (plan = this.planOf(entry)));
301
+ }
302
+ case 'list': {
303
+ const element = this.compile(type.of);
304
+ return (r) => r.readList(element);
305
+ }
306
+ case 'dict': {
307
+ const key = this.compile(type.key), value = this.compile(type.value);
308
+ return (r) => r.readDictionary(key, value);
309
+ }
310
+ case 'pair': {
311
+ const key = this.compile(type.key), value = this.compile(type.value), keyName = this.keyName, valueName = this.valueName;
312
+ return (r) => {
313
+ const items = r.readItems([key, value]);
314
+ return Array.isArray(items) ? { [keyName]: items[0], [valueName]: items[1] } : items;
315
+ };
316
+ }
317
+ case 'tuple': {
318
+ const items = type.items.map((t) => this.compile(t));
319
+ return (r) => r.readItems(items);
320
+ }
321
+ case 'model': { // a class of the models: the entry of its name (none: the value is nil, or has a type id)
322
+ const index = this.schema.indexOf(type.name);
323
+ return index < 0 ? readDynamic : (r) => r.readObject(this.plan(index));
324
+ }
325
+ }
326
+ }
327
+ /** The kinds the formatters write most have their usual format read in place, without the dispatch of value(). */
328
+ kind(kind) {
329
+ switch (kind) {
330
+ case 'guid': return readGuid;
331
+ case 'string': return readString;
332
+ case 'int': return readInt;
333
+ case 'decimal': {
334
+ const mode = this.options.decimal, convert = this.converter(kind);
335
+ return (r) => {
336
+ const bytes = r.bytes, p = r.pos;
337
+ if (bytes[p] === 0xd8 && bytes[p + 1] === 1 && p + 18 <= bytes.length) { // fixext 16 of type 1
338
+ r.pos = p + 18;
339
+ return decodeDecimal(bytes, p + 2, mode);
340
+ }
341
+ return convert(r.value());
342
+ };
343
+ }
344
+ case 'datetime':
345
+ if (this.options.dates === 'iso')
346
+ return (r) => {
347
+ const v = r.readDate();
348
+ return v instanceof Date ? v.toISOString() : v;
349
+ };
350
+ return (r) => r.readDate();
351
+ }
352
+ const convert = this.converter(kind);
353
+ return convert === undefined ? readDynamic : (r) => convert(r.value());
354
+ }
355
+ /** Converts a value of the kind as it was read (undefined: no conversion). Values in another form are returned as they are. */
356
+ converter(kind) {
357
+ const o = this.options;
358
+ switch (kind) {
359
+ case 'long':
360
+ case 'ulong':
361
+ return o.int64 === 'bigint'
362
+ ? (v) => (typeof v === 'number' ? BigInt(v) : v)
363
+ : (v) => (typeof v === 'bigint' ? Number(v) : v);
364
+ case 'bigint': return (v) => (typeof v === 'number' ? BigInt(v) : v);
365
+ case 'char': return (v) => (typeof v === 'number' ? String.fromCharCode(v) : v);
366
+ case 'decimal': return (v) => (v instanceof MsgPackExtension && v.type === 1 && v.data.length === 16 ? decodeDecimal(v.data, 0, o.decimal) : convertDecimal(v, o.decimal));
367
+ case 'guid': return (v) => (v instanceof Uint8Array && v.length === 16 ? guidString(v) : v);
368
+ case 'datetime': return o.dates === 'iso' ? (v) => (v instanceof Date ? v.toISOString() : v) : undefined;
369
+ case 'dto':
370
+ case 'dto-clock': {
371
+ const clock = kind === 'dto-clock';
372
+ const iso = o.dates === 'iso';
373
+ return (v) => {
374
+ if (v instanceof Date)
375
+ return iso ? offsetText(v, 0) : v; // a bare timestamp (DateTimeOffsetFormat.Timestamp)
376
+ if (!Array.isArray(v) || v.length !== 2 || !(v[0] instanceof Date) || typeof v[1] !== 'number')
377
+ return v;
378
+ const moment = clock ? new Date(v[0].getTime() - v[1] * 60000) : v[0];
379
+ return iso ? offsetText(moment, v[1]) : moment;
380
+ };
381
+ }
382
+ case 'timespan': return (v) => (typeof v === 'number' || typeof v === 'bigint' ? timeSpanText(v) : v);
383
+ case 'dateonly': return (v) => (typeof v === 'number' ? dateOnlyText(v) : v);
384
+ case 'timeonly': return (v) => (typeof v === 'number' || typeof v === 'bigint' ? timeOnlyText(v) : v);
385
+ }
386
+ return undefined; // string, bool, int, float, bin, uri, any: as read
387
+ }
388
+ /** The value of a property the writer left out: the default of its type. */
389
+ defaultOf(type) {
390
+ const o = this.options;
391
+ switch (type.k) {
392
+ case 'kind':
393
+ switch (type.kind) {
394
+ case 'bool': return { value: false };
395
+ case 'int':
396
+ case 'float': return { value: 0 };
397
+ case 'long':
398
+ case 'ulong': return { value: o.int64 === 'bigint' ? 0n : 0 };
399
+ case 'bigint': return { value: 0n };
400
+ case 'decimal': return { value: convertDecimal(0, o.decimal) };
401
+ case 'char': return { value: '\0' };
402
+ case 'guid': return { value: EmptyGuid };
403
+ case 'datetime': return o.dates === 'iso' ? { value: new Date(MinDateTimeMs).toISOString() } : { value: null, make: () => new Date(MinDateTimeMs) };
404
+ case 'dto':
405
+ case 'dto-clock': return o.dates === 'iso' ? { value: offsetText(new Date(MinDateTimeMs), 0) } : { value: null, make: () => new Date(MinDateTimeMs) };
406
+ case 'timespan': return { value: '00:00:00' };
407
+ case 'dateonly': return { value: '0001-01-01' };
408
+ case 'timeonly': return { value: '00:00:00' };
409
+ }
410
+ return { value: null };
411
+ case 'object':
412
+ if (type.struct) {
413
+ const entry = type.entry;
414
+ return { value: null, make: () => this.planOf(entry).createComplete() };
415
+ }
416
+ return { value: null };
417
+ case 'pair': {
418
+ const key = this.defaultOf(type.key), value = this.defaultOf(type.value), keyName = this.keyName, valueName = this.valueName;
419
+ return { value: null, make: () => ({ [keyName]: key.make ? key.make() : key.value, [valueName]: value.make ? value.make() : value.value }) };
420
+ }
421
+ case 'model': {
422
+ if (!type.struct)
423
+ return { value: null };
424
+ const ctor = this.options.models?.byName(type.name)?.ctor;
425
+ return ctor === undefined ? { value: null } : { value: null, make: () => new ctor() };
426
+ }
427
+ }
428
+ return { value: null }; // classes, nullables, collections, tuples
429
+ }
430
+ /** A [DefaultValue] of the schema (as read: decimals raw), converted as the values of the property are. */
431
+ convertDefault(raw, type) {
432
+ const kind = type.k === 'nullable' ? type.of : type;
433
+ const convert = kind.k === 'kind' ? this.converter(kind.kind) : undefined;
434
+ const value = convert === undefined ? raw : convert(raw);
435
+ if (value instanceof Date)
436
+ return { value: null, make: () => new Date(value.getTime()) };
437
+ return { value };
438
+ }
439
+ }
440
+ const readDynamic = (r) => r.value();
441
+ /** A string: fixstr and str8 read in place. */
442
+ const readString = (r) => {
443
+ const bytes = r.bytes, p = r.pos, b = bytes[p];
444
+ if (b >= 0xa0 && b <= 0xbf) {
445
+ r.pos = p + 1;
446
+ return r.str(b & 0x1f);
447
+ }
448
+ if (b === 0xd9 && p + 1 < bytes.length) {
449
+ r.pos = p + 2;
450
+ return r.str(bytes[p + 1]);
451
+ }
452
+ return r.value();
453
+ };
454
+ /** An int (also enums): a positive fixint read in place. */
455
+ const readInt = (r) => {
456
+ const b = r.bytes[r.pos];
457
+ if (b <= 0x7f) {
458
+ r.pos++;
459
+ return b;
460
+ }
461
+ return r.value();
462
+ };
463
+ /** A Guid: bin 16 read in place (no copy of the bytes), any other form as the converter does. */
464
+ const readGuid = (r) => {
465
+ const bytes = r.bytes, p = r.pos;
466
+ if (bytes[p] === 0xc4 && bytes[p + 1] === 16 && p + 18 <= bytes.length) {
467
+ r.pos = p + 18;
468
+ return guidString(bytes, p + 2);
469
+ }
470
+ const v = r.value();
471
+ return v instanceof Uint8Array && v.length === 16 ? guidString(v) : v;
472
+ };
473
+ /** The reader of a body: values wrapped with a type id ({ "": index, "@": content }, or { "": index, 0: value, ... }) become what their schema entry describes. */
474
+ export class SchemaReader extends MsgPackReader {
475
+ constructor(bytes, options, plans, pos) {
476
+ super(bytes, options, pos);
477
+ this.plans = plans;
478
+ }
479
+ /** The first key of the map whose header was just read is "" (a type id). */
480
+ firstKeyIsTypeId() {
481
+ const k = this.bytes[this.pos];
482
+ return k === 0xa0 || (k === 0xd9 && this.bytes[this.pos + 1] === 0);
483
+ }
484
+ map(n) {
485
+ if (this.plans !== null && n > 0 && this.firstKeyIsTypeId())
486
+ return this.wrapped(n);
487
+ return super.map(n);
488
+ }
489
+ /** A value wrapped with a type id, the header (n entries) is read. */
490
+ wrapped(n) {
491
+ this.pos += this.bytes[this.pos] === 0xa0 ? 1 : 2; // the key ""
492
+ const index = this.value();
493
+ if (typeof index !== 'number')
494
+ throw new MsgPackError('A type id is not a schema index.', this.pos);
495
+ const plan = this.plans.plan(index);
496
+ this.enter();
497
+ let result = null;
498
+ if (plan.isObject) {
499
+ const o = plan.createTyped();
500
+ for (let t = 1; t < n; t++) {
501
+ const key = this.value();
502
+ if (key === '@') { // the array layout: { "": index, "@": [values] }
503
+ const count = this.arrayLength();
504
+ if (count >= 0)
505
+ this.fill(plan, o, count);
506
+ else
507
+ this.value();
508
+ }
509
+ else
510
+ this.setByKey(plan, o, key);
511
+ }
512
+ result = plan.complete(o);
513
+ }
514
+ else {
515
+ for (let t = 1; t < n; t++) {
516
+ const key = this.value();
517
+ if (key === '@')
518
+ result = plan.content(this);
519
+ else
520
+ this.value(); // a property of a collection ([SerializeEnumerable(SerializeProperties)]): not kept, a JS array has no place for it
521
+ }
522
+ }
523
+ this.depth--;
524
+ return result;
525
+ }
526
+ /** An object of the plan: an array of its values (the array layout), a map from property index to value, or a value with a type id. */
527
+ readObject(plan) {
528
+ const b = this.peek();
529
+ if (b === 0xc0) {
530
+ this.pos++;
531
+ return null;
532
+ }
533
+ let n = this.arrayLength();
534
+ if (n >= 0) {
535
+ this.enter();
536
+ const readArray = plan.readArrayFn;
537
+ if (readArray !== undefined) {
538
+ const o = readArray(this, n);
539
+ this.depth--;
540
+ return o;
541
+ }
542
+ const o = plan.create();
543
+ this.fill(plan, o, n);
544
+ this.depth--;
545
+ return plan.complete(o);
546
+ }
547
+ n = this.mapLength();
548
+ if (n < 0)
549
+ return this.value(); // not an object: as it is
550
+ if (n > 0 && this.firstKeyIsTypeId())
551
+ return this.wrapped(n);
552
+ this.enter();
553
+ const o = plan.create();
554
+ for (let t = 0; t < n; t++)
555
+ this.setByKey(plan, o, this.value());
556
+ this.depth--;
557
+ return plan.complete(o);
558
+ }
559
+ /**
560
+ * Values by position, values beyond the entry are skipped. A nil is the value the writer left out (Plan.nilValue: the object made with the defaults has it, an instance of a class gets it),
561
+ * a value missing after the end of the array keeps what create made (for a class: what its constructor sets).
562
+ */
563
+ fill(plan, o, n) {
564
+ if (plan.fillFn !== undefined)
565
+ return plan.fillFn(this, o, n);
566
+ const names = plan.names, reads = plan.reads, count = names.length, bytes = this.bytes;
567
+ for (let k = 0; k < n; k++) {
568
+ if (bytes[this.pos] === 0xc0) {
569
+ this.pos++;
570
+ if (plan.cls !== undefined && k < count)
571
+ setOwn(o, names[k], plan.nilValue(k));
572
+ continue;
573
+ }
574
+ if (k < count)
575
+ setOwn(o, names[k], reads[k](this));
576
+ else
577
+ this.value();
578
+ }
579
+ }
580
+ /** ObjectLayout.Map: the key is the index of the property in the entry (or its name). */
581
+ setByKey(plan, o, key) {
582
+ const index = typeof key === 'number' ? key : typeof key === 'string' ? plan.indexOfWriterName(key) : -1;
583
+ if (index >= 0 && index < plan.names.length)
584
+ setOwn(o, plan.names[index], plan.reads[index](this));
585
+ else
586
+ this.value();
587
+ }
588
+ readList(element) {
589
+ const b = this.peek();
590
+ if (b === 0xc0) {
591
+ this.pos++;
592
+ return null;
593
+ }
594
+ const n = this.arrayLength();
595
+ if (n < 0)
596
+ return this.value(); // wrapped with a type id, or not a list
597
+ this.enter();
598
+ const result = new Array(n);
599
+ for (let t = 0; t < n; t++)
600
+ result[t] = element(this);
601
+ this.depth--;
602
+ return result;
603
+ }
604
+ readDictionary(key, value) {
605
+ const b = this.peek();
606
+ if (b === 0xc0) {
607
+ this.pos++;
608
+ return null;
609
+ }
610
+ if (this.startsWithTypeId())
611
+ return this.value();
612
+ const n = this.mapLength();
613
+ if (n < 0)
614
+ return this.value();
615
+ this.enter();
616
+ const result = {};
617
+ for (let t = 0; t < n; t++) {
618
+ const k = key(this);
619
+ setOwn(result, typeof k === 'string' ? k : String(k), value(this));
620
+ }
621
+ this.depth--;
622
+ return result;
623
+ }
624
+ /** An array of items of the given types (pairs, tuples), extra items as they are. */
625
+ readItems(items) {
626
+ const b = this.peek();
627
+ if (b === 0xc0) {
628
+ this.pos++;
629
+ return null;
630
+ }
631
+ const n = this.arrayLength();
632
+ if (n < 0)
633
+ return this.value();
634
+ this.enter();
635
+ const result = new Array(n);
636
+ for (let t = 0; t < n; t++)
637
+ result[t] = t < items.length ? items[t](this) : this.value();
638
+ this.depth--;
639
+ return result;
640
+ }
641
+ /** A DateTime: the timestamps of 4 and 8 bytes read in place. */
642
+ readDate() {
643
+ const bytes = this.bytes, p = this.pos, b = bytes[p];
644
+ if (bytes[p + 1] === 0xff && (b === 0xd6 || b === 0xd7)) {
645
+ const length = b === 0xd6 ? 4 : 8;
646
+ if (p + 2 + length <= bytes.length) {
647
+ this.pos = p + 2;
648
+ return this.timestamp(length);
649
+ }
650
+ }
651
+ return this.value();
652
+ }
653
+ readRoot() {
654
+ return this.plans === null ? this.value() : this.plans.root(this);
655
+ }
656
+ }
657
+ /** The models' TypeScript types assume the options they were generated with. */
658
+ function checkModelOptions(models, o) {
659
+ const differs = (name, model, actual) => {
660
+ if (model !== undefined && model !== actual)
661
+ throw new Error(`The models (${models.generator ?? 'lsmsgpack-ts'}) were generated for ${name}: '${model}', the client reads with '${typeof actual === 'function' ? 'a function' : actual}'. Generate them with the same option, or leave it out here.`);
662
+ };
663
+ differs('int64', models.int64, o.int64);
664
+ differs('dates', models.dates, o.dates);
665
+ if (typeof o.decimal !== 'function')
666
+ differs('decimal', models.decimal, o.decimal); // a function of the text: the user's own type
667
+ differs('propertyNames', models.propertyNames, o.propertyNames); // the field names of the classes
668
+ }
669
+ /** Decodes payloads with the given options (one per client). Thread-safe in the sense of JavaScript: no state between calls except the plans per schema. */
670
+ export class Decoder {
671
+ constructor(options = {}) {
672
+ this.plans = new WeakMap();
673
+ const models = options.models;
674
+ this.options = {
675
+ decimal: options.decimal ?? models?.decimal ?? 'number',
676
+ int64: options.int64 ?? models?.int64 ?? 'number',
677
+ dates: options.dates ?? models?.dates ?? 'date',
678
+ models,
679
+ classes: options.classes,
680
+ propertyNames: options.propertyNames ?? models?.propertyNames ?? 'camelCase',
681
+ generateCode: options.generateCode ?? true,
682
+ };
683
+ if (models !== undefined)
684
+ checkModelOptions(models, this.options);
685
+ this.readerOptions = { decimal: this.options.decimal, maxDepth: options.maxDepth };
686
+ this.indexedSchema = options.indexedSchema ?? true;
687
+ }
688
+ /**
689
+ * Decodes application/x-lsmsgpack data: the schema (or a reference to it) followed by the body.
690
+ * @param lookup finds a schema the data refers to by its id
691
+ * @param inline makes the schema of the inline bytes (e.g. the known instance when the bytes are the same), default: parses them
692
+ */
693
+ decode(bytes, lookup, inline) {
694
+ if (bytes.length === 0)
695
+ return { value: undefined, reference: false };
696
+ if (!this.indexedSchema || bytes[0] === 0xc0) // null is written without a schema
697
+ return { value: this.read(bytes, null, 0), reference: false };
698
+ if (bytes[0] === 0xd8 && bytes[1] === 2) { // a reference: fixext 16 of type 2 with the id
699
+ if (bytes.length < 18)
700
+ throw new MsgPackError('Unexpected end of data.', bytes.length);
701
+ const id = toHex(bytes, 2, 18);
702
+ const schema = lookup?.(id);
703
+ if (schema === undefined)
704
+ throw new MissingSchemaError(id);
705
+ return { value: this.read(bytes, schema, 18), schema, reference: true };
706
+ }
707
+ const b = bytes[0];
708
+ if (!((b >= 0x80 && b <= 0x8f) || b === 0xde || b === 0xdf))
709
+ throw new MsgPackError(`The data does not start with a schema (0x${b.toString(16)}), set indexedSchema to false for data without it.`, 0);
710
+ const skip = new MsgPackReader(bytes, { decimal: 'raw' });
711
+ skip.skip();
712
+ const schemaBytes = bytes.subarray(0, skip.pos);
713
+ const schema = inline ? inline(schemaBytes) : new Schema(schemaBytes);
714
+ return { value: this.read(bytes, schema, skip.pos), schema, reference: false };
715
+ }
716
+ /** Plain MsgPack (application/msgpack): any value, maps as objects. */
717
+ decodePlain(bytes) {
718
+ if (bytes.length === 0)
719
+ return undefined;
720
+ return this.read(bytes, null, 0);
721
+ }
722
+ read(bytes, schema, pos) {
723
+ const reader = new SchemaReader(bytes, this.readerOptions, schema === null ? null : this.plansOf(schema), pos);
724
+ const value = reader.readRoot();
725
+ if (reader.pos !== bytes.length)
726
+ throw new MsgPackError('Data after the value.', reader.pos);
727
+ return value;
728
+ }
729
+ plansOf(schema) {
730
+ let plans = this.plans.get(schema);
731
+ if (plans === undefined) {
732
+ plans = new Plans(schema, this.options);
733
+ this.plans.set(schema, plans);
734
+ }
735
+ return plans;
736
+ }
737
+ }
738
+ //# sourceMappingURL=decoder.js.map