@aperturesyndicate/synx-format 3.6.0 → 3.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,17 +1,17 @@
1
- # SYNX for JS/TS — @aperturesyndicate/synx
1
+ # SYNX for JS/TS — @aperturesyndicate/synx-format
2
2
 
3
3
  The official JavaScript & TypeScript parser for the SYNX format.
4
4
 
5
5
  ## Install
6
6
 
7
7
  ```bash
8
- npm install @aperturesyndicate/synx
8
+ npm install @aperturesyndicate/synx-format
9
9
  ```
10
10
 
11
11
  ## Usage
12
12
 
13
13
  ```javascript
14
- const Synx = require('@aperturesyndicate/synx');
14
+ const Synx = require('@aperturesyndicate/synx-format');
15
15
 
16
16
  // Load from file
17
17
  const data = Synx.loadSync('config.synx');
@@ -25,7 +25,7 @@ console.log(data2.name); // "Wario"
25
25
  ### TypeScript
26
26
 
27
27
  ```typescript
28
- import Synx from '@aperturesyndicate/synx';
28
+ import Synx from '@aperturesyndicate/synx-format';
29
29
 
30
30
  interface Config {
31
31
  server: { port: number; host: string };
@@ -63,7 +63,7 @@ console.log(data.server.port); // typed as number
63
63
  Install globally:
64
64
 
65
65
  ```bash
66
- npm install -g @aperturesyndicate/synx
66
+ npm install -g @aperturesyndicate/synx-format
67
67
  ```
68
68
 
69
69
  ### Commands
package/SPECIFICATION.md CHANGED
@@ -2,5 +2,6 @@
2
2
 
3
3
  The normative specification files live in the monorepo (not duplicated here for npm):
4
4
 
5
- - English: [`docs/spec/SPECIFICATION_EN.md`](https://github.com/kaiserrberg/synx-format/blob/main/docs/spec/SPECIFICATION_EN.md)
6
- - Russian: [`docs/spec/SPECIFICATION_RU.md`](https://github.com/kaiserrberg/synx-format/blob/main/docs/spec/SPECIFICATION_RU.md)
5
+ - English: [`docs/spec/SPECIFICATION_EN.md`](https://github.com/APERTURESyndicate/synx-format/blob/main/docs/spec/SPECIFICATION_EN.md)
6
+ - Russian: [`docs/spec/SPECIFICATION_RU.md`](https://github.com/APERTURESyndicate/synx-format/blob/main/docs/spec/SPECIFICATION_RU.md)
7
+ - Normative (SYNX 3.6): [`docs/spec/SYNX-3.6-NORMATIVE.md`](https://github.com/APERTURESyndicate/synx-format/blob/main/docs/spec/SYNX-3.6-NORMATIVE.md)
package/bin/synx.js CHANGED
@@ -14,7 +14,7 @@ SYNX CLI — The Active Data Format (v3.6.0)
14
14
 
15
15
  DEPRECATED: This Node.js CLI is superseded by the native Rust CLI.
16
16
  Install: cargo install synx-cli
17
- Docs: https://github.com/kaiserrberg/synx-format#cli-rust
17
+ Docs: https://github.com/APERTURESyndicate/synx-format#cli-rust
18
18
 
19
19
  Usage:
20
20
  synx convert <file.synx> [--format json|yaml|toml|env] [--strict] [--active]
package/dist/browser.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * SYNX Browser Bundle — @aperturesyndicate/synx
2
+ * SYNX Browser Bundle — @aperturesyndicate/synx-format
3
3
  *
4
4
  * Lightweight browser-compatible build.
5
5
  * No Node.js dependencies (fs, path).
package/dist/browser.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /**
3
- * SYNX Browser Bundle — @aperturesyndicate/synx
3
+ * SYNX Browser Bundle — @aperturesyndicate/synx-format
4
4
  *
5
5
  * Lightweight browser-compatible build.
6
6
  * No Node.js dependencies (fs, path).
package/dist/calc.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * SYNX Safe Calculator — @aperturesyndicate/synx
2
+ * SYNX Safe Calculator — @aperturesyndicate/synx-format
3
3
  *
4
4
  * Evaluates arithmetic expressions WITHOUT eval() or new Function().
5
5
  * Supports: +, -, *, /, %, parentheses, and numeric literals.
package/dist/calc.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /**
3
- * SYNX Safe Calculator — @aperturesyndicate/synx
3
+ * SYNX Safe Calculator — @aperturesyndicate/synx-format
4
4
  *
5
5
  * Evaluates arithmetic expressions WITHOUT eval() or new Function().
6
6
  * Supports: +, -, *, /, %, parentheses, and numeric literals.
package/dist/engine.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * SYNX Engine — @aperturesyndicate/synx
2
+ * SYNX Engine — @aperturesyndicate/synx-format
3
3
  *
4
4
  * Resolves active markers (:random, :calc, :env, :alias, :secret, etc.)
5
5
  * in a parsed SYNX object tree. Only runs in !active mode.
package/dist/engine.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /**
3
- * SYNX Engine — @aperturesyndicate/synx
3
+ * SYNX Engine — @aperturesyndicate/synx-format
4
4
  *
5
5
  * Resolves active markers (:random, :calc, :env, :alias, :secret, etc.)
6
6
  * in a parsed SYNX object tree. Only runs in !active mode.
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * SYNX — @aperturesyndicate/synx
2
+ * SYNX — @aperturesyndicate/synx-format
3
3
  *
4
4
  * The Active Data Format.
5
5
  * Faster than JSON. Cheaper for AI tokens. Built-in logic.
@@ -54,6 +54,32 @@ declare class Synx {
54
54
  * ```
55
55
  */
56
56
  static load<T extends SynxObject = SynxObject>(filePath: string, options?: SynxOptions): Promise<T>;
57
+ /**
58
+ * Save a JS object to a .synx file synchronously.
59
+ *
60
+ * @param filePath - Path to the .synx file.
61
+ * @param obj - The object to serialize and save.
62
+ * @param active - If true, include `!active` directive.
63
+ *
64
+ * @example
65
+ * ```ts
66
+ * Synx.saveSync('config.synx', { app_name: 'TotalWario', port: 8080 });
67
+ * ```
68
+ */
69
+ static saveSync(filePath: string, obj: SynxObject, active?: boolean): void;
70
+ /**
71
+ * Save a JS object to a .synx file asynchronously.
72
+ *
73
+ * @param filePath - Path to the .synx file.
74
+ * @param obj - The object to serialize and save.
75
+ * @param active - If true, include `!active` directive.
76
+ *
77
+ * @example
78
+ * ```ts
79
+ * await Synx.save('config.synx', { app_name: 'TotalWario', port: 8080 });
80
+ * ```
81
+ */
82
+ static save(filePath: string, obj: SynxObject, active?: boolean): Promise<void>;
57
83
  /**
58
84
  * Serialize a JS object back to .synx format string.
59
85
  *
@@ -169,6 +195,70 @@ declare class Synx {
169
195
  * ```
170
196
  */
171
197
  static diff(a: SynxObject, b: SynxObject): SynxDiff;
198
+ /** Magic header for .synxb files */
199
+ private static readonly SYNXB_MAGIC;
200
+ /**
201
+ * Check if data is a `.synxb` binary file.
202
+ *
203
+ * @param data - Raw bytes to check.
204
+ * @returns True if the data starts with the SYNXB magic header.
205
+ *
206
+ * @example
207
+ * ```ts
208
+ * const buf = fs.readFileSync('config.synxb');
209
+ * if (Synx.isSynxb(buf)) { ... }
210
+ * ```
211
+ */
212
+ static isSynxb(data: Buffer | Uint8Array): boolean;
213
+ /**
214
+ * Compile a .synx string into compact binary `.synxb` format.
215
+ *
216
+ * The binary format stores the parsed value tree with an interned string table.
217
+ * It is deterministic and much faster to load than re-parsing text.
218
+ *
219
+ * @param text - The .synx source text.
220
+ * @param resolved - If true and text is `!active`, resolve markers first.
221
+ * @returns A Uint8Array containing the `.synxb` binary.
222
+ *
223
+ * @example
224
+ * ```ts
225
+ * const binary = Synx.compile('name Alice\nage 30');
226
+ * fs.writeFileSync('config.synxb', binary);
227
+ * ```
228
+ */
229
+ static compile(text: string, resolved?: boolean): Uint8Array;
230
+ /**
231
+ * Decompile a `.synxb` binary back into a .synx text string.
232
+ *
233
+ * @param data - Raw `.synxb` bytes.
234
+ * @returns The reconstructed .synx text.
235
+ * @throws Error if the data is not valid `.synxb`.
236
+ *
237
+ * @example
238
+ * ```ts
239
+ * const buf = fs.readFileSync('config.synxb');
240
+ * const text = Synx.decompile(buf);
241
+ * console.log(text);
242
+ * ```
243
+ */
244
+ static decompile(data: Buffer | Uint8Array): string;
245
+ /**
246
+ * Parse a `!tool` mode SYNX text, reshaping into `{ tool, params }` format.
247
+ *
248
+ * @param text - The .synx text (should contain `!tool` directive).
249
+ * @param options - Optional settings.
250
+ * @returns `{ tool: string, params: SynxObject }`.
251
+ *
252
+ * @example
253
+ * ```ts
254
+ * const toolCall = Synx.parseTool('!tool\ntool search\nparams\n query AI');
255
+ * // { tool: 'search', params: { query: 'AI' } }
256
+ * ```
257
+ */
258
+ static parseTool(text: string, options?: SynxOptions): {
259
+ tool: string;
260
+ params: SynxObject;
261
+ };
172
262
  }
173
263
  type WatchCallback = (config: SynxObject, error?: Error) => void;
174
264
  interface WatchHandle {
@@ -178,9 +268,10 @@ interface SynxSchemaProperty {
178
268
  type?: string;
179
269
  minimum?: number;
180
270
  maximum?: number;
271
+ minLength?: number;
272
+ maxLength?: number;
181
273
  pattern?: string;
182
274
  enum?: string[];
183
- required?: boolean;
184
275
  }
185
276
  interface SynxSchema {
186
277
  $schema: string;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /**
3
- * SYNX — @aperturesyndicate/synx
3
+ * SYNX — @aperturesyndicate/synx-format
4
4
  *
5
5
  * The Active Data Format.
6
6
  * Faster than JSON. Cheaper for AI tokens. Built-in logic.
@@ -189,6 +189,40 @@ class Synx {
189
189
  const opts = options.basePath ? options : { ...options, basePath: path.dirname(absPath) };
190
190
  return Synx.parse(text, opts);
191
191
  }
192
+ /**
193
+ * Save a JS object to a .synx file synchronously.
194
+ *
195
+ * @param filePath - Path to the .synx file.
196
+ * @param obj - The object to serialize and save.
197
+ * @param active - If true, include `!active` directive.
198
+ *
199
+ * @example
200
+ * ```ts
201
+ * Synx.saveSync('config.synx', { app_name: 'TotalWario', port: 8080 });
202
+ * ```
203
+ */
204
+ static saveSync(filePath, obj, active = false) {
205
+ const absPath = path.resolve(filePath);
206
+ const text = Synx.stringify(obj, active);
207
+ fs.writeFileSync(absPath, text, 'utf-8');
208
+ }
209
+ /**
210
+ * Save a JS object to a .synx file asynchronously.
211
+ *
212
+ * @param filePath - Path to the .synx file.
213
+ * @param obj - The object to serialize and save.
214
+ * @param active - If true, include `!active` directive.
215
+ *
216
+ * @example
217
+ * ```ts
218
+ * await Synx.save('config.synx', { app_name: 'TotalWario', port: 8080 });
219
+ * ```
220
+ */
221
+ static async save(filePath, obj, active = false) {
222
+ const absPath = path.resolve(filePath);
223
+ const text = Synx.stringify(obj, active);
224
+ await fs.promises.writeFile(absPath, text, 'utf-8');
225
+ }
192
226
  /**
193
227
  * Serialize a JS object back to .synx format string.
194
228
  *
@@ -447,7 +481,6 @@ class Synx {
447
481
  if (c.enum)
448
482
  prop.enum = c.enum;
449
483
  if (c.required) {
450
- prop.required = true;
451
484
  required.push(key);
452
485
  }
453
486
  properties[key] = prop;
@@ -506,9 +539,296 @@ class Synx {
506
539
  }
507
540
  return { added, removed, changed, unchanged };
508
541
  }
542
+ /**
543
+ * Check if data is a `.synxb` binary file.
544
+ *
545
+ * @param data - Raw bytes to check.
546
+ * @returns True if the data starts with the SYNXB magic header.
547
+ *
548
+ * @example
549
+ * ```ts
550
+ * const buf = fs.readFileSync('config.synxb');
551
+ * if (Synx.isSynxb(buf)) { ... }
552
+ * ```
553
+ */
554
+ static isSynxb(data) {
555
+ if (data.length < 6)
556
+ return false;
557
+ for (let i = 0; i < 5; i++) {
558
+ if (data[i] !== Synx.SYNXB_MAGIC[i])
559
+ return false;
560
+ }
561
+ return true;
562
+ }
563
+ /**
564
+ * Compile a .synx string into compact binary `.synxb` format.
565
+ *
566
+ * The binary format stores the parsed value tree with an interned string table.
567
+ * It is deterministic and much faster to load than re-parsing text.
568
+ *
569
+ * @param text - The .synx source text.
570
+ * @param resolved - If true and text is `!active`, resolve markers first.
571
+ * @returns A Uint8Array containing the `.synxb` binary.
572
+ *
573
+ * @example
574
+ * ```ts
575
+ * const binary = Synx.compile('name Alice\nage 30');
576
+ * fs.writeFileSync('config.synxb', binary);
577
+ * ```
578
+ */
579
+ static compile(text, resolved = false) {
580
+ const input = resolved && !/(?:^|\n)\s*!active\b/.test(text) ? '!active\n' + text : text;
581
+ const parsed = Synx.parse(input);
582
+ const strings = [];
583
+ const stringIndex = new Map();
584
+ function internString(s) {
585
+ if (stringIndex.has(s))
586
+ return stringIndex.get(s);
587
+ const idx = strings.length;
588
+ strings.push(s);
589
+ stringIndex.set(s, idx);
590
+ return idx;
591
+ }
592
+ // Collect all strings first
593
+ function collectStrings(val) {
594
+ if (val === null || val === undefined)
595
+ return;
596
+ if (typeof val === 'string') {
597
+ internString(val);
598
+ return;
599
+ }
600
+ if (Array.isArray(val)) {
601
+ val.forEach(collectStrings);
602
+ return;
603
+ }
604
+ if (typeof val === 'object') {
605
+ for (const [k, v] of Object.entries(val)) {
606
+ internString(k);
607
+ collectStrings(v);
608
+ }
609
+ }
610
+ }
611
+ collectStrings(parsed);
612
+ // Encode
613
+ const buf = [];
614
+ // Magic + version
615
+ buf.push(0x53, 0x59, 0x4e, 0x58, 0x42); // SYNXB
616
+ buf.push(0x01); // version 1
617
+ // Flags: bit0=active, bit3=resolved
618
+ const isActive = text.trimStart().startsWith('!active');
619
+ let flags = 0;
620
+ if (isActive)
621
+ flags |= 0x01;
622
+ if (resolved)
623
+ flags |= 0x08;
624
+ buf.push(flags);
625
+ // String table
626
+ writeVarint(buf, strings.length);
627
+ for (const s of strings) {
628
+ const encoded = new TextEncoder().encode(s);
629
+ writeVarint(buf, encoded.length);
630
+ for (const b of encoded)
631
+ buf.push(b);
632
+ }
633
+ // Value tree
634
+ function writeValue(val) {
635
+ if (val === null || val === undefined) {
636
+ buf.push(0x00);
637
+ return;
638
+ }
639
+ if (typeof val === 'boolean') {
640
+ buf.push(val ? 0x02 : 0x01);
641
+ return;
642
+ }
643
+ if (typeof val === 'number') {
644
+ if (Number.isInteger(val)) {
645
+ buf.push(0x03);
646
+ writeZigzag(buf, val);
647
+ }
648
+ else {
649
+ buf.push(0x04);
650
+ const view = new DataView(new ArrayBuffer(8));
651
+ view.setFloat64(0, val, true); // little-endian
652
+ for (let i = 0; i < 8; i++)
653
+ buf.push(view.getUint8(i));
654
+ }
655
+ return;
656
+ }
657
+ if (typeof val === 'string') {
658
+ buf.push(0x05);
659
+ writeVarint(buf, internString(val));
660
+ return;
661
+ }
662
+ if (Array.isArray(val)) {
663
+ buf.push(0x06);
664
+ writeVarint(buf, val.length);
665
+ val.forEach(writeValue);
666
+ return;
667
+ }
668
+ if (typeof val === 'object') {
669
+ const entries = Object.entries(val);
670
+ buf.push(0x07);
671
+ writeVarint(buf, entries.length);
672
+ for (const [k, v] of entries) {
673
+ writeVarint(buf, internString(k));
674
+ writeValue(v);
675
+ }
676
+ }
677
+ }
678
+ writeValue(parsed);
679
+ return new Uint8Array(buf);
680
+ }
681
+ /**
682
+ * Decompile a `.synxb` binary back into a .synx text string.
683
+ *
684
+ * @param data - Raw `.synxb` bytes.
685
+ * @returns The reconstructed .synx text.
686
+ * @throws Error if the data is not valid `.synxb`.
687
+ *
688
+ * @example
689
+ * ```ts
690
+ * const buf = fs.readFileSync('config.synxb');
691
+ * const text = Synx.decompile(buf);
692
+ * console.log(text);
693
+ * ```
694
+ */
695
+ static decompile(data) {
696
+ if (!Synx.isSynxb(data)) {
697
+ throw new types_1.SynxError('Not a valid .synxb file');
698
+ }
699
+ let offset = 5; // skip magic
700
+ const version = data[offset++];
701
+ if (version !== 1)
702
+ throw new types_1.SynxError(`Unsupported .synxb version: ${version}`);
703
+ const flags = data[offset++];
704
+ const isActive = (flags & 0x01) !== 0;
705
+ const isLocked = (flags & 0x02) !== 0;
706
+ // String table
707
+ const [strCount, o1] = readVarint(data, offset);
708
+ offset = o1;
709
+ const strings = [];
710
+ const decoder = new TextDecoder();
711
+ for (let i = 0; i < strCount; i++) {
712
+ const [len, o2] = readVarint(data, offset);
713
+ offset = o2;
714
+ strings.push(decoder.decode(data.slice(offset, offset + len)));
715
+ offset += len;
716
+ }
717
+ // Value tree
718
+ function readValue() {
719
+ const tag = data[offset++];
720
+ switch (tag) {
721
+ case 0x00: return null;
722
+ case 0x01: return false;
723
+ case 0x02: return true;
724
+ case 0x03: {
725
+ const [v, o] = readZigzag(data, offset);
726
+ offset = o;
727
+ return v;
728
+ }
729
+ case 0x04: {
730
+ const view = new DataView(data.buffer, data.byteOffset + offset, 8);
731
+ offset += 8;
732
+ return view.getFloat64(0, true);
733
+ }
734
+ case 0x05: {
735
+ const [idx, o] = readVarint(data, offset);
736
+ offset = o;
737
+ return strings[idx];
738
+ }
739
+ case 0x06: {
740
+ const [len, o] = readVarint(data, offset);
741
+ offset = o;
742
+ const arr = [];
743
+ for (let i = 0; i < len; i++)
744
+ arr.push(readValue());
745
+ return arr;
746
+ }
747
+ case 0x07: {
748
+ const [len, o] = readVarint(data, offset);
749
+ offset = o;
750
+ const obj = {};
751
+ for (let i = 0; i < len; i++) {
752
+ const [ki, o2] = readVarint(data, offset);
753
+ offset = o2;
754
+ obj[strings[ki]] = readValue();
755
+ }
756
+ return obj;
757
+ }
758
+ case 0x08: {
759
+ const [idx, o] = readVarint(data, offset);
760
+ offset = o;
761
+ return '[SECRET]';
762
+ }
763
+ default: throw new types_1.SynxError(`Unknown value tag: 0x${tag.toString(16)}`);
764
+ }
765
+ }
766
+ const value = readValue();
767
+ let header = '';
768
+ if (isActive)
769
+ header += '!active\n';
770
+ if (isLocked)
771
+ header += '!lock\n';
772
+ if (header)
773
+ header += '\n';
774
+ return header + serializeObject(value, 0);
775
+ }
776
+ /**
777
+ * Parse a `!tool` mode SYNX text, reshaping into `{ tool, params }` format.
778
+ *
779
+ * @param text - The .synx text (should contain `!tool` directive).
780
+ * @param options - Optional settings.
781
+ * @returns `{ tool: string, params: SynxObject }`.
782
+ *
783
+ * @example
784
+ * ```ts
785
+ * const toolCall = Synx.parseTool('!tool\ntool search\nparams\n query AI');
786
+ * // { tool: 'search', params: { query: 'AI' } }
787
+ * ```
788
+ */
789
+ static parseTool(text, options = {}) {
790
+ const parsed = Synx.parse(text, options);
791
+ const tool = typeof parsed.tool === 'string' ? parsed.tool : '';
792
+ const params = (typeof parsed.params === 'object' && parsed.params !== null && !Array.isArray(parsed.params))
793
+ ? parsed.params
794
+ : {};
795
+ return { tool, params };
796
+ }
509
797
  }
510
798
  exports.Synx = Synx;
511
- // ─── Deep equality helper ─────────────────────────────────
799
+ // ─── Binary Format (.synxb) ─────────────────────────────
800
+ /** Magic header for .synxb files */
801
+ Synx.SYNXB_MAGIC = new Uint8Array([0x53, 0x59, 0x4e, 0x58, 0x42]); // "SYNXB"
802
+ // ─── Binary encoding helpers ──────────────────────────────
803
+ function writeVarint(buf, value) {
804
+ let v = value >>> 0;
805
+ while (v > 0x7f) {
806
+ buf.push((v & 0x7f) | 0x80);
807
+ v >>>= 7;
808
+ }
809
+ buf.push(v);
810
+ }
811
+ function readVarint(data, offset) {
812
+ let result = 0;
813
+ let shift = 0;
814
+ while (offset < data.length) {
815
+ const byte = data[offset++];
816
+ result |= (byte & 0x7f) << shift;
817
+ if ((byte & 0x80) === 0)
818
+ break;
819
+ shift += 7;
820
+ }
821
+ return [result >>> 0, offset];
822
+ }
823
+ function writeZigzag(buf, value) {
824
+ const zigzag = (value << 1) ^ (value >> 31);
825
+ writeVarint(buf, zigzag >>> 0);
826
+ }
827
+ function readZigzag(data, offset) {
828
+ const [raw, newOffset] = readVarint(data, offset);
829
+ const value = (raw >>> 1) ^ -(raw & 1);
830
+ return [value, newOffset];
831
+ }
512
832
  function deepEqual(a, b) {
513
833
  if (a === b)
514
834
  return true;