sygnal 7.0.0 → 7.1.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/CHANGELOG.md +126 -0
- package/dist/astro/server.cjs.js +32 -43
- package/dist/astro/server.cjs.js.map +1 -1
- package/dist/astro/server.mjs +32 -43
- package/dist/astro/server.mjs.map +1 -1
- package/dist/diagnostics.cjs.js +67 -2
- package/dist/diagnostics.cjs.js.map +1 -1
- package/dist/diagnostics.esm.js +67 -2
- package/dist/diagnostics.esm.js.map +1 -1
- package/dist/guide/accessibility.md +1 -1
- package/dist/guide/resources.md +2 -0
- package/dist/guide/timers.md +3 -3
- package/dist/index.cjs.js +189 -77
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +103 -16
- package/dist/index.esm.js +188 -78
- package/dist/index.esm.js.map +1 -1
- package/dist/sygnal.min.js +1 -1
- package/dist/sygnal.min.js.map +1 -1
- package/dist/vike/config/package.json +1 -1
- package/llms.txt +7 -7
- package/package.json +1 -1
- package/src/core/hosts/switchable.ts +26 -8
- package/src/core/instance.ts +12 -7
- package/src/core/statics.ts +5 -5
- package/src/extra/ai/transports/shared.ts +3 -1
- package/src/extra/behaviors.ts +3 -3
- package/src/extra/diagnostics/checks/next.ts +46 -3
- package/src/extra/diagnostics/checks/props.ts +6 -1
- package/src/extra/diagnostics/codes.ts +11 -0
- package/src/extra/fetchDriver.ts +3 -0
- package/src/extra/fetchEventSource.ts +6 -2
- package/src/extra/socketDriver.ts +51 -6
- package/src/extra/ssr.ts +30 -41
- package/src/extra/testing.ts +32 -7
- package/src/extra/timers.ts +5 -2
- package/src/index.d.ts +102 -15
- package/src/index.ts +2 -0
package/dist/index.esm.js
CHANGED
|
@@ -695,6 +695,7 @@ const DEV_CODE_SEVERITY = {
|
|
|
695
695
|
SYG112: 'error',
|
|
696
696
|
SYG115: 'warn',
|
|
697
697
|
SYG116: 'error',
|
|
698
|
+
SYG117: 'warn',
|
|
698
699
|
// PLAN-4 1-D: controls, static (sygnal-check)
|
|
699
700
|
SYG124: 'error',
|
|
700
701
|
SYG125: 'error',
|
|
@@ -706,6 +707,10 @@ const DEV_CODE_SEVERITY = {
|
|
|
706
707
|
SYG129: 'warn',
|
|
707
708
|
// PLAN-10 X-1: static only (sygnal-check)
|
|
708
709
|
SYG170: 'warn',
|
|
710
|
+
// PLAN-13 X-1: static only (sygnal-check)
|
|
711
|
+
SYG302: 'info',
|
|
712
|
+
// PLAN-13 F-3 (checks/next.ts)
|
|
713
|
+
SYG429: 'warn',
|
|
709
714
|
SYG171: 'warn',
|
|
710
715
|
SYG221: 'error',
|
|
711
716
|
// PLAN-4 GS-4 (state.ts)
|
|
@@ -1792,23 +1797,24 @@ const shownOf = (inst) => {
|
|
|
1792
1797
|
function checkStatics(inst) {
|
|
1793
1798
|
if (inst.disposed)
|
|
1794
1799
|
return;
|
|
1795
|
-
const s = inst.cell.get(), shown = shownOf(inst);
|
|
1800
|
+
const s = inst.cell.get(), shown = shownOf(inst), c = inst.context();
|
|
1796
1801
|
// no state yet: nothing declared (as the state stream, which skips undefined)
|
|
1797
|
-
if (s === undefined || (s === inst.sS && shown === inst.sH))
|
|
1802
|
+
if (s === undefined || (s === inst.sS && shown === inst.sH && c === inst.sC))
|
|
1798
1803
|
return;
|
|
1799
1804
|
inst.sS = s;
|
|
1800
1805
|
inst.sH = shown;
|
|
1806
|
+
inst.sC = c;
|
|
1801
1807
|
const view = inst.def.view, sv = inst.sv;
|
|
1802
1808
|
for (const [n, k] of inst.st) {
|
|
1803
1809
|
const f = view[k];
|
|
1804
1810
|
let v = f;
|
|
1805
1811
|
try {
|
|
1806
1812
|
if (typeof f == 'function')
|
|
1807
|
-
v = f(s);
|
|
1813
|
+
v = f(s, c);
|
|
1808
1814
|
else if (f && typeof f == 'object') {
|
|
1809
1815
|
v = {};
|
|
1810
1816
|
for (const r in f)
|
|
1811
|
-
v[r] = f[r](s);
|
|
1817
|
+
v[r] = f[r](s, c);
|
|
1812
1818
|
}
|
|
1813
1819
|
}
|
|
1814
1820
|
catch (e) {
|
|
@@ -2219,6 +2225,14 @@ function bindingChanged(a, b) {
|
|
|
2219
2225
|
return x && y ? !x.every((v, i) => Object.is(v, y[i]))
|
|
2220
2226
|
: !!(x || y || typeof a == 'string' || typeof b == 'string' || a === undefined || b === undefined);
|
|
2221
2227
|
}
|
|
2228
|
+
/** a binding whose target is known (a key, a byId selection): its deletion ends the child */
|
|
2229
|
+
const known$1 = (b) => typeof b == 'string' || b?.[BINDING];
|
|
2230
|
+
/** F-3: `owner` watches child (or Switchable page) `k`'s target: notify() disposes it when deleted */
|
|
2231
|
+
function watch(owner, k) {
|
|
2232
|
+
k.bs = bindingState(k);
|
|
2233
|
+
(owner.bound || (owner.bound = new Set())).add(k);
|
|
2234
|
+
owner.app.watchers.add(owner);
|
|
2235
|
+
}
|
|
2222
2236
|
function bindingState(k) {
|
|
2223
2237
|
const c = k.base || k.cell || k.arr;
|
|
2224
2238
|
return c && (c.has?.() ?? c.get() !== undefined) ? (k.cell || k.arr).get() : undefined;
|
|
@@ -2805,11 +2819,8 @@ class Inst {
|
|
|
2805
2819
|
k.setProps(props, children, n, id);
|
|
2806
2820
|
else
|
|
2807
2821
|
this.kids.set(id, k = host ? this.host(host, props, children, id, n) : this.child(view, props, children, id, !!data.props?.resetState));
|
|
2808
|
-
if (
|
|
2809
|
-
|
|
2810
|
-
(this.bound || (this.bound = new Set())).add(k);
|
|
2811
|
-
this.app.watchers.add(this);
|
|
2812
|
-
}
|
|
2822
|
+
if (known$1(props[binding]))
|
|
2823
|
+
watch(this, k);
|
|
2813
2824
|
return;
|
|
2814
2825
|
}
|
|
2815
2826
|
if (posts[sel] && !(ps || (ps = [])).includes(sel))
|
|
@@ -4218,6 +4229,8 @@ function check$3(owner, props) {
|
|
|
4218
4229
|
if (!(cur in of))
|
|
4219
4230
|
fail$2('SYG416', owner, `Switchable 'current' '${cur}' is not a key of 'of'`, CUR_FIX);
|
|
4220
4231
|
}
|
|
4232
|
+
/** F-3: a per-page map (an object that is not a lens or a byId selection: no `get`), or null */
|
|
4233
|
+
const mapOf = (st) => isObj$2(st) && typeof st.get != 'function' ? st : null;
|
|
4221
4234
|
/** a page's props: the Switchable's, kept by identity while equal (`of` compared shallowly) */
|
|
4222
4235
|
function sameProps(a, b) {
|
|
4223
4236
|
if (!a || !b)
|
|
@@ -4239,10 +4252,11 @@ class SwitchableHost {
|
|
|
4239
4252
|
this.lr = true;
|
|
4240
4253
|
this.kids = [];
|
|
4241
4254
|
check$3(owner, props);
|
|
4242
|
-
const st = props.state
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4255
|
+
const st = props.state;
|
|
4256
|
+
// F-3: `state={{ page: key | lens | byId }}` (not a lens, not a byId selection): each listed
|
|
4257
|
+
// page binds its own target, with a tag child's lifetime rules; the others share the owner's state
|
|
4258
|
+
this.map = mapOf(st);
|
|
4259
|
+
this.cell = this.bind(this.map ? undefined : st);
|
|
4246
4260
|
this.uidBase = owner.uid(uidPart(id.replace(/.*::(r\.)?/, '')));
|
|
4247
4261
|
this.props = props;
|
|
4248
4262
|
this.kids = children;
|
|
@@ -4252,9 +4266,16 @@ class SwitchableHost {
|
|
|
4252
4266
|
}
|
|
4253
4267
|
this.setProps(props, children);
|
|
4254
4268
|
}
|
|
4269
|
+
bind(st) {
|
|
4270
|
+
const owner = this.owner, calc = typeof st == 'string' && owner.def.calcNames?.has(pathOf$1(st)[0]);
|
|
4271
|
+
return typeof st == 'string' ? keyCell(owner.cell, st, calc && owner.def.name, undefined, 'Switchable sub-component')
|
|
4272
|
+
: st !== undefined ? lensCell(owner.cell, st, owner.def.name, (e) => owner.app.appError(owner, e, 'view'))
|
|
4273
|
+
: owner.cell;
|
|
4274
|
+
}
|
|
4255
4275
|
make(name, view) {
|
|
4256
4276
|
const o = this.owner, app = o.app, def = app.def(view), scope = app.scope();
|
|
4257
|
-
const
|
|
4277
|
+
const c = this.map ? this.bind(this.map[name]) : this.cell;
|
|
4278
|
+
const inst = new Inst(app, def, o, def.isolated ? localCell(app, c) : c, o.dom && o.dom.isolateSource(o.dom, scope), this.props, this.kids, scope, this.uidBase + '-' + uidPart(name), 'page');
|
|
4258
4279
|
inst.shown = false;
|
|
4259
4280
|
return inst;
|
|
4260
4281
|
}
|
|
@@ -4271,15 +4292,21 @@ class SwitchableHost {
|
|
|
4271
4292
|
for (const n in this.pages)
|
|
4272
4293
|
this.pages[n].inst.setProps(props, children);
|
|
4273
4294
|
}
|
|
4295
|
+
// F-3: a page whose own binding target changed (7.0's rule, per page), or that the owner
|
|
4296
|
+
// disposed because its target was deleted, is made again
|
|
4297
|
+
const m = this.map, nm = this.map = mapOf(props.state);
|
|
4274
4298
|
// G-317: a lazy() page that has loaded is made again with the loaded component
|
|
4275
4299
|
for (const n in this.pages) {
|
|
4276
4300
|
const p = this.pages[n], fn = resolve$2(p.view, this.owner);
|
|
4277
|
-
if (fn !== p.fn) {
|
|
4301
|
+
if (fn !== p.fn || p.inst.disposed || m && nm && bindingChanged(m[n], nm[n])) {
|
|
4278
4302
|
const shown = p.inst.shown;
|
|
4279
|
-
p.inst.dispose();
|
|
4303
|
+
p.inst.disposed || p.inst.dispose();
|
|
4280
4304
|
p.inst = this.make(n, p.fn = fn);
|
|
4281
4305
|
p.inst.shown = shown;
|
|
4282
4306
|
}
|
|
4307
|
+
// the owner disposes a page whose target is deleted, as a tag child; it is made again above
|
|
4308
|
+
if (nm && known$1(nm[n]))
|
|
4309
|
+
watch(this.owner, p.inst);
|
|
4283
4310
|
}
|
|
4284
4311
|
const page = this.pages[cur];
|
|
4285
4312
|
if (page) {
|
|
@@ -5891,6 +5918,10 @@ function makeFetchDriver(options = {}) {
|
|
|
5891
5918
|
write(s, n, R, L.status, L.data, L.error, true);
|
|
5892
5919
|
else
|
|
5893
5920
|
write(s, n, R, 'loading');
|
|
5921
|
+
// R-1: the write is dispatched synchronously; when it changed the request (a navigation
|
|
5922
|
+
// recomputed the statics and a nested load sent the new one) or paused it, q is stale
|
|
5923
|
+
if (R.q !== q || R.p)
|
|
5924
|
+
return;
|
|
5894
5925
|
if (go || q.validate)
|
|
5895
5926
|
R.i = send(q, s, n, after, R);
|
|
5896
5927
|
};
|
|
@@ -6355,6 +6386,9 @@ async function* sse(res, meta) {
|
|
|
6355
6386
|
try {
|
|
6356
6387
|
for (;;) {
|
|
6357
6388
|
const r = await reader.read();
|
|
6389
|
+
// PLAN-13 S-1: any bytes (a heartbeat comment too) show the stream is alive
|
|
6390
|
+
if (!r.done)
|
|
6391
|
+
meta?.beat?.();
|
|
6358
6392
|
// line ends -> \n (a \r at a chunk's end waits: it may be half of a \r\n)
|
|
6359
6393
|
let s = cr + (r.done ? '\n\n' : dec.decode(r.value, { stream: true }));
|
|
6360
6394
|
cr = !r.done && s.endsWith('\r') ? '\r' : '';
|
|
@@ -6436,6 +6470,8 @@ const dev$8 = (...a) => g$6.__SYGNAL_DIAGNOSTICS__?.chat?.(...a);
|
|
|
6436
6470
|
* - a non-2xx status or another content type: `error` with readyState CLOSED (no retry; the
|
|
6437
6471
|
* driver's reconnect policy decides);
|
|
6438
6472
|
* - `close()` aborts the request and stops retrying.
|
|
6473
|
+
* - PLAN-13 S-1: `init.lastEventId` seeds the id sent as `Last-Event-ID` (a driver reconnect that
|
|
6474
|
+
* resumes); `onbeat` (not EventSource's) is called for any bytes read, heartbeat comments too.
|
|
6439
6475
|
*/
|
|
6440
6476
|
const NAMES = ['CONNECTING', 'OPEN', 'CLOSED'];
|
|
6441
6477
|
class FetchEventSource {
|
|
@@ -6444,11 +6480,14 @@ class FetchEventSource {
|
|
|
6444
6480
|
this.onopen = null;
|
|
6445
6481
|
this.onmessage = null;
|
|
6446
6482
|
this.onerror = null;
|
|
6483
|
+
this.onbeat = null;
|
|
6447
6484
|
this.ls = new Map();
|
|
6448
|
-
this.meta = { id: '', retry: 3000 };
|
|
6485
|
+
this.meta = { id: '', retry: 3000, beat: () => this.onbeat?.() };
|
|
6449
6486
|
this.url = url;
|
|
6450
6487
|
this.withCredentials = !!init.withCredentials;
|
|
6451
6488
|
this.init = init;
|
|
6489
|
+
if (init.lastEventId)
|
|
6490
|
+
this.meta.id = init.lastEventId;
|
|
6452
6491
|
queueMicrotask(() => this.connect());
|
|
6453
6492
|
}
|
|
6454
6493
|
addEventListener(type, f) {
|
|
@@ -6584,6 +6623,12 @@ FetchEventSource.CLOSED = 2;
|
|
|
6584
6623
|
* changed header reconnects) is read with FetchEventSource (fetch, `options.fetch` if given),
|
|
6585
6624
|
* since EventSource can't send headers. An `options.EventSource` (a polyfill, the test fake)
|
|
6586
6625
|
* still wins and gets the headers in its init.
|
|
6626
|
+
* - PLAN-13 S-1: SSE `meta: true`: message and `events` actions get `{ data, id, event }`. `resume:
|
|
6627
|
+
* true | 'param'`: the transport keeps the last event id, and a reconnect the driver makes (after
|
|
6628
|
+
* EventSource gave up) asks to resume from it: `?lastEventId=` (or `?param=`) on the URL, or
|
|
6629
|
+
* `Last-Event-ID` on the fetch path (`init.lastEventId`). `staleAfterMs` (SSE and WebSocket): no
|
|
6630
|
+
* event for that long (over fetch: no bytes, heartbeat comments count) closes the connection as a
|
|
6631
|
+
* drop (`close` gets `stale: true`), so the reconnect policy applies.
|
|
6587
6632
|
*/
|
|
6588
6633
|
const SCOPE$1 = '__sygnalScope';
|
|
6589
6634
|
const scopeOf = (v) => v[SCOPE$1] || [];
|
|
@@ -6653,20 +6698,48 @@ function makeSocketDriver(options = {}) {
|
|
|
6653
6698
|
deliver(e, 'error', { error });
|
|
6654
6699
|
}
|
|
6655
6700
|
};
|
|
6701
|
+
// S-1: an SSE event as a subscriber gets it (`meta`: with its id and type); the transport
|
|
6702
|
+
// keeps the last id (resume) and counts it as life (staleAfterMs)
|
|
6703
|
+
const got = (t, m, e) => e.sse && e.spec.meta ? { data: parse(m.data), id: m.lastEventId ?? '', event: m.type || 'message' } : parse(m.data);
|
|
6704
|
+
const seen = (t, m) => { if (m.lastEventId)
|
|
6705
|
+
t.id = m.lastEventId; alive(t); };
|
|
6656
6706
|
// SSE named events: one listener per name across the subscribers
|
|
6657
6707
|
const listen = (t) => t.s && t.sse && each(t, e => Object.keys(e.spec.events || {}).forEach(name => {
|
|
6658
6708
|
if (t.on.has(name))
|
|
6659
6709
|
return;
|
|
6660
6710
|
t.on.add(name);
|
|
6661
6711
|
const s = t.s;
|
|
6662
|
-
s.addEventListener(name, (m) =>
|
|
6663
|
-
|
|
6664
|
-
|
|
6665
|
-
|
|
6666
|
-
|
|
6712
|
+
s.addEventListener(name, (m) => {
|
|
6713
|
+
if (t.s !== s)
|
|
6714
|
+
return;
|
|
6715
|
+
seen(t, m);
|
|
6716
|
+
each(t, x => {
|
|
6717
|
+
const a = x.spec.events?.[name];
|
|
6718
|
+
if (typeof a == 'string' && x.sender !== undefined)
|
|
6719
|
+
reply(x.sender, a, got(t, m, x));
|
|
6720
|
+
});
|
|
6721
|
+
});
|
|
6667
6722
|
}));
|
|
6723
|
+
// S-1: staleAfterMs (the smallest of the subscribers'): re-armed by every event; when it fires
|
|
6724
|
+
// the connection is dropped (as if the server had gone) and the reconnect policy applies
|
|
6725
|
+
const alive = (t) => {
|
|
6726
|
+
clearTimeout(t.st);
|
|
6727
|
+
const ms = Math.min(...[...t.subs].map(e => e.spec.staleAfterMs).filter(x => x > 0));
|
|
6728
|
+
const s = t.s;
|
|
6729
|
+
if (s && ms < 1 / 0)
|
|
6730
|
+
t.st = setTimeout(() => {
|
|
6731
|
+
if (t.s !== s)
|
|
6732
|
+
return;
|
|
6733
|
+
try {
|
|
6734
|
+
s.close();
|
|
6735
|
+
}
|
|
6736
|
+
catch (_) { }
|
|
6737
|
+
gone(t, t.sse ? { stale: true } : { code: 1006, reason: 'stale', stale: true });
|
|
6738
|
+
}, ms);
|
|
6739
|
+
};
|
|
6668
6740
|
// the socket closed (or failed to open) without the driver closing it
|
|
6669
6741
|
const gone = (t, info) => {
|
|
6742
|
+
clearTimeout(t.st);
|
|
6670
6743
|
t.s = null;
|
|
6671
6744
|
t.open = false;
|
|
6672
6745
|
let again;
|
|
@@ -6687,9 +6760,19 @@ function makeSocketDriver(options = {}) {
|
|
|
6687
6760
|
const C = t.sse ? options.EventSource ?? (hdr && typeof g.fetch == 'function' ? FetchEventSource : g.EventSource) : options.WebSocket ?? g.WebSocket;
|
|
6688
6761
|
if (typeof C != 'function')
|
|
6689
6762
|
return;
|
|
6763
|
+
// S-1: resume from the last event id: Last-Event-ID on the fetch path (init.lastEventId, which
|
|
6764
|
+
// a polyfill or the test fake gets too), else a URL parameter
|
|
6765
|
+
// (a parameter the URL already has is replaced, not added again)
|
|
6766
|
+
const r = t.sse && t.id && [...t.subs].map(e => e.spec.resume).find(x => x);
|
|
6767
|
+
let url = t.url;
|
|
6768
|
+
if (r && !hdr) {
|
|
6769
|
+
const p = encodeURIComponent(r === true ? 'lastEventId' : r) + '=', v = p + encodeURIComponent(t.id);
|
|
6770
|
+
const re = new RegExp('([?&])' + p.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '[^&#]*');
|
|
6771
|
+
url = re.test(url) ? url.replace(re, '$1' + v) : url + (url.includes('?') ? '&' : '?') + v;
|
|
6772
|
+
}
|
|
6690
6773
|
let s;
|
|
6691
6774
|
try {
|
|
6692
|
-
s = t.sse ? new C(
|
|
6775
|
+
s = t.sse ? new C(url, { withCredentials: !!t.spec.withCredentials, ...(hdr && { headers: hdr, fetch: options.fetch }), ...(r && { lastEventId: t.id }) }) : new C(t.url, t.spec.protocols);
|
|
6693
6776
|
}
|
|
6694
6777
|
catch (error) {
|
|
6695
6778
|
// a URL the constructor rejects never opens: no retry
|
|
@@ -6704,12 +6787,22 @@ function makeSocketDriver(options = {}) {
|
|
|
6704
6787
|
s.onopen = mine(() => {
|
|
6705
6788
|
t.n = 0;
|
|
6706
6789
|
t.open = true;
|
|
6790
|
+
alive(t);
|
|
6707
6791
|
each(t, e => { deliver(e, 'open', { reconnected: !!e.opened }); e.opened = true; });
|
|
6708
6792
|
const q = t.queue;
|
|
6709
6793
|
t.queue = [];
|
|
6710
6794
|
q.forEach((x) => t.s === s && out(t, x.data, x.e));
|
|
6711
6795
|
});
|
|
6712
|
-
s.onmessage = mine((m) => {
|
|
6796
|
+
s.onmessage = mine((m) => {
|
|
6797
|
+
if (t.sse)
|
|
6798
|
+
seen(t, m);
|
|
6799
|
+
else
|
|
6800
|
+
alive(t);
|
|
6801
|
+
const d = parse(m.data);
|
|
6802
|
+
each(t, e => deliver(e, 'message', e.spec.meta && t.sse ? got(t, m, e) : d));
|
|
6803
|
+
});
|
|
6804
|
+
// S-1: FetchEventSource's bytes (heartbeat comments) count as life
|
|
6805
|
+
s.onbeat = mine(() => alive(t));
|
|
6713
6806
|
s.onerror = mine((error) => {
|
|
6714
6807
|
each(t, e => deliver(e, 'error', { error }));
|
|
6715
6808
|
if (t.sse) {
|
|
@@ -6731,6 +6824,7 @@ function makeSocketDriver(options = {}) {
|
|
|
6731
6824
|
// closes the driver makes: no `close` action, late events ignored, retry cancelled
|
|
6732
6825
|
const end = (t) => {
|
|
6733
6826
|
clearTimeout(t.timer);
|
|
6827
|
+
clearTimeout(t.st);
|
|
6734
6828
|
transports.delete(t.key);
|
|
6735
6829
|
const s = t.s;
|
|
6736
6830
|
t.s = null;
|
|
@@ -8529,7 +8623,8 @@ function makeHeadDriver(options = {}) {
|
|
|
8529
8623
|
* falsy one stops, a changed spec restarts (an equal one keeps running).
|
|
8530
8624
|
* - `every`: drift-free; tick n is due at start + n * every (each delay is computed from the
|
|
8531
8625
|
* start, not chained). A late tick (a throttled background tab) coalesces the missed ones:
|
|
8532
|
-
* n jumps.
|
|
8626
|
+
* n jumps. A delay is at most `every`, so a frozen Date.now() (a test clock that fixes the time
|
|
8627
|
+
* while timers run) ticks once per interval. Data `{ n, t }`: the tick number from 1, `t` = Date.now().
|
|
8533
8628
|
* - `after`: fires once, data `{ t }`. It doesn't fire again while the same spec stays declared.
|
|
8534
8629
|
* - `frame`: every animation frame (requestAnimationFrame; setTimeout 16 ms without it), data
|
|
8535
8630
|
* `{ t, dt }`: Date.now() and the ms since the previous frame (0 on the first).
|
|
@@ -8561,7 +8656,9 @@ const start$1 = (s, send, e) => {
|
|
|
8561
8656
|
}
|
|
8562
8657
|
else if (every) {
|
|
8563
8658
|
n = Math.max(n + 1, Math.floor((t - t0) / every));
|
|
8564
|
-
|
|
8659
|
+
// never more than one interval: under a frozen Date.now() (Playwright's setFixedTime: Date is
|
|
8660
|
+
// fixed, timers run) the drift-free delay grew by one interval per tick
|
|
8661
|
+
later(Math.min(every, t0 + (n + 1) * every - t));
|
|
8565
8662
|
send(s.action, { n, t });
|
|
8566
8663
|
}
|
|
8567
8664
|
else {
|
|
@@ -10634,13 +10731,13 @@ const addTimers = (v) => {
|
|
|
10634
10731
|
return;
|
|
10635
10732
|
timed.add(v);
|
|
10636
10733
|
let own = v.timers;
|
|
10637
|
-
const all = (st) => {
|
|
10734
|
+
const all = (st, c) => {
|
|
10638
10735
|
const o = {}, f = own;
|
|
10639
10736
|
if (typeof f == 'function')
|
|
10640
|
-
Object.assign(o, f(st));
|
|
10737
|
+
Object.assign(o, f(st, c));
|
|
10641
10738
|
else if (f && typeof f == 'object')
|
|
10642
10739
|
for (const r in f)
|
|
10643
|
-
o[r] = f[r](st);
|
|
10740
|
+
o[r] = f[r](st, c);
|
|
10644
10741
|
const u = v.uses;
|
|
10645
10742
|
for (const k in u) {
|
|
10646
10743
|
const b = u[k], t = b?.timers?.(st?.[k] ?? b.state, b.options, k), rn = (a) => a in (b.model || {}) ? k + '.' + a : a;
|
|
@@ -12274,7 +12371,7 @@ function withUses(def, state) {
|
|
|
12274
12371
|
* state reads from the cache ('success' with the cached data), else 'loading' ('idle' for a
|
|
12275
12372
|
* falsy request), as the client's first paint does with the same (hydrated) cache
|
|
12276
12373
|
*/
|
|
12277
|
-
function withResources(def, state) {
|
|
12374
|
+
function withResources(def, state, ctx) {
|
|
12278
12375
|
state = withUses(def, state);
|
|
12279
12376
|
const res = def && def.resources;
|
|
12280
12377
|
if (!res || !state || typeof state !== 'object' || Array.isArray(state))
|
|
@@ -12285,7 +12382,7 @@ function withResources(def, state) {
|
|
|
12285
12382
|
continue;
|
|
12286
12383
|
let q;
|
|
12287
12384
|
try {
|
|
12288
|
-
q = typeof res[n] === 'function' ? res[n](state) : res[n];
|
|
12385
|
+
q = typeof res[n] === 'function' ? res[n](state, ctx) : res[n];
|
|
12289
12386
|
}
|
|
12290
12387
|
catch (_) { }
|
|
12291
12388
|
const E = q && ssrCache ? ssrCache.get(ssrCache.key(q, 1)) : undefined;
|
|
@@ -12332,15 +12429,33 @@ function viewFailed(def, err, name) {
|
|
|
12332
12429
|
}
|
|
12333
12430
|
return vnode;
|
|
12334
12431
|
}
|
|
12335
|
-
function collectHead(def, state) {
|
|
12432
|
+
function collectHead(def, state, ctx) {
|
|
12336
12433
|
const h = def && def.head;
|
|
12337
12434
|
if (!heads || !h)
|
|
12338
12435
|
return;
|
|
12339
12436
|
try {
|
|
12340
|
-
heads.push(typeof h === 'function' ? h(state) : h);
|
|
12437
|
+
heads.push(typeof h === 'function' ? h(state, ctx) : h);
|
|
12341
12438
|
}
|
|
12342
12439
|
catch (_) { }
|
|
12343
12440
|
}
|
|
12441
|
+
/**
|
|
12442
|
+
* PLAN-13 F-1: the context a component sees (its parent's, plus its own entries computed on
|
|
12443
|
+
* `state`). The statics get it as on the client; for `resources` it is computed on the state
|
|
12444
|
+
* before the resources are filled in (a context entry can't read the component's own resource)
|
|
12445
|
+
*/
|
|
12446
|
+
function contextOf(def, state, parent) {
|
|
12447
|
+
const own = def.context || {}, out = { ...parent };
|
|
12448
|
+
for (const key of Object.keys(own)) {
|
|
12449
|
+
const f = own[key];
|
|
12450
|
+
if (typeof f === 'function' && state != null) {
|
|
12451
|
+
try {
|
|
12452
|
+
out[key] = f(state);
|
|
12453
|
+
}
|
|
12454
|
+
catch (_) { }
|
|
12455
|
+
}
|
|
12456
|
+
}
|
|
12457
|
+
return out;
|
|
12458
|
+
}
|
|
12344
12459
|
/**
|
|
12345
12460
|
* Render a Sygnal component to an HTML string.
|
|
12346
12461
|
*
|
|
@@ -12381,20 +12496,10 @@ function renderRoot(componentDef, options) {
|
|
|
12381
12496
|
const uid = (options.uid || 'u').replace(/[^\w-]+/g, '_');
|
|
12382
12497
|
const ownState = state !== undefined ? state : componentDef.initialState;
|
|
12383
12498
|
// 5-5: the view sees its resources; the hydration script keeps the state as it was given
|
|
12384
|
-
const resolvedState = withResources(componentDef, ownState);
|
|
12385
|
-
collectHead(componentDef, resolvedState);
|
|
12499
|
+
const resolvedState = withResources(componentDef, ownState, contextOf(componentDef, ownState, context));
|
|
12386
12500
|
// Build context: merge parent context with component's own context definitions
|
|
12387
|
-
const
|
|
12388
|
-
|
|
12389
|
-
for (const key of Object.keys(componentContext)) {
|
|
12390
|
-
const contextFn = componentContext[key];
|
|
12391
|
-
if (typeof contextFn === 'function' && resolvedState != null) {
|
|
12392
|
-
try {
|
|
12393
|
-
mergedContext[key] = contextFn(resolvedState);
|
|
12394
|
-
}
|
|
12395
|
-
catch (_) { }
|
|
12396
|
-
}
|
|
12397
|
-
}
|
|
12501
|
+
const mergedContext = contextOf(componentDef, resolvedState, context);
|
|
12502
|
+
collectHead(componentDef, resolvedState, mergedContext);
|
|
12398
12503
|
// Call the view function
|
|
12399
12504
|
let vnode;
|
|
12400
12505
|
try {
|
|
@@ -12605,19 +12710,9 @@ function renderSubComponent(vnode, context, parentState, uid) {
|
|
|
12605
12710
|
// State passed directly
|
|
12606
12711
|
childState = stateProp;
|
|
12607
12712
|
}
|
|
12608
|
-
childState = withResources(componentDef, childState);
|
|
12713
|
+
childState = withResources(componentDef, childState, contextOf(componentDef, childState, context));
|
|
12609
12714
|
// Build child context
|
|
12610
|
-
const childContext =
|
|
12611
|
-
const componentContext = componentDef.context || {};
|
|
12612
|
-
for (const key of Object.keys(componentContext)) {
|
|
12613
|
-
const contextFn = componentContext[key];
|
|
12614
|
-
if (typeof contextFn === 'function' && childState != null) {
|
|
12615
|
-
try {
|
|
12616
|
-
childContext[key] = contextFn(childState);
|
|
12617
|
-
}
|
|
12618
|
-
catch (_) { }
|
|
12619
|
-
}
|
|
12620
|
-
}
|
|
12715
|
+
const childContext = contextOf(componentDef, childState, context);
|
|
12621
12716
|
// Extract slots from children
|
|
12622
12717
|
const slots = {};
|
|
12623
12718
|
const defaultSlotChildren = [];
|
|
@@ -12639,7 +12734,7 @@ function renderSubComponent(vnode, context, parentState, uid) {
|
|
|
12639
12734
|
slots.default = [];
|
|
12640
12735
|
slots.default.push(...defaultSlotChildren);
|
|
12641
12736
|
}
|
|
12642
|
-
collectHead(componentDef, childState);
|
|
12737
|
+
collectHead(componentDef, childState, childContext);
|
|
12643
12738
|
// Call the view function
|
|
12644
12739
|
let result;
|
|
12645
12740
|
try {
|
|
@@ -12856,19 +12951,10 @@ function renderSwitchable(vnode, context, parentState, uid) {
|
|
|
12856
12951
|
* Internal helper: render a component def to a VNode (not HTML string).
|
|
12857
12952
|
*/
|
|
12858
12953
|
function renderToStringInternal(componentDef, state, context, uid) {
|
|
12859
|
-
const
|
|
12860
|
-
const
|
|
12861
|
-
const mergedContext =
|
|
12862
|
-
|
|
12863
|
-
const contextFn = componentContext[key];
|
|
12864
|
-
if (typeof contextFn === 'function' && resolvedState != null) {
|
|
12865
|
-
try {
|
|
12866
|
-
mergedContext[key] = contextFn(resolvedState);
|
|
12867
|
-
}
|
|
12868
|
-
catch (_) { }
|
|
12869
|
-
}
|
|
12870
|
-
}
|
|
12871
|
-
collectHead(componentDef, resolvedState);
|
|
12954
|
+
const own = state !== undefined ? state : componentDef.initialState;
|
|
12955
|
+
const resolvedState = withResources(componentDef, own, contextOf(componentDef, own, context));
|
|
12956
|
+
const mergedContext = contextOf(componentDef, resolvedState, context);
|
|
12957
|
+
collectHead(componentDef, resolvedState, mergedContext);
|
|
12872
12958
|
let vnode;
|
|
12873
12959
|
try {
|
|
12874
12960
|
vnode = componentDef({
|
|
@@ -15640,6 +15726,8 @@ const sockKey = (sse, url, arg) => (sse ? 'e' + !!(arg && arg.withCredentials) :
|
|
|
15640
15726
|
* PLAN-3 2-C: the in-memory WebSocket / EventSource the fake's makeSocketDriver opens. The
|
|
15641
15727
|
* harness drives it (t.open / t.push / t.drop); the driver's own close() is the app's close.
|
|
15642
15728
|
*/
|
|
15729
|
+
/** `url` without its query parameter `name` (PLAN-13 S-1: a resumed reconnect's id parameter) */
|
|
15730
|
+
const withoutParam = (url, name) => url.replace(new RegExp('([?&])' + name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '=[^&#]*&?'), '$1').replace(/[?&](#|$)/, '$1');
|
|
15643
15731
|
const fakeSocketClass = (sse, made) => class {
|
|
15644
15732
|
constructor(url, arg) {
|
|
15645
15733
|
this.readyState = 0;
|
|
@@ -15647,7 +15735,14 @@ const fakeSocketClass = (sse, made) => class {
|
|
|
15647
15735
|
this.byApp = false;
|
|
15648
15736
|
this.ls = {};
|
|
15649
15737
|
this.url = String(url);
|
|
15650
|
-
|
|
15738
|
+
// PLAN-13 S-1: a resumed SSE reconnect's URL has the id parameter (added, or replacing the
|
|
15739
|
+
// declared one): its key leaves that parameter out (`rn`: its name; made() compares the
|
|
15740
|
+
// connections' URLs without it too)
|
|
15741
|
+
const id = sse && arg?.lastEventId;
|
|
15742
|
+
const m = id && new RegExp('[?&]([^?&=]+)=' + encodeURIComponent(id).replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '(?=&|#|$)').exec(this.url);
|
|
15743
|
+
if (m)
|
|
15744
|
+
this.rn = m[1];
|
|
15745
|
+
this.k = sockKey(sse, m ? withoutParam(this.url, m[1]) : this.url, arg);
|
|
15651
15746
|
made(this);
|
|
15652
15747
|
}
|
|
15653
15748
|
send() { }
|
|
@@ -16246,7 +16341,7 @@ function renderComponent(componentDef, options = {}) {
|
|
|
16246
16341
|
const w = { src: null, in$: xs$1.create(), conns: new Map(), sockets: [], sent: [], declaring: null };
|
|
16247
16342
|
const made = (s) => {
|
|
16248
16343
|
w.sockets.push(s);
|
|
16249
|
-
const all = [...w.conns.values()].flatMap(m => [...m.values()]).filter(c => c.k == s.k);
|
|
16344
|
+
const all = [...w.conns.values()].flatMap(m => [...m.values()]).filter(c => (s.rn ? sockKey(true, withoutParam(c.url, s.rn), c.spec) : c.k) == s.k);
|
|
16250
16345
|
if (w.declaring) {
|
|
16251
16346
|
// a declaration opened it: the first new connection with its key (and, shared, the
|
|
16252
16347
|
// other new shared ones)
|
|
@@ -16383,7 +16478,7 @@ function renderComponent(componentDef, options = {}) {
|
|
|
16383
16478
|
names.push(k + '.' + a);
|
|
16384
16479
|
// G-275: the caller's initialState is owned (the dev statics freeze leaves it alone). G-289: a
|
|
16385
16480
|
// copy is marked, not the caller's object
|
|
16386
|
-
|
|
16481
|
+
let init = initialState !== undefined ? ownedCopy(initialState) : componentDef.initialState;
|
|
16387
16482
|
// G-028: with no intent, model or initialState nothing would ever emit state, so the view
|
|
16388
16483
|
// never renders. Leave intent/model unset so the component falls back to the same no-op
|
|
16389
16484
|
// model run() uses, and renders.
|
|
@@ -16675,6 +16770,18 @@ function renderComponent(componentDef, options = {}) {
|
|
|
16675
16770
|
},
|
|
16676
16771
|
};
|
|
16677
16772
|
};
|
|
16773
|
+
// PLAN-13 F-2: a root that declares `route` starts from the test URL's route: its initialState's
|
|
16774
|
+
// router.current() ran at module load, before the test's URL existed. The route entry's STATE
|
|
16775
|
+
// reducer is applied to the initial state (its other sinks, a guard's redirect, run once when the
|
|
16776
|
+
// router answers the declaration), so the first render and the statics see the page the test
|
|
16777
|
+
// starts on: no render of '/' and no request for it
|
|
16778
|
+
const re = rt && typeof componentDef.route == 'string' && model[componentDef.route];
|
|
16779
|
+
const seed = typeof re == 'function' ? re : re && typeof re == 'object' && typeof re.STATE == 'function' && re.STATE;
|
|
16780
|
+
if (seed && init !== undefined) {
|
|
16781
|
+
const v = seed(init, rt.r.current(), noop, { state: init, previousState: init });
|
|
16782
|
+
if (v !== undefined && !isAbort(v))
|
|
16783
|
+
init = v;
|
|
16784
|
+
}
|
|
16678
16785
|
let sources, sinks;
|
|
16679
16786
|
try {
|
|
16680
16787
|
// the root runs with the test intent, model, initial state and name, and the drivers above.
|
|
@@ -17383,11 +17490,14 @@ function renderComponent(componentDef, options = {}) {
|
|
|
17383
17490
|
};
|
|
17384
17491
|
const open = (name, target) => sockCall('open', name, target, [0], sockOpen);
|
|
17385
17492
|
const push = (name, data, target) => {
|
|
17386
|
-
const o = target && typeof target == 'object' && !Array.isArray(target) && Object.keys(target).length && Object.keys(target).every(k => k == 'event' || k == 'connection') ? target : { connection: target };
|
|
17493
|
+
const o = target && typeof target == 'object' && !Array.isArray(target) && Object.keys(target).length && Object.keys(target).every(k => k == 'event' || k == 'connection' || k == 'id') ? target : { connection: target };
|
|
17387
17494
|
const raw = typeof data == 'string' || (data && typeof data == 'object' && (data instanceof ArrayBuffer || ArrayBuffer.isView(data) || (typeof Blob != 'undefined' && data instanceof Blob))) ? data : JSON.stringify(data);
|
|
17388
17495
|
const ev = o.event;
|
|
17389
17496
|
return sockCall('push', name, o.connection, [1], s => {
|
|
17390
|
-
|
|
17497
|
+
// PLAN-13 S-1: `id` sets the event id; as in EventSource, lastEventId keeps the last one
|
|
17498
|
+
if (s.sse && o.id != null)
|
|
17499
|
+
s.lastId = String(o.id);
|
|
17500
|
+
const m = { type: ev || 'message', data: raw, ...(s.sse && { lastEventId: s.lastId ?? '' }) };
|
|
17391
17501
|
if (!ev || ev == 'message')
|
|
17392
17502
|
sockFire(s, 'message', m);
|
|
17393
17503
|
if (s.sse)
|
|
@@ -26386,5 +26496,5 @@ function flattenSequentially(ins) {
|
|
|
26386
26496
|
return new Stream(new FlattenSeqOperator(ins));
|
|
26387
26497
|
}
|
|
26388
26498
|
|
|
26389
|
-
export { ABORT, Collection, Composed, MainDOMSource, MockedDOMSource, Portal, Slot, Suspense, Switchable, Transition, VirtualCollection, abort, agentTools, agui, answers, anthropicMessages, askCard, byId, chat, chatCompletions, checkForm, choice, chromePrompt, classes, clearDiagnostics, clipboardSource, commandBar, compose, composeWith, concat, controls, createCommand, createElement, createInstallPrompt, createRef, createRef$, debounce, decide, defineBehavior, defineCatalog, defineComponent, defineWidget, delay, driverFromAsync, dropRepeats, emit$1 as emit, enableHMR, encodeOpenResponses, event, exactState, experimentalExposeWebMcp, fieldName, fieldNames, flattenConcurrently, flattenSequentially, focusInvalid, focusWithin, form, formErrors, formTool, fromAISDK, fromTree, geolocationSource, getDevTools, getDiagnostics, getField, hasField, intersectionSource, isSelected, jsonSchema, lazy, makeBrowserDriver, makeBrowserDriverWith, makeChatDriver, makeDOMDriver, makeDragDriver, makeFetchDriver, makeHeadDriver, makeMcpAppDriver, makeRouter, makeRouterDriver, makeServiceWorkerDriver, makeSocketDriver, makeTimerDriver, makeViewTransitionDOMDriver, mediaSource, messageText, mockDOMSource, noul, onDiagnostic, onlineSource, onlineStatus$, openResponses, openToolCalls, outputJsonSchema, pager, parseInput, persist, Portal as portal, processDrag, processForm, queryCache, removeNode, renderComponent, renderHead, renderToString, replyErrors, resizeSource, run, sampleCombine, score, selection, set, setField, sortable, storageSource, strictSchemas, throttle, thunk, toJsonSchema, toTree, toggle, uiMessageStream, undo, undoable, visibilitySource, withToolResults, xs$1 as xs };
|
|
26499
|
+
export { ABORT, Collection, Composed, MainDOMSource, MockedDOMSource, Portal, Slot, Suspense, Switchable, Transition, VirtualCollection, abort, agentTools, agui, answers, anthropicMessages, askCard, byId, chat, chatCompletions, checkForm, choice, chromePrompt, classes, clearDiagnostics, clipboardSource, commandBar, compose, composeWith, concat, controls, createCommand, createElement, createInstallPrompt, createRef, createRef$, debounce, decide, defineBehavior, defineCatalog, defineComponent, defineWidget, delay, driverFromAsync, dropRepeats, emit$1 as emit, enableHMR, encodeOpenResponses, event, exactState, experimentalExposeWebMcp, fieldName, fieldNames, flattenConcurrently, flattenSequentially, focusInvalid, focusWithin, form, formErrors, formTool, fromAISDK, fromTree, geolocationSource, getDevTools, getDiagnostics, getField, hasField, intersectionSource, isSelected, jsonSchema, lazy, makeBrowserDriver, makeBrowserDriverWith, makeChatDriver, makeDOMDriver, makeDragDriver, makeFetchDriver, makeHeadDriver, makeMcpAppDriver, makeReplies, makeRouter, makeRouterDriver, makeServiceWorkerDriver, makeSocketDriver, makeTimerDriver, makeViewTransitionDOMDriver, mediaSource, messageText, mockDOMSource, noul, onDiagnostic, onlineSource, onlineStatus$, openResponses, openToolCalls, outputJsonSchema, pager, parseInput, persist, Portal as portal, processDrag, processForm, queryCache, removeNode, renderComponent, renderHead, renderToString, replyErrors, resizeSource, run, sampleCombine, score, selection, senderOf, set, setField, sortable, storageSource, strictSchemas, throttle, thunk, toJsonSchema, toTree, toggle, uiMessageStream, undo, undoable, visibilitySource, withToolResults, xs$1 as xs };
|
|
26390
26500
|
//# sourceMappingURL=index.esm.js.map
|