config2ts 2.5.9 → 2.6.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/README.md CHANGED
@@ -25,7 +25,9 @@ config2ts -d config -o dist -n csv.ts -a public
25
25
  | Enum[] | type[] |
26
26
  | Ref | namespace.Record |
27
27
  | RefEnum | namespace.type |
28
- | RefEnum[] | namespace.type[] |
28
+ | RefEnum[] | namespace.type[] |
29
+ | Template | namespace.Record |
30
+ | Template[] | namespace.Record[] |
29
31
  | Object | type (Record) |
30
32
  | Object[] | type[] (Record[]) |
31
33
 
@@ -34,6 +36,7 @@ config2ts -d config -o dist -n csv.ts -a public
34
36
  * `Enum[]` union contains every value actually present in the data (including `""` for empty slots)
35
37
  * `EnumIndex` will generate index type, and will use `Enum` type to generate interface
36
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`)
37
40
  * `Object` parse `key:value,key:value` format, auto-infer value types (number/boolean/string), generates a dedicated `type` that merges all keys
38
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[]`
39
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
@@ -55,6 +55,12 @@ const RefPrefix = "Ref[";
55
55
  const RefSuffix = "]";
56
56
  const RefEnumPrefix = "RefEnum[";
57
57
  const RefEnumArraySuffix = "][]";
58
+ const TemplatePrefix = "Template[";
59
+ // Inside a Template cell, '|' separates the referenced base row id from the
60
+ // Object-style override pairs; ';' separates multiple entries in the array
61
+ // form (same element separator as Object[]).
62
+ const TemplateOverrideSeparator = "|";
63
+ const TemplateEntrySeparator = ";";
58
64
  const AutoGenHeader = "// Auto Generated by config2ts, DO NOT EDIT\n\n";
59
65
  exports.Convert = {
60
66
  ini: function (str, moduleName) {
@@ -131,6 +137,28 @@ function csv2ts(csvString, moduleName) {
131
137
  }
132
138
  }
133
139
  }
140
+ else if (parseTemplate(convert[k])) {
141
+ const template = parseTemplate(convert[k]);
142
+ if (template.isArray) {
143
+ const entries = d[k] === "" ? [] : d[k].split(TemplateEntrySeparator).map((part) => parseTemplateEntry(part));
144
+ for (let entryIndex = 0; entryIndex < entries.length; entryIndex++) {
145
+ if (entries[entryIndex].baseId === "") {
146
+ console.warn(`[config2ts] warning: ${moduleName} row ${i} field "${k}" template array entry ${entryIndex + 1} has an empty base id`);
147
+ }
148
+ }
149
+ d[k] = entries;
150
+ }
151
+ else {
152
+ if (d[k] === "") {
153
+ console.warn(`[config2ts] warning: ${moduleName} row ${i} field "${k}" template value is empty`);
154
+ }
155
+ const entry = parseTemplateEntry(d[k]);
156
+ if (d[k] !== "" && entry.baseId === "") {
157
+ console.warn(`[config2ts] warning: ${moduleName} row ${i} field "${k}" template base id is empty`);
158
+ }
159
+ d[k] = entry;
160
+ }
161
+ }
134
162
  else {
135
163
  d[k] = String(d[k]);
136
164
  }
@@ -226,6 +254,11 @@ function csv2ts(csvString, moduleName) {
226
254
  const refModuleName = changeCase.pascalCase(refEnum.refModule);
227
255
  fieldType = refEnum.isArray ? `${refModuleName}.${refEnum.refField}[]` : `${refModuleName}.${refEnum.refField}`;
228
256
  }
257
+ else if (parseTemplate(convert[field])) {
258
+ const template = parseTemplate(convert[field]);
259
+ const refModuleName = changeCase.pascalCase(template.refModule);
260
+ fieldType = template.isArray ? `${refModuleName}.Record[]` : `${refModuleName}.Record`;
261
+ }
229
262
  else {
230
263
  fieldType = convert[field].toLowerCase();
231
264
  }
