config2ts 2.6.0 → 2.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
@@ -36,7 +36,7 @@ config2ts -d config -o dist -n csv.ts -a public
36
36
  * `Enum[]` union contains every value actually present in the data (including `""` for empty slots)
37
37
  * `EnumIndex` will generate index type, and will use `Enum` type to generate interface
38
38
  * `Ref` / `RefEnum` reference another csv's Record/enum by id: `Ref[file]` is one row (`file.Map["id"]`), `Ref[file][]` is an array of rows with comma-separated ids (`[file.Map["1"],file.Map["2"]]`, typed `File.Record[]`); use `Object[]` for inline private structures and `Ref[]` for shared, identity-bearing rows
39
- * `Template[file]` inherits a whole row from another table and overrides selected fields: a cell is `<baseId>|<key>:<value>,<key>:<value>` — a pipe separates the base row id from Object-style override pairs (e.g. `101|damage:150,range:8`), emitting `{ ...file.Map["101"], damage: 150, range: 8 }` typed `File.Record`; with no overrides the cell is just the id and emits a plain `file.Map["id"]` like `Ref`. `Template[file][]` separates entries with `;` (the same element separator as `Object[]`), e.g. `101|damage:150;102|heal:500` emits `[{ ...file.Map["101"], damage: 150 },{ ...file.Map["102"], heal: 500 }]`. Override key typos and value-type mismatches fail at tsc compile time; overrides are flat top-level fields only, and the target table must have an Index/Map (same file-ordering rule as `Ref`)
39
+ * `Template[file]` inherits a whole row from another table and overrides selected fields: a cell is `<baseId>|<key>:<value>,<key>:<value>` — a pipe separates the base row id from override pairs (e.g. `101|damage:150,range:8`), emitting `{ ...file.Map["101"], damage: 150, range: 8 }` typed `File.Record`; with no overrides the cell is just the id and emits a plain `file.Map["id"]` like `Ref`. Override values may be bare scalars or bracket-wrapped composites mirroring the generated TS syntax — arrays as `[v1,v2]` (e.g. `Params:[6,2.07]`, required for array-typed fields; a bare `Params:6,2.07` gets chopped by the pair comma and prints a warning) and flat objects as `{k:v,k:v}`. `Template[file][]` separates entries with `;` (the same element separator as `Object[]`), e.g. `101|damage:150;102|heal:500` emits `[{ ...file.Map["101"], damage: 150 },{ ...file.Map["102"], heal: 500 }]`. Override key typos and value-type mismatches fail at tsc compile time; overrides are flat top-level fields (no dot-path deep merge), and the target table must have an Index/EnumIndex Map (same file-ordering rule as `Ref`)
40
40
  * `Object` parse `key:value,key:value` format, auto-infer value types (number/boolean/string), generates a dedicated `type` that merges all keys
41
41
  * `Object[]` separates objects with `;` while commas still separate `key:value` pairs inside each object (same syntax as the `Object` type), e.g. `num:1,,str:a;num:2` makes `[{num:1,str:'a'},{num:2}]`, a bare `;` makes `[{},{}]`; generates a dedicated element `type` merging all keys with the field typed as `type[]`
42
42
  * Array convention: primitive arrays (`String[]`/`Number[]`/`Enum[]`/`RefEnum[...] []`) separate elements with `,` and `Object[]` separates objects with `;`; raw cell values are normalized (line breaks removed, whitespace trimmed); an empty cell generates `[]`; otherwise n separators make n+1 slots and every slot is kept (including interior or trailing ones) so parallel arrays stay index-aligned. Empty slots use the type's own zero value — `''` for string/enum/ref-enum, `0` for number, `{}` for empty object slots; `null` is never generated
package/lib/config2ts.js CHANGED
@@ -139,8 +139,9 @@ function csv2ts(csvString, moduleName) {
139
139
  }
