@kubohiroya/turbowarp-extension-data-editor 0.0.0-stage → 0.2.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.
Files changed (133) hide show
  1. package/README.md +151 -2
  2. package/dist/build-info.json +11 -0
  3. package/dist/codec.d.ts +6 -0
  4. package/dist/codec.js +57 -0
  5. package/dist/data.d.ts +7 -0
  6. package/dist/data.js +76 -0
  7. package/dist/descendant-types.d.ts +89 -0
  8. package/dist/descendant-types.js +22 -0
  9. package/dist/descendants.d.ts +21 -0
  10. package/dist/descendants.js +345 -0
  11. package/dist/field-errors.d.ts +2 -0
  12. package/dist/field-errors.js +91 -0
  13. package/dist/index.d.ts +6 -0
  14. package/dist/index.js +5 -0
  15. package/dist/json-value.d.ts +4 -0
  16. package/dist/json-value.js +12 -0
  17. package/dist/prepared-save.d.ts +5 -0
  18. package/dist/prepared-save.js +47 -0
  19. package/dist/prepared-types.d.ts +13 -0
  20. package/dist/prepared-types.js +1 -0
  21. package/dist/react.css +183 -0
  22. package/dist/react.d.ts +79 -0
  23. package/dist/react.js +233 -0
  24. package/dist/registry.d.ts +13 -0
  25. package/dist/registry.js +116 -0
  26. package/dist/rjsf.d.ts +57 -0
  27. package/dist/rjsf.js +242 -0
  28. package/dist/schema-compose.d.ts +6 -0
  29. package/dist/schema-compose.js +103 -0
  30. package/dist/schema-engine.d.ts +18 -0
  31. package/dist/schema-engine.js +298 -0
  32. package/dist/schema-json.d.ts +10 -0
  33. package/dist/schema-json.js +68 -0
  34. package/dist/schema-types.d.ts +161 -0
  35. package/dist/schema-types.js +2 -0
  36. package/dist/schema-view.d.ts +7 -0
  37. package/dist/schema-view.js +102 -0
  38. package/dist/schema.d.ts +8 -0
  39. package/dist/schema.js +86 -0
  40. package/dist/session.d.ts +18 -0
  41. package/dist/session.js +200 -0
  42. package/dist/source-document.d.ts +3 -0
  43. package/dist/source-document.js +415 -0
  44. package/dist/source-editor.d.ts +13 -0
  45. package/dist/source-editor.js +235 -0
  46. package/dist/source-schema-hints.d.ts +3 -0
  47. package/dist/source-schema-hints.js +13 -0
  48. package/dist/source-types.d.ts +47 -0
  49. package/dist/source-types.js +1 -0
  50. package/dist/source-view.d.ts +3 -0
  51. package/dist/source-view.js +318 -0
  52. package/dist/subtree.d.ts +10 -0
  53. package/dist/subtree.js +100 -0
  54. package/dist/turbowarp/appearance.d.ts +7 -0
  55. package/dist/turbowarp/appearance.js +46 -0
  56. package/dist/turbowarp/bridge.d.ts +21 -0
  57. package/dist/turbowarp/bridge.js +168 -0
  58. package/dist/turbowarp/error-session.d.ts +18 -0
  59. package/dist/turbowarp/error-session.js +30 -0
  60. package/dist/turbowarp/host.d.ts +24 -0
  61. package/dist/turbowarp/host.js +289 -0
  62. package/dist/turbowarp/icon.d.ts +7 -0
  63. package/dist/turbowarp/icon.js +76 -0
  64. package/dist/turbowarp/subtree.d.ts +10 -0
  65. package/dist/turbowarp/subtree.js +59 -0
  66. package/dist/turbowarp.d.ts +66 -0
  67. package/dist/turbowarp.js +40 -0
  68. package/dist/types.d.ts +94 -0
  69. package/dist/types.js +7 -0
  70. package/dist/view.d.ts +7 -0
  71. package/dist/view.js +85 -0
  72. package/dist/webpack4/index.js +30 -0
  73. package/dist/webpack4/index.js.map +7 -0
  74. package/dist/webpack4/metafile.json +17025 -0
  75. package/dist/webpack4/react.js +373 -0
  76. package/dist/webpack4/react.js.map +7 -0
  77. package/dist/webpack4/rjsf.js +21364 -0
  78. package/dist/webpack4/rjsf.js.map +7 -0
  79. package/dist/webpack4/schema.js +17 -0
  80. package/dist/webpack4/schema.js.map +7 -0
  81. package/dist/webpack4/shared-BKTRZUZH.js +42 -0
  82. package/dist/webpack4/shared-BKTRZUZH.js.map +7 -0
  83. package/dist/webpack4/shared-CQB3NG2A.js +1 -0
  84. package/dist/webpack4/shared-CQB3NG2A.js.map +7 -0
  85. package/dist/webpack4/shared-KQOUMWJ4.js +8490 -0
  86. package/dist/webpack4/shared-KQOUMWJ4.js.map +7 -0
  87. package/dist/webpack4/shared-MRPGBJIH.js +75 -0
  88. package/dist/webpack4/shared-MRPGBJIH.js.map +7 -0
  89. package/dist/webpack4/shared-N6YQE2X6.js +1029 -0
  90. package/dist/webpack4/shared-N6YQE2X6.js.map +7 -0
  91. package/dist/webpack4/shared-RN7GOT65.js +43 -0
  92. package/dist/webpack4/shared-RN7GOT65.js.map +7 -0
  93. package/dist/webpack4/turbowarp.js +705 -0
  94. package/dist/webpack4/turbowarp.js.map +7 -0
  95. package/docs/README.md +27 -0
  96. package/docs/api.md +134 -0
  97. package/docs/block-editor-acceptance.md +104 -0
  98. package/docs/block-editor-descendants.md +133 -0
  99. package/docs/block-editor-e2e.md +205 -0
  100. package/docs/block-editor-sb3-validation.md +19 -0
  101. package/docs/block-editor.md +131 -0
  102. package/docs/ci-success-gate.md +84 -0
  103. package/docs/descendants.md +96 -0
  104. package/docs/editor/issue-12-public-validation/README.md +61 -0
  105. package/docs/editor/issue-12-public-validation/dark-invalid.png +0 -0
  106. package/docs/editor/issue-12-public-validation/dark-styles.json +173 -0
  107. package/docs/editor/issue-12-public-validation/dark-valid.png +0 -0
  108. package/docs/editor/issue-12-public-validation/deployment-target.json +5 -0
  109. package/docs/editor/issue-12-public-validation/deployment-wait.json +7 -0
  110. package/docs/editor/issue-12-public-validation/light-invalid.png +0 -0
  111. package/docs/editor/issue-12-public-validation/light-styles.json +173 -0
  112. package/docs/editor/issue-12-public-validation/light-valid.png +0 -0
  113. package/docs/editor/issue-12-public-validation/provenance.json +88 -0
  114. package/docs/editor/issue-12-public-validation/results.json +677 -0
  115. package/docs/editor/issue-12-public-validation/summary.json +206 -0
  116. package/docs/github-pages.md +64 -0
  117. package/docs/package-distribution.md +36 -0
  118. package/docs/schema-stage1-browser.json +10 -0
  119. package/docs/schema-stage1-contracts.json +242 -0
  120. package/docs/schema-stage1-dependencies.json +202 -0
  121. package/docs/schema-stage1-git-artifact-20261018.ja.md +17 -0
  122. package/docs/schema-stage1-profile-compat.json +30 -0
  123. package/docs/schema-stage1-review-fixes-20261018.ja.md +36 -0
  124. package/docs/schema-stage1-verification.json +541 -0
  125. package/docs/schema-stage1.ja.md +80 -0
  126. package/docs/schema.md +43 -0
  127. package/docs/source-forms.ja.md +19 -0
  128. package/docs/stage-2-publication.ja.md +29 -0
  129. package/docs/template-fixture-separation.ja.md +43 -0
  130. package/docs/turbowarp-adapter.md +64 -0
  131. package/docs/webpack4.md +16 -0
  132. package/package.json +99 -4
  133. package/webpack4.cjs +20 -0
