sygnal 7.2.0-preview.0 → 7.2.0-preview.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/CHANGELOG.md +49 -0
- package/dist/astro/server.cjs.js +54 -69
- package/dist/astro/server.cjs.js.map +1 -1
- package/dist/astro/server.mjs +54 -69
- package/dist/astro/server.mjs.map +1 -1
- package/dist/diagnostics.cjs.js +2 -0
- package/dist/diagnostics.cjs.js.map +1 -1
- package/dist/diagnostics.esm.js +2 -0
- package/dist/diagnostics.esm.js.map +1 -1
- package/dist/guide/http.md +110 -8
- package/dist/guide/resources.md +114 -6
- package/dist/guide/ssr.md +5 -3
- package/dist/host.cjs.js +11 -5
- package/dist/host.cjs.js.map +1 -1
- package/dist/host.esm.js +11 -5
- package/dist/host.esm.js.map +1 -1
- package/dist/index.cjs.js +729 -412
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +165 -15
- package/dist/index.esm.js +729 -412
- 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-full.txt +6 -6
- package/llms.txt +6 -6
- package/package.json +1 -1
- package/src/core/cell.ts +1 -1
- package/src/core/hosts/collection.ts +1 -1
- package/src/core/instance.ts +5 -3
- package/src/core/markers/clientonly.ts +3 -3
- package/src/core/markers/portal.ts +21 -11
- package/src/cycle/dom/isolate.ts +3 -1
- package/src/cycle/dom/makeDOMDriver.ts +4 -2
- package/src/cycle/dom/mockDOMSource.ts +3 -3
- package/src/cycle/dom/propsModule.ts +5 -1
- package/src/extra/ai/transports/shared.ts +3 -44
- package/src/extra/diagnostics/checks/fetch.ts +1 -0
- package/src/extra/fetchDriver.ts +180 -39
- package/src/extra/fetchEventSource.ts +1 -1
- package/src/extra/queryCache.ts +40 -10
- package/src/extra/replay/version.ts +1 -1
- package/src/extra/sse.ts +48 -0
- package/src/extra/ssr.ts +34 -75
- package/src/extra/testing.ts +97 -9
- package/src/extra/virtual.ts +3 -1
- package/src/extra/widget.ts +1 -6
- package/src/index.d.ts +163 -13
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,55 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to Sygnal are listed here. Versions follow [semantic versioning](https://semver.org). Releases before 5.4.0 are described in the [GitHub releases](https://github.com/tpresley/sygnal/releases) and tags.
|
|
4
4
|
|
|
5
|
+
## 7.2.0-preview.1 — dev preview
|
|
6
|
+
|
|
7
|
+
Fixes and additions from porting a large React app to the 7.2 preview. Everything is additive except
|
|
8
|
+
the two entries marked **Behaviour change** (a view returning `null`, and `false` on a string prop).
|
|
9
|
+
|
|
10
|
+
**New.**
|
|
11
|
+
- **Streamed responses on `makeFetchDriver`:** a request with `stream: 'sse' | 'ndjson' | 'text'` (a
|
|
12
|
+
POST too) sends each piece to its `chunk` action, then `ok` with `{ status, headers }` at the end.
|
|
13
|
+
`done: '[DONE]'` (or a predicate) ends the stream on a sentinel; `meta: true` gives SSE pieces as
|
|
14
|
+
`{ data, event, id }`. `abort`, `latest` and `timeoutMs` (the time to the first byte) apply; retry,
|
|
15
|
+
the cache, `parse` and `validate` don't, and resources never stream. Tests: `t.push('HTTP', piece)`,
|
|
16
|
+
then `t.respond('HTTP', null)` to end it.
|
|
17
|
+
- **`keepHeaders: ['X-Computed-At']` on a request:** a resource's state gets `headers` (lowercased
|
|
18
|
+
names, `null` when absent) next to `data`. The query cache stores them, so cache hits, SSR
|
|
19
|
+
`dehydrate()` / `hydrate()` and refetches keep them. `cache.set(request, data, headers?)`.
|
|
20
|
+
- **`{ reset: true | tag | tags | '/prefix' | predicate }` on the HTTP sink** (logout): matching cache
|
|
21
|
+
entries are dropped and matching mounted resources go back to `'loading'` with no data, then refetch
|
|
22
|
+
(React Query's `resetQueries`). `queryCache().clear()` drops every entry.
|
|
23
|
+
- **Response headers on failures:** the `error` reply, `errors()` and the Error itself carry `headers`.
|
|
24
|
+
- **`Comp<{ state, props, actions, calculated, context, parent, ... }>` / `RootComp<{ ... }>`**
|
|
25
|
+
(`ComponentSpec`, `RootComponentSpec`): components typed by name instead of seven positional
|
|
26
|
+
generics; `parent: Payload` types the parent's `CHILD.select(C)`. A misspelled key is a type error.
|
|
27
|
+
The positional `Component<...>` is unchanged. This is the form docs and llms.txt now show; 8.0 plans
|
|
28
|
+
to make it `Component`'s only form.
|
|
29
|
+
- **`t.location.query`** (the parsed query, as `state.route.query`).
|
|
30
|
+
|
|
31
|
+
**Changed.**
|
|
32
|
+
- **Behaviour change:** a view returning `null`, `false` or `undefined` renders nothing (an empty
|
|
33
|
+
comment), as in React; it rendered an empty `<div>`. `ClientOnly` with no children renders nothing too.
|
|
34
|
+
- **Behaviour change:** `false` on a string DOM property (`title={on && 'tip'}`) removes the attribute,
|
|
35
|
+
as React and `renderToString` already did; it rendered `title="false"`. `value`, `contentEditable`
|
|
36
|
+
and custom-element properties are unchanged.
|
|
37
|
+
- A `next()` timeout in `renderComponent` names the usual cause: an awaited `t.respond` /
|
|
38
|
+
`t.simulateEvent` has already settled, so read `t.state`.
|
|
39
|
+
|
|
40
|
+
**Fixed.**
|
|
41
|
+
- A resource whose behaviour-only fields change (`refetchEvery: s.live ? 5000 : undefined`,
|
|
42
|
+
`keepPrevious`, `retry`, `staleTime`, `ok` / `error`, …) keeps its data and status: polling starts,
|
|
43
|
+
stops or reschedules without a new request. It went back to `'loading'` with its data cleared.
|
|
44
|
+
- Components inside `<Portal>` are instantiated (they rendered as a literal tag).
|
|
45
|
+
- On the server, a child without a `state` prop gets its parent's state, as on the client (it got its
|
|
46
|
+
own `initialState`).
|
|
47
|
+
|
|
48
|
+
**Docs.** Reading response headers (`keepHeaders`, or a `parse` function, which is uncached), the logout
|
|
49
|
+
reset, a cursor infinite list, streamed POSTs (a live demo), and the router block's leave-page prompt in
|
|
50
|
+
llms.txt.
|
|
51
|
+
|
|
52
|
+
**Companion packages.** sygnal-check accepts `chunk` as an HTTP reply action.
|
|
53
|
+
|
|
5
54
|
## 7.2.0-preview.0 — dev preview
|
|
6
55
|
|
|
7
56
|
A dev preview (`npm install sygnal@next sygnal-check@next`). Its new subpaths and the `compile` option are
|
package/dist/astro/server.cjs.js
CHANGED
|
@@ -2885,6 +2885,17 @@ const isObj = (o) => o !== null && typeof o == 'object' && !Array.isArray(o);
|
|
|
2885
2885
|
* Children read through their parent's decorated cell, so they see its calculated fields, and a
|
|
2886
2886
|
* child's write stores freshly computed ones (4-F).
|
|
2887
2887
|
*/
|
|
2888
|
+
/**
|
|
2889
|
+
* SPIKE (state paths): a state designator as segments. A plain key is one segment; 'a.b.0' and
|
|
2890
|
+
* 'a[0].b' are paths into nested objects and arrays. No array form: an array `from` / `state` is
|
|
2891
|
+
* the data passed by mistake (SYG410 / SYG412), and ['x', 'y'] would be ambiguous.
|
|
2892
|
+
*/
|
|
2893
|
+
const pathOf = (p) => p.replace(/\[(\d+)\]/g, '.$1').split('.');
|
|
2894
|
+
const getIn = (o, ks) => { for (const k of ks) {
|
|
2895
|
+
if (o == null)
|
|
2896
|
+
return undefined;
|
|
2897
|
+
o = o[k];
|
|
2898
|
+
} return o; };
|
|
2888
2899
|
// ------------------------------------------------------------------ Collection items
|
|
2889
2900
|
/**
|
|
2890
2901
|
* An item's key: the `id` of an object item (anything but undefined / null, compared as a string,
|
|
@@ -3099,7 +3110,7 @@ function withResources(def, state, ctx) {
|
|
|
3099
3110
|
}
|
|
3100
3111
|
catch (_) { }
|
|
3101
3112
|
const E = q && ssrCache ? ssrCache.get(ssrCache.key(q, 1)) : undefined;
|
|
3102
|
-
out[n] = E && E.at > 0 ? { status: 'success', data: E.v } : { status: q ? 'loading' : 'idle' };
|
|
3113
|
+
out[n] = E && E.at > 0 ? { status: 'success', data: E.v, ...(E.h && { headers: E.h }) } : { status: q ? 'loading' : 'idle' };
|
|
3103
3114
|
}
|
|
3104
3115
|
return out;
|
|
3105
3116
|
}
|
|
@@ -3116,7 +3127,10 @@ function childUid(uid, vnode, path) {
|
|
|
3116
3127
|
// G-567: a view that returns a string or a number renders it as text (core/instance.ts view());
|
|
3117
3128
|
// another falsy value, an empty <div>
|
|
3118
3129
|
const textOr = (r) => typeof r == 'string' || typeof r == 'number' ? { text: '' + r } : r;
|
|
3119
|
-
|
|
3130
|
+
// a view that returns null, false or undefined renders nothing: an empty comment, as the client's
|
|
3131
|
+
// (core/instance.ts view()), so hydration pairs it
|
|
3132
|
+
const nothing = () => ({ sel: '!', data: {}, children: undefined, text: '', elm: undefined, key: undefined });
|
|
3133
|
+
const viewOut = (r) => textOr(r) || nothing();
|
|
3120
3134
|
const errorDiv = () => ({ sel: 'div', data: { attrs: { 'data-sygnal-error': '' } }, children: [], text: undefined, elm: undefined, key: undefined });
|
|
3121
3135
|
/**
|
|
3122
3136
|
* A view threw: the component's onError boundary picks the fallback (the error <div> without
|
|
@@ -3229,9 +3243,7 @@ function renderRoot(componentDef, options) {
|
|
|
3229
3243
|
// Error boundary
|
|
3230
3244
|
vnode = viewFailed(componentDef, err, componentDef.componentName || componentDef.name || 'Component');
|
|
3231
3245
|
}
|
|
3232
|
-
|
|
3233
|
-
vnode = { sel: 'div', data: {}, children: [], text: undefined, elm: undefined, key: undefined };
|
|
3234
|
-
}
|
|
3246
|
+
vnode = viewOut(vnode);
|
|
3235
3247
|
// Process special components in the VNode tree
|
|
3236
3248
|
vnode = processSSRTree(vnode, mergedContext, resolvedState, uid, 'r');
|
|
3237
3249
|
// PLAN-4 3-R: mark the root element (a fragment's first element) as Sygnal's server markup, so
|
|
@@ -3248,7 +3260,7 @@ function renderRoot(componentDef, options) {
|
|
|
3248
3260
|
return f;
|
|
3249
3261
|
}
|
|
3250
3262
|
};
|
|
3251
|
-
const first =
|
|
3263
|
+
const first = firstEl(vnode);
|
|
3252
3264
|
if (first && !innerHtmlMode)
|
|
3253
3265
|
first.data = { ...first.data, attrs: { 'data-sygnal-ssr': '', ...first.data?.attrs } };
|
|
3254
3266
|
// Serialize to HTML
|
|
@@ -3425,16 +3437,26 @@ function renderSubComponent(vnode, context, parentState, uid) {
|
|
|
3425
3437
|
key: undefined,
|
|
3426
3438
|
};
|
|
3427
3439
|
}
|
|
3428
|
-
//
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3440
|
+
// the child's state, as the client's cells give it (core/cell.ts, Inst.child): a key or path
|
|
3441
|
+
// (`"a.b"`, `"a[0].b"`), a lens's get(), else the parent's state (an isolatedState child: its
|
|
3442
|
+
// initialState; a `state` prop that is not a lens: the parent's whole state, SYG410)
|
|
3443
|
+
const stateProp = childProps.state, own = componentDef.initialState;
|
|
3444
|
+
let childState;
|
|
3445
|
+
if (typeof stateProp === 'string') {
|
|
3446
|
+
childState = parentState == null ? undefined : getIn(parentState, pathOf(stateProp));
|
|
3447
|
+
if (childState === undefined)
|
|
3448
|
+
childState = own;
|
|
3449
|
+
}
|
|
3450
|
+
else if (typeof stateProp?.get === 'function') {
|
|
3451
|
+
try {
|
|
3452
|
+
childState = stateProp.get(parentState);
|
|
3453
|
+
}
|
|
3454
|
+
catch (_) {
|
|
3455
|
+
childState = undefined;
|
|
3456
|
+
}
|
|
3437
3457
|
}
|
|
3458
|
+
else
|
|
3459
|
+
childState = componentDef.isolatedState ? own : parentState;
|
|
3438
3460
|
childState = withResources(componentDef, childState, contextOf(componentDef, childState, context));
|
|
3439
3461
|
// Build child context
|
|
3440
3462
|
const childContext = contextOf(componentDef, childState, context);
|
|
@@ -3591,14 +3613,15 @@ function renderCollection(vnode, context, parentState, uid, limit, skip, deco) {
|
|
|
3591
3613
|
catch (err) {
|
|
3592
3614
|
itemVnode = viewFailed(itemComponent, err, itemComponent.name || 'CollectionItem');
|
|
3593
3615
|
}
|
|
3594
|
-
const out = processSSRTree(
|
|
3616
|
+
const out = processSSRTree(viewOut(itemVnode), itemContext, itemState, itemUid, 'r');
|
|
3595
3617
|
// PLAN-5 A-1: the names the client's Collection gives a keyed item's root element
|
|
3596
|
-
const
|
|
3618
|
+
const el = out?.sel && out.sel != '!';
|
|
3619
|
+
const named = vtn && isItemObj && itemState.id != null && el
|
|
3597
3620
|
? { ...out, data: { ...out.data, style: vtStyle(vtn, itemState.id, out.data?.style) } }
|
|
3598
3621
|
: out;
|
|
3599
3622
|
// a VirtualCollection's row decoration, with the row's position in the list (G-431: counted
|
|
3600
3623
|
// before the rows that render nothing are left out, as the client counts them)
|
|
3601
|
-
return deco &&
|
|
3624
|
+
return deco && el ? deco(named, pos) : named;
|
|
3602
3625
|
}).filter((v) => v != null);
|
|
3603
3626
|
return frag(renderedItems);
|
|
3604
3627
|
}
|
|
@@ -3641,59 +3664,19 @@ function renderVirtual(vnode, context, parentState, uid) {
|
|
|
3641
3664
|
]);
|
|
3642
3665
|
}
|
|
3643
3666
|
/**
|
|
3644
|
-
*
|
|
3667
|
+
* A Switchable: its `current` page of `of`, rendered as the client's host makes it
|
|
3668
|
+
* (core/hosts/switchable.ts): the Switchable's props and children, its `state` (a key, a lens,
|
|
3669
|
+
* or a per-page map `{ page: key | lens }`; none: the owner's state). A `current` that isn't a
|
|
3670
|
+
* key of `of` renders nothing (the client renders the owner's error fallback, SYG416)
|
|
3645
3671
|
*/
|
|
3646
3672
|
function renderSwitchable(vnode, context, parentState, uid) {
|
|
3647
|
-
const props = vnode.data?.props || {};
|
|
3648
|
-
const
|
|
3649
|
-
if (
|
|
3650
|
-
return
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
if (typeof active === 'string') {
|
|
3655
|
-
// Direct name or state property
|
|
3656
|
-
if (components[active]) {
|
|
3657
|
-
activeName = active;
|
|
3658
|
-
}
|
|
3659
|
-
else if (parentState && typeof parentState[active] === 'string') {
|
|
3660
|
-
activeName = parentState[active];
|
|
3661
|
-
}
|
|
3662
|
-
}
|
|
3663
|
-
if (!activeName && initial) {
|
|
3664
|
-
activeName = initial;
|
|
3665
|
-
}
|
|
3666
|
-
if (!activeName) {
|
|
3667
|
-
activeName = Object.keys(components)[0];
|
|
3668
|
-
}
|
|
3669
|
-
const activeComponent = components[activeName];
|
|
3670
|
-
if (!activeComponent || typeof activeComponent !== 'function') {
|
|
3671
|
-
return { sel: 'div', data: {}, children: [], text: undefined, elm: undefined, key: undefined };
|
|
3672
|
-
}
|
|
3673
|
-
return renderToStringInternal(activeComponent, parentState, context, uid + '-' + uidPart(activeName));
|
|
3674
|
-
}
|
|
3675
|
-
/**
|
|
3676
|
-
* Internal helper: render a component def to a VNode (not HTML string).
|
|
3677
|
-
*/
|
|
3678
|
-
function renderToStringInternal(componentDef, state, context, uid) {
|
|
3679
|
-
const own = state !== undefined ? state : componentDef.initialState;
|
|
3680
|
-
const resolvedState = withResources(componentDef, own, contextOf(componentDef, own, context));
|
|
3681
|
-
const mergedContext = contextOf(componentDef, resolvedState, context);
|
|
3682
|
-
collectHead(componentDef, resolvedState, mergedContext);
|
|
3683
|
-
let vnode;
|
|
3684
|
-
try {
|
|
3685
|
-
vnode = componentDef({
|
|
3686
|
-
state: resolvedState,
|
|
3687
|
-
children: [],
|
|
3688
|
-
slots: {},
|
|
3689
|
-
context: mergedContext,
|
|
3690
|
-
uid: makeUid(uid),
|
|
3691
|
-
});
|
|
3692
|
-
}
|
|
3693
|
-
catch (err) {
|
|
3694
|
-
vnode = viewFailed(componentDef, err, componentDef.name || 'Component');
|
|
3695
|
-
}
|
|
3696
|
-
return processSSRTree(viewOut(vnode), mergedContext, resolvedState, uid, 'r');
|
|
3673
|
+
const props = vnode.data?.props || {}, of = props.of, cur = props.current;
|
|
3674
|
+
const page = of && typeof cur == 'string' && of[cur];
|
|
3675
|
+
if (typeof page != 'function')
|
|
3676
|
+
return null;
|
|
3677
|
+
const st = props.state, map = st && typeof st == 'object' && typeof st.get != 'function' ? st : null;
|
|
3678
|
+
const pageProps = { ...props, state: map ? map[cur] : st };
|
|
3679
|
+
return renderSubComponent({ sel: cur, data: { props: pageProps, c: page }, children: vnode.children }, context, parentState, uid + '-' + uidPart(cur));
|
|
3697
3680
|
}
|
|
3698
3681
|
const ws = (t) => t.replace(/[\t\n\f\r ]+/g, ' ').replace(/^ | $/g, '');
|
|
3699
3682
|
const fieldValue = (data) => data?.props?.value ?? data?.attrs?.value;
|
|
@@ -3730,6 +3713,8 @@ function vnodeToHtml(vnode, sv) {
|
|
|
3730
3713
|
return escapeHtml(String(vnode.text));
|
|
3731
3714
|
// VNode with text content and a selector
|
|
3732
3715
|
const sel = vnode.sel;
|
|
3716
|
+
if (sel == '!')
|
|
3717
|
+
return `<!--${String(vnode.text ?? '').replace(/--/g, '- -')}-->`;
|
|
3733
3718
|
if (!sel) {
|
|
3734
3719
|
if (vnode.text != null)
|
|
3735
3720
|
return escapeHtml(String(vnode.text));
|