openxiangda-devkit-core 2.0.0-alpha.12 → 2.0.0-alpha.121

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