novac 2.0.1 → 2.2.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/LICENSE +1 -1
- package/README.md +1574 -597
- package/bin/novac +468 -171
- package/bin/nvc +522 -0
- package/bin/nvml +78 -17
- package/demo.nv +0 -0
- package/demo_builtins.nv +0 -0
- package/demo_http.nv +0 -0
- package/examples/bf.nv +69 -0
- package/examples/math.nv +21 -0
- package/kits/birdAPI/kitdef.js +954 -0
- package/kits/kitRNG/kitdef.js +740 -0
- package/kits/kitSSH/kitdef.js +1272 -0
- package/kits/kitadb/kitdef.js +606 -0
- package/kits/kitai/kitdef.js +2185 -0
- package/kits/kitansi/kitdef.js +1402 -0
- package/kits/kitcanvas/kitdef.js +914 -0
- package/kits/kitclippy/kitdef.js +925 -0
- package/kits/kitformat/kitdef.js +1485 -0
- package/kits/kitgps/kitdef.js +1862 -0
- package/kits/kitlibproc/kitdef.js +3 -2
- package/kits/kitmatrix/ex.js +19 -0
- package/kits/kitmatrix/kitdef.js +960 -0
- package/kits/kitmorse/kitdef.js +229 -0
- package/kits/kitmpatch/kitdef.js +906 -0
- package/kits/kitnet/kitdef.js +1401 -0
- package/kits/kitnovacweb/README.md +1416 -143
- package/kits/kitnovacweb/kitdef.js +92 -2
- package/kits/kitnovacweb/nvml/executor.js +578 -176
- package/kits/kitnovacweb/nvml/index.js +2 -2
- package/kits/kitnovacweb/nvml/lexer.js +72 -69
- package/kits/kitnovacweb/nvml/parser.js +328 -159
- package/kits/kitnovacweb/nvml/renderer.js +770 -270
- package/kits/kitparse/kitdef.js +1688 -0
- package/kits/kitproto/kitdef.js +613 -0
- package/kits/kitqr/kitdef.js +637 -0
- package/kits/kitregex++/kitdef.js +1353 -0
- package/kits/kitrequire/kitdef.js +1599 -0
- package/kits/kitx11/kitdef.js +1 -0
- package/kits/kitx11/kitx11.js +2472 -0
- package/kits/kitx11/kitx11_conn.js +948 -0
- package/kits/kitx11/kitx11_worker.js +121 -0
- package/kits/libtea/kitdef.js +2691 -0
- package/kits/libterm/ex.js +285 -0
- package/kits/libterm/kitdef.js +1927 -0
- package/novac/LICENSE +21 -0
- package/novac/README.md +1823 -0
- package/novac/bin/novac +950 -0
- package/novac/bin/nvc +522 -0
- package/novac/bin/nvml +542 -0
- package/novac/demo.nv +245 -0
- package/novac/demo_builtins.nv +209 -0
- package/novac/demo_http.nv +62 -0
- package/novac/examples/bf.nv +69 -0
- package/novac/examples/math.nv +21 -0
- package/novac/kits/kitai/kitdef.js +2185 -0
- package/novac/kits/kitansi/kitdef.js +1402 -0
- package/novac/kits/kitformat/kitdef.js +1485 -0
- package/novac/kits/kitgps/kitdef.js +1862 -0
- package/novac/kits/kitlibfs/kitdef.js +231 -0
- package/{examples/example-project/nova_modules → novac/kits}/kitlibproc/kitdef.js +3 -2
- package/novac/kits/kitmatrix/ex.js +19 -0
- package/novac/kits/kitmatrix/kitdef.js +960 -0
- package/novac/kits/kitmpatch/kitdef.js +906 -0
- package/novac/kits/kitnovacweb/README.md +1572 -0
- package/novac/kits/kitnovacweb/demo.nv +12 -0
- package/novac/kits/kitnovacweb/demo.nvml +71 -0
- package/novac/kits/kitnovacweb/index.nova +12 -0
- package/novac/kits/kitnovacweb/kitdef.js +692 -0
- package/novac/kits/kitnovacweb/nova.kit.json +8 -0
- package/novac/kits/kitnovacweb/nvml/executor.js +739 -0
- package/novac/kits/kitnovacweb/nvml/index.js +67 -0
- package/novac/kits/kitnovacweb/nvml/lexer.js +263 -0
- package/novac/kits/kitnovacweb/nvml/parser.js +508 -0
- package/novac/kits/kitnovacweb/nvml/renderer.js +924 -0
- package/novac/kits/kitparse/kitdef.js +1688 -0
- package/novac/kits/kitregex++/kitdef.js +1353 -0
- package/novac/kits/kitrequire/kitdef.js +1599 -0
- package/novac/kits/kitx11/kitdef.js +1 -0
- package/novac/kits/kitx11/kitx11.js +2472 -0
- package/novac/kits/kitx11/kitx11_conn.js +948 -0
- package/novac/kits/kitx11/kitx11_worker.js +121 -0
- package/novac/kits/libtea/tf.js +2691 -0
- package/novac/kits/libterm/ex.js +285 -0
- package/novac/kits/libterm/kitdef.js +1927 -0
- package/novac/node_modules/chalk/license +9 -0
- package/novac/node_modules/chalk/package.json +83 -0
- package/novac/node_modules/chalk/readme.md +297 -0
- package/novac/node_modules/chalk/source/index.d.ts +325 -0
- package/novac/node_modules/chalk/source/index.js +225 -0
- package/novac/node_modules/chalk/source/utilities.js +33 -0
- package/novac/node_modules/chalk/source/vendor/ansi-styles/index.d.ts +236 -0
- package/novac/node_modules/chalk/source/vendor/ansi-styles/index.js +223 -0
- package/novac/node_modules/chalk/source/vendor/supports-color/browser.d.ts +1 -0
- package/novac/node_modules/chalk/source/vendor/supports-color/browser.js +34 -0
- package/novac/node_modules/chalk/source/vendor/supports-color/index.d.ts +55 -0
- package/novac/node_modules/chalk/source/vendor/supports-color/index.js +190 -0
- package/novac/node_modules/commander/LICENSE +22 -0
- package/novac/node_modules/commander/Readme.md +1176 -0
- package/novac/node_modules/commander/esm.mjs +16 -0
- package/novac/node_modules/commander/index.js +24 -0
- package/novac/node_modules/commander/lib/argument.js +150 -0
- package/novac/node_modules/commander/lib/command.js +2777 -0
- package/novac/node_modules/commander/lib/error.js +39 -0
- package/novac/node_modules/commander/lib/help.js +747 -0
- package/novac/node_modules/commander/lib/option.js +380 -0
- package/novac/node_modules/commander/lib/suggestSimilar.js +101 -0
- package/novac/node_modules/commander/package-support.json +19 -0
- package/novac/node_modules/commander/package.json +82 -0
- package/novac/node_modules/commander/typings/esm.d.mts +3 -0
- package/novac/node_modules/commander/typings/index.d.ts +1113 -0
- package/novac/node_modules/node-addon-api/LICENSE.md +9 -0
- package/novac/node_modules/node-addon-api/README.md +95 -0
- package/novac/node_modules/node-addon-api/common.gypi +21 -0
- package/novac/node_modules/node-addon-api/except.gypi +25 -0
- package/novac/node_modules/node-addon-api/index.js +14 -0
- package/novac/node_modules/node-addon-api/napi-inl.deprecated.h +186 -0
- package/novac/node_modules/node-addon-api/napi-inl.h +7165 -0
- package/novac/node_modules/node-addon-api/napi.h +3364 -0
- package/novac/node_modules/node-addon-api/node_addon_api.gyp +42 -0
- package/novac/node_modules/node-addon-api/node_api.gyp +9 -0
- package/novac/node_modules/node-addon-api/noexcept.gypi +26 -0
- package/novac/node_modules/node-addon-api/package-support.json +21 -0
- package/novac/node_modules/node-addon-api/package.json +480 -0
- package/novac/node_modules/node-addon-api/tools/README.md +73 -0
- package/novac/node_modules/node-addon-api/tools/check-napi.js +99 -0
- package/novac/node_modules/node-addon-api/tools/clang-format.js +71 -0
- package/novac/node_modules/node-addon-api/tools/conversion.js +301 -0
- package/novac/node_modules/serialize-javascript/LICENSE +27 -0
- package/novac/node_modules/serialize-javascript/README.md +149 -0
- package/novac/node_modules/serialize-javascript/index.js +297 -0
- package/novac/node_modules/serialize-javascript/package.json +33 -0
- package/novac/package.json +27 -0
- package/novac/scripts/update-bin.js +24 -0
- package/novac/src/core/bstd.js +1035 -0
- package/novac/src/core/config.js +155 -0
- package/novac/src/core/describe.js +187 -0
- package/novac/src/core/emitter.js +499 -0
- package/novac/src/core/error.js +86 -0
- package/novac/src/core/executor.js +5606 -0
- package/novac/src/core/formatter.js +686 -0
- package/novac/src/core/lexer.js +1026 -0
- package/novac/src/core/nova_builtins.js +717 -0
- package/novac/src/core/nova_thread_worker.js +166 -0
- package/novac/src/core/parser.js +2181 -0
- package/novac/src/core/types.js +112 -0
- package/novac/src/index.js +28 -0
- package/novac/src/runtime/stdlib.js +244 -0
- package/package.json +6 -3
- package/scripts/update-bin.js +0 -0
- package/src/core/bstd.js +838 -362
- package/src/core/executor.js +2578 -170
- package/src/core/lexer.js +502 -54
- package/src/core/nova_builtins.js +21 -3
- package/src/core/parser.js +413 -72
- package/src/core/types.js +30 -2
- package/src/index.js +0 -0
- package/examples/example-project/README.md +0 -3
- package/examples/example-project/src/main.nova +0 -3
- package/src/core/environment.js +0 -0
- /package/{examples/example-project/bin/example-project.nv → novac/node_modules/node-addon-api/nothing.c} +0 -0
|
@@ -0,0 +1,613 @@
|
|
|
1
|
+
// kitproto — novac protocol buffers / binary serialization kit
|
|
2
|
+
// Pure JS. No external deps.
|
|
3
|
+
// Define message schemas, encode to compact binary, decode back.
|
|
4
|
+
// Supports: int32, int64, uint32, uint64, sint32, sint64, float, double,
|
|
5
|
+
// bool, string, bytes, nested messages, repeated fields, maps,
|
|
6
|
+
// oneofs, enums, optional/required fields, versioning.
|
|
7
|
+
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
// ─── WIRE TYPES ───────────────────────────────────────────────────────────────
|
|
11
|
+
|
|
12
|
+
const WIRE = {
|
|
13
|
+
VARINT: 0, // int32, int64, uint32, uint64, sint32, sint64, bool, enum
|
|
14
|
+
I64: 1, // fixed64, sfixed64, double
|
|
15
|
+
LEN: 2, // string, bytes, embedded messages, packed repeated
|
|
16
|
+
I32: 5, // fixed32, sfixed32, float
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const FIELD_TYPES = {
|
|
20
|
+
int32: { wire: WIRE.VARINT, js: 'number' },
|
|
21
|
+
int64: { wire: WIRE.VARINT, js: 'bigint' },
|
|
22
|
+
uint32: { wire: WIRE.VARINT, js: 'number' },
|
|
23
|
+
uint64: { wire: WIRE.VARINT, js: 'bigint' },
|
|
24
|
+
sint32: { wire: WIRE.VARINT, js: 'number' }, // zigzag
|
|
25
|
+
sint64: { wire: WIRE.VARINT, js: 'bigint' }, // zigzag
|
|
26
|
+
bool: { wire: WIRE.VARINT, js: 'boolean' },
|
|
27
|
+
enum: { wire: WIRE.VARINT, js: 'number' },
|
|
28
|
+
fixed32: { wire: WIRE.I32, js: 'number' },
|
|
29
|
+
sfixed32: { wire: WIRE.I32, js: 'number' },
|
|
30
|
+
float: { wire: WIRE.I32, js: 'number' },
|
|
31
|
+
fixed64: { wire: WIRE.I64, js: 'bigint' },
|
|
32
|
+
sfixed64: { wire: WIRE.I64, js: 'bigint' },
|
|
33
|
+
double: { wire: WIRE.I64, js: 'number' },
|
|
34
|
+
string: { wire: WIRE.LEN, js: 'string' },
|
|
35
|
+
bytes: { wire: WIRE.LEN, js: 'buffer' },
|
|
36
|
+
message: { wire: WIRE.LEN, js: 'object' },
|
|
37
|
+
map: { wire: WIRE.LEN, js: 'object' },
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// ─── VARINT ENCODING ─────────────────────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
function encodeVarint(value) {
|
|
43
|
+
const bytes = [];
|
|
44
|
+
let v = typeof value === 'bigint' ? value : BigInt(Math.trunc(value));
|
|
45
|
+
// Handle negative numbers as two's complement 64-bit
|
|
46
|
+
if (v < 0n) v = v + (1n << 64n);
|
|
47
|
+
do {
|
|
48
|
+
let byte = Number(v & 0x7fn);
|
|
49
|
+
v >>= 7n;
|
|
50
|
+
if (v > 0n) byte |= 0x80;
|
|
51
|
+
bytes.push(byte);
|
|
52
|
+
} while (v > 0n);
|
|
53
|
+
return Buffer.from(bytes);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function decodeVarint(buf, offset) {
|
|
57
|
+
let result = 0n, shift = 0n;
|
|
58
|
+
let pos = offset;
|
|
59
|
+
while (pos < buf.length) {
|
|
60
|
+
const byte = buf[pos++];
|
|
61
|
+
result |= BigInt(byte & 0x7f) << shift;
|
|
62
|
+
shift += 7n;
|
|
63
|
+
if (!(byte & 0x80)) break;
|
|
64
|
+
}
|
|
65
|
+
return { value: result, offset: pos };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function decodeVarintSigned(buf, offset) {
|
|
69
|
+
const { value, offset: newOffset } = decodeVarint(buf, offset);
|
|
70
|
+
// Convert from unsigned 64-bit to signed
|
|
71
|
+
const signed = value >= (1n << 63n) ? value - (1n << 64n) : value;
|
|
72
|
+
return { value: signed, offset: newOffset };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Zigzag encoding for sint32/sint64
|
|
76
|
+
function zigzagEncode32(n) { return ((n << 1) ^ (n >> 31)) >>> 0; }
|
|
77
|
+
function zigzagDecode32(n) { return ((n >>> 1) ^ -(n & 1)); }
|
|
78
|
+
function zigzagEncode64(n) { return (n << 1n) ^ (n >> 63n); }
|
|
79
|
+
function zigzagDecode64(n) { return (n >> 1n) ^ -(n & 1n); }
|
|
80
|
+
|
|
81
|
+
// ─── FIELD TAG ────────────────────────────────────────────────────────────────
|
|
82
|
+
|
|
83
|
+
function encodeTag(fieldNumber, wireType) {
|
|
84
|
+
return encodeVarint(BigInt((fieldNumber << 3) | wireType));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function decodeTag(buf, offset) {
|
|
88
|
+
const { value, offset: newOffset } = decodeVarint(buf, offset);
|
|
89
|
+
const tag = Number(value);
|
|
90
|
+
const fieldNumber = tag >>> 3;
|
|
91
|
+
const wireType = tag & 0x7;
|
|
92
|
+
return { fieldNumber, wireType, offset: newOffset };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ─── PRIMITIVE ENCODERS ───────────────────────────────────────────────────────
|
|
96
|
+
|
|
97
|
+
function encodeField(type, value) {
|
|
98
|
+
switch (type) {
|
|
99
|
+
case 'int32':
|
|
100
|
+
case 'uint32':
|
|
101
|
+
case 'enum':
|
|
102
|
+
return encodeVarint(value);
|
|
103
|
+
case 'int64':
|
|
104
|
+
case 'uint64':
|
|
105
|
+
return encodeVarint(value);
|
|
106
|
+
case 'sint32':
|
|
107
|
+
return encodeVarint(zigzagEncode32(value));
|
|
108
|
+
case 'sint64':
|
|
109
|
+
return encodeVarint(zigzagEncode64(typeof value === 'bigint' ? value : BigInt(value)));
|
|
110
|
+
case 'bool':
|
|
111
|
+
return encodeVarint(value ? 1 : 0);
|
|
112
|
+
case 'fixed32':
|
|
113
|
+
case 'sfixed32': {
|
|
114
|
+
const b = Buffer.allocUnsafe(4);
|
|
115
|
+
b.writeInt32LE(value, 0);
|
|
116
|
+
return b;
|
|
117
|
+
}
|
|
118
|
+
case 'float': {
|
|
119
|
+
const b = Buffer.allocUnsafe(4);
|
|
120
|
+
b.writeFloatLE(value, 0);
|
|
121
|
+
return b;
|
|
122
|
+
}
|
|
123
|
+
case 'fixed64':
|
|
124
|
+
case 'sfixed64': {
|
|
125
|
+
const b = Buffer.allocUnsafe(8);
|
|
126
|
+
b.writeBigInt64LE(typeof value === 'bigint' ? value : BigInt(value), 0);
|
|
127
|
+
return b;
|
|
128
|
+
}
|
|
129
|
+
case 'double': {
|
|
130
|
+
const b = Buffer.allocUnsafe(8);
|
|
131
|
+
b.writeDoubleLE(value, 0);
|
|
132
|
+
return b;
|
|
133
|
+
}
|
|
134
|
+
case 'string': {
|
|
135
|
+
const encoded = Buffer.from(value, 'utf8');
|
|
136
|
+
return Buffer.concat([encodeVarint(encoded.length), encoded]);
|
|
137
|
+
}
|
|
138
|
+
case 'bytes': {
|
|
139
|
+
const buf = Buffer.isBuffer(value) ? value : Buffer.from(value);
|
|
140
|
+
return Buffer.concat([encodeVarint(buf.length), buf]);
|
|
141
|
+
}
|
|
142
|
+
default:
|
|
143
|
+
throw new Error(`Unknown primitive type: ${type}`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function decodeField(type, buf, offset, length) {
|
|
148
|
+
switch (type) {
|
|
149
|
+
case 'int32': {
|
|
150
|
+
const { value, offset: o } = decodeVarintSigned(buf, offset);
|
|
151
|
+
return { value: Number(BigInt.asIntN(32, value)), offset: o };
|
|
152
|
+
}
|
|
153
|
+
case 'uint32': {
|
|
154
|
+
const { value, offset: o } = decodeVarint(buf, offset);
|
|
155
|
+
return { value: Number(value) >>> 0, offset: o };
|
|
156
|
+
}
|
|
157
|
+
case 'int64': {
|
|
158
|
+
const { value, offset: o } = decodeVarintSigned(buf, offset);
|
|
159
|
+
return { value, offset: o };
|
|
160
|
+
}
|
|
161
|
+
case 'uint64': {
|
|
162
|
+
const { value, offset: o } = decodeVarint(buf, offset);
|
|
163
|
+
return { value, offset: o };
|
|
164
|
+
}
|
|
165
|
+
case 'sint32': {
|
|
166
|
+
const { value, offset: o } = decodeVarint(buf, offset);
|
|
167
|
+
return { value: zigzagDecode32(Number(value)), offset: o };
|
|
168
|
+
}
|
|
169
|
+
case 'sint64': {
|
|
170
|
+
const { value, offset: o } = decodeVarint(buf, offset);
|
|
171
|
+
return { value: zigzagDecode64(value), offset: o };
|
|
172
|
+
}
|
|
173
|
+
case 'bool': {
|
|
174
|
+
const { value, offset: o } = decodeVarint(buf, offset);
|
|
175
|
+
return { value: value !== 0n, offset: o };
|
|
176
|
+
}
|
|
177
|
+
case 'enum': {
|
|
178
|
+
const { value, offset: o } = decodeVarint(buf, offset);
|
|
179
|
+
return { value: Number(value), offset: o };
|
|
180
|
+
}
|
|
181
|
+
case 'fixed32':
|
|
182
|
+
case 'sfixed32':
|
|
183
|
+
return { value: buf.readInt32LE(offset), offset: offset + 4 };
|
|
184
|
+
case 'float':
|
|
185
|
+
return { value: buf.readFloatLE(offset), offset: offset + 4 };
|
|
186
|
+
case 'fixed64':
|
|
187
|
+
case 'sfixed64':
|
|
188
|
+
return { value: buf.readBigInt64LE(offset), offset: offset + 8 };
|
|
189
|
+
case 'double':
|
|
190
|
+
return { value: buf.readDoubleLE(offset), offset: offset + 8 };
|
|
191
|
+
case 'string': {
|
|
192
|
+
const { value: len, offset: o } = decodeVarint(buf, offset);
|
|
193
|
+
const end = o + Number(len);
|
|
194
|
+
return { value: buf.slice(o, end).toString('utf8'), offset: end };
|
|
195
|
+
}
|
|
196
|
+
case 'bytes': {
|
|
197
|
+
const { value: len, offset: o } = decodeVarint(buf, offset);
|
|
198
|
+
const end = o + Number(len);
|
|
199
|
+
return { value: buf.slice(o, end), offset: end };
|
|
200
|
+
}
|
|
201
|
+
default:
|
|
202
|
+
throw new Error(`Unknown primitive type: ${type}`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// ─── SCHEMA DEFINITION ───────────────────────────────────────────────────────
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Define a message schema.
|
|
210
|
+
*
|
|
211
|
+
* @example
|
|
212
|
+
* const Person = defineMessage('Person', {
|
|
213
|
+
* name: { field: 1, type: 'string' },
|
|
214
|
+
* age: { field: 2, type: 'int32' },
|
|
215
|
+
* email: { field: 3, type: 'string', optional: true },
|
|
216
|
+
* scores: { field: 4, type: 'int32', repeated: true },
|
|
217
|
+
* address:{ field: 5, type: 'message', message: Address },
|
|
218
|
+
* meta: { field: 6, type: 'map', keyType: 'string', valueType: 'string' },
|
|
219
|
+
* });
|
|
220
|
+
*/
|
|
221
|
+
function defineMessage(name, fields) {
|
|
222
|
+
// Build field number → field name + def lookup
|
|
223
|
+
const byNumber = {};
|
|
224
|
+
const byName = {};
|
|
225
|
+
for (const [fname, def] of Object.entries(fields)) {
|
|
226
|
+
if (!def.field) throw new Error(`Field '${fname}' in message '${name}' missing 'field' number`);
|
|
227
|
+
if (!def.type) throw new Error(`Field '${fname}' in message '${name}' missing 'type'`);
|
|
228
|
+
byNumber[def.field] = { name: fname, ...def };
|
|
229
|
+
byName[fname] = { name: fname, ...def };
|
|
230
|
+
}
|
|
231
|
+
return { _name: name, _fields: byName, _byNumber: byNumber };
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Define an enum.
|
|
236
|
+
* @example
|
|
237
|
+
* const Status = defineEnum('Status', { UNKNOWN: 0, ACTIVE: 1, INACTIVE: 2 });
|
|
238
|
+
*/
|
|
239
|
+
function defineEnum(name, values) {
|
|
240
|
+
const byName = { ...values };
|
|
241
|
+
const byValue = Object.fromEntries(Object.entries(values).map(([k, v]) => [v, k]));
|
|
242
|
+
return { _name: name, _byName: byName, _byValue: byValue, _isEnum: true };
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// ─── ENCODER ─────────────────────────────────────────────────────────────────
|
|
246
|
+
|
|
247
|
+
function encode(schema, obj) {
|
|
248
|
+
if (!schema || !schema._fields) throw new Error('Invalid schema — use defineMessage()');
|
|
249
|
+
const parts = [];
|
|
250
|
+
|
|
251
|
+
for (const [fname, def] of Object.entries(schema._fields)) {
|
|
252
|
+
const value = obj[fname];
|
|
253
|
+
if (value === undefined || value === null) {
|
|
254
|
+
if (!def.optional && !def.repeated) {
|
|
255
|
+
// Use default: 0 / '' / false / [] depending on type — just skip for now
|
|
256
|
+
}
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const fieldNum = def.field;
|
|
261
|
+
|
|
262
|
+
// Map field
|
|
263
|
+
if (def.type === 'map') {
|
|
264
|
+
for (const [k, v] of Object.entries(value)) {
|
|
265
|
+
// Encode as a message with field 1 = key, field 2 = value
|
|
266
|
+
const entrySchema = defineMessage(`${fname}_entry`, {
|
|
267
|
+
key: { field: 1, type: def.keyType ?? 'string' },
|
|
268
|
+
value: { field: 2, type: def.valueType ?? 'string' },
|
|
269
|
+
});
|
|
270
|
+
const entryBuf = encode(entrySchema, { key: k, value: v });
|
|
271
|
+
parts.push(encodeTag(fieldNum, WIRE.LEN));
|
|
272
|
+
parts.push(encodeVarint(entryBuf.length));
|
|
273
|
+
parts.push(entryBuf);
|
|
274
|
+
}
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Repeated field
|
|
279
|
+
if (def.repeated) {
|
|
280
|
+
if (!Array.isArray(value)) throw new Error(`Field '${fname}' is repeated but got non-array`);
|
|
281
|
+
const isPacked = FIELD_TYPES[def.type]?.wire === WIRE.VARINT ||
|
|
282
|
+
FIELD_TYPES[def.type]?.wire === WIRE.I32 ||
|
|
283
|
+
FIELD_TYPES[def.type]?.wire === WIRE.I64;
|
|
284
|
+
if (isPacked && def.packed !== false) {
|
|
285
|
+
// Pack into single LEN field
|
|
286
|
+
const packed = Buffer.concat(value.map(v => encodeField(def.type, v)));
|
|
287
|
+
parts.push(encodeTag(fieldNum, WIRE.LEN));
|
|
288
|
+
parts.push(encodeVarint(packed.length));
|
|
289
|
+
parts.push(packed);
|
|
290
|
+
} else {
|
|
291
|
+
for (const item of value) {
|
|
292
|
+
if (def.type === 'message') {
|
|
293
|
+
const nested = encode(def.message, item);
|
|
294
|
+
parts.push(encodeTag(fieldNum, WIRE.LEN));
|
|
295
|
+
parts.push(encodeVarint(nested.length));
|
|
296
|
+
parts.push(nested);
|
|
297
|
+
} else {
|
|
298
|
+
parts.push(encodeTag(fieldNum, FIELD_TYPES[def.type].wire));
|
|
299
|
+
parts.push(encodeField(def.type, item));
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Nested message
|
|
307
|
+
if (def.type === 'message') {
|
|
308
|
+
const nested = encode(def.message, value);
|
|
309
|
+
parts.push(encodeTag(fieldNum, WIRE.LEN));
|
|
310
|
+
parts.push(encodeVarint(nested.length));
|
|
311
|
+
parts.push(nested);
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Oneof — just encode the present field
|
|
316
|
+
if (def.oneof) {
|
|
317
|
+
// handled as regular field
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Enum
|
|
321
|
+
if (def.type === 'enum' && def.enum) {
|
|
322
|
+
const numVal = typeof value === 'string' ? def.enum._byName[value] : value;
|
|
323
|
+
parts.push(encodeTag(fieldNum, WIRE.VARINT));
|
|
324
|
+
parts.push(encodeVarint(numVal ?? 0));
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Primitive
|
|
329
|
+
const ft = FIELD_TYPES[def.type];
|
|
330
|
+
if (!ft) throw new Error(`Unknown field type '${def.type}' for field '${fname}'`);
|
|
331
|
+
parts.push(encodeTag(fieldNum, ft.wire));
|
|
332
|
+
parts.push(encodeField(def.type, value));
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return Buffer.concat(parts);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// ─── DECODER ─────────────────────────────────────────────────────────────────
|
|
339
|
+
|
|
340
|
+
function decode(schema, buf) {
|
|
341
|
+
if (!schema || !schema._fields) throw new Error('Invalid schema — use defineMessage()');
|
|
342
|
+
const obj = {};
|
|
343
|
+
let offset = 0;
|
|
344
|
+
|
|
345
|
+
// Initialize repeated fields and maps
|
|
346
|
+
for (const [fname, def] of Object.entries(schema._fields)) {
|
|
347
|
+
if (def.repeated) obj[fname] = [];
|
|
348
|
+
if (def.type === 'map') obj[fname] = {};
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
while (offset < buf.length) {
|
|
352
|
+
const { fieldNumber, wireType, offset: tagEnd } = decodeTag(buf, offset);
|
|
353
|
+
offset = tagEnd;
|
|
354
|
+
|
|
355
|
+
const def = schema._byNumber[fieldNumber];
|
|
356
|
+
if (!def) {
|
|
357
|
+
// Unknown field — skip it
|
|
358
|
+
offset = skipField(buf, offset, wireType);
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Map field
|
|
363
|
+
if (def.type === 'map') {
|
|
364
|
+
const { value: len, offset: o } = decodeVarint(buf, offset);
|
|
365
|
+
const entryBuf = buf.slice(o, o + Number(len));
|
|
366
|
+
offset = o + Number(len);
|
|
367
|
+
const entrySchema = defineMessage(`${def.name}_entry`, {
|
|
368
|
+
key: { field: 1, type: def.keyType ?? 'string' },
|
|
369
|
+
value: { field: 2, type: def.valueType ?? 'string' },
|
|
370
|
+
});
|
|
371
|
+
const entry = decode(entrySchema, entryBuf);
|
|
372
|
+
obj[def.name][entry.key] = entry.value;
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// LEN-delimited
|
|
377
|
+
if (wireType === WIRE.LEN) {
|
|
378
|
+
const { value: len, offset: o } = decodeVarint(buf, offset);
|
|
379
|
+
const end = o + Number(len);
|
|
380
|
+
const fieldBuf = buf.slice(o, end);
|
|
381
|
+
offset = end;
|
|
382
|
+
|
|
383
|
+
if (def.type === 'message') {
|
|
384
|
+
const nested = decode(def.message, fieldBuf);
|
|
385
|
+
if (def.repeated) obj[def.name].push(nested);
|
|
386
|
+
else obj[def.name] = nested;
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (def.type === 'string') {
|
|
391
|
+
const val = fieldBuf.toString('utf8');
|
|
392
|
+
if (def.repeated) obj[def.name].push(val);
|
|
393
|
+
else obj[def.name] = val;
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
if (def.type === 'bytes') {
|
|
398
|
+
if (def.repeated) obj[def.name].push(fieldBuf);
|
|
399
|
+
else obj[def.name] = fieldBuf;
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Packed repeated
|
|
404
|
+
if (def.repeated) {
|
|
405
|
+
let pos = 0;
|
|
406
|
+
while (pos < fieldBuf.length) {
|
|
407
|
+
const { value, offset: newPos } = decodeField(def.type, fieldBuf, pos);
|
|
408
|
+
obj[def.name].push(value);
|
|
409
|
+
pos = newPos;
|
|
410
|
+
}
|
|
411
|
+
continue;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// VARINT
|
|
416
|
+
if (wireType === WIRE.VARINT) {
|
|
417
|
+
if (def.type === 'enum' && def.enum) {
|
|
418
|
+
const { value, offset: o } = decodeVarint(buf, offset);
|
|
419
|
+
const numVal = Number(value);
|
|
420
|
+
const strVal = def.enum._byValue[numVal] ?? numVal;
|
|
421
|
+
if (def.repeated) obj[def.name].push(strVal);
|
|
422
|
+
else obj[def.name] = strVal;
|
|
423
|
+
offset = o;
|
|
424
|
+
continue;
|
|
425
|
+
}
|
|
426
|
+
const { value, offset: o } = decodeField(def.type, buf, offset);
|
|
427
|
+
if (def.repeated) obj[def.name].push(value);
|
|
428
|
+
else obj[def.name] = value;
|
|
429
|
+
offset = o;
|
|
430
|
+
continue;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// I32
|
|
434
|
+
if (wireType === WIRE.I32) {
|
|
435
|
+
const { value, offset: o } = decodeField(def.type, buf, offset);
|
|
436
|
+
if (def.repeated) obj[def.name].push(value);
|
|
437
|
+
else obj[def.name] = value;
|
|
438
|
+
offset = o;
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// I64
|
|
443
|
+
if (wireType === WIRE.I64) {
|
|
444
|
+
const { value, offset: o } = decodeField(def.type, buf, offset);
|
|
445
|
+
if (def.repeated) obj[def.name].push(value);
|
|
446
|
+
else obj[def.name] = value;
|
|
447
|
+
offset = o;
|
|
448
|
+
continue;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
offset = skipField(buf, offset, wireType);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
return obj;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function skipField(buf, offset, wireType) {
|
|
458
|
+
switch (wireType) {
|
|
459
|
+
case WIRE.VARINT: {
|
|
460
|
+
while (offset < buf.length && buf[offset] & 0x80) offset++;
|
|
461
|
+
return offset + 1;
|
|
462
|
+
}
|
|
463
|
+
case WIRE.I64: return offset + 8;
|
|
464
|
+
case WIRE.I32: return offset + 4;
|
|
465
|
+
case WIRE.LEN: {
|
|
466
|
+
const { value: len, offset: o } = decodeVarint(buf, offset);
|
|
467
|
+
return o + Number(len);
|
|
468
|
+
}
|
|
469
|
+
default: throw new Error(`Unknown wire type ${wireType} during skip`);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// ─── MESSAGE CLASS FACTORY ───────────────────────────────────────────────────
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Create a Message class from a schema.
|
|
477
|
+
* Instances have encode(), toJSON(), static decode().
|
|
478
|
+
*
|
|
479
|
+
* @example
|
|
480
|
+
* const Person = createMessageClass(PersonSchema);
|
|
481
|
+
* const p = new Person({ name: 'Alice', age: 30 });
|
|
482
|
+
* const buf = p.encode();
|
|
483
|
+
* const p2 = Person.decode(buf);
|
|
484
|
+
*/
|
|
485
|
+
function createMessageClass(schema) {
|
|
486
|
+
class Message {
|
|
487
|
+
constructor(data = {}) {
|
|
488
|
+
Object.assign(this, data);
|
|
489
|
+
}
|
|
490
|
+
encode() { return encode(schema, this); }
|
|
491
|
+
toJSON() {
|
|
492
|
+
const obj = {};
|
|
493
|
+
for (const key of Object.keys(schema._fields)) {
|
|
494
|
+
if (this[key] !== undefined) obj[key] = this[key];
|
|
495
|
+
}
|
|
496
|
+
return obj;
|
|
497
|
+
}
|
|
498
|
+
static decode(buf) { return new Message(decode(schema, buf)); }
|
|
499
|
+
static get schema() { return schema; }
|
|
500
|
+
static get name() { return schema._name; }
|
|
501
|
+
}
|
|
502
|
+
Object.defineProperty(Message, 'name', { value: schema._name });
|
|
503
|
+
return Message;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// ─── VERSIONING ───────────────────────────────────────────────────────────────
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* Wrap a message with a version header.
|
|
510
|
+
* @param {number} version
|
|
511
|
+
* @param {Buffer} payload
|
|
512
|
+
* @returns {Buffer}
|
|
513
|
+
*/
|
|
514
|
+
function encodeVersioned(version, payload) {
|
|
515
|
+
const verBuf = Buffer.allocUnsafe(4);
|
|
516
|
+
verBuf.writeUInt32BE(version, 0);
|
|
517
|
+
return Buffer.concat([verBuf, payload]);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* Decode a versioned message.
|
|
522
|
+
* @param {Buffer} buf
|
|
523
|
+
* @returns {{ version: number, payload: Buffer }}
|
|
524
|
+
*/
|
|
525
|
+
function decodeVersioned(buf) {
|
|
526
|
+
const version = buf.readUInt32BE(0);
|
|
527
|
+
const payload = buf.slice(4);
|
|
528
|
+
return { version, payload };
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// ─── STREAM ENCODER / DECODER ────────────────────────────────────────────────
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* Encode multiple messages into a length-delimited stream.
|
|
535
|
+
* @param {object} schema
|
|
536
|
+
* @param {object[]} messages
|
|
537
|
+
* @returns {Buffer}
|
|
538
|
+
*/
|
|
539
|
+
function encodeStream(schema, messages) {
|
|
540
|
+
const parts = [];
|
|
541
|
+
for (const msg of messages) {
|
|
542
|
+
const encoded = encode(schema, msg);
|
|
543
|
+
const lenBuf = Buffer.allocUnsafe(4);
|
|
544
|
+
lenBuf.writeUInt32BE(encoded.length, 0);
|
|
545
|
+
parts.push(lenBuf, encoded);
|
|
546
|
+
}
|
|
547
|
+
return Buffer.concat(parts);
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* Decode a length-delimited stream into multiple messages.
|
|
552
|
+
* @param {object} schema
|
|
553
|
+
* @param {Buffer} buf
|
|
554
|
+
* @returns {object[]}
|
|
555
|
+
*/
|
|
556
|
+
function decodeStream(schema, buf) {
|
|
557
|
+
const messages = [];
|
|
558
|
+
let offset = 0;
|
|
559
|
+
while (offset + 4 <= buf.length) {
|
|
560
|
+
const len = buf.readUInt32BE(offset);
|
|
561
|
+
offset += 4;
|
|
562
|
+
const msgBuf = buf.slice(offset, offset + len);
|
|
563
|
+
offset += len;
|
|
564
|
+
messages.push(decode(schema, msgBuf));
|
|
565
|
+
}
|
|
566
|
+
return messages;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// ─── SCHEMA INTROSPECTION ─────────────────────────────────────────────────────
|
|
570
|
+
|
|
571
|
+
/**
|
|
572
|
+
* Dump a schema as a human-readable description.
|
|
573
|
+
* @param {object} schema
|
|
574
|
+
* @returns {string}
|
|
575
|
+
*/
|
|
576
|
+
function describeSchema(schema) {
|
|
577
|
+
const lines = [`message ${schema._name} {`];
|
|
578
|
+
for (const [fname, def] of Object.entries(schema._fields)) {
|
|
579
|
+
const repeated = def.repeated ? 'repeated ' : '';
|
|
580
|
+
const optional = def.optional ? 'optional ' : '';
|
|
581
|
+
const type = def.type === 'message' ? def.message._name : def.type;
|
|
582
|
+
lines.push(` ${optional}${repeated}${type} ${fname} = ${def.field};`);
|
|
583
|
+
}
|
|
584
|
+
lines.push('}');
|
|
585
|
+
return lines.join('\n');
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// ─── EXPORTS ─────────────────────────────────────────────────────────────────
|
|
589
|
+
|
|
590
|
+
module.exports = {
|
|
591
|
+
kitdef: {
|
|
592
|
+
defineMessage,
|
|
593
|
+
defineEnum,
|
|
594
|
+
createMessageClass,
|
|
595
|
+
encode,
|
|
596
|
+
decode,
|
|
597
|
+
encodeVersioned,
|
|
598
|
+
decodeVersioned,
|
|
599
|
+
encodeStream,
|
|
600
|
+
decodeStream,
|
|
601
|
+
describeSchema,
|
|
602
|
+
// Low-level
|
|
603
|
+
encodeVarint,
|
|
604
|
+
decodeVarint,
|
|
605
|
+
encodeField,
|
|
606
|
+
decodeField,
|
|
607
|
+
encodeTag,
|
|
608
|
+
decodeTag,
|
|
609
|
+
WIRE,
|
|
610
|
+
FIELD_TYPES,
|
|
611
|
+
}
|
|
612
|
+
};
|
|
613
|
+
|