@tiaforge-ui/sinter 0.0.0-stage → 0.2.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.
Files changed (82) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +158 -2
  3. package/dist/cache.d.ts +5 -0
  4. package/dist/cache.js +95 -0
  5. package/dist/check/index.d.ts +85 -0
  6. package/dist/check/index.js +156 -0
  7. package/dist/check/openapi-types.d.ts +6 -0
  8. package/dist/check/openapi-types.js +84 -0
  9. package/dist/check/shadow.d.ts +49 -0
  10. package/dist/check/shadow.js +279 -0
  11. package/dist/cli.d.ts +2 -0
  12. package/dist/cli.js +67 -0
  13. package/dist/compiler/check-unit.d.ts +109 -0
  14. package/dist/compiler/check-unit.js +3 -0
  15. package/dist/compiler/compile.d.ts +66 -0
  16. package/dist/compiler/compile.js +1316 -0
  17. package/dist/compiler/diagnostic-docs.d.ts +10 -0
  18. package/dist/compiler/diagnostic-docs.js +142 -0
  19. package/dist/compiler/diagnostics.d.ts +57 -0
  20. package/dist/compiler/diagnostics.js +77 -0
  21. package/dist/compiler/expr.d.ts +97 -0
  22. package/dist/compiler/expr.js +853 -0
  23. package/dist/compiler/index.d.ts +22 -0
  24. package/dist/compiler/index.js +160 -0
  25. package/dist/compiler/openapi.d.ts +40 -0
  26. package/dist/compiler/openapi.js +177 -0
  27. package/dist/compiler/vocabulary.d.ts +12 -0
  28. package/dist/compiler/vocabulary.js +39 -0
  29. package/dist/dev.d.ts +10 -0
  30. package/dist/dev.js +92 -0
  31. package/dist/each.d.ts +3 -0
  32. package/dist/each.js +121 -0
  33. package/dist/index.d.ts +7 -0
  34. package/dist/index.js +4 -0
  35. package/dist/ir.d.ts +152 -0
  36. package/dist/ir.js +121 -0
  37. package/dist/lsp/quickfix.d.ts +15 -0
  38. package/dist/lsp/quickfix.js +58 -0
  39. package/dist/lsp/rpc.d.ts +31 -0
  40. package/dist/lsp/rpc.js +120 -0
  41. package/dist/lsp/server.d.ts +36 -0
  42. package/dist/lsp/server.js +281 -0
  43. package/dist/lsp/spec-location.d.ts +9 -0
  44. package/dist/lsp/spec-location.js +42 -0
  45. package/dist/lsp/typescript.d.ts +77 -0
  46. package/dist/lsp/typescript.js +242 -0
  47. package/dist/lsp/workspace.d.ts +56 -0
  48. package/dist/lsp/workspace.js +228 -0
  49. package/dist/optimistic.d.ts +3 -0
  50. package/dist/optimistic.js +15 -0
  51. package/dist/paginate.d.ts +3 -0
  52. package/dist/paginate.js +198 -0
  53. package/dist/persist.d.ts +3 -0
  54. package/dist/persist.js +55 -0
  55. package/dist/project.d.ts +42 -0
  56. package/dist/project.js +103 -0
  57. package/dist/runtime/app.d.ts +152 -0
  58. package/dist/runtime/app.js +1256 -0
  59. package/dist/runtime/config.d.ts +53 -0
  60. package/dist/runtime/config.js +118 -0
  61. package/dist/runtime/env.d.ts +18 -0
  62. package/dist/runtime/env.js +23 -0
  63. package/dist/runtime/helpers.d.ts +9 -0
  64. package/dist/runtime/helpers.js +55 -0
  65. package/dist/runtime/request.d.ts +23 -0
  66. package/dist/runtime/request.js +93 -0
  67. package/dist/runtime/security.d.ts +12 -0
  68. package/dist/runtime/security.js +34 -0
  69. package/dist/runtime/store.d.ts +98 -0
  70. package/dist/runtime/store.js +255 -0
  71. package/dist/sse.d.ts +7 -0
  72. package/dist/sse.js +209 -0
  73. package/dist/testing/index.d.ts +73 -0
  74. package/dist/testing/index.js +359 -0
  75. package/dist/vite.d.ts +11 -0
  76. package/dist/vite.js +96 -0
  77. package/dist/ws.d.ts +18 -0
  78. package/dist/ws.js +57 -0
  79. package/docs/diagnostics.md +283 -0
  80. package/html-custom-data.json +250 -0
  81. package/llms.txt +632 -0
  82. package/package.json +93 -4
