@fugood/bricks-project 2.24.0-beta.9 → 2.24.1-beta.0

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 (125) hide show
  1. package/compile/action-name-map.ts +38 -0
  2. package/compile/index.ts +460 -158
  3. package/compile/util.ts +2 -0
  4. package/package.json +8 -3
  5. package/package.json.bak +28 -0
  6. package/skills/{bricks-project → bricks-ctor}/SKILL.md +2 -2
  7. package/skills/{bricks-project → bricks-ctor}/rules/animation.md +1 -1
  8. package/skills/{bricks-project → bricks-ctor}/rules/architecture-patterns.md +7 -0
  9. package/skills/{bricks-project → bricks-ctor}/rules/buttress.md +10 -7
  10. package/skills/{bricks-project → bricks-ctor}/rules/data-calculation.md +3 -2
  11. package/skills/{bricks-project → bricks-ctor}/rules/local-sync.md +2 -2
  12. package/skills/{bricks-project → bricks-ctor}/rules/media-flow.md +3 -3
  13. package/skills/{bricks-project → bricks-ctor}/rules/remote-data-bank.md +6 -6
  14. package/skills/{bricks-project → bricks-ctor}/rules/standby-transition.md +1 -1
  15. package/skills/bricks-design/LICENSE.txt +180 -0
  16. package/skills/bricks-design/SKILL.md +66 -0
  17. package/tools/_git-author.ts +29 -0
  18. package/tools/_shell.ts +173 -0
  19. package/tools/deploy.ts +91 -23
  20. package/tools/icons/fa6pro-meta.json +3669 -26125
  21. package/tools/mcp-server.ts +11 -878
  22. package/tools/mcp-tools/compile.ts +92 -0
  23. package/tools/mcp-tools/huggingface.ts +762 -0
  24. package/tools/mcp-tools/icons.ts +70 -0
  25. package/tools/mcp-tools/lottie.ts +102 -0
  26. package/tools/mcp-tools/media.ts +110 -0
  27. package/tools/postinstall.ts +143 -40
  28. package/tools/preview-main.mjs +135 -2
  29. package/tools/preview.ts +68 -33
  30. package/tools/pull.ts +56 -32
  31. package/tsconfig.json +16 -0
  32. package/types/animation.ts +4 -0
  33. package/types/automation.ts +4 -1
  34. package/types/brick-base.ts +1 -1
  35. package/types/bricks/Camera.ts +47 -12
  36. package/types/bricks/Chart.ts +9 -3
  37. package/types/bricks/GenerativeMedia.ts +29 -13
  38. package/types/bricks/Icon.ts +8 -4
  39. package/types/bricks/Image.ts +9 -5
  40. package/types/bricks/Items.ts +28 -14
  41. package/types/bricks/Lottie.ts +14 -6
  42. package/types/bricks/Maps.ts +15 -7
  43. package/types/bricks/QrCode.ts +8 -4
  44. package/types/bricks/Rect.ts +44 -5
  45. package/types/bricks/RichText.ts +8 -4
  46. package/types/bricks/Rive.ts +20 -10
  47. package/types/bricks/Slideshow.ts +19 -9
  48. package/types/bricks/Svg.ts +7 -3
  49. package/types/bricks/Text.ts +8 -4
  50. package/types/bricks/TextInput.ts +22 -12
  51. package/types/bricks/Video.ts +10 -6
  52. package/types/bricks/VideoStreaming.ts +7 -3
  53. package/types/bricks/WebRtcStream.ts +6 -2
  54. package/types/bricks/WebView.ts +11 -7
  55. package/types/canvas.ts +2 -0
  56. package/types/common.ts +15 -8
  57. package/types/data-calc-command.ts +2 -0
  58. package/types/data-calc.ts +1 -0
  59. package/types/data.ts +2 -0
  60. package/types/generators/AlarmClock.ts +16 -10
  61. package/types/generators/Assistant.ts +68 -17
  62. package/types/generators/BleCentral.ts +30 -10
  63. package/types/generators/BlePeripheral.ts +10 -6
  64. package/types/generators/CanvasMap.ts +9 -5
  65. package/types/generators/CastlesPay.ts +14 -6
  66. package/types/generators/DataBank.ts +43 -8
  67. package/types/generators/File.ts +108 -29
  68. package/types/generators/GraphQl.ts +11 -5
  69. package/types/generators/Http.ts +32 -9
  70. package/types/generators/HttpServer.ts +22 -14
  71. package/types/generators/Information.ts +8 -4
  72. package/types/generators/Intent.ts +14 -4
  73. package/types/generators/Iterator.ts +14 -10
  74. package/types/generators/Keyboard.ts +26 -12
  75. package/types/generators/LlmAnthropicCompat.ts +32 -10
  76. package/types/generators/LlmAppleBuiltin.ts +24 -9
  77. package/types/generators/LlmGgml.ts +139 -30
  78. package/types/generators/LlmMediaTekNeuroPilot.ts +235 -0
  79. package/types/generators/LlmMlx.ts +227 -0
  80. package/types/generators/LlmOnnx.ts +33 -13
  81. package/types/generators/LlmOpenAiCompat.ts +46 -10
  82. package/types/generators/LlmQualcommAiEngine.ts +44 -12
  83. package/types/generators/Mcp.ts +374 -33
  84. package/types/generators/McpServer.ts +57 -18
  85. package/types/generators/MediaFlow.ts +37 -11
  86. package/types/generators/MqttBroker.ts +28 -10
  87. package/types/generators/MqttClient.ts +18 -8
  88. package/types/generators/Question.ts +12 -8
  89. package/types/generators/RealtimeTranscription.ts +107 -18
  90. package/types/generators/RerankerGgml.ts +42 -11
  91. package/types/generators/SerialPort.ts +17 -9
  92. package/types/generators/SoundPlayer.ts +9 -3
  93. package/types/generators/SoundRecorder.ts +23 -8
  94. package/types/generators/SpeechToTextGgml.ts +51 -17
  95. package/types/generators/SpeechToTextOnnx.ts +17 -10
  96. package/types/generators/SpeechToTextPlatform.ts +14 -6
  97. package/types/generators/SqLite.ts +19 -9
  98. package/types/generators/Step.ts +8 -4
  99. package/types/generators/SttAppleBuiltin.ts +21 -8
  100. package/types/generators/Tcp.ts +12 -8
  101. package/types/generators/TcpServer.ts +19 -13
  102. package/types/generators/TextToSpeechAppleBuiltin.ts +20 -7
  103. package/types/generators/TextToSpeechGgml.ts +28 -10
  104. package/types/generators/TextToSpeechOnnx.ts +18 -11
  105. package/types/generators/TextToSpeechOpenAiLike.ts +13 -7
  106. package/types/generators/ThermalPrinter.ts +12 -8
  107. package/types/generators/Tick.ts +10 -6
  108. package/types/generators/Udp.ts +16 -7
  109. package/types/generators/VadGgml.ts +50 -13
  110. package/types/generators/VadOnnx.ts +41 -11
  111. package/types/generators/VadTraditional.ts +27 -12
  112. package/types/generators/VectorStore.ts +32 -11
  113. package/types/generators/Watchdog.ts +18 -9
  114. package/types/generators/WebCrawler.ts +10 -6
  115. package/types/generators/WebRtc.ts +29 -15
  116. package/types/generators/WebSocket.ts +10 -6
  117. package/types/generators/index.ts +2 -0
  118. package/types/subspace.ts +4 -0
  119. package/types/system.ts +1 -1
  120. package/utils/event-props.ts +833 -1022
  121. package/api/index.ts +0 -1
  122. package/api/instance.ts +0 -213
  123. package/types/generators/TextToSpeechApple.ts +0 -113
  124. package/types/generators/TtsAppleBuiltin.ts +0 -105
  125. /package/skills/{bricks-project → bricks-ctor}/rules/automations.md +0 -0
