tina4js 1.0.15 → 1.1.1

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 (54) hide show
  1. package/dist/api/fetch.d.ts +151 -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/api.cjs.js +1 -1
  6. package/dist/api.es.js +77 -52
  7. package/dist/core/component.d.ts +59 -0
  8. package/dist/core/component.d.ts.map +1 -0
  9. package/dist/core/html.d.ts +37 -0
  10. package/dist/core/html.d.ts.map +1 -0
  11. package/dist/core/index.d.ts +9 -0
  12. package/dist/core/index.d.ts.map +1 -0
  13. package/dist/core/signal.d.ts +101 -0
  14. package/dist/core/signal.d.ts.map +1 -0
  15. package/dist/debug/index.d.ts +17 -0
  16. package/dist/debug/index.d.ts.map +1 -0
  17. package/dist/debug/overlay.d.ts +25 -0
  18. package/dist/debug/overlay.d.ts.map +1 -0
  19. package/dist/debug/panels/api.d.ts +5 -0
  20. package/dist/debug/panels/api.d.ts.map +1 -0
  21. package/dist/debug/panels/components.d.ts +5 -0
  22. package/dist/debug/panels/components.d.ts.map +1 -0
  23. package/dist/debug/panels/routes.d.ts +5 -0
  24. package/dist/debug/panels/routes.d.ts.map +1 -0
  25. package/dist/debug/panels/signals.d.ts +5 -0
  26. package/dist/debug/panels/signals.d.ts.map +1 -0
  27. package/dist/debug/styles.d.ts +5 -0
  28. package/dist/debug/styles.d.ts.map +1 -0
  29. package/dist/debug/trackers.d.ts +89 -0
  30. package/dist/debug/trackers.d.ts.map +1 -0
  31. package/dist/index.d.ts +21 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/pwa/index.d.ts +6 -0
  34. package/dist/pwa/index.d.ts.map +1 -0
  35. package/dist/pwa/pwa.d.ts +24 -0
  36. package/dist/pwa/pwa.d.ts.map +1 -0
  37. package/dist/router/index.d.ts +6 -0
  38. package/dist/router/index.d.ts.map +1 -0
  39. package/dist/router/router.d.ts +72 -0
  40. package/dist/router/router.d.ts.map +1 -0
  41. package/dist/sse/index.d.ts +6 -0
  42. package/dist/sse/index.d.ts.map +1 -0
  43. package/dist/sse/sse.d.ts +72 -0
  44. package/dist/sse/sse.d.ts.map +1 -0
  45. package/dist/sse.cjs.js +2 -0
  46. package/dist/sse.es.js +137 -0
  47. package/dist/tina4.cjs.js +1 -1
  48. package/dist/tina4.es.js +6 -4
  49. package/dist/tina4js.min.js +48 -0
  50. package/dist/ws/index.d.ts +6 -0
  51. package/dist/ws/index.d.ts.map +1 -0
  52. package/dist/ws/ws.d.ts +62 -0
  53. package/dist/ws/ws.d.ts.map +1 -0
  54. package/package.json +5 -1
