@tiaforge-ui/sinter 0.0.0-stage → 0.1.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 (80) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +132 -2
  3. package/dist/cache.d.ts +5 -0
  4. package/dist/cache.js +95 -0
  5. package/dist/check/index.d.ts +82 -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 +47 -0
  10. package/dist/check/shadow.js +245 -0
  11. package/dist/cli.d.ts +2 -0
  12. package/dist/cli.js +67 -0
  13. package/dist/compiler/check-unit.d.ts +97 -0
  14. package/dist/compiler/check-unit.js +3 -0
  15. package/dist/compiler/compile.d.ts +62 -0
  16. package/dist/compiler/compile.js +1133 -0
  17. package/dist/compiler/diagnostic-docs.d.ts +10 -0
  18. package/dist/compiler/diagnostic-docs.js +137 -0
  19. package/dist/compiler/diagnostics.d.ts +56 -0
  20. package/dist/compiler/diagnostics.js +76 -0
  21. package/dist/compiler/expr.d.ts +89 -0
  22. package/dist/compiler/expr.js +838 -0
  23. package/dist/compiler/index.d.ts +21 -0
  24. package/dist/compiler/index.js +106 -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 +38 -0
  29. package/dist/dev.d.ts +10 -0
  30. package/dist/dev.js +92 -0
  31. package/dist/index.d.ts +7 -0
  32. package/dist/index.js +4 -0
  33. package/dist/ir.d.ts +127 -0
  34. package/dist/ir.js +121 -0
  35. package/dist/lsp/quickfix.d.ts +15 -0
  36. package/dist/lsp/quickfix.js +58 -0
  37. package/dist/lsp/rpc.d.ts +31 -0
  38. package/dist/lsp/rpc.js +120 -0
  39. package/dist/lsp/server.d.ts +36 -0
  40. package/dist/lsp/server.js +281 -0
  41. package/dist/lsp/spec-location.d.ts +9 -0
  42. package/dist/lsp/spec-location.js +42 -0
  43. package/dist/lsp/typescript.d.ts +77 -0
  44. package/dist/lsp/typescript.js +242 -0
  45. package/dist/lsp/workspace.d.ts +56 -0
  46. package/dist/lsp/workspace.js +227 -0
  47. package/dist/optimistic.d.ts +3 -0
  48. package/dist/optimistic.js +15 -0
  49. package/dist/paginate.d.ts +3 -0
  50. package/dist/paginate.js +184 -0
  51. package/dist/persist.d.ts +3 -0
  52. package/dist/persist.js +55 -0
  53. package/dist/project.d.ts +36 -0
  54. package/dist/project.js +100 -0
  55. package/dist/runtime/app.d.ts +125 -0
  56. package/dist/runtime/app.js +1183 -0
  57. package/dist/runtime/config.d.ts +53 -0
  58. package/dist/runtime/config.js +118 -0
  59. package/dist/runtime/env.d.ts +18 -0
  60. package/dist/runtime/env.js +23 -0
  61. package/dist/runtime/helpers.d.ts +9 -0
  62. package/dist/runtime/helpers.js +55 -0
  63. package/dist/runtime/request.d.ts +23 -0
  64. package/dist/runtime/request.js +93 -0
  65. package/dist/runtime/security.d.ts +12 -0
  66. package/dist/runtime/security.js +34 -0
  67. package/dist/runtime/store.d.ts +95 -0
  68. package/dist/runtime/store.js +142 -0
  69. package/dist/sse.d.ts +7 -0
  70. package/dist/sse.js +206 -0
  71. package/dist/testing/index.d.ts +71 -0
  72. package/dist/testing/index.js +345 -0
  73. package/dist/vite.d.ts +11 -0
  74. package/dist/vite.js +94 -0
  75. package/dist/ws.d.ts +18 -0
  76. package/dist/ws.js +57 -0
  77. package/docs/diagnostics.md +273 -0
  78. package/html-custom-data.json +234 -0
  79. package/llms.txt +531 -0
  80. package/package.json +90 -4
