@wcstack/typescript 1.32.0 → 2.0.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.ja.md +106 -106
- package/README.md +106 -106
- package/dist/index.d.ts +34 -16
- package/dist/index.esm.js +96 -28
- package/dist/index.esm.js.map +1 -1
- package/dist/schema-core.cjs +220 -187
- package/dist/tsc-core.cjs +2 -2
- package/dist/wcs-schema.mjs +149 -47
- package/dist/wcs-schema.mjs.map +1 -1
- package/dist/wcs-tsc.mjs +1 -1
- package/dist/wcs-tsc.mjs.map +1 -1
- package/package.json +84 -84
package/README.ja.md
CHANGED
|
@@ -1,106 +1,106 @@
|
|
|
1
|
-
# @wcstack/typescript
|
|
2
|
-
|
|
3
|
-
> 🤖 **AI coding agents**: This README is a package-level reference, not the primary entry point for building a wcstack application. If you have not already done so, first read the repository [README](https://github.com/wcstack/wcstack#readme) and [AGENTS.md](https://github.com/wcstack/wcstack/blob/main/AGENTS.md), then use the [wcstack-app skill](https://github.com/wcstack/wcstack-skill).
|
|
4
|
-
|
|
5
|
-
**もし、state に書いた型がそれをバインドする HTML まで届いたら?**
|
|
6
|
-
|
|
7
|
-
`@wcstack/state` のページは DOM と state をパス文字列(`users.*.name`)で結びます。TypeScript の state ファイルはそのパス全部と型を知っています — しかし HTML は `.d.ts` を消費できず、静的検証器(`wcs-validate`・VS Code 拡張)はインラインスクリプトを型注釈を理解しない正規表現アナライザで読みます。`users: [] as { name: string }[]` では `users.*.name` が解決できず、本物の typo は warning 止まり、正しいバインドには偽警告が付きます。
|
|
8
|
-
|
|
9
|
-
`@wcstack/typescript` はこの穴をコマンド 1 つで塞ぎます。`wcs-schema` は state ファイルを TypeScript コンパイラ API でコンパイルし、検証器が消費する sidecar `wcstack.manifest.json` の `stateSchema` を書き出します — CI でも、どのエディタでも。manifest は**派生物**です: 正本は型のままで、乖離すれば `wcs-schema check` が CI を落とします。
|
|
10
|
-
|
|
11
|
-
```bash
|
|
12
|
-
npm install -D @wcstack/typescript typescript
|
|
13
|
-
npx wcs-schema emit src/state.ts # ./wcstack.manifest.json を書く
|
|
14
|
-
npx wcs-validate --strict index.html # typo は error に、偽警告は消える
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
ランタイム依存ゼロ。`typescript` は peer dependency — プロジェクト自身のコンパイラを使います。
|
|
18
|
-
|
|
19
|
-
## コマンド
|
|
20
|
-
|
|
21
|
-
### `wcs-schema emit <state.ts|state.js> [options]`
|
|
22
|
-
|
|
23
|
-
ファイルの `export default` から `states[<name>].stateSchema` を生成します(`defineState(...)` は構文的に剥がすので、このパッケージは `@wcstack/state` を解決できなくても動きます)。生成結果は書き出す前に検証器自身の manifest 検査を通します。
|
|
24
|
-
|
|
25
|
-
| オプション | 説明 |
|
|
26
|
-
|---|---|
|
|
27
|
-
| `--state=<name>` | state 名(既定 `default`) |
|
|
28
|
-
| `--out=<path>` | 出力先 manifest(作業ディレクトリ相対・既定 `wcstack.manifest.json`)。`--out=-` で stdout |
|
|
29
|
-
| `--merge` | 既存 manifest の他の内容(他 state・`filters`・`listContexts`)を保持し、この state の `stateSchema` だけ置き換える。同じ state の手書き schema は合成されず置き換わる |
|
|
30
|
-
| `--tsconfig=<path>` | コンパイルに使う `tsconfig.json`(既定: state ファイルから上に辿って最も近いもの、無ければ組み込み既定) |
|
|
31
|
-
| `--max-depth=<n>` | ここより深い入れ子は素の `{}` で打ち切る(既定 `5` = 検証器の探索予算と同じ) |
|
|
32
|
-
|
|
33
|
-
exit code: `0` 書き出し済み · `2` usage エラー / 読めない / 構文エラー / 生成物が自己検査に落ちた。
|
|
34
|
-
|
|
35
|
-
### `wcs-schema check <state.ts|state.js> [options]`
|
|
36
|
-
|
|
37
|
-
schema を再生成し、`--manifest=<path>`(既定 `wcstack.manifest.json`)の内容とキー順を無視して比較します。
|
|
38
|
-
|
|
39
|
-
exit code: `0` 最新 · `1` 乖離あり(変更は JSON pointer で列挙: `+` 型にだけある / `-` manifest にだけある / `~` 違う) · `2` usage エラー / manifest が無い / その state の `stateSchema` がまだ無い。
|
|
40
|
-
|
|
41
|
-
推奨する CI ゲート:
|
|
42
|
-
|
|
43
|
-
```bash
|
|
44
|
-
npx wcs-schema check src/state.ts && npx wcs-validate --strict index.html
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
### `wcs-tsc [--url-imports=any|error] [--wcs-defaults] [tsc の引数...]`
|
|
48
|
-
|
|
49
|
-
`.html` に対する `tsc`: 各 `<wcs-state>` のインライン `<script type="module">` を VS Code 拡張と同じ言語プラグイン(型付き `this`・自動 `defineState` ラップ・bare でも CDN URL でも `@wcstack/state` import を除去)で型検査し、診断は HTML の位置を指します:
|
|
50
|
-
|
|
51
|
-
```bash
|
|
52
|
-
npm i -D @volar/typescript@~2.4.0 @volar/language-core@~2.4.0 # optional peer・wcs-tsc だけが必要
|
|
53
|
-
npx wcs-tsc --noEmit
|
|
54
|
-
# index.html(9,14): error TS2551: Property 'coutn' does not exist on type '_WcsThis<…>'. Did you mean 'count'?
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
| オプション | 説明 |
|
|
58
|
-
|---|---|
|
|
59
|
-
| `--url-imports=any`(既定) | 全ての `http(s)://` モジュール import を `any` に型付ける — buildless なページは tsc が解決できない CDN から import する |
|
|
60
|
-
| `--url-imports=error` | URL import をそのまま残す(それぞれ TS2307) |
|
|
61
|
-
| `--wcs-defaults` | プロジェクトの tsconfig に `**/*.html` を覆う `include`・`noImplicitThis`・`allowJs`・`checkJs` が無ければ、元を extends してそれらを足した一時 config で実行する(無指定なら警告だけで HTML が検査されないことがある) |
|
|
62
|
-
| それ以外 | tsc にそのまま渡す(`-p`・`--noEmit` …) |
|
|
63
|
-
|
|
64
|
-
exit code は tsc のもの(`0` クリーン・診断があれば非ゼロ)。`typescript` / `@volar/typescript` が解決できないときと不正なオプションは `2`。1 ページに複数の `<wcs-state>` があれば 1 本の仮想モジュールに合成(import は巻き上げ・各ブロックは自分のスコープ)し、インライン state の無いページは空のモジュールになります。仕組みは vue-tsc と同じ: `@volar/typescript` の `runTsc` がプロジェクト自身の `typescript/lib/tsc.js` にパッチを当てます。
|
|
65
|
-
|
|
66
|
-
## 生成される schema の中身
|
|
67
|
-
|
|
68
|
-
sidecar 規範が許す JSON-Schema サブセットだけ(`type` / `properties` / `required` / `items` / `enum` / `const` / `anyOf`):
|
|
69
|
-
|
|
70
|
-
| TypeScript | stateSchema |
|
|
71
|
-
|---|---|
|
|
72
|
-
| `string` / `number` / `boolean` / `null` | `{ "type": … }`(`integer` は使わず全て `number`) |
|
|
73
|
-
| `"a" \| "b"`、`1 \| 2` | `{ "type": "string", "enum": ["a", "b"] }` |
|
|
74
|
-
| `T[]`、`readonly T[]`、タプル | `{ "type": "array", "items": … }` |
|
|
75
|
-
| `T \| null` | `{ "anyOf": [T, { "type": "null" }] }` |
|
|
76
|
-
| `T \| undefined`、`x?: T` | `T`。`x` は `required` に入らない |
|
|
77
|
-
| `A \| B`(オブジェクトや混在プリミティブ) | `{ "anyOf": [A, B] }` |
|
|
78
|
-
| 自分のコードのオブジェクトリテラル / interface / class | `{ "type": "object", "properties": …, "required": … }` |
|
|
79
|
-
| `Date`、`Map`、`Set`、DOM 型、ライブラリ由来の型、`any`、`unknown`、`Record<string, T>` | **素の `{}`** |
|
|
80
|
-
| `get x(): T` | `x: T` — getter はメンバー |
|
|
81
|
-
| `get "users.*.ageCategory"(): string` | `users.items.properties.ageCategory` に注入 — パス getter は計算先のパスのメンバー |
|
|
82
|
-
| メソッド、関数値プロパティ、`$` 始まりキー(`$watch`・`$commandTokens` …) | 捨てる |
|
|
83
|
-
| `--max-depth` より深い入れ子 | 素の `{}` |
|
|
84
|
-
|
|
85
|
-
素の `{}` が要点です: 検証器はこれを *unknown* として扱いその下では沈黙しますが、メンバーを欠く型付きオブジェクトは *nonexistent* で **error** です。`Date` や `Record<string, unknown>` の下のパスが偽 error になることはありません。
|
|
86
|
-
|
|
87
|
-
## ライブラリ API
|
|
88
|
-
|
|
89
|
-
```ts
|
|
90
|
-
import { generateStateSchema, buildManifest, compareStateSchema } from "@wcstack/typescript";
|
|
91
|
-
|
|
92
|
-
const { schema, warnings } = generateStateSchema("src/state.ts"); // { tsconfig?, maxDepth? }
|
|
93
|
-
const manifest = buildManifest("default", schema /*, 既存の manifest オブジェクト */);
|
|
94
|
-
const result = compareStateSchema(JSON.stringify(manifest), "default", schema); // { kind: "same" } | { kind: "differs", changes } | …
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
`loadStateFile` / `stateTypeToSchema` は 2 つの半分(コンパイラ program・型 → schema)を別々に組み合わせたいツール向けに公開しています。
|
|
98
|
-
|
|
99
|
-
## 位置づけ
|
|
100
|
-
|
|
101
|
-
- 検証器側 — 最近傍 `wcstack.manifest.json` の発見、`wcs/path-nonexistent`、`wcs/path-type-mismatch` — は [`@wcstack/lint`](../lint/README.ja.md#state-の契約を宣言するstateschema) と、規範としては [`docs/wcstack-manifest-schema.md`](https://github.com/wcstack/wcstack/blob/main/docs/wcstack-manifest-schema.md) にあります。
|
|
102
|
-
- wcstack アプリの TypeScript の話全体(`defineState` による `this` の型付け、このパッケージ、tag name map、`wcs-tsc`)は [`docs/typescript.ja.md`](https://github.com/wcstack/wcstack/blob/main/docs/typescript.ja.md) にまとめています。
|
|
103
|
-
|
|
104
|
-
## License
|
|
105
|
-
|
|
106
|
-
MIT
|
|
1
|
+
# @wcstack/typescript
|
|
2
|
+
|
|
3
|
+
> 🤖 **AI coding agents**: This README is a package-level reference, not the primary entry point for building a wcstack application. If you have not already done so, first read the repository [README](https://github.com/wcstack/wcstack#readme) and [AGENTS.md](https://github.com/wcstack/wcstack/blob/main/AGENTS.md), then use the [wcstack-app skill](https://github.com/wcstack/wcstack-skill).
|
|
4
|
+
|
|
5
|
+
**もし、state に書いた型がそれをバインドする HTML まで届いたら?**
|
|
6
|
+
|
|
7
|
+
`@wcstack/state` のページは DOM と state をパス文字列(`users.*.name`)で結びます。TypeScript の state ファイルはそのパス全部と型を知っています — しかし HTML は `.d.ts` を消費できず、静的検証器(`wcs-validate`・VS Code 拡張)はインラインスクリプトを型注釈を理解しない正規表現アナライザで読みます。`users: [] as { name: string }[]` では `users.*.name` が解決できず、本物の typo は warning 止まり、正しいバインドには偽警告が付きます。
|
|
8
|
+
|
|
9
|
+
`@wcstack/typescript` はこの穴をコマンド 1 つで塞ぎます。`wcs-schema` は state ファイルを TypeScript コンパイラ API でコンパイルし、検証器が消費する sidecar `wcstack.manifest.json` の `stateSchema` を書き出します — CI でも、どのエディタでも。manifest は**派生物**です: 正本は型のままで、乖離すれば `wcs-schema check` が CI を落とします。
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install -D @wcstack/typescript typescript
|
|
13
|
+
npx wcs-schema emit src/state.ts # ./wcstack.manifest.json を書く
|
|
14
|
+
npx wcs-validate --strict index.html # typo は error に、偽警告は消える
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
ランタイム依存ゼロ。`typescript` は peer dependency — プロジェクト自身のコンパイラを使います。
|
|
18
|
+
|
|
19
|
+
## コマンド
|
|
20
|
+
|
|
21
|
+
### `wcs-schema emit <state.ts|state.js> [options]`
|
|
22
|
+
|
|
23
|
+
ファイルの `export default` から `states[<name>].stateSchema` を生成します(`defineState(...)` は構文的に剥がすので、このパッケージは `@wcstack/state` を解決できなくても動きます)。生成結果は書き出す前に検証器自身の manifest 検査を通します。
|
|
24
|
+
|
|
25
|
+
| オプション | 説明 |
|
|
26
|
+
|---|---|
|
|
27
|
+
| `--state=<name>` | state 名(既定 `default`) |
|
|
28
|
+
| `--out=<path>` | 出力先 manifest(作業ディレクトリ相対・既定 `wcstack.manifest.json`)。`--out=-` で stdout |
|
|
29
|
+
| `--merge` | 既存 manifest の他の内容(他 state・`filters`・`listContexts`)を保持し、この state の `stateSchema` だけ置き換える。同じ state の手書き schema は合成されず置き換わる |
|
|
30
|
+
| `--tsconfig=<path>` | コンパイルに使う `tsconfig.json`(既定: state ファイルから上に辿って最も近いもの、無ければ組み込み既定) |
|
|
31
|
+
| `--max-depth=<n>` | ここより深い入れ子は素の `{}` で打ち切る(既定 `5` = 検証器の探索予算と同じ) |
|
|
32
|
+
|
|
33
|
+
exit code: `0` 書き出し済み · `2` usage エラー / 読めない / 構文エラー / 生成物が自己検査に落ちた。
|
|
34
|
+
|
|
35
|
+
### `wcs-schema check <state.ts|state.js> [options]`
|
|
36
|
+
|
|
37
|
+
schema を再生成し、`--manifest=<path>`(既定 `wcstack.manifest.json`)の内容とキー順を無視して比較します。
|
|
38
|
+
|
|
39
|
+
exit code: `0` 最新 · `1` 乖離あり(変更は JSON pointer で列挙: `+` 型にだけある / `-` manifest にだけある / `~` 違う) · `2` usage エラー / manifest が無い / その state の `stateSchema` がまだ無い。
|
|
40
|
+
|
|
41
|
+
推奨する CI ゲート:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npx wcs-schema check src/state.ts && npx wcs-validate --strict index.html
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### `wcs-tsc [--url-imports=any|error] [--wcs-defaults] [tsc の引数...]`
|
|
48
|
+
|
|
49
|
+
`.html` に対する `tsc`: 各 `<wcs-state>` のインライン `<script type="module">` を VS Code 拡張と同じ言語プラグイン(型付き `this`・自動 `defineState` ラップ・bare でも CDN URL でも `@wcstack/state` import を除去)で型検査し、診断は HTML の位置を指します:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
npm i -D @volar/typescript@~2.4.0 @volar/language-core@~2.4.0 # optional peer・wcs-tsc だけが必要
|
|
53
|
+
npx wcs-tsc --noEmit
|
|
54
|
+
# index.html(9,14): error TS2551: Property 'coutn' does not exist on type '_WcsThis<…>'. Did you mean 'count'?
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
| オプション | 説明 |
|
|
58
|
+
|---|---|
|
|
59
|
+
| `--url-imports=any`(既定) | 全ての `http(s)://` モジュール import を `any` に型付ける — buildless なページは tsc が解決できない CDN から import する |
|
|
60
|
+
| `--url-imports=error` | URL import をそのまま残す(それぞれ TS2307) |
|
|
61
|
+
| `--wcs-defaults` | プロジェクトの tsconfig に `**/*.html` を覆う `include`・`noImplicitThis`・`allowJs`・`checkJs` が無ければ、元を extends してそれらを足した一時 config で実行する(無指定なら警告だけで HTML が検査されないことがある) |
|
|
62
|
+
| それ以外 | tsc にそのまま渡す(`-p`・`--noEmit` …) |
|
|
63
|
+
|
|
64
|
+
exit code は tsc のもの(`0` クリーン・診断があれば非ゼロ)。`typescript` / `@volar/typescript` が解決できないときと不正なオプションは `2`。1 ページに複数の `<wcs-state>` があれば 1 本の仮想モジュールに合成(import は巻き上げ・各ブロックは自分のスコープ)し、インライン state の無いページは空のモジュールになります。仕組みは vue-tsc と同じ: `@volar/typescript` の `runTsc` がプロジェクト自身の `typescript/lib/tsc.js` にパッチを当てます。
|
|
65
|
+
|
|
66
|
+
## 生成される schema の中身
|
|
67
|
+
|
|
68
|
+
sidecar 規範が許す JSON-Schema サブセットだけ(`type` / `properties` / `required` / `items` / `enum` / `const` / `anyOf`):
|
|
69
|
+
|
|
70
|
+
| TypeScript | stateSchema |
|
|
71
|
+
|---|---|
|
|
72
|
+
| `string` / `number` / `boolean` / `null` | `{ "type": … }`(`integer` は使わず全て `number`) |
|
|
73
|
+
| `"a" \| "b"`、`1 \| 2` | `{ "type": "string", "enum": ["a", "b"] }` |
|
|
74
|
+
| `T[]`、`readonly T[]`、タプル | `{ "type": "array", "items": … }` |
|
|
75
|
+
| `T \| null` | `{ "anyOf": [T, { "type": "null" }] }` |
|
|
76
|
+
| `T \| undefined`、`x?: T` | `T`。`x` は `required` に入らない |
|
|
77
|
+
| `A \| B`(オブジェクトや混在プリミティブ) | `{ "anyOf": [A, B] }` |
|
|
78
|
+
| 自分のコードのオブジェクトリテラル / interface / class | `{ "type": "object", "properties": …, "required": … }` |
|
|
79
|
+
| `Date`、`Map`、`Set`、DOM 型、ライブラリ由来の型、`any`、`unknown`、`Record<string, T>` | **素の `{}`** |
|
|
80
|
+
| `get x(): T` | `x: T` — getter はメンバー |
|
|
81
|
+
| `get "users.*.ageCategory"(): string` | `users.items.properties.ageCategory` に注入 — パス getter は計算先のパスのメンバー |
|
|
82
|
+
| メソッド、関数値プロパティ、`$` 始まりキー(`$watch`・`$commandTokens` …) | 捨てる |
|
|
83
|
+
| `--max-depth` より深い入れ子 | 素の `{}` |
|
|
84
|
+
|
|
85
|
+
素の `{}` が要点です: 検証器はこれを *unknown* として扱いその下では沈黙しますが、メンバーを欠く型付きオブジェクトは *nonexistent* で **error** です。`Date` や `Record<string, unknown>` の下のパスが偽 error になることはありません。
|
|
86
|
+
|
|
87
|
+
## ライブラリ API
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
import { generateStateSchema, buildManifest, compareStateSchema } from "@wcstack/typescript";
|
|
91
|
+
|
|
92
|
+
const { schema, warnings } = generateStateSchema("src/state.ts"); // { tsconfig?, maxDepth? }
|
|
93
|
+
const manifest = buildManifest("default", schema /*, 既存の manifest オブジェクト */);
|
|
94
|
+
const result = compareStateSchema(JSON.stringify(manifest), "default", schema); // { kind: "same" } | { kind: "differs", changes } | …
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`loadStateFile` / `stateTypeToSchema` は 2 つの半分(コンパイラ program・型 → schema)を別々に組み合わせたいツール向けに公開しています。
|
|
98
|
+
|
|
99
|
+
## 位置づけ
|
|
100
|
+
|
|
101
|
+
- 検証器側 — 最近傍 `wcstack.manifest.json` の発見、`wcs/path-nonexistent`、`wcs/path-type-mismatch` — は [`@wcstack/lint`](../lint/README.ja.md#state-の契約を宣言するstateschema) と、規範としては [`docs/wcstack-manifest-schema.md`](https://github.com/wcstack/wcstack/blob/main/docs/wcstack-manifest-schema.md) にあります。
|
|
102
|
+
- wcstack アプリの TypeScript の話全体(`defineState` による `this` の型付け、このパッケージ、tag name map、`wcs-tsc`)は [`docs/typescript.ja.md`](https://github.com/wcstack/wcstack/blob/main/docs/typescript.ja.md) にまとめています。
|
|
103
|
+
|
|
104
|
+
## License
|
|
105
|
+
|
|
106
|
+
MIT
|
package/README.md
CHANGED
|
@@ -1,106 +1,106 @@
|
|
|
1
|
-
# @wcstack/typescript
|
|
2
|
-
|
|
3
|
-
> 🤖 **AI coding agents**: This README is a package-level reference, not the primary entry point for building a wcstack application. If you have not already done so, first read the repository [README](https://github.com/wcstack/wcstack#readme) and [AGENTS.md](https://github.com/wcstack/wcstack/blob/main/AGENTS.md), then use the [wcstack-app skill](https://github.com/wcstack/wcstack-skill).
|
|
4
|
-
|
|
5
|
-
**What if the types you already wrote for your state reached the HTML that binds to it?**
|
|
6
|
-
|
|
7
|
-
`@wcstack/state` pages bind DOM to state through path strings (`users.*.name`). A TypeScript state file already knows every one of those paths and its type — but HTML cannot consume a `.d.ts`, and the static validator (`wcs-validate`, the VS Code extension) reads the inline script with a regular-expression analyzer that does not understand annotations: `users: [] as { name: string }[]` leaves `users.*.name` unresolvable, so a real typo is only a warning and a correct binding gets a false one.
|
|
8
|
-
|
|
9
|
-
`@wcstack/typescript` closes that gap with one command. `wcs-schema` compiles the state file with the TypeScript compiler API and writes the sidecar `wcstack.manifest.json` `stateSchema` that the validator consumes — in CI and in every editor. The manifest is a **derived artifact**: the type stays the source of truth, and `wcs-schema check` fails CI when they drift.
|
|
10
|
-
|
|
11
|
-
```bash
|
|
12
|
-
npm install -D @wcstack/typescript typescript
|
|
13
|
-
npx wcs-schema emit src/state.ts # writes ./wcstack.manifest.json
|
|
14
|
-
npx wcs-validate --strict index.html # typos are now errors, false warnings are gone
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
Zero runtime dependencies. `typescript` is a peer dependency — the project's own compiler is used.
|
|
18
|
-
|
|
19
|
-
## Commands
|
|
20
|
-
|
|
21
|
-
### `wcs-schema emit <state.ts|state.js> [options]`
|
|
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.
|
|
24
|
-
|
|
25
|
-
| Option | Description |
|
|
26
|
-
|---|---|
|
|
27
|
-
| `--state=<name>` | State name (default `default`) |
|
|
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 |
|
|
30
|
-
| `--tsconfig=<path>` | `tsconfig.json` to compile with (default: the nearest one above the state file, else built-in defaults) |
|
|
31
|
-
| `--max-depth=<n>` | Object nesting depth at which the schema stops with a bare `{}` (default `5`, the validator's own budget) |
|
|
32
|
-
|
|
33
|
-
Exit codes: `0` written · `2` usage error, unreadable file, syntax error, or a generated manifest that failed its self-check.
|
|
34
|
-
|
|
35
|
-
### `wcs-schema check <state.ts|state.js> [options]`
|
|
36
|
-
|
|
37
|
-
Regenerates the schema and compares it with `--manifest=<path>` (default `wcstack.manifest.json`), key order ignored.
|
|
38
|
-
|
|
39
|
-
Exit codes: `0` up to date · `1` drift (each change is listed as a JSON pointer: `+` only in the type, `-` only in the manifest, `~` different) · `2` usage error, missing manifest, or the state has no `stateSchema` yet.
|
|
40
|
-
|
|
41
|
-
The recommended CI gate is
|
|
42
|
-
|
|
43
|
-
```bash
|
|
44
|
-
npx wcs-schema check src/state.ts && npx wcs-validate --strict index.html
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
### `wcs-tsc [--url-imports=any|error] [--wcs-defaults] [tsc arguments...]`
|
|
48
|
-
|
|
49
|
-
`tsc` over `.html`: every `<wcs-state>` inline `<script type="module">` is type-checked through the same language plugin the VS Code extension uses (typed `this`, automatic `defineState` wrap, `@wcstack/state` imports stripped whether bare or a CDN URL), and diagnostics point into the HTML:
|
|
50
|
-
|
|
51
|
-
```bash
|
|
52
|
-
npm i -D @volar/typescript@~2.4.0 @volar/language-core@~2.4.0 # optional peers, only wcs-tsc needs them
|
|
53
|
-
npx wcs-tsc --noEmit
|
|
54
|
-
# index.html(9,14): error TS2551: Property 'coutn' does not exist on type '_WcsThis<…>'. Did you mean 'count'?
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
| Option | Description |
|
|
58
|
-
|---|---|
|
|
59
|
-
| `--url-imports=any` (default) | Every `http(s)://` module import types as `any` — buildless pages import from a CDN that tsc cannot resolve |
|
|
60
|
-
| `--url-imports=error` | Leave URL imports alone (TS2307 each) |
|
|
61
|
-
| `--wcs-defaults` | If the project tsconfig lacks `include` covering `**/*.html`, `noImplicitThis`, `allowJs` or `checkJs`, run with a temporary config that extends it and adds them (otherwise only a warning is printed and HTML may go unchecked) |
|
|
62
|
-
| anything else | passed to tsc verbatim (`-p`, `--noEmit`, …) |
|
|
63
|
-
|
|
64
|
-
Exit codes are tsc's (`0` clean, non-zero with diagnostics); `2` when `typescript` / `@volar/typescript` cannot be resolved or an option is invalid. A page with several `<wcs-state>` blocks is combined into one virtual module (imports hoisted, each block in its own scope); a page without inline state is an empty module. The mechanism is vue-tsc's: `@volar/typescript`'s `runTsc` patches the project's own `typescript/lib/tsc.js`.
|
|
65
|
-
|
|
66
|
-
## What the generated schema contains
|
|
67
|
-
|
|
68
|
-
Only the JSON-Schema subset the sidecar spec allows (`type`, `properties`, `required`, `items`, `enum`, `const`, `anyOf`):
|
|
69
|
-
|
|
70
|
-
| TypeScript | stateSchema |
|
|
71
|
-
|---|---|
|
|
72
|
-
| `string` / `number` / `boolean` / `null` | `{ "type": … }` (`integer`-less: numbers are `number`) |
|
|
73
|
-
| `"a" \| "b"`, `1 \| 2` | `{ "type": "string", "enum": ["a", "b"] }` |
|
|
74
|
-
| `T[]`, `readonly T[]`, tuples | `{ "type": "array", "items": … }` |
|
|
75
|
-
| `T \| null` | `{ "anyOf": [T, { "type": "null" }] }` |
|
|
76
|
-
| `T \| undefined`, `x?: T` | `T`, and `x` is left out of `required` |
|
|
77
|
-
| `A \| B` (objects or mixed primitives) | `{ "anyOf": [A, B] }` |
|
|
78
|
-
| object literals, interfaces, classes from your code | `{ "type": "object", "properties": …, "required": … }` |
|
|
79
|
-
| `Date`, `Map`, `Set`, DOM types, anything from a library, `any`, `unknown`, `Record<string, T>` | a **bare `{}`** |
|
|
80
|
-
| `get x(): T` | `x: T` — getters are members |
|
|
81
|
-
| `get "users.*.ageCategory"(): string` | injected as `users.items.properties.ageCategory` — a path getter is a member at the path it computes |
|
|
82
|
-
| methods, function-valued properties, `$`-prefixed keys (`$watch`, `$commandTokens`, …) | dropped |
|
|
83
|
-
| nesting deeper than `--max-depth` | a bare `{}` |
|
|
84
|
-
|
|
85
|
-
The bare `{}` matters: the validator treats it as *unknown* and stays silent below it, whereas a typed object that lacks a member is *nonexistent* and an **error**. A `Date` or a `Record<string, unknown>` therefore never produces false errors for the paths under it.
|
|
86
|
-
|
|
87
|
-
## Library API
|
|
88
|
-
|
|
89
|
-
```ts
|
|
90
|
-
import { generateStateSchema, buildManifest, compareStateSchema } from "@wcstack/typescript";
|
|
91
|
-
|
|
92
|
-
const { schema, warnings } = generateStateSchema("src/state.ts"); // { tsconfig?, maxDepth? }
|
|
93
|
-
const manifest = buildManifest("default", schema /*, existingManifestObject */);
|
|
94
|
-
const result = compareStateSchema(JSON.stringify(manifest), "default", schema); // { kind: "same" } | { kind: "differs", changes } | …
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
`loadStateFile` / `stateTypeToSchema` expose the two halves (compiler program, type → schema) for tools that want to compose them differently.
|
|
98
|
-
|
|
99
|
-
## Where this fits
|
|
100
|
-
|
|
101
|
-
- The validator side — discovery of the nearest `wcstack.manifest.json`, `wcs/path-nonexistent`, `wcs/path-type-mismatch` — is documented in [`@wcstack/lint`](../lint/README.md#declaring-a-state-contract-stateschema) and normatively in [`docs/wcstack-manifest-schema.md`](https://github.com/wcstack/wcstack/blob/main/docs/wcstack-manifest-schema.md).
|
|
102
|
-
- The full TypeScript story for wcstack apps (typing `this` with `defineState`, this package, tag name maps, `wcs-tsc`) is collected in [`docs/typescript.md`](https://github.com/wcstack/wcstack/blob/main/docs/typescript.md).
|
|
103
|
-
|
|
104
|
-
## License
|
|
105
|
-
|
|
106
|
-
MIT
|
|
1
|
+
# @wcstack/typescript
|
|
2
|
+
|
|
3
|
+
> 🤖 **AI coding agents**: This README is a package-level reference, not the primary entry point for building a wcstack application. If you have not already done so, first read the repository [README](https://github.com/wcstack/wcstack#readme) and [AGENTS.md](https://github.com/wcstack/wcstack/blob/main/AGENTS.md), then use the [wcstack-app skill](https://github.com/wcstack/wcstack-skill).
|
|
4
|
+
|
|
5
|
+
**What if the types you already wrote for your state reached the HTML that binds to it?**
|
|
6
|
+
|
|
7
|
+
`@wcstack/state` pages bind DOM to state through path strings (`users.*.name`). A TypeScript state file already knows every one of those paths and its type — but HTML cannot consume a `.d.ts`, and the static validator (`wcs-validate`, the VS Code extension) reads the inline script with a regular-expression analyzer that does not understand annotations: `users: [] as { name: string }[]` leaves `users.*.name` unresolvable, so a real typo is only a warning and a correct binding gets a false one.
|
|
8
|
+
|
|
9
|
+
`@wcstack/typescript` closes that gap with one command. `wcs-schema` compiles the state file with the TypeScript compiler API and writes the sidecar `wcstack.manifest.json` `stateSchema` that the validator consumes — in CI and in every editor. The manifest is a **derived artifact**: the type stays the source of truth, and `wcs-schema check` fails CI when they drift.
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install -D @wcstack/typescript typescript
|
|
13
|
+
npx wcs-schema emit src/state.ts # writes ./wcstack.manifest.json
|
|
14
|
+
npx wcs-validate --strict index.html # typos are now errors, false warnings are gone
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Zero runtime dependencies. `typescript` is a peer dependency — the project's own compiler is used.
|
|
18
|
+
|
|
19
|
+
## Commands
|
|
20
|
+
|
|
21
|
+
### `wcs-schema emit <state.ts|state.js> [options]`
|
|
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.
|
|
24
|
+
|
|
25
|
+
| Option | Description |
|
|
26
|
+
|---|---|
|
|
27
|
+
| `--state=<name>` | State name (default `default`) |
|
|
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 |
|
|
30
|
+
| `--tsconfig=<path>` | `tsconfig.json` to compile with (default: the nearest one above the state file, else built-in defaults) |
|
|
31
|
+
| `--max-depth=<n>` | Object nesting depth at which the schema stops with a bare `{}` (default `5`, the validator's own budget) |
|
|
32
|
+
|
|
33
|
+
Exit codes: `0` written · `2` usage error, unreadable file, syntax error, or a generated manifest that failed its self-check.
|
|
34
|
+
|
|
35
|
+
### `wcs-schema check <state.ts|state.js> [options]`
|
|
36
|
+
|
|
37
|
+
Regenerates the schema and compares it with `--manifest=<path>` (default `wcstack.manifest.json`), key order ignored.
|
|
38
|
+
|
|
39
|
+
Exit codes: `0` up to date · `1` drift (each change is listed as a JSON pointer: `+` only in the type, `-` only in the manifest, `~` different) · `2` usage error, missing manifest, or the state has no `stateSchema` yet.
|
|
40
|
+
|
|
41
|
+
The recommended CI gate is
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npx wcs-schema check src/state.ts && npx wcs-validate --strict index.html
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### `wcs-tsc [--url-imports=any|error] [--wcs-defaults] [tsc arguments...]`
|
|
48
|
+
|
|
49
|
+
`tsc` over `.html`: every `<wcs-state>` inline `<script type="module">` is type-checked through the same language plugin the VS Code extension uses (typed `this`, automatic `defineState` wrap, `@wcstack/state` imports stripped whether bare or a CDN URL), and diagnostics point into the HTML:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
npm i -D @volar/typescript@~2.4.0 @volar/language-core@~2.4.0 # optional peers, only wcs-tsc needs them
|
|
53
|
+
npx wcs-tsc --noEmit
|
|
54
|
+
# index.html(9,14): error TS2551: Property 'coutn' does not exist on type '_WcsThis<…>'. Did you mean 'count'?
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
| Option | Description |
|
|
58
|
+
|---|---|
|
|
59
|
+
| `--url-imports=any` (default) | Every `http(s)://` module import types as `any` — buildless pages import from a CDN that tsc cannot resolve |
|
|
60
|
+
| `--url-imports=error` | Leave URL imports alone (TS2307 each) |
|
|
61
|
+
| `--wcs-defaults` | If the project tsconfig lacks `include` covering `**/*.html`, `noImplicitThis`, `allowJs` or `checkJs`, run with a temporary config that extends it and adds them (otherwise only a warning is printed and HTML may go unchecked) |
|
|
62
|
+
| anything else | passed to tsc verbatim (`-p`, `--noEmit`, …) |
|
|
63
|
+
|
|
64
|
+
Exit codes are tsc's (`0` clean, non-zero with diagnostics); `2` when `typescript` / `@volar/typescript` cannot be resolved or an option is invalid. A page with several `<wcs-state>` blocks is combined into one virtual module (imports hoisted, each block in its own scope); a page without inline state is an empty module. The mechanism is vue-tsc's: `@volar/typescript`'s `runTsc` patches the project's own `typescript/lib/tsc.js`.
|
|
65
|
+
|
|
66
|
+
## What the generated schema contains
|
|
67
|
+
|
|
68
|
+
Only the JSON-Schema subset the sidecar spec allows (`type`, `properties`, `required`, `items`, `enum`, `const`, `anyOf`):
|
|
69
|
+
|
|
70
|
+
| TypeScript | stateSchema |
|
|
71
|
+
|---|---|
|
|
72
|
+
| `string` / `number` / `boolean` / `null` | `{ "type": … }` (`integer`-less: numbers are `number`) |
|
|
73
|
+
| `"a" \| "b"`, `1 \| 2` | `{ "type": "string", "enum": ["a", "b"] }` |
|
|
74
|
+
| `T[]`, `readonly T[]`, tuples | `{ "type": "array", "items": … }` |
|
|
75
|
+
| `T \| null` | `{ "anyOf": [T, { "type": "null" }] }` |
|
|
76
|
+
| `T \| undefined`, `x?: T` | `T`, and `x` is left out of `required` |
|
|
77
|
+
| `A \| B` (objects or mixed primitives) | `{ "anyOf": [A, B] }` |
|
|
78
|
+
| object literals, interfaces, classes from your code | `{ "type": "object", "properties": …, "required": … }` |
|
|
79
|
+
| `Date`, `Map`, `Set`, DOM types, anything from a library, `any`, `unknown`, `Record<string, T>` | a **bare `{}`** |
|
|
80
|
+
| `get x(): T` | `x: T` — getters are members |
|
|
81
|
+
| `get "users.*.ageCategory"(): string` | injected as `users.items.properties.ageCategory` — a path getter is a member at the path it computes |
|
|
82
|
+
| methods, function-valued properties, `$`-prefixed keys (`$watch`, `$commandTokens`, …) | dropped |
|
|
83
|
+
| nesting deeper than `--max-depth` | a bare `{}` |
|
|
84
|
+
|
|
85
|
+
The bare `{}` matters: the validator treats it as *unknown* and stays silent below it, whereas a typed object that lacks a member is *nonexistent* and an **error**. A `Date` or a `Record<string, unknown>` therefore never produces false errors for the paths under it.
|
|
86
|
+
|
|
87
|
+
## Library API
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
import { generateStateSchema, buildManifest, compareStateSchema } from "@wcstack/typescript";
|
|
91
|
+
|
|
92
|
+
const { schema, warnings } = generateStateSchema("src/state.ts"); // { tsconfig?, maxDepth? }
|
|
93
|
+
const manifest = buildManifest("default", schema /*, existingManifestObject */);
|
|
94
|
+
const result = compareStateSchema(JSON.stringify(manifest), "default", schema); // { kind: "same" } | { kind: "differs", changes } | …
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`loadStateFile` / `stateTypeToSchema` expose the two halves (compiler program, type → schema) for tools that want to compose them differently.
|
|
98
|
+
|
|
99
|
+
## Where this fits
|
|
100
|
+
|
|
101
|
+
- The validator side — discovery of the nearest `wcstack.manifest.json`, `wcs/path-nonexistent`, `wcs/path-type-mismatch` — is documented in [`@wcstack/lint`](../lint/README.md#declaring-a-state-contract-stateschema) and normatively in [`docs/wcstack-manifest-schema.md`](https://github.com/wcstack/wcstack/blob/main/docs/wcstack-manifest-schema.md).
|
|
102
|
+
- The full TypeScript story for wcstack apps (typing `this` with `defineState`, this package, tag name maps, `wcs-tsc`) is collected in [`docs/typescript.md`](https://github.com/wcstack/wcstack/blob/main/docs/typescript.md).
|
|
103
|
+
|
|
104
|
+
## License
|
|
105
|
+
|
|
106
|
+
MIT
|
package/dist/index.d.ts
CHANGED
|
@@ -85,14 +85,20 @@ declare function generateStateSchema(file: string, options?: GenerateOptions): G
|
|
|
85
85
|
*
|
|
86
86
|
* The manifest is a **derived artifact** (D9): the TypeScript type is the source
|
|
87
87
|
* of truth, `wcs-schema emit` writes the manifest, and `wcs-schema check`
|
|
88
|
-
* detects drift between the two in CI.
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
88
|
+
* detects drift between the two in CI.
|
|
89
|
+
*
|
|
90
|
+
* v2 (schemaVersion 2): the application namespace carries a **single**
|
|
91
|
+
* `stateSchema` — one state tree per root, no name dimension
|
|
92
|
+
* (docs/state-mount-design.md D15). A volume (`<wcs-state mount="path">`)
|
|
93
|
+
* contributes a **subtree**: `--mount=<path>` merges the module's schema under
|
|
94
|
+
* that path inside the single `stateSchema`. `--merge` keeps everything else in
|
|
95
|
+
* an existing manifest (filters, listContexts); a hand-written schema for the
|
|
96
|
+
* same slot does not survive, by design: there is no implicit merge in the
|
|
97
|
+
* sidecar spec (§5).
|
|
92
98
|
*/
|
|
93
99
|
|
|
94
100
|
declare const APPLICATION_MANIFEST_FILENAME = "wcstack.manifest.json";
|
|
95
|
-
declare const SCHEMA_VERSION =
|
|
101
|
+
declare const SCHEMA_VERSION = 2;
|
|
96
102
|
declare const APPLICATION_NAMESPACE = "wcstack.application";
|
|
97
103
|
interface ApplicationManifest {
|
|
98
104
|
schemaVersion: number;
|
|
@@ -100,9 +106,7 @@ interface ApplicationManifest {
|
|
|
100
106
|
manifestExtensions: {
|
|
101
107
|
[APPLICATION_NAMESPACE]: {
|
|
102
108
|
version: number;
|
|
103
|
-
|
|
104
|
-
stateSchema: JsonSchemaNode;
|
|
105
|
-
}>;
|
|
109
|
+
stateSchema?: JsonSchemaNode;
|
|
106
110
|
[key: string]: unknown;
|
|
107
111
|
};
|
|
108
112
|
[namespace: string]: unknown;
|
|
@@ -110,12 +114,23 @@ interface ApplicationManifest {
|
|
|
110
114
|
[key: string]: unknown;
|
|
111
115
|
}
|
|
112
116
|
/**
|
|
113
|
-
* Create the manifest object for
|
|
114
|
-
* (a parsed manifest object; envelope fields are
|
|
117
|
+
* Create the manifest object for the state tree, or graft into `existing`
|
|
118
|
+
* (a parsed manifest object; envelope fields are normalized to v2 — a v1
|
|
119
|
+
* manifest's `states` map does not survive, its replacement is exactly this
|
|
120
|
+
* regeneration path).
|
|
121
|
+
*
|
|
122
|
+
* `mountPath === null` replaces the whole `stateSchema`; a mount path merges
|
|
123
|
+
* the schema as a subtree at that path (intermediate object nodes are created).
|
|
115
124
|
*/
|
|
116
|
-
declare function buildManifest(
|
|
117
|
-
/** Read `
|
|
118
|
-
declare function readStateSchema(manifest: unknown
|
|
125
|
+
declare function buildManifest(mountPath: string | null, schema: JsonSchemaNode, existing?: unknown): ApplicationManifest;
|
|
126
|
+
/** Read the single `stateSchema` from a parsed manifest object, or undefined. */
|
|
127
|
+
declare function readStateSchema(manifest: unknown): unknown;
|
|
128
|
+
/**
|
|
129
|
+
* True for a v1-shaped manifest (`schemaVersion: 1` or a `states` map in the
|
|
130
|
+
* application namespace). `check` uses it to point at the regeneration path
|
|
131
|
+
* instead of reporting a confusing "missing stateSchema".
|
|
132
|
+
*/
|
|
133
|
+
declare function isV1Manifest(manifest: unknown): boolean;
|
|
119
134
|
/** JSON with object keys sorted at every level — the canonical form used for comparison. */
|
|
120
135
|
declare function stableStringify(value: unknown): string;
|
|
121
136
|
type SchemaComparison = {
|
|
@@ -125,16 +140,19 @@ type SchemaComparison = {
|
|
|
125
140
|
readonly changes: readonly string[];
|
|
126
141
|
} | {
|
|
127
142
|
readonly kind: "missing-state";
|
|
143
|
+
} | {
|
|
144
|
+
readonly kind: "v1-manifest";
|
|
128
145
|
} | {
|
|
129
146
|
readonly kind: "broken";
|
|
130
147
|
readonly message: string;
|
|
131
148
|
};
|
|
132
149
|
/**
|
|
133
|
-
* Compare the schema generated from the type with the one stored in `manifestText
|
|
150
|
+
* Compare the schema generated from the type with the one stored in `manifestText`
|
|
151
|
+
* (the whole tree, or the subtree at `mountPath`).
|
|
134
152
|
* `changes` lists JSON pointers: `+ ptr` (only in generated), `- ptr` (only in
|
|
135
153
|
* manifest), `~ ptr` (both, different value).
|
|
136
154
|
*/
|
|
137
|
-
declare function compareStateSchema(manifestText: string,
|
|
155
|
+
declare function compareStateSchema(manifestText: string, mountPath: string | null, generated: JsonSchemaNode): SchemaComparison;
|
|
138
156
|
|
|
139
157
|
/**
|
|
140
158
|
* schemaCore.ts — the validator core bundle (`dist/schema-core.cjs`, built from
|
|
@@ -173,5 +191,5 @@ declare function loadSchemaCore(): SchemaCore;
|
|
|
173
191
|
|
|
174
192
|
declare const VERSION: string;
|
|
175
193
|
|
|
176
|
-
export { APPLICATION_MANIFEST_FILENAME, APPLICATION_NAMESPACE, DEFAULT_COMPILER_OPTIONS, DEFAULT_MAX_DEPTH, SCHEMA_VERSION, VERSION, buildManifest, compareStateSchema, generateStateSchema, loadSchemaCore, loadStateFile, readStateSchema, resolveCompilerOptions, schemaCoreCandidates, stableStringify, stateTypeToSchema };
|
|
194
|
+
export { APPLICATION_MANIFEST_FILENAME, APPLICATION_NAMESPACE, DEFAULT_COMPILER_OPTIONS, DEFAULT_MAX_DEPTH, SCHEMA_VERSION, VERSION, buildManifest, compareStateSchema, generateStateSchema, isV1Manifest, loadSchemaCore, loadStateFile, readStateSchema, resolveCompilerOptions, schemaCoreCandidates, stableStringify, stateTypeToSchema };
|
|
177
195
|
export type { ApplicationManifest, GenerateOptions, GeneratedSchema, JsonSchemaNode, LoadStateOptions, LoadedState, SchemaComparison, SchemaCore, SchemaOptions, WcsDiagnostic };
|