openyida 2026.7.19 → 2026.7.21
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 +14 -0
- package/bin/yida.js +107 -18
- package/lib/app/create-app.js +12 -1
- package/lib/app/create-form/args.js +13 -0
- package/lib/app/create-form.js +154 -67
- package/lib/app/create-page.js +45 -9
- package/lib/app/get-schema.js +65 -14
- package/lib/app/import-app.js +92 -41
- package/lib/app/publish.js +289 -556
- package/lib/app/schema-field-resolution.js +370 -0
- package/lib/app/services/app-reader.js +85 -0
- package/lib/app/services/app-service.js +235 -0
- package/lib/app/services/canvas-page-compiler.js +91 -0
- package/lib/app/services/canvas-page-schema-builder.js +149 -0
- package/lib/app/services/field-bindings.js +68 -0
- package/lib/app/services/form-compiler.js +1922 -0
- package/lib/app/services/form-mode-service.js +182 -0
- package/lib/app/services/form-schema-patcher.js +333 -0
- package/lib/app/services/form-schema-reader.js +59 -0
- package/lib/app/services/form-service.js +379 -0
- package/lib/app/services/form-validation.js +562 -0
- package/lib/app/services/native-page-compiler.js +92 -0
- package/lib/app/services/native-page-schema-builder.js +428 -0
- package/lib/app/services/page-resource-service.js +185 -0
- package/lib/app/update-app.js +18 -6
- package/lib/auth/token-auth.js +34 -3
- package/lib/auth/token-store.js +22 -4
- package/lib/bridge/bridge.js +37 -5
- package/lib/core/agent-capabilities.js +5 -3
- package/lib/core/command-manifest.js +40 -0
- package/lib/core/doctor.js +15 -4
- package/lib/core/env-cmd.js +1 -1
- package/lib/core/env.js +7 -6
- package/lib/core/legacy-schema-guard.js +408 -0
- package/lib/core/locales/en.js +8 -46
- package/lib/core/locales/zh.js +8 -46
- package/lib/core/sample.js +1 -1
- package/lib/core/utils.js +334 -66
- package/lib/core/yida-client.js +104 -3
- package/lib/formula/evaluate.js +8 -66
- package/lib/formula/field-refs.js +83 -0
- package/lib/process/configure-process.js +44 -1992
- package/lib/process/create-process.js +54 -87
- package/lib/process/services/process-compiler.js +2056 -0
- package/lib/process/services/process-reader.js +232 -0
- package/lib/process/services/process-resource-service.js +571 -0
- package/lib/process/services/process-service.js +122 -0
- package/lib/process/services/process-stage-checkpoint.js +246 -0
- package/lib/report/append.js +7 -26
- package/lib/report/http.js +31 -5
- package/lib/report/index.js +10 -2
- package/lib/samples/yida-canvas-custom-page/business-list.canvas.jsx +63 -14
- package/lib/samples/yida-canvas-custom-page/dashboard-overview.canvas.jsx +176 -32
- package/lib/samples/yida-canvas-custom-page/data-management.canvas.jsx +228 -14
- package/lib/samples/yida-canvas-custom-page/split-pane-detail.canvas.jsx +204 -8
- package/lib/schema/adapters/app-adapter.js +210 -0
- package/lib/schema/adapters/form-adapter.js +1362 -0
- package/lib/schema/adapters/keys.js +19 -0
- package/lib/schema/adapters/page-adapter.js +566 -0
- package/lib/schema/adapters/process-adapter.js +519 -0
- package/lib/schema/applier.js +1878 -0
- package/lib/schema/apply-store.js +1083 -0
- package/lib/schema/command.js +645 -0
- package/lib/schema/dependency-graph.js +83 -0
- package/lib/schema/errors.js +481 -0
- package/lib/schema/hash.js +9 -0
- package/lib/schema/manifest-limits.js +177 -0
- package/lib/schema/manifest-loader.js +309 -0
- package/lib/schema/manifest-schema-v1.json +193 -0
- package/lib/schema/normalize-manifest.js +281 -0
- package/lib/schema/page-canvas-foundation.js +447 -0
- package/lib/schema/page-data-source-builder.js +561 -0
- package/lib/schema/page-foundation.js +556 -0
- package/lib/schema/page-source-loader.js +314 -0
- package/lib/schema/planner.js +691 -0
- package/lib/schema/remote-dispatch-boundary.js +39 -0
- package/lib/schema/remote-missing.js +21 -0
- package/lib/schema/remote-reader.js +189 -0
- package/lib/schema/resource-registry.js +157 -0
- package/lib/schema/server-revision.js +71 -0
- package/lib/schema/sort.js +28 -0
- package/lib/schema/state-store.js +589 -0
- package/package.json +2 -2
- package/scripts/postinstall.js +8 -12
- package/scripts/validate-package-size.js +1 -1
- package/yida-skills/SKILL.md +114 -29
- package/yida-skills/references/schema-as-code-phase1.md +99 -0
- package/yida-skills/references/setup-and-env.md +59 -59
- package/yida-skills/references/task-retrospective.md +6 -6
- package/yida-skills/skills/yida-app/SKILL.md +60 -14
- package/yida-skills/skills/yida-canvas-custom-page/SKILL.md +18 -7
- package/yida-skills/skills/yida-canvas-custom-page/references/data-bridge-guide.md +3 -3
- package/yida-skills/skills/yida-canvas-custom-page/references/page-generation-guide.md +7 -1
- package/yida-skills/skills/yida-create-app/SKILL.md +21 -3
- package/yida-skills/skills/yida-create-form-page/SKILL.md +19 -1
- package/yida-skills/skills/yida-create-page/SKILL.md +16 -3
- package/yida-skills/skills/yida-create-process/SKILL.md +100 -4
- package/yida-skills/skills/yida-custom-page/SKILL.md +19 -4
- package/yida-skills/skills/yida-dashboard/SKILL.md +1 -1
- package/yida-skills/skills/yida-dashboard/references/pitfalls.md +1 -1
- package/yida-skills/skills/yida-get-schema/SKILL.md +49 -5
- package/yida-skills/skills/yida-i18n/SKILL.md +1 -1
- package/yida-skills/skills/yida-integration/SKILL.md +7 -1
- package/yida-skills/skills/yida-login/SKILL.md +50 -99
- package/yida-skills/skills/yida-page-config/SKILL.md +7 -1
- package/yida-skills/skills/yida-page-uiux/references/app/blueprint.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/app/navigation-patterns.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/app/role-journey.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/dashboard/app-blueprint.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/dashboard/chart-contracts.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/dashboard/component-contracts.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/dashboard/layout-patterns.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/dashboard/theme-recipes.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/detail/object-narrative.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/detail/section-patterns.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/detail/timeline-and-related.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/landing/assets-workflow.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/landing/industry-playbooks.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/landing/research-levels.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/landing/section-patterns.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/list/drawer-detail.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/list/empty-loading-error.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/list/filter-patterns.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/list/table-patterns.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/scenes/screen.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/workbench/entry-patterns.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/workbench/portal-layouts.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/workbench/task-feed.md +0 -1
- package/yida-skills/skills/yida-page-uiux/workflow/output-decision-block.md +1 -1
- package/yida-skills/skills/yida-process-rule/SKILL.md +35 -1
- package/yida-skills/skills/yida-publish-page/SKILL.md +46 -4
- package/yida-skills/skills/yida-report/SKILL.md +7 -1
- package/yida-skills/skills-index.json +14 -46
- package/yida-skills/skills/large-file-write/SKILL.md +0 -93
- package/yida-skills/skills/large-file-write/references/write-patterns.md +0 -147
- package/yida-skills/skills/large-file-write/scripts/write.js +0 -157
- package/yida-skills/skills/sls-log-workbench/SKILL.md +0 -134
- package/yida-skills/skills/sls-log-workbench/references/workbench-reference.md +0 -130
- package/yida-skills/skills/sls-log-workbench/sls-query.js +0 -295
|
@@ -0,0 +1,561 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const crypto = require('crypto');
|
|
4
|
+
const { isDeepStrictEqual } = require('util');
|
|
5
|
+
const { schemaError } = require('./errors');
|
|
6
|
+
const { hashStable } = require('./state-store');
|
|
7
|
+
const {
|
|
8
|
+
buildDefaultPageDataSource,
|
|
9
|
+
buildNativePageSchemaContent,
|
|
10
|
+
extractPageDataSource,
|
|
11
|
+
mergePageDataSource,
|
|
12
|
+
} = require('../app/services/native-page-schema-builder');
|
|
13
|
+
const {
|
|
14
|
+
buildCanvasPageSchemaContent,
|
|
15
|
+
} = require('../app/services/canvas-page-schema-builder');
|
|
16
|
+
const {
|
|
17
|
+
NATIVE_PAGE_PROFILE,
|
|
18
|
+
isCompiledNativePage,
|
|
19
|
+
} = require('../app/services/native-page-compiler');
|
|
20
|
+
const {
|
|
21
|
+
CANVAS_PAGE_PROFILE,
|
|
22
|
+
isCompiledCanvasPage,
|
|
23
|
+
} = require('../app/services/canvas-page-compiler');
|
|
24
|
+
const {
|
|
25
|
+
projectCanvasPageSchema,
|
|
26
|
+
} = require('./page-canvas-foundation');
|
|
27
|
+
const {
|
|
28
|
+
computeNativePageShellFingerprint,
|
|
29
|
+
createNativePageShellProfile,
|
|
30
|
+
projectNativePageSchema,
|
|
31
|
+
} = require('./page-foundation');
|
|
32
|
+
|
|
33
|
+
const DISPLAY_FORM_TYPE = 'display';
|
|
34
|
+
const INITIAL_PROFILE_CONTRACT_VERSION = 1;
|
|
35
|
+
const HASH_PATTERN = /^sha256:[a-f0-9]{64}$/;
|
|
36
|
+
const PROFILE_ID_PATTERN = /^[a-z][a-z0-9._-]{0,63}$/;
|
|
37
|
+
const INITIAL_PROFILES = new WeakSet();
|
|
38
|
+
const INITIAL_ROOT_KEYS = Object.freeze(['gmtModified', 'i18nData', 'pages', 'status']);
|
|
39
|
+
const INITIAL_PAGE_KEYS = Object.freeze(['componentsTree']);
|
|
40
|
+
const INITIAL_TREE_KEYS = Object.freeze(['dataSource']);
|
|
41
|
+
const INITIAL_DATA_SOURCE_KEYS = Object.freeze([
|
|
42
|
+
'globalConfig',
|
|
43
|
+
'list',
|
|
44
|
+
'offline',
|
|
45
|
+
'online',
|
|
46
|
+
'sync',
|
|
47
|
+
]);
|
|
48
|
+
|
|
49
|
+
function createDataSourceOnlyShellProfile(input = {}) {
|
|
50
|
+
const profileId = normalizeProfileId(input.profileId);
|
|
51
|
+
const fingerprint = normalizeHash(
|
|
52
|
+
input.fingerprint,
|
|
53
|
+
'SCHEMA_PAGE_FOUNDATION_INITIAL_PROFILE_INVALID'
|
|
54
|
+
);
|
|
55
|
+
const profile = Object.freeze({
|
|
56
|
+
contractVersion: INITIAL_PROFILE_CONTRACT_VERSION,
|
|
57
|
+
fingerprint,
|
|
58
|
+
profileId,
|
|
59
|
+
});
|
|
60
|
+
INITIAL_PROFILES.add(profile);
|
|
61
|
+
return profile;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function computeDataSourceOnlyShellFingerprint(schemaInput) {
|
|
65
|
+
const schema = normalizeSchema(schemaInput);
|
|
66
|
+
inspectDataSourceOnlyShell(schema, 'SCHEMA_PAGE_FOUNDATION_SCHEMA_INVALID');
|
|
67
|
+
return hashStable(normalizeInitialFingerprint(schema));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function projectDataSourceOnlyShell(input = {}) {
|
|
71
|
+
const observedIdentity = normalizeObservedIdentity(input);
|
|
72
|
+
const shellProfile = requireInitialProfile(input.shellProfile);
|
|
73
|
+
const schema = normalizeSchema(input.schema);
|
|
74
|
+
const structure = inspectDataSourceOnlyShell(schema, 'SCHEMA_PAGE_FOUNDATION_CONFLICT');
|
|
75
|
+
const fingerprint = hashStable(normalizeInitialFingerprint(schema));
|
|
76
|
+
if (fingerprint !== shellProfile.fingerprint) {
|
|
77
|
+
foundationError(
|
|
78
|
+
'SCHEMA_PAGE_FOUNDATION_CONFLICT',
|
|
79
|
+
'Initial page Schema does not match the approved data-source shell profile.'
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
const managed = {
|
|
83
|
+
formType: observedIdentity.formType,
|
|
84
|
+
profile: NATIVE_PAGE_PROFILE,
|
|
85
|
+
title: observedIdentity.title,
|
|
86
|
+
};
|
|
87
|
+
return {
|
|
88
|
+
classification: 'PAGE_CREATED',
|
|
89
|
+
dataSourceHash: hashStable(structure.dataSource),
|
|
90
|
+
managed,
|
|
91
|
+
managedHash: hashStable(managed),
|
|
92
|
+
remoteSchemaHash: hashInitialRemoteSchema(schema),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function buildDataSourceOnlyNativePagePatch(input = {}) {
|
|
97
|
+
const compiledPage = requireCompiledPage(input.compiledPage);
|
|
98
|
+
const observedIdentity = normalizeObservedIdentity(input);
|
|
99
|
+
const formUuid = normalizeBinding(input.formUuid);
|
|
100
|
+
const operationId = normalizeHash(
|
|
101
|
+
input.operationId,
|
|
102
|
+
'SCHEMA_PAGE_FOUNDATION_TRANSITION_INVALID'
|
|
103
|
+
);
|
|
104
|
+
const desiredManaged = buildDesiredManaged(observedIdentity.title, compiledPage);
|
|
105
|
+
if (
|
|
106
|
+
input.expectedManagedHash !== undefined &&
|
|
107
|
+
hashStable(desiredManaged) !== input.expectedManagedHash
|
|
108
|
+
) {
|
|
109
|
+
foundationError(
|
|
110
|
+
'SCHEMA_PAGE_FOUNDATION_CONFLICT',
|
|
111
|
+
'Desired managed page content changed before patch preparation.'
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const initialSchema = normalizeSchema(input.remoteSchema);
|
|
116
|
+
const initialProjection = projectDataSourceOnlyShell({
|
|
117
|
+
observedFormType: observedIdentity.formType,
|
|
118
|
+
observedTitle: observedIdentity.title,
|
|
119
|
+
schema: initialSchema,
|
|
120
|
+
shellProfile: input.shellProfile,
|
|
121
|
+
});
|
|
122
|
+
if (
|
|
123
|
+
input.expectedInitialRemoteSchemaHash !== undefined &&
|
|
124
|
+
input.expectedInitialRemoteSchemaHash !== initialProjection.remoteSchemaHash
|
|
125
|
+
) {
|
|
126
|
+
foundationError(
|
|
127
|
+
'SCHEMA_PAGE_FOUNDATION_CONFLICT',
|
|
128
|
+
'Initial page Schema changed before the native page patch was prepared.'
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const initialDataSource = extractInitialDataSource(initialSchema);
|
|
133
|
+
const generators = createDeterministicGenerators({
|
|
134
|
+
compiledHash: compiledPage.compiledHash,
|
|
135
|
+
operationId,
|
|
136
|
+
sourceHash: compiledPage.sourceHash,
|
|
137
|
+
});
|
|
138
|
+
let preparedSchema;
|
|
139
|
+
try {
|
|
140
|
+
preparedSchema = JSON.parse(buildNativePageSchemaContent(
|
|
141
|
+
compiledPage.source,
|
|
142
|
+
compiledPage.compiled,
|
|
143
|
+
formUuid,
|
|
144
|
+
{
|
|
145
|
+
existingDataSource: initialDataSource,
|
|
146
|
+
nextNodeId: generators.nextNodeId,
|
|
147
|
+
nextSuffix: generators.nextSuffix,
|
|
148
|
+
}
|
|
149
|
+
));
|
|
150
|
+
} catch (error) {
|
|
151
|
+
foundationError(
|
|
152
|
+
'SCHEMA_PAGE_FOUNDATION_SCHEMA_INVALID',
|
|
153
|
+
'Native page Schema could not be prepared.'
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const preparedDataSource = extractPageDataSource(preparedSchema);
|
|
158
|
+
assertPreparedDataSourceContract(initialDataSource, preparedDataSource, formUuid);
|
|
159
|
+
const savedShellProfile = createNativePageShellProfile({
|
|
160
|
+
fingerprint: computeNativePageShellFingerprint(preparedSchema),
|
|
161
|
+
profileId: 'sac-page-deterministic-native-v1',
|
|
162
|
+
});
|
|
163
|
+
const savedProjection = projectNativePageSchema({
|
|
164
|
+
observedFormType: observedIdentity.formType,
|
|
165
|
+
observedTitle: observedIdentity.title,
|
|
166
|
+
schema: preparedSchema,
|
|
167
|
+
shellProfile: savedShellProfile,
|
|
168
|
+
});
|
|
169
|
+
if (
|
|
170
|
+
savedProjection.classification !== 'SCHEMA_SAVED' ||
|
|
171
|
+
!isDeepStrictEqual(savedProjection.managed, desiredManaged)
|
|
172
|
+
) {
|
|
173
|
+
foundationError(
|
|
174
|
+
'SCHEMA_PAGE_FOUNDATION_SCHEMA_INVALID',
|
|
175
|
+
'Prepared native page Schema does not match desired managed content.'
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return deepFreeze({
|
|
180
|
+
initialDataSourceHash: hashStable(initialDataSource),
|
|
181
|
+
initialManagedHash: initialProjection.managedHash,
|
|
182
|
+
initialRemoteSchemaHash: initialProjection.remoteSchemaHash,
|
|
183
|
+
managedHash: savedProjection.managedHash,
|
|
184
|
+
preparedDataSourceHash: hashStable(preparedDataSource),
|
|
185
|
+
preparedRemoteSchemaHash: savedProjection.remoteSchemaHash,
|
|
186
|
+
schema: clonePlain(preparedSchema),
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function buildDataSourceOnlyCanvasPagePatch(input = {}) {
|
|
191
|
+
const compiledPage = requireCompiledCanvas(input.compiledPage);
|
|
192
|
+
const observedIdentity = normalizeObservedIdentity(input);
|
|
193
|
+
const formUuid = normalizeBinding(input.formUuid);
|
|
194
|
+
const operationId = normalizeHash(
|
|
195
|
+
input.operationId,
|
|
196
|
+
'SCHEMA_PAGE_FOUNDATION_TRANSITION_INVALID'
|
|
197
|
+
);
|
|
198
|
+
const desiredManaged = buildCanvasDesiredManaged(observedIdentity.title, compiledPage);
|
|
199
|
+
if (
|
|
200
|
+
input.expectedManagedHash !== undefined &&
|
|
201
|
+
hashStable(desiredManaged) !== input.expectedManagedHash
|
|
202
|
+
) {
|
|
203
|
+
foundationError(
|
|
204
|
+
'SCHEMA_PAGE_FOUNDATION_CONFLICT',
|
|
205
|
+
'Desired managed Canvas page content changed before patch preparation.'
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const initialSchema = normalizeSchema(input.remoteSchema);
|
|
210
|
+
const initialProjection = projectDataSourceOnlyShell({
|
|
211
|
+
observedFormType: observedIdentity.formType,
|
|
212
|
+
observedTitle: observedIdentity.title,
|
|
213
|
+
schema: initialSchema,
|
|
214
|
+
shellProfile: input.shellProfile,
|
|
215
|
+
});
|
|
216
|
+
if (
|
|
217
|
+
input.expectedInitialRemoteSchemaHash !== undefined &&
|
|
218
|
+
input.expectedInitialRemoteSchemaHash !== initialProjection.remoteSchemaHash
|
|
219
|
+
) {
|
|
220
|
+
foundationError(
|
|
221
|
+
'SCHEMA_PAGE_FOUNDATION_CONFLICT',
|
|
222
|
+
'Initial page Schema changed before the Canvas page patch was prepared.'
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const generators = createDeterministicGenerators({
|
|
227
|
+
compiledHash: compiledPage.compiledHash,
|
|
228
|
+
operationId,
|
|
229
|
+
sourceHash: compiledPage.sourceHash,
|
|
230
|
+
});
|
|
231
|
+
let preparedSchema;
|
|
232
|
+
try {
|
|
233
|
+
preparedSchema = JSON.parse(buildCanvasPageSchemaContent(
|
|
234
|
+
compiledPage.source,
|
|
235
|
+
compiledPage.runtimeCode,
|
|
236
|
+
compiledPage.importedModules,
|
|
237
|
+
formUuid,
|
|
238
|
+
{
|
|
239
|
+
nextNodeId: generators.nextNodeId,
|
|
240
|
+
nextSuffix: generators.nextSuffix,
|
|
241
|
+
}
|
|
242
|
+
));
|
|
243
|
+
} catch (error) {
|
|
244
|
+
foundationError(
|
|
245
|
+
'SCHEMA_PAGE_FOUNDATION_SCHEMA_INVALID',
|
|
246
|
+
'Canvas page Schema could not be prepared.'
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const savedProjection = projectCanvasPageSchema({
|
|
251
|
+
observedFormType: observedIdentity.formType,
|
|
252
|
+
observedTitle: observedIdentity.title,
|
|
253
|
+
schema: preparedSchema,
|
|
254
|
+
});
|
|
255
|
+
if (
|
|
256
|
+
savedProjection.classification !== 'SCHEMA_SAVED' ||
|
|
257
|
+
!isDeepStrictEqual(savedProjection.managed, desiredManaged)
|
|
258
|
+
) {
|
|
259
|
+
foundationError(
|
|
260
|
+
'SCHEMA_PAGE_FOUNDATION_SCHEMA_INVALID',
|
|
261
|
+
'Prepared Canvas page Schema does not match desired managed content.'
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return deepFreeze({
|
|
266
|
+
initialManagedHash: initialProjection.managedHash,
|
|
267
|
+
initialRemoteSchemaHash: initialProjection.remoteSchemaHash,
|
|
268
|
+
managedHash: savedProjection.managedHash,
|
|
269
|
+
preparedRemoteSchemaHash: savedProjection.remoteSchemaHash,
|
|
270
|
+
schema: clonePlain(preparedSchema),
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function inspectDataSourceOnlyShell(schema, code) {
|
|
275
|
+
assertExactKeys(schema, INITIAL_ROOT_KEYS, code);
|
|
276
|
+
if (
|
|
277
|
+
!Number.isFinite(schema.gmtModified) ||
|
|
278
|
+
schema.gmtModified < 0 ||
|
|
279
|
+
!Array.isArray(schema.i18nData) ||
|
|
280
|
+
schema.i18nData.length !== 0 ||
|
|
281
|
+
schema.status !== 'ONLINE' ||
|
|
282
|
+
!Array.isArray(schema.pages) ||
|
|
283
|
+
schema.pages.length !== 1
|
|
284
|
+
) {
|
|
285
|
+
foundationError(code, 'Initial page Schema root does not match the data-source shell contract.');
|
|
286
|
+
}
|
|
287
|
+
const page = schema.pages[0];
|
|
288
|
+
assertPlainObject(page, code);
|
|
289
|
+
assertExactKeys(page, INITIAL_PAGE_KEYS, code);
|
|
290
|
+
if (!Array.isArray(page.componentsTree) || page.componentsTree.length !== 1) {
|
|
291
|
+
foundationError(code, 'Initial page Schema must contain exactly one data-source tree entry.');
|
|
292
|
+
}
|
|
293
|
+
const root = page.componentsTree[0];
|
|
294
|
+
assertPlainObject(root, code);
|
|
295
|
+
assertExactKeys(root, INITIAL_TREE_KEYS, code);
|
|
296
|
+
const dataSource = root.dataSource;
|
|
297
|
+
assertPlainObject(dataSource, code);
|
|
298
|
+
assertExactKeys(dataSource, INITIAL_DATA_SOURCE_KEYS, code);
|
|
299
|
+
const expectedGlobalConfig = buildDefaultPageDataSource('FORM-FOUNDATION').globalConfig;
|
|
300
|
+
if (
|
|
301
|
+
!Array.isArray(dataSource.offline) ||
|
|
302
|
+
dataSource.offline.length !== 0 ||
|
|
303
|
+
!Array.isArray(dataSource.online) ||
|
|
304
|
+
dataSource.online.length !== 0 ||
|
|
305
|
+
!Array.isArray(dataSource.list) ||
|
|
306
|
+
dataSource.list.length !== 0 ||
|
|
307
|
+
dataSource.sync !== true ||
|
|
308
|
+
!isDeepStrictEqual(dataSource.globalConfig, expectedGlobalConfig)
|
|
309
|
+
) {
|
|
310
|
+
foundationError(code, 'Initial page data-source shell contains unsupported content.');
|
|
311
|
+
}
|
|
312
|
+
return { dataSource };
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function normalizeInitialFingerprint(schema) {
|
|
316
|
+
const normalized = clonePlain(schema);
|
|
317
|
+
normalized.gmtModified = '<root-gmtModified>';
|
|
318
|
+
return normalized;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function hashInitialRemoteSchema(schema) {
|
|
322
|
+
const normalized = clonePlain(schema);
|
|
323
|
+
delete normalized.gmtModified;
|
|
324
|
+
return hashStable(normalized);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function extractInitialDataSource(schema) {
|
|
328
|
+
return clonePlain(schema.pages[0].componentsTree[0].dataSource);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function assertPreparedDataSourceContract(initialDataSource, preparedDataSource, formUuid) {
|
|
332
|
+
const expected = mergePageDataSource(
|
|
333
|
+
initialDataSource,
|
|
334
|
+
buildDefaultPageDataSource(formUuid)
|
|
335
|
+
);
|
|
336
|
+
if (!preparedDataSource || !isDeepStrictEqual(preparedDataSource, expected)) {
|
|
337
|
+
foundationError(
|
|
338
|
+
'SCHEMA_PAGE_FOUNDATION_SCHEMA_INVALID',
|
|
339
|
+
'Native page builder produced an unsupported data-source transition.'
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function createDeterministicGenerators(input) {
|
|
345
|
+
const seed = hashStable(input);
|
|
346
|
+
return {
|
|
347
|
+
nextNodeId: createDeterministicTokenFactory(seed, 'node_oc', 18),
|
|
348
|
+
nextSuffix: createDeterministicTokenFactory(seed, '', 20),
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function createDeterministicTokenFactory(seed, prefix, length) {
|
|
353
|
+
let counter = 0;
|
|
354
|
+
return function nextToken() {
|
|
355
|
+
counter += 1;
|
|
356
|
+
return prefix + crypto
|
|
357
|
+
.createHash('sha256')
|
|
358
|
+
.update(`${seed}:${prefix}:${counter}`)
|
|
359
|
+
.digest('hex')
|
|
360
|
+
.slice(0, length);
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function buildDesiredManaged(title, compiledPage) {
|
|
365
|
+
return {
|
|
366
|
+
compiledHash: compiledPage.compiledHash,
|
|
367
|
+
formType: DISPLAY_FORM_TYPE,
|
|
368
|
+
profile: NATIVE_PAGE_PROFILE,
|
|
369
|
+
sourceHash: compiledPage.sourceHash,
|
|
370
|
+
title,
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function buildCanvasDesiredManaged(title, compiledPage) {
|
|
375
|
+
return {
|
|
376
|
+
compiledHash: compiledPage.compiledHash,
|
|
377
|
+
formType: DISPLAY_FORM_TYPE,
|
|
378
|
+
profile: CANVAS_PAGE_PROFILE,
|
|
379
|
+
sourceHash: compiledPage.sourceHash,
|
|
380
|
+
title,
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function normalizeObservedIdentity(input) {
|
|
385
|
+
const title = typeof input.observedTitle === 'string' ? input.observedTitle.trim() : '';
|
|
386
|
+
const formType = typeof input.observedFormType === 'string'
|
|
387
|
+
? input.observedFormType.trim().toLowerCase()
|
|
388
|
+
: '';
|
|
389
|
+
if (
|
|
390
|
+
!title ||
|
|
391
|
+
title.length > 256 ||
|
|
392
|
+
containsUnsafeText(title) ||
|
|
393
|
+
formType !== DISPLAY_FORM_TYPE
|
|
394
|
+
) {
|
|
395
|
+
foundationError(
|
|
396
|
+
'SCHEMA_PAGE_FOUNDATION_OBSERVED_IDENTITY_INVALID',
|
|
397
|
+
'Observed page identity is invalid.'
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
return { formType, title };
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function normalizeSchema(value) {
|
|
404
|
+
let candidate = value;
|
|
405
|
+
if (isPlainObject(candidate) && candidate.success === true) {
|
|
406
|
+
if (!isPlainObject(candidate.content) || Object.keys(candidate.content).length === 0) {
|
|
407
|
+
foundationError(
|
|
408
|
+
'SCHEMA_PAGE_FOUNDATION_SCHEMA_INVALID',
|
|
409
|
+
'Page Schema read returned empty content.'
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
candidate = candidate.content;
|
|
413
|
+
} else if (isPlainObject(candidate) && candidate.success === false) {
|
|
414
|
+
foundationError(
|
|
415
|
+
'SCHEMA_PAGE_FOUNDATION_SCHEMA_INVALID',
|
|
416
|
+
'Page Schema read was not successful.'
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
if (typeof candidate === 'string') {
|
|
420
|
+
try {
|
|
421
|
+
candidate = JSON.parse(candidate);
|
|
422
|
+
} catch (error) {
|
|
423
|
+
foundationError(
|
|
424
|
+
'SCHEMA_PAGE_FOUNDATION_SCHEMA_INVALID',
|
|
425
|
+
'Page Schema content is invalid.'
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
assertPlainObject(candidate, 'SCHEMA_PAGE_FOUNDATION_SCHEMA_INVALID');
|
|
430
|
+
return clonePlain(candidate);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function requireInitialProfile(value) {
|
|
434
|
+
if (
|
|
435
|
+
!value ||
|
|
436
|
+
typeof value !== 'object' ||
|
|
437
|
+
!INITIAL_PROFILES.has(value) ||
|
|
438
|
+
value.contractVersion !== INITIAL_PROFILE_CONTRACT_VERSION
|
|
439
|
+
) {
|
|
440
|
+
foundationError(
|
|
441
|
+
'SCHEMA_PAGE_FOUNDATION_INITIAL_PROFILE_REQUIRED',
|
|
442
|
+
'An approved data-source-only shell profile is required.'
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
return value;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
function requireCompiledPage(value) {
|
|
449
|
+
if (!isCompiledNativePage(value)) {
|
|
450
|
+
foundationError(
|
|
451
|
+
'SCHEMA_PAGE_SOURCE_INVALID',
|
|
452
|
+
'Page builder requires trusted compiled native source.'
|
|
453
|
+
);
|
|
454
|
+
}
|
|
455
|
+
return value;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
function requireCompiledCanvas(value) {
|
|
459
|
+
if (!isCompiledCanvasPage(value)) {
|
|
460
|
+
foundationError(
|
|
461
|
+
'SCHEMA_PAGE_SOURCE_INVALID',
|
|
462
|
+
'Page builder requires trusted compiled Canvas source.'
|
|
463
|
+
);
|
|
464
|
+
}
|
|
465
|
+
return value;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
function normalizeProfileId(value) {
|
|
469
|
+
const profileId = typeof value === 'string' ? value.trim() : '';
|
|
470
|
+
if (!PROFILE_ID_PATTERN.test(profileId)) {
|
|
471
|
+
foundationError(
|
|
472
|
+
'SCHEMA_PAGE_FOUNDATION_INITIAL_PROFILE_INVALID',
|
|
473
|
+
'Data-source shell profile is invalid.'
|
|
474
|
+
);
|
|
475
|
+
}
|
|
476
|
+
return profileId;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function normalizeHash(value, code) {
|
|
480
|
+
const hash = typeof value === 'string' ? value.trim() : '';
|
|
481
|
+
if (!HASH_PATTERN.test(hash)) {
|
|
482
|
+
foundationError(code, 'Page foundation hash is invalid.');
|
|
483
|
+
}
|
|
484
|
+
return hash;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function normalizeBinding(value) {
|
|
488
|
+
const binding = typeof value === 'string' ? value.trim() : '';
|
|
489
|
+
if (!binding || binding.length > 256 || containsUnsafeText(binding)) {
|
|
490
|
+
foundationError(
|
|
491
|
+
'SCHEMA_PAGE_FOUNDATION_TRANSITION_INVALID',
|
|
492
|
+
'Page builder binding is invalid.'
|
|
493
|
+
);
|
|
494
|
+
}
|
|
495
|
+
return binding;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
function assertExactKeys(value, expected, code) {
|
|
499
|
+
assertPlainObject(value, code);
|
|
500
|
+
const keys = Object.keys(value).sort();
|
|
501
|
+
if (
|
|
502
|
+
keys.length !== expected.length ||
|
|
503
|
+
keys.some((key, index) => key !== expected[index])
|
|
504
|
+
) {
|
|
505
|
+
foundationError(code, 'Page Schema contains unsupported structure.');
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function assertPlainObject(value, code) {
|
|
510
|
+
if (!isPlainObject(value)) {
|
|
511
|
+
foundationError(code, 'Page Schema value must be a plain object.');
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
function isPlainObject(value) {
|
|
516
|
+
const prototype = value && typeof value === 'object'
|
|
517
|
+
? Object.getPrototypeOf(value)
|
|
518
|
+
: undefined;
|
|
519
|
+
return !!(
|
|
520
|
+
value &&
|
|
521
|
+
typeof value === 'object' &&
|
|
522
|
+
!Array.isArray(value) &&
|
|
523
|
+
(prototype === Object.prototype || prototype === null)
|
|
524
|
+
);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
function containsUnsafeText(value) {
|
|
528
|
+
for (let index = 0; index < value.length; index++) {
|
|
529
|
+
const code = value.charCodeAt(index);
|
|
530
|
+
if ((code < 32 && code !== 9 && code !== 10 && code !== 13) || code === 127) {
|
|
531
|
+
return true;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
return false;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
function deepFreeze(value) {
|
|
538
|
+
if (!value || typeof value !== 'object' || Object.isFrozen(value)) {
|
|
539
|
+
return value;
|
|
540
|
+
}
|
|
541
|
+
for (const child of Object.values(value)) {
|
|
542
|
+
deepFreeze(child);
|
|
543
|
+
}
|
|
544
|
+
return Object.freeze(value);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
function clonePlain(value) {
|
|
548
|
+
return JSON.parse(JSON.stringify(value));
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
function foundationError(code, message) {
|
|
552
|
+
throw schemaError(code, message);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
module.exports = Object.freeze({
|
|
556
|
+
buildDataSourceOnlyCanvasPagePatch,
|
|
557
|
+
buildDataSourceOnlyNativePagePatch,
|
|
558
|
+
computeDataSourceOnlyShellFingerprint,
|
|
559
|
+
createDataSourceOnlyShellProfile,
|
|
560
|
+
projectDataSourceOnlyShell,
|
|
561
|
+
});
|