nytra 0.0.3

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,14 @@
1
+ import { Reader } from "./Reader.ts";
2
+ import { Writer } from "./Writer.ts";
3
+ export type RegisterFieldOptions = {
4
+ nullable?: boolean;
5
+ };
6
+ export declare class Binson {
7
+ #private;
8
+ static registerField(position: number, targetTypeId?: number | Function, options?: RegisterFieldOptions): (v: undefined, ctx: ClassFieldDecoratorContext) => void;
9
+ static registerClass(typeId: number): <T extends Function>(decoratedClass: T, ctx: ClassDecoratorContext) => void;
10
+ static autoguessType(data: unknown): number;
11
+ static getTypeIdForClass(ctor: Function): number;
12
+ static encode(data: unknown, type?: number | null, withType?: boolean, writer?: Writer | null): Uint8Array;
13
+ static decode(data: Uint8Array | Reader, type?: number | null): unknown;
14
+ }
package/dist/Binson.js ADDED
@@ -0,0 +1,468 @@
1
+ import { Reader } from "./Reader.js";
2
+ import { Registry } from "./Registry.js";
3
+ import { Writer } from "./Writer.js";
4
+ import { TYPE_ARRAY, TYPE_BIGINT, TYPE_BOOLEAN, TYPE_EXTENSION, TYPE_FLOAT32, TYPE_FLOAT64, TYPE_INT16, TYPE_INT32, TYPE_INT64, TYPE_INT8, TYPE_JSON, TYPE_NULL, TYPE_OBJECT, TYPE_STRING, TYPE_STRING_16_INTERNAL, TYPE_STRING_32_INTERNAL, TYPE_UINT16, TYPE_UINT32, TYPE_UINT64, TYPE_UINT8 } from "./Types.js";
5
+ const MAX_UINT_32 = 2 ** 32;
6
+ const MAX_UINT_16 = 2 ** 16;
7
+ const MAX_UINT_8 = 2 ** 8;
8
+ const MIN_INT_32 = -(2 ** 31);
9
+ const MIN_INT_16 = -(2 ** 15);
10
+ const MIN_INT_8 = -(2 ** 8);
11
+ function createDecoder(cls) {
12
+ const meta = classMetaStore.get(cls);
13
+ const fields = meta?.fields;
14
+ return function (reader) {
15
+ let obj = {};
16
+ const nullableFields = fields.filter(([_name, meta]) => meta.options.nullable).map(([name, _meta]) => name);
17
+ const nullableBytes = Math.ceil(nullableFields.length / 8);
18
+ const nullableBitfield = nullableBytes > 0 ? reader.readBytes(nullableBytes) : [];
19
+ const isFieldNull = (index) => {
20
+ const byteIndex = Math.floor(index / 8);
21
+ const bitOffset = index % 8;
22
+ return (nullableBitfield[byteIndex] & (1 << bitOffset)) !== 0;
23
+ };
24
+ for (let [name, meta] of fields) {
25
+ if (nullableBytes > 0 && meta.options.nullable) {
26
+ const nullIndex = nullableFields.indexOf(name);
27
+ if (nullIndex !== -1 && isFieldNull(nullIndex)) {
28
+ obj[name] = null;
29
+ continue;
30
+ }
31
+ }
32
+ if (typeof meta.targetTypeId === "function") {
33
+ const found = classMetaStore.get(meta.targetTypeId);
34
+ meta.targetTypeId = found ? found.typeId : TYPE_JSON;
35
+ }
36
+ let type = meta.targetTypeId;
37
+ if (type === TYPE_STRING) {
38
+ type = reader.readType();
39
+ }
40
+ obj[name] = Binson.decode(reader, type);
41
+ }
42
+ Object.setPrototypeOf(obj, cls.prototype);
43
+ return obj;
44
+ };
45
+ }
46
+ function createEncoder(cls) {
47
+ const meta = classMetaStore.get(cls);
48
+ const cachedEncoders = [];
49
+ const nullableFields = [];
50
+ let nullableBytes = 0;
51
+ return function (data, writer) {
52
+ if (writer === null) {
53
+ writer = new Writer();
54
+ }
55
+ if (cachedEncoders.length == 0) {
56
+ meta.fields.forEach(([name, fieldMeta]) => {
57
+ if (fieldMeta.options.nullable) {
58
+ nullableFields.push(name);
59
+ }
60
+ let typeId = fieldMeta.targetTypeId;
61
+ if (!typeId) {
62
+ cachedEncoders.push({ name: name, encode: null, options: fieldMeta.options });
63
+ return;
64
+ }
65
+ // Resolve Function → typeId hier, nicht im Loop!
66
+ if (typeof typeId === 'function') {
67
+ const found = classMetaStore.get(typeId);
68
+ typeId = found ? found.typeId : TYPE_JSON;
69
+ }
70
+ // Encoder-Funktion vorgebunden
71
+ let encodeFn;
72
+ if (typeId < 255) {
73
+ encodeFn = (value, writer) => Binson.encode(value, typeId, false, writer);
74
+ }
75
+ else {
76
+ encodeFn = CustomRegistry.getEncoder(typeId);
77
+ }
78
+ cachedEncoders.push({ name: name, encode: encodeFn, options: fieldMeta.options });
79
+ });
80
+ nullableBytes = Math.ceil(nullableFields.length / 8);
81
+ }
82
+ if (nullableBytes > 0) {
83
+ const nullableBitfield = new Uint8Array(nullableBytes);
84
+ for (let i = 0; i < nullableFields.length; i++) {
85
+ const field = nullableFields[i];
86
+ if (data[field] === null) {
87
+ const byteIndex = Math.floor(i / 8);
88
+ const bitOffset = i % 8;
89
+ nullableBitfield[byteIndex] |= (1 << bitOffset);
90
+ }
91
+ }
92
+ writer.writeBytes(nullableBitfield);
93
+ }
94
+ for (const encodeInfo of cachedEncoders) {
95
+ const value = data[encodeInfo.name];
96
+ if (value === null && encodeInfo.options.nullable) {
97
+ continue;
98
+ }
99
+ encodeInfo.encode ?
100
+ encodeInfo.encode(data[encodeInfo.name], writer) :
101
+ Binson.encode(data[encodeInfo.name], null, true, writer);
102
+ }
103
+ return writer.toUint8Array();
104
+ };
105
+ }
106
+ const classMetaStore = new WeakMap();
107
+ const SYMBOL_FIELDS = Symbol('binson:fields');
108
+ const CustomRegistry = new Registry();
109
+ export class Binson {
110
+ static registerField(position, targetTypeId, options = {}) {
111
+ const defaultOpts = {
112
+ nullable: false
113
+ };
114
+ Object.assign(defaultOpts, options);
115
+ return function (v, ctx) {
116
+ if (ctx.private) {
117
+ throw new Error('Only public fields can be registered');
118
+ }
119
+ const metadata = ctx.metadata;
120
+ if (typeof metadata[SYMBOL_FIELDS] === 'undefined') {
121
+ metadata[SYMBOL_FIELDS] = new Map();
122
+ }
123
+ const fieldMeta = {
124
+ position,
125
+ targetTypeId,
126
+ options: defaultOpts
127
+ };
128
+ metadata[SYMBOL_FIELDS].set(ctx.name, fieldMeta);
129
+ };
130
+ }
131
+ static registerClass(typeId) {
132
+ if (typeId < 256 || typeId > 65535) {
133
+ throw new Error('typeId must be in range of 256 and 65535');
134
+ }
135
+ return function (decoratedClass, ctx) {
136
+ const metadata = ctx.metadata;
137
+ const fields = metadata[SYMBOL_FIELDS] ?
138
+ [...metadata[SYMBOL_FIELDS].entries()].sort(([_aName, aMeta], [_bName, bMeta]) => aMeta.position - bMeta.position)
139
+ : [];
140
+ classMetaStore.set(decoratedClass, { typeId, fields });
141
+ CustomRegistry.register(typeId, {
142
+ encoder: createEncoder(decoratedClass),
143
+ decoder: createDecoder(decoratedClass)
144
+ });
145
+ };
146
+ }
147
+ static autoguessType(data) {
148
+ if (data === null) {
149
+ return TYPE_NULL;
150
+ }
151
+ if (typeof data === 'object') {
152
+ const ctor = data.constructor;
153
+ const found = classMetaStore.get(ctor);
154
+ if (found) {
155
+ return found.typeId;
156
+ }
157
+ }
158
+ if (Array.isArray(data)) {
159
+ return TYPE_ARRAY;
160
+ }
161
+ if (typeof data === 'object') {
162
+ return TYPE_OBJECT;
163
+ }
164
+ if (typeof data === 'string') {
165
+ return TYPE_STRING;
166
+ }
167
+ if (typeof data === 'boolean') {
168
+ return TYPE_BOOLEAN;
169
+ }
170
+ if (typeof data === 'number') {
171
+ if (Number.isInteger(data)) {
172
+ if (data >= 0) {
173
+ //unsigned possible
174
+ if (data <= MAX_UINT_8) {
175
+ return TYPE_UINT8;
176
+ }
177
+ if (data <= MAX_UINT_16) {
178
+ return TYPE_UINT16;
179
+ }
180
+ if (data <= MAX_UINT_32) {
181
+ return TYPE_UINT32;
182
+ }
183
+ return TYPE_UINT64;
184
+ }
185
+ else {
186
+ if (data >= MIN_INT_8) {
187
+ return TYPE_INT8;
188
+ }
189
+ if (data >= MIN_INT_16) {
190
+ return TYPE_INT16;
191
+ }
192
+ if (data >= MIN_INT_32) {
193
+ return TYPE_INT32;
194
+ }
195
+ return TYPE_INT64;
196
+ }
197
+ }
198
+ if (Math.fround(data) === data) {
199
+ return TYPE_FLOAT32;
200
+ }
201
+ return TYPE_FLOAT64;
202
+ }
203
+ if (typeof data === 'bigint') {
204
+ return TYPE_BIGINT;
205
+ }
206
+ return TYPE_JSON;
207
+ }
208
+ static getTypeIdForClass(ctor) {
209
+ const found = classMetaStore.get(ctor);
210
+ return found ? found.typeId : TYPE_JSON;
211
+ }
212
+ static #TEXT_ENCODER = new TextEncoder();
213
+ static encode(data, type = null, withType = true, writer = null) {
214
+ if (writer === null) {
215
+ writer = new Writer();
216
+ }
217
+ if (type === null) {
218
+ type = this.autoguessType(data);
219
+ }
220
+ if (type >= 256) {
221
+ const encodeFunction = CustomRegistry.getEncoder(type);
222
+ if (typeof encodeFunction !== 'function') {
223
+ throw new Error('Unknown type:' + type);
224
+ }
225
+ if (withType) {
226
+ writer.writeUint8(TYPE_EXTENSION);
227
+ writer.writeUint16(type);
228
+ }
229
+ encodeFunction(data, writer);
230
+ return writer.toUint8Array();
231
+ }
232
+ if (type >= TYPE_STRING) { //automatically
233
+ const str = data;
234
+ const bytes = this.#TEXT_ENCODER.encode(str);
235
+ const len = bytes.length;
236
+ if (len <= 127) {
237
+ writer.writeUint8(128 + len);
238
+ writer.writeBytes(bytes);
239
+ return writer.toUint8Array();
240
+ }
241
+ if (len <= 65535) {
242
+ writer.writeUint8(TYPE_STRING_16_INTERNAL);
243
+ writer.writeUint16(len);
244
+ writer.writeBytes(bytes);
245
+ return writer.toUint8Array();
246
+ }
247
+ writer.writeUint8(TYPE_STRING_32_INTERNAL);
248
+ writer.writeUint32(len);
249
+ writer.writeBytes(bytes);
250
+ return writer.toUint8Array();
251
+ }
252
+ switch (type) {
253
+ case TYPE_NULL:
254
+ writer.writeUint8(TYPE_NULL);
255
+ return writer.toUint8Array();
256
+ case TYPE_ARRAY: {
257
+ if (!Array.isArray(data)) {
258
+ throw new Error('Data must be an array');
259
+ }
260
+ const arr = data;
261
+ if (withType)
262
+ writer.writeUint8(TYPE_ARRAY);
263
+ const startIndex = writer.offset;
264
+ writer.setOffset(startIndex + 4); // reserve space for length
265
+ for (let value of arr) {
266
+ this.encode(value, null, true, writer);
267
+ }
268
+ const endIndex = writer.offset;
269
+ writer.setOffset(startIndex);
270
+ writer.writeUint32(endIndex - startIndex - 4);
271
+ writer.setOffset(endIndex);
272
+ return writer.toUint8Array();
273
+ }
274
+ case TYPE_OBJECT: {
275
+ if (typeof data !== 'object') {
276
+ throw new Error('Data must be an array');
277
+ }
278
+ const obj = data;
279
+ if (withType)
280
+ writer.writeUint8(TYPE_OBJECT);
281
+ const startIndex = writer.offset;
282
+ writer.setOffset(startIndex + 4);
283
+ let keys = Object.keys(obj);
284
+ for (let key of keys) {
285
+ this.encode(key, TYPE_STRING, true, writer);
286
+ this.encode(obj[key], null, true, writer);
287
+ }
288
+ const endIndex = writer.offset;
289
+ writer.setOffset(startIndex);
290
+ writer.writeUint32(endIndex - startIndex - 4);
291
+ writer.setOffset(endIndex);
292
+ return writer.toUint8Array();
293
+ }
294
+ case TYPE_UINT8: {
295
+ if (withType)
296
+ writer.writeUint8(TYPE_UINT8);
297
+ writer.writeUint8(data);
298
+ return writer.toUint8Array();
299
+ }
300
+ case TYPE_UINT16: {
301
+ if (withType)
302
+ writer.writeUint8(TYPE_UINT16);
303
+ writer.writeUint16(data);
304
+ return writer.toUint8Array();
305
+ }
306
+ case TYPE_UINT32: {
307
+ if (withType)
308
+ writer.writeUint8(TYPE_UINT32);
309
+ writer.writeUint32(data);
310
+ return writer.toUint8Array();
311
+ }
312
+ case TYPE_UINT64: {
313
+ if (withType)
314
+ writer.writeUint8(TYPE_UINT64);
315
+ writer.writeUint64(BigInt(data));
316
+ return writer.toUint8Array();
317
+ }
318
+ case TYPE_INT8: {
319
+ if (withType)
320
+ writer.writeUint8(TYPE_INT8);
321
+ writer.writeInt8(data);
322
+ return writer.toUint8Array();
323
+ }
324
+ case TYPE_INT16: {
325
+ if (withType)
326
+ writer.writeUint8(TYPE_INT16);
327
+ writer.writeInt16(data);
328
+ return writer.toUint8Array();
329
+ }
330
+ case TYPE_INT32: {
331
+ if (withType)
332
+ writer.writeUint8(TYPE_INT32);
333
+ writer.writeInt32(data);
334
+ return writer.toUint8Array();
335
+ }
336
+ case TYPE_INT64: {
337
+ if (withType)
338
+ writer.writeUint8(TYPE_INT64);
339
+ writer.writeInt64(BigInt(data));
340
+ return writer.toUint8Array();
341
+ }
342
+ case TYPE_BOOLEAN: {
343
+ if (withType)
344
+ writer.writeUint8(TYPE_BOOLEAN);
345
+ writer.writeUint8(data ? 1 : 0);
346
+ return writer.toUint8Array();
347
+ }
348
+ case TYPE_JSON: {
349
+ return this.encode(JSON.stringify(data), TYPE_STRING, withType, writer);
350
+ }
351
+ case TYPE_FLOAT32: {
352
+ if (withType)
353
+ writer.writeUint8(TYPE_FLOAT32);
354
+ writer.writeFloat32(data);
355
+ return writer.toUint8Array();
356
+ }
357
+ case TYPE_FLOAT64: {
358
+ if (withType)
359
+ writer.writeUint8(TYPE_FLOAT64);
360
+ writer.writeFloat64(data);
361
+ return writer.toUint8Array();
362
+ }
363
+ case TYPE_BIGINT: {
364
+ if (withType)
365
+ writer.writeUint8(TYPE_BIGINT);
366
+ writer.writeBigInt(BigInt(data));
367
+ return writer.toUint8Array();
368
+ }
369
+ }
370
+ throw new Error('Type unknown: ' + type);
371
+ }
372
+ static decode(data, type = null) {
373
+ let reader = data instanceof Reader ? data : new Reader(data);
374
+ if (type === null)
375
+ type = reader.readType();
376
+ if (type > 255) {
377
+ const decodeFunction = CustomRegistry.getDecoder(type);
378
+ if (typeof decodeFunction !== 'function') {
379
+ throw new Error('Unknown type:' + type);
380
+ }
381
+ return decodeFunction(reader);
382
+ }
383
+ if (type >= TYPE_STRING) {
384
+ let length = type & 0b01111111;
385
+ return reader.readString(length);
386
+ }
387
+ switch (type) {
388
+ case TYPE_STRING_16_INTERNAL: {
389
+ const length = reader.readUINT16();
390
+ return reader.readString(length);
391
+ }
392
+ case TYPE_STRING_32_INTERNAL: {
393
+ const length = reader.readUINT32();
394
+ return reader.readString(length);
395
+ }
396
+ case TYPE_BOOLEAN: {
397
+ return reader.readUINT8() !== 0;
398
+ }
399
+ case TYPE_JSON: {
400
+ const json = this.decode(reader);
401
+ return JSON.parse(json);
402
+ }
403
+ case TYPE_UINT8: {
404
+ return reader.readUINT8();
405
+ }
406
+ case TYPE_UINT16: {
407
+ return reader.readUINT16();
408
+ }
409
+ case TYPE_UINT32: {
410
+ return reader.readUINT32();
411
+ }
412
+ case TYPE_UINT64: {
413
+ return bigintToSafeNumber(reader.readUINT64());
414
+ }
415
+ case TYPE_INT8: {
416
+ return reader.readINT8();
417
+ }
418
+ case TYPE_INT16: {
419
+ return reader.readINT16();
420
+ }
421
+ case TYPE_INT32: {
422
+ return reader.readINT32();
423
+ }
424
+ case TYPE_INT64: {
425
+ return bigintToSafeNumber(reader.readINT64());
426
+ }
427
+ case TYPE_FLOAT64: {
428
+ return reader.readFloat64();
429
+ }
430
+ case TYPE_FLOAT32: {
431
+ return reader.readFloat32();
432
+ }
433
+ case TYPE_ARRAY: {
434
+ const len = reader.readUINT32();
435
+ const end = reader.offset + len;
436
+ const targetArray = [];
437
+ while (reader.offset < end) {
438
+ targetArray.push(this.decode(reader));
439
+ }
440
+ return targetArray;
441
+ }
442
+ case TYPE_OBJECT: {
443
+ const len = reader.readUINT32();
444
+ const end = reader.offset + len;
445
+ const targetObj = {};
446
+ while (reader.offset < end) {
447
+ const key = this.decode(reader);
448
+ targetObj[key] = this.decode(reader);
449
+ }
450
+ return targetObj;
451
+ }
452
+ case TYPE_BIGINT: {
453
+ return reader.readBigInt();
454
+ }
455
+ case TYPE_NULL: {
456
+ return null;
457
+ }
458
+ }
459
+ throw new Error('Unknown type:' + type);
460
+ }
461
+ }
462
+ function bigintToSafeNumber(value) {
463
+ if (value > BigInt(Number.MAX_SAFE_INTEGER) ||
464
+ value < BigInt(Number.MIN_SAFE_INTEGER)) {
465
+ return value;
466
+ }
467
+ return Number(value);
468
+ }
@@ -0,0 +1,25 @@
1
+ import { Reader } from "./Reader.ts";
2
+ export declare const NativeHandlers: {
3
+ readonly [x: number]: {
4
+ encoder: (data: null) => Uint8Array<ArrayBuffer>;
5
+ decoder: (reader: Reader) => null;
6
+ } | {
7
+ encoder: (data: number) => Uint8Array<ArrayBufferLike>;
8
+ decoder: (reader: Reader) => number;
9
+ } | {
10
+ encoder: (data: bigint) => Uint8Array<ArrayBufferLike>;
11
+ decoder: (reader: Reader) => bigint;
12
+ } | {
13
+ encoder: (data: boolean) => Uint8Array<ArrayBuffer>;
14
+ decoder: (reader: Reader) => boolean;
15
+ } | {
16
+ decoder(reader: Reader): object;
17
+ encoder(data: object): Uint8Array;
18
+ } | {
19
+ encoder: (json: unknown) => Uint8Array<ArrayBufferLike>;
20
+ decoder: (reader: Reader) => any;
21
+ } | {
22
+ encoder: (data: string) => Uint8Array<ArrayBufferLike>;
23
+ decoder: (reader: Reader) => string;
24
+ };
25
+ };
@@ -0,0 +1,137 @@
1
+ import { Writer } from "./Writer.js";
2
+ import { Binson } from "./Binson.js";
3
+ import { Reader } from "./Reader.js";
4
+ const TRUE = new Uint8Array([1]);
5
+ const FALSE = new Uint8Array([0]);
6
+ export const NativeHandlers = {
7
+ [Types.NULL]: {
8
+ encoder: (data) => new Uint8Array(0),
9
+ decoder: (reader) => null
10
+ },
11
+ [Types.UINT8]: {
12
+ encoder: (data) => new Writer(1).writeUint8(data).toUint8Array(),
13
+ decoder: (reader) => reader.readUINT8()
14
+ },
15
+ [Types.UINT16]: {
16
+ encoder: (data) => new Writer(2).writeUint16(data).toUint8Array(),
17
+ decoder: (reader) => reader.readUINT16()
18
+ },
19
+ [Types.UINT32]: {
20
+ encoder: (data) => new Writer(4).writeUint32(data).toUint8Array(),
21
+ decoder: (reader) => reader.readUINT32()
22
+ },
23
+ [Types.UINT64]: {
24
+ encoder: (data) => new Writer(8).writeUint64(BigInt(data)).toUint8Array(),
25
+ decoder: (reader) => Number(reader.readUINT64())
26
+ },
27
+ [Types.INT8]: {
28
+ encoder: (data) => new Writer(1).writeUint8(data).toUint8Array(),
29
+ decoder: (reader) => reader.readINT8()
30
+ },
31
+ [Types.INT16]: {
32
+ encoder: (data) => new Writer(2).writeInt16(data).toUint8Array(),
33
+ decoder: (reader) => reader.readINT16()
34
+ },
35
+ [Types.INT32]: {
36
+ encoder: (data) => new Writer(4).writeInt32(data).toUint8Array(),
37
+ decoder: (reader) => reader.readINT32()
38
+ },
39
+ [Types.INT64]: {
40
+ encoder: (data) => new Writer(8).writeInt64(BigInt(data)).toUint8Array(),
41
+ decoder: (reader) => Number(reader.readINT64())
42
+ },
43
+ [Types.FLOAT32]: {
44
+ encoder: (data) => new Writer(4).writeFloat32(data).toUint8Array(),
45
+ decoder: (reader) => reader.readFloat32()
46
+ },
47
+ [Types.FLOAT64]: {
48
+ encoder: (data) => new Writer(4).writeFloat32(data).toUint8Array(),
49
+ decoder: (reader) => reader.readFloat32()
50
+ },
51
+ [Types.BIGINT]: {
52
+ encoder: (data) => new Writer(10).writeBigInt(data).toUint8Array(),
53
+ decoder: (reader) => reader.readBigInt()
54
+ },
55
+ [Types.BOOLEAN]: {
56
+ encoder: (data) => data ? TRUE : FALSE,
57
+ decoder: (reader) => reader.readUINT8() > 0
58
+ },
59
+ [Types.ARRAY]: {
60
+ decoder(reader) {
61
+ let fullData = [];
62
+ let length = reader.readUINT32();
63
+ let buffer = reader.readBytes(length);
64
+ let bufferReader = new Reader(buffer);
65
+ while (bufferReader.remaining > 0) {
66
+ fullData.push(Binson.decode(bufferReader));
67
+ }
68
+ return fullData;
69
+ }, encoder(data) {
70
+ const buffer = flattenUint8Array(data.map(d => Binson.encode(d, null)));
71
+ const length = new Uint8Array(4);
72
+ new DataView(length.buffer).setUint32(0, buffer.length, true);
73
+ const out = new Uint8Array(4 + buffer.length);
74
+ out.set(length, 0);
75
+ out.set(buffer, 4);
76
+ return out;
77
+ }
78
+ },
79
+ [Types.OBJECT]: {
80
+ decoder(reader) {
81
+ let fullData = {};
82
+ let length = reader.readUINT32();
83
+ let buffer = reader.readBytes(length);
84
+ let bufferReader = new Reader(buffer);
85
+ while (bufferReader.remaining > 0) {
86
+ let key = bufferReader.readString();
87
+ fullData[key] = Binson.decode(bufferReader);
88
+ }
89
+ return fullData;
90
+ }, encoder(data) {
91
+ let keys = Object.keys(data);
92
+ let dataArray = new Array(keys.length * 2);
93
+ for (let i = 0; i < keys.length; i++) {
94
+ let key = keys[i];
95
+ dataArray[i * 2] = Binson.encodeValue(key, Types.STRING);
96
+ dataArray[i * 2 + 1] = Binson.encode(data[key], null);
97
+ }
98
+ let buffer = flattenUint8Array(dataArray);
99
+ const length = new Uint8Array(4);
100
+ new DataView(length.buffer).setUint32(0, buffer.length, true);
101
+ const out = new Uint8Array(4 + buffer.length);
102
+ out.set(length, 0);
103
+ out.set(buffer, 4);
104
+ return out;
105
+ }
106
+ },
107
+ [Types.JSON]: {
108
+ encoder: (json) => {
109
+ let data = JSON.stringify(json);
110
+ const bytes = new TextEncoder().encode(data);
111
+ return new Writer(2 + bytes.length)
112
+ .writeString(data)
113
+ .toUint8Array();
114
+ },
115
+ decoder: (reader) => JSON.parse(reader.readString())
116
+ },
117
+ [Types.STRING]: {
118
+ encoder: (data) => {
119
+ const bytes = new TextEncoder().encode(data);
120
+ return new Writer(2 + bytes.length)
121
+ .writeString(data)
122
+ .toUint8Array();
123
+ },
124
+ decoder: (reader) => reader.readString()
125
+ },
126
+ };
127
+ function flattenUint8Array(chunks) {
128
+ const totalLength = chunks.reduce((sum, c) => sum + c.length, 0);
129
+ const result = new Uint8Array(totalLength);
130
+ let offset = 0;
131
+ for (let i = 0; i < chunks.length; i++) {
132
+ const chunk = chunks[i];
133
+ result.set(chunk, offset);
134
+ offset += chunk.length;
135
+ }
136
+ return result;
137
+ }
@@ -0,0 +1,35 @@
1
+ export declare class Reader {
2
+ #private;
3
+ /**
4
+ *
5
+ * @param {Uint8Array} buffer
6
+ */
7
+ constructor(buffer: Uint8Array);
8
+ /**
9
+ *
10
+ * @param {number} length
11
+ */
12
+ readBytes(length: number): Uint8Array<ArrayBufferLike>;
13
+ readUINT8(): number;
14
+ readUINT16(): number;
15
+ readUINT32(): number;
16
+ readUINT64(): bigint;
17
+ readINT8(): number;
18
+ readINT16(): number;
19
+ readINT32(): number;
20
+ readINT64(): bigint;
21
+ readFloat32(): number;
22
+ readFloat64(): number;
23
+ readString(length: number): string;
24
+ readBigInt(): bigint;
25
+ readType(): number;
26
+ /**
27
+ * Current read offset
28
+ */
29
+ get offset(): number;
30
+ /**
31
+ * Number of bytes remaining to read
32
+ */
33
+ get remaining(): number;
34
+ skip(bytes: number): void;
35
+ }
package/dist/Reader.js ADDED
@@ -0,0 +1,133 @@
1
+ import { TYPE_EXTENSION } from "./Types.js";
2
+ const LITTLE_ENDIAN = true;
3
+ export class Reader {
4
+ static #decoder = new TextDecoder();
5
+ #ind = 0;
6
+ #data;
7
+ #dataView;
8
+ /**
9
+ *
10
+ * @param {Uint8Array} buffer
11
+ */
12
+ constructor(buffer) {
13
+ this.#data = buffer;
14
+ this.#dataView = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
15
+ }
16
+ /**
17
+ *
18
+ * @param {number} length
19
+ */
20
+ readBytes(length) {
21
+ if (Number.isInteger(length) && length > 0) {
22
+ const start = this.#ind;
23
+ const end = start + length;
24
+ if (end > this.#data.length) {
25
+ throw new Error('Out of bounds read');
26
+ }
27
+ this.#ind = end;
28
+ return this.#data.subarray(start, end);
29
+ }
30
+ throw new Error('Invalid length:' + length);
31
+ }
32
+ readUINT8() {
33
+ return this.#dataView.getUint8(this.#ind++);
34
+ }
35
+ readUINT16() {
36
+ const offset = this.#ind;
37
+ this.#ind += 2;
38
+ return this.#dataView.getUint16(offset, LITTLE_ENDIAN);
39
+ }
40
+ readUINT32() {
41
+ const offset = this.#ind;
42
+ this.#ind += 4;
43
+ return this.#dataView.getUint32(offset, LITTLE_ENDIAN);
44
+ }
45
+ readUINT64() {
46
+ const offset = this.#ind;
47
+ this.#ind += 8;
48
+ return this.#dataView.getBigUint64(offset, LITTLE_ENDIAN);
49
+ }
50
+ readINT8() {
51
+ return this.#dataView.getInt8(this.#ind++);
52
+ }
53
+ readINT16() {
54
+ const offset = this.#ind;
55
+ this.#ind += 2;
56
+ return this.#dataView.getInt16(offset, LITTLE_ENDIAN);
57
+ }
58
+ readINT32() {
59
+ const offset = this.#ind;
60
+ this.#ind += 4;
61
+ return this.#dataView.getInt32(offset, LITTLE_ENDIAN);
62
+ }
63
+ readINT64() {
64
+ const offset = this.#ind;
65
+ this.#ind += 8;
66
+ return this.#dataView.getBigInt64(offset, LITTLE_ENDIAN);
67
+ }
68
+ readFloat32() {
69
+ const offset = this.#ind;
70
+ this.#ind += 4;
71
+ return this.#dataView.getFloat32(offset, LITTLE_ENDIAN);
72
+ }
73
+ readFloat64() {
74
+ const offset = this.#ind;
75
+ this.#ind += 8;
76
+ return this.#dataView.getFloat64(offset, LITTLE_ENDIAN);
77
+ }
78
+ readString(length) {
79
+ const bytes = this.readBytes(length);
80
+ return Reader.#decoder.decode(bytes);
81
+ }
82
+ readBigInt() {
83
+ const length = this.readUINT16();
84
+ const bytes = this.readBytes(length);
85
+ return uint8ArrayLEToBigint(bytes);
86
+ }
87
+ readType() {
88
+ let type = this.readUINT8();
89
+ if (type === TYPE_EXTENSION) {
90
+ type = this.readUINT16();
91
+ }
92
+ return type;
93
+ }
94
+ /**
95
+ * Current read offset
96
+ */
97
+ get offset() {
98
+ return this.#ind;
99
+ }
100
+ /**
101
+ * Number of bytes remaining to read
102
+ */
103
+ get remaining() {
104
+ return this.#data.length - this.#ind;
105
+ }
106
+ skip(bytes) {
107
+ this.#ind += bytes;
108
+ }
109
+ }
110
+ function uint8ArrayLEToBigint(bytes) {
111
+ const len = bytes.length;
112
+ if (len === 0)
113
+ return 0n;
114
+ const isNegative = (bytes[len - 1] & 0x80) !== 0;
115
+ let value = 0n;
116
+ let shift = 0n;
117
+ if (!isNegative) {
118
+ for (let i = 0; i < len; i++) {
119
+ value |= BigInt(bytes[i]) << shift;
120
+ shift += 8n;
121
+ }
122
+ return value;
123
+ }
124
+ // two's complement decode without temp allocation
125
+ let carry = 1;
126
+ for (let i = 0; i < len; i++) {
127
+ const t = (bytes[i] ^ 0xff) + carry;
128
+ carry = t >>> 8; // 0 or 1
129
+ value |= BigInt(t & 0xff) << shift;
130
+ shift += 8n;
131
+ }
132
+ return -value;
133
+ }
@@ -0,0 +1,14 @@
1
+ import type { Reader } from "./Reader.ts";
2
+ import { Writer } from "./Writer.ts";
3
+ export type Encoder<T = unknown> = (data: T, writer: Writer | null) => Uint8Array;
4
+ export type Decoder<T = unknown> = (reader: Reader) => T;
5
+ export type TypeHandler<T = unknown> = {
6
+ encoder: Encoder<T>;
7
+ decoder: Decoder<T>;
8
+ };
9
+ export declare class Registry {
10
+ #private;
11
+ register<T>(type: number, handler: TypeHandler<T>): void;
12
+ getDecoder(type: number): Decoder;
13
+ getEncoder(type: number): Encoder;
14
+ }
@@ -0,0 +1,27 @@
1
+ export class Registry {
2
+ /**
3
+ *
4
+ * @type {Map<number, TypeHandler>}
5
+ */
6
+ #registry = new Map();
7
+ register(type, handler) {
8
+ if (this.#registry.has(type)) {
9
+ throw new Error('Type already registered:' + type);
10
+ }
11
+ this.#registry.set(type, handler);
12
+ }
13
+ getDecoder(type) {
14
+ const decoder = this.#registry.get(type)?.decoder;
15
+ if (!decoder) {
16
+ throw new Error('Unknown type: ' + type);
17
+ }
18
+ return decoder;
19
+ }
20
+ getEncoder(type) {
21
+ const encoder = this.#registry.get(type)?.encoder;
22
+ if (!encoder) {
23
+ throw new Error('Unknown type: ' + type);
24
+ }
25
+ return encoder;
26
+ }
27
+ }
@@ -0,0 +1 @@
1
+ export declare const TYPE_NULL = 0, TYPE_BOOLEAN = 1, TYPE_ARRAY = 2, TYPE_OBJECT = 3, TYPE_STRING_16_INTERNAL = 4, TYPE_STRING_32_INTERNAL = 5, TYPE_JSON = 9, TYPE_UINT8 = 11, TYPE_UINT16 = 12, TYPE_UINT32 = 13, TYPE_UINT64 = 14, TYPE_INT8 = 21, TYPE_INT16 = 22, TYPE_INT32 = 23, TYPE_INT64 = 24, TYPE_FLOAT32 = 30, TYPE_FLOAT64 = 31, TYPE_BIGINT = 40, TYPE_EXTENSION = 127, TYPE_STRING = 128;
package/dist/Types.js ADDED
@@ -0,0 +1,3 @@
1
+ export const TYPE_NULL = 0, TYPE_BOOLEAN = 1, TYPE_ARRAY = 2, TYPE_OBJECT = 3, TYPE_STRING_16_INTERNAL = 4, TYPE_STRING_32_INTERNAL = 5, TYPE_JSON = 9, TYPE_UINT8 = 11, TYPE_UINT16 = 12, TYPE_UINT32 = 13, TYPE_UINT64 = 14, TYPE_INT8 = 21, TYPE_INT16 = 22, TYPE_INT32 = 23, TYPE_INT64 = 24, TYPE_FLOAT32 = 30, TYPE_FLOAT64 = 31, TYPE_BIGINT = 40, TYPE_EXTENSION = 127,
2
+ //128-255 -> String use 7 bits for length (max 127)
3
+ TYPE_STRING = 128;
@@ -0,0 +1,22 @@
1
+ export declare class Writer {
2
+ #private;
3
+ static DEFAULT_SIZE: number;
4
+ setOffset(value: number, automaticallyExpand?: boolean): void;
5
+ get offset(): number;
6
+ constructor(initialSize?: number);
7
+ private ensureCapacity;
8
+ writeInt8(value: number): this;
9
+ writeInt16(value: number): this;
10
+ writeInt32(value: number): this;
11
+ writeInt64(value: bigint): this;
12
+ writeUint8(value: number): this;
13
+ writeUint16(value: number): this;
14
+ writeUint32(value: number): this;
15
+ writeUint64(value: bigint): this;
16
+ writeFloat32(value: number): this;
17
+ writeFloat64(value: number): this;
18
+ writeBytes(bytes: Uint8Array): this;
19
+ writeBigInt(value: bigint): this;
20
+ toUint8Array(): Uint8Array;
21
+ get length(): number;
22
+ }
package/dist/Writer.js ADDED
@@ -0,0 +1,150 @@
1
+ const LITTLE_ENDIAN = true;
2
+ export class Writer {
3
+ static DEFAULT_SIZE = 1024;
4
+ static #encoder = new TextEncoder();
5
+ #buffer;
6
+ #view;
7
+ #offset = 0;
8
+ setOffset(value, automaticallyExpand = false) {
9
+ if (value < 0) {
10
+ throw new Error('Offset cannot be negative');
11
+ }
12
+ if (value >= this.#buffer.byteLength) {
13
+ if (!automaticallyExpand) {
14
+ throw new Error('Offset out of bounds');
15
+ }
16
+ this.ensureCapacity(value - this.#offset);
17
+ }
18
+ this.#offset = value;
19
+ }
20
+ get offset() {
21
+ return this.#offset;
22
+ }
23
+ constructor(initialSize = Writer.DEFAULT_SIZE) {
24
+ this.#buffer = new ArrayBuffer(initialSize);
25
+ this.#view = new DataView(this.#buffer);
26
+ }
27
+ ensureCapacity(additionalLength) {
28
+ const required = this.#offset + additionalLength;
29
+ if (required <= this.#buffer.byteLength)
30
+ return;
31
+ let newLength = this.#buffer.byteLength * 2;
32
+ while (newLength < required) {
33
+ newLength *= 2;
34
+ }
35
+ const newBuffer = new ArrayBuffer(newLength);
36
+ // copy old data
37
+ new Uint8Array(newBuffer).set(new Uint8Array(this.#buffer));
38
+ this.#buffer = newBuffer;
39
+ this.#view = new DataView(newBuffer);
40
+ }
41
+ writeInt8(value) {
42
+ this.ensureCapacity(1);
43
+ this.#view.setInt8(this.#offset, value);
44
+ this.#offset += 1;
45
+ return this;
46
+ }
47
+ writeInt16(value) {
48
+ this.ensureCapacity(2);
49
+ this.#view.setInt16(this.#offset, value, LITTLE_ENDIAN);
50
+ this.#offset += 2;
51
+ return this;
52
+ }
53
+ writeInt32(value) {
54
+ this.ensureCapacity(4);
55
+ this.#view.setInt32(this.#offset, value, LITTLE_ENDIAN);
56
+ this.#offset += 4;
57
+ return this;
58
+ }
59
+ writeInt64(value) {
60
+ this.ensureCapacity(8);
61
+ this.#view.setBigInt64(this.#offset, value, LITTLE_ENDIAN);
62
+ this.#offset += 8;
63
+ return this;
64
+ }
65
+ writeUint8(value) {
66
+ this.ensureCapacity(1);
67
+ this.#view.setUint8(this.#offset, value);
68
+ this.#offset += 1;
69
+ return this;
70
+ }
71
+ writeUint16(value) {
72
+ this.ensureCapacity(2);
73
+ this.#view.setUint16(this.#offset, value, LITTLE_ENDIAN);
74
+ this.#offset += 2;
75
+ return this;
76
+ }
77
+ writeUint32(value) {
78
+ this.ensureCapacity(4);
79
+ this.#view.setUint32(this.#offset, value, LITTLE_ENDIAN);
80
+ this.#offset += 4;
81
+ return this;
82
+ }
83
+ writeUint64(value) {
84
+ this.ensureCapacity(8);
85
+ this.#view.setBigUint64(this.#offset, value, LITTLE_ENDIAN);
86
+ this.#offset += 8;
87
+ return this;
88
+ }
89
+ writeFloat32(value) {
90
+ this.ensureCapacity(4);
91
+ this.#view.setFloat32(this.#offset, value, LITTLE_ENDIAN);
92
+ this.#offset += 4;
93
+ return this;
94
+ }
95
+ writeFloat64(value) {
96
+ this.ensureCapacity(8);
97
+ this.#view.setFloat64(this.#offset, value, LITTLE_ENDIAN);
98
+ this.#offset += 8;
99
+ return this;
100
+ }
101
+ writeBytes(bytes) {
102
+ this.ensureCapacity(bytes.length);
103
+ new Uint8Array(this.#buffer, this.#offset, bytes.length).set(bytes);
104
+ this.#offset += bytes.length;
105
+ return this;
106
+ }
107
+ writeBigInt(value) {
108
+ let bytes = bigintToUint8ArrayLE(value);
109
+ this.writeUint16(bytes.length);
110
+ this.writeBytes(bytes);
111
+ return this;
112
+ }
113
+ toUint8Array() {
114
+ return new Uint8Array(this.#buffer, 0, this.#offset);
115
+ }
116
+ get length() {
117
+ return this.#offset;
118
+ }
119
+ }
120
+ function bigintToUint8ArrayLE(value) {
121
+ if (value === 0n)
122
+ return new Uint8Array([0]);
123
+ const isNegative = value < 0n;
124
+ // Work in absolute space first if negative
125
+ let abs = isNegative ? -value : value;
126
+ // Collect bytes (little-endian magnitude)
127
+ const bytes = [];
128
+ while (abs > 0n) {
129
+ bytes.push(Number(abs & 0xffn));
130
+ abs >>= 8n;
131
+ }
132
+ // Ensure sign bit correctness in the highest byte
133
+ const signBitSet = (bytes[bytes.length - 1] & 0x80) !== 0;
134
+ if (isNegative) {
135
+ // two's complement across variable length
136
+ let carry = 1;
137
+ for (let i = 0; i < bytes.length; i++) {
138
+ const inverted = (~bytes[i] & 0xff) + carry;
139
+ bytes[i] = inverted & 0xff;
140
+ carry = inverted > 0xff ? 1 : 0;
141
+ }
142
+ if (carry)
143
+ bytes.push(0xff);
144
+ }
145
+ else if (signBitSet) {
146
+ // prevent misinterpretation as negative
147
+ bytes.push(0x00);
148
+ }
149
+ return new Uint8Array(bytes);
150
+ }
@@ -0,0 +1,2 @@
1
+ export { Binson } from "./Binson.js";
2
+ export * as Types from "./Types.ts";
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { Binson } from "./Binson.js";
2
+ export * as Types from "./Types.js";
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "private": false,
3
+ "name": "nytra",
4
+ "version": "0.0.3",
5
+ "description": "A javascript ES6 Decorator driven library to encode/decode javascript objects to/from binary optimized buffers to reduce payload on network",
6
+ "license": "MIT",
7
+ "author": "",
8
+ "type": "module",
9
+ "main": "index.js",
10
+ "scripts": {
11
+ "test": "echo \"Error: no test specified\" && exit 1",
12
+ "build": "tsc",
13
+ "prepublishOnly": "npm run build"
14
+ },
15
+ "files": ["dist"],
16
+ "devDependencies": {
17
+ "@msgpack/msgpack": "^3.1.3",
18
+ "@types/bun": "^1.3.13",
19
+ "typescript": "^6.0.3"
20
+ },
21
+ "exports": {
22
+ ".": {
23
+ "import": "./dist/index.js",
24
+ "types": "./dist/index.d.ts"
25
+ }
26
+ }
27
+ }