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.esm.js
CHANGED
|
@@ -63,6 +63,8 @@ const NOT_SINK = /^(__|(props|children|dispose|commands)\$$|(CHILD|PARENT|EFFECT
|
|
|
63
63
|
*/
|
|
64
64
|
const abortReason = {};
|
|
65
65
|
const abort = (reason) => (abortReason.r = reason, ABORT);
|
|
66
|
+
/** Private semantic binding identity, shared by built-in helpers and the runtime. */
|
|
67
|
+
const BINDING = /*#__PURE__*/ Symbol.for('sygnal.binding');
|
|
66
68
|
|
|
67
69
|
/**
|
|
68
70
|
* PLAN-4.6 (D162): `defineComponent(opts)` builds a component from an options object, for code
|
|
@@ -702,6 +704,9 @@ const DEV_CODE_SEVERITY = {
|
|
|
702
704
|
SYG128: 'error',
|
|
703
705
|
// PLAN-4 4-G2: static only (sygnal-check); listed so the entry knows its title
|
|
704
706
|
SYG129: 'warn',
|
|
707
|
+
// PLAN-10 X-1: static only (sygnal-check)
|
|
708
|
+
SYG170: 'warn',
|
|
709
|
+
SYG171: 'warn',
|
|
705
710
|
SYG221: 'error',
|
|
706
711
|
// PLAN-4 GS-4 (state.ts)
|
|
707
712
|
SYG222: 'warn',
|
|
@@ -750,6 +755,8 @@ const DEV_CODE_SEVERITY = {
|
|
|
750
755
|
SYG634: 'info',
|
|
751
756
|
// PLAN-3 5-5 (fetch.ts)
|
|
752
757
|
SYG635: 'warn',
|
|
758
|
+
// PLAN-11 U-1 (fetch.ts)
|
|
759
|
+
SYG636: 'warn',
|
|
753
760
|
// PLAN-4 GS-2 (checks/elementCommands.ts, through the core bridge; renderComponent too)
|
|
754
761
|
SYG640: 'warn',
|
|
755
762
|
SYG641: 'error',
|
|
@@ -866,6 +873,12 @@ const DEV_CODE_SEVERITY = {
|
|
|
866
873
|
// PLAN-9 X-2: static only (sygnal-check); listed so the entry knows their titles
|
|
867
874
|
SYG253: 'warn',
|
|
868
875
|
SYG254: 'info',
|
|
876
|
+
// PLAN-12: static only; proven after-state output guard contradiction.
|
|
877
|
+
SYG256: 'warn',
|
|
878
|
+
// 7.0: the opt-in timing audit (checks/next.ts; configureTimingAudit)
|
|
879
|
+
SYG257: 'info',
|
|
880
|
+
// PLAN-12: static only for legacy (6.x) binding contracts. Sygnal 7 remounts known targets.
|
|
881
|
+
SYG454: 'warn',
|
|
869
882
|
};
|
|
870
883
|
function docsUrlFor(code) {
|
|
871
884
|
return `${DOCS_BASE_URL}/reference/errors#${code.toLowerCase()}`;
|
|
@@ -1261,6 +1274,8 @@ function normalize$1(view, src) {
|
|
|
1261
1274
|
const list = [];
|
|
1262
1275
|
for (const sink in e)
|
|
1263
1276
|
list.push([sink, e[sink]]), sinks.add(sink);
|
|
1277
|
+
// 7.0: STATE first, so the other sinks get the state it committed
|
|
1278
|
+
list.sort((a, b) => +(a[0] != 'STATE') - +(b[0] != 'STATE'));
|
|
1264
1279
|
handlers.set(key, list);
|
|
1265
1280
|
}
|
|
1266
1281
|
const c = normCalc(name, src.calculated, src.initialState);
|
|
@@ -1682,6 +1697,8 @@ class View {
|
|
|
1682
1697
|
get state() { return this.i.cell.get(); }
|
|
1683
1698
|
get props() { return this.i.props; }
|
|
1684
1699
|
get context() { return this.i.context(); }
|
|
1700
|
+
/** D530: the context keys the last render's view read (null: none rendered, or no context) */
|
|
1701
|
+
get reads() { return this.i.keys; }
|
|
1685
1702
|
get isRoot() { return !this.i.parent; }
|
|
1686
1703
|
get kind() { return this.i.kind; }
|
|
1687
1704
|
/** false on a hidden Switchable page and below it */
|
|
@@ -1850,7 +1867,7 @@ function handle(inst, type, data, cause, src) {
|
|
|
1850
1867
|
return;
|
|
1851
1868
|
const def = inst.def;
|
|
1852
1869
|
const pre = inst.cell.get();
|
|
1853
|
-
let props, outs;
|
|
1870
|
+
let props, afterProps, post = pre, outs;
|
|
1854
1871
|
const next = (t, d, ms = 10, effect) => {
|
|
1855
1872
|
if (typeof ms !== 'number')
|
|
1856
1873
|
fail$2('SYG215', inst, `next() delay in '${type}' must be a number`, "Use next('ACTION', data, ms)");
|
|
@@ -1865,35 +1882,28 @@ function handle(inst, type, data, cause, src) {
|
|
|
1865
1882
|
};
|
|
1866
1883
|
for (const [sink, h0] of hs) {
|
|
1867
1884
|
const h = H.wrapHandler ? H.wrapHandler(viewOf(inst), type, sink, h0) || h0 : h0;
|
|
1868
|
-
|
|
1869
|
-
if (typeof h != 'function')
|
|
1870
|
-
continue;
|
|
1871
|
-
const failed = (e) => app.caught(inst, 'SYG214', `EFFECT handler '${type}' threw`, e, 'effect', type);
|
|
1872
|
-
try {
|
|
1873
|
-
const p = { ...(props || (props = inst_props(inst, pre))), signal: (inst.ac || (inst.ac = globalThis.AbortController && new AbortController()))?.signal };
|
|
1874
|
-
const r = h(pre, data, (t, d, ms) => next(t, d, ms, true), p);
|
|
1875
|
-
if (r?.then)
|
|
1876
|
-
r.then(null, failed);
|
|
1877
|
-
else if (r !== undefined && !isAbort(r))
|
|
1878
|
-
warn('SYG219', inst, `EFFECT handler '${type}' returned a value, which is ignored`, 'Use a STATE or driver sink');
|
|
1879
|
-
}
|
|
1880
|
-
catch (e) {
|
|
1881
|
-
failed(e);
|
|
1882
|
-
}
|
|
1883
|
-
continue;
|
|
1884
|
-
}
|
|
1885
|
+
const effect = sink == 'EFFECT';
|
|
1885
1886
|
let v;
|
|
1886
1887
|
if (typeof h == 'function') {
|
|
1887
1888
|
try {
|
|
1888
|
-
|
|
1889
|
+
const p = sink == 'STATE' ? props || (props = inst_props(inst, pre, pre)) : afterProps || (afterProps = inst_props(inst, post, pre));
|
|
1890
|
+
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);
|
|
1891
|
+
if (effect) {
|
|
1892
|
+
if (v?.then)
|
|
1893
|
+
v.then(null, (e) => handlerFailed(inst, type, sink, e));
|
|
1894
|
+
else if (v !== undefined && !isAbort(v))
|
|
1895
|
+
warn('SYG219', inst, `EFFECT handler '${type}' returned a value, which is ignored`, 'Use a STATE or driver sink');
|
|
1896
|
+
}
|
|
1889
1897
|
}
|
|
1890
1898
|
catch (e) {
|
|
1891
|
-
|
|
1899
|
+
handlerFailed(inst, type, sink, e);
|
|
1892
1900
|
continue;
|
|
1893
1901
|
}
|
|
1894
1902
|
}
|
|
1895
1903
|
else
|
|
1896
1904
|
v = h === undefined || h === true ? data : h;
|
|
1905
|
+
if (effect)
|
|
1906
|
+
continue;
|
|
1897
1907
|
if (sink == 'STATE' && (isAbort(v) || v === pre)) {
|
|
1898
1908
|
// D205: the DOM event being dispatched (window.event) is the one this action came from
|
|
1899
1909
|
if (/^(input|change)$/.test(globalThis.event?.type))
|
|
@@ -1906,6 +1916,11 @@ function handle(inst, type, data, cause, src) {
|
|
|
1906
1916
|
H.onReducer?.(viewOf(inst), type, pre, v);
|
|
1907
1917
|
dbg(inst, () => `<${type}> State reducer added`);
|
|
1908
1918
|
inst.cell.set(v);
|
|
1919
|
+
// a STATE that removed the component's own state (a deleted record it is bound to): the
|
|
1920
|
+
// outputs get its last state, as a Collection item's do
|
|
1921
|
+
post = inst.cell.get();
|
|
1922
|
+
if (post === undefined)
|
|
1923
|
+
post = pre;
|
|
1909
1924
|
// GS-12: this app's DOM driver patches inside a View Transition (makeViewTransitionDOMDriver
|
|
1910
1925
|
// reads the flag on its IsolateModule); includes?.: a non-array static lists nothing (G-228).
|
|
1911
1926
|
// Only for a state that changed structurally: today an equal one renders nothing (its
|
|
@@ -1954,9 +1969,14 @@ function handle(inst, type, data, cause, src) {
|
|
|
1954
1969
|
send$1(inst, outs[i], outs[i + 1]);
|
|
1955
1970
|
}
|
|
1956
1971
|
}
|
|
1957
|
-
/** the
|
|
1958
|
-
function
|
|
1959
|
-
|
|
1972
|
+
/** Keep the same failure boundary for sync handlers and rejected EFFECT promises. */
|
|
1973
|
+
function handlerFailed(inst, type, sink, e) {
|
|
1974
|
+
const effect = sink == 'EFFECT';
|
|
1975
|
+
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);
|
|
1976
|
+
}
|
|
1977
|
+
/** the handlers' 4th argument: the props, children, slots, context, uid, the state and the pre-action state */
|
|
1978
|
+
function inst_props(inst, state, previousState) {
|
|
1979
|
+
return { ...inst.props, children: inst.children, slots: inst.slots, context: inst.context(), uid: inst.uid, state, previousState };
|
|
1960
1980
|
}
|
|
1961
1981
|
/**
|
|
1962
1982
|
* A sink value of an instance: stamped with its sender (EVENTS; object values for a reply-capable
|
|
@@ -2067,9 +2087,11 @@ function isEqualNamespace(a, b) {
|
|
|
2067
2087
|
return true;
|
|
2068
2088
|
}
|
|
2069
2089
|
// G-261: a DOM change Sygnal makes outside a patch (a Transition's leave, a Portal mounted late)
|
|
2070
|
-
// re-emits the DOM source of the app whose root contains `el` (makeDOMDriver listens on its root)
|
|
2090
|
+
// re-emits the DOM source of the app whose root contains `el` (makeDOMDriver listens on its root).
|
|
2091
|
+
// The event comes from the element's own window: jsdom refuses an `Event` from another realm (a
|
|
2092
|
+
// Transition's leave timer that fires after a jsdom test environment was torn down)
|
|
2071
2093
|
const POKE = 'sygnal-dom';
|
|
2072
|
-
const pokeDOM = (el) => el?.dispatchEvent(new Event(POKE, { bubbles: true }));
|
|
2094
|
+
const pokeDOM = (el) => el?.dispatchEvent(new (el.ownerDocument?.defaultView?.Event || Event)(POKE, { bubbles: true }));
|
|
2073
2095
|
const obj = (o) => o !== null && typeof o == 'object' && !Array.isArray(o);
|
|
2074
2096
|
/**
|
|
2075
2097
|
* P46-P: two vnodes' data are the same for the DOM modules: the same keys, each value identical
|
|
@@ -2188,6 +2210,19 @@ const flat = (v) => {
|
|
|
2188
2210
|
* - dispose(): DISPOSE, onDispose, dispose$, the EFFECT signal aborted, the subtree, then the
|
|
2189
2211
|
* streams (the scoped _remove swap: no xstream stop timers, D165/Q18).
|
|
2190
2212
|
*/
|
|
2213
|
+
// v7: a known binding target joins key/id/path in the lifetime identity. Fresh equivalent
|
|
2214
|
+
// byId helpers retain identity; opaque lenses need an explicit key for a new target.
|
|
2215
|
+
function bindingChanged(a, b) {
|
|
2216
|
+
if (a === b)
|
|
2217
|
+
return false;
|
|
2218
|
+
const x = a?.[BINDING], y = b?.[BINDING];
|
|
2219
|
+
return x && y ? !x.every((v, i) => Object.is(v, y[i]))
|
|
2220
|
+
: !!(x || y || typeof a == 'string' || typeof b == 'string' || a === undefined || b === undefined);
|
|
2221
|
+
}
|
|
2222
|
+
function bindingState(k) {
|
|
2223
|
+
const c = k.base || k.cell || k.arr;
|
|
2224
|
+
return c && (c.has?.() ?? c.get() !== undefined) ? (k.cell || k.arr).get() : undefined;
|
|
2225
|
+
}
|
|
2191
2226
|
const ERR_FIX$1 = 'See the attached error';
|
|
2192
2227
|
// 4-I G-557: a fragment root (a Collection's): each of its top-level elements is marked.
|
|
2193
2228
|
// 4-K G-572: a text root (no element; an attribute would land on a Text node) gets `ready: false`
|
|
@@ -2230,8 +2265,8 @@ const idOf$1 = (n, path) => {
|
|
|
2230
2265
|
return `${n.sel}::${(s && s.replace(/"/g, '')) || path}`;
|
|
2231
2266
|
};
|
|
2232
2267
|
/**
|
|
2233
|
-
* a component vnode's props, without `resetState` (D174: read at creation,
|
|
2234
|
-
* prop of the child)
|
|
2268
|
+
* a component vnode's props, without `resetState` (D174: read at creation, never a
|
|
2269
|
+
* prop of the child). Known `state` target changes start a new lifetime in v7.
|
|
2235
2270
|
*/
|
|
2236
2271
|
function propsOf(p) {
|
|
2237
2272
|
if (!p)
|
|
@@ -2295,10 +2330,14 @@ const EMPTY_CTX = Object.freeze({});
|
|
|
2295
2330
|
/** a view read every key (spread, Object.keys): any context change re-renders it */
|
|
2296
2331
|
const ALL = '\u0000all';
|
|
2297
2332
|
let IDS = 0;
|
|
2298
|
-
/**
|
|
2333
|
+
/**
|
|
2334
|
+
* a child that failed to instantiate: renders the owner's error fallback (SYG408). It keeps its
|
|
2335
|
+
* props, so only a changed binding target makes another attempt (not every owner render)
|
|
2336
|
+
*/
|
|
2299
2337
|
class Failed {
|
|
2300
|
-
constructor(outv) {
|
|
2338
|
+
constructor(outv, props) {
|
|
2301
2339
|
this.outv = outv;
|
|
2340
|
+
this.props = props;
|
|
2302
2341
|
this.lr = true;
|
|
2303
2342
|
this.ready = true;
|
|
2304
2343
|
this.def = null;
|
|
@@ -2306,7 +2345,7 @@ class Failed {
|
|
|
2306
2345
|
this.shown = true;
|
|
2307
2346
|
}
|
|
2308
2347
|
render() { return this.outv; }
|
|
2309
|
-
setProps() { }
|
|
2348
|
+
setProps(p) { this.props = p; }
|
|
2310
2349
|
dispose() { }
|
|
2311
2350
|
}
|
|
2312
2351
|
class Inst {
|
|
@@ -2314,6 +2353,7 @@ class Inst {
|
|
|
2314
2353
|
this.app = app;
|
|
2315
2354
|
this.def = def;
|
|
2316
2355
|
this.parent = parent;
|
|
2356
|
+
this.base = base;
|
|
2317
2357
|
this.dom = dom;
|
|
2318
2358
|
this.scope = scope;
|
|
2319
2359
|
this.kind = kind;
|
|
@@ -2480,10 +2520,32 @@ class Inst {
|
|
|
2480
2520
|
}
|
|
2481
2521
|
/** called after each action and before each render pass: STATE.stream emits a new state */
|
|
2482
2522
|
notify() {
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2523
|
+
// A completed action can remove and reinsert a target before the next render. Owners
|
|
2524
|
+
// observe each transition, including hidden pages; the current action finishes first.
|
|
2525
|
+
// only the bound children (a string / byId binding), not every child
|
|
2526
|
+
if (this.bound)
|
|
2527
|
+
for (const k of this.bound) {
|
|
2528
|
+
if (k.disposed) {
|
|
2529
|
+
this.bound.delete(k);
|
|
2530
|
+
continue;
|
|
2531
|
+
}
|
|
2532
|
+
const previous = k.bs, state = k.bs = bindingState(k);
|
|
2533
|
+
if (previous !== undefined && state === undefined) {
|
|
2534
|
+
// Cleanup reads its last owned snapshot. A dying path must not recreate the
|
|
2535
|
+
// deleted target; the fresh lifetime receives a new writable cell at reconciliation.
|
|
2536
|
+
const cell = k.cell || k.arr;
|
|
2537
|
+
cell.get = () => previous;
|
|
2538
|
+
cell.set = () => { };
|
|
2539
|
+
k.dispose();
|
|
2540
|
+
this.forced = true;
|
|
2541
|
+
}
|
|
2542
|
+
}
|
|
2543
|
+
if (this.st$) {
|
|
2544
|
+
const v = this.cell.raw();
|
|
2545
|
+
if (v !== this.stLast && v !== undefined) {
|
|
2546
|
+
this.stLast = v;
|
|
2547
|
+
this.st$.shamefullySendNext(v);
|
|
2548
|
+
}
|
|
2487
2549
|
}
|
|
2488
2550
|
}
|
|
2489
2551
|
dispose$() {
|
|
@@ -2732,7 +2794,9 @@ class Inst {
|
|
|
2732
2794
|
for (const r of resolvers)
|
|
2733
2795
|
view = r(view, this) || view;
|
|
2734
2796
|
let k = this.kids.get(id);
|
|
2735
|
-
|
|
2797
|
+
// Collection and VirtualCollection share an array cell; Switchable binds `state`.
|
|
2798
|
+
const binding = host && sel != 'switchable' ? 'from' : 'state';
|
|
2799
|
+
if (k && (k.disposed || (view && k.def && k.def.view !== view) || bindingChanged(k.props?.[binding], props[binding]))) {
|
|
2736
2800
|
k.dispose();
|
|
2737
2801
|
this.kids.delete(id);
|
|
2738
2802
|
k = undefined;
|
|
@@ -2740,7 +2804,12 @@ class Inst {
|
|
|
2740
2804
|
if (k)
|
|
2741
2805
|
k.setProps(props, children, n, id);
|
|
2742
2806
|
else
|
|
2743
|
-
this.kids.set(id, host ? this.host(host, props, children, id, n) : this.child(view, props, children, id, !!data.props?.resetState));
|
|
2807
|
+
this.kids.set(id, k = host ? this.host(host, props, children, id, n) : this.child(view, props, children, id, !!data.props?.resetState));
|
|
2808
|
+
if (typeof props[binding] == 'string' || props[binding]?.[BINDING]) {
|
|
2809
|
+
k.bs = bindingState(k);
|
|
2810
|
+
(this.bound || (this.bound = new Set())).add(k);
|
|
2811
|
+
this.app.watchers.add(this);
|
|
2812
|
+
}
|
|
2744
2813
|
return;
|
|
2745
2814
|
}
|
|
2746
2815
|
if (posts[sel] && !(ps || (ps = [])).includes(sel))
|
|
@@ -2765,11 +2834,11 @@ class Inst {
|
|
|
2765
2834
|
return make(this, props, children, id, n);
|
|
2766
2835
|
}
|
|
2767
2836
|
catch (err) {
|
|
2768
|
-
return this.failed(err);
|
|
2837
|
+
return this.failed(err, props);
|
|
2769
2838
|
}
|
|
2770
2839
|
}
|
|
2771
2840
|
/** the owner's error fallback for a child that failed to instantiate (SYG408) */
|
|
2772
|
-
failed(err) {
|
|
2841
|
+
failed(err, props) {
|
|
2773
2842
|
const e = err instanceof Error ? err : new Error(String(err));
|
|
2774
2843
|
let out = errorDiv$1(this.def.name);
|
|
2775
2844
|
if (this.def.onError) {
|
|
@@ -2781,7 +2850,7 @@ class Inst {
|
|
|
2781
2850
|
}
|
|
2782
2851
|
}
|
|
2783
2852
|
this.app.caught(this, 'SYG408', 'Sub-component threw; rendering the error fallback', e, 'instantiate');
|
|
2784
|
-
return new Failed(out);
|
|
2853
|
+
return new Failed(out, props);
|
|
2785
2854
|
}
|
|
2786
2855
|
/** a tag child: state="key" | state={lens} | none (the parent's state) | isolatedState (D174: `resetState`) */
|
|
2787
2856
|
child(view, props, children, id, reset = false) {
|
|
@@ -2800,7 +2869,7 @@ class Inst {
|
|
|
2800
2869
|
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);
|
|
2801
2870
|
}
|
|
2802
2871
|
catch (err) {
|
|
2803
|
-
return this.failed(err);
|
|
2872
|
+
return this.failed(err, props);
|
|
2804
2873
|
}
|
|
2805
2874
|
}
|
|
2806
2875
|
inject(n, path) {
|
|
@@ -2863,8 +2932,7 @@ class Inst {
|
|
|
2863
2932
|
tearDown(() => { for (const s of subs)
|
|
2864
2933
|
s.unsubscribe(); }, app.dq, this);
|
|
2865
2934
|
this.subs = [];
|
|
2866
|
-
|
|
2867
|
-
app.watchers.delete(this);
|
|
2935
|
+
app.watchers.delete(this);
|
|
2868
2936
|
this.hub = null;
|
|
2869
2937
|
}
|
|
2870
2938
|
}
|
|
@@ -2963,7 +3031,7 @@ function arrayCell(owner, from) {
|
|
|
2963
3031
|
if (typeof from == 'string')
|
|
2964
3032
|
return keyCell(oc, from, owner.def.calcNames?.has(pathOf$1(from)[0]) && name, undefined, 'Collection');
|
|
2965
3033
|
if (isObj$2(from) && typeof from.get == 'function') {
|
|
2966
|
-
let lp = {}, lv;
|
|
3034
|
+
let lp = {}, lv, raw;
|
|
2967
3035
|
return {
|
|
2968
3036
|
get() {
|
|
2969
3037
|
const p = oc.get();
|
|
@@ -2979,7 +3047,7 @@ function arrayCell(owner, from) {
|
|
|
2979
3047
|
owner.app.appError(owner, e, 'view');
|
|
2980
3048
|
return lv;
|
|
2981
3049
|
}
|
|
2982
|
-
lv = g;
|
|
3050
|
+
raw = lv = g;
|
|
2983
3051
|
if (!Array.isArray(lv)) {
|
|
2984
3052
|
warn('SYG401', name, "Collection 'from' getter returned a non-array; it renders nothing", 'Return an array from get()', lv);
|
|
2985
3053
|
lv = NONE$1;
|
|
@@ -2987,6 +3055,7 @@ function arrayCell(owner, from) {
|
|
|
2987
3055
|
}
|
|
2988
3056
|
return lv;
|
|
2989
3057
|
},
|
|
3058
|
+
has() { this.get(); return raw !== undefined; },
|
|
2990
3059
|
set(v) {
|
|
2991
3060
|
if (typeof from.set != 'function')
|
|
2992
3061
|
return;
|
|
@@ -5348,8 +5417,11 @@ const validateWith = async (schema, value) => {
|
|
|
5348
5417
|
* declarations in src/index.d.ts and docs/guide/drivers.md. Summary:
|
|
5349
5418
|
*
|
|
5350
5419
|
* - Request (sink value): `{ url, ok?, error?, key?, category?, method?, headers?, query?, json?,
|
|
5351
|
-
* body?, latest?, timeoutMs?, parse?, init? }` or a URL string. `json` is stringified
|
|
5352
|
-
* content-type;
|
|
5420
|
+
* multipart?, body?, latest?, timeoutMs?, parse?, init? }` or a URL string. `json` is stringified
|
|
5421
|
+
* with a JSON content-type; PLAN-11 U-5: `multipart` (an object) becomes a FormData body (a
|
|
5422
|
+
* File / Blob a file part, an array repeats the name, null / undefined skipped, an object JSON,
|
|
5423
|
+
* anything else a string), the content-type left to the browser (its boundary); method defaults
|
|
5424
|
+
* to POST with a body/json/multipart, else GET. `query` is appended as a
|
|
5353
5425
|
* query string (null/undefined values skipped, arrays repeat the key, before any #fragment).
|
|
5354
5426
|
* Other fetch options (credentials, mode, cache, ...) go under `init` (R4-5); other top-level
|
|
5355
5427
|
* keys are the app's own: not sent, but returned on `request`.
|
|
@@ -5426,6 +5498,15 @@ const validateWith = async (schema, value) => {
|
|
|
5426
5498
|
* the request itself) into requests sent again in parallel and combined, at most 3 deep. One
|
|
5427
5499
|
* failure fails the request once (`error.status` / `error.body`). No cache entry sharing, no
|
|
5428
5500
|
* per-part retry. The test fake (`_whole`) sends and answers it as one request. decide() uses it.
|
|
5501
|
+
* - PLAN-11 U-1 (D555): `progress: 'ACTION'` replies to the sender with each progress step,
|
|
5502
|
+
* `progress: true` (or a string with no sender) sends it down `progress(category?)`:
|
|
5503
|
+
* `{ phase: 'upload' | 'download', loaded, total, fraction, category, request }` (`total` /
|
|
5504
|
+
* `fraction` null when the length is unknown). Such a request goes over XMLHttpRequest (the
|
|
5505
|
+
* only transport with upload progress in every engine; S-1), turned back into a Response so
|
|
5506
|
+
* parsing, status, retry and timeouts are the same. Steps are throttled to one per 50 ms per
|
|
5507
|
+
* phase, keeping the first and the last. Not for resources or `batch`; never shares a fetch.
|
|
5508
|
+
* No XMLHttpRequest (Node, a service worker): plain fetch, no steps. The transport is
|
|
5509
|
+
* `options.XMLHttpRequest`, else the global one, read at request time.
|
|
5429
5510
|
* - dispose / sink completion aborts everything in flight.
|
|
5430
5511
|
* - `fetch` is read at request time (options.fetch, else globalThis.fetch), so test stubs work.
|
|
5431
5512
|
* - PLAN-3 5-1 (H-9): renderComponent's HTTP fake runs this driver over an in-memory fetch;
|
|
@@ -5519,6 +5600,81 @@ const mergeHeaders = (...all) => {
|
|
|
5519
5600
|
h.forEach((v, k) => { out[k] = v; });
|
|
5520
5601
|
return out;
|
|
5521
5602
|
};
|
|
5603
|
+
// PLAN-11 U-1: statuses whose Response must have a null body
|
|
5604
|
+
const NULL_BODY = [101, 103, 204, 205, 304];
|
|
5605
|
+
const abortError$1 = () => typeof DOMException == 'function'
|
|
5606
|
+
? new DOMException('The operation was aborted.', 'AbortError')
|
|
5607
|
+
: Object.assign(new Error('The operation was aborted.'), { name: 'AbortError' });
|
|
5608
|
+
/**
|
|
5609
|
+
* PLAN-11 U-1 (S-1): a fetch-shaped call over XMLHttpRequest that reports progress:
|
|
5610
|
+
* `on(phase, loaded, total)` (total 0 when unknown), at most every 50 ms per phase, always the
|
|
5611
|
+
* first and the final step. Resolves with a real Response (blob body, parsed headers); network
|
|
5612
|
+
* errors and timeouts reject with a TypeError, an abort with an AbortError, like fetch.
|
|
5613
|
+
*/
|
|
5614
|
+
const xhrFetch = (X, url, init, on) => new Promise((res, rej) => {
|
|
5615
|
+
const x = new X();
|
|
5616
|
+
const last = {};
|
|
5617
|
+
const step = (phase, loaded, total, final) => {
|
|
5618
|
+
const l = last[phase], now = Date.now();
|
|
5619
|
+
if (l && (l.loaded === loaded || (!final && now - l.at < 50 && !(total && loaded >= total))))
|
|
5620
|
+
return;
|
|
5621
|
+
last[phase] = { loaded, at: now };
|
|
5622
|
+
on(phase, loaded, total);
|
|
5623
|
+
};
|
|
5624
|
+
const ev = (phase) => (e) => step(phase, e.loaded, e.lengthComputable ? e.total : 0);
|
|
5625
|
+
x.open(init.method || 'GET', url);
|
|
5626
|
+
x.responseType = 'blob';
|
|
5627
|
+
const h = init.headers || {};
|
|
5628
|
+
Object.keys(h).forEach(k => x.setRequestHeader(k, h[k]));
|
|
5629
|
+
if (init.credentials == 'include')
|
|
5630
|
+
x.withCredentials = true;
|
|
5631
|
+
const sig = init.signal;
|
|
5632
|
+
if (sig) {
|
|
5633
|
+
if (sig.aborted)
|
|
5634
|
+
return rej(abortError$1());
|
|
5635
|
+
sig.addEventListener?.('abort', () => x.abort(), { once: true });
|
|
5636
|
+
}
|
|
5637
|
+
if (init.body != null && x.upload) {
|
|
5638
|
+
x.upload.onprogress = ev('upload');
|
|
5639
|
+
x.upload.onload = (e) => step('upload', e.loaded, e.lengthComputable ? e.total : e.loaded, 1);
|
|
5640
|
+
}
|
|
5641
|
+
x.onprogress = ev('download');
|
|
5642
|
+
x.onload = () => {
|
|
5643
|
+
const size = x.response?.size ?? last.download?.loaded ?? 0;
|
|
5644
|
+
step('download', size, size, 1);
|
|
5645
|
+
const headers = typeof Headers == 'function' ? new Headers() : undefined;
|
|
5646
|
+
String(x.getAllResponseHeaders?.() || '').trim().split(/[\r\n]+/).forEach((line) => {
|
|
5647
|
+
const i = line.indexOf(':');
|
|
5648
|
+
if (i > 0)
|
|
5649
|
+
try {
|
|
5650
|
+
headers?.append(line.slice(0, i).trim(), line.slice(i + 1).trim());
|
|
5651
|
+
}
|
|
5652
|
+
catch (_) { }
|
|
5653
|
+
});
|
|
5654
|
+
try {
|
|
5655
|
+
res(new Response(NULL_BODY.includes(x.status) ? null : x.response, { status: x.status, statusText: x.statusText, headers }));
|
|
5656
|
+
}
|
|
5657
|
+
catch (e) {
|
|
5658
|
+
rej(e);
|
|
5659
|
+
}
|
|
5660
|
+
};
|
|
5661
|
+
x.onerror = x.ontimeout = () => rej(new TypeError(`Network request failed: ${url}`));
|
|
5662
|
+
x.onabort = () => rej(abortError$1());
|
|
5663
|
+
x.send(init.body ?? null);
|
|
5664
|
+
});
|
|
5665
|
+
/** PLAN-11 U-5: a request's `multipart` object as FormData (see the header) */
|
|
5666
|
+
const toFormData = (parts) => {
|
|
5667
|
+
const fd = new FormData();
|
|
5668
|
+
Object.keys(parts || {}).forEach(k => [].concat(parts[k]).forEach((v) => {
|
|
5669
|
+
if (v == null)
|
|
5670
|
+
return;
|
|
5671
|
+
if (typeof Blob != 'undefined' && v instanceof Blob)
|
|
5672
|
+
fd.append(k, v, v.name ?? 'blob');
|
|
5673
|
+
else
|
|
5674
|
+
fd.append(k, typeof v == 'object' ? JSON.stringify(v) : String(v));
|
|
5675
|
+
}));
|
|
5676
|
+
return fd;
|
|
5677
|
+
};
|
|
5522
5678
|
const IDEMPOTENT$1 = /^(GET|HEAD)$/i;
|
|
5523
5679
|
// D80: an invalidate value matches a request by tag, URL prefix ('/…') or predicate
|
|
5524
5680
|
const hit = (x, q) => {
|
|
@@ -5543,6 +5699,22 @@ function makeFetchDriver(options = {}) {
|
|
|
5543
5699
|
// q (the request), timer, f (the fetch it waits on), ok / ko (its outcomes) }
|
|
5544
5700
|
const inflight = new Map();
|
|
5545
5701
|
const subs = new Set();
|
|
5702
|
+
// PLAN-11 U-1: progress(category?) listeners (not held while nobody listens)
|
|
5703
|
+
const psubs = new Set();
|
|
5704
|
+
const emitProgress = (v) => psubs.forEach(s => {
|
|
5705
|
+
let m = false;
|
|
5706
|
+
try {
|
|
5707
|
+
m = matches(s, v);
|
|
5708
|
+
}
|
|
5709
|
+
catch (_) { }
|
|
5710
|
+
if (m)
|
|
5711
|
+
try {
|
|
5712
|
+
s.l.next(v);
|
|
5713
|
+
}
|
|
5714
|
+
catch (e) {
|
|
5715
|
+
console.error('[Sygnal] makeFetchDriver: a listener threw', e);
|
|
5716
|
+
}
|
|
5717
|
+
});
|
|
5546
5718
|
// R4-4: replies held until the first select() listener, failures until the first errors()
|
|
5547
5719
|
// listener (or the next macrotask, when they're logged if nothing listens)
|
|
5548
5720
|
const started = [false, false];
|
|
@@ -5605,6 +5777,10 @@ function makeFetchDriver(options = {}) {
|
|
|
5605
5777
|
return {
|
|
5606
5778
|
select: make(false),
|
|
5607
5779
|
errors: make(true),
|
|
5780
|
+
progress: (sel) => {
|
|
5781
|
+
let sub;
|
|
5782
|
+
return xs$2.create({ start: l => { psubs.add((sub = { l, sel, ns })); }, stop: () => { psubs.delete(sub); } });
|
|
5783
|
+
},
|
|
5608
5784
|
isolateSource: (_, scope) => source(ns.concat(scope)),
|
|
5609
5785
|
isolateSink: (sink$, scope) => sink$.map((req) => tagRequest(req, scope)),
|
|
5610
5786
|
// PLAN-4.6: the same per value (the core scopes a request without a stream per instance)
|
|
@@ -5724,7 +5900,7 @@ function makeFetchDriver(options = {}) {
|
|
|
5724
5900
|
req = { url: req };
|
|
5725
5901
|
if (!req || typeof req != 'object' || disposed || !allowed$1(req, 'makeFetchDriver'))
|
|
5726
5902
|
return;
|
|
5727
|
-
const { url, category, method, headers, query, json, body, latest, timeoutMs, parse, abort, init, ok, error, key, resources, refresh, invalidates, updates, retry, validate, prefetch } = req;
|
|
5903
|
+
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;
|
|
5728
5904
|
if ('invalidate' in req)
|
|
5729
5905
|
return inval(req.invalidate);
|
|
5730
5906
|
if (prefetch)
|
|
@@ -5776,7 +5952,9 @@ function makeFetchDriver(options = {}) {
|
|
|
5776
5952
|
}
|
|
5777
5953
|
const scope = scopeKey(req);
|
|
5778
5954
|
// reply actions: keyed per (sender, key ?? ok ?? error); a resource per (sender, '\0' + name)
|
|
5779
|
-
|
|
5955
|
+
// PLAN-11 U-1: a progress action is a reply action too (abort on dispose, its own lane)
|
|
5956
|
+
const pr = !pf && rn === undefined && !req.batch && progress;
|
|
5957
|
+
const rk = rn !== undefined ? '\0' + rn : !pf && sender !== undefined && (ok || error || typeof pr == 'string') ? key ?? ok ?? error ?? pr : undefined;
|
|
5780
5958
|
if (sender !== undefined && (typeof abort == 'string' || (abort && key !== undefined)))
|
|
5781
5959
|
return cancel(r => r.sender === sender && r.rk === (key ?? abort));
|
|
5782
5960
|
// per (scope, category): another component's requests are never cancelled
|
|
@@ -5786,7 +5964,7 @@ function makeFetchDriver(options = {}) {
|
|
|
5786
5964
|
cancel(rk === undefined ? r => r.rk === undefined && r.scope === scope && r.category === category : r => r.sender === sender && r.rk === rk);
|
|
5787
5965
|
const base = options.init;
|
|
5788
5966
|
// a request's URL and fetch() init (the batch's sub-requests inherit the request's fields)
|
|
5789
|
-
const prep = ({ url, method, headers, query, json, body, init: own }) => {
|
|
5967
|
+
const prep = ({ url, method, headers, query, json, multipart, body, init: own }) => {
|
|
5790
5968
|
const h = mergeHeaders(options.headers, base?.headers, own?.headers, headers);
|
|
5791
5969
|
let b = body ?? own?.body;
|
|
5792
5970
|
if (json !== undefined) {
|
|
@@ -5794,6 +5972,12 @@ function makeFetchDriver(options = {}) {
|
|
|
5794
5972
|
if (!Object.keys(h).some(k => k.toLowerCase() == 'content-type'))
|
|
5795
5973
|
h['content-type'] = 'application/json';
|
|
5796
5974
|
}
|
|
5975
|
+
else if (multipart !== undefined) {
|
|
5976
|
+
b = toFormData(multipart);
|
|
5977
|
+
// the browser writes multipart/form-data with its boundary; a content-type set here would break it
|
|
5978
|
+
Object.keys(h).forEach(k => { if (k.toLowerCase() == 'content-type')
|
|
5979
|
+
delete h[k]; });
|
|
5980
|
+
}
|
|
5797
5981
|
return [withQuery((options.baseUrl || '') + (url ?? ''), query), { ...pickInit(base), ...pickInit(own), method: method || own?.method || base?.method || (b !== undefined ? 'POST' : 'GET'), headers: h, body: b }];
|
|
5798
5982
|
};
|
|
5799
5983
|
const [href, fetchInit] = prep(req);
|
|
@@ -5802,7 +5986,8 @@ function makeFetchDriver(options = {}) {
|
|
|
5802
5986
|
// parts, at most 3 deep), their parsed bodies merged by `combine` into one reply. The test
|
|
5803
5987
|
// fake (_whole) answers such a request as one
|
|
5804
5988
|
const { batch, split, combine = (x) => x } = options._whole ? {} : req;
|
|
5805
|
-
|
|
5989
|
+
// PLAN-11 U-1: a progress request never shares a fetch (each sender gets its own steps)
|
|
5990
|
+
const k = pr ? undefined : C?.key(req, rn !== undefined || pf);
|
|
5806
5991
|
const id = ++seq;
|
|
5807
5992
|
const r = { category, scope, sender, rk };
|
|
5808
5993
|
inflight.set(id, r);
|
|
@@ -5850,7 +6035,19 @@ function makeFetchDriver(options = {}) {
|
|
|
5850
6035
|
const parser = parserOf(parse || options.parse);
|
|
5851
6036
|
// the global fetch is called as a method: a detached window.fetch throws "Illegal invocation"
|
|
5852
6037
|
const g = globalThis;
|
|
5853
|
-
|
|
6038
|
+
let doFetch = options.fetch || (typeof g.fetch == 'function' && ((u, i) => g.fetch(u, i)));
|
|
6039
|
+
// PLAN-11 U-1: a progress request over XMLHttpRequest (renderComponent's fake: `_progress`)
|
|
6040
|
+
const X = options.XMLHttpRequest ?? g.XMLHttpRequest;
|
|
6041
|
+
if (pr && (options._progress || typeof X == 'function')) {
|
|
6042
|
+
const step = (phase, loaded, total) => {
|
|
6043
|
+
if (!inflight.has(id) || disposed)
|
|
6044
|
+
return;
|
|
6045
|
+
const t = total > 0 ? total : null;
|
|
6046
|
+
const v = { phase, loaded, total: t, fraction: t ? Math.min(1, loaded / t) : null, category, request: req };
|
|
6047
|
+
typeof pr == 'string' && sender !== undefined ? reply(sender, pr, v) : emitProgress(v);
|
|
6048
|
+
};
|
|
6049
|
+
doFetch = options._progress ? (u, i) => options._progress(u, i, step, req) : (u, i) => xhrFetch(X, u, i, step);
|
|
6050
|
+
}
|
|
5854
6051
|
// the outcome, to every request waiting on this fetch (the failure once, after the last attempt)
|
|
5855
6052
|
const end = (e, x, status) => {
|
|
5856
6053
|
done(F);
|
|
@@ -6012,9 +6209,10 @@ function queryCache(o = {}) {
|
|
|
6012
6209
|
const key = (q, res) => {
|
|
6013
6210
|
if (typeof q == 'string')
|
|
6014
6211
|
q = { url: q };
|
|
6015
|
-
const { method, json, body, parse, query } = q;
|
|
6016
|
-
const m = (method || (json !== undefined || body !== undefined ? 'POST' : 'GET')).toUpperCase();
|
|
6017
|
-
|
|
6212
|
+
const { method, json, body, parse, query, multipart } = q;
|
|
6213
|
+
const m = (method || (json !== undefined || body !== undefined || multipart !== undefined ? 'POST' : 'GET')).toUpperCase();
|
|
6214
|
+
// PLAN-11 U-5: a multipart request is never cached (its body isn't a string)
|
|
6215
|
+
const b = json !== undefined ? JSON.stringify(json) : multipart !== undefined ? multipart : body ?? '';
|
|
6018
6216
|
if (q.cache === false || typeof b != 'string' || typeof parse == 'function' || parse == 'response' ||
|
|
6019
6217
|
!(q.cache || q.staleTime != null || (res && IDEMPOTENT.test(m))))
|
|
6020
6218
|
return;
|
|
@@ -6083,6 +6281,272 @@ function queryCache(o = {}) {
|
|
|
6083
6281
|
return C;
|
|
6084
6282
|
}
|
|
6085
6283
|
|
|
6284
|
+
/*
|
|
6285
|
+
* PLAN-6 L-2: what the HTTP transports share (side-effect free; each transport imports only what
|
|
6286
|
+
* it uses).
|
|
6287
|
+
* - post(): the fetch (the `fetch` option, 0-S5), the headers option (an object or a function of
|
|
6288
|
+
* the request), HTTP errors as Errors with `status` and the provider's message, and SYG670.
|
|
6289
|
+
* - sse(): a fetch Response body as SSE frames ({ event, data }), per the HTML spec's parsing
|
|
6290
|
+
* rules: CRLF / LF, comments, multi-line data. The reader is cancelled when the consumer stops
|
|
6291
|
+
* (abort, a thrown error), so the connection closes.
|
|
6292
|
+
* - SYG670: in a browser, a request with an auth header (Authorization, x-api-key, api-key,
|
|
6293
|
+
* x-goog-api-key) to a host that is neither local (localhost, 127.0.0.1, [::1], *.localhost)
|
|
6294
|
+
* nor the page's own origin is refused unless the transport has `dangerouslyAllowBrowser: true`:
|
|
6295
|
+
* the key would ship to every visitor. Same-origin requests are the app's own server (its
|
|
6296
|
+
* proxy), so a session header there is fine.
|
|
6297
|
+
*/
|
|
6298
|
+
const g$6 = globalThis;
|
|
6299
|
+
const AUTH = /^(authorization|x-api-key|api-key|x-goog-api-key)$/i;
|
|
6300
|
+
const LOCAL = /^(localhost|127\.0\.0\.1|\[::1\]|.+\.localhost)$/;
|
|
6301
|
+
/** the URL a transport posts to, or a SYG670 Error when it must not (see the header) */
|
|
6302
|
+
function guard(url, headers, opts, name) {
|
|
6303
|
+
if (opts.dangerouslyAllowBrowser || typeof g$6.document == 'undefined' || !Object.keys(headers).some(h => AUTH.test(h)))
|
|
6304
|
+
return;
|
|
6305
|
+
const here = g$6.location?.href;
|
|
6306
|
+
let u;
|
|
6307
|
+
try {
|
|
6308
|
+
u = new URL(url, here);
|
|
6309
|
+
}
|
|
6310
|
+
catch (_) {
|
|
6311
|
+
return;
|
|
6312
|
+
}
|
|
6313
|
+
if (LOCAL.test(u.hostname) || (here && u.origin == new URL(here).origin))
|
|
6314
|
+
return;
|
|
6315
|
+
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 })`;
|
|
6316
|
+
const msg = `${name}: refused to send an auth header from the browser to ${u.origin}: anyone who opens the page can read it`;
|
|
6317
|
+
error('SYG670', undefined, msg, fix);
|
|
6318
|
+
fail$2('SYG670', undefined, msg, fix);
|
|
6319
|
+
}
|
|
6320
|
+
/**
|
|
6321
|
+
* POST JSON; a non-2xx response is an Error with `status`, `body` and the provider's message.
|
|
6322
|
+
* The transport's `body` option and the request's `body` are merged over the built body (so they
|
|
6323
|
+
* can override provider defaults such as `max_tokens`); `fixed` fields are merged last and can't be
|
|
6324
|
+
* overridden (G-654: uiMessageStream's protocol fields, as the AI SDK's DefaultChatTransport)
|
|
6325
|
+
*/
|
|
6326
|
+
async function post$1(url, body, req, signal, opts, name, fixed) {
|
|
6327
|
+
const h = typeof opts.headers == 'function' ? await opts.headers(req) : opts.headers || {};
|
|
6328
|
+
guard(url, h, opts, name);
|
|
6329
|
+
const f = opts.fetch || ((u, i) => g$6.fetch(u, i));
|
|
6330
|
+
const res = await f(url, { method: 'POST', headers: { 'content-type': 'application/json', ...h }, body: JSON.stringify({ ...body, ...opts.body, ...req.body, ...fixed }), signal });
|
|
6331
|
+
if (!res.ok) {
|
|
6332
|
+
let text = '', message = '';
|
|
6333
|
+
try {
|
|
6334
|
+
text = await res.text();
|
|
6335
|
+
const j = JSON.parse(text);
|
|
6336
|
+
message = j.error?.message ?? j.error ?? j.message ?? '';
|
|
6337
|
+
}
|
|
6338
|
+
catch (_) {
|
|
6339
|
+
message || (message = text.slice(0, 200));
|
|
6340
|
+
}
|
|
6341
|
+
throw Object.assign(new Error(`${name}: HTTP ${res.status}${message ? ': ' + message : ''}`), { status: res.status, body: text });
|
|
6342
|
+
}
|
|
6343
|
+
if (!res.body)
|
|
6344
|
+
throw new Error(`${name}: the response has no body`);
|
|
6345
|
+
return res;
|
|
6346
|
+
}
|
|
6347
|
+
/**
|
|
6348
|
+
* the SSE frames of a response body: `{ event, data }` (data: the joined data lines). PLAN-11
|
|
6349
|
+
* E-1: `meta` (optional) follows the stream's `id` (the last event id, kept across frames) and
|
|
6350
|
+
* `retry` (ms) fields, as EventSource does, for a reconnect
|
|
6351
|
+
*/
|
|
6352
|
+
async function* sse(res, meta) {
|
|
6353
|
+
const reader = res.body.getReader(), dec = new TextDecoder();
|
|
6354
|
+
let buf = '', cr = '', done = false;
|
|
6355
|
+
try {
|
|
6356
|
+
for (;;) {
|
|
6357
|
+
const r = await reader.read();
|
|
6358
|
+
// line ends -> \n (a \r at a chunk's end waits: it may be half of a \r\n)
|
|
6359
|
+
let s = cr + (r.done ? '\n\n' : dec.decode(r.value, { stream: true }));
|
|
6360
|
+
cr = !r.done && s.endsWith('\r') ? '\r' : '';
|
|
6361
|
+
if (cr)
|
|
6362
|
+
s = s.slice(0, -1);
|
|
6363
|
+
buf += s.replace(/\r\n?/g, '\n');
|
|
6364
|
+
done = r.done;
|
|
6365
|
+
let i;
|
|
6366
|
+
while ((i = buf.indexOf('\n\n')) >= 0) {
|
|
6367
|
+
const frame = buf.slice(0, i);
|
|
6368
|
+
buf = buf.slice(i + 2);
|
|
6369
|
+
let event = 'message', data = [];
|
|
6370
|
+
for (const line of frame.split('\n')) {
|
|
6371
|
+
if (!line || line[0] == ':')
|
|
6372
|
+
continue;
|
|
6373
|
+
const c = line.indexOf(':'), field = c < 0 ? line : line.slice(0, c);
|
|
6374
|
+
const v = c < 0 ? '' : line.slice(c + 1).replace(/^ /, '');
|
|
6375
|
+
if (field == 'event')
|
|
6376
|
+
event = v;
|
|
6377
|
+
else if (field == 'data')
|
|
6378
|
+
data.push(v);
|
|
6379
|
+
else if (meta && field == 'id' && !v.includes('\0'))
|
|
6380
|
+
meta.id = v;
|
|
6381
|
+
else if (meta && field == 'retry' && /^\d+$/.test(v))
|
|
6382
|
+
meta.retry = +v;
|
|
6383
|
+
}
|
|
6384
|
+
if (data.length)
|
|
6385
|
+
yield { event, data: data.join('\n') };
|
|
6386
|
+
}
|
|
6387
|
+
if (done)
|
|
6388
|
+
return;
|
|
6389
|
+
}
|
|
6390
|
+
}
|
|
6391
|
+
finally {
|
|
6392
|
+
if (!done)
|
|
6393
|
+
reader.cancel().catch(() => { });
|
|
6394
|
+
}
|
|
6395
|
+
}
|
|
6396
|
+
/** JSON.parse, or the text itself when it isn't JSON (a model's malformed tool arguments go to validation as is) */
|
|
6397
|
+
const json$1 = (s) => {
|
|
6398
|
+
if (typeof s != 'string')
|
|
6399
|
+
return s;
|
|
6400
|
+
try {
|
|
6401
|
+
return JSON.parse(s || '{}');
|
|
6402
|
+
}
|
|
6403
|
+
catch (_) {
|
|
6404
|
+
return s;
|
|
6405
|
+
}
|
|
6406
|
+
};
|
|
6407
|
+
/** a provider's usage as `{ inputTokens, outputTokens, totalTokens, reasoningTokens? }` (AI SDK names) */
|
|
6408
|
+
const usageOf = (u) => u && {
|
|
6409
|
+
inputTokens: u.input_tokens ?? u.prompt_tokens,
|
|
6410
|
+
outputTokens: u.output_tokens ?? u.completion_tokens,
|
|
6411
|
+
totalTokens: u.total_tokens,
|
|
6412
|
+
...((u.output_tokens_details ?? u.completion_tokens_details)?.reasoning_tokens != null && { reasoningTokens: (u.output_tokens_details ?? u.completion_tokens_details).reasoning_tokens }),
|
|
6413
|
+
};
|
|
6414
|
+
/** the parts of a message (a `content` string is one text part) */
|
|
6415
|
+
const partsOf = (m) => m.parts ?? (typeof m.content == 'string' ? [{ type: 'text', text: m.content }] : []);
|
|
6416
|
+
/** the tool name of a `tool-<name>` / `dynamic-tool` part, or '' */
|
|
6417
|
+
const toolName = (p) => p.type == 'dynamic-tool' ? p.toolName : p.type.startsWith('tool-') ? p.type.slice(5) : '';
|
|
6418
|
+
/** what a tool part sends back to the model, or undefined when it has no result yet */
|
|
6419
|
+
const toolOutput = (p) => p.state == 'output-available' ? (typeof p.output == 'string' ? p.output : JSON.stringify(p.output ?? null))
|
|
6420
|
+
: p.state == 'output-error' ? JSON.stringify({ error: p.errorText ?? 'error' })
|
|
6421
|
+
: p.state == 'output-denied' ? JSON.stringify({ error: 'The user denied this tool call' })
|
|
6422
|
+
: undefined;
|
|
6423
|
+
// dev-only diagnostics (SYG675): the dev entry formats them (checks/chat.ts)
|
|
6424
|
+
const dev$8 = (...a) => g$6.__SYGNAL_DIAGNOSTICS__?.chat?.(...a);
|
|
6425
|
+
|
|
6426
|
+
/*
|
|
6427
|
+
* PLAN-11 E-1: an EventSource over fetch, for server-sent events that need request headers
|
|
6428
|
+
* (`Authorization`), which the native EventSource can't send. makeSocketDriver uses it for an
|
|
6429
|
+
* `sse` connection with `headers`. It behaves as EventSource does where the driver relies on it:
|
|
6430
|
+
*
|
|
6431
|
+
* - connects on construction (a microtask later, so handlers can be set first); `readyState`
|
|
6432
|
+
* CONNECTING (0) → OPEN (1) on a 2xx `text/event-stream` response;
|
|
6433
|
+
* - `onmessage` and `addEventListener(type)` get `{ type, data, lastEventId }`;
|
|
6434
|
+
* - the stream ending or a network error: `error`, readyState CONNECTING, a reconnect after
|
|
6435
|
+
* `retry` ms (the stream's `retry:` field, default 3000) with `Last-Event-ID`;
|
|
6436
|
+
* - a non-2xx status or another content type: `error` with readyState CLOSED (no retry; the
|
|
6437
|
+
* driver's reconnect policy decides);
|
|
6438
|
+
* - `close()` aborts the request and stops retrying.
|
|
6439
|
+
*/
|
|
6440
|
+
const NAMES = ['CONNECTING', 'OPEN', 'CLOSED'];
|
|
6441
|
+
class FetchEventSource {
|
|
6442
|
+
constructor(url, init = {}) {
|
|
6443
|
+
this.readyState = 0;
|
|
6444
|
+
this.onopen = null;
|
|
6445
|
+
this.onmessage = null;
|
|
6446
|
+
this.onerror = null;
|
|
6447
|
+
this.ls = new Map();
|
|
6448
|
+
this.meta = { id: '', retry: 3000 };
|
|
6449
|
+
this.url = url;
|
|
6450
|
+
this.withCredentials = !!init.withCredentials;
|
|
6451
|
+
this.init = init;
|
|
6452
|
+
queueMicrotask(() => this.connect());
|
|
6453
|
+
}
|
|
6454
|
+
addEventListener(type, f) {
|
|
6455
|
+
let s = this.ls.get(type);
|
|
6456
|
+
if (!s)
|
|
6457
|
+
this.ls.set(type, (s = new Set()));
|
|
6458
|
+
s.add(f);
|
|
6459
|
+
}
|
|
6460
|
+
removeEventListener(type, f) { this.ls.get(type)?.delete(f); }
|
|
6461
|
+
close() {
|
|
6462
|
+
this.readyState = 2;
|
|
6463
|
+
clearTimeout(this.timer);
|
|
6464
|
+
try {
|
|
6465
|
+
this.ctl?.abort();
|
|
6466
|
+
}
|
|
6467
|
+
catch (_) { }
|
|
6468
|
+
}
|
|
6469
|
+
emit(type, ev) {
|
|
6470
|
+
if (type == 'message')
|
|
6471
|
+
this.onmessage?.(ev);
|
|
6472
|
+
else if (type == 'open')
|
|
6473
|
+
this.onopen?.(ev);
|
|
6474
|
+
else if (type == 'error')
|
|
6475
|
+
this.onerror?.(ev);
|
|
6476
|
+
this.ls.get(type)?.forEach(f => { try {
|
|
6477
|
+
f(ev);
|
|
6478
|
+
}
|
|
6479
|
+
catch (e) {
|
|
6480
|
+
console.error(e);
|
|
6481
|
+
} });
|
|
6482
|
+
}
|
|
6483
|
+
async connect() {
|
|
6484
|
+
if (this.readyState == 2)
|
|
6485
|
+
return;
|
|
6486
|
+
const g = globalThis;
|
|
6487
|
+
const f = this.init.fetch || (typeof g.fetch == 'function' && ((u, i) => g.fetch(u, i)));
|
|
6488
|
+
if (typeof f != 'function')
|
|
6489
|
+
return this.fail(new Error('fetch is not available in this environment'));
|
|
6490
|
+
const ctl = this.ctl = typeof AbortController == 'function' ? new AbortController() : {};
|
|
6491
|
+
const headers = { accept: 'text/event-stream', ...this.init.headers };
|
|
6492
|
+
if (this.meta.id)
|
|
6493
|
+
headers['last-event-id'] = this.meta.id;
|
|
6494
|
+
let res;
|
|
6495
|
+
try {
|
|
6496
|
+
res = await f(this.url, { headers, credentials: this.withCredentials ? 'include' : 'same-origin', cache: 'no-store', signal: ctl.signal });
|
|
6497
|
+
}
|
|
6498
|
+
catch (error) {
|
|
6499
|
+
return this.retry(error);
|
|
6500
|
+
}
|
|
6501
|
+
if (this.readyState == 2 || this.ctl !== ctl)
|
|
6502
|
+
return;
|
|
6503
|
+
const type = res.headers?.get?.('content-type') || '';
|
|
6504
|
+
if (!(res.status >= 200 && res.status < 300) || !/^text\/event-stream/i.test(type) || !res.body) {
|
|
6505
|
+
try {
|
|
6506
|
+
res.body?.cancel?.();
|
|
6507
|
+
}
|
|
6508
|
+
catch (_) { }
|
|
6509
|
+
return this.fail(Object.assign(new Error(`SSE ${res.status}${type ? ' ' + type : ''}: ${this.url}`), { status: res.status }));
|
|
6510
|
+
}
|
|
6511
|
+
this.readyState = 1;
|
|
6512
|
+
this.emit('open', { type: 'open' });
|
|
6513
|
+
try {
|
|
6514
|
+
for await (const fr of sse(res, this.meta)) {
|
|
6515
|
+
if (this.readyState == 2 || this.ctl !== ctl)
|
|
6516
|
+
return;
|
|
6517
|
+
this.emit(fr.event, { type: fr.event, data: fr.data, lastEventId: this.meta.id });
|
|
6518
|
+
}
|
|
6519
|
+
}
|
|
6520
|
+
catch (error) {
|
|
6521
|
+
if (this.readyState == 2 || this.ctl !== ctl)
|
|
6522
|
+
return;
|
|
6523
|
+
return this.retry(error);
|
|
6524
|
+
}
|
|
6525
|
+
if (this.readyState != 2 && this.ctl === ctl)
|
|
6526
|
+
this.retry();
|
|
6527
|
+
}
|
|
6528
|
+
/** a drop: error, CONNECTING, try again after `retry` ms (unless the error handler closed it) */
|
|
6529
|
+
retry(error) {
|
|
6530
|
+
if (this.readyState == 2)
|
|
6531
|
+
return;
|
|
6532
|
+
this.readyState = 0;
|
|
6533
|
+
this.emit('error', { type: 'error', error });
|
|
6534
|
+
if (this.readyState == 2)
|
|
6535
|
+
return;
|
|
6536
|
+
this.timer = setTimeout(() => this.connect(), this.meta.retry);
|
|
6537
|
+
}
|
|
6538
|
+
/** a failed connection: error, CLOSED */
|
|
6539
|
+
fail(error) {
|
|
6540
|
+
this.readyState = 2;
|
|
6541
|
+
this.emit('error', { type: 'error', error });
|
|
6542
|
+
}
|
|
6543
|
+
/** the state's name (debugging) */
|
|
6544
|
+
get state() { return NAMES[this.readyState]; }
|
|
6545
|
+
}
|
|
6546
|
+
FetchEventSource.CONNECTING = 0;
|
|
6547
|
+
FetchEventSource.OPEN = 1;
|
|
6548
|
+
FetchEventSource.CLOSED = 2;
|
|
6549
|
+
|
|
6086
6550
|
/*
|
|
6087
6551
|
* makeSocketDriver(options?) (PLAN-3 §1.3): WebSocket and server-sent events, declared per
|
|
6088
6552
|
* component instance and answered with reply actions (./replies.ts). Docs on the declarations
|
|
@@ -6091,7 +6555,7 @@ function queryCache(o = {}) {
|
|
|
6091
6555
|
* - Sink values:
|
|
6092
6556
|
* - `{ connections: { [name]: spec | falsy } }`: the sender's whole set of connections. Diffed
|
|
6093
6557
|
* per (sender, name): a new name opens, a removed or falsy one closes, a changed URL /
|
|
6094
|
-
* protocols / withCredentials / share closes and reopens, anything else (action names,
|
|
6558
|
+
* protocols / withCredentials / headers / share closes and reopens, anything else (action names,
|
|
6095
6559
|
* reconnect) only rebinds.
|
|
6096
6560
|
* - `{ to: name, json? | text? | binary? }`: send on the sender's connection `name` (json is
|
|
6097
6561
|
* stringified). Queued while (re)connecting, bounded (`queueLimit`, oldest dropped). A send
|
|
@@ -6116,6 +6580,10 @@ function queryCache(o = {}) {
|
|
|
6116
6580
|
* - A disposed sender's connections close (and their retries stop); app dispose closes all.
|
|
6117
6581
|
* - No WebSocket / EventSource (SSR): nothing opens, nothing is reported.
|
|
6118
6582
|
* - `WebSocket` / `EventSource` are read at connect time: options.WebSocket ?? globalThis.WebSocket.
|
|
6583
|
+
* - PLAN-11 E-1: an `sse` connection with `headers` (data: a token in it comes from state, and a
|
|
6584
|
+
* changed header reconnects) is read with FetchEventSource (fetch, `options.fetch` if given),
|
|
6585
|
+
* since EventSource can't send headers. An `options.EventSource` (a polyfill, the test fake)
|
|
6586
|
+
* still wins and gets the headers in its init.
|
|
6119
6587
|
*/
|
|
6120
6588
|
const SCOPE$1 = '__sygnalScope';
|
|
6121
6589
|
const scopeOf = (v) => v[SCOPE$1] || [];
|
|
@@ -6215,12 +6683,13 @@ function makeSocketDriver(options = {}) {
|
|
|
6215
6683
|
};
|
|
6216
6684
|
const connect = (t) => {
|
|
6217
6685
|
t.timer = 0;
|
|
6218
|
-
const
|
|
6686
|
+
const hdr = t.sse && t.spec.headers && typeof t.spec.headers == 'object' ? t.spec.headers : undefined;
|
|
6687
|
+
const C = t.sse ? options.EventSource ?? (hdr && typeof g.fetch == 'function' ? FetchEventSource : g.EventSource) : options.WebSocket ?? g.WebSocket;
|
|
6219
6688
|
if (typeof C != 'function')
|
|
6220
6689
|
return;
|
|
6221
6690
|
let s;
|
|
6222
6691
|
try {
|
|
6223
|
-
s = t.sse ? new C(t.url, { withCredentials: !!t.spec.withCredentials }) : new C(t.url, t.spec.protocols);
|
|
6692
|
+
s = t.sse ? new C(t.url, { withCredentials: !!t.spec.withCredentials, ...(hdr && { headers: hdr, fetch: options.fetch }) }) : new C(t.url, t.spec.protocols);
|
|
6224
6693
|
}
|
|
6225
6694
|
catch (error) {
|
|
6226
6695
|
// a URL the constructor rejects never opens: no retry
|
|
@@ -6324,7 +6793,7 @@ function makeSocketDriver(options = {}) {
|
|
|
6324
6793
|
const sse = typeof spec.sse == 'string';
|
|
6325
6794
|
const url = resolve(sse ? spec.sse : spec.socket, sse);
|
|
6326
6795
|
// the connection's identity: a change reconnects, anything else only rebinds
|
|
6327
|
-
const key = (spec.share === false ? 'u' : '') + (sse ? 'e' + !!spec.withCredentials : 's' + JSON.stringify(spec.protocols)) + url;
|
|
6796
|
+
const key = (spec.share === false ? 'u' : '') + (sse ? 'e' + !!spec.withCredentials + (spec.headers ? JSON.stringify(spec.headers) : '') : 's' + JSON.stringify(spec.protocols)) + url;
|
|
6328
6797
|
if (old && old.key === key) {
|
|
6329
6798
|
old.spec = spec;
|
|
6330
6799
|
if (old.t)
|
|
@@ -6401,7 +6870,7 @@ const dec = (s) => { try {
|
|
|
6401
6870
|
catch (_) {
|
|
6402
6871
|
return s;
|
|
6403
6872
|
} };
|
|
6404
|
-
const g$
|
|
6873
|
+
const g$5 = globalThis;
|
|
6405
6874
|
const PARAM$1 = /:(\w+)/g;
|
|
6406
6875
|
// G-571: synchronous navigations (redirects) before a microtask, after which each waits a task
|
|
6407
6876
|
const HOPS = 32;
|
|
@@ -6409,7 +6878,7 @@ const HOPS = 32;
|
|
|
6409
6878
|
const paramsOf$2 = (pat) => (pat.match(PARAM$1) || []).map(p => p.slice(1));
|
|
6410
6879
|
// the core's coded error() (published on the diagnostics bridge): no import, so the router
|
|
6411
6880
|
// costs nothing more standalone; prints in production too (like SYG611)
|
|
6412
|
-
const bad$1 = (v, msg, fix) => (g$
|
|
6881
|
+
const bad$1 = (v, msg, fix) => (g$5.__SYGNAL_DIAGNOSTICS__?.error || console.error)('SYG620', v.__emitterName, `router: ${msg}; not navigated`, fix, v);
|
|
6413
6882
|
function makeRouter(options = {}) {
|
|
6414
6883
|
const { mode, prefetch, navigate } = options, routes = options.routes || {};
|
|
6415
6884
|
const base = (options.base || '').replace(/\/$/, '');
|
|
@@ -6418,7 +6887,7 @@ function makeRouter(options = {}) {
|
|
|
6418
6887
|
const doScroll = options.scroll ?? !navigate;
|
|
6419
6888
|
const href = (name, params = {}, query, hash) => {
|
|
6420
6889
|
// dev entry only (SYG130: unknown name, missing or extra params)
|
|
6421
|
-
g$
|
|
6890
|
+
g$5.__SYGNAL_DIAGNOSTICS__?.routerHref?.(routes, name, params);
|
|
6422
6891
|
const pat = routes[name];
|
|
6423
6892
|
const path = pat == null || pat == '*' ? '/' : pat.replace(PARAM$1, (_, k) => enc(params[k] ?? ''));
|
|
6424
6893
|
const q = query && new URLSearchParams(Object.entries(query).filter(([, v]) => v != null)) + '';
|
|
@@ -6528,7 +6997,7 @@ function makeRouter(options = {}) {
|
|
|
6528
6997
|
if (!hops++)
|
|
6529
6998
|
queueMicrotask(() => { hops = 0; });
|
|
6530
6999
|
if (hops > HOPS) {
|
|
6531
|
-
if (g$
|
|
7000
|
+
if (g$5.__SYGNAL_DIAGNOSTICS__?.hooks?.on?.() && !warned++)
|
|
6532
7001
|
console.warn(`[Sygnal] router: more than ${HOPS} navigations in one flush (a redirect loop?); each next one waits a task (reported once)`, r.path);
|
|
6533
7002
|
setTimeout(send);
|
|
6534
7003
|
}
|
|
@@ -8070,7 +8539,7 @@ function makeHeadDriver(options = {}) {
|
|
|
8070
8539
|
* - An invalid spec (non-positive `every`, negative `after`, no action) doesn't start; the dev
|
|
8071
8540
|
* entry reports SYG422 through the `timerSpec` diagnostics hook.
|
|
8072
8541
|
*/
|
|
8073
|
-
const g$
|
|
8542
|
+
const g$4 = globalThis;
|
|
8074
8543
|
const now$1 = () => Date.now();
|
|
8075
8544
|
const valid = (s) => s.frame
|
|
8076
8545
|
? typeof s.frame == 'string'
|
|
@@ -8080,7 +8549,7 @@ const valid = (s) => s.frame
|
|
|
8080
8549
|
// changes) the timer clears the pending one.
|
|
8081
8550
|
const start$1 = (s, send, e) => {
|
|
8082
8551
|
let id, n = 0, last;
|
|
8083
|
-
const t0 = now$1(), every = s.every, raf = s.frame && g$
|
|
8552
|
+
const t0 = now$1(), every = s.every, raf = s.frame && g$4.requestAnimationFrame;
|
|
8084
8553
|
const later = (ms) => id = raf ? requestAnimationFrame(tick) : setTimeout(tick, ms);
|
|
8085
8554
|
// a frame's `ts` is requestAnimationFrame's timestamp (the fallback passes none: the clock)
|
|
8086
8555
|
const tick = (ts = now$1()) => {
|
|
@@ -8125,7 +8594,7 @@ const timerDriver = (runners) => (sink$) => {
|
|
|
8125
8594
|
if (e.ok = valid(s))
|
|
8126
8595
|
e.x = start$1(s, (type, data) => reply(id, type, data), e);
|
|
8127
8596
|
else
|
|
8128
|
-
g$
|
|
8597
|
+
g$4.__SYGNAL_DIAGNOSTICS__?.timerSpec?.(n, s, c);
|
|
8129
8598
|
}
|
|
8130
8599
|
if (!d)
|
|
8131
8600
|
runners.delete(id);
|
|
@@ -8183,10 +8652,10 @@ const makeTimerDriver = () => timerDriver(new Map());
|
|
|
8183
8652
|
* an intersection / resize declaration with nothing to observe (no DOM source bound). A
|
|
8184
8653
|
* component declaring `browser` with no driver is SYG643, as timers.
|
|
8185
8654
|
*/
|
|
8186
|
-
const g$
|
|
8655
|
+
const g$3 = globalThis;
|
|
8187
8656
|
const noop = () => { };
|
|
8188
8657
|
const KINDS = ['intersection', 'resize', 'media', 'storage', 'visibility', 'online', 'geolocation'];
|
|
8189
|
-
const diag = (...a) => g$
|
|
8658
|
+
const diag = (...a) => g$3.__SYGNAL_DIAGNOSTICS__?.browserSource?.(...a);
|
|
8190
8659
|
// the driver names in use (the definition hook looks each up in a declaring instance's sources)
|
|
8191
8660
|
const names$1 = new Set();
|
|
8192
8661
|
let hooked;
|
|
@@ -8272,16 +8741,16 @@ const track = (c, kind, target, Obs, opts, data) => {
|
|
|
8272
8741
|
};
|
|
8273
8742
|
/** Element visibility: `{ intersection: '.cover' | true, action, threshold?, rootMargin? }`, data `{ visible, ratio, index, dataset }` */
|
|
8274
8743
|
const intersectionSource = { d: {
|
|
8275
|
-
intersection: (s, c) => track(c, 'i', s.intersection, g$
|
|
8744
|
+
intersection: (s, c) => track(c, 'i', s.intersection, g$3.IntersectionObserver, { threshold: s.threshold, rootMargin: s.rootMargin }, (e) => ({ visible: e.isIntersecting, ratio: e.intersectionRatio })),
|
|
8276
8745
|
} };
|
|
8277
8746
|
/** Element size: `{ resize: '.chart' | true, action }`, data `{ width, height, index, dataset }` (the content box) */
|
|
8278
8747
|
const resizeSource = { d: {
|
|
8279
|
-
resize: (s, c) => track(c, 'r', s.resize, g$
|
|
8748
|
+
resize: (s, c) => track(c, 'r', s.resize, g$3.ResizeObserver, undefined, (e) => ({ width: e.contentRect.width, height: e.contentRect.height })),
|
|
8280
8749
|
} };
|
|
8281
8750
|
/** A media query: `{ media: '(prefers-color-scheme: dark)', action }`, data `{ matches, media }` */
|
|
8282
8751
|
const mediaSource = { d: {
|
|
8283
8752
|
media: (s, c) => {
|
|
8284
|
-
const m = g$
|
|
8753
|
+
const m = g$3.matchMedia?.(s.media);
|
|
8285
8754
|
if (!m)
|
|
8286
8755
|
return;
|
|
8287
8756
|
const f = () => c.send({ matches: m.matches, media: s.media });
|
|
@@ -8290,7 +8759,7 @@ const mediaSource = { d: {
|
|
|
8290
8759
|
return () => m.removeEventListener('change', f);
|
|
8291
8760
|
},
|
|
8292
8761
|
} };
|
|
8293
|
-
const area = (s) => g$
|
|
8762
|
+
const area = (s) => g$3[s.area == 'session' ? 'sessionStorage' : 'localStorage'];
|
|
8294
8763
|
const failed$1 = (e) => ({ name: e?.name, message: e?.message });
|
|
8295
8764
|
/**
|
|
8296
8765
|
* A storage key (read and observed, other tabs' writes included): `{ storage: 'theme', action,
|
|
@@ -8319,8 +8788,8 @@ const storageSource = {
|
|
|
8319
8788
|
};
|
|
8320
8789
|
f();
|
|
8321
8790
|
watching.add(f);
|
|
8322
|
-
g$
|
|
8323
|
-
return () => { watching.delete(f); g$
|
|
8791
|
+
g$3.addEventListener?.('storage', f);
|
|
8792
|
+
return () => { watching.delete(f); g$3.removeEventListener?.('storage', f); };
|
|
8324
8793
|
} },
|
|
8325
8794
|
c: (() => {
|
|
8326
8795
|
const write = (v, value, ok, fail) => {
|
|
@@ -8347,7 +8816,7 @@ const storageSource = {
|
|
|
8347
8816
|
/** The document's visibility: `{ visibility: true, action }`, data `{ visible }` */
|
|
8348
8817
|
const visibilitySource = { d: {
|
|
8349
8818
|
visibility: (_, c) => {
|
|
8350
|
-
const d = g$
|
|
8819
|
+
const d = g$3.document;
|
|
8351
8820
|
if (!d)
|
|
8352
8821
|
return;
|
|
8353
8822
|
const f = () => c.send({ visible: d.visibilityState != 'hidden' });
|
|
@@ -8359,13 +8828,13 @@ const visibilitySource = { d: {
|
|
|
8359
8828
|
/** The network: `{ online: true, action }`, data `{ online }` */
|
|
8360
8829
|
const onlineSource = { d: {
|
|
8361
8830
|
online: (_, c) => {
|
|
8362
|
-
if (!g$
|
|
8831
|
+
if (!g$3.navigator || !g$3.addEventListener)
|
|
8363
8832
|
return;
|
|
8364
|
-
const f = () => c.send({ online: g$
|
|
8833
|
+
const f = () => c.send({ online: g$3.navigator.onLine });
|
|
8365
8834
|
f();
|
|
8366
|
-
g$
|
|
8367
|
-
g$
|
|
8368
|
-
return () => { g$
|
|
8835
|
+
g$3.addEventListener('online', f);
|
|
8836
|
+
g$3.addEventListener('offline', f);
|
|
8837
|
+
return () => { g$3.removeEventListener('online', f); g$3.removeEventListener('offline', f); };
|
|
8369
8838
|
},
|
|
8370
8839
|
} };
|
|
8371
8840
|
/**
|
|
@@ -8375,7 +8844,7 @@ const onlineSource = { d: {
|
|
|
8375
8844
|
*/
|
|
8376
8845
|
const geolocationSource = { d: {
|
|
8377
8846
|
geolocation: (s, c) => {
|
|
8378
|
-
const geo = g$
|
|
8847
|
+
const geo = g$3.navigator?.geolocation;
|
|
8379
8848
|
if (!geo)
|
|
8380
8849
|
return c.fail({ code: 2, message: 'Geolocation is not available' });
|
|
8381
8850
|
const id = geo.watchPosition((p) => {
|
|
@@ -8391,11 +8860,11 @@ const geolocationSource = { d: {
|
|
|
8391
8860
|
*/
|
|
8392
8861
|
const clipboardSource = { c: {
|
|
8393
8862
|
copy: (v, ok, fail) => {
|
|
8394
|
-
const text = '' + v.copy, cb = g$
|
|
8863
|
+
const text = '' + v.copy, cb = g$3.navigator?.clipboard;
|
|
8395
8864
|
cb ? cb.writeText(text).then(() => ok({ text }), (e) => fail(failed$1(e))) : fail({ name: 'NotSupportedError', message: 'Clipboard is not available' });
|
|
8396
8865
|
},
|
|
8397
8866
|
paste: (_, ok, fail) => {
|
|
8398
|
-
const cb = g$
|
|
8867
|
+
const cb = g$3.navigator?.clipboard;
|
|
8399
8868
|
cb ? cb.readText().then((text) => ok({ text }), (e) => fail(failed$1(e))) : fail({ name: 'NotSupportedError', message: 'Clipboard is not available' });
|
|
8400
8869
|
},
|
|
8401
8870
|
} };
|
|
@@ -9100,6 +9569,9 @@ class App {
|
|
|
9100
9569
|
}
|
|
9101
9570
|
/** STATE.stream: the instances whose state changed emit it */
|
|
9102
9571
|
notify() {
|
|
9572
|
+
if (this.nv === this.ver)
|
|
9573
|
+
return;
|
|
9574
|
+
this.nv = this.ver;
|
|
9103
9575
|
for (const i of this.watchers)
|
|
9104
9576
|
i.notify();
|
|
9105
9577
|
}
|
|
@@ -9227,7 +9699,7 @@ class App {
|
|
|
9227
9699
|
this.ran = false;
|
|
9228
9700
|
this.last = v;
|
|
9229
9701
|
this.hooks.onPatch?.(v);
|
|
9230
|
-
this.vdomL?.next(v);
|
|
9702
|
+
this.vdomL?.next(v ?? null);
|
|
9231
9703
|
}
|
|
9232
9704
|
if (this.dq.length)
|
|
9233
9705
|
this.stopLater();
|
|
@@ -9916,7 +10388,7 @@ const W$1 = '__sw';
|
|
|
9916
10388
|
const HOST = /^(id|class(Name)?|style|title|name|placeholder|role|tab[iI]ndex|hidden|lang|dir|attrs)$|^(aria|data)-/;
|
|
9917
10389
|
/** the dev entry's reporter (checks/widgets.ts); returns the diagnostic when one was reported
|
|
9918
10390
|
* (r: G-580, a 661 the widget reported through error(e), not a throwing update) */
|
|
9919
|
-
const dev$
|
|
10391
|
+
const dev$7 = (code, w, o, x, r) => globalThis.__SYGNAL_DIAGNOSTICS__?.widget?.(code, w, o, x, r);
|
|
9920
10392
|
let ids$1 = 0;
|
|
9921
10393
|
// shallow; G-362: an object host prop (style, attrs, a class map) compares one level deep, so a
|
|
9922
10394
|
// new object with the same entries is no change (snabbdom patches the host from it anyway)
|
|
@@ -9929,16 +10401,16 @@ const same$2 = (a, b, d) => {
|
|
|
9929
10401
|
// reported through error(e)
|
|
9930
10402
|
function fail$1(o, w, k, code, e, r) {
|
|
9931
10403
|
e = e instanceof Error ? e : Error(e);
|
|
9932
|
-
dev$
|
|
10404
|
+
dev$7(code, w, o, e, r) || console.error(`[Sygnal SYG${code}]`, e);
|
|
9933
10405
|
o.app.appError(o, e, 'widget');
|
|
9934
10406
|
if (code < 662 && !o.disposed)
|
|
9935
10407
|
(o.$wf || (o.$wf = new Map())).set(k, [e]), o.refresh();
|
|
9936
10408
|
}
|
|
9937
10409
|
function mount(v) {
|
|
9938
10410
|
const el = v.elm, { ww: w, wp: p, wo: o } = v.data;
|
|
9939
|
-
const d = (n, detail) => { dev$
|
|
10411
|
+
const d = (n, detail) => { dev$7(140, w, o, n); el.dispatchEvent(new CustomEvent(n, { detail, bubbles: true })); };
|
|
9940
10412
|
let i;
|
|
9941
|
-
dev$
|
|
10413
|
+
dev$7(144, w, o, el);
|
|
9942
10414
|
// G-409: mount's fourth parameter reports a later failure (an adapter's own redraw): SYG661,
|
|
9943
10415
|
// the owner's onError fallback in this instance's place, as for a throwing update (G-580: the
|
|
9944
10416
|
// dev text says error(e) reported it)
|
|
@@ -10058,7 +10530,7 @@ function defineWidget(def) {
|
|
|
10058
10530
|
},
|
|
10059
10531
|
$ssr: host,
|
|
10060
10532
|
// D190/D199: a widget tag is not a selector (SYG143 in dev): select its className or a control
|
|
10061
|
-
toString: () => (dev$
|
|
10533
|
+
toString: () => (dev$7(143, w), ':not(*)'),
|
|
10062
10534
|
});
|
|
10063
10535
|
}
|
|
10064
10536
|
|
|
@@ -10107,6 +10579,30 @@ function defineWidget(def) {
|
|
|
10107
10579
|
* can still be assigned), which reads the host's current `uses`.
|
|
10108
10580
|
* - Limitations: a host intent must return an object (not one stream).
|
|
10109
10581
|
*/
|
|
10582
|
+
/** A behavior output's slice from before the action (outputs get the updated one): for a transition shared with STATE */
|
|
10583
|
+
const was = (s, p, k) => p?.previousState ? p.previousState[k] : s;
|
|
10584
|
+
/**
|
|
10585
|
+
* A behavior model whose outputs compute from the state before the action (its slice and
|
|
10586
|
+
* `props.state`), for behaviors whose STATE and outputs share one transition; STATE / HOST as is
|
|
10587
|
+
*/
|
|
10588
|
+
const fromPrevious = (model) => {
|
|
10589
|
+
const out = {};
|
|
10590
|
+
for (const a in model) {
|
|
10591
|
+
const e = model[a];
|
|
10592
|
+
if (!e || typeof e != 'object') {
|
|
10593
|
+
out[a] = e;
|
|
10594
|
+
continue;
|
|
10595
|
+
}
|
|
10596
|
+
const m = {};
|
|
10597
|
+
for (const s in e) {
|
|
10598
|
+
const f = e[s];
|
|
10599
|
+
m[s] = s == 'STATE' || s == 'HOST' || typeof f != 'function' ? f
|
|
10600
|
+
: (x, d, n, p, o, k) => f(was(x, p, k), d, n, p?.previousState ? { ...p, state: p.previousState } : p, o, k);
|
|
10601
|
+
}
|
|
10602
|
+
out[a] = m;
|
|
10603
|
+
}
|
|
10604
|
+
return out;
|
|
10605
|
+
};
|
|
10110
10606
|
// a model entry as { sink: fn }: a constant is sent as is, `true` sends the action's data, an
|
|
10111
10607
|
// EFFECT constant does nothing (as the core treats them)
|
|
10112
10608
|
const sinksOf = (e, S) => {
|
|
@@ -10289,6 +10785,8 @@ const pager = /*#__PURE__*/ defineBehavior({
|
|
|
10289
10785
|
});
|
|
10290
10786
|
|
|
10291
10787
|
const isRow = (r) => r?.id != null;
|
|
10788
|
+
/** PLAN-11 F-2: a File / Blob is a field value (a leaf), not an object of fields */
|
|
10789
|
+
const isFile = (v) => typeof Blob != 'undefined' && v instanceof Blob;
|
|
10292
10790
|
// the key of segment `s` in `v`: an array's row with that id, else the index unless the element
|
|
10293
10791
|
// there is a row (G-380: a row is named by its id only; -1, no element), an object's key
|
|
10294
10792
|
const key = (v, s) => {
|
|
@@ -10335,7 +10833,7 @@ const setField = (values, name, value) => {
|
|
|
10335
10833
|
* array-level error such as "at least one address"); a row's `id` is not a field
|
|
10336
10834
|
*/
|
|
10337
10835
|
const fieldNames = (v, at = '', row) => {
|
|
10338
|
-
if (!v || typeof v != 'object')
|
|
10836
|
+
if (!v || typeof v != 'object' || isFile(v))
|
|
10339
10837
|
return at ? [at] : [];
|
|
10340
10838
|
const a = Array.isArray(v), out = a ? [at] : [];
|
|
10341
10839
|
for (const k in v) {
|
|
@@ -10416,7 +10914,12 @@ const focusInvalid = (names, within) => {
|
|
|
10416
10914
|
* field.
|
|
10417
10915
|
*
|
|
10418
10916
|
* Options: `values` (the start values; D239: or a function of the host's state, called when the
|
|
10419
|
-
* form starts), `submit` (host action), `
|
|
10917
|
+
* form starts), `submit` (host action), `request` (PLAN-11 F-3: the form sends the valid submit
|
|
10918
|
+
* itself on the `http` sink: a request, or `(data, hostState) => request`, completed with the
|
|
10919
|
+
* body (`multipart: data` when a top-level value is a File / Blob or an array of them, else
|
|
10920
|
+
* `json: data`, unless it sets json / multipart / body), `progress: '<key>.PROGRESS'` (unless set)
|
|
10921
|
+
* and its own `ok: '<key>.DONE'`, `error: '<key>.ERRORS'`; with `submit` too, that host action is
|
|
10922
|
+
* still dispatched (for state), and should send no request), `check` (async per-field checks through a driver: a
|
|
10420
10923
|
* request with a reply action, `latest`), `show` ('blur' default: a schema error shows once its
|
|
10421
10924
|
* field was blurred; 'input'; 'submit'), `form` (selector), `http` (the driver sink of the
|
|
10422
10925
|
* checks, 'HTTP'), `resetOnShow` (D239), `tool` (PLAN-6 A-3, experimental: `formTool({ … })` from
|
|
@@ -10435,6 +10938,8 @@ const focusInvalid = (names, within) => {
|
|
|
10435
10938
|
* before that (a child's first render) the fields come from calling it with a blank state.
|
|
10436
10939
|
*
|
|
10437
10940
|
* Slice (state.form): values, initial, errors (all current schema errors by name), touched,
|
|
10941
|
+
* progress (PLAN-11 F-2: the submit's last progress step or null), resets (counts RESET and
|
|
10942
|
+
* resetOnShow starts: key a file input with it, since a file input can't be cleared by state),
|
|
10438
10943
|
* server (server errors), remote (async check results: '' passed), pending (name -> value being
|
|
10439
10944
|
* checked), submitting, submitted, submitCount, queued (a submit waits for a check or an async
|
|
10440
10945
|
* schema), validating (an async schema runs), validated (the schema has answered since the start
|
|
@@ -10444,11 +10949,14 @@ const focusInvalid = (names, within) => {
|
|
|
10444
10949
|
*
|
|
10445
10950
|
* Actions: form.CHANGE ({ name, value, item? }: G-376: a checkbox gives `checked`, and `item`, its
|
|
10446
10951
|
* value, which toggles membership when the field is an array; <select multiple> an array of the
|
|
10447
|
-
* selected values;
|
|
10952
|
+
* selected values; PLAN-11 F-2: a file input its File, `multiple` an array of them, none null),
|
|
10953
|
+
* form.SET ({ values }: G-647, field name -> value, several
|
|
10448
10954
|
* fields changed at once as if typed: one validation, `touched` as for CHANGE, unknown names
|
|
10449
10955
|
* skipped; e.g. an AI fill), form.BLUR (name), form.SUBMIT, form.ADD ({ field,
|
|
10450
10956
|
* value }), form.REMOVE ({ field, id }), form.ERRORS (an error reply or a map: server errors),
|
|
10451
|
-
* form.DONE (saved), form.RESET (values?)
|
|
10957
|
+
* form.DONE (saved), form.RESET (values?), form.PROGRESS (PLAN-11 F-2: a progress step of the
|
|
10958
|
+
* submit request, `progress: 'form.PROGRESS'`: kept as `progress` while submitting, cleared by
|
|
10959
|
+
* DONE / ERRORS / RESET); internal: form.RESULT (an async schema's result),
|
|
10452
10960
|
* form.CHECKED_<name> (a check's reply; a failed check passes: the server checks on submit),
|
|
10453
10961
|
* form.VALIDATE (the first validation, when the host starts: G-375; `valid` is false until the
|
|
10454
10962
|
* schema answered; with resetOnShow also each time the form element appears, D239).
|
|
@@ -10472,8 +10980,14 @@ const focusInvalid = (names, within) => {
|
|
|
10472
10980
|
* fields. The host checks (SYG234 submit, SYG235 check names, SYG236 row ids)
|
|
10473
10981
|
* run when the host is created.
|
|
10474
10982
|
*/
|
|
10475
|
-
|
|
10983
|
+
// PLAN-11 F-3: a submit's data goes as multipart when a top-level value is a file (or files)
|
|
10984
|
+
const hasFile = (d) => !!d && typeof d == 'object' && Object.values(d).some(x => isFile(x) || Array.isArray(x) && x.some(isFile));
|
|
10985
|
+
const dev$6 = (...a) => globalThis.__SYGNAL_DIAGNOSTICS__?.form?.(...a);
|
|
10476
10986
|
const keys = (o) => Object.keys(o).filter(k => o[k]);
|
|
10987
|
+
// PLAN-11 F-2: values compared as JSON, a File by identity-ish fields (JSON gives every File {})
|
|
10988
|
+
const fileIds = new WeakMap();
|
|
10989
|
+
let fileSeq = 0;
|
|
10990
|
+
const json = (v) => JSON.stringify(v, (_k, x) => isFile(x) ? `\0file:${fileIds.get(x) ?? (fileIds.set(x, ++fileSeq), fileSeq)}` : x);
|
|
10477
10991
|
const drop = (o, p) => {
|
|
10478
10992
|
const r = {};
|
|
10479
10993
|
for (const k in o)
|
|
@@ -10482,8 +10996,8 @@ const drop = (o, p) => {
|
|
|
10482
10996
|
return r;
|
|
10483
10997
|
};
|
|
10484
10998
|
const form = (schema, o = {}) => {
|
|
10485
|
-
isStandardSchema(schema) || dev$
|
|
10486
|
-
const { values: vo = {}, submit, check: checks = {}, show = 'blur', http = 'HTTP', form: sel = 'form', resetOnShow: again } = o;
|
|
10999
|
+
isStandardSchema(schema) || dev$6(231, schema);
|
|
11000
|
+
const { values: vo = {}, submit, request: rq, check: checks = {}, show = 'blur', http = 'HTTP', form: sel = 'form', resetOnShow: again } = o;
|
|
10487
11001
|
// D239: `values` can be a function of the host's state, called when the form starts (and each
|
|
10488
11002
|
// show, resetOnShow). Before that (the host's first render, before its first action) the
|
|
10489
11003
|
// fields come from calling it with a blank state: every read gives '' in the result
|
|
@@ -10515,7 +11029,7 @@ const form = (schema, o = {}) => {
|
|
|
10515
11029
|
// from a named button)
|
|
10516
11030
|
const known = (s, n, quiet) => {
|
|
10517
11031
|
const k = n && hasField(s.values, n);
|
|
10518
|
-
n && !k && !quiet && dev$
|
|
11032
|
+
n && !k && !quiet && dev$6(230, n, s.values);
|
|
10519
11033
|
return k;
|
|
10520
11034
|
};
|
|
10521
11035
|
// new values: errors now (sync schema), or after the schema's Promise (RESULT)
|
|
@@ -10530,7 +11044,7 @@ const form = (schema, o = {}) => {
|
|
|
10530
11044
|
});
|
|
10531
11045
|
const ask = (s, f, k, queued) => {
|
|
10532
11046
|
const x = getField(s.values, f), q = checks[f].request(x), ok = `${k}.CHECKED_${f}`;
|
|
10533
|
-
dev$
|
|
11047
|
+
dev$6(235, f, q);
|
|
10534
11048
|
return { s: { ...s, queued, pending: { ...s.pending, [f]: x } }, req: { ...q, ok, error: ok, latest: true } };
|
|
10535
11049
|
};
|
|
10536
11050
|
// a submit with s's errors: blocked (focus the first), a check to run or to wait for, or sent
|
|
@@ -10541,10 +11055,11 @@ const form = (schema, o = {}) => {
|
|
|
10541
11055
|
: Object.keys(s.pending).length ? { s: { ...s, queued: true } }
|
|
10542
11056
|
: { s: req ? { ...s, queued: false, submitting: true } : done({ ...s, queued: false }), send: 1 };
|
|
10543
11057
|
};
|
|
10544
|
-
const base = (vals) => ({ initial: vals, values: vals, touched: {}, server: {}, remote: {}, pending: {},
|
|
10545
|
-
submitting: false, submitted: false, submitCount: 0, queued: false, errors: {}, validated: false });
|
|
10546
|
-
|
|
10547
|
-
const
|
|
11058
|
+
const base = (vals, resets = 0) => ({ initial: vals, values: vals, touched: {}, server: {}, remote: {}, pending: {},
|
|
11059
|
+
submitting: false, submitted: false, submitCount: 0, queued: false, errors: {}, validated: false, progress: null, resets });
|
|
11060
|
+
// a start over (RESET, resetOnShow): `resets` counts them, so a file input keyed by it is made anew
|
|
11061
|
+
const fresh = (vals, s) => edit(base(vals, s ? (s.resets || 0) + 1 : 0), vals);
|
|
11062
|
+
const done = (s) => ({ ...s, submitting: false, submitted: true, initial: s.values, server: {}, touched: {}, progress: null });
|
|
10548
11063
|
const group = (cur, d) => {
|
|
10549
11064
|
if (!Array.isArray(cur) || !('item' in d))
|
|
10550
11065
|
return d.value;
|
|
@@ -10553,10 +11068,10 @@ const form = (schema, o = {}) => {
|
|
|
10553
11068
|
};
|
|
10554
11069
|
// fields changed as typed ones ([name, value] pairs; unknown names skipped, SYG230): their
|
|
10555
11070
|
// server errors, check results and running checks go, `touched` as show 'input' says; one validation
|
|
10556
|
-
const put = (s, d) => {
|
|
11071
|
+
const put = (s, d, quiet) => {
|
|
10557
11072
|
let vals = s.values, server = drop(s.server, ''), remote = s.remote, pending = s.pending, touched = s.touched;
|
|
10558
11073
|
for (const [n, y] of d)
|
|
10559
|
-
if (known(s, n)) {
|
|
11074
|
+
if (known(s, n, quiet)) {
|
|
10560
11075
|
vals = setField(vals, n, y), server = drop(server, n), remote = drop(remote, n), pending = drop(pending, n);
|
|
10561
11076
|
if (show == 'input')
|
|
10562
11077
|
touched = { ...touched, [n]: true };
|
|
@@ -10566,7 +11081,9 @@ const form = (schema, o = {}) => {
|
|
|
10566
11081
|
const steps = {
|
|
10567
11082
|
// G-376: a checkbox (`item`: its value) on an array field is one of a group: checked adds
|
|
10568
11083
|
// its value, unchecked removes it
|
|
10569
|
-
|
|
11084
|
+
// PLAN-11 F-2: a file input whose name isn't in values is left alone, quietly (an app that reads
|
|
11085
|
+
// its files in its own intent, as before 6.7)
|
|
11086
|
+
CHANGE: (s, d) => put(s, d?.name ? [[d.name, group(getField(s.values, d.name), d)]] : [], d?.file),
|
|
10570
11087
|
BLUR: (s, n, k) => {
|
|
10571
11088
|
if (!known(s, n, 1))
|
|
10572
11089
|
return null;
|
|
@@ -10575,7 +11092,7 @@ const form = (schema, o = {}) => {
|
|
|
10575
11092
|
},
|
|
10576
11093
|
SUBMIT: (s, _, k) => {
|
|
10577
11094
|
if (s.submitting || s.queued)
|
|
10578
|
-
return void dev$
|
|
11095
|
+
return void dev$6(232, s);
|
|
10579
11096
|
const n = { ...s, submitCount: s.submitCount + 1 }, r = v(s.values);
|
|
10580
11097
|
// G-371: a submit waiting for an async schema is queued (a second one is dropped)
|
|
10581
11098
|
return r.then ? { s: { ...n, validating: true, queued: true }, wait: { values: s.values, submit: 1 } } : attempt({ ...n, errors: r.errors }, k);
|
|
@@ -10599,16 +11116,20 @@ const form = (schema, o = {}) => {
|
|
|
10599
11116
|
SET: (s, d) => put(s, Object.entries(d?.values || {})),
|
|
10600
11117
|
ERRORS: (s, d) => {
|
|
10601
11118
|
const e = replyErrors(d, s.values);
|
|
10602
|
-
return { s: { ...s, server: e, submitting: false, queued: false }, focus: focusInvalid(e, sel) };
|
|
11119
|
+
return { s: { ...s, server: e, submitting: false, queued: false, progress: null }, focus: focusInvalid(e, sel) };
|
|
10603
11120
|
},
|
|
10604
11121
|
DONE: (s) => ({ s: done(s) }),
|
|
10605
|
-
|
|
11122
|
+
// PLAN-11 F-2: a progress step of the submit's request (`progress: 'form.PROGRESS'`), while it is pending
|
|
11123
|
+
PROGRESS: (s, d) => s.submitting && d && typeof d == 'object'
|
|
11124
|
+
? { s: { ...s, progress: { phase: d.phase, loaded: d.loaded, total: d.total ?? null, fraction: d.fraction ?? null } } } : null,
|
|
11125
|
+
// PLAN-11: only a plain object is new values (a click event from `DOM.click('.reset')` is not)
|
|
11126
|
+
RESET: (s, d) => fresh(d && typeof d == 'object' && [Object.prototype, null].includes(Object.getPrototypeOf(d)) ? d : s.initial, s),
|
|
10606
11127
|
// G-375: the first validation, when the host starts (form() doesn't validate at module load).
|
|
10607
11128
|
// G-575: a slice kept in parent state from a host that unmounted mid-submit starts unstuck.
|
|
10608
11129
|
// D239: with resetOnShow (also each time the form element appears again), or a `values`
|
|
10609
11130
|
// function not called yet, the slice starts over from the start values (h: the host state)
|
|
10610
|
-
VALIDATE: (s, _, _k, h) => again || fn && s.values === values ? fresh(fn ? vo(h) : vo)
|
|
10611
|
-
: edit({ ...s, submitting: false, queued: false, pending: {} }, s.values),
|
|
11131
|
+
VALIDATE: (s, _, _k, h) => again || fn && s.values === values ? fresh(fn ? vo(h) : vo, again && s)
|
|
11132
|
+
: edit({ ...s, submitting: false, queued: false, pending: {}, progress: null }, s.values),
|
|
10612
11133
|
};
|
|
10613
11134
|
// a check's reply (ok and error: an error reply carries an Error and passes)
|
|
10614
11135
|
for (const f of checked)
|
|
@@ -10618,6 +11139,14 @@ const form = (schema, o = {}) => {
|
|
|
10618
11139
|
const n = { ...s, pending: drop(s.pending, f), remote: { ...s.remote, [f]: d?.error instanceof Error ? '' : checks[f].error?.(d) || '' } };
|
|
10619
11140
|
return s.queued ? attempt(n, k) : { s: n };
|
|
10620
11141
|
};
|
|
11142
|
+
// PLAN-11 F-3: the `request` option's request for a valid submit (data: the schema's output)
|
|
11143
|
+
const send = (vals, h, k) => {
|
|
11144
|
+
const data = v(vals).value, q = typeof rq == 'function' ? rq(data, h) : rq;
|
|
11145
|
+
if (!q || q === ABORT)
|
|
11146
|
+
return ABORT;
|
|
11147
|
+
const own = 'json' in q || 'multipart' in q || 'body' in q;
|
|
11148
|
+
return { ...(!own && (hasFile(data) ? { multipart: data } : { json: data })), progress: `${k}.PROGRESS`, ...q, ok: `${k}.DONE`, error: `${k}.ERRORS` };
|
|
11149
|
+
};
|
|
10621
11150
|
const model = {};
|
|
10622
11151
|
for (const a in steps) {
|
|
10623
11152
|
let last = [];
|
|
@@ -10627,19 +11156,28 @@ const form = (schema, o = {}) => {
|
|
|
10627
11156
|
// VALIDATE: on the host's state (HOST), for a `values` function; the slice's own steps on it
|
|
10628
11157
|
...a == 'VALIDATE' ? { HOST: (h, d, _n, _p, _o, k) => { const x = out(h[k], d, k, h); return x ? { ...h, [k]: x.s } : h; } }
|
|
10629
11158
|
: { STATE: (s, d, _n, _p, _o, k) => out(s, d, k)?.s || ABORT },
|
|
10630
|
-
|
|
11159
|
+
// outputs share STATE's transition: computed from the slice before the action
|
|
11160
|
+
EFFECT: (s, d, next, p, _o, k) => {
|
|
11161
|
+
s = was(s, p, k);
|
|
10631
11162
|
const x = out(s, d, k), w = x?.wait;
|
|
10632
11163
|
if (x?.send) {
|
|
10633
11164
|
const r = v(x.s.values).value;
|
|
10634
|
-
dev$
|
|
11165
|
+
dev$6(233, s.values, r);
|
|
10635
11166
|
// G-577: a submit named after one of the form's own actions goes to that one
|
|
10636
|
-
submit in steps && dev$
|
|
10637
|
-
|
|
11167
|
+
submit in steps && dev$6(234, submit, k, b);
|
|
11168
|
+
// PLAN-11 F-3: with `request`, `submit` is optional (dispatched for state when given)
|
|
11169
|
+
if (submit !== undefined || !rq)
|
|
11170
|
+
next(submit, r, 0);
|
|
10638
11171
|
}
|
|
10639
11172
|
w && Promise.resolve(v(w.values)).then(() => next('RESULT', w, 0));
|
|
10640
11173
|
},
|
|
10641
|
-
ELEMENT: (s, d, _n,
|
|
10642
|
-
|
|
11174
|
+
ELEMENT: (s, d, _n, p, _o, k) => out(was(s, p, k), d, k)?.focus || ABORT,
|
|
11175
|
+
// the checks' requests, and (PLAN-11 F-3) the `request` option's for a valid submit; the
|
|
11176
|
+
// step from the slice before the action (7.0: outputs get the updated one), the host state as committed
|
|
11177
|
+
...((checked.length || rq) && { [http]: (s, d, _n, p, _o, k) => {
|
|
11178
|
+
const x = out(was(s, p, k), d, k);
|
|
11179
|
+
return x?.req || (x?.send && rq ? send(x.s.values, p?.state, k) : ABORT);
|
|
11180
|
+
} }),
|
|
10643
11181
|
};
|
|
10644
11182
|
}
|
|
10645
11183
|
const b = defineBehavior({
|
|
@@ -10655,9 +11193,11 @@ const form = (schema, o = {}) => {
|
|
|
10655
11193
|
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),
|
|
10656
11194
|
// G-376: a checkbox (native, or a hyphenated tag with a boolean `checked`: a
|
|
10657
11195
|
// form-associated custom checkbox / switch) gives `checked` and its value as `item`; a
|
|
10658
|
-
// <select multiple> the selected values;
|
|
10659
|
-
|
|
10660
|
-
|
|
11196
|
+
// <select multiple> the selected values; PLAN-11 F-2: a file input its File (`multiple`:
|
|
11197
|
+
// an array of them; none: null); others give `value`
|
|
11198
|
+
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 }
|
|
11199
|
+
: t.type == 'checkbox' || /-/.test(t.tagName) && typeof t.checked == 'boolean' ? { name: t.name, value: t.checked, item: t.value }
|
|
11200
|
+
: { name: t.name, value: t.type == 'select-multiple' ? [...t.selectedOptions].map((o) => o.value) : t.value }),
|
|
10661
11201
|
BLUR: f.events('focusout').map((e) => e.target.name),
|
|
10662
11202
|
SUBMIT: f.events('submit', { preventDefault: true }),
|
|
10663
11203
|
};
|
|
@@ -10675,7 +11215,7 @@ const form = (schema, o = {}) => {
|
|
|
10675
11215
|
},
|
|
10676
11216
|
// G-382: an async schema re-validating keeps the last answer's validity (no flicker)
|
|
10677
11217
|
valid: (s) => s.validated && !keys(s.errors).length && !keys(s.remote).length,
|
|
10678
|
-
dirty: (s) =>
|
|
11218
|
+
dirty: (s) => json(s.values) != json(s.initial),
|
|
10679
11219
|
error: (s) => s.server[''] || (s.submitCount && s.errors['']) || '',
|
|
10680
11220
|
},
|
|
10681
11221
|
})({ ...o, values }), merge = b.merge;
|
|
@@ -10683,7 +11223,7 @@ const form = (schema, o = {}) => {
|
|
|
10683
11223
|
o.tool?.$?.(b, v);
|
|
10684
11224
|
b.merge = (c, k) => {
|
|
10685
11225
|
const e = c.model?.[submit];
|
|
10686
|
-
req = !!e && typeof e == 'object' && http in e;
|
|
11226
|
+
req = !!rq || !!e && typeof e == 'object' && http in e;
|
|
10687
11227
|
merge(c, k);
|
|
10688
11228
|
};
|
|
10689
11229
|
return b;
|
|
@@ -10832,7 +11372,7 @@ const selection = (options = {}) => {
|
|
|
10832
11372
|
* interaction, SYG147 a `from` key that isn't an array in the host state, SYG435 (3-H) a list
|
|
10833
11373
|
* shown in another order than its array's (a Collection's sort / filter).
|
|
10834
11374
|
*/
|
|
10835
|
-
const dev$
|
|
11375
|
+
const dev$5 = (...a) => globalThis.__SYGNAL_DIAGNOSTICS__?.sortable?.(...a);
|
|
10836
11376
|
const LIFT = /^( |Enter|Spacebar)$/;
|
|
10837
11377
|
const KEYS = /^(Arrow(Up|Down|Left|Right)|Home|End|Escape|Tab)$/;
|
|
10838
11378
|
const S$1 = String;
|
|
@@ -10946,7 +11486,7 @@ const sortable = (options = {}) => {
|
|
|
10946
11486
|
} } } } };
|
|
10947
11487
|
};
|
|
10948
11488
|
// dev: SYG435, the list shown in another order than the array's (G-449)
|
|
10949
|
-
const check = (st, seen, k) => seen && dev$
|
|
11489
|
+
const check = (st, seen, k) => seen && dev$5(435, seen, st, lists, idField, k);
|
|
10950
11490
|
// a press or key handler: drag state this instance didn't start (its token `n` isn't the
|
|
10951
11491
|
// event's: restored, synced) is dropped first (G-452)
|
|
10952
11492
|
const live = (h) => (st, d, next, _p, _o, k) => {
|
|
@@ -10987,7 +11527,7 @@ const sortable = (options = {}) => {
|
|
|
10987
11527
|
const gripOf = (e, self) => {
|
|
10988
11528
|
const t = e.target, r = e.ownerTarget || e.currentTarget;
|
|
10989
11529
|
if (!checked)
|
|
10990
|
-
checked = dev$
|
|
11530
|
+
checked = dev$5(146, r, item, handle, attr);
|
|
10991
11531
|
const g = !claimed.has(e) && t?.closest?.(grip);
|
|
10992
11532
|
if (!g || self && g !== t)
|
|
10993
11533
|
return null;
|
|
@@ -11001,14 +11541,14 @@ const sortable = (options = {}) => {
|
|
|
11001
11541
|
return null;
|
|
11002
11542
|
const id = idOf(it);
|
|
11003
11543
|
if (id == null)
|
|
11004
|
-
dev$
|
|
11544
|
+
dev$5(145, it, item, attr);
|
|
11005
11545
|
return id;
|
|
11006
11546
|
};
|
|
11007
11547
|
const keyed = (keys, kb) => (e) => keys.test(e.key) && (kb || e.key != 'Tab') && gripOf(e, true) != null;
|
|
11008
11548
|
const lift = keyed(LIFT), step = keyed(KEYS, 1);
|
|
11009
11549
|
// dev (SYG435): this host's own items' ids in the order they're shown (computed by the
|
|
11010
11550
|
// diagnostics entry only)
|
|
11011
|
-
const shown = (e) => dev$
|
|
11551
|
+
const shown = (e) => dev$5(435, () => {
|
|
11012
11552
|
const r = e.ownerTarget || e.currentTarget;
|
|
11013
11553
|
return [[...r.querySelectorAll(item)].filter((it) => own(it, r)), r];
|
|
11014
11554
|
}, key, lists, idOf);
|
|
@@ -11052,7 +11592,7 @@ const sortable = (options = {}) => {
|
|
|
11052
11592
|
// again by HMR, a second host of the slice) is reset, as at END
|
|
11053
11593
|
INIT: { HOST: (st, _d, _n, _p, _o, k) => {
|
|
11054
11594
|
for (const l of lists)
|
|
11055
|
-
Array.isArray(st?.[l]) || dev$
|
|
11595
|
+
Array.isArray(st?.[l]) || dev$5(147, l, st, k);
|
|
11056
11596
|
return st[k].press || st[k].dragging ? back(st, k) : ABORT;
|
|
11057
11597
|
} },
|
|
11058
11598
|
// unmounted mid-drag: the drag is cancelled (no DROPPED; a keyboard drag's item goes back
|
|
@@ -11464,14 +12004,14 @@ const undo = (options) => {
|
|
|
11464
12004
|
* too), once per kind (restore / save / clear; a save that works again re-arms it); the app
|
|
11465
12005
|
* continues on initialState / unsaved, and a failed save is retried at the next change.
|
|
11466
12006
|
*/
|
|
11467
|
-
const g$
|
|
12007
|
+
const g$2 = globalThis;
|
|
11468
12008
|
const setupPersist = (c, o) => {
|
|
11469
12009
|
const { key, pick, omit, version = 1, migrate, sync, debounceMs = 100 } = o, plain = o.format == 'plain', src = c.sources, m = src.__m;
|
|
11470
12010
|
const env = src.__storage, calc = c.calculated || {};
|
|
11471
12011
|
// hydrating: the option, else the integration's signal (__hydrate: Astro, Vike), else whether
|
|
11472
12012
|
// run()'s mount point starts with renderToString's markup (its root element is marked; a
|
|
11473
12013
|
// client-only app's loading placeholder isn't server markup)
|
|
11474
|
-
const hydrate = o.hydrate ?? src.__hydrate ?? !!(typeof m == 'string' ? g$
|
|
12014
|
+
const hydrate = o.hydrate ?? src.__hydrate ?? !!(typeof m == 'string' ? g$2.document?.querySelector(m) : m)?.firstElementChild?.hasAttribute('data-sygnal-ssr');
|
|
11475
12015
|
let S, t, last, raw, skip, off;
|
|
11476
12016
|
// (the docs link the message ends with explains the causes and fixes). Once per kind (restore,
|
|
11477
12017
|
// save, clear): a storage that throws on every write must not warn on every change; a save that
|
|
@@ -11544,7 +12084,7 @@ const setupPersist = (c, o) => {
|
|
|
11544
12084
|
}
|
|
11545
12085
|
c.model = model;
|
|
11546
12086
|
try {
|
|
11547
|
-
S = typeof o.storage == 'object' ? o.storage : (env || g$
|
|
12087
|
+
S = typeof o.storage == 'object' ? o.storage : (env || g$2)[(o.storage || 'local') + (env ? '' : 'Storage')];
|
|
11548
12088
|
}
|
|
11549
12089
|
catch (_) { }
|
|
11550
12090
|
if (!S)
|
|
@@ -11581,14 +12121,14 @@ const setupPersist = (c, o) => {
|
|
|
11581
12121
|
send(read(raw = v)); };
|
|
11582
12122
|
const onStorage = (e) => (!e.storageArea || e.storageArea == S) && onSync(e.key, e.newValue);
|
|
11583
12123
|
if (sync)
|
|
11584
|
-
off = S.subscribe ? S.subscribe(onSync) : (g$
|
|
11585
|
-
g$
|
|
12124
|
+
off = S.subscribe ? S.subscribe(onSync) : (g$2.addEventListener?.('storage', onStorage), () => g$2.removeEventListener?.('storage', onStorage));
|
|
12125
|
+
g$2.addEventListener?.('pagehide', flush);
|
|
11586
12126
|
env?.f.add(flush);
|
|
11587
12127
|
c._dispose$.addListener({ next: () => {
|
|
11588
12128
|
flush();
|
|
11589
12129
|
state$.removeListener(L);
|
|
11590
12130
|
off?.();
|
|
11591
|
-
g$
|
|
12131
|
+
g$2.removeEventListener?.('pagehide', flush);
|
|
11592
12132
|
env?.f.delete(flush);
|
|
11593
12133
|
} });
|
|
11594
12134
|
};
|
|
@@ -12820,7 +13360,7 @@ const snake = (s) => s.replace(/([a-z0-9])([A-Z])/g, '$1_$2').replace(/\W+/g, '_
|
|
|
12820
13360
|
const keyOf$1 = (iv) => iv.state?.id;
|
|
12821
13361
|
const done = new WeakMap();
|
|
12822
13362
|
/** a dev diagnostic, once per (declaration, what); 'error' mode rethrows outside the caller */
|
|
12823
|
-
const dev$
|
|
13363
|
+
const dev$4 = (code, decl, what, component, message) => {
|
|
12824
13364
|
let s = done.get(decl);
|
|
12825
13365
|
if (!s)
|
|
12826
13366
|
done.set(decl, s = new Set());
|
|
@@ -13029,7 +13569,7 @@ function agentTools(target, options = {}) {
|
|
|
13029
13569
|
v = d.read(s);
|
|
13030
13570
|
}
|
|
13031
13571
|
catch (e) {
|
|
13032
|
-
dev$
|
|
13572
|
+
dev$4('SYG241', d, 'read', iv.name, `agent.read threw: ${e?.message ?? e}`);
|
|
13033
13573
|
}
|
|
13034
13574
|
reads.set(iv, { s, v });
|
|
13035
13575
|
return v;
|
|
@@ -13048,7 +13588,7 @@ function agentTools(target, options = {}) {
|
|
|
13048
13588
|
v = !!w(s);
|
|
13049
13589
|
}
|
|
13050
13590
|
catch (e) {
|
|
13051
|
-
dev$
|
|
13591
|
+
dev$4('SYG241', declOf(iv), 'when' + action, iv.name, `agent.actions.${action}.when threw: ${e?.message ?? e}`);
|
|
13052
13592
|
}
|
|
13053
13593
|
m.set(action, { s, v });
|
|
13054
13594
|
return v;
|
|
@@ -13059,7 +13599,7 @@ function agentTools(target, options = {}) {
|
|
|
13059
13599
|
return d.label ? String(d.label(iv.state)) : undefined;
|
|
13060
13600
|
}
|
|
13061
13601
|
catch (e) {
|
|
13062
|
-
dev$
|
|
13602
|
+
dev$4('SYG241', d, 'label', iv.name, `agent.label threw: ${e?.message ?? e}`);
|
|
13063
13603
|
}
|
|
13064
13604
|
};
|
|
13065
13605
|
function walk(iv, out) {
|
|
@@ -13094,7 +13634,7 @@ function agentTools(target, options = {}) {
|
|
|
13094
13634
|
else if (e.decl === d && e.item && item)
|
|
13095
13635
|
e.ivs.push(iv);
|
|
13096
13636
|
else
|
|
13097
|
-
dev$
|
|
13637
|
+
dev$4('SYG440', d, name, iv.name, `two agent declarations are named '${name}'; only the first is offered`);
|
|
13098
13638
|
}
|
|
13099
13639
|
for (const e of by.values())
|
|
13100
13640
|
if (e.item) {
|
|
@@ -13103,9 +13643,9 @@ function agentTools(target, options = {}) {
|
|
|
13103
13643
|
const k = keyOf$1(iv);
|
|
13104
13644
|
// an item without an id is keyed by its index (the core's `_i<index>` uid part): unstable
|
|
13105
13645
|
if (k == null || /-_i\d+$/.test(iv.uid))
|
|
13106
|
-
dev$
|
|
13646
|
+
dev$4('SYG441', e.decl, 'nokey', iv.name, `items of '${e.name}' have no id: agents address them by index`);
|
|
13107
13647
|
else if (ks.has(k + ''))
|
|
13108
|
-
dev$
|
|
13648
|
+
dev$4('SYG441', e.decl, 'dup', iv.name, `two items of '${e.name}' have the id ${JSON.stringify(k)}; calls reach the first`);
|
|
13109
13649
|
else
|
|
13110
13650
|
ks.add(k + '');
|
|
13111
13651
|
}
|
|
@@ -13131,11 +13671,11 @@ function agentTools(target, options = {}) {
|
|
|
13131
13671
|
for (const [action, a, isQuery] of defs) {
|
|
13132
13672
|
const name = e.name + '_' + snake(action), comp = e.ivs[0].name, where = `agent.${isQuery ? 'queries' : 'actions'}.${action}`;
|
|
13133
13673
|
if (isQuery && recs.has(name)) {
|
|
13134
|
-
dev$
|
|
13674
|
+
dev$4('SYG247', d, action, comp, `${where} has the tool name ${name} of an action; the query is not offered (rename it)`);
|
|
13135
13675
|
continue;
|
|
13136
13676
|
}
|
|
13137
13677
|
if (isQuery && typeof a?.run != 'function') {
|
|
13138
|
-
dev$
|
|
13678
|
+
dev$4('SYG246', d, action, comp, `${where}.run is not a function; the query is not offered`);
|
|
13139
13679
|
continue;
|
|
13140
13680
|
}
|
|
13141
13681
|
const c = a.input ? toJsonSchema(a.input) : undefined;
|
|
@@ -13150,11 +13690,11 @@ function agentTools(target, options = {}) {
|
|
|
13150
13690
|
if (c?.error) {
|
|
13151
13691
|
tool.error = `SYG240 ${c.error}`;
|
|
13152
13692
|
rec.off = 1;
|
|
13153
|
-
dev$
|
|
13693
|
+
dev$4('SYG240', d, action, comp, `${where}.input: ${c.error}; the tool is not offered`);
|
|
13154
13694
|
continue;
|
|
13155
13695
|
}
|
|
13156
13696
|
if (c?.lossy)
|
|
13157
|
-
dev$
|
|
13697
|
+
dev$4('SYG243', d, action, comp, `${where}.input: the model doesn't see ${c.lossy}`);
|
|
13158
13698
|
const live = e.ivs.filter(iv => whenOk(iv, action, a.when));
|
|
13159
13699
|
if (!live.length) {
|
|
13160
13700
|
rec.off = 1;
|
|
@@ -13326,7 +13866,7 @@ function agentTools(target, options = {}) {
|
|
|
13326
13866
|
return;
|
|
13327
13867
|
const res = iv.def.view.resources;
|
|
13328
13868
|
if (!res || !(u in res)) {
|
|
13329
|
-
dev$
|
|
13869
|
+
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`);
|
|
13330
13870
|
return;
|
|
13331
13871
|
}
|
|
13332
13872
|
return (s) => { const r = s?.[u]; return !r || (r.status != 'loading' && !r.refreshing); };
|
|
@@ -13344,7 +13884,7 @@ function agentTools(target, options = {}) {
|
|
|
13344
13884
|
return !!test(iv.state);
|
|
13345
13885
|
}
|
|
13346
13886
|
catch (err) {
|
|
13347
|
-
dev$
|
|
13887
|
+
dev$4('SYG241', declOf(iv), 'until' + action, iv.name, `agent.actions.${action}.until threw: ${err?.message ?? err}`);
|
|
13348
13888
|
return true;
|
|
13349
13889
|
}
|
|
13350
13890
|
};
|
|
@@ -13370,11 +13910,11 @@ function agentTools(target, options = {}) {
|
|
|
13370
13910
|
}
|
|
13371
13911
|
catch (err) {
|
|
13372
13912
|
const m = err?.message ?? err;
|
|
13373
|
-
dev$
|
|
13913
|
+
dev$4('SYG241', declOf(iv), 'query' + query, iv.name, `agent.queries.${query}.run threw: ${m}`);
|
|
13374
13914
|
return { ok: false, error: `${name} failed: ${m}` };
|
|
13375
13915
|
}
|
|
13376
13916
|
if (result && typeof result.then == 'function') {
|
|
13377
|
-
dev$
|
|
13917
|
+
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`);
|
|
13378
13918
|
return { ok: false, error: `${name} is not available: it returned a promise` };
|
|
13379
13919
|
}
|
|
13380
13920
|
return { ok: true, result };
|
|
@@ -13735,28 +14275,28 @@ function withToolResults(message, results) {
|
|
|
13735
14275
|
const SCOPE = '__sygnalScope';
|
|
13736
14276
|
const tag$1 = (v, scope) => v && typeof v == 'object'
|
|
13737
14277
|
? Object.defineProperty(keepSender(v, { ...v }), SCOPE, { value: [scope, ...(v[SCOPE] || [])] }) : v;
|
|
13738
|
-
const g$
|
|
13739
|
-
const now = () => g$
|
|
14278
|
+
const g$1 = globalThis;
|
|
14279
|
+
const now = () => g$1.performance?.now?.() ?? Date.now();
|
|
13740
14280
|
// internal (D254): the 120 Hz floor and the hidden-tab fallback
|
|
13741
14281
|
const MIN_GAP = 15, FALLBACK = 100;
|
|
13742
14282
|
/** rAF raced with a timer (a hidden tab never runs rAF), else a 16 ms timer; `f` gets the frame's time */
|
|
13743
14283
|
const frame = (f) => {
|
|
13744
|
-
if (typeof g$
|
|
14284
|
+
if (typeof g$1.requestAnimationFrame != 'function')
|
|
13745
14285
|
return void setTimeout(() => f(now()), 16);
|
|
13746
14286
|
let done = 0, a, t;
|
|
13747
14287
|
const go = (ts) => {
|
|
13748
14288
|
if (done++)
|
|
13749
14289
|
return;
|
|
13750
|
-
g$
|
|
14290
|
+
g$1.cancelAnimationFrame?.(a);
|
|
13751
14291
|
clearTimeout(t);
|
|
13752
14292
|
f(typeof ts == 'number' ? ts : now());
|
|
13753
14293
|
};
|
|
13754
|
-
a = g$
|
|
14294
|
+
a = g$1.requestAnimationFrame(go);
|
|
13755
14295
|
t = setTimeout(go, FALLBACK);
|
|
13756
14296
|
};
|
|
13757
14297
|
// dev-only diagnostics (SYG673, SYG677): the dev entry formats them (checks/chat.ts), so their
|
|
13758
14298
|
// text costs apps nothing; without it they're silent
|
|
13759
|
-
const dev$
|
|
14299
|
+
const dev$3 = (...a) => g$1.__SYGNAL_DIAGNOSTICS__?.chat?.(...a);
|
|
13760
14300
|
const FIX = "Send { messages, ok: 'DONE', error: 'FAILED' }";
|
|
13761
14301
|
/** why a request can't be sent ('' when it can) */
|
|
13762
14302
|
const problem = (r) => !Array.isArray(r.messages) ? 'no messages array'
|
|
@@ -13778,7 +14318,7 @@ function makeChatDriver(options = {}) {
|
|
|
13778
14318
|
const { replies, reply } = makeReplies(sender => abortWhere(r => r.sender === sender));
|
|
13779
14319
|
// SYG673 (dev): `why` is 'type' | 'delta' | 'name' | 'id'; the first five per driver
|
|
13780
14320
|
const malformed = (req, ev, why) => { if (warned++ < 5)
|
|
13781
|
-
dev$
|
|
14321
|
+
dev$3('SYG673', req, ev, why); };
|
|
13782
14322
|
const start = async (req, sender) => {
|
|
13783
14323
|
const key = req.key ?? req.ok ?? req.error ?? 'default';
|
|
13784
14324
|
if (req.latest !== false)
|
|
@@ -13971,7 +14511,7 @@ function makeChatDriver(options = {}) {
|
|
|
13971
14511
|
return;
|
|
13972
14512
|
const sender = senderOf(req);
|
|
13973
14513
|
if (sender === undefined)
|
|
13974
|
-
return void dev$
|
|
14514
|
+
return void dev$3('SYG677', req);
|
|
13975
14515
|
const { abort } = req;
|
|
13976
14516
|
if (abort !== undefined && abort !== false) {
|
|
13977
14517
|
// D469 (PLAN-9 H-2): a list of keys stops each (STOP: the reply and a card being composed)
|
|
@@ -15027,6 +15567,17 @@ const validMs = (v) => typeof v == 'number' && Number.isFinite(v) && v >= 0 && v
|
|
|
15027
15567
|
// too, so its time is the clock's (Date may be left real by `toFake`), and its waits drive it.
|
|
15028
15568
|
const fakeClock = () => setTimeout.clock;
|
|
15029
15569
|
const clockNow = () => { const c = fakeClock(); return c ? c.now : Date.now(); };
|
|
15570
|
+
/** PLAN-11 Q-3: ms until the next pending fake timer (0: none, or real timers) */
|
|
15571
|
+
const nextFakeTimer = () => {
|
|
15572
|
+
const c = fakeClock();
|
|
15573
|
+
let due = Infinity;
|
|
15574
|
+
try {
|
|
15575
|
+
Object.values(c?.timers || {}).forEach((t) => { if (t && t.callAt > c.now && t.callAt < due)
|
|
15576
|
+
due = t.callAt; });
|
|
15577
|
+
}
|
|
15578
|
+
catch (_) { }
|
|
15579
|
+
return due < Infinity ? due - c.now : 0;
|
|
15580
|
+
};
|
|
15030
15581
|
/**
|
|
15031
15582
|
* Under fake timers, advance the clock timer by timer (nextAsync flushes promises around each
|
|
15032
15583
|
* one) until `p` settles. Every pending wait has a timer (its timeout, a quiet-window tick), so
|
|
@@ -15217,6 +15768,7 @@ const headFake = () => {
|
|
|
15217
15768
|
};
|
|
15218
15769
|
let savedConfig;
|
|
15219
15770
|
let savedStrict;
|
|
15771
|
+
let savedAudit;
|
|
15220
15772
|
/*
|
|
15221
15773
|
* PLAN-4 GS-2, mock DOM: could the element a command targets have this method? The core runs any
|
|
15222
15774
|
* method of the element; the mock DOM has no elements, so: the documented commands, common
|
|
@@ -15327,7 +15879,7 @@ function fakeElementMethods(W) {
|
|
|
15327
15879
|
Object.defineProperty(proto, name, own); });
|
|
15328
15880
|
}
|
|
15329
15881
|
function renderComponent(componentDef, options = {}) {
|
|
15330
|
-
const { initialState, mockConfig = {}, drivers = {}, diagnostics, strict, dom = 'mock', autoConnect = true, socketSink = 'WS', resourceSink = 'HTTP', llmSink = 'LLM', http: httpOptions, context: ancestors } = options;
|
|
15882
|
+
const { initialState, mockConfig = {}, drivers = {}, diagnostics, strict, timingAudit, dom = 'mock', autoConnect = true, socketSink = 'WS', resourceSink = 'HTTP', llmSink = 'LLM', http: httpOptions, context: ancestors } = options;
|
|
15331
15883
|
const { intent, model = {} } = componentDef;
|
|
15332
15884
|
// E4: real DOM mode
|
|
15333
15885
|
const real = dom == 'real';
|
|
@@ -15375,6 +15927,7 @@ function renderComponent(componentDef, options = {}) {
|
|
|
15375
15927
|
if (!active++) {
|
|
15376
15928
|
savedConfig = _getDiagnosticsConfig();
|
|
15377
15929
|
savedStrict = core.strict;
|
|
15930
|
+
savedAudit = core.timingAudit;
|
|
15378
15931
|
// G-051: forget the checks' report dedupe (once()), so a finding from an earlier test is
|
|
15379
15932
|
// reported again. Only that set: resetChecks() would also drop inspect()'s records of
|
|
15380
15933
|
// other live instances / apps.
|
|
@@ -15383,6 +15936,9 @@ function renderComponent(componentDef, options = {}) {
|
|
|
15383
15936
|
configureDiagnostics({ mode: diagnostics || (prevMode == 'off' ? 'collect' : prevMode) });
|
|
15384
15937
|
if (strict !== undefined)
|
|
15385
15938
|
core.strict = strict;
|
|
15939
|
+
// 7.0 timing audit (SYG257, 'sygnal/diagnostics'): same bridge flag as configureTimingAudit()
|
|
15940
|
+
if (timingAudit !== undefined)
|
|
15941
|
+
core.timingAudit = timingAudit;
|
|
15386
15942
|
// PLAN-4 GS-2: element commands run on the real DOM (jsdom gets the missing methods), and their
|
|
15387
15943
|
// SYG640/SYG641 are reported without the dev entry too
|
|
15388
15944
|
if (real && !domFakes)
|
|
@@ -15573,6 +16129,7 @@ function renderComponent(componentDef, options = {}) {
|
|
|
15573
16129
|
if (!--active) {
|
|
15574
16130
|
configureDiagnostics(savedConfig);
|
|
15575
16131
|
core.strict = savedStrict;
|
|
16132
|
+
core.timingAudit = savedAudit;
|
|
15576
16133
|
domFakes?.();
|
|
15577
16134
|
domFakes = undefined;
|
|
15578
16135
|
}
|
|
@@ -15618,14 +16175,22 @@ function renderComponent(componentDef, options = {}) {
|
|
|
15618
16175
|
const lin$ = xs$1.create();
|
|
15619
16176
|
llm = { in$: lin$, src: makeChatDriver({ transport: tr, _frame: (f) => { frames.push(f); } })(lin$) };
|
|
15620
16177
|
}
|
|
16178
|
+
let memFetch;
|
|
15621
16179
|
const http = makeFetchDriver({
|
|
15622
16180
|
...httpOptions,
|
|
16181
|
+
// PLAN-11 U-2: a progress request is answered like any other (t.respond / t.fail); its
|
|
16182
|
+
// pending entry keeps the driver's progress callback for t.progress
|
|
16183
|
+
_progress: (url, init, step) => {
|
|
16184
|
+
const r = memFetch(url, init);
|
|
16185
|
+
pending[pending.length - 1].step = step;
|
|
16186
|
+
return r;
|
|
16187
|
+
},
|
|
15623
16188
|
// 5-3: focus / online come from t.focus() / t.online() only
|
|
15624
16189
|
_on: (f) => { signals.add(f); return () => signals.delete(f); },
|
|
15625
16190
|
_tap: (req, res, pf) => { tapped = [req, res, pf]; },
|
|
15626
16191
|
// D472: a batch / split request (a large decide()) is one fetch here, answered as one
|
|
15627
16192
|
_whole: true,
|
|
15628
|
-
fetch: (url, init) => new Promise((resolve, reject) => {
|
|
16193
|
+
fetch: memFetch = (url, init) => new Promise((resolve, reject) => {
|
|
15629
16194
|
const [req, res, pf] = tapped || [{ url }];
|
|
15630
16195
|
tapped = undefined;
|
|
15631
16196
|
// G-171(1): a resource fetch is listed as { url, ...request, resource: name }; 5-5: a
|
|
@@ -15659,6 +16224,7 @@ function renderComponent(componentDef, options = {}) {
|
|
|
15659
16224
|
return {
|
|
15660
16225
|
select: (sel) => xs$1.merge(hs.select(sel), own(false)(sel), sock.select(sel)),
|
|
15661
16226
|
errors: (sel) => xs$1.merge(hs.errors(sel), own(true)(sel)),
|
|
16227
|
+
progress: (sel) => hs.progress(sel),
|
|
15662
16228
|
subs, at, pending, in$, http, ws, llm,
|
|
15663
16229
|
// 5-3: the driver's matcher / inspection (the dev checks' SYG632, t.cache)
|
|
15664
16230
|
__matches: http.__matches, __inspect: http.__inspect,
|
|
@@ -16424,7 +16990,7 @@ function renderComponent(componentDef, options = {}) {
|
|
|
16424
16990
|
return { match: cat, desc: catDesc, o };
|
|
16425
16991
|
if (typeof tg == 'string') {
|
|
16426
16992
|
// an ok/error action name, key, category, resource name or URL
|
|
16427
|
-
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 };
|
|
16993
|
+
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 };
|
|
16428
16994
|
}
|
|
16429
16995
|
if (typeof tg == 'function') {
|
|
16430
16996
|
return { match: r => { try {
|
|
@@ -16681,6 +17247,27 @@ function renderComponent(componentDef, options = {}) {
|
|
|
16681
17247
|
const err = typeof error == 'number' || resp ? Object.assign(new Error(`HTTP ${status}`), { status }) : x;
|
|
16682
17248
|
return { error: err, category, request: e?.req, status: status ?? x?.status, body };
|
|
16683
17249
|
});
|
|
17250
|
+
// PLAN-11 U-2: t.progress. One progress step of a pending request sent with `progress` (its
|
|
17251
|
+
// reply action, or progress() for `progress: true`); the request stays pending. Targets as
|
|
17252
|
+
// t.respond's; the phase defaults to 'upload' for a request with a body, else 'download'
|
|
17253
|
+
const progress = (name, step, opts) => {
|
|
17254
|
+
throwFailure();
|
|
17255
|
+
if (drivers[name])
|
|
17256
|
+
throw new Error(`[Sygnal] t.progress('${name}'): ${name} has a real driver (passed in drivers), so there is nothing to script`);
|
|
17257
|
+
if (!step || typeof step != 'object' || typeof step.loaded != 'number')
|
|
17258
|
+
throw new Error(`[Sygnal] t.progress('${name}', step): step is { loaded, total?, phase? } (got ${brief(step)})`);
|
|
17259
|
+
const tg = targetOf(opts);
|
|
17260
|
+
const what = `t.progress('${name}'${typeof opts == 'string' ? `, …, '${opts}'` : ''})`;
|
|
17261
|
+
const now = isReady && !inputs.length ? pick(name, tg) : undefined;
|
|
17262
|
+
if (now && !now.step)
|
|
17263
|
+
throw new Error(`[Sygnal] ${what}: the request ${brief(now.value)} has no progress: send it with progress: 'UPLOADING' (a reply action) or progress: true`);
|
|
17264
|
+
return scripted(() => pick(name, tg), w => noPending(what, name, tg, w), (e) => {
|
|
17265
|
+
if (!e.step)
|
|
17266
|
+
return new Error(`[Sygnal] ${what}: the request ${brief(e.value)} has no progress: send it with progress: 'UPLOADING' (a reply action) or progress: true`);
|
|
17267
|
+
const r = e.req || {};
|
|
17268
|
+
e.step(step.phase || (r.body !== undefined || r.json !== undefined || r.multipart !== undefined ? 'upload' : 'download'), step.loaded, step.total ?? 0);
|
|
17269
|
+
}, sendDue);
|
|
17270
|
+
};
|
|
16684
17271
|
// PLAN-6 L-4: t.stream. Targets as t.respond's (an action name or key, a partial request, a
|
|
16685
17272
|
// predicate, { request, nth }); `end: false` (the last argument, or in the target options)
|
|
16686
17273
|
// leaves the stream open. Chunks become ChatEvents pushed into the in-memory stream; the fake's
|
|
@@ -17185,7 +17772,14 @@ function renderComponent(componentDef, options = {}) {
|
|
|
17185
17772
|
let msg = `${name} timed out after ${timeoutMs}ms.`;
|
|
17186
17773
|
const [why, pending] = explainNext(start);
|
|
17187
17774
|
msg += why;
|
|
17188
|
-
if (
|
|
17775
|
+
if (inputs.length)
|
|
17776
|
+
msg += ` ${inputs.length} simulated input(s) still pending.`;
|
|
17777
|
+
// PLAN-11 Q-3: on the fake clock a state a timer (TIMER driver, periodic, delay) brings
|
|
17778
|
+
// later than the timeout can't arrive in time: name the next pending timer
|
|
17779
|
+
const later = pending ? 0 : nextFakeTimer();
|
|
17780
|
+
if (later)
|
|
17781
|
+
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.`;
|
|
17782
|
+
if (pending || later)
|
|
17189
17783
|
msg += ` Wait longer: t.${name}(pred, ms), or the renderComponent timeoutMs option (default for every wait).`;
|
|
17190
17784
|
// G-065: a state from before the call matched; next() only looks at new ones
|
|
17191
17785
|
for (let i = from - 1; i >= 0; i--) {
|
|
@@ -17385,6 +17979,7 @@ function renderComponent(componentDef, options = {}) {
|
|
|
17385
17979
|
requests,
|
|
17386
17980
|
respond,
|
|
17387
17981
|
fail,
|
|
17982
|
+
progress,
|
|
17388
17983
|
stream,
|
|
17389
17984
|
connections,
|
|
17390
17985
|
cache,
|
|
@@ -17961,7 +18556,7 @@ function jsonSchema(schema, options = {}) {
|
|
|
17961
18556
|
* Side-effect free: an app that doesn't import it pays 0 B.
|
|
17962
18557
|
*/
|
|
17963
18558
|
const NAME = 30, DESC = 500, PARAM = 150, OUT = 1500, SUMMARY = 300;
|
|
17964
|
-
const g
|
|
18559
|
+
const g = globalThis;
|
|
17965
18560
|
/** cut to n characters with an ellipsis, never inside a surrogate pair */
|
|
17966
18561
|
const cut = (s, n) => {
|
|
17967
18562
|
if (s.length <= n)
|
|
@@ -17978,7 +18573,7 @@ const hash = (s) => {
|
|
|
17978
18573
|
return (h % 1679616).toString(36).padStart(4, '0');
|
|
17979
18574
|
};
|
|
17980
18575
|
function experimentalExposeWebMcp(app, options = {}) {
|
|
17981
|
-
const mc = options.modelContext ?? g
|
|
18576
|
+
const mc = options.modelContext ?? g.document?.modelContext ?? g.navigator?.modelContext;
|
|
17982
18577
|
const said = new Set();
|
|
17983
18578
|
const dev = (code, key, message, severity = 'warn') => {
|
|
17984
18579
|
if (said.has(code + key))
|
|
@@ -18181,7 +18776,7 @@ function experimentalExposeWebMcp(app, options = {}) {
|
|
|
18181
18776
|
let n$2 = 0;
|
|
18182
18777
|
/** the default confirmation: a native modal dialog outside the app's tree (Escape and "Deny" decline) */
|
|
18183
18778
|
function dialog(info, setClose) {
|
|
18184
|
-
const doc = g
|
|
18779
|
+
const doc = g.document;
|
|
18185
18780
|
if (!doc?.createElement)
|
|
18186
18781
|
return Promise.resolve(false);
|
|
18187
18782
|
return new Promise(resolve => {
|
|
@@ -18437,7 +19032,7 @@ const toolsOf$1 = (e) => e.o.agent === false ? null : e.tools || (e.tools = agen
|
|
|
18437
19032
|
}),
|
|
18438
19033
|
}));
|
|
18439
19034
|
// dev diagnostics (SYG684, SYG685): no-ops with diagnostics off
|
|
18440
|
-
const dev$
|
|
19035
|
+
const dev$2 = (code, message, fix) => { try {
|
|
18441
19036
|
report(code, { component: 'chat', message, fix, severity: 'warn' });
|
|
18442
19037
|
}
|
|
18443
19038
|
catch (e) {
|
|
@@ -18647,7 +19242,7 @@ function steps(c, o) {
|
|
|
18647
19242
|
const openPart = (s, id, what, guard = (_p) => true) => {
|
|
18648
19243
|
const p = openToolCalls(s).find(x => x.toolCallId === id && isClient(c, x) && guard(x));
|
|
18649
19244
|
if (!p)
|
|
18650
|
-
dev$
|
|
19245
|
+
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');
|
|
18651
19246
|
return p;
|
|
18652
19247
|
};
|
|
18653
19248
|
/** after a call closed or a card changed: the next queued card, then rule 3 */
|
|
@@ -18775,7 +19370,7 @@ function request(e, o, k, messages, fresh, c, host) {
|
|
|
18775
19370
|
for (const n in c.client) {
|
|
18776
19371
|
if (n in tools && !shadowed.has(n)) {
|
|
18777
19372
|
shadowed.add(n);
|
|
18778
|
-
dev$
|
|
19373
|
+
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');
|
|
18779
19374
|
}
|
|
18780
19375
|
const x = c.client[n];
|
|
18781
19376
|
tools[n] = c.ask && n == 'ask_user' ? c.kit.askTool(c.ask, host) : { description: x.description, inputSchema: schemaOf(x) };
|
|
@@ -18892,7 +19487,7 @@ const chat = (options = {}) => {
|
|
|
18892
19487
|
...(c.ask?.controls && DOM && { CONTROL: DOM.select('[data-compose-control]').events('click').map(controlOf$1).filter((x) => !!x) }),
|
|
18893
19488
|
};
|
|
18894
19489
|
},
|
|
18895
|
-
model: {
|
|
19490
|
+
model: fromPrevious({
|
|
18896
19491
|
PROMPT: (s, p) => typeof p == 'string' && p !== s.prompt ? { ...s, prompt: p } : ABORT,
|
|
18897
19492
|
SEND: {
|
|
18898
19493
|
STATE: (s, d) => sendStep(s, d) || ABORT,
|
|
@@ -18990,153 +19585,19 @@ const chat = (options = {}) => {
|
|
|
18990
19585
|
ANSWER: { STATE: stateOf(S.answerCard), ...sinks(S.answerCard) },
|
|
18991
19586
|
CONTROL: (s, d) => S.controlStep(s, d) || ABORT,
|
|
18992
19587
|
}),
|
|
18993
|
-
},
|
|
19588
|
+
}),
|
|
18994
19589
|
})(options);
|
|
18995
19590
|
// the `prompt` option is a selector, not the slice's start value (defineBehavior's option override)
|
|
18996
19591
|
b.state = { ...b.state, prompt: '' };
|
|
18997
19592
|
return linked(b);
|
|
18998
19593
|
};
|
|
18999
19594
|
|
|
19000
|
-
/*
|
|
19001
|
-
* PLAN-6 L-2: what the HTTP transports share (side-effect free; each transport imports only what
|
|
19002
|
-
* it uses).
|
|
19003
|
-
* - post(): the fetch (the `fetch` option, 0-S5), the headers option (an object or a function of
|
|
19004
|
-
* the request), HTTP errors as Errors with `status` and the provider's message, and SYG670.
|
|
19005
|
-
* - sse(): a fetch Response body as SSE frames ({ event, data }), per the HTML spec's parsing
|
|
19006
|
-
* rules: CRLF / LF, comments, multi-line data. The reader is cancelled when the consumer stops
|
|
19007
|
-
* (abort, a thrown error), so the connection closes.
|
|
19008
|
-
* - SYG670: in a browser, a request with an auth header (Authorization, x-api-key, api-key,
|
|
19009
|
-
* x-goog-api-key) to a host that is neither local (localhost, 127.0.0.1, [::1], *.localhost)
|
|
19010
|
-
* nor the page's own origin is refused unless the transport has `dangerouslyAllowBrowser: true`:
|
|
19011
|
-
* the key would ship to every visitor. Same-origin requests are the app's own server (its
|
|
19012
|
-
* proxy), so a session header there is fine.
|
|
19013
|
-
*/
|
|
19014
|
-
const g = globalThis;
|
|
19015
|
-
const AUTH = /^(authorization|x-api-key|api-key|x-goog-api-key)$/i;
|
|
19016
|
-
const LOCAL = /^(localhost|127\.0\.0\.1|\[::1\]|.+\.localhost)$/;
|
|
19017
|
-
/** the URL a transport posts to, or a SYG670 Error when it must not (see the header) */
|
|
19018
|
-
function guard(url, headers, opts, name) {
|
|
19019
|
-
if (opts.dangerouslyAllowBrowser || typeof g.document == 'undefined' || !Object.keys(headers).some(h => AUTH.test(h)))
|
|
19020
|
-
return;
|
|
19021
|
-
const here = g.location?.href;
|
|
19022
|
-
let u;
|
|
19023
|
-
try {
|
|
19024
|
-
u = new URL(url, here);
|
|
19025
|
-
}
|
|
19026
|
-
catch (_) {
|
|
19027
|
-
return;
|
|
19028
|
-
}
|
|
19029
|
-
if (LOCAL.test(u.hostname) || (here && u.origin == new URL(here).origin))
|
|
19030
|
-
return;
|
|
19031
|
-
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 })`;
|
|
19032
|
-
const msg = `${name}: refused to send an auth header from the browser to ${u.origin}: anyone who opens the page can read it`;
|
|
19033
|
-
error('SYG670', undefined, msg, fix);
|
|
19034
|
-
fail$2('SYG670', undefined, msg, fix);
|
|
19035
|
-
}
|
|
19036
|
-
/**
|
|
19037
|
-
* POST JSON; a non-2xx response is an Error with `status`, `body` and the provider's message.
|
|
19038
|
-
* The transport's `body` option and the request's `body` are merged over the built body (so they
|
|
19039
|
-
* can override provider defaults such as `max_tokens`); `fixed` fields are merged last and can't be
|
|
19040
|
-
* overridden (G-654: uiMessageStream's protocol fields, as the AI SDK's DefaultChatTransport)
|
|
19041
|
-
*/
|
|
19042
|
-
async function post$1(url, body, req, signal, opts, name, fixed) {
|
|
19043
|
-
const h = typeof opts.headers == 'function' ? await opts.headers(req) : opts.headers || {};
|
|
19044
|
-
guard(url, h, opts, name);
|
|
19045
|
-
const f = opts.fetch || ((u, i) => g.fetch(u, i));
|
|
19046
|
-
const res = await f(url, { method: 'POST', headers: { 'content-type': 'application/json', ...h }, body: JSON.stringify({ ...body, ...opts.body, ...req.body, ...fixed }), signal });
|
|
19047
|
-
if (!res.ok) {
|
|
19048
|
-
let text = '', message = '';
|
|
19049
|
-
try {
|
|
19050
|
-
text = await res.text();
|
|
19051
|
-
const j = JSON.parse(text);
|
|
19052
|
-
message = j.error?.message ?? j.error ?? j.message ?? '';
|
|
19053
|
-
}
|
|
19054
|
-
catch (_) {
|
|
19055
|
-
message || (message = text.slice(0, 200));
|
|
19056
|
-
}
|
|
19057
|
-
throw Object.assign(new Error(`${name}: HTTP ${res.status}${message ? ': ' + message : ''}`), { status: res.status, body: text });
|
|
19058
|
-
}
|
|
19059
|
-
if (!res.body)
|
|
19060
|
-
throw new Error(`${name}: the response has no body`);
|
|
19061
|
-
return res;
|
|
19062
|
-
}
|
|
19063
|
-
/** the SSE frames of a response body: `{ event, data }` (data: the joined data lines) */
|
|
19064
|
-
async function* sse(res) {
|
|
19065
|
-
const reader = res.body.getReader(), dec = new TextDecoder();
|
|
19066
|
-
let buf = '', cr = '', done = false;
|
|
19067
|
-
try {
|
|
19068
|
-
for (;;) {
|
|
19069
|
-
const r = await reader.read();
|
|
19070
|
-
// line ends -> \n (a \r at a chunk's end waits: it may be half of a \r\n)
|
|
19071
|
-
let s = cr + (r.done ? '\n\n' : dec.decode(r.value, { stream: true }));
|
|
19072
|
-
cr = !r.done && s.endsWith('\r') ? '\r' : '';
|
|
19073
|
-
if (cr)
|
|
19074
|
-
s = s.slice(0, -1);
|
|
19075
|
-
buf += s.replace(/\r\n?/g, '\n');
|
|
19076
|
-
done = r.done;
|
|
19077
|
-
let i;
|
|
19078
|
-
while ((i = buf.indexOf('\n\n')) >= 0) {
|
|
19079
|
-
const frame = buf.slice(0, i);
|
|
19080
|
-
buf = buf.slice(i + 2);
|
|
19081
|
-
let event = 'message', data = [];
|
|
19082
|
-
for (const line of frame.split('\n')) {
|
|
19083
|
-
if (!line || line[0] == ':')
|
|
19084
|
-
continue;
|
|
19085
|
-
const c = line.indexOf(':'), field = c < 0 ? line : line.slice(0, c);
|
|
19086
|
-
const v = c < 0 ? '' : line.slice(c + 1).replace(/^ /, '');
|
|
19087
|
-
if (field == 'event')
|
|
19088
|
-
event = v;
|
|
19089
|
-
else if (field == 'data')
|
|
19090
|
-
data.push(v);
|
|
19091
|
-
}
|
|
19092
|
-
if (data.length)
|
|
19093
|
-
yield { event, data: data.join('\n') };
|
|
19094
|
-
}
|
|
19095
|
-
if (done)
|
|
19096
|
-
return;
|
|
19097
|
-
}
|
|
19098
|
-
}
|
|
19099
|
-
finally {
|
|
19100
|
-
if (!done)
|
|
19101
|
-
reader.cancel().catch(() => { });
|
|
19102
|
-
}
|
|
19103
|
-
}
|
|
19104
|
-
/** JSON.parse, or the text itself when it isn't JSON (a model's malformed tool arguments go to validation as is) */
|
|
19105
|
-
const json = (s) => {
|
|
19106
|
-
if (typeof s != 'string')
|
|
19107
|
-
return s;
|
|
19108
|
-
try {
|
|
19109
|
-
return JSON.parse(s || '{}');
|
|
19110
|
-
}
|
|
19111
|
-
catch (_) {
|
|
19112
|
-
return s;
|
|
19113
|
-
}
|
|
19114
|
-
};
|
|
19115
|
-
/** a provider's usage as `{ inputTokens, outputTokens, totalTokens, reasoningTokens? }` (AI SDK names) */
|
|
19116
|
-
const usageOf = (u) => u && {
|
|
19117
|
-
inputTokens: u.input_tokens ?? u.prompt_tokens,
|
|
19118
|
-
outputTokens: u.output_tokens ?? u.completion_tokens,
|
|
19119
|
-
totalTokens: u.total_tokens,
|
|
19120
|
-
...((u.output_tokens_details ?? u.completion_tokens_details)?.reasoning_tokens != null && { reasoningTokens: (u.output_tokens_details ?? u.completion_tokens_details).reasoning_tokens }),
|
|
19121
|
-
};
|
|
19122
|
-
/** the parts of a message (a `content` string is one text part) */
|
|
19123
|
-
const partsOf = (m) => m.parts ?? (typeof m.content == 'string' ? [{ type: 'text', text: m.content }] : []);
|
|
19124
|
-
/** the tool name of a `tool-<name>` / `dynamic-tool` part, or '' */
|
|
19125
|
-
const toolName = (p) => p.type == 'dynamic-tool' ? p.toolName : p.type.startsWith('tool-') ? p.type.slice(5) : '';
|
|
19126
|
-
/** what a tool part sends back to the model, or undefined when it has no result yet */
|
|
19127
|
-
const toolOutput = (p) => p.state == 'output-available' ? (typeof p.output == 'string' ? p.output : JSON.stringify(p.output ?? null))
|
|
19128
|
-
: p.state == 'output-error' ? JSON.stringify({ error: p.errorText ?? 'error' })
|
|
19129
|
-
: p.state == 'output-denied' ? JSON.stringify({ error: 'The user denied this tool call' })
|
|
19130
|
-
: undefined;
|
|
19131
|
-
// dev-only diagnostics (SYG675): the dev entry formats them (checks/chat.ts)
|
|
19132
|
-
const dev$2 = (...a) => g.__SYGNAL_DIAGNOSTICS__?.chat?.(...a);
|
|
19133
|
-
|
|
19134
19595
|
const EMPTY$1 = { type: 'object', properties: {} };
|
|
19135
19596
|
function prepare(req, strict, seen, transport, dialect = 'openai', limits) {
|
|
19136
19597
|
const sent = {};
|
|
19137
19598
|
if (strict && typeof strict != 'function' && !seen.has('')) {
|
|
19138
19599
|
seen.add('');
|
|
19139
|
-
dev$
|
|
19600
|
+
dev$8('SYG672', req, { transport });
|
|
19140
19601
|
}
|
|
19141
19602
|
const fn = typeof strict == 'function' ? strict : undefined;
|
|
19142
19603
|
// what the request has left of `limits` (strictSchemas counts it down)
|
|
@@ -19149,7 +19610,7 @@ function prepare(req, strict, seen, transport, dialect = 'openai', limits) {
|
|
|
19149
19610
|
return { schema: s.schema, strict: true };
|
|
19150
19611
|
if (!seen.has(what)) {
|
|
19151
19612
|
seen.add(what);
|
|
19152
|
-
dev$
|
|
19613
|
+
dev$8('SYG675', req, { tool: what, errors: s.errors });
|
|
19153
19614
|
}
|
|
19154
19615
|
return { schema, strict: false };
|
|
19155
19616
|
};
|
|
@@ -19220,12 +19681,12 @@ function openResponses(options = {}) {
|
|
|
19220
19681
|
c.done = 1;
|
|
19221
19682
|
called++;
|
|
19222
19683
|
const name = c.name ?? item?.name;
|
|
19223
|
-
return { type: 'tool-call', id: c.call_id ?? item?.call_id ?? id, name, input: p.input(name, json(args ?? item?.arguments ?? c.args)) };
|
|
19684
|
+
return { type: 'tool-call', id: c.call_id ?? item?.call_id ?? id, name, input: p.input(name, json$1(args ?? item?.arguments ?? c.args)) };
|
|
19224
19685
|
};
|
|
19225
19686
|
for await (const { data } of sse(res)) {
|
|
19226
19687
|
if (data == '[DONE]')
|
|
19227
19688
|
break;
|
|
19228
|
-
const e = json(data), t = e?.type;
|
|
19689
|
+
const e = json$1(data), t = e?.type;
|
|
19229
19690
|
if (typeof t != 'string')
|
|
19230
19691
|
continue;
|
|
19231
19692
|
if (t == 'response.output_text.delta' || t == 'response.refusal.delta' || REASONING.test(t)) {
|
|
@@ -19317,13 +19778,13 @@ function chatCompletions(options = {}) {
|
|
|
19317
19778
|
const done = function* () {
|
|
19318
19779
|
for (const c of calls)
|
|
19319
19780
|
if (c)
|
|
19320
|
-
yield { type: 'tool-call', id: c.id, name: c.name, input: p.input(c.name, json(c.args)) };
|
|
19781
|
+
yield { type: 'tool-call', id: c.id, name: c.name, input: p.input(c.name, json$1(c.args)) };
|
|
19321
19782
|
calls = [];
|
|
19322
19783
|
};
|
|
19323
19784
|
for await (const { data } of sse(res)) {
|
|
19324
19785
|
if (data == '[DONE]')
|
|
19325
19786
|
break;
|
|
19326
|
-
const e = json(data);
|
|
19787
|
+
const e = json$1(data);
|
|
19327
19788
|
if (!e || typeof e != 'object')
|
|
19328
19789
|
continue;
|
|
19329
19790
|
if (e.error)
|
|
@@ -19411,7 +19872,7 @@ function uiMessageStream(url, options = {}) {
|
|
|
19411
19872
|
for await (const { data } of sse(res)) {
|
|
19412
19873
|
if (data == '[DONE]')
|
|
19413
19874
|
break;
|
|
19414
|
-
const c = json(data), t = c?.type;
|
|
19875
|
+
const c = json$1(data), t = c?.type;
|
|
19415
19876
|
if (typeof t != 'string')
|
|
19416
19877
|
continue;
|
|
19417
19878
|
if (ONE.test(t)) {
|
|
@@ -20142,7 +20603,7 @@ const commandBar = (options = {}) => {
|
|
|
20142
20603
|
...(deny && { DENY: on(DOM, deny) }),
|
|
20143
20604
|
};
|
|
20144
20605
|
},
|
|
20145
|
-
model: {
|
|
20606
|
+
model: fromPrevious({
|
|
20146
20607
|
INPUT: (s, v) => typeof v == 'string' && v !== s.text ? { ...s, text: v } : ABORT,
|
|
20147
20608
|
RUN: {
|
|
20148
20609
|
STATE: (s, d, _n, p, o, k) => runStep(s, d, E(p, k, o))?.s || ABORT,
|
|
@@ -20178,7 +20639,7 @@ const commandBar = (options = {}) => {
|
|
|
20178
20639
|
// a command ran (or was refused): the result; the input is cleared when it was this command
|
|
20179
20640
|
DONE: (s, d) => s.status != 'running' ? ABORT
|
|
20180
20641
|
: { ...s, status: 'ready', command: null, pending: null, result: d, text: d?.ok && s.text.trim() === d.command ? '' : s.text },
|
|
20181
|
-
},
|
|
20642
|
+
}),
|
|
20182
20643
|
})(options);
|
|
20183
20644
|
return linked(b);
|
|
20184
20645
|
};
|
|
@@ -20278,7 +20739,7 @@ function anthropicMessages(options = {}) {
|
|
|
20278
20739
|
return { inputTokens: i, outputTokens: o, totalTokens: i + o, ...(u.output_tokens_details?.thinking_tokens != null && { reasoningTokens: u.output_tokens_details.thinking_tokens }) };
|
|
20279
20740
|
};
|
|
20280
20741
|
for await (const { data } of sse(res)) {
|
|
20281
|
-
const e = json(data), t = e?.type;
|
|
20742
|
+
const e = json$1(data), t = e?.type;
|
|
20282
20743
|
if (typeof t != 'string')
|
|
20283
20744
|
continue;
|
|
20284
20745
|
if (t == 'message_start') {
|
|
@@ -20314,9 +20775,9 @@ function anthropicMessages(options = {}) {
|
|
|
20314
20775
|
if (b?.type == 'thinking' && b.signature)
|
|
20315
20776
|
yield { type: 'reasoning', delta: '', providerMetadata: { anthropic: { signature: b.signature } } };
|
|
20316
20777
|
else if (b?.type == 'tool_use')
|
|
20317
|
-
yield { type: 'tool-call', id: b.id, name: b.name, input: p.input(b.name, b.json ? json(b.json) : b.input ?? {}) };
|
|
20778
|
+
yield { type: 'tool-call', id: b.id, name: b.name, input: p.input(b.name, b.json ? json$1(b.json) : b.input ?? {}) };
|
|
20318
20779
|
else if (b?.type == 'server_tool_use')
|
|
20319
|
-
yield { type: 'tool-call', id: b.id, name: b.name, input: b.json ? json(b.json) : b.input ?? {}, executed: true, providerExecuted: true };
|
|
20780
|
+
yield { type: 'tool-call', id: b.id, name: b.name, input: b.json ? json$1(b.json) : b.input ?? {}, executed: true, providerExecuted: true };
|
|
20320
20781
|
}
|
|
20321
20782
|
else if (t == 'message_delta') {
|
|
20322
20783
|
if (e.delta?.stop_reason)
|
|
@@ -20456,10 +20917,10 @@ function agui(url, options = {}) {
|
|
|
20456
20917
|
if (!c || c.done)
|
|
20457
20918
|
return;
|
|
20458
20919
|
c.done = 1;
|
|
20459
|
-
open.push({ type: 'tool-call', id, name: c.name, input: json(c.args || '{}') });
|
|
20920
|
+
open.push({ type: 'tool-call', id, name: c.name, input: json$1(c.args || '{}') });
|
|
20460
20921
|
};
|
|
20461
20922
|
for await (const { data } of sse(res)) {
|
|
20462
|
-
const e = json(data), t = e?.type;
|
|
20923
|
+
const e = json$1(data), t = e?.type;
|
|
20463
20924
|
if (typeof t != 'string')
|
|
20464
20925
|
continue;
|
|
20465
20926
|
if (t == 'TEXT_MESSAGE_START') {
|
|
@@ -20507,7 +20968,7 @@ function agui(url, options = {}) {
|
|
|
20507
20968
|
else if (t == 'TOOL_CALL_RESULT') {
|
|
20508
20969
|
end(e.toolCallId);
|
|
20509
20970
|
yield* take(e.toolCallId);
|
|
20510
|
-
yield { type: 'tool-result', id: e.toolCallId, output: typeof e.content == 'string' ? json(e.content) : e.content };
|
|
20971
|
+
yield { type: 'tool-result', id: e.toolCallId, output: typeof e.content == 'string' ? json$1(e.content) : e.content };
|
|
20511
20972
|
}
|
|
20512
20973
|
else if (t == 'STATE_SNAPSHOT' || t == 'STATE_DELTA') {
|
|
20513
20974
|
state = t == 'STATE_SNAPSHOT' ? e.snapshot : applyPatch(state, e.delta);
|
|
@@ -21708,7 +22169,7 @@ function composeBehavior(catalog, options = {}) {
|
|
|
21708
22169
|
.map(({ key, name, value }) => ({ key, name, value })) }),
|
|
21709
22170
|
};
|
|
21710
22171
|
},
|
|
21711
|
-
model: {
|
|
22172
|
+
model: fromPrevious({
|
|
21712
22173
|
PROMPT: (s, p) => typeof p == 'string' && p !== s.prompt ? { ...s, prompt: p } : ABORT,
|
|
21713
22174
|
COMPOSE: { HOST: host(start), [S]: sink(start) },
|
|
21714
22175
|
REPLY: {
|
|
@@ -21750,7 +22211,7 @@ function composeBehavior(catalog, options = {}) {
|
|
|
21750
22211
|
CONTROL: (s, c) => c && typeof c.key == 'string' && typeof c.name == 'string'
|
|
21751
22212
|
? { ...s, ui: { ...s.ui, [c.key]: { ...s.ui?.[c.key], [c.name]: c.value } } } : ABORT,
|
|
21752
22213
|
DONE: () => ABORT,
|
|
21753
|
-
},
|
|
22214
|
+
}),
|
|
21754
22215
|
})(rest);
|
|
21755
22216
|
}
|
|
21756
22217
|
|
|
@@ -25815,7 +26276,7 @@ const compose = /*#__PURE__*/ Object.assign((catalog, options) => composeBehavio
|
|
|
25815
26276
|
* is gone); set: the array with that element replaced (the state unchanged when it is gone).
|
|
25816
26277
|
* Side-effect free.
|
|
25817
26278
|
*/
|
|
25818
|
-
const byId = (field, id, idKey = 'id') => ({
|
|
26279
|
+
const byId = (field, id, idKey = 'id') => Object.defineProperty({
|
|
25819
26280
|
get: (s) => (Array.isArray(s?.[field]) ? s[field].find((x) => x?.[idKey] === id) : undefined),
|
|
25820
26281
|
set: (s, v) => {
|
|
25821
26282
|
const a = s?.[field];
|
|
@@ -25823,7 +26284,7 @@ const byId = (field, id, idKey = 'id') => ({
|
|
|
25823
26284
|
return s;
|
|
25824
26285
|
return { ...s, [field]: a.map((x) => (x?.[idKey] === id ? v : x)) };
|
|
25825
26286
|
},
|
|
25826
|
-
});
|
|
26287
|
+
}, BINDING, { value: [field, id, idKey] });
|
|
25827
26288
|
|
|
25828
26289
|
/**
|
|
25829
26290
|
* ESM ports of xstream's `flattenConcurrently` and `flattenSequentially` extras
|