@@ -0,0 +1,151 @@
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
+ * Execute a GraphQL query or mutation.
89
+ *
90
+ * Sends a POST request with `{ query, variables }` body to the given path.
91
+ * Returns `{ data, errors }` — throws if the HTTP request itself fails.
92
+ *
93
+ * @param path - GraphQL endpoint path (e.g. `/api/graphql`).
94
+ * @param query - GraphQL query or mutation string.
95
+ * @param variables - Optional variables object.
96
+ * @param options - `{ params, headers }`.
97
+ *
98
+ * @example
99
+ * const { data, errors } = await api.graphql('/api/graphql',
100
+ * '{ products(limit: 10) { id name price } }'
101
+ * );
102
+ *
103
+ * @example
104
+ * const { data } = await api.graphql('/api/graphql',
105
+ * 'query ($term: String!) { search_products(term: $term) { id name } }',
106
+ * { term: "widget" }
107
+ * );
108
+ */
109
+ graphql<T = unknown>(path: string, query: string, variables?: Record<string, unknown>, options?: RequestOptions): Promise<{
110
+ data: T | null;
111
+ errors?: Array<{
112
+ message: string;
113
+ }>;
114
+ }>;
115
+ /**
116
+ * Upload files via FormData (multipart/form-data).
117
+ *
118
+ * Unlike `post()`, this does NOT JSON-stringify the body or set
119
+ * Content-Type — the browser sets the multipart boundary automatically.
120
+ * Auth uses the Bearer token header (formToken cannot be injected into FormData).
121
+ *
122
+ * @param path - API path relative to `baseUrl`.
123
+ * @param formData - A FormData instance containing files and fields.
124
+ * @param options - `{ params, headers }` — query string params and per-request headers.
125
+ *
126
+ * @example
127
+ * const form = new FormData();
128
+ * form.append('avatar', fileInput.files[0]);
129
+ * form.append('name', 'Alice');
130
+ * const result = await api.upload('/users/avatar', form);
131
+ */
132
+ upload<T = unknown>(path: string, formData: FormData, options?: RequestOptions): Promise<T>;
133
+ /**
134
+ * Register a request or response interceptor.
135
+ *
136
+ * @example
137
+ * // Add a custom header to every request
138
+ * api.intercept('request', (config) => {
139
+ * config.headers['X-Client'] = 'my-app';
140
+ * });
141
+ *
142
+ * // Redirect to login on 401
143
+ * api.intercept('response', (res) => {
144
+ * if (res.status === 401) navigate('/login');
145
+ * });
146
+ */
147
+ intercept(type: "request" | "response", fn: RequestInterceptor | ResponseInterceptor): void;
148
+ /** @internal Reset state (for tests). */
149
+ _reset(): void;
150
+ };
151
+ //# 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;;;;;;;;;;;;;;;;;;;;;OAqBG;YACW,CAAC,kBAAkB,MAAM,SAAS,MAAM,cAAc,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,YAAY,cAAc,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC;QAAC,MAAM,CAAC,EAAE,KAAK,CAAC;YAAE,OAAO,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;KAAE,CAAC;IAIxL;;;;;;;;;;;;;;;;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"}
package/dist/api.cjs.js CHANGED
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const a={baseUrl:"",auth:!1,tokenKey:"tina4_token",headers:{}},k=[],T=[];let j=0;function y(){try{return localStorage.getItem(a.tokenKey)}catch{return null}}function I(e){try{localStorage.setItem(a.tokenKey,e)}catch{}}async function h(e,s,r,n){let c={method:e,headers:{"Content-Type":"application/json",...a.headers}};if(a.auth){const t=y();t&&(c.headers.Authorization=`Bearer ${t}`)}if(r!==void 0&&e!=="GET"){let t=typeof r=="object"&&r!==null?{...r}:r;if(a.auth&&typeof t=="object"&&t!==null){const u=y();u&&(t.formToken=u)}c.body=JSON.stringify(t)}if(n!=null&&n.headers&&Object.assign(c.headers,n.headers),n!=null&&n.params){const t=Object.entries(n.params).map(([u,m])=>`${encodeURIComponent(u)}=${encodeURIComponent(String(m))}`).join("&");s+=(s.includes("?")?"&":"?")+t}const d=a.baseUrl+s;c._url=d,c._requestId=++j;for(const t of k){const u=t(c);u&&(c=u)}const f=await fetch(d,c),g=f.headers.get("FreshToken");g&&I(g);const i=f.headers.get("Content-Type")??"";let l;i.includes("json")?l=await f.json():l=await f.text();let o={status:f.status,data:l,ok:f.ok,headers:f.headers,_requestId:c._requestId};for(const t of T){const u=t(o);u&&(o=u)}if(!f.ok)throw o;return o.data}const q={configure(e){Object.assign(a,e)},get(e,s){return h("GET",e,void 0,s)},post(e,s,r){return h("POST",e,s,r)},put(e,s,r){return h("PUT",e,s,r)},patch(e,s,r){return h("PATCH",e,s,r)},delete(e,s){return h("DELETE",e,void 0,s)},async upload(e,s,r){let n={method:"POST",headers:{...a.headers},body:s};if(delete n.headers["Content-Type"],delete n.headers["content-type"],a.auth){const o=y();o&&(n.headers.Authorization=`Bearer ${o}`)}if(r!=null&&r.headers&&Object.assign(n.headers,r.headers),r!=null&&r.params){const o=Object.entries(r.params).map(([t,u])=>`${encodeURIComponent(t)}=${encodeURIComponent(String(u))}`).join("&");e+=(e.includes("?")?"&":"?")+o}const c=a.baseUrl+e;n._url=c,n._requestId=++j;for(const o of k){const t=o(n);t&&(n=t)}const d=await fetch(c,n),f=d.headers.get("FreshToken");f&&I(f);const g=d.headers.get("Content-Type")??"";let i;g.includes("json")?i=await d.json():i=await d.text();let l={status:d.status,data:i,ok:d.ok,headers:d.headers,_requestId:n._requestId};for(const o of T){const t=o(l);t&&(l=t)}if(!d.ok)throw l;return l.data},intercept(e,s){e==="request"?k.push(s):T.push(s)},_reset(){a.baseUrl="",a.auth=!1,a.tokenKey="tina4_token",a.headers={},k.length=0,T.length=0}};exports.api=q;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const a={baseUrl:"",auth:!1,tokenKey:"tina4_token",headers:{}},k=[],T=[];let j=0;function y(){try{return localStorage.getItem(a.tokenKey)}catch{return null}}function q(e){try{localStorage.setItem(a.tokenKey,e)}catch{}}async function i(e,s,t,r){let c={method:e,headers:{"Content-Type":"application/json",...a.headers}};if(a.auth){const n=y();n&&(c.headers.Authorization=`Bearer ${n}`)}if(t!==void 0&&e!=="GET"){let n=typeof t=="object"&&t!==null?{...t}:t;if(a.auth&&typeof n=="object"&&n!==null){const u=y();u&&(n.formToken=u)}c.body=JSON.stringify(n)}if(r!=null&&r.headers&&Object.assign(c.headers,r.headers),r!=null&&r.params){const n=Object.entries(r.params).map(([u,I])=>`${encodeURIComponent(u)}=${encodeURIComponent(String(I))}`).join("&");s+=(s.includes("?")?"&":"?")+n}const d=a.baseUrl+s;c._url=d,c._requestId=++j;for(const n of k){const u=n(c);u&&(c=u)}const l=await fetch(d,c),g=l.headers.get("FreshToken");g&&q(g);const h=l.headers.get("Content-Type")??"";let f;h.includes("json")?f=await l.json():f=await l.text();let o={status:l.status,data:f,ok:l.ok,headers:l.headers,_requestId:c._requestId};for(const n of T){const u=n(o);u&&(o=u)}if(!l.ok)throw o;return o.data}const m={configure(e){Object.assign(a,e)},get(e,s){return i("GET",e,void 0,s)},post(e,s,t){return i("POST",e,s,t)},put(e,s,t){return i("PUT",e,s,t)},patch(e,s,t){return i("PATCH",e,s,t)},delete(e,s){return i("DELETE",e,void 0,s)},async graphql(e,s,t,r){return i("POST",e,{query:s,variables:t||{}},r)},async upload(e,s,t){let r={method:"POST",headers:{...a.headers},body:s};if(delete r.headers["Content-Type"],delete r.headers["content-type"],a.auth){const o=y();o&&(r.headers.Authorization=`Bearer ${o}`)}if(t!=null&&t.headers&&Object.assign(r.headers,t.headers),t!=null&&t.params){const o=Object.entries(t.params).map(([n,u])=>`${encodeURIComponent(n)}=${encodeURIComponent(String(u))}`).join("&");e+=(e.includes("?")?"&":"?")+o}const c=a.baseUrl+e;r._url=c,r._requestId=++j;for(const o of k){const n=o(r);n&&(r=n)}const d=await fetch(c,r),l=d.headers.get("FreshToken");l&&q(l);const g=d.headers.get("Content-Type")??"";let h;g.includes("json")?h=await d.json():h=await d.text();let f={status:d.status,data:h,ok:d.ok,headers:d.headers,_requestId:r._requestId};for(const o of T){const n=o(f);n&&(f=n)}if(!d.ok)throw f;return f.data},intercept(e,s){e==="request"?k.push(s):T.push(s)},_reset(){a.baseUrl="",a.auth=!1,a.tokenKey="tina4_token",a.headers={},k.length=0,T.length=0}};exports.api=m;
package/dist/api.es.js CHANGED
@@ -3,8 +3,8 @@ const a = {
3
3
  auth: !1,
4
4
  tokenKey: "tina4_token",
5
5
  headers: {}
6
- }, g = [], T = [];
7
- let I = 0;
6
+ }, k = [], T = [];
7
+ let q = 0;
8
8
  function y() {
9
9
  try {
10
10
  return localStorage.getItem(a.tokenKey);
@@ -12,13 +12,13 @@ function y() {
12
12
  return null;
13
13
  }
14
14
  }
15
- function j(e) {
15
+ function I(e) {
16
16
  try {
17
17
  localStorage.setItem(a.tokenKey, e);
18
18
  } catch {
19
19
  }
20
20
  }
21
- async function h(e, s, r, n) {
21
+ async function i(e, s, t, r) {
22
22
  let c = {
23
23
  method: e,
24
24
  headers: {
@@ -27,32 +27,32 @@ async function h(e, s, r, n) {
27
27
  }
28
28
  };
29
29
  if (a.auth) {
30
- const t = y();
31
- t && (c.headers.Authorization = `Bearer ${t}`);
30
+ const n = y();
31
+ n && (c.headers.Authorization = `Bearer ${n}`);
32
32
  }
33
- if (r !== void 0 && e !== "GET") {
34
- let t = typeof r == "object" && r !== null ? { ...r } : r;
35
- if (a.auth && typeof t == "object" && t !== null) {
33
+ if (t !== void 0 && e !== "GET") {
34
+ let n = typeof t == "object" && t !== null ? { ...t } : t;
35
+ if (a.auth && typeof n == "object" && n !== null) {
36
36
  const u = y();
37
- u && (t.formToken = u);
37
+ u && (n.formToken = u);
38
38
  }
39
- c.body = JSON.stringify(t);
39
+ c.body = JSON.stringify(n);
40
40
  }
41
- if (n != null && n.headers && Object.assign(c.headers, n.headers), n != null && n.params) {
42
- const t = Object.entries(n.params).map(([u, q]) => `${encodeURIComponent(u)}=${encodeURIComponent(String(q))}`).join("&");
43
- s += (s.includes("?") ? "&" : "?") + t;
41
+ if (r != null && r.headers && Object.assign(c.headers, r.headers), r != null && r.params) {
42
+ const n = Object.entries(r.params).map(([u, j]) => `${encodeURIComponent(u)}=${encodeURIComponent(String(j))}`).join("&");
43
+ s += (s.includes("?") ? "&" : "?") + n;
44
44
  }
45
45
  const d = a.baseUrl + s;
46
- c._url = d, c._requestId = ++I;
47
- for (const t of g) {
48
- const u = t(c);
46
+ c._url = d, c._requestId = ++q;
47
+ for (const n of k) {
48
+ const u = n(c);
49
49
  u && (c = u);
50
50
  }
51
- const f = await fetch(d, c), k = f.headers.get("FreshToken");
52
- k && j(k);
53
- const i = f.headers.get("Content-Type") ?? "";
51
+ const f = await fetch(d, c), g = f.headers.get("FreshToken");
52
+ g && I(g);
53
+ const h = f.headers.get("Content-Type") ?? "";
54
54
  let l;
55
- i.includes("json") ? l = await f.json() : l = await f.text();
55
+ h.includes("json") ? l = await f.json() : l = await f.text();
56
56
  let o = {
57
57
  status: f.status,
58
58
  data: l,
@@ -60,8 +60,8 @@ async function h(e, s, r, n) {
60
60
  headers: f.headers,
61
61
  _requestId: c._requestId
62
62
  };
63
- for (const t of T) {
64
- const u = t(o);
63
+ for (const n of T) {
64
+ const u = n(o);
65
65
  u && (o = u);
66
66
  }
67
67
  if (!f.ok)
@@ -91,7 +91,7 @@ const m = {
91
91
  * const data = await api.get('/products', { params: { page: 2, limit: 20 } });
92
92
  */
93
93
  get(e, s) {
94
- return h("GET", e, void 0, s);
94
+ return i("GET", e, void 0, s);
95
95
  },
96
96
  /**
97
97
  * HTTP POST request.
@@ -101,20 +101,45 @@ const m = {
101
101
  * @example
102
102
  * await api.post('/users', { name: 'Alice', role: 'admin' });
103
103
  */
104
- post(e, s, r) {
105
- return h("POST", e, s, r);
104
+ post(e, s, t) {
105
+ return i("POST", e, s, t);
106
106
  },
107
107
  /** HTTP PUT — full replace. */
108
- put(e, s, r) {
109
- return h("PUT", e, s, r);
108
+ put(e, s, t) {
109
+ return i("PUT", e, s, t);
110
110
  },
111
111
  /** HTTP PATCH — partial update. */
112
- patch(e, s, r) {
113
- return h("PATCH", e, s, r);
112
+ patch(e, s, t) {
113
+ return i("PATCH", e, s, t);
114
114
  },
115
115
  /** HTTP DELETE. */
116
116
  delete(e, s) {
117
- return h("DELETE", e, void 0, s);
117
+ return i("DELETE", e, void 0, s);
118
+ },
119
+ /**
120
+ * Execute a GraphQL query or mutation.
121
+ *
122
+ * Sends a POST request with `{ query, variables }` body to the given path.
123
+ * Returns `{ data, errors }` — throws if the HTTP request itself fails.
124
+ *
125
+ * @param path - GraphQL endpoint path (e.g. `/api/graphql`).
126
+ * @param query - GraphQL query or mutation string.
127
+ * @param variables - Optional variables object.
128
+ * @param options - `{ params, headers }`.
129
+ *
130
+ * @example
131
+ * const { data, errors } = await api.graphql('/api/graphql',
132
+ * '{ products(limit: 10) { id name price } }'
133
+ * );
134
+ *
135
+ * @example
136
+ * const { data } = await api.graphql('/api/graphql',
137
+ * 'query ($term: String!) { search_products(term: $term) { id name } }',
138
+ * { term: "widget" }
139
+ * );
140
+ */
141
+ async graphql(e, s, t, r) {
142
+ return i("POST", e, { query: s, variables: t || {} }, r);
118
143
  },
119
144
  /**
120
145
  * Upload files via FormData (multipart/form-data).
@@ -133,8 +158,8 @@ const m = {
133
158
  * form.append('name', 'Alice');
134
159
  * const result = await api.upload('/users/avatar', form);
135
160
  */
136
- async upload(e, s, r) {
137
- let n = {
161
+ async upload(e, s, t) {
162
+ let r = {
138
163
  method: "POST",
139
164
  headers: {
140
165
  ...a.headers
@@ -142,35 +167,35 @@ const m = {
142
167
  },
143
168
  body: s
144
169
  };
145
- if (delete n.headers["Content-Type"], delete n.headers["content-type"], a.auth) {
170
+ if (delete r.headers["Content-Type"], delete r.headers["content-type"], a.auth) {
146
171
  const o = y();
147
- o && (n.headers.Authorization = `Bearer ${o}`);
172
+ o && (r.headers.Authorization = `Bearer ${o}`);
148
173
  }
149
- if (r != null && r.headers && Object.assign(n.headers, r.headers), r != null && r.params) {
150
- const o = Object.entries(r.params).map(([t, u]) => `${encodeURIComponent(t)}=${encodeURIComponent(String(u))}`).join("&");
174
+ if (t != null && t.headers && Object.assign(r.headers, t.headers), t != null && t.params) {
175
+ const o = Object.entries(t.params).map(([n, u]) => `${encodeURIComponent(n)}=${encodeURIComponent(String(u))}`).join("&");
151
176
  e += (e.includes("?") ? "&" : "?") + o;
152
177
  }
153
178
  const c = a.baseUrl + e;
154
- n._url = c, n._requestId = ++I;
155
- for (const o of g) {
156
- const t = o(n);
157
- t && (n = t);
179
+ r._url = c, r._requestId = ++q;
180
+ for (const o of k) {
181
+ const n = o(r);
182
+ n && (r = n);
158
183
  }
159
- const d = await fetch(c, n), f = d.headers.get("FreshToken");
160
- f && j(f);
161
- const k = d.headers.get("Content-Type") ?? "";
162
- let i;
163
- k.includes("json") ? i = await d.json() : i = await d.text();
184
+ const d = await fetch(c, r), f = d.headers.get("FreshToken");
185
+ f && I(f);
186
+ const g = d.headers.get("Content-Type") ?? "";
187
+ let h;
188
+ g.includes("json") ? h = await d.json() : h = await d.text();
164
189
  let l = {
165
190
  status: d.status,
166
- data: i,
191
+ data: h,
167
192
  ok: d.ok,
168
193
  headers: d.headers,
169
- _requestId: n._requestId
194
+ _requestId: r._requestId
170
195
  };
171
196
  for (const o of T) {
172
- const t = o(l);
173
- t && (l = t);
197
+ const n = o(l);
198
+ n && (l = n);
174
199
  }
175
200
  if (!d.ok)
176
201
  throw l;
@@ -191,11 +216,11 @@ const m = {
191
216
  * });
192
217
  */
193
218
  intercept(e, s) {
194
- e === "request" ? g.push(s) : T.push(s);
219
+ e === "request" ? k.push(s) : T.push(s);
195
220
  },
196
221
  /** @internal Reset state (for tests). */
197
222
  _reset() {
198
- a.baseUrl = "", a.auth = !1, a.tokenKey = "tina4_token", a.headers = {}, g.length = 0, T.length = 0;
223
+ a.baseUrl = "", a.auth = !1, a.tokenKey = "tina4_token", a.headers = {}, k.length = 0, T.length = 0;
199
224
  }
200
225
  };
201
226
  export {
@@ -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