@wcstack/typescript 2.1.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 +3 -3
- package/dist/index.esm.js +1 -1
- package/dist/schema-core.cjs +98 -5
- package/dist/wcs-schema.mjs +1 -1
- package/dist/wcs-tsc.mjs +1 -1
- package/package.json +1 -1
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 `
|
|
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
|
-
| `--
|
|
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 (
|
|
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
package/dist/schema-core.cjs
CHANGED
|
@@ -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 =
|
|
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(
|
|
1861
|
+
return text.slice(openIndex + 1, i);
|
|
1769
1862
|
}
|
|
1770
1863
|
}
|
|
1771
1864
|
}
|
|
1772
|
-
return text.slice(
|
|
1865
|
+
return text.slice(openIndex + 1);
|
|
1773
1866
|
}
|
|
1774
1867
|
function isArrayLiteral(value) {
|
|
1775
1868
|
return value.trimStart().startsWith("[");
|
package/dist/wcs-schema.mjs
CHANGED
package/dist/wcs-tsc.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wcstack/typescript",
|
|
3
|
-
"version": "2.1.
|
|
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",
|