@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,103 @@
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
+ const manifest = (given, base) => {
24
+ const rel = find(given, base);
25
+ const file = rel ? resolve(root, rel) : null;
26
+ if (!rel || !file)
27
+ return [{ module: null, names: [], namespaces: {} }, null];
28
+ return [readHelperManifest(readFileSync(file, 'utf8'), {
29
+ module: url(rel),
30
+ file,
31
+ readFile,
32
+ resolve: (spec, from) => {
33
+ if (!spec.startsWith('.'))
34
+ return null;
35
+ const b = resolve(dirname(from), spec);
36
+ for (const q of [b, b.replace(/\.js$/, '.ts'), `${b}.js`, `${b}.ts`])
37
+ if (existsSync(q))
38
+ return q;
39
+ return null;
40
+ },
41
+ }), file];
42
+ };
43
+ const [helpers, helpersFile] = manifest(options.helpers, 'src/sinter.helpers');
44
+ const [actions, actionsFile] = manifest(options.actions, 'src/sinter.actions');
45
+ const configRel = find(options.config, 'src/sinter.config');
46
+ const typesRel = find(options.types, 'src/sinter.types', ['.ts', '.d.ts', '.js']);
47
+ const openapi = [];
48
+ const diagnostics = [];
49
+ for (const rel of options.openapi == null ? [] : Array.isArray(options.openapi) ? options.openapi : [options.openapi]) {
50
+ const file = resolve(root, rel);
51
+ const fail = (why) => diagnostics.push({ severity: 'error', code: 'SN307', message: `OpenAPI spec \`${rel}\` ${why}`, file, line: 1, col: 1 });
52
+ let text;
53
+ try {
54
+ text = readFile(file);
55
+ }
56
+ catch (e) {
57
+ fail(`cannot be read: ${e.message}`);
58
+ continue;
59
+ }
60
+ let doc;
61
+ try {
62
+ doc = /\.json$/i.test(file) ? JSON.parse(text) : createRequire(import.meta.url)('yaml').parse(text);
63
+ }
64
+ catch (e) {
65
+ fail(`is not valid ${/\.json$/i.test(file) ? 'JSON' : 'YAML'}: ${e.message.split('\n')[0]}`);
66
+ continue;
67
+ }
68
+ const d = doc;
69
+ if (!d || typeof d.openapi !== 'string' || !/^3\./.test(d.openapi) || typeof d.paths !== 'object') {
70
+ fail('is not an OpenAPI 3 document (needs `openapi: 3.x` and `paths`)');
71
+ continue;
72
+ }
73
+ openapi.push({ file, doc });
74
+ }
75
+ return {
76
+ root, helpers, helpersFile, actions, actionsFile, readFile, openapi, diagnostics,
77
+ config: configRel ? url(configRel) : null,
78
+ types: typesRel ? resolve(root, typesRel) : null,
79
+ };
80
+ }
81
+ /**
82
+ * The pages (HTML entries) and sinter plugin options from the root's Vite config. Without
83
+ * Vite or entries, `index.html` if it exists.
84
+ */
85
+ export async function viteProject(root) {
86
+ const fallback = () => (existsSync(resolve(root, 'index.html')) ? [resolve(root, 'index.html')] : []);
87
+ let vite;
88
+ try {
89
+ vite = await import('vite');
90
+ }
91
+ catch {
92
+ return { pages: fallback(), options: {} };
93
+ }
94
+ const config = await vite.resolveConfig({ root, logLevel: 'silent' }, 'build', 'production');
95
+ const plugin = config.plugins.find((x) => x.name === 'sinter');
96
+ const build = config.build;
97
+ const input = build.rolldownOptions?.input ?? build.rollupOptions?.input;
98
+ const list = typeof input === 'string' ? [input] : Array.isArray(input) ? input : input && typeof input === 'object' ? Object.values(input) : [];
99
+ let pages = list.filter((f) => typeof f === 'string' && f.endsWith('.html')).map((f) => resolve(config.root, f));
100
+ if (!pages.length)
101
+ pages = fallback();
102
+ return { pages, options: plugin?.api?.options ?? {} };
103
+ }
@@ -0,0 +1,152 @@
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
+ /** The last argument of an action function (`data-sn-call`, `<sn-resource call>`). */
51
+ export interface CallContext {
52
+ /** aborts when the page is hidden (pagehide) or, for a resource, when its inputs change */
53
+ signal: AbortSignal;
54
+ /** a request through the app's middleware chain (401 handling, origins, timeout) */
55
+ request(url: string, init?: SendInit): Promise<unknown>;
56
+ /** the element whose action this is; null for a resource */
57
+ el: Element | null;
58
+ }
59
+ export interface ResourceLoader {
60
+ def: ResourceDef;
61
+ res: Resource;
62
+ app: App;
63
+ pager: Pager | null;
64
+ /** first load (or embedded/cached data) */
65
+ start(): void;
66
+ /** `src`, `when` (or an `each` list) may have changed */
67
+ changed(): void;
68
+ load(move?: unknown): Promise<void>;
69
+ refetch(hint?: object): Promise<void>;
70
+ /** for feature modules: the scope `src` and `when` are evaluated in */
71
+ scope(): any;
72
+ /** for feature modules: a shared GET (concurrent loads of one URL share one request) */
73
+ get(url: string, signal: AbortSignal): Promise<unknown>;
74
+ /** for feature modules: the collection items of a response body (`items` path, checked, cleaned) */
75
+ items(body: unknown): any[];
76
+ /** for feature modules: a failed load: `$meta.error`, then the unhandled-error path */
77
+ failed(e: unknown): void;
78
+ loading(on: boolean): void;
79
+ }
80
+ export interface App {
81
+ readonly ir: IR;
82
+ readonly fns: Fn[];
83
+ readonly env: Env;
84
+ readonly req: RequestLayer;
85
+ /** the base scope: resources, status, magic names */
86
+ readonly page: any;
87
+ readonly transport: (url: string) => Transport | undefined;
88
+ readonly ext: {
89
+ optimistic?: {
90
+ begin(fn: number, scope: any): () => void;
91
+ };
92
+ each?: {
93
+ fanOut(loader: ResourceLoader): void;
94
+ };
95
+ paginate?: {
96
+ pager(loader: ResourceLoader): Pager;
97
+ page(name: string, target: string | number): void;
98
+ };
99
+ persist?: {
100
+ model(rb: BoundBinding, set: (v: unknown) => void): void;
101
+ state(key: string, obj: Record<string, unknown>): void;
102
+ saveState(key: string, obj: Record<string, unknown>): void;
103
+ };
104
+ cache?: {
105
+ /** the cached data for a request, if any and not older than the resource allows */
106
+ read(def: ResourceDef, url: string): {
107
+ t: number;
108
+ items: unknown;
109
+ total: number | null;
110
+ } | null;
111
+ /** a load (or embedded data) stored `items`: cache them, and the resource's later changes */
112
+ loaded(def: ResourceDef, r: Resource, url: string, items: unknown, total: number | null, appended: boolean): void;
113
+ };
114
+ };
115
+ readonly beforeFlush: Array<(dirty: string[][]) => void>;
116
+ readonly afterFlush: Array<(dirty: string[][]) => void>;
117
+ readonly onListen: Array<(event: string) => void>;
118
+ /** root state scopes: [sid, state object] */
119
+ readonly states: Array<[number, any]>;
120
+ /** pending debounce timers and waiting actions, for the testing module */
121
+ busy: number;
122
+ readonly isDirty: boolean;
123
+ /** dev hooks: dev-only checks on, warnings by code (or text), and item preparation (freezing) */
124
+ dev: boolean;
125
+ warn(code: number | string, ...args: unknown[]): void;
126
+ prep?: <T>(item: T) => T;
127
+ /** dev: an EventSource stream opened (its connection counts against HTTP/1.1's 6 per origin) */
128
+ streamOpened?: (url: string) => void;
129
+ resource<T extends object = any>(name: string): Resource<T>;
130
+ request(url: string, init?: SendInit): Promise<unknown>;
131
+ optimistic<T>(change: () => void, run: () => Promise<T>): Promise<T>;
132
+ on(event: string, fn: (payload: any) => void): () => void;
133
+ state<T = any>(el: Element): StateHandle<T>;
134
+ destroy(): void;
135
+ url(tpl: UrlTemplate, scope: any): string;
136
+ /** the scope of root node `i` (its state), or the page scope for -1 */
137
+ scopeOf(i: number): any;
138
+ mark(path: string, scope?: any): void;
139
+ flush(): void;
140
+ record(change: () => void): () => void;
141
+ storeFns(el: Element | null): Record<string, (...a: any[]) => unknown>;
142
+ serverEvent(name: string, payload: unknown): void;
143
+ addCleanup(fn: () => void): void;
144
+ }
145
+ /** The instance mount() created on `root`; throws if none. */
146
+ export declare function instance(root: Document | Element): App;
147
+ export declare function mount(root: Document | Element, o: MountOptions): App;
148
+ export declare function withParams(url: string, params: Record<string, string> | null): string;
149
+ /** Indexes (into `seq`) of a longest increasing subsequence, ignoring -1 entries. */
150
+ export declare function lis(seq: number[]): Set<number>;
151
+ /** Serializes a form's named controls to nested JSON, or FormData if it has a file input. */
152
+ export declare function serialize(form: HTMLFormElement): unknown;