@fugood/bricks-project 2.23.2 → 2.23.4

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 (95) hide show
  1. package/compile/index.ts +343 -125
  2. package/package.json +4 -2
  3. package/skills/bricks-project/rules/automations.md +7 -7
  4. package/tools/deploy.ts +39 -10
  5. package/tools/mcp-server.ts +10 -877
  6. package/tools/mcp-tools/compile.ts +91 -0
  7. package/tools/mcp-tools/huggingface.ts +653 -0
  8. package/tools/mcp-tools/icons.ts +60 -0
  9. package/tools/mcp-tools/lottie.ts +102 -0
  10. package/tools/mcp-tools/media.ts +110 -0
  11. package/tools/postinstall.ts +11 -6
  12. package/tools/pull.ts +25 -9
  13. package/tsconfig.json +16 -0
  14. package/types/bricks/Camera.ts +1 -1
  15. package/types/bricks/Chart.ts +1 -1
  16. package/types/bricks/GenerativeMedia.ts +1 -1
  17. package/types/bricks/Icon.ts +1 -1
  18. package/types/bricks/Image.ts +1 -1
  19. package/types/bricks/Items.ts +1 -1
  20. package/types/bricks/Lottie.ts +1 -1
  21. package/types/bricks/Maps.ts +1 -1
  22. package/types/bricks/QrCode.ts +1 -1
  23. package/types/bricks/Rect.ts +1 -1
  24. package/types/bricks/RichText.ts +1 -1
  25. package/types/bricks/Rive.ts +1 -1
  26. package/types/bricks/Slideshow.ts +1 -1
  27. package/types/bricks/Svg.ts +1 -1
  28. package/types/bricks/Text.ts +1 -1
  29. package/types/bricks/TextInput.ts +1 -1
  30. package/types/bricks/Video.ts +1 -1
  31. package/types/bricks/VideoStreaming.ts +1 -1
  32. package/types/bricks/WebRtcStream.ts +1 -1
  33. package/types/bricks/WebView.ts +1 -1
  34. package/types/canvas.ts +2 -2
  35. package/types/common.ts +4 -4
  36. package/types/generators/AlarmClock.ts +1 -1
  37. package/types/generators/Assistant.ts +1 -1
  38. package/types/generators/BleCentral.ts +1 -1
  39. package/types/generators/BlePeripheral.ts +1 -1
  40. package/types/generators/CanvasMap.ts +1 -1
  41. package/types/generators/CastlesPay.ts +1 -1
  42. package/types/generators/DataBank.ts +1 -1
  43. package/types/generators/File.ts +1 -1
  44. package/types/generators/GraphQl.ts +1 -1
  45. package/types/generators/Http.ts +1 -1
  46. package/types/generators/HttpServer.ts +1 -1
  47. package/types/generators/Information.ts +1 -1
  48. package/types/generators/Intent.ts +1 -1
  49. package/types/generators/Iterator.ts +1 -1
  50. package/types/generators/Keyboard.ts +1 -1
  51. package/types/generators/LlmAnthropicCompat.ts +1 -1
  52. package/types/generators/LlmAppleBuiltin.ts +1 -1
  53. package/types/generators/LlmGgml.ts +1 -1
  54. package/types/generators/LlmOnnx.ts +1 -1
  55. package/types/generators/LlmOpenAiCompat.ts +1 -1
  56. package/types/generators/LlmQualcommAiEngine.ts +1 -1
  57. package/types/generators/Mcp.ts +1 -1
  58. package/types/generators/McpServer.ts +1 -1
  59. package/types/generators/MediaFlow.ts +1 -1
  60. package/types/generators/MqttBroker.ts +1 -1
  61. package/types/generators/MqttClient.ts +1 -1
  62. package/types/generators/Question.ts +1 -1
  63. package/types/generators/RealtimeTranscription.ts +1 -1
  64. package/types/generators/RerankerGgml.ts +1 -1
  65. package/types/generators/SerialPort.ts +1 -1
  66. package/types/generators/SoundPlayer.ts +1 -1
  67. package/types/generators/SoundRecorder.ts +1 -1
  68. package/types/generators/SpeechToTextGgml.ts +1 -1
  69. package/types/generators/SpeechToTextOnnx.ts +1 -1
  70. package/types/generators/SpeechToTextPlatform.ts +1 -1
  71. package/types/generators/SqLite.ts +1 -1
  72. package/types/generators/Step.ts +1 -1
  73. package/types/generators/SttAppleBuiltin.ts +1 -1
  74. package/types/generators/Tcp.ts +1 -1
  75. package/types/generators/TcpServer.ts +1 -1
  76. package/types/generators/TextToSpeechAppleBuiltin.ts +1 -1
  77. package/types/generators/TextToSpeechGgml.ts +1 -1
  78. package/types/generators/TextToSpeechOnnx.ts +1 -1
  79. package/types/generators/TextToSpeechOpenAiLike.ts +1 -1
  80. package/types/generators/ThermalPrinter.ts +1 -1
  81. package/types/generators/Tick.ts +1 -1
  82. package/types/generators/Udp.ts +1 -1
  83. package/types/generators/VadGgml.ts +1 -1
  84. package/types/generators/VadOnnx.ts +1 -1
  85. package/types/generators/VadTraditional.ts +1 -1
  86. package/types/generators/VectorStore.ts +1 -1
  87. package/types/generators/Watchdog.ts +1 -1
  88. package/types/generators/WebCrawler.ts +1 -1
  89. package/types/generators/WebRtc.ts +1 -1
  90. package/types/generators/WebSocket.ts +1 -1
  91. package/types/system.ts +1 -1
  92. package/utils/calc.ts +16 -10
  93. package/utils/id.ts +4 -0
  94. package/api/index.ts +0 -1
  95. package/api/instance.ts +0 -213
package/compile/index.ts CHANGED
@@ -34,6 +34,40 @@ import type {
34
34
  TestVariable,
35
35
  } from '../types'
36
36
 