@@ -260,6 +293,17 @@ function csv2ts(csvString, moduleName) {
260
293
  template += ` ${serializeField(field)}: ${refModuleName}.Map[${JSON.stringify(value)}],\n`;
261
294
  }
262
295
  }
296
+ else if (parseTemplate(convert[field])) {
297
+ const templateType = parseTemplate(convert[field]);
298
+ const refModuleName = changeCase.pascalCase(templateType.refModule);
299
+ if (templateType.isArray) {
300
+ const entries = value.map((entry) => serializeTemplateEntry(entry, refModuleName)).join(",");
301
+ template += ` ${serializeField(field)}: [${entries}],\n`;
302
+ }
303
+ else {
304
+ template += ` ${serializeField(field)}: ${serializeTemplateEntry(value, refModuleName)},\n`;
305
+ }
306
+ }
263
307
  else {
264
308
  template += ` ${serializeField(field)}: ${json5.stringify(value)},\n`;
265
309
  }
@@ -364,6 +408,9 @@ function isKnownType(typeStr) {
364
408
  if (parseRefEnum(typeStr)) {
365
409
  return true;
366
410
  }
411
+ if (parseTemplate(typeStr)) {
412
+ return true;
413
+ }
367
414
  if (typeof global[typeStr] === "function") {
368
415
  return true;
369
416
  }
@@ -409,6 +456,56 @@ function parseRefEnum(typeStr) {
409
456
  }
410
457
  return null;
411
458
  }
459
+ // Parse Template[module] (single) and Template[module][] (array of templated
460
+ // row references). "Template[" starts with 'T', so it never collides with the
461
+ // "Ref[" / "RefEnum[" prefixes.
462
+ function parseTemplate(typeStr) {
463
+ if (!typeStr.startsWith(TemplatePrefix)) {
464
+ return null;
465
+ }
466
+ let isArray = false;
467
+ let inner = typeStr;
468
+ if (typeStr.endsWith(RefEnumArraySuffix)) {
469
+ isArray = true;
470
+ inner = typeStr.slice(0, -RefEnumArraySuffix.length) + RefSuffix;
471
+ }
472
+ if (inner.startsWith(TemplatePrefix) && inner.endsWith(RefSuffix)) {
473
+ const refModule = inner.slice(TemplatePrefix.length, -RefSuffix.length);
474
+ if (refModule !== "") {
475
+ return { refModule, isArray };
476
+ }
477
+ }
478
+ return null;
479
+ }
480
+ // 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) {
485
+ const separatorIndex = str.indexOf(TemplateOverrideSeparator);
486
+ if (separatorIndex < 0) {
487
+ return { baseId: str.trim(), overrides: {} };
488
+ }
489
+ const baseId = str.slice(0, separatorIndex).trim();
490
+ const overrides = parseObject(str.slice(separatorIndex + 1));
491
+ return { baseId, overrides };
492
+ }
493
+ // Serialize one parsed Template entry to runtime code. Entries without
494
+ // overrides emit a plain ref (same as Ref: `Module.Map["id"]`); entries with
495
+ // overrides spread the base record and apply the overrides on top. Because
496
+ // the spread result is contextually typed as the target Record, a typo'd
497
+ // override key or an incompatible value type fails at tsc compile time.
498
+ function serializeTemplateEntry(entry, refModuleName) {
499
+ const baseRef = `${refModuleName}.Map[${JSON.stringify(entry.baseId)}]`;
500
+ const overrideKeys = Object.keys(entry.overrides);
501
+ if (overrideKeys.length === 0) {
502
+ return baseRef;
503
+ }
504
+ const overrideStr = overrideKeys
505
+ .map((key) => `${serializeField(key)}:${json5.stringify(entry.overrides[key])}`)
506
+ .join(",");
507
+ return `{...${baseRef},${overrideStr}}`;
508
+ }
412
509
  function GetFileExt(filePath) {
413
510
  const pathObject = path.parse(filePath);
414
511
  return pathObject.ext.slice(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "config2ts",
3
- "version": "2.5.9",
3
+ "version": "2.6.0",
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",