@kubohiroya/turbowarp-structured-data 0.6.1 → 0.6.3

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.
@@ -0,0 +1,38 @@
1
+ # 編集metadataとruntimeの境界
2
+
3
+ 追跡: [共通data-editor #72](https://github.com/kubohiroya/turbowarp-extension-data-editor/issues/72)。
4
+ Camera Source #52と同じ責務分担を適用する。
5
+
6
+ | 担当 | 責務 |
7
+ | --- | --- |
8
+ | Structured Data | JSON/YAMLの既存parser、名前付きデータ、path/反復、正本Schema、原文decoder、pure owner検証、編集binding |
9
+ | 共通data-editor/GUI | 編集時のSchema compile、Schema制約の診断、フォーム描画、保存/Cancel、transaction |
10
+
11
+ `sourceBinding.validateSource` は原文を保持して既存ownerのreader/checkerを実行し、Schemaをcompileしない。
12
+ 共通GUIは宣言されたSchemaBundleを検証してから保存する。独自consumerがSchema keyword別診断を必要とする場合は、
13
+ consumer自身が共通data-editorのSchema検証を組み合わせる。owner callbackの診断はSchema診断の代わりにはしない。
14
+
15
+ `@kubohiroya/turbowarp-structured-data/editor-metadata` はSource/Formの3定義と固定MAX入力の1定義を公開する。
16
+ この入口はGUI/DOM/VMを参照せず、実行時importは既存production依存の`yaml`だけである。
17
+ 共通のJSON値SchemaBundleと新数値入力parserは開発依存の正本をビルド時に取り込み、手書きの複製を作らない。
18
+ 型宣言で共通型を利用するTypeScript consumerは、自身の開発依存にdata-editorを追加する。
19
+
20
+ data-editorの直接依存をdevDependenciesへ移す。standalone JSとmetadataにはAjv、ajv-formats、Schema compiler、
21
+ React/RJSFを含めない。Viteのmodule graph guardと隔離importテストで再流入を検出する。
22
+ 通常のYAML解析はruntime機能なので維持する。yaml本体の複数拡張間共有は[sb3-toolchain #65](https://github.com/kubohiroya/sb3-toolchain/issues/65)の後続課題である。
23
+
24
+ 既存のMVP・GUI編集フラグ、Extension ID、opcode、MAXの文法・範囲、既存raw値と無変更保存、Undo transactionを維持する。
25
+ 問題時はGUI編集フラグをOFFにして再読込し、必要なら検証済みのexact pin・lock・生成物へ一組で戻す。
26
+ 保存済みデータを自動移行しない。
27
+
28
+ 開発時は`pnpm run build`でstandaloneと公開metadataを生成してから対象テストを実行する。
29
+ ローカルの対象テストは実GUIや実機の代替であり、ブラウザ上の表示確認を実施した意味にはしない。
30
+
31
+ ## ローカル検証記録(2026-10-24)
32
+
33
+ - 基準: 公開main `4ae44bd6ee1d`。standalone JSは560,962 Bから256,729 Bへ変更。
34
+ - gzip level 9(参考値)は119,711 Bから58,861 B。SB3削減量や実行時RAMの測定値ではない。
35
+ - 対象テスト82件、型検査、変更したTypeScriptのESLint、standalone/metadata build、package export・repo policy確認が成功。
36
+ - GUIなしVMで通常登録1回、Ajv生成0回、編集用SchemaBundle compile 0回。2拡張をsb3-toolchain 0.16.0で束ねても同じ結果。
37
+ - 共通サービス経由のSchema制約拒否、owner受理条件、raw no-op/変更時commit、旧GUIでの非登録、隔離metadata importを確認。
38
+ - 実ブラウザ・3D描画・実カメラの確認、push、PR、merge、npm公開はこのローカル検証に含めない。
@@ -0,0 +1,10 @@
1
+ import { type JsonValue } from './core.js';
2
+ export declare const PARSE_LIMITS: {
3
+ readonly maxInputBytes: number;
4
+ readonly maxDepth: 64;
5
+ readonly maxNodes: 50000;
6
+ };
7
+ export declare function parseJsonDocument(source: string): JsonValue;
8
+ export declare function parseYamlDocument(source: string): JsonValue;
9
+ export declare function renderJsonDocument(value: JsonValue): string;
10
+ export declare function renderYamlDocument(value: JsonValue): string;
@@ -0,0 +1,25 @@
1
+ export declare const STRUCTURED_DATA_MVP_DEFAULT = false;
2
+ export declare const NAMED_DATA_REGISTRY_MVP_DEFAULT = false;
3
+ export declare const NAMED_DATA_SCHEMA_REF_DEFAULT = false;
4
+ export declare function isNamedDataSchemaRefEnabled(source?: {
5
+ __TW_NAMED_DATA_FEATURE_FLAGS__?: {
6
+ NAMED_DATA_SCHEMA_REF?: unknown;
7
+ };
8
+ }): boolean;
9
+ export declare function isStructuredDataMvpEnabled(source?: {
10
+ STRUCTURED_DATA_MVP?: unknown;
11
+ }): boolean;
12
+ export declare function isNamedDataRegistryMvpEnabled(source?: {
13
+ NAMED_DATA_REGISTRY_MVP?: unknown;
14
+ }): boolean;
15
+ export declare const extensionConfig: {
16
+ readonly id: "kubohiroyastructureddata";
17
+ readonly slug: "structured-data";
18
+ readonly name: "Structured Data";
19
+ readonly description: "Named structured data with JSON/YAML codecs for TurboWarp and server compilation.";
20
+ readonly author: "Hiroya Kubo";
21
+ readonly license: "MPL-2.0";
22
+ readonly unsandboxed: true;
23
+ readonly docsURI: "https://kubohiroya.github.io/turbowarp-structured-data/";
24
+ readonly blockIconURI: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0OCA0OCI+PHJlY3QgeD0iNCIgeT0iOCIgd2lkdGg9IjE4IiBoZWlnaHQ9IjE0IiByeD0iMyIgZmlsbD0iIzRDOTdGRiIvPjxyZWN0IHg9IjI2IiB5PSI4IiB3aWR0aD0iMTgiIGhlaWdodD0iMTQiIHJ4PSIzIiBmaWxsPSIjNTlDMDU5Ii8+PHJlY3QgeD0iMTUiIHk9IjI2IiB3aWR0aD0iMTgiIGhlaWdodD0iMTQiIHJ4PSIzIiBmaWxsPSIjRkZBQjE5Ii8+PC9zdmc+";
25
+ };
@@ -0,0 +1,34 @@
1
+ export declare const STRUCTURED_DATA_ERROR_CODES: readonly ["INVALID_JSON", "INVALID_YAML", "INVALID_NAME", "DATA_NOT_FOUND", "INVALID_PATH", "PATH_NOT_FOUND", "TYPE_MISMATCH", "INDEX_OUT_OF_RANGE", "ITERATION_LIMIT_EXCEEDED", "ITERATION_CONTEXT_REQUIRED", "PARSE_LIMIT_EXCEEDED"];
2
+ export type StructuredDataErrorCode = (typeof STRUCTURED_DATA_ERROR_CODES)[number];
3
+ export type JsonPrimitive = null | boolean | number | string;
4
+ export type JsonValue = JsonPrimitive | JsonValue[] | {
5
+ [key: string]: JsonValue;
6
+ };
7
+ export type PathSegment = {
8
+ kind: 'key';
9
+ value: string;
10
+ } | {
11
+ kind: 'index';
12
+ value: number;
13
+ };
14
+ export declare class StructuredDataError extends Error {
15
+ readonly code: StructuredDataErrorCode;
16
+ constructor(code: StructuredDataErrorCode, message: string);
17
+ }
18
+ export declare function parseJson(text: unknown): JsonValue;
19
+ export declare function isValidJson(text: unknown): boolean;
20
+ export declare function normalizeJson(text: unknown): string;
21
+ export declare function stringifyJson(value: JsonValue): string;
22
+ export declare function parsePath(input: unknown): PathSegment[];
23
+ export declare function getAtPath(root: JsonValue, segments: readonly PathSegment[]): JsonValue;
24
+ export declare function hasAtPath(root: JsonValue, segments: readonly PathSegment[]): boolean;
25
+ export declare function setAtPath(root: JsonValue, segments: readonly PathSegment[], replacement: JsonValue): JsonValue;
26
+ export declare function deleteAtPath(root: JsonValue, segments: readonly PathSegment[]): JsonValue;
27
+ export declare function keysAtPath(root: JsonValue, segments: readonly PathSegment[]): string[];
28
+ export declare function lengthAtPath(root: JsonValue, segments: readonly PathSegment[]): number;
29
+ export interface IterationEntry {
30
+ key: string;
31
+ value: JsonValue;
32
+ }
33
+ export declare function iterationEntries(root: JsonValue, segments: readonly PathSegment[], maximum: number): IterationEntry[];
34
+ export declare function compareUnicodeCodePoints(left: string, right: string): number;
@@ -0,0 +1,5 @@
1
+ /** Lightweight declarations and owner callbacks; the optional GUI owns Schema compilation. */
2
+ export { sourceEditorDefinitions } from './editor-schema.js';
3
+ export { canonicalBundle, registerSourceEditors, sourceBinding } from './source-editor.js';
4
+ export { fixedInputEditorDefinitions, registerFixedInputEditors } from './fixed-input-editors.js';
5
+ export { fixedInputSchemaBundle } from './fixed-input-schema.js';
@@ -0,0 +1,443 @@
1
+ import { LineCounter, isAlias, isMap, isScalar, isSeq, parseDocument } from "yaml";
2
+ var extensionConfig = {
3
+ id: "kubohiroyastructureddata",
4
+ slug: "structured-data",
5
+ name: "Structured Data",
6
+ description: "Named structured data with JSON/YAML codecs for TurboWarp and server compilation.",
7
+ author: "Hiroya Kubo",
8
+ license: "MPL-2.0",
9
+ unsandboxed: true,
10
+ docsURI: "https://kubohiroya.github.io/turbowarp-structured-data/",
11
+ blockIconURI: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0OCA0OCI+PHJlY3QgeD0iNCIgeT0iOCIgd2lkdGg9IjE4IiBoZWlnaHQ9IjE0IiByeD0iMyIgZmlsbD0iIzRDOTdGRiIvPjxyZWN0IHg9IjI2IiB5PSI4IiB3aWR0aD0iMTgiIGhlaWdodD0iMTQiIHJ4PSIzIiBmaWxsPSIjNTlDMDU5Ii8+PHJlY3QgeD0iMTUiIHk9IjI2IiB3aWR0aD0iMTgiIGhlaWdodD0iMTQiIHJ4PSIzIiBmaWxsPSIjRkZBQjE5Ii8+PC9zdmc+"
12
+ };
13
+ function canonicalBundle(ownerId, contractId, document, ownerSource) {
14
+ const source = {
15
+ repository: ownerId,
16
+ path: `schemas/${contractId}.schema.json`
17
+ };
18
+ return {
19
+ identity: {
20
+ ownerId,
21
+ contractId,
22
+ revision: "source-forms-v1",
23
+ variantId: "default"
24
+ },
25
+ dialect: "2020-12",
26
+ root: {
27
+ retrievalUri: `https://kubohiroya.github.io/${ownerId}/schemas/${contractId}.schema.json`,
28
+ document,
29
+ artifact: {
30
+ role: "canonical-schema",
31
+ source
32
+ }
33
+ },
34
+ resources: [],
35
+ provenance: {
36
+ canonicalSources: [source, {
37
+ repository: ownerId,
38
+ ...ownerSource
39
+ }],
40
+ generatedFiles: [],
41
+ referenceSchemas: [],
42
+ generationSources: []
43
+ }
44
+ };
45
+ }
46
+ function sourceBinding(argument, bundle, read, check = read, format = "json", uiSchema) {
47
+ return {
48
+ argument,
49
+ variants: [{
50
+ variantId: "default",
51
+ format,
52
+ schemaBundle: bundle,
53
+ ...uiSchema ? { uiSchema } : {}
54
+ }],
55
+ decode(source) {
56
+ return {
57
+ source,
58
+ format,
59
+ variantId: "default",
60
+ data: read(source)
61
+ };
62
+ },
63
+ validateSource(source, context) {
64
+ try {
65
+ read(source);
66
+ } catch {
67
+ return {
68
+ source,
69
+ result: {
70
+ kind: "invalid",
71
+ diagnostics: [{ code: "source-decode-invalid" }]
72
+ }
73
+ };
74
+ }
75
+ try {
76
+ const descriptors = Object.getOwnPropertyDescriptors(context);
77
+ if (typeof descriptors.consumerId?.value !== "string" || typeof descriptors.nowMs?.value !== "number" || !Number.isFinite(descriptors.nowMs.value) || !descriptors.settings || !("value" in descriptors.settings)) return {
78
+ source,
79
+ result: {
80
+ kind: "execution-error",
81
+ diagnostics: [{ code: "owner-context-invalid" }]
82
+ }
83
+ };
84
+ } catch {
85
+ return {
86
+ source,
87
+ result: {
88
+ kind: "execution-error",
89
+ diagnostics: [{ code: "owner-context-invalid" }]
90
+ }
91
+ };
92
+ }
93
+ try {
94
+ check(source);
95
+ } catch {
96
+ return {
97
+ source,
98
+ result: {
99
+ kind: "invalid",
100
+ diagnostics: [{ code: "source-owner-invalid" }]
101
+ }
102
+ };
103
+ }
104
+ return {
105
+ source,
106
+ result: {
107
+ kind: "valid",
108
+ diagnostics: []
109
+ }
110
+ };
111
+ }
112
+ };
113
+ }
114
+ /** The optional capability keeps legacy hosts and the startup flag safely inactive. */
115
+ function registerSourceEditors(scratch, definitions) {
116
+ const host = scratch;
117
+ const api = host.gui?.blockEditors;
118
+ if (api?.apiVersion !== 1 || typeof api.registerSourceEditor !== "function") return () => {};
119
+ const removers = [];
120
+ let active = true;
121
+ const dispose = () => {
122
+ if (!active) return;
123
+ active = false;
124
+ host.vm?.runtime?.off?.("RUNTIME_DISPOSED", dispose);
125
+ for (const remove of removers.splice(0).reverse()) remove();
126
+ };
127
+ try {
128
+ for (const definition of definitions) removers.push(api.registerSourceEditor.call(api, definition));
129
+ host.vm?.runtime?.on?.("RUNTIME_DISPOSED", dispose);
130
+ } catch (error) {
131
+ dispose();
132
+ throw error;
133
+ }
134
+ return dispose;
135
+ }
136
+ var source$1 = {
137
+ repository: "https://github.com/kubohiroya/turbowarp-extension-data-editor",
138
+ path: "src/json-value.ts",
139
+ symbol: "jsonValueSchema"
140
+ };
141
+ var jsonValueSchemaBundle = Object.freeze({
142
+ identity: {
143
+ ownerId: "@kubohiroya/turbowarp-extension-data-editor",
144
+ contractId: "json-value",
145
+ revision: "1",
146
+ variantId: "json-value"
147
+ },
148
+ dialect: "2020-12",
149
+ root: {
150
+ retrievalUri: "https://kubohiroya.github.io/turbowarp-extension-data-editor/schemas/json-value",
151
+ document: true,
152
+ artifact: {
153
+ role: "canonical-schema",
154
+ source: source$1
155
+ }
156
+ },
157
+ resources: [],
158
+ provenance: {
159
+ canonicalSources: [source$1],
160
+ generatedFiles: [],
161
+ referenceSchemas: [],
162
+ generationSources: []
163
+ }
164
+ });
165
+ var numberToken = /^-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?$/u;
166
+ var invalidNumber = Object.freeze({
167
+ kind: "invalid",
168
+ code: "fixed-number-input-invalid"
169
+ });
170
+ /**
171
+ * Parse a NEW numeric draft according to NP-finite-new-input-v1.
172
+ * Never apply this to opening/no-op raw values or use it as an owner validator.
173
+ * Keep the validated token so negative zero, precision and exponent spelling survive.
174
+ */
175
+ function parseFixedNumberInput(input) {
176
+ if (typeof input === "number") {
177
+ if (!Number.isFinite(input)) return invalidNumber;
178
+ return Object.freeze({
179
+ kind: "valid",
180
+ token: Object.is(input, -0) ? "-0" : String(input),
181
+ value: input
182
+ });
183
+ }
184
+ if (typeof input !== "string") return invalidNumber;
185
+ const token = input.trim();
186
+ if (!numberToken.test(token)) return invalidNumber;
187
+ const value = Number(token);
188
+ return Number.isFinite(value) ? Object.freeze({
189
+ kind: "valid",
190
+ token,
191
+ value
192
+ }) : invalidNumber;
193
+ }
194
+ var StructuredDataError = class extends Error {
195
+ constructor(code, message) {
196
+ super(`${code}: ${message}`);
197
+ this.name = "StructuredDataError";
198
+ this.code = code;
199
+ }
200
+ };
201
+ function parseJson(text) {
202
+ try {
203
+ const value = JSON.parse(String(text));
204
+ assertJsonValue(value);
205
+ return value;
206
+ } catch (error) {
207
+ if (error instanceof StructuredDataError) throw error;
208
+ throw new StructuredDataError("INVALID_JSON", "Input must be valid JSON.");
209
+ }
210
+ }
211
+ function assertJsonValue(value) {
212
+ if (value === null || typeof value === "string" || typeof value === "boolean") return;
213
+ if (typeof value === "number" && Number.isFinite(value)) return;
214
+ if (Array.isArray(value)) {
215
+ for (const item of value) assertJsonValue(item);
216
+ return;
217
+ }
218
+ if (typeof value === "object") {
219
+ for (const item of Object.values(value)) assertJsonValue(item);
220
+ return;
221
+ }
222
+ throw new StructuredDataError("INVALID_JSON", "JSON contains an unsupported value.");
223
+ }
224
+ var PARSE_LIMITS = {
225
+ maxInputBytes: 262144,
226
+ maxDepth: 64,
227
+ maxNodes: 5e4
228
+ };
229
+ function parseJsonDocument(source) {
230
+ validateInputSize(source);
231
+ const value = parseJson(source);
232
+ validateValueBudget(value, { nodes: 0 }, 0);
233
+ return value;
234
+ }
235
+ function parseYamlDocument(source) {
236
+ validateInputSize(source);
237
+ const lineCounter = new LineCounter();
238
+ const document = parseDocument(source, {
239
+ lineCounter,
240
+ prettyErrors: false,
241
+ schema: "core",
242
+ uniqueKeys: true
243
+ });
244
+ const problem = document.errors[0] ?? document.warnings[0];
245
+ if (problem) {
246
+ const { line, column } = yamlPosition(lineCounter, problem.pos[0]);
247
+ throw new StructuredDataError("INVALID_YAML", `${problem.code} at line ${line}, column ${column}: ${problem.message}`);
248
+ }
249
+ try {
250
+ return valueFromYamlNode(document.contents, { nodes: 0 }, 0, lineCounter);
251
+ } catch (error) {
252
+ if (error instanceof StructuredDataError) throw error;
253
+ throw new StructuredDataError("INVALID_YAML", error instanceof Error ? error.message : String(error));
254
+ }
255
+ }
256
+ function valueFromYamlNode(node, budget, depth, lineCounter) {
257
+ consumeBudget(budget, depth);
258
+ if (node === null) return null;
259
+ if (isAlias(node)) throw new StructuredDataError("INVALID_YAML", "YAML aliases are not allowed.");
260
+ if (isScalar(node)) {
261
+ const value = node.value;
262
+ if (value === null || typeof value === "string" || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value)) return value;
263
+ throw new StructuredDataError("INVALID_YAML", "YAML contains an unsupported scalar value.");
264
+ }
265
+ if (isSeq(node)) return node.items.map((item) => valueFromYamlNode(item, budget, depth + 1, lineCounter));
266
+ if (isMap(node)) return Object.fromEntries(node.items.map((item) => yamlPairToEntry(item, budget, depth, lineCounter)));
267
+ throw new StructuredDataError("INVALID_YAML", "YAML contains an unsupported node type.");
268
+ }
269
+ function yamlPairToEntry(pair, budget, depth, lineCounter) {
270
+ if (!isScalar(pair.key) || typeof pair.key.value !== "string") throw new StructuredDataError("INVALID_YAML", "YAML map keys must be strings.");
271
+ return [pair.key.value, valueFromYamlNode(pair.value, budget, depth + 1, lineCounter)];
272
+ }
273
+ function validateValueBudget(value, budget, depth) {
274
+ consumeBudget(budget, depth);
275
+ if (Array.isArray(value)) for (const item of value) validateValueBudget(item, budget, depth + 1);
276
+ else if (typeof value === "object" && value !== null) for (const item of Object.values(value)) validateValueBudget(item, budget, depth + 1);
277
+ }
278
+ function consumeBudget(budget, depth) {
279
+ if (depth > PARSE_LIMITS.maxDepth) throw new StructuredDataError("PARSE_LIMIT_EXCEEDED", `Data exceeds maximum nesting depth ${PARSE_LIMITS.maxDepth}.`);
280
+ budget.nodes += 1;
281
+ if (budget.nodes > PARSE_LIMITS.maxNodes) throw new StructuredDataError("PARSE_LIMIT_EXCEEDED", `Data exceeds maximum node count ${PARSE_LIMITS.maxNodes}.`);
282
+ }
283
+ function validateInputSize(source) {
284
+ const bytes = new TextEncoder().encode(source).byteLength;
285
+ if (bytes > PARSE_LIMITS.maxInputBytes) throw new StructuredDataError("PARSE_LIMIT_EXCEEDED", `Input is ${bytes} bytes; maximum is ${PARSE_LIMITS.maxInputBytes}.`);
286
+ }
287
+ function yamlPosition(counter, offset) {
288
+ const position = counter.linePos(offset);
289
+ return {
290
+ line: position.line,
291
+ column: position.col
292
+ };
293
+ }
294
+ var sourceEditorDefinitions = [
295
+ {
296
+ editorId: "json-document-source",
297
+ opcodes: ["parseJson"],
298
+ bindings: [sourceBinding("TEXT", jsonValueSchemaBundle, parseJsonDocument)]
299
+ },
300
+ {
301
+ editorId: "yaml-document-source",
302
+ opcodes: ["parseYaml"],
303
+ bindings: [sourceBinding("TEXT", jsonValueSchemaBundle, parseYamlDocument, parseYamlDocument, "yaml")]
304
+ },
305
+ {
306
+ editorId: "json-path-value-source",
307
+ opcodes: ["setJsonAtPath"],
308
+ bindings: [sourceBinding("VALUE", jsonValueSchemaBundle, parseJson)]
309
+ }
310
+ ].map(({ editorId, opcodes, bindings }) => ({
311
+ apiVersion: 1,
312
+ extensionId: extensionConfig.id,
313
+ editorId,
314
+ opcodes: opcodes.map((opcode) => `${extensionConfig.id}_${opcode}`),
315
+ bindings,
316
+ validationContext: () => ({
317
+ consumerId: extensionConfig.id,
318
+ nowMs: Date.now(),
319
+ settings: null
320
+ })
321
+ }));
322
+ var block_editor_fixed_inputs_schema_default = {
323
+ $schema: "https://json-schema.org/draft/2020-12/schema",
324
+ $id: "https://kubohiroya.github.io/turbowarp-structured-data/schemas/block-editor-fixed-inputs.schema.json",
325
+ $defs: { "forEachAtPath": {
326
+ "type": "object",
327
+ "properties": { "MAX": {
328
+ "type": "integer",
329
+ "minimum": 1,
330
+ "maximum": 1e3
331
+ } },
332
+ "required": ["MAX"],
333
+ "additionalProperties": true
334
+ } }
335
+ };
336
+ var source = {
337
+ repository: "turbowarp-structured-data",
338
+ path: "schemas/block-editor-fixed-inputs.schema.json",
339
+ symbol: "#/$defs/forEachAtPath/properties/MAX"
340
+ };
341
+ /** The on-disk owner Schema is canonical; this is its single-field projection. */
342
+ var fixedInputSchemaBundle = {
343
+ identity: {
344
+ ownerId: "turbowarp-structured-data",
345
+ contractId: "forEachAtPath.MAX",
346
+ revision: "fixed-inputs-v1",
347
+ variantId: "default"
348
+ },
349
+ dialect: "2020-12",
350
+ root: {
351
+ retrievalUri: "https://kubohiroya.github.io/turbowarp-structured-data/schemas/forEachAtPath-MAX.schema.json",
352
+ document: block_editor_fixed_inputs_schema_default.$defs.forEachAtPath.properties.MAX,
353
+ artifact: {
354
+ role: "canonical-schema",
355
+ source
356
+ }
357
+ },
358
+ resources: [],
359
+ provenance: {
360
+ canonicalSources: [source, {
361
+ repository: "turbowarp-structured-data",
362
+ path: "src/core.ts",
363
+ symbol: "iterationEntries"
364
+ }],
365
+ generatedFiles: [{
366
+ repository: "turbowarp-structured-data",
367
+ path: "docs/schemas/block-editor-fixed-inputs.schema.json"
368
+ }],
369
+ referenceSchemas: [],
370
+ generationSources: [{
371
+ repository: "turbowarp-structured-data",
372
+ path: "scripts/generate-fixed-input-docs.mjs"
373
+ }]
374
+ }
375
+ };
376
+ var fixedInputEditorDefinitions = [{
377
+ apiVersion: 1,
378
+ extensionId: extensionConfig.id,
379
+ editorId: "forEachAtPath-fixed-inputs",
380
+ opcodes: [`${extensionConfig.id}_forEachAtPath`],
381
+ contractId: "turbowarp-structured-data:forEachAtPath.MAX",
382
+ contractRevision: "fixed-inputs-v1",
383
+ bindings: [{
384
+ argument: "MAX",
385
+ schemaBundle: fixedInputSchemaBundle,
386
+ control: {
387
+ kind: "number",
388
+ label: "MAX"
389
+ },
390
+ read(value) {
391
+ if (typeof value !== "string" && typeof value !== "number") throw new TypeError("Expected a literal maximum.");
392
+ if (typeof value === "string" && value.trim() === "") throw new TypeError("Blank maximum stays readonly.");
393
+ const number = Number(value);
394
+ if (!Number.isFinite(number)) throw new TypeError("Maximum must be finite.");
395
+ return number;
396
+ },
397
+ encode(value) {
398
+ const parsed = parseFixedNumberInput(value);
399
+ if (parsed.kind !== "valid") throw new TypeError("Expected a finite numeric token.");
400
+ return parsed.token;
401
+ },
402
+ validateCandidate(value) {
403
+ const parsed = parseFixedNumberInput(value);
404
+ return parsed.kind === "valid" && Number.isInteger(parsed.value) && parsed.value >= 1 && parsed.value <= 1e3 ? [] : [{
405
+ path: "/inputs/MAX",
406
+ message: "Maximum must be an integer from 1 to 1000."
407
+ }];
408
+ }
409
+ }],
410
+ validationContext: () => ({
411
+ consumerId: extensionConfig.id,
412
+ nowMs: Date.now(),
413
+ settings: null
414
+ })
415
+ }];
416
+ /** Register metadata only; the GUI startup capability owns editor activation. */
417
+ function registerFixedInputEditors(scratch, definitions, runtimeOverride) {
418
+ const host = scratch;
419
+ const api = host.gui?.blockEditors;
420
+ if (!definitions.length || api?.apiVersion !== 1 || typeof api.registerFixedInputEditor !== "function") return () => {};
421
+ const runtime = runtimeOverride ?? host.vm?.runtime;
422
+ const removers = [];
423
+ let active = true;
424
+ const dispose = () => {
425
+ if (!active) return;
426
+ active = false;
427
+ runtime?.off?.("RUNTIME_DISPOSED", dispose);
428
+ for (const remove of removers.splice(0).reverse()) remove();
429
+ };
430
+ try {
431
+ for (const definition of definitions) {
432
+ const remove = api.registerFixedInputEditor.call(api, definition);
433
+ if (typeof remove !== "function") throw new TypeError("Invalid fixed input registration disposer.");
434
+ removers.push(remove);
435
+ }
436
+ runtime?.on?.("RUNTIME_DISPOSED", dispose);
437
+ } catch (error) {
438
+ dispose();
439
+ throw error;
440
+ }
441
+ return dispose;
442
+ }
443
+ export { canonicalBundle, fixedInputEditorDefinitions, fixedInputSchemaBundle, registerFixedInputEditors, registerSourceEditors, sourceBinding, sourceEditorDefinitions };
@@ -0,0 +1,2 @@
1
+ import type { SourceEditorOptions } from '@kubohiroya/turbowarp-extension-data-editor/schema';
2
+ export declare const sourceEditorDefinitions: readonly SourceEditorOptions[];
@@ -0,0 +1,9 @@
1
+ import type { FixedInputEditorOptions } from '@kubohiroya/turbowarp-extension-data-editor';
2
+ export declare const fixedInputEditorDefinitions: readonly FixedInputEditorOptions[];
3
+ interface RuntimeLifetime {
4
+ on?: (event: string, listener: () => void) => void;
5
+ off?: (event: string, listener: () => void) => void;
6
+ }
7
+ /** Register metadata only; the GUI startup capability owns editor activation. */
8
+ export declare function registerFixedInputEditors(scratch: unknown, definitions: readonly FixedInputEditorOptions[], runtimeOverride?: RuntimeLifetime): () => void;
9
+ export {};
@@ -0,0 +1,3 @@
1
+ import type { SchemaBundle } from '@kubohiroya/turbowarp-extension-data-editor/schema';
2
+ /** The on-disk owner Schema is canonical; this is its single-field projection. */
3
+ export declare const fixedInputSchemaBundle: SchemaBundle;
@@ -0,0 +1,8 @@
1
+ import type { SchemaBundle, SchemaDocument, SourceBinding, SourceEditorOptions } from '@kubohiroya/turbowarp-extension-data-editor/schema';
2
+ export declare function canonicalBundle(ownerId: string, contractId: string, document: SchemaDocument, ownerSource: {
3
+ path: string;
4
+ symbol: string;
5
+ }): SchemaBundle;
6
+ export declare function sourceBinding(argument: string, bundle: SchemaBundle, read: (source: string) => unknown, check?: (source: string) => unknown, format?: 'json' | 'yaml', uiSchema?: SourceBinding['variants'][number]['uiSchema']): SourceBinding;
7
+ /** The optional capability keeps legacy hosts and the startup flag safely inactive. */
8
+ export declare function registerSourceEditors(scratch: unknown, definitions: readonly SourceEditorOptions[]): () => void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubohiroya/turbowarp-structured-data",
3
- "version": "0.6.1",
3
+ "version": "0.6.3",
4
4
  "description": "Named structured data with JSON/YAML codecs and deterministic path operations for TurboWarp.",
5
5
  "author": "Hiroya Kubo <hiroya@cuc.ac.jp>",
6
6
  "license": "MPL-2.0",
@@ -32,7 +32,8 @@
32
32
  "README.md",
33
33
  "README.ja.md",
34
34
  "THIRD_PARTY_NOTICES.md",
35
- "LICENSE"
35
+ "LICENSE",
36
+ "lib-editor/"
36
37
  ],
