tina4js 1.0.15 → 1.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 (51) hide show
  1. package/dist/api/fetch.d.ts +123 -0
  2. package/dist/api/fetch.d.ts.map +1 -0
  3. package/dist/api/index.d.ts +6 -0
  4. package/dist/api/index.d.ts.map +1 -0
  5. package/dist/core/component.d.ts +59 -0
  6. package/dist/core/component.d.ts.map +1 -0
  7. package/dist/core/html.d.ts +37 -0
  8. package/dist/core/html.d.ts.map +1 -0
  9. package/dist/core/index.d.ts +9 -0
  10. package/dist/core/index.d.ts.map +1 -0
  11. package/dist/core/signal.d.ts +101 -0
  12. package/dist/core/signal.d.ts.map +1 -0
  13. package/dist/debug/index.d.ts +17 -0
  14. package/dist/debug/index.d.ts.map +1 -0
  15. package/dist/debug/overlay.d.ts +25 -0
  16. package/dist/debug/overlay.d.ts.map +1 -0
  17. package/dist/debug/panels/api.d.ts +5 -0
  18. package/dist/debug/panels/api.d.ts.map +1 -0
  19. package/dist/debug/panels/components.d.ts +5 -0
  20. package/dist/debug/panels/components.d.ts.map +1 -0
  21. package/dist/debug/panels/routes.d.ts +5 -0
  22. package/dist/debug/panels/routes.d.ts.map +1 -0
  23. package/dist/debug/panels/signals.d.ts +5 -0
  24. package/dist/debug/panels/signals.d.ts.map +1 -0
  25. package/dist/debug/styles.d.ts +5 -0
  26. package/dist/debug/styles.d.ts.map +1 -0
  27. package/dist/debug/trackers.d.ts +89 -0
  28. package/dist/debug/trackers.d.ts.map +1 -0
  29. package/dist/index.d.ts +21 -0
  30. package/dist/index.d.ts.map +1 -0
  31. package/dist/pwa/index.d.ts +6 -0
  32. package/dist/pwa/index.d.ts.map +1 -0
  33. package/dist/pwa/pwa.d.ts +24 -0
  34. package/dist/pwa/pwa.d.ts.map +1 -0
  35. package/dist/router/index.d.ts +6 -0
  36. package/dist/router/index.d.ts.map +1 -0
  37. package/dist/router/router.d.ts +72 -0
  38. package/dist/router/router.d.ts.map +1 -0
  39. package/dist/sse/index.d.ts +6 -0
  40. package/dist/sse/index.d.ts.map +1 -0
  41. package/dist/sse/sse.d.ts +72 -0
  42. package/dist/sse/sse.d.ts.map +1 -0
  43. package/dist/sse.cjs.js +2 -0
  44. package/dist/sse.es.js +137 -0
  45. package/dist/tina4.cjs.js +1 -1
  46. package/dist/tina4.es.js +6 -4
  47. package/dist/ws/index.d.ts +6 -0
  48. package/dist/ws/index.d.ts.map +1 -0
  49. package/dist/ws/ws.d.ts +62 -0
  50. package/dist/ws/ws.d.ts.map +1 -0
  51. package/package.json +5 -1
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Tina4 API — Fetch wrapper with auth token management.
3
+ *
4
+ * Compatible with tina4-php and tina4-python backends:
5
+ * - Sends Authorization: Bearer <token>
6
+ * - Reads FreshToken response header for token rotation
7
+ * - Sends formToken in POST/PUT/PATCH/DELETE bodies
8
+ */
9
+ export interface ApiConfig {
10
+ baseUrl: string;
11
+ auth: boolean;
12
+ tokenKey: string;
13
+ headers: Record<string, string>;
14
+ }
15
+ export interface ApiResponse<T = unknown> {
16
+ status: number;
17
+ data: T;
18
+ ok: boolean;
19
+ headers: Headers;
20
+ /** @internal Used by debug tracker for request/response correlation. */
21
+ _requestId?: number;
22
+ }
23
+ export type RequestInterceptor = (config: RequestInit & {
24
+ headers: Record<string, string>;
25
+ }) => (RequestInit & {
26
+ headers: Record<string, string>;
27
+ }) | void;
28
+ export type ResponseInterceptor = (response: ApiResponse) => ApiResponse | void;
29
+ export interface RequestOptions {
30
+ headers?: Record<string, string>;
31
+ params?: Record<string, string | number | boolean>;
32
+ }
33
+ /**
34
+ * HTTP client pre-configured for tina4-php / tina4-python backends.
35
+ *
36
+ * Features:
37
+ * - Automatic `Authorization: Bearer <token>` header when `auth: true`
38
+ * - Token rotation via `FreshToken` response header
39
+ * - `formToken` injected into POST/PUT/PATCH/DELETE bodies for CSRF protection
40
+ * - Per-request `headers` and `params` via `RequestOptions`
41
+ * - Request/response interceptors
42
+ *
43
+ * @example
44
+ * api.configure({ baseUrl: 'https://api.example.com', auth: true });
45
+ *
46
+ * const users = await api.get('/users');
47
+ * const user = await api.get('/users', { params: { id: 42 } });
48
+ * await api.post('/users', { name: 'Alice' });
49
+ * await api.delete('/users/1');
50
+ */
51
+ export declare const api: {
52
+ /**
53
+ * Configure the API client. Call once at app startup.
54
+ *
55
+ * @example
56
+ * api.configure({
57
+ * baseUrl: 'https://api.example.com',
58
+ * auth: true,
59
+ * tokenKey: 'my_token', // localStorage key (default: 'tina4_token')
60
+ * headers: { 'X-App': '1' }, // default headers on every request
61
+ * });
62
+ */
63
+ configure(c: Partial<ApiConfig>): void;
64
+ /**
65
+ * HTTP GET request.
66
+ * @param path - API path relative to `baseUrl`.
67
+ * @param options - `{ params, headers }` — query string params and per-request headers.
68
+ * @example
69
+ * const data = await api.get('/products', { params: { page: 2, limit: 20 } });
70
+ */
71
+ get<T = unknown>(path: string, options?: RequestOptions): Promise<T>;
72
+ /**
73
+ * HTTP POST request.
74
+ * @param path - API path.
75
+ * @param body - Request body (serialised as JSON).
76
+ * @param options - `{ params, headers }`.
77
+ * @example
78
+ * await api.post('/users', { name: 'Alice', role: 'admin' });
79
+ */
80
+ post<T = unknown>(path: string, body?: unknown, options?: RequestOptions): Promise<T>;
81
+ /** HTTP PUT — full replace. */
82
+ put<T = unknown>(path: string, body?: unknown, options?: RequestOptions): Promise<T>;
83
+ /** HTTP PATCH — partial update. */
84
+ patch<T = unknown>(path: string, body?: unknown, options?: RequestOptions): Promise<T>;
85
+ /** HTTP DELETE. */
86
+ delete<T = unknown>(path: string, options?: RequestOptions): Promise<T>;
87
+ /**
88
+ * Upload files via FormData (multipart/form-data).
89
+ *
90
+ * Unlike `post()`, this does NOT JSON-stringify the body or set
91
+ * Content-Type — the browser sets the multipart boundary automatically.
92
+ * Auth uses the Bearer token header (formToken cannot be injected into FormData).
93
+ *
94
+ * @param path - API path relative to `baseUrl`.
95
+ * @param formData - A FormData instance containing files and fields.
96
+ * @param options - `{ params, headers }` — query string params and per-request headers.
97
+ *
98
+ * @example
99
+ * const form = new FormData();
100
+ * form.append('avatar', fileInput.files[0]);
101
+ * form.append('name', 'Alice');
102
+ * const result = await api.upload('/users/avatar', form);
103
+ */
104
+ upload<T = unknown>(path: string, formData: FormData, options?: RequestOptions): Promise<T>;
105
+ /**
106
+ * Register a request or response interceptor.
107
+ *
108
+ * @example
109
+ * // Add a custom header to every request
110
+ * api.intercept('request', (config) => {
111
+ * config.headers['X-Client'] = 'my-app';
112
+ * });
113
+ *
114
+ * // Redirect to login on 401
115
+ * api.intercept('response', (res) => {
116
+ * if (res.status === 401) navigate('/login');
117
+ * });
118
+ */
119
+ intercept(type: "request" | "response", fn: RequestInterceptor | ResponseInterceptor): void;
120
+ /** @internal Reset state (for tests). */
121
+ _reset(): void;
122
+ };
123
+ //# sourceMappingURL=fetch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../../src/api/fetch.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,OAAO,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC;AAED,MAAM,WAAW,WAAW,CAAC,CAAC,GAAG,OAAO;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,CAAC,CAAC;IACR,EAAE,EAAE,OAAO,CAAC;IACZ,OAAO,EAAE,OAAO,CAAC;IACjB,wEAAwE;IACxE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,MAAM,kBAAkB,GAAG,CAAC,MAAM,EAAE,WAAW,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,KAAK,CAAC,WAAW,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,CAAC,GAAG,IAAI,CAAC;AAC3J,MAAM,MAAM,mBAAmB,GAAG,CAAC,QAAQ,EAAE,WAAW,KAAK,WAAW,GAAG,IAAI,CAAC;AA+BhF,MAAM,WAAW,cAAc;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC;CACpD;AA+FD;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,GAAG;IACd;;;;;;;;;;OAUG;iBACU,OAAO,CAAC,SAAS,CAAC,GAAG,IAAI;IAItC;;;;;;OAMG;QACC,CAAC,kBAAkB,MAAM,YAAY,cAAc,GAAG,OAAO,CAAC,CAAC,CAAC;IAIpE;;;;;;;OAOG;SACE,CAAC,kBAAkB,MAAM,SAAS,OAAO,YAAY,cAAc,GAAG,OAAO,CAAC,CAAC,CAAC;IAIrF,+BAA+B;QAC3B,CAAC,kBAAkB,MAAM,SAAS,OAAO,YAAY,cAAc,GAAG,OAAO,CAAC,CAAC,CAAC;IAIpF,mCAAmC;UAC7B,CAAC,kBAAkB,MAAM,SAAS,OAAO,YAAY,cAAc,GAAG,OAAO,CAAC,CAAC,CAAC;IAItF,mBAAmB;WACZ,CAAC,kBAAkB,MAAM,YAAY,cAAc,GAAG,OAAO,CAAC,CAAC,CAAC;IAIvE;;;;;;;;;;;;;;;;OAgBG;WACU,CAAC,kBAAkB,MAAM,YAAY,QAAQ,YAAY,cAAc,GAAG,OAAO,CAAC,CAAC,CAAC;IAiFjG;;;;;;;;;;;;;OAaG;oBACa,SAAS,GAAG,UAAU,MAAM,kBAAkB,GAAG,mBAAmB,GAAG,IAAI;IAQ3F,yCAAyC;cAC/B,IAAI;CAQf,CAAC"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * tina4js/api — Fetch wrapper with auth token management.
3
+ */
4
+ export { api } from './fetch';
5
+ export type { ApiConfig, ApiResponse, RequestOptions, RequestInterceptor, ResponseInterceptor } from './fetch';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,GAAG,EAAE,MAAM,SAAS,CAAC;AAC9B,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,cAAc,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Tina4 Component — Base class for web components.
3
+ *
4
+ * Extends HTMLElement with reactive props, lifecycle hooks,
5
+ * optional Shadow DOM, and scoped styles.
6
+ */
7
+ import { type Signal } from './signal';
8
+ /** @internal Called when a Tina4Element is connected to the DOM. */
9
+ export declare let __debugComponentMount: ((el: Tina4Element) => void) | null;
10
+ /** @internal Called when a Tina4Element is disconnected from the DOM. */
11
+ export declare let __debugComponentUnmount: ((el: Tina4Element) => void) | null;
12
+ /** @internal Set the debug hooks. */
13
+ export declare function __setDebugComponentHooks(onMount: typeof __debugComponentMount, onUnmount: typeof __debugComponentUnmount): void;
14
+ export type PropType = typeof String | typeof Number | typeof Boolean;
15
+ export declare abstract class Tina4Element extends HTMLElement {
16
+ /** Declare reactive props and their types. Override in subclass. */
17
+ static props: Record<string, PropType>;
18
+ /** Scoped CSS styles. Override in subclass. */
19
+ static styles: string;
20
+ /** Use Shadow DOM (true) or light DOM (false). Override in subclass. */
21
+ static shadow: boolean;
22
+ /** Internal reactive prop signals. */
23
+ private _props;
24
+ /** The render root (shadow or this). */
25
+ private _root;
26
+ /** Track if we've rendered. */
27
+ private _rendered;
28
+ static get observedAttributes(): string[];
29
+ constructor();
30
+ connectedCallback(): void;
31
+ disconnectedCallback(): void;
32
+ attributeChangedCallback(name: string, _old: string | null, value: string | null): void;
33
+ /**
34
+ * Get a reactive signal for a declared prop.
35
+ *
36
+ * ```ts
37
+ * render() {
38
+ * return html`<span>${this.prop('name')}</span>`;
39
+ * }
40
+ * ```
41
+ */
42
+ prop<T = unknown>(name: string): Signal<T>;
43
+ /**
44
+ * Dispatch a custom event from this component.
45
+ *
46
+ * ```ts
47
+ * this.emit('activate', { detail: 42 });
48
+ * ```
49
+ */
50
+ emit(name: string, init?: CustomEventInit): void;
51
+ /** Called after first render. */
52
+ onMount(): void;
53
+ /** Called when removed from DOM. */
54
+ onUnmount(): void;
55
+ /** Return DOM content. Override in subclass. */
56
+ abstract render(): DocumentFragment | Node | null;
57
+ private _coerce;
58
+ }
59
+ //# sourceMappingURL=component.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"component.d.ts","sourceRoot":"","sources":["../../src/core/component.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAU,KAAK,MAAM,EAAE,MAAM,UAAU,CAAC;AAI/C,oEAAoE;AACpE,eAAO,IAAI,qBAAqB,EAAE,CAAC,CAAC,EAAE,EAAE,YAAY,KAAK,IAAI,CAAC,GAAG,IAAW,CAAC;AAC7E,yEAAyE;AACzE,eAAO,IAAI,uBAAuB,EAAE,CAAC,CAAC,EAAE,EAAE,YAAY,KAAK,IAAI,CAAC,GAAG,IAAW,CAAC;AAC/E,qCAAqC;AACrC,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,OAAO,qBAAqB,EACrC,SAAS,EAAE,OAAO,uBAAuB,QAI1C;AAED,MAAM,MAAM,QAAQ,GAAG,OAAO,MAAM,GAAG,OAAO,MAAM,GAAG,OAAO,OAAO,CAAC;AAEtE,8BAAsB,YAAa,SAAQ,WAAW;IACpD,oEAAoE;IACpE,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAM;IAE5C,+CAA+C;IAC/C,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM;IAE3B,wEAAwE;IACxE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAQ;IAE9B,sCAAsC;IACtC,OAAO,CAAC,MAAM,CAAuC;IAErD,wCAAwC;IACxC,OAAO,CAAC,KAAK,CAA2B;IAExC,+BAA+B;IAC/B,OAAO,CAAC,SAAS,CAAS;IAE1B,MAAM,KAAK,kBAAkB,IAAI,MAAM,EAAE,CAExC;;IAaD,iBAAiB,IAAI,IAAI;IAuBzB,oBAAoB,IAAI,IAAI;IAK5B,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAQvF;;;;;;;;OAQG;IACH,IAAI,CAAC,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC;IAO1C;;;;;;OAMG;IACH,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,eAAe,GAAG,IAAI;IAUhD,iCAAiC;IACjC,OAAO,IAAI,IAAI;IAEf,oCAAoC;IACpC,SAAS,IAAI,IAAI;IAEjB,gDAAgD;IAChD,QAAQ,CAAC,MAAM,IAAI,gBAAgB,GAAG,IAAI,GAAG,IAAI;IAIjD,OAAO,CAAC,OAAO;CAKhB"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Tina4 HTML — Tagged template literal renderer.
3
+ *
4
+ * html`<div>${value}</div>` returns real DOM nodes (DocumentFragment).
5
+ * When a signal is interpolated, the DOM updates surgically — no diffing.
6
+ */
7
+ /**
8
+ * Tagged template literal renderer — builds real DOM nodes with surgical reactive updates.
9
+ *
10
+ * Interpolated values are bound as follows:
11
+ * - `${signal}` → reactive text node, updates in place when the signal changes
12
+ * - `${() => expr}` → reactive block, re-renders when any signal read inside changes
13
+ * - `${fragment}` → inserts a DocumentFragment (nested `html\`\``)
14
+ * - `${array}` → renders each item as nodes
15
+ * - `${value}` → static text node (escaped — XSS-safe)
16
+ *
17
+ * Attribute binding syntax (in tag attributes):
18
+ * - `.innerHTML=${val}` → sets DOM property (use for raw HTML / inline SVG)
19
+ * - `.value=${signal}` → reactive DOM property binding
20
+ * - `?disabled=${sig}` → boolean attribute — added/removed reactively
21
+ * - `@click=${fn}` → event listener
22
+ *
23
+ * **Important:** `${svgString}` in content position renders as escaped text.
24
+ * To inject raw HTML or SVG, use: `<div .innerHTML=${svgString}></div>`
25
+ *
26
+ * @param strings - Template string parts (static, cached by identity).
27
+ * @param values - Interpolated values.
28
+ * @returns A DocumentFragment ready to append to the DOM.
29
+ *
30
+ * @example
31
+ * const name = signal('World');
32
+ * const frag = html`<h1>Hello, ${name}!</h1>`;
33
+ * document.body.appendChild(frag);
34
+ * name.value = 'Tina4'; // DOM updates automatically
35
+ */
36
+ export declare function html(strings: TemplateStringsArray, ...values: unknown[]): DocumentFragment;
37
+ //# sourceMappingURL=html.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"html.d.ts","sourceRoot":"","sources":["../../src/core/html.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAYH;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,IAAI,CAAC,OAAO,EAAE,oBAAoB,EAAE,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG,gBAAgB,CAiC1F"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * tina4js/core — Reactive primitives, HTML renderer, and web component base.
3
+ */
4
+ export { signal, computed, effect, batch, isSignal } from './signal';
5
+ export type { Signal, ReadonlySignal } from './signal';
6
+ export { html } from './html';
7
+ export { Tina4Element } from './component';
8
+ export type { PropType } from './component';
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AACrE,YAAY,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AACvD,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,YAAY,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Tina4 Signals — Reactive state primitives.
3
+ *
4
+ * signal(value) — create a reactive value
5
+ * computed(fn) — derive a value that auto-tracks dependencies
6
+ * effect(fn) — run a side-effect that auto-tracks dependencies
7
+ * batch(fn) — batch multiple signal updates into one notification
8
+ */
9
+ /** @internal Start collecting effect disposers (used by router). */
10
+ export declare function _setEffectCollector(collector: (() => void)[] | null): void;
11
+ /** @internal Read the current effect collector (used by html renderer). */
12
+ export declare function _getEffectCollector(): (() => void)[] | null;
13
+ /** @internal Called when a signal is created. */
14
+ export declare let __debugSignalCreate: ((s: Signal<unknown>, label?: string) => void) | null;
15
+ /** @internal Called when a signal value changes. */
16
+ export declare let __debugSignalUpdate: ((s: Signal<unknown>, oldVal: unknown, newVal: unknown) => void) | null;
17
+ /** @internal Set the debug hooks. */
18
+ export declare function __setDebugSignalHooks(onCreate: typeof __debugSignalCreate, onUpdate: typeof __debugSignalUpdate): void;
19
+ export interface Signal<T> {
20
+ value: T;
21
+ /** @internal */
22
+ readonly _t4: true;
23
+ /** @internal subscribe directly (used by html renderer) */
24
+ _subscribe(fn: () => void): () => void;
25
+ /** @internal read without tracking */
26
+ peek(): T;
27
+ }
28
+ export interface ReadonlySignal<T> {
29
+ readonly value: T;
30
+ /** @internal */
31
+ readonly _t4: true;
32
+ /** @internal */
33
+ _subscribe(fn: () => void): () => void;
34
+ peek(): T;
35
+ }
36
+ /**
37
+ * Create a reactive signal — a value that notifies subscribers when it changes.
38
+ *
39
+ * @param initial - The initial value.
40
+ * @param label - Optional debug label shown in the debug overlay.
41
+ * @returns A signal whose `.value` getter/setter is reactive.
42
+ *
43
+ * @example
44
+ * const count = signal(0);
45
+ * count.value; // read: 0
46
+ * count.value = 5; // write: triggers all subscribers
47
+ *
48
+ * const name = signal('Alice', 'userName'); // labelled for debug overlay
49
+ */
50
+ export declare function signal<T>(initial: T, label?: string): Signal<T>;
51
+ /**
52
+ * Create a derived signal that auto-tracks its dependencies.
53
+ * Re-computes whenever any signal read inside `fn` changes.
54
+ * The result is read-only — writing throws.
55
+ *
56
+ * @param fn - Pure function that reads signals and returns a derived value.
57
+ * @returns A read-only signal.
58
+ *
59
+ * @example
60
+ * const price = signal(10);
61
+ * const qty = signal(3);
62
+ * const total = computed(() => price.value * qty.value);
63
+ * total.value; // 30 — updates automatically when price or qty change
64
+ */
65
+ export declare function computed<T>(fn: () => T): ReadonlySignal<T>;
66
+ /**
67
+ * Run a side-effect that auto-tracks signal dependencies.
68
+ * Runs immediately, then re-runs whenever a dependency changes.
69
+ * If the effect throws, sibling effects still run; the first error is re-thrown
70
+ * after all subscribers have been notified.
71
+ *
72
+ * @param fn - The side-effect function. Reads signals to establish tracking.
73
+ * @returns A `dispose` function — call it to stop the effect.
74
+ *
75
+ * @example
76
+ * const count = signal(0);
77
+ * const stop = effect(() => {
78
+ * console.log('count is', count.value);
79
+ * });
80
+ * // logs immediately, then on every count change
81
+ * stop(); // unsubscribe
82
+ */
83
+ export declare function effect(fn: () => void): () => void;
84
+ /**
85
+ * Batch multiple signal updates into a single notification pass.
86
+ * Subscribers are notified once after `fn` completes, not after each write.
87
+ *
88
+ * @param fn - Function containing one or more signal writes.
89
+ *
90
+ * @example
91
+ * const a = signal(1);
92
+ * const b = signal(2);
93
+ * batch(() => {
94
+ * a.value = 10;
95
+ * b.value = 20;
96
+ * }); // effects run once, not twice
97
+ */
98
+ export declare function batch(fn: () => void): void;
99
+ /** Check if a value is a tina4 signal. */
100
+ export declare function isSignal(value: unknown): value is Signal<unknown>;
101
+ //# sourceMappingURL=signal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signal.d.ts","sourceRoot":"","sources":["../../src/core/signal.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAWH,oEAAoE;AACpE,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,CAAC,MAAM,IAAI,CAAC,EAAE,GAAG,IAAI,GAAG,IAAI,CAE1E;AAED,2EAA2E;AAC3E,wBAAgB,mBAAmB,IAAI,CAAC,MAAM,IAAI,CAAC,EAAE,GAAG,IAAI,CAE3D;AAID,iDAAiD;AACjD,eAAO,IAAI,mBAAmB,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,IAAW,CAAC;AAC7F,oDAAoD;AACpD,eAAO,IAAI,mBAAmB,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC,GAAG,IAAW,CAAC;AAC/G,qCAAqC;AACrC,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,OAAO,mBAAmB,EACpC,QAAQ,EAAE,OAAO,mBAAmB,QAIrC;AAUD,MAAM,WAAW,MAAM,CAAC,CAAC;IACvB,KAAK,EAAE,CAAC,CAAC;IACT,gBAAgB;IAChB,QAAQ,CAAC,GAAG,EAAE,IAAI,CAAC;IACnB,2DAA2D;IAC3D,UAAU,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC;IACvC,sCAAsC;IACtC,IAAI,IAAI,CAAC,CAAC;CACX;AAED,MAAM,WAAW,cAAc,CAAC,CAAC;IAC/B,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;IAClB,gBAAgB;IAChB,QAAQ,CAAC,GAAG,EAAE,IAAI,CAAC;IACnB,gBAAgB;IAChB,UAAU,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC;IACvC,IAAI,IAAI,CAAC,CAAC;CACX;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,MAAM,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,CAoD/D;AAID;;;;;;;;;;;;;GAaG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC,CA2B1D;AAID;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,MAAM,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAgCjD;AAID;;;;;;;;;;;;;GAaG;AACH,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,IAAI,CAgB1C;AAID,0CAA0C;AAC1C,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,OAAO,CAAC,CAEjE"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * tina4js/debug — Developer debug overlay.
3
+ *
4
+ * Import this module to enable the debug overlay:
5
+ * import 'tina4js/debug';
6
+ *
7
+ * Or conditionally:
8
+ * if (import.meta.env.DEV) import('tina4js/debug');
9
+ *
10
+ * Toggle: Ctrl+Shift+D
11
+ */
12
+ /**
13
+ * Enable the debug overlay. Called automatically on import,
14
+ * or can be called explicitly.
15
+ */
16
+ export declare function enableDebug(): void;
17
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/debug/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAaH;;;GAGG;AACH,wBAAgB,WAAW,IAAI,IAAI,CAsDlC"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Debug Overlay — Shadow DOM custom element with tabbed panel UI.
3
+ */
4
+ export declare class Tina4Debug extends HTMLElement {
5
+ private _shadow;
6
+ private _visible;
7
+ private _activeTab;
8
+ private _refreshTimer;
9
+ constructor();
10
+ connectedCallback(): void;
11
+ disconnectedCallback(): void;
12
+ toggle(): void;
13
+ show(): void;
14
+ hide(): void;
15
+ private _startAutoRefresh;
16
+ private _stopAutoRefresh;
17
+ private _switchTab;
18
+ private _getTabContent;
19
+ private _renderBody;
20
+ private _updateTabCounts;
21
+ private _render;
22
+ }
23
+ /** Register the custom element. Call before creating instances. */
24
+ export declare function registerDebugElement(): void;
25
+ //# sourceMappingURL=overlay.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"overlay.d.ts","sourceRoot":"","sources":["../../src/debug/overlay.ts"],"names":[],"mappings":"AAAA;;GAEG;AAWH,qBAAa,UAAW,SAAQ,WAAW;IACzC,OAAO,CAAC,OAAO,CAAa;IAC5B,OAAO,CAAC,QAAQ,CAAQ;IACxB,OAAO,CAAC,UAAU,CAAoB;IACtC,OAAO,CAAC,aAAa,CAAuB;;IAO5C,iBAAiB;IAKjB,oBAAoB;IAIpB,MAAM;IAKN,IAAI;IAKJ,IAAI;IAKJ,OAAO,CAAC,iBAAiB;IAMzB,OAAO,CAAC,gBAAgB;IAOxB,OAAO,CAAC,UAAU;IAKlB,OAAO,CAAC,cAAc;IAStB,OAAO,CAAC,WAAW;IAOnB,OAAO,CAAC,gBAAgB;IAaxB,OAAO,CAAC,OAAO;CAsDhB;AAED,mEAAmE;AACnE,wBAAgB,oBAAoB,IAAI,IAAI,CAI3C"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * API Panel — Request/response log.
3
+ */
4
+ export declare function renderApiPanel(): string;
5
+ //# sourceMappingURL=api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../../src/debug/panels/api.ts"],"names":[],"mappings":"AAAA;;GAEG;AAuBH,wBAAgB,cAAc,IAAI,MAAM,CAyBvC"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Components Panel — Mounted Tina4Elements inspector.
3
+ */
4
+ export declare function renderComponentsPanel(): string;
5
+ //# sourceMappingURL=components.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"components.d.ts","sourceRoot":"","sources":["../../../src/debug/panels/components.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,wBAAgB,qBAAqB,IAAI,MAAM,CAsB9C"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Routes Panel — Registered routes and navigation history.
3
+ */
4
+ export declare function renderRoutesPanel(): string;
5
+ //# sourceMappingURL=routes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../../src/debug/panels/routes.ts"],"names":[],"mappings":"AAAA;;GAEG;AAeH,wBAAgB,iBAAiB,IAAI,MAAM,CA8C1C"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Signals Panel — Live signal inspector.
3
+ */
4
+ export declare function renderSignalsPanel(): string;
5
+ //# sourceMappingURL=signals.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signals.d.ts","sourceRoot":"","sources":["../../../src/debug/panels/signals.ts"],"names":[],"mappings":"AAAA;;GAEG;AAgBH,wBAAgB,kBAAkB,IAAI,MAAM,CAuB3C"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Debug Overlay Styles — Scoped inside Shadow DOM.
3
+ */
4
+ export declare const debugStyles = "\n:host {\n all: initial;\n position: fixed;\n bottom: 0;\n right: 0;\n z-index: 999999;\n font-family: 'SF Mono', 'Fira Code', 'Cascadia Code', monospace;\n font-size: 12px;\n color: #e0e0e0;\n line-height: 1.4;\n}\n\n* { box-sizing: border-box; }\n\n.t4-debug {\n width: 480px;\n max-height: 420px;\n background: #1a1a2e;\n border: 1px solid #333;\n border-radius: 8px 0 0 0;\n display: flex;\n flex-direction: column;\n box-shadow: -4px -4px 20px rgba(0,0,0,0.5);\n}\n\n.t4-debug.collapsed {\n max-height: none;\n}\n\n.t4-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 6px 12px;\n background: #16213e;\n border-bottom: 1px solid #333;\n border-radius: 8px 0 0 0;\n cursor: move;\n user-select: none;\n}\n\n.t4-logo {\n font-weight: 700;\n font-size: 13px;\n color: #00d4ff;\n letter-spacing: 0.5px;\n}\n\n.t4-badge {\n font-size: 10px;\n padding: 1px 6px;\n border-radius: 4px;\n background: #0f3460;\n color: #00d4ff;\n margin-left: 8px;\n}\n\n.t4-header-right {\n display: flex;\n align-items: center;\n gap: 6px;\n}\n\n.t4-close {\n background: none;\n border: none;\n color: #888;\n cursor: pointer;\n font-size: 16px;\n padding: 2px 6px;\n border-radius: 4px;\n}\n.t4-close:hover { color: #ff6b6b; background: rgba(255,107,107,0.1); }\n\n.t4-tabs {\n display: flex;\n border-bottom: 1px solid #333;\n background: #16213e;\n}\n\n.t4-tab {\n flex: 1;\n padding: 6px 8px;\n background: none;\n border: none;\n color: #888;\n cursor: pointer;\n font-size: 11px;\n font-family: inherit;\n border-bottom: 2px solid transparent;\n transition: all 0.15s;\n}\n.t4-tab:hover { color: #ccc; background: rgba(255,255,255,0.03); }\n.t4-tab.active {\n color: #00d4ff;\n border-bottom-color: #00d4ff;\n background: rgba(0,212,255,0.05);\n}\n\n.t4-tab-count {\n font-size: 10px;\n color: #666;\n margin-left: 4px;\n}\n\n.t4-body {\n flex: 1;\n overflow-y: auto;\n padding: 8px;\n min-height: 200px;\n max-height: 340px;\n}\n\n.t4-body::-webkit-scrollbar { width: 6px; }\n.t4-body::-webkit-scrollbar-track { background: transparent; }\n.t4-body::-webkit-scrollbar-thumb { background: #333; border-radius: 3px; }\n\n/* Panel table styles */\ntable {\n width: 100%;\n border-collapse: collapse;\n}\n\nth {\n text-align: left;\n padding: 4px 8px;\n color: #888;\n font-weight: 500;\n font-size: 10px;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n border-bottom: 1px solid #2a2a3e;\n position: sticky;\n top: 0;\n background: #1a1a2e;\n}\n\ntd {\n padding: 3px 8px;\n border-bottom: 1px solid rgba(255,255,255,0.03);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n max-width: 150px;\n}\n\ntr:hover td { background: rgba(255,255,255,0.02); }\n\n/* Value displays */\n.val-string { color: #a8e6cf; }\n.val-number { color: #ffd3a5; }\n.val-boolean { color: #ff8b94; }\n.val-object { color: #b8b5ff; }\n.val-null { color: #666; font-style: italic; }\n\n/* Status badges */\n.status-ok { color: #66bb6a; }\n.status-err { color: #ff6b6b; }\n.status-pending { color: #ffa726; }\n\n/* Route pattern */\n.route-pattern { color: #b8b5ff; }\n.route-param { color: #ffd3a5; }\n\n/* Duration */\n.duration { color: #888; font-size: 11px; }\n.duration.fast { color: #66bb6a; }\n.duration.slow { color: #ffa726; }\n.duration.very-slow { color: #ff6b6b; }\n\n/* Empty state */\n.t4-empty {\n text-align: center;\n color: #555;\n padding: 32px 16px;\n font-size: 12px;\n}\n\n/* Collapsed mini-badge */\n.t4-mini {\n position: fixed;\n bottom: 12px;\n right: 12px;\n background: #1a1a2e;\n border: 1px solid #333;\n border-radius: 8px;\n padding: 6px 12px;\n cursor: pointer;\n font-family: inherit;\n font-size: 11px;\n color: #00d4ff;\n box-shadow: -2px -2px 10px rgba(0,0,0,0.3);\n z-index: 999999;\n display: flex;\n align-items: center;\n gap: 6px;\n}\n.t4-mini:hover { background: #16213e; }\n\n.t4-mini-dot {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: #66bb6a;\n}\n";
5
+ //# sourceMappingURL=styles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"styles.d.ts","sourceRoot":"","sources":["../../src/debug/styles.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,eAAO,MAAM,WAAW,+9HA8MvB,CAAC"}
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Debug Trackers — Data collection for signals, components, routes, and API.
3
+ */
4
+ import type { Signal } from '../core/signal';
5
+ import type { Tina4Element } from '../core/component';
6
+ import type { ChangeEvent } from '../router/router';
7
+ export interface TrackedSignal {
8
+ ref: WeakRef<Signal<unknown>>;
9
+ label?: string;
10
+ createdAt: number;
11
+ updateCount: number;
12
+ subs: WeakRef<Set<() => void>>;
13
+ }
14
+ export declare const signalTracker: {
15
+ add(s: Signal<unknown>, label?: string): void;
16
+ onUpdate(s: Signal<unknown>): void;
17
+ getAll(): {
18
+ label?: string;
19
+ value: unknown;
20
+ subscriberCount: number;
21
+ updateCount: number;
22
+ alive: boolean;
23
+ }[];
24
+ readonly count: number;
25
+ };
26
+ export interface TrackedComponent {
27
+ ref: WeakRef<Tina4Element>;
28
+ tagName: string;
29
+ mountedAt: number;
30
+ }
31
+ export declare const componentTracker: {
32
+ onMount(el: Tina4Element): void;
33
+ onUnmount(el: Tina4Element): void;
34
+ getAll(): {
35
+ tagName: string;
36
+ props: Record<string, unknown>;
37
+ alive: boolean;
38
+ }[];
39
+ readonly count: number;
40
+ };
41
+ export interface RouteEntry {
42
+ path: string;
43
+ pattern: string;
44
+ params: Record<string, string>;
45
+ durationMs: number;
46
+ timestamp: number;
47
+ }
48
+ declare let _getRoutesFn: (() => ReadonlyArray<{
49
+ pattern: string;
50
+ hasGuard: boolean;
51
+ }>) | null;
52
+ export declare const routeTracker: {
53
+ /** Set the function that reads registered routes (avoids direct import from router). */
54
+ setGetRoutes(fn: typeof _getRoutesFn): void;
55
+ getRegisteredRoutes(): ReadonlyArray<{
56
+ pattern: string;
57
+ hasGuard: boolean;
58
+ }>;
59
+ onNavigate(event: ChangeEvent): void;
60
+ getHistory(): readonly RouteEntry[];
61
+ readonly count: number;
62
+ };
63
+ export interface ApiEntry {
64
+ id: number;
65
+ method: string;
66
+ url: string;
67
+ status?: number;
68
+ durationMs?: number;
69
+ hasAuth: boolean;
70
+ timestamp: number;
71
+ pending: boolean;
72
+ error?: string;
73
+ }
74
+ export declare const apiTracker: {
75
+ onRequest(config: RequestInit & {
76
+ headers: Record<string, string>;
77
+ _url?: string;
78
+ _requestId?: number;
79
+ }): void;
80
+ onResponse(response: {
81
+ status: number;
82
+ ok: boolean;
83
+ _requestId?: number;
84
+ }): void;
85
+ getLog(): readonly ApiEntry[];
86
+ readonly count: number;
87
+ };
88
+ export {};
89
+ //# sourceMappingURL=trackers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trackers.d.ts","sourceRoot":"","sources":["../../src/debug/trackers.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAIpD,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC;CAChC;AAID,eAAO,MAAM,aAAa;WACjB,MAAM,CAAC,OAAO,CAAC,UAAU,MAAM;gBAW1B,MAAM,CAAC,OAAO,CAAC;cASjB;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,OAAO,CAAC;QAAC,eAAe,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,EAAE;;CAsB7G,CAAC;AAIF,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAID,eAAO,MAAM,gBAAgB;gBACf,YAAY;kBAQV,YAAY;cAKhB;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,EAAE;;CAuBhF,CAAC;AAIF,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB;AAID,QAAA,IAAI,YAAY,EAAE,CAAC,MAAM,aAAa,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,CAAC,CAAC,GAAG,IAAW,CAAC;AAE9F,eAAO,MAAM,YAAY;IACvB,wFAAwF;qBACvE,OAAO,YAAY;2BAEb,aAAa,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAA;KAAE,CAAC;sBAG1D,WAAW;kBAWf,SAAS,UAAU,EAAE;;CAKpC,CAAC;AAIF,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAOD,eAAO,MAAM,UAAU;sBACH,WAAW,GAAG;QAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;yBAezF;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,OAAO,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;cAatE,SAAS,QAAQ,EAAE;;CAK9B,CAAC"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * tina4js — Sub-3KB core, reactive framework.
3
+ *
4
+ * Signals + tagged template rendering + web components + routing + API + PWA.
5
+ */
6
+ export { signal, computed, effect, batch, isSignal } from './core/signal';
7
+ export type { Signal, ReadonlySignal } from './core/signal';
8
+ export { html } from './core/html';
9
+ export { Tina4Element } from './core/component';
10
+ export type { PropType } from './core/component';
11
+ export { route, navigate, router } from './router/router';
12
+ export type { RouteParams, RouteHandler, RouteGuard, RouteConfig } from './router/router';
13
+ export { api } from './api/fetch';
14
+ export type { ApiConfig, ApiResponse, RequestOptions } from './api/fetch';
15
+ export { pwa } from './pwa/pwa';
16
+ export type { PWAConfig } from './pwa/pwa';
17
+ export { ws } from './ws/ws';
18
+ export type { SocketStatus, SocketOptions, ManagedSocket } from './ws/ws';
19
+ export { sse } from './sse/sse';
20
+ export type { StreamStatus, StreamOptions, ManagedStream } from './sse/sse';
21
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC1E,YAAY,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC5D,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AACnC,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAChD,YAAY,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAGjD,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAC1D,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAG1F,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAClC,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAG1E,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAChC,YAAY,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAG3C,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAC7B,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAG1E,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAChC,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * tina4js/pwa — Service worker and manifest generation.
3
+ */
4
+ export { pwa } from './pwa';
5
+ export type { PWAConfig } from './pwa';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/pwa/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAC5B,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Tina4 PWA — Service worker registration and manifest generation.
3
+ *
4
+ * Optional module — tree-shaken away if not imported.
5
+ */
6
+ export interface PWAConfig {
7
+ name: string;
8
+ shortName?: string;
9
+ themeColor?: string;
10
+ backgroundColor?: string;
11
+ display?: 'standalone' | 'fullscreen' | 'minimal-ui' | 'browser';
12
+ icon?: string;
13
+ cacheStrategy?: 'cache-first' | 'network-first' | 'stale-while-revalidate';
14
+ precache?: string[];
15
+ offlineRoute?: string;
16
+ }
17
+ export declare const pwa: {
18
+ register(config: PWAConfig): void;
19
+ /** Generate SW code as a string (for build tools that write sw.js to disk). */
20
+ generateServiceWorker(config: PWAConfig): string;
21
+ /** Generate manifest object (for build tools that write manifest.json to disk). */
22
+ generateManifest(config: PWAConfig): object;
23
+ };
24
+ //# sourceMappingURL=pwa.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pwa.d.ts","sourceRoot":"","sources":["../../src/pwa/pwa.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,EAAE,YAAY,GAAG,YAAY,GAAG,YAAY,GAAG,SAAS,CAAC;IACjE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,aAAa,GAAG,eAAe,GAAG,wBAAwB,CAAC;IAC3E,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAkFD,eAAO,MAAM,GAAG;qBACG,SAAS,GAAG,IAAI;IA6BjC,+EAA+E;kCACjD,SAAS,GAAG,MAAM;IAIhD,mFAAmF;6BAC1D,SAAS,GAAG,MAAM;CAG5C,CAAC"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * tina4js/router — Client-side routing.
3
+ */
4
+ export { route, navigate, router, _resetRouter } from './router';
5
+ export type { RouteParams, RouteHandler, RouteGuard, RouteConfig } from './router';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/router/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACjE,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC"}
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Tina4 Router — Client-side routing with history and hash modes.
3
+ *
4
+ * route(path, handler) — register a route
5
+ * navigate(path) — programmatic navigation
6
+ * router.start(config) — start the router
7
+ */
8
+ export type RouteParams = Record<string, string>;
9
+ export type RouteHandler = (params: RouteParams) => unknown;
10
+ export type RouteGuard = () => boolean | string;
11
+ export interface RouteConfig {
12
+ guard?: RouteGuard;
13
+ handler: RouteHandler;
14
+ }
15
+ interface RouterConfig {
16
+ target: string;
17
+ mode?: 'history' | 'hash';
18
+ }
19
+ export type ChangeEvent = {
20
+ path: string;
21
+ params: RouteParams;
22
+ pattern: string;
23
+ durationMs: number;
24
+ };
25
+ type ChangeListener = (event: ChangeEvent) => void;
26
+ /**
27
+ * Register a route. Pattern is ALWAYS the first argument.
28
+ *
29
+ * Patterns support `{param}` segments — extracted and passed to the handler.
30
+ * Use `'*'` to match any path (catch-all / 404 route).
31
+ *
32
+ * @param pattern - URL pattern, e.g. `'/'`, `'/users/{id}'`, `'*'`
33
+ * @param handlerOrConfig - Handler function or `{ handler, guard }` config.
34
+ *
35
+ * @example
36
+ * route('/', () => html`<h1>Home</h1>`);
37
+ * route('/users/{id}', ({ id }) => html`<p>User: ${id}</p>`);
38
+ * route('*', () => html`<h1>404</h1>`);
39
+ *
40
+ * // With a guard:
41
+ * route('/admin', {
42
+ * guard: () => isLoggedIn.value || '/login',
43
+ * handler: () => html`<admin-panel></admin-panel>`,
44
+ * });
45
+ */
46
+ export declare function route(pattern: string, handlerOrConfig: RouteHandler | RouteConfig): void;
47
+ /**
48
+ * Programmatically navigate to a path.
49
+ *
50
+ * @param path - The path to navigate to.
51
+ * @param opts - `{ replace: true }` uses `history.replaceState` instead of `pushState`.
52
+ *
53
+ * @example
54
+ * navigate('/dashboard');
55
+ * navigate('/login', { replace: true }); // no back-button entry
56
+ */
57
+ export declare function navigate(path: string, opts?: {
58
+ replace?: boolean;
59
+ }): void;
60
+ export declare const router: {
61
+ start(config: RouterConfig): void;
62
+ on(event: "change", fn: ChangeListener): () => void;
63
+ };
64
+ /** @internal Read-only access to registered routes (for debug overlay). */
65
+ export declare function _getRoutes(): ReadonlyArray<{
66
+ pattern: string;
67
+ hasGuard: boolean;
68
+ }>;
69
+ /** @internal Reset router state (for tests only). */
70
+ export declare function _resetRouter(): void;
71
+ export {};
72
+ //# sourceMappingURL=router.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../src/router/router.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AACjD,MAAM,MAAM,YAAY,GAAG,CAAC,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC;AAC5D,MAAM,MAAM,UAAU,GAAG,MAAM,OAAO,GAAG,MAAM,CAAC;AAEhD,MAAM,WAAW,WAAW;IAC1B,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,OAAO,EAAE,YAAY,CAAC;CACvB;AAUD,UAAU,YAAY;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,SAAS,GAAG,MAAM,CAAC;CAC3B;AAED,MAAM,MAAM,WAAW,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,WAAW,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC;AACrG,KAAK,cAAc,GAAG,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;AAiBnD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,YAAY,GAAG,WAAW,GAAG,IAAI,CA2BxF;AAED;;;;;;;;;GASG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CAkBzE;AAiFD,eAAO,MAAM,MAAM;kBACH,YAAY,GAAG,IAAI;cAsCvB,QAAQ,MAAM,cAAc,GAAG,MAAM,IAAI;CAOpD,CAAC;AAEF,2EAA2E;AAC3E,wBAAgB,UAAU,IAAI,aAAa,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,CAAC,CAElF;AAID,qDAAqD;AACrD,wBAAgB,YAAY,IAAI,IAAI,CASnC"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * tina4js/sse — Signal-driven SSE / NDJSON streaming client.
3
+ */
4
+ export { sse } from './sse';
5
+ export type { StreamStatus, StreamOptions, ManagedStream } from './sse';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/sse/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAC5B,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC"}
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Tina4 SSE — Signal-driven Server-Sent Events / NDJSON streaming client.
3
+ *
4
+ * sse.connect(url, options?) — create a managed event stream
5
+ * stream.status — reactive signal: 'connecting' | 'open' | 'closed' | 'reconnecting'
6
+ * stream.connected — reactive signal: boolean
7
+ * stream.lastMessage — reactive signal: last parsed message
8
+ * stream.lastEvent — reactive signal: last SSE event name (or null)
9
+ * stream.on(event, handler) — listen for messages/open/close/error
10
+ * stream.pipe(signal, reducer) — pipe messages directly into a signal
11
+ * stream.close() — disconnect (stops reconnect)
12
+ */
13
+ import type { Signal } from '../core/signal';
14
+ export type StreamStatus = 'connecting' | 'open' | 'closed' | 'reconnecting';
15
+ export interface StreamOptions {
16
+ /** Transport: 'eventsource' (default) or 'fetch' for NDJSON/POST. */
17
+ mode?: 'eventsource' | 'fetch';
18
+ /** HTTP method for fetch mode (default: 'GET'). */
19
+ method?: string;
20
+ /** Custom headers for fetch mode. */
21
+ headers?: Record<string, string>;
22
+ /** Request body for fetch mode (objects are JSON.stringify'd). */
23
+ body?: unknown;
24
+ /** Enable auto-reconnect (default: true). */
25
+ reconnect?: boolean;
26
+ /** Initial reconnect delay in ms (default: 1000). */
27
+ reconnectDelay?: number;
28
+ /** Max reconnect delay in ms (default: 30000). */
29
+ reconnectMaxDelay?: number;
30
+ /** Max reconnect attempts (default: Infinity). */
31
+ reconnectAttempts?: number;
32
+ /** Named SSE events to listen for (eventsource mode only). */
33
+ events?: string[];
34
+ /** Parse each message as JSON (default: true). */
35
+ json?: boolean;
36
+ }
37
+ export type MessageHandler = (data: unknown, event?: string) => void;
38
+ export type OpenHandler = () => void;
39
+ export type CloseHandler = () => void;
40
+ export type ErrorHandler = (error: Event | Error) => void;
41
+ type EventMap = {
42
+ message: MessageHandler;
43
+ open: OpenHandler;
44
+ close: CloseHandler;
45
+ error: ErrorHandler;
46
+ };
47
+ export interface ManagedStream {
48
+ /** Reactive connection status. */
49
+ readonly status: Signal<StreamStatus>;
50
+ /** Reactive boolean — true when status is 'open'. */
51
+ readonly connected: Signal<boolean>;
52
+ /** Reactive — last received message (parsed JSON or raw string). */
53
+ readonly lastMessage: Signal<unknown>;
54
+ /** Reactive — last SSE event name, or null for unnamed/NDJSON. */
55
+ readonly lastEvent: Signal<string | null>;
56
+ /** Reactive — last error, or null. */
57
+ readonly error: Signal<Event | Error | null>;
58
+ /** Number of reconnect attempts so far. */
59
+ readonly reconnectCount: Signal<number>;
60
+ /** Listen for events. Returns unsubscribe function. */
61
+ on<K extends keyof EventMap>(event: K, handler: EventMap[K]): () => void;
62
+ /** Pipe messages into a signal via a reducer. */
63
+ pipe<T>(target: Signal<T>, reducer: (message: unknown, current: T) => T): () => void;
64
+ /** Close the connection and stop reconnecting. */
65
+ close(): void;
66
+ }
67
+ declare function createStream(url: string, options?: StreamOptions): ManagedStream;
68
+ export declare const sse: {
69
+ connect: typeof createStream;
70
+ };
71
+ export {};
72
+ //# sourceMappingURL=sse.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sse.d.ts","sourceRoot":"","sources":["../../src/sse/sse.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAI7C,MAAM,MAAM,YAAY,GAAG,YAAY,GAAG,MAAM,GAAG,QAAQ,GAAG,cAAc,CAAC;AAE7E,MAAM,WAAW,aAAa;IAC5B,qEAAqE;IACrE,IAAI,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC;IAC/B,mDAAmD;IACnD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,qCAAqC;IACrC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,kEAAkE;IAClE,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,6CAA6C;IAC7C,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,qDAAqD;IACrD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,kDAAkD;IAClD,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,kDAAkD;IAClD,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,8DAA8D;IAC9D,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,kDAAkD;IAClD,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,MAAM,cAAc,GAAG,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;AACrE,MAAM,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC;AACrC,MAAM,MAAM,YAAY,GAAG,MAAM,IAAI,CAAC;AACtC,MAAM,MAAM,YAAY,GAAG,CAAC,KAAK,EAAE,KAAK,GAAG,KAAK,KAAK,IAAI,CAAC;AAE1D,KAAK,QAAQ,GAAG;IACd,OAAO,EAAE,cAAc,CAAC;IACxB,IAAI,EAAE,WAAW,CAAC;IAClB,KAAK,EAAE,YAAY,CAAC;IACpB,KAAK,EAAE,YAAY,CAAC;CACrB,CAAC;AAEF,MAAM,WAAW,aAAa;IAC5B,kCAAkC;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,YAAY,CAAC,CAAC;IACtC,qDAAqD;IACrD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;IACpC,oEAAoE;IACpE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;IACtC,kEAAkE;IAClE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC1C,sCAAsC;IACtC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,KAAK,GAAG,KAAK,GAAG,IAAI,CAAC,CAAC;IAC7C,2CAA2C;IAC3C,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;IAExC,uDAAuD;IACvD,EAAE,CAAC,CAAC,SAAS,MAAM,QAAQ,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,CAAC;IAEzE,iDAAiD;IACjD,IAAI,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC;IAErF,kDAAkD;IAClD,KAAK,IAAI,IAAI,CAAC;CACf;AAmBD,iBAAS,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,aAAkB,GAAG,aAAa,CAqO7E;AAID,eAAO,MAAM,GAAG;;CAEf,CAAC"}
@@ -0,0 +1,2 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const u=require("./signal.cjs.js"),I={mode:"eventsource",method:"GET",headers:{},body:void 0,reconnect:!0,reconnectDelay:1e3,reconnectMaxDelay:3e4,reconnectAttempts:1/0,events:[],json:!0};function N(E,k={}){const t={...I,...k},c=u.signal("connecting"),f=u.signal(!1),D=u.signal(null),T=u.signal(null),h=u.signal(null),b=u.signal(0),s={message:[],open:[],close:[],error:[]};let o=null,r=null,x=!1,v=t.reconnectDelay,d=null,a=0;function g(e){if(!t.json||typeof e!="string")return e;try{return JSON.parse(e)}catch{return e}}function m(e,n){D.value=e,T.value=n;for(const l of s.message)l(e,n??void 0)}function A(){c.value="open",f.value=!0,h.value=null,a=0,v=t.reconnectDelay,b.value=0;for(const e of s.open)e()}function y(){c.value="closed",f.value=!1;for(const e of s.close)e();!x&&t.reconnect&&a<t.reconnectAttempts&&L()}function S(e){h.value=e;for(const n of s.error)n(e)}function F(){c.value=a>0?"reconnecting":"connecting";try{o=new EventSource(E)}catch{c.value="closed",f.value=!1;return}o.onopen=()=>A(),o.onmessage=e=>{m(g(e.data),null)};for(const e of t.events)o.addEventListener(e,n=>{m(g(n.data),e)});o.onerror=e=>{S(e),o&&o.readyState===2&&(o=null,y())}}function J(){c.value=a>0?"reconnecting":"connecting",r=new AbortController;const e={method:t.method,headers:t.headers,signal:r.signal};t.body!==void 0&&(e.body=typeof t.body=="string"?t.body:JSON.stringify(t.body)),fetch(E,e).then(async n=>{if(!n.ok){S(new Error(`[tina4] SSE fetch ${n.status}`)),y();return}A();const l=n.body.getReader(),i=new TextDecoder;let p="";for(;;){const{done:R,value:q}=await l.read();if(R)break;p+=i.decode(q,{stream:!0});const j=p.split(`
2
+ `);p=j.pop();for(const G of j){const w=G.trim();w&&m(g(w),null)}}const C=p.trim();C&&m(g(C),null),r=null,y()}).catch(n=>{n.name!=="AbortError"&&(r=null,S(n),y())})}function L(){a++,b.value=a,c.value="reconnecting",d=setTimeout(()=>{d=null,M()},v),v=Math.min(v*2,t.reconnectMaxDelay)}function M(){t.mode==="fetch"?J():F()}const O={status:c,connected:f,lastMessage:D,lastEvent:T,error:h,reconnectCount:b,on(e,n){return s[e].push(n),()=>{const l=s[e],i=l.indexOf(n);i>=0&&l.splice(i,1)}},pipe(e,n){const l=i=>{e.value=n(i,e.value)};return O.on("message",l)},close(){x=!0,d&&(clearTimeout(d),d=null),o&&(o.close(),o=null),r&&(r.abort(),r=null),c.value="closed",f.value=!1}};return M(),O}const P={connect:N};exports.sse=P;
package/dist/sse.es.js ADDED
@@ -0,0 +1,137 @@
1
+ import { s as i } from "./signal.es.js";
2
+ const N = {
3
+ mode: "eventsource",
4
+ method: "GET",
5
+ headers: {},
6
+ body: void 0,
7
+ reconnect: !0,
8
+ reconnectDelay: 1e3,
9
+ reconnectMaxDelay: 3e4,
10
+ reconnectAttempts: 1 / 0,
11
+ events: [],
12
+ json: !0
13
+ };
14
+ function U(D, k = {}) {
15
+ const t = { ...N, ...k }, c = i("connecting"), f = i(!1), S = i(null), x = i(null), h = i(null), b = i(0), s = {
16
+ message: [],
17
+ open: [],
18
+ close: [],
19
+ error: []
20
+ };
21
+ let o = null, l = null, T = !1, v = t.reconnectDelay, d = null, a = 0;
22
+ function m(e) {
23
+ if (!t.json || typeof e != "string") return e;
24
+ try {
25
+ return JSON.parse(e);
26
+ } catch {
27
+ return e;
28
+ }
29
+ }
30
+ function p(e, n) {
31
+ S.value = e, x.value = n;
32
+ for (const r of s.message) r(e, n ?? void 0);
33
+ }
34
+ function A() {
35
+ c.value = "open", f.value = !0, h.value = null, a = 0, v = t.reconnectDelay, b.value = 0;
36
+ for (const e of s.open) e();
37
+ }
38
+ function y() {
39
+ c.value = "closed", f.value = !1;
40
+ for (const e of s.close) e();
41
+ !T && t.reconnect && a < t.reconnectAttempts && L();
42
+ }
43
+ function E(e) {
44
+ h.value = e;
45
+ for (const n of s.error) n(e);
46
+ }
47
+ function F() {
48
+ c.value = a > 0 ? "reconnecting" : "connecting";
49
+ try {
50
+ o = new EventSource(D);
51
+ } catch {
52
+ c.value = "closed", f.value = !1;
53
+ return;
54
+ }
55
+ o.onopen = () => A(), o.onmessage = (e) => {
56
+ p(m(e.data), null);
57
+ };
58
+ for (const e of t.events)
59
+ o.addEventListener(e, (n) => {
60
+ p(m(n.data), e);
61
+ });
62
+ o.onerror = (e) => {
63
+ E(e), o && o.readyState === 2 && (o = null, y());
64
+ };
65
+ }
66
+ function J() {
67
+ c.value = a > 0 ? "reconnecting" : "connecting", l = new AbortController();
68
+ const e = {
69
+ method: t.method,
70
+ headers: t.headers,
71
+ signal: l.signal
72
+ };
73
+ t.body !== void 0 && (e.body = typeof t.body == "string" ? t.body : JSON.stringify(t.body)), fetch(D, e).then(async (n) => {
74
+ if (!n.ok) {
75
+ E(new Error(`[tina4] SSE fetch ${n.status}`)), y();
76
+ return;
77
+ }
78
+ A();
79
+ const r = n.body.getReader(), u = new TextDecoder();
80
+ let g = "";
81
+ for (; ; ) {
82
+ const { done: R, value: G } = await r.read();
83
+ if (R) break;
84
+ g += u.decode(G, { stream: !0 });
85
+ const w = g.split(`
86
+ `);
87
+ g = w.pop();
88
+ for (const I of w) {
89
+ const j = I.trim();
90
+ j && p(m(j), null);
91
+ }
92
+ }
93
+ const O = g.trim();
94
+ O && p(m(O), null), l = null, y();
95
+ }).catch((n) => {
96
+ n.name !== "AbortError" && (l = null, E(n), y());
97
+ });
98
+ }
99
+ function L() {
100
+ a++, b.value = a, c.value = "reconnecting", d = setTimeout(() => {
101
+ d = null, C();
102
+ }, v), v = Math.min(v * 2, t.reconnectMaxDelay);
103
+ }
104
+ function C() {
105
+ t.mode === "fetch" ? J() : F();
106
+ }
107
+ const M = {
108
+ status: c,
109
+ connected: f,
110
+ lastMessage: S,
111
+ lastEvent: x,
112
+ error: h,
113
+ reconnectCount: b,
114
+ on(e, n) {
115
+ return s[e].push(n), () => {
116
+ const r = s[e], u = r.indexOf(n);
117
+ u >= 0 && r.splice(u, 1);
118
+ };
119
+ },
120
+ pipe(e, n) {
121
+ const r = (u) => {
122
+ e.value = n(u, e.value);
123
+ };
124
+ return M.on("message", r);
125
+ },
126
+ close() {
127
+ T = !0, d && (clearTimeout(d), d = null), o && (o.close(), o = null), l && (l.abort(), l = null), c.value = "closed", f.value = !1;
128
+ }
129
+ };
130
+ return C(), M;
131
+ }
132
+ const q = {
133
+ connect: U
134
+ };
135
+ export {
136
+ q as sse
137
+ };
package/dist/tina4.cjs.js CHANGED
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./signal.cjs.js"),r=require("./core.cjs.js"),i=require("./component.cjs.js"),t=require("./index.cjs.js"),n=require("./api.cjs.js"),a=require("./pwa.cjs.js"),o=require("./ws.cjs.js");exports.batch=e.batch;exports.computed=e.computed;exports.effect=e.effect;exports.isSignal=e.isSignal;exports.signal=e.signal;exports.html=r.html;exports.Tina4Element=i.Tina4Element;exports.navigate=t.navigate;exports.route=t.route;exports.router=t.router;exports.api=n.api;exports.pwa=a.pwa;exports.ws=o.ws;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./signal.cjs.js"),r=require("./core.cjs.js"),i=require("./component.cjs.js"),t=require("./index.cjs.js"),s=require("./api.cjs.js"),n=require("./pwa.cjs.js"),o=require("./ws.cjs.js"),a=require("./sse.cjs.js");exports.batch=e.batch;exports.computed=e.computed;exports.effect=e.effect;exports.isSignal=e.isSignal;exports.signal=e.signal;exports.html=r.html;exports.Tina4Element=i.Tina4Element;exports.navigate=t.navigate;exports.route=t.route;exports.router=t.router;exports.api=s.api;exports.pwa=n.pwa;exports.ws=o.ws;exports.sse=a.sse;
package/dist/tina4.es.js CHANGED
@@ -1,14 +1,15 @@
1
- import { b as o, c as e, e as t, i as s, s as m } from "./signal.es.js";
1
+ import { b as a, c as e, e as t, i as s, s as m } from "./signal.es.js";
2
2
  import { html as f } from "./core.es.js";
