@kubohiroya/turbowarp-extension-data-editor 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -6
- package/dist/build-info.json +4 -4
- package/dist/fixed-input-branches.d.ts +12 -0
- package/dist/fixed-input-branches.js +180 -0
- package/dist/fixed-input-editor.d.ts +39 -0
- package/dist/fixed-input-editor.js +538 -0
- package/dist/fixed-input-schema.d.ts +15 -0
- package/dist/fixed-input-schema.js +21 -0
- package/dist/fixed-input-snapshot.d.ts +7 -0
- package/dist/fixed-input-snapshot.js +180 -0
- package/dist/fixed-input-types.d.ts +138 -0
- package/dist/fixed-input-types.js +23 -0
- package/dist/fixed-input-view.d.ts +14 -0
- package/dist/fixed-input-view.js +168 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +4 -0
- package/dist/react.css +42 -0
- package/dist/react.d.ts +3 -0
- package/dist/react.js +10 -1
- package/dist/registry.d.ts +4 -0
- package/dist/registry.js +45 -7
- package/dist/rjsf.d.ts +24 -1
- package/dist/rjsf.js +22 -2
- package/dist/schema.d.ts +2 -0
- package/dist/schema.js +1 -0
- package/dist/session.d.ts +3 -1
- package/dist/session.js +205 -7
- package/dist/turbowarp/bridge.d.ts +2 -1
- package/dist/turbowarp/bridge.js +34 -5
- package/dist/turbowarp/fixed-fields.d.ts +82 -0
- package/dist/turbowarp/fixed-fields.js +311 -0
- package/dist/turbowarp/fixed-transaction.d.ts +18 -0
- package/dist/turbowarp/fixed-transaction.js +460 -0
- package/dist/turbowarp/host.d.ts +3 -8
- package/dist/turbowarp/host.js +68 -1
- package/dist/turbowarp.d.ts +4 -0
- package/dist/turbowarp.js +1 -1
- package/dist/types.d.ts +5 -0
- package/dist/view.js +4 -0
- package/dist/webpack4/index.js +22 -5
- package/dist/webpack4/metafile.json +588 -319
- package/dist/webpack4/react.js +9 -1
- package/dist/webpack4/react.js.map +2 -2
- package/dist/webpack4/rjsf.js +249 -34
- package/dist/webpack4/rjsf.js.map +4 -4
- package/dist/webpack4/schema.js +6 -3
- package/dist/webpack4/{shared-KQOUMWJ4.js → shared-75QIWLZY.js} +1129 -1244
- package/dist/webpack4/{shared-KQOUMWJ4.js.map → shared-75QIWLZY.js.map} +4 -4
- package/dist/webpack4/shared-FYUILHED.js +1 -0
- package/dist/webpack4/shared-GQLPPRT2.js +210 -0
- package/dist/webpack4/shared-GQLPPRT2.js.map +7 -0
- package/dist/webpack4/shared-P2JBRZFP.js +2206 -0
- package/dist/webpack4/shared-P2JBRZFP.js.map +7 -0
- package/dist/webpack4/turbowarp.js +910 -9
- package/dist/webpack4/turbowarp.js.map +4 -4
- package/docs/fixed-input-contracts.md +77 -0
- package/docs/stage-2-publication.ja.md +6 -0
- package/package.json +8 -1
- package/dist/webpack4/shared-CQB3NG2A.js +0 -1
- package/dist/webpack4/shared-MRPGBJIH.js +0 -75
- package/dist/webpack4/shared-MRPGBJIH.js.map +0 -7
- package/dist/webpack4/shared-N6YQE2X6.js +0 -1029
- package/dist/webpack4/shared-N6YQE2X6.js.map +0 -7
- /package/dist/webpack4/{shared-CQB3NG2A.js.map → shared-FYUILHED.js.map} +0 -0
|
@@ -0,0 +1,538 @@
|
|
|
1
|
+
import { compileSchemaBundle } from './schema-engine.js';
|
|
2
|
+
import { isJsonRecord, snapshotJson } from './schema-json.js';
|
|
3
|
+
import { isLiteral } from './data.js';
|
|
4
|
+
import { parseFixedNumberInput } from './fixed-input-schema.js';
|
|
5
|
+
import { captureFixedInputDeclaration, captureFixedSnapshot } from './fixed-input-snapshot.js';
|
|
6
|
+
import { resolveFixedBranches, validateFixedBranchChanges } from './fixed-input-branches.js';
|
|
7
|
+
function fixedDeclarationError() { throw new TypeError('Invalid fixed editor declaration.'); }
|
|
8
|
+
function plainDeclaration(value) {
|
|
9
|
+
if (!value || typeof value !== 'object' || Array.isArray(value) ||
|
|
10
|
+
(Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) ||
|
|
11
|
+
Object.getOwnPropertySymbols(value).length)
|
|
12
|
+
fixedDeclarationError();
|
|
13
|
+
}
|
|
14
|
+
/** Callback-bearing declarations cannot be JSON-copied as a whole. Read own data only. */
|
|
15
|
+
function ownData(value, key, optional = false) {
|
|
16
|
+
if (!value || typeof value !== 'object')
|
|
17
|
+
return fixedDeclarationError();
|
|
18
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
19
|
+
if (!descriptor) {
|
|
20
|
+
if (optional)
|
|
21
|
+
return undefined;
|
|
22
|
+
return fixedDeclarationError();
|
|
23
|
+
}
|
|
24
|
+
if (!('value' in descriptor))
|
|
25
|
+
return fixedDeclarationError();
|
|
26
|
+
return descriptor.value;
|
|
27
|
+
}
|
|
28
|
+
function declarationText(value) {
|
|
29
|
+
if (typeof value !== 'string' || !value)
|
|
30
|
+
return fixedDeclarationError();
|
|
31
|
+
return value;
|
|
32
|
+
}
|
|
33
|
+
/** Capture callback-bearing array elements by index, without running input iterators. */
|
|
34
|
+
function declarationArray(value) {
|
|
35
|
+
if (!Array.isArray(value) || Object.getOwnPropertySymbols(value).length)
|
|
36
|
+
return fixedDeclarationError();
|
|
37
|
+
const length = ownData(value, 'length');
|
|
38
|
+
if (typeof length !== 'number' || !Number.isSafeInteger(length) || length < 0 || length > 100000)
|
|
39
|
+
return fixedDeclarationError();
|
|
40
|
+
for (const key of Object.getOwnPropertyNames(value)) {
|
|
41
|
+
if (key !== 'length' && (!/^(0|[1-9][0-9]*)$/.test(key) || Number(key) >= length))
|
|
42
|
+
return fixedDeclarationError();
|
|
43
|
+
}
|
|
44
|
+
const items = [];
|
|
45
|
+
for (let i = 0; i < length; i++)
|
|
46
|
+
items.push(ownData(value, String(i)));
|
|
47
|
+
return Object.freeze(items);
|
|
48
|
+
}
|
|
49
|
+
function declarationJson(value) {
|
|
50
|
+
try {
|
|
51
|
+
return snapshotJson(value);
|
|
52
|
+
}
|
|
53
|
+
catch (_a) {
|
|
54
|
+
return fixedDeclarationError();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function fixedDeclaration(value) {
|
|
58
|
+
try {
|
|
59
|
+
return captureFixedInputDeclaration(value);
|
|
60
|
+
}
|
|
61
|
+
catch (_a) {
|
|
62
|
+
return fixedDeclarationError();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function captureOpcodes(value, extensionId) {
|
|
66
|
+
const items = declarationJson(value);
|
|
67
|
+
if (!Array.isArray(items) || !items.length || items.some(opcode => typeof opcode !== 'string' ||
|
|
68
|
+
!opcode.startsWith(`${extensionId}_`) || opcode === `${extensionId}_`) || new Set(items).size !== items.length)
|
|
69
|
+
return fixedDeclarationError();
|
|
70
|
+
return Object.freeze(items);
|
|
71
|
+
}
|
|
72
|
+
/** Capture exactly the callbacks and declaration values subsequently registered. */
|
|
73
|
+
export function captureFixedInputEditorDefinition(value) {
|
|
74
|
+
plainDeclaration(value);
|
|
75
|
+
const apiVersion = ownData(value, 'apiVersion');
|
|
76
|
+
const extensionId = declarationText(ownData(value, 'extensionId'));
|
|
77
|
+
const editorId = declarationText(ownData(value, 'editorId'));
|
|
78
|
+
const saveContract = ownData(value, 'saveContract');
|
|
79
|
+
if (apiVersion !== 1 || saveContract !== 'prepared-fixed-v1')
|
|
80
|
+
return fixedDeclarationError();
|
|
81
|
+
const opcodes = captureOpcodes(ownData(value, 'opcodes'), extensionId);
|
|
82
|
+
const branchRules = ownData(value, 'branchRules', true);
|
|
83
|
+
const declaration = fixedDeclaration({
|
|
84
|
+
contractId: declarationText(ownData(value, 'contractId')), contractRevision: declarationText(ownData(value, 'contractRevision')),
|
|
85
|
+
editableArguments: ownData(value, 'editableArguments'),
|
|
86
|
+
readOnlyArguments: ownData(value, 'readOnlyArguments'),
|
|
87
|
+
dependencies: ownData(value, 'dependencies'),
|
|
88
|
+
...(branchRules === undefined ? {} : { branchRules: branchRules })
|
|
89
|
+
});
|
|
90
|
+
const read = ownData(value, 'read');
|
|
91
|
+
const validate = ownData(value, 'validate');
|
|
92
|
+
const createPatch = ownData(value, 'createPatch');
|
|
93
|
+
const readFixed = ownData(value, 'readFixed');
|
|
94
|
+
const prepareFixedSave = ownData(value, 'prepareFixedSave');
|
|
95
|
+
const mount = ownData(value, 'mount', true);
|
|
96
|
+
const describeFixed = ownData(value, 'describeFixed', true);
|
|
97
|
+
if (typeof read !== 'function' || typeof validate !== 'function' || typeof createPatch !== 'function' ||
|
|
98
|
+
typeof readFixed !== 'function' || typeof prepareFixedSave !== 'function' ||
|
|
99
|
+
(mount !== undefined && typeof mount !== 'function') ||
|
|
100
|
+
(describeFixed !== undefined && typeof describeFixed !== 'function'))
|
|
101
|
+
return fixedDeclarationError();
|
|
102
|
+
return Object.freeze({ apiVersion, extensionId, editorId, opcodes, saveContract, ...declaration,
|
|
103
|
+
read: read, validate: validate, createPatch: createPatch,
|
|
104
|
+
readFixed: readFixed, prepareFixedSave: prepareFixedSave,
|
|
105
|
+
...(describeFixed === undefined ? {} : { describeFixed: describeFixed }),
|
|
106
|
+
...(mount === undefined ? {} : { mount: mount }) });
|
|
107
|
+
}
|
|
108
|
+
function drain(value) {
|
|
109
|
+
try {
|
|
110
|
+
void Promise.prototype.then.call(value, () => undefined, () => undefined);
|
|
111
|
+
}
|
|
112
|
+
catch ( /* Reject arbitrary thenables without invoking then. */_a) { /* Reject arbitrary thenables without invoking then. */ }
|
|
113
|
+
}
|
|
114
|
+
function copySync(value) {
|
|
115
|
+
try {
|
|
116
|
+
return snapshotJson(value);
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
drain(value);
|
|
120
|
+
throw error;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function freeze(value) {
|
|
124
|
+
if (value && typeof value === 'object' && !Object.isFrozen(value)) {
|
|
125
|
+
Object.values(value).forEach(freeze);
|
|
126
|
+
Object.freeze(value);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
function sameProjection(left, right) {
|
|
130
|
+
if (Object.is(left, right))
|
|
131
|
+
return true;
|
|
132
|
+
if (left === null || right === null || typeof left !== 'object' || typeof right !== 'object')
|
|
133
|
+
return false;
|
|
134
|
+
if (Array.isArray(left) || Array.isArray(right)) {
|
|
135
|
+
return Array.isArray(left) && Array.isArray(right) && left.length === right.length && left.every((value, index) => sameProjection(value, right[index]));
|
|
136
|
+
}
|
|
137
|
+
const names = Object.keys(left);
|
|
138
|
+
return names.length === Object.keys(right).length && names.every(name => Object.hasOwn(right, name) && sameProjection(left[name], right[name]));
|
|
139
|
+
}
|
|
140
|
+
const pointer = (name) => `/${name.replace(/~/g, '~0').replace(/\//g, '~1')}`;
|
|
141
|
+
export function isFixedInputEditor(definition) {
|
|
142
|
+
return definition.saveContract === 'prepared-fixed-v1';
|
|
143
|
+
}
|
|
144
|
+
/** Capture a synchronous callback result without invoking getters, iterators, or toJSON. */
|
|
145
|
+
export function captureFixedPreparedSave(definition, draft, snapshot) {
|
|
146
|
+
const value = copySync(definition.prepareFixedSave(draft, snapshot));
|
|
147
|
+
if (!isJsonRecord(value))
|
|
148
|
+
throw new TypeError('Invalid fixed prepared save.');
|
|
149
|
+
if (value.kind === 'invalid' && Array.isArray(value.issues) && value.issues.length > 0) {
|
|
150
|
+
const issues = [];
|
|
151
|
+
for (let i = 0; i < value.issues.length; i++) {
|
|
152
|
+
const issue = value.issues[i];
|
|
153
|
+
if (!isJsonRecord(issue) || typeof issue.message !== 'string' || (issue.path !== undefined && typeof issue.path !== 'string'))
|
|
154
|
+
throw new TypeError('Invalid fixed issue.');
|
|
155
|
+
issues.push(Object.freeze({ message: issue.message, ...(issue.path === undefined ? {} : { path: issue.path }) }));
|
|
156
|
+
}
|
|
157
|
+
return Object.freeze({ kind: 'invalid', issues: Object.freeze(issues) });
|
|
158
|
+
}
|
|
159
|
+
if (value.kind !== 'ready' || !Array.isArray(value.changes) || !isJsonRecord(value.context) ||
|
|
160
|
+
typeof value.context.consumerId !== 'string' || !value.context.consumerId ||
|
|
161
|
+
typeof value.context.nowMs !== 'number' || !Number.isFinite(value.context.nowMs) || !Object.hasOwn(value.context, 'settings'))
|
|
162
|
+
throw new TypeError('Invalid fixed prepared save.');
|
|
163
|
+
const changes = [];
|
|
164
|
+
for (let i = 0; i < value.changes.length; i++) {
|
|
165
|
+
const change = value.changes[i];
|
|
166
|
+
if (!isJsonRecord(change) || typeof change.argument !== 'string' || !isLiteral(change.value))
|
|
167
|
+
throw new TypeError('Invalid fixed change.');
|
|
168
|
+
changes.push(Object.freeze({ argument: change.argument, value: change.value }));
|
|
169
|
+
}
|
|
170
|
+
freeze(value.context);
|
|
171
|
+
return Object.freeze({ kind: 'ready', changes: Object.freeze(changes), context: value.context });
|
|
172
|
+
}
|
|
173
|
+
/** Fixed inputs are additive: legacy callbacks cannot accidentally save their drafts. */
|
|
174
|
+
export function createFixedInputEditor(options) {
|
|
175
|
+
var _a;
|
|
176
|
+
plainDeclaration(options);
|
|
177
|
+
const apiVersion = ownData(options, 'apiVersion');
|
|
178
|
+
const extensionId = declarationText(ownData(options, 'extensionId'));
|
|
179
|
+
const editorId = declarationText(ownData(options, 'editorId'));
|
|
180
|
+
const contractId = declarationText(ownData(options, 'contractId'));
|
|
181
|
+
const contractRevision = declarationText(ownData(options, 'contractRevision'));
|
|
182
|
+
const contextProvider = ownData(options, 'validationContext');
|
|
183
|
+
const opcodes = captureOpcodes(ownData(options, 'opcodes'), extensionId);
|
|
184
|
+
const inputBindings = declarationArray(ownData(options, 'bindings'));
|
|
185
|
+
const readOnlyArguments = ownData(options, 'readOnlyArguments', true);
|
|
186
|
+
const dependencies = ownData(options, 'dependencies', true);
|
|
187
|
+
const branchPolicies = ownData(options, 'branchPolicies', true);
|
|
188
|
+
if (apiVersion !== 1 || !inputBindings.length || typeof contextProvider !== 'function')
|
|
189
|
+
throw new TypeError('Invalid fixed editor options.');
|
|
190
|
+
const provider = contextProvider;
|
|
191
|
+
const compileBinding = (binding) => {
|
|
192
|
+
plainDeclaration(binding);
|
|
193
|
+
const argument = declarationText(ownData(binding, 'argument'));
|
|
194
|
+
const read = ownData(binding, 'read');
|
|
195
|
+
const encode = ownData(binding, 'encode');
|
|
196
|
+
const validateCandidate = ownData(binding, 'validateCandidate');
|
|
197
|
+
const schemaBundle = declarationJson(ownData(binding, 'schemaBundle'));
|
|
198
|
+
const inputControl = ownData(binding, 'control', true);
|
|
199
|
+
let control;
|
|
200
|
+
if (inputControl !== undefined) {
|
|
201
|
+
const item = declarationJson(inputControl);
|
|
202
|
+
if (!isJsonRecord(item) || !['text', 'number', 'enum', 'color'].includes(item.kind) ||
|
|
203
|
+
(item.label !== undefined && typeof item.label !== 'string') ||
|
|
204
|
+
(item.choices !== undefined && (!Array.isArray(item.choices) || item.choices.some(choice => !isJsonRecord(choice) || !Object.hasOwn(choice, 'value') || (choice.label !== undefined && typeof choice.label !== 'string')))))
|
|
205
|
+
return fixedDeclarationError();
|
|
206
|
+
freeze(item);
|
|
207
|
+
control = item;
|
|
208
|
+
}
|
|
209
|
+
if (typeof read !== 'function' || typeof encode !== 'function' || typeof validateCandidate !== 'function')
|
|
210
|
+
throw new TypeError('Invalid fixed binding callbacks.');
|
|
211
|
+
const result = compileSchemaBundle(schemaBundle);
|
|
212
|
+
if (result.kind !== 'compiled')
|
|
213
|
+
throw new TypeError('Invalid fixed input schema.');
|
|
214
|
+
freeze(schemaBundle);
|
|
215
|
+
return Object.freeze({ argument, read: read, encode: encode,
|
|
216
|
+
validateCandidate: validateCandidate, validator: result.validator, schemaBundle,
|
|
217
|
+
...(control === undefined ? {} : { control }) });
|
|
218
|
+
};
|
|
219
|
+
const bindings = inputBindings.map(compileBinding);
|
|
220
|
+
const policyBindings = [];
|
|
221
|
+
const policyRules = [];
|
|
222
|
+
if (branchPolicies !== undefined) {
|
|
223
|
+
for (const input of declarationArray(branchPolicies)) {
|
|
224
|
+
plainDeclaration(input);
|
|
225
|
+
const selector = declarationText(ownData(input, 'selector'));
|
|
226
|
+
const normalization = ownData(input, 'normalize', true);
|
|
227
|
+
const cases = [];
|
|
228
|
+
const variants = [];
|
|
229
|
+
for (const caseInput of declarationArray(ownData(input, 'cases'))) {
|
|
230
|
+
plainDeclaration(caseInput);
|
|
231
|
+
const requirements = ownData(caseInput, 'requireOnSelectorChange', true);
|
|
232
|
+
const overrides = ownData(caseInput, 'bindings', true);
|
|
233
|
+
cases.push({ values: declarationJson(ownData(caseInput, 'values')),
|
|
234
|
+
activeDependents: declarationJson(ownData(caseInput, 'activeDependents')),
|
|
235
|
+
...(requirements === undefined ? {} : { requireOnSelectorChange: declarationJson(requirements) }) });
|
|
236
|
+
variants.push(overrides === undefined ? [] : declarationArray(overrides).map(compileBinding));
|
|
237
|
+
}
|
|
238
|
+
policyRules.push({ selector, cases, ...(normalization === undefined ? {} : { normalize: declarationJson(normalization) }) });
|
|
239
|
+
policyBindings.push(variants);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
const declaration = fixedDeclaration({ contractId, contractRevision,
|
|
243
|
+
editableArguments: bindings.map(binding => binding.argument), readOnlyArguments: (readOnlyArguments !== null && readOnlyArguments !== void 0 ? readOnlyArguments : []),
|
|
244
|
+
dependencies: (dependencies !== null && dependencies !== void 0 ? dependencies : []),
|
|
245
|
+
...(branchPolicies === undefined ? {} : { branchRules: policyRules }) });
|
|
246
|
+
for (const [index, rule] of ((_a = declaration.branchRules) !== null && _a !== void 0 ? _a : []).entries()) {
|
|
247
|
+
const group = declaration.dependencies.find(item => item.selector === rule.selector);
|
|
248
|
+
for (const variants of policyBindings[index]) {
|
|
249
|
+
if (new Set(variants.map(binding => binding.argument)).size !== variants.length ||
|
|
250
|
+
variants.some(binding => !group.dependents.includes(binding.argument)))
|
|
251
|
+
return fixedDeclarationError();
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
const diagnostic = (code, argument) => Object.freeze({
|
|
255
|
+
message: `Fixed input validation failed: ${code}; contract=${JSON.stringify(contractId)}${argument === undefined ? '' : `; argument=${JSON.stringify(argument)}`}.`,
|
|
256
|
+
...(argument === undefined ? {} : { path: pointer(argument) })
|
|
257
|
+
});
|
|
258
|
+
const literal = (snapshot, argument) => {
|
|
259
|
+
var _a;
|
|
260
|
+
const metadata = snapshot.fixedContext.inputs[argument];
|
|
261
|
+
return ((_a = snapshot.arguments[argument]) === null || _a === void 0 ? void 0 : _a.kind) === 'literal' && (metadata === null || metadata === void 0 ? void 0 : metadata.role) === 'editable' &&
|
|
262
|
+
(metadata.source === 'field' || metadata.source === 'shadow');
|
|
263
|
+
};
|
|
264
|
+
const project = (binding, snapshot) => {
|
|
265
|
+
const slot = snapshot.arguments[binding.argument];
|
|
266
|
+
if ((slot === null || slot === void 0 ? void 0 : slot.kind) !== 'literal' || !literal(snapshot, binding.argument))
|
|
267
|
+
return { readOnly: true };
|
|
268
|
+
try {
|
|
269
|
+
return { value: copySync(binding.read(slot.value)), changed: false };
|
|
270
|
+
}
|
|
271
|
+
catch (_a) {
|
|
272
|
+
return { readOnly: true };
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
const entryFor = (draft, argument) => {
|
|
276
|
+
const entry = isJsonRecord(draft.inputs) && Object.hasOwn(draft.inputs, argument) ? draft.inputs[argument] : undefined;
|
|
277
|
+
return isJsonRecord(entry) ? entry : undefined;
|
|
278
|
+
};
|
|
279
|
+
const selection = (snapshot, draft) => {
|
|
280
|
+
var _a;
|
|
281
|
+
const arguments_ = { ...snapshot.arguments };
|
|
282
|
+
if (draft)
|
|
283
|
+
for (const rule of (_a = declaration.branchRules) !== null && _a !== void 0 ? _a : []) {
|
|
284
|
+
const entry = entryFor(draft, rule.selector);
|
|
285
|
+
if ((entry === null || entry === void 0 ? void 0 : entry.changed) === true && Object.hasOwn(entry, 'value') && isLiteral(entry.value)) {
|
|
286
|
+
Object.defineProperty(arguments_, rule.selector, { value: { kind: 'literal', value: entry.value }, enumerable: true });
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
const branches = resolveFixedBranches(declaration, snapshot, arguments_);
|
|
290
|
+
const selected = new Map(bindings.map(binding => [binding.argument, binding]));
|
|
291
|
+
const states = new Map();
|
|
292
|
+
const unruled = new Set();
|
|
293
|
+
for (const group of declaration.dependencies) {
|
|
294
|
+
if (branches.some(branch => branch.selector === group.selector))
|
|
295
|
+
continue;
|
|
296
|
+
for (const argument of [group.selector, ...group.dependents]) {
|
|
297
|
+
states.set(argument, 'readonly');
|
|
298
|
+
unruled.add(argument);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
for (const [policyIndex, branch] of branches.entries()) {
|
|
302
|
+
const group = declaration.dependencies.find(item => item.selector === branch.selector);
|
|
303
|
+
if (!branch.selected) {
|
|
304
|
+
for (const argument of group.dependents)
|
|
305
|
+
states.set(argument, 'readonly');
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
for (const argument of group.dependents)
|
|
309
|
+
if (!branch.selected.activeDependents.includes(argument))
|
|
310
|
+
states.set(argument, 'hidden');
|
|
311
|
+
const caseIndex = branch.rule.cases.indexOf(branch.selected);
|
|
312
|
+
for (const binding of policyBindings[policyIndex][caseIndex])
|
|
313
|
+
selected.set(binding.argument, binding);
|
|
314
|
+
}
|
|
315
|
+
return { branches, bindings: [...selected.values()], states, unruled };
|
|
316
|
+
};
|
|
317
|
+
const initialProjection = (argument, snapshot) => {
|
|
318
|
+
const binding = selection(snapshot).bindings.find(item => item.argument === argument);
|
|
319
|
+
return project(binding, snapshot);
|
|
320
|
+
};
|
|
321
|
+
const parseEntry = (_binding, entry, original) => {
|
|
322
|
+
if (original.readOnly === true) {
|
|
323
|
+
if (sameProjection(original, entry))
|
|
324
|
+
return { changed: false, value: null };
|
|
325
|
+
if (entry.repair !== true)
|
|
326
|
+
throw new TypeError();
|
|
327
|
+
}
|
|
328
|
+
if (!Object.hasOwn(entry, 'value') || typeof entry.changed !== 'boolean' || entry.readOnly === true)
|
|
329
|
+
throw new TypeError();
|
|
330
|
+
let value = copySync(entry.value);
|
|
331
|
+
let token;
|
|
332
|
+
if (Object.hasOwn(entry, 'buffer')) {
|
|
333
|
+
if (!entry.changed || typeof entry.buffer !== 'string')
|
|
334
|
+
throw new TypeError();
|
|
335
|
+
const parsed = parseFixedNumberInput(entry.buffer);
|
|
336
|
+
if (parsed.kind !== 'valid' || typeof value !== 'number' || !Object.is(parsed.value, value))
|
|
337
|
+
throw new TypeError();
|
|
338
|
+
token = parsed.token;
|
|
339
|
+
value = token;
|
|
340
|
+
}
|
|
341
|
+
if (token === undefined && original.readOnly !== true && sameProjection(original.value, value))
|
|
342
|
+
return { changed: false, value };
|
|
343
|
+
if (!entry.changed)
|
|
344
|
+
throw new TypeError();
|
|
345
|
+
return { changed: true, value, ...(token === undefined ? {} : { token }) };
|
|
346
|
+
};
|
|
347
|
+
const validateExact = (binding, candidate, context, snapshot) => {
|
|
348
|
+
const projectedCandidate = copySync(binding.read(candidate));
|
|
349
|
+
if (binding.validator.validate(projectedCandidate, { consumerId: context.consumerId }).kind !== 'valid')
|
|
350
|
+
return diagnostic('schema-data-invalid', binding.argument);
|
|
351
|
+
const owner = copySync(binding.validateCandidate(candidate, context, snapshot));
|
|
352
|
+
if (!Array.isArray(owner))
|
|
353
|
+
throw new TypeError();
|
|
354
|
+
for (const issue of owner) {
|
|
355
|
+
if (!isJsonRecord(issue) || typeof issue.message !== 'string' || (issue.path !== undefined && typeof issue.path !== 'string'))
|
|
356
|
+
throw new TypeError();
|
|
357
|
+
}
|
|
358
|
+
return owner.length ? diagnostic('owner-input-invalid', binding.argument) : undefined;
|
|
359
|
+
};
|
|
360
|
+
const description = (draft, snapshot) => {
|
|
361
|
+
const current = selection(snapshot, draft);
|
|
362
|
+
const inputs = Object.create(null);
|
|
363
|
+
const issues = [];
|
|
364
|
+
const changes = [];
|
|
365
|
+
const describe = (argument, binding) => {
|
|
366
|
+
var _a, _b, _c, _d;
|
|
367
|
+
const metadata = snapshot.fixedContext.inputs[argument];
|
|
368
|
+
const slot = snapshot.arguments[argument];
|
|
369
|
+
const entry = entryFor(draft, argument);
|
|
370
|
+
const projected = binding ? project(binding, snapshot) : undefined;
|
|
371
|
+
const schema = binding === null || binding === void 0 ? void 0 : binding.schemaBundle.root.document;
|
|
372
|
+
const schemaType = schema && typeof schema === 'object' && !Array.isArray(schema) ? schema.type : undefined;
|
|
373
|
+
const value = (_a = entry === null || entry === void 0 ? void 0 : entry.value) !== null && _a !== void 0 ? _a : projected === null || projected === void 0 ? void 0 : projected.value;
|
|
374
|
+
const valueType = schemaType === 'number' || schemaType === 'integer' ? 'number' :
|
|
375
|
+
schemaType === 'string' ? 'string' : typeof value === 'number' ? 'number' : 'string';
|
|
376
|
+
const explicit = binding === null || binding === void 0 ? void 0 : binding.control;
|
|
377
|
+
const state = (_b = current.states.get(argument)) !== null && _b !== void 0 ? _b : (binding && literal(snapshot, argument) ? 'editable' : 'readonly');
|
|
378
|
+
const schemaEnum = schema && typeof schema === 'object' && !Array.isArray(schema) ? schema.enum : undefined;
|
|
379
|
+
const choices = (_c = explicit === null || explicit === void 0 ? void 0 : explicit.choices) !== null && _c !== void 0 ? _c : (Array.isArray(schemaEnum) ? schemaEnum.map(value => ({ value: value })) : undefined);
|
|
380
|
+
const control = (_d = explicit === null || explicit === void 0 ? void 0 : explicit.kind) !== null && _d !== void 0 ? _d : (choices ? 'enum' : valueType === 'number' ? 'number' : 'text');
|
|
381
|
+
const rawDisplay = (slot === null || slot === void 0 ? void 0 : slot.kind) === 'literal' ? String(slot.value) : undefined;
|
|
382
|
+
const reason = state === 'hidden' ? 'Inactive branch; its draft is retained and is not saved.' :
|
|
383
|
+
current.states.get(argument) === 'readonly' ? 'The selector is unavailable; the dependent input is read-only.' :
|
|
384
|
+
state === 'readonly' ? 'This input is read-only; connected expressions are not evaluated.' : undefined;
|
|
385
|
+
inputs[argument] = Object.freeze({ state, control, valueType,
|
|
386
|
+
...(rawDisplay === undefined ? {} : { rawDisplay }),
|
|
387
|
+
...((explicit === null || explicit === void 0 ? void 0 : explicit.label) === undefined ? {} : { label: explicit.label }), ...(choices === undefined ? {} : { choices }),
|
|
388
|
+
...(explicit && Object.hasOwn(explicit, 'reset') ? { reset: explicit.reset } : {}),
|
|
389
|
+
...(reason === undefined ? {} : { reason }), ...((metadata === null || metadata === void 0 ? void 0 : metadata.source) === 'reporter' ? { unverified: true } : {}) });
|
|
390
|
+
if (!binding || state !== 'editable' || !entry)
|
|
391
|
+
return;
|
|
392
|
+
try {
|
|
393
|
+
const parsed = parseEntry(binding, entry, initialProjection(argument, snapshot));
|
|
394
|
+
if (!parsed.changed)
|
|
395
|
+
return;
|
|
396
|
+
const projection = parsed.token === undefined ? parsed.value : parseFixedNumberInput(parsed.token);
|
|
397
|
+
const data = typeof projection === 'object' && projection !== null && 'kind' in projection && projection.kind === 'valid' ? projection.value : projection;
|
|
398
|
+
if (binding.validator.validate(data, { consumerId: contractId }).kind !== 'valid')
|
|
399
|
+
issues.push(diagnostic('schema-data-invalid', argument));
|
|
400
|
+
if (isLiteral(parsed.value))
|
|
401
|
+
changes.push({ argument, value: parsed.value });
|
|
402
|
+
}
|
|
403
|
+
catch (_e) {
|
|
404
|
+
issues.push(diagnostic('fixed-input-invalid', argument));
|
|
405
|
+
}
|
|
406
|
+
};
|
|
407
|
+
for (const binding of current.bindings)
|
|
408
|
+
describe(binding.argument, binding);
|
|
409
|
+
for (const argument of declaration.readOnlyArguments)
|
|
410
|
+
describe(argument);
|
|
411
|
+
issues.push(...validateFixedBranchChanges(declaration, snapshot, changes));
|
|
412
|
+
for (const branch of current.branches) {
|
|
413
|
+
if (!branch.selected || !changes.some(change => change.argument === branch.selector))
|
|
414
|
+
continue;
|
|
415
|
+
for (const argument of branch.selected.activeDependents) {
|
|
416
|
+
if (changes.some(change => change.argument === argument))
|
|
417
|
+
continue;
|
|
418
|
+
const binding = current.bindings.find(binding => binding.argument === argument);
|
|
419
|
+
const slot = snapshot.arguments[argument];
|
|
420
|
+
if ((slot === null || slot === void 0 ? void 0 : slot.kind) !== 'literal')
|
|
421
|
+
continue;
|
|
422
|
+
try {
|
|
423
|
+
const projected = copySync(binding.read(slot.value));
|
|
424
|
+
if (binding.validator.validate(projected, { consumerId: contractId }).kind !== 'valid')
|
|
425
|
+
issues.push(diagnostic('schema-data-invalid', argument));
|
|
426
|
+
}
|
|
427
|
+
catch (_a) {
|
|
428
|
+
issues.push(diagnostic('fixed-input-invalid', argument));
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
return Object.freeze({ inputs: Object.freeze(inputs), issues: Object.freeze(issues), branches: Object.freeze(current.branches.map(({ selector, status, value }) => Object.freeze({ selector, status, ...(value === undefined ? {} : { value }) }))) });
|
|
433
|
+
};
|
|
434
|
+
return Object.freeze({
|
|
435
|
+
apiVersion, extensionId, editorId, opcodes, ...declaration,
|
|
436
|
+
saveContract: 'prepared-fixed-v1',
|
|
437
|
+
read: () => { throw new TypeError('Fixed editors require a compatible host.'); },
|
|
438
|
+
validate: () => [diagnostic('fixed-host-required')],
|
|
439
|
+
createPatch: () => { throw new TypeError('Fixed editors require a compatible host.'); },
|
|
440
|
+
readFixed: (snapshot) => {
|
|
441
|
+
const inputs = {};
|
|
442
|
+
for (const binding of selection(snapshot).bindings)
|
|
443
|
+
Object.defineProperty(inputs, binding.argument, { value: project(binding, snapshot), enumerable: true });
|
|
444
|
+
return { inputs };
|
|
445
|
+
},
|
|
446
|
+
describeFixed: description,
|
|
447
|
+
prepareFixedSave: (draft, snapshot) => {
|
|
448
|
+
const issues = [];
|
|
449
|
+
const changes = [];
|
|
450
|
+
let context;
|
|
451
|
+
try {
|
|
452
|
+
const value = copySync(provider());
|
|
453
|
+
if (!isJsonRecord(value) || typeof value.consumerId !== 'string' || !value.consumerId || typeof value.nowMs !== 'number' || !Number.isFinite(value.nowMs) || !Object.hasOwn(value, 'settings'))
|
|
454
|
+
throw new TypeError();
|
|
455
|
+
freeze(value);
|
|
456
|
+
context = value;
|
|
457
|
+
}
|
|
458
|
+
catch (_a) {
|
|
459
|
+
return { kind: 'invalid', issues: [diagnostic('owner-context-invalid')] };
|
|
460
|
+
}
|
|
461
|
+
const current = selection(snapshot, draft);
|
|
462
|
+
const candidates = new Map();
|
|
463
|
+
const finalArguments = { ...snapshot.arguments };
|
|
464
|
+
for (const binding of current.bindings) {
|
|
465
|
+
const { argument } = binding;
|
|
466
|
+
try {
|
|
467
|
+
const entry = entryFor(draft, argument);
|
|
468
|
+
if (!entry)
|
|
469
|
+
throw new TypeError();
|
|
470
|
+
if (current.states.get(argument) === 'hidden')
|
|
471
|
+
continue;
|
|
472
|
+
if (current.states.get(argument) === 'readonly') {
|
|
473
|
+
if (sameProjection(project(binding, snapshot), entry))
|
|
474
|
+
continue;
|
|
475
|
+
if (current.unruled.has(argument)) {
|
|
476
|
+
issues.push(diagnostic('fixed-dependency-validation-unavailable', argument));
|
|
477
|
+
continue;
|
|
478
|
+
}
|
|
479
|
+
throw new TypeError();
|
|
480
|
+
}
|
|
481
|
+
const original = initialProjection(argument, snapshot);
|
|
482
|
+
if (!literal(snapshot, argument)) {
|
|
483
|
+
if (sameProjection(original, entry))
|
|
484
|
+
continue;
|
|
485
|
+
throw new TypeError();
|
|
486
|
+
}
|
|
487
|
+
const parsed = parseEntry(binding, entry, original);
|
|
488
|
+
if (!parsed.changed)
|
|
489
|
+
continue;
|
|
490
|
+
const raw = binding.encode(parsed.value, context);
|
|
491
|
+
if (!isLiteral(raw)) {
|
|
492
|
+
drain(raw);
|
|
493
|
+
throw new TypeError();
|
|
494
|
+
}
|
|
495
|
+
if (parsed.token !== undefined && raw !== parsed.token)
|
|
496
|
+
throw new TypeError();
|
|
497
|
+
candidates.set(argument, raw);
|
|
498
|
+
Object.defineProperty(finalArguments, argument, { value: { kind: 'literal', value: raw }, enumerable: true, configurable: true });
|
|
499
|
+
const slot = snapshot.arguments[argument];
|
|
500
|
+
if ((slot === null || slot === void 0 ? void 0 : slot.kind) === 'literal' && !Object.is(raw, slot.value))
|
|
501
|
+
changes.push(Object.freeze({ argument, value: raw }));
|
|
502
|
+
}
|
|
503
|
+
catch (_b) {
|
|
504
|
+
issues.push(diagnostic('fixed-input-invalid', argument));
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
issues.push(...validateFixedBranchChanges(declaration, snapshot, changes));
|
|
508
|
+
const finalBranches = resolveFixedBranches(declaration, snapshot, finalArguments);
|
|
509
|
+
// Encoding a selector must not silently choose a different codec from the displayed finite branch.
|
|
510
|
+
for (let index = 0; index < current.branches.length; index++) {
|
|
511
|
+
if (current.branches[index].selected !== finalBranches[index].selected)
|
|
512
|
+
issues.push(diagnostic('selector-codec-mismatch', current.branches[index].selector));
|
|
513
|
+
}
|
|
514
|
+
const validateNames = new Set(candidates.keys());
|
|
515
|
+
for (const branch of finalBranches)
|
|
516
|
+
if (branch.selected && changes.some(change => change.argument === branch.selector)) {
|
|
517
|
+
for (const argument of branch.selected.activeDependents)
|
|
518
|
+
validateNames.add(argument);
|
|
519
|
+
}
|
|
520
|
+
const finalSnapshot = captureFixedSnapshot({ ...snapshot, arguments: finalArguments }, snapshot, declaration);
|
|
521
|
+
for (const argument of validateNames) {
|
|
522
|
+
const binding = current.bindings.find(binding => binding.argument === argument);
|
|
523
|
+
const slot = finalArguments[argument];
|
|
524
|
+
if ((slot === null || slot === void 0 ? void 0 : slot.kind) !== 'literal')
|
|
525
|
+
continue;
|
|
526
|
+
try {
|
|
527
|
+
const issue = validateExact(binding, slot.value, context, finalSnapshot);
|
|
528
|
+
if (issue)
|
|
529
|
+
issues.push(issue);
|
|
530
|
+
}
|
|
531
|
+
catch (_c) {
|
|
532
|
+
issues.push(diagnostic('fixed-input-invalid', argument));
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
return issues.length ? { kind: 'invalid', issues } : { kind: 'ready', changes, context };
|
|
536
|
+
}
|
|
537
|
+
});
|
|
538
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** A changed numeric input is distinct from an existing raw field or display projection. */
|
|
2
|
+
export type FixedNumberInputResult = {
|
|
3
|
+
readonly kind: 'valid';
|
|
4
|
+
readonly token: string;
|
|
5
|
+
readonly value: number;
|
|
6
|
+
} | {
|
|
7
|
+
readonly kind: 'invalid';
|
|
8
|
+
readonly code: 'fixed-number-input-invalid';
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Parse a NEW numeric draft according to NP-finite-new-input-v1.
|
|
12
|
+
* Never apply this to opening/no-op raw values or use it as an owner validator.
|
|
13
|
+
* Keep the validated token so negative zero, precision and exponent spelling survive.
|
|
14
|
+
*/
|
|
15
|
+
export declare function parseFixedNumberInput(input: unknown): FixedNumberInputResult;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const numberToken = /^-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?$/u;
|
|
2
|
+
const invalidNumber = Object.freeze({ kind: 'invalid', code: 'fixed-number-input-invalid' });
|
|
3
|
+
/**
|
|
4
|
+
* Parse a NEW numeric draft according to NP-finite-new-input-v1.
|
|
5
|
+
* Never apply this to opening/no-op raw values or use it as an owner validator.
|
|
6
|
+
* Keep the validated token so negative zero, precision and exponent spelling survive.
|
|
7
|
+
*/
|
|
8
|
+
export function parseFixedNumberInput(input) {
|
|
9
|
+
if (typeof input === 'number') {
|
|
10
|
+
if (!Number.isFinite(input))
|
|
11
|
+
return invalidNumber;
|
|
12
|
+
return Object.freeze({ kind: 'valid', token: Object.is(input, -0) ? '-0' : String(input), value: input });
|
|
13
|
+
}
|
|
14
|
+
if (typeof input !== 'string')
|
|
15
|
+
return invalidNumber;
|
|
16
|
+
const token = input.trim();
|
|
17
|
+
if (!numberToken.test(token))
|
|
18
|
+
return invalidNumber;
|
|
19
|
+
const value = Number(token);
|
|
20
|
+
return Number.isFinite(value) ? Object.freeze({ kind: 'valid', token, value }) : invalidNumber;
|
|
21
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { FixedBlockSnapshot, FixedInputDeclaration } from './fixed-input-types.js';
|
|
2
|
+
import type { BlockReference } from './types.js';
|
|
3
|
+
export declare function captureFixedInputDeclaration(value: FixedInputDeclaration): FixedInputDeclaration;
|
|
4
|
+
export declare const validateFixedInputDeclaration: typeof captureFixedInputDeclaration;
|
|
5
|
+
/** Preserve raw literals with Object.is semantics while copying metadata as finite JSON. */
|
|
6
|
+
export declare function captureFixedSnapshot(value: FixedBlockSnapshot, reference?: BlockReference, declaration?: FixedInputDeclaration): FixedBlockSnapshot;
|
|
7
|
+
export declare function sameFixedSnapshot(left: FixedBlockSnapshot, right: FixedBlockSnapshot): boolean;
|