sygnal 6.6.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 +132 -0
- package/README.md +2 -0
- package/dist/astro/index.cjs.js +32 -9
- package/dist/astro/index.cjs.js.map +1 -1
- package/dist/astro/index.mjs +32 -9
- 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.map +1 -1
- package/dist/devtools.esm.js.map +1 -1
- package/dist/diagnostics.cjs.js +87 -4
- package/dist/diagnostics.cjs.js.map +1 -1
- package/dist/diagnostics.esm.js +86 -5
- 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 +772 -318
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +198 -31
- package/dist/index.esm.js +772 -318
- 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/vite/plugin.cjs.js +19 -9
- package/dist/vite/plugin.cjs.js.map +1 -1
- package/dist/vite/plugin.mjs +19 -9
- 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 +2 -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/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/diagnostics/checks/fetch.ts +21 -1
- package/src/extra/diagnostics/checks/forms.ts +4 -3
- package/src/extra/diagnostics/checks/index.ts +1 -0
- package/src/extra/diagnostics/checks/next.ts +3 -0
- package/src/extra/diagnostics/checks/public.d.ts +12 -0
- package/src/extra/diagnostics/checks/timingAudit.ts +47 -0
- package/src/extra/diagnostics/codes.ts +13 -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 +197 -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/vite/graph.ts +11 -3
- package/src/vite/plugin.d.ts +7 -0
- package/src/vite/plugin.ts +10 -5
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
|
|
@@ -753,6 +755,8 @@ const DEV_CODE_SEVERITY = {
|
|
|
753
755
|
SYG634: 'info',
|
|
754
756
|
// PLAN-3 5-5 (fetch.ts)
|
|
755
757
|
SYG635: 'warn',
|
|
758
|
+
// PLAN-11 U-1 (fetch.ts)
|
|
759
|
+
SYG636: 'warn',
|
|
756
760
|
// PLAN-4 GS-2 (checks/elementCommands.ts, through the core bridge; renderComponent too)
|
|
757
761
|
SYG640: 'warn',
|
|
758
762
|
SYG641: 'error',
|
|
@@ -869,6 +873,12 @@ const DEV_CODE_SEVERITY = {
|
|
|
869
873
|
// PLAN-9 X-2: static only (sygnal-check); listed so the entry knows their titles
|
|
870
874
|
SYG253: 'warn',
|
|
871
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',
|
|
872
882
|
};
|
|
873
883
|
function docsUrlFor(code) {
|
|
874
884
|
return `${DOCS_BASE_URL}/reference/errors#${code.toLowerCase()}`;
|
|
@@ -1264,6 +1274,8 @@ function normalize$1(view, src) {
|
|
|
1264
1274
|
const list = [];
|
|
1265
1275
|
for (const sink in e)
|
|
1266
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'));
|
|
1267
1279
|
handlers.set(key, list);
|
|
1268
1280
|
}
|
|
1269
1281
|
const c = normCalc(name, src.calculated, src.initialState);
|
|
@@ -1855,7 +1867,7 @@ function handle(inst, type, data, cause, src) {
|
|
|
1855
1867
|
return;
|
|
1856
1868
|
const def = inst.def;
|
|
1857
1869
|
const pre = inst.cell.get();
|
|
1858
|
-
let props, outs;
|
|
1870
|
+
let props, afterProps, post = pre, outs;
|
|
1859
1871
|
const next = (t, d, ms = 10, effect) => {
|
|
1860
1872
|
if (typeof ms !== 'number')
|
|
1861
1873
|
fail$2('SYG215', inst, `next() delay in '${type}' must be a number`, "Use next('ACTION', data, ms)");
|
|
@@ -1870,35 +1882,28 @@ function handle(inst, type, data, cause, src) {
|
|
|
1870
1882
|
};
|
|
1871
1883
|
for (const [sink, h0] of hs) {
|
|
1872
1884
|
const h = H.wrapHandler ? H.wrapHandler(viewOf(inst), type, sink, h0) || h0 : h0;
|
|
1873
|
-
|
|
1874
|
-
if (typeof h != 'function')
|
|
1875
|
-
continue;
|
|
1876
|
-
const failed = (e) => app.caught(inst, 'SYG214', `EFFECT handler '${type}' threw`, e, 'effect', type);
|
|
1877
|
-
try {
|
|
1878
|
-
const p = { ...(props || (props = inst_props(inst, pre))), signal: (inst.ac || (inst.ac = globalThis.AbortController && new AbortController()))?.signal };
|
|
1879
|
-
const r = h(pre, data, (t, d, ms) => next(t, d, ms, true), p);
|
|
1880
|
-
if (r?.then)
|
|
1881
|
-
r.then(null, failed);
|
|
1882
|
-
else if (r !== undefined && !isAbort(r))
|
|
1883
|
-
warn('SYG219', inst, `EFFECT handler '${type}' returned a value, which is ignored`, 'Use a STATE or driver sink');
|
|
1884
|
-
}
|
|
1885
|
-
catch (e) {
|
|
1886
|
-
failed(e);
|
|
1887
|
-
}
|
|
1888
|
-
continue;
|
|
1889
|
-
}
|
|
1885
|
+
const effect = sink == 'EFFECT';
|
|
1890
1886
|
let v;
|
|
1891
1887
|
if (typeof h == 'function') {
|
|
1892
1888
|
try {
|
|
1893
|
-
|
|
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
|
+
}
|
|
1894
1897
|
}
|
|
1895
1898
|
catch (e) {
|
|
1896
|
-
|
|
1899
|
+
handlerFailed(inst, type, sink, e);
|
|
1897
1900
|
continue;
|
|
1898
1901
|
}
|
|
1899
1902
|
}
|
|
1900
1903
|
else
|
|
1901
1904
|
v = h === undefined || h === true ? data : h;
|
|
1905
|
+
if (effect)
|
|
1906
|
+
continue;
|
|
1902
1907
|
if (sink == 'STATE' && (isAbort(v) || v === pre)) {
|
|
1903
1908
|
// D205: the DOM event being dispatched (window.event) is the one this action came from
|
|
1904
1909
|
if (/^(input|change)$/.test(globalThis.event?.type))
|
|
@@ -1911,6 +1916,11 @@ function handle(inst, type, data, cause, src) {
|
|
|
1911
1916
|
H.onReducer?.(viewOf(inst), type, pre, v);
|
|
1912
1917
|
dbg(inst, () => `<${type}> State reducer added`);
|
|
1913
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;
|
|
1914
1924
|
// GS-12: this app's DOM driver patches inside a View Transition (makeViewTransitionDOMDriver
|
|
1915
1925
|
// reads the flag on its IsolateModule); includes?.: a non-array static lists nothing (G-228).
|
|
1916
1926
|
// Only for a state that changed structurally: today an equal one renders nothing (its
|
|
@@ -1959,9 +1969,14 @@ function handle(inst, type, data, cause, src) {
|
|
|
1959
1969
|
send$1(inst, outs[i], outs[i + 1]);
|
|
1960
1970
|
}
|
|
1961
1971
|
}
|
|
1962
|
-
/** the
|
|
1963
|
-
function
|
|
1964
|
-
|
|
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 };
|
|
1965
1980
|
}
|
|
1966
1981
|
/**
|
|
1967
1982
|
* A sink value of an instance: stamped with its sender (EVENTS; object values for a reply-capable
|
|
@@ -2195,6 +2210,19 @@ const flat = (v) => {
|
|
|
2195
2210
|
* - dispose(): DISPOSE, onDispose, dispose$, the EFFECT signal aborted, the subtree, then the
|
|
2196
2211
|
* streams (the scoped _remove swap: no xstream stop timers, D165/Q18).
|
|
2197
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
|
+
}
|
|
2198
2226
|
const ERR_FIX$1 = 'See the attached error';
|
|
2199
2227
|
// 4-I G-557: a fragment root (a Collection's): each of its top-level elements is marked.
|
|
2200
2228
|
// 4-K G-572: a text root (no element; an attribute would land on a Text node) gets `ready: false`
|
|
@@ -2237,8 +2265,8 @@ const idOf$1 = (n, path) => {
|
|
|
2237
2265
|
return `${n.sel}::${(s && s.replace(/"/g, '')) || path}`;
|
|
2238
2266
|
};
|
|
2239
2267
|
/**
|
|
2240
|
-
* a component vnode's props, without `resetState` (D174: read at creation,
|
|
2241
|
-
* 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.
|
|
2242
2270
|
*/
|
|
2243
2271
|
function propsOf(p) {
|
|
2244
2272
|
if (!p)
|
|
@@ -2302,10 +2330,14 @@ const EMPTY_CTX = Object.freeze({});
|
|
|
2302
2330
|
/** a view read every key (spread, Object.keys): any context change re-renders it */
|
|
2303
2331
|
const ALL = '\u0000all';
|
|
2304
2332
|
let IDS = 0;
|
|
2305
|
-
/**
|
|
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
|
+
*/
|
|
2306
2337
|
class Failed {
|
|
2307
|
-
constructor(outv) {
|
|
2338
|
+
constructor(outv, props) {
|
|
2308
2339
|
this.outv = outv;
|
|
2340
|
+
this.props = props;
|
|
2309
2341
|
this.lr = true;
|
|
2310
2342
|
this.ready = true;
|
|
2311
2343
|
this.def = null;
|
|
@@ -2313,7 +2345,7 @@ class Failed {
|
|
|
2313
2345
|
this.shown = true;
|
|
2314
2346
|
}
|
|
2315
2347
|
render() { return this.outv; }
|
|
2316
|
-
setProps() { }
|
|
2348
|
+
setProps(p) { this.props = p; }
|
|
2317
2349
|
dispose() { }
|
|
2318
2350
|
}
|
|
2319
2351
|
class Inst {
|
|
@@ -2321,6 +2353,7 @@ class Inst {
|
|
|
2321
2353
|
this.app = app;
|
|
2322
2354
|
this.def = def;
|
|
2323
2355
|
this.parent = parent;
|
|
2356
|
+
this.base = base;
|
|
2324
2357
|
this.dom = dom;
|
|
2325
2358
|
this.scope = scope;
|
|
2326
2359
|
this.kind = kind;
|
|
@@ -2487,10 +2520,32 @@ class Inst {
|
|
|
2487
2520
|
}
|
|
2488
2521
|
/** called after each action and before each render pass: STATE.stream emits a new state */
|
|
2489
2522
|
notify() {
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
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
|
+
}
|
|
2494
2549
|
}
|
|
2495
2550
|
}
|
|
2496
2551
|
dispose$() {
|
|
@@ -2739,7 +2794,9 @@ class Inst {
|
|
|
2739
2794
|
for (const r of resolvers)
|
|
2740
2795
|
view = r(view, this) || view;
|
|
2741
2796
|
let k = this.kids.get(id);
|
|
2742
|
-
|
|
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]))) {
|
|
2743
2800
|
k.dispose();
|
|
2744
2801
|
this.kids.delete(id);
|
|
2745
2802
|
k = undefined;
|
|
@@ -2747,7 +2804,12 @@ class Inst {
|
|
|
2747
2804
|
if (k)
|
|
2748
2805
|
k.setProps(props, children, n, id);
|
|
2749
2806
|
else
|
|
2750
|
-
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
|
+
}
|
|
2751
2813
|
return;
|
|
2752
2814
|
}
|
|
2753
2815
|
if (posts[sel] && !(ps || (ps = [])).includes(sel))
|
|
@@ -2772,11 +2834,11 @@ class Inst {
|
|
|
2772
2834
|
return make(this, props, children, id, n);
|
|
2773
2835
|
}
|
|
2774
2836
|
catch (err) {
|
|
2775
|
-
return this.failed(err);
|
|
2837
|
+
return this.failed(err, props);
|
|
2776
2838
|
}
|
|
2777
2839
|
}
|
|
2778
2840
|
/** the owner's error fallback for a child that failed to instantiate (SYG408) */
|
|
2779
|
-
failed(err) {
|
|
2841
|
+
failed(err, props) {
|
|
2780
2842
|
const e = err instanceof Error ? err : new Error(String(err));
|
|
2781
2843
|
let out = errorDiv$1(this.def.name);
|
|
2782
2844
|
if (this.def.onError) {
|
|
@@ -2788,7 +2850,7 @@ class Inst {
|
|
|
2788
2850
|
}
|
|
2789
2851
|
}
|
|
2790
2852
|
this.app.caught(this, 'SYG408', 'Sub-component threw; rendering the error fallback', e, 'instantiate');
|
|
2791
|
-
return new Failed(out);
|
|
2853
|
+
return new Failed(out, props);
|
|
2792
2854
|
}
|
|
2793
2855
|
/** a tag child: state="key" | state={lens} | none (the parent's state) | isolatedState (D174: `resetState`) */
|
|
2794
2856
|
child(view, props, children, id, reset = false) {
|
|
@@ -2807,7 +2869,7 @@ class Inst {
|
|
|
2807
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);
|
|
2808
2870
|
}
|
|
2809
2871
|
catch (err) {
|
|
2810
|
-
return this.failed(err);
|
|
2872
|
+
return this.failed(err, props);
|
|
2811
2873
|
}
|
|
2812
2874
|
}
|
|
2813
2875
|
inject(n, path) {
|
|
@@ -2870,8 +2932,7 @@ class Inst {
|
|
|
2870
2932
|
tearDown(() => { for (const s of subs)
|
|
2871
2933
|
s.unsubscribe(); }, app.dq, this);
|
|
2872
2934
|
this.subs = [];
|
|
2873
|
-
|
|
2874
|
-
app.watchers.delete(this);
|
|
2935
|
+
app.watchers.delete(this);
|
|
2875
2936
|
this.hub = null;
|
|
2876
2937
|
}
|
|
2877
2938
|
}
|
|
@@ -2970,7 +3031,7 @@ function arrayCell(owner, from) {
|
|
|
2970
3031
|
if (typeof from == 'string')
|
|
2971
3032
|
return keyCell(oc, from, owner.def.calcNames?.has(pathOf$1(from)[0]) && name, undefined, 'Collection');
|
|
2972
3033
|
if (isObj$2(from) && typeof from.get == 'function') {
|
|
2973
|
-
let lp = {}, lv;
|
|
3034
|
+
let lp = {}, lv, raw;
|
|
2974
3035
|
return {
|
|
2975
3036
|
get() {
|
|
2976
3037
|
const p = oc.get();
|
|
@@ -2986,7 +3047,7 @@ function arrayCell(owner, from) {
|
|
|
2986
3047
|
owner.app.appError(owner, e, 'view');
|
|
2987
3048
|
return lv;
|
|
2988
3049
|
}
|
|
2989
|
-
lv = g;
|
|
3050
|
+
raw = lv = g;
|
|
2990
3051
|
if (!Array.isArray(lv)) {
|
|
2991
3052
|
warn('SYG401', name, "Collection 'from' getter returned a non-array; it renders nothing", 'Return an array from get()', lv);
|
|
2992
3053
|
lv = NONE$1;
|
|
@@ -2994,6 +3055,7 @@ function arrayCell(owner, from) {
|
|
|
2994
3055
|
}
|
|
2995
3056
|
return lv;
|
|
2996
3057
|
},
|
|
3058
|
+
has() { this.get(); return raw !== undefined; },
|
|
2997
3059
|
set(v) {
|
|
2998
3060
|
if (typeof from.set != 'function')
|
|
2999
3061
|
return;
|
|
@@ -5355,8 +5417,11 @@ const validateWith = async (schema, value) => {
|
|
|
5355
5417
|
* declarations in src/index.d.ts and docs/guide/drivers.md. Summary:
|
|
5356
5418
|
*
|
|
5357
5419
|
* - Request (sink value): `{ url, ok?, error?, key?, category?, method?, headers?, query?, json?,
|
|
5358
|
-
* body?, latest?, timeoutMs?, parse?, init? }` or a URL string. `json` is stringified
|
|
5359
|
-
* 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
|
|
5360
5425
|
* query string (null/undefined values skipped, arrays repeat the key, before any #fragment).
|
|
5361
5426
|
* Other fetch options (credentials, mode, cache, ...) go under `init` (R4-5); other top-level
|
|
5362
5427
|
* keys are the app's own: not sent, but returned on `request`.
|
|
@@ -5433,6 +5498,15 @@ const validateWith = async (schema, value) => {
|
|
|
5433
5498
|
* the request itself) into requests sent again in parallel and combined, at most 3 deep. One
|
|
5434
5499
|
* failure fails the request once (`error.status` / `error.body`). No cache entry sharing, no
|
|
5435
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.
|
|
5436
5510
|
* - dispose / sink completion aborts everything in flight.
|
|
5437
5511
|
* - `fetch` is read at request time (options.fetch, else globalThis.fetch), so test stubs work.
|
|
5438
5512
|
* - PLAN-3 5-1 (H-9): renderComponent's HTTP fake runs this driver over an in-memory fetch;
|
|
@@ -5526,6 +5600,81 @@ const mergeHeaders = (...all) => {
|
|
|
5526
5600
|
h.forEach((v, k) => { out[k] = v; });
|
|
5527
5601
|
return out;
|
|
5528
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
|
+
};
|
|
5529
5678
|
const IDEMPOTENT$1 = /^(GET|HEAD)$/i;
|
|
5530
5679
|
// D80: an invalidate value matches a request by tag, URL prefix ('/…') or predicate
|
|
5531
5680
|
const hit = (x, q) => {
|
|
@@ -5550,6 +5699,22 @@ function makeFetchDriver(options = {}) {
|
|
|
5550
5699
|
// q (the request), timer, f (the fetch it waits on), ok / ko (its outcomes) }
|
|
5551
5700
|
const inflight = new Map();
|
|
5552
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
|
+
});
|
|
5553
5718
|
// R4-4: replies held until the first select() listener, failures until the first errors()
|
|
5554
5719
|
// listener (or the next macrotask, when they're logged if nothing listens)
|
|
5555
5720
|
const started = [false, false];
|
|
@@ -5612,6 +5777,10 @@ function makeFetchDriver(options = {}) {
|
|
|
5612
5777
|
return {
|
|
5613
5778
|
select: make(false),
|
|
5614
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
|
+
},
|
|
5615
5784
|
isolateSource: (_, scope) => source(ns.concat(scope)),
|
|
5616
5785
|
isolateSink: (sink$, scope) => sink$.map((req) => tagRequest(req, scope)),
|
|
5617
5786
|
// PLAN-4.6: the same per value (the core scopes a request without a stream per instance)
|
|
@@ -5731,7 +5900,7 @@ function makeFetchDriver(options = {}) {
|
|
|
5731
5900
|
req = { url: req };
|
|
5732
5901
|
if (!req || typeof req != 'object' || disposed || !allowed$1(req, 'makeFetchDriver'))
|
|
5733
5902
|
return;
|
|
5734
|
-
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;
|
|
5735
5904
|
if ('invalidate' in req)
|
|
5736
5905
|
return inval(req.invalidate);
|
|
5737
5906
|
if (prefetch)
|
|
@@ -5783,7 +5952,9 @@ function makeFetchDriver(options = {}) {
|
|
|
5783
5952
|
}
|
|
5784
5953
|
const scope = scopeKey(req);
|
|
5785
5954
|
// reply actions: keyed per (sender, key ?? ok ?? error); a resource per (sender, '\0' + name)
|
|
5786
|
-
|
|
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;
|
|
5787
5958
|
if (sender !== undefined && (typeof abort == 'string' || (abort && key !== undefined)))
|
|
5788
5959
|
return cancel(r => r.sender === sender && r.rk === (key ?? abort));
|
|
5789
5960
|
// per (scope, category): another component's requests are never cancelled
|
|
@@ -5793,7 +5964,7 @@ function makeFetchDriver(options = {}) {
|
|
|
5793
5964
|
cancel(rk === undefined ? r => r.rk === undefined && r.scope === scope && r.category === category : r => r.sender === sender && r.rk === rk);
|
|
5794
5965
|
const base = options.init;
|
|
5795
5966
|
// a request's URL and fetch() init (the batch's sub-requests inherit the request's fields)
|
|
5796
|
-
const prep = ({ url, method, headers, query, json, body, init: own }) => {
|
|
5967
|
+
const prep = ({ url, method, headers, query, json, multipart, body, init: own }) => {
|
|
5797
5968
|
const h = mergeHeaders(options.headers, base?.headers, own?.headers, headers);
|
|
5798
5969
|
let b = body ?? own?.body;
|
|
5799
5970
|
if (json !== undefined) {
|
|
@@ -5801,6 +5972,12 @@ function makeFetchDriver(options = {}) {
|
|
|
5801
5972
|
if (!Object.keys(h).some(k => k.toLowerCase() == 'content-type'))
|
|
5802
5973
|
h['content-type'] = 'application/json';
|
|
5803
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
|
+
}
|
|
5804
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 }];
|
|
5805
5982
|
};
|
|
5806
5983
|
const [href, fetchInit] = prep(req);
|
|
@@ -5809,7 +5986,8 @@ function makeFetchDriver(options = {}) {
|
|
|
5809
5986
|
// parts, at most 3 deep), their parsed bodies merged by `combine` into one reply. The test
|
|
5810
5987
|
// fake (_whole) answers such a request as one
|
|
5811
5988
|
const { batch, split, combine = (x) => x } = options._whole ? {} : req;
|
|
5812
|
-
|
|
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);
|
|
5813
5991
|
const id = ++seq;
|
|
5814
5992
|
const r = { category, scope, sender, rk };
|
|
5815
5993
|
inflight.set(id, r);
|
|
@@ -5857,7 +6035,19 @@ function makeFetchDriver(options = {}) {
|
|
|
5857
6035
|
const parser = parserOf(parse || options.parse);
|
|
5858
6036
|
// the global fetch is called as a method: a detached window.fetch throws "Illegal invocation"
|
|
5859
6037
|
const g = globalThis;
|
|
5860
|
-
|
|
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
|
+
}
|
|
5861
6051
|
// the outcome, to every request waiting on this fetch (the failure once, after the last attempt)
|
|
5862
6052
|
const end = (e, x, status) => {
|
|
5863
6053
|
done(F);
|
|
@@ -6019,9 +6209,10 @@ function queryCache(o = {}) {
|
|
|
6019
6209
|
const key = (q, res) => {
|
|
6020
6210
|
if (typeof q == 'string')
|
|
6021
6211
|
q = { url: q };
|
|
6022
|
-
const { method, json, body, parse, query } = q;
|
|
6023
|
-
const m = (method || (json !== undefined || body !== undefined ? 'POST' : 'GET')).toUpperCase();
|
|
6024
|
-
|
|
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 ?? '';
|
|
6025
6216
|
if (q.cache === false || typeof b != 'string' || typeof parse == 'function' || parse == 'response' ||
|
|
6026
6217
|
!(q.cache || q.staleTime != null || (res && IDEMPOTENT.test(m))))
|
|
6027
6218
|
return;
|
|
@@ -6090,6 +6281,272 @@ function queryCache(o = {}) {
|
|
|
6090
6281
|
return C;
|
|
6091
6282
|
}
|
|
6092
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
|
+
|
|
6093
6550
|
/*
|
|
6094
6551
|
* makeSocketDriver(options?) (PLAN-3 §1.3): WebSocket and server-sent events, declared per
|
|
6095
6552
|
* component instance and answered with reply actions (./replies.ts). Docs on the declarations
|
|
@@ -6098,7 +6555,7 @@ function queryCache(o = {}) {
|
|
|
6098
6555
|
* - Sink values:
|
|
6099
6556
|
* - `{ connections: { [name]: spec | falsy } }`: the sender's whole set of connections. Diffed
|
|
6100
6557
|
* per (sender, name): a new name opens, a removed or falsy one closes, a changed URL /
|
|
6101
|
-
* protocols / withCredentials / share closes and reopens, anything else (action names,
|
|
6558
|
+
* protocols / withCredentials / headers / share closes and reopens, anything else (action names,
|
|
6102
6559
|
* reconnect) only rebinds.
|
|
6103
6560
|
* - `{ to: name, json? | text? | binary? }`: send on the sender's connection `name` (json is
|
|
6104
6561
|
* stringified). Queued while (re)connecting, bounded (`queueLimit`, oldest dropped). A send
|
|
@@ -6123,6 +6580,10 @@ function queryCache(o = {}) {
|
|
|
6123
6580
|
* - A disposed sender's connections close (and their retries stop); app dispose closes all.
|
|
6124
6581
|
* - No WebSocket / EventSource (SSR): nothing opens, nothing is reported.
|
|
6125
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.
|
|
6126
6587
|
*/
|
|
6127
6588
|
const SCOPE$1 = '__sygnalScope';
|
|
6128
6589
|
const scopeOf = (v) => v[SCOPE$1] || [];
|
|
@@ -6222,12 +6683,13 @@ function makeSocketDriver(options = {}) {
|
|
|
6222
6683
|
};
|
|
6223
6684
|
const connect = (t) => {
|
|
6224
6685
|
t.timer = 0;
|
|
6225
|
-
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;
|
|
6226
6688
|
if (typeof C != 'function')
|
|
6227
6689
|
return;
|
|
6228
6690
|
let s;
|
|
6229
6691
|
try {
|
|
6230
|
-
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);
|
|
6231
6693
|
}
|
|
6232
6694
|
catch (error) {
|
|
6233
6695
|
// a URL the constructor rejects never opens: no retry
|
|
@@ -6331,7 +6793,7 @@ function makeSocketDriver(options = {}) {
|
|
|
6331
6793
|
const sse = typeof spec.sse == 'string';
|
|
6332
6794
|
const url = resolve(sse ? spec.sse : spec.socket, sse);
|
|
6333
6795
|
// the connection's identity: a change reconnects, anything else only rebinds
|
|
6334
|
-
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;
|
|
6335
6797
|
if (old && old.key === key) {
|
|
6336
6798
|
old.spec = spec;
|
|
6337
6799
|
if (old.t)
|
|
@@ -6408,7 +6870,7 @@ const dec = (s) => { try {
|
|
|
6408
6870
|
catch (_) {
|
|
6409
6871
|
return s;
|
|
6410
6872
|
} };
|
|
6411
|
-
const g$
|
|
6873
|
+
const g$5 = globalThis;
|
|
6412
6874
|
const PARAM$1 = /:(\w+)/g;
|
|
6413
6875
|
// G-571: synchronous navigations (redirects) before a microtask, after which each waits a task
|
|
6414
6876
|
const HOPS = 32;
|
|
@@ -6416,7 +6878,7 @@ const HOPS = 32;
|
|
|
6416
6878
|
const paramsOf$2 = (pat) => (pat.match(PARAM$1) || []).map(p => p.slice(1));
|
|
6417
6879
|
// the core's coded error() (published on the diagnostics bridge): no import, so the router
|
|
6418
6880
|
// costs nothing more standalone; prints in production too (like SYG611)
|
|
6419
|
-
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);
|
|
6420
6882
|
function makeRouter(options = {}) {
|
|
6421
6883
|
const { mode, prefetch, navigate } = options, routes = options.routes || {};
|
|
6422
6884
|
const base = (options.base || '').replace(/\/$/, '');
|
|
@@ -6425,7 +6887,7 @@ function makeRouter(options = {}) {
|
|
|
6425
6887
|
const doScroll = options.scroll ?? !navigate;
|
|
6426
6888
|
const href = (name, params = {}, query, hash) => {
|
|
6427
6889
|
// dev entry only (SYG130: unknown name, missing or extra params)
|
|
6428
|
-
g$
|
|
6890
|
+
g$5.__SYGNAL_DIAGNOSTICS__?.routerHref?.(routes, name, params);
|
|
6429
6891
|
const pat = routes[name];
|
|
6430
6892
|
const path = pat == null || pat == '*' ? '/' : pat.replace(PARAM$1, (_, k) => enc(params[k] ?? ''));
|
|
6431
6893
|
const q = query && new URLSearchParams(Object.entries(query).filter(([, v]) => v != null)) + '';
|
|
@@ -6535,7 +6997,7 @@ function makeRouter(options = {}) {
|
|
|
6535
6997
|
if (!hops++)
|
|
6536
6998
|
queueMicrotask(() => { hops = 0; });
|
|
6537
6999
|
if (hops > HOPS) {
|
|
6538
|
-
if (g$
|
|
7000
|
+
if (g$5.__SYGNAL_DIAGNOSTICS__?.hooks?.on?.() && !warned++)
|
|
6539
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);
|
|
6540
7002
|
setTimeout(send);
|
|
6541
7003
|
}
|
|
@@ -8077,7 +8539,7 @@ function makeHeadDriver(options = {}) {
|
|
|
8077
8539
|
* - An invalid spec (non-positive `every`, negative `after`, no action) doesn't start; the dev
|
|
8078
8540
|
* entry reports SYG422 through the `timerSpec` diagnostics hook.
|
|
8079
8541
|
*/
|
|
8080
|
-
const g$
|
|
8542
|
+
const g$4 = globalThis;
|
|
8081
8543
|
const now$1 = () => Date.now();
|
|
8082
8544
|
const valid = (s) => s.frame
|
|
8083
8545
|
? typeof s.frame == 'string'
|
|
@@ -8087,7 +8549,7 @@ const valid = (s) => s.frame
|
|
|
8087
8549
|
// changes) the timer clears the pending one.
|
|
8088
8550
|
const start$1 = (s, send, e) => {
|
|
8089
8551
|
let id, n = 0, last;
|
|
8090
|
-
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;
|
|
8091
8553
|
const later = (ms) => id = raf ? requestAnimationFrame(tick) : setTimeout(tick, ms);
|
|
8092
8554
|
// a frame's `ts` is requestAnimationFrame's timestamp (the fallback passes none: the clock)
|
|
8093
8555
|
const tick = (ts = now$1()) => {
|
|
@@ -8132,7 +8594,7 @@ const timerDriver = (runners) => (sink$) => {
|
|
|
8132
8594
|
if (e.ok = valid(s))
|
|
8133
8595
|
e.x = start$1(s, (type, data) => reply(id, type, data), e);
|
|
8134
8596
|
else
|
|
8135
|
-
g$
|
|
8597
|
+
g$4.__SYGNAL_DIAGNOSTICS__?.timerSpec?.(n, s, c);
|
|
8136
8598
|
}
|
|
8137
8599
|
if (!d)
|
|
8138
8600
|
runners.delete(id);
|
|
@@ -8190,10 +8652,10 @@ const makeTimerDriver = () => timerDriver(new Map());
|
|
|
8190
8652
|
* an intersection / resize declaration with nothing to observe (no DOM source bound). A
|
|
8191
8653
|
* component declaring `browser` with no driver is SYG643, as timers.
|
|
8192
8654
|
*/
|
|
8193
|
-
const g$
|
|
8655
|
+
const g$3 = globalThis;
|
|
8194
8656
|
const noop = () => { };
|
|
8195
8657
|
const KINDS = ['intersection', 'resize', 'media', 'storage', 'visibility', 'online', 'geolocation'];
|
|
8196
|
-
const diag = (...a) => g$
|
|
8658
|
+
const diag = (...a) => g$3.__SYGNAL_DIAGNOSTICS__?.browserSource?.(...a);
|
|
8197
8659
|
// the driver names in use (the definition hook looks each up in a declaring instance's sources)
|
|
8198
8660
|
const names$1 = new Set();
|
|
8199
8661
|
let hooked;
|
|
@@ -8279,16 +8741,16 @@ const track = (c, kind, target, Obs, opts, data) => {
|
|
|
8279
8741
|
};
|
|
8280
8742
|
/** Element visibility: `{ intersection: '.cover' | true, action, threshold?, rootMargin? }`, data `{ visible, ratio, index, dataset }` */
|
|
8281
8743
|
const intersectionSource = { d: {
|
|
8282
|
-
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 })),
|
|
8283
8745
|
} };
|
|
8284
8746
|
/** Element size: `{ resize: '.chart' | true, action }`, data `{ width, height, index, dataset }` (the content box) */
|
|
8285
8747
|
const resizeSource = { d: {
|
|
8286
|
-
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 })),
|
|
8287
8749
|
} };
|
|
8288
8750
|
/** A media query: `{ media: '(prefers-color-scheme: dark)', action }`, data `{ matches, media }` */
|
|
8289
8751
|
const mediaSource = { d: {
|
|
8290
8752
|
media: (s, c) => {
|
|
8291
|
-
const m = g$
|
|
8753
|
+
const m = g$3.matchMedia?.(s.media);
|
|
8292
8754
|
if (!m)
|
|
8293
8755
|
return;
|
|
8294
8756
|
const f = () => c.send({ matches: m.matches, media: s.media });
|
|
@@ -8297,7 +8759,7 @@ const mediaSource = { d: {
|
|
|
8297
8759
|
return () => m.removeEventListener('change', f);
|
|
8298
8760
|
},
|
|
8299
8761
|
} };
|
|
8300
|
-
const area = (s) => g$
|
|
8762
|
+
const area = (s) => g$3[s.area == 'session' ? 'sessionStorage' : 'localStorage'];
|
|
8301
8763
|
const failed$1 = (e) => ({ name: e?.name, message: e?.message });
|
|
8302
8764
|
/**
|
|
8303
8765
|
* A storage key (read and observed, other tabs' writes included): `{ storage: 'theme', action,
|
|
@@ -8326,8 +8788,8 @@ const storageSource = {
|
|
|
8326
8788
|
};
|
|
8327
8789
|
f();
|
|
8328
8790
|
watching.add(f);
|
|
8329
|
-
g$
|
|
8330
|
-
return () => { watching.delete(f); g$
|
|
8791
|
+
g$3.addEventListener?.('storage', f);
|
|
8792
|
+
return () => { watching.delete(f); g$3.removeEventListener?.('storage', f); };
|
|
8331
8793
|
} },
|
|
8332
8794
|
c: (() => {
|
|
8333
8795
|
const write = (v, value, ok, fail) => {
|
|
@@ -8354,7 +8816,7 @@ const storageSource = {
|
|
|
8354
8816
|
/** The document's visibility: `{ visibility: true, action }`, data `{ visible }` */
|
|
8355
8817
|
const visibilitySource = { d: {
|
|
8356
8818
|
visibility: (_, c) => {
|
|
8357
|
-
const d = g$
|
|
8819
|
+
const d = g$3.document;
|
|
8358
8820
|
if (!d)
|
|
8359
8821
|
return;
|
|
8360
8822
|
const f = () => c.send({ visible: d.visibilityState != 'hidden' });
|
|
@@ -8366,13 +8828,13 @@ const visibilitySource = { d: {
|
|
|
8366
8828
|
/** The network: `{ online: true, action }`, data `{ online }` */
|
|
8367
8829
|
const onlineSource = { d: {
|
|
8368
8830
|
online: (_, c) => {
|
|
8369
|
-
if (!g$
|
|
8831
|
+
if (!g$3.navigator || !g$3.addEventListener)
|
|
8370
8832
|
return;
|
|
8371
|
-
const f = () => c.send({ online: g$
|
|
8833
|
+
const f = () => c.send({ online: g$3.navigator.onLine });
|
|
8372
8834
|
f();
|
|
8373
|
-
g$
|
|
8374
|
-
g$
|
|
8375
|
-
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); };
|
|
8376
8838
|
},
|
|
8377
8839
|
} };
|
|
8378
8840
|
/**
|
|
@@ -8382,7 +8844,7 @@ const onlineSource = { d: {
|
|
|
8382
8844
|
*/
|
|
8383
8845
|
const geolocationSource = { d: {
|
|
8384
8846
|
geolocation: (s, c) => {
|
|
8385
|
-
const geo = g$
|
|
8847
|
+
const geo = g$3.navigator?.geolocation;
|
|
8386
8848
|
if (!geo)
|
|
8387
8849
|
return c.fail({ code: 2, message: 'Geolocation is not available' });
|
|
8388
8850
|
const id = geo.watchPosition((p) => {
|
|
@@ -8398,11 +8860,11 @@ const geolocationSource = { d: {
|
|
|
8398
8860
|
*/
|
|
8399
8861
|
const clipboardSource = { c: {
|
|
8400
8862
|
copy: (v, ok, fail) => {
|
|
8401
|
-
const text = '' + v.copy, cb = g$
|
|
8863
|
+
const text = '' + v.copy, cb = g$3.navigator?.clipboard;
|
|
8402
8864
|
cb ? cb.writeText(text).then(() => ok({ text }), (e) => fail(failed$1(e))) : fail({ name: 'NotSupportedError', message: 'Clipboard is not available' });
|
|
8403
8865
|
},
|
|
8404
8866
|
paste: (_, ok, fail) => {
|
|
8405
|
-
const cb = g$
|
|
8867
|
+
const cb = g$3.navigator?.clipboard;
|
|
8406
8868
|
cb ? cb.readText().then((text) => ok({ text }), (e) => fail(failed$1(e))) : fail({ name: 'NotSupportedError', message: 'Clipboard is not available' });
|
|
8407
8869
|
},
|
|
8408
8870
|
} };
|
|
@@ -9107,6 +9569,9 @@ class App {
|
|
|
9107
9569
|
}
|
|
9108
9570
|
/** STATE.stream: the instances whose state changed emit it */
|
|
9109
9571
|
notify() {
|
|
9572
|
+
if (this.nv === this.ver)
|
|
9573
|
+
return;
|
|
9574
|
+
this.nv = this.ver;
|
|
9110
9575
|
for (const i of this.watchers)
|
|
9111
9576
|
i.notify();
|
|
9112
9577
|
}
|
|
@@ -9234,7 +9699,7 @@ class App {
|
|
|
9234
9699
|
this.ran = false;
|
|
9235
9700
|
this.last = v;
|
|
9236
9701
|
this.hooks.onPatch?.(v);
|
|
9237
|
-
this.vdomL?.next(v);
|
|
9702
|
+
this.vdomL?.next(v ?? null);
|
|
9238
9703
|
}
|
|
9239
9704
|
if (this.dq.length)
|
|
9240
9705
|
this.stopLater();
|
|
@@ -9923,7 +10388,7 @@ const W$1 = '__sw';
|
|
|
9923
10388
|
const HOST = /^(id|class(Name)?|style|title|name|placeholder|role|tab[iI]ndex|hidden|lang|dir|attrs)$|^(aria|data)-/;
|
|
9924
10389
|
/** the dev entry's reporter (checks/widgets.ts); returns the diagnostic when one was reported
|
|
9925
10390
|
* (r: G-580, a 661 the widget reported through error(e), not a throwing update) */
|
|
9926
|
-
const dev$
|
|
10391
|
+
const dev$7 = (code, w, o, x, r) => globalThis.__SYGNAL_DIAGNOSTICS__?.widget?.(code, w, o, x, r);
|
|
9927
10392
|
let ids$1 = 0;
|
|
9928
10393
|
// shallow; G-362: an object host prop (style, attrs, a class map) compares one level deep, so a
|
|
9929
10394
|
// new object with the same entries is no change (snabbdom patches the host from it anyway)
|
|
@@ -9936,16 +10401,16 @@ const same$2 = (a, b, d) => {
|
|
|
9936
10401
|
// reported through error(e)
|
|
9937
10402
|
function fail$1(o, w, k, code, e, r) {
|
|
9938
10403
|
e = e instanceof Error ? e : Error(e);
|
|
9939
|
-
dev$
|
|
10404
|
+
dev$7(code, w, o, e, r) || console.error(`[Sygnal SYG${code}]`, e);
|
|
9940
10405
|
o.app.appError(o, e, 'widget');
|
|
9941
10406
|
if (code < 662 && !o.disposed)
|
|
9942
10407
|
(o.$wf || (o.$wf = new Map())).set(k, [e]), o.refresh();
|
|
9943
10408
|
}
|
|
9944
10409
|
function mount(v) {
|
|
9945
10410
|
const el = v.elm, { ww: w, wp: p, wo: o } = v.data;
|
|
9946
|
-
const d = (n, detail) => { dev$
|
|
10411
|
+
const d = (n, detail) => { dev$7(140, w, o, n); el.dispatchEvent(new CustomEvent(n, { detail, bubbles: true })); };
|
|
9947
10412
|
let i;
|
|
9948
|
-
dev$
|
|
10413
|
+
dev$7(144, w, o, el);
|
|
9949
10414
|
// G-409: mount's fourth parameter reports a later failure (an adapter's own redraw): SYG661,
|
|
9950
10415
|
// the owner's onError fallback in this instance's place, as for a throwing update (G-580: the
|
|
9951
10416
|
// dev text says error(e) reported it)
|
|
@@ -10065,7 +10530,7 @@ function defineWidget(def) {
|
|
|
10065
10530
|
},
|
|
10066
10531
|
$ssr: host,
|
|
10067
10532
|
// D190/D199: a widget tag is not a selector (SYG143 in dev): select its className or a control
|
|
10068
|
-
toString: () => (dev$
|
|
10533
|
+
toString: () => (dev$7(143, w), ':not(*)'),
|
|
10069
10534
|
});
|
|
10070
10535
|
}
|
|
10071
10536
|
|
|
@@ -10114,6 +10579,30 @@ function defineWidget(def) {
|
|
|
10114
10579
|
* can still be assigned), which reads the host's current `uses`.
|
|
10115
10580
|
* - Limitations: a host intent must return an object (not one stream).
|
|
10116
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
|
+
};
|
|
10117
10606
|
// a model entry as { sink: fn }: a constant is sent as is, `true` sends the action's data, an
|
|
10118
10607
|
// EFFECT constant does nothing (as the core treats them)
|
|
10119
10608
|
const sinksOf = (e, S) => {
|
|
@@ -10296,6 +10785,8 @@ const pager = /*#__PURE__*/ defineBehavior({
|
|
|
10296
10785
|
});
|
|
10297
10786
|
|
|
10298
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;
|
|
10299
10790
|
// the key of segment `s` in `v`: an array's row with that id, else the index unless the element
|
|
10300
10791
|
// there is a row (G-380: a row is named by its id only; -1, no element), an object's key
|
|
10301
10792
|
const key = (v, s) => {
|
|
@@ -10342,7 +10833,7 @@ const setField = (values, name, value) => {
|
|
|
10342
10833
|
* array-level error such as "at least one address"); a row's `id` is not a field
|
|
10343
10834
|
*/
|
|
10344
10835
|
const fieldNames = (v, at = '', row) => {
|
|
10345
|
-
if (!v || typeof v != 'object')
|
|
10836
|
+
if (!v || typeof v != 'object' || isFile(v))
|
|
10346
10837
|
return at ? [at] : [];
|
|
10347
10838
|
const a = Array.isArray(v), out = a ? [at] : [];
|
|
10348
10839
|
for (const k in v) {
|
|
@@ -10423,7 +10914,12 @@ const focusInvalid = (names, within) => {
|
|
|
10423
10914
|
* field.
|
|
10424
10915
|
*
|
|
10425
10916
|
* Options: `values` (the start values; D239: or a function of the host's state, called when the
|
|
10426
|
-
* 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
|
|
10427
10923
|
* request with a reply action, `latest`), `show` ('blur' default: a schema error shows once its
|
|
10428
10924
|
* field was blurred; 'input'; 'submit'), `form` (selector), `http` (the driver sink of the
|
|
10429
10925
|
* checks, 'HTTP'), `resetOnShow` (D239), `tool` (PLAN-6 A-3, experimental: `formTool({ … })` from
|
|
@@ -10442,6 +10938,8 @@ const focusInvalid = (names, within) => {
|
|
|
10442
10938
|
* before that (a child's first render) the fields come from calling it with a blank state.
|
|
10443
10939
|
*
|
|
10444
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),
|
|
10445
10943
|
* server (server errors), remote (async check results: '' passed), pending (name -> value being
|
|
10446
10944
|
* checked), submitting, submitted, submitCount, queued (a submit waits for a check or an async
|
|
10447
10945
|
* schema), validating (an async schema runs), validated (the schema has answered since the start
|
|
@@ -10451,11 +10949,14 @@ const focusInvalid = (names, within) => {
|
|
|
10451
10949
|
*
|
|
10452
10950
|
* Actions: form.CHANGE ({ name, value, item? }: G-376: a checkbox gives `checked`, and `item`, its
|
|
10453
10951
|
* value, which toggles membership when the field is an array; <select multiple> an array of the
|
|
10454
|
-
* 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
|
|
10455
10954
|
* fields changed at once as if typed: one validation, `touched` as for CHANGE, unknown names
|
|
10456
10955
|
* skipped; e.g. an AI fill), form.BLUR (name), form.SUBMIT, form.ADD ({ field,
|
|
10457
10956
|
* value }), form.REMOVE ({ field, id }), form.ERRORS (an error reply or a map: server errors),
|
|
10458
|
-
* 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),
|
|
10459
10960
|
* form.CHECKED_<name> (a check's reply; a failed check passes: the server checks on submit),
|
|
10460
10961
|
* form.VALIDATE (the first validation, when the host starts: G-375; `valid` is false until the
|
|
10461
10962
|
* schema answered; with resetOnShow also each time the form element appears, D239).
|
|
@@ -10479,8 +10980,14 @@ const focusInvalid = (names, within) => {
|
|
|
10479
10980
|
* fields. The host checks (SYG234 submit, SYG235 check names, SYG236 row ids)
|
|
10480
10981
|
* run when the host is created.
|
|
10481
10982
|
*/
|
|
10482
|
-
|
|
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);
|
|
10483
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);
|
|
10484
10991
|
const drop = (o, p) => {
|
|
10485
10992
|
const r = {};
|
|
10486
10993
|
for (const k in o)
|
|
@@ -10489,8 +10996,8 @@ const drop = (o, p) => {
|
|
|
10489
10996
|
return r;
|
|
10490
10997
|
};
|
|
10491
10998
|
const form = (schema, o = {}) => {
|
|
10492
|
-
isStandardSchema(schema) || dev$
|
|
10493
|
-
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;
|
|
10494
11001
|
// D239: `values` can be a function of the host's state, called when the form starts (and each
|
|
10495
11002
|
// show, resetOnShow). Before that (the host's first render, before its first action) the
|
|
10496
11003
|
// fields come from calling it with a blank state: every read gives '' in the result
|
|
@@ -10522,7 +11029,7 @@ const form = (schema, o = {}) => {
|
|
|
10522
11029
|
// from a named button)
|
|
10523
11030
|
const known = (s, n, quiet) => {
|
|
10524
11031
|
const k = n && hasField(s.values, n);
|
|
10525
|
-
n && !k && !quiet && dev$
|
|
11032
|
+
n && !k && !quiet && dev$6(230, n, s.values);
|
|
10526
11033
|
return k;
|
|
10527
11034
|
};
|
|
10528
11035
|
// new values: errors now (sync schema), or after the schema's Promise (RESULT)
|
|
@@ -10537,7 +11044,7 @@ const form = (schema, o = {}) => {
|
|
|
10537
11044
|
});
|
|
10538
11045
|
const ask = (s, f, k, queued) => {
|
|
10539
11046
|
const x = getField(s.values, f), q = checks[f].request(x), ok = `${k}.CHECKED_${f}`;
|
|
10540
|
-
dev$
|
|
11047
|
+
dev$6(235, f, q);
|
|
10541
11048
|
return { s: { ...s, queued, pending: { ...s.pending, [f]: x } }, req: { ...q, ok, error: ok, latest: true } };
|
|
10542
11049
|
};
|
|
10543
11050
|
// a submit with s's errors: blocked (focus the first), a check to run or to wait for, or sent
|
|
@@ -10548,10 +11055,11 @@ const form = (schema, o = {}) => {
|
|
|
10548
11055
|
: Object.keys(s.pending).length ? { s: { ...s, queued: true } }
|
|
10549
11056
|
: { s: req ? { ...s, queued: false, submitting: true } : done({ ...s, queued: false }), send: 1 };
|
|
10550
11057
|
};
|
|
10551
|
-
const base = (vals) => ({ initial: vals, values: vals, touched: {}, server: {}, remote: {}, pending: {},
|
|
10552
|
-
submitting: false, submitted: false, submitCount: 0, queued: false, errors: {}, validated: false });
|
|
10553
|
-
|
|
10554
|
-
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 });
|
|
10555
11063
|
const group = (cur, d) => {
|
|
10556
11064
|
if (!Array.isArray(cur) || !('item' in d))
|
|
10557
11065
|
return d.value;
|
|
@@ -10560,10 +11068,10 @@ const form = (schema, o = {}) => {
|
|
|
10560
11068
|
};
|
|
10561
11069
|
// fields changed as typed ones ([name, value] pairs; unknown names skipped, SYG230): their
|
|
10562
11070
|
// server errors, check results and running checks go, `touched` as show 'input' says; one validation
|
|
10563
|
-
const put = (s, d) => {
|
|
11071
|
+
const put = (s, d, quiet) => {
|
|
10564
11072
|
let vals = s.values, server = drop(s.server, ''), remote = s.remote, pending = s.pending, touched = s.touched;
|
|
10565
11073
|
for (const [n, y] of d)
|
|
10566
|
-
if (known(s, n)) {
|
|
11074
|
+
if (known(s, n, quiet)) {
|
|
10567
11075
|
vals = setField(vals, n, y), server = drop(server, n), remote = drop(remote, n), pending = drop(pending, n);
|
|
10568
11076
|
if (show == 'input')
|
|
10569
11077
|
touched = { ...touched, [n]: true };
|
|
@@ -10573,7 +11081,9 @@ const form = (schema, o = {}) => {
|
|
|
10573
11081
|
const steps = {
|
|
10574
11082
|
// G-376: a checkbox (`item`: its value) on an array field is one of a group: checked adds
|
|
10575
11083
|
// its value, unchecked removes it
|
|
10576
|
-
|
|
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),
|
|
10577
11087
|
BLUR: (s, n, k) => {
|
|
10578
11088
|
if (!known(s, n, 1))
|
|
10579
11089
|
return null;
|
|
@@ -10582,7 +11092,7 @@ const form = (schema, o = {}) => {
|
|
|
10582
11092
|
},
|
|
10583
11093
|
SUBMIT: (s, _, k) => {
|
|
10584
11094
|
if (s.submitting || s.queued)
|
|
10585
|
-
return void dev$
|
|
11095
|
+
return void dev$6(232, s);
|
|
10586
11096
|
const n = { ...s, submitCount: s.submitCount + 1 }, r = v(s.values);
|
|
10587
11097
|
// G-371: a submit waiting for an async schema is queued (a second one is dropped)
|
|
10588
11098
|
return r.then ? { s: { ...n, validating: true, queued: true }, wait: { values: s.values, submit: 1 } } : attempt({ ...n, errors: r.errors }, k);
|
|
@@ -10606,16 +11116,20 @@ const form = (schema, o = {}) => {
|
|
|
10606
11116
|
SET: (s, d) => put(s, Object.entries(d?.values || {})),
|
|
10607
11117
|
ERRORS: (s, d) => {
|
|
10608
11118
|
const e = replyErrors(d, s.values);
|
|
10609
|
-
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) };
|
|
10610
11120
|
},
|
|
10611
11121
|
DONE: (s) => ({ s: done(s) }),
|
|
10612
|
-
|
|
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),
|
|
10613
11127
|
// G-375: the first validation, when the host starts (form() doesn't validate at module load).
|
|
10614
11128
|
// G-575: a slice kept in parent state from a host that unmounted mid-submit starts unstuck.
|
|
10615
11129
|
// D239: with resetOnShow (also each time the form element appears again), or a `values`
|
|
10616
11130
|
// function not called yet, the slice starts over from the start values (h: the host state)
|
|
10617
|
-
VALIDATE: (s, _, _k, h) => again || fn && s.values === values ? fresh(fn ? vo(h) : vo)
|
|
10618
|
-
: 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),
|
|
10619
11133
|
};
|
|
10620
11134
|
// a check's reply (ok and error: an error reply carries an Error and passes)
|
|
10621
11135
|
for (const f of checked)
|
|
@@ -10625,6 +11139,14 @@ const form = (schema, o = {}) => {
|
|
|
10625
11139
|
const n = { ...s, pending: drop(s.pending, f), remote: { ...s.remote, [f]: d?.error instanceof Error ? '' : checks[f].error?.(d) || '' } };
|
|
10626
11140
|
return s.queued ? attempt(n, k) : { s: n };
|
|
10627
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
|
+
};
|
|
10628
11150
|
const model = {};
|
|
10629
11151
|
for (const a in steps) {
|
|
10630
11152
|
let last = [];
|
|
@@ -10634,19 +11156,28 @@ const form = (schema, o = {}) => {
|
|
|
10634
11156
|
// VALIDATE: on the host's state (HOST), for a `values` function; the slice's own steps on it
|
|
10635
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; } }
|
|
10636
11158
|
: { STATE: (s, d, _n, _p, _o, k) => out(s, d, k)?.s || ABORT },
|
|
10637
|
-
|
|
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);
|
|
10638
11162
|
const x = out(s, d, k), w = x?.wait;
|
|
10639
11163
|
if (x?.send) {
|
|
10640
11164
|
const r = v(x.s.values).value;
|
|
10641
|
-
dev$
|
|
11165
|
+
dev$6(233, s.values, r);
|
|
10642
11166
|
// G-577: a submit named after one of the form's own actions goes to that one
|
|
10643
|
-
submit in steps && dev$
|
|
10644
|
-
|
|
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);
|
|
10645
11171
|
}
|
|
10646
11172
|
w && Promise.resolve(v(w.values)).then(() => next('RESULT', w, 0));
|
|
10647
11173
|
},
|
|
10648
|
-
ELEMENT: (s, d, _n,
|
|
10649
|
-
|
|
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
|
+
} }),
|
|
10650
11181
|
};
|
|
10651
11182
|
}
|
|
10652
11183
|
const b = defineBehavior({
|
|
@@ -10662,9 +11193,11 @@ const form = (schema, o = {}) => {
|
|
|
10662
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),
|
|
10663
11194
|
// G-376: a checkbox (native, or a hyphenated tag with a boolean `checked`: a
|
|
10664
11195
|
// form-associated custom checkbox / switch) gives `checked` and its value as `item`; a
|
|
10665
|
-
// <select multiple> the selected values;
|
|
10666
|
-
|
|
10667
|
-
|
|
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 }),
|
|
10668
11201
|
BLUR: f.events('focusout').map((e) => e.target.name),
|
|
10669
11202
|
SUBMIT: f.events('submit', { preventDefault: true }),
|
|
10670
11203
|
};
|
|
@@ -10682,7 +11215,7 @@ const form = (schema, o = {}) => {
|
|
|
10682
11215
|
},
|
|
10683
11216
|
// G-382: an async schema re-validating keeps the last answer's validity (no flicker)
|
|
10684
11217
|
valid: (s) => s.validated && !keys(s.errors).length && !keys(s.remote).length,
|
|
10685
|
-
dirty: (s) =>
|
|
11218
|
+
dirty: (s) => json(s.values) != json(s.initial),
|
|
10686
11219
|
error: (s) => s.server[''] || (s.submitCount && s.errors['']) || '',
|
|
10687
11220
|
},
|
|
10688
11221
|
})({ ...o, values }), merge = b.merge;
|
|
@@ -10690,7 +11223,7 @@ const form = (schema, o = {}) => {
|
|
|
10690
11223
|
o.tool?.$?.(b, v);
|
|
10691
11224
|
b.merge = (c, k) => {
|
|
10692
11225
|
const e = c.model?.[submit];
|
|
10693
|
-
req = !!e && typeof e == 'object' && http in e;
|
|
11226
|
+
req = !!rq || !!e && typeof e == 'object' && http in e;
|
|
10694
11227
|
merge(c, k);
|
|
10695
11228
|
};
|
|
10696
11229
|
return b;
|
|
@@ -10839,7 +11372,7 @@ const selection = (options = {}) => {
|
|
|
10839
11372
|
* interaction, SYG147 a `from` key that isn't an array in the host state, SYG435 (3-H) a list
|
|
10840
11373
|
* shown in another order than its array's (a Collection's sort / filter).
|
|
10841
11374
|
*/
|
|
10842
|
-
const dev$
|
|
11375
|
+
const dev$5 = (...a) => globalThis.__SYGNAL_DIAGNOSTICS__?.sortable?.(...a);
|
|
10843
11376
|
const LIFT = /^( |Enter|Spacebar)$/;
|
|
10844
11377
|
const KEYS = /^(Arrow(Up|Down|Left|Right)|Home|End|Escape|Tab)$/;
|
|
10845
11378
|
const S$1 = String;
|
|
@@ -10953,7 +11486,7 @@ const sortable = (options = {}) => {
|
|
|
10953
11486
|
} } } } };
|
|
10954
11487
|
};
|
|
10955
11488
|
// dev: SYG435, the list shown in another order than the array's (G-449)
|
|
10956
|
-
const check = (st, seen, k) => seen && dev$
|
|
11489
|
+
const check = (st, seen, k) => seen && dev$5(435, seen, st, lists, idField, k);
|
|
10957
11490
|
// a press or key handler: drag state this instance didn't start (its token `n` isn't the
|
|
10958
11491
|
// event's: restored, synced) is dropped first (G-452)
|
|
10959
11492
|
const live = (h) => (st, d, next, _p, _o, k) => {
|
|
@@ -10994,7 +11527,7 @@ const sortable = (options = {}) => {
|
|
|
10994
11527
|
const gripOf = (e, self) => {
|
|
10995
11528
|
const t = e.target, r = e.ownerTarget || e.currentTarget;
|
|
10996
11529
|
if (!checked)
|
|
10997
|
-
checked = dev$
|
|
11530
|
+
checked = dev$5(146, r, item, handle, attr);
|
|
10998
11531
|
const g = !claimed.has(e) && t?.closest?.(grip);
|
|
10999
11532
|
if (!g || self && g !== t)
|
|
11000
11533
|
return null;
|
|
@@ -11008,14 +11541,14 @@ const sortable = (options = {}) => {
|
|
|
11008
11541
|
return null;
|
|
11009
11542
|
const id = idOf(it);
|
|
11010
11543
|
if (id == null)
|
|
11011
|
-
dev$
|
|
11544
|
+
dev$5(145, it, item, attr);
|
|
11012
11545
|
return id;
|
|
11013
11546
|
};
|
|
11014
11547
|
const keyed = (keys, kb) => (e) => keys.test(e.key) && (kb || e.key != 'Tab') && gripOf(e, true) != null;
|
|
11015
11548
|
const lift = keyed(LIFT), step = keyed(KEYS, 1);
|
|
11016
11549
|
// dev (SYG435): this host's own items' ids in the order they're shown (computed by the
|
|
11017
11550
|
// diagnostics entry only)
|
|
11018
|
-
const shown = (e) => dev$
|
|
11551
|
+
const shown = (e) => dev$5(435, () => {
|
|
11019
11552
|
const r = e.ownerTarget || e.currentTarget;
|
|
11020
11553
|
return [[...r.querySelectorAll(item)].filter((it) => own(it, r)), r];
|
|
11021
11554
|
}, key, lists, idOf);
|
|
@@ -11059,7 +11592,7 @@ const sortable = (options = {}) => {
|
|
|
11059
11592
|
// again by HMR, a second host of the slice) is reset, as at END
|
|
11060
11593
|
INIT: { HOST: (st, _d, _n, _p, _o, k) => {
|
|
11061
11594
|
for (const l of lists)
|
|
11062
|
-
Array.isArray(st?.[l]) || dev$
|
|
11595
|
+
Array.isArray(st?.[l]) || dev$5(147, l, st, k);
|
|
11063
11596
|
return st[k].press || st[k].dragging ? back(st, k) : ABORT;
|
|
11064
11597
|
} },
|
|
11065
11598
|
// unmounted mid-drag: the drag is cancelled (no DROPPED; a keyboard drag's item goes back
|
|
@@ -11471,14 +12004,14 @@ const undo = (options) => {
|
|
|
11471
12004
|
* too), once per kind (restore / save / clear; a save that works again re-arms it); the app
|
|
11472
12005
|
* continues on initialState / unsaved, and a failed save is retried at the next change.
|
|
11473
12006
|
*/
|
|
11474
|
-
const g$
|
|
12007
|
+
const g$2 = globalThis;
|
|
11475
12008
|
const setupPersist = (c, o) => {
|
|
11476
12009
|
const { key, pick, omit, version = 1, migrate, sync, debounceMs = 100 } = o, plain = o.format == 'plain', src = c.sources, m = src.__m;
|
|
11477
12010
|
const env = src.__storage, calc = c.calculated || {};
|
|
11478
12011
|
// hydrating: the option, else the integration's signal (__hydrate: Astro, Vike), else whether
|
|
11479
12012
|
// run()'s mount point starts with renderToString's markup (its root element is marked; a
|
|
11480
12013
|
// client-only app's loading placeholder isn't server markup)
|
|
11481
|
-
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');
|
|
11482
12015
|
let S, t, last, raw, skip, off;
|
|
11483
12016
|
// (the docs link the message ends with explains the causes and fixes). Once per kind (restore,
|
|
11484
12017
|
// save, clear): a storage that throws on every write must not warn on every change; a save that
|
|
@@ -11551,7 +12084,7 @@ const setupPersist = (c, o) => {
|
|
|
11551
12084
|
}
|
|
11552
12085
|
c.model = model;
|
|
11553
12086
|
try {
|
|
11554
|
-
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')];
|
|
11555
12088
|
}
|
|
11556
12089
|
catch (_) { }
|
|
11557
12090
|
if (!S)
|
|
@@ -11588,14 +12121,14 @@ const setupPersist = (c, o) => {
|
|
|
11588
12121
|
send(read(raw = v)); };
|
|
11589
12122
|
const onStorage = (e) => (!e.storageArea || e.storageArea == S) && onSync(e.key, e.newValue);
|
|
11590
12123
|
if (sync)
|
|
11591
|
-
off = S.subscribe ? S.subscribe(onSync) : (g$
|
|
11592
|
-
g$
|
|
12124
|
+
off = S.subscribe ? S.subscribe(onSync) : (g$2.addEventListener?.('storage', onStorage), () => g$2.removeEventListener?.('storage', onStorage));
|
|
12125
|
+
g$2.addEventListener?.('pagehide', flush);
|
|
11593
12126
|
env?.f.add(flush);
|
|
11594
12127
|
c._dispose$.addListener({ next: () => {
|
|
11595
12128
|
flush();
|
|
11596
12129
|
state$.removeListener(L);
|
|
11597
12130
|
off?.();
|
|
11598
|
-
g$
|
|
12131
|
+
g$2.removeEventListener?.('pagehide', flush);
|
|
11599
12132
|
env?.f.delete(flush);
|
|
11600
12133
|
} });
|
|
11601
12134
|
};
|
|
@@ -12827,7 +13360,7 @@ const snake = (s) => s.replace(/([a-z0-9])([A-Z])/g, '$1_$2').replace(/\W+/g, '_
|
|
|
12827
13360
|
const keyOf$1 = (iv) => iv.state?.id;
|
|
12828
13361
|
const done = new WeakMap();
|
|
12829
13362
|
/** a dev diagnostic, once per (declaration, what); 'error' mode rethrows outside the caller */
|
|
12830
|
-
const dev$
|
|
13363
|
+
const dev$4 = (code, decl, what, component, message) => {
|
|
12831
13364
|
let s = done.get(decl);
|
|
12832
13365
|
if (!s)
|
|
12833
13366
|
done.set(decl, s = new Set());
|
|
@@ -13036,7 +13569,7 @@ function agentTools(target, options = {}) {
|
|
|
13036
13569
|
v = d.read(s);
|
|
13037
13570
|
}
|
|
13038
13571
|
catch (e) {
|
|
13039
|
-
dev$
|
|
13572
|
+
dev$4('SYG241', d, 'read', iv.name, `agent.read threw: ${e?.message ?? e}`);
|
|
13040
13573
|
}
|
|
13041
13574
|
reads.set(iv, { s, v });
|
|
13042
13575
|
return v;
|
|
@@ -13055,7 +13588,7 @@ function agentTools(target, options = {}) {
|
|
|
13055
13588
|
v = !!w(s);
|
|
13056
13589
|
}
|
|
13057
13590
|
catch (e) {
|
|
13058
|
-
dev$
|
|
13591
|
+
dev$4('SYG241', declOf(iv), 'when' + action, iv.name, `agent.actions.${action}.when threw: ${e?.message ?? e}`);
|
|
13059
13592
|
}
|
|
13060
13593
|
m.set(action, { s, v });
|
|
13061
13594
|
return v;
|
|
@@ -13066,7 +13599,7 @@ function agentTools(target, options = {}) {
|
|
|
13066
13599
|
return d.label ? String(d.label(iv.state)) : undefined;
|
|
13067
13600
|
}
|
|
13068
13601
|
catch (e) {
|
|
13069
|
-
dev$
|
|
13602
|
+
dev$4('SYG241', d, 'label', iv.name, `agent.label threw: ${e?.message ?? e}`);
|
|
13070
13603
|
}
|
|
13071
13604
|
};
|
|
13072
13605
|
function walk(iv, out) {
|
|
@@ -13101,7 +13634,7 @@ function agentTools(target, options = {}) {
|
|
|
13101
13634
|
else if (e.decl === d && e.item && item)
|
|
13102
13635
|
e.ivs.push(iv);
|
|
13103
13636
|
else
|
|
13104
|
-
dev$
|
|
13637
|
+
dev$4('SYG440', d, name, iv.name, `two agent declarations are named '${name}'; only the first is offered`);
|
|
13105
13638
|
}
|
|
13106
13639
|
for (const e of by.values())
|
|
13107
13640
|
if (e.item) {
|
|
@@ -13110,9 +13643,9 @@ function agentTools(target, options = {}) {
|
|
|
13110
13643
|
const k = keyOf$1(iv);
|
|
13111
13644
|
// an item without an id is keyed by its index (the core's `_i<index>` uid part): unstable
|
|
13112
13645
|
if (k == null || /-_i\d+$/.test(iv.uid))
|
|
13113
|
-
dev$
|
|
13646
|
+
dev$4('SYG441', e.decl, 'nokey', iv.name, `items of '${e.name}' have no id: agents address them by index`);
|
|
13114
13647
|
else if (ks.has(k + ''))
|
|
13115
|
-
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`);
|
|
13116
13649
|
else
|
|
13117
13650
|
ks.add(k + '');
|
|
13118
13651
|
}
|
|
@@ -13138,11 +13671,11 @@ function agentTools(target, options = {}) {
|
|
|
13138
13671
|
for (const [action, a, isQuery] of defs) {
|
|
13139
13672
|
const name = e.name + '_' + snake(action), comp = e.ivs[0].name, where = `agent.${isQuery ? 'queries' : 'actions'}.${action}`;
|
|
13140
13673
|
if (isQuery && recs.has(name)) {
|
|
13141
|
-
dev$
|
|
13674
|
+
dev$4('SYG247', d, action, comp, `${where} has the tool name ${name} of an action; the query is not offered (rename it)`);
|
|
13142
13675
|
continue;
|
|
13143
13676
|
}
|
|
13144
13677
|
if (isQuery && typeof a?.run != 'function') {
|
|
13145
|
-
dev$
|
|
13678
|
+
dev$4('SYG246', d, action, comp, `${where}.run is not a function; the query is not offered`);
|
|
13146
13679
|
continue;
|
|
13147
13680
|
}
|
|
13148
13681
|
const c = a.input ? toJsonSchema(a.input) : undefined;
|
|
@@ -13157,11 +13690,11 @@ function agentTools(target, options = {}) {
|
|
|
13157
13690
|
if (c?.error) {
|
|
13158
13691
|
tool.error = `SYG240 ${c.error}`;
|
|
13159
13692
|
rec.off = 1;
|
|
13160
|
-
dev$
|
|
13693
|
+
dev$4('SYG240', d, action, comp, `${where}.input: ${c.error}; the tool is not offered`);
|
|
13161
13694
|
continue;
|
|
13162
13695
|
}
|
|
13163
13696
|
if (c?.lossy)
|
|
13164
|
-
dev$
|
|
13697
|
+
dev$4('SYG243', d, action, comp, `${where}.input: the model doesn't see ${c.lossy}`);
|
|
13165
13698
|
const live = e.ivs.filter(iv => whenOk(iv, action, a.when));
|
|
13166
13699
|
if (!live.length) {
|
|
13167
13700
|
rec.off = 1;
|
|
@@ -13333,7 +13866,7 @@ function agentTools(target, options = {}) {
|
|
|
13333
13866
|
return;
|
|
13334
13867
|
const res = iv.def.view.resources;
|
|
13335
13868
|
if (!res || !(u in res)) {
|
|
13336
|
-
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`);
|
|
13337
13870
|
return;
|
|
13338
13871
|
}
|
|
13339
13872
|
return (s) => { const r = s?.[u]; return !r || (r.status != 'loading' && !r.refreshing); };
|
|
@@ -13351,7 +13884,7 @@ function agentTools(target, options = {}) {
|
|
|
13351
13884
|
return !!test(iv.state);
|
|
13352
13885
|
}
|
|
13353
13886
|
catch (err) {
|
|
13354
|
-
dev$
|
|
13887
|
+
dev$4('SYG241', declOf(iv), 'until' + action, iv.name, `agent.actions.${action}.until threw: ${err?.message ?? err}`);
|
|
13355
13888
|
return true;
|
|
13356
13889
|
}
|
|
13357
13890
|
};
|
|
@@ -13377,11 +13910,11 @@ function agentTools(target, options = {}) {
|
|
|
13377
13910
|
}
|
|
13378
13911
|
catch (err) {
|
|
13379
13912
|
const m = err?.message ?? err;
|
|
13380
|
-
dev$
|
|
13913
|
+
dev$4('SYG241', declOf(iv), 'query' + query, iv.name, `agent.queries.${query}.run threw: ${m}`);
|
|
13381
13914
|
return { ok: false, error: `${name} failed: ${m}` };
|
|
13382
13915
|
}
|
|
13383
13916
|
if (result && typeof result.then == 'function') {
|
|
13384
|
-
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`);
|
|
13385
13918
|
return { ok: false, error: `${name} is not available: it returned a promise` };
|
|
13386
13919
|
}
|
|
13387
13920
|
return { ok: true, result };
|
|
@@ -13742,28 +14275,28 @@ function withToolResults(message, results) {
|
|
|
13742
14275
|
const SCOPE = '__sygnalScope';
|
|
13743
14276
|
const tag$1 = (v, scope) => v && typeof v == 'object'
|
|
13744
14277
|
? Object.defineProperty(keepSender(v, { ...v }), SCOPE, { value: [scope, ...(v[SCOPE] || [])] }) : v;
|
|
13745
|
-
const g$
|
|
13746
|
-
const now = () => g$
|
|
14278
|
+
const g$1 = globalThis;
|
|
14279
|
+
const now = () => g$1.performance?.now?.() ?? Date.now();
|
|
13747
14280
|
// internal (D254): the 120 Hz floor and the hidden-tab fallback
|
|
13748
14281
|
const MIN_GAP = 15, FALLBACK = 100;
|
|
13749
14282
|
/** rAF raced with a timer (a hidden tab never runs rAF), else a 16 ms timer; `f` gets the frame's time */
|
|
13750
14283
|
const frame = (f) => {
|
|
13751
|
-
if (typeof g$
|
|
14284
|
+
if (typeof g$1.requestAnimationFrame != 'function')
|
|
13752
14285
|
return void setTimeout(() => f(now()), 16);
|
|
13753
14286
|
let done = 0, a, t;
|
|
13754
14287
|
const go = (ts) => {
|
|
13755
14288
|
if (done++)
|
|
13756
14289
|
return;
|
|
13757
|
-
g$
|
|
14290
|
+
g$1.cancelAnimationFrame?.(a);
|
|
13758
14291
|
clearTimeout(t);
|
|
13759
14292
|
f(typeof ts == 'number' ? ts : now());
|
|
13760
14293
|
};
|
|
13761
|
-
a = g$
|
|
14294
|
+
a = g$1.requestAnimationFrame(go);
|
|
13762
14295
|
t = setTimeout(go, FALLBACK);
|
|
13763
14296
|
};
|
|
13764
14297
|
// dev-only diagnostics (SYG673, SYG677): the dev entry formats them (checks/chat.ts), so their
|
|
13765
14298
|
// text costs apps nothing; without it they're silent
|
|
13766
|
-
const dev$
|
|
14299
|
+
const dev$3 = (...a) => g$1.__SYGNAL_DIAGNOSTICS__?.chat?.(...a);
|
|
13767
14300
|
const FIX = "Send { messages, ok: 'DONE', error: 'FAILED' }";
|
|
13768
14301
|
/** why a request can't be sent ('' when it can) */
|
|
13769
14302
|
const problem = (r) => !Array.isArray(r.messages) ? 'no messages array'
|
|
@@ -13785,7 +14318,7 @@ function makeChatDriver(options = {}) {
|
|
|
13785
14318
|
const { replies, reply } = makeReplies(sender => abortWhere(r => r.sender === sender));
|
|
13786
14319
|
// SYG673 (dev): `why` is 'type' | 'delta' | 'name' | 'id'; the first five per driver
|
|
13787
14320
|
const malformed = (req, ev, why) => { if (warned++ < 5)
|
|
13788
|
-
dev$
|
|
14321
|
+
dev$3('SYG673', req, ev, why); };
|
|
13789
14322
|
const start = async (req, sender) => {
|
|
13790
14323
|
const key = req.key ?? req.ok ?? req.error ?? 'default';
|
|
13791
14324
|
if (req.latest !== false)
|
|
@@ -13978,7 +14511,7 @@ function makeChatDriver(options = {}) {
|
|
|
13978
14511
|
return;
|
|
13979
14512
|
const sender = senderOf(req);
|
|
13980
14513
|
if (sender === undefined)
|
|
13981
|
-
return void dev$
|
|
14514
|
+
return void dev$3('SYG677', req);
|
|
13982
14515
|
const { abort } = req;
|
|
13983
14516
|
if (abort !== undefined && abort !== false) {
|
|
13984
14517
|
// D469 (PLAN-9 H-2): a list of keys stops each (STOP: the reply and a card being composed)
|
|
@@ -15034,6 +15567,17 @@ const validMs = (v) => typeof v == 'number' && Number.isFinite(v) && v >= 0 && v
|
|
|
15034
15567
|
// too, so its time is the clock's (Date may be left real by `toFake`), and its waits drive it.
|
|
15035
15568
|
const fakeClock = () => setTimeout.clock;
|
|
15036
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
|
+
};
|
|
15037
15581
|
/**
|
|
15038
15582
|
* Under fake timers, advance the clock timer by timer (nextAsync flushes promises around each
|
|
15039
15583
|
* one) until `p` settles. Every pending wait has a timer (its timeout, a quiet-window tick), so
|
|
@@ -15224,6 +15768,7 @@ const headFake = () => {
|
|
|
15224
15768
|
};
|
|
15225
15769
|
let savedConfig;
|
|
15226
15770
|
let savedStrict;
|
|
15771
|
+
let savedAudit;
|
|
15227
15772
|
/*
|
|
15228
15773
|
* PLAN-4 GS-2, mock DOM: could the element a command targets have this method? The core runs any
|
|
15229
15774
|
* method of the element; the mock DOM has no elements, so: the documented commands, common
|
|
@@ -15334,7 +15879,7 @@ function fakeElementMethods(W) {
|
|
|
15334
15879
|
Object.defineProperty(proto, name, own); });
|
|
15335
15880
|
}
|
|
15336
15881
|
function renderComponent(componentDef, options = {}) {
|
|
15337
|
-
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;
|
|
15338
15883
|
const { intent, model = {} } = componentDef;
|
|
15339
15884
|
// E4: real DOM mode
|
|
15340
15885
|
const real = dom == 'real';
|
|
@@ -15382,6 +15927,7 @@ function renderComponent(componentDef, options = {}) {
|
|
|
15382
15927
|
if (!active++) {
|
|
15383
15928
|
savedConfig = _getDiagnosticsConfig();
|
|
15384
15929
|
savedStrict = core.strict;
|
|
15930
|
+
savedAudit = core.timingAudit;
|
|
15385
15931
|
// G-051: forget the checks' report dedupe (once()), so a finding from an earlier test is
|
|
15386
15932
|
// reported again. Only that set: resetChecks() would also drop inspect()'s records of
|
|
15387
15933
|
// other live instances / apps.
|
|
@@ -15390,6 +15936,9 @@ function renderComponent(componentDef, options = {}) {
|
|
|
15390
15936
|
configureDiagnostics({ mode: diagnostics || (prevMode == 'off' ? 'collect' : prevMode) });
|
|
15391
15937
|
if (strict !== undefined)
|
|
15392
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;
|
|
15393
15942
|
// PLAN-4 GS-2: element commands run on the real DOM (jsdom gets the missing methods), and their
|
|
15394
15943
|
// SYG640/SYG641 are reported without the dev entry too
|
|
15395
15944
|
if (real && !domFakes)
|
|
@@ -15580,6 +16129,7 @@ function renderComponent(componentDef, options = {}) {
|
|
|
15580
16129
|
if (!--active) {
|
|
15581
16130
|
configureDiagnostics(savedConfig);
|
|
15582
16131
|
core.strict = savedStrict;
|
|
16132
|
+
core.timingAudit = savedAudit;
|
|
15583
16133
|
domFakes?.();
|
|
15584
16134
|
domFakes = undefined;
|
|
15585
16135
|
}
|
|
@@ -15625,14 +16175,22 @@ function renderComponent(componentDef, options = {}) {
|
|
|
15625
16175
|
const lin$ = xs$1.create();
|
|
15626
16176
|
llm = { in$: lin$, src: makeChatDriver({ transport: tr, _frame: (f) => { frames.push(f); } })(lin$) };
|
|
15627
16177
|
}
|
|
16178
|
+
let memFetch;
|
|
15628
16179
|
const http = makeFetchDriver({
|
|
15629
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
|
+
},
|
|
15630
16188
|
// 5-3: focus / online come from t.focus() / t.online() only
|
|
15631
16189
|
_on: (f) => { signals.add(f); return () => signals.delete(f); },
|
|
15632
16190
|
_tap: (req, res, pf) => { tapped = [req, res, pf]; },
|
|
15633
16191
|
// D472: a batch / split request (a large decide()) is one fetch here, answered as one
|
|
15634
16192
|
_whole: true,
|
|
15635
|
-
fetch: (url, init) => new Promise((resolve, reject) => {
|
|
16193
|
+
fetch: memFetch = (url, init) => new Promise((resolve, reject) => {
|
|
15636
16194
|
const [req, res, pf] = tapped || [{ url }];
|
|
15637
16195
|
tapped = undefined;
|
|
15638
16196
|
// G-171(1): a resource fetch is listed as { url, ...request, resource: name }; 5-5: a
|
|
@@ -15666,6 +16224,7 @@ function renderComponent(componentDef, options = {}) {
|
|
|
15666
16224
|
return {
|
|
15667
16225
|
select: (sel) => xs$1.merge(hs.select(sel), own(false)(sel), sock.select(sel)),
|
|
15668
16226
|
errors: (sel) => xs$1.merge(hs.errors(sel), own(true)(sel)),
|
|
16227
|
+
progress: (sel) => hs.progress(sel),
|
|
15669
16228
|
subs, at, pending, in$, http, ws, llm,
|
|
15670
16229
|
// 5-3: the driver's matcher / inspection (the dev checks' SYG632, t.cache)
|
|
15671
16230
|
__matches: http.__matches, __inspect: http.__inspect,
|
|
@@ -16431,7 +16990,7 @@ function renderComponent(componentDef, options = {}) {
|
|
|
16431
16990
|
return { match: cat, desc: catDesc, o };
|
|
16432
16991
|
if (typeof tg == 'string') {
|
|
16433
16992
|
// an ok/error action name, key, category, resource name or URL
|
|
16434
|
-
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 };
|
|
16435
16994
|
}
|
|
16436
16995
|
if (typeof tg == 'function') {
|
|
16437
16996
|
return { match: r => { try {
|
|
@@ -16688,6 +17247,27 @@ function renderComponent(componentDef, options = {}) {
|
|
|
16688
17247
|
const err = typeof error == 'number' || resp ? Object.assign(new Error(`HTTP ${status}`), { status }) : x;
|
|
16689
17248
|
return { error: err, category, request: e?.req, status: status ?? x?.status, body };
|
|
16690
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
|
+
};
|
|
16691
17271
|
// PLAN-6 L-4: t.stream. Targets as t.respond's (an action name or key, a partial request, a
|
|
16692
17272
|
// predicate, { request, nth }); `end: false` (the last argument, or in the target options)
|
|
16693
17273
|
// leaves the stream open. Chunks become ChatEvents pushed into the in-memory stream; the fake's
|
|
@@ -17192,7 +17772,14 @@ function renderComponent(componentDef, options = {}) {
|
|
|
17192
17772
|
let msg = `${name} timed out after ${timeoutMs}ms.`;
|
|
17193
17773
|
const [why, pending] = explainNext(start);
|
|
17194
17774
|
msg += why;
|
|
17195
|
-
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)
|
|
17196
17783
|
msg += ` Wait longer: t.${name}(pred, ms), or the renderComponent timeoutMs option (default for every wait).`;
|
|
17197
17784
|
// G-065: a state from before the call matched; next() only looks at new ones
|
|
17198
17785
|
for (let i = from - 1; i >= 0; i--) {
|
|
@@ -17392,6 +17979,7 @@ function renderComponent(componentDef, options = {}) {
|
|
|
17392
17979
|
requests,
|
|
17393
17980
|
respond,
|
|
17394
17981
|
fail,
|
|
17982
|
+
progress,
|
|
17395
17983
|
stream,
|
|
17396
17984
|
connections,
|
|
17397
17985
|
cache,
|
|
@@ -17968,7 +18556,7 @@ function jsonSchema(schema, options = {}) {
|
|
|
17968
18556
|
* Side-effect free: an app that doesn't import it pays 0 B.
|
|
17969
18557
|
*/
|
|
17970
18558
|
const NAME = 30, DESC = 500, PARAM = 150, OUT = 1500, SUMMARY = 300;
|
|
17971
|
-
const g
|
|
18559
|
+
const g = globalThis;
|
|
17972
18560
|
/** cut to n characters with an ellipsis, never inside a surrogate pair */
|
|
17973
18561
|
const cut = (s, n) => {
|
|
17974
18562
|
if (s.length <= n)
|
|
@@ -17985,7 +18573,7 @@ const hash = (s) => {
|
|
|
17985
18573
|
return (h % 1679616).toString(36).padStart(4, '0');
|
|
17986
18574
|
};
|
|
17987
18575
|
function experimentalExposeWebMcp(app, options = {}) {
|
|
17988
|
-
const mc = options.modelContext ?? g
|
|
18576
|
+
const mc = options.modelContext ?? g.document?.modelContext ?? g.navigator?.modelContext;
|
|
17989
18577
|
const said = new Set();
|
|
17990
18578
|
const dev = (code, key, message, severity = 'warn') => {
|
|
17991
18579
|
if (said.has(code + key))
|
|
@@ -18188,7 +18776,7 @@ function experimentalExposeWebMcp(app, options = {}) {
|
|
|
18188
18776
|
let n$2 = 0;
|
|
18189
18777
|
/** the default confirmation: a native modal dialog outside the app's tree (Escape and "Deny" decline) */
|
|
18190
18778
|
function dialog(info, setClose) {
|
|
18191
|
-
const doc = g
|
|
18779
|
+
const doc = g.document;
|
|
18192
18780
|
if (!doc?.createElement)
|
|
18193
18781
|
return Promise.resolve(false);
|
|
18194
18782
|
return new Promise(resolve => {
|
|
@@ -18444,7 +19032,7 @@ const toolsOf$1 = (e) => e.o.agent === false ? null : e.tools || (e.tools = agen
|
|
|
18444
19032
|
}),
|
|
18445
19033
|
}));
|
|
18446
19034
|
// dev diagnostics (SYG684, SYG685): no-ops with diagnostics off
|
|
18447
|
-
const dev$
|
|
19035
|
+
const dev$2 = (code, message, fix) => { try {
|
|
18448
19036
|
report(code, { component: 'chat', message, fix, severity: 'warn' });
|
|
18449
19037
|
}
|
|
18450
19038
|
catch (e) {
|
|
@@ -18654,7 +19242,7 @@ function steps(c, o) {
|
|
|
18654
19242
|
const openPart = (s, id, what, guard = (_p) => true) => {
|
|
18655
19243
|
const p = openToolCalls(s).find(x => x.toolCallId === id && isClient(c, x) && guard(x));
|
|
18656
19244
|
if (!p)
|
|
18657
|
-
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');
|
|
18658
19246
|
return p;
|
|
18659
19247
|
};
|
|
18660
19248
|
/** after a call closed or a card changed: the next queued card, then rule 3 */
|
|
@@ -18782,7 +19370,7 @@ function request(e, o, k, messages, fresh, c, host) {
|
|
|
18782
19370
|
for (const n in c.client) {
|
|
18783
19371
|
if (n in tools && !shadowed.has(n)) {
|
|
18784
19372
|
shadowed.add(n);
|
|
18785
|
-
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');
|
|
18786
19374
|
}
|
|
18787
19375
|
const x = c.client[n];
|
|
18788
19376
|
tools[n] = c.ask && n == 'ask_user' ? c.kit.askTool(c.ask, host) : { description: x.description, inputSchema: schemaOf(x) };
|
|
@@ -18899,7 +19487,7 @@ const chat = (options = {}) => {
|
|
|
18899
19487
|
...(c.ask?.controls && DOM && { CONTROL: DOM.select('[data-compose-control]').events('click').map(controlOf$1).filter((x) => !!x) }),
|
|
18900
19488
|
};
|
|
18901
19489
|
},
|
|
18902
|
-
model: {
|
|
19490
|
+
model: fromPrevious({
|
|
18903
19491
|
PROMPT: (s, p) => typeof p == 'string' && p !== s.prompt ? { ...s, prompt: p } : ABORT,
|
|
18904
19492
|
SEND: {
|
|
18905
19493
|
STATE: (s, d) => sendStep(s, d) || ABORT,
|
|
@@ -18997,153 +19585,19 @@ const chat = (options = {}) => {
|
|
|
18997
19585
|
ANSWER: { STATE: stateOf(S.answerCard), ...sinks(S.answerCard) },
|
|
18998
19586
|
CONTROL: (s, d) => S.controlStep(s, d) || ABORT,
|
|
18999
19587
|
}),
|
|
19000
|
-
},
|
|
19588
|
+
}),
|
|
19001
19589
|
})(options);
|
|
19002
19590
|
// the `prompt` option is a selector, not the slice's start value (defineBehavior's option override)
|
|
19003
19591
|
b.state = { ...b.state, prompt: '' };
|
|
19004
19592
|
return linked(b);
|
|
19005
19593
|
};
|
|
19006
19594
|
|
|
19007
|
-
/*
|
|
19008
|
-
* PLAN-6 L-2: what the HTTP transports share (side-effect free; each transport imports only what
|
|
19009
|
-
* it uses).
|
|
19010
|
-
* - post(): the fetch (the `fetch` option, 0-S5), the headers option (an object or a function of
|
|
19011
|
-
* the request), HTTP errors as Errors with `status` and the provider's message, and SYG670.
|
|
19012
|
-
* - sse(): a fetch Response body as SSE frames ({ event, data }), per the HTML spec's parsing
|
|
19013
|
-
* rules: CRLF / LF, comments, multi-line data. The reader is cancelled when the consumer stops
|
|
19014
|
-
* (abort, a thrown error), so the connection closes.
|
|
19015
|
-
* - SYG670: in a browser, a request with an auth header (Authorization, x-api-key, api-key,
|
|
19016
|
-
* x-goog-api-key) to a host that is neither local (localhost, 127.0.0.1, [::1], *.localhost)
|
|
19017
|
-
* nor the page's own origin is refused unless the transport has `dangerouslyAllowBrowser: true`:
|
|
19018
|
-
* the key would ship to every visitor. Same-origin requests are the app's own server (its
|
|
19019
|
-
* proxy), so a session header there is fine.
|
|
19020
|
-
*/
|
|
19021
|
-
const g = globalThis;
|
|
19022
|
-
const AUTH = /^(authorization|x-api-key|api-key|x-goog-api-key)$/i;
|
|
19023
|
-
const LOCAL = /^(localhost|127\.0\.0\.1|\[::1\]|.+\.localhost)$/;
|
|
19024
|
-
/** the URL a transport posts to, or a SYG670 Error when it must not (see the header) */
|
|
19025
|
-
function guard(url, headers, opts, name) {
|
|
19026
|
-
if (opts.dangerouslyAllowBrowser || typeof g.document == 'undefined' || !Object.keys(headers).some(h => AUTH.test(h)))
|
|
19027
|
-
return;
|
|
19028
|
-
const here = g.location?.href;
|
|
19029
|
-
let u;
|
|
19030
|
-
try {
|
|
19031
|
-
u = new URL(url, here);
|
|
19032
|
-
}
|
|
19033
|
-
catch (_) {
|
|
19034
|
-
return;
|
|
19035
|
-
}
|
|
19036
|
-
if (LOCAL.test(u.hostname) || (here && u.origin == new URL(here).origin))
|
|
19037
|
-
return;
|
|
19038
|
-
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 })`;
|
|
19039
|
-
const msg = `${name}: refused to send an auth header from the browser to ${u.origin}: anyone who opens the page can read it`;
|
|
19040
|
-
error('SYG670', undefined, msg, fix);
|
|
19041
|
-
fail$2('SYG670', undefined, msg, fix);
|
|
19042
|
-
}
|
|
19043
|
-
/**
|
|
19044
|
-
* POST JSON; a non-2xx response is an Error with `status`, `body` and the provider's message.
|
|
19045
|
-
* The transport's `body` option and the request's `body` are merged over the built body (so they
|
|
19046
|
-
* can override provider defaults such as `max_tokens`); `fixed` fields are merged last and can't be
|
|
19047
|
-
* overridden (G-654: uiMessageStream's protocol fields, as the AI SDK's DefaultChatTransport)
|
|
19048
|
-
*/
|
|
19049
|
-
async function post$1(url, body, req, signal, opts, name, fixed) {
|
|
19050
|
-
const h = typeof opts.headers == 'function' ? await opts.headers(req) : opts.headers || {};
|
|
19051
|
-
guard(url, h, opts, name);
|
|
19052
|
-
const f = opts.fetch || ((u, i) => g.fetch(u, i));
|
|
19053
|
-
const res = await f(url, { method: 'POST', headers: { 'content-type': 'application/json', ...h }, body: JSON.stringify({ ...body, ...opts.body, ...req.body, ...fixed }), signal });
|
|
19054
|
-
if (!res.ok) {
|
|
19055
|
-
let text = '', message = '';
|
|
19056
|
-
try {
|
|
19057
|
-
text = await res.text();
|
|
19058
|
-
const j = JSON.parse(text);
|
|
19059
|
-
message = j.error?.message ?? j.error ?? j.message ?? '';
|
|
19060
|
-
}
|
|
19061
|
-
catch (_) {
|
|
19062
|
-
message || (message = text.slice(0, 200));
|
|
19063
|
-
}
|
|
19064
|
-
throw Object.assign(new Error(`${name}: HTTP ${res.status}${message ? ': ' + message : ''}`), { status: res.status, body: text });
|
|
19065
|
-
}
|
|
19066
|
-
if (!res.body)
|
|
19067
|
-
throw new Error(`${name}: the response has no body`);
|
|
19068
|
-
return res;
|
|
19069
|
-
}
|
|
19070
|
-
/** the SSE frames of a response body: `{ event, data }` (data: the joined data lines) */
|
|
19071
|
-
async function* sse(res) {
|
|
19072
|
-
const reader = res.body.getReader(), dec = new TextDecoder();
|
|
19073
|
-
let buf = '', cr = '', done = false;
|
|
19074
|
-
try {
|
|
19075
|
-
for (;;) {
|
|
19076
|
-
const r = await reader.read();
|
|
19077
|
-
// line ends -> \n (a \r at a chunk's end waits: it may be half of a \r\n)
|
|
19078
|
-
let s = cr + (r.done ? '\n\n' : dec.decode(r.value, { stream: true }));
|
|
19079
|
-
cr = !r.done && s.endsWith('\r') ? '\r' : '';
|
|
19080
|
-
if (cr)
|
|
19081
|
-
s = s.slice(0, -1);
|
|
19082
|
-
buf += s.replace(/\r\n?/g, '\n');
|
|
19083
|
-
done = r.done;
|
|
19084
|
-
let i;
|
|
19085
|
-
while ((i = buf.indexOf('\n\n')) >= 0) {
|
|
19086
|
-
const frame = buf.slice(0, i);
|
|
19087
|
-
buf = buf.slice(i + 2);
|
|
19088
|
-
let event = 'message', data = [];
|
|
19089
|
-
for (const line of frame.split('\n')) {
|
|
19090
|
-
if (!line || line[0] == ':')
|
|
19091
|
-
continue;
|
|
19092
|
-
const c = line.indexOf(':'), field = c < 0 ? line : line.slice(0, c);
|
|
19093
|
-
const v = c < 0 ? '' : line.slice(c + 1).replace(/^ /, '');
|
|
19094
|
-
if (field == 'event')
|
|
19095
|
-
event = v;
|
|
19096
|
-
else if (field == 'data')
|
|
19097
|
-
data.push(v);
|
|
19098
|
-
}
|
|
19099
|
-
if (data.length)
|
|
19100
|
-
yield { event, data: data.join('\n') };
|
|
19101
|
-
}
|
|
19102
|
-
if (done)
|
|
19103
|
-
return;
|
|
19104
|
-
}
|
|
19105
|
-
}
|
|
19106
|
-
finally {
|
|
19107
|
-
if (!done)
|
|
19108
|
-
reader.cancel().catch(() => { });
|
|
19109
|
-
}
|
|
19110
|
-
}
|
|
19111
|
-
/** JSON.parse, or the text itself when it isn't JSON (a model's malformed tool arguments go to validation as is) */
|
|
19112
|
-
const json = (s) => {
|
|
19113
|
-
if (typeof s != 'string')
|
|
19114
|
-
return s;
|
|
19115
|
-
try {
|
|
19116
|
-
return JSON.parse(s || '{}');
|
|
19117
|
-
}
|
|
19118
|
-
catch (_) {
|
|
19119
|
-
return s;
|
|
19120
|
-
}
|
|
19121
|
-
};
|
|
19122
|
-
/** a provider's usage as `{ inputTokens, outputTokens, totalTokens, reasoningTokens? }` (AI SDK names) */
|
|
19123
|
-
const usageOf = (u) => u && {
|
|
19124
|
-
inputTokens: u.input_tokens ?? u.prompt_tokens,
|
|
19125
|
-
outputTokens: u.output_tokens ?? u.completion_tokens,
|
|
19126
|
-
totalTokens: u.total_tokens,
|
|
19127
|
-
...((u.output_tokens_details ?? u.completion_tokens_details)?.reasoning_tokens != null && { reasoningTokens: (u.output_tokens_details ?? u.completion_tokens_details).reasoning_tokens }),
|
|
19128
|
-
};
|
|
19129
|
-
/** the parts of a message (a `content` string is one text part) */
|
|
19130
|
-
const partsOf = (m) => m.parts ?? (typeof m.content == 'string' ? [{ type: 'text', text: m.content }] : []);
|
|
19131
|
-
/** the tool name of a `tool-<name>` / `dynamic-tool` part, or '' */
|
|
19132
|
-
const toolName = (p) => p.type == 'dynamic-tool' ? p.toolName : p.type.startsWith('tool-') ? p.type.slice(5) : '';
|
|
19133
|
-
/** what a tool part sends back to the model, or undefined when it has no result yet */
|
|
19134
|
-
const toolOutput = (p) => p.state == 'output-available' ? (typeof p.output == 'string' ? p.output : JSON.stringify(p.output ?? null))
|
|
19135
|
-
: p.state == 'output-error' ? JSON.stringify({ error: p.errorText ?? 'error' })
|
|
19136
|
-
: p.state == 'output-denied' ? JSON.stringify({ error: 'The user denied this tool call' })
|
|
19137
|
-
: undefined;
|
|
19138
|
-
// dev-only diagnostics (SYG675): the dev entry formats them (checks/chat.ts)
|
|
19139
|
-
const dev$2 = (...a) => g.__SYGNAL_DIAGNOSTICS__?.chat?.(...a);
|
|
19140
|
-
|
|
19141
19595
|
const EMPTY$1 = { type: 'object', properties: {} };
|
|
19142
19596
|
function prepare(req, strict, seen, transport, dialect = 'openai', limits) {
|
|
19143
19597
|
const sent = {};
|
|
19144
19598
|
if (strict && typeof strict != 'function' && !seen.has('')) {
|
|
19145
19599
|
seen.add('');
|
|
19146
|
-
dev$
|
|
19600
|
+
dev$8('SYG672', req, { transport });
|
|
19147
19601
|
}
|
|
19148
19602
|
const fn = typeof strict == 'function' ? strict : undefined;
|
|
19149
19603
|
// what the request has left of `limits` (strictSchemas counts it down)
|
|
@@ -19156,7 +19610,7 @@ function prepare(req, strict, seen, transport, dialect = 'openai', limits) {
|
|
|
19156
19610
|
return { schema: s.schema, strict: true };
|
|
19157
19611
|
if (!seen.has(what)) {
|
|
19158
19612
|
seen.add(what);
|
|
19159
|
-
dev$
|
|
19613
|
+
dev$8('SYG675', req, { tool: what, errors: s.errors });
|
|
19160
19614
|
}
|
|
19161
19615
|
return { schema, strict: false };
|
|
19162
19616
|
};
|
|
@@ -19227,12 +19681,12 @@ function openResponses(options = {}) {
|
|
|
19227
19681
|
c.done = 1;
|
|
19228
19682
|
called++;
|
|
19229
19683
|
const name = c.name ?? item?.name;
|
|
19230
|
-
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)) };
|
|
19231
19685
|
};
|
|
19232
19686
|
for await (const { data } of sse(res)) {
|
|
19233
19687
|
if (data == '[DONE]')
|
|
19234
19688
|
break;
|
|
19235
|
-
const e = json(data), t = e?.type;
|
|
19689
|
+
const e = json$1(data), t = e?.type;
|
|
19236
19690
|
if (typeof t != 'string')
|
|
19237
19691
|
continue;
|
|
19238
19692
|
if (t == 'response.output_text.delta' || t == 'response.refusal.delta' || REASONING.test(t)) {
|
|
@@ -19324,13 +19778,13 @@ function chatCompletions(options = {}) {
|
|
|
19324
19778
|
const done = function* () {
|
|
19325
19779
|
for (const c of calls)
|
|
19326
19780
|
if (c)
|
|
19327
|
-
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)) };
|
|
19328
19782
|
calls = [];
|
|
19329
19783
|
};
|
|
19330
19784
|
for await (const { data } of sse(res)) {
|
|
19331
19785
|
if (data == '[DONE]')
|
|
19332
19786
|
break;
|
|
19333
|
-
const e = json(data);
|
|
19787
|
+
const e = json$1(data);
|
|
19334
19788
|
if (!e || typeof e != 'object')
|
|
19335
19789
|
continue;
|
|
19336
19790
|
if (e.error)
|
|
@@ -19418,7 +19872,7 @@ function uiMessageStream(url, options = {}) {
|
|
|
19418
19872
|
for await (const { data } of sse(res)) {
|
|
19419
19873
|
if (data == '[DONE]')
|
|
19420
19874
|
break;
|
|
19421
|
-
const c = json(data), t = c?.type;
|
|
19875
|
+
const c = json$1(data), t = c?.type;
|
|
19422
19876
|
if (typeof t != 'string')
|
|
19423
19877
|
continue;
|
|
19424
19878
|
if (ONE.test(t)) {
|
|
@@ -20149,7 +20603,7 @@ const commandBar = (options = {}) => {
|
|
|
20149
20603
|
...(deny && { DENY: on(DOM, deny) }),
|
|
20150
20604
|
};
|
|
20151
20605
|
},
|
|
20152
|
-
model: {
|
|
20606
|
+
model: fromPrevious({
|
|
20153
20607
|
INPUT: (s, v) => typeof v == 'string' && v !== s.text ? { ...s, text: v } : ABORT,
|
|
20154
20608
|
RUN: {
|
|
20155
20609
|
STATE: (s, d, _n, p, o, k) => runStep(s, d, E(p, k, o))?.s || ABORT,
|
|
@@ -20185,7 +20639,7 @@ const commandBar = (options = {}) => {
|
|
|
20185
20639
|
// a command ran (or was refused): the result; the input is cleared when it was this command
|
|
20186
20640
|
DONE: (s, d) => s.status != 'running' ? ABORT
|
|
20187
20641
|
: { ...s, status: 'ready', command: null, pending: null, result: d, text: d?.ok && s.text.trim() === d.command ? '' : s.text },
|
|
20188
|
-
},
|
|
20642
|
+
}),
|
|
20189
20643
|
})(options);
|
|
20190
20644
|
return linked(b);
|
|
20191
20645
|
};
|
|
@@ -20285,7 +20739,7 @@ function anthropicMessages(options = {}) {
|
|
|
20285
20739
|
return { inputTokens: i, outputTokens: o, totalTokens: i + o, ...(u.output_tokens_details?.thinking_tokens != null && { reasoningTokens: u.output_tokens_details.thinking_tokens }) };
|
|
20286
20740
|
};
|
|
20287
20741
|
for await (const { data } of sse(res)) {
|
|
20288
|
-
const e = json(data), t = e?.type;
|
|
20742
|
+
const e = json$1(data), t = e?.type;
|
|
20289
20743
|
if (typeof t != 'string')
|
|
20290
20744
|
continue;
|
|
20291
20745
|
if (t == 'message_start') {
|
|
@@ -20321,9 +20775,9 @@ function anthropicMessages(options = {}) {
|
|
|
20321
20775
|
if (b?.type == 'thinking' && b.signature)
|
|
20322
20776
|
yield { type: 'reasoning', delta: '', providerMetadata: { anthropic: { signature: b.signature } } };
|
|
20323
20777
|
else if (b?.type == 'tool_use')
|
|
20324
|
-
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 ?? {}) };
|
|
20325
20779
|
else if (b?.type == 'server_tool_use')
|
|
20326
|
-
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 };
|
|
20327
20781
|
}
|
|
20328
20782
|
else if (t == 'message_delta') {
|
|
20329
20783
|
if (e.delta?.stop_reason)
|
|
@@ -20463,10 +20917,10 @@ function agui(url, options = {}) {
|
|
|
20463
20917
|
if (!c || c.done)
|
|
20464
20918
|
return;
|
|
20465
20919
|
c.done = 1;
|
|
20466
|
-
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 || '{}') });
|
|
20467
20921
|
};
|
|
20468
20922
|
for await (const { data } of sse(res)) {
|
|
20469
|
-
const e = json(data), t = e?.type;
|
|
20923
|
+
const e = json$1(data), t = e?.type;
|
|
20470
20924
|
if (typeof t != 'string')
|
|
20471
20925
|
continue;
|
|
20472
20926
|
if (t == 'TEXT_MESSAGE_START') {
|
|
@@ -20514,7 +20968,7 @@ function agui(url, options = {}) {
|
|
|
20514
20968
|
else if (t == 'TOOL_CALL_RESULT') {
|
|
20515
20969
|
end(e.toolCallId);
|
|
20516
20970
|
yield* take(e.toolCallId);
|
|
20517
|
-
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 };
|
|
20518
20972
|
}
|
|
20519
20973
|
else if (t == 'STATE_SNAPSHOT' || t == 'STATE_DELTA') {
|
|
20520
20974
|
state = t == 'STATE_SNAPSHOT' ? e.snapshot : applyPatch(state, e.delta);
|
|
@@ -21715,7 +22169,7 @@ function composeBehavior(catalog, options = {}) {
|
|
|
21715
22169
|
.map(({ key, name, value }) => ({ key, name, value })) }),
|
|
21716
22170
|
};
|
|
21717
22171
|
},
|
|
21718
|
-
model: {
|
|
22172
|
+
model: fromPrevious({
|
|
21719
22173
|
PROMPT: (s, p) => typeof p == 'string' && p !== s.prompt ? { ...s, prompt: p } : ABORT,
|
|
21720
22174
|
COMPOSE: { HOST: host(start), [S]: sink(start) },
|
|
21721
22175
|
REPLY: {
|
|
@@ -21757,7 +22211,7 @@ function composeBehavior(catalog, options = {}) {
|
|
|
21757
22211
|
CONTROL: (s, c) => c && typeof c.key == 'string' && typeof c.name == 'string'
|
|
21758
22212
|
? { ...s, ui: { ...s.ui, [c.key]: { ...s.ui?.[c.key], [c.name]: c.value } } } : ABORT,
|
|
21759
22213
|
DONE: () => ABORT,
|
|
21760
|
-
},
|
|
22214
|
+
}),
|
|
21761
22215
|
})(rest);
|
|
21762
22216
|
}
|
|
21763
22217
|
|
|
@@ -25822,7 +26276,7 @@ const compose = /*#__PURE__*/ Object.assign((catalog, options) => composeBehavio
|
|
|
25822
26276
|
* is gone); set: the array with that element replaced (the state unchanged when it is gone).
|
|
25823
26277
|
* Side-effect free.
|
|
25824
26278
|
*/
|
|
25825
|
-
const byId = (field, id, idKey = 'id') => ({
|
|
26279
|
+
const byId = (field, id, idKey = 'id') => Object.defineProperty({
|
|
25826
26280
|
get: (s) => (Array.isArray(s?.[field]) ? s[field].find((x) => x?.[idKey] === id) : undefined),
|
|
25827
26281
|
set: (s, v) => {
|
|
25828
26282
|
const a = s?.[field];
|
|
@@ -25830,7 +26284,7 @@ const byId = (field, id, idKey = 'id') => ({
|
|
|
25830
26284
|
return s;
|
|
25831
26285
|
return { ...s, [field]: a.map((x) => (x?.[idKey] === id ? v : x)) };
|
|
25832
26286
|
},
|
|
25833
|
-
});
|
|
26287
|
+
}, BINDING, { value: [field, id, idKey] });
|
|
25834
26288
|
|
|
25835
26289
|
/**
|
|
25836
26290
|
* ESM ports of xstream's `flattenConcurrently` and `flattenSequentially` extras
|