@fugood/bricks-ctor 2.25.0-beta.52 → 2.25.0-beta.53
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.
|
@@ -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
|
@@ -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))
|
|
@@ -744,13 +769,20 @@ const recordConfigChange = async (previousConfig: unknown, config: unknown) => {
|
|
|
744
769
|
|
|
745
770
|
export const compile = async (app: Application) => {
|
|
746
771
|
await new Promise((resolve) => setImmediate(resolve, 0))
|
|
772
|
+
// Collected entity-level compile errors (see collect/collectReduce). Aggregated and
|
|
773
|
+
// thrown at the end so one compile reports every entity's first error.
|
|
774
|
+
const errors: string[] = []
|
|
747
775
|
// Snapshot the prior build artifact before the caller's compile.ts overwrites it, so
|
|
748
776
|
// the config change introduced by this compile can be recorded on return.
|
|
749
777
|
const previousConfig = await readBuildConfig(process.cwd())
|
|
750
778
|
const timestamp = Date.now()
|
|
751
779
|
// Pre-index subspace ids so the canvas-item validation below stays O(1).
|
|
752
780
|
const subspaceIdSet = new Set(app.subspaces.map((s) => s.id))
|
|
753
|
-
|
|
781
|
+
let compiledAutomationMap: ReturnType<typeof compileAutomation> | null = null
|
|
782
|
+
if (app.automationMap) {
|
|
783
|
+
const { automationMap } = app
|
|
784
|
+
compiledAutomationMap = collect(errors, () => compileAutomation(automationMap), null)
|
|
785
|
+
}
|
|
754
786
|
const config = {
|
|
755
787
|
title: `${app.name || 'Unknown'}(${timestamp})`,
|
|
756
788
|
subspace_map: app.subspaces.reduce((subspaceMap, subspace, subspaceIndex) => {
|
|
@@ -836,74 +868,78 @@ export const compile = async (app: Application) => {
|
|
|
836
868
|
change_canvas: subspace.localSyncChangeCanvas,
|
|
837
869
|
}
|
|
838
870
|
: undefined,
|
|
839
|
-
animation_map:
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
871
|
+
animation_map: collectReduce(
|
|
872
|
+
errors,
|
|
873
|
+
subspace.animations,
|
|
874
|
+
(map, animation, animationIndex) => {
|
|
875
|
+
const animationId = assertEntryId(
|
|
876
|
+
animation?.id,
|
|
877
|
+
'ANIMATION',
|
|
878
|
+
`(animation index: ${animationIndex}, subspace: ${subspaceId})`,
|
|
879
|
+
)
|
|
845
880
|
|
|
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
|
-
|
|
881
|
+
const animationTypename = animation.__typename
|
|
882
|
+
if (animationTypename === 'Animation') {
|
|
883
|
+
const animationDef = animation as AnimationDef
|
|
884
|
+
const animationErrorReference = `(animation: ${animationId}, subspace ${subspaceId})`
|
|
885
|
+
const animationWarningMetadata = {
|
|
886
|
+
animationIndex,
|
|
887
|
+
animationTitle: animationDef.title,
|
|
888
|
+
animationAlias: animationDef.alias,
|
|
889
|
+
animationProperty: animationDef.property,
|
|
890
|
+
subspaceIndex,
|
|
891
|
+
subspaceTitle: subspace.title,
|
|
892
|
+
}
|
|
893
|
+
const animationType = getAnimationType(animationDef.config, animationErrorReference)
|
|
894
|
+
map[animationId] = {
|
|
895
|
+
alias: animationDef.alias,
|
|
896
|
+
title: animationDef.title,
|
|
897
|
+
description: animationDef.description,
|
|
898
|
+
hide_short_ref: animationDef.hideShortRef,
|
|
899
|
+
animationRunType: animationDef.runType,
|
|
900
|
+
property: assertAnimationProperty(animationDef.property, animationErrorReference),
|
|
901
|
+
type: animationType,
|
|
902
|
+
config: compileAnimationConfig(
|
|
903
|
+
animationType,
|
|
904
|
+
animationDef.config,
|
|
905
|
+
animationErrorReference,
|
|
906
|
+
animationWarningMetadata,
|
|
907
|
+
),
|
|
908
|
+
}
|
|
909
|
+
} else if (animationTypename === 'AnimationCompose') {
|
|
910
|
+
const animationDef = animation as AnimationComposeDef
|
|
911
|
+
const animationErrorReference = `(animation: ${animationId}, subspace ${subspaceId})`
|
|
912
|
+
map[animationId] = {
|
|
913
|
+
alias: animationDef.alias,
|
|
914
|
+
title: animationDef.title,
|
|
915
|
+
description: animationDef.description,
|
|
916
|
+
hide_short_ref: animationDef.hideShortRef,
|
|
917
|
+
animationRunType: animationDef.runType,
|
|
918
|
+
compose_type: assertAnimationComposeType(
|
|
919
|
+
animationDef.composeType,
|
|
920
|
+
animationErrorReference,
|
|
921
|
+
),
|
|
922
|
+
item_list: animationDef.items.map((item, index) => {
|
|
923
|
+
const innerAnimation = item()
|
|
924
|
+
const innerAnimationId = assertEntryId(
|
|
925
|
+
innerAnimation?.id,
|
|
926
|
+
'ANIMATION',
|
|
927
|
+
`(animation item index: ${index}, animation: ${animationId}, subspace ${subspaceId})`,
|
|
928
|
+
)
|
|
929
|
+
return { animation_id: innerAnimationId }
|
|
930
|
+
}),
|
|
931
|
+
}
|
|
932
|
+
} else {
|
|
933
|
+
throw new Error(
|
|
934
|
+
`Invalid animation typename (animation: ${animationId}, subspace ${subspaceId}): ${String(
|
|
935
|
+
animationTypename,
|
|
936
|
+
)}`,
|
|
937
|
+
)
|
|
896
938
|
}
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
)}`,
|
|
902
|
-
)
|
|
903
|
-
}
|
|
904
|
-
return map
|
|
905
|
-
}, {}),
|
|
906
|
-
brick_map: subspace.bricks.reduce((map, brick, brickIndex) => {
|
|
939
|
+
return map
|
|
940
|
+
},
|
|
941
|
+
),
|
|
942
|
+
brick_map: collectReduce(errors, subspace.bricks, (map, brick, brickIndex) => {
|
|
907
943
|
const brickId = assertEntryId(
|
|
908
944
|
brick.id,
|
|
909
945
|
'BRICK',
|
|
@@ -1068,9 +1104,9 @@ export const compile = async (app: Application) => {
|
|
|
1068
1104
|
}, {}),
|
|
1069
1105
|
}
|
|
1070
1106
|
return map
|
|
1071
|
-
}
|
|
1107
|
+
}),
|
|
1072
1108
|
root_canvas_id: rootCanvasId,
|
|
1073
|
-
canvas_map: subspace.canvases
|
|
1109
|
+
canvas_map: collectReduce(errors, subspace.canvases, (map, canvas, canvasIndex) => {
|
|
1074
1110
|
const canvasId = assertEntryId(
|
|
1075
1111
|
canvas.id,
|
|
1076
1112
|
'CANVAS',
|
|
@@ -1150,81 +1186,85 @@ export const compile = async (app: Application) => {
|
|
|
1150
1186
|
}),
|
|
1151
1187
|
}
|
|
1152
1188
|
return map
|
|
1153
|
-
}
|
|
1154
|
-
generator_map:
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1189
|
+
}),
|
|
1190
|
+
generator_map: collectReduce(
|
|
1191
|
+
errors,
|
|
1192
|
+
subspace.generators,
|
|
1193
|
+
(map, generator, generatorIndex) => {
|
|
1194
|
+
const generatorId = assertEntryId(
|
|
1195
|
+
generator.id,
|
|
1196
|
+
'GENERATOR',
|
|
1197
|
+
`(generator index: ${generatorIndex}, subspace: ${subspaceId})`,
|
|
1198
|
+
)
|
|
1160
1199
|
|
|
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
|
-
|
|
1200
|
+
map[generatorId] = {
|
|
1201
|
+
template_key: generator.templateKey,
|
|
1202
|
+
alias: generator.alias,
|
|
1203
|
+
title: generator.title,
|
|
1204
|
+
description: generator.description,
|
|
1205
|
+
hide_short_ref: generator.hideShortRef,
|
|
1206
|
+
local_sync: generator.localSyncRunMode
|
|
1207
|
+
? {
|
|
1208
|
+
run_mode: generator.localSyncRunMode,
|
|
1209
|
+
}
|
|
1210
|
+
: undefined,
|
|
1211
|
+
property: compileProperty(
|
|
1212
|
+
generator.property || {},
|
|
1213
|
+
`(generator: ${generatorId}, subspace ${subspaceId})`,
|
|
1214
|
+
),
|
|
1215
|
+
event_map: compileEvents(generator.templateKey, generator.events || {}, {
|
|
1216
|
+
camelCase: false,
|
|
1217
|
+
errorReference: `(generator: ${generatorId}, subspace ${subspaceId})`,
|
|
1218
|
+
}),
|
|
1219
|
+
outlet: compileOutlets(
|
|
1220
|
+
generator.templateKey,
|
|
1221
|
+
generator.outlets || {},
|
|
1222
|
+
`(generator: ${generatorId}, subspace ${subspaceId})`,
|
|
1223
|
+
),
|
|
1224
|
+
state_group: generator.switches?.reduce((acc, switchCase, switchIndex) => {
|
|
1225
|
+
const switchId = assertEntryId(
|
|
1226
|
+
switchCase.id,
|
|
1227
|
+
'BRICK_STATE_GROUP',
|
|
1228
|
+
`(generator: ${generatorId}, switch index: ${switchIndex}, subspace ${subspaceId})`,
|
|
1229
|
+
)
|
|
1191
1230
|
|
|
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
|
-
|
|
1231
|
+
acc[switchId] = {
|
|
1232
|
+
title: switchCase.title,
|
|
1233
|
+
description: switchCase.description,
|
|
1234
|
+
break: switchCase.break,
|
|
1235
|
+
override: switchCase.override,
|
|
1236
|
+
commented: switchCase.disabled,
|
|
1237
|
+
conds: compileSwitchConds(
|
|
1238
|
+
generator.templateKey,
|
|
1239
|
+
switchCase.conds || [],
|
|
1240
|
+
`(generator: ${generatorId}, switch: ${switchId}, subspace ${subspaceId})`,
|
|
1241
|
+
),
|
|
1242
|
+
property: compileProperty(
|
|
1243
|
+
switchCase.property,
|
|
1244
|
+
`(generator: ${generatorId}, switch: ${switchId}, subspace ${subspaceId})`,
|
|
1245
|
+
),
|
|
1246
|
+
outlet: compileOutlets(
|
|
1247
|
+
generator.templateKey,
|
|
1248
|
+
switchCase.outlets || {},
|
|
1249
|
+
`(generator: ${generatorId}, switch: ${switchId}, subspace ${subspaceId})`,
|
|
1250
|
+
),
|
|
1251
|
+
event_map: compileEvents(generator.templateKey, switchCase.events || {}, {
|
|
1252
|
+
camelCase: false,
|
|
1253
|
+
errorReference: `(generator: ${generatorId}, switch: ${switchId}, subspace ${subspaceId})`,
|
|
1254
|
+
}),
|
|
1255
|
+
animation: compileAnimations(
|
|
1256
|
+
generator.templateKey,
|
|
1257
|
+
switchCase.animation || {},
|
|
1258
|
+
`(generator: ${generatorId}, switch: ${switchId}, subspace ${subspaceId})`,
|
|
1259
|
+
),
|
|
1260
|
+
}
|
|
1261
|
+
return acc
|
|
1262
|
+
}, {}),
|
|
1263
|
+
}
|
|
1264
|
+
return map
|
|
1265
|
+
},
|
|
1266
|
+
),
|
|
1267
|
+
property_bank_map: collectReduce(errors, subspace.data, (map, data, dataIndex) => {
|
|
1228
1268
|
const dataId = assertEntryId(
|
|
1229
1269
|
data.id,
|
|
1230
1270
|
'PROPERTY_BANK_DATA_NODE',
|
|
@@ -1258,225 +1298,229 @@ export const compile = async (app: Application) => {
|
|
|
1258
1298
|
hit_regex: data.hit_regex,
|
|
1259
1299
|
}
|
|
1260
1300
|
return map
|
|
1261
|
-
}
|
|
1262
|
-
property_bank_calc_map:
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1301
|
+
}),
|
|
1302
|
+
property_bank_calc_map: collectReduce(
|
|
1303
|
+
errors,
|
|
1304
|
+
subspace.dataCalculation,
|
|
1305
|
+
(map, dataCalc, dataCalcIndex) => {
|
|
1306
|
+
const dataCalcId = assertEntryId(
|
|
1307
|
+
dataCalc.id,
|
|
1308
|
+
'PROPERTY_BANK_COMMAND_MAP',
|
|
1309
|
+
`(data calc index: ${dataCalcIndex}, subspace: ${subspaceId})`,
|
|
1310
|
+
)
|
|
1268
1311
|
|
|
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)}`)
|
|
1312
|
+
const calc: any = {
|
|
1313
|
+
alias: dataCalc.alias,
|
|
1314
|
+
title: dataCalc.title,
|
|
1315
|
+
description: dataCalc.description,
|
|
1316
|
+
hide_short_ref: dataCalc.hideShortRef,
|
|
1305
1317
|
}
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
+
if (dataCalc.triggerMode) calc.trigger_type = dataCalc.triggerMode
|
|
1319
|
+
if (dataCalc.__typename === 'DataCalculationMap') {
|
|
1320
|
+
calc.type = 'general'
|
|
1321
|
+
const mapCalc = dataCalc as DataCalculationMap
|
|
1322
|
+
|
|
1323
|
+
const getNodeId = (
|
|
1324
|
+
node: DataCalculationData | DataCommand,
|
|
1325
|
+
reference = '',
|
|
1326
|
+
nodeIndex?: number,
|
|
1327
|
+
) => {
|
|
1328
|
+
const nodeReference = [
|
|
1329
|
+
`data calc: ${dataCalcId}`,
|
|
1330
|
+
`subspace: ${subspaceId}`,
|
|
1331
|
+
typeof nodeIndex === 'number' ? `node index: ${nodeIndex}` : undefined,
|
|
1332
|
+
reference || undefined,
|
|
1333
|
+
]
|
|
1334
|
+
.filter(Boolean)
|
|
1335
|
+
.join(', ')
|
|
1336
|
+
|
|
1337
|
+
if (node.__typename === 'DataCalculationData') {
|
|
1338
|
+
return assertEntryId(
|
|
1339
|
+
node.data()?.id,
|
|
1340
|
+
'PROPERTY_BANK_DATA_NODE',
|
|
1341
|
+
`(${nodeReference})`,
|
|
1342
|
+
)
|
|
1318
1343
|
}
|
|
1344
|
+
if (node.__typename === 'DataCommand') {
|
|
1345
|
+
return assertEntryId(node.id, 'PROPERTY_BANK_COMMAND_NODE', `(${nodeReference})`)
|
|
1346
|
+
}
|
|
1347
|
+
throw new Error(`Invalid node: ${JSON.stringify(node)}`)
|
|
1348
|
+
}
|
|
1319
1349
|
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1350
|
+
const generateInputPorts = (inputs) =>
|
|
1351
|
+
inputs.reduce((acc, port, portIndex) => {
|
|
1352
|
+
if (!acc[port.key]) acc[port.key] = null
|
|
1353
|
+
|
|
1354
|
+
let sourceId
|
|
1355
|
+
const sourceNode = port.source()
|
|
1356
|
+
if (
|
|
1357
|
+
sourceNode?.__typename === 'DataCalculationData' ||
|
|
1358
|
+
sourceNode?.__typename === 'DataCommand'
|
|
1359
|
+
) {
|
|
1360
|
+
sourceId = getNodeId(sourceNode, `input port index: ${portIndex}`)
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
if (!sourceId) return acc
|
|
1364
|
+
if (!acc[port.key]) acc[port.key] = []
|
|
1365
|
+
|
|
1366
|
+
acc[port.key].push({
|
|
1367
|
+
id: sourceId,
|
|
1368
|
+
port: port.sourceKey,
|
|
1369
|
+
disable_trigger_command: !port.trigger ? true : undefined,
|
|
1370
|
+
})
|
|
1371
|
+
return acc
|
|
1372
|
+
}, {})
|
|
1373
|
+
|
|
1374
|
+
const generateOutputPorts = (outputs) =>
|
|
1375
|
+
outputs.reduce((acc, port, portIndex) => {
|
|
1376
|
+
if (!acc[port.key]) acc[port.key] = null
|
|
1377
|
+
|
|
1378
|
+
let targetId
|
|
1379
|
+
const targetNode = port.target()
|
|
1380
|
+
if (
|
|
1381
|
+
targetNode?.__typename === 'DataCalculationData' ||
|
|
1382
|
+
targetNode?.__typename === 'DataCommand'
|
|
1383
|
+
) {
|
|
1384
|
+
targetId = getNodeId(targetNode, `output port index: ${portIndex}`)
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
if (!targetId) return acc
|
|
1388
|
+
if (!acc[port.key]) acc[port.key] = []
|
|
1389
|
+
|
|
1390
|
+
acc[port.key].push({
|
|
1391
|
+
id: targetId,
|
|
1392
|
+
port: port.targetKey,
|
|
1393
|
+
})
|
|
1394
|
+
return acc
|
|
1395
|
+
}, {})
|
|
1396
|
+
|
|
1397
|
+
calc.map = mapCalc.nodes.reduce((acc, node, nodeIndex) => {
|
|
1398
|
+
if (node.__typename === 'DataCalculationData') {
|
|
1399
|
+
const dataNode = node as DataCalculationData
|
|
1400
|
+
acc[getNodeId(dataNode, 'data node', nodeIndex)] = {
|
|
1401
|
+
title: dataNode.title,
|
|
1402
|
+
description: dataNode.description,
|
|
1403
|
+
hide_short_ref: dataNode.hideShortRef,
|
|
1404
|
+
type: 'data-node',
|
|
1405
|
+
properties: {},
|
|
1406
|
+
in: generateInputPorts(dataNode.inputs),
|
|
1407
|
+
out: generateOutputPorts(dataNode.outputs),
|
|
1408
|
+
}
|
|
1409
|
+
} else if (node.__typename === 'DataCommand') {
|
|
1410
|
+
const commandNode = node as DataCommand
|
|
1411
|
+
const commandName = commandNode.__commandName
|
|
1412
|
+
const type = commandName.split('_')[0].toLowerCase()
|
|
1413
|
+
|
|
1414
|
+
const args = commandNode.inputs.filter(
|
|
1415
|
+
(input) =>
|
|
1416
|
+
typeof input.source !== 'function' ||
|
|
1417
|
+
(input.source()?.__typename !== 'DataCalculationData' &&
|
|
1418
|
+
input.source()?.__typename !== 'DataCommand'),
|
|
1419
|
+
)
|
|
1420
|
+
const inputs = commandNode.inputs.filter(
|
|
1421
|
+
(input) =>
|
|
1422
|
+
typeof input.source === 'function' &&
|
|
1423
|
+
(input.source()?.__typename === 'DataCalculationData' ||
|
|
1424
|
+
input.source()?.__typename === 'DataCommand'),
|
|
1425
|
+
)
|
|
1426
|
+
acc[getNodeId(commandNode, 'command node', nodeIndex)] = {
|
|
1427
|
+
title: commandNode.title,
|
|
1428
|
+
description: commandNode.description,
|
|
1429
|
+
hide_short_ref: commandNode.hideShortRef,
|
|
1430
|
+
type: `command-node-${type}`,
|
|
1431
|
+
properties: {
|
|
1432
|
+
command: commandNode.__commandName,
|
|
1433
|
+
args: args.reduce((argsAcc, input) => {
|
|
1434
|
+
argsAcc[input.key] = input.source
|
|
1435
|
+
return argsAcc
|
|
1436
|
+
}, {}),
|
|
1437
|
+
},
|
|
1438
|
+
in: generateInputPorts(inputs),
|
|
1439
|
+
out: generateOutputPorts(commandNode.outputs),
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1328
1442
|
return acc
|
|
1329
1443
|
}, {})
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
) {
|
|
1341
|
-
targetId = getNodeId(targetNode, `output port index: ${portIndex}`)
|
|
1444
|
+
calc.editor_info = mapCalc.editorInfo.reduce((acc, editorInfo) => {
|
|
1445
|
+
acc[getNodeId(editorInfo.node, 'editor info node')] = {
|
|
1446
|
+
position: editorInfo.position,
|
|
1447
|
+
points: editorInfo.points.reduce((pointsAcc, point) => {
|
|
1448
|
+
const sourceId = getNodeId(point.source, 'editor info point source')
|
|
1449
|
+
const targetId = getNodeId(point.target, 'editor info point target')
|
|
1450
|
+
const key = `${sourceId}-${point.sourceOutputKey}-${targetId}-${point.targetInputKey}`
|
|
1451
|
+
pointsAcc[key] = point.positions
|
|
1452
|
+
return pointsAcc
|
|
1453
|
+
}, {}),
|
|
1342
1454
|
}
|
|
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
1455
|
return acc
|
|
1352
1456
|
}, {})
|
|
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
|
|
1457
|
+
} else if (dataCalc.__typename === 'DataCalculationScript') {
|
|
1458
|
+
const scriptCalc = dataCalc as DataCalculationScript
|
|
1459
|
+
calc.type = 'script'
|
|
1460
|
+
|
|
1461
|
+
const code = compileScriptCalculationCode(scriptCalc.code)
|
|
1462
|
+
calc.script_config = {
|
|
1463
|
+
title: scriptCalc.title ?? '',
|
|
1464
|
+
note: scriptCalc.note ?? '',
|
|
1465
|
+
code,
|
|
1466
|
+
enable_async: scriptCalc.enableAsync,
|
|
1467
|
+
trigger_mode: scriptCalc.triggerMode,
|
|
1468
|
+
inputs: scriptCalc.inputs.reduce((acc, input) => {
|
|
1469
|
+
const inputId = assertEntryId(
|
|
1470
|
+
input.data()?.id,
|
|
1471
|
+
'PROPERTY_BANK_DATA_NODE',
|
|
1472
|
+
`(data calc: ${dataCalcId}, script input: ${input.key}, subspace: ${subspaceId})`,
|
|
1473
|
+
)
|
|
1474
|
+
acc[inputId] = input.key
|
|
1475
|
+
return acc
|
|
1410
1476
|
}, {}),
|
|
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,
|
|
1477
|
+
disabled_triggers: scriptCalc.inputs.reduce((acc, input) => {
|
|
1478
|
+
const inputId = assertEntryId(
|
|
1479
|
+
input.data()?.id,
|
|
1446
1480
|
'PROPERTY_BANK_DATA_NODE',
|
|
1447
|
-
`(data calc: ${dataCalcId}, script
|
|
1481
|
+
`(data calc: ${dataCalcId}, script trigger input: ${input.key}, subspace: ${subspaceId})`,
|
|
1448
1482
|
)
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1483
|
+
acc[inputId] = !input.trigger
|
|
1484
|
+
return acc
|
|
1485
|
+
}, {}),
|
|
1486
|
+
output: scriptCalc.output
|
|
1487
|
+
? assertEntryId(
|
|
1488
|
+
scriptCalc.output()?.id,
|
|
1489
|
+
'PROPERTY_BANK_DATA_NODE',
|
|
1490
|
+
`(data calc: ${dataCalcId}, script output, subspace: ${subspaceId})`,
|
|
1491
|
+
)
|
|
1492
|
+
: null,
|
|
1493
|
+
outputs: scriptCalc.outputs.reduce((acc, output) => {
|
|
1494
|
+
if (!acc[output.key]) acc[output.key] = []
|
|
1495
|
+
const outputId = assertEntryId(
|
|
1496
|
+
output.data()?.id,
|
|
1463
1497
|
'PROPERTY_BANK_DATA_NODE',
|
|
1464
|
-
`(data calc: ${dataCalcId}, script
|
|
1498
|
+
`(data calc: ${dataCalcId}, script outputs key: ${output.key}, subspace: ${subspaceId})`,
|
|
1465
1499
|
)
|
|
1466
|
-
|
|
1467
|
-
|
|
1500
|
+
acc[output.key].push(outputId)
|
|
1501
|
+
return acc
|
|
1502
|
+
}, {}),
|
|
1503
|
+
error: scriptCalc.error
|
|
1504
|
+
? assertEntryId(
|
|
1505
|
+
scriptCalc.error()?.id,
|
|
1506
|
+
'PROPERTY_BANK_DATA_NODE',
|
|
1507
|
+
`(data calc: ${dataCalcId}, script error output, subspace: ${subspaceId})`,
|
|
1508
|
+
)
|
|
1509
|
+
: null,
|
|
1510
|
+
}
|
|
1468
1511
|
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1512
|
+
Object.assign(calc, generateCalulationMap(calc.script_config, dataCalcId))
|
|
1513
|
+
}
|
|
1514
|
+
map[dataCalcId] = calc
|
|
1515
|
+
return map
|
|
1516
|
+
},
|
|
1517
|
+
),
|
|
1474
1518
|
action_map: subspace.actions || undefined,
|
|
1475
1519
|
event_map: compileEvents('', subspace.events || {}, {
|
|
1476
1520
|
camelCase: false,
|
|
1477
1521
|
errorReference: `(subspace ${subspaceId})`,
|
|
1478
1522
|
}),
|
|
1479
|
-
routing: subspace.dataRouting
|
|
1523
|
+
routing: collectReduce(errors, subspace.dataRouting, (acc, data, index) => {
|
|
1480
1524
|
const dataId = assertEntryId(
|
|
1481
1525
|
data?.id,
|
|
1482
1526
|
'PROPERTY_BANK_DATA_NODE',
|
|
@@ -1484,15 +1528,15 @@ export const compile = async (app: Application) => {
|
|
|
1484
1528
|
)
|
|
1485
1529
|
acc[dataId] = { enabled_routing: true }
|
|
1486
1530
|
return acc
|
|
1487
|
-
}
|
|
1531
|
+
}),
|
|
1488
1532
|
...compileModule(subspace),
|
|
1489
1533
|
}
|
|
1490
1534
|
return subspaceMap
|
|
1491
1535
|
}, {}),
|
|
1492
|
-
root_subspace_id:
|
|
1493
|
-
|
|
1494
|
-
'SUBSPACE',
|
|
1495
|
-
'
|
|
1536
|
+
root_subspace_id: collect(
|
|
1537
|
+
errors,
|
|
1538
|
+
() => assertEntryId(app.rootSubspace?.id, 'SUBSPACE', '(application root subspace)'),
|
|
1539
|
+
'',
|
|
1496
1540
|
),
|
|
1497
1541
|
fonts: app.fonts,
|
|
1498
1542
|
...compileApplicationSettings(app.settings),
|
|
@@ -1503,6 +1547,12 @@ export const compile = async (app: Application) => {
|
|
|
1503
1547
|
automation_map: compiledAutomationMap || app.metadata?.TEMP_automation_map || {},
|
|
1504
1548
|
update_timestamp: timestamp,
|
|
1505
1549
|
}
|
|
1550
|
+
if (errors.length > 0) {
|
|
1551
|
+
throw new Error(
|
|
1552
|
+
`Compile failed with ${errors.length} error(s):\n` +
|
|
1553
|
+
errors.map((message, index) => ` ${index + 1}. ${message}`).join('\n'),
|
|
1554
|
+
)
|
|
1555
|
+
}
|
|
1506
1556
|
await recordConfigChange(previousConfig, config)
|
|
1507
1557
|
return config
|
|
1508
1558
|
}
|
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.53",
|
|
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": "b08f881540787ad817115880ebcee1487a0a81ab"
|
|
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'
|