package/README.md CHANGED
@@ -1,3 +1,152 @@
1
- # Temporary Holding Version
1
+ # turbowarp-extension-data-editor
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ TurboWarp拡張が扱う構造化データを、フォームや独自UIで編集するための実験的な共通API v1です。
4
+ [共通基盤 #1](https://github.com/kubohiroya/turbowarp-extension-data-editor/issues/1) と
5
+ [GUI 接続 #1](https://github.com/kubohiroya/scratch-gui/issues/1) に対応します。
6
+
7
+ 日本語の利用手順・API・GUI接続・公開・検証記録は[ドキュメント一覧](docs/README.md)へ集約しています。
8
+
9
+ [公開エディター・日英案内](https://kubohiroya.github.io/turbowarp-extension-data-editor/) と
10
+ [公開・検証の運用Issue #26](https://github.com/kubohiroya/turbowarp-extension-data-editor/issues/26) も本リポジトリが所有します。
11
+
12
+ ## 標準の JSON Schema エディター
13
+
14
+ JSON/YAML を格納する文字列引数と JSON Schema を宣言すると、react-jsonschema-form がフォームを生成します。
15
+ GUI の登録 API は `schema` を検出して標準エディターを選びます。本文 UI の実装は不要です。
16
+
17
+ ```js
18
+ const unregister = Scratch.gui.blockEditors.register({
19
+ apiVersion: 1,
20
+ extensionId: 'story',
21
+ editorId: 'properties',
22
+ opcodes: ['story_scene'],
23
+ argument: 'PROPERTIES',
24
+ format: 'yaml', // Omit for JSON compatibility.
25
+ schema: {
26
+ type: 'object',
27
+ properties: {
28
+ label: {type: 'string', minLength: 1, title: '名前'},
29
+ duration: {type: 'number', minimum: 1, maximum: 60, title: '秒数'}
30
+ },
31
+ required: ['label', 'duration'],
32
+ additionalProperties: false
33
+ },
34
+ uiSchema: {'ui:order': ['label', 'duration']}
35
+ });
36
+ ```
37
+
38
+ `registerSchema()` も同じ標準エディターを明示的に登録できます。
39
+ 必須項目・型・範囲などの検証エラーがある間は保存ボタンを無効化し、保存時にも必ず再検証します。
40
+ 任意の非同期 `validateData(data, snapshot)` による業務検証は保存時に追加実行されます。
41
+ スキーマの検証は AJV 8、描画は React 16.14 に対応する RJSF 5.24.13 を使用します。
42
+ 対応の基本は JSON Schema draft-07 とローカル参照です。外部参照の取得は行わず、解決できない参照は登録時に拒否します。
43
+ `default` は下書きへ補完し、保存するまで元ブロックへ書き込みません。
44
+
45
+ `format: 'json' | 'yaml'` を指定できます。省略時は従来どおり JSON です。
46
+ YAML はホスト側の `yaml` 2.x で単一の YAML 1.2 core 文書を decode し、
47
+ JSON と同じ Schema で検証した後、指定形式で encode します。
48
+ 文字列キーの mapping、sequence、有限数値・文字列・真偽値・null が対象です。
49
+ 重複キー、非文字列キー、独自 tag、非有限数値、循環 alias、複数文書、YAML 1.1 は拒否し、元の引数は変更しません。
50
+ 非循環 alias は制限付きで展開します。merge key の特別な展開は行いません。
51
+ 変更なしの保存はコメント・空白を含む元文字列を保持し、commit しません。
52
+ 変更がある保存はデータの意味を保持して再出力しますが、コメント・引用符・anchor・キー順の保持は保証しません。
53
+ フォームで編集しない追加プロパティも、Schema が許可する限り保持します。
54
+ GUIのwebpack 4接続では[互換配布物と設定API](docs/webpack4.md)を使い、RJSF/YAMLの変換を本パッケージ側で行います。
55
+
56
+ ## API とトランザクション
57
+
58
+ ルートの `EditorRegistry` / `EditorSession` は React / Blockly に依存しません。
59
+ `@kubohiroya/turbowarp-extension-data-editor/rjsf` の `createSchemaEditor()` が標準フォームを提供します。
60
+ 通常 DOM や独自 React UI の `mount()` を持つ定義も登録できます。
61
+ `/rjsf` の `createBlockEditorService()` がSchema検出・登録/解除・通知を提供します。
62
+ `/react` の `BlockEditorDialog` は保存/キャンセル・エラー・階層UIを提供し、ホストのModal・翻訳・テーマを注入します。
63
+ CSSは `/react/styles.css` をホストのCSS Modules経由で読み込みます。
64
+
65
+ 開始時のスナップショットと独立した JSON の下書きを持ち、キャンセルでは変更を破棄します。
66
+ 保存は検証、引数の許可リスト、reporter 保護、競合確認を通し、ホストへ1回だけ変更を要求します。
67
+ GUI ホストは画面と VM を同期し、保存を Undo/Redo 各1回にまとめます。失敗時の復元もホストの責務です。
68
+ `session.reference` で対象の target / block を識別できます。本文の unmount は下書きとエラーを保持し、再マウントできます。
69
+ 解除済みビューからの更新を拒否し、ビューの購読を解放します。標準フォームのフィールド ID も各表示で分離します。
70
+ 詳しくは [API とホスト契約](docs/api.md) を参照してください。
71
+
72
+ ## TurboWarp 接続
73
+
74
+ `/turbowarp` の `attachTurboWarpBlockEditors()` にホストのScratchBlocks / VM / workspace、
75
+ 登録サービスと画面コールバックを渡すと、アイコン装飾・セッション起動・VM同期・一括保存を接続します。
76
+ `enabled` / `descendants` は省略時OFF、戻り値は `refresh()` / 冪等な `dispose()` です。
77
+ GUIには起動時フラグ・ライフサイクル・Modal/翻訳/テーマの接続を残します。
78
+ 詳細は [TurboWarp アダプターAPI](docs/turbowarp-adapter.md) を参照してください。
79
+
80
+ ## 子孫をまとめる階層セッション
81
+
82
+ [共通基盤 #2](https://github.com/kubohiroya/turbowarp-extension-data-editor/issues/2) の追加 API として、
83
+ `EditorRegistry.openDescendants()` と `DescendantSession` を提供します。
84
+ ホストが `descendant-editing-v1` capability と完全な subtree snapshot / 一括 commit を提供する場合だけ使用します。
85
+ 既存の API v1 の本文を再利用し、親子孫・複数枝の下書きと検証結果を保持して、全変更を1回でホストへ渡します。
86
+ 未登録の中間ノードを構造として保持し、非表示ノードも検証します。構造・値・登録の競合、上限超過、非同期処理中の無効化に対応します。
87
+
88
+ 上限は500ノード・深さ32。GUI は起動時固定・既定 OFF の `blockEditorDescendants` と基盤フラグで選択し、
89
+ フラグ OFF/未対応ホストでは既存の単一編集を使う設計です。
90
+ 詳細と業務非依存サンプルは [階層セッションの契約](docs/descendants.md) を参照してください。
91
+ 実 GUI の探索・階層モーダル・一括 Undo/Redo・`.sb3` 結合確認は scratch-gui #2 での接続が必要です。
92
+
93
+ ## ローカル検証
94
+
95
+ Node.js 22.18 以降と pnpm を使用します。
96
+
97
+ ```sh
98
+ pnpm install
99
+ pnpm typecheck
100
+ pnpm test
101
+ ```
102
+
103
+ `pnpm test` は型宣言を含むビルド後、コアと標準フォームのテストを実行します。
104
+ パッケージは ESM です。ブラウザには ES2022 の組み込み API が必要です。
105
+ 標準フォームの React / ReactDOM peer は 16.14 / 17 / 18。GUI は既存の 16.14 を共有します。
106
+ 初回の実験版は `@kubohiroya/turbowarp-extension-data-editor@0.1.0` です。固定SchemaのSource/Form編集を追加する今回の配布版は `0.2.0` です。
107
+
108
+ ```sh
109
+ npm install --save-exact @kubohiroya/turbowarp-extension-data-editor@0.2.0
110
+ ```
111
+
112
+ 配布物・改名時の互換性・公開手順は[パッケージ配布](docs/package-distribution.md)を参照してください。
113
+
114
+ ## GUI で試す
115
+
116
+ `scratch-gui` は本パッケージを検証済みのnpm完全version、またはGitの完全SHAで固定参照します。通常のGUIビルドに共通リポジトリの隣接配置は不要です。
117
+ [GUIの起動・制約・検証手順](docs/block-editor.md) に従ってください。
118
+ 既存の拡張アイコンをクリックすると、標準モーダルで下書きを編集できます。
119
+
120
+ 現在の GUI 適用範囲は、配置済みブロックの通常の text shadow に保存された JSON/YAML 文字列です。
121
+ reporter、メニュー、独自 validator 付きフィールドは書き換えません。
122
+ `.sb3` の実保存と標準 `project_url` 経路での再読込を確認済みです。OS のファイル選択による再読込、アイコンがないブロックへの追加表示、翻訳・全テーマは後続の確認項目です。
123
+
124
+ ## フラグとロールバック
125
+
126
+ GUI の `src/lib/tw-feature-flags.js` で起動時固定の `blockEditorDialogs` を読み取り、既定 OFF とします。
127
+ `?blockEditorDialogs=1` の場合だけ登録 API と UI 接続を初期化します。
128
+ 標準フォームは機能有効時の既定の描画方法であり、機能フラグ自体を既定 ON にしません。
129
+ フラグを外して再読込すれば従来の操作へ戻せます。
130
+ 確定値は既存引数へ保存し、UI・下書き・関数・revision をプロジェクトデータへ追加しません。
131
+
132
+ ## webpack 4 / E2E
133
+
134
+ [webpack 4配布物と設定API](docs/webpack4.md)、[GUI接続E2EとCIの実行手順](docs/block-editor-e2e.md)を参照。専用編集UIから互換ビルド・E2Eまで共通側で管理し、GUIには明示的なホスト接続とCI呼出しを残す。
135
+
136
+ ## Schemaの共通型と診断変換
137
+
138
+ `@kubohiroya/turbowarp-extension-data-editor/schema`はDOM・React・RJSFをロードせずに利用できる入口です。Schema bundle、正本・生成物・参照用の役割、処理段階別の診断・結果、同期owner検証の型を公開します。
139
+
140
+ `toValidationIssues()`は入力診断を既存の`ValidationIssue`へ変換し、JSON Pointerのpath・順序・件数を維持した凍結結果を返します。不正な診断やcompile/execution診断はTypeErrorで拒否します。既存RJSFのpathや保存時専用の非同期`validateData`の契約は変わりません。
141
+
142
+ `compileSchemaBundle()`はdraft-07/2020-12と同梱resourceからオフラインの同期validatorを生成します。Schemaと入力を変更せず、構造化診断を返します。[pure検証API](docs/schema.md)を参照してください。同期owner合成と明示bundleのRJSF経路も追加済みです。既存公開入口・webpack4/build-info・CSS buildを維持しています。
143
+
144
+ 同期owner検証には`/schema`の`validateSchemaInput`を利用できます。明示bundleフォームは`/rjsf`の`createSchemaBundleEditor` / `registerSchemaBundle`で登録します。context providerは呼出しごとに時刻・設定を渡します。表示上の対応範囲と既存APIの扱いは[Schema基盤](docs/schema.md)を参照してください。
145
+
146
+ 現在のowner横断検証は**14契約・8 package**です。[Template分離後の再検証手順](docs/template-fixture-separation.ja.md#current-verification)で、Templateを含まないisolation indexと2つの検証commandを確認してください。段階2では、この14本番契約と新規予定の9固定契約を区別して計画します。旧16契約・9 packageの結果と当時の開始条件は[段階1の履歴](docs/schema-stage1.ja.md)として保持します。
147
+
148
+ ## Optional editing examples
149
+
150
+ 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.
151
+
152
+ 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.
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "@kubohiroya/turbowarp-extension-data-editor",
3
+ "version": "0.2.0",
4
+ "repository": "https://github.com/kubohiroya/turbowarp-extension-data-editor",
5
+ "revision": "76fa1d50a3c188082ceef723ea018a9d8769ea6e",
6
+ "sourceState": "commit",
7
+ "sourceSha256": "912efc0438710958698527aa5c26a33b0b1c2da280f91d36b048f6f2389a48f0",
8
+ "capabilities": {
9
+ "preparedSource": 1
10
+ }
11
+ }
@@ -0,0 +1,6 @@
1
+ import type { JsonValue } from './types.js';
2
+ export type SourceFormat = 'json' | 'yaml';
3
+ export declare function decodeSource(source: string, format: SourceFormat): JsonValue;
4
+ export declare function encodeSource(data: JsonValue, format: SourceFormat): string;
5
+ /** Ignore object key order when deciding whether saving would change the data. */
6
+ export declare function sameData(left: JsonValue, right: JsonValue): boolean;
package/dist/codec.js ADDED
@@ -0,0 +1,57 @@
1
+ import { parseDocument, stringify } from 'yaml';
2
+ import { cloneJson } from './data.js';
3
+ /** Convert YAML mappings without coercing non-string keys into JSON property names. */
4
+ function jsonData(value, ancestors = new Set(), depth = 0) {
5
+ if (depth > 100)
6
+ throw new TypeError('YAML data exceeds the maximum depth.');
7
+ if (value === null || typeof value !== 'object')
8
+ return cloneJson(value);
9
+ if (ancestors.has(value))
10
+ throw new TypeError('YAML data must not contain cycles.');
11
+ ancestors.add(value);
12
+ try {
13
+ if (Array.isArray(value))
14
+ return value.map(child => jsonData(child, ancestors, depth + 1));
15
+ if (value instanceof Map) {
16
+ return Object.fromEntries([...value.entries()].map(([key, child]) => {
17
+ if (typeof key !== 'string')
18
+ throw new TypeError('YAML mapping keys must be strings.');
19
+ return [key, jsonData(child, ancestors, depth + 1)];
20
+ }));
21
+ }
22
+ throw new TypeError('Expected JSON-compatible YAML data.');
23
+ }
24
+ finally {
25
+ ancestors.delete(value);
26
+ }
27
+ }
28
+ export function decodeSource(source, format) {
29
+ if (format === 'json')
30
+ return cloneJson(JSON.parse(source));
31
+ const doc = parseDocument(source, { version: '1.2', schema: 'core', uniqueKeys: true });
32
+ const problems = [...doc.errors, ...doc.warnings];
33
+ if (problems.length)
34
+ throw new TypeError(problems.map(problem => problem.message).join('\n'));
35
+ if (doc.directives.yaml.version !== '1.2')
36
+ throw new TypeError('Only YAML 1.2 is supported.');
37
+ return jsonData(doc.toJS({ mapAsMap: true, maxAliasCount: 100 }));
38
+ }
39
+ export function encodeSource(data, format) {
40
+ const value = cloneJson(data);
41
+ return format === 'json' ? JSON.stringify(value) : stringify(value, { version: '1.2' });
42
+ }
43
+ /** Ignore object key order when deciding whether saving would change the data. */
44
+ export function sameData(left, right) {
45
+ if (left === right)
46
+ return true;
47
+ if (left === null || right === null || typeof left !== 'object' || typeof right !== 'object')
48
+ return false;
49
+ if (Array.isArray(left)) {
50
+ return Array.isArray(right) && left.length === right.length &&
51
+ left.every((value, index) => sameData(value, right[index]));
52
+ }
53
+ if (Array.isArray(right))
54
+ return false;
55
+ const keys = Object.keys(left);
56
+ return keys.length === Object.keys(right).length && keys.every(key => Object.hasOwn(right, key) && sameData(left[key], right[key]));
57
+ }
package/dist/data.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ import type { BlockReference, BlockSnapshot, JsonValue, LiteralValue } from './types.js';
2
+ export declare function isLiteral(value: unknown): value is LiteralValue;
3
+ /** Reject values whose meaning would change during JSON persistence. */
4
+ export declare function cloneJson<T extends JsonValue>(value: T): T;
5
+ export declare function captureReference(reference: BlockReference): BlockReference;
6
+ export declare function captureSnapshot(snapshot: BlockSnapshot, reference: BlockReference): BlockSnapshot;
7
+ export declare function sameSnapshot(left: BlockSnapshot, right: BlockSnapshot): boolean;
package/dist/data.js ADDED
@@ -0,0 +1,76 @@
1
+ export function isLiteral(value) {
2
+ return typeof value === 'string' || typeof value === 'boolean' ||
3
+ (typeof value === 'number' && Number.isFinite(value));
4
+ }
5
+ /** Reject values whose meaning would change during JSON persistence. */
6
+ export function cloneJson(value) {
7
+ const ancestors = new Set();
8
+ const visit = (item, depth) => {
9
+ if (depth > 100)
10
+ throw new TypeError('JSON data exceeds the maximum depth.');
11
+ if (item === null || isLiteral(item))
12
+ return item;
13
+ if (typeof item !== 'object')
14
+ throw new TypeError('Expected JSON-compatible data.');
15
+ if (ancestors.has(item))
16
+ throw new TypeError('JSON data must not contain cycles.');
17
+ ancestors.add(item);
18
+ try {
19
+ if (Array.isArray(item))
20
+ return Array.from(item, child => visit(child, depth + 1));
21
+ const prototype = Object.getPrototypeOf(item);
22
+ if (prototype !== Object.prototype && prototype !== null) {
23
+ throw new TypeError('JSON objects must be plain objects.');
24
+ }
25
+ if (Object.getOwnPropertySymbols(item).length > 0) {
26
+ throw new TypeError('JSON objects must not have symbol properties.');
27
+ }
28
+ return Object.fromEntries(Object.entries(item).map(([key, child]) => [key, visit(child, depth + 1)]));
29
+ }
30
+ finally {
31
+ ancestors.delete(item);
32
+ }
33
+ };
34
+ return visit(value, 0);
35
+ }
36
+ export function captureReference(reference) {
37
+ if (!reference || typeof reference.targetId !== 'string' || !reference.targetId ||
38
+ typeof reference.blockId !== 'string' || !reference.blockId) {
39
+ throw new TypeError('A target ID and block ID are required.');
40
+ }
41
+ return Object.freeze({ targetId: reference.targetId, blockId: reference.blockId });
42
+ }
43
+ export function captureSnapshot(snapshot, reference) {
44
+ if (snapshot.targetId !== reference.targetId || snapshot.blockId !== reference.blockId ||
45
+ typeof snapshot.opcode !== 'string' || !snapshot.opcode ||
46
+ typeof snapshot.revision !== 'string' || !snapshot.revision ||
47
+ !snapshot.arguments || typeof snapshot.arguments !== 'object' ||
48
+ Array.isArray(snapshot.arguments)) {
49
+ throw new TypeError('The host returned an invalid block snapshot.');
50
+ }
51
+ const entries = Object.entries(snapshot.arguments).map(([name, slot]) => {
52
+ if (!slot || (slot.kind !== 'reporter' && slot.kind !== 'literal') ||
53
+ (slot.kind === 'literal' && !isLiteral(slot.value))) {
54
+ throw new TypeError(`Invalid argument slot: ${name}`);
55
+ }
56
+ const copy = slot.kind === 'reporter'
57
+ ? Object.freeze({ kind: 'reporter' })
58
+ : Object.freeze({ kind: 'literal', value: slot.value });
59
+ return [name, copy];
60
+ });
61
+ return Object.freeze({
62
+ ...captureReference(reference), opcode: snapshot.opcode, revision: snapshot.revision,
63
+ arguments: Object.freeze(Object.fromEntries(entries))
64
+ });
65
+ }
66
+ export function sameSnapshot(left, right) {
67
+ const names = Object.keys(left.arguments);
68
+ return left.targetId === right.targetId && left.blockId === right.blockId &&
69
+ left.opcode === right.opcode && left.revision === right.revision &&
70
+ names.length === Object.keys(right.arguments).length && names.every(name => {
71
+ const a = left.arguments[name];
72
+ const b = Object.hasOwn(right.arguments, name) ? right.arguments[name] : undefined;
73
+ return b !== undefined && a.kind === b.kind &&
74
+ (a.kind === 'reporter' || (b.kind === 'literal' && a.value === b.value));
75
+ });
76
+ }
@@ -0,0 +1,89 @@
1
+ import type { BlockEditorHost, BlockReference, BlockSnapshot, EditorDescriptor, EditorViewContext, LiteralArgumentChange, SaveResult, SessionStatus, ValidationIssue } from './types.js';
2
+ export declare const DESCENDANT_EDITING_CAPABILITY: "descendant-editing-v1";
3
+ export declare const DEFAULT_DESCENDANT_LIMITS: Readonly<{
4
+ maxDepth: 32;
5
+ maxNodes: 500;
6
+ }>;
7
+ export interface DescendantLimits {
8
+ readonly maxDepth?: number;
9
+ readonly maxNodes?: number;
10
+ }
11
+ /** Ordered statement inputs; each list includes its internal next chain, never reporters. */
12
+ export interface StatementBranch {
13
+ readonly input: string;
14
+ readonly blockIds: readonly string[];
15
+ }
16
+ export interface SubtreeNodeSnapshot {
17
+ readonly block: BlockSnapshot;
18
+ readonly branches: readonly StatementBranch[];
19
+ }
20
+ export interface SubtreeSnapshot extends BlockReference {
21
+ /** Changes across project reloads and editing sessions, even when block IDs are reused. */
22
+ readonly generation: string;
23
+ /** The complete scope, including unregistered structural nodes. No truncation is allowed. */
24
+ readonly nodes: readonly SubtreeNodeSnapshot[];
25
+ }
26
+ export interface SubtreeArgumentChange extends LiteralArgumentChange {
27
+ readonly blockId: string;
28
+ }
29
+ export interface SubtreeCommitRequest {
30
+ readonly expected: SubtreeSnapshot;
31
+ readonly changes: readonly SubtreeArgumentChange[];
32
+ /** Check both this signal and expected at the actual write boundary. */
33
+ readonly signal: AbortSignal;
34
+ }
35
+ export interface DescendantEditorHost {
36
+ readonly capability: typeof DESCENDANT_EDITING_CAPABILITY;
37
+ readSubtree(reference: BlockReference): SubtreeSnapshot | null;
38
+ /** One atomic transaction and one Undo/Redo step; rejection leaves data and history unchanged. */
39
+ commitSubtree(request: SubtreeCommitRequest): void | Promise<void>;
40
+ /** Notify project/target invalidation promptly, including during pending validation/commit. */
41
+ subscribeInvalidation?(reference: BlockReference, listener: (reason?: unknown) => void): () => void;
42
+ }
43
+ export declare function supportsDescendantEditing(host: BlockEditorHost): host is BlockEditorHost & {
44
+ descendants: DescendantEditorHost;
45
+ };
46
+ export interface InputPathStep {
47
+ readonly blockId: string;
48
+ readonly input: string;
49
+ readonly index: number;
50
+ }
51
+ export interface DescendantNodeDescriptor {
52
+ readonly reference: BlockReference;
53
+ readonly opcode: string;
54
+ readonly inputPath: readonly InputPathStep[];
55
+ readonly editor?: EditorDescriptor;
56
+ }
57
+ /** Body-only access. Nodes have no independent save, cancel, dispose or host commit. */
58
+ export interface DescendantNodeEditor extends EditorViewContext {
59
+ readonly reference: BlockReference;
60
+ mount(container: HTMLElement): () => void;
61
+ }
62
+ export interface DescendantValidationIssue extends ValidationIssue {
63
+ readonly reference: BlockReference;
64
+ readonly inputPath: readonly InputPathStep[];
65
+ }
66
+ export type DescendantSessionStatus = SessionStatus | 'invalidated';
67
+ export interface DescendantSessionState {
68
+ readonly status: DescendantSessionStatus;
69
+ readonly canSave: boolean;
70
+ readonly issues: readonly DescendantValidationIssue[];
71
+ readonly error: unknown;
72
+ }
73
+ export type DescendantSaveResult = Exclude<SaveResult, {
74
+ status: 'invalid';
75
+ }> | {
76
+ readonly status: 'invalid';
77
+ readonly issues: readonly DescendantValidationIssue[];
78
+ } | {
79
+ readonly status: 'invalidated';
80
+ };
81
+ export declare class BlockEditorStructureError extends Error {
82
+ readonly code: 'invalid-structure' | 'cycle' | 'limit';
83
+ constructor(message: string, code: 'invalid-structure' | 'cycle' | 'limit');
84
+ }
85
+ export declare class DescendantNodeError extends Error {
86
+ readonly reference: BlockReference;
87
+ readonly inputPath: readonly InputPathStep[];
88
+ constructor(reference: BlockReference, inputPath: readonly InputPathStep[], cause: unknown);
89
+ }
@@ -0,0 +1,22 @@
1
+ export const DESCENDANT_EDITING_CAPABILITY = 'descendant-editing-v1';
2
+ export const DEFAULT_DESCENDANT_LIMITS = Object.freeze({ maxDepth: 32, maxNodes: 500 });
3
+ export function supportsDescendantEditing(host) {
4
+ const port = host.descendants;
5
+ return (port === null || port === void 0 ? void 0 : port.capability) === DESCENDANT_EDITING_CAPABILITY &&
6
+ typeof port.readSubtree === 'function' && typeof port.commitSubtree === 'function';
7
+ }
8
+ export class BlockEditorStructureError extends Error {
9
+ constructor(message, code) {
10
+ super(message);
11
+ this.code = code;
12
+ this.name = 'BlockEditorStructureError';
13
+ }
14
+ }
15
+ export class DescendantNodeError extends Error {
16
+ constructor(reference, inputPath, cause) {
17
+ super(`Editor callback failed for block ${reference.blockId}.`, { cause });
18
+ this.reference = reference;
19
+ this.inputPath = inputPath;
20
+ this.name = 'DescendantNodeError';
21
+ }
22
+ }
@@ -0,0 +1,21 @@
1
+ import type { CapturedSubtree } from './subtree.js';
2
+ import type { DescendantEditorHost, DescendantLimits, DescendantNodeDescriptor, DescendantNodeEditor, DescendantSaveResult, DescendantSessionState } from './descendant-types.js';
3
+ import type { BlockReference, EditorDefinition } from './types.js';
4
+ /** Aggregate draft/validation only. The negotiated host owns the atomic write. */
5
+ export declare class DescendantSession {
6
+ #private;
7
+ /** Use EditorRegistry.openDescendants() to negotiate and capture the scope. */
8
+ constructor(captured: CapturedSubtree, host: DescendantEditorHost, lookup: (opcode: string) => EditorDefinition | undefined, subscribeRegistry: (listener: () => void) => () => void, limits: Required<DescendantLimits>);
9
+ get reference(): BlockReference;
10
+ get generation(): string;
11
+ getNodes(): readonly DescendantNodeDescriptor[];
12
+ getEditor(blockId: string): DescendantNodeEditor | undefined;
13
+ getState(): DescendantSessionState;
14
+ subscribe(listener: () => void): () => void;
15
+ save(): Promise<DescendantSaveResult>;
16
+ /** Validation may be cancelled; an already dispatched commit must finish atomically. */
17
+ cancel(): boolean;
18
+ dispose(): boolean;
19
+ /** The host must observe the abort signal before any deferred write. */
20
+ invalidate(reason?: unknown): boolean;
21
+ }