@fugood/bricks-ctor 2.24.0-beta.40

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 (129) hide show
  1. package/compile/action-name-map.ts +988 -0
  2. package/compile/index.ts +1245 -0
  3. package/compile/util.ts +358 -0
  4. package/index.ts +6 -0
  5. package/package.json +28 -0
  6. package/skills/bricks-design/LICENSE.txt +180 -0
  7. package/skills/bricks-design/SKILL.md +66 -0
  8. package/skills/bricks-project/SKILL.md +32 -0
  9. package/skills/bricks-project/rules/animation.md +159 -0
  10. package/skills/bricks-project/rules/architecture-patterns.md +69 -0
  11. package/skills/bricks-project/rules/automations.md +221 -0
  12. package/skills/bricks-project/rules/buttress.md +156 -0
  13. package/skills/bricks-project/rules/data-calculation.md +208 -0
  14. package/skills/bricks-project/rules/local-sync.md +129 -0
  15. package/skills/bricks-project/rules/media-flow.md +158 -0
  16. package/skills/bricks-project/rules/remote-data-bank.md +196 -0
  17. package/skills/bricks-project/rules/standby-transition.md +124 -0
  18. package/skills/rive-marketplace/SKILL.md +99 -0
  19. package/tools/deploy.ts +151 -0
  20. package/tools/icons/.gitattributes +1 -0
  21. package/tools/icons/fa6pro-glyphmap.json +4686 -0
  22. package/tools/icons/fa6pro-meta.json +3671 -0
  23. package/tools/mcp-server.ts +28 -0
  24. package/tools/mcp-tools/compile.ts +91 -0
  25. package/tools/mcp-tools/huggingface.ts +762 -0
  26. package/tools/mcp-tools/icons.ts +70 -0
  27. package/tools/mcp-tools/lottie.ts +102 -0
  28. package/tools/mcp-tools/media.ts +110 -0
  29. package/tools/postinstall.ts +229 -0
  30. package/tools/preview-main.mjs +293 -0
  31. package/tools/preview.ts +143 -0
  32. package/tools/pull.ts +116 -0
  33. package/tsconfig.json +16 -0
  34. package/types/animation.ts +100 -0
  35. package/types/automation.ts +235 -0
  36. package/types/brick-base.ts +80 -0
  37. package/types/bricks/Camera.ts +246 -0
  38. package/types/bricks/Chart.ts +372 -0
  39. package/types/bricks/GenerativeMedia.ts +276 -0
  40. package/types/bricks/Icon.ts +98 -0
  41. package/types/bricks/Image.ts +114 -0
  42. package/types/bricks/Items.ts +476 -0
  43. package/types/bricks/Lottie.ts +168 -0
  44. package/types/bricks/Maps.ts +262 -0
  45. package/types/bricks/QrCode.ts +117 -0
  46. package/types/bricks/Rect.ts +150 -0
  47. package/types/bricks/RichText.ts +128 -0
  48. package/types/bricks/Rive.ts +220 -0
  49. package/types/bricks/Slideshow.ts +201 -0
  50. package/types/bricks/Svg.ts +99 -0
  51. package/types/bricks/Text.ts +148 -0
  52. package/types/bricks/TextInput.ts +242 -0
  53. package/types/bricks/Video.ts +175 -0
  54. package/types/bricks/VideoStreaming.ts +112 -0
  55. package/types/bricks/WebRtcStream.ts +65 -0
  56. package/types/bricks/WebView.ts +168 -0
  57. package/types/bricks/index.ts +21 -0
  58. package/types/canvas.ts +82 -0
  59. package/types/common.ts +144 -0
  60. package/types/data-calc-command.ts +7005 -0
  61. package/types/data-calc-script.ts +21 -0
  62. package/types/data-calc.ts +11 -0
  63. package/types/data.ts +95 -0
  64. package/types/generators/AlarmClock.ts +110 -0
  65. package/types/generators/Assistant.ts +621 -0
  66. package/types/generators/BleCentral.ts +247 -0
  67. package/types/generators/BlePeripheral.ts +208 -0
  68. package/types/generators/CanvasMap.ts +74 -0
  69. package/types/generators/CastlesPay.ts +87 -0
  70. package/types/generators/DataBank.ts +160 -0
  71. package/types/generators/File.ts +432 -0
  72. package/types/generators/GraphQl.ts +132 -0
  73. package/types/generators/Http.ts +222 -0
  74. package/types/generators/HttpServer.ts +176 -0
  75. package/types/generators/Information.ts +103 -0
  76. package/types/generators/Intent.ts +168 -0
  77. package/types/generators/Iterator.ts +108 -0
  78. package/types/generators/Keyboard.ts +105 -0
  79. package/types/generators/LlmAnthropicCompat.ts +212 -0
  80. package/types/generators/LlmAppleBuiltin.ts +159 -0
  81. package/types/generators/LlmGgml.ts +861 -0
  82. package/types/generators/LlmMediaTekNeuroPilot.ts +235 -0
  83. package/types/generators/LlmMlx.ts +227 -0
  84. package/types/generators/LlmOnnx.ts +213 -0
  85. package/types/generators/LlmOpenAiCompat.ts +244 -0
  86. package/types/generators/LlmQualcommAiEngine.ts +247 -0
  87. package/types/generators/Mcp.ts +637 -0
  88. package/types/generators/McpServer.ts +289 -0
  89. package/types/generators/MediaFlow.ts +170 -0
  90. package/types/generators/MqttBroker.ts +141 -0
  91. package/types/generators/MqttClient.ts +141 -0
  92. package/types/generators/Question.ts +408 -0
  93. package/types/generators/RealtimeTranscription.ts +279 -0
  94. package/types/generators/RerankerGgml.ts +191 -0
  95. package/types/generators/SerialPort.ts +151 -0
  96. package/types/generators/SoundPlayer.ts +94 -0
  97. package/types/generators/SoundRecorder.ts +130 -0
  98. package/types/generators/SpeechToTextGgml.ts +415 -0
  99. package/types/generators/SpeechToTextOnnx.ts +236 -0
  100. package/types/generators/SpeechToTextPlatform.ts +85 -0
  101. package/types/generators/SqLite.ts +159 -0
  102. package/types/generators/Step.ts +107 -0
  103. package/types/generators/SttAppleBuiltin.ts +130 -0
  104. package/types/generators/Tcp.ts +126 -0
  105. package/types/generators/TcpServer.ts +147 -0
  106. package/types/generators/TextToSpeechAppleBuiltin.ts +127 -0
  107. package/types/generators/TextToSpeechGgml.ts +221 -0
  108. package/types/generators/TextToSpeechOnnx.ts +178 -0
  109. package/types/generators/TextToSpeechOpenAiLike.ts +121 -0
  110. package/types/generators/ThermalPrinter.ts +191 -0
  111. package/types/generators/Tick.ts +83 -0
  112. package/types/generators/Udp.ts +120 -0
  113. package/types/generators/VadGgml.ts +250 -0
  114. package/types/generators/VadOnnx.ts +231 -0
  115. package/types/generators/VadTraditional.ts +138 -0
  116. package/types/generators/VectorStore.ts +257 -0
  117. package/types/generators/Watchdog.ts +107 -0
  118. package/types/generators/WebCrawler.ts +103 -0
  119. package/types/generators/WebRtc.ts +181 -0
  120. package/types/generators/WebSocket.ts +148 -0
  121. package/types/generators/index.ts +57 -0
  122. package/types/index.ts +13 -0
  123. package/types/subspace.ts +59 -0
  124. package/types/switch.ts +51 -0
  125. package/types/system.ts +707 -0
  126. package/utils/calc.ts +126 -0
  127. package/utils/data.ts +497 -0
  128. package/utils/event-props.ts +836 -0
  129. package/utils/id.ts +80 -0