@@ -0,0 +1,255 @@
1
+ // The store: one keyed (or single-object) cache per resource. It knows nothing
2
+ // about the DOM, fetch or SSE. Each resource holds a base (what the server last
3
+ // confirmed) and ordered optimistic layers; readers see base + layers.
4
+ import { BAD_KEY } from './security.js';
5
+ /** Copies own enumerable fields, dropping keys that could pollute prototypes. */
6
+ export function clean(obj) {
7
+ if (!obj || typeof obj != 'object' || Array.isArray(obj))
8
+ return obj;
9
+ const out = {};
10
+ for (const k of Object.keys(obj))
11
+ if (!BAD_KEY.test(k))
12
+ out[k] = obj[k];
13
+ return out;
14
+ }
15
+ /** One collator per locale: constructing one is costly in some engines. */
16
+ const collators = new Map();
17
+ const collator = (l = '') => collators.get(l) ?? (collators.set(l, new Intl.Collator(l || undefined, { numeric: true })), collators.get(l));
18
+ export function createResource(name, o) {
19
+ const meta = { loading: false, error: null, loadedAt: null, pending: 0, total: null };
20
+ const coll = !!o.key, field = o.order?.replace(/^-/, ''), dir = o.order?.[0] == '-' ? -1 : 1;
21
+ const subs = new Set();
22
+ const prep = (x) => (o.prep ? o.prep(x) : x);
23
+ /** base items in arrival order, and each key's arrival number (ties in `order` keep arrival order) */
24
+ let base = new Map(), seq = new Map(), next = 0;
25
+ let single = null, total = null;
26
+ let layers = [], lastLayer = 0;
27
+ /**
28
+ * The base in display order, the locale it was sorted for, and the keys written since, with
29
+ * their value before the first write. Reads patch it instead of sorting
30
+ * again. Arrays handed out are never changed afterwards.
31
+ */
32
+ let sorted = null, sortedFor, touched = new Map();
33
+ /** Optimistic layers applied over the base: changed keys (null: removed), keys they add (arrival numbers), removals counted for `total`. */
34
+ let over = null;
35
+ /** what readers see; rebuilt on the first read after a change */
36
+ let view = null;
37
+ let journal = null, loads = 0;
38
+ const keyOf = (x) => typeof x == 'string' ? x : (o.key ?? []).map((f) => {
39
+ if (x[f] === undefined)
40
+ o.warn?.(14, name, f);
41
+ return x[f];
42
+ }).join('\x1f');
43
+ const merge = (cur, op) => op.op == 'upsert' ? prep(op.item)
44
+ : op.op == 'remove' ? null
45
+ : cur && prep({ ...cur, ...clean(op.fields) });
46
+ /** Applies one op to the base. */
47
+ const apply = (op) => {
48
+ if (!coll)
49
+ return void (single = merge(single, op));
50
+ const k = op.op == 'upsert' ? keyOf(op.item) : op.key, cur = base.get(k), v = merge(cur, op);
51
+ if (sorted && !touched.has(k))
52
+ touched.set(k, cur);
53
+ if (v) {
54
+ if (!cur)
55
+ seq.set(k, next++);
56
+ base.set(k, v);
57
+ }
58
+ else if (cur) {
59
+ base.delete(k);
60
+ seq.delete(k);
61
+ }
62
+ };
63
+ /** By `order` (none: all equal, so arrival order decides). */
64
+ const compare = (a, b) => {
65
+ if (!field)
66
+ return 0;
67
+ const x = a[field], y = b[field];
68
+ if (x == null || y == null)
69
+ return +(x == null) - +(y == null);
70
+ return dir * (typeof x == 'string' && typeof y == 'string' ? collator(sortedFor).compare(x, y) : +(x > y) - +(x < y));
71
+ };
72
+ /** A sorted copy of `list` without the `drop` items and with the `add` items (each with its arrival number). */
73
+ function patch(list, drop, add, arrival) {
74
+ const out = list.slice();
75
+ /** the first index whose item does not sort before `v` (with arrival number `n`, after its ties) */
76
+ const find = (v, n) => {
77
+ let lo = 0, hi = out.length;
78
+ while (lo < hi) {
79
+ const mid = (lo + hi) >> 1, c = compare(out[mid], v) || (n == null ? 0 : arrival(keyOf(out[mid])) - n);
80
+ c < 0 ? (lo = mid + 1) : (hi = mid);
81
+ }
82
+ return lo;
83
+ };
84
+ for (const v of drop) {
85
+ // among its ties, by identity; an item whose order field was changed in place is found by a scan
86
+ let i = find(v);
87
+ while (out[i] !== v && i < out.length && !compare(out[i], v))
88
+ i++;
89
+ out.splice(out[i] === v ? i : out.indexOf(v), 1);
90
+ }
91
+ for (const [v, n] of add)
92
+ out.splice(find(v, n), 0, v);
93
+ return out;
94
+ }
95
+ /** The base in display order. */
96
+ function baseList() {
97
+ const locale = o.locale?.();
98
+ if (sorted && locale === sortedFor && touched.size <= 64) {
99
+ if (touched.size) {
100
+ const drop = [], add = [];
101
+ for (const [k, old] of touched) {
102
+ if (old)
103
+ drop.push(old);
104
+ const v = base.get(k);
105
+ if (v)
106
+ add.push([v, seq.get(k)]);
107
+ }
108
+ sorted = patch(sorted, drop, add, (k) => seq.get(k));
109
+ }
110
+ }
111
+ else {
112
+ sortedFor = locale;
113
+ // a stable sort of the Map's arrival order
114
+ sorted = [...base.values()].sort(compare);
115
+ }
116
+ touched = new Map();
117
+ return sorted;
118
+ }
119
+ /** Recomputes the layers' effect and `meta.total` after any change; marks `meta` if the total changed. */
120
+ function sync() {
121
+ over = null;
122
+ if (layers.length) {
123
+ const m = new Map(), add = new Map();
124
+ let removed = 0, n = next, s = single;
125
+ for (const l of layers)
126
+ for (const op of l.ops) {
127
+ if (!coll) {
128
+ s = merge(s, op);
129
+ continue;
130
+ }
131
+ const k = op.op == 'upsert' ? keyOf(op.item) : op.key, cur = m.has(k) ? m.get(k) : base.get(k), v = merge(cur, op);
132
+ if (v) {
133
+ if (!cur)
134
+ add.set(k, n++);
135
+ m.set(k, v);
136
+ }
137
+ else {
138
+ if (cur)
139
+ removed++;
140
+ m.set(k, null);
141
+ add.delete(k);
142
+ }
143
+ }
144
+ over = { m, add, removed, s };
145
+ }
146
+ const t = total == null ? null : Math.max(0, total - (over?.removed ?? 0));
147
+ if (meta.total !== t) {
148
+ meta.total = t;
149
+ r.onDirty('meta');
150
+ }
151
+ }
152
+ /** A data change: readers rebuild their view; `meta` too when the pending count changed. */
153
+ const changed = (pending) => {
154
+ view = null;
155
+ sync();
156
+ r.onDirty('data');
157
+ if (pending) {
158
+ meta.pending = layers.length;
159
+ r.onDirty('meta');
160
+ }
161
+ };
162
+ function visible() {
163
+ if (view)
164
+ return view;
165
+ if (!coll)
166
+ return (view = { l: [], get: () => undefined, s: over ? over.s : single });
167
+ let l = baseList(), get = (k) => base.get(k);
168
+ if (over) {
169
+ const { m, add } = over, drop = [], put = [], at = (k) => add.get(k) ?? seq.get(k);
170
+ for (const [k, v] of m) {
171
+ const b = base.get(k);
172
+ if (b)
173
+ drop.push(b);
174
+ if (v)
175
+ put.push([v, at(k)]);
176
+ }
177
+ l = patch(l, drop, put, at);
178
+ get = (k) => (m.has(k) ? m.get(k) ?? undefined : base.get(k));
179
+ }
180
+ // dev builds freeze what expressions see; the compiler refuses writes to resources (SN205)
181
+ return (view = { l: o.freeze ? Object.freeze(l.slice()) : l, get, s: null });
182
+ }
183
+ function write(op) {
184
+ if (r.recording)
185
+ return void r.recording.push(op);
186
+ journal?.push(op);
187
+ apply(op);
188
+ changed();
189
+ for (const f of subs)
190
+ f({ resource: name, ...op });
191
+ }
192
+ const r = {
193
+ name, meta, key: o.key, isCollection: coll, keyOf, recording: null, loader: null, onDirty() { },
194
+ get: (k) => (coll ? visible().get(keyOf(k)) : visible().s ?? undefined),
195
+ has: (k) => r.get(k) !== undefined,
196
+ all: () => visible().l,
197
+ value: () => (coll ? visible().l : visible().s),
198
+ replace(items) {
199
+ if (coll) {
200
+ base = new Map();
201
+ seq = new Map();
202
+ sorted = null;
203
+ touched = new Map();
204
+ for (const x of Array.isArray(items) ? items : []) {
205
+ const k = keyOf(x);
206
+ if (base.has(k))
207
+ o.warn?.(13, name, k);
208
+ base.delete(k);
209
+ base.set(k, prep(x));
210
+ seq.set(k, next++);
211
+ }
212
+ }
213
+ else
214
+ single = items == null ? null : prep(items);
215
+ changed();
216
+ for (const f of subs)
217
+ f({ resource: name, op: 'refetch' });
218
+ },
219
+ upsert: (item) => write({ op: 'upsert', item }),
220
+ patch: (k, fields) => write({ op: 'patch', key: keyOf(k), fields }),
221
+ remove: (k) => write({ op: 'remove', key: keyOf(k) }),
222
+ refetch: (hint) => r.loader?.refetch(hint) ?? Promise.resolve(),
223
+ subscribe: (fn) => (subs.add(fn), () => subs.delete(fn)),
224
+ startLoad() {
225
+ loads++;
226
+ journal ??= [];
227
+ return journal.length;
228
+ },
229
+ endLoad(mark, loaded) {
230
+ const ops = journal.slice(mark);
231
+ if (--loads == 0)
232
+ journal = null;
233
+ if (!loaded || !ops.length)
234
+ return;
235
+ for (const op of ops)
236
+ apply(op);
237
+ changed();
238
+ },
239
+ addLayer(ops) {
240
+ layers.push({ id: ++lastLayer, ops });
241
+ changed(true);
242
+ return lastLayer;
243
+ },
244
+ dropLayer(id) {
245
+ const n = layers.length;
246
+ layers = layers.filter((l) => l.id != id);
247
+ if (n != layers.length)
248
+ changed(true);
249
+ },
250
+ /** the total changes `meta` only: bindings that read the items do not re-run */
251
+ setTotal(t) { total = t; sync(); },
252
+ base: () => ({ items: coll ? [...base.values()] : single, total }),
253
+ };
254
+ return r;
255
+ }
package/dist/sse.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ import type { Feature, Transport } from './runtime/app.js';
2
+ /** JSON payloads are parsed; anything else is delivered as the raw string. */
3
+ export declare const parsePayload: (data: string) => unknown;
4
+ /** The default transport: one EventSource. */
5
+ export declare function eventSourceTransport(): Transport;
6
+ declare const sse: Feature;
7
+ export default sse;
package/dist/sse.js ADDED
@@ -0,0 +1,209 @@
1
+ import { config, SinterError } from './runtime/config.js';
2
+ import { clean } from './runtime/store.js';
3
+ const MIN_BACKOFF = 1000;
4
+ const MAX_BACKOFF = 30_000;
5
+ /** JSON payloads are parsed; anything else is delivered as the raw string. */
6
+ export const parsePayload = (data) => { try {
7
+ return JSON.parse(data);
8
+ }
9
+ catch {
10
+ return data;
11
+ } };
12
+ /** The default transport: one EventSource. */
13
+ export function eventSourceTransport() {
14
+ let es = null;
15
+ let deliver = null;
16
+ const names = new Set();
17
+ const add = (name) => {
18
+ if (!es || names.has(name))
19
+ return;
20
+ names.add(name);
21
+ es.addEventListener(name, (e) => deliver?.(name, parsePayload(e.data)));
22
+ };
23
+ return {
24
+ connect(url, onEvent, onState, initial) {
25
+ deliver = onEvent;
26
+ es = new EventSource(url, { withCredentials: true });
27
+ es.onopen = () => onState('open');
28
+ es.onerror = () => onState('error');
29
+ names.clear();
30
+ for (const n of initial)
31
+ add(n);
32
+ },
33
+ listen: add,
34
+ close() { es?.close(); es = null; },
35
+ };
36
+ }
37
+ class Connection {
38
+ app;
39
+ def;
40
+ t = null;
41
+ backoff = MIN_BACKOFF;
42
+ timer = null;
43
+ closed = false;
44
+ hidden = false;
45
+ everOpen = false;
46
+ url;
47
+ fed;
48
+ /** where `src` and the events' expressions are evaluated: the enclosing state's scope, or the page */
49
+ base;
50
+ constructor(app, def) {
51
+ this.app = app;
52
+ this.def = def;
53
+ this.base = app.scopeOf(def.scope ?? -1);
54
+ this.url = app.url(def.src, this.base);
55
+ this.fed = [...new Set(def.events.map((e) => e.resource))];
56
+ }
57
+ start() {
58
+ const env = this.app.env;
59
+ this.app.addCleanup(env.listen('pagehide', () => { this.hidden = true; this.stop(); }));
60
+ this.app.addCleanup(env.listen('pageshow', () => {
61
+ if (!this.hidden)
62
+ return;
63
+ this.hidden = false;
64
+ this.closed = false;
65
+ void this.probe();
66
+ }));
67
+ this.app.onListen.push((name) => this.t?.listen?.(name));
68
+ this.app.addCleanup(() => { this.closed = true; this.stop(); });
69
+ this.open();
70
+ }
71
+ names() {
72
+ return [...new Set(this.def.events.map((e) => e.name))];
73
+ }
74
+ open() {
75
+ if (this.closed && !this.hidden)
76
+ return;
77
+ const custom = this.app.transport(this.url) ?? config.transport?.(this.url);
78
+ const t = custom ?? eventSourceTransport();
79
+ this.t = t;
80
+ const reconnect = this.everOpen;
81
+ t.connect(this.url, (name, data) => this.event(name, data), (state) => {
82
+ if (this.t !== t)
83
+ return;
84
+ if (state === 'open') {
85
+ this.backoff = MIN_BACKOFF;
86
+ this.everOpen = true;
87
+ if (!custom)
88
+ this.app.streamOpened?.(this.url);
89
+ // events sent while disconnected are lost: refetch everything this stream feeds
90
+ if (reconnect)
91
+ for (const name of this.fed)
92
+ void this.app.resource(name).refetch();
93
+ }
94
+ else {
95
+ t.close();
96
+ this.t = null;
97
+ void this.probe();
98
+ }
99
+ }, this.names());
100
+ }
101
+ stop() {
102
+ this.t?.close();
103
+ this.t = null;
104
+ if (this.timer != null) {
105
+ this.app.env.clearTimeout(this.timer);
106
+ this.timer = null;
107
+ }
108
+ }
109
+ /** The session probe gates every reconnect: 2xx reconnects, 401 leads to login, an outage backs off. */
110
+ async probe() {
111
+ if (this.closed || this.hidden)
112
+ return;
113
+ if (!config.sessionProbe) {
114
+ this.wait(() => this.open());
115
+ return;
116
+ }
117
+ let status;
118
+ try {
119
+ status = (await this.app.req.raw(config.sessionProbe)).status;
120
+ }
121
+ catch (e) {
122
+ status = e instanceof SinterError ? e.status ?? 0 : 0;
123
+ }
124
+ if (this.closed || this.hidden)
125
+ return;
126
+ if (status >= 200 && status < 300)
127
+ this.open();
128
+ else if (status !== 401)
129
+ this.wait(() => void this.probe());
130
+ // 401: the request layer's onUnauthorized handles login
131
+ }
132
+ wait(fn) {
133
+ const delay = Math.round(this.backoff * (0.75 + this.app.env.random() * 0.5));
134
+ this.backoff = Math.min(this.backoff * 2, MAX_BACKOFF);
135
+ this.timer = this.app.env.setTimeout(() => { this.timer = null; fn(); }, delay, 'backoff');
136
+ }
137
+ event(name, payload) {
138
+ for (const def of this.def.events)
139
+ if (def.name === name)
140
+ this.apply(def, payload);
141
+ this.app.serverEvent(name, payload);
142
+ }
143
+ apply(def, payload) {
144
+ const app = this.app;
145
+ const scope = Object.create(this.base);
146
+ scope.$p = payload && typeof payload === 'object' ? payload : {};
147
+ const r = app.resource(def.resource);
148
+ try {
149
+ if (def.when != null && !app.fns[def.when](scope))
150
+ return;
151
+ const paginated = !!app.ir.resources.find((x) => x.name === def.resource)?.paginate;
152
+ switch (def.op) {
153
+ case 'refetch':
154
+ void r.refetch(scope.$p);
155
+ return;
156
+ case 'upsert': {
157
+ const item = def.item != null ? app.fns[def.item](scope) : payload;
158
+ if (item == null || typeof item !== 'object') {
159
+ if (def.fallback === 'refetch')
160
+ void r.refetch(scope.$p);
161
+ return;
162
+ }
163
+ if (paginated && !r.has(item)) {
164
+ void r.refetch();
165
+ return;
166
+ }
167
+ r.upsert(clean(item));
168
+ return;
169
+ }
170
+ case 'patch': {
171
+ if (r.isCollection && !this.hasKey(r.key, scope.$p, def))
172
+ return;
173
+ const key = r.keyOf(scope.$p);
174
+ if (r.isCollection && !r.has(key))
175
+ return;
176
+ r.patch(key, clean(app.fns[def.set](scope)));
177
+ return;
178
+ }
179
+ case 'remove': {
180
+ if (r.isCollection && !this.hasKey(r.key, scope.$p, def))
181
+ return;
182
+ const key = r.keyOf(scope.$p);
183
+ r.remove(key);
184
+ return;
185
+ }
186
+ }
187
+ }
188
+ catch (e) {
189
+ app.warn(`event \`${def.name}\` could not be applied: ${e.message}`, payload);
190
+ }
191
+ }
192
+ hasKey(fields, payload, def) {
193
+ const missing = fields.filter((f) => payload[f] === undefined);
194
+ if (missing.length)
195
+ this.app.warn(`event \`${def.name}\` payload is missing key field(s) ${missing.join(', ')} for \`${def.resource}\``, payload);
196
+ return missing.length === 0;
197
+ }
198
+ }
199
+ const sse = {
200
+ name: 'sse',
201
+ install(app) {
202
+ // connect after mount has created resources and loaders
203
+ app.env.queueMicrotask(() => {
204
+ for (const def of app.ir.syncs)
205
+ new Connection(app, def).start();
206
+ });
207
+ },
208
+ };
209
+ export default sse;
@@ -0,0 +1,73 @@
1
+ import { type App, type Transport } from '../runtime/app.js';
2
+ import { type Middleware } from '../runtime/config.js';
3
+ export type RouteResponse = ((call: RecordedCall) => unknown) | {
4
+ status: number;
5
+ body?: unknown;
6
+ headers?: Record<string, string>;
7
+ } | object | string | number | boolean | null;
8
+ export interface RecordedCall {
9
+ method: string;
10
+ url: string;
11
+ path: string;
12
+ body: unknown;
13
+ headers: Record<string, string>;
14
+ }
15
+ export interface MockRequests extends Middleware {
16
+ /** every request that reached the mock, in order */
17
+ readonly calls: RecordedCall[];
18
+ /** requests waiting for settle() */
19
+ readonly pending: number;
20
+ /** answers every waiting request; returns how many */
21
+ deliver(): Promise<number>;
22
+ /** errors for unmatched requests; render() rethrows them */
23
+ readonly errors: Error[];
24
+ }
25
+ /** Request middleware answering `'METHOD /path'` routes. Responses are held until `settle()`. */
26
+ export declare function mockRequests(routes: Record<string, RouteResponse>): MockRequests;
27
+ export interface MockStream {
28
+ readonly connected: boolean;
29
+ /** delivers a server event with a JSON payload */
30
+ emit(name: string, payload?: unknown): void;
31
+ /** drops the connection, as a network error would */
32
+ drop(): void;
33
+ /** @internal */
34
+ transport(): Transport;
35
+ }
36
+ export declare function mockStream(): MockStream;
37
+ export interface RenderOptions {
38
+ requests?: MockRequests;
39
+ streams?: Record<string, MockStream>;
40
+ /** page URL, for `$url` (default `http://localhost/`) */
41
+ url?: string;
42
+ locale?: string;
43
+ /** start time of sinter's clock */
44
+ now?: number | Date;
45
+ /** helper functions, and namespaces as objects of functions */
46
+ helpers?: Record<string, unknown>;
47
+ /** action functions for `data-sn-call` and `<sn-resource call>`, and namespaces as objects of functions */
48
+ actions?: Record<string, unknown>;
49
+ /** partials for `<sn-include>`, by path relative to the page */
50
+ files?: Record<string, string>;
51
+ /** localStorage and sessionStorage (default: empty ones); pass the previous page's to test what carries over */
52
+ storage?: Storage;
53
+ session?: Storage;
54
+ }
55
+ export interface Rendered {
56
+ root: HTMLElement;
57
+ app: App;
58
+ /** runs the scheduler until no binding is dirty */
59
+ flush(): Promise<void>;
60
+ /** flushes until every request, queue and debounce has finished */
61
+ settle(): Promise<void>;
62
+ /** moves sinter's clock: debounce, backoff, `$now` */
63
+ advance(ms: number): Promise<void>;
64
+ /** URLs sinter tried to navigate to */
65
+ navigations: string[];
66
+ /** current page URL (after `$url` writes) */
67
+ url(): string;
68
+ /** dispatches pagehide/pageshow/popstate to sinter */
69
+ window(type: 'pagehide' | 'pageshow' | 'popstate'): Promise<void>;
70
+ }
71
+ export declare function render(html: string, options?: RenderOptions): Promise<Rendered>;
72
+ /** An in-memory `Storage`, e.g. to share `session` between two render() calls. */
73
+ export declare function memoryStorage(): Storage;