sygnal 5.2.1 → 5.3.1
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/README.md +20 -1
- package/dist/astro/client.cjs.js +242 -12
- package/dist/astro/client.mjs +242 -12
- package/dist/index.cjs.js +437 -15
- package/dist/index.d.ts +29 -0
- package/dist/index.esm.js +435 -16
- package/dist/sygnal.min.js +1 -1
- package/package.json +1 -1
- package/src/component.ts +34 -4
- package/src/extra/command.ts +13 -2
- package/src/extra/devtools.ts +221 -7
- package/src/extra/dragDriver.ts +59 -5
- package/src/extra/eventDriver.ts +6 -2
- package/src/extra/pwa.ts +181 -0
- package/src/index.d.ts +29 -0
- package/src/index.ts +1 -0
package/dist/astro/client.mjs
CHANGED
|
@@ -5027,7 +5027,12 @@ function makeDOMDriver(container, options = {}) {
|
|
|
5027
5027
|
function eventBusDriver(out$) {
|
|
5028
5028
|
const events = new EventTarget();
|
|
5029
5029
|
out$.subscribe({
|
|
5030
|
-
next: (event) =>
|
|
5030
|
+
next: (event) => {
|
|
5031
|
+
events.dispatchEvent(new CustomEvent('data', { detail: event }));
|
|
5032
|
+
if (typeof window !== 'undefined' && window.__SYGNAL_DEVTOOLS__?.connected) {
|
|
5033
|
+
window.__SYGNAL_DEVTOOLS__.onBusEvent(event);
|
|
5034
|
+
}
|
|
5035
|
+
},
|
|
5031
5036
|
error: (err) => console.error('[EVENTS driver] Error in sink stream:', err),
|
|
5032
5037
|
});
|
|
5033
5038
|
return {
|
|
@@ -5681,6 +5686,9 @@ class Component {
|
|
|
5681
5686
|
this.sources.props$ = props$.map((val) => {
|
|
5682
5687
|
const { sygnalFactory, sygnalOptions, ...sanitizedProps } = val;
|
|
5683
5688
|
this.currentProps = sanitizedProps;
|
|
5689
|
+
if (typeof window !== 'undefined' && window.__SYGNAL_DEVTOOLS__?.connected) {
|
|
5690
|
+
window.__SYGNAL_DEVTOOLS__.onPropsChanged(this._componentNumber, this.name, sanitizedProps);
|
|
5691
|
+
}
|
|
5684
5692
|
return val;
|
|
5685
5693
|
});
|
|
5686
5694
|
}
|
|
@@ -5752,6 +5760,9 @@ class Component {
|
|
|
5752
5760
|
}
|
|
5753
5761
|
}
|
|
5754
5762
|
dispose() {
|
|
5763
|
+
if (typeof window !== 'undefined' && window.__SYGNAL_DEVTOOLS__?.connected) {
|
|
5764
|
+
window.__SYGNAL_DEVTOOLS__.onComponentDisposed(this._componentNumber, this.name);
|
|
5765
|
+
}
|
|
5755
5766
|
// Fire the DISPOSE built-in action so model handlers can run cleanup logic
|
|
5756
5767
|
const hasDispose = this.model && (this.model[DISPOSE_ACTION] || Object.keys(this.model).some(k => k.includes('|') && k.split('|')[0].trim() === DISPOSE_ACTION));
|
|
5757
5768
|
if (hasDispose && this.action$ && typeof this.action$.shamefullySendNext === 'function') {
|
|
@@ -6149,6 +6160,12 @@ class Component {
|
|
|
6149
6160
|
else {
|
|
6150
6161
|
acc[name] = xs.merge((this.model$[name] || xs.never()), subComponentSink$, ...(this.peers$[name] || []));
|
|
6151
6162
|
}
|
|
6163
|
+
// Stamp EVENTS sink emissions with emitter component info for devtools
|
|
6164
|
+
if (name === 'EVENTS' && acc[name]) {
|
|
6165
|
+
const _componentNumber = this._componentNumber;
|
|
6166
|
+
const _name = this.name;
|
|
6167
|
+
acc[name] = acc[name].map((ev) => ({ ...ev, __emitterId: _componentNumber, __emitterName: _name }));
|
|
6168
|
+
}
|
|
6152
6169
|
return acc;
|
|
6153
6170
|
}, {});
|
|
6154
6171
|
this.sinks[this.DOMSourceName] = this.vdom$;
|
|
@@ -6671,6 +6688,13 @@ class Component {
|
|
|
6671
6688
|
if (!isObj(sink$)) {
|
|
6672
6689
|
throw new Error(`[${this.name}] Invalid sinks returned from component factory of collection element`);
|
|
6673
6690
|
}
|
|
6691
|
+
// Notify devtools of collection mount
|
|
6692
|
+
if (typeof window !== 'undefined' && window.__SYGNAL_DEVTOOLS__?.connected) {
|
|
6693
|
+
const itemName = typeof collectionOf === 'function'
|
|
6694
|
+
? (collectionOf.componentName || collectionOf.label || collectionOf.name || 'anonymous')
|
|
6695
|
+
: String(collectionOf);
|
|
6696
|
+
window.__SYGNAL_DEVTOOLS__.onCollectionMounted(this._componentNumber, this.name, itemName, typeof stateField === 'string' ? stateField : null);
|
|
6697
|
+
}
|
|
6674
6698
|
return sink$;
|
|
6675
6699
|
}
|
|
6676
6700
|
instantiateSwitchable(el, props$, children$) {
|
|
@@ -6809,6 +6833,7 @@ class Component {
|
|
|
6809
6833
|
for (const key of Object.keys(props)) {
|
|
6810
6834
|
const val = props[key];
|
|
6811
6835
|
if (val && val.__sygnalCommand) {
|
|
6836
|
+
val._targetComponentName = componentName;
|
|
6812
6837
|
sources.commands$ = makeCommandSource(val);
|
|
6813
6838
|
break;
|
|
6814
6839
|
}
|
|
@@ -6851,10 +6876,15 @@ class Component {
|
|
|
6851
6876
|
const wasReady = this._childReadyState[id];
|
|
6852
6877
|
this._childReadyState[id] = !!ready;
|
|
6853
6878
|
// When READY state changes, trigger a re-render
|
|
6854
|
-
if (wasReady !== !!ready
|
|
6855
|
-
|
|
6856
|
-
|
|
6857
|
-
|
|
6879
|
+
if (wasReady !== !!ready) {
|
|
6880
|
+
if (this._readyChangedListener) {
|
|
6881
|
+
setTimeout(() => {
|
|
6882
|
+
this._readyChangedListener?.next(null);
|
|
6883
|
+
}, 0);
|
|
6884
|
+
}
|
|
6885
|
+
if (typeof window !== 'undefined' && window.__SYGNAL_DEVTOOLS__?.connected) {
|
|
6886
|
+
window.__SYGNAL_DEVTOOLS__.onReadyChanged(this._componentNumber, this.name, id, !!ready);
|
|
6887
|
+
}
|
|
6858
6888
|
}
|
|
6859
6889
|
},
|
|
6860
6890
|
error: () => { },
|
|
@@ -7490,6 +7520,12 @@ class SygnalDevTools {
|
|
|
7490
7520
|
case 'TIME_TRAVEL':
|
|
7491
7521
|
this._timeTravel(msg.payload);
|
|
7492
7522
|
break;
|
|
7523
|
+
case 'SNAPSHOT':
|
|
7524
|
+
this._takeSnapshot();
|
|
7525
|
+
break;
|
|
7526
|
+
case 'RESTORE_SNAPSHOT':
|
|
7527
|
+
this._restoreSnapshot(msg.payload);
|
|
7528
|
+
break;
|
|
7493
7529
|
case 'GET_STATE':
|
|
7494
7530
|
this._sendComponentState(msg.payload.componentId);
|
|
7495
7531
|
break;
|
|
@@ -7509,6 +7545,7 @@ class SygnalDevTools {
|
|
|
7509
7545
|
children: [],
|
|
7510
7546
|
debug: instance._debug,
|
|
7511
7547
|
createdAt: Date.now(),
|
|
7548
|
+
mviGraph: this._extractMviGraph(instance),
|
|
7512
7549
|
_instanceRef: new WeakRef(instance),
|
|
7513
7550
|
};
|
|
7514
7551
|
this._components.set(componentNumber, meta);
|
|
@@ -7533,7 +7570,6 @@ class SygnalDevTools {
|
|
|
7533
7570
|
componentId: componentNumber,
|
|
7534
7571
|
componentName: name,
|
|
7535
7572
|
state: entry.state,
|
|
7536
|
-
historyIndex: this._stateHistory.length - 1,
|
|
7537
7573
|
});
|
|
7538
7574
|
}
|
|
7539
7575
|
onActionDispatched(componentNumber, name, actionType, data) {
|
|
@@ -7570,6 +7606,76 @@ class SygnalDevTools {
|
|
|
7570
7606
|
componentId: componentNumber,
|
|
7571
7607
|
componentName: name,
|
|
7572
7608
|
context: this._safeClone(context),
|
|
7609
|
+
contextTrace: this._buildContextTrace(componentNumber, context),
|
|
7610
|
+
});
|
|
7611
|
+
}
|
|
7612
|
+
onPropsChanged(componentNumber, name, props) {
|
|
7613
|
+
if (!this.connected)
|
|
7614
|
+
return;
|
|
7615
|
+
this._post('PROPS_CHANGED', {
|
|
7616
|
+
componentId: componentNumber,
|
|
7617
|
+
componentName: name,
|
|
7618
|
+
props: this._safeClone(props),
|
|
7619
|
+
});
|
|
7620
|
+
}
|
|
7621
|
+
onBusEvent(event) {
|
|
7622
|
+
if (!this.connected)
|
|
7623
|
+
return;
|
|
7624
|
+
this._post('BUS_EVENT', {
|
|
7625
|
+
type: event.type,
|
|
7626
|
+
data: this._safeClone(event.data),
|
|
7627
|
+
componentId: event.__emitterId ?? null,
|
|
7628
|
+
componentName: event.__emitterName ?? null,
|
|
7629
|
+
timestamp: Date.now(),
|
|
7630
|
+
});
|
|
7631
|
+
}
|
|
7632
|
+
onCommandSent(type, data, targetComponentId, targetComponentName) {
|
|
7633
|
+
if (!this.connected)
|
|
7634
|
+
return;
|
|
7635
|
+
this._post('COMMAND_SENT', {
|
|
7636
|
+
type,
|
|
7637
|
+
data: this._safeClone(data),
|
|
7638
|
+
targetComponentName: targetComponentName ?? null,
|
|
7639
|
+
timestamp: Date.now(),
|
|
7640
|
+
});
|
|
7641
|
+
}
|
|
7642
|
+
onReadyChanged(parentId, parentName, childKey, ready) {
|
|
7643
|
+
if (!this.connected)
|
|
7644
|
+
return;
|
|
7645
|
+
this._post('READY_CHANGED', {
|
|
7646
|
+
parentId,
|
|
7647
|
+
parentName,
|
|
7648
|
+
childKey,
|
|
7649
|
+
ready,
|
|
7650
|
+
timestamp: Date.now(),
|
|
7651
|
+
});
|
|
7652
|
+
}
|
|
7653
|
+
onCollectionMounted(parentId, parentName, itemComponentName, stateField) {
|
|
7654
|
+
const meta = this._components.get(parentId);
|
|
7655
|
+
if (meta) {
|
|
7656
|
+
meta.collection = { itemComponent: itemComponentName, stateField };
|
|
7657
|
+
}
|
|
7658
|
+
if (!this.connected)
|
|
7659
|
+
return;
|
|
7660
|
+
this._post('COLLECTION_MOUNTED', {
|
|
7661
|
+
parentId,
|
|
7662
|
+
parentName,
|
|
7663
|
+
itemComponent: itemComponentName,
|
|
7664
|
+
stateField,
|
|
7665
|
+
});
|
|
7666
|
+
}
|
|
7667
|
+
onComponentDisposed(componentNumber, name) {
|
|
7668
|
+
const meta = this._components.get(componentNumber);
|
|
7669
|
+
if (meta) {
|
|
7670
|
+
meta.disposed = true;
|
|
7671
|
+
meta.disposedAt = Date.now();
|
|
7672
|
+
}
|
|
7673
|
+
if (!this.connected)
|
|
7674
|
+
return;
|
|
7675
|
+
this._post('COMPONENT_DISPOSED', {
|
|
7676
|
+
componentId: componentNumber,
|
|
7677
|
+
componentName: name,
|
|
7678
|
+
timestamp: Date.now(),
|
|
7573
7679
|
});
|
|
7574
7680
|
}
|
|
7575
7681
|
onDebugLog(componentNumber, message) {
|
|
@@ -7598,20 +7704,79 @@ class SygnalDevTools {
|
|
|
7598
7704
|
}
|
|
7599
7705
|
}
|
|
7600
7706
|
}
|
|
7601
|
-
_timeTravel({
|
|
7602
|
-
|
|
7603
|
-
|
|
7707
|
+
_timeTravel({ componentId, componentName, state }) {
|
|
7708
|
+
if (componentId == null || !state) {
|
|
7709
|
+
console.warn('[Sygnal DevTools] _timeTravel: missing componentId or state', { componentId, hasState: !!state });
|
|
7604
7710
|
return;
|
|
7711
|
+
}
|
|
7605
7712
|
if (typeof window === 'undefined')
|
|
7606
7713
|
return;
|
|
7714
|
+
const newState = this._safeClone(state);
|
|
7715
|
+
// Try per-component time-travel via the component's STATE sink (reducer stream)
|
|
7716
|
+
const meta = this._components.get(componentId);
|
|
7717
|
+
if (meta) {
|
|
7718
|
+
const instance = meta._instanceRef?.deref();
|
|
7719
|
+
if (!instance) {
|
|
7720
|
+
console.warn(`[Sygnal DevTools] _timeTravel: WeakRef for component #${componentId} (${componentName}) has been GC'd`);
|
|
7721
|
+
}
|
|
7722
|
+
else {
|
|
7723
|
+
// sinks[stateSourceName] is the reducer stream — push a reducer that replaces state
|
|
7724
|
+
const stateSinkName = instance.stateSourceName || 'STATE';
|
|
7725
|
+
const stateSink = instance.sinks?.[stateSinkName];
|
|
7726
|
+
if (stateSink?.shamefullySendNext) {
|
|
7727
|
+
stateSink.shamefullySendNext(() => ({ ...newState }));
|
|
7728
|
+
this._post('TIME_TRAVEL_APPLIED', {
|
|
7729
|
+
componentId,
|
|
7730
|
+
componentName,
|
|
7731
|
+
state: newState,
|
|
7732
|
+
});
|
|
7733
|
+
return;
|
|
7734
|
+
}
|
|
7735
|
+
console.warn(`[Sygnal DevTools] _timeTravel: component #${componentId} (${componentName}) has no STATE sink with shamefullySendNext. sinkName=${stateSinkName}, hasSinks=${!!instance.sinks}, sinkKeys=${instance.sinks ? Object.keys(instance.sinks).join(',') : 'none'}`);
|
|
7736
|
+
}
|
|
7737
|
+
}
|
|
7738
|
+
else {
|
|
7739
|
+
console.warn(`[Sygnal DevTools] _timeTravel: no meta for componentId ${componentId}`);
|
|
7740
|
+
}
|
|
7741
|
+
// Fall back to root STATE sink for root-level components
|
|
7607
7742
|
const app = window.__SYGNAL_DEVTOOLS_APP__;
|
|
7608
7743
|
if (app?.sinks?.STATE?.shamefullySendNext) {
|
|
7609
|
-
app.sinks.STATE.shamefullySendNext(() => ({ ...
|
|
7744
|
+
app.sinks.STATE.shamefullySendNext(() => ({ ...newState }));
|
|
7610
7745
|
this._post('TIME_TRAVEL_APPLIED', {
|
|
7611
|
-
|
|
7612
|
-
|
|
7746
|
+
componentId,
|
|
7747
|
+
componentName,
|
|
7748
|
+
state: newState,
|
|
7613
7749
|
});
|
|
7614
7750
|
}
|
|
7751
|
+
else {
|
|
7752
|
+
console.warn(`[Sygnal DevTools] _timeTravel: no fallback root STATE sink available`);
|
|
7753
|
+
}
|
|
7754
|
+
}
|
|
7755
|
+
_takeSnapshot() {
|
|
7756
|
+
const entries = [];
|
|
7757
|
+
for (const [id, meta] of this._components) {
|
|
7758
|
+
if (meta.disposed)
|
|
7759
|
+
continue;
|
|
7760
|
+
const instance = meta._instanceRef?.deref();
|
|
7761
|
+
if (instance?.currentState != null) {
|
|
7762
|
+
entries.push({
|
|
7763
|
+
componentId: id,
|
|
7764
|
+
componentName: meta.name,
|
|
7765
|
+
state: this._safeClone(instance.currentState),
|
|
7766
|
+
});
|
|
7767
|
+
}
|
|
7768
|
+
}
|
|
7769
|
+
this._post('SNAPSHOT_TAKEN', {
|
|
7770
|
+
entries,
|
|
7771
|
+
timestamp: Date.now(),
|
|
7772
|
+
});
|
|
7773
|
+
}
|
|
7774
|
+
_restoreSnapshot(snapshot) {
|
|
7775
|
+
if (!snapshot?.entries)
|
|
7776
|
+
return;
|
|
7777
|
+
for (const entry of snapshot.entries) {
|
|
7778
|
+
this._timeTravel(entry);
|
|
7779
|
+
}
|
|
7615
7780
|
}
|
|
7616
7781
|
_sendComponentState(componentId) {
|
|
7617
7782
|
const meta = this._components.get(componentId);
|
|
@@ -7622,11 +7787,38 @@ class SygnalDevTools {
|
|
|
7622
7787
|
componentId,
|
|
7623
7788
|
state: this._safeClone(instance.currentState),
|
|
7624
7789
|
context: this._safeClone(instance.currentContext),
|
|
7790
|
+
contextTrace: this._buildContextTrace(componentId, instance.currentContext),
|
|
7625
7791
|
props: this._safeClone(instance.currentProps),
|
|
7626
7792
|
});
|
|
7627
7793
|
}
|
|
7628
7794
|
}
|
|
7629
7795
|
}
|
|
7796
|
+
_buildContextTrace(componentId, context) {
|
|
7797
|
+
if (!context || typeof context !== 'object')
|
|
7798
|
+
return [];
|
|
7799
|
+
const trace = [];
|
|
7800
|
+
const fields = Object.keys(context);
|
|
7801
|
+
for (const field of fields) {
|
|
7802
|
+
// Walk up parent chain to find which component provides this field
|
|
7803
|
+
let currentId = componentId;
|
|
7804
|
+
let found = false;
|
|
7805
|
+
while (currentId != null) {
|
|
7806
|
+
const meta = this._components.get(currentId);
|
|
7807
|
+
if (!meta)
|
|
7808
|
+
break;
|
|
7809
|
+
if (meta.mviGraph?.contextProvides?.includes(field)) {
|
|
7810
|
+
trace.push({ field, providerId: meta.id, providerName: meta.name });
|
|
7811
|
+
found = true;
|
|
7812
|
+
break;
|
|
7813
|
+
}
|
|
7814
|
+
currentId = meta.parentId;
|
|
7815
|
+
}
|
|
7816
|
+
if (!found) {
|
|
7817
|
+
trace.push({ field, providerId: -1, providerName: 'unknown' });
|
|
7818
|
+
}
|
|
7819
|
+
}
|
|
7820
|
+
return trace;
|
|
7821
|
+
}
|
|
7630
7822
|
_sendFullTree() {
|
|
7631
7823
|
const tree = [];
|
|
7632
7824
|
for (const [, meta] of this._components) {
|
|
@@ -7661,6 +7853,44 @@ class SygnalDevTools {
|
|
|
7661
7853
|
return '[unserializable]';
|
|
7662
7854
|
}
|
|
7663
7855
|
}
|
|
7856
|
+
_extractMviGraph(instance) {
|
|
7857
|
+
if (!instance.model)
|
|
7858
|
+
return null;
|
|
7859
|
+
try {
|
|
7860
|
+
const sources = instance.sourceNames ? [...instance.sourceNames] : [];
|
|
7861
|
+
const actions = [];
|
|
7862
|
+
const model = instance.model;
|
|
7863
|
+
for (const key of Object.keys(model)) {
|
|
7864
|
+
let actionName = key;
|
|
7865
|
+
let entry = model[key];
|
|
7866
|
+
// Handle shorthand 'ACTION | SINK'
|
|
7867
|
+
if (key.includes('|')) {
|
|
7868
|
+
const parts = key.split('|').map((s) => s.trim());
|
|
7869
|
+
if (parts.length === 2 && parts[0] && parts[1]) {
|
|
7870
|
+
actionName = parts[0];
|
|
7871
|
+
actions.push({ name: actionName, sinks: [parts[1]] });
|
|
7872
|
+
continue;
|
|
7873
|
+
}
|
|
7874
|
+
}
|
|
7875
|
+
// Function → targets STATE
|
|
7876
|
+
if (typeof entry === 'function') {
|
|
7877
|
+
actions.push({ name: actionName, sinks: [instance.stateSourceName || 'STATE'] });
|
|
7878
|
+
continue;
|
|
7879
|
+
}
|
|
7880
|
+
// Object → keys are sink names
|
|
7881
|
+
if (entry && typeof entry === 'object') {
|
|
7882
|
+
actions.push({ name: actionName, sinks: Object.keys(entry) });
|
|
7883
|
+
continue;
|
|
7884
|
+
}
|
|
7885
|
+
}
|
|
7886
|
+
const contextProvides = instance.context && typeof instance.context === 'object'
|
|
7887
|
+
? Object.keys(instance.context) : [];
|
|
7888
|
+
return { sources, actions, contextProvides };
|
|
7889
|
+
}
|
|
7890
|
+
catch (e) {
|
|
7891
|
+
return null;
|
|
7892
|
+
}
|
|
7893
|
+
}
|
|
7664
7894
|
_serializeMeta(meta) {
|
|
7665
7895
|
const { _instanceRef, ...rest } = meta;
|
|
7666
7896
|
return rest;
|