@kubohiroya/turbowarp-extension-data-editor 0.1.0 → 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 (91) hide show
  1. package/README.md +24 -3
  2. package/dist/build-info.json +7 -2
  3. package/dist/descendants.js +11 -2
  4. package/dist/index.d.ts +1 -0
  5. package/dist/json-value.d.ts +4 -0
  6. package/dist/json-value.js +12 -0
  7. package/dist/prepared-save.d.ts +5 -0
  8. package/dist/prepared-save.js +47 -0
  9. package/dist/prepared-types.d.ts +13 -0
  10. package/dist/prepared-types.js +1 -0
  11. package/dist/registry.d.ts +2 -0
  12. package/dist/registry.js +57 -45
  13. package/dist/rjsf.d.ts +25 -1
  14. package/dist/rjsf.js +70 -6
  15. package/dist/schema-compose.d.ts +6 -0
  16. package/dist/schema-compose.js +103 -0
  17. package/dist/schema-engine.d.ts +18 -0
  18. package/dist/schema-engine.js +298 -0
  19. package/dist/schema-json.d.ts +10 -0
  20. package/dist/schema-json.js +68 -0
  21. package/dist/schema-types.d.ts +161 -0
  22. package/dist/schema-types.js +2 -0
  23. package/dist/schema-view.d.ts +7 -0
  24. package/dist/schema-view.js +102 -0
  25. package/dist/schema.d.ts +8 -0
  26. package/dist/schema.js +86 -0
  27. package/dist/session.d.ts +1 -1
  28. package/dist/session.js +18 -5
  29. package/dist/source-document.d.ts +3 -0
  30. package/dist/source-document.js +415 -0
  31. package/dist/source-editor.d.ts +13 -0
  32. package/dist/source-editor.js +235 -0
  33. package/dist/source-schema-hints.d.ts +3 -0
  34. package/dist/source-schema-hints.js +13 -0
  35. package/dist/source-types.d.ts +47 -0
  36. package/dist/source-types.js +1 -0
  37. package/dist/source-view.d.ts +3 -0
  38. package/dist/source-view.js +318 -0
  39. package/dist/types.d.ts +2 -4
  40. package/dist/webpack4/index.js +4 -2
  41. package/dist/webpack4/metafile.json +9551 -8644
  42. package/dist/webpack4/react.js +2 -1
  43. package/dist/webpack4/react.js.map +1 -1
  44. package/dist/webpack4/rjsf.js +15014 -20975
  45. package/dist/webpack4/rjsf.js.map +4 -4
  46. package/dist/webpack4/schema.js +17 -0
  47. package/dist/webpack4/schema.js.map +7 -0
  48. package/dist/webpack4/shared-BKTRZUZH.js +42 -0
  49. package/dist/webpack4/{shared-KIFRVTSM.js.map → shared-BKTRZUZH.js.map} +1 -1
  50. package/dist/webpack4/shared-KQOUMWJ4.js +8490 -0
  51. package/dist/webpack4/shared-KQOUMWJ4.js.map +7 -0
  52. package/dist/webpack4/shared-MRPGBJIH.js +75 -0
  53. package/dist/webpack4/shared-MRPGBJIH.js.map +7 -0
  54. package/dist/webpack4/{shared-WHOAW2VF.js → shared-N6YQE2X6.js} +116 -44
  55. package/dist/webpack4/shared-N6YQE2X6.js.map +7 -0
  56. package/dist/webpack4/{shared-KIFRVTSM.js → shared-RN7GOT65.js} +2 -41
  57. package/dist/webpack4/shared-RN7GOT65.js.map +7 -0
  58. package/dist/webpack4/turbowarp.js +4 -2
  59. package/dist/webpack4/turbowarp.js.map +1 -1
  60. package/docs/README.md +1 -0
  61. package/docs/block-editor-e2e.md +98 -16
  62. package/docs/ci-success-gate.md +84 -0
  63. package/docs/editor/issue-12-public-validation/README.md +61 -0
  64. package/docs/editor/issue-12-public-validation/dark-invalid.png +0 -0
  65. package/docs/editor/issue-12-public-validation/dark-styles.json +173 -0
  66. package/docs/editor/issue-12-public-validation/dark-valid.png +0 -0
  67. package/docs/editor/issue-12-public-validation/deployment-target.json +5 -0
  68. package/docs/editor/issue-12-public-validation/deployment-wait.json +7 -0
  69. package/docs/editor/issue-12-public-validation/light-invalid.png +0 -0
  70. package/docs/editor/issue-12-public-validation/light-styles.json +173 -0
  71. package/docs/editor/issue-12-public-validation/light-valid.png +0 -0
  72. package/docs/editor/issue-12-public-validation/provenance.json +88 -0
  73. package/docs/editor/issue-12-public-validation/results.json +677 -0
  74. package/docs/editor/issue-12-public-validation/summary.json +206 -0
  75. package/docs/github-pages.md +48 -41
  76. package/docs/package-distribution.md +2 -2
  77. package/docs/schema-stage1-browser.json +10 -0
  78. package/docs/schema-stage1-contracts.json +242 -0
  79. package/docs/schema-stage1-dependencies.json +202 -0
  80. package/docs/schema-stage1-git-artifact-20261018.ja.md +17 -0
  81. package/docs/schema-stage1-profile-compat.json +30 -0
  82. package/docs/schema-stage1-review-fixes-20261018.ja.md +36 -0
  83. package/docs/schema-stage1-verification.json +541 -0
  84. package/docs/schema-stage1.ja.md +80 -0
  85. package/docs/schema.md +43 -0
  86. package/docs/source-forms.ja.md +19 -0
  87. package/docs/stage-2-publication.ja.md +29 -0
  88. package/docs/template-fixture-separation.ja.md +43 -0
  89. package/package.json +11 -3
  90. package/webpack4.cjs +1 -1
  91. package/dist/webpack4/shared-WHOAW2VF.js.map +0 -7
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,17 +103,17 @@ 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` です。
104
107
 
105
108
  ```sh
