@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.
@@ -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
- // export function main() { ... }
105
- const declarationBody = (
106
- (program.body[0] as ExportNamedDeclaration).declaration as FunctionDeclaration
107
- )?.body
108
- return escodegen.generate(declarationBody, {
109
- format: {
110
- indent: { style: ' ' },
111
- semicolons: false,
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
- comment: true,
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
- const compiledAutomationMap = app.automationMap ? compileAutomation(app.automationMap) : null
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: subspace.animations.reduce((map, animation, animationIndex) => {
840
- const animationId = assertEntryId(
841
- animation?.id,
842
- 'ANIMATION',
843
- `(animation index: ${animationIndex}, subspace: ${subspaceId})`,
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
- const animationTypename = animation.__typename
847
- if (animationTypename === 'Animation') {
848
- const animationDef = animation as AnimationDef
849
- const animationErrorReference = `(animation: ${animationId}, subspace ${subspaceId})`
850
- const animationWarningMetadata = {
851
- animationIndex,
852
- animationTitle: animationDef.title,
853
- animationAlias: animationDef.alias,
854
- animationProperty: animationDef.property,
855
- subspaceIndex,
856
- subspaceTitle: subspace.title,
857
- }
858
- const animationType = getAnimationType(animationDef.config, animationErrorReference)
859
- map[animationId] = {
860
- alias: animationDef.alias,
861
- title: animationDef.title,
862
- description: animationDef.description,
863
- hide_short_ref: animationDef.hideShortRef,
864
- animationRunType: animationDef.runType,
865
- property: assertAnimationProperty(animationDef.property, animationErrorReference),
866
- type: animationType,
867
- config: compileAnimationConfig(
868
- animationType,
869
- animationDef.config,
870
- animationErrorReference,
871
- animationWarningMetadata,
872
- ),
873
- }
874
- } else if (animationTypename === 'AnimationCompose') {
875
- const animationDef = animation as AnimationComposeDef
876
- const animationErrorReference = `(animation: ${animationId}, subspace ${subspaceId})`
877
- map[animationId] = {
878
- alias: animationDef.alias,
879
- title: animationDef.title,
880
- description: animationDef.description,
881
- hide_short_ref: animationDef.hideShortRef,
882
- animationRunType: animationDef.runType,
883
- compose_type: assertAnimationComposeType(
884
- animationDef.composeType,
885
- animationErrorReference,
886
- ),
887
- item_list: animationDef.items.map((item, index) => {
888
- const innerAnimation = item()
889
- const innerAnimationId = assertEntryId(
890
- innerAnimation?.id,
891
- 'ANIMATION',
892
- `(animation item index: ${index}, animation: ${animationId}, subspace ${subspaceId})`,
893
- )
894
- return { animation_id: innerAnimationId }
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
- } else {
898
- throw new Error(
899
- `Invalid animation typename (animation: ${animationId}, subspace ${subspaceId}): ${String(
900
- animationTypename,
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.reduce((map, canvas, canvasIndex) => {
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: subspace.generators.reduce((map, generator, generatorIndex) => {
1155
- const generatorId = assertEntryId(
1156
- generator.id,
1157
- 'GENERATOR',
1158
- `(generator index: ${generatorIndex}, subspace: ${subspaceId})`,
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
- map[generatorId] = {
1162
- template_key: generator.templateKey,
1163
- alias: generator.alias,
1164
- title: generator.title,
1165
- description: generator.description,
1166
- hide_short_ref: generator.hideShortRef,
1167
- local_sync: generator.localSyncRunMode
1168
- ? {
1169
- run_mode: generator.localSyncRunMode,
1170
- }
1171
- : undefined,
1172
- property: compileProperty(
1173
- generator.property || {},
1174
- `(generator: ${generatorId}, subspace ${subspaceId})`,
1175
- ),
1176
- event_map: compileEvents(generator.templateKey, generator.events || {}, {
1177
- camelCase: false,
1178
- errorReference: `(generator: ${generatorId}, subspace ${subspaceId})`,
1179
- }),
1180
- outlet: compileOutlets(
1181
- generator.templateKey,
1182
- generator.outlets || {},
1183
- `(generator: ${generatorId}, subspace ${subspaceId})`,
1184
- ),
1185
- state_group: generator.switches?.reduce((acc, switchCase, switchIndex) => {
1186
- const switchId = assertEntryId(
1187
- switchCase.id,
1188
- 'BRICK_STATE_GROUP',
1189
- `(generator: ${generatorId}, switch index: ${switchIndex}, subspace ${subspaceId})`,
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
- acc[switchId] = {
1193
- title: switchCase.title,
1194
- description: switchCase.description,
1195
- break: switchCase.break,
1196
- override: switchCase.override,
1197
- commented: switchCase.disabled,
1198
- conds: compileSwitchConds(
1199
- generator.templateKey,
1200
- switchCase.conds || [],
1201
- `(generator: ${generatorId}, switch: ${switchId}, subspace ${subspaceId})`,
1202
- ),
1203
- property: compileProperty(
1204
- switchCase.property,
1205
- `(generator: ${generatorId}, switch: ${switchId}, subspace ${subspaceId})`,
1206
- ),
1207
- outlet: compileOutlets(
1208
- generator.templateKey,
1209
- switchCase.outlets || {},
1210
- `(generator: ${generatorId}, switch: ${switchId}, subspace ${subspaceId})`,
1211
- ),
1212
- event_map: compileEvents(generator.templateKey, switchCase.events || {}, {
1213
- camelCase: false,
1214
- errorReference: `(generator: ${generatorId}, switch: ${switchId}, subspace ${subspaceId})`,
1215
- }),
1216
- animation: compileAnimations(
1217
- generator.templateKey,
1218
- switchCase.animation || {},
1219
- `(generator: ${generatorId}, switch: ${switchId}, subspace ${subspaceId})`,
1220
- ),
1221
- }
1222
- return acc
1223
- }, {}),
1224
- }
1225
- return map
1226
- }, {}),
1227
- property_bank_map: subspace.data.reduce((map, data, dataIndex) => {
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: subspace.dataCalculation.reduce((map, dataCalc, dataCalcIndex) => {
1263
- const dataCalcId = assertEntryId(
1264
- dataCalc.id,
1265
- 'PROPERTY_BANK_COMMAND_MAP',
1266
- `(data calc index: ${dataCalcIndex}, subspace: ${subspaceId})`,
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
- const calc: any = {
1270
- alias: dataCalc.alias,
1271
- title: dataCalc.title,
1272
- description: dataCalc.description,
1273
- hide_short_ref: dataCalc.hideShortRef,
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
- const generateInputPorts = (inputs) =>
1308
- inputs.reduce((acc, port, portIndex) => {
1309
- if (!acc[port.key]) acc[port.key] = null
1310
-
1311
- let sourceId
1312
- const sourceNode = port.source()
1313
- if (
1314
- sourceNode?.__typename === 'DataCalculationData' ||
1315
- sourceNode?.__typename === 'DataCommand'
1316
- ) {
1317
- sourceId = getNodeId(sourceNode, `input port index: ${portIndex}`)
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
- if (!sourceId) return acc
1321
- if (!acc[port.key]) acc[port.key] = []
1322
-
1323
- acc[port.key].push({
1324
- id: sourceId,
1325
- port: port.sourceKey,
1326
- disable_trigger_command: !port.trigger ? true : undefined,
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
- const generateOutputPorts = (outputs) =>
1332
- outputs.reduce((acc, port, portIndex) => {
1333
- if (!acc[port.key]) acc[port.key] = null
1334
-
1335
- let targetId
1336
- const targetNode = port.target()
1337
- if (
1338
- targetNode?.__typename === 'DataCalculationData' ||
1339
- targetNode?.__typename === 'DataCommand'
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
- calc.map = mapCalc.nodes.reduce((acc, node, nodeIndex) => {
1355
- if (node.__typename === 'DataCalculationData') {
1356
- const dataNode = node as DataCalculationData
1357
- acc[getNodeId(dataNode, 'data node', nodeIndex)] = {
1358
- title: dataNode.title,
1359
- description: dataNode.description,
1360
- hide_short_ref: dataNode.hideShortRef,
1361
- type: 'data-node',
1362
- properties: {},
1363
- in: generateInputPorts(dataNode.inputs),
1364
- out: generateOutputPorts(dataNode.outputs),
1365
- }
1366
- } else if (node.__typename === 'DataCommand') {
1367
- const commandNode = node as DataCommand
1368
- const commandName = commandNode.__commandName
1369
- const type = commandName.split('_')[0].toLowerCase()
1370
-
1371
- const args = commandNode.inputs.filter(
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
- return acc
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 output, subspace: ${subspaceId})`,
1492
+ `(data calc: ${dataCalcId}, script trigger input: ${input.key}, subspace: ${subspaceId})`,
1448
1493
  )
1449
- : null,
1450
- outputs: scriptCalc.outputs.reduce((acc, output) => {
1451
- if (!acc[output.key]) acc[output.key] = []
1452
- const outputId = assertEntryId(
1453
- output.data()?.id,
1454
- 'PROPERTY_BANK_DATA_NODE',
1455
- `(data calc: ${dataCalcId}, script outputs key: ${output.key}, subspace: ${subspaceId})`,
1456
- )
1457
- acc[output.key].push(outputId)
1458
- return acc
1459
- }, {}),
1460
- error: scriptCalc.error
1461
- ? assertEntryId(
1462
- scriptCalc.error()?.id,
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 error output, subspace: ${subspaceId})`,
1509
+ `(data calc: ${dataCalcId}, script outputs key: ${output.key}, subspace: ${subspaceId})`,
1465
1510
  )
1466
- : null,
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
- Object.assign(calc, generateCalulationMap(calc.script_config, dataCalcId))
1470
- }
1471
- map[dataCalcId] = calc
1472
- return map
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.reduce((acc, data, index) => {
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: assertEntryId(
1493
- app.rootSubspace?.id,
1494
- 'SUBSPACE',
1495
- '(application root subspace)',
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.52",
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.52",
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": "f6869285ea21123990934411ea190d9fbf12ea7f"
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, and async response mode
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, and async response mode */
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
@@ -83,7 +83,7 @@ type SystemDataName =
83
83
  type SystemDataInfo = {
84
84
  name: SystemDataName
85
85
  id: string
86
- type: 'string' | 'number' | 'bool' | 'boolean' | 'array' | 'object' | 'any'
86
+ type: Data['type']
87
87
  title?: string
88
88
  description?: string
89
89
  schema?: object