37
38
  "publishConfig": {
38
39
  "access": "public",
@@ -45,8 +46,8 @@
45
46
  "scripts": {
46
47
  "dev": "vite build --watch",
47
48
  "typecheck": "tsc --noEmit",
48
- "build": "vite build",
49
- "lint": "eslint src tests scripts vite.config.ts eslint.config.ts",
49
+ "build": "vite build && pnpm run build:editor",
50
+ "lint": "eslint src tests scripts vite.config.ts vite.editor.config.ts eslint.config.ts",
50
51
  "test": "vitest run",
51
52
  "docs": "node scripts/generate-readme.ts && node scripts/generate-fixed-input-docs.mjs",
52
53
  "docs:check": "node scripts/generate-readme.ts --check && node scripts/generate-fixed-input-docs.mjs --check",
@@ -54,12 +55,13 @@
54
55
  "repo:check": "node scripts/check-repo.ts",
55
56
  "pack:check": "npm pack --dry-run --ignore-scripts",
56
57
  "check": "pnpm run typecheck && pnpm run lint && pnpm run test && pnpm run docs:check && pnpm run check:dist && pnpm run repo:check && pnpm run pack:check",
57
- "check:dist": "pnpm run build && node scripts/check-dist.ts"
58
+ "check:dist": "pnpm run build && node scripts/check-dist.ts",
59
+ "build:editor": "tsc -p tsconfig.editor.json && vite build --config vite.editor.config.ts"
58
60
  },
59
61
  "devDependencies": {
60
62
  "@eslint/js": "^9.39.5",
61
63
  "@kubohiroya/turbowarp-extension-manifest": "0.3.2",
62
- "@kubohiroya/turbowarp-named-data": "0.3.1",
64
+ "@kubohiroya/turbowarp-named-data": "0.4.0",
63
65
  "@kubohiroya/vite-plugin-turbowarp-extension": "0.6.1",
64
66
  "@types/node": "^24.13.3",
65
67
  "eslint": "^9.39.5",
@@ -68,19 +70,27 @@
68
70
  "typescript-eslint": "^8.66.0",
69
71
  "vite": "^8.2.2",
70
72
  "vitest": "^5.0.0",
71
- "react-dom": "18.3.1"
73
+ "react-dom": "18.3.1",
74
+ "@kubohiroya/turbowarp-extension-data-editor": "0.2.1",
75
+ "@kubohiroya/turbowarp-named-functions": "0.1.1"
72
76
  },
73
77
  "dependencies": {
74
- "yaml": "^2.9.1",
75
- "@kubohiroya/turbowarp-extension-data-editor": "0.2.1"
78
+ "yaml": "^2.9.1"
76
79
  },
77
80
  "peerDependencies": {
78
- "@kubohiroya/turbowarp-named-data": "0.3.1"
81
+ "@kubohiroya/turbowarp-named-data": ">=0.4.0 <1"
79
82
  },
80
- "gitHead": "7429d0707e0c46099fa89d09f456a43a29919395",
83
+ "gitHead": "870ebcaa4071e0eacb4d36a6d9e98734e51f2361",
81
84
  "x-turbowarp-source": {
82
- "revision": "7429d0707e0c46099fa89d09f456a43a29919395",
85
+ "revision": "870ebcaa4071e0eacb4d36a6d9e98734e51f2361",
83
86
  "revisionRole": "base-commit",
84
87
  "sourceState": "local-release-overlay"
88
+ },
89
+ "exports": {
90
+ "./*": "./*",
91
+ "./editor-metadata": {
92
+ "types": "./lib-editor/src/editor-metadata.d.ts",
93
+ "import": "./lib-editor/src/editor-metadata.js"
94
+ }
85
95
  }
86
96
  }