package/compile/index.ts CHANGED
@@ -1,9 +1,12 @@
1
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'
2
4
  import snakeCase from 'lodash/snakeCase'
3
5
  import omit from 'lodash/omit'
4
6
  import { parse as parseAST } from 'acorn'
5
7
  import type { ExportNamedDeclaration, FunctionDeclaration } from 'acorn'
6
8
  import escodegen from 'escodegen'
9
+ import { makeId } from '../utils/id'
7
10
  import { generateCalulationMap } from './util'
8
11
  import { templateActionNameMap } from './action-name-map'
9
12
  import { templateEventPropsMap } from '../utils/event-props'
@@ -34,6 +37,40 @@ import type {
34
37
  TestVariable,
35
38
  } from '../types'
36
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
+
37
74
  const compileProperty = (property, errorReference: string, result = {}) => {
38
75
  if (Array.isArray(property)) {
39
76
  return property.map((p) => compileProperty(p, errorReference))
@@ -57,14 +94,39 @@ const compileProperty = (property, errorReference: string, result = {}) => {
57
94
  return property
58
95
  }
59
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
+
60
119
  const compileEventActionValue = (templateKey, eventKey, value, errorReference) => {
61
- const tmplEventProperties = templateEventPropsMap[templateKey]
120
+ const tmplEventProperties = templateEventPropsMap[getTemplateName(templateKey)]
62
121
  const props = tmplEventProperties?.[eventKey]
63
122
  if (!props) return compileProperty(value, errorReference)
64
- if (props.includes(value)) return value
65
- if (typeof value === 'string' && value?.startsWith(templateKey)) {
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)) {
66
128
  console.warn(
67
- `[Warning] Value start with template key but there is no event property: ${value} ${errorReference}`,
129
+ `[Warning] Event property "${value}" is not compatible with event "${eventKey}" of ${templateKey} ${errorReference}`,
68
130
  )
69
131
  }
70
132
  return compileProperty(value, errorReference)
@@ -79,8 +141,9 @@ const compileOutlets = (
79
141
  errorReference: string,
80
142
  ) =>
81
143
  Object.entries(outlets).reduce((acc, [key, data]) => {
82
- if (!data()?.id) throw new Error(`Invalid data reference ${errorReference}`)
83
- acc[convertOutletKey(templateKey, key)] = data().id
144
+ const dataInstance = data?.()
145
+ const dataId = assertEntryId(dataInstance?.id, 'PROPERTY_BANK_DATA_NODE', errorReference)
146
+ acc[convertOutletKey(templateKey, key)] = dataId
84
147
  return acc
85
148
  }, {})
86
149
 
@@ -95,9 +158,9 @@ const compileAnimations = (
95
158
  errorReference: string,
96
159
  ) =>
97
160
  Object.entries(animations).reduce((acc, [key, animation]) => {
98
- if (!animation?.id) throw new Error(`Invalid animation reference ${errorReference}`)
161
+ const animationId = assertEntryId(animation?.id, 'ANIMATION', errorReference)
99
162
  acc[convertEventKey(basicAnimationEvents.includes(key) ? 'BRICK' : templateKey, key)] =
100
- `ANIMATION#${animation.id}`
163
+ `ANIMATION#${animationId}`
101
164
  return acc
102
165
  }, {})
103
166
 
@@ -134,7 +197,7 @@ const compileEvents = (
134
197
  if (!handlerKey) throw new Error(`Invalid handler: ${handler} ${errorReference}`)
135
198
 
136
199
  const parameterList: Array<object> = []
137
- if (Object.hasOwn(action, 'params')) {
200
+ if (Object.prototype.hasOwnProperty.call(action, 'params')) {
138
201
  const actionDef = action as ActionWithParams
139
202
  ;(actionDef.params || []).forEach(({ input, value, mapping }) => {
140
203
  const param = {
@@ -147,7 +210,7 @@ const compileEvents = (
147
210
  if (mapping) param[camelCase ? 'resultDataMapping' : 'result_data_mapping'] = true
148
211
  parameterList.push(param)
149
212
  })
150
- } else if (Object.hasOwn(action, 'dataParams')) {
213
+ } else if (Object.prototype.hasOwnProperty.call(action, 'dataParams')) {
151
214
  const actionDef = action as ActionWithDataParams
152
215
  ;(actionDef.dataParams || []).forEach(({ input, value, mapping }) => {
153
216
  if (!input) return
@@ -231,6 +294,7 @@ const compileApplicationSettings = (settings: Application['settings']) => ({
231
294
  use_gemini_api_key_system_data: settings.ai.useGeminiApiKeySystemData,
232
295
  }
233
296
  : undefined,
297
+ hide_short_refs: settings?.hideShortRefs,
234
298
  })
235
299
 
236
300
  const animationTypeMap = {
@@ -299,10 +363,10 @@ const compileRemoteUpdate = (remoteUpdate: Data['remoteUpdate']) => {
299
363
  }
300
364
 
301
365
  const compileModule = (subspace: Subspace) => {
302
- if (!subspace.module) return {}
366
+ if (!subspace.module?.id) return {}
303
367
  return {
304
368
  module: {
305
- link: true,
369
+ link: subspace.module.link ?? false,
306
370
  id: subspace.module.id,
307
371
  version: subspace.module.version,
308
372
  },
@@ -350,20 +414,28 @@ function compileRunArray(run: unknown[]): unknown[] {
350
414
  const compileTestCase = (testCase: TestCase) => ({
351
415
  id: testCase.id,
352
416
  name: testCase.name,
417
+ hide_short_ref: testCase.hideShortRef,
353
418
  run: compileRunArray(testCase.run),
354
419
  exit_on_failed: testCase.exit_on_failed,
355
420
  commented: testCase.commented,
356
421
  pre_delay: testCase.pre_delay,
357
422
  post_delay: testCase.post_delay,
358
423
  post_delay_rule: testCase.post_delay_rule,
359
- jump_cond: testCase.jump_cond.map((cond) => ({
360
- type: cond.type,
361
- status: cond.status,
362
- variable: cond.variable,
363
- operator: cond.operator,
364
- value: cond.value,
365
- jump_to: cond.jump_to,
366
- })),
424
+ jump_cond: testCase.jump_cond.map((cond) => {
425
+ if (cond.jump_to == null) {
426
+ console.warn(
427
+ `[Warning] jump_cond is missing jump_to in test case "${testCase.name}" (${testCase.id})`,
428
+ )
429
+ }
430
+ return {
431
+ type: cond.type,
432
+ status: cond.status,
433
+ variable: cond.variable,
434
+ operator: cond.operator,
435
+ value: cond.value,
436
+ jump_to: cond.jump_to,
437
+ }
438
+ }),
367
439
  })
368
440
 
369
441
  /**
@@ -381,60 +453,158 @@ const compileTestVariable = (variable: TestVariable) => ({
381
453
  */
382
454
  const arrayToIdMap = <T extends { id: string }, R>(
383
455
  items: T[],
384
- transform: (item: T) => R,
385
- ): Record<string, R> => Object.fromEntries(items.map((item) => [item.id, transform(item)]))
456
+ transform: (item: T, index: number) => R,
457
+ options: {
458
+ patternKey: EntryIdPatternKey
459
+ getErrorReference: (item: T, index: number) => string
460
+ },
461
+ ): Record<string, R> =>
462
+ Object.fromEntries(
463
+ items.map((item, index) => {
464
+ const itemId = assertEntryId(
465
+ item.id,
466
+ options.patternKey,
467
+ options.getErrorReference(item, index),
468
+ )
469
+ return [itemId, transform(item, index)]
470
+ }),
471
+ )
386
472
 
387
473
  /**
388
474
  * Compile typed AutomationTest to raw format
389
475
  */
390
- const compileAutomationTest = (test: AutomationTest) => ({
391
- id: test.id,
392
- title: test.title,
393
- timeout: test.timeout,
394
- trigger_type: test.trigger_type,
395
- cron: test.cron,
396
- skip_trigger_type_check: test.skip_trigger_type_check,
397
- local_sync: test.local_sync,
398
- meta: test.meta,
399
- case_map: arrayToIdMap(test.cases, compileTestCase),
400
- var_map: arrayToIdMap(test.variables, compileTestVariable),
401
- })
476
+ const compileAutomationTest = (
477
+ test: AutomationTest,
478
+ options: { mapId: string; testIndex: number },
479
+ ) => {
480
+ const testId = assertEntryId(
481
+ test.id,
482
+ 'TEST',
483
+ `(automation map: ${options.mapId}, test index: ${options.testIndex})`,
484
+ )
485
+
486
+ return {
487
+ id: testId,
488
+ title: test.title,
489
+ hide_short_ref: test.hideShortRef,
490
+ timeout: test.timeout,
491
+ trigger_type: test.trigger_type,
492
+ cron: test.cron,
493
+ skip_trigger_type_check: test.skip_trigger_type_check,
494
+ local_sync: test.local_sync,
495
+ meta: test.meta,
496
+ case_map: arrayToIdMap(test.cases, compileTestCase, {
497
+ patternKey: 'TEST_CASE',
498
+ getErrorReference: (_, index) =>
499
+ `(automation map: ${options.mapId}, test: ${testId}, case index: ${index})`,
500
+ }),
501
+ var_map: arrayToIdMap(test.variables, compileTestVariable, {
502
+ patternKey: 'TEST_VAR',
503
+ getErrorReference: (_, index) =>
504
+ `(automation map: ${options.mapId}, test: ${testId}, variable index: ${index})`,
505
+ }),
506
+ }
507
+ }
402
508
 
403
509
  /**
404
510
  * Compile typed AutomationTestMap to raw format
405
511
  */
406
- const compileAutomationTestMap = (testMap: AutomationTestMap) => ({
407
- title: testMap.title,
408
- createdAt: testMap.createdAt,
409
- map: arrayToIdMap(testMap.tests, compileAutomationTest),
410
- })
512
+ const compileAutomationTestMap = (testMap: AutomationTestMap, mapId: string) => {
513
+ assertEntryId(testMap.id, 'AUTOMATION_MAP', `(automation map id field: ${mapId})`)
514
+
515
+ return {
516
+ title: testMap.title,
517
+ hide_short_ref: testMap.hideShortRef,
518
+ createdAt: testMap.createdAt,
519
+ map: arrayToIdMap(
520
+ testMap.tests,
521
+ (test, index) => compileAutomationTest(test, { mapId, testIndex: index }),
522
+ {
523
+ patternKey: 'TEST',
524
+ getErrorReference: (_, index) => `(automation map: ${mapId}, test index: ${index})`,
525
+ },
526
+ ),
527
+ }
528
+ }
411
529
 
412
530
  /**
413
531
  * Compile typed AutomationMap to raw automation_map format
414
532
  */
415
533
  const compileAutomation = (automationMap: AutomationMap) =>
416
534
  Object.fromEntries(
417
- Object.entries(automationMap).map(([mapId, testMap]) => [
418
- mapId,
419
- compileAutomationTestMap(testMap),
420
- ]),
535
+ Object.entries(automationMap).map(([mapId, testMap]) => {
536
+ const automationMapId = assertEntryId(mapId, 'AUTOMATION_MAP', '(automation map key)')
537
+ return [automationMapId, compileAutomationTestMap(testMap, automationMapId)]
538
+ }),
421
539
  )
422
540
 
423
541
  export const compile = async (app: Application) => {
424
542
  await new Promise((resolve) => setImmediate(resolve, 0))
425
543
  const timestamp = Date.now()
544
+ // Pre-index subspace ids so the canvas-item validation below stays O(1).
545
+ const subspaceIdSet = new Set(app.subspaces.map((s) => s.id))
426
546
  const config = {
427
547
  title: `${app.name || 'Unknown'}(${timestamp})`,
428
- subspace_map: app.subspaces.reduce((subspaceMap, subspace) => {
429
- subspaceMap[subspace.id] = {
548
+ subspace_map: app.subspaces.reduce((subspaceMap, subspace, subspaceIndex) => {
549
+ const subspaceId = assertEntryId(
550
+ subspace.id,
551
+ 'SUBSPACE',
552
+ `(subspace index: ${subspaceIndex})`,
553
+ )
554
+
555
+ // Linked module subspaces reference external content — only include
556
+ // module metadata, not local canvas/brick/data compilation.
557
+ // Include a placeholder root canvas so the config passes schema
558
+ // validation (root_canvas_id is required before the conditional
559
+ // schema fix is published).
560
+ if (subspace.module?.link) {
561
+ const placeholderCanvasId = makeId('canvas')
562
+ subspaceMap[subspaceId] = {
563
+ title: subspace.title,
564
+ description: subspace.description,
565
+ unused: subspace.unused,
566
+ portal: subspace.portal,
567
+ layout: {
568
+ width: subspace.layout?.width,
569
+ height: subspace.layout?.height,
570
+ resize_mode: subspace.layout?.resizeMode,
571
+ },
572
+ root_canvas_id: placeholderCanvasId,
573
+ property_bank_map: {},
574
+ brick_map: {},
575
+ generator_map: {},
576
+ animation_map: {},
577
+ canvas_map: {
578
+ [placeholderCanvasId]: { item_list: [] },
579
+ },
580
+ ...compileModule(subspace),
581
+ }
582
+ return subspaceMap
583
+ }
584
+
585
+ const rootCanvasId = assertEntryId(
586
+ subspace.rootCanvas?.id,
587
+ 'CANVAS',
588
+ `(subspace: ${subspaceId}, root canvas)`,
589
+ )
590
+
591
+ subspaceMap[subspaceId] = {
430
592
  title: subspace.title,
431
593
  description: subspace.description,
594
+ hide_short_ref: subspace.hideShortRef,
595
+ unused: subspace.unused,
596
+ portal: subspace.portal,
432
597
  _expanded: subspace.unexpanded
433
598
  ? {
434
599
  brick: !subspace.unexpanded.brick,
435
600
  generator: !subspace.unexpanded.generator,
436
- canvas: subspace.unexpanded.canvas?.reduce((acc, canvas) => {
437
- acc[canvas.id] = !canvas?.id
601
+ canvas: subspace.unexpanded.canvas?.reduce((acc, canvas, canvasIndex) => {
602
+ const unexpandedCanvasId = assertEntryId(
603
+ canvas?.id,
604
+ 'CANVAS',
605
+ `(subspace: ${subspaceId}, unexpanded canvas index: ${canvasIndex})`,
606
+ )
607
+ acc[unexpandedCanvasId] = !canvas?.id
438
608
  return acc
439
609
  }, {}),
440
610
  property_bank: !subspace.unexpanded.data,
@@ -451,32 +621,42 @@ export const compile = async (app: Application) => {
451
621
  change_canvas: subspace.localSyncChangeCanvas,
452
622
  }
453
623
  : undefined,
454
- animation_map: subspace.animations.reduce((map, animation) => {
624
+ animation_map: subspace.animations.reduce((map, animation, animationIndex) => {
625
+ const animationId = assertEntryId(
626
+ animation?.id,
627
+ 'ANIMATION',
628
+ `(animation index: ${animationIndex}, subspace: ${subspaceId})`,
629
+ )
630
+
455
631
  if (animation.__typename === 'Animation') {
456
632
  const animationDef = animation as AnimationDef
457
- map[animationDef.id] = {
633
+ map[animationId] = {
634
+ alias: animationDef.alias,
458
635
  title: animationDef.title,
459
636
  description: animationDef.description,
637
+ hide_short_ref: animationDef.hideShortRef,
460
638
  animationRunType: animationDef.runType,
461
639
  property: animationDef.property,
462
640
  type: animationTypeMap[animationDef.config.__type],
463
641
  config: compileProperty(
464
642
  omit(animationDef.config, '__type'),
465
- `(animation: ${animation.id}, subspace ${subspace.id})`,
643
+ `(animation: ${animationId}, subspace ${subspaceId})`,
466
644
  ),
467
645
  }
468
646
  } else if (animation.__typename === 'AnimationCompose') {
469
647
  const animationDef = animation as AnimationComposeDef
470
- map[animationDef.id] = {
648
+ map[animationId] = {
649
+ alias: animationDef.alias,
471
650
  title: animationDef.title,
472
651
  description: animationDef.description,
652
+ hide_short_ref: animationDef.hideShortRef,
473
653
  animationRunType: animationDef.runType,
474
654
  compose_type: animationDef.composeType,
475
655
  item_list: animationDef.items.map((item, index) => {
476
656
  const innerAnimation = item()
477
657
  if (!innerAnimation?.id)
478
658
  throw new Error(
479
- `Invalid animation index: ${index} (animation: ${innerAnimation.id}, subspace ${subspace.id})`,
659
+ `Invalid animation index: ${index} (animation: ${innerAnimation.id}, subspace ${subspaceId})`,
480
660
  )
481
661
  return { animation_id: innerAnimation.id }
482
662
  }),
@@ -484,10 +664,15 @@ export const compile = async (app: Application) => {
484
664
  }
485
665
  return map
486
666
  }, {}),
487
- brick_map: subspace.bricks.reduce((map, brick) => {
667
+ brick_map: subspace.bricks.reduce((map, brick, brickIndex) => {
668
+ const brickId = assertEntryId(
669
+ brick.id,
670
+ 'BRICK',
671
+ `(brick index: ${brickIndex}, subspace: ${subspaceId})`,
672
+ )
488
673
  const property = compileProperty(
489
674
  brick.property || {},
490
- `(brick: ${brick.id}, subspace ${subspace.id})`,
675
+ `(brick: ${brickId}, subspace ${subspaceId})`,
491
676
  )
492
677
  if (brick.templateKey === 'BRICK_ITEMS') {
493
678
  const brickItems = brick as BrickItems
@@ -499,25 +684,31 @@ export const compile = async (app: Application) => {
499
684
  frame: itemBrick.frame,
500
685
  property: compileProperty(
501
686
  itemBrick.property,
502
- `(brick: ${brick.id}, ${key}: ${index}, subspace ${subspace.id})`,
687
+ `(brick: ${brickId}, ${key}: ${index}, subspace ${subspaceId})`,
503
688
  ),
504
689
  propertyMapping: itemBrick.propertyMapping,
505
690
  animation: compileAnimations(
506
691
  itemBrick.templateKey,
507
692
  itemBrick.animation || {},
508
- `(brick: ${brick.id}, ${key}: ${index}, subspace ${subspace.id})`,
693
+ `(brick: ${brickId}, ${key}: ${index}, subspace ${subspaceId})`,
509
694
  ),
510
695
  outlet: compileOutlets(
511
696
  itemBrick.templateKey,
512
697
  itemBrick.outlets || {},
513
- `(brick: ${brick.id}, ${key}: ${index}, subspace ${subspace.id})`,
698
+ `(brick: ${brickId}, ${key}: ${index}, subspace ${subspaceId})`,
514
699
  ),
515
700
  eventMap: compileEvents(itemBrick.templateKey, itemBrick.eventMap || {}, {
516
701
  camelCase: true,
517
- errorReference: `(brick: ${brick.id}, ${key}: ${index}, subspace ${subspace.id})`,
702
+ errorReference: `(brick: ${brickId}, ${key}: ${index}, subspace ${subspaceId})`,
518
703
  }),
519
- stateGroup: itemBrick.stateGroup.reduce((acc, stateGroup) => {
520
- acc[stateGroup.id] = {
704
+ stateGroup: itemBrick.stateGroup.reduce((acc, stateGroup, stateGroupIndex) => {
705
+ const stateGroupId = assertEntryId(
706
+ stateGroup.id,
707
+ 'BRICK_STATE_GROUP',
708
+ `(brick: ${brickId}, ${key}: ${index}, switch index: ${stateGroupIndex}, subspace ${subspaceId})`,
709
+ )
710
+
711
+ acc[stateGroupId] = {
521
712
  title: stateGroup.title,
522
713
  description: stateGroup.description,
523
714
  override: stateGroup.override,
@@ -526,35 +717,40 @@ export const compile = async (app: Application) => {
526
717
  conds: compileSwitchConds(
527
718
  itemBrick.templateKey,
528
719
  stateGroup.conds || [],
529
- `(brick: ${brick.id}, ${key}: ${index}, switch: ${stateGroup.id}, subspace ${subspace.id})`,
720
+ `(brick: ${brickId}, ${key}: ${index}, switch: ${stateGroupId}, subspace ${subspaceId})`,
530
721
  ),
531
722
  property: compileProperty(
532
723
  stateGroup.property,
533
- `(brick: ${brick.id}, ${key}: ${index}, switch: ${stateGroup.id}, subspace ${subspace.id})`,
724
+ `(brick: ${brickId}, ${key}: ${index}, switch: ${stateGroupId}, subspace ${subspaceId})`,
534
725
  ),
535
726
  animation: compileAnimations(
536
727
  itemBrick.templateKey,
537
728
  stateGroup.animation || {},
538
- `(brick: ${brick.id}, ${key}: ${index}, switch: ${stateGroup.id}, subspace ${subspace.id})`,
729
+ `(brick: ${brickId}, ${key}: ${index}, switch: ${stateGroupId}, subspace ${subspaceId})`,
539
730
  ),
540
731
  outlet: compileOutlets(
541
732
  itemBrick.templateKey,
542
733
  stateGroup.outlets || {},
543
- `(brick: ${brick.id}, ${key}: ${index}, switch: ${stateGroup.id}, subspace ${subspace.id})`,
734
+ `(brick: ${brickId}, ${key}: ${index}, switch: ${stateGroupId}, subspace ${subspaceId})`,
544
735
  ),
545
736
  eventMap: compileEvents(itemBrick.templateKey, stateGroup.eventMap || {}, {
546
737
  camelCase: true,
547
- errorReference: `(brick: ${brick.id}, ${key}: ${index}, switch: ${stateGroup.id}, subspace ${subspace.id})`,
738
+ errorReference: `(brick: ${brickId}, ${key}: ${index}, switch: ${stateGroupId}, subspace ${subspaceId})`,
548
739
  }),
549
740
  }
550
741
  return acc
551
742
  }, {}),
743
+ show: itemBrick.show,
744
+ pressToOpenDetail: itemBrick.pressToOpenDetail,
745
+ pressToBackList: itemBrick.pressToBackList,
552
746
  })
553
747
  if (Array.isArray(brickItems.brickList)) {
554
748
  const brickList = (brickItems.brickList || []).map((item, index) =>
555
749
  buildList(item, index, 'brickList'),
556
750
  )
557
751
  property.brickList = brickList
752
+ } else if (!brickItems.brickList) {
753
+ property.brickList = []
558
754
  } else {
559
755
  // Not supported Data for brickList
560
756
  throw new TypeError('Not supported Data for brickList directly')
@@ -564,32 +760,42 @@ export const compile = async (app: Application) => {
564
760
  buildList(item, index, 'brickDetails'),
565
761
  )
566
762
  property.brickDetails = brickDetails
763
+ } else if (!brickItems.brickDetails) {
764
+ property.brickDetails = []
567
765
  } else {
568
766
  // Not supported Data for brickList
569
767
  throw new TypeError('Not supported Data for brickList directly')
570
768
  }
571
769
  }
572
- map[brick.id] = {
770
+ map[brickId] = {
573
771
  template_key: brick.templateKey,
772
+ alias: brick.alias,
574
773
  title: brick.title,
575
774
  description: brick.description,
775
+ hide_short_ref: brick.hideShortRef,
576
776
  property,
577
777
  animation: compileAnimations(
578
778
  brick.templateKey,
579
779
  brick.animation || {},
580
- `(brick: ${brick.id}, subspace ${subspace.id})`,
780
+ `(brick: ${brickId}, subspace ${subspaceId})`,
581
781
  ),
582
782
  event_map: compileEvents(brick.templateKey, brick.events || {}, {
583
783
  camelCase: false,
584
- errorReference: `(brick: ${brick.id}, subspace ${subspace.id})`,
784
+ errorReference: `(brick: ${brickId}, subspace ${subspaceId})`,
585
785
  }),
586
786
  outlet: compileOutlets(
587
787
  brick.templateKey,
588
788
  brick.outlets || {},
589
- `(brick: ${brick.id}, subspace ${subspace.id})`,
789
+ `(brick: ${brickId}, subspace ${subspaceId})`,
590
790
  ),
591
- state_group: brick.switches?.reduce((acc, switchCase) => {
592
- acc[switchCase.id] = {
791
+ state_group: brick.switches?.reduce((acc, switchCase, switchIndex) => {
792
+ const switchId = assertEntryId(
793
+ switchCase.id,
794
+ 'BRICK_STATE_GROUP',
795
+ `(brick: ${brickId}, switch index: ${switchIndex}, subspace ${subspaceId})`,
796
+ )
797
+
798
+ acc[switchId] = {
593
799
  title: switchCase.title,
594
800
  description: switchCase.description,
595
801
  break: switchCase.break,
@@ -598,25 +804,25 @@ export const compile = async (app: Application) => {
598
804
  conds: compileSwitchConds(
599
805
  brick.templateKey,
600
806
  switchCase.conds || [],
601
- `(brick: ${brick.id}, switch: ${switchCase.id}, subspace ${subspace.id})`,
807
+ `(brick: ${brickId}, switch: ${switchId}, subspace ${subspaceId})`,
602
808
  ),
603
809
  property: compileProperty(
604
810
  switchCase.property,
605
- `(brick: ${brick.id}, switch: ${switchCase.id}, subspace ${subspace.id})`,
811
+ `(brick: ${brickId}, switch: ${switchId}, subspace ${subspaceId})`,
606
812
  ),
607
813
  outlet: compileOutlets(
608
814
  brick.templateKey,
609
815
  switchCase.outlets || {},
610
- `(brick: ${brick.id}, switch: ${switchCase.id}, subspace ${subspace.id})`,
816
+ `(brick: ${brickId}, switch: ${switchId}, subspace ${subspaceId})`,
611
817
  ),
612
818
  event_map: compileEvents(brick.templateKey, switchCase.events || {}, {
613
819
  camelCase: false,
614
- errorReference: `(brick: ${brick.id}, switch: ${switchCase.id}, subspace ${subspace.id})`,
820
+ errorReference: `(brick: ${brickId}, switch: ${switchId}, subspace ${subspaceId})`,
615
821
  }),
616
822
  animation: compileAnimations(
617
823
  brick.templateKey,
618
824
  switchCase.animation || {},
619
- `(brick: ${brick.id}, switch: ${switchCase.id}, subspace ${subspace.id})`,
825
+ `(brick: ${brickId}, switch: ${switchId}, subspace ${subspaceId})`,
620
826
  ),
621
827
  }
622
828
  return acc
@@ -624,21 +830,35 @@ export const compile = async (app: Application) => {
624
830
  }
625
831
  return map
626
832
  }, {}),
627
- root_canvas_id: subspace.rootCanvas.id,
628
- canvas_map: subspace.canvases.reduce((map, canvas) => {
629
- map[canvas.id] = {
833
+ root_canvas_id: rootCanvasId,
834
+ canvas_map: subspace.canvases.reduce((map, canvas, canvasIndex) => {
835
+ const canvasId = assertEntryId(
836
+ canvas.id,
837
+ 'CANVAS',
838
+ `(canvas index: ${canvasIndex}, subspace: ${subspaceId})`,
839
+ )
840
+
841
+ map[canvasId] = {
842
+ alias: canvas.alias,
630
843
  title: canvas.title,
631
844
  description: canvas.description,
845
+ hide_short_ref: canvas.hideShortRef,
632
846
  property: compileProperty(
633
847
  canvas.property,
634
- `(canvas: ${canvas.id}, subspace ${subspace.id})`,
848
+ `(canvas: ${canvasId}, subspace ${subspaceId})`,
635
849
  ),
636
850
  event_map: compileEvents('CANVAS', canvas.events || {}, {
637
851
  camelCase: false,
638
- errorReference: `(canvas: ${canvas.id}, subspace ${subspace.id})`,
852
+ errorReference: `(canvas: ${canvasId}, subspace ${subspaceId})`,
639
853
  }),
640
- state_group: canvas.switches?.reduce((acc, switchCase) => {
641
- acc[switchCase.id] = {
854
+ state_group: canvas.switches?.reduce((acc, switchCase, switchIndex) => {
855
+ const switchId = assertEntryId(
856
+ switchCase.id,
857
+ 'BRICK_STATE_GROUP',
858
+ `(canvas: ${canvasId}, switch index: ${switchIndex}, subspace ${subspaceId})`,
859
+ )
860
+
861
+ acc[switchId] = {
642
862
  title: switchCase.title,
643
863
  description: switchCase.description,
644
864
  break: switchCase.break,
@@ -647,20 +867,20 @@ export const compile = async (app: Application) => {
647
867
  conds: compileSwitchConds(
648
868
  'CANVAS',
649
869
  switchCase.conds || [],
650
- `(canvas: ${canvas.id}, switch: ${switchCase.id}, subspace ${subspace.id})`,
870
+ `(canvas: ${canvasId}, switch: ${switchId}, subspace ${subspaceId})`,
651
871
  ),
652
872
  property: compileProperty(
653
873
  switchCase.property,
654
- `(canvas: ${canvas.id}, switch: ${switchCase.id}, subspace ${subspace.id})`,
874
+ `(canvas: ${canvasId}, switch: ${switchId}, subspace ${subspaceId})`,
655
875
  ),
656
876
  event_map: compileEvents('CANVAS', switchCase.events || {}, {
657
877
  camelCase: false,
658
- errorReference: `(canvas: ${canvas.id}, switch: ${switchCase.id}, subspace ${subspace.id})`,
878
+ errorReference: `(canvas: ${canvasId}, switch: ${switchId}, subspace ${subspaceId})`,
659
879
  }),
660
880
  animation: compileAnimations(
661
881
  'CANVAS',
662
882
  switchCase.animation || {},
663
- `(canvas: ${canvas.id}, switch: ${switchCase.id}, subspace ${subspace.id})`,
883
+ `(canvas: ${canvasId}, switch: ${switchId}, subspace ${subspaceId})`,
664
884
  ),
665
885
  }
666
886
  return acc
@@ -671,16 +891,16 @@ export const compile = async (app: Application) => {
671
891
  const brick = (item.item as () => Brick)()
672
892
  if (!brick?.id)
673
893
  throw new Error(
674
- `Invalid canvas item index: ${index}, brick not found (canvas: ${canvas.id}, subspace ${subspace.id})`,
894
+ `Invalid canvas item index: ${index}, brick not found (canvas: ${canvasId}, subspace ${subspaceId})`,
675
895
  )
676
896
  itemPayload = { brick_id: brick.id }
677
897
  } else {
678
- const subspaceId = item.item
679
- if (!app.subspaces.some((s) => s.id === subspaceId))
898
+ const targetSubspaceId = item.item
899
+ if (!subspaceIdSet.has(targetSubspaceId))
680
900
  throw new Error(
681
- `Invalid canvas item index: ${index}, subspace not found (canvas: ${canvas.id}, subspace ${subspace.id})`,
901
+ `Invalid canvas item index: ${index}, subspace not found (canvas: ${canvasId}, subspace ${subspaceId})`,
682
902
  )
683
- itemPayload = { subspace_id: subspaceId }
903
+ itemPayload = { subspace_id: targetSubspaceId }
684
904
  }
685
905
  return {
686
906
  type: typeof item.item === 'function' ? 'brick' : 'subspace',
@@ -692,11 +912,19 @@ export const compile = async (app: Application) => {
692
912
  }
693
913
  return map
694
914
  }, {}),
695
- generator_map: subspace.generators.reduce((map, generator) => {
696
- map[generator.id] = {
915
+ generator_map: subspace.generators.reduce((map, generator, generatorIndex) => {
916
+ const generatorId = assertEntryId(
917
+ generator.id,
918
+ 'GENERATOR',
919
+ `(generator index: ${generatorIndex}, subspace: ${subspaceId})`,
920
+ )
921
+
922
+ map[generatorId] = {
697
923
  template_key: generator.templateKey,
924
+ alias: generator.alias,
698
925
  title: generator.title,
699
926
  description: generator.description,
927
+ hide_short_ref: generator.hideShortRef,
700
928
  local_sync: generator.localSyncRunMode
701
929
  ? {
702
930
  run_mode: generator.localSyncRunMode,
@@ -704,19 +932,25 @@ export const compile = async (app: Application) => {
704
932
  : undefined,
705
933
  property: compileProperty(
706
934
  generator.property || {},
707
- `(generator: ${generator.id}, subspace ${subspace.id})`,
935
+ `(generator: ${generatorId}, subspace ${subspaceId})`,
708
936
  ),
709
937
  event_map: compileEvents(generator.templateKey, generator.events || {}, {
710
938
  camelCase: false,
711
- errorReference: `(generator: ${generator.id}, subspace ${subspace.id})`,
939
+ errorReference: `(generator: ${generatorId}, subspace ${subspaceId})`,
712
940
  }),
713
941
  outlet: compileOutlets(
714
942
  generator.templateKey,
715
943
  generator.outlets || {},
716
- `(generator: ${generator.id}, subspace ${subspace.id})`,
944
+ `(generator: ${generatorId}, subspace ${subspaceId})`,
717
945
  ),
718
- state_group: generator.switches?.reduce((acc, switchCase) => {
719
- acc[switchCase.id] = {
946
+ state_group: generator.switches?.reduce((acc, switchCase, switchIndex) => {
947
+ const switchId = assertEntryId(
948
+ switchCase.id,
949
+ 'BRICK_STATE_GROUP',
950
+ `(generator: ${generatorId}, switch index: ${switchIndex}, subspace ${subspaceId})`,
951
+ )
952
+
953
+ acc[switchId] = {
720
954
  title: switchCase.title,
721
955
  description: switchCase.description,
722
956
  break: switchCase.break,
@@ -725,25 +959,25 @@ export const compile = async (app: Application) => {
725
959
  conds: compileSwitchConds(
726
960
  generator.templateKey,
727
961
  switchCase.conds || [],
728
- `(generator: ${generator.id}, switch: ${switchCase.id}, subspace ${subspace.id})`,
962
+ `(generator: ${generatorId}, switch: ${switchId}, subspace ${subspaceId})`,
729
963
  ),
730
964
  property: compileProperty(
731
965
  switchCase.property,
732
- `(generator: ${generator.id}, switch: ${switchCase.id}, subspace ${subspace.id})`,
966
+ `(generator: ${generatorId}, switch: ${switchId}, subspace ${subspaceId})`,
733
967
  ),
734
968
  outlet: compileOutlets(
735
969
  generator.templateKey,
736
970
  switchCase.outlets || {},
737
- `(generator: ${generator.id}, switch: ${switchCase.id}, subspace ${subspace.id})`,
971
+ `(generator: ${generatorId}, switch: ${switchId}, subspace ${subspaceId})`,
738
972
  ),
739
973
  event_map: compileEvents(generator.templateKey, switchCase.events || {}, {
740
974
  camelCase: false,
741
- errorReference: `(generator: ${generator.id}, switch: ${switchCase.id}, subspace ${subspace.id})`,
975
+ errorReference: `(generator: ${generatorId}, switch: ${switchId}, subspace ${subspaceId})`,
742
976
  }),
743
977
  animation: compileAnimations(
744
978
  generator.templateKey,
745
979
  switchCase.animation || {},
746
- `(generator: ${generator.id}, switch: ${switchCase.id}, subspace ${subspace.id})`,
980
+ `(generator: ${generatorId}, switch: ${switchId}, subspace ${subspaceId})`,
747
981
  ),
748
982
  }
749
983
  return acc
@@ -751,10 +985,18 @@ export const compile = async (app: Application) => {
751
985
  }
752
986
  return map
753
987
  }, {}),
754
- property_bank_map: subspace.data.reduce((map, data) => {
755
- map[data.id] = {
988
+ property_bank_map: subspace.data.reduce((map, data, dataIndex) => {
989
+ const dataId = assertEntryId(
990
+ data.id,
991
+ 'PROPERTY_BANK_DATA_NODE',
992
+ `(data index: ${dataIndex}, subspace: ${subspaceId})`,
993
+ )
994
+
995
+ map[dataId] = {
996
+ alias: data.alias,
756
997
  title: data.title,
757
998
  description: data.description,
999
+ hide_short_ref: data.hideShortRef,
758
1000
  linked: data.metadata?.linked,
759
1001
  linkedFrom: data.metadata?.linkedFrom,
760
1002
  local_sync: data.localSyncUpdateMode
@@ -768,35 +1010,72 @@ export const compile = async (app: Application) => {
768
1010
  schema: data.schema,
769
1011
  type: data.type,
770
1012
  ...compileKind(data.kind),
771
- value: compileProperty(data.value, `(data: ${data.id}, subspace ${subspace.id})`),
1013
+ value: compileProperty(data.value, `(data: ${dataId}, subspace ${subspaceId})`),
772
1014
  event_map: compileEvents('PROPERTY_BANK', data.events || {}, {
773
1015
  camelCase: false,
774
- errorReference: `(data: ${data.id}, subspace ${subspace.id})`,
1016
+ errorReference: `(data: ${dataId}, subspace ${subspaceId})`,
775
1017
  }),
776
1018
  hit_equal: data.hit_equal,
777
1019
  hit_regex: data.hit_regex,
778
1020
  }
779
1021
  return map
780
1022
  }, {}),
781
- property_bank_calc_map: subspace.dataCalculation.reduce((map, dataCalc) => {
1023
+ property_bank_calc_map: subspace.dataCalculation.reduce((map, dataCalc, dataCalcIndex) => {
1024
+ const dataCalcId = assertEntryId(
1025
+ dataCalc.id,
1026
+ 'PROPERTY_BANK_COMMAND_MAP',
1027
+ `(data calc index: ${dataCalcIndex}, subspace: ${subspaceId})`,
1028
+ )
1029
+
782
1030
  const calc: any = {
783
1031
  title: dataCalc.title,
784
1032
  description: dataCalc.description,
1033
+ hide_short_ref: dataCalc.hideShortRef,
785
1034
  }
786
1035
  if (dataCalc.triggerMode) calc.trigger_type = dataCalc.triggerMode
787
1036
  if (dataCalc.__typename === 'DataCalculationMap') {
788
1037
  calc.type = 'general'
789
1038
  const mapCalc = dataCalc as DataCalculationMap
790
1039
 
1040
+ const getNodeId = (
1041
+ node: DataCalculationData | DataCommand,
1042
+ reference = '',
1043
+ nodeIndex?: number,
1044
+ ) => {
1045
+ const nodeReference = [
1046
+ `data calc: ${dataCalcId}`,
1047
+ `subspace: ${subspaceId}`,
1048
+ typeof nodeIndex === 'number' ? `node index: ${nodeIndex}` : undefined,
1049
+ reference || undefined,
1050
+ ]
1051
+ .filter(Boolean)
1052
+ .join(', ')
1053
+
1054
+ if (node.__typename === 'DataCalculationData') {
1055
+ return assertEntryId(
1056
+ node.data()?.id,
1057
+ 'PROPERTY_BANK_DATA_NODE',
1058
+ `(${nodeReference})`,
1059
+ )
1060
+ }
1061
+ if (node.__typename === 'DataCommand') {
1062
+ return assertEntryId(node.id, 'PROPERTY_BANK_COMMAND_NODE', `(${nodeReference})`)
1063
+ }
1064
+ throw new Error(`Invalid node: ${JSON.stringify(node)}`)
1065
+ }
1066
+
791
1067
  const generateInputPorts = (inputs) =>
792
- inputs.reduce((acc, port) => {
1068
+ inputs.reduce((acc, port, portIndex) => {
793
1069
  if (!acc[port.key]) acc[port.key] = null
794
1070
 
795
1071
  let sourceId
796
1072
  const sourceNode = port.source()
797
- if (sourceNode?.__typename === 'DataCalculationData')
798
- sourceId = (sourceNode as DataCalculationData).data().id
799
- if (sourceNode?.__typename === 'DataCommand') sourceId = sourceNode.id
1073
+ if (
1074
+ sourceNode?.__typename === 'DataCalculationData' ||
1075
+ sourceNode?.__typename === 'DataCommand'
1076
+ ) {
1077
+ sourceId = getNodeId(sourceNode, `input port index: ${portIndex}`)
1078
+ }
800
1079
 
801
1080
  if (!sourceId) return acc
802
1081
  if (!acc[port.key]) acc[port.key] = []
@@ -810,14 +1089,17 @@ export const compile = async (app: Application) => {
810
1089
  }, {})
811
1090
 
812
1091
  const generateOutputPorts = (outputs) =>
813
- outputs.reduce((acc, port) => {
1092
+ outputs.reduce((acc, port, portIndex) => {
814
1093
  if (!acc[port.key]) acc[port.key] = null
815
1094
 
816
1095
  let targetId
817
1096
  const targetNode = port.target()
818
- if (targetNode?.__typename === 'DataCalculationData')
819
- targetId = (targetNode as DataCalculationData).data().id
820
- if (targetNode?.__typename === 'DataCommand') targetId = targetNode.id
1097
+ if (
1098
+ targetNode?.__typename === 'DataCalculationData' ||
1099
+ targetNode?.__typename === 'DataCommand'
1100
+ ) {
1101
+ targetId = getNodeId(targetNode, `output port index: ${portIndex}`)
1102
+ }
821
1103
 
822
1104
  if (!targetId) return acc
823
1105
  if (!acc[port.key]) acc[port.key] = []
@@ -829,18 +1111,13 @@ export const compile = async (app: Application) => {
829
1111
  return acc
830
1112
  }, {})
831
1113
 
832
- const getNodeId = (node) => {
833
- if (node.__typename === 'DataCalculationData') return node.data().id
834
- if (node.__typename === 'DataCommand') return node.id
835
- throw new Error(`Invalid node: ${JSON.stringify(node)}`)
836
- }
837
-
838
- calc.map = mapCalc.nodes.reduce((acc, node) => {
1114
+ calc.map = mapCalc.nodes.reduce((acc, node, nodeIndex) => {
839
1115
  if (node.__typename === 'DataCalculationData') {
840
1116
  const dataNode = node as DataCalculationData
841
- acc[getNodeId(dataNode)] = {
1117
+ acc[getNodeId(dataNode, 'data node', nodeIndex)] = {
842
1118
  title: dataNode.title,
843
1119
  description: dataNode.description,
1120
+ hide_short_ref: dataNode.hideShortRef,
844
1121
  type: 'data-node',
845
1122
  properties: {},
846
1123
  in: generateInputPorts(dataNode.inputs),
@@ -863,9 +1140,10 @@ export const compile = async (app: Application) => {
863
1140
  (input.source()?.__typename === 'DataCalculationData' ||
864
1141
  input.source()?.__typename === 'DataCommand'),
865
1142
  )
866
- acc[getNodeId(commandNode)] = {
1143
+ acc[getNodeId(commandNode, 'command node', nodeIndex)] = {
867
1144
  title: commandNode.title,
868
1145
  description: commandNode.description,
1146
+ hide_short_ref: commandNode.hideShortRef,
869
1147
  type: `command-node-${type}`,
870
1148
  properties: {
871
1149
  command: commandNode.__commandName,
@@ -881,11 +1159,11 @@ export const compile = async (app: Application) => {
881
1159
  return acc
882
1160
  }, {})
883
1161
  calc.editor_info = mapCalc.editorInfo.reduce((acc, editorInfo) => {
884
- acc[getNodeId(editorInfo.node)] = {
1162
+ acc[getNodeId(editorInfo.node, 'editor info node')] = {
885
1163
  position: editorInfo.position,
886
1164
  points: editorInfo.points.reduce((pointsAcc, point) => {
887
- const sourceId = getNodeId(point.source)
888
- const targetId = getNodeId(point.target)
1165
+ const sourceId = getNodeId(point.source, 'editor info point source')
1166
+ const targetId = getNodeId(point.target, 'editor info point target')
889
1167
  const key = `${sourceId}-${point.sourceOutputKey}-${targetId}-${point.targetInputKey}`
890
1168
  pointsAcc[key] = point.positions
891
1169
  return pointsAcc
@@ -897,43 +1175,55 @@ export const compile = async (app: Application) => {
897
1175
  const scriptCalc = dataCalc as DataCalculationScript
898
1176
  calc.type = 'script'
899
1177
 
900
- let code: string
901
- try {
902
- const program = parseAST(scriptCalc.code, { sourceType: 'module', ecmaVersion: 2020 })
903
- // export function main() { ... }
904
- const declarationBody = (
905
- (program.body[0] as ExportNamedDeclaration).declaration as FunctionDeclaration
906
- )?.body
907
- code = escodegen.generate(declarationBody, {
908
- format: {
909
- indent: { style: ' ' },
910
- semicolons: false,
911
- },
912
- comment: true,
913
- })
914
- } catch {
915
- code = scriptCalc.code || ''
916
- }
1178
+ const code = compileScriptCalculationCode(scriptCalc.code)
917
1179
  calc.script_config = {
918
- note: scriptCalc.note,
1180
+ title: scriptCalc.title ?? '',
1181
+ note: scriptCalc.note ?? '',
919
1182
  code,
920
1183
  enable_async: scriptCalc.enableAsync,
921
1184
  trigger_mode: scriptCalc.triggerMode,
922
1185
  inputs: scriptCalc.inputs.reduce((acc, input) => {
923
- acc[input.data().id] = input.key
1186
+ const inputId = assertEntryId(
1187
+ input.data()?.id,
1188
+ 'PROPERTY_BANK_DATA_NODE',
1189
+ `(data calc: ${dataCalcId}, script input: ${input.key}, subspace: ${subspaceId})`,
1190
+ )
1191
+ acc[inputId] = input.key
924
1192
  return acc
925
1193
  }, {}),
926
1194
  disabled_triggers: scriptCalc.inputs.reduce((acc, input) => {
927
- acc[input.data().id] = !input.trigger
1195
+ const inputId = assertEntryId(
1196
+ input.data()?.id,
1197
+ 'PROPERTY_BANK_DATA_NODE',
1198
+ `(data calc: ${dataCalcId}, script trigger input: ${input.key}, subspace: ${subspaceId})`,
1199
+ )
1200
+ acc[inputId] = !input.trigger
928
1201
  return acc
929
1202
  }, {}),
930
- output: scriptCalc.output?.().id,
1203
+ output: scriptCalc.output
1204
+ ? assertEntryId(
1205
+ scriptCalc.output()?.id,
1206
+ 'PROPERTY_BANK_DATA_NODE',
1207
+ `(data calc: ${dataCalcId}, script output, subspace: ${subspaceId})`,
1208
+ )
1209
+ : null,
931
1210
  outputs: scriptCalc.outputs.reduce((acc, output) => {
932
1211
  if (!acc[output.key]) acc[output.key] = []
933
- acc[output.key].push(output.data().id)
1212
+ const outputId = assertEntryId(
1213
+ output.data()?.id,
1214
+ 'PROPERTY_BANK_DATA_NODE',
1215
+ `(data calc: ${dataCalcId}, script outputs key: ${output.key}, subspace: ${subspaceId})`,
1216
+ )
1217
+ acc[output.key].push(outputId)
934
1218
  return acc
935
1219
  }, {}),
936
- error: scriptCalc.error?.().id,
1220
+ error: scriptCalc.error
1221
+ ? assertEntryId(
1222
+ scriptCalc.error()?.id,
1223
+ 'PROPERTY_BANK_DATA_NODE',
1224
+ `(data calc: ${dataCalcId}, script error output, subspace: ${subspaceId})`,
1225
+ )
1226
+ : null,
937
1227
  }
938
1228
 
939
1229
  Object.assign(
@@ -943,25 +1233,32 @@ export const compile = async (app: Application) => {
943
1233
  }),
944
1234
  )
945
1235
  }
946
- map[dataCalc.id] = calc
1236
+ map[dataCalcId] = calc
947
1237
  return map
948
1238
  }, {}),
949
1239
  action_map: subspace.actions || undefined,
950
1240
  event_map: compileEvents('', subspace.events || {}, {
951
1241
  camelCase: false,
952
- errorReference: `(subspace ${subspace.id})`,
1242
+ errorReference: `(subspace ${subspaceId})`,
953
1243
  }),
954
1244
  routing: subspace.dataRouting.reduce((acc, data, index) => {
955
- if (!data?.id)
956
- throw new Error(`Invalid data routing index: ${index} (subspace ${subspace.id})`)
957
- acc[data.id] = { enabled_routing: true }
1245
+ const dataId = assertEntryId(
1246
+ data?.id,
1247
+ 'PROPERTY_BANK_DATA_NODE',
1248
+ `(data routing index: ${index}, subspace ${subspaceId})`,
1249
+ )
1250
+ acc[dataId] = { enabled_routing: true }
958
1251
  return acc
959
1252
  }, {}),
960
1253
  ...compileModule(subspace),
961
1254
  }
962
1255
  return subspaceMap
963
1256
  }, {}),
964
- root_subspace_id: app.rootSubspace.id,
1257
+ root_subspace_id: assertEntryId(
1258
+ app.rootSubspace?.id,
1259
+ 'SUBSPACE',
1260
+ '(application root subspace)',
1261
+ ),
965
1262
  fonts: app.fonts,
966
1263
  ...compileApplicationSettings(app.settings),
967
1264
  // Use typed automationMap if available, otherwise fall back to TEMP metadata
@@ -975,3 +1272,8 @@ export const compile = async (app: Application) => {
975
1272
  }
976
1273
  return config
977
1274
  }
1275
+
1276
+ export const checkConfig = async (configPath: string) => {
1277
+ const { sh } = await import('../tools/_shell')
1278
+ await sh`bricks app check-config ${configPath}`
1279
+ }