37
+ 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}'
38
+
39
+ const entryIdPatterns = {
40
+ SUBSPACE: new RegExp(`^SUBSPACE_${uuidPattern}$`),
41
+ CANVAS: new RegExp(`^CANVAS_${uuidPattern}$`),
42
+ BRICK: new RegExp(`^BRICK_${uuidPattern}$`),
43
+ GENERATOR: new RegExp(`^(GENERATOR|AUTO_GENERATOR)_${uuidPattern}$`),
44
+ ANIMATION: new RegExp(`^ANIMATION_${uuidPattern}$`),
45
+ PROPERTY_BANK_DATA_NODE: new RegExp(`^PROPERTY_BANK_DATA_NODE_${uuidPattern}$`),
46
+ PROPERTY_BANK_COMMAND_NODE: new RegExp(`^PROPERTY_BANK_COMMAND_NODE_${uuidPattern}$`),
47
+ PROPERTY_BANK_COMMAND_MAP: new RegExp(`^PROPERTY_BANK_COMMAND_MAP_${uuidPattern}$`),
48
+ BRICK_STATE_GROUP: new RegExp(`^BRICK_STATE_GROUP_${uuidPattern}$`),
49
+ TEST: new RegExp(`^TEST_${uuidPattern}$`),
50
+ TEST_CASE: new RegExp(`^TEST_CASE_${uuidPattern}$`),
51
+ TEST_VAR: new RegExp(`^TEST_VAR_${uuidPattern}$`),
52
+ AUTOMATION_MAP: /^AUTOMATION_MAP_.*/,
53
+ } as const
54
+
55
+ type EntryIdPatternKey = keyof typeof entryIdPatterns
56
+
57
+ const assertEntryId = (
58
+ id: unknown,
59
+ patternKey: EntryIdPatternKey,
60
+ errorReference: string = '',
61
+ ): string => {
62
+ const pattern = entryIdPatterns[patternKey]
63
+ if (typeof id !== 'string' || !pattern.test(id)) {
64
+ throw new Error(
65
+ `Invalid ${patternKey} id${errorReference ? ` ${errorReference}` : ''}: ${String(id)}`,
66
+ )
67
+ }
68
+ return id
69
+ }
70
+
37
71
  const compileProperty = (property, errorReference: string, result = {}) => {
38
72
  if (Array.isArray(property)) {
39
73
  return property.map((p) => compileProperty(p, errorReference))
@@ -79,8 +113,9 @@ const compileOutlets = (
79
113
  errorReference: string,
80
114
  ) =>
81
115
  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
116
+ const dataInstance = data?.()
117
+ const dataId = assertEntryId(dataInstance?.id, 'PROPERTY_BANK_DATA_NODE', errorReference)
118
+ acc[convertOutletKey(templateKey, key)] = dataId
84
119
  return acc
85
120
  }, {})
86
121
 
@@ -95,9 +130,9 @@ const compileAnimations = (
95
130
  errorReference: string,
96
131
  ) =>
97
132
  Object.entries(animations).reduce((acc, [key, animation]) => {
98
- if (!animation?.id) throw new Error(`Invalid animation reference ${errorReference}`)
133
+ const animationId = assertEntryId(animation?.id, 'ANIMATION', errorReference)
99
134
  acc[convertEventKey(basicAnimationEvents.includes(key) ? 'BRICK' : templateKey, key)] =
100
- `ANIMATION#${animation.id}`
135
+ `ANIMATION#${animationId}`
101
136
  return acc
102
137
  }, {})
103
138
 
@@ -381,43 +416,87 @@ const compileTestVariable = (variable: TestVariable) => ({
381
416
  */
382
417
  const arrayToIdMap = <T extends { id: string }, R>(
383
418
  items: T[],
384
- transform: (item: T) => R,
385
- ): Record<string, R> => Object.fromEntries(items.map((item) => [item.id, transform(item)]))
419
+ transform: (item: T, index: number) => R,
420
+ options: {
421
+ patternKey: EntryIdPatternKey
422
+ getErrorReference: (item: T, index: number) => string
423
+ },
424
+ ): Record<string, R> =>
425
+ Object.fromEntries(
426
+ items.map((item, index) => {
427
+ const itemId = assertEntryId(
428
+ item.id,
429
+ options.patternKey,
430
+ options.getErrorReference(item, index),
431
+ )
432
+ return [itemId, transform(item, index)]
433
+ }),
434
+ )
386
435
 
387
436
  /**
388
437
  * Compile typed AutomationTest to raw format
389
438
  */
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
- })
439
+ const compileAutomationTest = (
440
+ test: AutomationTest,
441
+ options: { mapId: string; testIndex: number },
442
+ ) => {
443
+ const testId = assertEntryId(
444
+ test.id,
445
+ 'TEST',
446
+ `(automation map: ${options.mapId}, test index: ${options.testIndex})`,
447
+ )
448
+
449
+ return {
450
+ id: testId,
451
+ title: test.title,
452
+ timeout: test.timeout,
453
+ trigger_type: test.trigger_type,
454
+ cron: test.cron,
455
+ skip_trigger_type_check: test.skip_trigger_type_check,
456
+ local_sync: test.local_sync,
457
+ meta: test.meta,
458
+ case_map: arrayToIdMap(test.cases, compileTestCase, {
459
+ patternKey: 'TEST_CASE',
460
+ getErrorReference: (_, index) =>
461
+ `(automation map: ${options.mapId}, test: ${testId}, case index: ${index})`,
462
+ }),
463
+ var_map: arrayToIdMap(test.variables, compileTestVariable, {
464
+ patternKey: 'TEST_VAR',
465
+ getErrorReference: (_, index) =>
466
+ `(automation map: ${options.mapId}, test: ${testId}, variable index: ${index})`,
467
+ }),
468
+ }
469
+ }
402
470
 
403
471
  /**
404
472
  * Compile typed AutomationTestMap to raw format
405
473
  */