3
- import { T as x } from "./component.es.js";
3
+ import { T as i } from "./component.es.js";
4
4
  import { n as c, r as l, a as g } from "./index.es.js";
5
5
  import { api as b } from "./api.es.js";
6
6
  import { pwa as w } from "./pwa.es.js";
7
7
  import { ws as d } from "./ws.es.js";
8
+ import { sse as E } from "./sse.es.js";
8
9
  export {
9
- x as Tina4Element,
10
+ i as Tina4Element,
10
11
  b as api,
11
- o as batch,
12
+ a as batch,
12
13
  e as computed,
13
14
  t as effect,
14
15
  f as html,
@@ -18,5 +19,6 @@ export {
18
19
  l as route,
19
20
  g as router,
20
21
  m as signal,
22
+ E as sse,
21
23
  d as ws
22
24
  };
@@ -0,0 +1,6 @@
1
+ /**
2
+ * tina4js/ws — Signal-driven WebSocket client with auto-reconnect.
3
+ */
4
+ export { ws } from './ws';
5
+ export type { SocketStatus, SocketOptions, ManagedSocket } from './ws';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ws/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,EAAE,EAAE,MAAM,MAAM,CAAC;AAC1B,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,MAAM,CAAC"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Tina4 WebSocket — Signal-driven WebSocket client with auto-reconnect.
3
+ *
4
+ * ws.connect(url, options?) — create a managed WebSocket connection
5
+ * socket.status — reactive signal: 'connecting' | 'open' | 'closed' | 'reconnecting'
6
+ * socket.connected — reactive signal: boolean
7
+ * socket.lastMessage — reactive signal: last parsed message
8
+ * socket.send(data) — send data (auto-stringify objects)
9
+ * socket.on(event, handler) — listen for messages/open/close/error
10
+ * socket.pipe(signal, reducer) — pipe messages directly into a signal
11
+ * socket.close() — disconnect (stops reconnect)
12
+ */
13
+ import type { Signal } from '../core/signal';
14
+ export type SocketStatus = 'connecting' | 'open' | 'closed' | 'reconnecting';
15
+ export interface SocketOptions {
16
+ /** Enable auto-reconnect on disconnect (default: true). */
17
+ reconnect?: boolean;
18
+ /** Initial reconnect delay in ms (default: 1000). */
19
+ reconnectDelay?: number;
20
+ /** Max reconnect delay in ms, for exponential backoff (default: 30000). */
21
+ reconnectMaxDelay?: number;
22
+ /** Max reconnect attempts before giving up (default: Infinity). */
23
+ reconnectAttempts?: number;
24
+ /** WebSocket sub-protocols. */
25
+ protocols?: string | string[];
26
+ }
27
+ export type MessageHandler = (data: unknown) => void;
28
+ export type OpenHandler = () => void;
29
+ export type CloseHandler = (code: number, reason: string) => void;
30
+ export type ErrorHandler = (error: Event) => void;
31
+ type EventMap = {
32
+ message: MessageHandler;
33
+ open: OpenHandler;
34
+ close: CloseHandler;
35
+ error: ErrorHandler;
36
+ };
37
+ export interface ManagedSocket {
38
+ /** Reactive connection status. */
39
+ readonly status: Signal<SocketStatus>;
40
+ /** Reactive boolean — true when status is 'open'. */
41
+ readonly connected: Signal<boolean>;
42
+ /** Reactive — last received message (parsed JSON or raw string). */
43
+ readonly lastMessage: Signal<unknown>;
44
+ /** Reactive — last error event, or null. */
45
+ readonly error: Signal<Event | null>;
46
+ /** Number of reconnect attempts so far. */
47
+ readonly reconnectCount: Signal<number>;
48
+ /** Send data. Objects are JSON.stringify'd automatically. */
49
+ send(data: unknown): void;
50
+ /** Listen for events. Returns unsubscribe function. */
51
+ on<K extends keyof EventMap>(event: K, handler: EventMap[K]): () => void;
52
+ /** Pipe messages into a signal via a reducer. */
53
+ pipe<T>(target: Signal<T>, reducer: (message: unknown, current: T) => T): () => void;
54
+ /** Close the connection and stop reconnecting. */
55
+ close(code?: number, reason?: string): void;
56
+ }
57
+ declare function createSocket(url: string, options?: SocketOptions): ManagedSocket;
58
+ export declare const ws: {
59
+ connect: typeof createSocket;
60
+ };
61
+ export {};
62
+ //# sourceMappingURL=ws.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ws.d.ts","sourceRoot":"","sources":["../../src/ws/ws.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAI7C,MAAM,MAAM,YAAY,GAAG,YAAY,GAAG,MAAM,GAAG,QAAQ,GAAG,cAAc,CAAC;AAE7E,MAAM,WAAW,aAAa;IAC5B,2DAA2D;IAC3D,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,qDAAqD;IACrD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,2EAA2E;IAC3E,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,mEAAmE;IACnE,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,+BAA+B;IAC/B,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;CAC/B;AAED,MAAM,MAAM,cAAc,GAAG,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;AACrD,MAAM,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC;AACrC,MAAM,MAAM,YAAY,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;AAClE,MAAM,MAAM,YAAY,GAAG,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;AAElD,KAAK,QAAQ,GAAG;IACd,OAAO,EAAE,cAAc,CAAC;IACxB,IAAI,EAAE,WAAW,CAAC;IAClB,KAAK,EAAE,YAAY,CAAC;IACpB,KAAK,EAAE,YAAY,CAAC;CACrB,CAAC;AAEF,MAAM,WAAW,aAAa;IAC5B,kCAAkC;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,YAAY,CAAC,CAAC;IACtC,qDAAqD;IACrD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;IACpC,oEAAoE;IACpE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;IACtC,4CAA4C;IAC5C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC;IACrC,2CAA2C;IAC3C,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;IAExC,6DAA6D;IAC7D,IAAI,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAAC;IAE1B,uDAAuD;IACvD,EAAE,CAAC,CAAC,SAAS,MAAM,QAAQ,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,CAAC;IAEzE,iDAAiD;IACjD,IAAI,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC;IAErF,kDAAkD;IAClD,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7C;AAcD,iBAAS,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,aAAkB,GAAG,aAAa,CA0I7E;AAID,eAAO,MAAM,EAAE;;CAEd,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tina4js",
3
- "version": "1.0.15",
3
+ "version": "1.1.0",
4
4
  "description": "1.5KB core gzipped, reactive framework — signals, web components, routing, PWA",
5
5
  "type": "module",
6
6
  "main": "dist/tina4.cjs.js",
@@ -35,6 +35,10 @@
35
35
  "./ws": {
36
36
  "import": "./dist/ws.es.js",
37
37
  "types": "./dist/ws/index.d.ts"
38
+ },
39
+ "./sse": {
40
+ "import": "./dist/sse.es.js",
41
+ "types": "./dist/sse/index.d.ts"
38
42
  }
39
43
  },
40
44
  "sideEffects": false,