sygnal 2.6.8 → 2.7.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/dist/index.cjs.js +195 -26
- package/dist/index.esm.js +195 -27
- package/dist/sygnal.min.js +1 -1
- package/package.json +1 -1
package/dist/index.cjs.js
CHANGED
|
@@ -3150,7 +3150,6 @@ const INITIALIZE_ACTION = 'INITIALIZE';
|
|
|
3150
3150
|
const HYDRATE_ACTION = 'HYDRATE';
|
|
3151
3151
|
|
|
3152
3152
|
|
|
3153
|
-
let IS_ROOT_COMPONENT = true;
|
|
3154
3153
|
let COMPONENT_COUNT = 0;
|
|
3155
3154
|
|
|
3156
3155
|
|
|
@@ -3211,6 +3210,7 @@ class Component {
|
|
|
3211
3210
|
// DOMSourceName
|
|
3212
3211
|
// stateSourceName
|
|
3213
3212
|
// requestSourceName
|
|
3213
|
+
// debug
|
|
3214
3214
|
|
|
3215
3215
|
// [ PRIVATE / CALCULATED VALUES ]
|
|
3216
3216
|
// sourceNames
|
|
@@ -3224,6 +3224,7 @@ class Component {
|
|
|
3224
3224
|
// subComponentSink$
|
|
3225
3225
|
// unmountRequest$
|
|
3226
3226
|
// unmount()
|
|
3227
|
+
// stateCache
|
|
3227
3228
|
|
|
3228
3229
|
// [ INSTANTIATED STREAM OPERATOR ]
|
|
3229
3230
|
// log
|
|
@@ -3231,7 +3232,7 @@ class Component {
|
|
|
3231
3232
|
// [ OUTPUT ]
|
|
3232
3233
|
// sinks
|
|
3233
3234
|
|
|
3234
|
-
constructor({ name='NO NAME', sources, intent, request, model, response, view, children={}, components={}, initialState, calculated, storeCalculatedInState=true, DOMSourceName='DOM', stateSourceName='STATE', requestSourceName='HTTP' }) {
|
|
3235
|
+
constructor({ name='NO NAME', sources, intent, request, model, response, view, children={}, components={}, initialState, calculated, storeCalculatedInState=true, DOMSourceName='DOM', stateSourceName='STATE', requestSourceName='HTTP', debug=false }) {
|
|
3235
3236
|
if (!sources || typeof sources != 'object') throw new Error('Missing or invalid sources')
|
|
3236
3237
|
|
|
3237
3238
|
this.name = name;
|
|
@@ -3250,6 +3251,7 @@ class Component {
|
|
|
3250
3251
|
this.stateSourceName = stateSourceName;
|
|
3251
3252
|
this.requestSourceName = requestSourceName;
|
|
3252
3253
|
this.sourceNames = Object.keys(sources);
|
|
3254
|
+
this._debug = debug;
|
|
3253
3255
|
|
|
3254
3256
|
this.isSubComponent = this.sourceNames.includes('props$');
|
|
3255
3257
|
|
|
@@ -3263,17 +3265,17 @@ class Component {
|
|
|
3263
3265
|
}));
|
|
3264
3266
|
}
|
|
3265
3267
|
|
|
3266
|
-
//
|
|
3267
|
-
//
|
|
3268
|
-
if (
|
|
3268
|
+
// Ensure that the root component has an intent and model
|
|
3269
|
+
// This is necessary to ensure that the component tree's state sink is subscribed to
|
|
3270
|
+
if (!this.isSubComponent && typeof this.intent === 'undefined' && typeof this.model === 'undefined') {
|
|
3269
3271
|
this.initialState = initialState || true;
|
|
3270
3272
|
this.intent = _ => ({__NOOP_ACTION__:xs$1.never()});
|
|
3271
3273
|
this.model = {
|
|
3272
3274
|
__NOOP_ACTION__: state => state
|
|
3273
3275
|
};
|
|
3274
3276
|
}
|
|
3275
|
-
IS_ROOT_COMPONENT = false;
|
|
3276
3277
|
|
|
3278
|
+
this.addCalculated = this.createMemoizedAddCalculated();
|
|
3277
3279
|
this.log = makeLog(name);
|
|
3278
3280
|
|
|
3279
3281
|
this.initIntent$();
|
|
@@ -3290,11 +3292,15 @@ class Component {
|
|
|
3290
3292
|
|
|
3291
3293
|
this.sinks.__index = COMPONENT_COUNT++;
|
|
3292
3294
|
|
|
3293
|
-
if (
|
|
3295
|
+
if (debug) {
|
|
3294
3296
|
console.log(`[${ this.name }] Instantiated (#${ this.sinks.__index })`);
|
|
3295
3297
|
}
|
|
3296
3298
|
}
|
|
3297
3299
|
|
|
3300
|
+
get debug() {
|
|
3301
|
+
return this._debug || (ENVIRONMENT.DEBUG === 'true' || ENVIRONMENT.DEBUG === true)
|
|
3302
|
+
}
|
|
3303
|
+
|
|
3298
3304
|
initIntent$() {
|
|
3299
3305
|
if (!this.intent) {
|
|
3300
3306
|
return
|
|
@@ -3392,7 +3398,7 @@ class Component {
|
|
|
3392
3398
|
|
|
3393
3399
|
console.log(`${ timestamp } ${ ip } ${ req.method } ${ req.url }`);
|
|
3394
3400
|
|
|
3395
|
-
if (
|
|
3401
|
+
if (this.debug) {
|
|
3396
3402
|
this.action$.setDebugListener({next: ({ type }) => console.log(`[${ this.name }] Action from ${ this.requestSourceName } request: <${ type }>`)});
|
|
3397
3403
|
}
|
|
3398
3404
|
|
|
@@ -3698,20 +3704,39 @@ class Component {
|
|
|
3698
3704
|
}
|
|
3699
3705
|
}
|
|
3700
3706
|
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
if (typeof
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
} catch(e) {
|
|
3710
|
-
console.warn(`Calculated field '${ field }' threw an error during calculation: ${ e.message }`);
|
|
3707
|
+
createMemoizedAddCalculated() {
|
|
3708
|
+
let lastState;
|
|
3709
|
+
let lastResult;
|
|
3710
|
+
|
|
3711
|
+
return function(state) {
|
|
3712
|
+
if (!this.calculated || typeof state !== 'object' || state instanceof Array) return state
|
|
3713
|
+
if (state === lastState) {
|
|
3714
|
+
return lastResult
|
|
3711
3715
|
}
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3716
|
+
if (typeof this.calculated !== 'object') throw new Error(`'calculated' parameter must be an object mapping calculated state field named to functions`)
|
|
3717
|
+
const entries = Object.entries(this.calculated);
|
|
3718
|
+
if (entries.length === 0) {
|
|
3719
|
+
lastState = state;
|
|
3720
|
+
lastResult = state;
|
|
3721
|
+
return state
|
|
3722
|
+
}
|
|
3723
|
+
const calculated = entries.reduce((acc, [field, fn]) => {
|
|
3724
|
+
if (typeof fn !== 'function') throw new Error(`Missing or invalid calculator function for calculated field '${ field }`)
|
|
3725
|
+
try {
|
|
3726
|
+
acc[field] = fn(state);
|
|
3727
|
+
} catch(e) {
|
|
3728
|
+
console.warn(`Calculated field '${ field }' threw an error during calculation: ${ e.message }`);
|
|
3729
|
+
}
|
|
3730
|
+
return acc
|
|
3731
|
+
}, {});
|
|
3732
|
+
|
|
3733
|
+
const newState = { ...state, ...calculated };
|
|
3734
|
+
|
|
3735
|
+
lastState = state;
|
|
3736
|
+
lastResult = newState;
|
|
3737
|
+
|
|
3738
|
+
return newState
|
|
3739
|
+
}
|
|
3715
3740
|
}
|
|
3716
3741
|
|
|
3717
3742
|
cleanupCalculated(incomingState) {
|
|
@@ -3738,15 +3763,33 @@ class Component {
|
|
|
3738
3763
|
const enhancedState = state && state.isolateSource(state, { get: state => this.addCalculated(state) });
|
|
3739
3764
|
const stateStream = (enhancedState && enhancedState.stream) || xs$1.never();
|
|
3740
3765
|
|
|
3741
|
-
|
|
3742
|
-
|
|
3766
|
+
const objRepeatChecker = (a, b) => {
|
|
3767
|
+
const { state, sygnalFactory, __props, __children, ...sanitized } = a;
|
|
3768
|
+
const keys = Object.keys(sanitized);
|
|
3769
|
+
if (keys.length === 0) {
|
|
3770
|
+
const { state, sygnalFactory, __props, __children, ...sanitizedB } = b;
|
|
3771
|
+
return Object.keys(sanitizedB).length === 0
|
|
3772
|
+
}
|
|
3773
|
+
return keys.every(key => a[key] === b[key])
|
|
3774
|
+
};
|
|
3775
|
+
|
|
3776
|
+
const arrRepeatChecker = (a, b) => {
|
|
3777
|
+
if (a === b) return true
|
|
3778
|
+
if (a.length !== b.length) return false
|
|
3779
|
+
for (let i=0; i < a.length; i++) {
|
|
3780
|
+
if (a[i] !== b[i]) return false
|
|
3781
|
+
}
|
|
3782
|
+
return true
|
|
3783
|
+
};
|
|
3784
|
+
|
|
3785
|
+
renderParams.state = stateStream.compose(_default$5(objRepeatChecker));
|
|
3743
3786
|
|
|
3744
3787
|
if (this.sources.props$) {
|
|
3745
|
-
renderParams.__props = this.sources.props
|
|
3788
|
+
renderParams.__props = this.sources.props$.compose(_default$5(objRepeatChecker));
|
|
3746
3789
|
}
|
|
3747
3790
|
|
|
3748
3791
|
if (this.sources.children$) {
|
|
3749
|
-
renderParams.__children = this.sources.children
|
|
3792
|
+
renderParams.__children = this.sources.children$.compose(_default$5(arrRepeatChecker));
|
|
3750
3793
|
}
|
|
3751
3794
|
|
|
3752
3795
|
const names = [];
|
|
@@ -3762,6 +3805,7 @@ class Component {
|
|
|
3762
3805
|
.map(arr => {
|
|
3763
3806
|
return names.reduce((acc, name, index) => {
|
|
3764
3807
|
acc[name] = arr[index];
|
|
3808
|
+
if (name === 'state') acc[this.stateSourceName] = arr[index];
|
|
3765
3809
|
return acc
|
|
3766
3810
|
}, {})
|
|
3767
3811
|
});
|
|
@@ -3770,6 +3814,7 @@ class Component {
|
|
|
3770
3814
|
}
|
|
3771
3815
|
|
|
3772
3816
|
instantiateSubComponents(vDom$) {
|
|
3817
|
+
let count = 0;
|
|
3773
3818
|
return vDom$.fold((previousComponents, vDom) => {
|
|
3774
3819
|
const componentNames = Object.keys(this.components);
|
|
3775
3820
|
const foundComponents = getComponents(vDom, componentNames);
|
|
@@ -3800,6 +3845,7 @@ class Component {
|
|
|
3800
3845
|
|
|
3801
3846
|
|
|
3802
3847
|
if (previousComponents[id]) {
|
|
3848
|
+
if (this.debug) console.log(this.name, 'sameful', count++);
|
|
3803
3849
|
const entry = previousComponents[id];
|
|
3804
3850
|
acc[id] = entry;
|
|
3805
3851
|
entry.props$.shamefullySendNext(props);
|
|
@@ -3808,6 +3854,8 @@ class Component {
|
|
|
3808
3854
|
return acc
|
|
3809
3855
|
}
|
|
3810
3856
|
|
|
3857
|
+
if (this.debug) console.log(this.name, 'non-shameful', count++);
|
|
3858
|
+
|
|
3811
3859
|
const props$ = xs$1.create().startWith(props);
|
|
3812
3860
|
const children$ = xs$1.create().startWith(children);
|
|
3813
3861
|
|
|
@@ -3871,6 +3919,7 @@ class Component {
|
|
|
3871
3919
|
|
|
3872
3920
|
const stateSource = new state.StateSource(combined$);
|
|
3873
3921
|
const stateField = props.from;
|
|
3922
|
+
props.filter;
|
|
3874
3923
|
let lense;
|
|
3875
3924
|
|
|
3876
3925
|
const factory = typeof props.of === 'function' ? props.of : this.components[props.of];
|
|
@@ -4164,7 +4213,7 @@ class Component {
|
|
|
4164
4213
|
const fixedMsg = (typeof msg === 'function') ? msg : _ => msg;
|
|
4165
4214
|
return stream => {
|
|
4166
4215
|
return stream.debug(msg => {
|
|
4167
|
-
if (
|
|
4216
|
+
if (this.debug) {
|
|
4168
4217
|
console.log(`[${context}] ${fixedMsg(msg)}`);
|
|
4169
4218
|
}
|
|
4170
4219
|
})
|
|
@@ -4280,6 +4329,125 @@ function deepCopyVdom(obj) {
|
|
|
4280
4329
|
return { ...obj, children: Array.isArray(obj.children) ? obj.children.map(deepCopyVdom) : undefined, data: obj.data && { ...obj.data, componentsInjected: false } }
|
|
4281
4330
|
}
|
|
4282
4331
|
|
|
4332
|
+
function driverFromAsync(promiseReturningFunction, opts = {}) {
|
|
4333
|
+
const {
|
|
4334
|
+
selector: selectorProperty = 'category',
|
|
4335
|
+
args: functionArgs = 'value',
|
|
4336
|
+
return: returnProperty = 'value',
|
|
4337
|
+
pre: preFunction = (val) => val,
|
|
4338
|
+
post: postFunction = (val) => val
|
|
4339
|
+
} = opts;
|
|
4340
|
+
|
|
4341
|
+
const functionName = promiseReturningFunction.name || '[anonymous function]';
|
|
4342
|
+
const functionArgsType = typeof functionArgs;
|
|
4343
|
+
if (functionArgsType !== 'string' && functionArgsType !== 'function' && !(Array.isArray(functionArgs) && functionArgs.every((arg) => typeof arg === 'string'))) {
|
|
4344
|
+
throw new Error(`The 'args' option for driverFromAsync(${ functionName }) must be a string, array of strings, or a function. Received ${functionArgsType}`)
|
|
4345
|
+
}
|
|
4346
|
+
|
|
4347
|
+
if (typeof selectorProperty !== 'string') {
|
|
4348
|
+
throw new Error(`The 'selector' option for driverFromAsync(${ functionName }) must be a string. Received ${typeof selectorProperty}`)
|
|
4349
|
+
}
|
|
4350
|
+
|
|
4351
|
+
return (fromApp$) => {
|
|
4352
|
+
let sendFn = null;
|
|
4353
|
+
|
|
4354
|
+
const toApp$ = xs$1.create({
|
|
4355
|
+
start: (listener) => {
|
|
4356
|
+
sendFn = listener.next.bind(listener);
|
|
4357
|
+
},
|
|
4358
|
+
stop: () => {}
|
|
4359
|
+
});
|
|
4360
|
+
|
|
4361
|
+
fromApp$.addListener({
|
|
4362
|
+
next: (incoming) => {
|
|
4363
|
+
const preProcessed = preFunction(incoming);
|
|
4364
|
+
let argArr = [];
|
|
4365
|
+
if (typeof preProcessed === 'object' && preProcessed !== null) {
|
|
4366
|
+
if (typeof functionArgs === 'function') {
|
|
4367
|
+
const extractedArgs = functionArgs(preProcessed);
|
|
4368
|
+
argArr = Array.isArray(extractedArgs) ? extractedArgs : [extractedArgs];
|
|
4369
|
+
}
|
|
4370
|
+
if (typeof functionArgs === 'string') {
|
|
4371
|
+
argArr = [preProcessed[functionArgs]];
|
|
4372
|
+
}
|
|
4373
|
+
if (Array.isArray(functionArgs)) {
|
|
4374
|
+
argArr = functionArgs.map((arg) => preProcessed[arg]);
|
|
4375
|
+
}
|
|
4376
|
+
}
|
|
4377
|
+
const errMsg = `Error in driver created using driverFromAsync(${ functionName })`;
|
|
4378
|
+
promiseReturningFunction(...argArr)
|
|
4379
|
+
.then((innerVal) => {
|
|
4380
|
+
const constructReply = (rawVal) => {
|
|
4381
|
+
let outgoing;
|
|
4382
|
+
if (returnProperty === undefined) {
|
|
4383
|
+
outgoing = rawVal;
|
|
4384
|
+
if (typeof outgoing === 'object' && outgoing !== null) {
|
|
4385
|
+
outgoing[selectorProperty] = incoming[selectorProperty];
|
|
4386
|
+
} else {
|
|
4387
|
+
console.warn(`The 'return' option for driverFromAsync(${ functionName }) was not set, but the promise returned an non-object. The result will be returned as-is, but the '${selectorProperty}' property will not be set, so will not be filtered by the 'select' method of the driver.`);
|
|
4388
|
+
}
|
|
4389
|
+
} else if (typeof returnProperty === 'string') {
|
|
4390
|
+
outgoing = {
|
|
4391
|
+
[returnProperty]: rawVal,
|
|
4392
|
+
[selectorProperty]: incoming[selectorProperty]
|
|
4393
|
+
};
|
|
4394
|
+
} else {
|
|
4395
|
+
throw new Error(`The 'return' option for driverFromAsync(${ functionName }) must be a string. Received ${typeof returnProperty}`)
|
|
4396
|
+
}
|
|
4397
|
+
return outgoing
|
|
4398
|
+
};
|
|
4399
|
+
|
|
4400
|
+
|
|
4401
|
+
// handle nested promises and promises returned by postFunction
|
|
4402
|
+
if (typeof innerVal.then === 'function') {
|
|
4403
|
+
innerVal
|
|
4404
|
+
.then((innerOutgoing) => {
|
|
4405
|
+
const processedOutgoing = postFunction(innerOutgoing, incoming);
|
|
4406
|
+
if (typeof processedOutgoing.then === 'function') {
|
|
4407
|
+
processedOutgoing
|
|
4408
|
+
.then((innerProcessedOutgoing) => {
|
|
4409
|
+
sendFn(constructReply(innerProcessedOutgoing));
|
|
4410
|
+
})
|
|
4411
|
+
.catch((err) => console.error(`${ errMsg }: ${ err }`));
|
|
4412
|
+
} else {
|
|
4413
|
+
sendFn(constructReply(rocessedOutgoing));
|
|
4414
|
+
}
|
|
4415
|
+
})
|
|
4416
|
+
.catch((err) => console.error(`${ errMsg }: ${ err }`));
|
|
4417
|
+
} else {
|
|
4418
|
+
const processedOutgoing = postFunction(innerVal, incoming);
|
|
4419
|
+
if (typeof processedOutgoing.then === 'function') {
|
|
4420
|
+
processedOutgoing
|
|
4421
|
+
.then((innerProcessedOutgoing) => {
|
|
4422
|
+
sendFn(constructReply(innerProcessedOutgoing));
|
|
4423
|
+
})
|
|
4424
|
+
.catch((err) => console.error(`${ errMsg }: ${ err }`));
|
|
4425
|
+
} else {
|
|
4426
|
+
sendFn(constructReply(processedOutgoing));
|
|
4427
|
+
}
|
|
4428
|
+
}
|
|
4429
|
+
})
|
|
4430
|
+
.catch((err) => console.error(`${ errMsg }: ${ err }`));
|
|
4431
|
+
},
|
|
4432
|
+
error: (err) => {
|
|
4433
|
+
console.error(`Error recieved from sink stream in driver created using driverFromAsync(${ functionName }): ${ err }`);
|
|
4434
|
+
},
|
|
4435
|
+
complete: () => {
|
|
4436
|
+
console.warn(`Unexpected completion of sink stream to driver created using driverFromAsync(${ functionName })`);
|
|
4437
|
+
}
|
|
4438
|
+
});
|
|
4439
|
+
|
|
4440
|
+
return {
|
|
4441
|
+
select: (selector) => {
|
|
4442
|
+
if (selector === undefined) return toApp$
|
|
4443
|
+
if (typeof selector === 'function') return toApp$.filter(selector)
|
|
4444
|
+
return toApp$.filter((val) => val?.[selectorProperty] === selector)
|
|
4445
|
+
}
|
|
4446
|
+
}
|
|
4447
|
+
|
|
4448
|
+
}
|
|
4449
|
+
}
|
|
4450
|
+
|
|
4283
4451
|
function processForm(form, options={}) {
|
|
4284
4452
|
let { events = ['input', 'submit'], preventDefault = true } = options;
|
|
4285
4453
|
if (typeof events === 'string') events = [events];
|
|
@@ -4759,6 +4927,7 @@ exports.collection = collection;
|
|
|
4759
4927
|
exports.component = component;
|
|
4760
4928
|
exports.debounce = _default$2;
|
|
4761
4929
|
exports.delay = _default$4;
|
|
4930
|
+
exports.driverFromAsync = driverFromAsync;
|
|
4762
4931
|
exports.dropRepeats = _default$5;
|
|
4763
4932
|
exports.processForm = processForm;
|
|
4764
4933
|
exports.run = run;
|