140
140
  else if (parseTemplate(convert[k])) {
141
141
  const template = parseTemplate(convert[k]);
142
+ const templateContext = { moduleName, field: k, row: i };
142
143
  if (template.isArray) {
143
- const entries = d[k] === "" ? [] : d[k].split(TemplateEntrySeparator).map((part) => parseTemplateEntry(part));
144
+ const entries = d[k] === "" ? [] : d[k].split(TemplateEntrySeparator).map((part) => parseTemplateEntry(part, templateContext));
144
145
  for (let entryIndex = 0; entryIndex < entries.length; entryIndex++) {
145
146
  if (entries[entryIndex].baseId === "") {
146
147
  console.warn(`[config2ts] warning: ${moduleName} row ${i} field "${k}" template array entry ${entryIndex + 1} has an empty base id`);
@@ -152,7 +153,7 @@ function csv2ts(csvString, moduleName) {
152
153
  if (d[k] === "") {
153
154
  console.warn(`[config2ts] warning: ${moduleName} row ${i} field "${k}" template value is empty`);
154
155
  }
155
- const entry = parseTemplateEntry(d[k]);
156
+ const entry = parseTemplateEntry(d[k], templateContext);
156
157
  if (d[k] !== "" && entry.baseId === "") {
157
158
  console.warn(`[config2ts] warning: ${moduleName} row ${i} field "${k}" template base id is empty`);
158
159
  }
@@ -478,18 +479,88 @@ function parseTemplate(typeStr) {
478
479
  return null;
479
480
  }
480
481
  // Parse one Template cell entry: "<baseId>|<key>:<value>,<key>:<value>".
481
- // The part before the first '|' is the base row id; the part after it reuses
482
- // the Object rule for the override pairs. No '|' means zero overrides, which
483
- // is equivalent to a plain Ref.
484
- function parseTemplateEntry(str) {
482
+ // The part before the first '|' is the base row id; the part after it holds
483
+ // the override pairs. Pair splitting is bracket-aware, so composite values
484
+ // are not chopped apart: array values are wrapped in [...] (e.g.
485
+ // "Params:[6,2.07]") and flat object values in {...} (e.g.
486
+ // "params:{damage:100,range:5}"), mirroring the generated TypeScript syntax.
487
+ // No '|' means zero overrides, which is equivalent to a plain Ref.
488
+ function parseTemplateEntry(str, context) {
485
489
  const separatorIndex = str.indexOf(TemplateOverrideSeparator);
486
490
  if (separatorIndex < 0) {
487
491
  return { baseId: str.trim(), overrides: {} };
488
492
  }
489
493
  const baseId = str.slice(0, separatorIndex).trim();
490
- const overrides = parseObject(str.slice(separatorIndex + 1));
494
+ const overrides = parseTemplateOverrides(str.slice(separatorIndex + 1), context);
491
495
  return { baseId, overrides };
492
496
  }
497
+ // Split a string on a separator while ignoring separators inside [] / {}
498
+ // brackets, so commas inside composite values stay intact.
499
+ function splitBracketAware(str, separator) {
500
+ const parts = [];
501
+ let depth = 0;
502
+ let start = 0;
503
+ for (let i = 0; i < str.length; i++) {
504
+ const ch = str[i];
505
+ if (ch === "[" || ch === "{") {
506
+ depth++;
507
+ }
508
+ else if (ch === "]" || ch === "}") {
509
+ if (depth > 0)
510
+ depth--;
511
+ }
512
+ else if (ch === separator && depth === 0) {
513
+ parts.push(str.slice(start, i));
514
+ start = i + 1;
515
+ }
516
+ }
517
+ parts.push(str.slice(start));
518
+ return parts;
519
+ }
520
+ // Parse the override part of a Template entry. Scalar pairs reuse the Object
521
+ // rule ("key:value"); a stray segment without a key (e.g. the "2.07" produced
522
+ // by writing "Params:6,2.07" without brackets) is reported instead of being
523
+ // silently dropped.
524
+ function parseTemplateOverrides(str, context) {
525
+ const overrides = {};
526
+ for (const pair of splitBracketAware(str, ",")) {
527
+ const colonIndex = pair.indexOf(":");
528
+ const trimmedPair = pair.trim();
529
+ if (colonIndex <= 0) {
530
+ if (trimmedPair !== "" && context) {
531
+ console.warn(`[config2ts] warning: ${context.moduleName} row ${context.row} field "${context.field}" template override segment "${trimmedPair}" is not a key:value pair and was ignored; wrap array values in brackets, e.g. Params:[6,2.07]`);
532
+ }
533
+ continue;
534
+ }
535
+ const key = pair.slice(0, colonIndex).trim();
536
+ overrides[key] = parseOverrideValue(pair.slice(colonIndex + 1));
537
+ }
538
+ return overrides;
539
+ }
540
+ // Parse one override value: "[...]" resolves to an array (elements inferred
541
+ // like Object values, flat {...} elements are supported), "{...}" resolves to
542
+ // a flat object (same rule as the Object type), and anything else resolves to
543
+ // a scalar (number/boolean/string).
544
+ function parseOverrideValue(value) {
545
+ const trimmed = value.trim();
546
+ if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
547
+ const inner = trimmed.slice(1, -1).trim();
548
+ if (inner === "") {
549
+ return [];
550
+ }
551
+ return splitBracketAware(inner, ",").map((element) => {
552
+ const elementTrimmed = element.trim();
553
+ if (elementTrimmed.startsWith("{") && elementTrimmed.endsWith("}")) {
554
+ return parseObject(elementTrimmed.slice(1, -1));
555
+ }
556
+ return parseObjectValue(elementTrimmed);
557
+ });
558
+ }
559
+ if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
560
+ return parseObject(trimmed.slice(1, -1));
561
+ }
562
+ return parseObjectValue(trimmed);
563
+ }
493
564
  // Serialize one parsed Template entry to runtime code. Entries without
494
565
  // overrides emit a plain ref (same as Ref: `Module.Map["id"]`); entries with
495
566
  // overrides spread the base record and apply the overrides on top. Because
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "config2ts",
3
- "version": "2.6.0",
3
+ "version": "2.6.1",
4
4
  "description": "Generate type-safe TypeScript from CSV/INI/TOML config tables — inferred types, enums, cross-table references, and a typed asset index, all from one CLI command.",
5
5
  "main": "lib/config2ts.js",
6
6
  "types": "lib/config2ts.d.ts",