sygnal 6.5.0 → 7.0.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 +156 -0
- package/README.md +2 -0
- package/dist/astro/index.cjs.js +423 -27
- package/dist/astro/index.cjs.js.map +1 -1
- package/dist/astro/index.mjs +423 -27
- package/dist/astro/index.mjs.map +1 -1
- package/dist/astro/server.cjs.js.map +1 -1
- package/dist/astro/server.mjs.map +1 -1
- package/dist/devtools.cjs.js +141 -3
- package/dist/devtools.cjs.js.map +1 -1
- package/dist/devtools.esm.js +141 -4
- package/dist/devtools.esm.js.map +1 -1
- package/dist/diagnostics.cjs.js +169 -14
- package/dist/diagnostics.cjs.js.map +1 -1
- package/dist/diagnostics.esm.js +168 -15
- package/dist/diagnostics.esm.js.map +1 -1
- package/dist/guide/accessibility.md +2 -0
- package/dist/guide/ai-chat.md +6 -6
- package/dist/guide/behaviors.md +1 -1
- package/dist/guide/element-commands.md +4 -2
- package/dist/guide/forms-reference.md +89 -3
- package/dist/guide/forms.md +8 -8
- package/dist/guide/http.md +77 -7
- package/dist/guide/migrating-to-7.md +314 -0
- package/dist/guide/recipes/ai-summarize.md +2 -1
- package/dist/guide/recipes/i18n.md +1 -1
- package/dist/guide/ui/tabs.md +5 -4
- package/dist/guide/ui/tooltip.md +24 -0
- package/dist/index.cjs.js +781 -320
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +319 -43
- package/dist/index.esm.js +781 -320
- 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/ui.cjs.js +96 -11
- package/dist/ui.cjs.js.map +1 -1
- package/dist/ui.esm.js +96 -12
- package/dist/ui.esm.js.map +1 -1
- package/dist/vike/config/package.json +1 -1
- package/dist/vike/onRenderClient.cjs.js +63 -43
- package/dist/vike/onRenderClient.cjs.js.map +1 -1
- package/dist/vike/onRenderClient.mjs +63 -43
- package/dist/vike/onRenderClient.mjs.map +1 -1
- package/dist/vike/onRenderHtml.cjs.js +21 -25
- package/dist/vike/onRenderHtml.cjs.js.map +1 -1
- package/dist/vike/onRenderHtml.mjs +21 -25
- package/dist/vike/onRenderHtml.mjs.map +1 -1
- package/dist/vite/plugin.cjs.js +410 -27
- package/dist/vite/plugin.cjs.js.map +1 -1
- package/dist/vite/plugin.mjs +410 -27
- package/dist/vite/plugin.mjs.map +1 -1
- package/llms.txt +17 -16
- package/package.json +1 -1
- package/src/astro/index.d.ts +8 -0
- package/src/astro/index.ts +18 -0
- package/src/core/actions.ts +28 -21
- package/src/core/define.ts +2 -0
- package/src/core/hooks.ts +8 -2
- package/src/core/hosts/collection.ts +4 -3
- package/src/core/instance.ts +60 -16
- package/src/core/runtime.ts +5 -1
- package/src/core/view.ts +2 -0
- package/src/cycle/dom/utils.ts +4 -2
- package/src/devtools.d.ts +13 -0
- package/src/devtools.ts +3 -0
- package/src/extra/ai/chat/behavior.ts +3 -3
- package/src/extra/ai/commandBar.ts +3 -3
- package/src/extra/ai/compose/behavior.ts +3 -3
- package/src/extra/ai/transports/shared.ts +8 -2
- package/src/extra/behaviors.ts +21 -0
- package/src/extra/devGraph.ts +111 -0
- package/src/extra/devMcp.ts +1 -1
- package/src/extra/devtools.ts +18 -0
- package/src/extra/diagnostics/checks/fetch.ts +21 -1
- package/src/extra/diagnostics/checks/forms.ts +4 -3
- package/src/extra/diagnostics/checks/index.ts +2 -1
- package/src/extra/diagnostics/checks/inspect.ts +68 -6
- package/src/extra/diagnostics/checks/next.ts +3 -0
- package/src/extra/diagnostics/checks/public.d.ts +133 -12
- package/src/extra/diagnostics/checks/timingAudit.ts +47 -0
- package/src/extra/diagnostics/codes.ts +19 -0
- package/src/extra/fetchDriver.ts +112 -7
- package/src/extra/fetchEventSource.ts +113 -0
- package/src/extra/form.ts +68 -26
- package/src/extra/formHelpers.ts +3 -1
- package/src/extra/lens.ts +3 -2
- package/src/extra/queryCache.ts +4 -3
- package/src/extra/socketDriver.ts +10 -4
- package/src/extra/testing.ts +68 -11
- package/src/index.d.ts +199 -30
- package/src/index.ts +1 -0
- package/src/shared.ts +3 -0
- package/src/ui/dialog.ts +3 -2
- package/src/ui/tooltips.ts +92 -0
- package/src/ui.d.ts +35 -0
- package/src/ui.ts +1 -0
- package/src/vike/onRenderClient.ts +44 -46
- package/src/vike/onRenderHtml.ts +5 -26
- package/src/vike/shell.ts +33 -0
- package/src/vite/graph.ts +255 -0
- package/src/vite/mcp.ts +3 -3
- package/src/vite/plugin.d.ts +19 -0
- package/src/vite/plugin.ts +144 -25
package/dist/index.cjs.js
CHANGED
|
@@ -82,6 +82,8 @@ const NOT_SINK = /^(__|(props|children|dispose|commands)\$$|(CHILD|PARENT|EFFECT
|
|
|
82
82
|
*/
|
|
83
83
|
const abortReason = {};
|
|
84
84
|
const abort = (reason) => (abortReason.r = reason, ABORT);
|
|
85
|
+
/** Private semantic binding identity, shared by built-in helpers and the runtime. */
|
|
86
|
+
const BINDING = /*#__PURE__*/ Symbol.for('sygnal.binding');
|
|
85
87
|
|
|
86
88
|
/**
|
|
87
89
|
* PLAN-4.6 (D162): `defineComponent(opts)` builds a component from an options object, for code
|
|
@@ -721,6 +723,9 @@ const DEV_CODE_SEVERITY = {
|
|
|
721
723
|
SYG128: 'error',
|
|
722
724
|
// PLAN-4 4-G2: static only (sygnal-check); listed so the entry knows its title
|
|
723
725
|
SYG129: 'warn',
|
|
726
|
+
// PLAN-10 X-1: static only (sygnal-check)
|
|
727
|
+
SYG170: 'warn',
|
|
728
|
+
SYG171: 'warn',
|
|
724
729
|
SYG221: 'error',
|
|
725
730
|
// PLAN-4 GS-4 (state.ts)
|
|
726
731
|
SYG222: 'warn',
|
|
@@ -769,6 +774,8 @@ const DEV_CODE_SEVERITY = {
|
|
|
769
774
|
SYG634: 'info',
|
|
770
775
|
// PLAN-3 5-5 (fetch.ts)
|
|
771
776
|
SYG635: 'warn',
|
|
777
|
+
// PLAN-11 U-1 (fetch.ts)
|
|
778
|
+
SYG636: 'warn',
|
|
772
779
|
// PLAN-4 GS-2 (checks/elementCommands.ts, through the core bridge; renderComponent too)
|
|
773
780
|
SYG640: 'warn',
|
|
774
781
|
SYG641: 'error',
|
|
@@ -885,6 +892,12 @@ const DEV_CODE_SEVERITY = {
|
|
|
885
892
|
// PLAN-9 X-2: static only (sygnal-check); listed so the entry knows their titles
|
|
886
893
|
SYG253: 'warn',
|
|
887
894
|
SYG254: 'info',
|
|
895
|
+
// PLAN-12: static only; proven after-state output guard contradiction.
|
|
896
|
+
SYG256: 'warn',
|
|
897
|
+
// 7.0: the opt-in timing audit (checks/next.ts; configureTimingAudit)
|
|
898
|
+
SYG257: 'info',
|
|
899
|
+
// PLAN-12: static only for legacy (6.x) binding contracts. Sygnal 7 remounts known targets.
|
|
900
|
+
SYG454: 'warn',
|
|
888
901
|
};
|
|
889
902
|
function docsUrlFor(code) {
|
|
890
903
|
return `${DOCS_BASE_URL}/reference/errors#${code.toLowerCase()}`;
|
|
@@ -1280,6 +1293,8 @@ function normalize$1(view, src) {
|
|
|
1280
1293
|
const list = [];
|
|
1281
1294
|
for (const sink in e)
|
|
1282
1295
|
list.push([sink, e[sink]]), sinks.add(sink);
|
|
1296
|
+
// 7.0: STATE first, so the other sinks get the state it committed
|
|
1297
|
+
list.sort((a, b) => +(a[0] != 'STATE') - +(b[0] != 'STATE'));
|
|
1283
1298
|
handlers.set(key, list);
|
|
1284
1299
|
}
|
|
1285
1300
|
const c = normCalc(name, src.calculated, src.initialState);
|
|
@@ -1701,6 +1716,8 @@ class View {
|
|
|
1701
1716
|
get state() { return this.i.cell.get(); }
|
|
1702
1717
|
get props() { return this.i.props; }
|
|
1703
1718
|
get context() { return this.i.context(); }
|
|
1719
|
+
/** D530: the context keys the last render's view read (null: none rendered, or no context) */
|
|
1720
|
+
get reads() { return this.i.keys; }
|
|
1704
1721
|
get isRoot() { return !this.i.parent; }
|
|
1705
1722
|
get kind() { return this.i.kind; }
|
|
1706
1723
|
/** false on a hidden Switchable page and below it */
|
|
@@ -1869,7 +1886,7 @@ function handle(inst, type, data, cause, src) {
|
|
|
1869
1886
|
return;
|
|
1870
1887
|
const def = inst.def;
|
|
1871
1888
|
const pre = inst.cell.get();
|
|
1872
|
-
let props, outs;
|
|
1889
|
+
let props, afterProps, post = pre, outs;
|
|
1873
1890
|
const next = (t, d, ms = 10, effect) => {
|
|
1874
1891
|
if (typeof ms !== 'number')
|
|
1875
1892
|
fail$2('SYG215', inst, `next() delay in '${type}' must be a number`, "Use next('ACTION', data, ms)");
|
|
@@ -1884,35 +1901,28 @@ function handle(inst, type, data, cause, src) {
|
|
|
1884
1901
|
};
|
|
1885
1902
|
for (const [sink, h0] of hs) {
|
|
1886
1903
|
const h = H.wrapHandler ? H.wrapHandler(viewOf(inst), type, sink, h0) || h0 : h0;
|
|
1887
|
-
|
|
1888
|
-
if (typeof h != 'function')
|
|
1889
|
-
continue;
|
|
1890
|
-
const failed = (e) => app.caught(inst, 'SYG214', `EFFECT handler '${type}' threw`, e, 'effect', type);
|
|
1891
|
-
try {
|
|
1892
|
-
const p = { ...(props || (props = inst_props(inst, pre))), signal: (inst.ac || (inst.ac = globalThis.AbortController && new AbortController()))?.signal };
|
|
1893
|
-
const r = h(pre, data, (t, d, ms) => next(t, d, ms, true), p);
|
|
1894
|
-
if (r?.then)
|
|
1895
|
-
r.then(null, failed);
|
|
1896
|
-
else if (r !== undefined && !isAbort(r))
|
|
1897
|
-
warn('SYG219', inst, `EFFECT handler '${type}' returned a value, which is ignored`, 'Use a STATE or driver sink');
|
|
1898
|
-
}
|
|
1899
|
-
catch (e) {
|
|
1900
|
-
failed(e);
|
|
1901
|
-
}
|
|
1902
|
-
continue;
|
|
1903
|
-
}
|
|
1904
|
+
const effect = sink == 'EFFECT';
|
|
1904
1905
|
let v;
|
|
1905
1906
|
if (typeof h == 'function') {
|
|
1906
1907
|
try {
|
|
1907
|
-
|
|
1908
|
+
const p = sink == 'STATE' ? props || (props = inst_props(inst, pre, pre)) : afterProps || (afterProps = inst_props(inst, post, pre));
|
|
1909
|
+
v = h(p.state, data, effect ? (t, d, ms) => next(t, d, ms, true) : next, effect ? { ...p, signal: (inst.ac || (inst.ac = globalThis.AbortController && new AbortController()))?.signal } : p);
|
|
1910
|
+
if (effect) {
|
|
1911
|
+
if (v?.then)
|
|
1912
|
+
v.then(null, (e) => handlerFailed(inst, type, sink, e));
|
|
1913
|
+
else if (v !== undefined && !isAbort(v))
|
|
1914
|
+
warn('SYG219', inst, `EFFECT handler '${type}' returned a value, which is ignored`, 'Use a STATE or driver sink');
|
|
1915
|
+
}
|
|
1908
1916
|
}
|
|
1909
1917
|
catch (e) {
|
|
1910
|
-
|
|
1918
|
+
handlerFailed(inst, type, sink, e);
|
|
1911
1919
|
continue;
|
|
1912
1920
|
}
|
|
1913
1921
|
}
|
|
1914
1922
|
else
|
|
1915
1923
|
v = h === undefined || h === true ? data : h;
|
|
1924
|
+
if (effect)
|
|
1925
|
+
continue;
|
|
1916
1926
|
if (sink == 'STATE' && (isAbort(v) || v === pre)) {
|
|
1917
1927
|
// D205: the DOM event being dispatched (window.event) is the one this action came from
|
|
1918
1928
|
if (/^(input|change)$/.test(globalThis.event?.type))
|
|
@@ -1925,6 +1935,11 @@ function handle(inst, type, data, cause, src) {
|
|
|
1925
1935
|
H.onReducer?.(viewOf(inst), type, pre, v);
|
|
1926
1936
|
dbg(inst, () => `<${type}> State reducer added`);
|
|
1927
1937
|
inst.cell.set(v);
|
|
1938
|
+
// a STATE that removed the component's own state (a deleted record it is bound to): the
|
|
1939
|
+
// outputs get its last state, as a Collection item's do
|
|
1940
|
+
post = inst.cell.get();
|
|
1941
|
+
if (post === undefined)
|
|
1942
|
+
post = pre;
|
|
1928
1943
|
// GS-12: this app's DOM driver patches inside a View Transition (makeViewTransitionDOMDriver
|
|
1929
1944
|
// reads the flag on its IsolateModule); includes?.: a non-array static lists nothing (G-228).
|
|
1930
1945
|
// Only for a state that changed structurally: today an equal one renders nothing (its
|
|
@@ -1973,9 +1988,14 @@ function handle(inst, type, data, cause, src) {
|
|
|
1973
1988
|
send$1(inst, outs[i], outs[i + 1]);
|
|
1974
1989
|
}
|
|
1975
1990
|
}
|
|
1976
|
-
/** the
|
|
1977
|
-
function
|
|
1978
|
-
|
|
1991
|
+
/** Keep the same failure boundary for sync handlers and rejected EFFECT promises. */
|
|
1992
|
+
function handlerFailed(inst, type, sink, e) {
|
|
1993
|
+
const effect = sink == 'EFFECT';
|
|
1994
|
+
inst.app.caught(inst, effect ? 'SYG214' : 'SYG216', effect ? `EFFECT handler '${type}' threw` : `Reducer for '${type}' threw; ${sink == 'STATE' ? 'state unchanged' : 'nothing sent'}`, e, effect ? 'effect' : 'reducer', type);
|
|
1995
|
+
}
|
|
1996
|
+
/** the handlers' 4th argument: the props, children, slots, context, uid, the state and the pre-action state */
|
|
1997
|
+
function inst_props(inst, state, previousState) {
|
|
1998
|
+
return { ...inst.props, children: inst.children, slots: inst.slots, context: inst.context(), uid: inst.uid, state, previousState };
|
|
1979
1999
|
}
|
|
1980
2000
|
/**
|
|
1981
2001
|
* A sink value of an instance: stamped with its sender (EVENTS; object values for a reply-capable
|
|
@@ -2086,9 +2106,11 @@ function isEqualNamespace(a, b) {
|
|
|
2086
2106
|
return true;
|
|
2087
2107
|
}
|
|
2088
2108
|
// G-261: a DOM change Sygnal makes outside a patch (a Transition's leave, a Portal mounted late)
|
|
2089
|
-
// re-emits the DOM source of the app whose root contains `el` (makeDOMDriver listens on its root)
|
|
2109
|
+
// re-emits the DOM source of the app whose root contains `el` (makeDOMDriver listens on its root).
|
|
2110
|
+
// The event comes from the element's own window: jsdom refuses an `Event` from another realm (a
|
|
2111
|
+
// Transition's leave timer that fires after a jsdom test environment was torn down)
|
|
2090
2112
|
const POKE = 'sygnal-dom';
|
|
2091
|
-
const pokeDOM = (el) => el?.dispatchEvent(new Event(POKE, { bubbles: true }));
|
|
2113
|
+
const pokeDOM = (el) => el?.dispatchEvent(new (el.ownerDocument?.defaultView?.Event || Event)(POKE, { bubbles: true }));
|
|
2092
2114
|
const obj = (o) => o !== null && typeof o == 'object' && !Array.isArray(o);
|
|
2093
2115
|
/**
|
|
2094
2116
|
* P46-P: two vnodes' data are the same for the DOM modules: the same keys, each value identical
|
|
@@ -2207,6 +2229,19 @@ const flat = (v) => {
|
|
|
2207
2229
|
* - dispose(): DISPOSE, onDispose, dispose$, the EFFECT signal aborted, the subtree, then the
|
|
2208
2230
|
* streams (the scoped _remove swap: no xstream stop timers, D165/Q18).
|
|
2209
2231
|
*/
|
|
2232
|
+
// v7: a known binding target joins key/id/path in the lifetime identity. Fresh equivalent
|
|
2233
|
+
// byId helpers retain identity; opaque lenses need an explicit key for a new target.
|
|
2234
|
+
function bindingChanged(a, b) {
|
|
2235
|
+
if (a === b)
|
|
2236
|
+
return false;
|
|
2237
|
+
const x = a?.[BINDING], y = b?.[BINDING];
|
|
2238
|
+
return x && y ? !x.every((v, i) => Object.is(v, y[i]))
|
|
2239
|
+
: !!(x || y || typeof a == 'string' || typeof b == 'string' || a === undefined || b === undefined);
|
|
2240
|
+
}
|
|
2241
|
+
function bindingState(k) {
|
|
2242
|
+
const c = k.base || k.cell || k.arr;
|
|
2243
|
+
return c && (c.has?.() ?? c.get() !== undefined) ? (k.cell || k.arr).get() : undefined;
|
|
2244
|
+
}
|
|
2210
2245
|
const ERR_FIX$1 = 'See the attached error';
|
|
2211
2246
|
// 4-I G-557: a fragment root (a Collection's): each of its top-level elements is marked.
|
|
2212
2247
|
// 4-K G-572: a text root (no element; an attribute would land on a Text node) gets `ready: false`
|
|
@@ -2249,8 +2284,8 @@ const idOf$1 = (n, path) => {
|
|
|
2249
2284
|
return `${n.sel}::${(s && s.replace(/"/g, '')) || path}`;
|
|
2250
2285
|
};
|
|
2251
2286
|
/**
|
|
2252
|
-
* a component vnode's props, without `resetState` (D174: read at creation,
|
|
2253
|
-
* prop of the child)
|
|
2287
|
+
* a component vnode's props, without `resetState` (D174: read at creation, never a
|
|
2288
|
+
* prop of the child). Known `state` target changes start a new lifetime in v7.
|
|
2254
2289
|
*/
|
|
2255
2290
|
function propsOf(p) {
|
|
2256
2291
|
if (!p)
|
|
@@ -2314,10 +2349,14 @@ const EMPTY_CTX = Object.freeze({});
|
|
|
2314
2349
|
/** a view read every key (spread, Object.keys): any context change re-renders it */
|
|
2315
2350
|
const ALL = '\u0000all';
|
|
2316
2351
|
let IDS = 0;
|
|
2317
|
-
/**
|
|
2352
|
+
/**
|
|
2353
|
+
* a child that failed to instantiate: renders the owner's error fallback (SYG408). It keeps its
|
|
2354
|
+
* props, so only a changed binding target makes another attempt (not every owner render)
|
|
2355
|
+
*/
|
|
2318
2356
|
class Failed {
|
|
2319
|
-
constructor(outv) {
|
|
2357
|
+
constructor(outv, props) {
|
|
2320
2358
|
this.outv = outv;
|
|
2359
|
+
this.props = props;
|
|
2321
2360
|
this.lr = true;
|
|
2322
2361
|
this.ready = true;
|
|
2323
2362
|
this.def = null;
|
|
@@ -2325,7 +2364,7 @@ class Failed {
|
|
|
2325
2364
|
this.shown = true;
|
|
2326
2365
|
}
|
|
2327
2366
|
render() { return this.outv; }
|
|
2328
|
-
setProps() { }
|
|
2367
|
+
setProps(p) { this.props = p; }
|
|
2329
2368
|
dispose() { }
|
|
2330
2369
|
}
|
|
2331
2370
|
class Inst {
|
|
@@ -2333,6 +2372,7 @@ class Inst {
|
|
|
2333
2372
|
this.app = app;
|
|
2334
2373
|
this.def = def;
|
|
2335
2374
|
this.parent = parent;
|
|
2375
|
+
this.base = base;
|
|
2336
2376
|
this.dom = dom;
|
|
2337
2377
|
this.scope = scope;
|
|
2338
2378
|
this.kind = kind;
|
|
@@ -2499,10 +2539,32 @@ class Inst {
|
|
|
2499
2539
|
}
|
|
2500
2540
|
/** called after each action and before each render pass: STATE.stream emits a new state */
|
|
2501
2541
|
notify() {
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2542
|
+
// A completed action can remove and reinsert a target before the next render. Owners
|
|
2543
|
+
// observe each transition, including hidden pages; the current action finishes first.
|
|
2544
|
+
// only the bound children (a string / byId binding), not every child
|
|
2545
|
+
if (this.bound)
|
|
2546
|
+
for (const k of this.bound) {
|
|
2547
|
+
if (k.disposed) {
|
|
2548
|
+
this.bound.delete(k);
|
|
2549
|
+
continue;
|
|
2550
|
+
}
|
|
2551
|
+
const previous = k.bs, state = k.bs = bindingState(k);
|
|
2552
|
+
if (previous !== undefined && state === undefined) {
|
|
2553
|
+
// Cleanup reads its last owned snapshot. A dying path must not recreate the
|
|
2554
|
+
// deleted target; the fresh lifetime receives a new writable cell at reconciliation.
|
|
2555
|
+
const cell = k.cell || k.arr;
|
|
2556
|
+
cell.get = () => previous;
|
|
2557
|
+
cell.set = () => { };
|
|
2558
|
+
k.dispose();
|
|
2559
|
+
this.forced = true;
|
|
2560
|
+
}
|
|
2561
|
+
}
|
|
2562
|
+
if (this.st$) {
|
|
2563
|
+
const v = this.cell.raw();
|
|
2564
|
+
if (v !== this.stLast && v !== undefined) {
|
|
2565
|
+
this.stLast = v;
|
|
2566
|
+
this.st$.shamefullySendNext(v);
|
|
2567
|
+
}
|
|
2506
2568
|
}
|
|
2507
2569
|
}
|
|
2508
2570
|
dispose$() {
|
|
@@ -2751,7 +2813,9 @@ class Inst {
|
|
|
2751
2813
|
for (const r of resolvers)
|
|
2752
2814
|
view = r(view, this) || view;
|
|
2753
2815
|
let k = this.kids.get(id);
|
|
2754
|
-
|
|
2816
|
+
// Collection and VirtualCollection share an array cell; Switchable binds `state`.
|
|
2817
|
+
const binding = host && sel != 'switchable' ? 'from' : 'state';
|
|
2818
|
+
if (k && (k.disposed || (view && k.def && k.def.view !== view) || bindingChanged(k.props?.[binding], props[binding]))) {
|
|
2755
2819
|
k.dispose();
|
|
2756
2820
|
this.kids.delete(id);
|
|
2757
2821
|
k = undefined;
|
|
@@ -2759,7 +2823,12 @@ class Inst {
|
|
|
2759
2823
|
if (k)
|
|
2760
2824
|
k.setProps(props, children, n, id);
|
|
2761
2825
|
else
|
|
2762
|
-
this.kids.set(id, host ? this.host(host, props, children, id, n) : this.child(view, props, children, id, !!data.props?.resetState));
|
|
2826
|
+
this.kids.set(id, k = host ? this.host(host, props, children, id, n) : this.child(view, props, children, id, !!data.props?.resetState));
|
|
2827
|
+
if (typeof props[binding] == 'string' || props[binding]?.[BINDING]) {
|
|
2828
|
+
k.bs = bindingState(k);
|
|
2829
|
+
(this.bound || (this.bound = new Set())).add(k);
|
|
2830
|
+
this.app.watchers.add(this);
|
|
2831
|
+
}
|
|
2763
2832
|
return;
|
|
2764
2833
|
}
|
|
2765
2834
|
if (posts[sel] && !(ps || (ps = [])).includes(sel))
|
|
@@ -2784,11 +2853,11 @@ class Inst {
|
|
|
2784
2853
|
return make(this, props, children, id, n);
|
|
2785
2854
|
}
|
|
2786
2855
|
catch (err) {
|
|
2787
|
-
return this.failed(err);
|
|
2856
|
+
return this.failed(err, props);
|
|
2788
2857
|
}
|
|
2789
2858
|
}
|
|
2790
2859
|
/** the owner's error fallback for a child that failed to instantiate (SYG408) */
|
|
2791
|
-
failed(err) {
|
|
2860
|
+
failed(err, props) {
|
|
2792
2861
|
const e = err instanceof Error ? err : new Error(String(err));
|
|
2793
2862
|
let out = errorDiv$1(this.def.name);
|
|
2794
2863
|
if (this.def.onError) {
|
|
@@ -2800,7 +2869,7 @@ class Inst {
|
|
|
2800
2869
|
}
|
|
2801
2870
|
}
|
|
2802
2871
|
this.app.caught(this, 'SYG408', 'Sub-component threw; rendering the error fallback', e, 'instantiate');
|
|
2803
|
-
return new Failed(out);
|
|
2872
|
+
return new Failed(out, props);
|
|
2804
2873
|
}
|
|
2805
2874
|
/** a tag child: state="key" | state={lens} | none (the parent's state) | isolatedState (D174: `resetState`) */
|
|
2806
2875
|
child(view, props, children, id, reset = false) {
|
|
@@ -2819,7 +2888,7 @@ class Inst {
|
|
|
2819
2888
|
return new Inst(app, def, this, cell, this.dom && this.dom.isolateSource(this.dom, scope), props, children, scope, this.uid(uidPart(id.replace(/.*::(r\.)?/, ''))), 'child', reset);
|
|
2820
2889
|
}
|
|
2821
2890
|
catch (err) {
|
|
2822
|
-
return this.failed(err);
|
|
2891
|
+
return this.failed(err, props);
|
|
2823
2892
|
}
|
|
2824
2893
|
}
|
|
2825
2894
|
inject(n, path) {
|
|
@@ -2882,8 +2951,7 @@ class Inst {
|
|
|
2882
2951
|
tearDown(() => { for (const s of subs)
|
|
2883
2952
|
s.unsubscribe(); }, app.dq, this);
|
|
2884
2953
|
this.subs = [];
|
|
2885
|
-
|
|
2886
|
-
app.watchers.delete(this);
|
|
2954
|
+
app.watchers.delete(this);
|
|
2887
2955
|
this.hub = null;
|
|
2888
2956
|
}
|
|
2889
2957
|
}
|
|
@@ -2982,7 +3050,7 @@ function arrayCell(owner, from) {
|
|
|
2982
3050
|
if (typeof from == 'string')
|
|
2983
3051
|
return keyCell(oc, from, owner.def.calcNames?.has(pathOf$1(from)[0]) && name, undefined, 'Collection');
|
|
2984
3052
|
if (isObj$2(from) && typeof from.get == 'function') {
|
|
2985
|
-
let lp = {}, lv;
|
|
3053
|
+
let lp = {}, lv, raw;
|
|
2986
3054
|
return {
|
|
2987
3055
|
get() {
|
|
2988
3056
|
const p = oc.get();
|
|
@@ -2998,7 +3066,7 @@ function arrayCell(owner, from) {
|
|
|
2998
3066
|
owner.app.appError(owner, e, 'view');
|
|
2999
3067
|
return lv;
|
|
3000
3068
|
}
|
|
3001
|
-
lv = g;
|
|
3069
|
+
raw = lv = g;
|
|
3002
3070
|
if (!Array.isArray(lv)) {
|
|
3003
3071
|
warn('SYG401', name, "Collection 'from' getter returned a non-array; it renders nothing", 'Return an array from get()', lv);
|
|
3004
3072
|
lv = NONE$1;
|
|
@@ -3006,6 +3074,7 @@ function arrayCell(owner, from) {
|
|
|
3006
3074
|
}
|
|
3007
3075
|
return lv;
|
|
3008
3076
|
},
|
|
3077
|
+
has() { this.get(); return raw !== undefined; },
|
|
3009
3078
|
set(v) {
|
|
3010
3079
|
if (typeof from.set != 'function')
|
|
3011
3080
|
return;
|
|
@@ -5367,8 +5436,11 @@ const validateWith = async (schema, value) => {
|
|
|
5367
5436
|
* declarations in src/index.d.ts and docs/guide/drivers.md. Summary:
|
|
5368
5437
|
*
|
|
5369
5438
|
* - Request (sink value): `{ url, ok?, error?, key?, category?, method?, headers?, query?, json?,
|
|
5370
|
-
* body?, latest?, timeoutMs?, parse?, init? }` or a URL string. `json` is stringified
|
|
5371
|
-
* content-type;
|
|
5439
|
+
* multipart?, body?, latest?, timeoutMs?, parse?, init? }` or a URL string. `json` is stringified
|
|
5440
|
+
* with a JSON content-type; PLAN-11 U-5: `multipart` (an object) becomes a FormData body (a
|
|
5441
|
+
* File / Blob a file part, an array repeats the name, null / undefined skipped, an object JSON,
|
|
5442
|
+
* anything else a string), the content-type left to the browser (its boundary); method defaults
|
|
5443
|
+
* to POST with a body/json/multipart, else GET. `query` is appended as a
|
|
5372
5444
|
* query string (null/undefined values skipped, arrays repeat the key, before any #fragment).
|
|
5373
5445
|
* Other fetch options (credentials, mode, cache, ...) go under `init` (R4-5); other top-level
|
|
5374
5446
|
* keys are the app's own: not sent, but returned on `request`.
|
|
@@ -5445,6 +5517,15 @@ const validateWith = async (schema, value) => {
|
|
|
5445
5517
|
* the request itself) into requests sent again in parallel and combined, at most 3 deep. One
|
|
5446
5518
|
* failure fails the request once (`error.status` / `error.body`). No cache entry sharing, no
|
|
5447
5519
|
* per-part retry. The test fake (`_whole`) sends and answers it as one request. decide() uses it.
|
|
5520
|
+
* - PLAN-11 U-1 (D555): `progress: 'ACTION'` replies to the sender with each progress step,
|
|
5521
|
+
* `progress: true` (or a string with no sender) sends it down `progress(category?)`:
|
|
5522
|
+
* `{ phase: 'upload' | 'download', loaded, total, fraction, category, request }` (`total` /
|
|
5523
|
+
* `fraction` null when the length is unknown). Such a request goes over XMLHttpRequest (the
|
|
5524
|
+
* only transport with upload progress in every engine; S-1), turned back into a Response so
|
|
5525
|
+
* parsing, status, retry and timeouts are the same. Steps are throttled to one per 50 ms per
|
|
5526
|
+
* phase, keeping the first and the last. Not for resources or `batch`; never shares a fetch.
|
|
5527
|
+
* No XMLHttpRequest (Node, a service worker): plain fetch, no steps. The transport is
|
|
5528
|
+
* `options.XMLHttpRequest`, else the global one, read at request time.
|
|
5448
5529
|
* - dispose / sink completion aborts everything in flight.
|
|
5449
5530
|
* - `fetch` is read at request time (options.fetch, else globalThis.fetch), so test stubs work.
|
|
5450
5531
|
* - PLAN-3 5-1 (H-9): renderComponent's HTTP fake runs this driver over an in-memory fetch;
|
|
@@ -5538,6 +5619,81 @@ const mergeHeaders = (...all) => {
|
|
|
5538
5619
|
h.forEach((v, k) => { out[k] = v; });
|
|
5539
5620
|
return out;
|
|
5540
5621
|
};
|
|
5622
|
+
// PLAN-11 U-1: statuses whose Response must have a null body
|
|
5623
|
+
const NULL_BODY = [101, 103, 204, 205, 304];
|
|
5624
|
+
const abortError$1 = () => typeof DOMException == 'function'
|
|
5625
|
+
? new DOMException('The operation was aborted.', 'AbortError')
|
|
5626
|
+
: Object.assign(new Error('The operation was aborted.'), { name: 'AbortError' });
|
|
5627
|
+
/**
|
|
5628
|
+
* PLAN-11 U-1 (S-1): a fetch-shaped call over XMLHttpRequest that reports progress:
|
|
5629
|
+
* `on(phase, loaded, total)` (total 0 when unknown), at most every 50 ms per phase, always the
|
|
5630
|
+
* first and the final step. Resolves with a real Response (blob body, parsed headers); network
|
|
5631
|
+
* errors and timeouts reject with a TypeError, an abort with an AbortError, like fetch.
|
|
5632
|
+
*/
|
|
5633
|
+
const xhrFetch = (X, url, init, on) => new Promise((res, rej) => {
|
|
5634
|
+
const x = new X();
|
|
5635
|
+
const last = {};
|
|
5636
|
+
const step = (phase, loaded, total, final) => {
|
|
5637
|
+
const l = last[phase], now = Date.now();
|
|
5638
|
+
if (l && (l.loaded === loaded || (!final && now - l.at < 50 && !(total && loaded >= total))))
|
|
5639
|
+
return;
|
|
5640
|
+
last[phase] = { loaded, at: now };
|
|
5641
|
+
on(phase, loaded, total);
|
|
5642
|
+
};
|
|
5643
|
+
const ev = (phase) => (e) => step(phase, e.loaded, e.lengthComputable ? e.total : 0);
|
|
5644
|
+
x.open(init.method || 'GET', url);
|
|
5645
|
+
x.responseType = 'blob';
|
|
5646
|
+
const h = init.headers || {};
|
|
5647
|
+
Object.keys(h).forEach(k => x.setRequestHeader(k, h[k]));
|
|
5648
|
+
if (init.credentials == 'include')
|
|
5649
|
+
x.withCredentials = true;
|
|
5650
|
+
const sig = init.signal;
|
|
5651
|
+
if (sig) {
|
|
5652
|
+
if (sig.aborted)
|
|
5653
|
+
return rej(abortError$1());
|
|
5654
|
+
sig.addEventListener?.('abort', () => x.abort(), { once: true });
|
|
5655
|
+
}
|
|
5656
|
+
if (init.body != null && x.upload) {
|
|
5657
|
+
x.upload.onprogress = ev('upload');
|
|
5658
|
+
x.upload.onload = (e) => step('upload', e.loaded, e.lengthComputable ? e.total : e.loaded, 1);
|
|
5659
|
+
}
|
|
5660
|
+
x.onprogress = ev('download');
|
|
5661
|
+
x.onload = () => {
|
|
5662
|
+
const size = x.response?.size ?? last.download?.loaded ?? 0;
|
|
5663
|
+
step('download', size, size, 1);
|
|
5664
|
+
const headers = typeof Headers == 'function' ? new Headers() : undefined;
|
|
5665
|
+
String(x.getAllResponseHeaders?.() || '').trim().split(/[\r\n]+/).forEach((line) => {
|
|
5666
|
+
const i = line.indexOf(':');
|
|
5667
|
+
if (i > 0)
|
|
5668
|
+
try {
|
|
5669
|
+
headers?.append(line.slice(0, i).trim(), line.slice(i + 1).trim());
|
|
5670
|
+
}
|
|
5671
|
+
catch (_) { }
|
|
5672
|
+
});
|
|
5673
|
+
try {
|
|
5674
|
+
res(new Response(NULL_BODY.includes(x.status) ? null : x.response, { status: x.status, statusText: x.statusText, headers }));
|
|
5675
|
+
}
|
|
5676
|
+
catch (e) {
|
|
5677
|
+
rej(e);
|
|
5678
|
+
}
|
|
5679
|
+
};
|
|
5680
|
+
x.onerror = x.ontimeout = () => rej(new TypeError(`Network request failed: ${url}`));
|
|
5681
|
+
x.onabort = () => rej(abortError$1());
|
|
5682
|
+
x.send(init.body ?? null);
|
|
5683
|
+
});
|
|
5684
|
+
/** PLAN-11 U-5: a request's `multipart` object as FormData (see the header) */
|
|
5685
|
+
const toFormData = (parts) => {
|
|
5686
|
+
const fd = new FormData();
|
|
5687
|
+
Object.keys(parts || {}).forEach(k => [].concat(parts[k]).forEach((v) => {
|
|
5688
|
+
if (v == null)
|
|
5689
|
+
return;
|
|
5690
|
+
if (typeof Blob != 'undefined' && v instanceof Blob)
|
|
5691
|
+
fd.append(k, v, v.name ?? 'blob');
|
|
5692
|
+
else
|
|
5693
|
+
fd.append(k, typeof v == 'object' ? JSON.stringify(v) : String(v));
|
|
5694
|
+
}));
|
|
5695
|
+
return fd;
|
|
5696
|
+
};
|
|
5541
5697
|
const IDEMPOTENT$1 = /^(GET|HEAD)$/i;
|
|
5542
5698
|
// D80: an invalidate value matches a request by tag, URL prefix ('/…') or predicate
|
|
5543
5699
|
const hit = (x, q) => {
|
|
@@ -5562,6 +5718,22 @@ function makeFetchDriver(options = {}) {
|
|
|
5562
5718
|
// q (the request), timer, f (the fetch it waits on), ok / ko (its outcomes) }
|
|
5563
5719
|
const inflight = new Map();
|
|
5564
5720
|
const subs = new Set();
|
|
5721
|
+
// PLAN-11 U-1: progress(category?) listeners (not held while nobody listens)
|
|
5722
|
+
const psubs = new Set();
|
|
5723
|
+
const emitProgress = (v) => psubs.forEach(s => {
|
|
5724
|
+
let m = false;
|
|
5725
|
+
try {
|
|
5726
|
+
m = matches(s, v);
|
|
5727
|
+
}
|
|
5728
|
+
catch (_) { }
|
|
5729
|
+
if (m)
|
|
5730
|
+
try {
|
|
5731
|
+
s.l.next(v);
|
|
5732
|
+
}
|
|
5733
|
+
catch (e) {
|
|
5734
|
+
console.error('[Sygnal] makeFetchDriver: a listener threw', e);
|
|
5735
|
+
}
|
|
5736
|
+
});
|
|
5565
5737
|
// R4-4: replies held until the first select() listener, failures until the first errors()
|
|
5566
5738
|
// listener (or the next macrotask, when they're logged if nothing listens)
|
|
5567
5739
|
const started = [false, false];
|
|
@@ -5624,6 +5796,10 @@ function makeFetchDriver(options = {}) {
|
|
|
5624
5796
|
return {
|
|
5625
5797
|
select: make(false),
|
|
5626
5798
|
errors: make(true),
|
|
5799
|
+
progress: (sel) => {
|
|
5800
|
+
let sub;
|
|
5801
|
+
return xs$2.create({ start: l => { psubs.add((sub = { l, sel, ns })); }, stop: () => { psubs.delete(sub); } });
|
|
5802
|
+
},
|
|
5627
5803
|
isolateSource: (_, scope) => source(ns.concat(scope)),
|
|
5628
5804
|
isolateSink: (sink$, scope) => sink$.map((req) => tagRequest(req, scope)),
|
|
5629
5805
|
// PLAN-4.6: the same per value (the core scopes a request without a stream per instance)
|
|
@@ -5743,7 +5919,7 @@ function makeFetchDriver(options = {}) {
|
|
|
5743
5919
|
req = { url: req };
|
|
5744
5920
|
if (!req || typeof req != 'object' || disposed || !allowed$1(req, 'makeFetchDriver'))
|
|
5745
5921
|
return;
|
|
5746
|
-
const { url, category, method, headers, query, json, body, latest, timeoutMs, parse, abort, init, ok, error, key, resources, refresh, invalidates, updates, retry, validate, prefetch } = req;
|
|
5922
|
+
const { url, category, method, headers, query, json, body, latest, timeoutMs, parse, abort, init, ok, error, key, resources, refresh, invalidates, updates, retry, validate, prefetch, progress } = req;
|
|
5747
5923
|
if ('invalidate' in req)
|
|
5748
5924
|
return inval(req.invalidate);
|
|
5749
5925
|
if (prefetch)
|
|
@@ -5795,7 +5971,9 @@ function makeFetchDriver(options = {}) {
|
|
|
5795
5971
|
}
|
|
5796
5972
|
const scope = scopeKey(req);
|
|
5797
5973
|
// reply actions: keyed per (sender, key ?? ok ?? error); a resource per (sender, '\0' + name)
|
|
5798
|
-
|
|
5974
|
+
// PLAN-11 U-1: a progress action is a reply action too (abort on dispose, its own lane)
|
|
5975
|
+
const pr = !pf && rn === undefined && !req.batch && progress;
|
|
5976
|
+
const rk = rn !== undefined ? '\0' + rn : !pf && sender !== undefined && (ok || error || typeof pr == 'string') ? key ?? ok ?? error ?? pr : undefined;
|
|
5799
5977
|
if (sender !== undefined && (typeof abort == 'string' || (abort && key !== undefined)))
|
|
5800
5978
|
return cancel(r => r.sender === sender && r.rk === (key ?? abort));
|
|
5801
5979
|
// per (scope, category): another component's requests are never cancelled
|
|
@@ -5805,7 +5983,7 @@ function makeFetchDriver(options = {}) {
|
|
|
5805
5983
|
cancel(rk === undefined ? r => r.rk === undefined && r.scope === scope && r.category === category : r => r.sender === sender && r.rk === rk);
|
|
5806
5984
|
const base = options.init;
|
|
5807
5985
|
// a request's URL and fetch() init (the batch's sub-requests inherit the request's fields)
|
|
5808
|
-
const prep = ({ url, method, headers, query, json, body, init: own }) => {
|
|
5986
|
+
const prep = ({ url, method, headers, query, json, multipart, body, init: own }) => {
|
|
5809
5987
|
const h = mergeHeaders(options.headers, base?.headers, own?.headers, headers);
|
|
5810
5988
|
let b = body ?? own?.body;
|
|
5811
5989
|
if (json !== undefined) {
|
|
@@ -5813,6 +5991,12 @@ function makeFetchDriver(options = {}) {
|
|
|
5813
5991
|
if (!Object.keys(h).some(k => k.toLowerCase() == 'content-type'))
|
|
5814
5992
|
h['content-type'] = 'application/json';
|
|
5815
5993
|
}
|
|
5994
|
+
else if (multipart !== undefined) {
|
|
5995
|
+
b = toFormData(multipart);
|
|
5996
|
+
// the browser writes multipart/form-data with its boundary; a content-type set here would break it
|
|
5997
|
+
Object.keys(h).forEach(k => { if (k.toLowerCase() == 'content-type')
|
|
5998
|
+
delete h[k]; });
|
|
5999
|
+
}
|
|
5816
6000
|
return [withQuery((options.baseUrl || '') + (url ?? ''), query), { ...pickInit(base), ...pickInit(own), method: method || own?.method || base?.method || (b !== undefined ? 'POST' : 'GET'), headers: h, body: b }];
|
|
5817
6001
|
};
|
|
5818
6002
|
const [href, fetchInit] = prep(req);
|
|
@@ -5821,7 +6005,8 @@ function makeFetchDriver(options = {}) {
|
|
|
5821
6005
|
// parts, at most 3 deep), their parsed bodies merged by `combine` into one reply. The test
|
|
5822
6006
|
// fake (_whole) answers such a request as one
|
|
5823
6007
|
const { batch, split, combine = (x) => x } = options._whole ? {} : req;
|
|
5824
|
-
|
|
6008
|
+
// PLAN-11 U-1: a progress request never shares a fetch (each sender gets its own steps)
|
|
6009
|
+
const k = pr ? undefined : C?.key(req, rn !== undefined || pf);
|
|
5825
6010
|
const id = ++seq;
|
|
5826
6011
|
const r = { category, scope, sender, rk };
|
|
5827
6012
|
inflight.set(id, r);
|
|
@@ -5869,7 +6054,19 @@ function makeFetchDriver(options = {}) {
|
|
|
5869
6054
|
const parser = parserOf(parse || options.parse);
|
|
5870
6055
|
// the global fetch is called as a method: a detached window.fetch throws "Illegal invocation"
|
|
5871
6056
|
const g = globalThis;
|
|
5872
|
-
|
|
6057
|
+
let doFetch = options.fetch || (typeof g.fetch == 'function' && ((u, i) => g.fetch(u, i)));
|
|
6058
|
+
// PLAN-11 U-1: a progress request over XMLHttpRequest (renderComponent's fake: `_progress`)
|
|
6059
|
+
const X = options.XMLHttpRequest ?? g.XMLHttpRequest;
|
|
6060
|
+
if (pr && (options._progress || typeof X == 'function')) {
|
|
6061
|
+
const step = (phase, loaded, total) => {
|
|
6062
|
+
if (!inflight.has(id) || disposed)
|
|
6063
|
+
return;
|
|
6064
|
+
const t = total > 0 ? total : null;
|
|
6065
|
+
const v = { phase, loaded, total: t, fraction: t ? Math.min(1, loaded / t) : null, category, request: req };
|
|
6066
|
+
typeof pr == 'string' && sender !== undefined ? reply(sender, pr, v) : emitProgress(v);
|
|
6067
|
+
};
|
|
6068
|
+
doFetch = options._progress ? (u, i) => options._progress(u, i, step, req) : (u, i) => xhrFetch(X, u, i, step);
|
|
6069
|
+
}
|
|
5873
6070
|
// the outcome, to every request waiting on this fetch (the failure once, after the last attempt)
|
|
5874
6071
|
const end = (e, x, status) => {
|
|
5875
6072
|
done(F);
|
|
@@ -6031,9 +6228,10 @@ function queryCache(o = {}) {
|
|
|
6031
6228
|
const key = (q, res) => {
|
|
6032
6229
|
if (typeof q == 'string')
|
|
6033
6230
|
q = { url: q };
|
|
6034
|
-
const { method, json, body, parse, query } = q;
|
|
6035
|
-
const m = (method || (json !== undefined || body !== undefined ? 'POST' : 'GET')).toUpperCase();
|
|
6036
|
-
|
|
6231
|
+
const { method, json, body, parse, query, multipart } = q;
|
|
6232
|
+
const m = (method || (json !== undefined || body !== undefined || multipart !== undefined ? 'POST' : 'GET')).toUpperCase();
|
|
6233
|
+
// PLAN-11 U-5: a multipart request is never cached (its body isn't a string)
|
|
6234
|
+
const b = json !== undefined ? JSON.stringify(json) : multipart !== undefined ? multipart : body ?? '';
|
|
6037
6235
|
if (q.cache === false || typeof b != 'string' || typeof parse == 'function' || parse == 'response' ||
|
|
6038
6236
|
!(q.cache || q.staleTime != null || (res && IDEMPOTENT.test(m))))
|
|
6039
6237
|
return;
|
|
@@ -6102,6 +6300,272 @@ function queryCache(o = {}) {
|
|
|
6102
6300
|
return C;
|
|
6103
6301
|
}
|
|
6104
6302
|
|
|
6303
|
+
/*
|
|
6304
|
+
* PLAN-6 L-2: what the HTTP transports share (side-effect free; each transport imports only what
|
|
6305
|
+
* it uses).
|
|
6306
|
+
* - post(): the fetch (the `fetch` option, 0-S5), the headers option (an object or a function of
|
|
6307
|
+
* the request), HTTP errors as Errors with `status` and the provider's message, and SYG670.
|
|
6308
|
+
* - sse(): a fetch Response body as SSE frames ({ event, data }), per the HTML spec's parsing
|
|
6309
|
+
* rules: CRLF / LF, comments, multi-line data. The reader is cancelled when the consumer stops
|
|
6310
|
+
* (abort, a thrown error), so the connection closes.
|
|
6311
|
+
* - SYG670: in a browser, a request with an auth header (Authorization, x-api-key, api-key,
|
|
6312
|
+
* x-goog-api-key) to a host that is neither local (localhost, 127.0.0.1, [::1], *.localhost)
|
|
6313
|
+
* nor the page's own origin is refused unless the transport has `dangerouslyAllowBrowser: true`:
|
|
6314
|
+
* the key would ship to every visitor. Same-origin requests are the app's own server (its
|
|
6315
|
+
* proxy), so a session header there is fine.
|
|
6316
|
+
*/
|
|
6317
|
+
const g$6 = globalThis;
|
|
6318
|
+
const AUTH = /^(authorization|x-api-key|api-key|x-goog-api-key)$/i;
|
|
6319
|
+
const LOCAL = /^(localhost|127\.0\.0\.1|\[::1\]|.+\.localhost)$/;
|
|
6320
|
+
/** the URL a transport posts to, or a SYG670 Error when it must not (see the header) */
|
|
6321
|
+
function guard(url, headers, opts, name) {
|
|
6322
|
+
if (opts.dangerouslyAllowBrowser || typeof g$6.document == 'undefined' || !Object.keys(headers).some(h => AUTH.test(h)))
|
|
6323
|
+
return;
|
|
6324
|
+
const here = g$6.location?.href;
|
|
6325
|
+
let u;
|
|
6326
|
+
try {
|
|
6327
|
+
u = new URL(url, here);
|
|
6328
|
+
}
|
|
6329
|
+
catch (_) {
|
|
6330
|
+
return;
|
|
6331
|
+
}
|
|
6332
|
+
if (LOCAL.test(u.hostname) || (here && u.origin == new URL(here).origin))
|
|
6333
|
+
return;
|
|
6334
|
+
const fix = `Call ${u.origin} from your server and point the transport at that route (an AI SDK route with uiMessageStream('/api/chat'), or your own proxy), so the key stays there. For a key the user typed in themselves, pass ${name}({ ..., dangerouslyAllowBrowser: true })`;
|
|
6335
|
+
const msg = `${name}: refused to send an auth header from the browser to ${u.origin}: anyone who opens the page can read it`;
|
|
6336
|
+
error('SYG670', undefined, msg, fix);
|
|
6337
|
+
fail$2('SYG670', undefined, msg, fix);
|
|
6338
|
+
}
|
|
6339
|
+
/**
|
|
6340
|
+
* POST JSON; a non-2xx response is an Error with `status`, `body` and the provider's message.
|
|
6341
|
+
* The transport's `body` option and the request's `body` are merged over the built body (so they
|
|
6342
|
+
* can override provider defaults such as `max_tokens`); `fixed` fields are merged last and can't be
|
|
6343
|
+
* overridden (G-654: uiMessageStream's protocol fields, as the AI SDK's DefaultChatTransport)
|
|
6344
|
+
*/
|
|
6345
|
+
async function post$1(url, body, req, signal, opts, name, fixed) {
|
|
6346
|
+
const h = typeof opts.headers == 'function' ? await opts.headers(req) : opts.headers || {};
|
|
6347
|
+
guard(url, h, opts, name);
|
|
6348
|
+
const f = opts.fetch || ((u, i) => g$6.fetch(u, i));
|
|
6349
|
+
const res = await f(url, { method: 'POST', headers: { 'content-type': 'application/json', ...h }, body: JSON.stringify({ ...body, ...opts.body, ...req.body, ...fixed }), signal });
|
|
6350
|
+
if (!res.ok) {
|
|
6351
|
+
let text = '', message = '';
|
|
6352
|
+
try {
|
|
6353
|
+
text = await res.text();
|
|
6354
|
+
const j = JSON.parse(text);
|
|
6355
|
+
message = j.error?.message ?? j.error ?? j.message ?? '';
|
|
6356
|
+
}
|
|
6357
|
+
catch (_) {
|
|
6358
|
+
message || (message = text.slice(0, 200));
|
|
6359
|
+
}
|
|
6360
|
+
throw Object.assign(new Error(`${name}: HTTP ${res.status}${message ? ': ' + message : ''}`), { status: res.status, body: text });
|
|
6361
|
+
}
|
|
6362
|
+
if (!res.body)
|
|
6363
|
+
throw new Error(`${name}: the response has no body`);
|
|
6364
|
+
return res;
|
|
6365
|
+
}
|
|
6366
|
+
/**
|
|
6367
|
+
* the SSE frames of a response body: `{ event, data }` (data: the joined data lines). PLAN-11
|
|
6368
|
+
* E-1: `meta` (optional) follows the stream's `id` (the last event id, kept across frames) and
|
|
6369
|
+
* `retry` (ms) fields, as EventSource does, for a reconnect
|
|
6370
|
+
*/
|
|
6371
|
+
async function* sse(res, meta) {
|
|
6372
|
+
const reader = res.body.getReader(), dec = new TextDecoder();
|
|
6373
|
+
let buf = '', cr = '', done = false;
|
|
6374
|
+
try {
|
|
6375
|
+
for (;;) {
|
|
6376
|
+
const r = await reader.read();
|
|
6377
|
+
// line ends -> \n (a \r at a chunk's end waits: it may be half of a \r\n)
|
|
6378
|
+
let s = cr + (r.done ? '\n\n' : dec.decode(r.value, { stream: true }));
|
|
6379
|
+
cr = !r.done && s.endsWith('\r') ? '\r' : '';
|
|
6380
|
+
if (cr)
|
|
6381
|
+
s = s.slice(0, -1);
|
|
6382
|
+
buf += s.replace(/\r\n?/g, '\n');
|
|
6383
|
+
done = r.done;
|
|
6384
|
+
let i;
|
|
6385
|
+
while ((i = buf.indexOf('\n\n')) >= 0) {
|
|
6386
|
+
const frame = buf.slice(0, i);
|
|
6387
|
+
buf = buf.slice(i + 2);
|
|
6388
|
+
let event = 'message', data = [];
|
|
6389
|
+
for (const line of frame.split('\n')) {
|
|
6390
|
+
if (!line || line[0] == ':')
|
|
6391
|
+
continue;
|
|
6392
|
+
const c = line.indexOf(':'), field = c < 0 ? line : line.slice(0, c);
|
|
6393
|
+
const v = c < 0 ? '' : line.slice(c + 1).replace(/^ /, '');
|
|
6394
|
+
if (field == 'event')
|
|
6395
|
+
event = v;
|
|
6396
|
+
else if (field == 'data')
|
|
6397
|
+
data.push(v);
|
|
6398
|
+
else if (meta && field == 'id' && !v.includes('\0'))
|
|
6399
|
+
meta.id = v;
|
|
6400
|
+
else if (meta && field == 'retry' && /^\d+$/.test(v))
|
|
6401
|
+
meta.retry = +v;
|
|
6402
|
+
}
|
|
6403
|
+
if (data.length)
|
|
6404
|
+
yield { event, data: data.join('\n') };
|
|
6405
|
+
}
|
|
6406
|
+
if (done)
|
|
6407
|
+
return;
|
|
6408
|
+
}
|
|
6409
|
+
}
|
|
6410
|
+
finally {
|
|
6411
|
+
if (!done)
|
|
6412
|
+
reader.cancel().catch(() => { });
|
|
6413
|
+
}
|
|
6414
|
+
}
|
|
6415
|
+
/** JSON.parse, or the text itself when it isn't JSON (a model's malformed tool arguments go to validation as is) */
|
|
6416
|
+
const json$1 = (s) => {
|
|
6417
|
+
if (typeof s != 'string')
|
|
6418
|
+
return s;
|
|
6419
|
+
try {
|
|
6420
|
+
return JSON.parse(s || '{}');
|
|
6421
|
+
}
|
|
6422
|
+
catch (_) {
|
|
6423
|
+
return s;
|
|
6424
|
+
}
|
|
6425
|
+
};
|
|
6426
|
+
/** a provider's usage as `{ inputTokens, outputTokens, totalTokens, reasoningTokens? }` (AI SDK names) */
|
|
6427
|
+
const usageOf = (u) => u && {
|
|
6428
|
+
inputTokens: u.input_tokens ?? u.prompt_tokens,
|
|
6429
|
+
outputTokens: u.output_tokens ?? u.completion_tokens,
|
|
6430
|
+
totalTokens: u.total_tokens,
|
|
6431
|
+
...((u.output_tokens_details ?? u.completion_tokens_details)?.reasoning_tokens != null && { reasoningTokens: (u.output_tokens_details ?? u.completion_tokens_details).reasoning_tokens }),
|
|
6432
|
+
};
|
|
6433
|
+
/** the parts of a message (a `content` string is one text part) */
|
|
6434
|
+
const partsOf = (m) => m.parts ?? (typeof m.content == 'string' ? [{ type: 'text', text: m.content }] : []);
|
|
6435
|
+
/** the tool name of a `tool-<name>` / `dynamic-tool` part, or '' */
|
|
6436
|
+
const toolName = (p) => p.type == 'dynamic-tool' ? p.toolName : p.type.startsWith('tool-') ? p.type.slice(5) : '';
|
|
6437
|
+
/** what a tool part sends back to the model, or undefined when it has no result yet */
|
|
6438
|
+
const toolOutput = (p) => p.state == 'output-available' ? (typeof p.output == 'string' ? p.output : JSON.stringify(p.output ?? null))
|
|
6439
|
+
: p.state == 'output-error' ? JSON.stringify({ error: p.errorText ?? 'error' })
|
|
6440
|
+
: p.state == 'output-denied' ? JSON.stringify({ error: 'The user denied this tool call' })
|
|
6441
|
+
: undefined;
|
|
6442
|
+
// dev-only diagnostics (SYG675): the dev entry formats them (checks/chat.ts)
|
|
6443
|
+
const dev$8 = (...a) => g$6.__SYGNAL_DIAGNOSTICS__?.chat?.(...a);
|
|
6444
|
+
|
|
6445
|
+
/*
|
|
6446
|
+
* PLAN-11 E-1: an EventSource over fetch, for server-sent events that need request headers
|
|
6447
|
+
* (`Authorization`), which the native EventSource can't send. makeSocketDriver uses it for an
|
|
6448
|
+
* `sse` connection with `headers`. It behaves as EventSource does where the driver relies on it:
|
|
6449
|
+
*
|
|
6450
|
+
* - connects on construction (a microtask later, so handlers can be set first); `readyState`
|
|
6451
|
+
* CONNECTING (0) → OPEN (1) on a 2xx `text/event-stream` response;
|
|
6452
|
+
* - `onmessage` and `addEventListener(type)` get `{ type, data, lastEventId }`;
|
|
6453
|
+
* - the stream ending or a network error: `error`, readyState CONNECTING, a reconnect after
|
|
6454
|
+
* `retry` ms (the stream's `retry:` field, default 3000) with `Last-Event-ID`;
|
|
6455
|
+
* - a non-2xx status or another content type: `error` with readyState CLOSED (no retry; the
|
|
6456
|
+
* driver's reconnect policy decides);
|
|
6457
|
+
* - `close()` aborts the request and stops retrying.
|
|
6458
|
+
*/
|
|
6459
|
+
const NAMES = ['CONNECTING', 'OPEN', 'CLOSED'];
|
|
6460
|
+
class FetchEventSource {
|
|
6461
|
+
constructor(url, init = {}) {
|
|
6462
|
+
this.readyState = 0;
|
|
6463
|
+
this.onopen = null;
|
|
6464
|
+
this.onmessage = null;
|
|
6465
|
+
this.onerror = null;
|
|
6466
|
+
this.ls = new Map();
|
|
6467
|
+
this.meta = { id: '', retry: 3000 };
|
|
6468
|
+
this.url = url;
|
|
6469
|
+
this.withCredentials = !!init.withCredentials;
|
|
6470
|
+
this.init = init;
|
|
6471
|
+
queueMicrotask(() => this.connect());
|
|
6472
|
+
}
|
|
6473
|
+
addEventListener(type, f) {
|
|
6474
|
+
let s = this.ls.get(type);
|
|
6475
|
+
if (!s)
|
|
6476
|
+
this.ls.set(type, (s = new Set()));
|
|
6477
|
+
s.add(f);
|
|
6478
|
+
}
|
|
6479
|
+
removeEventListener(type, f) { this.ls.get(type)?.delete(f); }
|
|
6480
|
+
close() {
|
|
6481
|
+
this.readyState = 2;
|
|
6482
|
+
clearTimeout(this.timer);
|
|
6483
|
+
try {
|
|
6484
|
+
this.ctl?.abort();
|
|
6485
|
+
}
|
|
6486
|
+
catch (_) { }
|
|
6487
|
+
}
|
|
6488
|
+
emit(type, ev) {
|
|
6489
|
+
if (type == 'message')
|
|
6490
|
+
this.onmessage?.(ev);
|
|
6491
|
+
else if (type == 'open')
|
|
6492
|
+
this.onopen?.(ev);
|
|
6493
|
+
else if (type == 'error')
|
|
6494
|
+
this.onerror?.(ev);
|
|
6495
|
+
this.ls.get(type)?.forEach(f => { try {
|
|
6496
|
+
f(ev);
|
|
6497
|
+
}
|
|
6498
|
+
catch (e) {
|
|
6499
|
+
console.error(e);
|
|
6500
|
+
} });
|
|
6501
|
+
}
|
|
6502
|
+
async connect() {
|
|
6503
|
+
if (this.readyState == 2)
|
|
6504
|
+
return;
|
|
6505
|
+
const g = globalThis;
|
|
6506
|
+
const f = this.init.fetch || (typeof g.fetch == 'function' && ((u, i) => g.fetch(u, i)));
|
|
6507
|
+
if (typeof f != 'function')
|
|
6508
|
+
return this.fail(new Error('fetch is not available in this environment'));
|
|
6509
|
+
const ctl = this.ctl = typeof AbortController == 'function' ? new AbortController() : {};
|
|
6510
|
+
const headers = { accept: 'text/event-stream', ...this.init.headers };
|
|
6511
|
+
if (this.meta.id)
|
|
6512
|
+
headers['last-event-id'] = this.meta.id;
|
|
6513
|
+
let res;
|
|
6514
|
+
try {
|
|
6515
|
+
res = await f(this.url, { headers, credentials: this.withCredentials ? 'include' : 'same-origin', cache: 'no-store', signal: ctl.signal });
|
|
6516
|
+
}
|
|
6517
|
+
catch (error) {
|
|
6518
|
+
return this.retry(error);
|
|
6519
|
+
}
|
|
6520
|
+
if (this.readyState == 2 || this.ctl !== ctl)
|
|
6521
|
+
return;
|
|
6522
|
+
const type = res.headers?.get?.('content-type') || '';
|
|
6523
|
+
if (!(res.status >= 200 && res.status < 300) || !/^text\/event-stream/i.test(type) || !res.body) {
|
|
6524
|
+
try {
|
|
6525
|
+
res.body?.cancel?.();
|
|
6526
|
+
}
|
|
6527
|
+
catch (_) { }
|
|
6528
|
+
return this.fail(Object.assign(new Error(`SSE ${res.status}${type ? ' ' + type : ''}: ${this.url}`), { status: res.status }));
|
|
6529
|
+
}
|
|
6530
|
+
this.readyState = 1;
|
|
6531
|
+
this.emit('open', { type: 'open' });
|
|
6532
|
+
try {
|
|
6533
|
+
for await (const fr of sse(res, this.meta)) {
|
|
6534
|
+
if (this.readyState == 2 || this.ctl !== ctl)
|
|
6535
|
+
return;
|
|
6536
|
+
this.emit(fr.event, { type: fr.event, data: fr.data, lastEventId: this.meta.id });
|
|
6537
|
+
}
|
|
6538
|
+
}
|
|
6539
|
+
catch (error) {
|
|
6540
|
+
if (this.readyState == 2 || this.ctl !== ctl)
|
|
6541
|
+
return;
|
|
6542
|
+
return this.retry(error);
|
|
6543
|
+
}
|
|
6544
|
+
if (this.readyState != 2 && this.ctl === ctl)
|
|
6545
|
+
this.retry();
|
|
6546
|
+
}
|
|
6547
|
+
/** a drop: error, CONNECTING, try again after `retry` ms (unless the error handler closed it) */
|
|
6548
|
+
retry(error) {
|
|
6549
|
+
if (this.readyState == 2)
|
|
6550
|
+
return;
|
|
6551
|
+
this.readyState = 0;
|
|
6552
|
+
this.emit('error', { type: 'error', error });
|
|
6553
|
+
if (this.readyState == 2)
|
|
6554
|
+
return;
|
|
6555
|
+
this.timer = setTimeout(() => this.connect(), this.meta.retry);
|
|
6556
|
+
}
|
|
6557
|
+
/** a failed connection: error, CLOSED */
|
|
6558
|
+
fail(error) {
|
|
6559
|
+
this.readyState = 2;
|
|
6560
|
+
this.emit('error', { type: 'error', error });
|
|
6561
|
+
}
|
|
6562
|
+
/** the state's name (debugging) */
|
|
6563
|
+
get state() { return NAMES[this.readyState]; }
|
|
6564
|
+
}
|
|
6565
|
+
FetchEventSource.CONNECTING = 0;
|
|
6566
|
+
FetchEventSource.OPEN = 1;
|
|
6567
|
+
FetchEventSource.CLOSED = 2;
|
|
6568
|
+
|
|
6105
6569
|
/*
|
|
6106
6570
|
* makeSocketDriver(options?) (PLAN-3 §1.3): WebSocket and server-sent events, declared per
|
|
6107
6571
|
* component instance and answered with reply actions (./replies.ts). Docs on the declarations
|
|
@@ -6110,7 +6574,7 @@ function queryCache(o = {}) {
|
|
|
6110
6574
|
* - Sink values:
|
|
6111
6575
|
* - `{ connections: { [name]: spec | falsy } }`: the sender's whole set of connections. Diffed
|
|
6112
6576
|
* per (sender, name): a new name opens, a removed or falsy one closes, a changed URL /
|
|
6113
|
-
* protocols / withCredentials / share closes and reopens, anything else (action names,
|
|
6577
|
+
* protocols / withCredentials / headers / share closes and reopens, anything else (action names,
|
|
6114
6578
|
* reconnect) only rebinds.
|
|
6115
6579
|
* - `{ to: name, json? | text? | binary? }`: send on the sender's connection `name` (json is
|
|
6116
6580
|
* stringified). Queued while (re)connecting, bounded (`queueLimit`, oldest dropped). A send
|
|
@@ -6135,6 +6599,10 @@ function queryCache(o = {}) {
|
|
|
6135
6599
|
* - A disposed sender's connections close (and their retries stop); app dispose closes all.
|
|
6136
6600
|
* - No WebSocket / EventSource (SSR): nothing opens, nothing is reported.
|
|
6137
6601
|
* - `WebSocket` / `EventSource` are read at connect time: options.WebSocket ?? globalThis.WebSocket.
|
|
6602
|
+
* - PLAN-11 E-1: an `sse` connection with `headers` (data: a token in it comes from state, and a
|
|
6603
|
+
* changed header reconnects) is read with FetchEventSource (fetch, `options.fetch` if given),
|
|
6604
|
+
* since EventSource can't send headers. An `options.EventSource` (a polyfill, the test fake)
|
|
6605
|
+
* still wins and gets the headers in its init.
|
|
6138
6606
|
*/
|
|
6139
6607
|
const SCOPE$1 = '__sygnalScope';
|
|
6140
6608
|
const scopeOf = (v) => v[SCOPE$1] || [];
|
|
@@ -6234,12 +6702,13 @@ function makeSocketDriver(options = {}) {
|
|
|
6234
6702
|
};
|
|
6235
6703
|
const connect = (t) => {
|
|
6236
6704
|
t.timer = 0;
|
|
6237
|
-
const
|
|
6705
|
+
const hdr = t.sse && t.spec.headers && typeof t.spec.headers == 'object' ? t.spec.headers : undefined;
|
|
6706
|
+
const C = t.sse ? options.EventSource ?? (hdr && typeof g.fetch == 'function' ? FetchEventSource : g.EventSource) : options.WebSocket ?? g.WebSocket;
|
|
6238
6707
|
if (typeof C != 'function')
|
|
6239
6708
|
return;
|
|
6240
6709
|
let s;
|
|
6241
6710
|
try {
|
|
6242
|
-
s = t.sse ? new C(t.url, { withCredentials: !!t.spec.withCredentials }) : new C(t.url, t.spec.protocols);
|
|
6711
|
+
s = t.sse ? new C(t.url, { withCredentials: !!t.spec.withCredentials, ...(hdr && { headers: hdr, fetch: options.fetch }) }) : new C(t.url, t.spec.protocols);
|
|
6243
6712
|
}
|
|
6244
6713
|
catch (error) {
|
|
6245
6714
|
// a URL the constructor rejects never opens: no retry
|
|
@@ -6343,7 +6812,7 @@ function makeSocketDriver(options = {}) {
|
|
|
6343
6812
|
const sse = typeof spec.sse == 'string';
|
|
6344
6813
|
const url = resolve(sse ? spec.sse : spec.socket, sse);
|
|
6345
6814
|
// the connection's identity: a change reconnects, anything else only rebinds
|
|
6346
|
-
const key = (spec.share === false ? 'u' : '') + (sse ? 'e' + !!spec.withCredentials : 's' + JSON.stringify(spec.protocols)) + url;
|
|
6815
|
+
const key = (spec.share === false ? 'u' : '') + (sse ? 'e' + !!spec.withCredentials + (spec.headers ? JSON.stringify(spec.headers) : '') : 's' + JSON.stringify(spec.protocols)) + url;
|
|
6347
6816
|
if (old && old.key === key) {
|
|
6348
6817
|
old.spec = spec;
|
|
6349
6818
|
if (old.t)
|
|
@@ -6420,7 +6889,7 @@ const dec = (s) => { try {
|
|
|
6420
6889
|
catch (_) {
|
|
6421
6890
|
return s;
|
|
6422
6891
|
} };
|
|
6423
|
-
const g$
|
|
6892
|
+
const g$5 = globalThis;
|
|
6424
6893
|
const PARAM$1 = /:(\w+)/g;
|
|
6425
6894
|
// G-571: synchronous navigations (redirects) before a microtask, after which each waits a task
|
|
6426
6895
|
const HOPS = 32;
|
|
@@ -6428,7 +6897,7 @@ const HOPS = 32;
|
|
|
6428
6897
|
const paramsOf$2 = (pat) => (pat.match(PARAM$1) || []).map(p => p.slice(1));
|
|
6429
6898
|
// the core's coded error() (published on the diagnostics bridge): no import, so the router
|
|
6430
6899
|
// costs nothing more standalone; prints in production too (like SYG611)
|
|
6431
|
-
const bad$1 = (v, msg, fix) => (g$
|
|
6900
|
+
const bad$1 = (v, msg, fix) => (g$5.__SYGNAL_DIAGNOSTICS__?.error || console.error)('SYG620', v.__emitterName, `router: ${msg}; not navigated`, fix, v);
|
|
6432
6901
|
function makeRouter(options = {}) {
|
|
6433
6902
|
const { mode, prefetch, navigate } = options, routes = options.routes || {};
|
|
6434
6903
|
const base = (options.base || '').replace(/\/$/, '');
|
|
@@ -6437,7 +6906,7 @@ function makeRouter(options = {}) {
|
|
|
6437
6906
|
const doScroll = options.scroll ?? !navigate;
|
|
6438
6907
|
const href = (name, params = {}, query, hash) => {
|
|
6439
6908
|
// dev entry only (SYG130: unknown name, missing or extra params)
|
|
6440
|
-
g$
|
|
6909
|
+
g$5.__SYGNAL_DIAGNOSTICS__?.routerHref?.(routes, name, params);
|
|
6441
6910
|
const pat = routes[name];
|
|
6442
6911
|
const path = pat == null || pat == '*' ? '/' : pat.replace(PARAM$1, (_, k) => enc(params[k] ?? ''));
|
|
6443
6912
|
const q = query && new URLSearchParams(Object.entries(query).filter(([, v]) => v != null)) + '';
|
|
@@ -6547,7 +7016,7 @@ function makeRouter(options = {}) {
|
|
|
6547
7016
|
if (!hops++)
|
|
6548
7017
|
queueMicrotask(() => { hops = 0; });
|
|
6549
7018
|
if (hops > HOPS) {
|
|
6550
|
-
if (g$
|
|
7019
|
+
if (g$5.__SYGNAL_DIAGNOSTICS__?.hooks?.on?.() && !warned++)
|
|
6551
7020
|
console.warn(`[Sygnal] router: more than ${HOPS} navigations in one flush (a redirect loop?); each next one waits a task (reported once)`, r.path);
|
|
6552
7021
|
setTimeout(send);
|
|
6553
7022
|
}
|
|
@@ -8089,7 +8558,7 @@ function makeHeadDriver(options = {}) {
|
|
|
8089
8558
|
* - An invalid spec (non-positive `every`, negative `after`, no action) doesn't start; the dev
|
|
8090
8559
|
* entry reports SYG422 through the `timerSpec` diagnostics hook.
|
|
8091
8560
|
*/
|
|
8092
|
-
const g$
|
|
8561
|
+
const g$4 = globalThis;
|
|
8093
8562
|
const now$1 = () => Date.now();
|
|
8094
8563
|
const valid = (s) => s.frame
|
|
8095
8564
|
? typeof s.frame == 'string'
|
|
@@ -8099,7 +8568,7 @@ const valid = (s) => s.frame
|
|
|
8099
8568
|
// changes) the timer clears the pending one.
|
|
8100
8569
|
const start$1 = (s, send, e) => {
|
|
8101
8570
|
let id, n = 0, last;
|
|
8102
|
-
const t0 = now$1(), every = s.every, raf = s.frame && g$
|
|
8571
|
+
const t0 = now$1(), every = s.every, raf = s.frame && g$4.requestAnimationFrame;
|
|
8103
8572
|
const later = (ms) => id = raf ? requestAnimationFrame(tick) : setTimeout(tick, ms);
|
|
8104
8573
|
// a frame's `ts` is requestAnimationFrame's timestamp (the fallback passes none: the clock)
|
|
8105
8574
|
const tick = (ts = now$1()) => {
|
|
@@ -8144,7 +8613,7 @@ const timerDriver = (runners) => (sink$) => {
|
|
|
8144
8613
|
if (e.ok = valid(s))
|
|
8145
8614
|
e.x = start$1(s, (type, data) => reply(id, type, data), e);
|
|
8146
8615
|
else
|
|
8147
|
-
g$
|
|
8616
|
+
g$4.__SYGNAL_DIAGNOSTICS__?.timerSpec?.(n, s, c);
|
|
8148
8617
|
}
|
|
8149
8618
|
if (!d)
|
|
8150
8619
|
runners.delete(id);
|
|
@@ -8202,10 +8671,10 @@ const makeTimerDriver = () => timerDriver(new Map());
|
|
|
8202
8671
|
* an intersection / resize declaration with nothing to observe (no DOM source bound). A
|
|
8203
8672
|
* component declaring `browser` with no driver is SYG643, as timers.
|
|
8204
8673
|
*/
|
|
8205
|
-
const g$
|
|
8674
|
+
const g$3 = globalThis;
|
|
8206
8675
|
const noop = () => { };
|
|
8207
8676
|
const KINDS = ['intersection', 'resize', 'media', 'storage', 'visibility', 'online', 'geolocation'];
|
|
8208
|
-
const diag = (...a) => g$
|
|
8677
|
+
const diag = (...a) => g$3.__SYGNAL_DIAGNOSTICS__?.browserSource?.(...a);
|
|
8209
8678
|
// the driver names in use (the definition hook looks each up in a declaring instance's sources)
|
|
8210
8679
|
const names$1 = new Set();
|
|
8211
8680
|
let hooked;
|
|
@@ -8291,16 +8760,16 @@ const track = (c, kind, target, Obs, opts, data) => {
|
|
|
8291
8760
|
};
|
|
8292
8761
|
/** Element visibility: `{ intersection: '.cover' | true, action, threshold?, rootMargin? }`, data `{ visible, ratio, index, dataset }` */
|
|
8293
8762
|
const intersectionSource = { d: {
|
|
8294
|
-
intersection: (s, c) => track(c, 'i', s.intersection, g$
|
|
8763
|
+
intersection: (s, c) => track(c, 'i', s.intersection, g$3.IntersectionObserver, { threshold: s.threshold, rootMargin: s.rootMargin }, (e) => ({ visible: e.isIntersecting, ratio: e.intersectionRatio })),
|
|
8295
8764
|
} };
|
|
8296
8765
|
/** Element size: `{ resize: '.chart' | true, action }`, data `{ width, height, index, dataset }` (the content box) */
|
|
8297
8766
|
const resizeSource = { d: {
|
|
8298
|
-
resize: (s, c) => track(c, 'r', s.resize, g$
|
|
8767
|
+
resize: (s, c) => track(c, 'r', s.resize, g$3.ResizeObserver, undefined, (e) => ({ width: e.contentRect.width, height: e.contentRect.height })),
|
|
8299
8768
|
} };
|
|
8300
8769
|
/** A media query: `{ media: '(prefers-color-scheme: dark)', action }`, data `{ matches, media }` */
|
|
8301
8770
|
const mediaSource = { d: {
|
|
8302
8771
|
media: (s, c) => {
|
|
8303
|
-
const m = g$
|
|
8772
|
+
const m = g$3.matchMedia?.(s.media);
|
|
8304
8773
|
if (!m)
|
|
8305
8774
|
return;
|
|
8306
8775
|
const f = () => c.send({ matches: m.matches, media: s.media });
|
|
@@ -8309,7 +8778,7 @@ const mediaSource = { d: {
|
|
|
8309
8778
|
return () => m.removeEventListener('change', f);
|
|
8310
8779
|
},
|
|
8311
8780
|
} };
|
|
8312
|
-
const area = (s) => g$
|
|
8781
|
+
const area = (s) => g$3[s.area == 'session' ? 'sessionStorage' : 'localStorage'];
|
|
8313
8782
|
const failed$1 = (e) => ({ name: e?.name, message: e?.message });
|
|
8314
8783
|
/**
|
|
8315
8784
|
* A storage key (read and observed, other tabs' writes included): `{ storage: 'theme', action,
|
|
@@ -8338,8 +8807,8 @@ const storageSource = {
|
|
|
8338
8807
|
};
|
|
8339
8808
|
f();
|
|
8340
8809
|
watching.add(f);
|
|
8341
|
-
g$
|
|
8342
|
-
return () => { watching.delete(f); g$
|
|
8810
|
+
g$3.addEventListener?.('storage', f);
|
|
8811
|
+
return () => { watching.delete(f); g$3.removeEventListener?.('storage', f); };
|
|
8343
8812
|
} },
|
|
8344
8813
|
c: (() => {
|
|
8345
8814
|
const write = (v, value, ok, fail) => {
|
|
@@ -8366,7 +8835,7 @@ const storageSource = {
|
|
|
8366
8835
|
/** The document's visibility: `{ visibility: true, action }`, data `{ visible }` */
|
|
8367
8836
|
const visibilitySource = { d: {
|
|
8368
8837
|
visibility: (_, c) => {
|
|
8369
|
-
const d = g$
|
|
8838
|
+
const d = g$3.document;
|
|
8370
8839
|
if (!d)
|
|
8371
8840
|
return;
|
|
8372
8841
|
const f = () => c.send({ visible: d.visibilityState != 'hidden' });
|
|
@@ -8378,13 +8847,13 @@ const visibilitySource = { d: {
|
|
|
8378
8847
|
/** The network: `{ online: true, action }`, data `{ online }` */
|
|
8379
8848
|
const onlineSource = { d: {
|
|
8380
8849
|
online: (_, c) => {
|
|
8381
|
-
if (!g$
|
|
8850
|
+
if (!g$3.navigator || !g$3.addEventListener)
|
|
8382
8851
|
return;
|
|
8383
|
-
const f = () => c.send({ online: g$
|
|
8852
|
+
const f = () => c.send({ online: g$3.navigator.onLine });
|
|
8384
8853
|
f();
|
|
8385
|
-
g$
|
|
8386
|
-
g$
|
|
8387
|
-
return () => { g$
|
|
8854
|
+
g$3.addEventListener('online', f);
|
|
8855
|
+
g$3.addEventListener('offline', f);
|
|
8856
|
+
return () => { g$3.removeEventListener('online', f); g$3.removeEventListener('offline', f); };
|
|
8388
8857
|
},
|
|
8389
8858
|
} };
|
|
8390
8859
|
/**
|
|
@@ -8394,7 +8863,7 @@ const onlineSource = { d: {
|
|
|
8394
8863
|
*/
|
|
8395
8864
|
const geolocationSource = { d: {
|
|
8396
8865
|
geolocation: (s, c) => {
|
|
8397
|
-
const geo = g$
|
|
8866
|
+
const geo = g$3.navigator?.geolocation;
|
|
8398
8867
|
if (!geo)
|
|
8399
8868
|
return c.fail({ code: 2, message: 'Geolocation is not available' });
|
|
8400
8869
|
const id = geo.watchPosition((p) => {
|
|
@@ -8410,11 +8879,11 @@ const geolocationSource = { d: {
|
|
|
8410
8879
|
*/
|
|
8411
8880
|
const clipboardSource = { c: {
|
|
8412
8881
|
copy: (v, ok, fail) => {
|
|
8413
|
-
const text = '' + v.copy, cb = g$
|
|
8882
|
+
const text = '' + v.copy, cb = g$3.navigator?.clipboard;
|
|
8414
8883
|
cb ? cb.writeText(text).then(() => ok({ text }), (e) => fail(failed$1(e))) : fail({ name: 'NotSupportedError', message: 'Clipboard is not available' });
|
|
8415
8884
|
},
|
|
8416
8885
|
paste: (_, ok, fail) => {
|
|
8417
|
-
const cb = g$
|
|
8886
|
+
const cb = g$3.navigator?.clipboard;
|
|
8418
8887
|
cb ? cb.readText().then((text) => ok({ text }), (e) => fail(failed$1(e))) : fail({ name: 'NotSupportedError', message: 'Clipboard is not available' });
|
|
8419
8888
|
},
|
|
8420
8889
|
} };
|
|
@@ -9119,6 +9588,9 @@ class App {
|
|
|
9119
9588
|
}
|
|
9120
9589
|
/** STATE.stream: the instances whose state changed emit it */
|
|
9121
9590
|
notify() {
|
|
9591
|
+
if (this.nv === this.ver)
|
|
9592
|
+
return;
|
|
9593
|
+
this.nv = this.ver;
|
|
9122
9594
|
for (const i of this.watchers)
|
|
9123
9595
|
i.notify();
|
|
9124
9596
|
}
|
|
@@ -9246,7 +9718,7 @@ class App {
|
|
|
9246
9718
|
this.ran = false;
|
|
9247
9719
|
this.last = v;
|
|
9248
9720
|
this.hooks.onPatch?.(v);
|
|
9249
|
-
this.vdomL?.next(v);
|
|
9721
|
+
this.vdomL?.next(v ?? null);
|
|
9250
9722
|
}
|
|
9251
9723
|
if (this.dq.length)
|
|
9252
9724
|
this.stopLater();
|
|
@@ -9935,7 +10407,7 @@ const W$1 = '__sw';
|
|
|
9935
10407
|
const HOST = /^(id|class(Name)?|style|title|name|placeholder|role|tab[iI]ndex|hidden|lang|dir|attrs)$|^(aria|data)-/;
|
|
9936
10408
|
/** the dev entry's reporter (checks/widgets.ts); returns the diagnostic when one was reported
|
|
9937
10409
|
* (r: G-580, a 661 the widget reported through error(e), not a throwing update) */
|
|
9938
|
-
const dev$
|
|
10410
|
+
const dev$7 = (code, w, o, x, r) => globalThis.__SYGNAL_DIAGNOSTICS__?.widget?.(code, w, o, x, r);
|
|
9939
10411
|
let ids$1 = 0;
|
|
9940
10412
|
// shallow; G-362: an object host prop (style, attrs, a class map) compares one level deep, so a
|
|
9941
10413
|
// new object with the same entries is no change (snabbdom patches the host from it anyway)
|
|
@@ -9948,16 +10420,16 @@ const same$2 = (a, b, d) => {
|
|
|
9948
10420
|
// reported through error(e)
|
|
9949
10421
|
function fail$1(o, w, k, code, e, r) {
|
|
9950
10422
|
e = e instanceof Error ? e : Error(e);
|
|
9951
|
-
dev$
|
|
10423
|
+
dev$7(code, w, o, e, r) || console.error(`[Sygnal SYG${code}]`, e);
|
|
9952
10424
|
o.app.appError(o, e, 'widget');
|
|
9953
10425
|
if (code < 662 && !o.disposed)
|
|
9954
10426
|
(o.$wf || (o.$wf = new Map())).set(k, [e]), o.refresh();
|
|
9955
10427
|
}
|
|
9956
10428
|
function mount(v) {
|
|
9957
10429
|
const el = v.elm, { ww: w, wp: p, wo: o } = v.data;
|
|
9958
|
-
const d = (n, detail) => { dev$
|
|
10430
|
+
const d = (n, detail) => { dev$7(140, w, o, n); el.dispatchEvent(new CustomEvent(n, { detail, bubbles: true })); };
|
|
9959
10431
|
let i;
|
|
9960
|
-
dev$
|
|
10432
|
+
dev$7(144, w, o, el);
|
|
9961
10433
|
// G-409: mount's fourth parameter reports a later failure (an adapter's own redraw): SYG661,
|
|
9962
10434
|
// the owner's onError fallback in this instance's place, as for a throwing update (G-580: the
|
|
9963
10435
|
// dev text says error(e) reported it)
|
|
@@ -10077,7 +10549,7 @@ function defineWidget(def) {
|
|
|
10077
10549
|
},
|
|
10078
10550
|
$ssr: host,
|
|
10079
10551
|
// D190/D199: a widget tag is not a selector (SYG143 in dev): select its className or a control
|
|
10080
|
-
toString: () => (dev$
|
|
10552
|
+
toString: () => (dev$7(143, w), ':not(*)'),
|
|
10081
10553
|
});
|
|
10082
10554
|
}
|
|
10083
10555
|
|
|
@@ -10126,6 +10598,30 @@ function defineWidget(def) {
|
|
|
10126
10598
|
* can still be assigned), which reads the host's current `uses`.
|
|
10127
10599
|
* - Limitations: a host intent must return an object (not one stream).
|
|
10128
10600
|
*/
|
|
10601
|
+
/** A behavior output's slice from before the action (outputs get the updated one): for a transition shared with STATE */
|
|
10602
|
+
const was = (s, p, k) => p?.previousState ? p.previousState[k] : s;
|
|
10603
|
+
/**
|
|
10604
|
+
* A behavior model whose outputs compute from the state before the action (its slice and
|
|
10605
|
+
* `props.state`), for behaviors whose STATE and outputs share one transition; STATE / HOST as is
|
|
10606
|
+
*/
|
|
10607
|
+
const fromPrevious = (model) => {
|
|
10608
|
+
const out = {};
|
|
10609
|
+
for (const a in model) {
|
|
10610
|
+
const e = model[a];
|
|
10611
|
+
if (!e || typeof e != 'object') {
|
|
10612
|
+
out[a] = e;
|
|
10613
|
+
continue;
|
|
10614
|
+
}
|
|
10615
|
+
const m = {};
|
|
10616
|
+
for (const s in e) {
|
|
10617
|
+
const f = e[s];
|
|
10618
|
+
m[s] = s == 'STATE' || s == 'HOST' || typeof f != 'function' ? f
|
|
10619
|
+
: (x, d, n, p, o, k) => f(was(x, p, k), d, n, p?.previousState ? { ...p, state: p.previousState } : p, o, k);
|
|
10620
|
+
}
|
|
10621
|
+
out[a] = m;
|
|
10622
|
+
}
|
|
10623
|
+
return out;
|
|
10624
|
+
};
|
|
10129
10625
|
// a model entry as { sink: fn }: a constant is sent as is, `true` sends the action's data, an
|
|
10130
10626
|
// EFFECT constant does nothing (as the core treats them)
|
|
10131
10627
|
const sinksOf = (e, S) => {
|
|
@@ -10308,6 +10804,8 @@ const pager = /*#__PURE__*/ defineBehavior({
|
|
|
10308
10804
|
});
|
|
10309
10805
|
|
|
10310
10806
|
const isRow = (r) => r?.id != null;
|
|
10807
|
+
/** PLAN-11 F-2: a File / Blob is a field value (a leaf), not an object of fields */
|
|
10808
|
+
const isFile = (v) => typeof Blob != 'undefined' && v instanceof Blob;
|
|
10311
10809
|
// the key of segment `s` in `v`: an array's row with that id, else the index unless the element
|
|
10312
10810
|
// there is a row (G-380: a row is named by its id only; -1, no element), an object's key
|
|
10313
10811
|
const key = (v, s) => {
|
|
@@ -10354,7 +10852,7 @@ const setField = (values, name, value) => {
|
|
|
10354
10852
|
* array-level error such as "at least one address"); a row's `id` is not a field
|
|
10355
10853
|
*/
|
|
10356
10854
|
const fieldNames = (v, at = '', row) => {
|
|
10357
|
-
if (!v || typeof v != 'object')
|
|
10855
|
+
if (!v || typeof v != 'object' || isFile(v))
|
|
10358
10856
|
return at ? [at] : [];
|
|
10359
10857
|
const a = Array.isArray(v), out = a ? [at] : [];
|
|
10360
10858
|
for (const k in v) {
|
|
@@ -10435,7 +10933,12 @@ const focusInvalid = (names, within) => {
|
|
|
10435
10933
|
* field.
|
|
10436
10934
|
*
|
|
10437
10935
|
* Options: `values` (the start values; D239: or a function of the host's state, called when the
|
|
10438
|
-
* form starts), `submit` (host action), `
|
|
10936
|
+
* form starts), `submit` (host action), `request` (PLAN-11 F-3: the form sends the valid submit
|
|
10937
|
+
* itself on the `http` sink: a request, or `(data, hostState) => request`, completed with the
|
|
10938
|
+
* body (`multipart: data` when a top-level value is a File / Blob or an array of them, else
|
|
10939
|
+
* `json: data`, unless it sets json / multipart / body), `progress: '<key>.PROGRESS'` (unless set)
|
|
10940
|
+
* and its own `ok: '<key>.DONE'`, `error: '<key>.ERRORS'`; with `submit` too, that host action is
|
|
10941
|
+
* still dispatched (for state), and should send no request), `check` (async per-field checks through a driver: a
|
|
10439
10942
|
* request with a reply action, `latest`), `show` ('blur' default: a schema error shows once its
|
|
10440
10943
|
* field was blurred; 'input'; 'submit'), `form` (selector), `http` (the driver sink of the
|
|
10441
10944
|
* checks, 'HTTP'), `resetOnShow` (D239), `tool` (PLAN-6 A-3, experimental: `formTool({ … })` from
|
|
@@ -10454,6 +10957,8 @@ const focusInvalid = (names, within) => {
|
|
|
10454
10957
|
* before that (a child's first render) the fields come from calling it with a blank state.
|
|
10455
10958
|
*
|
|
10456
10959
|
* Slice (state.form): values, initial, errors (all current schema errors by name), touched,
|
|
10960
|
+
* progress (PLAN-11 F-2: the submit's last progress step or null), resets (counts RESET and
|
|
10961
|
+
* resetOnShow starts: key a file input with it, since a file input can't be cleared by state),
|
|
10457
10962
|
* server (server errors), remote (async check results: '' passed), pending (name -> value being
|
|
10458
10963
|
* checked), submitting, submitted, submitCount, queued (a submit waits for a check or an async
|
|
10459
10964
|
* schema), validating (an async schema runs), validated (the schema has answered since the start
|
|
@@ -10463,11 +10968,14 @@ const focusInvalid = (names, within) => {
|
|
|
10463
10968
|
*
|
|
10464
10969
|
* Actions: form.CHANGE ({ name, value, item? }: G-376: a checkbox gives `checked`, and `item`, its
|
|
10465
10970
|
* value, which toggles membership when the field is an array; <select multiple> an array of the
|
|
10466
|
-
* selected values;
|
|
10971
|
+
* selected values; PLAN-11 F-2: a file input its File, `multiple` an array of them, none null),
|
|
10972
|
+
* form.SET ({ values }: G-647, field name -> value, several
|
|
10467
10973
|
* fields changed at once as if typed: one validation, `touched` as for CHANGE, unknown names
|
|
10468
10974
|
* skipped; e.g. an AI fill), form.BLUR (name), form.SUBMIT, form.ADD ({ field,
|
|
10469
10975
|
* value }), form.REMOVE ({ field, id }), form.ERRORS (an error reply or a map: server errors),
|
|
10470
|
-
* form.DONE (saved), form.RESET (values?)
|
|
10976
|
+
* form.DONE (saved), form.RESET (values?), form.PROGRESS (PLAN-11 F-2: a progress step of the
|
|
10977
|
+
* submit request, `progress: 'form.PROGRESS'`: kept as `progress` while submitting, cleared by
|
|
10978
|
+
* DONE / ERRORS / RESET); internal: form.RESULT (an async schema's result),
|
|
10471
10979
|
* form.CHECKED_<name> (a check's reply; a failed check passes: the server checks on submit),
|
|
10472
10980
|
* form.VALIDATE (the first validation, when the host starts: G-375; `valid` is false until the
|
|
10473
10981
|
* schema answered; with resetOnShow also each time the form element appears, D239).
|
|
@@ -10491,8 +10999,14 @@ const focusInvalid = (names, within) => {
|
|
|
10491
10999
|
* fields. The host checks (SYG234 submit, SYG235 check names, SYG236 row ids)
|
|
10492
11000
|
* run when the host is created.
|
|
10493
11001
|
*/
|
|
10494
|
-
|
|
11002
|
+
// PLAN-11 F-3: a submit's data goes as multipart when a top-level value is a file (or files)
|
|
11003
|
+
const hasFile = (d) => !!d && typeof d == 'object' && Object.values(d).some(x => isFile(x) || Array.isArray(x) && x.some(isFile));
|
|
11004
|
+
const dev$6 = (...a) => globalThis.__SYGNAL_DIAGNOSTICS__?.form?.(...a);
|
|
10495
11005
|
const keys = (o) => Object.keys(o).filter(k => o[k]);
|
|
11006
|
+
// PLAN-11 F-2: values compared as JSON, a File by identity-ish fields (JSON gives every File {})
|
|
11007
|
+
const fileIds = new WeakMap();
|
|
11008
|
+
let fileSeq = 0;
|
|
11009
|
+
const json = (v) => JSON.stringify(v, (_k, x) => isFile(x) ? `\0file:${fileIds.get(x) ?? (fileIds.set(x, ++fileSeq), fileSeq)}` : x);
|
|
10496
11010
|
const drop = (o, p) => {
|
|
10497
11011
|
const r = {};
|
|
10498
11012
|
for (const k in o)
|
|
@@ -10501,8 +11015,8 @@ const drop = (o, p) => {
|
|
|
10501
11015
|
return r;
|
|
10502
11016
|
};
|
|
10503
11017
|
const form = (schema, o = {}) => {
|
|
10504
|
-
isStandardSchema(schema) || dev$
|
|
10505
|
-
const { values: vo = {}, submit, check: checks = {}, show = 'blur', http = 'HTTP', form: sel = 'form', resetOnShow: again } = o;
|
|
11018
|
+
isStandardSchema(schema) || dev$6(231, schema);
|
|
11019
|
+
const { values: vo = {}, submit, request: rq, check: checks = {}, show = 'blur', http = 'HTTP', form: sel = 'form', resetOnShow: again } = o;
|
|
10506
11020
|
// D239: `values` can be a function of the host's state, called when the form starts (and each
|
|
10507
11021
|
// show, resetOnShow). Before that (the host's first render, before its first action) the
|
|
10508
11022
|
// fields come from calling it with a blank state: every read gives '' in the result
|
|
@@ -10534,7 +11048,7 @@ const form = (schema, o = {}) => {
|
|
|
10534
11048
|
// from a named button)
|
|
10535
11049
|
const known = (s, n, quiet) => {
|
|
10536
11050
|
const k = n && hasField(s.values, n);
|
|
10537
|
-
n && !k && !quiet && dev$
|
|
11051
|
+
n && !k && !quiet && dev$6(230, n, s.values);
|
|
10538
11052
|
return k;
|
|
10539
11053
|
};
|
|
10540
11054
|
// new values: errors now (sync schema), or after the schema's Promise (RESULT)
|
|
@@ -10549,7 +11063,7 @@ const form = (schema, o = {}) => {
|
|
|
10549
11063
|
});
|
|
10550
11064
|
const ask = (s, f, k, queued) => {
|
|
10551
11065
|
const x = getField(s.values, f), q = checks[f].request(x), ok = `${k}.CHECKED_${f}`;
|
|
10552
|
-
dev$
|
|
11066
|
+
dev$6(235, f, q);
|
|
10553
11067
|
return { s: { ...s, queued, pending: { ...s.pending, [f]: x } }, req: { ...q, ok, error: ok, latest: true } };
|
|
10554
11068
|
};
|
|
10555
11069
|
// a submit with s's errors: blocked (focus the first), a check to run or to wait for, or sent
|
|
@@ -10560,10 +11074,11 @@ const form = (schema, o = {}) => {
|
|
|
10560
11074
|
: Object.keys(s.pending).length ? { s: { ...s, queued: true } }
|
|
10561
11075
|
: { s: req ? { ...s, queued: false, submitting: true } : done({ ...s, queued: false }), send: 1 };
|
|
10562
11076
|
};
|
|
10563
|
-
const base = (vals) => ({ initial: vals, values: vals, touched: {}, server: {}, remote: {}, pending: {},
|
|
10564
|
-
submitting: false, submitted: false, submitCount: 0, queued: false, errors: {}, validated: false });
|
|
10565
|
-
|
|
10566
|
-
const
|
|
11077
|
+
const base = (vals, resets = 0) => ({ initial: vals, values: vals, touched: {}, server: {}, remote: {}, pending: {},
|
|
11078
|
+
submitting: false, submitted: false, submitCount: 0, queued: false, errors: {}, validated: false, progress: null, resets });
|
|
11079
|
+
// a start over (RESET, resetOnShow): `resets` counts them, so a file input keyed by it is made anew
|
|
11080
|
+
const fresh = (vals, s) => edit(base(vals, s ? (s.resets || 0) + 1 : 0), vals);
|
|
11081
|
+
const done = (s) => ({ ...s, submitting: false, submitted: true, initial: s.values, server: {}, touched: {}, progress: null });
|
|
10567
11082
|
const group = (cur, d) => {
|
|
10568
11083
|
if (!Array.isArray(cur) || !('item' in d))
|
|
10569
11084
|
return d.value;
|
|
@@ -10572,10 +11087,10 @@ const form = (schema, o = {}) => {
|
|
|
10572
11087
|
};
|
|
10573
11088
|
// fields changed as typed ones ([name, value] pairs; unknown names skipped, SYG230): their
|
|
10574
11089
|
// server errors, check results and running checks go, `touched` as show 'input' says; one validation
|
|
10575
|
-
const put = (s, d) => {
|
|
11090
|
+
const put = (s, d, quiet) => {
|
|
10576
11091
|
let vals = s.values, server = drop(s.server, ''), remote = s.remote, pending = s.pending, touched = s.touched;
|
|
10577
11092
|
for (const [n, y] of d)
|
|
10578
|
-
if (known(s, n)) {
|
|
11093
|
+
if (known(s, n, quiet)) {
|
|
10579
11094
|
vals = setField(vals, n, y), server = drop(server, n), remote = drop(remote, n), pending = drop(pending, n);
|
|
10580
11095
|
if (show == 'input')
|
|
10581
11096
|
touched = { ...touched, [n]: true };
|
|
@@ -10585,7 +11100,9 @@ const form = (schema, o = {}) => {
|
|
|
10585
11100
|
const steps = {
|
|
10586
11101
|
// G-376: a checkbox (`item`: its value) on an array field is one of a group: checked adds
|
|
10587
11102
|
// its value, unchecked removes it
|
|
10588
|
-
|
|
11103
|
+
// PLAN-11 F-2: a file input whose name isn't in values is left alone, quietly (an app that reads
|
|
11104
|
+
// its files in its own intent, as before 6.7)
|
|
11105
|
+
CHANGE: (s, d) => put(s, d?.name ? [[d.name, group(getField(s.values, d.name), d)]] : [], d?.file),
|
|
10589
11106
|
BLUR: (s, n, k) => {
|
|
10590
11107
|
if (!known(s, n, 1))
|
|
10591
11108
|
return null;
|
|
@@ -10594,7 +11111,7 @@ const form = (schema, o = {}) => {
|
|
|
10594
11111
|
},
|
|
10595
11112
|
SUBMIT: (s, _, k) => {
|
|
10596
11113
|
if (s.submitting || s.queued)
|
|
10597
|
-
return void dev$
|
|
11114
|
+
return void dev$6(232, s);
|
|
10598
11115
|
const n = { ...s, submitCount: s.submitCount + 1 }, r = v(s.values);
|
|
10599
11116
|
// G-371: a submit waiting for an async schema is queued (a second one is dropped)
|
|
10600
11117
|
return r.then ? { s: { ...n, validating: true, queued: true }, wait: { values: s.values, submit: 1 } } : attempt({ ...n, errors: r.errors }, k);
|
|
@@ -10618,16 +11135,20 @@ const form = (schema, o = {}) => {
|
|
|
10618
11135
|
SET: (s, d) => put(s, Object.entries(d?.values || {})),
|
|
10619
11136
|
ERRORS: (s, d) => {
|
|
10620
11137
|
const e = replyErrors(d, s.values);
|
|
10621
|
-
return { s: { ...s, server: e, submitting: false, queued: false }, focus: focusInvalid(e, sel) };
|
|
11138
|
+
return { s: { ...s, server: e, submitting: false, queued: false, progress: null }, focus: focusInvalid(e, sel) };
|
|
10622
11139
|
},
|
|
10623
11140
|
DONE: (s) => ({ s: done(s) }),
|
|
10624
|
-
|
|
11141
|
+
// PLAN-11 F-2: a progress step of the submit's request (`progress: 'form.PROGRESS'`), while it is pending
|
|
11142
|
+
PROGRESS: (s, d) => s.submitting && d && typeof d == 'object'
|
|
11143
|
+
? { s: { ...s, progress: { phase: d.phase, loaded: d.loaded, total: d.total ?? null, fraction: d.fraction ?? null } } } : null,
|
|
11144
|
+
// PLAN-11: only a plain object is new values (a click event from `DOM.click('.reset')` is not)
|
|
11145
|
+
RESET: (s, d) => fresh(d && typeof d == 'object' && [Object.prototype, null].includes(Object.getPrototypeOf(d)) ? d : s.initial, s),
|
|
10625
11146
|
// G-375: the first validation, when the host starts (form() doesn't validate at module load).
|
|
10626
11147
|
// G-575: a slice kept in parent state from a host that unmounted mid-submit starts unstuck.
|
|
10627
11148
|
// D239: with resetOnShow (also each time the form element appears again), or a `values`
|
|
10628
11149
|
// function not called yet, the slice starts over from the start values (h: the host state)
|
|
10629
|
-
VALIDATE: (s, _, _k, h) => again || fn && s.values === values ? fresh(fn ? vo(h) : vo)
|
|
10630
|
-
: edit({ ...s, submitting: false, queued: false, pending: {} }, s.values),
|
|
11150
|
+
VALIDATE: (s, _, _k, h) => again || fn && s.values === values ? fresh(fn ? vo(h) : vo, again && s)
|
|
11151
|
+
: edit({ ...s, submitting: false, queued: false, pending: {}, progress: null }, s.values),
|
|
10631
11152
|
};
|
|
10632
11153
|
// a check's reply (ok and error: an error reply carries an Error and passes)
|
|
10633
11154
|
for (const f of checked)
|
|
@@ -10637,6 +11158,14 @@ const form = (schema, o = {}) => {
|
|
|
10637
11158
|
const n = { ...s, pending: drop(s.pending, f), remote: { ...s.remote, [f]: d?.error instanceof Error ? '' : checks[f].error?.(d) || '' } };
|
|
10638
11159
|
return s.queued ? attempt(n, k) : { s: n };
|
|
10639
11160
|
};
|
|
11161
|
+
// PLAN-11 F-3: the `request` option's request for a valid submit (data: the schema's output)
|
|
11162
|
+
const send = (vals, h, k) => {
|
|
11163
|
+
const data = v(vals).value, q = typeof rq == 'function' ? rq(data, h) : rq;
|
|
11164
|
+
if (!q || q === ABORT)
|
|
11165
|
+
return ABORT;
|
|
11166
|
+
const own = 'json' in q || 'multipart' in q || 'body' in q;
|
|
11167
|
+
return { ...(!own && (hasFile(data) ? { multipart: data } : { json: data })), progress: `${k}.PROGRESS`, ...q, ok: `${k}.DONE`, error: `${k}.ERRORS` };
|
|
11168
|
+
};
|
|
10640
11169
|
const model = {};
|
|
10641
11170
|
for (const a in steps) {
|
|
10642
11171
|
let last = [];
|
|
@@ -10646,19 +11175,28 @@ const form = (schema, o = {}) => {
|
|
|
10646
11175
|
// VALIDATE: on the host's state (HOST), for a `values` function; the slice's own steps on it
|
|
10647
11176
|
...a == 'VALIDATE' ? { HOST: (h, d, _n, _p, _o, k) => { const x = out(h[k], d, k, h); return x ? { ...h, [k]: x.s } : h; } }
|
|
10648
11177
|
: { STATE: (s, d, _n, _p, _o, k) => out(s, d, k)?.s || ABORT },
|
|
10649
|
-
|
|
11178
|
+
// outputs share STATE's transition: computed from the slice before the action
|
|
11179
|
+
EFFECT: (s, d, next, p, _o, k) => {
|
|
11180
|
+
s = was(s, p, k);
|
|
10650
11181
|
const x = out(s, d, k), w = x?.wait;
|
|
10651
11182
|
if (x?.send) {
|
|
10652
11183
|
const r = v(x.s.values).value;
|
|
10653
|
-
dev$
|
|
11184
|
+
dev$6(233, s.values, r);
|
|
10654
11185
|
// G-577: a submit named after one of the form's own actions goes to that one
|
|
10655
|
-
submit in steps && dev$
|
|
10656
|
-
|
|
11186
|
+
submit in steps && dev$6(234, submit, k, b);
|
|
11187
|
+
// PLAN-11 F-3: with `request`, `submit` is optional (dispatched for state when given)
|
|
11188
|
+
if (submit !== undefined || !rq)
|
|
11189
|
+
next(submit, r, 0);
|
|
10657
11190
|
}
|
|
10658
11191
|
w && Promise.resolve(v(w.values)).then(() => next('RESULT', w, 0));
|
|
10659
11192
|
},
|
|
10660
|
-
ELEMENT: (s, d, _n,
|
|
10661
|
-
|
|
11193
|
+
ELEMENT: (s, d, _n, p, _o, k) => out(was(s, p, k), d, k)?.focus || ABORT,
|
|
11194
|
+
// the checks' requests, and (PLAN-11 F-3) the `request` option's for a valid submit; the
|
|
11195
|
+
// step from the slice before the action (7.0: outputs get the updated one), the host state as committed
|
|
11196
|
+
...((checked.length || rq) && { [http]: (s, d, _n, p, _o, k) => {
|
|
11197
|
+
const x = out(was(s, p, k), d, k);
|
|
11198
|
+
return x?.req || (x?.send && rq ? send(x.s.values, p?.state, k) : ABORT);
|
|
11199
|
+
} }),
|
|
10662
11200
|
};
|
|
10663
11201
|
}
|
|
10664
11202
|
const b = defineBehavior({
|
|
@@ -10674,9 +11212,11 @@ const form = (schema, o = {}) => {
|
|
|
10674
11212
|
VALIDATE: again ? xs$1.merge(xs$1.of(0), f.elements().filter((e) => { const r = e.length && !on; on = e.length; return r; })) : xs$1.of(0),
|
|
10675
11213
|
// G-376: a checkbox (native, or a hyphenated tag with a boolean `checked`: a
|
|
10676
11214
|
// form-associated custom checkbox / switch) gives `checked` and its value as `item`; a
|
|
10677
|
-
// <select multiple> the selected values;
|
|
10678
|
-
|
|
10679
|
-
|
|
11215
|
+
// <select multiple> the selected values; PLAN-11 F-2: a file input its File (`multiple`:
|
|
11216
|
+
// an array of them; none: null); others give `value`
|
|
11217
|
+
CHANGE: f.events('input').map(({ target: t }) => t.type == 'file' ? { name: t.name, value: t.multiple || t.getAttribute?.('multiple') != null ? [...(t.files || [])] : t.files?.[0] ?? null, file: true }
|
|
11218
|
+
: t.type == 'checkbox' || /-/.test(t.tagName) && typeof t.checked == 'boolean' ? { name: t.name, value: t.checked, item: t.value }
|
|
11219
|
+
: { name: t.name, value: t.type == 'select-multiple' ? [...t.selectedOptions].map((o) => o.value) : t.value }),
|
|
10680
11220
|
BLUR: f.events('focusout').map((e) => e.target.name),
|
|
10681
11221
|
SUBMIT: f.events('submit', { preventDefault: true }),
|
|
10682
11222
|
};
|
|
@@ -10694,7 +11234,7 @@ const form = (schema, o = {}) => {
|
|
|
10694
11234
|
},
|
|
10695
11235
|
// G-382: an async schema re-validating keeps the last answer's validity (no flicker)
|
|
10696
11236
|
valid: (s) => s.validated && !keys(s.errors).length && !keys(s.remote).length,
|
|
10697
|
-
dirty: (s) =>
|
|
11237
|
+
dirty: (s) => json(s.values) != json(s.initial),
|
|
10698
11238
|
error: (s) => s.server[''] || (s.submitCount && s.errors['']) || '',
|
|
10699
11239
|
},
|
|
10700
11240
|
})({ ...o, values }), merge = b.merge;
|
|
@@ -10702,7 +11242,7 @@ const form = (schema, o = {}) => {
|
|
|
10702
11242
|
o.tool?.$?.(b, v);
|
|
10703
11243
|
b.merge = (c, k) => {
|
|
10704
11244
|
const e = c.model?.[submit];
|
|
10705
|
-
req = !!e && typeof e == 'object' && http in e;
|
|
11245
|
+
req = !!rq || !!e && typeof e == 'object' && http in e;
|
|
10706
11246
|
merge(c, k);
|
|
10707
11247
|
};
|
|
10708
11248
|
return b;
|
|
@@ -10851,7 +11391,7 @@ const selection = (options = {}) => {
|
|
|
10851
11391
|
* interaction, SYG147 a `from` key that isn't an array in the host state, SYG435 (3-H) a list
|
|
10852
11392
|
* shown in another order than its array's (a Collection's sort / filter).
|
|
10853
11393
|
*/
|
|
10854
|
-
const dev$
|
|
11394
|
+
const dev$5 = (...a) => globalThis.__SYGNAL_DIAGNOSTICS__?.sortable?.(...a);
|
|
10855
11395
|
const LIFT = /^( |Enter|Spacebar)$/;
|
|
10856
11396
|
const KEYS = /^(Arrow(Up|Down|Left|Right)|Home|End|Escape|Tab)$/;
|
|
10857
11397
|
const S$1 = String;
|
|
@@ -10965,7 +11505,7 @@ const sortable = (options = {}) => {
|
|
|
10965
11505
|
} } } } };
|
|
10966
11506
|
};
|
|
10967
11507
|
// dev: SYG435, the list shown in another order than the array's (G-449)
|
|
10968
|
-
const check = (st, seen, k) => seen && dev$
|
|
11508
|
+
const check = (st, seen, k) => seen && dev$5(435, seen, st, lists, idField, k);
|
|
10969
11509
|
// a press or key handler: drag state this instance didn't start (its token `n` isn't the
|
|
10970
11510
|
// event's: restored, synced) is dropped first (G-452)
|
|
10971
11511
|
const live = (h) => (st, d, next, _p, _o, k) => {
|
|
@@ -11006,7 +11546,7 @@ const sortable = (options = {}) => {
|
|
|
11006
11546
|
const gripOf = (e, self) => {
|
|
11007
11547
|
const t = e.target, r = e.ownerTarget || e.currentTarget;
|
|
11008
11548
|
if (!checked)
|
|
11009
|
-
checked = dev$
|
|
11549
|
+
checked = dev$5(146, r, item, handle, attr);
|
|
11010
11550
|
const g = !claimed.has(e) && t?.closest?.(grip);
|
|
11011
11551
|
if (!g || self && g !== t)
|
|
11012
11552
|
return null;
|
|
@@ -11020,14 +11560,14 @@ const sortable = (options = {}) => {
|
|
|
11020
11560
|
return null;
|
|
11021
11561
|
const id = idOf(it);
|
|
11022
11562
|
if (id == null)
|
|
11023
|
-
dev$
|
|
11563
|
+
dev$5(145, it, item, attr);
|
|
11024
11564
|
return id;
|
|
11025
11565
|
};
|
|
11026
11566
|
const keyed = (keys, kb) => (e) => keys.test(e.key) && (kb || e.key != 'Tab') && gripOf(e, true) != null;
|
|
11027
11567
|
const lift = keyed(LIFT), step = keyed(KEYS, 1);
|
|
11028
11568
|
// dev (SYG435): this host's own items' ids in the order they're shown (computed by the
|
|
11029
11569
|
// diagnostics entry only)
|
|
11030
|
-
const shown = (e) => dev$
|
|
11570
|
+
const shown = (e) => dev$5(435, () => {
|
|
11031
11571
|
const r = e.ownerTarget || e.currentTarget;
|
|
11032
11572
|
return [[...r.querySelectorAll(item)].filter((it) => own(it, r)), r];
|
|
11033
11573
|
}, key, lists, idOf);
|
|
@@ -11071,7 +11611,7 @@ const sortable = (options = {}) => {
|
|
|
11071
11611
|
// again by HMR, a second host of the slice) is reset, as at END
|
|
11072
11612
|
INIT: { HOST: (st, _d, _n, _p, _o, k) => {
|
|
11073
11613
|
for (const l of lists)
|
|
11074
|
-
Array.isArray(st?.[l]) || dev$
|
|
11614
|
+
Array.isArray(st?.[l]) || dev$5(147, l, st, k);
|
|
11075
11615
|
return st[k].press || st[k].dragging ? back(st, k) : ABORT;
|
|
11076
11616
|
} },
|
|
11077
11617
|
// unmounted mid-drag: the drag is cancelled (no DROPPED; a keyboard drag's item goes back
|
|
@@ -11483,14 +12023,14 @@ const undo = (options) => {
|
|
|
11483
12023
|
* too), once per kind (restore / save / clear; a save that works again re-arms it); the app
|
|
11484
12024
|
* continues on initialState / unsaved, and a failed save is retried at the next change.
|
|
11485
12025
|
*/
|
|
11486
|
-
const g$
|
|
12026
|
+
const g$2 = globalThis;
|
|
11487
12027
|
const setupPersist = (c, o) => {
|
|
11488
12028
|
const { key, pick, omit, version = 1, migrate, sync, debounceMs = 100 } = o, plain = o.format == 'plain', src = c.sources, m = src.__m;
|
|
11489
12029
|
const env = src.__storage, calc = c.calculated || {};
|
|
11490
12030
|
// hydrating: the option, else the integration's signal (__hydrate: Astro, Vike), else whether
|
|
11491
12031
|
// run()'s mount point starts with renderToString's markup (its root element is marked; a
|
|
11492
12032
|
// client-only app's loading placeholder isn't server markup)
|
|
11493
|
-
const hydrate = o.hydrate ?? src.__hydrate ?? !!(typeof m == 'string' ? g$
|
|
12033
|
+
const hydrate = o.hydrate ?? src.__hydrate ?? !!(typeof m == 'string' ? g$2.document?.querySelector(m) : m)?.firstElementChild?.hasAttribute('data-sygnal-ssr');
|
|
11494
12034
|
let S, t, last, raw, skip, off;
|
|
11495
12035
|
// (the docs link the message ends with explains the causes and fixes). Once per kind (restore,
|
|
11496
12036
|
// save, clear): a storage that throws on every write must not warn on every change; a save that
|
|
@@ -11563,7 +12103,7 @@ const setupPersist = (c, o) => {
|
|
|
11563
12103
|
}
|
|
11564
12104
|
c.model = model;
|
|
11565
12105
|
try {
|
|
11566
|
-
S = typeof o.storage == 'object' ? o.storage : (env || g$
|
|
12106
|
+
S = typeof o.storage == 'object' ? o.storage : (env || g$2)[(o.storage || 'local') + (env ? '' : 'Storage')];
|
|
11567
12107
|
}
|
|
11568
12108
|
catch (_) { }
|
|
11569
12109
|
if (!S)
|
|
@@ -11600,14 +12140,14 @@ const setupPersist = (c, o) => {
|
|
|
11600
12140
|
send(read(raw = v)); };
|
|
11601
12141
|
const onStorage = (e) => (!e.storageArea || e.storageArea == S) && onSync(e.key, e.newValue);
|
|
11602
12142
|
if (sync)
|
|
11603
|
-
off = S.subscribe ? S.subscribe(onSync) : (g$
|
|
11604
|
-
g$
|
|
12143
|
+
off = S.subscribe ? S.subscribe(onSync) : (g$2.addEventListener?.('storage', onStorage), () => g$2.removeEventListener?.('storage', onStorage));
|
|
12144
|
+
g$2.addEventListener?.('pagehide', flush);
|
|
11605
12145
|
env?.f.add(flush);
|
|
11606
12146
|
c._dispose$.addListener({ next: () => {
|
|
11607
12147
|
flush();
|
|
11608
12148
|
state$.removeListener(L);
|
|
11609
12149
|
off?.();
|
|
11610
|
-
g$
|
|
12150
|
+
g$2.removeEventListener?.('pagehide', flush);
|
|
11611
12151
|
env?.f.delete(flush);
|
|
11612
12152
|
} });
|
|
11613
12153
|
};
|
|
@@ -12839,7 +13379,7 @@ const snake = (s) => s.replace(/([a-z0-9])([A-Z])/g, '$1_$2').replace(/\W+/g, '_
|
|
|
12839
13379
|
const keyOf$1 = (iv) => iv.state?.id;
|
|
12840
13380
|
const done = new WeakMap();
|
|
12841
13381
|
/** a dev diagnostic, once per (declaration, what); 'error' mode rethrows outside the caller */
|
|
12842
|
-
const dev$
|
|
13382
|
+
const dev$4 = (code, decl, what, component, message) => {
|
|
12843
13383
|
let s = done.get(decl);
|
|
12844
13384
|
if (!s)
|
|
12845
13385
|
done.set(decl, s = new Set());
|
|
@@ -13048,7 +13588,7 @@ function agentTools(target, options = {}) {
|
|
|
13048
13588
|
v = d.read(s);
|
|
13049
13589
|
}
|
|
13050
13590
|
catch (e) {
|
|
13051
|
-
dev$
|
|
13591
|
+
dev$4('SYG241', d, 'read', iv.name, `agent.read threw: ${e?.message ?? e}`);
|
|
13052
13592
|
}
|
|
13053
13593
|
reads.set(iv, { s, v });
|
|
13054
13594
|
return v;
|
|
@@ -13067,7 +13607,7 @@ function agentTools(target, options = {}) {
|
|
|
13067
13607
|
v = !!w(s);
|
|
13068
13608
|
}
|
|
13069
13609
|
catch (e) {
|
|
13070
|
-
dev$
|
|
13610
|
+
dev$4('SYG241', declOf(iv), 'when' + action, iv.name, `agent.actions.${action}.when threw: ${e?.message ?? e}`);
|
|
13071
13611
|
}
|
|
13072
13612
|
m.set(action, { s, v });
|
|
13073
13613
|
return v;
|
|
@@ -13078,7 +13618,7 @@ function agentTools(target, options = {}) {
|
|
|
13078
13618
|
return d.label ? String(d.label(iv.state)) : undefined;
|
|
13079
13619
|
}
|
|
13080
13620
|
catch (e) {
|
|
13081
|
-
dev$
|
|
13621
|
+
dev$4('SYG241', d, 'label', iv.name, `agent.label threw: ${e?.message ?? e}`);
|
|
13082
13622
|
}
|
|
13083
13623
|
};
|
|
13084
13624
|
function walk(iv, out) {
|
|
@@ -13113,7 +13653,7 @@ function agentTools(target, options = {}) {
|
|
|
13113
13653
|
else if (e.decl === d && e.item && item)
|
|
13114
13654
|
e.ivs.push(iv);
|
|
13115
13655
|
else
|
|
13116
|
-
dev$
|
|
13656
|
+
dev$4('SYG440', d, name, iv.name, `two agent declarations are named '${name}'; only the first is offered`);
|
|
13117
13657
|
}
|
|
13118
13658
|
for (const e of by.values())
|
|
13119
13659
|
if (e.item) {
|
|
@@ -13122,9 +13662,9 @@ function agentTools(target, options = {}) {
|
|
|
13122
13662
|
const k = keyOf$1(iv);
|
|
13123
13663
|
// an item without an id is keyed by its index (the core's `_i<index>` uid part): unstable
|
|
13124
13664
|
if (k == null || /-_i\d+$/.test(iv.uid))
|
|
13125
|
-
dev$
|
|
13665
|
+
dev$4('SYG441', e.decl, 'nokey', iv.name, `items of '${e.name}' have no id: agents address them by index`);
|
|
13126
13666
|
else if (ks.has(k + ''))
|
|
13127
|
-
dev$
|
|
13667
|
+
dev$4('SYG441', e.decl, 'dup', iv.name, `two items of '${e.name}' have the id ${JSON.stringify(k)}; calls reach the first`);
|
|
13128
13668
|
else
|
|
13129
13669
|
ks.add(k + '');
|
|
13130
13670
|
}
|
|
@@ -13150,11 +13690,11 @@ function agentTools(target, options = {}) {
|
|
|
13150
13690
|
for (const [action, a, isQuery] of defs) {
|
|
13151
13691
|
const name = e.name + '_' + snake(action), comp = e.ivs[0].name, where = `agent.${isQuery ? 'queries' : 'actions'}.${action}`;
|
|
13152
13692
|
if (isQuery && recs.has(name)) {
|
|
13153
|
-
dev$
|
|
13693
|
+
dev$4('SYG247', d, action, comp, `${where} has the tool name ${name} of an action; the query is not offered (rename it)`);
|
|
13154
13694
|
continue;
|
|
13155
13695
|
}
|
|
13156
13696
|
if (isQuery && typeof a?.run != 'function') {
|
|
13157
|
-
dev$
|
|
13697
|
+
dev$4('SYG246', d, action, comp, `${where}.run is not a function; the query is not offered`);
|
|
13158
13698
|
continue;
|
|
13159
13699
|
}
|
|
13160
13700
|
const c = a.input ? toJsonSchema(a.input) : undefined;
|
|
@@ -13169,11 +13709,11 @@ function agentTools(target, options = {}) {
|
|
|
13169
13709
|
if (c?.error) {
|
|
13170
13710
|
tool.error = `SYG240 ${c.error}`;
|
|
13171
13711
|
rec.off = 1;
|
|
13172
|
-
dev$
|
|
13712
|
+
dev$4('SYG240', d, action, comp, `${where}.input: ${c.error}; the tool is not offered`);
|
|
13173
13713
|
continue;
|
|
13174
13714
|
}
|
|
13175
13715
|
if (c?.lossy)
|
|
13176
|
-
dev$
|
|
13716
|
+
dev$4('SYG243', d, action, comp, `${where}.input: the model doesn't see ${c.lossy}`);
|
|
13177
13717
|
const live = e.ivs.filter(iv => whenOk(iv, action, a.when));
|
|
13178
13718
|
if (!live.length) {
|
|
13179
13719
|
rec.off = 1;
|
|
@@ -13345,7 +13885,7 @@ function agentTools(target, options = {}) {
|
|
|
13345
13885
|
return;
|
|
13346
13886
|
const res = iv.def.view.resources;
|
|
13347
13887
|
if (!res || !(u in res)) {
|
|
13348
|
-
dev$
|
|
13888
|
+
dev$4('SYG248', declOf(iv), 'until' + action, iv.name, `agent.actions.${action}.until names '${u}', which is not one of ${iv.name}'s resources${res ? ` (${Object.keys(res).join(', ')})` : ''}; the call answers at once`);
|
|
13349
13889
|
return;
|
|
13350
13890
|
}
|
|
13351
13891
|
return (s) => { const r = s?.[u]; return !r || (r.status != 'loading' && !r.refreshing); };
|
|
@@ -13363,7 +13903,7 @@ function agentTools(target, options = {}) {
|
|
|
13363
13903
|
return !!test(iv.state);
|
|
13364
13904
|
}
|
|
13365
13905
|
catch (err) {
|
|
13366
|
-
dev$
|
|
13906
|
+
dev$4('SYG241', declOf(iv), 'until' + action, iv.name, `agent.actions.${action}.until threw: ${err?.message ?? err}`);
|
|
13367
13907
|
return true;
|
|
13368
13908
|
}
|
|
13369
13909
|
};
|
|
@@ -13389,11 +13929,11 @@ function agentTools(target, options = {}) {
|
|
|
13389
13929
|
}
|
|
13390
13930
|
catch (err) {
|
|
13391
13931
|
const m = err?.message ?? err;
|
|
13392
|
-
dev$
|
|
13932
|
+
dev$4('SYG241', declOf(iv), 'query' + query, iv.name, `agent.queries.${query}.run threw: ${m}`);
|
|
13393
13933
|
return { ok: false, error: `${name} failed: ${m}` };
|
|
13394
13934
|
}
|
|
13395
13935
|
if (result && typeof result.then == 'function') {
|
|
13396
|
-
dev$
|
|
13936
|
+
dev$4('SYG246', declOf(iv), 'promise' + query, iv.name, `agent.queries.${query}.run returned a promise: a query is a pure, synchronous function of the state`);
|
|
13397
13937
|
return { ok: false, error: `${name} is not available: it returned a promise` };
|
|
13398
13938
|
}
|
|
13399
13939
|
return { ok: true, result };
|
|
@@ -13754,28 +14294,28 @@ function withToolResults(message, results) {
|
|
|
13754
14294
|
const SCOPE = '__sygnalScope';
|
|
13755
14295
|
const tag$1 = (v, scope) => v && typeof v == 'object'
|
|
13756
14296
|
? Object.defineProperty(keepSender(v, { ...v }), SCOPE, { value: [scope, ...(v[SCOPE] || [])] }) : v;
|
|
13757
|
-
const g$
|
|
13758
|
-
const now = () => g$
|
|
14297
|
+
const g$1 = globalThis;
|
|
14298
|
+
const now = () => g$1.performance?.now?.() ?? Date.now();
|
|
13759
14299
|
// internal (D254): the 120 Hz floor and the hidden-tab fallback
|
|
13760
14300
|
const MIN_GAP = 15, FALLBACK = 100;
|
|
13761
14301
|
/** rAF raced with a timer (a hidden tab never runs rAF), else a 16 ms timer; `f` gets the frame's time */
|
|
13762
14302
|
const frame = (f) => {
|
|
13763
|
-
if (typeof g$
|
|
14303
|
+
if (typeof g$1.requestAnimationFrame != 'function')
|
|
13764
14304
|
return void setTimeout(() => f(now()), 16);
|
|
13765
14305
|
let done = 0, a, t;
|
|
13766
14306
|
const go = (ts) => {
|
|
13767
14307
|
if (done++)
|
|
13768
14308
|
return;
|
|
13769
|
-
g$
|
|
14309
|
+
g$1.cancelAnimationFrame?.(a);
|
|
13770
14310
|
clearTimeout(t);
|
|
13771
14311
|
f(typeof ts == 'number' ? ts : now());
|
|
13772
14312
|
};
|
|
13773
|
-
a = g$
|
|
14313
|
+
a = g$1.requestAnimationFrame(go);
|
|
13774
14314
|
t = setTimeout(go, FALLBACK);
|
|
13775
14315
|
};
|
|
13776
14316
|
// dev-only diagnostics (SYG673, SYG677): the dev entry formats them (checks/chat.ts), so their
|
|
13777
14317
|
// text costs apps nothing; without it they're silent
|
|
13778
|
-
const dev$
|
|
14318
|
+
const dev$3 = (...a) => g$1.__SYGNAL_DIAGNOSTICS__?.chat?.(...a);
|
|
13779
14319
|
const FIX = "Send { messages, ok: 'DONE', error: 'FAILED' }";
|
|
13780
14320
|
/** why a request can't be sent ('' when it can) */
|
|
13781
14321
|
const problem = (r) => !Array.isArray(r.messages) ? 'no messages array'
|
|
@@ -13797,7 +14337,7 @@ function makeChatDriver(options = {}) {
|
|
|
13797
14337
|
const { replies, reply } = makeReplies(sender => abortWhere(r => r.sender === sender));
|
|
13798
14338
|
// SYG673 (dev): `why` is 'type' | 'delta' | 'name' | 'id'; the first five per driver
|
|
13799
14339
|
const malformed = (req, ev, why) => { if (warned++ < 5)
|
|
13800
|
-
dev$
|
|
14340
|
+
dev$3('SYG673', req, ev, why); };
|
|
13801
14341
|
const start = async (req, sender) => {
|
|
13802
14342
|
const key = req.key ?? req.ok ?? req.error ?? 'default';
|
|
13803
14343
|
if (req.latest !== false)
|
|
@@ -13990,7 +14530,7 @@ function makeChatDriver(options = {}) {
|
|
|
13990
14530
|
return;
|
|
13991
14531
|
const sender = senderOf(req);
|
|
13992
14532
|
if (sender === undefined)
|
|
13993
|
-
return void dev$
|
|
14533
|
+
return void dev$3('SYG677', req);
|
|
13994
14534
|
const { abort } = req;
|
|
13995
14535
|
if (abort !== undefined && abort !== false) {
|
|
13996
14536
|
// D469 (PLAN-9 H-2): a list of keys stops each (STOP: the reply and a card being composed)
|
|
@@ -15046,6 +15586,17 @@ const validMs = (v) => typeof v == 'number' && Number.isFinite(v) && v >= 0 && v
|
|
|
15046
15586
|
// too, so its time is the clock's (Date may be left real by `toFake`), and its waits drive it.
|
|
15047
15587
|
const fakeClock = () => setTimeout.clock;
|
|
15048
15588
|
const clockNow = () => { const c = fakeClock(); return c ? c.now : Date.now(); };
|
|
15589
|
+
/** PLAN-11 Q-3: ms until the next pending fake timer (0: none, or real timers) */
|
|
15590
|
+
const nextFakeTimer = () => {
|
|
15591
|
+
const c = fakeClock();
|
|
15592
|
+
let due = Infinity;
|
|
15593
|
+
try {
|
|
15594
|
+
Object.values(c?.timers || {}).forEach((t) => { if (t && t.callAt > c.now && t.callAt < due)
|
|
15595
|
+
due = t.callAt; });
|
|
15596
|
+
}
|
|
15597
|
+
catch (_) { }
|
|
15598
|
+
return due < Infinity ? due - c.now : 0;
|
|
15599
|
+
};
|
|
15049
15600
|
/**
|
|
15050
15601
|
* Under fake timers, advance the clock timer by timer (nextAsync flushes promises around each
|
|
15051
15602
|
* one) until `p` settles. Every pending wait has a timer (its timeout, a quiet-window tick), so
|
|
@@ -15236,6 +15787,7 @@ const headFake = () => {
|
|
|
15236
15787
|
};
|
|
15237
15788
|
let savedConfig;
|
|
15238
15789
|
let savedStrict;
|
|
15790
|
+
let savedAudit;
|
|
15239
15791
|
/*
|
|
15240
15792
|
* PLAN-4 GS-2, mock DOM: could the element a command targets have this method? The core runs any
|
|
15241
15793
|
* method of the element; the mock DOM has no elements, so: the documented commands, common
|
|
@@ -15346,7 +15898,7 @@ function fakeElementMethods(W) {
|
|
|
15346
15898
|
Object.defineProperty(proto, name, own); });
|
|
15347
15899
|
}
|
|
15348
15900
|
function renderComponent(componentDef, options = {}) {
|
|
15349
|
-
const { initialState, mockConfig = {}, drivers = {}, diagnostics, strict, dom = 'mock', autoConnect = true, socketSink = 'WS', resourceSink = 'HTTP', llmSink = 'LLM', http: httpOptions, context: ancestors } = options;
|
|
15901
|
+
const { initialState, mockConfig = {}, drivers = {}, diagnostics, strict, timingAudit, dom = 'mock', autoConnect = true, socketSink = 'WS', resourceSink = 'HTTP', llmSink = 'LLM', http: httpOptions, context: ancestors } = options;
|
|
15350
15902
|
const { intent, model = {} } = componentDef;
|
|
15351
15903
|
// E4: real DOM mode
|
|
15352
15904
|
const real = dom == 'real';
|
|
@@ -15394,6 +15946,7 @@ function renderComponent(componentDef, options = {}) {
|
|
|
15394
15946
|
if (!active++) {
|
|
15395
15947
|
savedConfig = _getDiagnosticsConfig();
|
|
15396
15948
|
savedStrict = core.strict;
|
|
15949
|
+
savedAudit = core.timingAudit;
|
|
15397
15950
|
// G-051: forget the checks' report dedupe (once()), so a finding from an earlier test is
|
|
15398
15951
|
// reported again. Only that set: resetChecks() would also drop inspect()'s records of
|
|
15399
15952
|
// other live instances / apps.
|
|
@@ -15402,6 +15955,9 @@ function renderComponent(componentDef, options = {}) {
|
|
|
15402
15955
|
configureDiagnostics({ mode: diagnostics || (prevMode == 'off' ? 'collect' : prevMode) });
|
|
15403
15956
|
if (strict !== undefined)
|
|
15404
15957
|
core.strict = strict;
|
|
15958
|
+
// 7.0 timing audit (SYG257, 'sygnal/diagnostics'): same bridge flag as configureTimingAudit()
|
|
15959
|
+
if (timingAudit !== undefined)
|
|
15960
|
+
core.timingAudit = timingAudit;
|
|
15405
15961
|
// PLAN-4 GS-2: element commands run on the real DOM (jsdom gets the missing methods), and their
|
|
15406
15962
|
// SYG640/SYG641 are reported without the dev entry too
|
|
15407
15963
|
if (real && !domFakes)
|
|
@@ -15592,6 +16148,7 @@ function renderComponent(componentDef, options = {}) {
|
|
|
15592
16148
|
if (!--active) {
|
|
15593
16149
|
configureDiagnostics(savedConfig);
|
|
15594
16150
|
core.strict = savedStrict;
|
|
16151
|
+
core.timingAudit = savedAudit;
|
|
15595
16152
|
domFakes?.();
|
|
15596
16153
|
domFakes = undefined;
|
|
15597
16154
|
}
|
|
@@ -15637,14 +16194,22 @@ function renderComponent(componentDef, options = {}) {
|
|
|
15637
16194
|
const lin$ = xs$1.create();
|
|
15638
16195
|
llm = { in$: lin$, src: makeChatDriver({ transport: tr, _frame: (f) => { frames.push(f); } })(lin$) };
|
|
15639
16196
|
}
|
|
16197
|
+
let memFetch;
|
|
15640
16198
|
const http = makeFetchDriver({
|
|
15641
16199
|
...httpOptions,
|
|
16200
|
+
// PLAN-11 U-2: a progress request is answered like any other (t.respond / t.fail); its
|
|
16201
|
+
// pending entry keeps the driver's progress callback for t.progress
|
|
16202
|
+
_progress: (url, init, step) => {
|
|
16203
|
+
const r = memFetch(url, init);
|
|
16204
|
+
pending[pending.length - 1].step = step;
|
|
16205
|
+
return r;
|
|
16206
|
+
},
|
|
15642
16207
|
// 5-3: focus / online come from t.focus() / t.online() only
|
|
15643
16208
|
_on: (f) => { signals.add(f); return () => signals.delete(f); },
|
|
15644
16209
|
_tap: (req, res, pf) => { tapped = [req, res, pf]; },
|
|
15645
16210
|
// D472: a batch / split request (a large decide()) is one fetch here, answered as one
|
|
15646
16211
|
_whole: true,
|
|
15647
|
-
fetch: (url, init) => new Promise((resolve, reject) => {
|
|
16212
|
+
fetch: memFetch = (url, init) => new Promise((resolve, reject) => {
|
|
15648
16213
|
const [req, res, pf] = tapped || [{ url }];
|
|
15649
16214
|
tapped = undefined;
|
|
15650
16215
|
// G-171(1): a resource fetch is listed as { url, ...request, resource: name }; 5-5: a
|
|
@@ -15678,6 +16243,7 @@ function renderComponent(componentDef, options = {}) {
|
|
|
15678
16243
|
return {
|
|
15679
16244
|
select: (sel) => xs$1.merge(hs.select(sel), own(false)(sel), sock.select(sel)),
|
|
15680
16245
|
errors: (sel) => xs$1.merge(hs.errors(sel), own(true)(sel)),
|
|
16246
|
+
progress: (sel) => hs.progress(sel),
|
|
15681
16247
|
subs, at, pending, in$, http, ws, llm,
|
|
15682
16248
|
// 5-3: the driver's matcher / inspection (the dev checks' SYG632, t.cache)
|
|
15683
16249
|
__matches: http.__matches, __inspect: http.__inspect,
|
|
@@ -16443,7 +17009,7 @@ function renderComponent(componentDef, options = {}) {
|
|
|
16443
17009
|
return { match: cat, desc: catDesc, o };
|
|
16444
17010
|
if (typeof tg == 'string') {
|
|
16445
17011
|
// an ok/error action name, key, category, resource name or URL
|
|
16446
|
-
return { match: r => { const v = r.value || {}; return cat(r) && [v.ok, v.error, v.key, v.category, r.res, v.url].includes(tg); }, desc: ` matching '${tg}'${catDesc}`, o };
|
|
17012
|
+
return { match: r => { const v = r.value || {}; return cat(r) && [v.ok, v.error, v.progress, v.key, v.category, r.res, v.url].includes(tg); }, desc: ` matching '${tg}'${catDesc}`, o };
|
|
16447
17013
|
}
|
|
16448
17014
|
if (typeof tg == 'function') {
|
|
16449
17015
|
return { match: r => { try {
|
|
@@ -16700,6 +17266,27 @@ function renderComponent(componentDef, options = {}) {
|
|
|
16700
17266
|
const err = typeof error == 'number' || resp ? Object.assign(new Error(`HTTP ${status}`), { status }) : x;
|
|
16701
17267
|
return { error: err, category, request: e?.req, status: status ?? x?.status, body };
|
|
16702
17268
|
});
|
|
17269
|
+
// PLAN-11 U-2: t.progress. One progress step of a pending request sent with `progress` (its
|
|
17270
|
+
// reply action, or progress() for `progress: true`); the request stays pending. Targets as
|
|
17271
|
+
// t.respond's; the phase defaults to 'upload' for a request with a body, else 'download'
|
|
17272
|
+
const progress = (name, step, opts) => {
|
|
17273
|
+
throwFailure();
|
|
17274
|
+
if (drivers[name])
|
|
17275
|
+
throw new Error(`[Sygnal] t.progress('${name}'): ${name} has a real driver (passed in drivers), so there is nothing to script`);
|
|
17276
|
+
if (!step || typeof step != 'object' || typeof step.loaded != 'number')
|
|
17277
|
+
throw new Error(`[Sygnal] t.progress('${name}', step): step is { loaded, total?, phase? } (got ${brief(step)})`);
|
|
17278
|
+
const tg = targetOf(opts);
|
|
17279
|
+
const what = `t.progress('${name}'${typeof opts == 'string' ? `, …, '${opts}'` : ''})`;
|
|
17280
|
+
const now = isReady && !inputs.length ? pick(name, tg) : undefined;
|
|
17281
|
+
if (now && !now.step)
|
|
17282
|
+
throw new Error(`[Sygnal] ${what}: the request ${brief(now.value)} has no progress: send it with progress: 'UPLOADING' (a reply action) or progress: true`);
|
|
17283
|
+
return scripted(() => pick(name, tg), w => noPending(what, name, tg, w), (e) => {
|
|
17284
|
+
if (!e.step)
|
|
17285
|
+
return new Error(`[Sygnal] ${what}: the request ${brief(e.value)} has no progress: send it with progress: 'UPLOADING' (a reply action) or progress: true`);
|
|
17286
|
+
const r = e.req || {};
|
|
17287
|
+
e.step(step.phase || (r.body !== undefined || r.json !== undefined || r.multipart !== undefined ? 'upload' : 'download'), step.loaded, step.total ?? 0);
|
|
17288
|
+
}, sendDue);
|
|
17289
|
+
};
|
|
16703
17290
|
// PLAN-6 L-4: t.stream. Targets as t.respond's (an action name or key, a partial request, a
|
|
16704
17291
|
// predicate, { request, nth }); `end: false` (the last argument, or in the target options)
|
|
16705
17292
|
// leaves the stream open. Chunks become ChatEvents pushed into the in-memory stream; the fake's
|
|
@@ -17204,7 +17791,14 @@ function renderComponent(componentDef, options = {}) {
|
|
|
17204
17791
|
let msg = `${name} timed out after ${timeoutMs}ms.`;
|
|
17205
17792
|
const [why, pending] = explainNext(start);
|
|
17206
17793
|
msg += why;
|
|
17207
|
-
if (
|
|
17794
|
+
if (inputs.length)
|
|
17795
|
+
msg += ` ${inputs.length} simulated input(s) still pending.`;
|
|
17796
|
+
// PLAN-11 Q-3: on the fake clock a state a timer (TIMER driver, periodic, delay) brings
|
|
17797
|
+
// later than the timeout can't arrive in time: name the next pending timer
|
|
17798
|
+
const later = pending ? 0 : nextFakeTimer();
|
|
17799
|
+
if (later)
|
|
17800
|
+
msg += ` A timer is still pending on the fake clock (it fires ${later}ms after this timeout): if the state comes from it, this wait ended first.`;
|
|
17801
|
+
if (pending || later)
|
|
17208
17802
|
msg += ` Wait longer: t.${name}(pred, ms), or the renderComponent timeoutMs option (default for every wait).`;
|
|
17209
17803
|
// G-065: a state from before the call matched; next() only looks at new ones
|
|
17210
17804
|
for (let i = from - 1; i >= 0; i--) {
|
|
@@ -17404,6 +17998,7 @@ function renderComponent(componentDef, options = {}) {
|
|
|
17404
17998
|
requests,
|
|
17405
17999
|
respond,
|
|
17406
18000
|
fail,
|
|
18001
|
+
progress,
|
|
17407
18002
|
stream,
|
|
17408
18003
|
connections,
|
|
17409
18004
|
cache,
|
|
@@ -17980,7 +18575,7 @@ function jsonSchema(schema, options = {}) {
|
|
|
17980
18575
|
* Side-effect free: an app that doesn't import it pays 0 B.
|
|
17981
18576
|
*/
|
|
17982
18577
|
const NAME = 30, DESC = 500, PARAM = 150, OUT = 1500, SUMMARY = 300;
|
|
17983
|
-
const g
|
|
18578
|
+
const g = globalThis;
|
|
17984
18579
|
/** cut to n characters with an ellipsis, never inside a surrogate pair */
|
|
17985
18580
|
const cut = (s, n) => {
|
|
17986
18581
|
if (s.length <= n)
|
|
@@ -17997,7 +18592,7 @@ const hash = (s) => {
|
|
|
17997
18592
|
return (h % 1679616).toString(36).padStart(4, '0');
|
|
17998
18593
|
};
|
|
17999
18594
|
function experimentalExposeWebMcp(app, options = {}) {
|
|
18000
|
-
const mc = options.modelContext ?? g
|
|
18595
|
+
const mc = options.modelContext ?? g.document?.modelContext ?? g.navigator?.modelContext;
|
|
18001
18596
|
const said = new Set();
|
|
18002
18597
|
const dev = (code, key, message, severity = 'warn') => {
|
|
18003
18598
|
if (said.has(code + key))
|
|
@@ -18200,7 +18795,7 @@ function experimentalExposeWebMcp(app, options = {}) {
|
|
|
18200
18795
|
let n$2 = 0;
|
|
18201
18796
|
/** the default confirmation: a native modal dialog outside the app's tree (Escape and "Deny" decline) */
|
|
18202
18797
|
function dialog(info, setClose) {
|
|
18203
|
-
const doc = g
|
|
18798
|
+
const doc = g.document;
|
|
18204
18799
|
if (!doc?.createElement)
|
|
18205
18800
|
return Promise.resolve(false);
|
|
18206
18801
|
return new Promise(resolve => {
|
|
@@ -18456,7 +19051,7 @@ const toolsOf$1 = (e) => e.o.agent === false ? null : e.tools || (e.tools = agen
|
|
|
18456
19051
|
}),
|
|
18457
19052
|
}));
|
|
18458
19053
|
// dev diagnostics (SYG684, SYG685): no-ops with diagnostics off
|
|
18459
|
-
const dev$
|
|
19054
|
+
const dev$2 = (code, message, fix) => { try {
|
|
18460
19055
|
report(code, { component: 'chat', message, fix, severity: 'warn' });
|
|
18461
19056
|
}
|
|
18462
19057
|
catch (e) {
|
|
@@ -18666,7 +19261,7 @@ function steps(c, o) {
|
|
|
18666
19261
|
const openPart = (s, id, what, guard = (_p) => true) => {
|
|
18667
19262
|
const p = openToolCalls(s).find(x => x.toolCallId === id && isClient(c, x) && guard(x));
|
|
18668
19263
|
if (!p)
|
|
18669
|
-
dev$
|
|
19264
|
+
dev$2('SYG685', `${what} for the tool call '${id}', which is not waiting for an answer (answered, closed, or not a client tool call of the last message); it is ignored`, 'Answer only the calls in openToolCalls(state); a late answer after STOP, SEND or REGENERATE is dropped on purpose');
|
|
18670
19265
|
return p;
|
|
18671
19266
|
};
|
|
18672
19267
|
/** after a call closed or a card changed: the next queued card, then rule 3 */
|
|
@@ -18794,7 +19389,7 @@ function request(e, o, k, messages, fresh, c, host) {
|
|
|
18794
19389
|
for (const n in c.client) {
|
|
18795
19390
|
if (n in tools && !shadowed.has(n)) {
|
|
18796
19391
|
shadowed.add(n);
|
|
18797
|
-
dev$
|
|
19392
|
+
dev$2('SYG684', `the client tool '${n}' has the name of an agent tool; the client tool is sent and its calls are the app's to answer`, 'Rename the client tool, or the agent declaration or action');
|
|
18798
19393
|
}
|
|
18799
19394
|
const x = c.client[n];
|
|
18800
19395
|
tools[n] = c.ask && n == 'ask_user' ? c.kit.askTool(c.ask, host) : { description: x.description, inputSchema: schemaOf(x) };
|
|
@@ -18911,7 +19506,7 @@ const chat = (options = {}) => {
|
|
|
18911
19506
|
...(c.ask?.controls && DOM && { CONTROL: DOM.select('[data-compose-control]').events('click').map(controlOf$1).filter((x) => !!x) }),
|
|
18912
19507
|
};
|
|
18913
19508
|
},
|
|
18914
|
-
model: {
|
|
19509
|
+
model: fromPrevious({
|
|
18915
19510
|
PROMPT: (s, p) => typeof p == 'string' && p !== s.prompt ? { ...s, prompt: p } : ABORT,
|
|
18916
19511
|
SEND: {
|
|
18917
19512
|
STATE: (s, d) => sendStep(s, d) || ABORT,
|
|
@@ -19009,153 +19604,19 @@ const chat = (options = {}) => {
|
|
|
19009
19604
|
ANSWER: { STATE: stateOf(S.answerCard), ...sinks(S.answerCard) },
|
|
19010
19605
|
CONTROL: (s, d) => S.controlStep(s, d) || ABORT,
|
|
19011
19606
|
}),
|
|
19012
|
-
},
|
|
19607
|
+
}),
|
|
19013
19608
|
})(options);
|
|
19014
19609
|
// the `prompt` option is a selector, not the slice's start value (defineBehavior's option override)
|
|
19015
19610
|
b.state = { ...b.state, prompt: '' };
|
|
19016
19611
|
return linked(b);
|
|
19017
19612
|
};
|
|
19018
19613
|
|
|
19019
|
-
/*
|
|
19020
|
-
* PLAN-6 L-2: what the HTTP transports share (side-effect free; each transport imports only what
|
|
19021
|
-
* it uses).
|
|
19022
|
-
* - post(): the fetch (the `fetch` option, 0-S5), the headers option (an object or a function of
|
|
19023
|
-
* the request), HTTP errors as Errors with `status` and the provider's message, and SYG670.
|
|
19024
|
-
* - sse(): a fetch Response body as SSE frames ({ event, data }), per the HTML spec's parsing
|
|
19025
|
-
* rules: CRLF / LF, comments, multi-line data. The reader is cancelled when the consumer stops
|
|
19026
|
-
* (abort, a thrown error), so the connection closes.
|
|
19027
|
-
* - SYG670: in a browser, a request with an auth header (Authorization, x-api-key, api-key,
|
|
19028
|
-
* x-goog-api-key) to a host that is neither local (localhost, 127.0.0.1, [::1], *.localhost)
|
|
19029
|
-
* nor the page's own origin is refused unless the transport has `dangerouslyAllowBrowser: true`:
|
|
19030
|
-
* the key would ship to every visitor. Same-origin requests are the app's own server (its
|
|
19031
|
-
* proxy), so a session header there is fine.
|
|
19032
|
-
*/
|
|
19033
|
-
const g = globalThis;
|
|
19034
|
-
const AUTH = /^(authorization|x-api-key|api-key|x-goog-api-key)$/i;
|
|
19035
|
-
const LOCAL = /^(localhost|127\.0\.0\.1|\[::1\]|.+\.localhost)$/;
|
|
19036
|
-
/** the URL a transport posts to, or a SYG670 Error when it must not (see the header) */
|
|
19037
|
-
function guard(url, headers, opts, name) {
|
|
19038
|
-
if (opts.dangerouslyAllowBrowser || typeof g.document == 'undefined' || !Object.keys(headers).some(h => AUTH.test(h)))
|
|
19039
|
-
return;
|
|
19040
|
-
const here = g.location?.href;
|
|
19041
|
-
let u;
|
|
19042
|
-
try {
|
|
19043
|
-
u = new URL(url, here);
|
|
19044
|
-
}
|
|
19045
|
-
catch (_) {
|
|
19046
|
-
return;
|
|
19047
|
-
}
|
|
19048
|
-
if (LOCAL.test(u.hostname) || (here && u.origin == new URL(here).origin))
|
|
19049
|
-
return;
|
|
19050
|
-
const fix = `Call ${u.origin} from your server and point the transport at that route (an AI SDK route with uiMessageStream('/api/chat'), or your own proxy), so the key stays there. For a key the user typed in themselves, pass ${name}({ ..., dangerouslyAllowBrowser: true })`;
|
|
19051
|
-
const msg = `${name}: refused to send an auth header from the browser to ${u.origin}: anyone who opens the page can read it`;
|
|
19052
|
-
error('SYG670', undefined, msg, fix);
|
|
19053
|
-
fail$2('SYG670', undefined, msg, fix);
|
|
19054
|
-
}
|
|
19055
|
-
/**
|
|
19056
|
-
* POST JSON; a non-2xx response is an Error with `status`, `body` and the provider's message.
|
|
19057
|
-
* The transport's `body` option and the request's `body` are merged over the built body (so they
|
|
19058
|
-
* can override provider defaults such as `max_tokens`); `fixed` fields are merged last and can't be
|
|
19059
|
-
* overridden (G-654: uiMessageStream's protocol fields, as the AI SDK's DefaultChatTransport)
|
|
19060
|
-
*/
|
|
19061
|
-
async function post$1(url, body, req, signal, opts, name, fixed) {
|
|
19062
|
-
const h = typeof opts.headers == 'function' ? await opts.headers(req) : opts.headers || {};
|
|
19063
|
-
guard(url, h, opts, name);
|
|
19064
|
-
const f = opts.fetch || ((u, i) => g.fetch(u, i));
|
|
19065
|
-
const res = await f(url, { method: 'POST', headers: { 'content-type': 'application/json', ...h }, body: JSON.stringify({ ...body, ...opts.body, ...req.body, ...fixed }), signal });
|
|
19066
|
-
if (!res.ok) {
|
|
19067
|
-
let text = '', message = '';
|
|
19068
|
-
try {
|
|
19069
|
-
text = await res.text();
|
|
19070
|
-
const j = JSON.parse(text);
|
|
19071
|
-
message = j.error?.message ?? j.error ?? j.message ?? '';
|
|
19072
|
-
}
|
|
19073
|
-
catch (_) {
|
|
19074
|
-
message || (message = text.slice(0, 200));
|
|
19075
|
-
}
|
|
19076
|
-
throw Object.assign(new Error(`${name}: HTTP ${res.status}${message ? ': ' + message : ''}`), { status: res.status, body: text });
|
|
19077
|
-
}
|
|
19078
|
-
if (!res.body)
|
|
19079
|
-
throw new Error(`${name}: the response has no body`);
|
|
19080
|
-
return res;
|
|
19081
|
-
}
|
|
19082
|
-
/** the SSE frames of a response body: `{ event, data }` (data: the joined data lines) */
|
|
19083
|
-
async function* sse(res) {
|
|
19084
|
-
const reader = res.body.getReader(), dec = new TextDecoder();
|
|
19085
|
-
let buf = '', cr = '', done = false;
|
|
19086
|
-
try {
|
|
19087
|
-
for (;;) {
|
|
19088
|
-
const r = await reader.read();
|
|
19089
|
-
// line ends -> \n (a \r at a chunk's end waits: it may be half of a \r\n)
|
|
19090
|
-
let s = cr + (r.done ? '\n\n' : dec.decode(r.value, { stream: true }));
|
|
19091
|
-
cr = !r.done && s.endsWith('\r') ? '\r' : '';
|
|
19092
|
-
if (cr)
|
|
19093
|
-
s = s.slice(0, -1);
|
|
19094
|
-
buf += s.replace(/\r\n?/g, '\n');
|
|
19095
|
-
done = r.done;
|
|
19096
|
-
let i;
|
|
19097
|
-
while ((i = buf.indexOf('\n\n')) >= 0) {
|
|
19098
|
-
const frame = buf.slice(0, i);
|
|
19099
|
-
buf = buf.slice(i + 2);
|
|
19100
|
-
let event = 'message', data = [];
|
|
19101
|
-
for (const line of frame.split('\n')) {
|
|
19102
|
-
if (!line || line[0] == ':')
|
|
19103
|
-
continue;
|
|
19104
|
-
const c = line.indexOf(':'), field = c < 0 ? line : line.slice(0, c);
|
|
19105
|
-
const v = c < 0 ? '' : line.slice(c + 1).replace(/^ /, '');
|
|
19106
|
-
if (field == 'event')
|
|
19107
|
-
event = v;
|
|
19108
|
-
else if (field == 'data')
|
|
19109
|
-
data.push(v);
|
|
19110
|
-
}
|
|
19111
|
-
if (data.length)
|
|
19112
|
-
yield { event, data: data.join('\n') };
|
|
19113
|
-
}
|
|
19114
|
-
if (done)
|
|
19115
|
-
return;
|
|
19116
|
-
}
|
|
19117
|
-
}
|
|
19118
|
-
finally {
|
|
19119
|
-
if (!done)
|
|
19120
|
-
reader.cancel().catch(() => { });
|
|
19121
|
-
}
|
|
19122
|
-
}
|
|
19123
|
-
/** JSON.parse, or the text itself when it isn't JSON (a model's malformed tool arguments go to validation as is) */
|
|
19124
|
-
const json = (s) => {
|
|
19125
|
-
if (typeof s != 'string')
|
|
19126
|
-
return s;
|
|
19127
|
-
try {
|
|
19128
|
-
return JSON.parse(s || '{}');
|
|
19129
|
-
}
|
|
19130
|
-
catch (_) {
|
|
19131
|
-
return s;
|
|
19132
|
-
}
|
|
19133
|
-
};
|
|
19134
|
-
/** a provider's usage as `{ inputTokens, outputTokens, totalTokens, reasoningTokens? }` (AI SDK names) */
|
|
19135
|
-
const usageOf = (u) => u && {
|
|
19136
|
-
inputTokens: u.input_tokens ?? u.prompt_tokens,
|
|
19137
|
-
outputTokens: u.output_tokens ?? u.completion_tokens,
|
|
19138
|
-
totalTokens: u.total_tokens,
|
|
19139
|
-
...((u.output_tokens_details ?? u.completion_tokens_details)?.reasoning_tokens != null && { reasoningTokens: (u.output_tokens_details ?? u.completion_tokens_details).reasoning_tokens }),
|
|
19140
|
-
};
|
|
19141
|
-
/** the parts of a message (a `content` string is one text part) */
|
|
19142
|
-
const partsOf = (m) => m.parts ?? (typeof m.content == 'string' ? [{ type: 'text', text: m.content }] : []);
|
|
19143
|
-
/** the tool name of a `tool-<name>` / `dynamic-tool` part, or '' */
|
|
19144
|
-
const toolName = (p) => p.type == 'dynamic-tool' ? p.toolName : p.type.startsWith('tool-') ? p.type.slice(5) : '';
|
|
19145
|
-
/** what a tool part sends back to the model, or undefined when it has no result yet */
|
|
19146
|
-
const toolOutput = (p) => p.state == 'output-available' ? (typeof p.output == 'string' ? p.output : JSON.stringify(p.output ?? null))
|
|
19147
|
-
: p.state == 'output-error' ? JSON.stringify({ error: p.errorText ?? 'error' })
|
|
19148
|
-
: p.state == 'output-denied' ? JSON.stringify({ error: 'The user denied this tool call' })
|
|
19149
|
-
: undefined;
|
|
19150
|
-
// dev-only diagnostics (SYG675): the dev entry formats them (checks/chat.ts)
|
|
19151
|
-
const dev$2 = (...a) => g.__SYGNAL_DIAGNOSTICS__?.chat?.(...a);
|
|
19152
|
-
|
|
19153
19614
|
const EMPTY$1 = { type: 'object', properties: {} };
|
|
19154
19615
|
function prepare(req, strict, seen, transport, dialect = 'openai', limits) {
|
|
19155
19616
|
const sent = {};
|
|
19156
19617
|
if (strict && typeof strict != 'function' && !seen.has('')) {
|
|
19157
19618
|
seen.add('');
|
|
19158
|
-
dev$
|
|
19619
|
+
dev$8('SYG672', req, { transport });
|
|
19159
19620
|
}
|
|
19160
19621
|
const fn = typeof strict == 'function' ? strict : undefined;
|
|
19161
19622
|
// what the request has left of `limits` (strictSchemas counts it down)
|
|
@@ -19168,7 +19629,7 @@ function prepare(req, strict, seen, transport, dialect = 'openai', limits) {
|
|
|
19168
19629
|
return { schema: s.schema, strict: true };
|
|
19169
19630
|
if (!seen.has(what)) {
|
|
19170
19631
|
seen.add(what);
|
|
19171
|
-
dev$
|
|
19632
|
+
dev$8('SYG675', req, { tool: what, errors: s.errors });
|
|
19172
19633
|
}
|
|
19173
19634
|
return { schema, strict: false };
|
|
19174
19635
|
};
|
|
@@ -19239,12 +19700,12 @@ function openResponses(options = {}) {
|
|
|
19239
19700
|
c.done = 1;
|
|
19240
19701
|
called++;
|
|
19241
19702
|
const name = c.name ?? item?.name;
|
|
19242
|
-
return { type: 'tool-call', id: c.call_id ?? item?.call_id ?? id, name, input: p.input(name, json(args ?? item?.arguments ?? c.args)) };
|
|
19703
|
+
return { type: 'tool-call', id: c.call_id ?? item?.call_id ?? id, name, input: p.input(name, json$1(args ?? item?.arguments ?? c.args)) };
|
|
19243
19704
|
};
|
|
19244
19705
|
for await (const { data } of sse(res)) {
|
|
19245
19706
|
if (data == '[DONE]')
|
|
19246
19707
|
break;
|
|
19247
|
-
const e = json(data), t = e?.type;
|
|
19708
|
+
const e = json$1(data), t = e?.type;
|
|
19248
19709
|
if (typeof t != 'string')
|
|
19249
19710
|
continue;
|
|
19250
19711
|
if (t == 'response.output_text.delta' || t == 'response.refusal.delta' || REASONING.test(t)) {
|
|
@@ -19336,13 +19797,13 @@ function chatCompletions(options = {}) {
|
|
|
19336
19797
|
const done = function* () {
|
|
19337
19798
|
for (const c of calls)
|
|
19338
19799
|
if (c)
|
|
19339
|
-
yield { type: 'tool-call', id: c.id, name: c.name, input: p.input(c.name, json(c.args)) };
|
|
19800
|
+
yield { type: 'tool-call', id: c.id, name: c.name, input: p.input(c.name, json$1(c.args)) };
|
|
19340
19801
|
calls = [];
|
|
19341
19802
|
};
|
|
19342
19803
|
for await (const { data } of sse(res)) {
|
|
19343
19804
|
if (data == '[DONE]')
|
|
19344
19805
|
break;
|
|
19345
|
-
const e = json(data);
|
|
19806
|
+
const e = json$1(data);
|
|
19346
19807
|
if (!e || typeof e != 'object')
|
|
19347
19808
|
continue;
|
|
19348
19809
|
if (e.error)
|
|
@@ -19430,7 +19891,7 @@ function uiMessageStream(url, options = {}) {
|
|
|
19430
19891
|
for await (const { data } of sse(res)) {
|
|
19431
19892
|
if (data == '[DONE]')
|
|
19432
19893
|
break;
|
|
19433
|
-
const c = json(data), t = c?.type;
|
|
19894
|
+
const c = json$1(data), t = c?.type;
|
|
19434
19895
|
if (typeof t != 'string')
|
|
19435
19896
|
continue;
|
|
19436
19897
|
if (ONE.test(t)) {
|
|
@@ -20161,7 +20622,7 @@ const commandBar = (options = {}) => {
|
|
|
20161
20622
|
...(deny && { DENY: on(DOM, deny) }),
|
|
20162
20623
|
};
|
|
20163
20624
|
},
|
|
20164
|
-
model: {
|
|
20625
|
+
model: fromPrevious({
|
|
20165
20626
|
INPUT: (s, v) => typeof v == 'string' && v !== s.text ? { ...s, text: v } : ABORT,
|
|
20166
20627
|
RUN: {
|
|
20167
20628
|
STATE: (s, d, _n, p, o, k) => runStep(s, d, E(p, k, o))?.s || ABORT,
|
|
@@ -20197,7 +20658,7 @@ const commandBar = (options = {}) => {
|
|
|
20197
20658
|
// a command ran (or was refused): the result; the input is cleared when it was this command
|
|
20198
20659
|
DONE: (s, d) => s.status != 'running' ? ABORT
|
|
20199
20660
|
: { ...s, status: 'ready', command: null, pending: null, result: d, text: d?.ok && s.text.trim() === d.command ? '' : s.text },
|
|
20200
|
-
},
|
|
20661
|
+
}),
|
|
20201
20662
|
})(options);
|
|
20202
20663
|
return linked(b);
|
|
20203
20664
|
};
|
|
@@ -20297,7 +20758,7 @@ function anthropicMessages(options = {}) {
|
|
|
20297
20758
|
return { inputTokens: i, outputTokens: o, totalTokens: i + o, ...(u.output_tokens_details?.thinking_tokens != null && { reasoningTokens: u.output_tokens_details.thinking_tokens }) };
|
|
20298
20759
|
};
|
|
20299
20760
|
for await (const { data } of sse(res)) {
|
|
20300
|
-
const e = json(data), t = e?.type;
|
|
20761
|
+
const e = json$1(data), t = e?.type;
|
|
20301
20762
|
if (typeof t != 'string')
|
|
20302
20763
|
continue;
|
|
20303
20764
|
if (t == 'message_start') {
|
|
@@ -20333,9 +20794,9 @@ function anthropicMessages(options = {}) {
|
|
|
20333
20794
|
if (b?.type == 'thinking' && b.signature)
|
|
20334
20795
|
yield { type: 'reasoning', delta: '', providerMetadata: { anthropic: { signature: b.signature } } };
|
|
20335
20796
|
else if (b?.type == 'tool_use')
|
|
20336
|
-
yield { type: 'tool-call', id: b.id, name: b.name, input: p.input(b.name, b.json ? json(b.json) : b.input ?? {}) };
|
|
20797
|
+
yield { type: 'tool-call', id: b.id, name: b.name, input: p.input(b.name, b.json ? json$1(b.json) : b.input ?? {}) };
|
|
20337
20798
|
else if (b?.type == 'server_tool_use')
|
|
20338
|
-
yield { type: 'tool-call', id: b.id, name: b.name, input: b.json ? json(b.json) : b.input ?? {}, executed: true, providerExecuted: true };
|
|
20799
|
+
yield { type: 'tool-call', id: b.id, name: b.name, input: b.json ? json$1(b.json) : b.input ?? {}, executed: true, providerExecuted: true };
|
|
20339
20800
|
}
|
|
20340
20801
|
else if (t == 'message_delta') {
|
|
20341
20802
|
if (e.delta?.stop_reason)
|
|
@@ -20475,10 +20936,10 @@ function agui(url, options = {}) {
|
|
|
20475
20936
|
if (!c || c.done)
|
|
20476
20937
|
return;
|
|
20477
20938
|
c.done = 1;
|
|
20478
|
-
open.push({ type: 'tool-call', id, name: c.name, input: json(c.args || '{}') });
|
|
20939
|
+
open.push({ type: 'tool-call', id, name: c.name, input: json$1(c.args || '{}') });
|
|
20479
20940
|
};
|
|
20480
20941
|
for await (const { data } of sse(res)) {
|
|
20481
|
-
const e = json(data), t = e?.type;
|
|
20942
|
+
const e = json$1(data), t = e?.type;
|
|
20482
20943
|
if (typeof t != 'string')
|
|
20483
20944
|
continue;
|
|
20484
20945
|
if (t == 'TEXT_MESSAGE_START') {
|
|
@@ -20526,7 +20987,7 @@ function agui(url, options = {}) {
|
|
|
20526
20987
|
else if (t == 'TOOL_CALL_RESULT') {
|
|
20527
20988
|
end(e.toolCallId);
|
|
20528
20989
|
yield* take(e.toolCallId);
|
|
20529
|
-
yield { type: 'tool-result', id: e.toolCallId, output: typeof e.content == 'string' ? json(e.content) : e.content };
|
|
20990
|
+
yield { type: 'tool-result', id: e.toolCallId, output: typeof e.content == 'string' ? json$1(e.content) : e.content };
|
|
20530
20991
|
}
|
|
20531
20992
|
else if (t == 'STATE_SNAPSHOT' || t == 'STATE_DELTA') {
|
|
20532
20993
|
state = t == 'STATE_SNAPSHOT' ? e.snapshot : applyPatch(state, e.delta);
|
|
@@ -21727,7 +22188,7 @@ function composeBehavior(catalog, options = {}) {
|
|
|
21727
22188
|
.map(({ key, name, value }) => ({ key, name, value })) }),
|
|
21728
22189
|
};
|
|
21729
22190
|
},
|
|
21730
|
-
model: {
|
|
22191
|
+
model: fromPrevious({
|
|
21731
22192
|
PROMPT: (s, p) => typeof p == 'string' && p !== s.prompt ? { ...s, prompt: p } : ABORT,
|
|
21732
22193
|
COMPOSE: { HOST: host(start), [S]: sink(start) },
|
|
21733
22194
|
REPLY: {
|
|
@@ -21769,7 +22230,7 @@ function composeBehavior(catalog, options = {}) {
|
|
|
21769
22230
|
CONTROL: (s, c) => c && typeof c.key == 'string' && typeof c.name == 'string'
|
|
21770
22231
|
? { ...s, ui: { ...s.ui, [c.key]: { ...s.ui?.[c.key], [c.name]: c.value } } } : ABORT,
|
|
21771
22232
|
DONE: () => ABORT,
|
|
21772
|
-
},
|
|
22233
|
+
}),
|
|
21773
22234
|
})(rest);
|
|
21774
22235
|
}
|
|
21775
22236
|
|
|
@@ -25834,7 +26295,7 @@ const compose = /*#__PURE__*/ Object.assign((catalog, options) => composeBehavio
|
|
|
25834
26295
|
* is gone); set: the array with that element replaced (the state unchanged when it is gone).
|
|
25835
26296
|
* Side-effect free.
|
|
25836
26297
|
*/
|
|
25837
|
-
const byId = (field, id, idKey = 'id') => ({
|
|
26298
|
+
const byId = (field, id, idKey = 'id') => Object.defineProperty({
|
|
25838
26299
|
get: (s) => (Array.isArray(s?.[field]) ? s[field].find((x) => x?.[idKey] === id) : undefined),
|
|
25839
26300
|
set: (s, v) => {
|
|
25840
26301
|
const a = s?.[field];
|
|
@@ -25842,7 +26303,7 @@ const byId = (field, id, idKey = 'id') => ({
|
|
|
25842
26303
|
return s;
|
|
25843
26304
|
return { ...s, [field]: a.map((x) => (x?.[idKey] === id ? v : x)) };
|
|
25844
26305
|
},
|
|
25845
|
-
});
|
|
26306
|
+
}, BINDING, { value: [field, id, idKey] });
|
|
25846
26307
|
|
|
25847
26308
|
/**
|
|
25848
26309
|
* ESM ports of xstream's `flattenConcurrently` and `flattenSequentially` extras
|