@winkjs/composer 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +8 -8
- package/src/core/batch/aoo-encoder.js +4 -4
- package/src/core/batch/sliding-window.js +2 -2
- package/src/core/codec/index.js +4 -4
- package/src/core/emitter-manager/mqtt/emitter.js +44 -14
- package/src/core/emitter-manager/mqtt/index.js +2 -1
- package/src/core/emitter-manager/terminal/assert-columns.js +1 -1
- package/src/core/emitter-manager/terminal/index.js +4 -4
- package/src/core/env-vars.js +44 -3
- package/src/core/logger/index.js +174 -0
- package/src/core/mqtt-protocol.js +2 -2
- package/src/core/partition-manager/get-stats.js +31 -0
- package/src/core/partition-manager/index.js +1 -0
- package/src/core/partition-manager/init.js +15 -0
- package/src/core/partition-manager/update.js +143 -87
- package/src/core/partition-manager/validate-flow.js +1 -1
- package/src/core/semantics/loader.js +1 -1
- package/src/core/semantics/warnings.js +5 -3
- package/src/core/shutdown-manager/index.js +71 -21
- package/src/core/source-manager/csv/start.js +109 -35
- package/src/core/source-manager/mqtt/client.js +95 -36
- package/src/core/source-manager/mqtt/dedup.js +3 -3
- package/src/core/source-manager/mqtt/index.js +1 -1
- package/src/core/source-manager/mqtt/status.js +67 -14
- package/src/core/source-manager/record-shape.js +55 -0
- package/src/core/source-manager/test-harness/field-generator.js +1 -1
- package/src/core/source-manager/test-harness/start.js +52 -10
- package/src/core/source-manager/test-harness/validate.js +1 -1
- package/src/core/storage-manager/questdb/assert-columns.js +2 -2
- package/src/core/storage-manager/questdb/ensure-tables.js +1 -1
- package/src/core/storage-manager/questdb/index.js +55 -17
- package/src/core/storage-manager/questdb/persist-plan.js +32 -8
- package/src/core/tunable/helpers.js +1 -1
- package/src/core/utils/callback-guard/index.js +155 -0
- package/src/core/utils/half-life/index.js +4 -4
- package/src/core/utils/validate/composer-validators.js +2 -2
- package/src/core/utils/validate/index.js +1 -1
- package/src/core/utils/validate/validate.js +1 -1
- package/src/core/wiring/assert-handle.js +5 -5
- package/src/core/wiring/assert-module.js +2 -2
- package/src/core/wiring/wire-emitters.js +4 -3
- package/src/core/wiring/wire-linear-graph.js +1 -1
- package/src/core/wiring/wire-node.js +6 -6
- package/src/core/wiring/wire-semantics.js +1 -1
- package/src/core/wiring/wire-storages.js +4 -3
- package/src/flow/driver.js +31 -6
- package/src/flow/expand-single-input-array.js +4 -4
- package/src/flow/flow-api-schemas.js +4 -4
- package/src/flow/flow.js +41 -41
- package/src/flow/for-each.js +12 -11
- package/src/flow/get-target-array.js +2 -2
- package/src/flow/load-node-module.js +1 -1
- package/src/flow/make-node-method.js +4 -4
- package/src/flow/resolve-pick-by-field.js +1 -1
- package/src/flow/run.js +176 -38
- package/src/flow/serialize.js +1 -1
- package/src/flow/spec-builder.js +1 -1
- package/src/flow/validate.js +1 -1
- package/src/nodes/butterworth-filter/init.js +5 -4
- package/src/nodes/categorize/update.js +3 -1
- package/src/nodes/controller/update.js +2 -1
- package/src/nodes/dwell-time-tracker/update.js +2 -1
- package/src/nodes/emit-if/helpers.js +57 -11
- package/src/nodes/emit-if/init.js +1 -1
- package/src/nodes/emit-if/update.js +8 -11
- package/src/nodes/page-hinkley/update.js +2 -1
- package/src/nodes/pass-if/update.js +3 -1
- package/src/nodes/persist-if/helpers.js +60 -4
- package/src/nodes/persist-if/init.js +1 -1
- package/src/nodes/persist-if/update.js +8 -16
- package/src/nodes/persistence-check/update.js +2 -1
- package/src/nodes/sanitize/update.js +4 -2
- package/src/nodes/swing-watch/update.js +3 -2
- package/src/nodes/threshold/update.js +2 -1
- package/src/nodes/trend/compute-confidence.js +4 -4
- package/src/nodes/trend/update.js +2 -1
- package/src/nodes/winnow/update.js +3 -1
- package/src/tools/training/features.js +13 -13
- package/src/tools/training/logistic.js +1 -1
- package/src/tools/training/metrics.js +2 -2
- package/src/tools/training/scale.js +2 -2
- package/src/tools/training/validate.js +7 -7
- package/src/core/emitter-manager/mqtt/mqtt-store.js +0 -538
- package/src/core/utils/console-formatter/index.js +0 -207
- package/src/nodes/archive/csv/index.js +0 -6
- package/src/nodes/archive/csv/init.js +0 -36
- package/src/nodes/archive/csv/load.js +0 -70
- package/src/nodes/archive/csv/reset.js +0 -14
- package/src/nodes/archive/csv/simple-parser.js +0 -216
- package/src/nodes/archive/csv/test.js +0 -173
- package/src/nodes/archive/csv/update.js +0 -63
- package/src/nodes/sse-emitter/index.js +0 -7
- package/src/nodes/sse-emitter/init.js +0 -105
- package/src/nodes/sse-emitter/introspect.js +0 -134
- package/src/nodes/sse-emitter/publish-to.js +0 -6
- package/src/nodes/sse-emitter/recompute.js +0 -16
- package/src/nodes/sse-emitter/reset.js +0 -19
- package/src/nodes/sse-emitter/update.js +0 -69
- package/src/nodes/sse-emitter/utils/broadcast-heartbeat.js +0 -36
- package/src/nodes/sse-emitter/utils/build-message.js +0 -31
- package/src/nodes/sse-emitter/utils/connection-manager.js +0 -45
- package/src/nodes/sse-emitter/utils/endpoint-initializer.js +0 -20
- package/src/nodes/sse-emitter/utils/health-monitor.js +0 -38
- package/src/nodes/sse-emitter/utils/qos-handler.js +0 -41
package/src/flow/flow.js
CHANGED
|
@@ -91,7 +91,7 @@ const flow = function ( flowName = 'flow1' ) {
|
|
|
91
91
|
const enforceConfigFirst = function ( methodName ) {
|
|
92
92
|
if ( pipelineStarted ) {
|
|
93
93
|
throw new Error(
|
|
94
|
-
`
|
|
94
|
+
`winkComposer/flow: .${methodName}() must be called before any nodes or .switch()/.groupBy()`
|
|
95
95
|
);
|
|
96
96
|
}
|
|
97
97
|
};
|
|
@@ -100,11 +100,11 @@ const flow = function ( flowName = 'flow1' ) {
|
|
|
100
100
|
const validateSwitchComplete = function () {
|
|
101
101
|
if ( switchState.active ) {
|
|
102
102
|
if ( switchState.caseOrder.length === 0 ) {
|
|
103
|
-
throw new Error( '
|
|
103
|
+
throw new Error( 'winkComposer/flow: .switch() requires at least one .case()' );
|
|
104
104
|
}
|
|
105
105
|
if ( !switchState.caseEnded ) {
|
|
106
106
|
throw new Error(
|
|
107
|
-
`
|
|
107
|
+
`winkComposer/flow: case '${switchState.currentCase}' must end with .break()`
|
|
108
108
|
);
|
|
109
109
|
}
|
|
110
110
|
}
|
|
@@ -118,7 +118,7 @@ const flow = function ( flowName = 'flow1' ) {
|
|
|
118
118
|
const collisions = collectFlowOutputCollisions( switchState, flowDefinition );
|
|
119
119
|
if ( collisions.length > 0 ) {
|
|
120
120
|
throw new Error(
|
|
121
|
-
'
|
|
121
|
+
'winkComposer/flow: output field collision(s) - two nodes on one path ' +
|
|
122
122
|
'write the same message field:\n - ' + collisions.join( '\n - ' )
|
|
123
123
|
);
|
|
124
124
|
}
|
|
@@ -160,10 +160,10 @@ const flow = function ( flowName = 'flow1' ) {
|
|
|
160
160
|
api.source = function ( adapter, sourceConfig = {} ) {
|
|
161
161
|
enforceConfigFirst( 'source' );
|
|
162
162
|
if ( typeof adapter !== 'object' || adapter === null ) {
|
|
163
|
-
throw new Error( '
|
|
163
|
+
throw new Error( 'winkComposer/flow: source adapter must be an imported module' );
|
|
164
164
|
}
|
|
165
165
|
if ( typeof adapter.start !== 'function' ) {
|
|
166
|
-
throw new Error( '
|
|
166
|
+
throw new Error( 'winkComposer/flow: source adapter must have a start() function' );
|
|
167
167
|
}
|
|
168
168
|
|
|
169
169
|
// If the adapter provides a configSchema, we validate config here.
|
|
@@ -171,7 +171,7 @@ const flow = function ( flowName = 'flow1' ) {
|
|
|
171
171
|
// not later inside adapter.start().
|
|
172
172
|
if ( adapter.configSchema ) {
|
|
173
173
|
const validation = validateWithSchema( adapter.configSchema, sourceConfig, 'source' );
|
|
174
|
-
validation.throwIfInvalid( `flow
|
|
174
|
+
validation.throwIfInvalid( `flow.source.${adapter.id || 'unknown'}` );
|
|
175
175
|
}
|
|
176
176
|
|
|
177
177
|
runtime.source = { adapter, config: sourceConfig };
|
|
@@ -181,13 +181,13 @@ const flow = function ( flowName = 'flow1' ) {
|
|
|
181
181
|
api.emitter = function ( adapter, emitterConfig = {} ) {
|
|
182
182
|
enforceConfigFirst( 'emitter' );
|
|
183
183
|
if ( typeof adapter !== 'object' || adapter === null ) {
|
|
184
|
-
throw new Error( '
|
|
184
|
+
throw new Error( 'winkComposer/flow: emitter adapter must be an imported module' );
|
|
185
185
|
}
|
|
186
186
|
if ( typeof adapter.id !== 'string' || !adapter.id ) {
|
|
187
|
-
throw new Error( '
|
|
187
|
+
throw new Error( 'winkComposer/flow: emitter adapter must have an id' );
|
|
188
188
|
}
|
|
189
189
|
if ( typeof adapter.createEmitter !== 'function' ) {
|
|
190
|
-
throw new Error( '
|
|
190
|
+
throw new Error( 'winkComposer/flow: emitter adapter must have a createEmitter() function' );
|
|
191
191
|
}
|
|
192
192
|
|
|
193
193
|
// If the adapter provides a configSchema, we validate config here.
|
|
@@ -195,7 +195,7 @@ const flow = function ( flowName = 'flow1' ) {
|
|
|
195
195
|
// not later inside adapter.createEmitter().
|
|
196
196
|
if ( adapter.configSchema ) {
|
|
197
197
|
const validation = validateWithSchema( adapter.configSchema, emitterConfig, 'emitter' );
|
|
198
|
-
validation.throwIfInvalid( `flow
|
|
198
|
+
validation.throwIfInvalid( `flow.emitter.${adapter.id}` );
|
|
199
199
|
}
|
|
200
200
|
|
|
201
201
|
runtime.emitters[ adapter.id ] = { adapter, config: emitterConfig };
|
|
@@ -205,13 +205,13 @@ const flow = function ( flowName = 'flow1' ) {
|
|
|
205
205
|
api.storage = function ( adapter, storageConfig = {} ) {
|
|
206
206
|
enforceConfigFirst( 'storage' );
|
|
207
207
|
if ( typeof adapter !== 'object' || adapter === null ) {
|
|
208
|
-
throw new Error( '
|
|
208
|
+
throw new Error( 'winkComposer/flow: storage adapter must be an imported module' );
|
|
209
209
|
}
|
|
210
210
|
if ( typeof adapter.id !== 'string' || !adapter.id ) {
|
|
211
|
-
throw new Error( '
|
|
211
|
+
throw new Error( 'winkComposer/flow: storage adapter must have an id' );
|
|
212
212
|
}
|
|
213
213
|
if ( typeof adapter.createStorage !== 'function' ) {
|
|
214
|
-
throw new Error( '
|
|
214
|
+
throw new Error( 'winkComposer/flow: storage adapter must have a createStorage() function' );
|
|
215
215
|
}
|
|
216
216
|
|
|
217
217
|
// If the adapter provides a configSchema, we validate config here.
|
|
@@ -219,7 +219,7 @@ const flow = function ( flowName = 'flow1' ) {
|
|
|
219
219
|
// not later inside adapter.createStorage().
|
|
220
220
|
if ( adapter.configSchema ) {
|
|
221
221
|
const validation = validateWithSchema( adapter.configSchema, storageConfig, 'storage' );
|
|
222
|
-
validation.throwIfInvalid( `flow
|
|
222
|
+
validation.throwIfInvalid( `flow.storage.${adapter.id}` );
|
|
223
223
|
}
|
|
224
224
|
|
|
225
225
|
runtime.storages[ adapter.id ] = { adapter, config: storageConfig };
|
|
@@ -230,7 +230,7 @@ const flow = function ( flowName = 'flow1' ) {
|
|
|
230
230
|
enforceConfigFirst( 'assetId' );
|
|
231
231
|
|
|
232
232
|
if ( typeof field !== 'string' || field.length === 0 ) {
|
|
233
|
-
throw new Error( '
|
|
233
|
+
throw new Error( 'winkComposer/flow: .assetId() requires a non-empty string field name' );
|
|
234
234
|
}
|
|
235
235
|
|
|
236
236
|
// Schema-based validation for robust error handling
|
|
@@ -251,7 +251,7 @@ const flow = function ( flowName = 'flow1' ) {
|
|
|
251
251
|
if ( assetClassDef === null || assetClassDef === undefined || typeof assetClassDef !== 'object' ) {
|
|
252
252
|
const actualType = assetClassDef === null ? 'null' : typeof assetClassDef;
|
|
253
253
|
throw new Error(
|
|
254
|
-
`
|
|
254
|
+
`winkComposer/flow.assetClass: Expected object, got ${actualType}`
|
|
255
255
|
);
|
|
256
256
|
}
|
|
257
257
|
|
|
@@ -266,7 +266,7 @@ const flow = function ( flowName = 'flow1' ) {
|
|
|
266
266
|
const validation = validateWithSchema( assetClassSchema, assetClassDef, 'assetClass' );
|
|
267
267
|
if ( !validation.valid ) {
|
|
268
268
|
throw new Error(
|
|
269
|
-
`
|
|
269
|
+
`winkComposer/flow.assetClass: validation failed:\n - ${validation.errors.join( '\n - ' )}`
|
|
270
270
|
);
|
|
271
271
|
}
|
|
272
272
|
|
|
@@ -298,7 +298,7 @@ const flow = function ( flowName = 'flow1' ) {
|
|
|
298
298
|
*/
|
|
299
299
|
api.switch = function ( field ) {
|
|
300
300
|
if ( typeof field !== 'string' || field.length === 0 ) {
|
|
301
|
-
throw new Error( '
|
|
301
|
+
throw new Error( 'winkComposer/flow: .switch() requires a non-empty string field name' );
|
|
302
302
|
}
|
|
303
303
|
|
|
304
304
|
// Schema-based validation: field must be a valid identifier (mirrors .assetId())
|
|
@@ -307,21 +307,21 @@ const flow = function ( flowName = 'flow1' ) {
|
|
|
307
307
|
|
|
308
308
|
// Check groupBy before switchState (endGroup sets switchState.active)
|
|
309
309
|
if ( groupByState.active || groupByState.expanded ) {
|
|
310
|
-
throw new Error( '
|
|
310
|
+
throw new Error( 'winkComposer/flow: .switch() cannot be used with .groupBy()' );
|
|
311
311
|
}
|
|
312
312
|
if ( switchState.active ) {
|
|
313
|
-
throw new Error( '
|
|
313
|
+
throw new Error( 'winkComposer/flow: .switch() can only be called once per flow' );
|
|
314
314
|
}
|
|
315
315
|
if ( flowDefinition.length > 0 ) {
|
|
316
316
|
throw new Error(
|
|
317
|
-
'
|
|
317
|
+
'winkComposer/flow: .switch() cannot be called after nodes - ' +
|
|
318
318
|
'use .switch() before defining any nodes, or omit .switch() for single-pipeline flows'
|
|
319
319
|
);
|
|
320
320
|
}
|
|
321
321
|
|
|
322
322
|
// Validate field conflict with partition
|
|
323
323
|
if ( runtime.partitionField === field ) {
|
|
324
|
-
throw new Error( '
|
|
324
|
+
throw new Error( 'winkComposer/flow: partition field and switch field must be different' );
|
|
325
325
|
}
|
|
326
326
|
|
|
327
327
|
runtime.specializationField = field;
|
|
@@ -340,15 +340,15 @@ const flow = function ( flowName = 'flow1' ) {
|
|
|
340
340
|
*/
|
|
341
341
|
api.case = function ( key ) {
|
|
342
342
|
if ( !switchState.active ) {
|
|
343
|
-
throw new Error( '
|
|
343
|
+
throw new Error( 'winkComposer/flow: .case() requires an active .switch()' );
|
|
344
344
|
}
|
|
345
345
|
if ( switchState.currentCase !== null && !switchState.caseEnded ) {
|
|
346
346
|
throw new Error(
|
|
347
|
-
`
|
|
347
|
+
`winkComposer/flow: previous case '${switchState.currentCase}' must end with .break() before starting a new case`
|
|
348
348
|
);
|
|
349
349
|
}
|
|
350
350
|
if ( switchState.caseSpecs[ key ] !== undefined ) {
|
|
351
|
-
throw new Error( `
|
|
351
|
+
throw new Error( `winkComposer/flow: duplicate case key '${key}'` );
|
|
352
352
|
}
|
|
353
353
|
|
|
354
354
|
switchState.currentCase = key;
|
|
@@ -367,14 +367,14 @@ const flow = function ( flowName = 'flow1' ) {
|
|
|
367
367
|
*/
|
|
368
368
|
api.break = function () {
|
|
369
369
|
if ( !switchState.active ) {
|
|
370
|
-
throw new Error( '
|
|
370
|
+
throw new Error( 'winkComposer/flow: .break() requires an active .switch()' );
|
|
371
371
|
}
|
|
372
372
|
if ( switchState.currentCase === null || switchState.caseEnded ) {
|
|
373
|
-
throw new Error( '
|
|
373
|
+
throw new Error( 'winkComposer/flow: .break() must follow a .case() with at least one node' );
|
|
374
374
|
}
|
|
375
375
|
if ( switchState.caseSpecs[ switchState.currentCase ].length === 0 ) {
|
|
376
376
|
throw new Error(
|
|
377
|
-
`
|
|
377
|
+
`winkComposer/flow: case '${switchState.currentCase}' is empty - add at least one node before .break()`
|
|
378
378
|
);
|
|
379
379
|
}
|
|
380
380
|
|
|
@@ -408,7 +408,7 @@ const flow = function ( flowName = 'flow1' ) {
|
|
|
408
408
|
api.groupBy = function ( field, values ) {
|
|
409
409
|
// Validation: field must be non-empty string
|
|
410
410
|
if ( typeof field !== 'string' || field.length === 0 ) {
|
|
411
|
-
throw new Error( '
|
|
411
|
+
throw new Error( 'winkComposer/flow: .groupBy() requires a non-empty string field name' );
|
|
412
412
|
}
|
|
413
413
|
|
|
414
414
|
// Schema-based validation: field must be a valid identifier (mirrors .assetId())
|
|
@@ -417,14 +417,14 @@ const flow = function ( flowName = 'flow1' ) {
|
|
|
417
417
|
|
|
418
418
|
// Validation: values must be array with at least 2 elements
|
|
419
419
|
if ( !Array.isArray( values ) || values.length < 2 ) {
|
|
420
|
-
throw new Error( '
|
|
420
|
+
throw new Error( 'winkComposer/flow: .groupBy() requires at least 2 group values' );
|
|
421
421
|
}
|
|
422
422
|
|
|
423
423
|
// Validation: each value must be string or number
|
|
424
424
|
for ( let i = 0; i < values.length; i += 1 ) {
|
|
425
425
|
const v = values[ i ];
|
|
426
426
|
if ( typeof v !== 'string' && typeof v !== 'number' ) {
|
|
427
|
-
throw new Error( `
|
|
427
|
+
throw new Error( `winkComposer/flow: group value at index ${i} must be a string or number` );
|
|
428
428
|
}
|
|
429
429
|
}
|
|
430
430
|
|
|
@@ -433,32 +433,32 @@ const flow = function ( flowName = 'flow1' ) {
|
|
|
433
433
|
for ( let i = 0; i < values.length; i += 1 ) {
|
|
434
434
|
const v = values[ i ];
|
|
435
435
|
if ( seen[ v ] ) {
|
|
436
|
-
throw new Error( `
|
|
436
|
+
throw new Error( `winkComposer/flow: duplicate group value '${v}' in .groupBy()` );
|
|
437
437
|
}
|
|
438
438
|
seen[ v ] = 1;
|
|
439
439
|
}
|
|
440
440
|
|
|
441
441
|
// Validation: cannot call groupBy twice (check this first - user intent is to reuse groupBy)
|
|
442
442
|
if ( groupByState.active || groupByState.expanded ) {
|
|
443
|
-
throw new Error( '
|
|
443
|
+
throw new Error( 'winkComposer/flow: .groupBy() can only be called once per flow' );
|
|
444
444
|
}
|
|
445
445
|
|
|
446
446
|
// Validation: cannot use groupBy if switchState is active (manual switch, not from endGroup)
|
|
447
447
|
if ( switchState.active ) {
|
|
448
|
-
throw new Error( '
|
|
448
|
+
throw new Error( 'winkComposer/flow: .groupBy() cannot be used inside .switch()' );
|
|
449
449
|
}
|
|
450
450
|
|
|
451
451
|
// Validation: must come before any nodes
|
|
452
452
|
if ( flowDefinition.length > 0 ) {
|
|
453
453
|
throw new Error(
|
|
454
|
-
'
|
|
454
|
+
'winkComposer/flow: .groupBy() cannot be called after nodes - ' +
|
|
455
455
|
'use .groupBy() before defining any nodes'
|
|
456
456
|
);
|
|
457
457
|
}
|
|
458
458
|
|
|
459
459
|
// Validation: groupBy field cannot equal partition field
|
|
460
460
|
if ( runtime.partitionField === field ) {
|
|
461
|
-
throw new Error( '
|
|
461
|
+
throw new Error( 'winkComposer/flow: partition field and groupBy field must be different' );
|
|
462
462
|
}
|
|
463
463
|
|
|
464
464
|
// Initialize groupBy state
|
|
@@ -487,12 +487,12 @@ const flow = function ( flowName = 'flow1' ) {
|
|
|
487
487
|
api.endGroup = function () {
|
|
488
488
|
// Validation: must be in active groupBy state
|
|
489
489
|
if ( !groupByState.active ) {
|
|
490
|
-
throw new Error( '
|
|
490
|
+
throw new Error( 'winkComposer/flow: .endGroup() requires an active .groupBy()' );
|
|
491
491
|
}
|
|
492
492
|
|
|
493
493
|
// Validation: must have at least one template node
|
|
494
494
|
if ( groupByState.templateSpecs.length === 0 ) {
|
|
495
|
-
throw new Error( '
|
|
495
|
+
throw new Error( 'winkComposer/flow: .groupBy() must contain at least one node before .endGroup()' );
|
|
496
496
|
}
|
|
497
497
|
|
|
498
498
|
// Set specialization field (same as switch does)
|
|
@@ -553,7 +553,7 @@ const flow = function ( flowName = 'flow1' ) {
|
|
|
553
553
|
|
|
554
554
|
// Single-pipeline mode (backward compatible)
|
|
555
555
|
if ( flowDefinition.length === 0 ) {
|
|
556
|
-
throw new Error( '
|
|
556
|
+
throw new Error( 'winkComposer/flow: Cannot build empty flow - add at least one node' );
|
|
557
557
|
}
|
|
558
558
|
const imports = Array.from( importSet ).sort();
|
|
559
559
|
return serializeModule( { imports, flowName, specs: flowDefinition } );
|
|
@@ -646,7 +646,7 @@ const flow = function ( flowName = 'flow1' ) {
|
|
|
646
646
|
|
|
647
647
|
// Single-pipeline mode (backward compatible)
|
|
648
648
|
if ( flowDefinition.length === 0 ) {
|
|
649
|
-
throw new Error( '
|
|
649
|
+
throw new Error( 'winkComposer/flow: Cannot run empty flow - add at least one node' );
|
|
650
650
|
}
|
|
651
651
|
const handle = await runFlow( flowName, flowDefinition, importSet, runtime );
|
|
652
652
|
return handle;
|
package/src/flow/for-each.js
CHANGED
|
@@ -31,6 +31,7 @@ import { getSignaturePattern } from './get-signature-pattern.js';
|
|
|
31
31
|
import { specBuilder } from './spec-builder.js';
|
|
32
32
|
import { getTargetArray } from './get-target-array.js';
|
|
33
33
|
import { resolvePickByField } from './resolve-pick-by-field.js';
|
|
34
|
+
import { logger } from '../core/logger/index.js';
|
|
34
35
|
|
|
35
36
|
/**
|
|
36
37
|
* Locate the index of a named argument slot in a signature schema (the source of
|
|
@@ -90,7 +91,7 @@ export const makeForEach = function ( api, flowState ) {
|
|
|
90
91
|
// rather than emit a silently-wrong per-field controller.
|
|
91
92
|
if ( pattern === SIGNATURE_PATTERNS.nameLogic ) {
|
|
92
93
|
throw new Error(
|
|
93
|
-
`
|
|
94
|
+
`winkComposer/flow: forEach cannot fan a controller ('${args[ 0 ]}'); ` +
|
|
94
95
|
'fan data-processing nodes only.'
|
|
95
96
|
);
|
|
96
97
|
}
|
|
@@ -109,7 +110,7 @@ export const makeForEach = function ( api, flowState ) {
|
|
|
109
110
|
// Node-name collision check before building stats (matches make-node-method),
|
|
110
111
|
// so a rejected node never registers its output params in the global checker.
|
|
111
112
|
if ( isNodeNameDuplicate( t[ 0 ] ) ) {
|
|
112
|
-
throw new Error( `
|
|
113
|
+
throw new Error( `winkComposer/flow: duplicate node ${t[ 0 ]} found.` );
|
|
113
114
|
}
|
|
114
115
|
|
|
115
116
|
// Outputs: store each labelled output as `${field}_${label}`, and record the
|
|
@@ -121,8 +122,8 @@ export const makeForEach = function ( api, flowState ) {
|
|
|
121
122
|
for ( let i = 0; i < outKeys.length; i += 1 ) {
|
|
122
123
|
const label = rawOutputs[ outKeys[ i ] ];
|
|
123
124
|
if ( Object.prototype.hasOwnProperty.call( recorded, label ) ) {
|
|
124
|
-
|
|
125
|
-
`
|
|
125
|
+
logger.warn(
|
|
126
|
+
`winkComposer/flow: forEach output label '${label}' written by ` +
|
|
126
127
|
`more than one step on field '${field}'; each.out will return the last.`
|
|
127
128
|
);
|
|
128
129
|
}
|
|
@@ -146,7 +147,7 @@ export const makeForEach = function ( api, flowState ) {
|
|
|
146
147
|
targetArray.push( spec );
|
|
147
148
|
} catch ( error ) {
|
|
148
149
|
throw new Error(
|
|
149
|
-
`
|
|
150
|
+
`winkComposer/flow: Failed to process "${callContext}"\n` +
|
|
150
151
|
` Reason: ${error.message}\n`
|
|
151
152
|
);
|
|
152
153
|
}
|
|
@@ -170,7 +171,7 @@ export const makeForEach = function ( api, flowState ) {
|
|
|
170
171
|
each.out = function ( label ) {
|
|
171
172
|
if ( !Object.prototype.hasOwnProperty.call( recorded, label ) ) {
|
|
172
173
|
throw new Error(
|
|
173
|
-
`
|
|
174
|
+
`winkComposer/flow: each.out( '${label}' ) on field '${field}': ` +
|
|
174
175
|
`no earlier step in this chain wrote an output labelled '${label}'.`
|
|
175
176
|
);
|
|
176
177
|
}
|
|
@@ -191,25 +192,25 @@ export const makeForEach = function ( api, flowState ) {
|
|
|
191
192
|
// that only resolves at .endGroup(), so the callback cannot run where written.
|
|
192
193
|
if ( groupByState.active ) {
|
|
193
194
|
throw new Error(
|
|
194
|
-
'
|
|
195
|
+
'winkComposer/flow: forEach inside groupBy is not supported yet; ' +
|
|
195
196
|
'the field list must be a concrete array at the call site.'
|
|
196
197
|
);
|
|
197
198
|
}
|
|
198
199
|
if ( !Array.isArray( fields ) || fields.length === 0 ) {
|
|
199
|
-
throw new Error( '
|
|
200
|
+
throw new Error( 'winkComposer/flow: forEach requires a non-empty array of field names.' );
|
|
200
201
|
}
|
|
201
202
|
if ( typeof callback !== 'function' ) {
|
|
202
|
-
throw new Error( '
|
|
203
|
+
throw new Error( 'winkComposer/flow: forEach requires a callback function.' );
|
|
203
204
|
}
|
|
204
205
|
|
|
205
206
|
const seen = Object.create( null );
|
|
206
207
|
for ( let i = 0; i < fields.length; i += 1 ) {
|
|
207
208
|
const field = fields[ i ];
|
|
208
209
|
if ( typeof field !== 'string' || field.length === 0 ) {
|
|
209
|
-
throw new Error( '
|
|
210
|
+
throw new Error( 'winkComposer/flow: forEach field names must be non-empty strings.' );
|
|
210
211
|
}
|
|
211
212
|
if ( Object.prototype.hasOwnProperty.call( seen, field ) ) {
|
|
212
|
-
throw new Error( `
|
|
213
|
+
throw new Error( `winkComposer/flow: forEach duplicate field '${field}' in field list.` );
|
|
213
214
|
}
|
|
214
215
|
seen[ field ] = 1;
|
|
215
216
|
}
|
|
@@ -29,13 +29,13 @@ export const getTargetArray = function ( switchState, groupByState, flowDefiniti
|
|
|
29
29
|
if ( switchState && switchState.active ) {
|
|
30
30
|
if ( switchState.currentCase === null ) {
|
|
31
31
|
throw new Error(
|
|
32
|
-
'
|
|
32
|
+
'winkComposer/flow: node called outside of a .case() block - ' +
|
|
33
33
|
'after .switch(), you must call .case() before adding nodes'
|
|
34
34
|
);
|
|
35
35
|
}
|
|
36
36
|
if ( switchState.caseEnded ) {
|
|
37
37
|
throw new Error(
|
|
38
|
-
'
|
|
38
|
+
'winkComposer/flow: node called after .break() - ' +
|
|
39
39
|
'start a new .case() before adding more nodes'
|
|
40
40
|
);
|
|
41
41
|
}
|
|
@@ -31,7 +31,7 @@ const moduleCache = new Map();
|
|
|
31
31
|
export const loadNodeModule = async function ( nodeName ) {
|
|
32
32
|
// Validate node name
|
|
33
33
|
if ( !isValidNodeName( nodeName ) ) {
|
|
34
|
-
throw new Error( `Unknown node type: "${nodeName}"` );
|
|
34
|
+
throw new Error( `winkComposer/flow: Unknown node type: "${nodeName}"` );
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
// Return cached module if available
|
|
@@ -8,7 +8,7 @@ import { specBuilder } from './spec-builder.js';
|
|
|
8
8
|
import { getTargetArray } from './get-target-array.js';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
* Creates a chainable DSL method for a
|
|
11
|
+
* Creates a chainable DSL method for a winkComposer node.
|
|
12
12
|
*
|
|
13
13
|
* @param {Object} api - The fluent API object to extend
|
|
14
14
|
* @param {string} node - Node type name (e.g., 'esMean')
|
|
@@ -79,9 +79,9 @@ export const makeNodeMethod = function ( api, node, meta, flowState ) {
|
|
|
79
79
|
} else {
|
|
80
80
|
// Canonical builder path
|
|
81
81
|
const instName = args[ 0 ];
|
|
82
|
-
if ( effectiveNodeChecker( instName ) ) throw Error( `
|
|
82
|
+
if ( effectiveNodeChecker( instName ) ) throw Error( `winkComposer/flow: duplicate node ${instName} found.` );
|
|
83
83
|
const pattern = getSignaturePattern( schema );
|
|
84
|
-
if ( pattern === SIGNATURE_PATTERNS.unknown ) throw Error( `
|
|
84
|
+
if ( pattern === SIGNATURE_PATTERNS.unknown ) throw Error( `winkComposer/flow: unknown signature pattern found for ${instName}.` );
|
|
85
85
|
const status = validateWithSchema( schemas[ pattern ], [ ...args ], 'Arg' );
|
|
86
86
|
status.throwIfInvalid( 'argument' );
|
|
87
87
|
built = buildSpecFor[ pattern ]( [ ...args ] );
|
|
@@ -101,7 +101,7 @@ export const makeNodeMethod = function ( api, node, meta, flowState ) {
|
|
|
101
101
|
}
|
|
102
102
|
} catch ( error ) {
|
|
103
103
|
const enhancedError = new Error(
|
|
104
|
-
`
|
|
104
|
+
`winkComposer/flow: Failed to process "${callContext}"\n` +
|
|
105
105
|
` Reason: ${error.message}\n`
|
|
106
106
|
);
|
|
107
107
|
|
|
@@ -35,7 +35,7 @@ export const resolvePickByField = function ( value, field ) {
|
|
|
35
35
|
const map = value.semantics.map;
|
|
36
36
|
if ( !Object.prototype.hasOwnProperty.call( map, field ) ) {
|
|
37
37
|
throw new Error(
|
|
38
|
-
`
|
|
38
|
+
`winkComposer/flow: pickByField has no entry for field '${field}' ` +
|
|
39
39
|
`(map keys: ${Object.keys( map ).join( ', ' )}).`
|
|
40
40
|
);
|
|
41
41
|
}
|