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.
Files changed (161) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +1574 -597
  3. package/bin/novac +468 -171
  4. package/bin/nvc +522 -0
  5. package/bin/nvml +78 -17
  6. package/demo.nv +0 -0
  7. package/demo_builtins.nv +0 -0
  8. package/demo_http.nv +0 -0
  9. package/examples/bf.nv +69 -0
  10. package/examples/math.nv +21 -0
  11. package/kits/birdAPI/kitdef.js +954 -0
  12. package/kits/kitRNG/kitdef.js +740 -0
  13. package/kits/kitSSH/kitdef.js +1272 -0
  14. package/kits/kitadb/kitdef.js +606 -0
  15. package/kits/kitai/kitdef.js +2185 -0
  16. package/kits/kitansi/kitdef.js +1402 -0
  17. package/kits/kitcanvas/kitdef.js +914 -0
  18. package/kits/kitclippy/kitdef.js +925 -0
  19. package/kits/kitformat/kitdef.js +1485 -0
  20. package/kits/kitgps/kitdef.js +1862 -0
  21. package/kits/kitlibproc/kitdef.js +3 -2
  22. package/kits/kitmatrix/ex.js +19 -0
  23. package/kits/kitmatrix/kitdef.js +960 -0
  24. package/kits/kitmorse/kitdef.js +229 -0
  25. package/kits/kitmpatch/kitdef.js +906 -0
  26. package/kits/kitnet/kitdef.js +1401 -0
  27. package/kits/kitnovacweb/README.md +1416 -143
  28. package/kits/kitnovacweb/kitdef.js +92 -2
  29. package/kits/kitnovacweb/nvml/executor.js +578 -176
  30. package/kits/kitnovacweb/nvml/index.js +2 -2
  31. package/kits/kitnovacweb/nvml/lexer.js +72 -69
  32. package/kits/kitnovacweb/nvml/parser.js +328 -159
  33. package/kits/kitnovacweb/nvml/renderer.js +770 -270
  34. package/kits/kitparse/kitdef.js +1688 -0
  35. package/kits/kitproto/kitdef.js +613 -0
  36. package/kits/kitqr/kitdef.js +637 -0
  37. package/kits/kitregex++/kitdef.js +1353 -0
  38. package/kits/kitrequire/kitdef.js +1599 -0
  39. package/kits/kitx11/kitdef.js +1 -0
  40. package/kits/kitx11/kitx11.js +2472 -0
  41. package/kits/kitx11/kitx11_conn.js +948 -0
  42. package/kits/kitx11/kitx11_worker.js +121 -0
  43. package/kits/libtea/kitdef.js +2691 -0
  44. package/kits/libterm/ex.js +285 -0
  45. package/kits/libterm/kitdef.js +1927 -0
  46. package/novac/LICENSE +21 -0
  47. package/novac/README.md +1823 -0
  48. package/novac/bin/novac +950 -0
  49. package/novac/bin/nvc +522 -0
  50. package/novac/bin/nvml +542 -0
  51. package/novac/demo.nv +245 -0
  52. package/novac/demo_builtins.nv +209 -0
  53. package/novac/demo_http.nv +62 -0
  54. package/novac/examples/bf.nv +69 -0
  55. package/novac/examples/math.nv +21 -0
  56. package/novac/kits/kitai/kitdef.js +2185 -0
  57. package/novac/kits/kitansi/kitdef.js +1402 -0
  58. package/novac/kits/kitformat/kitdef.js +1485 -0
  59. package/novac/kits/kitgps/kitdef.js +1862 -0
  60. package/novac/kits/kitlibfs/kitdef.js +231 -0
  61. package/{examples/example-project/nova_modules → novac/kits}/kitlibproc/kitdef.js +3 -2
  62. package/novac/kits/kitmatrix/ex.js +19 -0
  63. package/novac/kits/kitmatrix/kitdef.js +960 -0
  64. package/novac/kits/kitmpatch/kitdef.js +906 -0
  65. package/novac/kits/kitnovacweb/README.md +1572 -0
  66. package/novac/kits/kitnovacweb/demo.nv +12 -0
  67. package/novac/kits/kitnovacweb/demo.nvml +71 -0
  68. package/novac/kits/kitnovacweb/index.nova +12 -0
  69. package/novac/kits/kitnovacweb/kitdef.js +692 -0
  70. package/novac/kits/kitnovacweb/nova.kit.json +8 -0
  71. package/novac/kits/kitnovacweb/nvml/executor.js +739 -0
  72. package/novac/kits/kitnovacweb/nvml/index.js +67 -0
  73. package/novac/kits/kitnovacweb/nvml/lexer.js +263 -0
  74. package/novac/kits/kitnovacweb/nvml/parser.js +508 -0
  75. package/novac/kits/kitnovacweb/nvml/renderer.js +924 -0
  76. package/novac/kits/kitparse/kitdef.js +1688 -0
  77. package/novac/kits/kitregex++/kitdef.js +1353 -0
  78. package/novac/kits/kitrequire/kitdef.js +1599 -0
  79. package/novac/kits/kitx11/kitdef.js +1 -0
  80. package/novac/kits/kitx11/kitx11.js +2472 -0
  81. package/novac/kits/kitx11/kitx11_conn.js +948 -0
  82. package/novac/kits/kitx11/kitx11_worker.js +121 -0
  83. package/novac/kits/libtea/tf.js +2691 -0
  84. package/novac/kits/libterm/ex.js +285 -0
  85. package/novac/kits/libterm/kitdef.js +1927 -0
  86. package/novac/node_modules/chalk/license +9 -0
  87. package/novac/node_modules/chalk/package.json +83 -0
  88. package/novac/node_modules/chalk/readme.md +297 -0
  89. package/novac/node_modules/chalk/source/index.d.ts +325 -0
  90. package/novac/node_modules/chalk/source/index.js +225 -0
  91. package/novac/node_modules/chalk/source/utilities.js +33 -0
  92. package/novac/node_modules/chalk/source/vendor/ansi-styles/index.d.ts +236 -0
  93. package/novac/node_modules/chalk/source/vendor/ansi-styles/index.js +223 -0
  94. package/novac/node_modules/chalk/source/vendor/supports-color/browser.d.ts +1 -0
  95. package/novac/node_modules/chalk/source/vendor/supports-color/browser.js +34 -0
  96. package/novac/node_modules/chalk/source/vendor/supports-color/index.d.ts +55 -0
  97. package/novac/node_modules/chalk/source/vendor/supports-color/index.js +190 -0
  98. package/novac/node_modules/commander/LICENSE +22 -0
  99. package/novac/node_modules/commander/Readme.md +1176 -0
  100. package/novac/node_modules/commander/esm.mjs +16 -0
  101. package/novac/node_modules/commander/index.js +24 -0
  102. package/novac/node_modules/commander/lib/argument.js +150 -0
  103. package/novac/node_modules/commander/lib/command.js +2777 -0
  104. package/novac/node_modules/commander/lib/error.js +39 -0
  105. package/novac/node_modules/commander/lib/help.js +747 -0
  106. package/novac/node_modules/commander/lib/option.js +380 -0
  107. package/novac/node_modules/commander/lib/suggestSimilar.js +101 -0
  108. package/novac/node_modules/commander/package-support.json +19 -0
  109. package/novac/node_modules/commander/package.json +82 -0
  110. package/novac/node_modules/commander/typings/esm.d.mts +3 -0
  111. package/novac/node_modules/commander/typings/index.d.ts +1113 -0
  112. package/novac/node_modules/node-addon-api/LICENSE.md +9 -0
  113. package/novac/node_modules/node-addon-api/README.md +95 -0
  114. package/novac/node_modules/node-addon-api/common.gypi +21 -0
  115. package/novac/node_modules/node-addon-api/except.gypi +25 -0
  116. package/novac/node_modules/node-addon-api/index.js +14 -0
  117. package/novac/node_modules/node-addon-api/napi-inl.deprecated.h +186 -0
  118. package/novac/node_modules/node-addon-api/napi-inl.h +7165 -0
  119. package/novac/node_modules/node-addon-api/napi.h +3364 -0
  120. package/novac/node_modules/node-addon-api/node_addon_api.gyp +42 -0
  121. package/novac/node_modules/node-addon-api/node_api.gyp +9 -0
  122. package/novac/node_modules/node-addon-api/noexcept.gypi +26 -0
  123. package/novac/node_modules/node-addon-api/package-support.json +21 -0
  124. package/novac/node_modules/node-addon-api/package.json +480 -0
  125. package/novac/node_modules/node-addon-api/tools/README.md +73 -0
  126. package/novac/node_modules/node-addon-api/tools/check-napi.js +99 -0
  127. package/novac/node_modules/node-addon-api/tools/clang-format.js +71 -0
  128. package/novac/node_modules/node-addon-api/tools/conversion.js +301 -0
  129. package/novac/node_modules/serialize-javascript/LICENSE +27 -0
  130. package/novac/node_modules/serialize-javascript/README.md +149 -0
  131. package/novac/node_modules/serialize-javascript/index.js +297 -0
  132. package/novac/node_modules/serialize-javascript/package.json +33 -0
  133. package/novac/package.json +27 -0
  134. package/novac/scripts/update-bin.js +24 -0
  135. package/novac/src/core/bstd.js +1035 -0
  136. package/novac/src/core/config.js +155 -0
  137. package/novac/src/core/describe.js +187 -0
  138. package/novac/src/core/emitter.js +499 -0
  139. package/novac/src/core/error.js +86 -0
  140. package/novac/src/core/executor.js +5606 -0
  141. package/novac/src/core/formatter.js +686 -0
  142. package/novac/src/core/lexer.js +1026 -0
  143. package/novac/src/core/nova_builtins.js +717 -0
  144. package/novac/src/core/nova_thread_worker.js +166 -0
  145. package/novac/src/core/parser.js +2181 -0
  146. package/novac/src/core/types.js +112 -0
  147. package/novac/src/index.js +28 -0
  148. package/novac/src/runtime/stdlib.js +244 -0
  149. package/package.json +6 -3
  150. package/scripts/update-bin.js +0 -0
  151. package/src/core/bstd.js +838 -362
  152. package/src/core/executor.js +2578 -170
  153. package/src/core/lexer.js +502 -54
  154. package/src/core/nova_builtins.js +21 -3
  155. package/src/core/parser.js +413 -72
  156. package/src/core/types.js +30 -2
  157. package/src/index.js +0 -0
  158. package/examples/example-project/README.md +0 -3
  159. package/examples/example-project/src/main.nova +0 -3
  160. package/src/core/environment.js +0 -0
  161. /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
+