406
- const compileAutomationTestMap = (testMap: AutomationTestMap) => ({
407
- title: testMap.title,
408
- createdAt: testMap.createdAt,
409
- map: arrayToIdMap(testMap.tests, compileAutomationTest),
410
- })
474
+ const compileAutomationTestMap = (testMap: AutomationTestMap, mapId: string) => {
475
+ assertEntryId(testMap.id, 'AUTOMATION_MAP', `(automation map id field: ${mapId})`)
476
+
477
+ return {
478
+ title: testMap.title,
479
+ createdAt: testMap.createdAt,
480
+ map: arrayToIdMap(
481
+ testMap.tests,
482
+ (test, index) => compileAutomationTest(test, { mapId, testIndex: index }),
483
+ {
484
+ patternKey: 'TEST',
485
+ getErrorReference: (_, index) => `(automation map: ${mapId}, test index: ${index})`,
486
+ },
487
+ ),
488
+ }
489
+ }
411
490
 
412
491
  /**
413
492
  * Compile typed AutomationMap to raw automation_map format
414
493
  */
415
494
  const compileAutomation = (automationMap: AutomationMap) =>
416
495
  Object.fromEntries(
417
- Object.entries(automationMap).map(([mapId, testMap]) => [
418
- mapId,
419
- compileAutomationTestMap(testMap),
420
- ]),
496
+ Object.entries(automationMap).map(([mapId, testMap]) => {
497
+ const automationMapId = assertEntryId(mapId, 'AUTOMATION_MAP', '(automation map key)')
498
+ return [automationMapId, compileAutomationTestMap(testMap, automationMapId)]
499
+ }),
421
500
  )
422
501
 