@@ -0,0 +1,1245 @@
1
+ /* eslint-disable no-underscore-dangle -- Uses __typename, __actionName, etc. for type system */
2
+ import camelCase from 'lodash/camelCase'
3
+ import upperFirst from 'lodash/upperFirst'
4
+ import snakeCase from 'lodash/snakeCase'
5
+ import omit from 'lodash/omit'
6
+ import { parse as parseAST } from 'acorn'
7
+ import type { ExportNamedDeclaration, FunctionDeclaration } from 'acorn'
8
+ import escodegen from 'escodegen'
9
+ import { generateCalulationMap } from './util'
10
+ import { templateActionNameMap } from './action-name-map'
11
+ import { templateEventPropsMap } from '../utils/event-props'
12
+ import type {
13
+ Application,
14
+ Data,
15
+ DataAssetKind,
16
+ Animation,
17
+ AnimationDef,
18
+ AnimationComposeDef,
19
+ ActionWithDataParams,
20
+ ActionWithParams,
21
+ BrickItems,
22
+ SwitchCondData,
23
+ SwitchCondInnerStateCurrentCanvas,
24
+ SwitchCondPropertyBankByItemKey,
25
+ DataCalculationMap,
26
+ DataCalculationScript,
27
+ DataCalculationData,
28
+ DataCommand,
29
+ Brick,
30
+ Canvas,
31
+ Subspace,
32
+ AutomationMap,
33
+ AutomationTestMap,
34
+ AutomationTest,
35
+ TestCase,
36
+ TestVariable,
37
+ } from '../types'
38
+
39
+ const uuidPattern = '[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}'
40
+
41
+ const entryIdPatterns = {
42
+ SUBSPACE: new RegExp(`^SUBSPACE_${uuidPattern}$`),
43
+ CANVAS: new RegExp(`^CANVAS_${uuidPattern}$`),
44
+ BRICK: new RegExp(`^BRICK_${uuidPattern}$`),
45
+ GENERATOR: new RegExp(`^(GENERATOR|AUTO_GENERATOR)_${uuidPattern}$`),
46
+ ANIMATION: new RegExp(`^ANIMATION_${uuidPattern}$`),
47
+ PROPERTY_BANK_DATA_NODE: new RegExp(`^PROPERTY_BANK_DATA_NODE_${uuidPattern}$`),
48
+ PROPERTY_BANK_COMMAND_NODE: new RegExp(`^PROPERTY_BANK_COMMAND_NODE_${uuidPattern}$`),
49
+ PROPERTY_BANK_COMMAND_MAP: new RegExp(`^PROPERTY_BANK_COMMAND_MAP_${uuidPattern}$`),
50
+ BRICK_STATE_GROUP: new RegExp(`^BRICK_STATE_GROUP_${uuidPattern}$`),
51
+ TEST: new RegExp(`^TEST_${uuidPattern}$`),
52
+ TEST_CASE: new RegExp(`^TEST_CASE_${uuidPattern}$`),
53
+ TEST_VAR: new RegExp(`^TEST_VAR_${uuidPattern}$`),
54
+ AUTOMATION_MAP: /^AUTOMATION_MAP_.*/,
55
+ } as const
56
+
57
+ type EntryIdPatternKey = keyof typeof entryIdPatterns
58
+
59
+ const assertEntryId = (
60
+ id: unknown,
61
+ patternKey: EntryIdPatternKey,
62
+ errorReference: string = '',
63
+ ): string => {
64
+ const pattern = entryIdPatterns[patternKey]
65
+ if (typeof id !== 'string' || !pattern.test(id)) {
66
+ throw new Error(
67
+ `Invalid ${patternKey} id${errorReference ? ` ${errorReference}` : ''}: ${String(id)}`,
68
+ )
69
+ }
70
+ return id
71
+ }
72
+
73
+ const compileProperty = (property, errorReference: string, result = {}) => {
74
+ if (Array.isArray(property)) {
75
+ return property.map((p) => compileProperty(p, errorReference))
76
+ }
77
+ if (property?.__typename === 'DataLink') {
78
+ const data = property.data?.()
79
+ if (!data?.id) throw new Error(`Invalid DataLink reference ${errorReference}`)
80
+ return `PROPERTY_BANK#${data.id}`
81
+ }
82
+ if (typeof property === 'function') {
83
+ const instance = property()
84
+ if (!instance?.id) throw new Error(`Invalid ID reference ${errorReference}`)
85
+ return instance?.id // defined type instance getter
86
+ }
87
+ if (property && typeof property === 'object') {
88
+ return Object.entries(property).reduce((acc, [key, value]) => {
89
+ acc[key] = compileProperty(value, errorReference)
90
+ return acc
91
+ }, result)
92
+ }
93
+ return property
94
+ }
95
+
96
+ const compileScriptCalculationCode = (code = '') => {
97
+ try {
98
+ const program = parseAST(code, { sourceType: 'module', ecmaVersion: 2020 })
99
+ // export function main() { ... }
100
+ const declarationBody = (
101
+ (program.body[0] as ExportNamedDeclaration).declaration as FunctionDeclaration
102
+ )?.body
103
+ return escodegen.generate(declarationBody, {
104
+ format: {
105
+ indent: { style: ' ' },
106
+ semicolons: false,
107
+ },
108
+ comment: true,
109
+ })
110
+ } catch {
111
+ return code || ''
112
+ }
113
+ }
114
+
115
+ const getTemplateName = (key: string) =>
116
+ upperFirst(camelCase(key.replace(/^(BRICK|GENERATOR)_/, '')))
117
+
118
+ const compileEventActionValue = (templateKey, eventKey, value, errorReference) => {
119
+ const tmplEventProperties = templateEventPropsMap[getTemplateName(templateKey)]
120
+ const props = tmplEventProperties?.[eventKey]
121
+ if (!props) return compileProperty(value, errorReference)
122
+ if (typeof value === 'string' && value in props) {
123
+ const expectedType = props[value]
124
+ if (expectedType) return value
125
+ }
126
+ if (typeof value === 'string' && /^(BRICK|GENERATOR)_/.test(value)) {
127
+ console.warn(
128
+ `[Warning] Event property "${value}" is not compatible with event "${eventKey}" of ${templateKey} ${errorReference}`,
129
+ )
130
+ }
131
+ return compileProperty(value, errorReference)
132
+ }
133
+
134
+ const convertOutletKey = (templateKey: string, key: string) =>
135
+ `${templateKey}_${snakeCase(key).toUpperCase()}`
136
+
137
+ const compileOutlets = (
138
+ templateKey: string,
139
+ outlets: { [key: string]: () => Data },
140
+ errorReference: string,
141
+ ) =>
142
+ Object.entries(outlets).reduce((acc, [key, data]) => {
143
+ const dataInstance = data?.()
144
+ const dataId = assertEntryId(dataInstance?.id, 'PROPERTY_BANK_DATA_NODE', errorReference)
145
+ acc[convertOutletKey(templateKey, key)] = dataId
146
+ return acc
147
+ }, {})
148
+
149
+ const convertEventKey = (templateKey: string, key: string) =>
150
+ `${templateKey ? `${templateKey}_` : ''}${snakeCase(key).toUpperCase()}`
151
+
152
+ const basicAnimationEvents = ['show', 'standby', 'breatheStart']
153
+
154
+ const compileAnimations = (
155
+ templateKey: string,
156
+ animations: { [key: string]: Animation },
157
+ errorReference: string,
158
+ ) =>
159
+ Object.entries(animations).reduce((acc, [key, animation]) => {
160
+ const animationId = assertEntryId(animation?.id, 'ANIMATION', errorReference)
161
+ acc[convertEventKey(basicAnimationEvents.includes(key) ? 'BRICK' : templateKey, key)] =
162
+ `ANIMATION#${animationId}`
163
+ return acc
164
+ }, {})
165
+
166
+ const compileActionParam = (templateKey: string, actionName: string, paramName: string) =>
167
+ templateActionNameMap[templateKey]?.[actionName]?.[paramName] || paramName
168
+
169
+ const compileEvents = (
170
+ templateKey: string,
171
+ eventMap: { [key: string]: Array<any> },
172
+ options = { camelCase: false, errorReference: '' },
173
+ ) => {
174
+ const { camelCase, errorReference } = options
175
+ return Object.entries(eventMap).reduce((acc, [key, events]) => {
176
+ acc[convertEventKey(templateKey, key)] = events.map((event) => {
177
+ const { handler, action } = event
178
+
179
+ let handlerKey
180
+ let handlerTemplateKey
181
+ if (handler === 'system' || typeof handler === 'string') {
182
+ if (handler.startsWith('SUBSPACE_')) handlerKey = handler
183
+ else handlerKey = handler.toUpperCase()
184
+ if (handlerKey === 'SYSTEM') handlerTemplateKey = 'SYSTEM'
185
+ } else if (typeof handler === 'function') {
186
+ let instance = handler()
187
+ if (instance?.id) {
188
+ instance = instance as { id: string }
189
+ handlerKey = instance?.id
190
+ } else if (instance?.brickId) {
191
+ instance = instance as { brickId: string; templateKey: string }
192
+ handlerKey = instance.brickId
193
+ }
194
+ handlerTemplateKey = instance?.templateKey
195
+ }
196
+ if (!handlerKey) throw new Error(`Invalid handler: ${handler} ${errorReference}`)
197
+
198
+ const parameterList: Array<object> = []
199
+ if (Object.prototype.hasOwnProperty.call(action, 'params')) {
200
+ const actionDef = action as ActionWithParams
201
+ ;(actionDef.params || []).forEach(({ input, value, mapping }) => {
202
+ const param = {
203
+ [camelCase ? 'inputToReceiver' : 'input_to_receiver']: handlerTemplateKey
204
+ ? compileActionParam(handlerTemplateKey, action.__actionName, input)
205
+ : input,
206
+ [camelCase ? 'resultFromSender' : 'result_from_sender']:
207
+ mapping || compileEventActionValue(handlerTemplateKey, key, value, errorReference),
208
+ }
209
+ if (mapping) param[camelCase ? 'resultDataMapping' : 'result_data_mapping'] = true
210
+ parameterList.push(param)
211
+ })
212
+ } else if (Object.prototype.hasOwnProperty.call(action, 'dataParams')) {
213
+ const actionDef = action as ActionWithDataParams
214
+ ;(actionDef.dataParams || []).forEach(({ input, value, mapping }) => {
215
+ if (!input) return
216
+ const param = {
217
+ [camelCase ? 'inputToReceiver' : 'input_to_receiver']: input().id,
218
+ [camelCase ? 'resultFromSender' : 'result_from_sender']:
219
+ mapping || compileEventActionValue(handlerTemplateKey, key, value, errorReference),
220
+ }
221
+ if (mapping) param[camelCase ? 'resultDataMapping' : 'result_data_mapping'] = true
222
+ parameterList.push(param)
223
+ })
224
+ }
225
+ return {
226
+ handler: handlerKey,
227
+ action: action.__actionName,
228
+ [camelCase ? 'parameterList' : 'parameter_list']: parameterList,
229
+ [camelCase ? 'waitAsync' : 'wait_async']: event.waitAsync,
230
+ }
231
+ })
232
+ return acc
233
+ }, {})
234
+ }
235
+
236
+ const compileSwitchConds = (templateKey, conds, errorReference) =>
237
+ (conds || []).map((item: any, index) => {
238
+ const result: any = { method: item.method }
239
+ if (!item.cond) return result
240
+ if (item.cond.__typename === 'SwitchCondData') {
241
+ const cond = item.cond as SwitchCondData
242
+ result.type = 'property_bank'
243
+ if (!cond.data().id)
244
+ throw new Error(`Invalid data reference in conds index: ${index} ${errorReference}`)
245
+ result.key = cond.data().id
246
+ result.value = cond.value
247
+ } else if (item.cond.__typename === 'SwitchCondPropertyBankByItemKey') {
248
+ const cond = item.cond as SwitchCondPropertyBankByItemKey
249
+ result.type = 'property_bank_by_item_key'
250
+ if (!cond.data().id)
251
+ throw new Error(`Invalid data reference in conds index: ${index} ${errorReference}`)
252
+ result.key = cond.data().id
253
+ result.value = cond.value
254
+ } else if (item.cond.__typename === 'SwitchCondInnerStateOutlet') {
255
+ const { cond } = item
256
+ result.type = 'inner_state'
257
+ result.key = convertOutletKey(templateKey, cond.outlet)
258
+ result.value = cond.value
259
+ } else if (item.cond.__typename === 'SwitchCondInnerStateCurrentCanvas') {
260
+ const cond = item.cond as SwitchCondInnerStateCurrentCanvas
261
+ result.type = 'inner_state'
262
+ result.key = 'current_canvas'
263
+ if (!cond.value().id)
264
+ throw new Error(`Invalid canvas reference in conds index: ${index} ${errorReference}`)
265
+ result.value = cond.value().id
266
+ }
267
+ return result
268
+ })
269
+
270
+ const compileApplicationSettings = (settings: Application['settings']) => ({
271
+ internet_reachability_url: settings?.internetReachabilityUrl,
272
+ enable_data_lock: settings?.enableDataLock,
273
+ show_deprecated_features: settings?.showDeprecatedFeatures,
274
+ enable_unstable_bricks: settings?.enableUnstableFeatures,
275
+ runtime_cache_options: settings?.runtimeCacheOptions
276
+ ? {
277
+ disabled: settings.runtimeCacheOptions.disabled,
278
+ behavior: settings.runtimeCacheOptions.behavior,
279
+ retry_attempt_for_failure: settings.runtimeCacheOptions.retryAttemptForFailure,
280
+ }
281
+ : undefined,
282
+ tv_options: settings?.tvOptions
283
+ ? {
284
+ disabled_selectable: settings.tvOptions.disabledSelectable,
285
+ selectable_color: settings.tvOptions.selectableColor,
286
+ selectable_border: settings.tvOptions.selectableBorder,
287
+ }
288
+ : undefined,
289
+ ai: settings?.ai
290
+ ? {
291
+ use_anthropic_api_key_system_data: settings.ai.useAnthropicApiKeySystemData,
292
+ use_openai_api_key_system_data: settings.ai.useOpenAiApiKeySystemData,
293
+ use_gemini_api_key_system_data: settings.ai.useGeminiApiKeySystemData,
294
+ }
295
+ : undefined,
296
+ hide_short_refs: settings?.hideShortRefs,
297
+ })
298
+
299
+ const animationTypeMap = {
300
+ AnimationTimingConfig: 'timing',
301
+ AnimationSpringConfig: 'spring',
302
+ AnimationDecayConfig: 'decay',
303
+ }
304
+
305
+ const compileFrame = (frame: Canvas['items'][number]['frame']) => ({
306
+ x: frame.x,
307
+ y: frame.y,
308
+ width: frame.width,
309
+ height: frame.height,
310
+ standby_mode: frame.standbyMode,
311
+ standby_frame: frame.standbyFrame,
312
+ standby_opacity: frame.standbyOpacity,
313
+ standby_delay: frame.standbyDelay,
314
+ standby_delay_random: frame.standbyDelayRandom,
315
+ standby_easing: frame.standbyEasing,
316
+ showing_delay: frame.showingDelay,
317
+ render_out_of_viewport: frame.renderOutOfViewport,
318
+ })
319
+
320
+ const preloadTypes = [
321
+ 'media-resource-image',
322
+ 'media-resource-video',
323
+ 'media-resource-audio',
324
+ 'media-resource-file',
325
+ 'lottie-file-uri',
326
+ 'ggml-model-asset',
327
+ 'gguf-model-asset',
328
+ 'binary-asset',
329
+ ]
330
+
331
+ const compileKind = (kind: Data['kind']) => {
332
+ const { type, ...rest } = kind || {}
333
+ if (!type) return {}
334
+ if (preloadTypes.includes(type)) {
335
+ const { preload, metadata } = kind as DataAssetKind
336
+ const result: any = { kind: type, ...rest }
337
+ if (preload) {
338
+ result.preload = {
339
+ type: preload.type,
340
+ hashType: preload.hashType,
341
+ }
342
+ if (preload.hashType) result.preload[preload.hashType] = preload.hash
343
+ }
344
+ // Handle metadata
345
+ if (type === 'gguf-model-asset') {
346
+ result._hfRepoInfo = metadata?.hfRepoInfo
347
+ }
348
+ return result
349
+ }
350
+
351
+ return { kind: type, ...rest }
352
+ }
353
+
354
+ const compileRemoteUpdate = (remoteUpdate: Data['remoteUpdate']) => {
355
+ if (!remoteUpdate) return {}
356
+ if (remoteUpdate.type === 'auto') return { enable_remote_update: true }
357
+ return {
358
+ enable_remote_update: true,
359
+ ...(remoteUpdate.type === 'device-specific' ? { use_remote_id_prefix: true } : {}),
360
+ ...(remoteUpdate.type === 'global-data' ? { global_remote_update_prop: remoteUpdate.id } : {}),
361
+ }
362
+ }
363
+
364
+ const compileModule = (subspace: Subspace) => {
365
+ if (!subspace.module) return {}
366
+ return {
367
+ module: {
368
+ link: true,
369
+ id: subspace.module.id,
370
+ version: subspace.module.version,
371
+ },
372
+ selected_requirements: subspace.module.selectedRequirements?.reduce((acc, requirement) => {
373
+ acc[requirement.subspace] = {
374
+ id: requirement.id,
375
+ version: requirement.version,
376
+ }
377
+ return acc
378
+ }, {}),
379
+ requirements: subspace.module.requirements?.reduce((acc, requirement) => {
380
+ acc[requirement.subspace] = {
381
+ id: requirement.id,
382
+ version: requirement.version,
383
+ }
384
+ return acc
385
+ }, {}),
386
+ }
387
+ }
388
+
389
+ /**
390
+ * Compile a run array element - if it's a getter function, call it and extract the id
391
+ * Note: All entity ids already include their prefix (e.g., SUBSPACE_xxx, BRICK_xxx, PROPERTY_BANK_DATA_NODE_xxx)
392
+ */
393
+ function compileRunElement(element: unknown): unknown {
394
+ if (typeof element !== 'function') return element
395
+
396
+ const result = element()
397
+ if (result && typeof result === 'object' && 'id' in result) {
398
+ return (result as { id: string }).id
399
+ }
400
+ return result
401
+ }
402
+
403
+ /**
404
+ * Compile a run array - resolve getter functions and extract ids with prefixes
405
+ */
406
+ function compileRunArray(run: unknown[]): unknown[] {
407
+ return run.map(compileRunElement)
408
+ }
409
+
410
+ /**
411
+ * Compile typed TestCase to raw format (strips __typename)
412
+ */
413
+ const compileTestCase = (testCase: TestCase) => ({
414
+ id: testCase.id,
415
+ name: testCase.name,
416
+ hide_short_ref: testCase.hideShortRef,
417
+ run: compileRunArray(testCase.run),
418
+ exit_on_failed: testCase.exit_on_failed,
419
+ commented: testCase.commented,
420
+ pre_delay: testCase.pre_delay,
421
+ post_delay: testCase.post_delay,
422
+ post_delay_rule: testCase.post_delay_rule,
423
+ jump_cond: testCase.jump_cond.map((cond) => {
424
+ if (cond.jump_to == null) {
425
+ console.warn(
426
+ `[Warning] jump_cond is missing jump_to in test case "${testCase.name}" (${testCase.id})`,
427
+ )
428
+ }
429
+ return {
430
+ type: cond.type,
431
+ status: cond.status,
432
+ variable: cond.variable,
433
+ operator: cond.operator,
434
+ value: cond.value,
435
+ jump_to: cond.jump_to,
436
+ }
437
+ }),
438
+ })
439
+
440
+ /**
441
+ * Compile typed TestVariable to raw format (strips __typename)
442
+ */
443
+ const compileTestVariable = (variable: TestVariable) => ({
444
+ id: variable.id,
445
+ name: variable.name,
446
+ type: variable.type,
447
+ value: variable.value,
448
+ })
449
+
450
+ /**
451
+ * Convert an array of items with id property to an id-keyed record
452
+ */
453
+ const arrayToIdMap = <T extends { id: string }, R>(
454
+ items: T[],
455
+ transform: (item: T, index: number) => R,
456
+ options: {
457
+ patternKey: EntryIdPatternKey
458
+ getErrorReference: (item: T, index: number) => string
459
+ },
460
+ ): Record<string, R> =>
461
+ Object.fromEntries(
462
+ items.map((item, index) => {
463
+ const itemId = assertEntryId(
464
+ item.id,
465
+ options.patternKey,
466
+ options.getErrorReference(item, index),
467
+ )
468
+ return [itemId, transform(item, index)]
469
+ }),
470
+ )
471
+
472
+ /**
473
+ * Compile typed AutomationTest to raw format
474
+ */
475
+ const compileAutomationTest = (
476
+ test: AutomationTest,
477
+ options: { mapId: string; testIndex: number },
478
+ ) => {
479
+ const testId = assertEntryId(
480
+ test.id,
481
+ 'TEST',
482
+ `(automation map: ${options.mapId}, test index: ${options.testIndex})`,
483
+ )
484
+
485
+ return {
486
+ id: testId,
487
+ title: test.title,
488
+ hide_short_ref: test.hideShortRef,
489
+ timeout: test.timeout,
490
+ trigger_type: test.trigger_type,
491
+ cron: test.cron,
492
+ skip_trigger_type_check: test.skip_trigger_type_check,
493
+ local_sync: test.local_sync,
494
+ meta: test.meta,
495
+ case_map: arrayToIdMap(test.cases, compileTestCase, {
496
+ patternKey: 'TEST_CASE',
497
+ getErrorReference: (_, index) =>
498
+ `(automation map: ${options.mapId}, test: ${testId}, case index: ${index})`,
499
+ }),
500
+ var_map: arrayToIdMap(test.variables, compileTestVariable, {
501
+ patternKey: 'TEST_VAR',
502
+ getErrorReference: (_, index) =>
503
+ `(automation map: ${options.mapId}, test: ${testId}, variable index: ${index})`,
504
+ }),
505
+ }
506
+ }
507
+
508
+ /**
509
+ * Compile typed AutomationTestMap to raw format
510
+ */
511
+ const compileAutomationTestMap = (testMap: AutomationTestMap, mapId: string) => {
512
+ assertEntryId(testMap.id, 'AUTOMATION_MAP', `(automation map id field: ${mapId})`)
513
+
514
+ return {
515
+ title: testMap.title,
516
+ hide_short_ref: testMap.hideShortRef,
517
+ createdAt: testMap.createdAt,
518
+ map: arrayToIdMap(
519
+ testMap.tests,
520
+ (test, index) => compileAutomationTest(test, { mapId, testIndex: index }),
521
+ {
522
+ patternKey: 'TEST',
523
+ getErrorReference: (_, index) => `(automation map: ${mapId}, test index: ${index})`,
524
+ },
525
+ ),
526
+ }
527
+ }
528
+
529
+ /**
530
+ * Compile typed AutomationMap to raw automation_map format
531
+ */
532
+ const compileAutomation = (automationMap: AutomationMap) =>
533
+ Object.fromEntries(
534
+ Object.entries(automationMap).map(([mapId, testMap]) => {
535
+ const automationMapId = assertEntryId(mapId, 'AUTOMATION_MAP', '(automation map key)')
536
+ return [automationMapId, compileAutomationTestMap(testMap, automationMapId)]
537
+ }),
538
+ )
539
+
540
+ export const compile = async (app: Application) => {
541
+ await new Promise((resolve) => setImmediate(resolve, 0))
542
+ const timestamp = Date.now()
543
+ const config = {
544
+ title: `${app.name || 'Unknown'}(${timestamp})`,
545
+ subspace_map: app.subspaces.reduce((subspaceMap, subspace, subspaceIndex) => {
546
+ const subspaceId = assertEntryId(
547
+ subspace.id,
548
+ 'SUBSPACE',
549
+ `(subspace index: ${subspaceIndex})`,
550
+ )
551
+ const rootCanvasId = assertEntryId(
552
+ subspace.rootCanvas?.id,
553
+ 'CANVAS',
554
+ `(subspace: ${subspaceId}, root canvas)`,
555
+ )
556
+
557
+ subspaceMap[subspaceId] = {
558
+ title: subspace.title,
559
+ description: subspace.description,
560
+ hide_short_ref: subspace.hideShortRef,
561
+ unused: subspace.unused,
562
+ portal: subspace.portal,
563
+ _expanded: subspace.unexpanded
564
+ ? {
565
+ brick: !subspace.unexpanded.brick,
566
+ generator: !subspace.unexpanded.generator,
567
+ canvas: subspace.unexpanded.canvas?.reduce((acc, canvas, canvasIndex) => {
568
+ const unexpandedCanvasId = assertEntryId(
569
+ canvas?.id,
570
+ 'CANVAS',
571
+ `(subspace: ${subspaceId}, unexpanded canvas index: ${canvasIndex})`,
572
+ )
573
+ acc[unexpandedCanvasId] = !canvas?.id
574
+ return acc
575
+ }, {}),
576
+ property_bank: !subspace.unexpanded.data,
577
+ property_bank_calc: !subspace.unexpanded.dataCalculation,
578
+ }
579
+ : undefined,
580
+ layout: {
581
+ width: subspace.layout?.width,
582
+ height: subspace.layout?.height,
583
+ resize_mode: subspace.layout?.resizeMode,
584
+ },
585
+ local_sync: subspace.localSyncChangeCanvas
586
+ ? {
587
+ change_canvas: subspace.localSyncChangeCanvas,
588
+ }
589
+ : undefined,
590
+ animation_map: subspace.animations.reduce((map, animation, animationIndex) => {
591
+ const animationId = assertEntryId(
592
+ animation?.id,
593
+ 'ANIMATION',
594
+ `(animation index: ${animationIndex}, subspace: ${subspaceId})`,
595
+ )
596
+
597
+ if (animation.__typename === 'Animation') {
598
+ const animationDef = animation as AnimationDef
599
+ map[animationId] = {
600
+ alias: animationDef.alias,
601
+ title: animationDef.title,
602
+ description: animationDef.description,
603
+ hide_short_ref: animationDef.hideShortRef,
604
+ animationRunType: animationDef.runType,
605
+ property: animationDef.property,
606
+ type: animationTypeMap[animationDef.config.__type],
607
+ config: compileProperty(
608
+ omit(animationDef.config, '__type'),
609
+ `(animation: ${animationId}, subspace ${subspaceId})`,
610
+ ),
611
+ }
612
+ } else if (animation.__typename === 'AnimationCompose') {
613
+ const animationDef = animation as AnimationComposeDef
614
+ map[animationId] = {
615
+ alias: animationDef.alias,
616
+ title: animationDef.title,
617
+ description: animationDef.description,
618
+ hide_short_ref: animationDef.hideShortRef,
619
+ animationRunType: animationDef.runType,
620
+ compose_type: animationDef.composeType,
621
+ item_list: animationDef.items.map((item, index) => {
622
+ const innerAnimation = item()
623
+ if (!innerAnimation?.id)
624
+ throw new Error(
625
+ `Invalid animation index: ${index} (animation: ${innerAnimation.id}, subspace ${subspaceId})`,
626
+ )
627
+ return { animation_id: innerAnimation.id }
628
+ }),
629
+ }
630
+ }
631
+ return map
632
+ }, {}),
633
+ brick_map: subspace.bricks.reduce((map, brick, brickIndex) => {
634
+ const brickId = assertEntryId(
635
+ brick.id,
636
+ 'BRICK',
637
+ `(brick index: ${brickIndex}, subspace: ${subspaceId})`,
638
+ )
639
+ const property = compileProperty(
640
+ brick.property || {},
641
+ `(brick: ${brickId}, subspace ${subspaceId})`,
642
+ )
643
+ if (brick.templateKey === 'BRICK_ITEMS') {
644
+ const brickItems = brick as BrickItems
645
+ const buildList = (itemBrick, index, key) => ({
646
+ title: itemBrick.title,
647
+ brickId: itemBrick.brickId,
648
+ brickIdPrefix: itemBrick.brickIdPrefix,
649
+ templateKey: itemBrick.templateKey,
650
+ frame: itemBrick.frame,
651
+ property: compileProperty(
652
+ itemBrick.property,
653
+ `(brick: ${brickId}, ${key}: ${index}, subspace ${subspaceId})`,
654
+ ),
655
+ propertyMapping: itemBrick.propertyMapping,
656
+ animation: compileAnimations(
657
+ itemBrick.templateKey,
658
+ itemBrick.animation || {},
659
+ `(brick: ${brickId}, ${key}: ${index}, subspace ${subspaceId})`,
660
+ ),
661
+ outlet: compileOutlets(
662
+ itemBrick.templateKey,
663
+ itemBrick.outlets || {},
664
+ `(brick: ${brickId}, ${key}: ${index}, subspace ${subspaceId})`,
665
+ ),
666
+ eventMap: compileEvents(itemBrick.templateKey, itemBrick.eventMap || {}, {
667
+ camelCase: true,
668
+ errorReference: `(brick: ${brickId}, ${key}: ${index}, subspace ${subspaceId})`,
669
+ }),
670
+ stateGroup: itemBrick.stateGroup.reduce((acc, stateGroup, stateGroupIndex) => {
671
+ const stateGroupId = assertEntryId(
672
+ stateGroup.id,
673
+ 'BRICK_STATE_GROUP',
674
+ `(brick: ${brickId}, ${key}: ${index}, switch index: ${stateGroupIndex}, subspace ${subspaceId})`,
675
+ )
676
+
677
+ acc[stateGroupId] = {
678
+ title: stateGroup.title,
679
+ description: stateGroup.description,
680
+ override: stateGroup.override,
681
+ break: stateGroup.break,
682
+ commented: stateGroup.disabled,
683
+ conds: compileSwitchConds(
684
+ itemBrick.templateKey,
685
+ stateGroup.conds || [],
686
+ `(brick: ${brickId}, ${key}: ${index}, switch: ${stateGroupId}, subspace ${subspaceId})`,
687
+ ),
688
+ property: compileProperty(
689
+ stateGroup.property,
690
+ `(brick: ${brickId}, ${key}: ${index}, switch: ${stateGroupId}, subspace ${subspaceId})`,
691
+ ),
692
+ animation: compileAnimations(
693
+ itemBrick.templateKey,
694
+ stateGroup.animation || {},
695
+ `(brick: ${brickId}, ${key}: ${index}, switch: ${stateGroupId}, subspace ${subspaceId})`,
696
+ ),
697
+ outlet: compileOutlets(
698
+ itemBrick.templateKey,
699
+ stateGroup.outlets || {},
700
+ `(brick: ${brickId}, ${key}: ${index}, switch: ${stateGroupId}, subspace ${subspaceId})`,
701
+ ),
702
+ eventMap: compileEvents(itemBrick.templateKey, stateGroup.eventMap || {}, {
703
+ camelCase: true,
704
+ errorReference: `(brick: ${brickId}, ${key}: ${index}, switch: ${stateGroupId}, subspace ${subspaceId})`,
705
+ }),
706
+ }
707
+ return acc
708
+ }, {}),
709
+ show: itemBrick.show,
710
+ pressToOpenDetail: itemBrick.pressToOpenDetail,
711
+ pressToBackList: itemBrick.pressToBackList,
712
+ })
713
+ if (Array.isArray(brickItems.brickList)) {
714
+ const brickList = (brickItems.brickList || []).map((item, index) =>
715
+ buildList(item, index, 'brickList'),
716
+ )
717
+ property.brickList = brickList
718
+ } else if (!brickItems.brickList) {
719
+ property.brickList = []
720
+ } else {
721
+ // Not supported Data for brickList
722
+ throw new TypeError('Not supported Data for brickList directly')
723
+ }
724
+ if (Array.isArray(brickItems.brickDetails)) {
725
+ const brickDetails = (brickItems.brickDetails || []).map((item, index) =>
726
+ buildList(item, index, 'brickDetails'),
727
+ )
728
+ property.brickDetails = brickDetails
729
+ } else if (!brickItems.brickDetails) {
730
+ property.brickDetails = []
731
+ } else {
732
+ // Not supported Data for brickList
733
+ throw new TypeError('Not supported Data for brickList directly')
734
+ }
735
+ }
736
+ map[brickId] = {
737
+ template_key: brick.templateKey,
738
+ alias: brick.alias,
739
+ title: brick.title,
740
+ description: brick.description,
741
+ hide_short_ref: brick.hideShortRef,
742
+ property,
743
+ animation: compileAnimations(
744
+ brick.templateKey,
745
+ brick.animation || {},
746
+ `(brick: ${brickId}, subspace ${subspaceId})`,
747
+ ),
748
+ event_map: compileEvents(brick.templateKey, brick.events || {}, {
749
+ camelCase: false,
750
+ errorReference: `(brick: ${brickId}, subspace ${subspaceId})`,
751
+ }),
752
+ outlet: compileOutlets(
753
+ brick.templateKey,
754
+ brick.outlets || {},
755
+ `(brick: ${brickId}, subspace ${subspaceId})`,
756
+ ),
757
+ state_group: brick.switches?.reduce((acc, switchCase, switchIndex) => {
758
+ const switchId = assertEntryId(
759
+ switchCase.id,
760
+ 'BRICK_STATE_GROUP',
761
+ `(brick: ${brickId}, switch index: ${switchIndex}, subspace ${subspaceId})`,
762
+ )
763
+
764
+ acc[switchId] = {
765
+ title: switchCase.title,
766
+ description: switchCase.description,
767
+ break: switchCase.break,
768
+ override: switchCase.override,
769
+ commented: switchCase.disabled,
770
+ conds: compileSwitchConds(
771
+ brick.templateKey,
772
+ switchCase.conds || [],
773
+ `(brick: ${brickId}, switch: ${switchId}, subspace ${subspaceId})`,
774
+ ),
775
+ property: compileProperty(
776
+ switchCase.property,
777
+ `(brick: ${brickId}, switch: ${switchId}, subspace ${subspaceId})`,
778
+ ),
779
+ outlet: compileOutlets(
780
+ brick.templateKey,
781
+ switchCase.outlets || {},
782
+ `(brick: ${brickId}, switch: ${switchId}, subspace ${subspaceId})`,
783
+ ),
784
+ event_map: compileEvents(brick.templateKey, switchCase.events || {}, {
785
+ camelCase: false,
786
+ errorReference: `(brick: ${brickId}, switch: ${switchId}, subspace ${subspaceId})`,
787
+ }),
788
+ animation: compileAnimations(
789
+ brick.templateKey,
790
+ switchCase.animation || {},
791
+ `(brick: ${brickId}, switch: ${switchId}, subspace ${subspaceId})`,
792
+ ),
793
+ }
794
+ return acc
795
+ }, {}),
796
+ }
797
+ return map
798
+ }, {}),
799
+ root_canvas_id: rootCanvasId,
800
+ canvas_map: subspace.canvases.reduce((map, canvas, canvasIndex) => {
801
+ const canvasId = assertEntryId(
802
+ canvas.id,
803
+ 'CANVAS',
804
+ `(canvas index: ${canvasIndex}, subspace: ${subspaceId})`,
805
+ )
806
+
807
+ map[canvasId] = {
808
+ alias: canvas.alias,
809
+ title: canvas.title,
810
+ description: canvas.description,
811
+ hide_short_ref: canvas.hideShortRef,
812
+ property: compileProperty(
813
+ canvas.property,
814
+ `(canvas: ${canvasId}, subspace ${subspaceId})`,
815
+ ),
816
+ event_map: compileEvents('CANVAS', canvas.events || {}, {
817
+ camelCase: false,
818
+ errorReference: `(canvas: ${canvasId}, subspace ${subspaceId})`,
819
+ }),
820
+ state_group: canvas.switches?.reduce((acc, switchCase, switchIndex) => {
821
+ const switchId = assertEntryId(
822
+ switchCase.id,
823
+ 'BRICK_STATE_GROUP',
824
+ `(canvas: ${canvasId}, switch index: ${switchIndex}, subspace ${subspaceId})`,
825
+ )
826
+
827
+ acc[switchId] = {
828
+ title: switchCase.title,
829
+ description: switchCase.description,
830
+ break: switchCase.break,
831
+ override: switchCase.override,
832
+ commented: switchCase.disabled,
833
+ conds: compileSwitchConds(
834
+ 'CANVAS',
835
+ switchCase.conds || [],
836
+ `(canvas: ${canvasId}, switch: ${switchId}, subspace ${subspaceId})`,
837
+ ),
838
+ property: compileProperty(
839
+ switchCase.property,
840
+ `(canvas: ${canvasId}, switch: ${switchId}, subspace ${subspaceId})`,
841
+ ),
842
+ event_map: compileEvents('CANVAS', switchCase.events || {}, {
843
+ camelCase: false,
844
+ errorReference: `(canvas: ${canvasId}, switch: ${switchId}, subspace ${subspaceId})`,
845
+ }),
846
+ animation: compileAnimations(
847
+ 'CANVAS',
848
+ switchCase.animation || {},
849
+ `(canvas: ${canvasId}, switch: ${switchId}, subspace ${subspaceId})`,
850
+ ),
851
+ }
852
+ return acc
853
+ }, {}),
854
+ item_list: canvas.items.map((item, index) => {
855
+ let itemPayload = {}
856
+ if (typeof item.item === 'function') {
857
+ const brick = (item.item as () => Brick)()
858
+ if (!brick?.id)
859
+ throw new Error(
860
+ `Invalid canvas item index: ${index}, brick not found (canvas: ${canvasId}, subspace ${subspaceId})`,
861
+ )
862
+ itemPayload = { brick_id: brick.id }
863
+ } else {
864
+ const targetSubspaceId = item.item
865
+ if (!app.subspaces.some((s) => s.id === targetSubspaceId))
866
+ throw new Error(
867
+ `Invalid canvas item index: ${index}, subspace not found (canvas: ${canvasId}, subspace ${subspaceId})`,
868
+ )
869
+ itemPayload = { subspace_id: targetSubspaceId }
870
+ }
871
+ return {
872
+ type: typeof item.item === 'function' ? 'brick' : 'subspace',
873
+ ...itemPayload,
874
+ frame: compileFrame(item.frame),
875
+ hidden: item.hidden,
876
+ }
877
+ }),
878
+ }
879
+ return map
880
+ }, {}),
881
+ generator_map: subspace.generators.reduce((map, generator, generatorIndex) => {
882
+ const generatorId = assertEntryId(
883
+ generator.id,
884
+ 'GENERATOR',
885
+ `(generator index: ${generatorIndex}, subspace: ${subspaceId})`,
886
+ )
887
+
888
+ map[generatorId] = {
889
+ template_key: generator.templateKey,
890
+ alias: generator.alias,
891
+ title: generator.title,
892
+ description: generator.description,
893
+ hide_short_ref: generator.hideShortRef,
894
+ local_sync: generator.localSyncRunMode
895
+ ? {
896
+ run_mode: generator.localSyncRunMode,
897
+ }
898
+ : undefined,
899
+ property: compileProperty(
900
+ generator.property || {},
901
+ `(generator: ${generatorId}, subspace ${subspaceId})`,
902
+ ),
903
+ event_map: compileEvents(generator.templateKey, generator.events || {}, {
904
+ camelCase: false,
905
+ errorReference: `(generator: ${generatorId}, subspace ${subspaceId})`,
906
+ }),
907
+ outlet: compileOutlets(
908
+ generator.templateKey,
909
+ generator.outlets || {},
910
+ `(generator: ${generatorId}, subspace ${subspaceId})`,
911
+ ),
912
+ state_group: generator.switches?.reduce((acc, switchCase, switchIndex) => {
913
+ const switchId = assertEntryId(
914
+ switchCase.id,
915
+ 'BRICK_STATE_GROUP',
916
+ `(generator: ${generatorId}, switch index: ${switchIndex}, subspace ${subspaceId})`,
917
+ )
918
+
919
+ acc[switchId] = {
920
+ title: switchCase.title,
921
+ description: switchCase.description,
922
+ break: switchCase.break,
923
+ override: switchCase.override,
924
+ commented: switchCase.disabled,
925
+ conds: compileSwitchConds(
926
+ generator.templateKey,
927
+ switchCase.conds || [],
928
+ `(generator: ${generatorId}, switch: ${switchId}, subspace ${subspaceId})`,
929
+ ),
930
+ property: compileProperty(
931
+ switchCase.property,
932
+ `(generator: ${generatorId}, switch: ${switchId}, subspace ${subspaceId})`,
933
+ ),
934
+ outlet: compileOutlets(
935
+ generator.templateKey,
936
+ switchCase.outlets || {},
937
+ `(generator: ${generatorId}, switch: ${switchId}, subspace ${subspaceId})`,
938
+ ),
939
+ event_map: compileEvents(generator.templateKey, switchCase.events || {}, {
940
+ camelCase: false,
941
+ errorReference: `(generator: ${generatorId}, switch: ${switchId}, subspace ${subspaceId})`,
942
+ }),
943
+ animation: compileAnimations(
944
+ generator.templateKey,
945
+ switchCase.animation || {},
946
+ `(generator: ${generatorId}, switch: ${switchId}, subspace ${subspaceId})`,
947
+ ),
948
+ }
949
+ return acc
950
+ }, {}),
951
+ }
952
+ return map
953
+ }, {}),
954
+ property_bank_map: subspace.data.reduce((map, data, dataIndex) => {
955
+ const dataId = assertEntryId(
956
+ data.id,
957
+ 'PROPERTY_BANK_DATA_NODE',
958
+ `(data index: ${dataIndex}, subspace: ${subspaceId})`,
959
+ )
960
+
961
+ map[dataId] = {
962
+ alias: data.alias,
963
+ title: data.title,
964
+ description: data.description,
965
+ hide_short_ref: data.hideShortRef,
966
+ linked: data.metadata?.linked,
967
+ linkedFrom: data.metadata?.linkedFrom,
968
+ local_sync: data.localSyncUpdateMode
969
+ ? {
970
+ update_mode: data.localSyncUpdateMode,
971
+ }
972
+ : undefined,
973
+ persist_data: data.persistData,
974
+ ...compileRemoteUpdate(data.remoteUpdate),
975
+ routing: data.routing,
976
+ schema: data.schema,
977
+ type: data.type,
978
+ ...compileKind(data.kind),
979
+ value: compileProperty(data.value, `(data: ${dataId}, subspace ${subspaceId})`),
980
+ event_map: compileEvents('PROPERTY_BANK', data.events || {}, {
981
+ camelCase: false,
982
+ errorReference: `(data: ${dataId}, subspace ${subspaceId})`,
983
+ }),
984
+ hit_equal: data.hit_equal,
985
+ hit_regex: data.hit_regex,
986
+ }
987
+ return map
988
+ }, {}),
989
+ property_bank_calc_map: subspace.dataCalculation.reduce((map, dataCalc, dataCalcIndex) => {
990
+ const dataCalcId = assertEntryId(
991
+ dataCalc.id,
992
+ 'PROPERTY_BANK_COMMAND_MAP',
993
+ `(data calc index: ${dataCalcIndex}, subspace: ${subspaceId})`,
994
+ )
995
+
996
+ const calc: any = {
997
+ title: dataCalc.title,
998
+ description: dataCalc.description,
999
+ hide_short_ref: dataCalc.hideShortRef,
1000
+ }
1001
+ if (dataCalc.triggerMode) calc.trigger_type = dataCalc.triggerMode
1002
+ if (dataCalc.__typename === 'DataCalculationMap') {
1003
+ calc.type = 'general'
1004
+ const mapCalc = dataCalc as DataCalculationMap
1005
+
1006
+ const getNodeId = (
1007
+ node: DataCalculationData | DataCommand,
1008
+ reference = '',
1009
+ nodeIndex?: number,
1010
+ ) => {
1011
+ const nodeReference = [
1012
+ `data calc: ${dataCalcId}`,
1013
+ `subspace: ${subspaceId}`,
1014
+ typeof nodeIndex === 'number' ? `node index: ${nodeIndex}` : undefined,
1015
+ reference || undefined,
1016
+ ]
1017
+ .filter(Boolean)
1018
+ .join(', ')
1019
+
1020
+ if (node.__typename === 'DataCalculationData') {
1021
+ return assertEntryId(
1022
+ node.data()?.id,
1023
+ 'PROPERTY_BANK_DATA_NODE',
1024
+ `(${nodeReference})`,
1025
+ )
1026
+ }
1027
+ if (node.__typename === 'DataCommand') {
1028
+ return assertEntryId(node.id, 'PROPERTY_BANK_COMMAND_NODE', `(${nodeReference})`)
1029
+ }
1030
+ throw new Error(`Invalid node: ${JSON.stringify(node)}`)
1031
+ }
1032
+
1033
+ const generateInputPorts = (inputs) =>
1034
+ inputs.reduce((acc, port, portIndex) => {
1035
+ if (!acc[port.key]) acc[port.key] = null
1036
+
1037
+ let sourceId
1038
+ const sourceNode = port.source()
1039
+ if (
1040
+ sourceNode?.__typename === 'DataCalculationData' ||
1041
+ sourceNode?.__typename === 'DataCommand'
1042
+ ) {
1043
+ sourceId = getNodeId(sourceNode, `input port index: ${portIndex}`)
1044
+ }
1045
+
1046
+ if (!sourceId) return acc
1047
+ if (!acc[port.key]) acc[port.key] = []
1048
+
1049
+ acc[port.key].push({
1050
+ id: sourceId,
1051
+ port: port.sourceKey,
1052
+ disable_trigger_command: !port.trigger ? true : undefined,
1053
+ })
1054
+ return acc
1055
+ }, {})
1056
+
1057
+ const generateOutputPorts = (outputs) =>
1058
+ outputs.reduce((acc, port, portIndex) => {
1059
+ if (!acc[port.key]) acc[port.key] = null
1060
+
1061
+ let targetId
1062
+ const targetNode = port.target()
1063
+ if (
1064
+ targetNode?.__typename === 'DataCalculationData' ||
1065
+ targetNode?.__typename === 'DataCommand'
1066
+ ) {
1067
+ targetId = getNodeId(targetNode, `output port index: ${portIndex}`)
1068
+ }
1069
+
1070
+ if (!targetId) return acc
1071
+ if (!acc[port.key]) acc[port.key] = []
1072
+
1073
+ acc[port.key].push({
1074
+ id: targetId,
1075
+ port: port.targetKey,
1076
+ })
1077
+ return acc
1078
+ }, {})
1079
+
1080
+ calc.map = mapCalc.nodes.reduce((acc, node, nodeIndex) => {
1081
+ if (node.__typename === 'DataCalculationData') {
1082
+ const dataNode = node as DataCalculationData
1083
+ acc[getNodeId(dataNode, 'data node', nodeIndex)] = {
1084
+ title: dataNode.title,
1085
+ description: dataNode.description,
1086
+ hide_short_ref: dataNode.hideShortRef,
1087
+ type: 'data-node',
1088
+ properties: {},
1089
+ in: generateInputPorts(dataNode.inputs),
1090
+ out: generateOutputPorts(dataNode.outputs),
1091
+ }
1092
+ } else if (node.__typename === 'DataCommand') {
1093
+ const commandNode = node as DataCommand
1094
+ const commandName = commandNode.__commandName
1095
+ const type = commandName.split('_')[0].toLowerCase()
1096
+
1097
+ const args = commandNode.inputs.filter(
1098
+ (input) =>
1099
+ typeof input.source !== 'function' ||
1100
+ (input.source()?.__typename !== 'DataCalculationData' &&
1101
+ input.source()?.__typename !== 'DataCommand'),
1102
+ )
1103
+ const inputs = commandNode.inputs.filter(
1104
+ (input) =>
1105
+ typeof input.source === 'function' &&
1106
+ (input.source()?.__typename === 'DataCalculationData' ||
1107
+ input.source()?.__typename === 'DataCommand'),
1108
+ )
1109
+ acc[getNodeId(commandNode, 'command node', nodeIndex)] = {
1110
+ title: commandNode.title,
1111
+ description: commandNode.description,
1112
+ hide_short_ref: commandNode.hideShortRef,
1113
+ type: `command-node-${type}`,
1114
+ properties: {
1115
+ command: commandNode.__commandName,
1116
+ args: args.reduce((argsAcc, input) => {
1117
+ argsAcc[input.key] = input.source
1118
+ return argsAcc
1119
+ }, {}),
1120
+ },
1121
+ in: generateInputPorts(inputs),
1122
+ out: generateOutputPorts(commandNode.outputs),
1123
+ }
1124
+ }
1125
+ return acc
1126
+ }, {})
1127
+ calc.editor_info = mapCalc.editorInfo.reduce((acc, editorInfo) => {
1128
+ acc[getNodeId(editorInfo.node, 'editor info node')] = {
1129
+ position: editorInfo.position,
1130
+ points: editorInfo.points.reduce((pointsAcc, point) => {
1131
+ const sourceId = getNodeId(point.source, 'editor info point source')
1132
+ const targetId = getNodeId(point.target, 'editor info point target')
1133
+ const key = `${sourceId}-${point.sourceOutputKey}-${targetId}-${point.targetInputKey}`
1134
+ pointsAcc[key] = point.positions
1135
+ return pointsAcc
1136
+ }, {}),
1137
+ }
1138
+ return acc
1139
+ }, {})
1140
+ } else if (dataCalc.__typename === 'DataCalculationScript') {
1141
+ const scriptCalc = dataCalc as DataCalculationScript
1142
+ calc.type = 'script'
1143
+
1144
+ const code = compileScriptCalculationCode(scriptCalc.code)
1145
+ calc.script_config = {
1146
+ title: scriptCalc.title ?? '',
1147
+ note: scriptCalc.note ?? '',
1148
+ code,
1149
+ enable_async: scriptCalc.enableAsync,
1150
+ trigger_mode: scriptCalc.triggerMode,
1151
+ inputs: scriptCalc.inputs.reduce((acc, input) => {
1152
+ const inputId = assertEntryId(
1153
+ input.data()?.id,
1154
+ 'PROPERTY_BANK_DATA_NODE',
1155
+ `(data calc: ${dataCalcId}, script input: ${input.key}, subspace: ${subspaceId})`,
1156
+ )
1157
+ acc[inputId] = input.key
1158
+ return acc
1159
+ }, {}),
1160
+ disabled_triggers: scriptCalc.inputs.reduce((acc, input) => {
1161
+ const inputId = assertEntryId(
1162
+ input.data()?.id,
1163
+ 'PROPERTY_BANK_DATA_NODE',
1164
+ `(data calc: ${dataCalcId}, script trigger input: ${input.key}, subspace: ${subspaceId})`,
1165
+ )
1166
+ acc[inputId] = !input.trigger
1167
+ return acc
1168
+ }, {}),
1169
+ output: scriptCalc.output
1170
+ ? assertEntryId(
1171
+ scriptCalc.output()?.id,
1172
+ 'PROPERTY_BANK_DATA_NODE',
1173
+ `(data calc: ${dataCalcId}, script output, subspace: ${subspaceId})`,
1174
+ )
1175
+ : null,
1176
+ outputs: scriptCalc.outputs.reduce((acc, output) => {
1177
+ if (!acc[output.key]) acc[output.key] = []
1178
+ const outputId = assertEntryId(
1179
+ output.data()?.id,
1180
+ 'PROPERTY_BANK_DATA_NODE',
1181
+ `(data calc: ${dataCalcId}, script outputs key: ${output.key}, subspace: ${subspaceId})`,
1182
+ )
1183
+ acc[output.key].push(outputId)
1184
+ return acc
1185
+ }, {}),
1186
+ error: scriptCalc.error
1187
+ ? assertEntryId(
1188
+ scriptCalc.error()?.id,
1189
+ 'PROPERTY_BANK_DATA_NODE',
1190
+ `(data calc: ${dataCalcId}, script error output, subspace: ${subspaceId})`,
1191
+ )
1192
+ : null,
1193
+ }
1194
+
1195
+ Object.assign(
1196
+ calc,
1197
+ generateCalulationMap(calc.script_config, {
1198
+ snapshotMode: process.env.BRICKS_SNAPSHOT_MODE === '1',
1199
+ }),
1200
+ )
1201
+ }
1202
+ map[dataCalcId] = calc
1203
+ return map
1204
+ }, {}),
1205
+ action_map: subspace.actions || undefined,
1206
+ event_map: compileEvents('', subspace.events || {}, {
1207
+ camelCase: false,
1208
+ errorReference: `(subspace ${subspaceId})`,
1209
+ }),
1210
+ routing: subspace.dataRouting.reduce((acc, data, index) => {
1211
+ const dataId = assertEntryId(
1212
+ data?.id,
1213
+ 'PROPERTY_BANK_DATA_NODE',
1214
+ `(data routing index: ${index}, subspace ${subspaceId})`,
1215
+ )
1216
+ acc[dataId] = { enabled_routing: true }
1217
+ return acc
1218
+ }, {}),
1219
+ ...compileModule(subspace),
1220
+ }
1221
+ return subspaceMap
1222
+ }, {}),
1223
+ root_subspace_id: assertEntryId(
1224
+ app.rootSubspace?.id,
1225
+ 'SUBSPACE',
1226
+ '(application root subspace)',
1227
+ ),
1228
+ fonts: app.fonts,
1229
+ ...compileApplicationSettings(app.settings),
1230
+ // Use typed automationMap if available, otherwise fall back to TEMP metadata
1231
+ test_map: app.automationMap
1232
+ ? compileAutomation(app.automationMap)['AUTOMATION_MAP_DEFAULT']?.map || {}
1233
+ : app.metadata?.TEMP_test_map || {},
1234
+ automation_map: app.automationMap
1235
+ ? compileAutomation(app.automationMap)
1236
+ : app.metadata?.TEMP_automation_map || {},
1237
+ update_timestamp: timestamp,
1238
+ }
1239
+ return config
1240
+ }
1241
+
1242
+ export const checkConfig = async (configPath: string) => {
1243
+ const { $ } = await import('bun')
1244
+ await $`bricks app check-config ${configPath}`
1245
+ }