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.
Files changed (139) hide show
  1. package/README.md +14 -0
  2. package/bin/yida.js +107 -18
  3. package/lib/app/create-app.js +12 -1
  4. package/lib/app/create-form/args.js +13 -0
  5. package/lib/app/create-form.js +154 -67
  6. package/lib/app/create-page.js +45 -9
  7. package/lib/app/get-schema.js +65 -14
  8. package/lib/app/import-app.js +92 -41
  9. package/lib/app/publish.js +289 -556
  10. package/lib/app/schema-field-resolution.js +370 -0
  11. package/lib/app/services/app-reader.js +85 -0
  12. package/lib/app/services/app-service.js +235 -0
  13. package/lib/app/services/canvas-page-compiler.js +91 -0
  14. package/lib/app/services/canvas-page-schema-builder.js +149 -0
  15. package/lib/app/services/field-bindings.js +68 -0
  16. package/lib/app/services/form-compiler.js +1922 -0
  17. package/lib/app/services/form-mode-service.js +182 -0
  18. package/lib/app/services/form-schema-patcher.js +333 -0
  19. package/lib/app/services/form-schema-reader.js +59 -0
  20. package/lib/app/services/form-service.js +379 -0
  21. package/lib/app/services/form-validation.js +562 -0
  22. package/lib/app/services/native-page-compiler.js +92 -0
  23. package/lib/app/services/native-page-schema-builder.js +428 -0
  24. package/lib/app/services/page-resource-service.js +185 -0
  25. package/lib/app/update-app.js +18 -6
  26. package/lib/auth/token-auth.js +34 -3
  27. package/lib/auth/token-store.js +22 -4
  28. package/lib/bridge/bridge.js +37 -5
  29. package/lib/core/agent-capabilities.js +5 -3
  30. package/lib/core/command-manifest.js +40 -0
  31. package/lib/core/doctor.js +15 -4
  32. package/lib/core/env-cmd.js +1 -1
  33. package/lib/core/env.js +7 -6
  34. package/lib/core/legacy-schema-guard.js +408 -0
  35. package/lib/core/locales/en.js +8 -46
  36. package/lib/core/locales/zh.js +8 -46
  37. package/lib/core/sample.js +1 -1
  38. package/lib/core/utils.js +334 -66
  39. package/lib/core/yida-client.js +104 -3
  40. package/lib/formula/evaluate.js +8 -66
  41. package/lib/formula/field-refs.js +83 -0
  42. package/lib/process/configure-process.js +44 -1992
  43. package/lib/process/create-process.js +54 -87
  44. package/lib/process/services/process-compiler.js +2056 -0
  45. package/lib/process/services/process-reader.js +232 -0
  46. package/lib/process/services/process-resource-service.js +571 -0
  47. package/lib/process/services/process-service.js +122 -0
  48. package/lib/process/services/process-stage-checkpoint.js +246 -0
  49. package/lib/report/append.js +7 -26
  50. package/lib/report/http.js +31 -5
  51. package/lib/report/index.js +10 -2
  52. package/lib/samples/yida-canvas-custom-page/business-list.canvas.jsx +63 -14
  53. package/lib/samples/yida-canvas-custom-page/dashboard-overview.canvas.jsx +176 -32
  54. package/lib/samples/yida-canvas-custom-page/data-management.canvas.jsx +228 -14
  55. package/lib/samples/yida-canvas-custom-page/split-pane-detail.canvas.jsx +204 -8
  56. package/lib/schema/adapters/app-adapter.js +210 -0
  57. package/lib/schema/adapters/form-adapter.js +1362 -0
  58. package/lib/schema/adapters/keys.js +19 -0
  59. package/lib/schema/adapters/page-adapter.js +566 -0
  60. package/lib/schema/adapters/process-adapter.js +519 -0
  61. package/lib/schema/applier.js +1878 -0
  62. package/lib/schema/apply-store.js +1083 -0
  63. package/lib/schema/command.js +645 -0
  64. package/lib/schema/dependency-graph.js +83 -0
  65. package/lib/schema/errors.js +481 -0
  66. package/lib/schema/hash.js +9 -0
  67. package/lib/schema/manifest-limits.js +177 -0
  68. package/lib/schema/manifest-loader.js +309 -0
  69. package/lib/schema/manifest-schema-v1.json +193 -0
  70. package/lib/schema/normalize-manifest.js +281 -0
  71. package/lib/schema/page-canvas-foundation.js +447 -0
  72. package/lib/schema/page-data-source-builder.js +561 -0
  73. package/lib/schema/page-foundation.js +556 -0
  74. package/lib/schema/page-source-loader.js +314 -0
  75. package/lib/schema/planner.js +691 -0
  76. package/lib/schema/remote-dispatch-boundary.js +39 -0
  77. package/lib/schema/remote-missing.js +21 -0
  78. package/lib/schema/remote-reader.js +189 -0
  79. package/lib/schema/resource-registry.js +157 -0
  80. package/lib/schema/server-revision.js +71 -0
  81. package/lib/schema/sort.js +28 -0
  82. package/lib/schema/state-store.js +589 -0
  83. package/package.json +2 -2
  84. package/scripts/postinstall.js +8 -12
  85. package/scripts/validate-package-size.js +1 -1
  86. package/yida-skills/SKILL.md +114 -29
  87. package/yida-skills/references/schema-as-code-phase1.md +99 -0
  88. package/yida-skills/references/setup-and-env.md +59 -59
  89. package/yida-skills/references/task-retrospective.md +6 -6
  90. package/yida-skills/skills/yida-app/SKILL.md +60 -14
  91. package/yida-skills/skills/yida-canvas-custom-page/SKILL.md +18 -7
  92. package/yida-skills/skills/yida-canvas-custom-page/references/data-bridge-guide.md +3 -3
  93. package/yida-skills/skills/yida-canvas-custom-page/references/page-generation-guide.md +7 -1
  94. package/yida-skills/skills/yida-create-app/SKILL.md +21 -3
  95. package/yida-skills/skills/yida-create-form-page/SKILL.md +19 -1
  96. package/yida-skills/skills/yida-create-page/SKILL.md +16 -3
  97. package/yida-skills/skills/yida-create-process/SKILL.md +100 -4
  98. package/yida-skills/skills/yida-custom-page/SKILL.md +19 -4
  99. package/yida-skills/skills/yida-dashboard/SKILL.md +1 -1
  100. package/yida-skills/skills/yida-dashboard/references/pitfalls.md +1 -1
  101. package/yida-skills/skills/yida-get-schema/SKILL.md +49 -5
  102. package/yida-skills/skills/yida-i18n/SKILL.md +1 -1
  103. package/yida-skills/skills/yida-integration/SKILL.md +7 -1
  104. package/yida-skills/skills/yida-login/SKILL.md +50 -99
  105. package/yida-skills/skills/yida-page-config/SKILL.md +7 -1
  106. package/yida-skills/skills/yida-page-uiux/references/app/blueprint.md +0 -1
  107. package/yida-skills/skills/yida-page-uiux/references/app/navigation-patterns.md +0 -1
  108. package/yida-skills/skills/yida-page-uiux/references/app/role-journey.md +0 -1
  109. package/yida-skills/skills/yida-page-uiux/references/dashboard/app-blueprint.md +0 -1
  110. package/yida-skills/skills/yida-page-uiux/references/dashboard/chart-contracts.md +0 -1
  111. package/yida-skills/skills/yida-page-uiux/references/dashboard/component-contracts.md +0 -1
  112. package/yida-skills/skills/yida-page-uiux/references/dashboard/layout-patterns.md +0 -1
  113. package/yida-skills/skills/yida-page-uiux/references/dashboard/theme-recipes.md +0 -1
  114. package/yida-skills/skills/yida-page-uiux/references/detail/object-narrative.md +0 -1
  115. package/yida-skills/skills/yida-page-uiux/references/detail/section-patterns.md +0 -1
  116. package/yida-skills/skills/yida-page-uiux/references/detail/timeline-and-related.md +0 -1
  117. package/yida-skills/skills/yida-page-uiux/references/landing/assets-workflow.md +0 -1
  118. package/yida-skills/skills/yida-page-uiux/references/landing/industry-playbooks.md +0 -1
  119. package/yida-skills/skills/yida-page-uiux/references/landing/research-levels.md +0 -1
  120. package/yida-skills/skills/yida-page-uiux/references/landing/section-patterns.md +0 -1
  121. package/yida-skills/skills/yida-page-uiux/references/list/drawer-detail.md +0 -1
  122. package/yida-skills/skills/yida-page-uiux/references/list/empty-loading-error.md +0 -1
  123. package/yida-skills/skills/yida-page-uiux/references/list/filter-patterns.md +0 -1
  124. package/yida-skills/skills/yida-page-uiux/references/list/table-patterns.md +0 -1
  125. package/yida-skills/skills/yida-page-uiux/references/scenes/screen.md +0 -1
  126. package/yida-skills/skills/yida-page-uiux/references/workbench/entry-patterns.md +0 -1
  127. package/yida-skills/skills/yida-page-uiux/references/workbench/portal-layouts.md +0 -1
  128. package/yida-skills/skills/yida-page-uiux/references/workbench/task-feed.md +0 -1
  129. package/yida-skills/skills/yida-page-uiux/workflow/output-decision-block.md +1 -1
  130. package/yida-skills/skills/yida-process-rule/SKILL.md +35 -1
  131. package/yida-skills/skills/yida-publish-page/SKILL.md +46 -4
  132. package/yida-skills/skills/yida-report/SKILL.md +7 -1
  133. package/yida-skills/skills-index.json +14 -46
  134. package/yida-skills/skills/large-file-write/SKILL.md +0 -93
  135. package/yida-skills/skills/large-file-write/references/write-patterns.md +0 -147
  136. package/yida-skills/skills/large-file-write/scripts/write.js +0 -157
  137. package/yida-skills/skills/sls-log-workbench/SKILL.md +0 -134
  138. package/yida-skills/skills/sls-log-workbench/references/workbench-reference.md +0 -130
  139. package/yida-skills/skills/sls-log-workbench/sls-query.js +0 -295
