@wcstack/typescript 2.0.0 → 2.1.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
@@ -20,13 +20,13 @@ Zero runtime dependencies. `typescript` is a peer dependency — the project's o
20
20
 
21
21
  ### `wcs-schema emit <state.ts|state.js> [options]`
22
22
 
23
- Generates `states[<name>].stateSchema` from the file's `export default` (a `defineState(...)` call is unwrapped syntactically, so the package does not even need to resolve `@wcstack/state`). The result is run through the validator core's own manifest check before anything is written.
23
+ Generates the manifest's single `stateSchema` (`schemaVersion` 2) from the file's `export default` (a `defineState(...)` call is unwrapped syntactically, so the package does not even need to resolve `@wcstack/state`). The result is run through the validator core's own manifest check before anything is written.
24
24
 
25
25
  | Option | Description |
26
26
  |---|---|
27
- | `--state=<name>` | State name (default `default`) |
27
+ | `--mount=<path>` | Emit only the subtree at this mount path — the type file of a `<wcs-state mount="…">` volume merges as a subtree of the single tree. Omit for the root state file |
28
28
  | `--out=<path>` | Output manifest, relative to the working directory (default `wcstack.manifest.json`). `--out=-` prints to stdout |
29
- | `--merge` | Keep everything else in an existing manifest (other states, `filters`, `listContexts`) and replace only this state's `stateSchema`. A hand-written schema for the same state is replaced, not merged |
29
+ | `--merge` | Keep everything else in an existing manifest (`filters`, `listContexts`, other subtrees) and replace only the slot being written — the whole `stateSchema`, or the subtree at `--mount`. A hand-written schema in that slot is replaced, not merged |
30
30
  | `--tsconfig=<path>` | `tsconfig.json` to compile with (default: the nearest one above the state file, else built-in defaults) |
