openxiangda-devkit-core 2.0.0-alpha.100

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 (126) hide show
  1. package/README.md +13 -0
  2. package/dist/app-spec.d.ts +210 -0
  3. package/dist/app-spec.d.ts.map +1 -0
  4. package/dist/app-spec.js +1262 -0
  5. package/dist/app-spec.js.map +1 -0
  6. package/dist/application-services.d.ts +600 -0
  7. package/dist/application-services.d.ts.map +1 -0
  8. package/dist/application-services.js +1603 -0
  9. package/dist/application-services.js.map +1 -0
  10. package/dist/backend-image-build.d.ts +23 -0
  11. package/dist/backend-image-build.d.ts.map +1 -0
  12. package/dist/backend-image-build.js +209 -0
  13. package/dist/backend-image-build.js.map +1 -0
  14. package/dist/command-registry.d.ts +177 -0
  15. package/dist/command-registry.d.ts.map +1 -0
  16. package/dist/command-registry.js +27 -0
  17. package/dist/command-registry.js.map +1 -0
  18. package/dist/compiler/ai-catalog.d.ts +4 -0
  19. package/dist/compiler/ai-catalog.d.ts.map +1 -0
  20. package/dist/compiler/ai-catalog.js +353 -0
  21. package/dist/compiler/ai-catalog.js.map +1 -0
  22. package/dist/compiler/authorization-transition.d.ts +7 -0
  23. package/dist/compiler/authorization-transition.d.ts.map +1 -0
  24. package/dist/compiler/authorization-transition.js +27 -0
  25. package/dist/compiler/authorization-transition.js.map +1 -0
  26. package/dist/compiler/bundle.d.ts +34 -0
  27. package/dist/compiler/bundle.d.ts.map +1 -0
  28. package/dist/compiler/bundle.js +1865 -0
  29. package/dist/compiler/bundle.js.map +1 -0
  30. package/dist/compiler/config.d.ts +857 -0
  31. package/dist/compiler/config.d.ts.map +1 -0
  32. package/dist/compiler/config.js +3848 -0
  33. package/dist/compiler/config.js.map +1 -0
  34. package/dist/compiler/data-policy-expression.d.ts +12 -0
  35. package/dist/compiler/data-policy-expression.d.ts.map +1 -0
  36. package/dist/compiler/data-policy-expression.js +43 -0
  37. package/dist/compiler/data-policy-expression.js.map +1 -0
  38. package/dist/compiler/field-codec.d.ts +14 -0
  39. package/dist/compiler/field-codec.d.ts.map +1 -0
  40. package/dist/compiler/field-codec.js +131 -0
  41. package/dist/compiler/field-codec.js.map +1 -0
  42. package/dist/compiler/field-physical-plan.d.ts +10 -0
  43. package/dist/compiler/field-physical-plan.d.ts.map +1 -0
  44. package/dist/compiler/field-physical-plan.js +66 -0
  45. package/dist/compiler/field-physical-plan.js.map +1 -0
  46. package/dist/compiler/field-policy-path.d.ts +10 -0
  47. package/dist/compiler/field-policy-path.d.ts.map +1 -0
  48. package/dist/compiler/field-policy-path.js +283 -0
  49. package/dist/compiler/field-policy-path.js.map +1 -0
  50. package/dist/compiler/field-query-plan.d.ts +13 -0
  51. package/dist/compiler/field-query-plan.d.ts.map +1 -0
  52. package/dist/compiler/field-query-plan.js +122 -0
  53. package/dist/compiler/field-query-plan.js.map +1 -0
  54. package/dist/compiler/field-surface.d.ts +15 -0
  55. package/dist/compiler/field-surface.d.ts.map +1 -0
  56. package/dist/compiler/field-surface.js +51 -0
  57. package/dist/compiler/field-surface.js.map +1 -0
  58. package/dist/compiler/index.d.ts +6 -0
  59. package/dist/compiler/index.d.ts.map +1 -0
  60. package/dist/compiler/index.js +6 -0
  61. package/dist/compiler/index.js.map +1 -0
  62. package/dist/compiler/package-compiler.d.ts +22 -0
  63. package/dist/compiler/package-compiler.d.ts.map +1 -0
  64. package/dist/compiler/package-compiler.js +246 -0
  65. package/dist/compiler/package-compiler.js.map +1 -0
  66. package/dist/compiler/schema-composition.d.ts +21 -0
  67. package/dist/compiler/schema-composition.d.ts.map +1 -0
  68. package/dist/compiler/schema-composition.js +193 -0
  69. package/dist/compiler/schema-composition.js.map +1 -0
  70. package/dist/config.d.ts +2 -0
  71. package/dist/config.d.ts.map +1 -0
  72. package/dist/config.js +2 -0
  73. package/dist/config.js.map +1 -0
  74. package/dist/connected-development.d.ts +62 -0
  75. package/dist/connected-development.d.ts.map +1 -0
  76. package/dist/connected-development.js +393 -0
  77. package/dist/connected-development.js.map +1 -0
  78. package/dist/control-plane-client.d.ts +684 -0
  79. package/dist/control-plane-client.d.ts.map +1 -0
  80. package/dist/control-plane-client.js +860 -0
  81. package/dist/control-plane-client.js.map +1 -0
  82. package/dist/deployment.d.ts +67 -0
  83. package/dist/deployment.d.ts.map +1 -0
  84. package/dist/deployment.js +311 -0
  85. package/dist/deployment.js.map +1 -0
  86. package/dist/index.d.ts +17 -0
  87. package/dist/index.d.ts.map +1 -0
  88. package/dist/index.js +17 -0
  89. package/dist/index.js.map +1 -0
  90. package/dist/internal/workflow.d.ts +83 -0
  91. package/dist/internal/workflow.d.ts.map +1 -0
  92. package/dist/internal/workflow.js +419 -0
  93. package/dist/internal/workflow.js.map +1 -0
  94. package/dist/nest-injection-contract.d.ts +4 -0
  95. package/dist/nest-injection-contract.d.ts.map +1 -0
  96. package/dist/nest-injection-contract.js +116 -0
  97. package/dist/nest-injection-contract.js.map +1 -0
  98. package/dist/package-compiler.d.ts +2 -0
  99. package/dist/package-compiler.d.ts.map +1 -0
  100. package/dist/package-compiler.js +2 -0
  101. package/dist/package-compiler.js.map +1 -0
  102. package/dist/session.d.ts +143 -0
  103. package/dist/session.d.ts.map +1 -0
  104. package/dist/session.js +351 -0
  105. package/dist/session.js.map +1 -0
  106. package/dist/testing.d.ts +77 -0
  107. package/dist/testing.d.ts.map +1 -0
  108. package/dist/testing.js +213 -0
  109. package/dist/testing.js.map +1 -0
  110. package/dist/toolchain-capsule.d.ts +45 -0
  111. package/dist/toolchain-capsule.d.ts.map +1 -0
  112. package/dist/toolchain-capsule.js +165 -0
  113. package/dist/toolchain-capsule.js.map +1 -0
  114. package/dist/version.d.ts +3 -0
  115. package/dist/version.d.ts.map +1 -0
  116. package/dist/version.js +11 -0
  117. package/dist/version.js.map +1 -0
  118. package/dist/workspace-loader.d.ts +17 -0
  119. package/dist/workspace-loader.d.ts.map +1 -0
  120. package/dist/workspace-loader.js +140 -0
  121. package/dist/workspace-loader.js.map +1 -0
  122. package/dist/workspace.d.ts +15 -0
  123. package/dist/workspace.d.ts.map +1 -0
  124. package/dist/workspace.js +69 -0
  125. package/dist/workspace.js.map +1 -0
  126. package/package.json +44 -0