@@ -0,0 +1,184 @@
1
+ class PagerImpl {
2
+ loader;
3
+ move;
4
+ cfg;
5
+ constructor(loader) {
6
+ this.loader = loader;
7
+ this.cfg = loader.def.paginate;
8
+ this.move = this.first();
9
+ const m = loader.res.meta;
10
+ if (this.cfg.mode === 'offset')
11
+ m.page = 1;
12
+ Object.assign(m, { pages: null, from: null, to: null, hasPrev: false, hasNext: false });
13
+ }
14
+ base(dir, size = this.cfg.size) {
15
+ const p = { [this.cfg.sizeParam]: String(size) };
16
+ if (dir)
17
+ p[this.cfg.directionParam] = dir;
18
+ return p;
19
+ }
20
+ first() {
21
+ if (this.cfg.mode === 'offset')
22
+ return { dir: 'page', page: 1, params: { [this.cfg.pageParam]: '1', ...this.base(null) }, from: null, to: null };
23
+ return { dir: 'first', page: 1, params: this.base('first'), from: null, to: null };
24
+ }
25
+ reset() {
26
+ this.move = this.first();
27
+ }
28
+ current() {
29
+ if (this.cfg.mode === 'append' && this.move.dir !== 'first') {
30
+ // refetch reloads everything loaded so far in one request
31
+ const n = Math.max(this.cfg.size, this.loader.res.all().length);
32
+ return { dir: 'all', page: 1, params: this.base('first', n), from: null, to: null };
33
+ }
34
+ return this.move;
35
+ }
36
+ params(move) {
37
+ return move.params;
38
+ }
39
+ appends(move) {
40
+ return this.cfg.mode === 'append' && move.dir === 'next';
41
+ }
42
+ go(target) {
43
+ const meta = this.loader.res.meta;
44
+ const items = this.loader.res.all();
45
+ const cursor = (item) => {
46
+ if (this.cfg.cursor == null || item === undefined)
47
+ return {};
48
+ const scope = Object.create(this.loader.app.page);
49
+ scope.$p = item;
50
+ const c = this.loader.app.fns[this.cfg.cursor](scope);
51
+ const out = {};
52
+ for (const [k, v] of Object.entries(c ?? {}))
53
+ out[k] = v == null ? '' : String(v);
54
+ return out;
55
+ };
56
+ let move;
57
+ const from = meta.from ?? null, to = meta.to ?? null;
58
+ if (this.cfg.mode === 'offset') {
59
+ const page = meta.page ?? 1;
60
+ let n;
61
+ if (typeof target === 'number')
62
+ n = target;
63
+ else if (target === 'first')
64
+ n = 1;
65
+ else if (target === 'prev')
66
+ n = page - 1;
67
+ else if (target === 'next')
68
+ n = page + 1;
69
+ else if (target === 'last') {
70
+ if (meta.pages == null) {
71
+ this.loader.app.warn(`page('${this.loader.def.name}', 'last') needs a known total`);
72
+ return;
73
+ }
74
+ n = meta.pages;
75
+ }
76
+ else
77
+ return;
78
+ n = Math.max(1, Math.floor(n));
79
+ move = { dir: 'page', page: n, params: { [this.cfg.pageParam]: String(n), ...this.base(null) }, from, to };
80
+ }
81
+ else {
82
+ if (typeof target === 'number') {
83
+ this.loader.app.warn('keyset pagination cannot jump to a page number; use paginate="offset"');
84
+ return;
85
+ }
86
+ if (this.cfg.mode === 'append' && target !== 'next')
87
+ return;
88
+ const dir = target;
89
+ const c = dir === 'next' ? cursor(items[items.length - 1]) : dir === 'prev' ? cursor(items[0]) : {};
90
+ move = { dir, page: 1, params: { ...c, ...this.base(dir) }, from, to };
91
+ }
92
+ void this.loader.load(move);
93
+ }
94
+ received(items, total, mv) {
95
+ const move = mv;
96
+ if (move.dir !== 'all')
97
+ this.move = move;
98
+ const meta = this.loader.res.meta;
99
+ const n = items.length;
100
+ const size = this.cfg.size;
101
+ const full = n >= size;
102
+ const pages = total != null ? Math.max(1, Math.ceil(total / size)) : null;
103
+ let from = null, to = null, hasPrev = false, hasNext = false;
104
+ switch (move.dir) {
105
+ case 'page':
106
+ from = n ? (move.page - 1) * size + 1 : 0;
107
+ to = n ? from + n - 1 : 0;
108
+ hasPrev = move.page > 1;
109
+ hasNext = pages != null ? move.page < pages : full;
110
+ meta.page = move.page;
111
+ break;
112
+ case 'first':
113
+ from = n ? 1 : 0;
114
+ to = n;
115
+ hasPrev = false;
116
+ hasNext = full;
117
+ break;
118
+ case 'all':
119
+ from = n ? 1 : 0;
120
+ to = n;
121
+ hasPrev = false;
122
+ hasNext = meta.hasNext ?? full;
123
+ break;
124
+ case 'next':
125
+ if (this.cfg.mode === 'append') {
126
+ const all = this.loader.res.all().length;
127
+ from = all ? 1 : 0;
128
+ to = all;
129
+ hasPrev = false;
130
+ hasNext = full;
131
+ break;
132
+ }
133
+ from = move.to != null ? move.to + 1 : null;
134
+ to = from != null ? from + n - 1 : null;
135
+ hasPrev = true;
136
+ hasNext = full;
137
+ break;
138
+ case 'prev':
139
+ hasNext = true;
140
+ if (!full) {
141
+ from = n ? 1 : 0;
142
+ to = n;
143
+ hasPrev = false;
144
+ break;
145
+ }
146
+ to = move.from != null ? move.from - 1 : null;
147
+ from = to != null ? to - n + 1 : null;
148
+ hasPrev = from == null || from > 1;
149
+ break;
150
+ case 'last':
151
+ hasNext = false;
152
+ if (total != null) {
153
+ to = total;
154
+ from = total - n + 1;
155
+ hasPrev = from > 1;
156
+ }
157
+ else
158
+ hasPrev = full;
159
+ break;
160
+ }
161
+ Object.assign(meta, { pages, from, to, hasPrev, hasNext });
162
+ this.loader.res.onDirty('meta');
163
+ }
164
+ }
165
+ const paginate = {
166
+ name: 'paginate',
167
+ install(app) {
168
+ const pagers = new Map();
169
+ app.ext.paginate = {
170
+ pager(loader) {
171
+ const p = new PagerImpl(loader);
172
+ pagers.set(loader.def.name, p);
173
+ return p;
174
+ },
175
+ page(name, target) {
176
+ const p = pagers.get(name);
177
+ if (!p)
178
+ throw new Error(`sinter: resource \`${name}\` is not paginated`);
179
+ p.go(target);
180
+ },
181
+ };
182
+ },
183
+ };
184
+ export default paginate;
@@ -0,0 +1,3 @@
1
+ import type { Feature } from './runtime/app.js';
2
+ declare const persist: Feature;
3
+ export default persist;
@@ -0,0 +1,55 @@
1
+ const PREFIX = 'sn:';
2
+ const persist = {
3
+ name: 'persist',
4
+ install(app) {
5
+ const read = (key) => {
6
+ try {
7
+ const v = app.env.storage()?.getItem(PREFIX + key);
8
+ return v == null ? undefined : JSON.parse(v);
9
+ }
10
+ catch {
11
+ return undefined;
12
+ }
13
+ };
14
+ const write = (key, value) => {
15
+ try {
16
+ app.env.storage()?.setItem(PREFIX + key, JSON.stringify(value));
17
+ }
18
+ catch { /* ignored */ }
19
+ };
20
+ app.ext.persist = {
21
+ model(rb, set) {
22
+ const key = rb.b.p;
23
+ const v = read(key);
24
+ if (v !== undefined)
25
+ set(v);
26
+ const paths = (rb.b.w ?? []).map((w) => w.split('.').map((s) => (s[0] === '{' ? '*' : s)));
27
+ app.afterFlush.push((dirty) => {
28
+ if (!dirty.some((d) => paths.some((p) => overlaps(p, d))))
29
+ return;
30
+ try {
31
+ write(key, app.fns[rb.b.f](rb.s));
32
+ }
33
+ catch { /* unreadable: skip */ }
34
+ });
35
+ },
36
+ state(key, obj) {
37
+ const v = read(key);
38
+ if (!v || typeof v !== 'object')
39
+ return;
40
+ for (const k of Object.keys(obj))
41
+ if (Object.hasOwn(v, k))
42
+ obj[k] = v[k];
43
+ },
44
+ saveState: write,
45
+ };
46
+ },
47
+ };
48
+ function overlaps(a, b) {
49
+ const n = Math.min(a.length, b.length);
50
+ for (let i = 0; i < n; i++)
51
+ if (a[i] !== b[i] && a[i] !== '*' && b[i] !== '*')
52
+ return false;
53
+ return true;
54
+ }
55
+ export default persist;
@@ -0,0 +1,36 @@
1
+ import { type Diagnostic, type HelperManifest } from './compiler/index.js';
2
+ import type { OpenApiSpec } from './compiler/openapi.js';
3
+ export interface ProjectOptions {
4
+ /** helpers module, relative to the root (default `src/sinter.helpers.js`, or `.ts`) */
5
+ helpers?: string;
6
+ /** app configuration module imported before mount (default `src/sinter.config.js`, or `.ts`) */
7
+ config?: string;
8
+ /** module whose exported types `type="…"` on `<sn-resource>` may name (default `src/sinter.types.ts`, `.d.ts` or `.js`) */
9
+ types?: string;
10
+ /** OpenAPI specs (JSON or YAML, inside the root) that resources without `type` are matched against */
11
+ openapi?: string | string[];
12
+ }
13
+ export interface Project {
14
+ root: string;
15
+ helpers: HelperManifest;
16
+ /** absolute path of the helpers module, or null */
17
+ helpersFile: string | null;
18
+ /** root-relative URL of the config module (`/src/sinter.config.js`), or null */
19
+ config: string | null;
20
+ /** absolute path of the types module, or null */
21
+ types: string | null;
22
+ /** reads a file only if it resolves inside the root, symlinks included */
23
+ readFile: (path: string) => string;
24
+ openapi: OpenApiSpec[];
25
+ /** problems loading the specs (SN307) */
26
+ diagnostics: Diagnostic[];
27
+ }
28
+ export declare function project(root: string, options?: ProjectOptions): Project;
29
+ /**
30
+ * The pages (HTML entries) and sinter plugin options from the root's Vite config. Without
31
+ * Vite or entries, `index.html` if it exists.
32
+ */
33
+ export declare function viteProject(root: string): Promise<{
34
+ pages: string[];
35
+ options: ProjectOptions;
36
+ }>;
@@ -0,0 +1,100 @@
1
+ // The app's conventional files (helpers, config and types modules), found the same
2
+ // way by the Vite plugin and by `sinter check`.
3
+ import { existsSync, readFileSync, realpathSync } from 'node:fs';
4
+ import { createRequire } from 'node:module';
5
+ import { dirname, resolve, sep } from 'node:path';
6
+ import { readHelperManifest } from './compiler/index.js';
7
+ export function project(root, options = {}) {
8
+ const find = (given, base, exts = ['.js', '.ts']) => {
9
+ const candidates = given ? [given] : exts.map((e) => base + e);
10
+ for (const c of candidates)
11
+ if (existsSync(resolve(root, c)))
12
+ return c;
13
+ return null;
14
+ };
15
+ const readFile = (path) => {
16
+ const real = realpathSync(path);
17
+ const realRoot = realpathSync(root);
18
+ if (real !== realRoot && !real.startsWith(realRoot + sep))
19
+ throw new Error(`outside the project root: ${path}`);
20
+ return readFileSync(real, 'utf8');
21
+ };
22
+ const url = (rel) => '/' + rel.split(sep).join('/');
23
+ let helpers = { module: null, names: [], namespaces: {} };
24
+ const helpersRel = find(options.helpers, 'src/sinter.helpers');
25
+ const helpersFile = helpersRel ? resolve(root, helpersRel) : null;
26
+ if (helpersRel && helpersFile) {
27
+ helpers = readHelperManifest(readFileSync(helpersFile, 'utf8'), {
28
+ module: url(helpersRel),
29
+ file: helpersFile,
30
+ readFile,
31
+ resolve: (spec, from) => {
32
+ if (!spec.startsWith('.'))
33
+ return null;
34
+ const base = resolve(dirname(from), spec);
35
+ for (const p of [base, base.replace(/\.js$/, '.ts'), `${base}.js`, `${base}.ts`])
36
+ if (existsSync(p))
37
+ return p;
38
+ return null;
39
+ },
40
+ });
41
+ }
42
+ const configRel = find(options.config, 'src/sinter.config');
43
+ const typesRel = find(options.types, 'src/sinter.types', ['.ts', '.d.ts', '.js']);
44
+ const openapi = [];
45
+ const diagnostics = [];
46
+ for (const rel of options.openapi == null ? [] : Array.isArray(options.openapi) ? options.openapi : [options.openapi]) {
47
+ const file = resolve(root, rel);
48
+ const fail = (why) => diagnostics.push({ severity: 'error', code: 'SN307', message: `OpenAPI spec \`${rel}\` ${why}`, file, line: 1, col: 1 });
49
+ let text;
50
+ try {
51
+ text = readFile(file);
52
+ }
53
+ catch (e) {
54
+ fail(`cannot be read: ${e.message}`);
55
+ continue;
56
+ }
57
+ let doc;
58
+ try {
59
+ doc = /\.json$/i.test(file) ? JSON.parse(text) : createRequire(import.meta.url)('yaml').parse(text);
60
+ }
61
+ catch (e) {
62
+ fail(`is not valid ${/\.json$/i.test(file) ? 'JSON' : 'YAML'}: ${e.message.split('\n')[0]}`);
63
+ continue;
64
+ }
65
+ const d = doc;
66
+ if (!d || typeof d.openapi !== 'string' || !/^3\./.test(d.openapi) || typeof d.paths !== 'object') {
67
+ fail('is not an OpenAPI 3 document (needs `openapi: 3.x` and `paths`)');
68
+ continue;
69
+ }
70
+ openapi.push({ file, doc });
71
+ }
72
+ return {
73
+ root, helpers, helpersFile, readFile, openapi, diagnostics,
74
+ config: configRel ? url(configRel) : null,
75
+ types: typesRel ? resolve(root, typesRel) : null,
76
+ };
77
+ }
78
+ /**
79
+ * The pages (HTML entries) and sinter plugin options from the root's Vite config. Without
80
+ * Vite or entries, `index.html` if it exists.
81
+ */
82
+ export async function viteProject(root) {
83
+ const fallback = () => (existsSync(resolve(root, 'index.html')) ? [resolve(root, 'index.html')] : []);
84
+ let vite;
85
+ try {
86
+ vite = await import('vite');
87
+ }
88
+ catch {
89
+ return { pages: fallback(), options: {} };
90
+ }
91
+ const config = await vite.resolveConfig({ root, logLevel: 'silent' }, 'build', 'production');
92
+ const plugin = config.plugins.find((x) => x.name === 'sinter');
93
+ const build = config.build;
94
+ const input = build.rolldownOptions?.input ?? build.rollupOptions?.input;
95
+ const list = typeof input === 'string' ? [input] : Array.isArray(input) ? input : input && typeof input === 'object' ? Object.values(input) : [];
96
+ let pages = list.filter((f) => typeof f === 'string' && f.endsWith('.html')).map((f) => resolve(config.root, f));
97
+ if (!pages.length)
98
+ pages = fallback();
99
+ return { pages, options: plugin?.api?.options ?? {} };
100
+ }
@@ -0,0 +1,125 @@
1
+ import type { Binding, Fn, IR, ResourceDef, UrlTemplate } from '../ir.js';
2
+ import { type Middleware } from './config.js';
3
+ import type { Env } from './env.js';
4
+ import { type RequestLayer, type SendInit } from './request.js';
5
+ import { type Resource } from './store.js';
6
+ export interface Feature {
7
+ name: string;
8
+ install(app: App): void;
9
+ }
10
+ export interface MountOptions {
11
+ ir: IR;
12
+ fns: Fn[];
13
+ features?: Feature[];
14
+ /** overrides for the testing module */
15
+ env?: Env;
16
+ request?: Middleware[];
17
+ /** stream transports by URL (testing); then `configure({ transport })`; default: EventSource */
18
+ transport?: (url: string) => Transport | undefined;
19
+ }
20
+ /**
21
+ * A server event transport (EventSource, WebSocket, SignalR, …). It delivers named events with
22
+ * their parsed payload and reports `open` once connected and `error` when the connection is lost;
23
+ * reconnecting (session probe, backoff, refetch) is the caller's job, so a transport never
24
+ * reconnects by itself. `names` are the event names the page maps; deliver at least those.
25
+ */
26
+ export interface Transport {
27
+ connect(url: string, onEvent: (name: string, payload: unknown) => void, onState: (state: 'open' | 'error') => void, names: string[]): void;
28
+ /** start delivering another event name (islands subscribing later) */
29
+ listen?(name: string): void;
30
+ close(): void;
31
+ }
32
+ export interface StateHandle<T = any> {
33
+ get(): T;
34
+ update(fn: (state: T) => void): void;
35
+ }
36
+ /** A binding attached to one element in one scope. */
37
+ export interface BoundBinding {
38
+ b: Binding;
39
+ el: Element;
40
+ s: any;
41
+ }
42
+ /** Pagination state of one resource; a "move" is one page request, repeated exactly by refetch. */
43
+ export interface Pager {
44
+ reset(): void;
45
+ current(): unknown;
46
+ params(move: unknown): Record<string, string> | null;
47
+ appends(move: unknown): boolean;
48
+ received(items: unknown[], total: number | null, move: unknown): void;
49
+ }
50
+ export interface ResourceLoader {
51
+ def: ResourceDef;
52
+ res: Resource;
53
+ app: App;
54
+ pager: Pager | null;
55
+ load(move?: unknown): Promise<void>;
56
+ refetch(): Promise<void>;
57
+ }
58
+ export interface App {
59
+ readonly ir: IR;
60
+ readonly fns: Fn[];
61
+ readonly env: Env;
62
+ readonly req: RequestLayer;
63
+ /** the base scope: resources, status, magic names */
64
+ readonly page: any;
65
+ readonly transport: (url: string) => Transport | undefined;
66
+ readonly ext: {
67
+ optimistic?: {
68
+ begin(fn: number, scope: any): () => void;
69
+ };
70
+ paginate?: {
71
+ pager(loader: ResourceLoader): Pager;
72
+ page(name: string, target: string | number): void;
73
+ };
74
+ persist?: {
75
+ model(rb: BoundBinding, set: (v: unknown) => void): void;
76
+ state(key: string, obj: Record<string, unknown>): void;
77
+ saveState(key: string, obj: Record<string, unknown>): void;
78
+ };
79
+ cache?: {
80
+ /** the cached data for a request, if any and not older than the resource allows */
81
+ read(def: ResourceDef, url: string): {
82
+ t: number;
83
+ items: unknown;
84
+ total: number | null;
85
+ } | null;
86
+ /** a load (or embedded data) stored `items`: cache them, and the resource's later changes */
87
+ loaded(def: ResourceDef, r: Resource, url: string, items: unknown, total: number | null, appended: boolean): void;
88
+ };
89
+ };
90
+ readonly beforeFlush: Array<(dirty: string[][]) => void>;
91
+ readonly afterFlush: Array<(dirty: string[][]) => void>;
92
+ readonly onListen: Array<(event: string) => void>;
93
+ /** root state scopes: [sid, state object] */
94
+ readonly states: Array<[number, any]>;
95
+ /** pending debounce timers and waiting actions, for the testing module */
96
+ busy: number;
97
+ readonly isDirty: boolean;
98
+ /** dev hooks: dev-only checks on, warnings by code (or text), and item preparation (freezing) */
99
+ dev: boolean;
100
+ warn(code: number | string, ...args: unknown[]): void;
101
+ prep?: <T>(item: T) => T;
102
+ /** dev: an EventSource stream opened (its connection counts against HTTP/1.1's 6 per origin) */
103
+ streamOpened?: (url: string) => void;
104
+ resource<T extends object = any>(name: string): Resource<T>;
105
+ request(url: string, init?: SendInit): Promise<unknown>;
106
+ optimistic<T>(change: () => void, run: () => Promise<T>): Promise<T>;
107
+ on(event: string, fn: (payload: any) => void): () => void;
108
+ state<T = any>(el: Element): StateHandle<T>;
109
+ destroy(): void;
110
+ url(tpl: UrlTemplate, scope: any): string;
111
+ mark(path: string, scope?: any): void;
112
+ flush(): void;
113
+ record(change: () => void): () => void;
114
+ storeFns(el: Element | null): Record<string, (...a: any[]) => unknown>;
115
+ serverEvent(name: string, payload: unknown): void;
116
+ addCleanup(fn: () => void): void;
117
+ }
118
+ /** The instance mount() created on `root`; throws if none. */
119
+ export declare function instance(root: Document | Element): App;
120
+ export declare function mount(root: Document | Element, o: MountOptions): App;
121
+ export declare function withParams(url: string, params: Record<string, string> | null): string;
122
+ /** Indexes (into `seq`) of a longest increasing subsequence, ignoring -1 entries. */
123
+ export declare function lis(seq: number[]): Set<number>;
124
+ /** Serializes a form's named controls to nested JSON, or FormData if it has a file input. */
125
+ export declare function serialize(form: HTMLFormElement): unknown;