31
31
  | `--max-depth=<n>` | Object nesting depth at which the schema stops with a bare `{}` (default `5`, the validator's own budget) |
32
32
 
package/dist/index.esm.js CHANGED
@@ -566,7 +566,7 @@ function loadSchemaCore() {
566
566
  return cached;
567
567
  }
568
568
 
569
- var version = "2.0.0";
569
+ var version = "2.1.1";
570
570
  var pkg = {
571
571
  version: version};
572
572
 
@@ -1382,6 +1382,7 @@ function analyzeStatePaths(scriptContent) {
1382
1382
  for (const listKeyEntry of pendingListKeys) {
1383
1383
  pushListKeyPaths(listKeyEntry, paths);
1384
1384
  }
1385
+ collectRowShapesFromAssignments(scriptContent, paths);
1385
1386
  return paths;
1386
1387
  }
1387
1388
  function analyzeWatchEntries(scriptContent) {
@@ -1527,6 +1528,95 @@ function extractStringLiteralValue(value) {
1527
1528
  const match = value.trim().match(/^["']([^"'\\]*)["']$/);
1528
1529
  return match && match[1].length > 0 ? match[1] : null;
1529
1530
  }
1531
+ var ROW_ASSIGN = new RegExp(
1532
+ String.raw`\bthis\s*(?:\.\s*([$\w]+)|\[\s*["']([^"']+)["']\s*\])\s*=(?![=>])\s*(?:(\[)|(?:[^;={}]|=>)*?\.\s*(?:concat|toSpliced|with)\s*(\())`,
1533
+ "gd"
1534
+ );
1535
+ function collectRowShapesFromAssignments(script, paths) {
1536
+ const scan = maskCommentsAndStrings(script);
1537
+ ROW_ASSIGN.lastIndex = 0;
1538
+ let match;
1539
+ while ((match = ROW_ASSIGN.exec(scan)) !== null) {
1540
+ const span = match.indices[1] ?? match.indices[2];
1541
+ const listPath = script.slice(span[0], span[1]);
1542
+ if (listPath.startsWith("$") || listPath.includes("*") || !hasPath(paths, `${listPath}.*`)) continue;
1543
+ const openIndex = match.index + match[0].length - 1;
1544
+ const isArrayLiteral2 = scan[openIndex] === "[";
1545
+ const inner = extractDelimitedContent(script, scan, openIndex, scan[openIndex], isArrayLiteral2 ? "]" : ")");
1546
+ for (const literal of collectRowLiterals(inner, isArrayLiteral2 ? 0 : 1)) {
1547
+ for (const field of extractRowLiteralFields(literal)) {
1548
+ pushRowFieldPaths(`${listPath}.*.${field.name}`, field, paths, 1);
1549
+ }
1550
+ }
1551
+ }
1552
+ }
1553
+ function collectRowLiterals(elementList, arrayDepth) {
1554
+ const out = [];
1555
+ for (const element of splitTopLevelElements(elementList)) {
1556
+ if (element.startsWith("{")) {
1557
+ out.push(element);
1558
+ } else if (element.startsWith("[") && arrayDepth > 0) {
1559
+ const scan = maskCommentsAndStrings(element);
1560
+ out.push(...collectRowLiterals(extractDelimitedContent(element, scan, 0, "[", "]"), arrayDepth - 1));
1561
+ }
1562
+ }
1563
+ return out;
1564
+ }
1565
+ function extractRowLiteralFields(literal) {
1566
+ const content = extractObjectContent(literal);
1567
+ const fields = parseTopLevelProperties(content).filter((p) => p.kind === "data");
1568
+ for (const element of splitTopLevelElements(content)) {
1569
+ const shorthand = /^([$\w]+)$/.exec(element);
1570
+ if (shorthand && !fields.some((f) => f.name === shorthand[1])) {
1571
+ fields.push({ name: shorthand[1], kind: "data" });
1572
+ }
1573
+ }
1574
+ return fields;
1575
+ }
1576
+ function pushRowFieldPaths(path, prop, paths, depth) {
1577
+ if (!hasPath(paths, path)) paths.push(withHint({ path, kind: "data" }, prop.typeHint));
1578
+ if (!prop.value) return;
1579
+ if (isArrayLiteral(prop.value)) {
1580
+ if (!hasPath(paths, `${path}.*`)) paths.push({ path: `${path}.*`, kind: "list" });
1581
+ if (!hasPath(paths, `${path}.length`)) {
1582
+ paths.push({ path: `${path}.length`, kind: "data", typeHint: "number" });
1583
+ }
1584
+ if (depth >= MAX_OBJECT_NEST_DEPTH) return;
1585
+ for (const child of extractArrayElementDataProperties(prop.value)) {
1586
+ pushRowFieldPaths(`${path}.*.${child.name}`, child, paths, depth + 1);
1587
+ }
1588
+ return;
1589
+ }
1590
+ if (isObjectLiteral(prop.value)) {
1591
+ if (depth >= MAX_OBJECT_NEST_DEPTH) return;
1592
+ for (const child of parseTopLevelProperties(extractObjectContent(prop.value))) {
1593
+ if (child.kind !== "data") continue;
1594
+ pushRowFieldPaths(`${path}.${child.name}`, child, paths, depth + 1);
1595
+ }
1596
+ }
1597
+ }
1598
+ function hasPath(paths, path) {
1599
+ return paths.some((p) => p.path === path);
1600
+ }
1601
+ function splitTopLevelElements(text) {
1602
+ const scan = maskCommentsAndStrings(text);
1603
+ const out = [];
1604
+ let depth = 0;
1605
+ let start = 0;
1606
+ for (let i = 0; i < scan.length; i++) {
1607
+ const ch = scan[i];
1608
+ if (ch === "{" || ch === "[" || ch === "(") {
1609
+ depth++;
1610
+ } else if (ch === "}" || ch === "]" || ch === ")") {
1611
+ depth--;
1612
+ } else if (ch === "," && depth === 0) {
1613
+ out.push(text.slice(start, i));
1614
+ start = i + 1;
1615
+ }
1616
+ }
1617
+ out.push(text.slice(start));
1618
+ return out.map((e) => e.trim()).filter((e) => e.length > 0);
1619
+ }
1530
1620
  function findStreamInitialProperty(entryValue) {
1531
1621
  const defProps = parseTopLevelProperties(extractObjectContent(entryValue));
1532
1622
  return defProps.find((p) => p.kind === "data" && p.name === "initial");
@@ -1748,9 +1838,12 @@ function extractFullValue(content, scan, startIndex) {
1748
1838
  return content.slice(startIndex, i).trim();
1749
1839
  }
1750
1840
  function extractBracedContent(text, scan, openBraceIndex) {
1841
+ return extractDelimitedContent(text, scan, openBraceIndex, "{", "}");
1842
+ }
1843
+ function extractDelimitedContent(text, scan, openIndex, open, close) {
1751
1844
  let depth = 0;
1752
1845
  let inString = null;
1753
- for (let i = openBraceIndex; i < scan.length; i++) {
1846
+ for (let i = openIndex; i < scan.length; i++) {
1754
1847
  const ch = scan[i];
1755
1848
  if (inString) {
1756
1849
  if (ch === inString && !isEscaped(scan, i)) {
@@ -1760,16 +1853,16 @@ function extractBracedContent(text, scan, openBraceIndex) {
1760
1853
  }
1761
1854
  if (ch === '"' || ch === "'" || ch === "`") {
1762
1855
  inString = ch;
1763
- } else if (ch === "{") {
1856
+ } else if (ch === open) {
1764
1857
  depth++;
1765
- } else if (ch === "}") {
1858
+ } else if (ch === close) {
1766
1859
  depth--;
1767
1860
  if (depth === 0) {
1768
- return text.slice(openBraceIndex + 1, i);
1861
+ return text.slice(openIndex + 1, i);
1769
1862
  }
1770
1863
  }
1771
1864
  }
1772
- return text.slice(openBraceIndex + 1);
1865
+ return text.slice(openIndex + 1);
1773
1866
  }
1774
1867
  function isArrayLiteral(value) {
1775
1868
  return value.trimStart().startsWith("[");
@@ -567,7 +567,7 @@ function loadSchemaCore() {
567
567
  return cached;
568
568
  }
569
569
 
570
- var version = "2.0.0";
570
+ var version = "2.1.1";
571
571
  var pkg = {
572
572
  version: version};
573
573
 
package/dist/wcs-tsc.mjs CHANGED
@@ -5,7 +5,7 @@ import { tmpdir } from 'node:os';
5
5
  import { dirname, join, isAbsolute, resolve, basename } from 'node:path';
6
6
  import { fileURLToPath } from 'node:url';
7
7
 
8
- var version = "2.0.0";
8
+ var version = "2.1.1";
9
9
  var pkg = {
10
10
  version: version};
11
11
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wcstack/typescript",
3
- "version": "2.0.0",
3
+ "version": "2.1.1",
4
4
  "description": "TypeScript tooling for wcstack apps: wcs-schema generates the sidecar stateSchema from a typed state file, so wcs-validate checks data-wcs paths against real types in CI and every editor.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.esm.js",