@fugood/bricks-ctor 2.25.0-beta.52 → 2.25.0-beta.55
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.
- package/compile/__tests__/index.test.js +44 -0
- package/compile/index.ts +418 -357
- package/package.json +3 -3
- package/skills/bricks-ctor/references/architecture-patterns.md +1 -0
- package/skills/bricks-ctor/references/data-calculation.md +13 -0
- package/types/generators/HttpServer.d.ts +56 -2
- package/utils/data.ts +1 -1
|
@@ -171,6 +171,50 @@ describe('compile animations', () => {
|
|
|
171
171
|
})
|
|
172
172
|
})
|
|
173
173
|
|
|
174
|
+
describe('compile error collection', () => {
|
|
175
|
+
test('reports every entity error in a single failure instead of bailing on the first', async () => {
|
|
176
|
+
const app = makeApp()
|
|
177
|
+
// Invalid ids across three independent entity loops — a fail-fast compile would only
|
|
178
|
+
// surface the first; collection must report all three in one run.
|
|
179
|
+
app.rootSubspace.bricks = [{ id: 'bad-brick', templateKey: 'BRICK_TEXT' }]
|
|
180
|
+
app.rootSubspace.generators = [{ id: 'bad-generator', templateKey: 'GENERATOR_HTTP' }]
|
|
181
|
+
app.rootSubspace.data = [{ __typename: 'Data', id: 'bad-data', type: 'string' }]
|
|
182
|
+
|
|
183
|
+
let error
|
|
184
|
+
try {
|
|
185
|
+
await compile(app)
|
|
186
|
+
} catch (caught) {
|
|
187
|
+
error = caught
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
expect(error).toBeInstanceOf(Error)
|
|
191
|
+
expect(error.message).toContain('Compile failed with 3 error(s)')
|
|
192
|
+
expect(error.message).toContain('Invalid BRICK id (brick index: 0')
|
|
193
|
+
expect(error.message).toContain('Invalid GENERATOR id (generator index: 0')
|
|
194
|
+
expect(error.message).toContain('Invalid PROPERTY_BANK_DATA_NODE id (data index: 0')
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
test('skipping a bad entity does not block compiling its siblings', async () => {
|
|
198
|
+
const app = makeApp()
|
|
199
|
+
app.rootSubspace.data = [
|
|
200
|
+
{ __typename: 'Data', id: 'bad-data', type: 'string' },
|
|
201
|
+
makeData(undefined), // valid DATA_ID sibling
|
|
202
|
+
]
|
|
203
|
+
|
|
204
|
+
let error
|
|
205
|
+
try {
|
|
206
|
+
await compile(app)
|
|
207
|
+
} catch (caught) {
|
|
208
|
+
error = caught
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Only the bad sibling is reported; the valid one compiled past it in the same pass.
|
|
212
|
+
expect(error.message).toContain('Compile failed with 1 error(s)')
|
|
213
|
+
expect(error.message).toContain('bad-data')
|
|
214
|
+
expect(error.message).not.toContain(DATA_ID)
|
|
215
|
+
})
|
|
216
|
+
})
|
|
217
|
+
|
|
174
218
|
describe('compile event handlers', () => {
|
|
175
219
|
const BRICK_ID = 'BRICK_00000000-0000-0000-0000-000000000002'
|
|
176
220
|
|
package/compile/index.ts
CHANGED
|
@@ -4,7 +4,7 @@ import upperFirst from 'lodash/upperFirst'
|
|
|
4
4
|
import snakeCase from 'lodash/snakeCase'
|
|
5
5
|
import omit from 'lodash/omit'
|
|
6
6
|
import { parse as parseAST } from 'acorn'
|
|
7
|
-
import type { ExportNamedDeclaration, FunctionDeclaration } from 'acorn'
|
|
7
|
+
import type { BlockStatement, ExportNamedDeclaration, FunctionDeclaration } from 'acorn'
|
|
8
8
|
import escodegen from 'escodegen'
|
|
9
9
|
import { makeSeededId } from '../utils/id'
|
|
10
10
|
import { generateCalulationMap } from './util'
|
|
@@ -75,6 +75,31 @@ const assertEntryId = (
|
|
|
75
75
|
return id
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
// Per-compile error collection. Instead of throwing on the first bad entity, record the
|
|
79
|
+
// message and skip that entity, so a single compile surfaces every entity's first error
|
|
80
|
+
// (e.g. an invalid brick id, generator id and data-calc id all in one run). `errors` is
|
|
81
|
+
// local to each compile() call — no shared state — and compile throws an aggregated error
|
|
82
|
+
// at the end when any were collected, so a failed compile still rejects and never returns
|
|
83
|
+
// or writes a partial config.
|
|
84
|
+
const collect = <T>(errors: string[], fn: () => T, fallback: T): T => {
|
|
85
|
+
try {
|
|
86
|
+
return fn()
|
|
87
|
+
} catch (error) {
|
|
88
|
+
errors.push(error instanceof Error ? error.message : String(error))
|
|
89
|
+
return fallback
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// reduce() into an id-keyed map, recording a per-item compile error and skipping that item
|
|
94
|
+
// instead of aborting the whole compile. The callback is the last argument (and the init is
|
|
95
|
+
// always {}) so the formatter keeps its body inline rather than re-indenting it.
|
|
96
|
+
const collectReduce = <T>(
|
|
97
|
+
errors: string[],
|
|
98
|
+
items: T[],
|
|
99
|
+
fn: (acc: Record<string, unknown>, item: T, index: number) => Record<string, unknown>,
|
|
100
|
+
): Record<string, unknown> =>
|
|
101
|
+
items.reduce((acc, item, index) => collect(errors, () => fn(acc, item, index), acc), {})
|
|
102
|
+
|
|
78
103
|
const compileProperty = (property, errorReference: string, result = {}) => {
|
|
79
104
|
if (Array.isArray(property)) {
|
|
80
105
|
return property.map((p) => compileProperty(p, errorReference))
|
|
@@ -101,17 +126,28 @@ const compileProperty = (property, errorReference: string, result = {}) => {
|
|
|
101
126
|
const compileScriptCalculationCode = (code = '') => {
|
|
102
127
|
try {
|
|
103
128
|
const program = parseAST(code, { sourceType: 'module', ecmaVersion: 2020 })
|
|
104
|
-
//
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
129
|
+
// The stored config holds the bare function body, which codegen re-wraps as
|
|
130
|
+
// `export function main() { <code> }`. Unwrap it back to that body here.
|
|
131
|
+
let block = ((program.body[0] as ExportNamedDeclaration).declaration as FunctionDeclaration)
|
|
132
|
+
?.body as BlockStatement | undefined
|
|
133
|
+
if (!block) return code || ''
|
|
134
|
+
// Earlier versions emitted the whole BlockStatement (braces included), so every
|
|
135
|
+
// compile -> codegen round-trip nested the body in one more `{ }`. Emit the inner
|
|
136
|
+
// statements instead, collapsing any wrapper blocks previous round-trips added so
|
|
137
|
+
// existing over-wrapped sandboxes heal on the next compile.
|
|
138
|
+
while (block.body.length === 1 && block.body[0].type === 'BlockStatement') {
|
|
139
|
+
block = block.body[0] as BlockStatement
|
|
140
|
+
}
|
|
141
|
+
return escodegen.generate(
|
|
142
|
+
{ type: 'Program', body: block.body },
|
|
143
|
+
{
|
|
144
|
+
format: {
|
|
145
|
+
indent: { style: ' ' },
|
|
146
|
+
semicolons: false,
|
|
147
|
+
},
|
|
148
|
+
comment: true,
|
|
112
149
|
},
|
|
113
|
-
|
|
114
|
-
})
|
|
150
|
+
)
|
|
115
151
|
} catch {
|
|
116
152
|
return code || ''
|
|
117
153
|
}
|
|
@@ -744,13 +780,20 @@ const recordConfigChange = async (previousConfig: unknown, config: unknown) => {
|
|
|
744
780
|
|
|
745
781
|
export const compile = async (app: Application) => {
|
|
746
782
|
await new Promise((resolve) => setImmediate(resolve, 0))
|
|
783
|
+
// Collected entity-level compile errors (see collect/collectReduce). Aggregated and
|
|
784
|
+
// thrown at the end so one compile reports every entity's first error.
|
|
785
|
+
const errors: string[] = []
|
|
747
786
|
// Snapshot the prior build artifact before the caller's compile.ts overwrites it, so
|
|
748
787
|
// the config change introduced by this compile can be recorded on return.
|
|
749
788
|
const previousConfig = await readBuildConfig(process.cwd())
|
|
750
789
|
const timestamp = Date.now()
|
|
751
790
|
// Pre-index subspace ids so the canvas-item validation below stays O(1).
|
|
752
791
|
const subspaceIdSet = new Set(app.subspaces.map((s) => s.id))
|
|
753
|
-
|
|
792
|
+
let compiledAutomationMap: ReturnType<typeof compileAutomation> | null = null
|
|
793
|
+
if (app.automationMap) {
|
|
794
|
+
const { automationMap } = app
|
|
795
|
+
compiledAutomationMap = collect(errors, () => compileAutomation(automationMap), null)
|
|
796
|
+
}
|
|
754
797
|
const config = {
|
|
755
798
|
title: `${app.name || 'Unknown'}(${timestamp})`,
|
|
756
799
|
subspace_map: app.subspaces.reduce((subspaceMap, subspace, subspaceIndex) => {
|
|
@@ -836,74 +879,78 @@ export const compile = async (app: Application) => {
|
|
|
836
879
|
change_canvas: subspace.localSyncChangeCanvas,
|
|
837
880
|
}
|
|
838
881
|
: undefined,
|
|
839
|
-
animation_map:
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
882
|
+
animation_map: collectReduce(
|
|
883
|
+
errors,
|
|
884
|
+
subspace.animations,
|
|
885
|
+
(map, animation, animationIndex) => {
|
|
886
|
+
const animationId = assertEntryId(
|
|
887
|
+
animation?.id,
|
|
888
|
+
'ANIMATION',
|
|
889
|
+
`(animation index: ${animationIndex}, subspace: ${subspaceId})`,
|
|
890
|
+
)
|
|
845
891
|
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
892
|
+
const animationTypename = animation.__typename
|
|
893
|
+
if (animationTypename === 'Animation') {
|
|
894
|
+
const animationDef = animation as AnimationDef
|
|
895
|
+
const animationErrorReference = `(animation: ${animationId}, subspace ${subspaceId})`
|
|
896
|
+
const animationWarningMetadata = {
|
|
897
|
+
animationIndex,
|
|
898
|
+
animationTitle: animationDef.title,
|
|
899
|
+
animationAlias: animationDef.alias,
|
|
900
|
+
animationProperty: animationDef.property,
|
|
901
|
+
subspaceIndex,
|
|
902
|
+
subspaceTitle: subspace.title,
|
|
903
|
+
}
|
|
904
|
+
const animationType = getAnimationType(animationDef.config, animationErrorReference)
|
|
905
|
+
map[animationId] = {
|
|
906
|
+
alias: animationDef.alias,
|
|
907
|
+
title: animationDef.title,
|
|
908
|
+
description: animationDef.description,
|
|
909
|
+
hide_short_ref: animationDef.hideShortRef,
|
|
910
|
+
animationRunType: animationDef.runType,
|
|
911
|
+
property: assertAnimationProperty(animationDef.property, animationErrorReference),
|
|
912
|
+
type: animationType,
|
|
913
|
+
config: compileAnimationConfig(
|
|
914
|
+
animationType,
|
|
915
|
+
animationDef.config,
|
|
916
|
+
animationErrorReference,
|
|
917
|
+
animationWarningMetadata,
|
|
918
|
+
),
|
|
919
|
+
}
|
|
920
|
+
} else if (animationTypename === 'AnimationCompose') {
|
|
921
|
+
const animationDef = animation as AnimationComposeDef
|
|
922
|
+
const animationErrorReference = `(animation: ${animationId}, subspace ${subspaceId})`
|
|
923
|
+
map[animationId] = {
|
|
924
|
+
alias: animationDef.alias,
|
|
925
|
+
title: animationDef.title,
|
|
926
|
+
description: animationDef.description,
|
|
927
|
+
hide_short_ref: animationDef.hideShortRef,
|
|
928
|
+
animationRunType: animationDef.runType,
|
|
929
|
+
compose_type: assertAnimationComposeType(
|
|
930
|
+
animationDef.composeType,
|
|
931
|
+
animationErrorReference,
|
|
932
|
+
),
|
|
933
|
+
item_list: animationDef.items.map((item, index) => {
|
|
934
|
+
const innerAnimation = item()
|
|
935
|
+
const innerAnimationId = assertEntryId(
|
|
936
|
+
innerAnimation?.id,
|
|
937
|
+
'ANIMATION',
|
|
938
|
+
`(animation item index: ${index}, animation: ${animationId}, subspace ${subspaceId})`,
|
|
939
|
+
)
|
|
940
|
+
return { animation_id: innerAnimationId }
|
|
941
|
+
}),
|
|
942
|
+
}
|
|
943
|
+
} else {
|
|
944
|
+
throw new Error(
|
|
945
|
+
`Invalid animation typename (animation: ${animationId}, subspace ${subspaceId}): ${String(
|
|
946
|
+
animationTypename,
|
|
947
|
+
)}`,
|
|
948
|
+
)
|
|
896
949
|
}
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
)}`,
|
|
902
|
-
)
|
|
903
|
-
}
|
|
904
|
-
return map
|
|
905
|
-
}, {}),
|
|
906
|
-
brick_map: subspace.bricks.reduce((map, brick, brickIndex) => {
|
|
950
|
+
return map
|
|
951
|
+
},
|
|
952
|
+
),
|
|
953
|
+
brick_map: collectReduce(errors, subspace.bricks, (map, brick, brickIndex) => {
|
|
907
954
|
const brickId = assertEntryId(
|
|
908
955
|
brick.id,
|
|
909
956
|
'BRICK',
|
|
@@ -1068,9 +1115,9 @@ export const compile = async (app: Application) => {
|
|
|
1068
1115
|
}, {}),
|
|
1069
1116
|
}
|
|
1070
1117
|
return map
|
|
1071
|
-
}
|
|
1118
|
+
}),
|
|
1072
1119
|
root_canvas_id: rootCanvasId,
|
|
1073
|
-
canvas_map: subspace.canvases
|
|
1120
|
+
canvas_map: collectReduce(errors, subspace.canvases, (map, canvas, canvasIndex) => {
|
|
1074
1121
|
const canvasId = assertEntryId(
|
|
1075
1122
|
canvas.id,
|
|
1076
1123
|
'CANVAS',
|
|
@@ -1150,81 +1197,85 @@ export const compile = async (app: Application) => {
|
|
|
1150
1197
|
}),
|
|
1151
1198
|
}
|
|
1152
1199
|
return map
|
|
1153
|
-
}
|
|
1154
|
-
generator_map:
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1200
|
+
}),
|
|
1201
|
+
generator_map: collectReduce(
|
|
1202
|
+
errors,
|
|
1203
|
+
subspace.generators,
|
|
1204
|
+
(map, generator, generatorIndex) => {
|
|
1205
|
+
const generatorId = assertEntryId(
|
|
1206
|
+
generator.id,
|
|
1207
|
+
'GENERATOR',
|
|
1208
|
+
`(generator index: ${generatorIndex}, subspace: ${subspaceId})`,
|
|
1209
|
+
)
|
|
1160
1210
|
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1211
|
+
map[generatorId] = {
|
|
1212
|
+
template_key: generator.templateKey,
|
|
1213
|
+
alias: generator.alias,
|
|
1214
|
+
title: generator.title,
|
|
1215
|
+
description: generator.description,
|
|
1216
|
+
hide_short_ref: generator.hideShortRef,
|
|
1217
|
+
local_sync: generator.localSyncRunMode
|
|
1218
|
+
? {
|
|
1219
|
+
run_mode: generator.localSyncRunMode,
|
|
1220
|
+
}
|
|
1221
|
+
: undefined,
|
|
1222
|
+
property: compileProperty(
|
|
1223
|
+
generator.property || {},
|
|
1224
|
+
`(generator: ${generatorId}, subspace ${subspaceId})`,
|
|
1225
|
+
),
|
|
1226
|
+
event_map: compileEvents(generator.templateKey, generator.events || {}, {
|
|
1227
|
+
camelCase: false,
|
|
1228
|
+
errorReference: `(generator: ${generatorId}, subspace ${subspaceId})`,
|
|
1229
|
+
}),
|
|
1230
|
+
outlet: compileOutlets(
|
|
1231
|
+
generator.templateKey,
|
|
1232
|
+
generator.outlets || {},
|
|
1233
|
+
`(generator: ${generatorId}, subspace ${subspaceId})`,
|
|
1234
|
+
),
|
|
1235
|
+
state_group: generator.switches?.reduce((acc, switchCase, switchIndex) => {
|
|
1236
|
+
const switchId = assertEntryId(
|
|
1237
|
+
switchCase.id,
|
|
1238
|
+
'BRICK_STATE_GROUP',
|
|
1239
|
+
`(generator: ${generatorId}, switch index: ${switchIndex}, subspace ${subspaceId})`,
|
|
1240
|
+
)
|
|
1191
1241
|
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1242
|
+
acc[switchId] = {
|
|
1243
|
+
title: switchCase.title,
|
|
1244
|
+
description: switchCase.description,
|
|
1245
|
+
break: switchCase.break,
|
|
1246
|
+
override: switchCase.override,
|
|
1247
|
+
commented: switchCase.disabled,
|
|
1248
|
+
conds: compileSwitchConds(
|
|
1249
|
+
generator.templateKey,
|
|
1250
|
+
switchCase.conds || [],
|
|
1251
|
+
`(generator: ${generatorId}, switch: ${switchId}, subspace ${subspaceId})`,
|
|
1252
|
+
),
|
|
1253
|
+
property: compileProperty(
|
|
1254
|
+
switchCase.property,
|
|
1255
|
+
`(generator: ${generatorId}, switch: ${switchId}, subspace ${subspaceId})`,
|
|
1256
|
+
),
|
|
1257
|
+
outlet: compileOutlets(
|
|
1258
|
+
generator.templateKey,
|
|
1259
|
+
switchCase.outlets || {},
|
|
1260
|
+
`(generator: ${generatorId}, switch: ${switchId}, subspace ${subspaceId})`,
|
|
1261
|
+
),
|
|
1262
|
+
event_map: compileEvents(generator.templateKey, switchCase.events || {}, {
|
|
1263
|
+
camelCase: false,
|
|
1264
|
+
errorReference: `(generator: ${generatorId}, switch: ${switchId}, subspace ${subspaceId})`,
|
|
1265
|
+
}),
|
|
1266
|
+
animation: compileAnimations(
|
|
1267
|
+
generator.templateKey,
|
|
1268
|
+
switchCase.animation || {},
|
|
1269
|
+
`(generator: ${generatorId}, switch: ${switchId}, subspace ${subspaceId})`,
|
|
1270
|
+
),
|
|
1271
|
+
}
|
|
1272
|
+
return acc
|
|
1273
|
+
}, {}),
|
|
1274
|
+
}
|
|
1275
|
+
return map
|
|
1276
|
+
},
|
|
1277
|
+
),
|
|
1278
|
+
property_bank_map: collectReduce(errors, subspace.data, (map, data, dataIndex) => {
|
|
1228
1279
|
const dataId = assertEntryId(
|
|
1229
1280
|
data.id,
|
|
1230
1281
|
'PROPERTY_BANK_DATA_NODE',
|
|
@@ -1258,225 +1309,229 @@ export const compile = async (app: Application) => {
|
|
|
1258
1309
|
hit_regex: data.hit_regex,
|
|
1259
1310
|
}
|
|
1260
1311
|
return map
|
|
1261
|
-
}
|
|
1262
|
-
property_bank_calc_map:
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1312
|
+
}),
|
|
1313
|
+
property_bank_calc_map: collectReduce(
|
|
1314
|
+
errors,
|
|
1315
|
+
subspace.dataCalculation,
|
|
1316
|
+
(map, dataCalc, dataCalcIndex) => {
|
|
1317
|
+
const dataCalcId = assertEntryId(
|
|
1318
|
+
dataCalc.id,
|
|
1319
|
+
'PROPERTY_BANK_COMMAND_MAP',
|
|
1320
|
+
`(data calc index: ${dataCalcIndex}, subspace: ${subspaceId})`,
|
|
1321
|
+
)
|
|
1268
1322
|
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
}
|
|
1275
|
-
if (dataCalc.triggerMode) calc.trigger_type = dataCalc.triggerMode
|
|
1276
|
-
if (dataCalc.__typename === 'DataCalculationMap') {
|
|
1277
|
-
calc.type = 'general'
|
|
1278
|
-
const mapCalc = dataCalc as DataCalculationMap
|
|
1279
|
-
|
|
1280
|
-
const getNodeId = (
|
|
1281
|
-
node: DataCalculationData | DataCommand,
|
|
1282
|
-
reference = '',
|
|
1283
|
-
nodeIndex?: number,
|
|
1284
|
-
) => {
|
|
1285
|
-
const nodeReference = [
|
|
1286
|
-
`data calc: ${dataCalcId}`,
|
|
1287
|
-
`subspace: ${subspaceId}`,
|
|
1288
|
-
typeof nodeIndex === 'number' ? `node index: ${nodeIndex}` : undefined,
|
|
1289
|
-
reference || undefined,
|
|
1290
|
-
]
|
|
1291
|
-
.filter(Boolean)
|
|
1292
|
-
.join(', ')
|
|
1293
|
-
|
|
1294
|
-
if (node.__typename === 'DataCalculationData') {
|
|
1295
|
-
return assertEntryId(
|
|
1296
|
-
node.data()?.id,
|
|
1297
|
-
'PROPERTY_BANK_DATA_NODE',
|
|
1298
|
-
`(${nodeReference})`,
|
|
1299
|
-
)
|
|
1300
|
-
}
|
|
1301
|
-
if (node.__typename === 'DataCommand') {
|
|
1302
|
-
return assertEntryId(node.id, 'PROPERTY_BANK_COMMAND_NODE', `(${nodeReference})`)
|
|
1303
|
-
}
|
|
1304
|
-
throw new Error(`Invalid node: ${JSON.stringify(node)}`)
|
|
1323
|
+
const calc: any = {
|
|
1324
|
+
alias: dataCalc.alias,
|
|
1325
|
+
title: dataCalc.title,
|
|
1326
|
+
description: dataCalc.description,
|
|
1327
|
+
hide_short_ref: dataCalc.hideShortRef,
|
|
1305
1328
|
}
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1329
|
+
if (dataCalc.triggerMode) calc.trigger_type = dataCalc.triggerMode
|
|
1330
|
+
if (dataCalc.__typename === 'DataCalculationMap') {
|
|
1331
|
+
calc.type = 'general'
|
|
1332
|
+
const mapCalc = dataCalc as DataCalculationMap
|
|
1333
|
+
|
|
1334
|
+
const getNodeId = (
|
|
1335
|
+
node: DataCalculationData | DataCommand,
|
|
1336
|
+
reference = '',
|
|
1337
|
+
nodeIndex?: number,
|
|
1338
|
+
) => {
|
|
1339
|
+
const nodeReference = [
|
|
1340
|
+
`data calc: ${dataCalcId}`,
|
|
1341
|
+
`subspace: ${subspaceId}`,
|
|
1342
|
+
typeof nodeIndex === 'number' ? `node index: ${nodeIndex}` : undefined,
|
|
1343
|
+
reference || undefined,
|
|
1344
|
+
]
|
|
1345
|
+
.filter(Boolean)
|
|
1346
|
+
.join(', ')
|
|
1347
|
+
|
|
1348
|
+
if (node.__typename === 'DataCalculationData') {
|
|
1349
|
+
return assertEntryId(
|
|
1350
|
+
node.data()?.id,
|
|
1351
|
+
'PROPERTY_BANK_DATA_NODE',
|
|
1352
|
+
`(${nodeReference})`,
|
|
1353
|
+
)
|
|
1318
1354
|
}
|
|
1355
|
+
if (node.__typename === 'DataCommand') {
|
|
1356
|
+
return assertEntryId(node.id, 'PROPERTY_BANK_COMMAND_NODE', `(${nodeReference})`)
|
|
1357
|
+
}
|
|
1358
|
+
throw new Error(`Invalid node: ${JSON.stringify(node)}`)
|
|
1359
|
+
}
|
|
1319
1360
|
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1361
|
+
const generateInputPorts = (inputs) =>
|
|
1362
|
+
inputs.reduce((acc, port, portIndex) => {
|
|
1363
|
+
if (!acc[port.key]) acc[port.key] = null
|
|
1364
|
+
|
|
1365
|
+
let sourceId
|
|
1366
|
+
const sourceNode = port.source()
|
|
1367
|
+
if (
|
|
1368
|
+
sourceNode?.__typename === 'DataCalculationData' ||
|
|
1369
|
+
sourceNode?.__typename === 'DataCommand'
|
|
1370
|
+
) {
|
|
1371
|
+
sourceId = getNodeId(sourceNode, `input port index: ${portIndex}`)
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
if (!sourceId) return acc
|
|
1375
|
+
if (!acc[port.key]) acc[port.key] = []
|
|
1376
|
+
|
|
1377
|
+
acc[port.key].push({
|
|
1378
|
+
id: sourceId,
|
|
1379
|
+
port: port.sourceKey,
|
|
1380
|
+
disable_trigger_command: !port.trigger ? true : undefined,
|
|
1381
|
+
})
|
|
1382
|
+
return acc
|
|
1383
|
+
}, {})
|
|
1384
|
+
|
|
1385
|
+
const generateOutputPorts = (outputs) =>
|
|
1386
|
+
outputs.reduce((acc, port, portIndex) => {
|
|
1387
|
+
if (!acc[port.key]) acc[port.key] = null
|
|
1388
|
+
|
|
1389
|
+
let targetId
|
|
1390
|
+
const targetNode = port.target()
|
|
1391
|
+
if (
|
|
1392
|
+
targetNode?.__typename === 'DataCalculationData' ||
|
|
1393
|
+
targetNode?.__typename === 'DataCommand'
|
|
1394
|
+
) {
|
|
1395
|
+
targetId = getNodeId(targetNode, `output port index: ${portIndex}`)
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
if (!targetId) return acc
|
|
1399
|
+
if (!acc[port.key]) acc[port.key] = []
|
|
1400
|
+
|
|
1401
|
+
acc[port.key].push({
|
|
1402
|
+
id: targetId,
|
|
1403
|
+
port: port.targetKey,
|
|
1404
|
+
})
|
|
1405
|
+
return acc
|
|
1406
|
+
}, {})
|
|
1407
|
+
|
|
1408
|
+
calc.map = mapCalc.nodes.reduce((acc, node, nodeIndex) => {
|
|
1409
|
+
if (node.__typename === 'DataCalculationData') {
|
|
1410
|
+
const dataNode = node as DataCalculationData
|
|
1411
|
+
acc[getNodeId(dataNode, 'data node', nodeIndex)] = {
|
|
1412
|
+
title: dataNode.title,
|
|
1413
|
+
description: dataNode.description,
|
|
1414
|
+
hide_short_ref: dataNode.hideShortRef,
|
|
1415
|
+
type: 'data-node',
|
|
1416
|
+
properties: {},
|
|
1417
|
+
in: generateInputPorts(dataNode.inputs),
|
|
1418
|
+
out: generateOutputPorts(dataNode.outputs),
|
|
1419
|
+
}
|
|
1420
|
+
} else if (node.__typename === 'DataCommand') {
|
|
1421
|
+
const commandNode = node as DataCommand
|
|
1422
|
+
const commandName = commandNode.__commandName
|
|
1423
|
+
const type = commandName.split('_')[0].toLowerCase()
|
|
1424
|
+
|
|
1425
|
+
const args = commandNode.inputs.filter(
|
|
1426
|
+
(input) =>
|
|
1427
|
+
typeof input.source !== 'function' ||
|
|
1428
|
+
(input.source()?.__typename !== 'DataCalculationData' &&
|
|
1429
|
+
input.source()?.__typename !== 'DataCommand'),
|
|
1430
|
+
)
|
|
1431
|
+
const inputs = commandNode.inputs.filter(
|
|
1432
|
+
(input) =>
|
|
1433
|
+
typeof input.source === 'function' &&
|
|
1434
|
+
(input.source()?.__typename === 'DataCalculationData' ||
|
|
1435
|
+
input.source()?.__typename === 'DataCommand'),
|
|
1436
|
+
)
|
|
1437
|
+
acc[getNodeId(commandNode, 'command node', nodeIndex)] = {
|
|
1438
|
+
title: commandNode.title,
|
|
1439
|
+
description: commandNode.description,
|
|
1440
|
+
hide_short_ref: commandNode.hideShortRef,
|
|
1441
|
+
type: `command-node-${type}`,
|
|
1442
|
+
properties: {
|
|
1443
|
+
command: commandNode.__commandName,
|
|
1444
|
+
args: args.reduce((argsAcc, input) => {
|
|
1445
|
+
argsAcc[input.key] = input.source
|
|
1446
|
+
return argsAcc
|
|
1447
|
+
}, {}),
|
|
1448
|
+
},
|
|
1449
|
+
in: generateInputPorts(inputs),
|
|
1450
|
+
out: generateOutputPorts(commandNode.outputs),
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1328
1453
|
return acc
|
|
1329
1454
|
}, {})
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
) {
|
|
1341
|
-
targetId = getNodeId(targetNode, `output port index: ${portIndex}`)
|
|
1455
|
+
calc.editor_info = mapCalc.editorInfo.reduce((acc, editorInfo) => {
|
|
1456
|
+
acc[getNodeId(editorInfo.node, 'editor info node')] = {
|
|
1457
|
+
position: editorInfo.position,
|
|
1458
|
+
points: editorInfo.points.reduce((pointsAcc, point) => {
|
|
1459
|
+
const sourceId = getNodeId(point.source, 'editor info point source')
|
|
1460
|
+
const targetId = getNodeId(point.target, 'editor info point target')
|
|
1461
|
+
const key = `${sourceId}-${point.sourceOutputKey}-${targetId}-${point.targetInputKey}`
|
|
1462
|
+
pointsAcc[key] = point.positions
|
|
1463
|
+
return pointsAcc
|
|
1464
|
+
}, {}),
|
|
1342
1465
|
}
|
|
1343
|
-
|
|
1344
|
-
if (!targetId) return acc
|
|
1345
|
-
if (!acc[port.key]) acc[port.key] = []
|
|
1346
|
-
|
|
1347
|
-
acc[port.key].push({
|
|
1348
|
-
id: targetId,
|
|
1349
|
-
port: port.targetKey,
|
|
1350
|
-
})
|
|
1351
1466
|
return acc
|
|
1352
1467
|
}, {})
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
(input) =>
|
|
1373
|
-
typeof input.source !== 'function' ||
|
|
1374
|
-
(input.source()?.__typename !== 'DataCalculationData' &&
|
|
1375
|
-
input.source()?.__typename !== 'DataCommand'),
|
|
1376
|
-
)
|
|
1377
|
-
const inputs = commandNode.inputs.filter(
|
|
1378
|
-
(input) =>
|
|
1379
|
-
typeof input.source === 'function' &&
|
|
1380
|
-
(input.source()?.__typename === 'DataCalculationData' ||
|
|
1381
|
-
input.source()?.__typename === 'DataCommand'),
|
|
1382
|
-
)
|
|
1383
|
-
acc[getNodeId(commandNode, 'command node', nodeIndex)] = {
|
|
1384
|
-
title: commandNode.title,
|
|
1385
|
-
description: commandNode.description,
|
|
1386
|
-
hide_short_ref: commandNode.hideShortRef,
|
|
1387
|
-
type: `command-node-${type}`,
|
|
1388
|
-
properties: {
|
|
1389
|
-
command: commandNode.__commandName,
|
|
1390
|
-
args: args.reduce((argsAcc, input) => {
|
|
1391
|
-
argsAcc[input.key] = input.source
|
|
1392
|
-
return argsAcc
|
|
1393
|
-
}, {}),
|
|
1394
|
-
},
|
|
1395
|
-
in: generateInputPorts(inputs),
|
|
1396
|
-
out: generateOutputPorts(commandNode.outputs),
|
|
1397
|
-
}
|
|
1398
|
-
}
|
|
1399
|
-
return acc
|
|
1400
|
-
}, {})
|
|
1401
|
-
calc.editor_info = mapCalc.editorInfo.reduce((acc, editorInfo) => {
|
|
1402
|
-
acc[getNodeId(editorInfo.node, 'editor info node')] = {
|
|
1403
|
-
position: editorInfo.position,
|
|
1404
|
-
points: editorInfo.points.reduce((pointsAcc, point) => {
|
|
1405
|
-
const sourceId = getNodeId(point.source, 'editor info point source')
|
|
1406
|
-
const targetId = getNodeId(point.target, 'editor info point target')
|
|
1407
|
-
const key = `${sourceId}-${point.sourceOutputKey}-${targetId}-${point.targetInputKey}`
|
|
1408
|
-
pointsAcc[key] = point.positions
|
|
1409
|
-
return pointsAcc
|
|
1468
|
+
} else if (dataCalc.__typename === 'DataCalculationScript') {
|
|
1469
|
+
const scriptCalc = dataCalc as DataCalculationScript
|
|
1470
|
+
calc.type = 'script'
|
|
1471
|
+
|
|
1472
|
+
const code = compileScriptCalculationCode(scriptCalc.code)
|
|
1473
|
+
calc.script_config = {
|
|
1474
|
+
title: scriptCalc.title ?? '',
|
|
1475
|
+
note: scriptCalc.note ?? '',
|
|
1476
|
+
code,
|
|
1477
|
+
enable_async: scriptCalc.enableAsync,
|
|
1478
|
+
trigger_mode: scriptCalc.triggerMode,
|
|
1479
|
+
inputs: scriptCalc.inputs.reduce((acc, input) => {
|
|
1480
|
+
const inputId = assertEntryId(
|
|
1481
|
+
input.data()?.id,
|
|
1482
|
+
'PROPERTY_BANK_DATA_NODE',
|
|
1483
|
+
`(data calc: ${dataCalcId}, script input: ${input.key}, subspace: ${subspaceId})`,
|
|
1484
|
+
)
|
|
1485
|
+
acc[inputId] = input.key
|
|
1486
|
+
return acc
|
|
1410
1487
|
}, {}),
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
} else if (dataCalc.__typename === 'DataCalculationScript') {
|
|
1415
|
-
const scriptCalc = dataCalc as DataCalculationScript
|
|
1416
|
-
calc.type = 'script'
|
|
1417
|
-
|
|
1418
|
-
const code = compileScriptCalculationCode(scriptCalc.code)
|
|
1419
|
-
calc.script_config = {
|
|
1420
|
-
title: scriptCalc.title ?? '',
|
|
1421
|
-
note: scriptCalc.note ?? '',
|
|
1422
|
-
code,
|
|
1423
|
-
enable_async: scriptCalc.enableAsync,
|
|
1424
|
-
trigger_mode: scriptCalc.triggerMode,
|
|
1425
|
-
inputs: scriptCalc.inputs.reduce((acc, input) => {
|
|
1426
|
-
const inputId = assertEntryId(
|
|
1427
|
-
input.data()?.id,
|
|
1428
|
-
'PROPERTY_BANK_DATA_NODE',
|
|
1429
|
-
`(data calc: ${dataCalcId}, script input: ${input.key}, subspace: ${subspaceId})`,
|
|
1430
|
-
)
|
|
1431
|
-
acc[inputId] = input.key
|
|
1432
|
-
return acc
|
|
1433
|
-
}, {}),
|
|
1434
|
-
disabled_triggers: scriptCalc.inputs.reduce((acc, input) => {
|
|
1435
|
-
const inputId = assertEntryId(
|
|
1436
|
-
input.data()?.id,
|
|
1437
|
-
'PROPERTY_BANK_DATA_NODE',
|
|
1438
|
-
`(data calc: ${dataCalcId}, script trigger input: ${input.key}, subspace: ${subspaceId})`,
|
|
1439
|
-
)
|
|
1440
|
-
acc[inputId] = !input.trigger
|
|
1441
|
-
return acc
|
|
1442
|
-
}, {}),
|
|
1443
|
-
output: scriptCalc.output
|
|
1444
|
-
? assertEntryId(
|
|
1445
|
-
scriptCalc.output()?.id,
|
|
1488
|
+
disabled_triggers: scriptCalc.inputs.reduce((acc, input) => {
|
|
1489
|
+
const inputId = assertEntryId(
|
|
1490
|
+
input.data()?.id,
|
|
1446
1491
|
'PROPERTY_BANK_DATA_NODE',
|
|
1447
|
-
`(data calc: ${dataCalcId}, script
|
|
1492
|
+
`(data calc: ${dataCalcId}, script trigger input: ${input.key}, subspace: ${subspaceId})`,
|
|
1448
1493
|
)
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1494
|
+
acc[inputId] = !input.trigger
|
|
1495
|
+
return acc
|
|
1496
|
+
}, {}),
|
|
1497
|
+
output: scriptCalc.output
|
|
1498
|
+
? assertEntryId(
|
|
1499
|
+
scriptCalc.output()?.id,
|
|
1500
|
+
'PROPERTY_BANK_DATA_NODE',
|
|
1501
|
+
`(data calc: ${dataCalcId}, script output, subspace: ${subspaceId})`,
|
|
1502
|
+
)
|
|
1503
|
+
: null,
|
|
1504
|
+
outputs: scriptCalc.outputs.reduce((acc, output) => {
|
|
1505
|
+
if (!acc[output.key]) acc[output.key] = []
|
|
1506
|
+
const outputId = assertEntryId(
|
|
1507
|
+
output.data()?.id,
|
|
1463
1508
|
'PROPERTY_BANK_DATA_NODE',
|
|
1464
|
-
`(data calc: ${dataCalcId}, script
|
|
1509
|
+
`(data calc: ${dataCalcId}, script outputs key: ${output.key}, subspace: ${subspaceId})`,
|
|
1465
1510
|
)
|
|
1466
|
-
|
|
1467
|
-
|
|
1511
|
+
acc[output.key].push(outputId)
|
|
1512
|
+
return acc
|
|
1513
|
+
}, {}),
|
|
1514
|
+
error: scriptCalc.error
|
|
1515
|
+
? assertEntryId(
|
|
1516
|
+
scriptCalc.error()?.id,
|
|
1517
|
+
'PROPERTY_BANK_DATA_NODE',
|
|
1518
|
+
`(data calc: ${dataCalcId}, script error output, subspace: ${subspaceId})`,
|
|
1519
|
+
)
|
|
1520
|
+
: null,
|
|
1521
|
+
}
|
|
1468
1522
|
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1523
|
+
Object.assign(calc, generateCalulationMap(calc.script_config, dataCalcId))
|
|
1524
|
+
}
|
|
1525
|
+
map[dataCalcId] = calc
|
|
1526
|
+
return map
|
|
1527
|
+
},
|
|
1528
|
+
),
|
|
1474
1529
|
action_map: subspace.actions || undefined,
|
|
1475
1530
|
event_map: compileEvents('', subspace.events || {}, {
|
|
1476
1531
|
camelCase: false,
|
|
1477
1532
|
errorReference: `(subspace ${subspaceId})`,
|
|
1478
1533
|
}),
|
|
1479
|
-
routing: subspace.dataRouting
|
|
1534
|
+
routing: collectReduce(errors, subspace.dataRouting, (acc, data, index) => {
|
|
1480
1535
|
const dataId = assertEntryId(
|
|
1481
1536
|
data?.id,
|
|
1482
1537
|
'PROPERTY_BANK_DATA_NODE',
|
|
@@ -1484,15 +1539,15 @@ export const compile = async (app: Application) => {
|
|
|
1484
1539
|
)
|
|
1485
1540
|
acc[dataId] = { enabled_routing: true }
|
|
1486
1541
|
return acc
|
|
1487
|
-
}
|
|
1542
|
+
}),
|
|
1488
1543
|
...compileModule(subspace),
|
|
1489
1544
|
}
|
|
1490
1545
|
return subspaceMap
|
|
1491
1546
|
}, {}),
|
|
1492
|
-
root_subspace_id:
|
|
1493
|
-
|
|
1494
|
-
'SUBSPACE',
|
|
1495
|
-
'
|
|
1547
|
+
root_subspace_id: collect(
|
|
1548
|
+
errors,
|
|
1549
|
+
() => assertEntryId(app.rootSubspace?.id, 'SUBSPACE', '(application root subspace)'),
|
|
1550
|
+
'',
|
|
1496
1551
|
),
|
|
1497
1552
|
fonts: app.fonts,
|
|
1498
1553
|
...compileApplicationSettings(app.settings),
|
|
@@ -1503,6 +1558,12 @@ export const compile = async (app: Application) => {
|
|
|
1503
1558
|
automation_map: compiledAutomationMap || app.metadata?.TEMP_automation_map || {},
|
|
1504
1559
|
update_timestamp: timestamp,
|
|
1505
1560
|
}
|
|
1561
|
+
if (errors.length > 0) {
|
|
1562
|
+
throw new Error(
|
|
1563
|
+
`Compile failed with ${errors.length} error(s):\n` +
|
|
1564
|
+
errors.map((message, index) => ` ${index + 1}. ${message}`).join('\n'),
|
|
1565
|
+
)
|
|
1566
|
+
}
|
|
1506
1567
|
await recordConfigChange(previousConfig, config)
|
|
1507
1568
|
return config
|
|
1508
1569
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fugood/bricks-ctor",
|
|
3
|
-
"version": "2.25.0-beta.
|
|
3
|
+
"version": "2.25.0-beta.55",
|
|
4
4
|
"main": "index.ts",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"typecheck": "tsc --noEmit",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"@babel/parser": "7.28.5",
|
|
12
12
|
"@babel/traverse": "7.28.5",
|
|
13
13
|
"@babel/types": "7.28.5",
|
|
14
|
-
"@fugood/bricks-cli": "^2.25.0-beta.
|
|
14
|
+
"@fugood/bricks-cli": "^2.25.0-beta.53",
|
|
15
15
|
"@huggingface/gguf": "^0.3.2",
|
|
16
16
|
"@iarna/toml": "^3.0.0",
|
|
17
17
|
"@modelcontextprotocol/sdk": "^1.15.0",
|
|
@@ -29,5 +29,5 @@
|
|
|
29
29
|
"peerDependencies": {
|
|
30
30
|
"oxfmt": "^0.36.0"
|
|
31
31
|
},
|
|
32
|
-
"gitHead": "
|
|
32
|
+
"gitHead": "08f3ae4a88db84f682384ecfa94e9d4583371bda"
|
|
33
33
|
}
|
|
@@ -25,6 +25,7 @@ The primary way to orchestrate multi-step flows. A single event can contain an a
|
|
|
25
25
|
- Use `waitAsync: true` to await async actions before the next step
|
|
26
26
|
- Use `dataParams` + `mapping` to pass event data downstream
|
|
27
27
|
- This is the "glue" that wires generators, state, and UI together
|
|
28
|
+
- For Generator result UI: await Generator, write done/version Data; route/current Data stays identity.
|
|
28
29
|
|
|
29
30
|
Sequential `PROPERTY_BANK` / `PROPERTY_BANK_EXPRESSION` actions in one chain read the data values that existed when the chain started. If a later action needs to read what an earlier action wrote, set `waitAsync: true` on the earlier action.
|
|
30
31
|
|
|
@@ -87,6 +87,17 @@ const triggerCalc: EventAction = {
|
|
|
87
87
|
- When a **later action reads the calc's outputs**, set `waitAsync: true` on the `PROPERTY_BANK_COMMAND` action itself — it awaits the full calc chain including output writes.
|
|
88
88
|
- A dataParam's `value` acts as an execution gate: `{ input: () => d, value: false }` skips that trigger (combine with `mapping` for conditional runs).
|
|
89
89
|
|
|
90
|
+
## One trigger source per result panel
|
|
91
|
+
|
|
92
|
+
A panel that shows the result of an async action (Generator HTTP/LLM response, request telemetry) reads from a calc whose **trigger should come from a single source**: either the live async outlet, or a completion marker the action chain writes — not both.
|
|
93
|
+
|
|
94
|
+
- **Outlet-triggered** (`{ data: () => dResponse, trigger: true }`): the calc reruns as outlets land. Use when each outlet is only written once its value is final.
|
|
95
|
+
- **Marker-triggered**: run the generator with `waitAsync: true`, then write a `done` Data the calc triggers on. Use when several outlets settle separately and the panel must wait for all of them.
|
|
96
|
+
|
|
97
|
+
Wiring both at once — triggering on the live outlet _and_ gating on a separately-written marker — lets the calc run before the outlets have settled (it renders `undefined`/stale) while the marker path masks it intermittently. A panel that updates "sometimes" usually has two competing triggers: pick one, and order it with `waitAsync` so the trigger fires after the values it reads are written.
|
|
98
|
+
|
|
99
|
+
Scope the panel's calc to the **single value it derives** (the formatted display string), and write sibling status labels — running/done, progress, selected route — imperatively in the event chain with `PROPERTY_BANK`. A calc rewrites _every_ one of its outputs on each run, so a return that omits a key writes `undefined` to that output Data (see [Field Rules](#field-rules-defaults-and-constraints)): folding status labels into a multi-output result calc resets them to `undefined` on any run that returns only the derived value. If a calc genuinely must drive several outputs, return all of their keys every run.
|
|
100
|
+
|
|
90
101
|
## Script Sandbox
|
|
91
102
|
|
|
92
103
|
Scripts run in `use strict` mode as a function body — top-level `return` returns the calc result. No `fetch`, `XMLHttpRequest`, or `require` in any mode: I/O belongs to Generators.
|
|
@@ -220,6 +231,8 @@ const appendHistory: DataCalculationScript = {
|
|
|
220
231
|
| `PROPERTY_BANK_COMMAND` does nothing | Auto calc + `trigger: false` input, or `input` doesn't reference an input Data of the calc | Command a `trigger: true` input (auto) or any input (manual) |
|
|
221
232
|
| Compile error `Not allow duplicate set property id...` | Auto mode with same Data as input and output | Use `triggerMode: 'manual'`, or split into separate Data |
|
|
222
233
|
| Calc reads stale value written earlier in the same chain | Missing `waitAsync: true` on the preceding write | Set `waitAsync: true` on the write action |
|
|
234
|
+
| Result/telemetry panel shows `undefined` or stale data intermittently | Calc triggered by two sources at once (live outlet + a separately-written marker) | Trigger from one source — see [One trigger source per result panel](#one-trigger-source-per-result-panel) |
|
|
235
|
+
| Sibling status Data (selected/progress) resets to `undefined` after a calc runs | Multi-output result calc whose return omitted those keys on that run | Scope the calc to one output and write status labels imperatively, or return every output key each run — see [One trigger source per result panel](#one-trigger-source-per-result-panel) |
|
|
223
236
|
| Works in Simulator, fails on device | V8 vs Hermes/JSC engine difference | Verify on device (Path 2); avoid engine-sensitive parsing |
|
|
224
237
|
| `console.log` shows nothing | Console only emits during DevTools debug sessions | Attach DevTools, or write debug values to an output Data |
|
|
225
238
|
| `Promise`/`setTimeout` undefined, or `Async mode is required` error | `enableAsync: false` | Set `enableAsync: true` |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* Auto generated by build script
|
|
2
2
|
*
|
|
3
|
-
* Embedded HTTP/HTTPS server with route matching, CORS, auth (Basic/Bearer), SSE streaming, file upload,
|
|
3
|
+
* Embedded HTTP/HTTPS server with route matching, CORS, auth (Basic/Bearer), SSE streaming, file upload, async response mode, and JS Sandbox route handlers
|
|
4
4
|
*/
|
|
5
5
|
import type { SwitchCondInnerStateCurrentCanvas, SwitchCondData, SwitchDef } from '../switch'
|
|
6
6
|
import type { Data, DataLink } from '../data'
|
|
@@ -42,6 +42,7 @@ Default property:
|
|
|
42
42
|
"init": false,
|
|
43
43
|
"method": "GET",
|
|
44
44
|
"path": "/",
|
|
45
|
+
"methods": [],
|
|
45
46
|
"idleTimeout": 10000,
|
|
46
47
|
"authType": "none",
|
|
47
48
|
"asyncMode": false,
|
|
@@ -70,6 +71,59 @@ Default property:
|
|
|
70
71
|
| DataLink
|
|
71
72
|
/* Path of HTTP request */
|
|
72
73
|
path?: string | DataLink
|
|
74
|
+
/* Additional route methods handled by JS Sandbox scripts. The script receives `inputs.request`, `inputs.query`, `inputs.headers`, `inputs.body`, and configured `additionalParams`. `scriptConfig.members` can expose generator or brick script-member functions, same as MCP Server. */
|
|
75
|
+
methods?:
|
|
76
|
+
| Array<
|
|
77
|
+
| DataLink
|
|
78
|
+
| {
|
|
79
|
+
enabled?: boolean | DataLink
|
|
80
|
+
name?: string | DataLink
|
|
81
|
+
method?:
|
|
82
|
+
| 'GET'
|
|
83
|
+
| 'POST'
|
|
84
|
+
| 'PUT'
|
|
85
|
+
| 'DELETE'
|
|
86
|
+
| 'HEAD'
|
|
87
|
+
| 'PATCH'
|
|
88
|
+
| 'OPTIONS'
|
|
89
|
+
| 'CONNECT'
|
|
90
|
+
| 'TRACE'
|
|
91
|
+
| DataLink
|
|
92
|
+
path?: string | DataLink
|
|
93
|
+
resStatusCode?: number | DataLink
|
|
94
|
+
resContentType?:
|
|
95
|
+
| 'text/plain'
|
|
96
|
+
| 'text/html'
|
|
97
|
+
| 'text/javascript'
|
|
98
|
+
| 'text/css'
|
|
99
|
+
| 'text/xml'
|
|
100
|
+
| 'application/xml'
|
|
101
|
+
| 'application/json'
|
|
102
|
+
| 'application/octet-stream'
|
|
103
|
+
| 'text/event-stream'
|
|
104
|
+
| DataLink
|
|
105
|
+
resHeader?: {} | DataLink
|
|
106
|
+
resBody?: any
|
|
107
|
+
sseEvent?: string | DataLink
|
|
108
|
+
scriptConfig?:
|
|
109
|
+
| DataLink
|
|
110
|
+
| {
|
|
111
|
+
code?: string | DataLink
|
|
112
|
+
timeout?: number | DataLink
|
|
113
|
+
members?:
|
|
114
|
+
| Array<
|
|
115
|
+
| DataLink
|
|
116
|
+
| {
|
|
117
|
+
handler?: string | DataLink
|
|
118
|
+
varName?: string | DataLink
|
|
119
|
+
}
|
|
120
|
+
>
|
|
121
|
+
| DataLink
|
|
122
|
+
additionalParams?: {} | DataLink
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
>
|
|
126
|
+
| DataLink
|
|
73
127
|
/* Max connection idle time, 0 is disable */
|
|
74
128
|
idleTimeout?: number | DataLink
|
|
75
129
|
/* HTTP request body limit, 0 is unlimited */
|
|
@@ -153,7 +207,7 @@ Default property:
|
|
|
153
207
|
}
|
|
154
208
|
}
|
|
155
209
|
|
|
156
|
-
/* Embedded HTTP/HTTPS server with route matching, CORS, auth (Basic/Bearer), SSE streaming, file upload,
|
|
210
|
+
/* Embedded HTTP/HTTPS server with route matching, CORS, auth (Basic/Bearer), SSE streaming, file upload, async response mode, and JS Sandbox route handlers */
|
|
157
211
|
export type GeneratorHTTPServer = Generator &
|
|
158
212
|
GeneratorHTTPServerDef & {
|
|
159
213
|
templateKey: 'GENERATOR_HTTP_SERVER'
|
package/utils/data.ts
CHANGED