@@ -0,0 +1,1865 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { canonicalJson, applicationEventHandlerPathV2, DATA_EVENT_TYPES_V2, nativePlatformCapabilityCatalog, OPENXIANGDA_COMPILER_CONTRACT_VERSION, SCHEMA_VERSIONS, WORKFLOW_EVENT_TYPES_V2, sha256Digest, } from 'openxiangda-contracts';
3
+ import { AppConfigValidationError, } from './config.js';
4
+ import { AUTHORIZATION_TRANSITION_CAPABILITY_CODE_PATTERN, AUTHORIZATION_TRANSITION_KEYS, AUTHORIZATION_TRANSITION_ROLE_CODE_PATTERN, exactKeys, isExactStringArray, isPlainRecord, } from './authorization-transition.js';
5
+ import { compileAiCapabilityCatalog } from './ai-catalog.js';
6
+ import { FIELD_VALUE_TYPESCRIPT_DECLARATIONS, fieldNullable, supportsGeneratedMutation, typescriptType, } from './field-codec.js';
7
+ import { normalizeWorkflowDefinition } from '../internal/workflow.js';
8
+ import { supportsSearch, supportsSort } from './field-query-plan.js';
9
+ import { resolveDataFieldSurfaceWidget } from './field-surface.js';
10
+ export const CONFIG_BUNDLE_SCHEMA = SCHEMA_VERSIONS.configurationBundle;
11
+ export const CONTRACT_BUNDLE_SCHEMA = SCHEMA_VERSIONS.contractBundle;
12
+ export const COMPILER_CONTRACT_VERSION = OPENXIANGDA_COMPILER_CONTRACT_VERSION;
13
+ const DEFAULT_EVENT_DELIVERY = {
14
+ timeoutMs: 10_000,
15
+ maxAttempts: 8,
16
+ initialBackoffMs: 1_000,
17
+ maxBackoffMs: 300_000,
18
+ ordering: 'none',
19
+ concurrency: 10,
20
+ };
21
+ function normalizeEventFilter(filter) {
22
+ if (!filter)
23
+ return {};
24
+ return {
25
+ ...(filter.resourceCodes
26
+ ? { resourceCodes: uniqueSorted(filter.resourceCodes) }
27
+ : {}),
28
+ ...(filter.subject ? { subject: { ...filter.subject } } : {}),
29
+ ...(filter.changedFields
30
+ ? {
31
+ changedFields: Object.fromEntries(Object.entries(filter.changedFields).map(([key, value]) => [
32
+ key,
33
+ uniqueSorted(value),
34
+ ])),
35
+ }
36
+ : {}),
37
+ ...(filter.changes
38
+ ? {
39
+ changes: sorted(filter.changes.map(change => ({
40
+ field: change.field,
41
+ ...(change.before ? { before: change.before } : {}),
42
+ ...(change.after ? { after: change.after } : {}),
43
+ })), change => String(change.field || '')),
44
+ }
45
+ : {}),
46
+ ...(filter.where ? { where: filter.where } : {}),
47
+ };
48
+ }
49
+ export function compileApplicationSources(config, generatorVersion = 'openxiangda-devkit-core') {
50
+ const configuration = normalizeConfiguration(config);
51
+ const configContent = canonicalJson(configuration);
52
+ const configDigest = sha256Bytes(configContent);
53
+ const aiCatalogValue = compileAiCapabilityCatalog(config, configDigest);
54
+ const aiCatalogContent = canonicalJson(aiCatalogValue);
55
+ const aiCatalogDigest = sha256Bytes(aiCatalogContent);
56
+ const contract = compileContractBundleFromNormalized(config, configDigest, generatorVersion, configuration);
57
+ const contractContent = canonicalJson(contract);
58
+ const contractDigest = sha256Bytes(contractContent);
59
+ return {
60
+ config: {
61
+ value: configuration,
62
+ content: configContent,
63
+ digest: configDigest,
64
+ artifact: {
65
+ kind: 'config',
66
+ digest: configDigest,
67
+ mediaType: 'application/vnd.openxiangda.config-bundle.v3+json',
68
+ size: Buffer.byteLength(configContent),
69
+ metadata: {
70
+ schemaVersion: CONFIG_BUNDLE_SCHEMA,
71
+ compilerContractVersion: COMPILER_CONTRACT_VERSION,
72
+ },
73
+ },
74
+ },
75
+ contracts: {
76
+ value: contract,
77
+ content: contractContent,
78
+ digest: contractDigest,
79
+ typescript: renderGeneratedContracts(config, contract),
80
+ artifact: {
81
+ kind: 'contracts',
82
+ digest: contractDigest,
83
+ mediaType: 'application/vnd.openxiangda.contract-bundle.v3+json',
84
+ size: Buffer.byteLength(contractContent),
85
+ metadata: {
86
+ schemaVersion: CONTRACT_BUNDLE_SCHEMA,
87
+ compilerContractVersion: COMPILER_CONTRACT_VERSION,
88
+ configDigest,
89
+ },
90
+ },
91
+ },
92
+ aiCatalog: {
93
+ value: aiCatalogValue,
94
+ content: aiCatalogContent,
95
+ digest: aiCatalogDigest,
96
+ mediaType: 'application/vnd.openxiangda.ai-capability.v1+json',
97
+ },
98
+ };
99
+ }
100
+ export function normalizeConfiguration(config) {
101
+ return {
102
+ schemaVersion: CONFIG_BUNDLE_SCHEMA,
103
+ compilerContractVersion: COMPILER_CONTRACT_VERSION,
104
+ appCode: config.app.code,
105
+ perspectives: compilePerspectives(config),
106
+ authz: {
107
+ ...(config.authz?.authenticatedUserRoleCode
108
+ ? {
109
+ authenticatedUserRoleCode: config.authz.authenticatedUserRoleCode,
110
+ }
111
+ : {}),
112
+ capabilities: sorted(config.authz?.capabilities || [], item => item.code),
113
+ roles: sorted((config.authz?.roles || []).map(role => ({
114
+ code: role.code,
115
+ name: role.name,
116
+ ...(role.description ? { description: role.description } : {}),
117
+ capabilities: uniqueSorted(role.capabilities),
118
+ })), item => item.code),
119
+ scopeDimensions: sorted((config.authz?.scopeDimensions || []).map(dimension => ({
120
+ code: dimension.code,
121
+ name: dimension.name,
122
+ ...(dimension.resourceCode
123
+ ? { resourceCode: dimension.resourceCode }
124
+ : {}),
125
+ ...(dimension.valueType ? { valueType: dimension.valueType } : {}),
126
+ ...(dimension.hierarchyMode
127
+ ? { hierarchyMode: dimension.hierarchyMode }
128
+ : {}),
129
+ ...(dimension.valueSource
130
+ ? {
131
+ valueSource: {
132
+ kind: dimension.valueSource.kind,
133
+ resourceCode: dimension.valueSource.resourceCode,
134
+ labelField: dimension.valueSource.labelField,
135
+ ...(dimension.valueSource.enabledField
136
+ ? { enabledField: dimension.valueSource.enabledField }
137
+ : {}),
138
+ },
139
+ }
140
+ : {}),
141
+ })), item => item.code),
142
+ scopeSources: sorted((config.authz?.scopeSources || []).map(source => ({
143
+ code: source.code,
144
+ name: source.name,
145
+ resourceCode: source.resourceCode,
146
+ subject: source.subject.type === 'role_membership'
147
+ ? {
148
+ type: source.subject.type,
149
+ userIdField: source.subject.userIdField,
150
+ roleCode: source.subject.roleCode,
151
+ }
152
+ : {
153
+ type: source.subject.type,
154
+ userIdField: source.subject.userIdField,
155
+ },
156
+ grants: sorted(source.grants.map(grant => ({
157
+ dimensionCode: grant.dimensionCode,
158
+ valueField: grant.valueField,
159
+ ...(grant.parentValueField
160
+ ? { parentValueField: grant.parentValueField }
161
+ : {}),
162
+ })), grant => grant.dimensionCode),
163
+ ...(source.operationField
164
+ ? { operationField: source.operationField }
165
+ : {}),
166
+ ...(source.enabledField ? { enabledField: source.enabledField } : {}),
167
+ ...(source.effectiveFromField
168
+ ? { effectiveFromField: source.effectiveFromField }
169
+ : {}),
170
+ ...(source.effectiveToField
171
+ ? { effectiveToField: source.effectiveToField }
172
+ : {}),
173
+ failureMode: source.failureMode,
174
+ })), item => item.code),
175
+ roleMembershipSources: sorted((config.authz?.roleMembershipSources || []).map(source => ({
176
+ code: source.code,
177
+ name: source.name,
178
+ resourceCode: source.resourceCode,
179
+ userIdField: source.userIdField,
180
+ roleCode: source.roleCode,
181
+ ...(source.enabledField ? { enabledField: source.enabledField } : {}),
182
+ ...(source.effectiveFromField
183
+ ? { effectiveFromField: source.effectiveFromField }
184
+ : {}),
185
+ ...(source.effectiveToField
186
+ ? { effectiveToField: source.effectiveToField }
187
+ : {}),
188
+ failureMode: source.failureMode,
189
+ })), item => item.code),
190
+ relationshipGrantSources: sorted((config.authz?.relationshipGrantSources || []).map(source => ({
191
+ code: source.code,
192
+ name: source.name,
193
+ resourceCode: source.resourceCode,
194
+ subject: source.subject.type === 'role_membership'
195
+ ? {
196
+ type: source.subject.type,
197
+ userIdField: source.subject.userIdField,
198
+ roleCode: source.subject.roleCode,
199
+ }
200
+ : {
201
+ type: source.subject.type,
202
+ userIdField: source.subject.userIdField,
203
+ },
204
+ relationCode: source.relationCode,
205
+ targetResourceCode: source.targetResourceCode,
206
+ resourceIdField: source.resourceIdField,
207
+ operations: uniqueSorted(source.operations),
208
+ ...(source.enabledField ? { enabledField: source.enabledField } : {}),
209
+ ...(source.effectiveFromField
210
+ ? { effectiveFromField: source.effectiveFromField }
211
+ : {}),
212
+ ...(source.effectiveToField
213
+ ? { effectiveToField: source.effectiveToField }
214
+ : {}),
215
+ failureMode: source.failureMode,
216
+ })), item => item.code),
217
+ dataPolicies: sorted((config.authz?.dataPolicies || []).map(normalizeDataPolicyDeclaration), item => item.code),
218
+ authorizationTransitions: sorted((config.authz?.authorizationTransitions || []).map(normalizeAuthorizationTransition), item => item.fromAuthzDigest),
219
+ },
220
+ backend: {
221
+ secrets: sorted((config.backend.secrets || []).map(secret => ({
222
+ name: secret.name,
223
+ env: secret.env,
224
+ ...(secret.description ? { description: secret.description } : {}),
225
+ ...(secret.required !== undefined
226
+ ? { required: secret.required }
227
+ : {}),
228
+ exposure: 'active_only',
229
+ })), item => item.name),
230
+ operations: sorted((config.backend.operations || []).map(operation => ({
231
+ code: operation.code,
232
+ method: operation.method,
233
+ path: operation.path,
234
+ capability: operation.capability,
235
+ requestSchema: operation.requestSchema,
236
+ responseSchema: operation.responseSchema,
237
+ ...(operation.platformAccess
238
+ ? {
239
+ platformAccess: {
240
+ ...(operation.platformAccess.directory
241
+ ? {
242
+ directory: {
243
+ mode: operation.platformAccess.directory.mode,
244
+ fields: uniqueSorted(operation.platformAccess.directory.fields),
245
+ },
246
+ }
247
+ : {}),
248
+ ...(operation.platformAccess.managedFiles
249
+ ? {
250
+ managedFiles: sorted(operation.platformAccess.managedFiles.map(item => ({
251
+ resourceCode: item.resourceCode,
252
+ fieldCodes: uniqueSorted(item.fieldCodes),
253
+ intents: uniqueSorted(item.intents),
254
+ })), item => item.resourceCode),
255
+ }
256
+ : {}),
257
+ ...(operation.platformAccess.managedFileCopies
258
+ ? {
259
+ managedFileCopies: sorted(operation.platformAccess.managedFileCopies.map(item => ({
260
+ mode: item.mode,
261
+ sourceResourceCode: item.sourceResourceCode,
262
+ sourceFieldCodes: uniqueSorted(item.sourceFieldCodes),
263
+ targetResourceCode: item.targetResourceCode,
264
+ targetFieldCodes: uniqueSorted(item.targetFieldCodes),
265
+ })), item => `${item.sourceResourceCode}:${item.sourceFieldCodes.join(',')}=>${item.targetResourceCode}:${item.targetFieldCodes.join(',')}`),
266
+ }
267
+ : {}),
268
+ ...(operation.platformAccess.notification
269
+ ? {
270
+ notification: {
271
+ mode: operation.platformAccess.notification.mode,
272
+ },
273
+ }
274
+ : {}),
275
+ ...(operation.platformAccess.workflow
276
+ ? {
277
+ workflow: {
278
+ codes: uniqueSorted(operation.platformAccess.workflow.codes),
279
+ },
280
+ }
281
+ : {}),
282
+ },
283
+ }
284
+ : {}),
285
+ ...(operation.ai
286
+ ? {
287
+ ai: {
288
+ name: operation.ai.name,
289
+ description: operation.ai.description,
290
+ risk: operation.ai.risk,
291
+ resources: uniqueSorted(operation.ai.resources),
292
+ sideEffects: uniqueSorted(operation.ai.sideEffects),
293
+ ...(operation.ai.concurrency
294
+ ? { concurrency: operation.ai.concurrency }
295
+ : {}),
296
+ ...(operation.ai.timeoutMs
297
+ ? { timeoutMs: operation.ai.timeoutMs }
298
+ : {}),
299
+ },
300
+ }
301
+ : {}),
302
+ ...(operation.description
303
+ ? { description: operation.description }
304
+ : {}),
305
+ })), item => `${item.method}:${item.path}:${item.code}`),
306
+ },
307
+ data: {
308
+ resources: sorted((config.data?.resources || []).map(normalizeDataResource), item => item.code),
309
+ },
310
+ events: {
311
+ schemas: sorted((config.events?.schemas || []).map(schema => ({
312
+ schemaVersion: SCHEMA_VERSIONS.eventSchema,
313
+ eventType: schema.eventType,
314
+ dataSchemaVersion: schema.dataSchemaVersion,
315
+ jsonSchema: schema.jsonSchema,
316
+ schemaDigest: sha256Digest(schema.jsonSchema),
317
+ sensitiveFields: uniqueSorted(schema.sensitiveFields || []),
318
+ owner: 'application',
319
+ })), item => `${item.eventType}:${item.dataSchemaVersion}`),
320
+ subscriptions: sorted((config.events?.subscriptions || []).map(subscription => ({
321
+ code: subscription.code,
322
+ ...(subscription.description
323
+ ? { description: subscription.description }
324
+ : {}),
325
+ eventTypes: uniqueSorted(subscription.eventTypes),
326
+ filter: normalizeEventFilter(subscription.filter),
327
+ payload: {
328
+ includeChanges: subscription.payload?.includeChanges !== false,
329
+ fields: uniqueSorted(subscription.payload?.fields || []),
330
+ },
331
+ ...(subscription.platformAccess?.notification
332
+ ? {
333
+ platformAccess: {
334
+ notification: {
335
+ mode: subscription.platformAccess.notification.mode,
336
+ },
337
+ },
338
+ }
339
+ : {}),
340
+ ...(subscription.platformAccess?.managedFileCopies
341
+ ? {
342
+ platformAccess: {
343
+ ...(subscription.platformAccess.notification
344
+ ? {
345
+ notification: {
346
+ mode: subscription.platformAccess.notification.mode,
347
+ },
348
+ }
349
+ : {}),
350
+ managedFileCopies: sorted(subscription.platformAccess.managedFileCopies.map(item => ({
351
+ mode: item.mode,
352
+ sourceResourceCode: item.sourceResourceCode,
353
+ sourceFieldCodes: uniqueSorted(item.sourceFieldCodes),
354
+ targetResourceCode: item.targetResourceCode,
355
+ targetFieldCodes: uniqueSorted(item.targetFieldCodes),
356
+ })), item => `${item.sourceResourceCode}:${item.sourceFieldCodes.join(',')}=>${item.targetResourceCode}:${item.targetFieldCodes.join(',')}`),
357
+ },
358
+ }
359
+ : {}),
360
+ endpointPath: applicationEventHandlerPathV2(subscription.code),
361
+ delivery: { ...DEFAULT_EVENT_DELIVERY, ...subscription.delivery },
362
+ })), item => item.code),
363
+ timers: sorted((config.events?.timers || []).map(timer => ({
364
+ code: timer.code,
365
+ eventType: timer.eventType,
366
+ cronExpression: timer.cronExpression,
367
+ timezone: timer.timezone,
368
+ payload: timer.payload,
369
+ misfirePolicy: timer.misfirePolicy || 'coalesce_one',
370
+ })), item => item.code),
371
+ dateTriggers: sorted((config.events?.dateTriggers || []).map(trigger => ({
372
+ code: trigger.code,
373
+ resourceCode: trigger.resourceCode,
374
+ field: trigger.field,
375
+ offset: trigger.offset,
376
+ eventType: trigger.eventType,
377
+ payload: trigger.payload,
378
+ })), item => item.code),
379
+ },
380
+ workflows: {
381
+ definitions: sorted((config.workflows?.definitions || []).map(item => ({
382
+ version: item.version,
383
+ definition: normalizeWorkflowDefinition(item.definition),
384
+ launch: normalizeWorkflowLaunchDeclaration(item.launch),
385
+ ...(item.detailRouteCode
386
+ ? { detailRouteCode: { ...item.detailRouteCode } }
387
+ : {}),
388
+ })), item => `${item.definition.code}:${pad(item.version)}`),
389
+ bindings: sorted((config.workflows?.bindings || []).map(item => ({
390
+ version: item.version,
391
+ binding: item.binding,
392
+ })), item => `${item.binding.workflowCode}:${pad(item.version)}`),
393
+ activations: sorted((config.workflows?.activations || []).map(item => ({
394
+ workflowCode: item.workflowCode,
395
+ definitionVersion: item.definitionVersion,
396
+ bindingVersion: item.bindingVersion,
397
+ acceptedCommandDeactivationPolicy: item.acceptedCommandDeactivationPolicy,
398
+ })), item => item.workflowCode),
399
+ providers: sorted((config.workflows?.providers || []).map(provider => ({
400
+ code: provider.code,
401
+ endpointPath: provider.endpointPath,
402
+ ...(provider.timeoutMs !== undefined
403
+ ? { timeoutMs: provider.timeoutMs }
404
+ : {}),
405
+ })), item => item.code),
406
+ editableParameters: sorted((config.workflows?.editableParameters || []).map(parameter => ({
407
+ code: parameter.code,
408
+ workflowCode: parameter.workflowCode,
409
+ bindingKey: parameter.bindingKey,
410
+ label: parameter.label,
411
+ valueType: parameter.valueType,
412
+ ...(parameter.required !== undefined
413
+ ? { required: parameter.required }
414
+ : {}),
415
+ ...(parameter.allowedRoleCodes
416
+ ? { allowedRoleCodes: uniqueSorted(parameter.allowedRoleCodes) }
417
+ : {}),
418
+ ...(parameter.allowedProviderCodes
419
+ ? {
420
+ allowedProviderCodes: uniqueSorted(parameter.allowedProviderCodes),
421
+ }
422
+ : {}),
423
+ ...(parameter.maxItems !== undefined
424
+ ? { maxItems: parameter.maxItems }
425
+ : {}),
426
+ })), item => item.code),
427
+ },
428
+ frontend: {
429
+ routes: compileFrontendRoutes(config),
430
+ user: {
431
+ applicationTodoCenter: config.frontend.user?.applicationTodoCenter === true,
432
+ },
433
+ devicePolicy: compileRouteManifestDevicePolicy(config),
434
+ authentication: compileApplicationAuthentication(config),
435
+ ...(config.frontend.publicAccess
436
+ ? { publicAccess: compileAnonymousPublicAccess(config) }
437
+ : {}),
438
+ admin: {
439
+ ...(config.frontend.admin?.access
440
+ ? {
441
+ access: {
442
+ ...(config.frontend.admin.access.allOf
443
+ ? {
444
+ allOf: uniqueSorted([
445
+ ...config.frontend.admin.access.allOf,
446
+ ]),
447
+ }
448
+ : {}),
449
+ ...(config.frontend.admin.access.anyOf
450
+ ? {
451
+ anyOf: uniqueSorted([
452
+ ...config.frontend.admin.access.anyOf,
453
+ ]),
454
+ }
455
+ : {}),
456
+ },
457
+ }
458
+ : {}),
459
+ navigation: (config.frontend.admin?.navigation || []).map(group => ({
460
+ code: group.code,
461
+ label: group.label,
462
+ ...(group.icon ? { icon: group.icon } : {}),
463
+ ...(group.order !== undefined ? { order: group.order } : {}),
464
+ items: group.items.map(item => ({
465
+ page: { ...item.page },
466
+ ...(item.label ? { label: item.label } : {}),
467
+ ...(item.icon ? { icon: item.icon } : {}),
468
+ ...(item.order !== undefined ? { order: item.order } : {}),
469
+ })),
470
+ })),
471
+ },
472
+ },
473
+ runtime: {
474
+ protocolCapabilities: runtimeProtocolCapabilities(config),
475
+ health: {
476
+ livePath: '/__platform/health',
477
+ readyPath: '/__platform/ready',
478
+ versionPath: '/__platform/version',
479
+ },
480
+ },
481
+ };
482
+ }
483
+ function normalizeAuthorizationTransition(raw, index) {
484
+ const path = `authz.authorizationTransitions[${index}]`;
485
+ if (!isPlainRecord(raw) || !exactKeys(raw, AUTHORIZATION_TRANSITION_KEYS)) {
486
+ throw invalidAuthorizationTransition(path);
487
+ }
488
+ if (typeof raw.fromAuthzDigest !== 'string' ||
489
+ !/^[0-9a-f]{64}$/.test(raw.fromAuthzDigest) ||
490
+ typeof raw.reason !== 'string' ||
491
+ !raw.reason.trim() ||
492
+ raw.reason.length > 2000 ||
493
+ (raw.removeRoleCodes !== undefined &&
494
+ !isExactStringArray(raw.removeRoleCodes, AUTHORIZATION_TRANSITION_ROLE_CODE_PATTERN, 128)) ||
495
+ (raw.removeCapabilityCodes !== undefined &&
496
+ !isExactStringArray(raw.removeCapabilityCodes, AUTHORIZATION_TRANSITION_CAPABILITY_CODE_PATTERN, 255))) {
497
+ throw invalidAuthorizationTransition(path);
498
+ }
499
+ return {
500
+ fromAuthzDigest: raw.fromAuthzDigest,
501
+ ...(raw.removeRoleCodes
502
+ ? { removeRoleCodes: uniqueSorted(raw.removeRoleCodes) }
503
+ : {}),
504
+ ...(raw.removeCapabilityCodes
505
+ ? {
506
+ removeCapabilityCodes: uniqueSorted(raw.removeCapabilityCodes),
507
+ }
508
+ : {}),
509
+ reason: raw.reason.trim(),
510
+ };
511
+ }
512
+ function invalidAuthorizationTransition(path) {
513
+ return new AppConfigValidationError([
514
+ {
515
+ schemaVersion: SCHEMA_VERSIONS.diagnostic,
516
+ code: 'APP_CONFIG_AUTHZ_TRANSITION_INVALID',
517
+ severity: 'error',
518
+ message: '授权 transition 必须是只含 canonical 字段且值形状有界的对象',
519
+ path,
520
+ source: 'openxiangda-app.config.ts',
521
+ retryable: false,
522
+ },
523
+ ]);
524
+ }
525
+ export function compileContractBundle(config, configDigest, generatorVersion = 'openxiangda-devkit-core') {
526
+ const normalizedConfiguration = normalizeConfiguration(config);
527
+ return compileContractBundleFromNormalized(config, configDigest ?? sha256Bytes(canonicalJson(normalizedConfiguration)), generatorVersion, normalizedConfiguration);
528
+ }
529
+ function compileContractBundleFromNormalized(config, configDigest, generatorVersion, normalizedConfiguration) {
530
+ const capabilities = compileCapabilities(normalizedConfiguration);
531
+ const eventConsumers = sorted(normalizedConfiguration.events.subscriptions.map(subscription => ({
532
+ code: subscription.code,
533
+ endpointPath: subscription.endpointPath,
534
+ eventTypes: subscription.eventTypes,
535
+ filter: subscription.filter,
536
+ payload: subscription.payload,
537
+ ...(subscription.platformAccess
538
+ ? {
539
+ platformAccess: {
540
+ ...(subscription.platformAccess.notification
541
+ ? {
542
+ notification: {
543
+ mode: subscription.platformAccess.notification.mode,
544
+ },
545
+ }
546
+ : {}),
547
+ ...(subscription.platformAccess.managedFileCopies
548
+ ? {
549
+ managedFileCopies: sorted(subscription.platformAccess.managedFileCopies.map(item => ({
550
+ mode: item.mode,
551
+ sourceResourceCode: item.sourceResourceCode,
552
+ sourceFieldCodes: uniqueSorted(item.sourceFieldCodes),
553
+ targetResourceCode: item.targetResourceCode,
554
+ targetFieldCodes: uniqueSorted(item.targetFieldCodes),
555
+ })), item => `${item.sourceResourceCode}:${item.sourceFieldCodes.join(',')}=>${item.targetResourceCode}:${item.targetFieldCodes.join(',')}`),
556
+ }
557
+ : {}),
558
+ },
559
+ }
560
+ : {}),
561
+ delivery: subscription.delivery,
562
+ })), item => item.code);
563
+ const applicationSchemaVersions = new Map(normalizedConfiguration.events.schemas.map(schema => [
564
+ schema.eventType,
565
+ schema.dataSchemaVersion,
566
+ ]));
567
+ const dataEventProducers = config.data?.resources.flatMap(resource => DATA_EVENT_TYPES_V2.map(eventType => ({
568
+ code: `data:${resource.code}:${eventType}`,
569
+ source: 'data',
570
+ eventType,
571
+ dataSchemaVersion: '2.0.0',
572
+ resourceCode: resource.code,
573
+ }))) || [];
574
+ const workflowEventProducers = config.workflows?.activations.length
575
+ ? WORKFLOW_EVENT_TYPES_V2.map(eventType => ({
576
+ code: `workflow:${eventType}`,
577
+ source: 'workflow',
578
+ eventType,
579
+ dataSchemaVersion: '2.0.0',
580
+ }))
581
+ : [];
582
+ const eventProducers = sorted([
583
+ ...dataEventProducers,
584
+ ...normalizedConfiguration.events.schemas.map(schema => ({
585
+ code: `app:${schema.eventType}`,
586
+ source: 'app',
587
+ eventType: schema.eventType,
588
+ dataSchemaVersion: schema.dataSchemaVersion,
589
+ })),
590
+ ...normalizedConfiguration.events.timers.map(timer => ({
591
+ code: timer.code,
592
+ source: 'timer',
593
+ eventType: timer.eventType,
594
+ dataSchemaVersion: applicationSchemaVersions.get(timer.eventType) || '2.0.0',
595
+ })),
596
+ ...normalizedConfiguration.events.dateTriggers.map(trigger => ({
597
+ code: trigger.code,
598
+ source: 'date',
599
+ eventType: trigger.eventType,
600
+ dataSchemaVersion: applicationSchemaVersions.get(trigger.eventType) || '2.0.0',
601
+ resourceCode: trigger.resourceCode,
602
+ field: trigger.field,
603
+ })),
604
+ ...workflowEventProducers,
605
+ ], item => `${item.source}:${item.code}:${item.eventType}`);
606
+ const eventTypes = uniqueSorted([
607
+ ...eventConsumers.flatMap(item => item.eventTypes),
608
+ ...eventProducers.map(item => item.eventType),
609
+ ]);
610
+ const workflows = compileWorkflows(config);
611
+ const adminPages = compileAdminPages(config);
612
+ return {
613
+ schemaVersion: CONTRACT_BUNDLE_SCHEMA,
614
+ compilerContractVersion: COMPILER_CONTRACT_VERSION,
615
+ generatorVersion,
616
+ appCode: config.app.code,
617
+ configDigest,
618
+ perspectives: normalizedConfiguration.perspectives,
619
+ resources: compileResources(config),
620
+ capabilities,
621
+ operations: compileOperations(config),
622
+ eventConsumers,
623
+ eventProducers,
624
+ eventSchemas: normalizedConfiguration.events.schemas,
625
+ eventHandlerManifest: {
626
+ schemaVersion: SCHEMA_VERSIONS.eventHandlerManifest,
627
+ appCode: config.app.code,
628
+ handlers: eventConsumers.map(consumer => ({
629
+ code: consumer.code,
630
+ endpointPath: consumer.endpointPath,
631
+ eventTypes: consumer.eventTypes,
632
+ dataSchemaVersions: uniqueSorted(consumer.eventTypes.map(eventType => eventType.startsWith('openxiangda.')
633
+ ? '2.0.0'
634
+ : applicationSchemaVersions.get(eventType) || '2.0.0')),
635
+ maxBodyBytes: 65_536,
636
+ receiptProtocolVersion: 2,
637
+ })),
638
+ },
639
+ eventTypes,
640
+ workflows,
641
+ routes: compileFrontendRoutes(config),
642
+ authentication: compileApplicationAuthentication(config),
643
+ ...(config.frontend.publicAccess
644
+ ? { publicAccess: compileAnonymousPublicAccess(config) }
645
+ : {}),
646
+ ...(normalizedConfiguration.frontend.admin.access
647
+ ? { adminAccess: normalizedConfiguration.frontend.admin.access }
648
+ : {}),
649
+ routeManifest: compileStandardRouteManifest(config),
650
+ adminPages,
651
+ adminNavigation: compileAdminNavigation(config),
652
+ };
653
+ }
654
+ function compilePerspectives(config) {
655
+ const capabilitiesByRole = new Map((config.authz?.roles || []).map(role => [role.code, role.capabilities]));
656
+ return sorted((config.perspectives || []).map(perspective => ({
657
+ code: perspective.code,
658
+ name: perspective.name,
659
+ ...(perspective.description
660
+ ? { description: perspective.description }
661
+ : {}),
662
+ roleCodes: uniqueSorted(perspective.roleCodes),
663
+ capabilityCodes: uniqueSorted(perspective.roleCodes.flatMap(roleCode => capabilitiesByRole.get(roleCode) || [])),
664
+ ...(perspective.default === true ? { default: true } : {}),
665
+ })), item => item.code);
666
+ }
667
+ function compileFrontendRoutes(config) {
668
+ return sorted((config.frontend.routes || []).map(route => ({
669
+ code: route.code,
670
+ path: route.path,
671
+ label: route.label,
672
+ surface: route.surface,
673
+ ...(route.parentCode ? { parentCode: route.parentCode } : {}),
674
+ ...(route.capability ? { capability: route.capability } : {}),
675
+ ...(route.access
676
+ ? {
677
+ access: {
678
+ ...(route.access.allOf
679
+ ? { allOf: uniqueSorted([...route.access.allOf]) }
680
+ : {}),
681
+ ...(route.access.anyOf
682
+ ? { anyOf: uniqueSorted([...route.access.anyOf]) }
683
+ : {}),
684
+ },
685
+ }
686
+ : {}),
687
+ ...(route.pinned !== undefined ? { pinned: route.pinned } : {}),
688
+ tabPersistence: route.tabPersistence ?? (/[:*]/.test(route.path) ? 'none' : 'session'),
689
+ keepAlive: route.keepAlive ?? 'none',
690
+ })), item => item.code);
691
+ }
692
+ function compileApplicationAuthentication(config) {
693
+ const authentication = config.frontend.authentication;
694
+ if (!authentication)
695
+ return null;
696
+ return {
697
+ accountMode: authentication.accountMode,
698
+ registration: { mode: authentication.registration.mode },
699
+ methods: authentication.methods.map(method => ({ ...method })),
700
+ surfaces: {
701
+ desktop: { ...authentication.surfaces.desktop },
702
+ mobile: { ...authentication.surfaces.mobile },
703
+ },
704
+ };
705
+ }
706
+ function compileAnonymousPublicAccess(config) {
707
+ const publicAccess = config.frontend.publicAccess;
708
+ if (!publicAccess)
709
+ return null;
710
+ return {
711
+ policies: sorted(publicAccess.policies.map(policy => ({
712
+ code: policy.code,
713
+ routeCode: policy.routeCode,
714
+ mode: policy.mode,
715
+ resourceCode: policy.resourceCode,
716
+ operations: uniqueSorted([...policy.operations]),
717
+ fields: uniqueSorted([...policy.fields]),
718
+ ...(policy.requiredFields
719
+ ? { requiredFields: uniqueSorted([...policy.requiredFields]) }
720
+ : {}),
721
+ ...(policy.ownRecordFields
722
+ ? { ownRecordFields: uniqueSorted([...policy.ownRecordFields]) }
723
+ : {}),
724
+ ...(policy.draft
725
+ ? {
726
+ draft: {
727
+ enabled: true,
728
+ inactivityTtlSeconds: policy.draft.inactivityTtlSeconds ?? 2_592_000,
729
+ maxBytes: policy.draft.maxBytes ?? 262_144,
730
+ },
731
+ }
732
+ : {}),
733
+ ...(policy.validations
734
+ ? {
735
+ validations: sorted(policy.validations.map(validation => ({
736
+ code: validation.code,
737
+ kind: validation.kind,
738
+ fields: uniqueSorted([...validation.fields]),
739
+ result: validation.result,
740
+ })), item => item.code),
741
+ }
742
+ : {}),
743
+ })), item => item.code),
744
+ };
745
+ }
746
+ function generatedResourceOperations(resource) {
747
+ const native = (resource.surface?.mutationOwner || 'native') === 'native';
748
+ return {
749
+ list: resource.surface?.generated?.list ?? true,
750
+ detail: resource.surface?.generated?.detail ?? true,
751
+ create: resource.surface?.generated?.create ?? native,
752
+ update: resource.surface?.generated?.update ?? native,
753
+ delete: resource.surface?.generated?.delete ?? native,
754
+ };
755
+ }
756
+ function normalizeWorkflowNamedOperationIntent(intent) {
757
+ return {
758
+ operationCode: intent.operationCode,
759
+ inputs: Object.fromEntries(Object.entries(intent.inputs)
760
+ .sort(([left], [right]) => compare(left, right))
761
+ .map(([inputCode, binding]) => [inputCode, { ...binding }])),
762
+ output: { ...intent.output },
763
+ };
764
+ }
765
+ function normalizeWorkflowLaunchDeclaration(launch) {
766
+ return {
767
+ mode: launch?.mode || 'work-center-only',
768
+ ...(launch?.submission
769
+ ? {
770
+ submission: {
771
+ kind: 'named-operation',
772
+ ...(launch.submission.create
773
+ ? {
774
+ create: normalizeWorkflowNamedOperationIntent(launch.submission.create),
775
+ }
776
+ : {}),
777
+ ...(launch.submission.existing
778
+ ? {
779
+ existing: normalizeWorkflowNamedOperationIntent(launch.submission.existing),
780
+ }
781
+ : {}),
782
+ ...(launch.submission.context
783
+ ? {
784
+ context: [...launch.submission.context]
785
+ .map(context => ({ ...context }))
786
+ .sort((left, right) => compare(left.queryParameter, right.queryParameter)),
787
+ }
788
+ : {}),
789
+ },
790
+ }
791
+ : {}),
792
+ };
793
+ }
794
+ function compileWorkflowNamedOperationIntent(config, intent) {
795
+ const operation = (config.backend?.operations || []).find(candidate => candidate.code === intent.operationCode);
796
+ return {
797
+ ...normalizeWorkflowNamedOperationIntent(intent),
798
+ method: 'POST',
799
+ path: operation.path,
800
+ requiredCapability: operation.capability,
801
+ requestSchemaDigest: sha256Digest(operation.requestSchema),
802
+ responseSchemaDigest: sha256Digest(operation.responseSchema),
803
+ };
804
+ }
805
+ function compileWorkflowLaunchContract(config, launch) {
806
+ const normalized = normalizeWorkflowLaunchDeclaration(launch);
807
+ if (!normalized.submission)
808
+ return { mode: normalized.mode };
809
+ return {
810
+ mode: normalized.mode,
811
+ submission: {
812
+ kind: 'named-operation',
813
+ ...(normalized.submission.create
814
+ ? {
815
+ create: compileWorkflowNamedOperationIntent(config, normalized.submission.create),
816
+ }
817
+ : {}),
818
+ ...(normalized.submission.existing
819
+ ? {
820
+ existing: compileWorkflowNamedOperationIntent(config, normalized.submission.existing),
821
+ }
822
+ : {}),
823
+ context: [...(normalized.submission.context || [])],
824
+ },
825
+ };
826
+ }
827
+ function generatedAdminResourcePath(resourceCode, operation) {
828
+ const base = `/admin/resources/${resourceCode}`;
829
+ if (operation === 'list')
830
+ return base;
831
+ if (operation === 'create')
832
+ return `${base}/new`;
833
+ if (operation === 'update')
834
+ return `${base}/:id/edit`;
835
+ return `${base}/:id`;
836
+ }
837
+ function compileAdminPages(config) {
838
+ const pages = [];
839
+ for (const resource of sorted(config.data?.resources || [], item => item.code)) {
840
+ const generated = generatedResourceOperations(resource);
841
+ const candidates = [
842
+ {
843
+ operation: 'list',
844
+ kind: 'resource-list',
845
+ path: generatedAdminResourcePath(resource.code, 'list'),
846
+ label: resource.name,
847
+ capability: resource.capabilities.read,
848
+ navigationEligible: true,
849
+ },
850
+ {
851
+ operation: 'detail',
852
+ kind: 'resource-detail',
853
+ path: generatedAdminResourcePath(resource.code, 'detail'),
854
+ label: `${resource.name}详情`,
855
+ capability: resource.capabilities.read,
856
+ navigationEligible: false,
857
+ },
858
+ {
859
+ operation: 'create',
860
+ kind: 'resource-create',
861
+ path: generatedAdminResourcePath(resource.code, 'create'),
862
+ label: `新增${resource.name}`,
863
+ capability: resource.capabilities.create,
864
+ navigationEligible: false,
865
+ },
866
+ {
867
+ operation: 'update',
868
+ kind: 'resource-update',
869
+ path: generatedAdminResourcePath(resource.code, 'update'),
870
+ label: `编辑${resource.name}`,
871
+ capability: resource.capabilities.update,
872
+ navigationEligible: false,
873
+ },
874
+ ];
875
+ for (const candidate of candidates) {
876
+ if (!generated[candidate.operation])
877
+ continue;
878
+ pages.push({
879
+ code: `resource:${resource.code}:${candidate.operation}`,
880
+ kind: candidate.kind,
881
+ path: candidate.path,
882
+ label: candidate.label,
883
+ navigationEligible: candidate.navigationEligible,
884
+ capability: candidate.capability,
885
+ resourceCode: resource.code,
886
+ });
887
+ }
888
+ }
889
+ for (const route of compileFrontendRoutes(config)) {
890
+ if (route.surface !== 'admin')
891
+ continue;
892
+ pages.push({
893
+ code: `operation:${route.code}`,
894
+ kind: 'operation',
895
+ path: route.path,
896
+ label: route.label,
897
+ navigationEligible: !route.path.startsWith('/m/admin') && !/[:*]/.test(route.path),
898
+ routeCode: route.code,
899
+ });
900
+ }
901
+ return pages.sort((left, right) => compare(left.code, right.code));
902
+ }
903
+ function routePathParams(path) {
904
+ return uniqueSorted([...path.matchAll(/:([A-Za-z][A-Za-z0-9_]*)/g)].map(match => match[1]));
905
+ }
906
+ export const DEFAULT_ROUTE_MANIFEST_DEVICE_POLICY = Object.freeze({
907
+ kind: 'viewport-family',
908
+ mobileMaxWidthPx: 900,
909
+ desktopMinWidthPx: 901,
910
+ });
911
+ function compileRouteManifestDevicePolicy(config) {
912
+ return {
913
+ ...DEFAULT_ROUTE_MANIFEST_DEVICE_POLICY,
914
+ ...(config.frontend.devicePolicy || {}),
915
+ };
916
+ }
917
+ function compileStandardRouteManifestRoute(routeCode, path, surface, access) {
918
+ return {
919
+ routeCode,
920
+ path,
921
+ surface,
922
+ pathParams: routePathParams(path),
923
+ ...(access?.capability ? { capability: access.capability } : {}),
924
+ ...(access?.access ? { access: access.access } : {}),
925
+ requiresAuthentication: true,
926
+ };
927
+ }
928
+ function standardRouteCode(kind, device, workflowCode) {
929
+ if (kind === 'workflow-launch')
930
+ return `workflow.${workflowCode}.launch.${device}`;
931
+ if (kind === 'application-todo-center')
932
+ return `application.todo-center.${device}`;
933
+ return `${kind.replaceAll('-', '.')}.${device}`;
934
+ }
935
+ function compileStandardRouteManifest(config) {
936
+ const routes = [];
937
+ const addRoute = (code, kind, desktopPath, mobilePath, workflowCode, access) => {
938
+ routes.push({
939
+ code,
940
+ kind,
941
+ ...(workflowCode ? { workflowCode } : {}),
942
+ desktop: compileStandardRouteManifestRoute(standardRouteCode(kind, 'desktop', workflowCode), desktopPath, 'user', access),
943
+ mobile: compileStandardRouteManifestRoute(standardRouteCode(kind, 'mobile', workflowCode), mobilePath, 'user', access),
944
+ });
945
+ };
946
+ if (config.frontend.user?.applicationTodoCenter === true) {
947
+ addRoute('application:todo-center', 'application-todo-center', '/todos', '/m/todos');
948
+ }
949
+ const workflows = compileWorkflows(config);
950
+ if (workflows.length > 0) {
951
+ addRoute('workflow:work-center', 'workflow-work-center', '/work-center', '/m/work-center');
952
+ addRoute('workflow:task', 'workflow-task', '/tasks/:taskId', '/m/tasks/:taskId');
953
+ addRoute('workflow:instance', 'workflow-instance', '/workflows/:instanceId', '/m/workflows/:instanceId');
954
+ }
955
+ for (const workflow of workflows) {
956
+ if (!['standalone', 'hidden-handoff'].includes(workflow.launch.mode))
957
+ continue;
958
+ const namedCapabilities = uniqueSorted([
959
+ workflow.launch.submission?.create?.requiredCapability,
960
+ workflow.launch.submission?.existing?.requiredCapability,
961
+ ].filter((capability) => Boolean(capability)));
962
+ addRoute(`workflow:${workflow.code}:launch`, 'workflow-launch', '/workflows/:workflowCode/start', '/m/workflows/:workflowCode/start', workflow.code, namedCapabilities.length === 1
963
+ ? { capability: namedCapabilities[0] }
964
+ : namedCapabilities.length > 1
965
+ ? { access: { anyOf: namedCapabilities } }
966
+ : undefined);
967
+ }
968
+ const normalizedRoutes = routes.sort((left, right) => compare(left.code, right.code));
969
+ const staticRoute = (device) => {
970
+ const expectedPath = device === 'mobile' ? '/m/' : '/';
971
+ const declared = (config.frontend.routes || [])
972
+ .filter(route => route.surface === 'user' &&
973
+ validManifestStaticPath(route.path) &&
974
+ route.path === expectedPath)
975
+ .sort((left, right) => compare(left.code, right.code))[0];
976
+ if (declared &&
977
+ declared.surface === 'user' &&
978
+ validManifestStaticPath(declared.path)) {
979
+ return { code: declared.code, path: declared.path };
980
+ }
981
+ const fallback = (config.frontend.routes || [])
982
+ .filter(route => route.surface === 'user' &&
983
+ validManifestStaticPath(route.path) &&
984
+ (device === 'mobile'
985
+ ? route.path.startsWith('/m/')
986
+ : !route.path.startsWith('/m/')))
987
+ .sort((left, right) => compare(left.code, right.code))[0];
988
+ return fallback
989
+ ? { code: fallback.code, path: fallback.path }
990
+ : {
991
+ code: device === 'mobile' ? 'application-root-mobile' : 'application-root',
992
+ path: expectedPath,
993
+ };
994
+ };
995
+ const authentication = config.frontend.authentication
996
+ ? {
997
+ desktop: {
998
+ routeCode: config.frontend.authentication.surfaces.desktop.routeCode,
999
+ path: config.frontend.authentication.surfaces.desktop.path,
1000
+ },
1001
+ mobile: {
1002
+ routeCode: config.frontend.authentication.surfaces.mobile.routeCode,
1003
+ path: config.frontend.authentication.surfaces.mobile.path,
1004
+ },
1005
+ }
1006
+ : {
1007
+ desktop: { routeCode: 'application-login', path: '/login' },
1008
+ mobile: { routeCode: 'application-login-mobile', path: '/m/login' },
1009
+ };
1010
+ const rootDesktop = staticRoute('desktop');
1011
+ const rootMobile = staticRoute('mobile');
1012
+ const payload = {
1013
+ schemaVersion: SCHEMA_VERSIONS.applicationRouteManifest,
1014
+ appCode: config.app.code,
1015
+ devicePolicy: compileRouteManifestDevicePolicy(config),
1016
+ rootEntry: {
1017
+ code: rootDesktop.code,
1018
+ desktop: rootDesktop.path,
1019
+ mobile: rootMobile.path,
1020
+ },
1021
+ authentication,
1022
+ routes: normalizedRoutes,
1023
+ };
1024
+ return { ...payload, digest: sha256Digest(payload) };
1025
+ }
1026
+ function validManifestStaticPath(path) {
1027
+ return (path.startsWith('/') &&
1028
+ !path.startsWith('//') &&
1029
+ !path.includes('..') &&
1030
+ !/[:*?#\\]/.test(path) &&
1031
+ path.length <= 2048);
1032
+ }
1033
+ function pageCodeForReference(reference) {
1034
+ if (reference.kind === 'resource')
1035
+ return `resource:${reference.resourceCode}:list`;
1036
+ return `operation:${reference.routeCode}`;
1037
+ }
1038
+ function compileAdminNavigation(config) {
1039
+ return (config.frontend.admin?.navigation || [])
1040
+ .map((group, groupIndex) => ({
1041
+ code: group.code,
1042
+ label: group.label,
1043
+ ...(group.icon ? { icon: group.icon } : {}),
1044
+ order: group.order ?? groupIndex,
1045
+ items: group.items
1046
+ .map((item, itemIndex) => ({
1047
+ pageCode: pageCodeForReference(item.page),
1048
+ ...(item.label ? { label: item.label } : {}),
1049
+ ...(item.icon ? { icon: item.icon } : {}),
1050
+ order: item.order ?? itemIndex,
1051
+ }))
1052
+ .sort((left, right) => left.order - right.order || compare(left.pageCode, right.pageCode)),
1053
+ }))
1054
+ .sort((left, right) => left.order - right.order || compare(left.code, right.code));
1055
+ }
1056
+ /**
1057
+ * Compile the capability projection from normalized configuration only.
1058
+ *
1059
+ * Catalog ownership is validated before compilation. This keeps the existing
1060
+ * platform -> explicit -> resource registration semantics and the resource
1061
+ * owner when a field policy reuses its code. The only new determinism rule is
1062
+ * that normalized resources/field keys and the platform's fixed operation
1063
+ * order are used instead of source object insertion order.
1064
+ */
1065
+ function compileCapabilities(configuration) {
1066
+ const capabilities = new Map();
1067
+ for (const item of platformCapabilities(configuration.appCode)) {
1068
+ capabilities.set(item.code, item);
1069
+ }
1070
+ for (const item of [...configuration.authz.capabilities].sort((left, right) => compare(left.code, right.code))) {
1071
+ capabilities.set(item.code, {
1072
+ ...item,
1073
+ source: 'explicit',
1074
+ });
1075
+ }
1076
+ for (const resource of [...configuration.data.resources].sort((left, right) => compare(left.code, right.code))) {
1077
+ const operationNames = {
1078
+ read: `读取${resource.name}`,
1079
+ create: `新建${resource.name}`,
1080
+ update: `更新${resource.name}`,
1081
+ delete: `删除${resource.name}`,
1082
+ };
1083
+ for (const operation of ['read', 'create', 'update', 'delete']) {
1084
+ const code = resource.capabilities[operation];
1085
+ if (!code)
1086
+ continue;
1087
+ capabilities.set(code, {
1088
+ code,
1089
+ kind: 'data',
1090
+ name: operationNames[operation] ?? `${resource.name} ${operation}`,
1091
+ source: 'data',
1092
+ });
1093
+ }
1094
+ for (const [fieldCode, policy] of Object.entries(resource.fieldPolicies || {}).sort(([left], [right]) => compare(left, right))) {
1095
+ for (const operation of ['read', 'create', 'update']) {
1096
+ const codes = policy[operation];
1097
+ if (!Array.isArray(codes))
1098
+ continue;
1099
+ for (const code of [...codes].sort(compare)) {
1100
+ if (capabilities.has(code))
1101
+ continue;
1102
+ capabilities.set(code, {
1103
+ code,
1104
+ kind: 'data',
1105
+ name: `${resource.name}.${fieldCode}.${operation}`,
1106
+ source: 'data',
1107
+ });
1108
+ }
1109
+ }
1110
+ }
1111
+ }
1112
+ return [...capabilities.values()].sort((left, right) => compare(left.code, right.code));
1113
+ }
1114
+ function compileResources(config) {
1115
+ return sorted((config.data?.resources || []).map(sourceResource => {
1116
+ const resource = normalizeDataResource(sourceResource);
1117
+ return {
1118
+ code: resource.code,
1119
+ // The platform only receives immutable artifact bytes. Bind the
1120
+ // contract to the normalized schema in config-bundle/v3, never to
1121
+ // ordering or optional-property details that existed only in source.
1122
+ schemaDigest: sha256Digest(resource.schema),
1123
+ fields: sorted(resource.schema.fields.map(field => ({
1124
+ code: field.code,
1125
+ type: field.type,
1126
+ })), field => field.code),
1127
+ };
1128
+ }), item => item.code);
1129
+ }
1130
+ function compileOperations(config) {
1131
+ return sorted((config.backend.operations || []).map(operation => ({
1132
+ code: operation.code,
1133
+ method: operation.method,
1134
+ path: operation.path,
1135
+ requiredCapability: operation.capability,
1136
+ requestSchemaDigest: sha256Digest(operation.requestSchema),
1137
+ responseSchemaDigest: sha256Digest(operation.responseSchema),
1138
+ ...(operation.description
1139
+ ? { description: operation.description }
1140
+ : {}),
1141
+ ...(operation.platformAccess
1142
+ ? {
1143
+ platformAccess: {
1144
+ ...(operation.platformAccess.directory
1145
+ ? {
1146
+ directory: {
1147
+ mode: operation.platformAccess.directory.mode,
1148
+ fields: uniqueSorted(operation.platformAccess.directory.fields),
1149
+ },
1150
+ }
1151
+ : {}),
1152
+ ...(operation.platformAccess.managedFiles
1153
+ ? {
1154
+ managedFiles: sorted(operation.platformAccess.managedFiles.map(item => ({
1155
+ resourceCode: item.resourceCode,
1156
+ fieldCodes: uniqueSorted(item.fieldCodes),
1157
+ intents: uniqueSorted(item.intents),
1158
+ })), item => item.resourceCode),
1159
+ }
1160
+ : {}),
1161
+ ...(operation.platformAccess.managedFileCopies
1162
+ ? {
1163
+ managedFileCopies: sorted(operation.platformAccess.managedFileCopies.map(item => ({
1164
+ mode: item.mode,
1165
+ sourceResourceCode: item.sourceResourceCode,
1166
+ sourceFieldCodes: uniqueSorted(item.sourceFieldCodes),
1167
+ targetResourceCode: item.targetResourceCode,
1168
+ targetFieldCodes: uniqueSorted(item.targetFieldCodes),
1169
+ })), item => `${item.sourceResourceCode}:${item.sourceFieldCodes.join(',')}=>${item.targetResourceCode}:${item.targetFieldCodes.join(',')}`),
1170
+ }
1171
+ : {}),
1172
+ ...(operation.platformAccess.notification
1173
+ ? {
1174
+ notification: {
1175
+ mode: operation.platformAccess.notification.mode,
1176
+ },
1177
+ }
1178
+ : {}),
1179
+ ...(operation.platformAccess.workflow
1180
+ ? {
1181
+ workflow: {
1182
+ codes: uniqueSorted(operation.platformAccess.workflow.codes),
1183
+ },
1184
+ }
1185
+ : {}),
1186
+ },
1187
+ }
1188
+ : {}),
1189
+ })), item => item.code);
1190
+ }
1191
+ function compileWorkflows(config) {
1192
+ const workflowCodes = uniqueSorted((config.workflows?.definitions || []).map(item => item.definition.code));
1193
+ return workflowCodes.map(code => {
1194
+ const definitions = (config.workflows?.definitions || []).filter(item => item.definition.code === code);
1195
+ const activation = (config.workflows?.activations || []).find(item => item.workflowCode === code);
1196
+ const currentDefinition = definitions.find(item => item.version === activation?.definitionVersion) ||
1197
+ [...definitions].sort((left, right) => right.version - left.version)[0];
1198
+ const allowedOperations = new Set();
1199
+ for (const declaration of definitions) {
1200
+ for (const node of Object.values(declaration.definition.nodes)) {
1201
+ if (node.kind !== 'approval')
1202
+ continue;
1203
+ for (const operation of node.allowedOperations || []) {
1204
+ allowedOperations.add(operation);
1205
+ }
1206
+ }
1207
+ }
1208
+ return {
1209
+ code,
1210
+ title: currentDefinition.definition.title,
1211
+ acceptedCommandDeactivationPolicy: currentDefinition.definition.acceptedCommandDeactivationPolicy,
1212
+ subject: {
1213
+ ...currentDefinition.definition.subject,
1214
+ summaryFields: Array.isArray(currentDefinition.definition.subject.summaryFields)
1215
+ ? [...currentDefinition.definition.subject.summaryFields]
1216
+ : [],
1217
+ },
1218
+ launch: compileWorkflowLaunchContract(config, currentDefinition.launch),
1219
+ ...(['standalone', 'hidden-handoff'].includes(currentDefinition.launch?.mode || 'work-center-only') && !currentDefinition.launch?.submission
1220
+ ? {
1221
+ processOperationCode: `openxiangda.workflow.${code}.submit`,
1222
+ }
1223
+ : {}),
1224
+ ...(currentDefinition.detailRouteCode
1225
+ ? { detailRouteCode: { ...currentDefinition.detailRouteCode } }
1226
+ : {}),
1227
+ definitionVersions: uniqueSortedNumbers(definitions.map(item => item.version)),
1228
+ bindingVersions: uniqueSortedNumbers((config.workflows?.bindings || [])
1229
+ .filter(item => item.binding.workflowCode === code)
1230
+ .map(item => item.version)),
1231
+ providerCodes: uniqueSorted((config.workflows?.providers || []).map(item => item.code)),
1232
+ allowedOperations: [...allowedOperations].sort(compare),
1233
+ editableParameterCodes: uniqueSorted((config.workflows?.editableParameters || [])
1234
+ .filter(item => item.workflowCode === code)
1235
+ .map(item => item.code)),
1236
+ };
1237
+ });
1238
+ }
1239
+ function runtimeProtocolCapabilities(config) {
1240
+ const operations = config.backend.operations || [];
1241
+ const usesDirectory = config.data?.resources.some(resource => resource.schema.fields.some(field => field.type.startsWith('user.') || field.type.startsWith('department.'))) || operations.some(operation => operation.platformAccess?.directory);
1242
+ return uniqueSorted([
1243
+ 'application-native-2',
1244
+ 'authz.batch-explain',
1245
+ 'deployment.durable-runs',
1246
+ 'deployment.platform-executor',
1247
+ ...(config.data?.resources.length ? ['data-api-v2'] : []),
1248
+ ...(usesDirectory ? ['directory-v2'] : []),
1249
+ ...(operations.some(operation => operation.platformAccess?.managedFiles ||
1250
+ operation.platformAccess?.managedFileCopies) || config.events?.subscriptions.some(subscription => subscription.platformAccess?.managedFileCopies)
1251
+ ? ['data.managed-files']
1252
+ : []),
1253
+ ...(operations.some(operation => operation.platformAccess?.notification) ||
1254
+ config.events?.subscriptions.some(subscription => subscription.platformAccess?.notification)
1255
+ ? ['notification-hub-v2']
1256
+ : []),
1257
+ ...(operations.some(operation => operation.platformAccess?.workflow) ||
1258
+ (config.workflows?.definitions || []).some(declaration => declaration.launch?.mode === 'standalone' ||
1259
+ declaration.launch?.mode === 'hidden-handoff')
1260
+ ? ['business-process.durable-command']
1261
+ : []),
1262
+ ...(config.events?.subscriptions.length ||
1263
+ config.events?.timers?.length ||
1264
+ config.events?.dateTriggers?.length
1265
+ ? ['events-v2', 'events.durable-receipts']
1266
+ : []),
1267
+ ...(config.workflows?.activations.length
1268
+ ? ['workflow-kernel-v2', 'workflow.fresh-command-token']
1269
+ : []),
1270
+ ...(config.frontend.authentication
1271
+ ? ['authentication.application-login-surface']
1272
+ : []),
1273
+ ...(config.frontend.publicAccess
1274
+ ? ['public-access.anonymous-owner-records']
1275
+ : []),
1276
+ ]);
1277
+ }
1278
+ function platformCapabilities(appCode) {
1279
+ return nativePlatformCapabilityCatalog(appCode);
1280
+ }
1281
+ function normalizeDataResource(resource) {
1282
+ return {
1283
+ schemaVersion: resource.schemaVersion,
1284
+ appCode: resource.appCode,
1285
+ code: resource.code,
1286
+ name: resource.name,
1287
+ schema: {
1288
+ fields: resource.schema.fields.map(field => ({
1289
+ code: field.code,
1290
+ type: field.type,
1291
+ ...(field.nullable !== undefined ? { nullable: field.nullable } : {}),
1292
+ ...(field.indexed !== undefined ? { indexed: field.indexed } : {}),
1293
+ ...(field.options ? { options: field.options } : {}),
1294
+ ...(field.source
1295
+ ? { source: normalizeDataFieldSource(field.source) }
1296
+ : {}),
1297
+ ...(field.maxLength !== undefined ? { maxLength: field.maxLength } : {}),
1298
+ ...(field.precision !== undefined ? { precision: field.precision } : {}),
1299
+ ...(field.scale !== undefined ? { scale: field.scale } : {}),
1300
+ ...(field.min !== undefined ? { min: field.min } : {}),
1301
+ ...(field.max !== undefined ? { max: field.max } : {}),
1302
+ ...(field.rangeBoundary
1303
+ ? { rangeBoundary: field.rangeBoundary }
1304
+ : {}),
1305
+ ...(field.timePrecision ? { timePrecision: field.timePrecision } : {}),
1306
+ ...(field.file
1307
+ ? {
1308
+ file: {
1309
+ ...(field.file.maxCount !== undefined
1310
+ ? { maxCount: field.file.maxCount }
1311
+ : {}),
1312
+ ...(field.file.maxSizeMb !== undefined
1313
+ ? { maxSizeMb: field.file.maxSizeMb }
1314
+ : {}),
1315
+ ...(field.file.accept
1316
+ ? { accept: uniqueSorted(field.file.accept) }
1317
+ : {}),
1318
+ },
1319
+ }
1320
+ : {}),
1321
+ ...(field.serial ? { serial: { ...field.serial } } : {}),
1322
+ ...(field.subtable ? { subtable: { ...field.subtable } } : {}),
1323
+ })),
1324
+ },
1325
+ ...(resource.surface
1326
+ ? { surface: normalizeResourceSurface(resource.surface) }
1327
+ : {}),
1328
+ ...(resource.invariants
1329
+ ? {
1330
+ invariants: [...resource.invariants]
1331
+ .sort((left, right) => compare(left.code, right.code))
1332
+ .map(invariant => ({
1333
+ code: invariant.code,
1334
+ ...(invariant.message ? { message: invariant.message } : {}),
1335
+ expression: { ...invariant.expression },
1336
+ })),
1337
+ }
1338
+ : {}),
1339
+ capabilities: { ...resource.capabilities },
1340
+ ...(resource.dataPolicyCode
1341
+ ? { dataPolicyCode: resource.dataPolicyCode }
1342
+ : {}),
1343
+ fieldPolicies: Object.fromEntries(resource.schema.fields
1344
+ .map(field => [field.code, resource.fieldPolicies?.[field.code]])
1345
+ .sort(([left], [right]) => compare(left, right))
1346
+ .map(([fieldCode, policy]) => [
1347
+ fieldCode,
1348
+ {
1349
+ ...(policy && Object.prototype.hasOwnProperty.call(policy, 'read')
1350
+ ? { read: uniqueSorted(policy.read || []) }
1351
+ : {}),
1352
+ ...(policy && Object.prototype.hasOwnProperty.call(policy, 'create')
1353
+ ? { create: uniqueSorted(policy.create || []) }
1354
+ : {}),
1355
+ ...(policy && Object.prototype.hasOwnProperty.call(policy, 'update')
1356
+ ? { update: uniqueSorted(policy.update || []) }
1357
+ : { update: [] }),
1358
+ ...(policy?.mask ? { mask: policy.mask } : {}),
1359
+ },
1360
+ ])),
1361
+ };
1362
+ }
1363
+ function normalizeDataPolicyRule(rule) {
1364
+ if ('operator' in rule) {
1365
+ if ('operand' in rule) {
1366
+ return {
1367
+ field: rule.field,
1368
+ operator: rule.operator,
1369
+ operand: rule.operand,
1370
+ ...(rule.roleCodes ? { roleCodes: uniqueSorted(rule.roleCodes) } : {}),
1371
+ };
1372
+ }
1373
+ if (!('value' in rule)) {
1374
+ return {
1375
+ field: rule.field,
1376
+ operator: rule.operator,
1377
+ ...(rule.roleCodes ? { roleCodes: uniqueSorted(rule.roleCodes) } : {}),
1378
+ };
1379
+ }
1380
+ return {
1381
+ field: rule.field,
1382
+ operator: rule.operator,
1383
+ value: Array.isArray(rule.value)
1384
+ ? uniqueSorted(rule.value)
1385
+ : rule.value,
1386
+ ...(rule.roleCodes ? { roleCodes: uniqueSorted(rule.roleCodes) } : {}),
1387
+ };
1388
+ }
1389
+ if ('subject' in rule) {
1390
+ return {
1391
+ subject: rule.subject,
1392
+ field: rule.field,
1393
+ ...(rule.roleCodes ? { roleCodes: uniqueSorted(rule.roleCodes) } : {}),
1394
+ };
1395
+ }
1396
+ if ('dimensionCode' in rule) {
1397
+ return {
1398
+ dimensionCode: rule.dimensionCode,
1399
+ field: rule.field,
1400
+ ...(rule.roleCodes ? { roleCodes: uniqueSorted(rule.roleCodes) } : {}),
1401
+ ...(rule.operation ? { operation: rule.operation } : {}),
1402
+ ...(rule.valuePath ? { valuePath: rule.valuePath } : {}),
1403
+ ...(rule.emptyMatchesAll !== undefined
1404
+ ? { emptyMatchesAll: rule.emptyMatchesAll }
1405
+ : {}),
1406
+ };
1407
+ }
1408
+ return {
1409
+ relationCode: rule.relationCode,
1410
+ resourceCode: rule.resourceCode,
1411
+ field: rule.field,
1412
+ ...(rule.roleCodes ? { roleCodes: uniqueSorted(rule.roleCodes) } : {}),
1413
+ ...(rule.operation ? { operation: rule.operation } : {}),
1414
+ ...(rule.valuePath ? { valuePath: rule.valuePath } : {}),
1415
+ };
1416
+ }
1417
+ function normalizeDataPolicyExpression(expression) {
1418
+ if ('allOf' in expression) {
1419
+ return { allOf: expression.allOf.map(normalizeDataPolicyExpression) };
1420
+ }
1421
+ if ('anyOf' in expression) {
1422
+ return { anyOf: expression.anyOf.map(normalizeDataPolicyExpression) };
1423
+ }
1424
+ return normalizeDataPolicyRule(expression);
1425
+ }
1426
+ function normalizeDataPolicyDeclaration(policy) {
1427
+ const common = {
1428
+ code: policy.code,
1429
+ name: policy.name,
1430
+ resourceCode: policy.resourceCode,
1431
+ ...(policy.unrestrictedRoleCodes !== undefined
1432
+ ? { unrestrictedRoleCodes: uniqueSorted(policy.unrestrictedRoleCodes) }
1433
+ : {}),
1434
+ };
1435
+ if (policy.readExpression !== undefined) {
1436
+ const readExpression = normalizeDataPolicyExpression(policy.readExpression);
1437
+ if (policy.writeBoundary === 'capability_only') {
1438
+ return {
1439
+ ...common,
1440
+ matchMode: 'AND',
1441
+ rules: [],
1442
+ readExpression,
1443
+ writeBoundary: 'capability_only',
1444
+ };
1445
+ }
1446
+ return {
1447
+ ...common,
1448
+ matchMode: policy.matchMode,
1449
+ rules: policy.rules.map(normalizeDataPolicyRule),
1450
+ readExpression,
1451
+ };
1452
+ }
1453
+ return {
1454
+ ...common,
1455
+ ...(policy.operations
1456
+ ? {
1457
+ operations: uniqueSorted(policy.operations),
1458
+ }
1459
+ : {}),
1460
+ matchMode: policy.matchMode,
1461
+ rules: policy.rules.map(normalizeDataPolicyRule),
1462
+ };
1463
+ }
1464
+ export function renderGeneratedContracts(config, contract = compileContractBundle(config)) {
1465
+ const resourceCodes = contract.resources.map(item => item.code);
1466
+ const capabilities = contract.capabilities.map(item => item.code);
1467
+ const eventSubscriptionCodes = contract.eventConsumers.map(item => item.code);
1468
+ const workflowCodes = contract.workflows.map(item => item.code);
1469
+ const resourceSurfaces = Object.fromEntries(sorted(config.data?.resources || [], item => item.code).map(item => [
1470
+ item.code,
1471
+ normalizeResourceSurface(item.surface || defaultResourceSurface(item)),
1472
+ ]));
1473
+ const resourceDefinitions = `{${resourceCodes.length
1474
+ ? `\n${sorted(config.data?.resources || [], item => item.code)
1475
+ .map(item => ` ${JSON.stringify(item.code)}: {\n` +
1476
+ ` code: ${JSON.stringify(item.code)},\n` +
1477
+ ` name: ${JSON.stringify(item.name)},\n` +
1478
+ ` capabilities: ${JSON.stringify(item.capabilities, null, 2)
1479
+ .split('\n')
1480
+ .map((line, index) => (index === 0 ? line : ` ${line}`))
1481
+ .join('\n')},\n` +
1482
+ ` surface: resourceSurfaces[${JSON.stringify(item.code)}],\n` +
1483
+ ' }')
1484
+ .join(',\n')}\n`
1485
+ : ''}}`;
1486
+ const operationEntries = contract.operations.map(operation => [
1487
+ camel(operation.code),
1488
+ operation,
1489
+ ]);
1490
+ const routeEntries = contract.routes.map(route => [camel(route.code), route]);
1491
+ const authenticationSurfaceEntries = contract.authentication
1492
+ ? ['desktop', 'mobile'].map(device => {
1493
+ const surface = contract.authentication.surfaces[device];
1494
+ return [camel(surface.routeCode), { device, ...surface }];
1495
+ })
1496
+ : [];
1497
+ const platformAuthManifest = contract.authentication
1498
+ ? {
1499
+ schemaVersion: 'openxiangda.platform-auth-manifest/v2',
1500
+ appCode: config.app.code,
1501
+ protectedUserRouteCount: contract.routes.filter(route => route.surface === 'user').length,
1502
+ methods: contract.authentication.methods,
1503
+ surfaces: ['desktop', 'mobile'].map(device => ({
1504
+ device,
1505
+ ...contract.authentication.surfaces[device],
1506
+ })),
1507
+ }
1508
+ : null;
1509
+ const eventHandlerEntries = contract.eventHandlerManifest.handlers.map(handler => [
1510
+ handler.code,
1511
+ handler,
1512
+ ]);
1513
+ const eventTypesForSubscription = contract.eventConsumers.length
1514
+ ? contract.eventConsumers
1515
+ .map(consumer => `TCode extends ${JSON.stringify(consumer.code)} ? ${consumer.eventTypes
1516
+ .map(eventType => JSON.stringify(eventType))
1517
+ .join(' | ')} :`)
1518
+ .join(' ') + ' never'
1519
+ : 'never';
1520
+ const lines = [
1521
+ '// Generated by OpenXiangda 2.0 Native compiler. Do not edit by hand.',
1522
+ `export const compilerContractVersion = ${JSON.stringify(COMPILER_CONTRACT_VERSION)} as const;`,
1523
+ `export const appCode = ${JSON.stringify(config.app.code)} as const;`,
1524
+ `export const appName = ${JSON.stringify(config.app.name)} as const;`,
1525
+ `export const appPerspectives = ${JSON.stringify(contract.perspectives, null, 2)} as const;`,
1526
+ `export const resourceCodes = ${JSON.stringify(resourceCodes, null, 2)} as const;`,
1527
+ `export const resourceSurfaces = ${JSON.stringify(resourceSurfaces, null, 2)} as const;`,
1528
+ `export const resourceDefinitions = ${resourceDefinitions} as const;`,
1529
+ `export const capabilities = ${JSON.stringify(capabilities, null, 2)} as const;`,
1530
+ `export const appOperations = ${renderEntries(operationEntries)} as const;`,
1531
+ `export const appRoutes = ${renderEntries(routeEntries)} as const;`,
1532
+ `export const authenticationMethods = ${JSON.stringify(contract.authentication?.methods || [], null, 2)} as const;`,
1533
+ `export const authenticationSurfaces = ${renderEntries(authenticationSurfaceEntries)} as const;`,
1534
+ contract.authentication
1535
+ ? `export const applicationAuthentication = ${JSON.stringify(contract.authentication, null, 2)} as const;`
1536
+ : 'export const applicationAuthentication = undefined;',
1537
+ contract.publicAccess
1538
+ ? `export const anonymousPublicAccess = ${JSON.stringify(contract.publicAccess, null, 2)} as const;`
1539
+ : 'export const anonymousPublicAccess = undefined;',
1540
+ platformAuthManifest
1541
+ ? `export const platformAuthManifest = ${JSON.stringify(platformAuthManifest, null, 2)} as const;`
1542
+ : 'export const platformAuthManifest = null;',
1543
+ contract.adminAccess
1544
+ ? `export const adminAccess = ${JSON.stringify(contract.adminAccess, null, 2)} as const;`
1545
+ : 'export const adminAccess = undefined;',
1546
+ `export const routeManifest = ${JSON.stringify(contract.routeManifest, null, 2)} as const;`,
1547
+ `export const adminPages = ${JSON.stringify(contract.adminPages, null, 2)} as const;`,
1548
+ `export const adminNavigation = ${JSON.stringify(contract.adminNavigation, null, 2)} as const;`,
1549
+ `export const eventTypes = ${JSON.stringify(contract.eventTypes, null, 2)} as const;`,
1550
+ `export const eventSubscriptionCodes = ${JSON.stringify(eventSubscriptionCodes, null, 2)} as const;`,
1551
+ `export const eventHandlerManifest = ${JSON.stringify(contract.eventHandlerManifest, null, 2)} as const;`,
1552
+ `export const eventHandlers = ${renderEntries(eventHandlerEntries)} as const;`,
1553
+ `export const eventSchemas = ${JSON.stringify(contract.eventSchemas, null, 2)} as const;`,
1554
+ `export const workflowCodes = ${JSON.stringify(workflowCodes, null, 2)} as const;`,
1555
+ `export const workflowDefinitions = ${JSON.stringify(contract.workflows.map(workflow => ({
1556
+ code: workflow.code,
1557
+ title: workflow.title,
1558
+ acceptedCommandDeactivationPolicy: workflow.acceptedCommandDeactivationPolicy,
1559
+ subject: workflow.subject,
1560
+ launch: workflow.launch,
1561
+ ...(workflow.processOperationCode
1562
+ ? { processOperationCode: workflow.processOperationCode }
1563
+ : {}),
1564
+ ...(workflow.detailRouteCode
1565
+ ? { detailRouteCode: workflow.detailRouteCode }
1566
+ : {}),
1567
+ })), null, 2)} as const;`,
1568
+ 'export const notificationTemplateCodes = {',
1569
+ ' applicationInformational: "application.informational.standard",',
1570
+ '} as const;',
1571
+ '',
1572
+ 'export type ResourceCode = (typeof resourceCodes)[number];',
1573
+ 'export type Capability = (typeof capabilities)[number];',
1574
+ 'export type AppOperation = (typeof appOperations)[keyof typeof appOperations];',
1575
+ 'export type AppRoute = (typeof appRoutes)[keyof typeof appRoutes];',
1576
+ 'export type AppRouteCode = AppRoute["code"];',
1577
+ 'export type ApplicationAuthenticationMethod = (typeof authenticationMethods)[number];',
1578
+ 'export type ApplicationAuthenticationSurface = (typeof authenticationSurfaces)[keyof typeof authenticationSurfaces];',
1579
+ 'export type ApplicationAuthenticationSurfaceCode = ApplicationAuthenticationSurface["routeCode"];',
1580
+ 'export type AnonymousPublicAccess = typeof anonymousPublicAccess;',
1581
+ 'export type AdminAccess = typeof adminAccess;',
1582
+ 'export type RouteManifest = typeof routeManifest;',
1583
+ 'export type AdminPage = (typeof adminPages)[number];',
1584
+ 'export type AdminNavigationGroup = (typeof adminNavigation)[number];',
1585
+ 'export type AppPerspective = (typeof appPerspectives)[number];',
1586
+ 'export type EventType = (typeof eventTypes)[number];',
1587
+ 'export type EventSubscriptionCode = (typeof eventSubscriptionCodes)[number];',
1588
+ `export type EventTypesForSubscription<TCode extends EventSubscriptionCode> = ${eventTypesForSubscription};`,
1589
+ 'export type WorkflowCode = (typeof workflowCodes)[number];',
1590
+ 'export type NotificationTemplateCode = (typeof notificationTemplateCodes)[keyof typeof notificationTemplateCodes];',
1591
+ '',
1592
+ 'export interface OpenXiangdaCloudEvent<TType extends EventType = EventType, TData extends Record<string, unknown> = Record<string, unknown>> {',
1593
+ ' specversion: "1.0";',
1594
+ ' id: string;',
1595
+ ' source: string;',
1596
+ ' type: TType;',
1597
+ ' subject?: string;',
1598
+ ' time: string;',
1599
+ ' datacontenttype: "application/json";',
1600
+ ' dataschema?: string;',
1601
+ ' data: TData;',
1602
+ ' tenantid: string;',
1603
+ ' appcode: typeof appCode;',
1604
+ ' environment: string;',
1605
+ ' traceid?: string;',
1606
+ ' schemaversion: string;',
1607
+ '}',
1608
+ 'export type EventHandlerInput<TCode extends EventSubscriptionCode> = OpenXiangdaCloudEvent<EventTypesForSubscription<TCode>>;',
1609
+ '',
1610
+ FIELD_VALUE_TYPESCRIPT_DECLARATIONS,
1611
+ '',
1612
+ ];
1613
+ for (const resource of sorted(config.data?.resources || [], item => item.code)) {
1614
+ lines.push(`export interface ${pascal(resource.code)}Record {`);
1615
+ lines.push(' id: string;');
1616
+ lines.push(' revision: number;');
1617
+ for (const field of resource.schema.fields) {
1618
+ const nullable = fieldNullable(field);
1619
+ const optional = nullable ? '?' : '';
1620
+ lines.push(` ${safeProperty(field.code)}${optional}: ${typescriptType(field.type)}${nullable ? ' | null' : ''};`);
1621
+ }
1622
+ lines.push('}', '');
1623
+ lines.push(`export interface ${pascal(resource.code)}CreateInput {`);
1624
+ for (const field of resource.schema.fields) {
1625
+ const surface = resourceSurfaces[resource.code]?.fields[field.code];
1626
+ if (!supportsGeneratedMutation({
1627
+ type: field.type,
1628
+ system: surface?.system,
1629
+ }) ||
1630
+ !surface?.createCapabilities.length)
1631
+ continue;
1632
+ const nullable = fieldNullable(field);
1633
+ const optional = nullable ? '?' : '';
1634
+ lines.push(` ${safeProperty(field.code)}${optional}: ${typescriptType(field.type)}${nullable ? ' | null' : ''};`);
1635
+ }
1636
+ lines.push('}', '');
1637
+ lines.push(`export interface ${pascal(resource.code)}UpdateInput {`);
1638
+ for (const field of resource.schema.fields) {
1639
+ const surface = resourceSurfaces[resource.code]?.fields[field.code];
1640
+ if (!supportsGeneratedMutation({
1641
+ type: field.type,
1642
+ system: surface?.system,
1643
+ }) ||
1644
+ !surface?.updateCapabilities.length)
1645
+ continue;
1646
+ const nullable = fieldNullable(field);
1647
+ lines.push(` ${safeProperty(field.code)}?: ${typescriptType(field.type)}${nullable ? ' | null' : ''};`);
1648
+ }
1649
+ lines.push('}', '');
1650
+ }
1651
+ return `${lines.join('\n').trimEnd()}\n`;
1652
+ }
1653
+ function normalizeResourceSurface(surface) {
1654
+ const fields = Object.fromEntries(Object.entries(surface.fields)
1655
+ .sort(([left], [right]) => compare(left, right))
1656
+ .map(([code, field]) => [
1657
+ code,
1658
+ {
1659
+ label: field.label,
1660
+ type: field.type,
1661
+ widget: field.widget,
1662
+ ...(field.section ? { section: field.section } : {}),
1663
+ ...(field.requiredHint !== undefined
1664
+ ? { requiredHint: field.requiredHint }
1665
+ : {}),
1666
+ ...(field.system !== undefined ? { system: field.system } : {}),
1667
+ readCapabilities: uniqueSorted(field.readCapabilities),
1668
+ createCapabilities: uniqueSorted(field.createCapabilities),
1669
+ updateCapabilities: uniqueSorted(field.updateCapabilities),
1670
+ ...(field.maxLength !== undefined ? { maxLength: field.maxLength } : {}),
1671
+ ...(field.precision !== undefined ? { precision: field.precision } : {}),
1672
+ ...(field.scale !== undefined ? { scale: field.scale } : {}),
1673
+ ...(field.min !== undefined ? { min: field.min } : {}),
1674
+ ...(field.max !== undefined ? { max: field.max } : {}),
1675
+ ...(field.rangeBoundary
1676
+ ? { rangeBoundary: field.rangeBoundary }
1677
+ : {}),
1678
+ ...(field.maxCount !== undefined ? { maxCount: field.maxCount } : {}),
1679
+ ...(field.maxSizeMb !== undefined ? { maxSizeMb: field.maxSizeMb } : {}),
1680
+ ...(field.accept !== undefined ? { accept: field.accept } : {}),
1681
+ ...(field.options ? { options: field.options } : {}),
1682
+ ...(field.source
1683
+ ? { source: normalizeDataFieldSource(field.source) }
1684
+ : {}),
1685
+ ...(field.timePrecision
1686
+ ? { timePrecision: field.timePrecision }
1687
+ : {}),
1688
+ ...(field.serial ? { serial: { ...field.serial } } : {}),
1689
+ ...(field.subtable ? { subtable: { ...field.subtable } } : {}),
1690
+ ...(field.list !== undefined ? { list: field.list } : {}),
1691
+ ...(field.searchable !== undefined
1692
+ ? { searchable: field.searchable }
1693
+ : {}),
1694
+ ...(field.sortable !== undefined ? { sortable: field.sortable } : {}),
1695
+ },
1696
+ ]));
1697
+ return {
1698
+ ...(surface.mutationOwner !== undefined
1699
+ ? { mutationOwner: surface.mutationOwner }
1700
+ : {}),
1701
+ ...(surface.generated !== undefined
1702
+ ? { generated: { ...surface.generated } }
1703
+ : {}),
1704
+ fields,
1705
+ ...(surface.list
1706
+ ? {
1707
+ list: {
1708
+ ...(surface.list.defaultPageSize !== undefined
1709
+ ? { defaultPageSize: surface.list.defaultPageSize }
1710
+ : {}),
1711
+ ...(surface.list.searchableFields
1712
+ ? { searchableFields: [...surface.list.searchableFields] }
1713
+ : {}),
1714
+ ...(surface.list.filterFields
1715
+ ? { filterFields: [...surface.list.filterFields] }
1716
+ : {}),
1717
+ ...(surface.list.defaultSort
1718
+ ? { defaultSort: { ...surface.list.defaultSort } }
1719
+ : {}),
1720
+ },
1721
+ }
1722
+ : {}),
1723
+ ...(surface.form ? { form: { ...surface.form } } : {}),
1724
+ ...(surface.detail ? { detail: { ...surface.detail } } : {}),
1725
+ ...(surface.mobile ? { mobile: { ...surface.mobile } } : {}),
1726
+ };
1727
+ }
1728
+ function defaultResourceSurface(resource) {
1729
+ const fields = resource.schema.fields.filter(field => field.code !== 'id');
1730
+ const visible = fields.slice(0, 8);
1731
+ const first = fields[0]?.code || 'id';
1732
+ const searchableFields = fields
1733
+ .filter(field => supportsSearch(field.type))
1734
+ .slice(0, 5)
1735
+ .map(field => field.code);
1736
+ const surfaceFields = Object.fromEntries(fields.map(field => [
1737
+ field.code,
1738
+ {
1739
+ label: field.code,
1740
+ type: field.type,
1741
+ widget: resolveDataFieldSurfaceWidget(field),
1742
+ ...(field.nullable === false ? { requiredHint: true } : {}),
1743
+ ...(field.maxLength !== undefined ? { maxLength: field.maxLength } : {}),
1744
+ ...(field.precision !== undefined ? { precision: field.precision } : {}),
1745
+ ...(field.scale !== undefined ? { scale: field.scale } : {}),
1746
+ ...(field.min !== undefined ? { min: field.min } : {}),
1747
+ ...(field.max !== undefined ? { max: field.max } : {}),
1748
+ ...(field.rangeBoundary
1749
+ ? { rangeBoundary: field.rangeBoundary }
1750
+ : {}),
1751
+ ...(field.file?.maxCount !== undefined
1752
+ ? { maxCount: field.file.maxCount }
1753
+ : {}),
1754
+ ...(field.file?.maxSizeMb !== undefined
1755
+ ? { maxSizeMb: field.file.maxSizeMb }
1756
+ : {}),
1757
+ ...(field.file?.accept ? { accept: uniqueSorted(field.file.accept) } : {}),
1758
+ ...(field.options ? { options: field.options } : {}),
1759
+ ...(field.source ? { source: field.source } : {}),
1760
+ ...(field.timePrecision ? { timePrecision: field.timePrecision } : {}),
1761
+ ...(field.serial ? { serial: { ...field.serial } } : {}),
1762
+ ...(field.subtable ? { subtable: { ...field.subtable } } : {}),
1763
+ readCapabilities: [resource.capabilities.read],
1764
+ createCapabilities: [resource.capabilities.create],
1765
+ updateCapabilities: [resource.capabilities.update],
1766
+ ...(visible.some(item => item.code === field.code)
1767
+ ? { list: true }
1768
+ : {}),
1769
+ ...(searchableFields.includes(field.code) ? { searchable: true } : {}),
1770
+ ...(visible.some(item => item.code === field.code) &&
1771
+ supportsSort(field.type)
1772
+ ? { sortable: true }
1773
+ : {}),
1774
+ },
1775
+ ]));
1776
+ return {
1777
+ mutationOwner: 'native',
1778
+ generated: {
1779
+ list: true,
1780
+ detail: true,
1781
+ create: true,
1782
+ update: true,
1783
+ delete: true,
1784
+ },
1785
+ fields: surfaceFields,
1786
+ list: {
1787
+ defaultPageSize: 20,
1788
+ ...(searchableFields.length ? { searchableFields } : {}),
1789
+ filterFields: visible.map(field => field.code),
1790
+ defaultSort: { field: first, order: 'asc' },
1791
+ },
1792
+ form: { layout: 'flat', fieldOrder: fields.map(field => field.code) },
1793
+ detail: { layout: 'flat', fieldOrder: fields.map(field => field.code) },
1794
+ mobile: { enabled: true },
1795
+ };
1796
+ }
1797
+ function normalizeDataFieldSource(source) {
1798
+ return {
1799
+ kind: source.kind,
1800
+ resourceCode: source.resourceCode,
1801
+ labelField: source.labelField,
1802
+ ...(source.searchFields
1803
+ ? { searchFields: [...source.searchFields] }
1804
+ : {}),
1805
+ ...(source.descriptionFields
1806
+ ? { descriptionFields: [...source.descriptionFields] }
1807
+ : {}),
1808
+ ...(source.snapshotFields
1809
+ ? { snapshotFields: [...source.snapshotFields] }
1810
+ : {}),
1811
+ ...(source.filters
1812
+ ? {
1813
+ filters: source.filters.map(filter => 'binding' in filter
1814
+ ? { ...filter, binding: { ...filter.binding } }
1815
+ : { ...filter }),
1816
+ }
1817
+ : {}),
1818
+ ...(source.pageSize !== undefined ? { pageSize: source.pageSize } : {}),
1819
+ ...(source.loadMode ? { loadMode: source.loadMode } : {}),
1820
+ };
1821
+ }
1822
+ export function sha256Bytes(content) {
1823
+ return createHash('sha256').update(content).digest('hex');
1824
+ }
1825
+ function sorted(items, key) {
1826
+ return [...items].sort((left, right) => compare(key(left), key(right)));
1827
+ }
1828
+ function uniqueSorted(items) {
1829
+ return [...new Set(items)].sort(compare);
1830
+ }
1831
+ function uniqueSortedNumbers(items) {
1832
+ return [...new Set(items)].sort((left, right) => left - right);
1833
+ }
1834
+ function compare(left, right) {
1835
+ return left === right ? 0 : left < right ? -1 : 1;
1836
+ }
1837
+ function pad(value) {
1838
+ return String(value).padStart(10, '0');
1839
+ }
1840
+ function pascal(value) {
1841
+ const normalized = value
1842
+ .split(/[^A-Za-z0-9]+/)
1843
+ .filter(Boolean)
1844
+ .map(part => `${part[0]?.toUpperCase() || ''}${part.slice(1)}`)
1845
+ .join('');
1846
+ return /^[A-Za-z_$]/.test(normalized) ? normalized : `Resource${normalized}`;
1847
+ }
1848
+ function camel(value) {
1849
+ const pascalValue = pascal(value);
1850
+ return `${pascalValue[0]?.toLowerCase() || ''}${pascalValue.slice(1)}`;
1851
+ }
1852
+ function renderEntries(entries) {
1853
+ if (entries.length === 0)
1854
+ return '{}';
1855
+ const body = entries
1856
+ .map(([key, value]) => ` ${safeProperty(key)}: ${JSON.stringify(value, null, 2).replace(/\n/g, '\n ')}`)
1857
+ .join(',\n');
1858
+ return `{\n${body}\n}`;
1859
+ }
1860
+ function safeProperty(value) {
1861
+ return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(value)
1862
+ ? value
1863
+ : JSON.stringify(value);
1864
+ }
1865
+ //# sourceMappingURL=bundle.js.map