@@ -0,0 +1,370 @@
1
+ /**
2
+ * 将 getFormSchema 的完整返回正规化为安全、确定性的字段解析契约。
3
+ * 本模块无网络、文件或进程状态依赖,不承担跨调用缓存。
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ const crypto = require('crypto');
9
+
10
+ const CONTRACT_KIND = 'yida_schema_field_resolution';
11
+ const CONTRACT_VERSION = 1;
12
+ const LABEL_LOCALES = [
13
+ 'zh_CN',
14
+ 'zh_HK',
15
+ 'en_US',
16
+ 'ja_JP',
17
+ 'ko_KR',
18
+ 'fr_FR',
19
+ 'de_DE',
20
+ 'es_ES',
21
+ 'pt_PT',
22
+ 'ar_SA',
23
+ 'hi_IN',
24
+ 'vi_VN',
25
+ ];
26
+ const LOCALE_KEY_PATTERN = /^[a-z]{2}(?:[_-][A-Z]{2})?$/;
27
+
28
+ function normalizeText(value) {
29
+ if (value === null || value === undefined) {
30
+ return '';
31
+ }
32
+ if (!['string', 'number', 'boolean'].includes(typeof value)) {
33
+ return '';
34
+ }
35
+ return String(value).normalize('NFC').trim();
36
+ }
37
+
38
+ function collectLocalizedLabels(value, fallback) {
39
+ const entries = [];
40
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
41
+ const knownLocales = LABEL_LOCALES.filter(locale => (
42
+ Object.prototype.hasOwnProperty.call(value, locale)
43
+ ));
44
+ const extraLocales = Object.keys(value)
45
+ .filter(locale => !LABEL_LOCALES.includes(locale) && LOCALE_KEY_PATTERN.test(locale))
46
+ .sort();
47
+ knownLocales.concat(extraLocales).forEach((locale) => {
48
+ const label = normalizeText(value[locale]);
49
+ if (label) {
50
+ entries.push({ locale, label });
51
+ }
52
+ });
53
+ } else {
54
+ const label = normalizeText(value);
55
+ if (label) {
56
+ entries.push({ locale: null, label });
57
+ }
58
+ }
59
+
60
+ if (entries.length === 0) {
61
+ entries.push({ locale: null, label: normalizeText(fallback) });
62
+ }
63
+ return {
64
+ label: entries[0].label,
65
+ entries,
66
+ };
67
+ }
68
+
69
+ function normalizeLabel(value, fallback) {
70
+ return collectLocalizedLabels(value, fallback).label;
71
+ }
72
+
73
+ function normalizeValueType(value) {
74
+ const normalized = typeof value === 'string' ? normalizeText(value) : '';
75
+ return normalized || null;
76
+ }
77
+
78
+ function stableCopy(value) {
79
+ if (Array.isArray(value)) {
80
+ return value.map(stableCopy);
81
+ }
82
+ if (value && typeof value === 'object') {
83
+ const result = {};
84
+ Object.keys(value).sort().forEach((key) => {
85
+ if (value[key] !== undefined) {
86
+ result[key] = stableCopy(value[key]);
87
+ }
88
+ });
89
+ return result;
90
+ }
91
+ return value;
92
+ }
93
+
94
+ function createSchemaHash(schemaResult) {
95
+ let content = schemaResult && Object.prototype.hasOwnProperty.call(schemaResult, 'content')
96
+ ? schemaResult.content
97
+ : null;
98
+ if (typeof content === 'string') {
99
+ try {
100
+ content = JSON.parse(content);
101
+ } catch (error) {
102
+ content = null;
103
+ }
104
+ }
105
+ if (content && typeof content === 'object' && !Array.isArray(content)) {
106
+ content = stableCopy(content);
107
+ delete content.gmtModified;
108
+ }
109
+ const serialized = JSON.stringify(stableCopy(content));
110
+ const digest = crypto
111
+ .createHash('sha256')
112
+ .update(serialized === undefined ? 'null' : serialized)
113
+ .digest('hex');
114
+ return `sha256:${digest}`;
115
+ }
116
+
117
+ function buildAliasByFieldId(schemaResult) {
118
+ const aliases = {};
119
+ const pages = schemaResult && schemaResult.content && schemaResult.content.pages;
120
+ if (!Array.isArray(pages)) {
121
+ return aliases;
122
+ }
123
+ pages.forEach((page) => {
124
+ const items = page && page.componentAlias && Array.isArray(page.componentAlias.items)
125
+ ? page.componentAlias.items
126
+ : [];
127
+ items.forEach((item) => {
128
+ const fieldId = item && item.fieldId ? normalizeText(item.fieldId) : '';
129
+ const alias = item && item.alias ? normalizeText(item.alias) : '';
130
+ if (fieldId && alias) {
131
+ aliases[fieldId] = alias;
132
+ }
133
+ });
134
+ });
135
+ return aliases;
136
+ }
137
+
138
+ function buildLabelPathSelectors(tableAncestors, labelInfo) {
139
+ const labelNodes = tableAncestors.map(item => item.labelInfo).concat(labelInfo);
140
+ const selectors = [];
141
+ const seen = new Set();
142
+
143
+ function append(locale, labels) {
144
+ const value = labels.join('/');
145
+ if (!seen.has(value)) {
146
+ seen.add(value);
147
+ selectors.push({ locale, value });
148
+ }
149
+ }
150
+
151
+ append(null, labelNodes.map(item => item.label));
152
+ const discoveredLocales = new Set();
153
+ labelNodes.forEach((item) => {
154
+ item.entries.forEach((entry) => {
155
+ if (entry.locale) {
156
+ discoveredLocales.add(entry.locale);
157
+ }
158
+ });
159
+ });
160
+ const orderedLocales = LABEL_LOCALES.filter(locale => discoveredLocales.has(locale))
161
+ .concat(Array.from(discoveredLocales)
162
+ .filter(locale => !LABEL_LOCALES.includes(locale))
163
+ .sort());
164
+ orderedLocales.forEach((locale) => {
165
+ append(locale, labelNodes.map((item) => {
166
+ const localized = item.entries.find(entry => entry.locale === locale);
167
+ return localized ? localized.label : item.label;
168
+ }));
169
+ });
170
+ return selectors;
171
+ }
172
+
173
+ function normalizeSchemaFields(schemaResult) {
174
+ const fields = [];
175
+ const pages = schemaResult && schemaResult.content && schemaResult.content.pages;
176
+ if (!Array.isArray(pages)) {
177
+ return fields;
178
+ }
179
+ const aliases = buildAliasByFieldId(schemaResult);
180
+
181
+ function traverse(node, tableAncestors) {
182
+ if (!node || typeof node !== 'object') {
183
+ return;
184
+ }
185
+
186
+ const props = node.props && typeof node.props === 'object' ? node.props : {};
187
+ const fieldId = props.fieldId ? normalizeText(props.fieldId) : '';
188
+ const componentType = node.componentName ? normalizeText(node.componentName) : '';
189
+ let childTableAncestors = tableAncestors;
190
+
191
+ if (fieldId && componentType) {
192
+ const labelInfo = collectLocalizedLabels(props.label, fieldId);
193
+ const label = labelInfo.label;
194
+ const parentTable = tableAncestors.length > 0
195
+ ? tableAncestors[tableAncestors.length - 1]
196
+ : null;
197
+ fields.push({
198
+ label,
199
+ fieldId,
200
+ componentType,
201
+ valueType: normalizeValueType(props.valueType),
202
+ path: tableAncestors.map(item => item.fieldId).concat(fieldId),
203
+ labelPath: tableAncestors.map(item => item.label).concat(label),
204
+ parentFieldId: parentTable ? parentTable.fieldId : null,
205
+ alias: aliases[fieldId] || '',
206
+ localizedLabels: labelInfo.entries,
207
+ labelPathSelectors: buildLabelPathSelectors(tableAncestors, labelInfo),
208
+ });
209
+
210
+ if (componentType === 'TableField') {
211
+ childTableAncestors = tableAncestors.concat({ label, fieldId, labelInfo });
212
+ }
213
+ }
214
+
215
+ if (Array.isArray(node.children)) {
216
+ node.children.forEach(child => traverse(child, childTableAncestors));
217
+ }
218
+ }
219
+
220
+ pages.forEach((page) => {
221
+ const roots = page && Array.isArray(page.componentsTree) ? page.componentsTree : [];
222
+ roots.forEach(root => traverse(root, []));
223
+ });
224
+
225
+ return fields;
226
+ }
227
+
228
+ function normalizeQueries(queries) {
229
+ const result = [];
230
+ (Array.isArray(queries) ? queries : []).forEach((query) => {
231
+ const normalized = normalizeText(query);
232
+ if (normalized && !result.includes(normalized)) {
233
+ result.push(normalized);
234
+ }
235
+ });
236
+ return result;
237
+ }
238
+
239
+ function findExactMatches(fields, query) {
240
+ const normalizedQuery = normalizeText(query);
241
+ if (!normalizedQuery) {
242
+ return [];
243
+ }
244
+ const matchesByFieldId = new Map();
245
+
246
+ fields.forEach((field) => {
247
+ const matchedBy = [];
248
+ if (field.fieldId === normalizedQuery) {
249
+ matchedBy.push('fieldId');
250
+ }
251
+ if (field.alias && field.alias === normalizedQuery) {
252
+ matchedBy.push('alias');
253
+ }
254
+ if (field.path.length > 1 && field.path.join('/') === normalizedQuery) {
255
+ matchedBy.push('path');
256
+ }
257
+ (field.localizedLabels || []).forEach((entry) => {
258
+ if (entry.label === normalizedQuery) {
259
+ matchedBy.push(entry.locale ? `label:${entry.locale}` : 'label');
260
+ }
261
+ });
262
+ (field.labelPathSelectors || []).forEach((selector) => {
263
+ if (field.labelPath.length > 1 && selector.value === normalizedQuery) {
264
+ matchedBy.push(selector.locale ? `labelPath:${selector.locale}` : 'labelPath');
265
+ }
266
+ });
267
+
268
+ if (matchedBy.length === 0) {
269
+ return;
270
+ }
271
+ const existing = matchesByFieldId.get(field.fieldId);
272
+ if (existing) {
273
+ matchedBy.forEach((identity) => {
274
+ if (!existing.matchedBy.includes(identity)) {
275
+ existing.matchedBy.push(identity);
276
+ }
277
+ });
278
+ } else {
279
+ matchesByFieldId.set(field.fieldId, { field, matchedBy });
280
+ }
281
+ });
282
+
283
+ return Array.from(matchesByFieldId.values()).map(({ field, matchedBy }) => ({
284
+ ...field,
285
+ matchedBy,
286
+ }));
287
+ }
288
+
289
+ function publicField(field, query) {
290
+ const result = {
291
+ query: query === undefined ? null : query,
292
+ label: field.label,
293
+ fieldId: field.fieldId,
294
+ componentType: field.componentType,
295
+ valueType: field.valueType,
296
+ path: field.path.slice(),
297
+ labelPath: field.labelPath.slice(),
298
+ parentFieldId: field.parentFieldId,
299
+ alias: field.alias,
300
+ };
301
+ if (Array.isArray(field.matchedBy) && field.matchedBy.length > 0) {
302
+ result.matchedBy = field.matchedBy.slice();
303
+ }
304
+ return result;
305
+ }
306
+
307
+ function resolveFields(fields, queries) {
308
+ const normalizedQueries = normalizeQueries(queries);
309
+ if (normalizedQueries.length === 0) {
310
+ return {
311
+ fields: fields.map(field => publicField(field)),
312
+ missingFields: [],
313
+ ambiguousFields: [],
314
+ };
315
+ }
316
+
317
+ const resolution = {
318
+ fields: [],
319
+ missingFields: [],
320
+ ambiguousFields: [],
321
+ };
322
+
323
+ normalizedQueries.forEach((query) => {
324
+ const matches = findExactMatches(fields, query);
325
+ if (matches.length === 0) {
326
+ resolution.missingFields.push(query);
327
+ } else if (matches.length === 1) {
328
+ resolution.fields.push(publicField(matches[0], query));
329
+ } else {
330
+ resolution.ambiguousFields.push({
331
+ query,
332
+ reason: 'multiple_exact_matches',
333
+ matches: matches.map(field => publicField(field, query)),
334
+ });
335
+ }
336
+ });
337
+
338
+ return resolution;
339
+ }
340
+
341
+ function buildFieldResolution(appType, formUuid, schemaResult, queries) {
342
+ const normalizedFields = normalizeSchemaFields(schemaResult);
343
+ const resolution = resolveFields(normalizedFields, queries);
344
+ return {
345
+ kind: CONTRACT_KIND,
346
+ contractVersion: CONTRACT_VERSION,
347
+ resource: {
348
+ appType,
349
+ formUuid,
350
+ schemaHash: createSchemaHash(schemaResult),
351
+ },
352
+ ...resolution,
353
+ };
354
+ }
355
+
356
+ module.exports = {
357
+ CONTRACT_KIND,
358
+ CONTRACT_VERSION,
359
+ LABEL_LOCALES,
360
+ normalizeText,
361
+ collectLocalizedLabels,
362
+ normalizeLabel,
363
+ stableCopy,
364
+ createSchemaHash,
365
+ normalizeSchemaFields,
366
+ normalizeQueries,
367
+ findExactMatches,
368
+ resolveFields,
369
+ buildFieldResolution,
370
+ };
@@ -0,0 +1,85 @@
1
+ 'use strict';
2
+
3
+ const { createAuthRef, createYidaClient } = require('../../core/yida-client');
4
+ const {
5
+ dispatchRemotePrimitive,
6
+ hasRemoteDispatchBoundary,
7
+ } = require('../../schema/remote-dispatch-boundary');
8
+
9
+ function resolveAuthRef(context) {
10
+ return context && context.authRef ? context.authRef : context;
11
+ }
12
+
13
+ function createAppReaderError(message, code, details) {
14
+ const error = new Error(message);
15
+ error.code = code || 'APP_READ_FAILED';
16
+ if (details) {
17
+ error.details = details;
18
+ }
19
+ return error;
20
+ }
21
+
22
+ async function readApp(context, input) {
23
+ const appType = input && input.appType;
24
+ if (!appType) {
25
+ throw createAppReaderError('appType is required', 'APP_READ_INVALID_ARGUMENTS');
26
+ }
27
+
28
+ const authRef = resolveAuthRef(context) || createAuthRef();
29
+ const result = await fetchAppDetail(authRef, appType, context);
30
+ const app = normalizeAppDetail(result, appType);
31
+
32
+ return {
33
+ appType,
34
+ appName: app.appName || app.name || '',
35
+ systemLink: app.systemLink || '',
36
+ };
37
+ }
38
+
39
+ function fetchAppDetail(authRef, appType, context) {
40
+ const client = createYidaClient({ authRef });
41
+ const method = hasRemoteDispatchBoundary(context) ? 'getOnce' : 'get';
42
+ return dispatchRemotePrimitive(context, () => client[method](`/${appType}/query/app/getAppIncludingAecpInfo.json`, (ref) => ({
43
+ _api: 'nattyFetch',
44
+ _mock: 'false',
45
+ appKey: appType,
46
+ _csrf_token: ref.csrfToken,
47
+ _stamp: Date.now(),
48
+ })));
49
+ }
50
+
51
+ function normalizeAppDetail(result, appType) {
52
+ if (result && result.success && result.content && typeof result.content === 'object') {
53
+ return result.content;
54
+ }
55
+ if (result && (result.__needLogin || result.__csrfExpired)) {
56
+ throw createAppReaderError('login required', 'APP_READ_AUTH_REQUIRED');
57
+ }
58
+ if (isAppDetailMissing(result)) {
59
+ throw createAppReaderError('app not found', 'APP_READ_NOT_FOUND', { appType });
60
+ }
61
+ throw createAppReaderError(result && result.errorMsg || 'app detail request failed', 'APP_READ_FAILED', { appType });
62
+ }
63
+
64
+ function isAppDetailMissing(result) {
65
+ if (!result) {
66
+ return false;
67
+ }
68
+ const statusCode = Number(result.__httpStatus || result.statusCode || result.status || 0);
69
+ const errorCode = String(result.errorCode || result.code || '').toUpperCase();
70
+ const message = String(result.errorMsg || result.message || result.throwable || '').toLowerCase();
71
+ return (
72
+ statusCode === 404 ||
73
+ errorCode === '404' ||
74
+ errorCode.includes('NOT_FOUND') ||
75
+ message.includes('not found') ||
76
+ message.includes('不存在') ||
77
+ message.includes('不存在该应用') ||
78
+ message.includes('应用不存在')
79
+ );
80
+ }
81
+
82
+ module.exports = {
83
+ readApp,
84
+ fetchAppDetail,
85
+ };
@@ -0,0 +1,235 @@
1
+ 'use strict';
2
+
3
+ const querystring = require('querystring');
4
+ const { httpPost, requestWithAutoLogin } = require('../../core/utils');
5
+ const { buildYidaI18n } = require('../../core/yida-i18n');
6
+ const {
7
+ dispatchRemotePrimitive,
8
+ hasRemoteDispatchBoundary,
9
+ rethrowRemoteDispatchBoundaryFailure,
10
+ } = require('../../schema/remote-dispatch-boundary');
11
+ const { isTokenAuthRef } = require('../../core/yida-client');
12
+
13
+ const DEFAULT_APP_OPTIONS = Object.freeze({
14
+ icon: 'xian-yingyong',
15
+ iconColor: '#0089FF',
16
+ colour: 'deepBlue',
17
+ navTheme: 'dark',
18
+ layoutDirection: 'slide',
19
+ });
20
+
21
+ function createAppServiceError(message, code, details) {
22
+ const error = new Error(message);
23
+ error.code = code || 'APP_SERVICE_FAILED';
24
+ if (details) {
25
+ error.details = details;
26
+ }
27
+ return error;
28
+ }
29
+
30
+ function resolveAuthRef(context) {
31
+ return context && context.authRef ? context.authRef : context;
32
+ }
33
+
34
+ function resolveServices(context) {
35
+ const services = context && context.services || {};
36
+ return {
37
+ httpPost: services.httpPost || httpPost,
38
+ requestWithAutoLogin: services.requestWithAutoLogin || requestWithAutoLogin,
39
+ };
40
+ }
41
+
42
+ async function readExclusiveAppConfig(context) {
43
+ const authRef = resolveAuthRef(context);
44
+ const services = resolveServices(context);
45
+ let openExclusive = 'n';
46
+ let openPhysicColumn = 'n';
47
+
48
+ try {
49
+ const result = await dispatchRemotePrimitive(context, () => services.httpPost(
50
+ authRef.baseUrl,
51
+ `/query/exclusive/queryCorpAppConfig.json?_api=Global.queryCorpAppConfig&_mock=false&_csrf_token=${authRef.csrfToken}&_locale_time_zone_offset=28800000&_stamp=${Date.now()}`,
52
+ '',
53
+ authRef.cookies
54
+ ));
55
+ if (result && result.content) {
56
+ if (result.content.forceExclusiveDb === 'y') {
57
+ openExclusive = 'y';
58
+ }
59
+ if (result.content.forcePhysicalColumn === 'y') {
60
+ openPhysicColumn = 'y';
61
+ }
62
+ }
63
+ } catch (error) {
64
+ rethrowRemoteDispatchBoundaryFailure(error);
65
+ // This lookup is advisory; legacy create-app also falls back to defaults.
66
+ }
67
+
68
+ return { openExclusive, openPhysicColumn };
69
+ }
70
+
71
+ async function createAppResource(context, input = {}) {
72
+ const authRef = resolveAuthRef(context);
73
+ const services = resolveServices(context);
74
+ const appName = input.appName || input.name;
75
+ if (!appName) {
76
+ throw createAppServiceError('app name is required', 'APP_CREATE_INVALID_ARGUMENTS');
77
+ }
78
+
79
+ const description = input.description || appName;
80
+ const icon = input.icon || DEFAULT_APP_OPTIONS.icon;
81
+ const iconColor = input.iconColor || DEFAULT_APP_OPTIONS.iconColor;
82
+ const colour = input.colour || DEFAULT_APP_OPTIONS.colour;
83
+ const navTheme = input.navTheme || DEFAULT_APP_OPTIONS.navTheme;
84
+ const layoutDirection = input.layoutDirection || DEFAULT_APP_OPTIONS.layoutDirection;
85
+ const contentLocale = input.contentLocale || input.defaultLanguage || 'zh_CN';
86
+ const exclusive = await readExclusiveAppConfig(context);
87
+ const iconValue = `${icon}%%${iconColor}`;
88
+
89
+ const response = await dispatchAppWrite(context, services, authRef, (auth) => {
90
+ const postData = querystring.stringify({
91
+ _csrf_token: auth.csrfToken,
92
+ appName: JSON.stringify(buildYidaI18n(appName, { en_US: appName, ja_JP: appName })),
93
+ description: JSON.stringify(buildYidaI18n(description, { en_US: description, ja_JP: description })),
94
+ icon: iconValue,
95
+ iconUrl: iconValue,
96
+ colour,
97
+ navTheme,
98
+ layoutDirection,
99
+ defaultLanguage: contentLocale,
100
+ openExclusive: exclusive.openExclusive,
101
+ openPhysicColumn: exclusive.openPhysicColumn,
102
+ openIsolationDatabase: 'n',
103
+ openExclusiveUnit: 'n',
104
+ group: 'ALL',
105
+ });
106
+ return services.httpPost(auth.baseUrl, '/query/app/registerApp.json', postData, auth.cookies);
107
+ });
108
+
109
+ if (!response || !response.success || !response.content) {
110
+ throw createAppServiceError(
111
+ response && response.errorMsg || 'app create request failed',
112
+ 'APP_CREATE_FAILED',
113
+ { result: sanitizeServiceResult(response) }
114
+ );
115
+ }
116
+
117
+ return {
118
+ appType: response.content,
119
+ appName,
120
+ response,
121
+ };
122
+ }
123
+
124
+ async function updateAppResource(context, input = {}) {
125
+ const authRef = resolveAuthRef(context);
126
+ const services = resolveServices(context);
127
+ const appType = input.appType;
128
+ if (!appType) {
129
+ throw createAppServiceError('appType is required', 'APP_UPDATE_INVALID_ARGUMENTS');
130
+ }
131
+
132
+ const updateFields = {
133
+ _locale_time_zone_offset: '28800000',
134
+ appType,
135
+ };
136
+ if (input.name) {
137
+ updateFields.appName = JSON.stringify(buildYidaI18n(input.name, {
138
+ en_US: input.name,
139
+ ja_JP: input.name,
140
+ }, { includePureEn: true }));
141
+ }
142
+ if (input.desc) {
143
+ updateFields.description = JSON.stringify(buildYidaI18n(input.desc, {
144
+ en_US: input.desc,
145
+ ja_JP: input.desc,
146
+ }));
147
+ }
148
+ if (input.icon) {
149
+ const iconValue = `${input.icon}%%${input.iconColor || DEFAULT_APP_OPTIONS.iconColor}`;
150
+ updateFields.icon = iconValue;
151
+ updateFields.iconUrl = iconValue;
152
+ }
153
+ if (input.colour) {
154
+ updateFields.colour = input.colour;
155
+ }
156
+ if (input.navTheme) {
157
+ updateFields.navTheme = input.navTheme;
158
+ }
159
+ if (input.layoutDirection) {
160
+ updateFields.layoutDirection = input.layoutDirection;
161
+ }
162
+
163
+ const response = await dispatchAppWrite(context, services, authRef, (auth) => {
164
+ const postDataObject = {
165
+ _csrf_token: auth.csrfToken,
166
+ ...updateFields,
167
+ };
168
+ return services.httpPost(
169
+ auth.baseUrl,
170
+ `/query/app/updateAppName.json?_api=Form.updateAppName&_mock=false&_stamp=${Date.now()}`,
171
+ querystring.stringify(postDataObject),
172
+ auth.cookies
173
+ );
174
+ });
175
+
176
+ if (!response || !response.success) {
177
+ throw createAppServiceError(
178
+ response && response.errorMsg || 'app update request failed',
179
+ 'APP_UPDATE_FAILED',
180
+ { result: sanitizeServiceResult(response) }
181
+ );
182
+ }
183
+
184
+ return { appType, response };
185
+ }
186
+
187
+ async function dispatchAppWrite(context, services, authRef, callback) {
188
+ if (!hasRemoteDispatchBoundary(context)) {
189
+ return services.requestWithAutoLogin(callback, authRef);
190
+ }
191
+ requireAppWriteAuth(authRef);
192
+ return dispatchRemotePrimitive(context, () => callback(authRef));
193
+ }
194
+
195
+ function requireAppWriteAuth(authRef) {
196
+ if (!authRef || typeof authRef.baseUrl !== 'string' || !authRef.baseUrl) {
197
+ throw createAppServiceError(
198
+ 'app write authentication is not ready',
199
+ 'APP_WRITE_PRECHECK_FAILED'
200
+ );
201
+ }
202
+ if (isTokenAuthRef(authRef)) {
203
+ return authRef;
204
+ }
205
+ if (
206
+ typeof authRef.csrfToken !== 'string' || !authRef.csrfToken ||
207
+ !Array.isArray(authRef.cookies)
208
+ ) {
209
+ throw createAppServiceError(
210
+ 'app write authentication is not ready',
211
+ 'APP_WRITE_PRECHECK_FAILED'
212
+ );
213
+ }
214
+ return authRef;
215
+ }
216
+
217
+ function sanitizeServiceResult(result) {
218
+ if (!result || typeof result !== 'object') {
219
+ return result || null;
220
+ }
221
+ const sanitized = {};
222
+ ['success', 'errorMsg', 'errorCode', 'code', 'message'].forEach((key) => {
223
+ if (Object.prototype.hasOwnProperty.call(result, key)) {
224
+ sanitized[key] = result[key];
225
+ }
226
+ });
227
+ return sanitized;
228
+ }
229
+
230
+ module.exports = {
231
+ DEFAULT_APP_OPTIONS,
232
+ createAppResource,
233
+ createAppServiceError,
234
+ updateAppResource,
235
+ };