106
- npm install --save-exact @kubohiroya/turbowarp-extension-data-editor@0.1.0
109
+ npm install --save-exact @kubohiroya/turbowarp-extension-data-editor@0.2.0
107
110
  ```
108
111
 
109
112
  配布物・改名時の互換性・公開手順は[パッケージ配布](docs/package-distribution.md)を参照してください。
110
113
 
111
114
  ## GUI で試す
112
115
 
113
- `scratch-gui` はnpmに公開した本パッケージを固定versionで参照します。通常のGUIビルドに共通リポジトリの隣接配置は不要です。
116
+ `scratch-gui` は本パッケージを検証済みのnpm完全version、またはGitの完全SHAで固定参照します。通常のGUIビルドに共通リポジトリの隣接配置は不要です。
114
117
  [GUIの起動・制約・検証手順](docs/block-editor.md) に従ってください。
115
118
  既存の拡張アイコンをクリックすると、標準モーダルで下書きを編集できます。
116
119
 
@@ -129,3 +132,21 @@ GUI の `src/lib/tw-feature-flags.js` で起動時固定の `blockEditorDialogs`
129
132
  ## webpack 4 / E2E
130
133
 
131
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.
@@ -1,6 +1,11 @@
1
1
  {
2
2
  "name": "@kubohiroya/turbowarp-extension-data-editor",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "repository": "https://github.com/kubohiroya/turbowarp-extension-data-editor",
5
- "revision": "9e041e5278a1ea70146a76c10732d1401955a12a"
5
+ "revision": "76fa1d50a3c188082ceef723ea018a9d8769ea6e",
6
+ "sourceState": "commit",
7
+ "sourceSha256": "912efc0438710958698527aa5c26a33b0b1c2da280f91d36b048f6f2389a48f0",
8
+ "capabilities": {
9
+ "preparedSource": 1
10
+ }
6
11
  }
@@ -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 issues = await abortable(record.definition.validate(cloneJson(record.draft), record.snapshot), __classPrivateFieldGet(this, _DescendantSession_abort, "f").signal);
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 patch = record.definition.createPatch(cloneJson(record.draft), record.snapshot);
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';
package/dist/index.d.ts CHANGED
@@ -3,3 +3,4 @@ export { EditorRegistry } from './registry.js';
3
3
  export { EditorSession } from './session.js';
4
4
  export * from './descendant-types.js';
5
5
  export { DescendantSession } from './descendants.js';
6
+ export type * from './prepared-types.js';
@@ -0,0 +1,4 @@
1
+ import type { SchemaBundle } from './schema-types.js';
2
+ /** Every finite JSON value is accepted. The shared snapshot guard enforces JSON safety. */
3
+ export declare const jsonValueSchema = true;
4
+ export declare const jsonValueSchemaBundle: SchemaBundle;
@@ -0,0 +1,12 @@
1
+ /** Every finite JSON value is accepted. The shared snapshot guard enforces JSON safety. */
2
+ export const jsonValueSchema = true;
3
+ const source = { repository: 'https://github.com/kubohiroya/turbowarp-extension-data-editor',
4
+ path: 'src/json-value.ts', symbol: 'jsonValueSchema' };
5
+ export const jsonValueSchemaBundle = Object.freeze({
6
+ identity: { ownerId: '@kubohiroya/turbowarp-extension-data-editor', contractId: 'json-value', revision: '1', variantId: 'json-value' },
7
+ dialect: '2020-12',
8
+ root: { retrievalUri: 'https://kubohiroya.github.io/turbowarp-extension-data-editor/schemas/json-value',
9
+ document: jsonValueSchema, artifact: { role: 'canonical-schema', source } },
10
+ resources: [],
11
+ provenance: { canonicalSources: [source], generatedFiles: [], referenceSchemas: [], generationSources: [] }
12
+ });
@@ -0,0 +1,5 @@
1
+ import type { PreparedEditorDefinition, PreparedSave } from './prepared-types.js';
2
+ import type { BlockSnapshot, EditorDefinition, JsonObject } from './types.js';
3
+ export declare function isPreparedEditor(definition: EditorDefinition): definition is PreparedEditorDefinition;
4
+ /** Capture callback output before checking it; accessors and iterators never supply writes. */
5
+ export declare function capturePreparedSave(definition: PreparedEditorDefinition, draft: JsonObject, snapshot: BlockSnapshot): PreparedSave;
@@ -0,0 +1,47 @@
1
+ import { snapshotJson } from './schema-json.js';
2
+ export function isPreparedEditor(definition) {
3
+ return definition.saveContract === 'prepared-source-v1';
4
+ }
5
+ /** Capture callback output before checking it; accessors and iterators never supply writes. */
6
+ export function capturePreparedSave(definition, draft, snapshot) {
7
+ const output = definition.prepareSave(draft, snapshot);
8
+ try {
9
+ const value = snapshotJson(output);
10
+ if (!value || typeof value !== 'object' || Array.isArray(value))
11
+ throw new TypeError('Invalid prepared save.');
12
+ const result = value;
13
+ if (result.kind === 'ready' && Array.isArray(result.changes)) {
14
+ const changes = [];
15
+ for (let i = 0; i < result.changes.length; i++) {
16
+ const change = result.changes[i];
17
+ if (!change || typeof change.argument !== 'string' ||
18
+ !(typeof change.value === 'string' || typeof change.value === 'boolean' ||
19
+ (typeof change.value === 'number' && Number.isFinite(change.value)))) {
20
+ throw new TypeError('Invalid prepared change.');
21
+ }
22
+ changes.push(Object.freeze({ argument: change.argument, value: change.value }));
23
+ }
24
+ return Object.freeze({ kind: 'ready', changes: Object.freeze(changes) });
25
+ }
26
+ if (result.kind === 'invalid' && Array.isArray(result.issues) && result.issues.length > 0) {
27
+ const issues = [];
28
+ for (let i = 0; i < result.issues.length; i++) {
29
+ const issue = result.issues[i];
30
+ if (!issue || typeof issue.message !== 'string' || (issue.path !== undefined && typeof issue.path !== 'string')) {
31
+ throw new TypeError('Invalid prepared issue.');
32
+ }
33
+ issues.push(Object.freeze({ message: issue.message, ...(issue.path === undefined ? {} : { path: issue.path }) }));
34
+ }
35
+ return Object.freeze({ kind: 'invalid', issues: Object.freeze(issues) });
36
+ }
37
+ throw new TypeError('Invalid prepared save.');
38
+ }
39
+ catch (error) {
40
+ // A synchronous contract never awaits foreign thenables. Drain native rejected promises.
41
+ try {
42
+ void Promise.prototype.then.call(output, () => undefined, () => undefined);
43
+ }
44
+ catch ( /* Non-promises are rejected without touching their then property. */_a) { /* Non-promises are rejected without touching their then property. */ }
45
+ throw error;
46
+ }
47
+ }
@@ -0,0 +1,13 @@
1
+ import type { BlockSnapshot, EditorDefinition, JsonObject, LiteralArgumentChange, ValidationIssue } from './types.js';
2
+ export type PreparedSave = {
3
+ readonly kind: 'ready';
4
+ readonly changes: readonly LiteralArgumentChange[];
5
+ } | {
6
+ readonly kind: 'invalid';
7
+ readonly issues: readonly ValidationIssue[];
8
+ };
9
+ export interface PreparedEditorDefinition extends EditorDefinition {
10
+ readonly saveContract: 'prepared-source-v1';
11
+ /** Construct, validate and capture each exact candidate once per save attempt. */
12
+ prepareSave(draft: JsonObject, snapshot: BlockSnapshot): PreparedSave;
13
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -2,9 +2,11 @@ import { EditorSession } from './session.js';
2
2
  import { DescendantSession } from './descendants.js';
3
3
  import type { DescendantLimits } from './descendant-types.js';
4
4
  import type { BlockEditorHost, BlockReference, EditorDefinition, EditorDescriptor } from './types.js';
5
+ import type { PreparedEditorDefinition } from './prepared-types.js';
5
6
  export declare class EditorRegistry {
6
7
  #private;
7
8
  register(definition: EditorDefinition): () => void;
9
+ registerPrepared(definition: PreparedEditorDefinition): () => void;
8
10
  resolve(opcode: string): EditorDescriptor | undefined;
9
11
  open(reference: BlockReference, host: BlockEditorHost): EditorSession | undefined;
10
12
  openDescendants(reference: BlockReference, host: BlockEditorHost, limits?: DescendantLimits): DescendantSession | undefined;
package/dist/registry.js CHANGED
@@ -3,13 +3,14 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
3
3
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
4
4
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
5
5
  };
6
- var _EditorRegistry_instances, _EditorRegistry_editors, _EditorRegistry_opcodes, _EditorRegistry_changes, _EditorRegistry_notifyChanges;
6
+ var _EditorRegistry_instances, _EditorRegistry_editors, _EditorRegistry_opcodes, _EditorRegistry_changes, _EditorRegistry_register, _EditorRegistry_notifyChanges;
7
7
  import { captureReference, captureSnapshot } from './data.js';
8
8
  import { EditorSession } from './session.js';
9
9
  import { DescendantSession } from './descendants.js';
10
10
  import { supportsDescendantEditing } from './descendant-types.js';
11
11
  import { captureSubtree, descendantLimits } from './subtree.js';
12
12
  import { EDITOR_API_VERSION } from './types.js';
13
+ import { isPreparedEditor } from './prepared-save.js';
13
14
  export class EditorRegistry {
14
15
  constructor() {
15
16
  _EditorRegistry_instances.add(this);
@@ -18,49 +19,15 @@ export class EditorRegistry {
18
19
  _EditorRegistry_changes.set(this, new Set());
19
20
  }
20
21
  register(definition) {
21
- if (definition.apiVersion !== EDITOR_API_VERSION)
22
- throw new TypeError('Unsupported editor API version.');
23
- if (!/^[a-z0-9]+$/i.test(definition.extensionId) ||
24
- !/^[a-z0-9][a-z0-9._-]*$/i.test(definition.editorId)) {
25
- throw new TypeError('Invalid extension ID or editor ID.');
26
- }
27
- if (!Array.isArray(definition.opcodes) || definition.opcodes.length === 0 ||
28
- new Set(definition.opcodes).size !== definition.opcodes.length ||
29
- definition.opcodes.some(opcode => typeof opcode !== 'string' ||
30
- !opcode.startsWith(`${definition.extensionId}_`) || opcode === `${definition.extensionId}_`)) {
31
- throw new TypeError('Expected distinct full opcodes belonging to the extension.');
32
- }
33
- if (!Array.isArray(definition.editableArguments) ||
34
- new Set(definition.editableArguments).size !== definition.editableArguments.length ||
35
- definition.editableArguments.some(name => typeof name !== 'string' || !name)) {
36
- throw new TypeError('Expected distinct editable argument names.');
37
- }
38
- if (typeof definition.read !== 'function' || typeof definition.validate !== 'function' ||
39
- typeof definition.createPatch !== 'function' ||
40
- (definition.mount !== undefined && typeof definition.mount !== 'function')) {
41
- throw new TypeError('Invalid editor callbacks.');
42
- }
43
- const key = JSON.stringify([definition.extensionId, definition.editorId]);
44
- if (__classPrivateFieldGet(this, _EditorRegistry_editors, "f").has(key) || definition.opcodes.some(opcode => __classPrivateFieldGet(this, _EditorRegistry_opcodes, "f").has(opcode))) {
45
- throw new Error('The editor ID or an opcode is already registered.');
22
+ if (isPreparedEditor(definition))
23
+ throw new TypeError('Prepared editors require registerPrepared().');
24
+ return __classPrivateFieldGet(this, _EditorRegistry_instances, "m", _EditorRegistry_register).call(this, definition);
25
+ }
26
+ registerPrepared(definition) {
27
+ if (!isPreparedEditor(definition) || typeof definition.prepareSave !== 'function') {
28
+ throw new TypeError('Expected a prepared source editor.');
46
29
  }
47
- const entry = Object.freeze({
48
- ...definition,
49
- opcodes: Object.freeze([...definition.opcodes]),
50
- editableArguments: Object.freeze([...definition.editableArguments])
51
- });
52
- __classPrivateFieldGet(this, _EditorRegistry_editors, "f").set(key, entry);
53
- for (const opcode of entry.opcodes)
54
- __classPrivateFieldGet(this, _EditorRegistry_opcodes, "f").set(opcode, entry);
55
- __classPrivateFieldGet(this, _EditorRegistry_instances, "m", _EditorRegistry_notifyChanges).call(this);
56
- return () => {
57
- if (__classPrivateFieldGet(this, _EditorRegistry_editors, "f").get(key) !== entry)
58
- return;
59
- __classPrivateFieldGet(this, _EditorRegistry_editors, "f").delete(key);
60
- for (const opcode of entry.opcodes)
61
- __classPrivateFieldGet(this, _EditorRegistry_opcodes, "f").delete(opcode);
62
- __classPrivateFieldGet(this, _EditorRegistry_instances, "m", _EditorRegistry_notifyChanges).call(this);
63
- };
30
+ return __classPrivateFieldGet(this, _EditorRegistry_instances, "m", _EditorRegistry_register).call(this, definition);
64
31
  }
65
32
  resolve(opcode) {
66
33
  const entry = __classPrivateFieldGet(this, _EditorRegistry_opcodes, "f").get(opcode);
@@ -78,7 +45,8 @@ export class EditorRegistry {
78
45
  return undefined;
79
46
  const snapshot = captureSnapshot(live, capturedReference);
80
47
  const entry = __classPrivateFieldGet(this, _EditorRegistry_opcodes, "f").get(snapshot.opcode);
81
- return entry ? new EditorSession(entry, snapshot, host) : undefined;
48
+ return entry ? new EditorSession(entry, snapshot, host, isPreparedEditor(entry)
49
+ ? () => __classPrivateFieldGet(this, _EditorRegistry_opcodes, "f").get(snapshot.opcode) === entry : undefined) : undefined;
82
50
  }
83
51
  openDescendants(reference, host, limits) {
84
52
  if (!supportsDescendantEditing(host))
@@ -98,7 +66,51 @@ export class EditorRegistry {
98
66
  }, resolvedLimits);
99
67
  }
100
68
  }
101
- _EditorRegistry_editors = new WeakMap(), _EditorRegistry_opcodes = new WeakMap(), _EditorRegistry_changes = new WeakMap(), _EditorRegistry_instances = new WeakSet(), _EditorRegistry_notifyChanges = function _EditorRegistry_notifyChanges() {
69
+ _EditorRegistry_editors = new WeakMap(), _EditorRegistry_opcodes = new WeakMap(), _EditorRegistry_changes = new WeakMap(), _EditorRegistry_instances = new WeakSet(), _EditorRegistry_register = function _EditorRegistry_register(definition) {
70
+ if (definition.apiVersion !== EDITOR_API_VERSION)
71
+ throw new TypeError('Unsupported editor API version.');
72
+ if (!/^[a-z0-9]+$/i.test(definition.extensionId) ||
73
+ !/^[a-z0-9][a-z0-9._-]*$/i.test(definition.editorId)) {
74
+ throw new TypeError('Invalid extension ID or editor ID.');
75
+ }
76
+ if (!Array.isArray(definition.opcodes) || definition.opcodes.length === 0 ||
77
+ new Set(definition.opcodes).size !== definition.opcodes.length ||
78
+ definition.opcodes.some(opcode => typeof opcode !== 'string' ||
79
+ !opcode.startsWith(`${definition.extensionId}_`) || opcode === `${definition.extensionId}_`)) {
80
+ throw new TypeError('Expected distinct full opcodes belonging to the extension.');
81
+ }
82
+ if (!Array.isArray(definition.editableArguments) ||
83
+ new Set(definition.editableArguments).size !== definition.editableArguments.length ||
84
+ definition.editableArguments.some(name => typeof name !== 'string' || !name)) {
85
+ throw new TypeError('Expected distinct editable argument names.');
86
+ }
87
+ if (typeof definition.read !== 'function' || typeof definition.validate !== 'function' ||
88
+ typeof definition.createPatch !== 'function' ||
89
+ (definition.mount !== undefined && typeof definition.mount !== 'function')) {
90
+ throw new TypeError('Invalid editor callbacks.');
91
+ }
92
+ const key = JSON.stringify([definition.extensionId, definition.editorId]);
93
+ if (__classPrivateFieldGet(this, _EditorRegistry_editors, "f").has(key) || definition.opcodes.some(opcode => __classPrivateFieldGet(this, _EditorRegistry_opcodes, "f").has(opcode))) {
94
+ throw new Error('The editor ID or an opcode is already registered.');
95
+ }
96
+ const entry = Object.freeze({
97
+ ...definition,
98
+ opcodes: Object.freeze([...definition.opcodes]),
99
+ editableArguments: Object.freeze([...definition.editableArguments])
100
+ });
101
+ __classPrivateFieldGet(this, _EditorRegistry_editors, "f").set(key, entry);
102
+ for (const opcode of entry.opcodes)
103
+ __classPrivateFieldGet(this, _EditorRegistry_opcodes, "f").set(opcode, entry);
104
+ __classPrivateFieldGet(this, _EditorRegistry_instances, "m", _EditorRegistry_notifyChanges).call(this);
105
+ return () => {
106
+ if (__classPrivateFieldGet(this, _EditorRegistry_editors, "f").get(key) !== entry)
107
+ return;
108
+ __classPrivateFieldGet(this, _EditorRegistry_editors, "f").delete(key);
109
+ for (const opcode of entry.opcodes)
110
+ __classPrivateFieldGet(this, _EditorRegistry_opcodes, "f").delete(opcode);
111
+ __classPrivateFieldGet(this, _EditorRegistry_instances, "m", _EditorRegistry_notifyChanges).call(this);
112
+ };
113
+ }, _EditorRegistry_notifyChanges = function _EditorRegistry_notifyChanges() {
102
114
  for (const listener of [...__classPrivateFieldGet(this, _EditorRegistry_changes, "f")])
103
115
  listener();
104
116
  };
package/dist/rjsf.d.ts CHANGED
@@ -1,4 +1,9 @@
1
+ import type { SourceEditorOptions } from './source-types.js';
2
+ export { createSourceEditor } from './source-editor.js';
3
+ export type * from './source-types.js';
4
+ export type * from './prepared-types.js';
1
5
  import type { RJSFSchema } from '@rjsf/utils';
6
+ import type { SchemaBundle, SchemaValidationContext, SyncOwnerValidator } from './schema-types.js';
2
7
  import type { SourceFormat } from './codec.js';
3
8
  import { EditorRegistry } from './registry.js';
4
9
  import { EDITOR_API_VERSION } from './types.js';
@@ -17,10 +22,27 @@ export interface SchemaEditorOptions {
17
22
  /** Optional business validation, always run after schema validation when saving. */
18
23
  validateData?(data: JsonValue, snapshot: BlockSnapshot): readonly ValidationIssue[] | Promise<readonly ValidationIssue[]>;
19
24
  }
25
+ /** Explicit bundle opt-in; the provider supplies fresh immutable validation inputs per call. */
26
+ export interface SchemaBundleEditorOptions<C = null> extends Omit<SchemaEditorOptions, 'schema'> {
27
+ readonly schemaBundle: SchemaBundle;
28
+ readonly validationContext: () => SchemaValidationContext<C>;
29
+ readonly ownerValidate?: SyncOwnerValidator<C>;
30
+ }
31
+ export declare function createSchemaBundleEditor<C>(options: SchemaBundleEditorOptions<C>): EditorDefinition;
20
32
  export interface BlockEditorRegistrationAPI {
21
33
  readonly apiVersion: typeof EDITOR_API_VERSION;
22
34
  register(definition: EditorDefinition | SchemaEditorOptions): () => void;
23
35
  registerSchema(definition: SchemaEditorOptions): () => void;
36
+ registerSchemaBundle?<C>(definition: SchemaBundleEditorOptions<C>): () => void;
37
+ registerSourceEditor?<C = null>(definition: SourceEditorOptions<C>): () => void;
38
+ }
39
+ /** Capability guaranteed by the service factory, optional on a legacy host-supplied API. */
40
+ export interface SchemaBundleBlockEditorRegistrationAPI extends BlockEditorRegistrationAPI {
41
+ registerSchemaBundle<C>(definition: SchemaBundleEditorOptions<C>): () => void;
42
+ }
43
+ /** New prepared-source capability; existing bundle-only host implementations remain compatible. */
44
+ export interface SourceBlockEditorRegistrationAPI extends SchemaBundleBlockEditorRegistrationAPI {
45
+ registerSourceEditor<C = null>(definition: SourceEditorOptions<C>): () => void;
24
46
  }
25
47
  export interface BlockEditorService {
26
48
  readonly registry: EditorRegistry;
@@ -28,6 +50,8 @@ export interface BlockEditorService {
28
50
  subscribe(listener: () => void): () => void;
29
51
  }
30
52
  /** A host owns the service's lifetime; this module never stores VM or workspace instances. */
31
- export declare function createBlockEditorService(): BlockEditorService;
53
+ export declare function createBlockEditorService(): BlockEditorService & {
54
+ readonly api: SourceBlockEditorRegistrationAPI;
55
+ };
32
56
  /** Build an editor with JSON/YAML serialization, schema validation and the standard RJSF body. */
33
57
  export declare function createSchemaEditor(options: SchemaEditorOptions): EditorDefinition;
package/dist/rjsf.js CHANGED
@@ -1,8 +1,14 @@
1
1
  import * as React from 'react';
2
+ import { createSourceEditor } from './source-editor.js';
3
+ export { createSourceEditor } from './source-editor.js';
2
4
  import * as ReactDOM from 'react-dom';
3
5
  import * as RjsfCore from '@rjsf/core';
4
6
  import * as RjsfUtils from '@rjsf/utils';
5
7
  import * as RjsfAjv from '@rjsf/validator-ajv8';
8
+ import { Ajv2020 } from 'ajv/dist/2020.js';
9
+ import { compileSchemaBundle, validateSchemaInput, toValidationIssues } from './schema.js';
10
+ import { schemaView, fieldErrors } from './schema-view.js';
11
+ import { snapshotJson } from './schema-json.js';
6
12
  import { cloneJson } from './data.js';
7
13
  import { decodeSource, encodeSource, sameData } from './codec.js';
8
14
  import { EditorRegistry } from './registry.js';
@@ -17,6 +23,48 @@ const Form = moduleExports(RjsfCore).default;
17
23
  const { getDefaultFormState } = moduleExports(RjsfUtils);
18
24
  const { customizeValidator } = moduleExports(RjsfAjv);
19
25
  let nextViewId = 0;
26
+ export function createSchemaBundleEditor(options) {
27
+ const { schemaBundle, validationContext: provider, ownerValidate, apiVersion, extensionId, editorId, opcodes, argument, format, uiSchema, validateData } = options;
28
+ let bundle;
29
+ try {
30
+ bundle = snapshotJson(schemaBundle);
31
+ }
32
+ catch (_a) {
33
+ throw new TypeError('Invalid schema bundle.');
34
+ }
35
+ const result = compileSchemaBundle(bundle);
36
+ if (result.kind !== 'compiled')
37
+ throw new TypeError(`Schema bundle compilation failed: ${result.diagnostics[0].code}`);
38
+ const schema = schemaView(bundle);
39
+ const issuesFor = (data) => {
40
+ let context;
41
+ try {
42
+ context = provider();
43
+ if (context && typeof context === 'object') {
44
+ const prototype = Object.getPrototypeOf(context);
45
+ if (prototype !== Object.prototype && prototype !== null) {
46
+ // Drain rejected native/cross-realm promises from a malformed synchronous provider.
47
+ try {
48
+ void Promise.prototype.then.call(context, () => undefined, () => undefined);
49
+ }
50
+ catch ( /* The common context guard rejects other non-JSON instances below. */_a) { /* The common context guard rejects other non-JSON instances below. */ }
51
+ }
52
+ }
53
+ }
54
+ catch (_b) {
55
+ return [{ message: 'Validation could not complete: owner-context-invalid.' }];
56
+ }
57
+ const validation = validateSchemaInput(result.validator, data, { context, ...(ownerValidate !== undefined ? { ownerValidate } : {}) });
58
+ if (validation.kind === 'valid')
59
+ return [];
60
+ if (validation.kind === 'execution-error')
61
+ return validation.diagnostics.map(d => ({ message: `Validation could not complete: ${d.code}.` }));
62
+ return toValidationIssues(validation.diagnostics);
63
+ };
64
+ return createSchemaEditorImpl({ apiVersion, extensionId, editorId, opcodes, argument, schema,
65
+ ...(format === undefined ? {} : { format }), ...(uiSchema === undefined ? {} : { uiSchema }),
66
+ ...(validateData === undefined ? {} : { validateData }) }, { issuesFor, dialect: result.validator.dialect });
67
+ }
20
68
  /** A host owns the service's lifetime; this module never stores VM or workspace instances. */
21
69
  export function createBlockEditorService() {
22
70
  const registry = new EditorRegistry();
@@ -42,7 +90,18 @@ export function createBlockEditorService() {
42
90
  notify();
43
91
  };
44
92
  },
45
- registerSchema: (definition) => api.register(createSchemaEditor(definition))
93
+ registerSchema: (definition) => api.register(createSchemaEditor(definition)),
94
+ registerSchemaBundle: (definition) => api.register(createSchemaBundleEditor(definition)),
95
+ registerSourceEditor: (definition) => {
96
+ const remove = registry.registerPrepared(createSourceEditor(definition));
97
+ notify();
98
+ let active = true;
99
+ return () => { if (active) {
100
+ active = false;
101
+ remove();
102
+ notify();
103
+ } };
104
+ }
46
105
  });
47
106
  return { registry, api, subscribe: listener => {
48
107
  listeners.add(listener);
@@ -51,7 +110,10 @@ export function createBlockEditorService() {
51
110
  }
52
111
  /** Build an editor with JSON/YAML serialization, schema validation and the standard RJSF body. */
53
112
  export function createSchemaEditor(options) {
54
- var _a;
113
+ return createSchemaEditorImpl(options);
114
+ }
115
+ function createSchemaEditorImpl(options, bundle) {
116
+ var _a, _b;
55
117
  if (options.apiVersion !== EDITOR_API_VERSION || typeof options.argument !== 'string' || !options.argument) {
56
118
  throw new TypeError('A supported API version and JSON argument name are required.');
57
119
  }
@@ -63,19 +125,20 @@ export function createSchemaEditor(options) {
63
125
  const uiSchema = options.uiSchema ? cloneJson(options.uiSchema) : {};
64
126
  const validateData = options.validateData;
65
127
  const validator = customizeValidator({
128
+ ...((bundle === null || bundle === void 0 ? void 0 : bundle.dialect) === '2020-12' ? { AjvClass: Ajv2020 } : {}),
66
129
  ajvOptionsOverrides: { allErrors: true, coerceTypes: false, useDefaults: false, removeAdditional: false }
67
130
  });
68
131
  const compilation = validator.rawValidation(schema);
69
132
  if (compilation.validationError) {
70
133
  throw new TypeError(`Invalid or unsupported JSON Schema: ${compilation.validationError.message}`);
71
134
  }
72
- const issuesFor = (data) => {
135
+ const issuesFor = (_b = bundle === null || bundle === void 0 ? void 0 : bundle.issuesFor) !== null && _b !== void 0 ? _b : ((data) => {
73
136
  const result = validator.validateFormData(data, schema, undefined, undefined, uiSchema);
74
137
  return result.errors.map(error => ({
75
138
  message: error.stack || error.message || 'Invalid value.',
76
139
  ...(error.property === undefined ? {} : { path: error.property })
77
140
  }));
78
- };
141
+ });
79
142
  // RJSF uses undefined for cleared controls; persist these as omitted properties (or null at the root).
80
143
  const formValue = (value) => JSON.parse(JSON.stringify(value !== null && value !== void 0 ? value : null));
81
144
  const dataFor = (draft) => {
@@ -94,7 +157,7 @@ export function createSchemaEditor(options) {
94
157
  }
95
158
  const data = decodeSource(slot.value, format);
96
159
  // Initialize the session draft once; remounting must not reset edits or errors.
97
- return { data: formValue(getDefaultFormState(validator, schema, data, schema)) };
160
+ return { data: bundle ? data : formValue(getDefaultFormState(validator, schema, data, schema)) };
98
161
  },
99
162
  validate: (draft, snapshot) => {
100
163
  const data = dataFor(draft);
@@ -134,7 +197,8 @@ export function createSchemaEditor(options) {
134
197
  constAsDefaults: 'never'
135
198
  },
136
199
  tagName: 'div',
137
- liveValidate: true,
200
+ liveValidate: !bundle,
201
+ ...(bundle ? { noValidate: true, extraErrors: fieldErrors(state.issues) } : {}),
138
202
  showErrorList: false,
139
203
  disabled: state.status !== 'editing',
140
204
  onChange: (event) => {
@@ -0,0 +1,6 @@
1
+ import type { CompiledSchema, SchemaInputValidationResult, SchemaJsonValue, SchemaValidationContext, SyncOwnerValidator } from './schema-types.js';
2
+ /** Validate one protected candidate/context snapshot, then invoke a synchronous owner at most once. */
3
+ export declare function validateSchemaInput<C>(compiled: CompiledSchema, data: SchemaJsonValue, options: {
4
+ readonly context: SchemaValidationContext<C>;
5
+ readonly ownerValidate?: SyncOwnerValidator<C>;
6
+ }): SchemaInputValidationResult;