423
502
  export const compile = async (app: Application) => {
@@ -425,16 +504,32 @@ export const compile = async (app: Application) => {
425
504
  const timestamp = Date.now()
426
505
  const config = {
427
506
  title: `${app.name || 'Unknown'}(${timestamp})`,
428
- subspace_map: app.subspaces.reduce((subspaceMap, subspace) => {
429
- subspaceMap[subspace.id] = {
507
+ subspace_map: app.subspaces.reduce((subspaceMap, subspace, subspaceIndex) => {
508
+ const subspaceId = assertEntryId(
509
+ subspace.id,
510
+ 'SUBSPACE',
511
+ `(subspace index: ${subspaceIndex})`,
512
+ )
513
+ const rootCanvasId = assertEntryId(
514
+ subspace.rootCanvas?.id,
515
+ 'CANVAS',
516
+ `(subspace: ${subspaceId}, root canvas)`,
517
+ )
518
+
519
+ subspaceMap[subspaceId] = {
430
520
  title: subspace.title,
431
521
  description: subspace.description,
432
522
  _expanded: subspace.unexpanded
433
523
  ? {
434
524
  brick: !subspace.unexpanded.brick,
435
525
  generator: !subspace.unexpanded.generator,
436
- canvas: subspace.unexpanded.canvas?.reduce((acc, canvas) => {
437
- acc[canvas.id] = !canvas?.id
526
+ canvas: subspace.unexpanded.canvas?.reduce((acc, canvas, canvasIndex) => {
527
+ const unexpandedCanvasId = assertEntryId(
528
+ canvas?.id,
529
+ 'CANVAS',
530
+ `(subspace: ${subspaceId}, unexpanded canvas index: ${canvasIndex})`,
531
+ )
532
+ acc[unexpandedCanvasId] = !canvas?.id
438
533
  return acc
439
534
  }, {}),
440
535
  property_bank: !subspace.unexpanded.data,
@@ -451,10 +546,16 @@ export const compile = async (app: Application) => {
451
546
  change_canvas: subspace.localSyncChangeCanvas,
452
547
  }
453
548
  : undefined,
454
- animation_map: subspace.animations.reduce((map, animation) => {
549
+ animation_map: subspace.animations.reduce((map, animation, animationIndex) => {
550
+ const animationId = assertEntryId(
551
+ animation?.id,
552
+ 'ANIMATION',
553
+ `(animation index: ${animationIndex}, subspace: ${subspaceId})`,
554
+ )
555
+
455
556
  if (animation.__typename === 'Animation') {
456
557
  const animationDef = animation as AnimationDef
457
- map[animationDef.id] = {
558
+ map[animationId] = {
458
559
  title: animationDef.title,
459
560
  description: animationDef.description,
460
561
  animationRunType: animationDef.runType,
@@ -462,12 +563,12 @@ export const compile = async (app: Application) => {
462
563
  type: animationTypeMap[animationDef.config.__type],
463
564
  config: compileProperty(
464
565
  omit(animationDef.config, '__type'),
465
- `(animation: ${animation.id}, subspace ${subspace.id})`,
566
+ `(animation: ${animationId}, subspace ${subspaceId})`,
466
567
  ),
467
568
  }
468
569
  } else if (animation.__typename === 'AnimationCompose') {
469
570
  const animationDef = animation as AnimationComposeDef
470
- map[animationDef.id] = {
571
+ map[animationId] = {
471
572
  title: animationDef.title,
472
573
  description: animationDef.description,
473
574
  animationRunType: animationDef.runType,
@@ -476,7 +577,7 @@ export const compile = async (app: Application) => {
476
577
  const innerAnimation = item()
477
578
  if (!innerAnimation?.id)
478
579
  throw new Error(
479
- `Invalid animation index: ${index} (animation: ${innerAnimation.id}, subspace ${subspace.id})`,
580
+ `Invalid animation index: ${index} (animation: ${innerAnimation.id}, subspace ${subspaceId})`,
480
581
  )
481
582
  return { animation_id: innerAnimation.id }
482
583
  }),
@@ -484,10 +585,15 @@ export const compile = async (app: Application) => {
484
585
  }
485
586
  return map
486
587
  }, {}),
487
- brick_map: subspace.bricks.reduce((map, brick) => {
588
+ brick_map: subspace.bricks.reduce((map, brick, brickIndex) => {
589
+ const brickId = assertEntryId(
590
+ brick.id,
591
+ 'BRICK',
592
+ `(brick index: ${brickIndex}, subspace: ${subspaceId})`,
593
+ )
488
594
  const property = compileProperty(
489
595
  brick.property || {},
490
- `(brick: ${brick.id}, subspace ${subspace.id})`,
596
+ `(brick: ${brickId}, subspace ${subspaceId})`,
491
597
  )
492
598
  if (brick.templateKey === 'BRICK_ITEMS') {
493
599
  const brickItems = brick as BrickItems
@@ -499,25 +605,31 @@ export const compile = async (app: Application) => {
499
605
  frame: itemBrick.frame,
500
606
  property: compileProperty(
501
607
  itemBrick.property,
502
- `(brick: ${brick.id}, ${key}: ${index}, subspace ${subspace.id})`,
608
+ `(brick: ${brickId}, ${key}: ${index}, subspace ${subspaceId})`,
503
609
  ),
504
610
  propertyMapping: itemBrick.propertyMapping,
505
611
  animation: compileAnimations(
506
612
  itemBrick.templateKey,
507
613
  itemBrick.animation || {},
508
- `(brick: ${brick.id}, ${key}: ${index}, subspace ${subspace.id})`,
614
+ `(brick: ${brickId}, ${key}: ${index}, subspace ${subspaceId})`,
509
615
  ),
510
616
  outlet: compileOutlets(
511
617
  itemBrick.templateKey,
512
618
  itemBrick.outlets || {},
513
- `(brick: ${brick.id}, ${key}: ${index}, subspace ${subspace.id})`,
619
+ `(brick: ${brickId}, ${key}: ${index}, subspace ${subspaceId})`,
514
620
  ),
515
621
  eventMap: compileEvents(itemBrick.templateKey, itemBrick.eventMap || {}, {
516
622
  camelCase: true,
517
- errorReference: `(brick: ${brick.id}, ${key}: ${index}, subspace ${subspace.id})`,
623
+ errorReference: `(brick: ${brickId}, ${key}: ${index}, subspace ${subspaceId})`,
518
624
  }),
519
- stateGroup: itemBrick.stateGroup.reduce((acc, stateGroup) => {
520
- acc[stateGroup.id] = {
625
+ stateGroup: itemBrick.stateGroup.reduce((acc, stateGroup, stateGroupIndex) => {
626
+ const stateGroupId = assertEntryId(
627
+ stateGroup.id,
628
+ 'BRICK_STATE_GROUP',
629
+ `(brick: ${brickId}, ${key}: ${index}, switch index: ${stateGroupIndex}, subspace ${subspaceId})`,
630
+ )
631
+
632
+ acc[stateGroupId] = {
521
633
  title: stateGroup.title,
522
634
  description: stateGroup.description,
523
635
  override: stateGroup.override,
@@ -526,29 +638,32 @@ export const compile = async (app: Application) => {
526
638
  conds: compileSwitchConds(
527
639
  itemBrick.templateKey,
528
640
  stateGroup.conds || [],
529
- `(brick: ${brick.id}, ${key}: ${index}, switch: ${stateGroup.id}, subspace ${subspace.id})`,
641
+ `(brick: ${brickId}, ${key}: ${index}, switch: ${stateGroupId}, subspace ${subspaceId})`,
530
642
  ),
531
643
  property: compileProperty(
532
644
  stateGroup.property,
533
- `(brick: ${brick.id}, ${key}: ${index}, switch: ${stateGroup.id}, subspace ${subspace.id})`,
645
+ `(brick: ${brickId}, ${key}: ${index}, switch: ${stateGroupId}, subspace ${subspaceId})`,
534
646
  ),
535
647
  animation: compileAnimations(
536
648
  itemBrick.templateKey,
537
649
  stateGroup.animation || {},
538
- `(brick: ${brick.id}, ${key}: ${index}, switch: ${stateGroup.id}, subspace ${subspace.id})`,
650
+ `(brick: ${brickId}, ${key}: ${index}, switch: ${stateGroupId}, subspace ${subspaceId})`,
539
651
  ),
540
652
  outlet: compileOutlets(
541
653
  itemBrick.templateKey,
542
654
  stateGroup.outlets || {},
543
- `(brick: ${brick.id}, ${key}: ${index}, switch: ${stateGroup.id}, subspace ${subspace.id})`,
655
+ `(brick: ${brickId}, ${key}: ${index}, switch: ${stateGroupId}, subspace ${subspaceId})`,
544
656
  ),
545
657
  eventMap: compileEvents(itemBrick.templateKey, stateGroup.eventMap || {}, {
546
658
  camelCase: true,
547
- errorReference: `(brick: ${brick.id}, ${key}: ${index}, switch: ${stateGroup.id}, subspace ${subspace.id})`,
659
+ errorReference: `(brick: ${brickId}, ${key}: ${index}, switch: ${stateGroupId}, subspace ${subspaceId})`,
548
660
  }),
549
661
  }
550
662
  return acc
551
663
  }, {}),
664
+ show: itemBrick.show,
665
+ pressToOpenDetail: itemBrick.pressToOpenDetail,
666
+ pressToBackList: itemBrick.pressToBackList,
552
667
  })
553
668
  if (Array.isArray(brickItems.brickList)) {
554
669
  const brickList = (brickItems.brickList || []).map((item, index) =>
@@ -569,7 +684,7 @@ export const compile = async (app: Application) => {
569
684
  throw new TypeError('Not supported Data for brickList directly')
570
685
  }
571
686
  }
572
- map[brick.id] = {
687
+ map[brickId] = {
573
688
  template_key: brick.templateKey,
574
689
  title: brick.title,
575
690
  description: brick.description,
@@ -577,19 +692,25 @@ export const compile = async (app: Application) => {
577
692
  animation: compileAnimations(
578
693
  brick.templateKey,
579
694
  brick.animation || {},
580
- `(brick: ${brick.id}, subspace ${subspace.id})`,
695
+ `(brick: ${brickId}, subspace ${subspaceId})`,
581
696
  ),
582
697
  event_map: compileEvents(brick.templateKey, brick.events || {}, {
583
698
  camelCase: false,
584
- errorReference: `(brick: ${brick.id}, subspace ${subspace.id})`,
699
+ errorReference: `(brick: ${brickId}, subspace ${subspaceId})`,
585
700
  }),
586
701
  outlet: compileOutlets(
587
702
  brick.templateKey,
588
703
  brick.outlets || {},
589
- `(brick: ${brick.id}, subspace ${subspace.id})`,
704
+ `(brick: ${brickId}, subspace ${subspaceId})`,
590
705
  ),
591
- state_group: brick.switches?.reduce((acc, switchCase) => {
592
- acc[switchCase.id] = {
706
+ state_group: brick.switches?.reduce((acc, switchCase, switchIndex) => {
707
+ const switchId = assertEntryId(
708
+ switchCase.id,
709
+ 'BRICK_STATE_GROUP',
710
+ `(brick: ${brickId}, switch index: ${switchIndex}, subspace ${subspaceId})`,
711
+ )
712
+
713
+ acc[switchId] = {
593
714
  title: switchCase.title,
594
715
  description: switchCase.description,
595
716
  break: switchCase.break,
@@ -598,25 +719,25 @@ export const compile = async (app: Application) => {
598
719
  conds: compileSwitchConds(
599
720
  brick.templateKey,
600
721
  switchCase.conds || [],
601
- `(brick: ${brick.id}, switch: ${switchCase.id}, subspace ${subspace.id})`,
722
+ `(brick: ${brickId}, switch: ${switchId}, subspace ${subspaceId})`,
602
723
  ),
603
724
  property: compileProperty(
604
725
  switchCase.property,
605
- `(brick: ${brick.id}, switch: ${switchCase.id}, subspace ${subspace.id})`,
726
+ `(brick: ${brickId}, switch: ${switchId}, subspace ${subspaceId})`,
606
727
  ),
607
728
  outlet: compileOutlets(
608
729
  brick.templateKey,
609
730
  switchCase.outlets || {},
610
- `(brick: ${brick.id}, switch: ${switchCase.id}, subspace ${subspace.id})`,
731
+ `(brick: ${brickId}, switch: ${switchId}, subspace ${subspaceId})`,
611
732
  ),
612
733
  event_map: compileEvents(brick.templateKey, switchCase.events || {}, {
613
734
  camelCase: false,
614
- errorReference: `(brick: ${brick.id}, switch: ${switchCase.id}, subspace ${subspace.id})`,
735
+ errorReference: `(brick: ${brickId}, switch: ${switchId}, subspace ${subspaceId})`,
615
736
  }),
616
737
  animation: compileAnimations(
617
738
  brick.templateKey,
618
739
  switchCase.animation || {},
619
- `(brick: ${brick.id}, switch: ${switchCase.id}, subspace ${subspace.id})`,
740
+ `(brick: ${brickId}, switch: ${switchId}, subspace ${subspaceId})`,
620
741
  ),
621
742
  }
622
743
  return acc
@@ -624,21 +745,33 @@ export const compile = async (app: Application) => {
624
745
  }
625
746
  return map
626
747
  }, {}),
627
- root_canvas_id: subspace.rootCanvas.id,
628
- canvas_map: subspace.canvases.reduce((map, canvas) => {
629
- map[canvas.id] = {
748
+ root_canvas_id: rootCanvasId,
749
+ canvas_map: subspace.canvases.reduce((map, canvas, canvasIndex) => {
750
+ const canvasId = assertEntryId(
751
+ canvas.id,
752
+ 'CANVAS',
753
+ `(canvas index: ${canvasIndex}, subspace: ${subspaceId})`,
754
+ )
755
+
756
+ map[canvasId] = {
630
757
  title: canvas.title,
631
758
  description: canvas.description,
632
759
  property: compileProperty(
633
760
  canvas.property,
634
- `(canvas: ${canvas.id}, subspace ${subspace.id})`,
761
+ `(canvas: ${canvasId}, subspace ${subspaceId})`,
635
762
  ),
636
763
  event_map: compileEvents('CANVAS', canvas.events || {}, {
637
764
  camelCase: false,
638
- errorReference: `(canvas: ${canvas.id}, subspace ${subspace.id})`,
765
+ errorReference: `(canvas: ${canvasId}, subspace ${subspaceId})`,
639
766
  }),
640
- state_group: canvas.switches?.reduce((acc, switchCase) => {
641
- acc[switchCase.id] = {
767
+ state_group: canvas.switches?.reduce((acc, switchCase, switchIndex) => {
768
+ const switchId = assertEntryId(
769
+ switchCase.id,
770
+ 'BRICK_STATE_GROUP',
771
+ `(canvas: ${canvasId}, switch index: ${switchIndex}, subspace ${subspaceId})`,
772
+ )
773
+
774
+ acc[switchId] = {
642
775
  title: switchCase.title,
643
776
  description: switchCase.description,
644
777
  break: switchCase.break,
@@ -647,20 +780,20 @@ export const compile = async (app: Application) => {
647
780
  conds: compileSwitchConds(
648
781
  'CANVAS',
649
782
  switchCase.conds || [],
650
- `(canvas: ${canvas.id}, switch: ${switchCase.id}, subspace ${subspace.id})`,
783
+ `(canvas: ${canvasId}, switch: ${switchId}, subspace ${subspaceId})`,
651
784
  ),
652
785
  property: compileProperty(
653
786
  switchCase.property,
654
- `(canvas: ${canvas.id}, switch: ${switchCase.id}, subspace ${subspace.id})`,
787
+ `(canvas: ${canvasId}, switch: ${switchId}, subspace ${subspaceId})`,
655
788
  ),
656
789
  event_map: compileEvents('CANVAS', switchCase.events || {}, {
657
790
  camelCase: false,
658
- errorReference: `(canvas: ${canvas.id}, switch: ${switchCase.id}, subspace ${subspace.id})`,
791
+ errorReference: `(canvas: ${canvasId}, switch: ${switchId}, subspace ${subspaceId})`,
659
792
  }),
660
793
  animation: compileAnimations(
661
794
  'CANVAS',
662
795
  switchCase.animation || {},
663
- `(canvas: ${canvas.id}, switch: ${switchCase.id}, subspace ${subspace.id})`,
796
+ `(canvas: ${canvasId}, switch: ${switchId}, subspace ${subspaceId})`,
664
797
  ),
665
798
  }
666
799
  return acc
@@ -671,16 +804,16 @@ export const compile = async (app: Application) => {
671
804
  const brick = (item.item as () => Brick)()
672
805
  if (!brick?.id)
673
806
  throw new Error(
674
- `Invalid canvas item index: ${index}, brick not found (canvas: ${canvas.id}, subspace ${subspace.id})`,
807
+ `Invalid canvas item index: ${index}, brick not found (canvas: ${canvasId}, subspace ${subspaceId})`,
675
808
  )
676
809
  itemPayload = { brick_id: brick.id }
677
810
  } else {
678
- const subspaceId = item.item
679
- if (!app.subspaces.some((s) => s.id === subspaceId))
811
+ const targetSubspaceId = item.item
812
+ if (!app.subspaces.some((s) => s.id === targetSubspaceId))
680
813
  throw new Error(
681
- `Invalid canvas item index: ${index}, subspace not found (canvas: ${canvas.id}, subspace ${subspace.id})`,
814
+ `Invalid canvas item index: ${index}, subspace not found (canvas: ${canvasId}, subspace ${subspaceId})`,
682
815
  )
683
- itemPayload = { subspace_id: subspaceId }
816
+ itemPayload = { subspace_id: targetSubspaceId }
684
817
  }
685
818
  return {
686
819
  type: typeof item.item === 'function' ? 'brick' : 'subspace',
@@ -692,8 +825,14 @@ export const compile = async (app: Application) => {
692
825
  }
693
826
  return map
694
827
  }, {}),
695
- generator_map: subspace.generators.reduce((map, generator) => {
696
- map[generator.id] = {
828
+ generator_map: subspace.generators.reduce((map, generator, generatorIndex) => {
829
+ const generatorId = assertEntryId(
830
+ generator.id,
831
+ 'GENERATOR',
832
+ `(generator index: ${generatorIndex}, subspace: ${subspaceId})`,
833
+ )
834
+
835
+ map[generatorId] = {
697
836
  template_key: generator.templateKey,
698
837
  title: generator.title,
699
838
  description: generator.description,
@@ -704,19 +843,25 @@ export const compile = async (app: Application) => {
704
843
  : undefined,
705
844
  property: compileProperty(
706
845
  generator.property || {},
707
- `(generator: ${generator.id}, subspace ${subspace.id})`,
846
+ `(generator: ${generatorId}, subspace ${subspaceId})`,
708
847
  ),
709
848
  event_map: compileEvents(generator.templateKey, generator.events || {}, {
710
849
  camelCase: false,
711
- errorReference: `(generator: ${generator.id}, subspace ${subspace.id})`,
850
+ errorReference: `(generator: ${generatorId}, subspace ${subspaceId})`,
712
851
  }),
713
852
  outlet: compileOutlets(
714
853
  generator.templateKey,
715
854
  generator.outlets || {},
716
- `(generator: ${generator.id}, subspace ${subspace.id})`,
855
+ `(generator: ${generatorId}, subspace ${subspaceId})`,
717
856
  ),
718
- state_group: generator.switches?.reduce((acc, switchCase) => {
719
- acc[switchCase.id] = {
857
+ state_group: generator.switches?.reduce((acc, switchCase, switchIndex) => {
858
+ const switchId = assertEntryId(
859
+ switchCase.id,
860
+ 'BRICK_STATE_GROUP',
861
+ `(generator: ${generatorId}, switch index: ${switchIndex}, subspace ${subspaceId})`,
862
+ )
863
+
864
+ acc[switchId] = {
720
865
  title: switchCase.title,
721
866
  description: switchCase.description,
722
867
  break: switchCase.break,
@@ -725,25 +870,25 @@ export const compile = async (app: Application) => {
725
870
  conds: compileSwitchConds(
726
871
  generator.templateKey,
727
872
  switchCase.conds || [],
728
- `(generator: ${generator.id}, switch: ${switchCase.id}, subspace ${subspace.id})`,
873
+ `(generator: ${generatorId}, switch: ${switchId}, subspace ${subspaceId})`,
729
874
  ),
730
875
  property: compileProperty(
731
876
  switchCase.property,
732
- `(generator: ${generator.id}, switch: ${switchCase.id}, subspace ${subspace.id})`,
877
+ `(generator: ${generatorId}, switch: ${switchId}, subspace ${subspaceId})`,
733
878
  ),
734
879
  outlet: compileOutlets(
735
880
  generator.templateKey,
736
881
  switchCase.outlets || {},
737
- `(generator: ${generator.id}, switch: ${switchCase.id}, subspace ${subspace.id})`,
882
+ `(generator: ${generatorId}, switch: ${switchId}, subspace ${subspaceId})`,
738
883
  ),
739
884
  event_map: compileEvents(generator.templateKey, switchCase.events || {}, {
740
885
  camelCase: false,
741
- errorReference: `(generator: ${generator.id}, switch: ${switchCase.id}, subspace ${subspace.id})`,
886
+ errorReference: `(generator: ${generatorId}, switch: ${switchId}, subspace ${subspaceId})`,
742
887
  }),
743
888
  animation: compileAnimations(
744
889
  generator.templateKey,
745
890
  switchCase.animation || {},
746
- `(generator: ${generator.id}, switch: ${switchCase.id}, subspace ${subspace.id})`,
891
+ `(generator: ${generatorId}, switch: ${switchId}, subspace ${subspaceId})`,
747
892
  ),
748
893
  }
749
894
  return acc
@@ -751,8 +896,14 @@ export const compile = async (app: Application) => {
751
896
  }
752
897
  return map
753
898
  }, {}),
754
- property_bank_map: subspace.data.reduce((map, data) => {
755
- map[data.id] = {
899
+ property_bank_map: subspace.data.reduce((map, data, dataIndex) => {
900
+ const dataId = assertEntryId(
901
+ data.id,
902
+ 'PROPERTY_BANK_DATA_NODE',
903
+ `(data index: ${dataIndex}, subspace: ${subspaceId})`,
904
+ )
905
+
906
+ map[dataId] = {
756
907
  title: data.title,
757
908
  description: data.description,
758
909
  linked: data.metadata?.linked,
@@ -768,17 +919,23 @@ export const compile = async (app: Application) => {
768
919
  schema: data.schema,
769
920
  type: data.type,
770
921
  ...compileKind(data.kind),
771
- value: compileProperty(data.value, `(data: ${data.id}, subspace ${subspace.id})`),
922
+ value: compileProperty(data.value, `(data: ${dataId}, subspace ${subspaceId})`),
772
923
  event_map: compileEvents('PROPERTY_BANK', data.events || {}, {
773
924
  camelCase: false,
774
- errorReference: `(data: ${data.id}, subspace ${subspace.id})`,
925
+ errorReference: `(data: ${dataId}, subspace ${subspaceId})`,
775
926
  }),
776
927
  hit_equal: data.hit_equal,
777
928
  hit_regex: data.hit_regex,
778
929
  }
779
930
  return map
780
931
  }, {}),
781
- property_bank_calc_map: subspace.dataCalculation.reduce((map, dataCalc) => {
932
+ property_bank_calc_map: subspace.dataCalculation.reduce((map, dataCalc, dataCalcIndex) => {
933
+ const dataCalcId = assertEntryId(
934
+ dataCalc.id,
935
+ 'PROPERTY_BANK_COMMAND_MAP',
936
+ `(data calc index: ${dataCalcIndex}, subspace: ${subspaceId})`,
937
+ )
938
+
782
939
  const calc: any = {
783
940
  title: dataCalc.title,
784
941
  description: dataCalc.description,
@@ -788,15 +945,45 @@ export const compile = async (app: Application) => {
788
945
  calc.type = 'general'
789
946
  const mapCalc = dataCalc as DataCalculationMap
790
947
 
948
+ const getNodeId = (
949
+ node: DataCalculationData | DataCommand,
950
+ reference = '',
951
+ nodeIndex?: number,
952
+ ) => {
953
+ const nodeReference = [
954
+ `data calc: ${dataCalcId}`,
955
+ `subspace: ${subspaceId}`,
956
+ typeof nodeIndex === 'number' ? `node index: ${nodeIndex}` : undefined,
957
+ reference || undefined,
958
+ ]
959
+ .filter(Boolean)
960
+ .join(', ')
961
+
962
+ if (node.__typename === 'DataCalculationData') {
963
+ return assertEntryId(
964
+ node.data()?.id,
965
+ 'PROPERTY_BANK_DATA_NODE',
966
+ `(${nodeReference})`,
967
+ )
968
+ }
969
+ if (node.__typename === 'DataCommand') {
970
+ return assertEntryId(node.id, 'PROPERTY_BANK_COMMAND_NODE', `(${nodeReference})`)
971
+ }
972
+ throw new Error(`Invalid node: ${JSON.stringify(node)}`)
973
+ }
974
+
791
975
  const generateInputPorts = (inputs) =>
792
- inputs.reduce((acc, port) => {
976
+ inputs.reduce((acc, port, portIndex) => {
793
977
  if (!acc[port.key]) acc[port.key] = null
794
978
 
795
979
  let sourceId
796
980
  const sourceNode = port.source()
797
- if (sourceNode?.__typename === 'DataCalculationData')
798
- sourceId = (sourceNode as DataCalculationData).data().id
799
- if (sourceNode?.__typename === 'DataCommand') sourceId = sourceNode.id
981
+ if (
982
+ sourceNode?.__typename === 'DataCalculationData' ||
983
+ sourceNode?.__typename === 'DataCommand'
984
+ ) {
985
+ sourceId = getNodeId(sourceNode, `input port index: ${portIndex}`)
986
+ }
800
987
 
801
988
  if (!sourceId) return acc
802
989
  if (!acc[port.key]) acc[port.key] = []
@@ -810,14 +997,17 @@ export const compile = async (app: Application) => {
810
997
  }, {})
811
998
 
812
999
  const generateOutputPorts = (outputs) =>
813
- outputs.reduce((acc, port) => {
1000
+ outputs.reduce((acc, port, portIndex) => {
814
1001
  if (!acc[port.key]) acc[port.key] = null
815
1002
 
816
1003
  let targetId
817
1004
  const targetNode = port.target()
818
- if (targetNode?.__typename === 'DataCalculationData')
819
- targetId = (targetNode as DataCalculationData).data().id
820
- if (targetNode?.__typename === 'DataCommand') targetId = targetNode.id
1005
+ if (
1006
+ targetNode?.__typename === 'DataCalculationData' ||
1007
+ targetNode?.__typename === 'DataCommand'
1008
+ ) {
1009
+ targetId = getNodeId(targetNode, `output port index: ${portIndex}`)
1010
+ }
821
1011
 
822
1012
  if (!targetId) return acc
823
1013
  if (!acc[port.key]) acc[port.key] = []
@@ -829,16 +1019,10 @@ export const compile = async (app: Application) => {
829
1019
  return acc
830
1020
  }, {})
831
1021
 
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) => {
1022
+ calc.map = mapCalc.nodes.reduce((acc, node, nodeIndex) => {
839
1023
  if (node.__typename === 'DataCalculationData') {
840
1024
  const dataNode = node as DataCalculationData
841
- acc[getNodeId(dataNode)] = {
1025
+ acc[getNodeId(dataNode, 'data node', nodeIndex)] = {
842
1026
  title: dataNode.title,
843
1027
  description: dataNode.description,
844
1028
  type: 'data-node',
@@ -863,7 +1047,7 @@ export const compile = async (app: Application) => {
863
1047
  (input.source()?.__typename === 'DataCalculationData' ||
864
1048
  input.source()?.__typename === 'DataCommand'),
865
1049
  )
866
- acc[getNodeId(commandNode)] = {
1050
+ acc[getNodeId(commandNode, 'command node', nodeIndex)] = {
867
1051
  title: commandNode.title,
868
1052
  description: commandNode.description,
869
1053
  type: `command-node-${type}`,
@@ -881,11 +1065,11 @@ export const compile = async (app: Application) => {
881
1065
  return acc
882
1066
  }, {})
883
1067
  calc.editor_info = mapCalc.editorInfo.reduce((acc, editorInfo) => {
884
- acc[getNodeId(editorInfo.node)] = {
1068
+ acc[getNodeId(editorInfo.node, 'editor info node')] = {
885
1069
  position: editorInfo.position,
886
1070
  points: editorInfo.points.reduce((pointsAcc, point) => {
887
- const sourceId = getNodeId(point.source)
888
- const targetId = getNodeId(point.target)
1071
+ const sourceId = getNodeId(point.source, 'editor info point source')
1072
+ const targetId = getNodeId(point.target, 'editor info point target')
889
1073
  const key = `${sourceId}-${point.sourceOutputKey}-${targetId}-${point.targetInputKey}`
890
1074
  pointsAcc[key] = point.positions
891
1075
  return pointsAcc
@@ -911,7 +1095,7 @@ export const compile = async (app: Application) => {
911
1095
  },
912
1096
  comment: true,
913
1097
  })
914
- } catch (e) {
1098
+ } catch {
915
1099
  code = scriptCalc.code || ''
916
1100
  }
917
1101
  calc.script_config = {
@@ -920,20 +1104,47 @@ export const compile = async (app: Application) => {
920
1104
  enable_async: scriptCalc.enableAsync,
921
1105
  trigger_mode: scriptCalc.triggerMode,
922
1106
  inputs: scriptCalc.inputs.reduce((acc, input) => {
923
- acc[input.data().id] = input.key
1107
+ const inputId = assertEntryId(
1108
+ input.data()?.id,
1109
+ 'PROPERTY_BANK_DATA_NODE',
1110
+ `(data calc: ${dataCalcId}, script input: ${input.key}, subspace: ${subspaceId})`,
1111
+ )
1112
+ acc[inputId] = input.key
924
1113
  return acc
925
1114
  }, {}),
926
1115
  disabled_triggers: scriptCalc.inputs.reduce((acc, input) => {
927
- acc[input.data().id] = !input.trigger
1116
+ const inputId = assertEntryId(
1117
+ input.data()?.id,
1118
+ 'PROPERTY_BANK_DATA_NODE',
1119
+ `(data calc: ${dataCalcId}, script trigger input: ${input.key}, subspace: ${subspaceId})`,
1120
+ )
1121
+ acc[inputId] = !input.trigger
928
1122
  return acc
929
1123
  }, {}),
930
- output: scriptCalc.output?.().id,
1124
+ output: scriptCalc.output
1125
+ ? assertEntryId(
1126
+ scriptCalc.output()?.id,
1127
+ 'PROPERTY_BANK_DATA_NODE',
1128
+ `(data calc: ${dataCalcId}, script output, subspace: ${subspaceId})`,
1129
+ )
1130
+ : undefined,
931
1131
  outputs: scriptCalc.outputs.reduce((acc, output) => {
932
1132
  if (!acc[output.key]) acc[output.key] = []
933
- acc[output.key].push(output.data().id)
1133
+ const outputId = assertEntryId(
1134
+ output.data()?.id,
1135
+ 'PROPERTY_BANK_DATA_NODE',
1136
+ `(data calc: ${dataCalcId}, script outputs key: ${output.key}, subspace: ${subspaceId})`,
1137
+ )
1138
+ acc[output.key].push(outputId)
934
1139
  return acc
935
1140
  }, {}),
936
- error: scriptCalc.error?.().id,
1141
+ error: scriptCalc.error
1142
+ ? assertEntryId(
1143
+ scriptCalc.error()?.id,
1144
+ 'PROPERTY_BANK_DATA_NODE',
1145
+ `(data calc: ${dataCalcId}, script error output, subspace: ${subspaceId})`,
1146
+ )
1147
+ : undefined,
937
1148
  }
938
1149
 
939
1150
  Object.assign(
@@ -943,25 +1154,32 @@ export const compile = async (app: Application) => {
943
1154
  }),
944
1155
  )
945
1156
  }
946
- map[dataCalc.id] = calc
1157
+ map[dataCalcId] = calc
947
1158
  return map
948
1159
  }, {}),
949
1160
  action_map: subspace.actions || undefined,
950
1161
  event_map: compileEvents('', subspace.events || {}, {
951
1162
  camelCase: false,
952
- errorReference: `(subspace ${subspace.id})`,
1163
+ errorReference: `(subspace ${subspaceId})`,
953
1164
  }),
954
1165
  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 }
1166
+ const dataId = assertEntryId(
1167
+ data?.id,
1168
+ 'PROPERTY_BANK_DATA_NODE',
1169
+ `(data routing index: ${index}, subspace ${subspaceId})`,
1170
+ )
1171
+ acc[dataId] = { enabled_routing: true }
958
1172
  return acc
959
1173
  }, {}),
960
1174
  ...compileModule(subspace),
961
1175
  }
962
1176
  return subspaceMap
963
1177
  }, {}),
964
- root_subspace_id: app.rootSubspace.id,
1178
+ root_subspace_id: assertEntryId(
1179
+ app.rootSubspace?.id,
1180
+ 'SUBSPACE',
1181
+ '(application root subspace)',
1182
+ ),
965
1183
  fonts: app.fonts,
966
1184
  ...compileApplicationSettings(app.settings),
967
1185
  // Use typed automationMap if available, otherwise fall back to TEMP metadata