@kubohiroya/turbowarp-extension-data-editor 0.1.0 → 0.2.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 +30 -6
- package/dist/build-info.json +7 -2
- package/dist/descendants.js +11 -2
- package/dist/fixed-input-branches.d.ts +12 -0
- package/dist/fixed-input-branches.js +180 -0
- package/dist/fixed-input-editor.d.ts +39 -0
- package/dist/fixed-input-editor.js +538 -0
- package/dist/fixed-input-schema.d.ts +15 -0
- package/dist/fixed-input-schema.js +21 -0
- package/dist/fixed-input-snapshot.d.ts +7 -0
- package/dist/fixed-input-snapshot.js +180 -0
- package/dist/fixed-input-types.d.ts +138 -0
- package/dist/fixed-input-types.js +23 -0
- package/dist/fixed-input-view.d.ts +14 -0
- package/dist/fixed-input-view.js +168 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +4 -0
- package/dist/json-value.d.ts +4 -0
- package/dist/json-value.js +12 -0
- package/dist/prepared-save.d.ts +5 -0
- package/dist/prepared-save.js +47 -0
- package/dist/prepared-types.d.ts +13 -0
- package/dist/prepared-types.js +1 -0
- package/dist/react.css +42 -0
- package/dist/react.d.ts +3 -0
- package/dist/react.js +10 -1
- package/dist/registry.d.ts +6 -0
- package/dist/registry.js +98 -48
- package/dist/rjsf.d.ts +48 -1
- package/dist/rjsf.js +92 -8
- package/dist/schema-compose.d.ts +6 -0
- package/dist/schema-compose.js +103 -0
- package/dist/schema-engine.d.ts +18 -0
- package/dist/schema-engine.js +298 -0
- package/dist/schema-json.d.ts +10 -0
- package/dist/schema-json.js +68 -0
- package/dist/schema-types.d.ts +161 -0
- package/dist/schema-types.js +2 -0
- package/dist/schema-view.d.ts +7 -0
- package/dist/schema-view.js +102 -0
- package/dist/schema.d.ts +10 -0
- package/dist/schema.js +87 -0
- package/dist/session.d.ts +3 -1
- package/dist/session.js +220 -9
- package/dist/source-document.d.ts +3 -0
- package/dist/source-document.js +415 -0
- package/dist/source-editor.d.ts +13 -0
- package/dist/source-editor.js +235 -0
- package/dist/source-schema-hints.d.ts +3 -0
- package/dist/source-schema-hints.js +13 -0
- package/dist/source-types.d.ts +47 -0
- package/dist/source-types.js +1 -0
- package/dist/source-view.d.ts +3 -0
- package/dist/source-view.js +318 -0
- package/dist/turbowarp/bridge.d.ts +2 -1
- package/dist/turbowarp/bridge.js +34 -5
- package/dist/turbowarp/fixed-fields.d.ts +82 -0
- package/dist/turbowarp/fixed-fields.js +311 -0
- package/dist/turbowarp/fixed-transaction.d.ts +18 -0
- package/dist/turbowarp/fixed-transaction.js +460 -0
- package/dist/turbowarp/host.d.ts +3 -8
- package/dist/turbowarp/host.js +68 -1
- package/dist/turbowarp.d.ts +4 -0
- package/dist/turbowarp.js +1 -1
- package/dist/types.d.ts +7 -4
- package/dist/view.js +4 -0
- package/dist/webpack4/index.js +24 -5
- package/dist/webpack4/metafile.json +9886 -8710
- package/dist/webpack4/react.js +11 -2
- package/dist/webpack4/react.js.map +2 -2
- package/dist/webpack4/rjsf.js +15205 -20951
- package/dist/webpack4/rjsf.js.map +4 -4
- package/dist/webpack4/schema.js +20 -0
- package/dist/webpack4/shared-75QIWLZY.js +8375 -0
- package/dist/webpack4/shared-75QIWLZY.js.map +7 -0
- package/dist/webpack4/shared-BKTRZUZH.js +42 -0
- package/dist/webpack4/{shared-KIFRVTSM.js.map → shared-BKTRZUZH.js.map} +1 -1
- package/dist/webpack4/shared-FYUILHED.js +1 -0
- package/dist/webpack4/shared-FYUILHED.js.map +7 -0
- package/dist/webpack4/shared-GQLPPRT2.js +210 -0
- package/dist/webpack4/shared-GQLPPRT2.js.map +7 -0
- package/dist/webpack4/shared-P2JBRZFP.js +2206 -0
- package/dist/webpack4/shared-P2JBRZFP.js.map +7 -0
- package/dist/webpack4/{shared-KIFRVTSM.js → shared-RN7GOT65.js} +2 -41
- package/dist/webpack4/shared-RN7GOT65.js.map +7 -0
- package/dist/webpack4/turbowarp.js +912 -9
- package/dist/webpack4/turbowarp.js.map +4 -4
- package/docs/README.md +1 -0
- package/docs/block-editor-e2e.md +98 -16
- package/docs/ci-success-gate.md +84 -0
- package/docs/editor/issue-12-public-validation/README.md +61 -0
- package/docs/editor/issue-12-public-validation/dark-invalid.png +0 -0
- package/docs/editor/issue-12-public-validation/dark-styles.json +173 -0
- package/docs/editor/issue-12-public-validation/dark-valid.png +0 -0
- package/docs/editor/issue-12-public-validation/deployment-target.json +5 -0
- package/docs/editor/issue-12-public-validation/deployment-wait.json +7 -0
- package/docs/editor/issue-12-public-validation/light-invalid.png +0 -0
- package/docs/editor/issue-12-public-validation/light-styles.json +173 -0
- package/docs/editor/issue-12-public-validation/light-valid.png +0 -0
- package/docs/editor/issue-12-public-validation/provenance.json +88 -0
- package/docs/editor/issue-12-public-validation/results.json +677 -0
- package/docs/editor/issue-12-public-validation/summary.json +206 -0
- package/docs/fixed-input-contracts.md +77 -0
- package/docs/github-pages.md +48 -41
- package/docs/package-distribution.md +2 -2
- package/docs/schema-stage1-browser.json +10 -0
- package/docs/schema-stage1-contracts.json +242 -0
- package/docs/schema-stage1-dependencies.json +202 -0
- package/docs/schema-stage1-git-artifact-20261018.ja.md +17 -0
- package/docs/schema-stage1-profile-compat.json +30 -0
- package/docs/schema-stage1-review-fixes-20261018.ja.md +36 -0
- package/docs/schema-stage1-verification.json +541 -0
- package/docs/schema-stage1.ja.md +80 -0
- package/docs/schema.md +43 -0
- package/docs/source-forms.ja.md +19 -0
- package/docs/stage-2-publication.ja.md +35 -0
- package/docs/template-fixture-separation.ja.md +43 -0
- package/package.json +18 -3
- package/webpack4.cjs +1 -1
- package/dist/webpack4/shared-CQB3NG2A.js +0 -1
- package/dist/webpack4/shared-WHOAW2VF.js +0 -957
- package/dist/webpack4/shared-WHOAW2VF.js.map +0 -7
- /package/dist/webpack4/{shared-CQB3NG2A.js.map → schema.js.map} +0 -0
package/README.md
CHANGED
|
@@ -6,6 +6,9 @@ TurboWarp拡張が扱う構造化データを、フォームや独自UIで編集
|
|
|
6
6
|
|
|
7
7
|
日本語の利用手順・API・GUI接続・公開・検証記録は[ドキュメント一覧](docs/README.md)へ集約しています。
|
|
8
8
|
|
|
9
|
+
[公開エディター・日英案内](https://kubohiroya.github.io/turbowarp-extension-data-editor/) と
|
|
10
|
+
[公開・検証の運用Issue #26](https://github.com/kubohiroya/turbowarp-extension-data-editor/issues/26) も本リポジトリが所有します。
|
|
11
|
+
|
|
9
12
|
## 標準の JSON Schema エディター
|
|
10
13
|
|
|
11
14
|
JSON/YAML を格納する文字列引数と JSON Schema を宣言すると、react-jsonschema-form がフォームを生成します。
|
|
@@ -100,23 +103,22 @@ pnpm test
|
|
|
100
103
|
`pnpm test` は型宣言を含むビルド後、コアと標準フォームのテストを実行します。
|
|
101
104
|
パッケージは ESM です。ブラウザには ES2022 の組み込み API が必要です。
|
|
102
105
|
標準フォームの React / ReactDOM peer は 16.14 / 17 / 18。GUI は既存の 16.14 を共有します。
|
|
103
|
-
初回の実験版は `@kubohiroya/turbowarp-extension-data-editor@0.1.0`
|
|
106
|
+
初回の実験版は `@kubohiroya/turbowarp-extension-data-editor@0.1.0` です。固定SchemaのSource/Form編集は `0.2.0` で追加しました。今回の配布版 `0.2.1` は、既定OFFの固定入力契約・標準field transaction・固定フォームとGUI接続も含みます。
|
|
104
107
|
|
|
105
108
|
```sh
|
|
106
|
-
npm install --save-exact @kubohiroya/turbowarp-extension-data-editor@0.1
|
|
109
|
+
npm install --save-exact @kubohiroya/turbowarp-extension-data-editor@0.2.1
|
|
107
110
|
```
|
|
108
111
|
|
|
109
112
|
配布物・改名時の互換性・公開手順は[パッケージ配布](docs/package-distribution.md)を参照してください。
|
|
110
113
|
|
|
111
114
|
## GUI で試す
|
|
112
115
|
|
|
113
|
-
`scratch-gui`
|
|
116
|
+
`scratch-gui` は本パッケージを検証済みのnpm完全version、またはGitの完全SHAで固定参照します。通常のGUIビルドに共通リポジトリの隣接配置は不要です。
|
|
114
117
|
[GUIの起動・制約・検証手順](docs/block-editor.md) に従ってください。
|
|
115
118
|
既存の拡張アイコンをクリックすると、標準モーダルで下書きを編集できます。
|
|
116
119
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
`.sb3` の実保存と標準 `project_url` 経路での再読込を確認済みです。OS のファイル選択による再読込、アイコンがないブロックへの追加表示、翻訳・全テーマは後続の確認項目です。
|
|
120
|
+
既存のSOURCE経路は、配置済みブロックの通常の text shadow に保存された JSON/YAML 文字列を扱います。この経路でreporter、メニュー、独自validator付きfieldを書き換えません。固定入力経路は対応hostと明示opt-inで静的menu・数値・色を扱い、reporterや動的menuを評価・置換しません。
|
|
121
|
+
`.sb3` の保存、`project_url` とOSのファイル選択による再読込、日英表示とテーマの検証手順・実行範囲は[GUI接続E2E](docs/block-editor-e2e.md)と[固定入力の契約](docs/fixed-input-contracts.md)を参照してください。
|
|
120
122
|
|
|
121
123
|
## フラグとロールバック
|
|
122
124
|
|
|
@@ -129,3 +131,25 @@ GUI の `src/lib/tw-feature-flags.js` で起動時固定の `blockEditorDialogs`
|
|
|
129
131
|
## webpack 4 / E2E
|
|
130
132
|
|
|
131
133
|
[webpack 4配布物と設定API](docs/webpack4.md)、[GUI接続E2EとCIの実行手順](docs/block-editor-e2e.md)を参照。専用編集UIから互換ビルド・E2Eまで共通側で管理し、GUIには明示的なホスト接続とCI呼出しを残す。
|
|
134
|
+
|
|
135
|
+
## Schemaの共通型と診断変換
|
|
136
|
+
|
|
137
|
+
`@kubohiroya/turbowarp-extension-data-editor/schema`はDOM・React・RJSFをロードせずに利用できる入口です。Schema bundle、正本・生成物・参照用の役割、処理段階別の診断・結果、同期owner検証の型を公開します。
|
|
138
|
+
|
|
139
|
+
`toValidationIssues()`は入力診断を既存の`ValidationIssue`へ変換し、JSON Pointerのpath・順序・件数を維持した凍結結果を返します。不正な診断やcompile/execution診断はTypeErrorで拒否します。既存RJSFのpathや保存時専用の非同期`validateData`の契約は変わりません。
|
|
140
|
+
|
|
141
|
+
`compileSchemaBundle()`はdraft-07/2020-12と同梱resourceからオフラインの同期validatorを生成します。Schemaと入力を変更せず、構造化診断を返します。[pure検証API](docs/schema.md)を参照してください。同期owner合成と明示bundleのRJSF経路も追加済みです。既存公開入口・webpack4/build-info・CSS buildを維持しています。
|
|
142
|
+
|
|
143
|
+
同期owner検証には`/schema`の`validateSchemaInput`を利用できます。明示bundleフォームは`/rjsf`の`createSchemaBundleEditor` / `registerSchemaBundle`で登録します。context providerは呼出しごとに時刻・設定を渡します。表示上の対応範囲と既存APIの扱いは[Schema基盤](docs/schema.md)を参照してください。
|
|
144
|
+
|
|
145
|
+
既存SOURCE契約の導入時に採取した横断検証記録は**14契約・8 package**です。[Template分離後の再検証手順](docs/template-fixture-separation.ja.md#current-verification)で、Templateを含まないisolation indexと2つの検証commandを確認してください。この導入時記録では14本番契約と9固定契約を区別しています。固定入力の実装・GUI接続は末尾の契約節を参照してください。旧16契約・9 packageの結果と当時の開始条件は[段階1の履歴](docs/schema-stage1.ja.md)として保持します。
|
|
146
|
+
|
|
147
|
+
## Optional editing examples
|
|
148
|
+
|
|
149
|
+
Repository examples are opt-in teaching code, not template blocks or additional package exports. See [the JSON/YAML document example](examples/document-properties.mjs) and [its guide](examples/README.ja.md). Each extension owns its actual data contract.
|
|
150
|
+
|
|
151
|
+
Prepared source editing is an optional host capability. See [the source/form contract](docs/source-forms.ja.md) for fixed-schema bindings, atomic saves, compatibility, and rollback.
|
|
152
|
+
|
|
153
|
+
## 固定入力とGUI接続(S3-1〜S3-3)
|
|
154
|
+
|
|
155
|
+
固定入力の型・Schema検査・同期prepared契約を追加しています。`createBlockEditorService({fixedInputs: true})`の明示時だけ新しい登録APIを提供し、対応hostがない場合は開けません。lower-level hostの`enableFixedInputs: true`で標準fieldの読取・原子的保存を利用できます。固定フォームとGUIの既定OFFフラグ接続も含みます。各拡張は正本Schema・codec・登録を所有します。[固定入力の契約](docs/fixed-input-contracts.md)を参照してください。配布物には元Git SHAと版更新を含むworking-tree release overlayの区分を記録しています。
|
package/dist/build-info.json
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kubohiroya/turbowarp-extension-data-editor",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"repository": "https://github.com/kubohiroya/turbowarp-extension-data-editor",
|
|
5
|
-
"revision": "
|
|
5
|
+
"revision": "485f0e550e622235624b5e9176820161aeeba252",
|
|
6
|
+
"sourceState": "working-tree",
|
|
7
|
+
"sourceSha256": "175674eaaffce60dd9ebcf4c31f0f02a3bbd9dc6b8659ab3101ea6d8bceeea2f",
|
|
8
|
+
"capabilities": {
|
|
9
|
+
"preparedSource": 1
|
|
10
|
+
}
|
|
6
11
|
}
|
package/dist/descendants.js
CHANGED
|
@@ -143,9 +143,15 @@ export class DescendantSession {
|
|
|
143
143
|
__classPrivateFieldGet(this, _DescendantSession_instances, "m", _DescendantSession_notify).call(this);
|
|
144
144
|
try {
|
|
145
145
|
__classPrivateFieldGet(this, _DescendantSession_instances, "m", _DescendantSession_assertSaving).call(this);
|
|
146
|
+
const prepared = new Map();
|
|
146
147
|
for (const record of __classPrivateFieldGet(this, _DescendantSession_records, "f").values()) {
|
|
147
148
|
try {
|
|
148
|
-
const
|
|
149
|
+
const result = isPreparedEditor(record.definition)
|
|
150
|
+
? capturePreparedSave(record.definition, cloneJson(record.draft), record.snapshot) : undefined;
|
|
151
|
+
if (result)
|
|
152
|
+
prepared.set(record.snapshot.blockId, result);
|
|
153
|
+
const issues = result ? (result.kind === 'invalid' ? result.issues : [])
|
|
154
|
+
: await abortable(record.definition.validate(cloneJson(record.draft), record.snapshot), __classPrivateFieldGet(this, _DescendantSession_abort, "f").signal);
|
|
149
155
|
__classPrivateFieldGet(this, _DescendantSession_instances, "m", _DescendantSession_assertSaving).call(this);
|
|
150
156
|
record.issues = copyIssues(issues);
|
|
151
157
|
}
|
|
@@ -161,7 +167,9 @@ export class DescendantSession {
|
|
|
161
167
|
const destinations = new Set();
|
|
162
168
|
for (const record of __classPrivateFieldGet(this, _DescendantSession_records, "f").values()) {
|
|
163
169
|
try {
|
|
164
|
-
const
|
|
170
|
+
const result = prepared.get(record.snapshot.blockId);
|
|
171
|
+
const patch = (result === null || result === void 0 ? void 0 : result.kind) === 'ready' ? result.changes
|
|
172
|
+
: record.definition.createPatch(cloneJson(record.draft), record.snapshot);
|
|
165
173
|
__classPrivateFieldGet(this, _DescendantSession_instances, "m", _DescendantSession_assertSaving).call(this);
|
|
166
174
|
if (!Array.isArray(patch))
|
|
167
175
|
throw new TypeError('createPatch() must return an array.');
|
|
@@ -334,3 +342,4 @@ _DescendantSession_snapshot = new WeakMap(), _DescendantSession_host = new WeakM
|
|
|
334
342
|
__classPrivateFieldGet(this, _DescendantSession_detach, "f").clear();
|
|
335
343
|
__classPrivateFieldGet(this, _DescendantSession_listeners, "f").clear();
|
|
336
344
|
};
|
|
345
|
+
import { isPreparedEditor, capturePreparedSave } from './prepared-save.js';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { FixedBlockSnapshot, FixedInputBranchCase, FixedInputBranchRule, FixedInputBranchState, FixedInputDeclaration, FixedInputDependency } from './fixed-input-types.js';
|
|
2
|
+
import type { ArgumentSlot, LiteralArgumentChange, ValidationIssue } from './types.js';
|
|
3
|
+
/** Capture finite JSON policy once; callbacks and getters are never part of host authority. */
|
|
4
|
+
export declare function captureFixedBranchRules(value: unknown, dependencies: readonly FixedInputDependency[], readOnlyArguments: readonly string[]): readonly FixedInputBranchRule[];
|
|
5
|
+
export interface ResolvedFixedBranch extends FixedInputBranchState {
|
|
6
|
+
readonly rule: FixedInputBranchRule;
|
|
7
|
+
readonly selected?: FixedInputBranchCase;
|
|
8
|
+
}
|
|
9
|
+
/** Resolve only captured literal metadata. Reporters and unavailable fields are never evaluated. */
|
|
10
|
+
export declare function resolveFixedBranches(declaration: FixedInputDeclaration, snapshot: FixedBlockSnapshot, arguments_?: Readonly<Record<string, ArgumentSlot>>): readonly ResolvedFixedBranch[];
|
|
11
|
+
/** Shared session/host gate. Schema and owner validation remain the prepared factory's responsibility. */
|
|
12
|
+
export declare function validateFixedBranchChanges(declaration: FixedInputDeclaration, snapshot: FixedBlockSnapshot, changes: readonly LiteralArgumentChange[]): readonly ValidationIssue[];
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { snapshotJson } from './schema-json.js';
|
|
2
|
+
const fail = () => { throw new TypeError('Invalid fixed branch declaration.'); };
|
|
3
|
+
const names = (value) => {
|
|
4
|
+
if (!Array.isArray(value) || !value.length || value.some(item => typeof item !== 'string' || !item) ||
|
|
5
|
+
new Set(value).size !== value.length)
|
|
6
|
+
return fail();
|
|
7
|
+
return Object.freeze(value);
|
|
8
|
+
};
|
|
9
|
+
const object = (value) => {
|
|
10
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
11
|
+
return fail();
|
|
12
|
+
return value;
|
|
13
|
+
};
|
|
14
|
+
const normalize = (value, rule) => {
|
|
15
|
+
var _a, _b;
|
|
16
|
+
const text = ((_a = rule.normalize) === null || _a === void 0 ? void 0 : _a.trim) ? value.trim() : value;
|
|
17
|
+
return ((_b = rule.normalize) === null || _b === void 0 ? void 0 : _b.lowercase) ? text.toLowerCase() : text;
|
|
18
|
+
};
|
|
19
|
+
/** Capture finite JSON policy once; callbacks and getters are never part of host authority. */
|
|
20
|
+
export function captureFixedBranchRules(value, dependencies, readOnlyArguments) {
|
|
21
|
+
if (value === undefined)
|
|
22
|
+
return Object.freeze([]);
|
|
23
|
+
const captured = snapshotJson(value);
|
|
24
|
+
if (!Array.isArray(captured))
|
|
25
|
+
return fail();
|
|
26
|
+
const selectors = new Set();
|
|
27
|
+
const occupied = new Set();
|
|
28
|
+
const result = [];
|
|
29
|
+
for (const item of captured) {
|
|
30
|
+
const rule = object(item);
|
|
31
|
+
if (typeof rule.selector !== 'string' || selectors.has(rule.selector))
|
|
32
|
+
return fail();
|
|
33
|
+
const group = dependencies.find(dependency => dependency.selector === rule.selector);
|
|
34
|
+
if (!group)
|
|
35
|
+
return fail();
|
|
36
|
+
for (const argument of [group.selector, ...group.dependents]) {
|
|
37
|
+
if (occupied.has(argument) || dependencies.some(other => other !== group &&
|
|
38
|
+
(other.selector === argument || other.dependents.includes(argument))))
|
|
39
|
+
return fail();
|
|
40
|
+
occupied.add(argument);
|
|
41
|
+
}
|
|
42
|
+
selectors.add(rule.selector);
|
|
43
|
+
let normalization;
|
|
44
|
+
if (rule.normalize !== undefined) {
|
|
45
|
+
const input = object(rule.normalize);
|
|
46
|
+
if (Object.keys(input).some(key => key !== 'trim' && key !== 'lowercase') ||
|
|
47
|
+
Object.values(input).some(value => typeof value !== 'boolean'))
|
|
48
|
+
return fail();
|
|
49
|
+
normalization = Object.freeze({ ...input });
|
|
50
|
+
}
|
|
51
|
+
const shell = { selector: rule.selector, cases: [], ...(normalization ? { normalize: normalization } : {}) };
|
|
52
|
+
if (!Array.isArray(rule.cases) || !rule.cases.length)
|
|
53
|
+
return fail();
|
|
54
|
+
const values = new Set();
|
|
55
|
+
const cases = [];
|
|
56
|
+
for (const input of rule.cases) {
|
|
57
|
+
const branch = object(input);
|
|
58
|
+
const caseValues = names(branch.values);
|
|
59
|
+
const normalized = caseValues.map(value => normalize(value, shell));
|
|
60
|
+
if (normalized.some(value => !value || values.has(value)))
|
|
61
|
+
return fail();
|
|
62
|
+
for (const value of normalized)
|
|
63
|
+
values.add(value);
|
|
64
|
+
if (new Set(normalized).size !== normalized.length)
|
|
65
|
+
return fail();
|
|
66
|
+
if (!Array.isArray(branch.activeDependents))
|
|
67
|
+
return fail();
|
|
68
|
+
const active = branch.activeDependents.length ? names(branch.activeDependents) : Object.freeze([]);
|
|
69
|
+
if (active.some(argument => !group.dependents.includes(argument)))
|
|
70
|
+
return fail();
|
|
71
|
+
const requirements = [];
|
|
72
|
+
if (branch.requireOnSelectorChange !== undefined) {
|
|
73
|
+
if (!Array.isArray(branch.requireOnSelectorChange))
|
|
74
|
+
return fail();
|
|
75
|
+
const required = new Set();
|
|
76
|
+
for (const input of branch.requireOnSelectorChange) {
|
|
77
|
+
const requirement = object(input);
|
|
78
|
+
if (typeof requirement.argument !== 'string' || !readOnlyArguments.includes(requirement.argument) ||
|
|
79
|
+
required.has(requirement.argument) || (requirement.rule !== 'literal' && requirement.rule !== 'nonempty-string'))
|
|
80
|
+
return fail();
|
|
81
|
+
required.add(requirement.argument);
|
|
82
|
+
requirements.push(Object.freeze({ argument: requirement.argument, rule: requirement.rule }));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
cases.push(Object.freeze({ values: caseValues, activeDependents: active,
|
|
86
|
+
...(branch.requireOnSelectorChange === undefined ? {} : { requireOnSelectorChange: Object.freeze(requirements) }) }));
|
|
87
|
+
}
|
|
88
|
+
result.push(Object.freeze({ ...shell, cases: Object.freeze(cases) }));
|
|
89
|
+
}
|
|
90
|
+
return Object.freeze(result);
|
|
91
|
+
}
|
|
92
|
+
/** Resolve only captured literal metadata. Reporters and unavailable fields are never evaluated. */
|
|
93
|
+
export function resolveFixedBranches(declaration, snapshot, arguments_ = snapshot.arguments) {
|
|
94
|
+
var _a;
|
|
95
|
+
return Object.freeze(((_a = declaration.branchRules) !== null && _a !== void 0 ? _a : []).map(rule => {
|
|
96
|
+
const metadata = snapshot.fixedContext.inputs[rule.selector];
|
|
97
|
+
const slot = arguments_[rule.selector];
|
|
98
|
+
let status;
|
|
99
|
+
if (!metadata || metadata.source === 'missing')
|
|
100
|
+
status = 'missing';
|
|
101
|
+
else if (metadata.source === 'reporter' || (slot === null || slot === void 0 ? void 0 : slot.kind) === 'reporter')
|
|
102
|
+
status = 'reporter';
|
|
103
|
+
else if ((metadata.source !== 'field' && metadata.source !== 'shadow') || metadata.role !== 'editable')
|
|
104
|
+
status = 'unsupported';
|
|
105
|
+
else if ((slot === null || slot === void 0 ? void 0 : slot.kind) !== 'literal' || typeof slot.value !== 'string')
|
|
106
|
+
status = 'unknown';
|
|
107
|
+
else {
|
|
108
|
+
const value = normalize(slot.value, rule);
|
|
109
|
+
const selected = rule.cases.find(branch => branch.values.some(item => normalize(item, rule) === value));
|
|
110
|
+
if (selected)
|
|
111
|
+
return Object.freeze({ selector: rule.selector, rule, selected,
|
|
112
|
+
status: slot.value === selected.values[0] ? 'known' : 'alias', value: selected.values[0] });
|
|
113
|
+
status = 'unknown';
|
|
114
|
+
}
|
|
115
|
+
return Object.freeze({ selector: rule.selector, rule, status });
|
|
116
|
+
}));
|
|
117
|
+
}
|
|
118
|
+
const writableLiteral = (snapshot, arguments_, name) => {
|
|
119
|
+
var _a;
|
|
120
|
+
const metadata = snapshot.fixedContext.inputs[name];
|
|
121
|
+
return ((_a = arguments_[name]) === null || _a === void 0 ? void 0 : _a.kind) === 'literal' && (metadata === null || metadata === void 0 ? void 0 : metadata.role) === 'editable' &&
|
|
122
|
+
(metadata.source === 'field' || metadata.source === 'shadow');
|
|
123
|
+
};
|
|
124
|
+
const diagnostic = (declaration, code, argument) => Object.freeze({
|
|
125
|
+
message: `Fixed branch validation failed: ${code}; contract=${JSON.stringify(declaration.contractId)}.`,
|
|
126
|
+
path: `/${argument.replace(/~/g, '~0').replace(/\//g, '~1')}`
|
|
127
|
+
});
|
|
128
|
+
/** Shared session/host gate. Schema and owner validation remain the prepared factory's responsibility. */
|
|
129
|
+
export function validateFixedBranchChanges(declaration, snapshot, changes) {
|
|
130
|
+
var _a;
|
|
131
|
+
const issues = [];
|
|
132
|
+
const finalArguments = { ...snapshot.arguments };
|
|
133
|
+
const changed = new Set();
|
|
134
|
+
const seen = new Set();
|
|
135
|
+
for (const change of changes) {
|
|
136
|
+
if (seen.has(change.argument) || !['string', 'number', 'boolean'].includes(typeof change.value) || !declaration.editableArguments.includes(change.argument) ||
|
|
137
|
+
!writableLiteral(snapshot, snapshot.arguments, change.argument)) {
|
|
138
|
+
issues.push(diagnostic(declaration, 'disallowed-change', change.argument));
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
seen.add(change.argument);
|
|
142
|
+
const original = snapshot.arguments[change.argument];
|
|
143
|
+
if ((original === null || original === void 0 ? void 0 : original.kind) === 'literal' && Object.is(original.value, change.value))
|
|
144
|
+
continue;
|
|
145
|
+
changed.add(change.argument);
|
|
146
|
+
Object.defineProperty(finalArguments, change.argument, { value: { kind: 'literal', value: change.value }, enumerable: true, configurable: true });
|
|
147
|
+
}
|
|
148
|
+
const resolved = resolveFixedBranches(declaration, snapshot, finalArguments);
|
|
149
|
+
for (const group of declaration.dependencies) {
|
|
150
|
+
if (![group.selector, ...group.dependents].some(name => changed.has(name)))
|
|
151
|
+
continue;
|
|
152
|
+
const branch = resolved.find(item => item.selector === group.selector);
|
|
153
|
+
if (!(branch === null || branch === void 0 ? void 0 : branch.selected)) {
|
|
154
|
+
issues.push(diagnostic(declaration, branch ? 'selector-unavailable' : 'fixed-dependency-validation-unavailable', group.selector));
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
for (const argument of group.dependents) {
|
|
158
|
+
if (changed.has(argument) && !branch.selected.activeDependents.includes(argument)) {
|
|
159
|
+
issues.push(diagnostic(declaration, 'inactive-change', argument));
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (!changed.has(group.selector))
|
|
163
|
+
continue;
|
|
164
|
+
for (const argument of branch.selected.activeDependents) {
|
|
165
|
+
if (!writableLiteral(snapshot, finalArguments, argument))
|
|
166
|
+
issues.push(diagnostic(declaration, 'dependency-unverifiable', argument));
|
|
167
|
+
}
|
|
168
|
+
for (const requirement of (_a = branch.selected.requireOnSelectorChange) !== null && _a !== void 0 ? _a : []) {
|
|
169
|
+
const metadata = snapshot.fixedContext.inputs[requirement.argument];
|
|
170
|
+
const slot = finalArguments[requirement.argument];
|
|
171
|
+
if (!metadata || metadata.role !== 'readonly' || (metadata.source !== 'field' && metadata.source !== 'shadow') || (slot === null || slot === void 0 ? void 0 : slot.kind) !== 'literal') {
|
|
172
|
+
issues.push(diagnostic(declaration, 'dependency-unverifiable', requirement.argument));
|
|
173
|
+
}
|
|
174
|
+
else if (requirement.rule === 'nonempty-string' && (typeof slot.value !== 'string' || !slot.value.trim())) {
|
|
175
|
+
issues.push(diagnostic(declaration, 'dependency-invalid', requirement.argument));
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return Object.freeze(issues);
|
|
180
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { FixedBlockSnapshot, FixedInputBranchRule, FixedInputControl, FixedInputDeclaration, FixedInputEditorDefinition, FixedPreparedSave } from './fixed-input-types.js';
|
|
2
|
+
import type { SchemaBundle, SchemaValidationContext } from './schema-types.js';
|
|
3
|
+
import type { EditorDefinition, JsonObject, JsonValue, LiteralValue, ValidationIssue } from './types.js';
|
|
4
|
+
export interface FixedInputBinding<C = null> {
|
|
5
|
+
readonly argument: string;
|
|
6
|
+
readonly schemaBundle: SchemaBundle;
|
|
7
|
+
readonly control?: FixedInputControl;
|
|
8
|
+
/** Produce a JSON editing projection. Throw to retain the raw literal as readonly. */
|
|
9
|
+
read(value: LiteralValue): JsonValue;
|
|
10
|
+
/** Construct the exact literal candidate once. A numeric buffer supplies its validated string token, which must be returned unchanged. Neither this nor validation may be asynchronous. */
|
|
11
|
+
encode(value: JsonValue, context: SchemaValidationContext<C>): LiteralValue;
|
|
12
|
+
/** Check the exact candidate. Messages from this callback are never rendered verbatim. */
|
|
13
|
+
validateCandidate(candidate: LiteralValue, context: SchemaValidationContext<C>, snapshot: FixedBlockSnapshot): readonly ValidationIssue[];
|
|
14
|
+
}
|
|
15
|
+
export interface FixedInputBranchPolicy<C = null> extends Omit<FixedInputBranchRule, 'cases'> {
|
|
16
|
+
readonly cases: readonly (FixedInputBranchRule['cases'][number] & {
|
|
17
|
+
readonly bindings?: readonly FixedInputBinding<C>[];
|
|
18
|
+
})[];
|
|
19
|
+
}
|
|
20
|
+
export interface FixedInputEditorOptions<C = null> {
|
|
21
|
+
readonly apiVersion: 1;
|
|
22
|
+
readonly extensionId: string;
|
|
23
|
+
readonly editorId: string;
|
|
24
|
+
readonly opcodes: readonly string[];
|
|
25
|
+
readonly contractId: string;
|
|
26
|
+
readonly contractRevision: string;
|
|
27
|
+
readonly readOnlyArguments?: readonly string[];
|
|
28
|
+
readonly dependencies?: FixedInputDeclaration['dependencies'];
|
|
29
|
+
readonly branchPolicies?: readonly FixedInputBranchPolicy<C>[];
|
|
30
|
+
readonly bindings: readonly FixedInputBinding<C>[];
|
|
31
|
+
readonly validationContext: () => SchemaValidationContext<C>;
|
|
32
|
+
}
|
|
33
|
+
/** Capture exactly the callbacks and declaration values subsequently registered. */
|
|
34
|
+
export declare function captureFixedInputEditorDefinition<C = null>(value: FixedInputEditorDefinition<C>): FixedInputEditorDefinition<C>;
|
|
35
|
+
export declare function isFixedInputEditor(definition: EditorDefinition): definition is FixedInputEditorDefinition<unknown>;
|
|
36
|
+
/** Capture a synchronous callback result without invoking getters, iterators, or toJSON. */
|
|
37
|
+
export declare function captureFixedPreparedSave<C = null>(definition: FixedInputEditorDefinition<C>, draft: JsonObject, snapshot: FixedBlockSnapshot): FixedPreparedSave<C>;
|
|
38
|
+
/** Fixed inputs are additive: legacy callbacks cannot accidentally save their drafts. */
|
|
39
|
+
export declare function createFixedInputEditor<C = null>(options: FixedInputEditorOptions<C>): FixedInputEditorDefinition<C>;
|