@fugood/bricks-ctor 2.25.0-beta.8 → 2.25.0-beta.80

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