tina4js 1.2.2 → 1.2.7
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.
- package/TINA4.md +3 -0
- package/bin/tina4.js +3 -1
- package/dist/core/signal.d.ts +1 -1
- package/dist/core/signal.d.ts.map +1 -1
- package/dist/debug.cjs.js +7 -7
- package/dist/debug.es.js +19 -19
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/signal.cjs.js +1 -1
- package/dist/signal.es.js +78 -62
- package/dist/storage/index.d.ts +9 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/persist.d.ts +75 -0
- package/dist/storage/persist.d.ts.map +1 -0
- package/dist/storage.cjs.js +1 -0
- package/dist/storage.es.js +144 -0
- package/dist/tina4.cjs.js +1 -1
- package/dist/tina4.es.js +17 -14
- package/package.json +5 -1
- package/readme.md +4 -2
package/TINA4.md
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
| API | `await api.get('/path')`, `.post`, `.put`, `.patch`, `.delete` |
|
|
17
17
|
| PWA | `pwa.register({ name, themeColor, cacheStrategy })` |
|
|
18
18
|
| WebSocket | `ws.connect('wss://...', { reconnect: true })` — signals for status/messages |
|
|
19
|
+
| Storage | `persist(signal('light'), { key: 'theme' })` from `tina4js/storage` — survives a refresh |
|
|
19
20
|
| Debug | `import 'tina4js/debug'` — toggle with Ctrl+Shift+D |
|
|
20
21
|
|
|
21
22
|
## File Conventions
|
|
@@ -40,6 +41,8 @@
|
|
|
40
41
|
10. Use `static shadow = false` for light DOM components
|
|
41
42
|
11. `route(pattern, handler)` — pattern is ALWAYS the first argument, handler/config is second
|
|
42
43
|
12. `api.configure()` must be called before any API calls if you need auth or a base URL
|
|
44
|
+
13. `persist()` is for user preferences only — never tokens, passwords, personal data, or any
|
|
45
|
+
credentials. localStorage is XSS-readable. See `STORAGE.md` for the full dangers list.
|
|
43
46
|
|
|
44
47
|
## Signal Patterns
|
|
45
48
|
|
package/bin/tina4.js
CHANGED
|
@@ -163,7 +163,9 @@ export default defineConfig({
|
|
|
163
163
|
let mainTs = `import { signal, computed, html, route, router, navigate, api } from 'tina4js';
|
|
164
164
|
import './routes/index';
|
|
165
165
|
|
|
166
|
-
// Debug overlay in dev mode (Ctrl+Shift+D to toggle, tree-shaken from
|
|
166
|
+
// Debug overlay in dev mode (Ctrl+Shift+D to toggle, tree-shaken from
|
|
167
|
+
// production builds). Signals created before this dynamic import resolves —
|
|
168
|
+
// including module-level store signals — are buffered and still tracked.
|
|
167
169
|
if (import.meta.env.DEV) import('tina4js/debug');
|
|
168
170
|
`;
|
|
169
171
|
|
package/dist/core/signal.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ export declare function _getEffectCollector(): (() => void)[] | null;
|
|
|
14
14
|
export declare let __debugSignalCreate: ((s: Signal<unknown>, label?: string) => void) | null;
|
|
15
15
|
/** @internal Called when a signal value changes. */
|
|
16
16
|
export declare let __debugSignalUpdate: ((s: Signal<unknown>, oldVal: unknown, newVal: unknown) => void) | null;
|
|
17
|
-
/** @internal Set the debug hooks. */
|
|
17
|
+
/** @internal Set the debug hooks, then replay signals created before now. */
|
|
18
18
|
export declare function __setDebugSignalHooks(onCreate: typeof __debugSignalCreate, onUpdate: typeof __debugSignalUpdate): void;
|
|
19
19
|
export interface Signal<T> {
|
|
20
20
|
value: T;
|
|
@@ -1 +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;
|
|
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;AAqB/G,6EAA6E;AAC7E,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,OAAO,mBAAmB,EACpC,QAAQ,EAAE,OAAO,mBAAmB,QAqBrC;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,CA6D/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"}
|
package/dist/debug.cjs.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const R=require("./signal.cjs.js"),A=require("./component.cjs.js"),x=require("./index.cjs.js"),y=require("./api.cjs.js"),
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const R=require("./signal.cjs.js"),A=require("./component.cjs.js"),x=require("./index.cjs.js"),y=require("./api.cjs.js"),d=[],b={add(t,o){const e=t._debugInfo;d.push({ref:new WeakRef(t),label:o,createdAt:(e==null?void 0:e.createdAt)??Date.now(),updateCount:0,subs:new WeakRef((e==null?void 0:e.subs)??new Set)})},onUpdate(t){for(const o of d)if(o.ref.deref()===t){o.updateCount++;break}},getAll(){var o;const t=[];for(let e=d.length-1;e>=0;e--){const n=d[e],r=n.ref.deref();if(!r){d.splice(e,1);continue}const s=n.subs.deref();t.push({label:n.label,value:r.peek(),subscriberCount:s?s.size:0,updateCount:((o=r._debugInfo)==null?void 0:o.updateCount)??n.updateCount,alive:!0})}return t},get count(){return d.length}},a=[],h={onMount(t){a.push({ref:new WeakRef(t),tagName:t.tagName.toLowerCase(),mountedAt:Date.now()})},onUnmount(t){const o=a.findIndex(e=>e.ref.deref()===t);o>=0&&a.splice(o,1)},getAll(){const t=[];for(let o=a.length-1;o>=0;o--){const e=a[o],n=e.ref.deref();if(!n||!n.isConnected){a.splice(o,1);continue}const r={},s=n.constructor;if(s.props)for(const i of Object.keys(s.props))try{r[i]=n.prop(i).peek()}catch{}t.push({tagName:e.tagName,props:r,alive:!0})}return t},get count(){return a.length}},l=[],D=50;let f=null;const p={setGetRoutes(t){f=t},getRegisteredRoutes(){return f?f():[]},onNavigate(t){l.unshift({path:t.path,pattern:t.pattern,params:t.params,durationMs:t.durationMs,timestamp:Date.now()}),l.length>D&&l.pop()},getHistory(){return l},get count(){return l.length}};let M=0;const c=[],m=new Map,j=100,g={onRequest(t){var n;const o=t._requestId??++M,e={id:o,method:t.method??"GET",url:t._url??"",hasAuth:!!((n=t.headers)!=null&&n.Authorization),timestamp:Date.now(),pending:!0};m.set(o,e),c.unshift(e),c.length>j&&c.pop()},onResponse(t){const o=t._requestId,e=o!=null?m.get(o):void 0;e&&(e.status=t.status,e.durationMs=Date.now()-e.timestamp,e.pending=!1,t.ok||(e.error=`HTTP ${t.status}`),m.delete(o))},getLog(){return c},get count(){return c.length}},v=`
|
|
2
2
|
:host {
|
|
3
3
|
all: initial;
|
|
4
4
|
position: fixed;
|
|
@@ -204,7 +204,7 @@ tr:hover td { background: rgba(255,255,255,0.02); }
|
|
|
204
204
|
border-radius: 50%;
|
|
205
205
|
background: #66bb6a;
|
|
206
206
|
}
|
|
207
|
-
`;function E(t){if(t==null)return{text:String(t),cls:"val-null"};if(typeof t=="string")return{text:`"${t.length>30?t.slice(0,30)+"...":t}"`,cls:"val-string"};if(typeof t=="number")return{text:String(t),cls:"val-number"};if(typeof t=="boolean")return{text:String(t),cls:"val-boolean"};if(Array.isArray(t))return{text:`Array(${t.length})`,cls:"val-object"};if(typeof t=="object")try{return{text:JSON.stringify(t).slice(0,40),cls:"val-object"}}catch{}return{text:String(t),cls:"val-object"}}function L(){const t=b.getAll();if(t.length===0)return'<div class="t4-empty">No signals tracked yet.<br>
|
|
207
|
+
`;function E(t){if(t==null)return{text:String(t),cls:"val-null"};if(typeof t=="string")return{text:`"${t.length>30?t.slice(0,30)+"...":t}"`,cls:"val-string"};if(typeof t=="number")return{text:String(t),cls:"val-number"};if(typeof t=="boolean")return{text:String(t),cls:"val-boolean"};if(Array.isArray(t))return{text:`Array(${t.length})`,cls:"val-object"};if(typeof t=="object")try{return{text:JSON.stringify(t).slice(0,40),cls:"val-object"}}catch{}return{text:String(t),cls:"val-object"}}function L(){const t=b.getAll();if(t.length===0)return'<div class="t4-empty">No signals tracked yet.<br>Create a signal — it appears here, even ones made before the overlay loaded.</div>';let o="";for(let e=0;e<t.length;e++){const n=t[e],{text:r,cls:s}=E(n.value);o+=`<tr>
|
|
208
208
|
<td>${n.label||`signal_${e}`}</td>
|
|
209
209
|
<td><span class="${s}">${H(r)}</span></td>
|
|
210
210
|
<td>${n.subscriberCount}</td>
|
|
@@ -213,12 +213,12 @@ tr:hover td { background: rgba(255,255,255,0.02); }
|
|
|
213
213
|
<thead><tr><th>Label</th><th>Value</th><th>Subs</th><th>Updates</th></tr></thead>
|
|
214
214
|
<tbody>${o}</tbody>
|
|
215
215
|
</table>`}function H(t){return t.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">")}function z(){const t=h.getAll();if(t.length===0)return'<div class="t4-empty">No Tina4Elements mounted.<br>Custom elements extending Tina4Element will appear here.</div>';let o="";for(const e of t){const n=Object.keys(e.props).length>0?Object.entries(e.props).map(([r,s])=>`${r}=${JSON.stringify(s)??"null"}`).join(", "):"—";o+=`<tr>
|
|
216
|
-
<td><${
|
|
217
|
-
<td>${
|
|
216
|
+
<td><${w(e.tagName)}></td>
|
|
217
|
+
<td>${w(n.length>60?n.slice(0,60)+"...":n)}</td>
|
|
218
218
|
</tr>`}return`<table>
|
|
219
219
|
<thead><tr><th>Element</th><th>Props</th></tr></thead>
|
|
220
220
|
<tbody>${o}</tbody>
|
|
221
|
-
</table>`}function
|
|
221
|
+
</table>`}function w(t){return t.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">")}function q(t){const o=t<1?"<1ms":`${Math.round(t)}ms`,e=t<5?"duration fast":t<50?"duration":t<200?"duration slow":"duration very-slow";return{text:o,cls:e}}function I(t){return new Date(t).toLocaleTimeString("en-US",{hour12:!1,hour:"2-digit",minute:"2-digit",second:"2-digit"})}function P(){const t=p.getRegisteredRoutes(),o=p.getHistory();let e="";if(t.length>0){let n="";for(const r of t)n+=`<tr>
|
|
222
222
|
<td><span class="route-pattern">${_(r.pattern)}</span></td>
|
|
223
223
|
<td>${r.hasGuard?"Yes":"—"}</td>
|
|
224
224
|
</tr>`;e+=`<table>
|
|
@@ -243,7 +243,7 @@ tr:hover td { background: rgba(255,255,255,0.02); }
|
|
|
243
243
|
<thead><tr><th>Time</th><th>Method</th><th>URL</th><th>Status</th><th>Duration</th><th>Auth</th></tr></thead>
|
|
244
244
|
<tbody>${o}</tbody>
|
|
245
245
|
</table>`}function B(t){return t.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">")}class F extends HTMLElement{constructor(){super(),this._visible=!0,this._activeTab="signals",this._refreshTimer=null,this._shadow=this.attachShadow({mode:"open"})}connectedCallback(){this._render(),this._startAutoRefresh()}disconnectedCallback(){this._stopAutoRefresh()}toggle(){this._visible=!this._visible,this._render()}show(){this._visible=!0,this._render()}hide(){this._visible=!1,this._render()}_startAutoRefresh(){this._refreshTimer=window.setInterval(()=>{this._visible&&this._renderBody()},1e3)}_stopAutoRefresh(){this._refreshTimer!==null&&(clearInterval(this._refreshTimer),this._refreshTimer=null)}_switchTab(o){this._activeTab=o,this._render()}_getTabContent(){switch(this._activeTab){case"signals":return L();case"components":return z();case"routes":return P();case"api":return G()}}_renderBody(){const o=this._shadow.querySelector(".t4-body");o&&(o.innerHTML=this._getTabContent()),this._updateTabCounts()}_updateTabCounts(){const o={signals:b.count,components:h.count,routes:p.count,api:g.count};for(const[e,n]of Object.entries(o)){const r=this._shadow.querySelector(`[data-tab-count="${e}"]`);r&&(r.textContent=n>0?`(${n})`:"")}}_render(){var n,r;const o=[{id:"signals",label:"Signals"},{id:"components",label:"Components"},{id:"routes",label:"Routes"},{id:"api",label:"API"}];if(!this._visible){this._shadow.innerHTML=`
|
|
246
|
-
<style>${
|
|
246
|
+
<style>${v}</style>
|
|
247
247
|
<div class="t4-mini" id="t4-mini">
|
|
248
248
|
<span class="t4-mini-dot"></span>
|
|
249
249
|
Debug
|
|
@@ -251,7 +251,7 @@ tr:hover td { background: rgba(255,255,255,0.02); }
|
|
|
251
251
|
`,(n=this._shadow.getElementById("t4-mini"))==null||n.addEventListener("click",()=>this.show());return}const e=o.map(s=>`<button class="t4-tab${this._activeTab===s.id?" active":""}" data-tab="${s.id}">
|
|
252
252
|
${s.label}<span class="t4-tab-count" data-tab-count="${s.id}"></span>
|
|
253
253
|
</button>`).join("");this._shadow.innerHTML=`
|
|
254
|
-
<style>${
|
|
254
|
+
<style>${v}</style>
|
|
255
255
|
<div class="t4-debug">
|
|
256
256
|
<div class="t4-header">
|
|
257
257
|
<div>
|
package/dist/debug.es.js
CHANGED
|
@@ -2,7 +2,7 @@ import { d as C } from "./signal.es.js";
|
|
|
2
2
|
import { _ as S } from "./component.es.js";
|
|
3
3
|
import { _ as R, a as A } from "./index.es.js";
|
|
4
4
|
import { api as x } from "./api.es.js";
|
|
5
|
-
const d = [],
|
|
5
|
+
const d = [], h = {
|
|
6
6
|
add(t, o) {
|
|
7
7
|
const e = t._debugInfo;
|
|
8
8
|
d.push({
|
|
@@ -43,7 +43,7 @@ const d = [], b = {
|
|
|
43
43
|
get count() {
|
|
44
44
|
return d.length;
|
|
45
45
|
}
|
|
46
|
-
}, a = [],
|
|
46
|
+
}, a = [], b = {
|
|
47
47
|
onMount(t) {
|
|
48
48
|
a.push({
|
|
49
49
|
ref: new WeakRef(t),
|
|
@@ -351,9 +351,9 @@ function L(t) {
|
|
|
351
351
|
return { text: String(t), cls: "val-object" };
|
|
352
352
|
}
|
|
353
353
|
function j() {
|
|
354
|
-
const t =
|
|
354
|
+
const t = h.getAll();
|
|
355
355
|
if (t.length === 0)
|
|
356
|
-
return '<div class="t4-empty">No signals tracked yet.<br>
|
|
356
|
+
return '<div class="t4-empty">No signals tracked yet.<br>Create a signal — it appears here, even ones made before the overlay loaded.</div>';
|
|
357
357
|
let o = "";
|
|
358
358
|
for (let e = 0; e < t.length; e++) {
|
|
359
359
|
const n = t[e], { text: r, cls: s } = L(n.value);
|
|
@@ -373,15 +373,15 @@ function H(t) {
|
|
|
373
373
|
return t.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
374
374
|
}
|
|
375
375
|
function z() {
|
|
376
|
-
const t =
|
|
376
|
+
const t = b.getAll();
|
|
377
377
|
if (t.length === 0)
|
|
378
378
|
return '<div class="t4-empty">No Tina4Elements mounted.<br>Custom elements extending Tina4Element will appear here.</div>';
|
|
379
379
|
let o = "";
|
|
380
380
|
for (const e of t) {
|
|
381
381
|
const n = Object.keys(e.props).length > 0 ? Object.entries(e.props).map(([r, s]) => `${r}=${JSON.stringify(s) ?? "null"}`).join(", ") : "—";
|
|
382
382
|
o += `<tr>
|
|
383
|
-
<td><${
|
|
384
|
-
<td>${
|
|
383
|
+
<td><${v(e.tagName)}></td>
|
|
384
|
+
<td>${v(n.length > 60 ? n.slice(0, 60) + "..." : n)}</td>
|
|
385
385
|
</tr>`;
|
|
386
386
|
}
|
|
387
387
|
return `<table>
|
|
@@ -389,7 +389,7 @@ function z() {
|
|
|
389
389
|
<tbody>${o}</tbody>
|
|
390
390
|
</table>`;
|
|
391
391
|
}
|
|
392
|
-
function
|
|
392
|
+
function v(t) {
|
|
393
393
|
return t.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
394
394
|
}
|
|
395
395
|
function I(t) {
|
|
@@ -406,7 +406,7 @@ function P() {
|
|
|
406
406
|
let n = "";
|
|
407
407
|
for (const r of t)
|
|
408
408
|
n += `<tr>
|
|
409
|
-
<td><span class="route-pattern">${
|
|
409
|
+
<td><span class="route-pattern">${w(r.pattern)}</span></td>
|
|
410
410
|
<td>${r.hasGuard ? "Yes" : "—"}</td>
|
|
411
411
|
</tr>`;
|
|
412
412
|
e += `<table>
|
|
@@ -421,7 +421,7 @@ function P() {
|
|
|
421
421
|
const { text: s, cls: i } = I(r.durationMs), $ = Object.keys(r.params).length > 0 ? Object.entries(r.params).map(([k, T]) => `<span class="route-param">${k}=${T}</span>`).join(" ") : "";
|
|
422
422
|
n += `<tr>
|
|
423
423
|
<td>${N(r.timestamp)}</td>
|
|
424
|
-
<td>${
|
|
424
|
+
<td>${w(r.path)}</td>
|
|
425
425
|
<td>${$ || "—"}</td>
|
|
426
426
|
<td><span class="${i}">${s}</span></td>
|
|
427
427
|
</tr>`;
|
|
@@ -433,7 +433,7 @@ function P() {
|
|
|
433
433
|
} else t.length === 0 && (e = '<div class="t4-empty">No routes registered yet.</div>');
|
|
434
434
|
return e;
|
|
435
435
|
}
|
|
436
|
-
function
|
|
436
|
+
function w(t) {
|
|
437
437
|
return t.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
438
438
|
}
|
|
439
439
|
function O(t) {
|
|
@@ -519,8 +519,8 @@ class F extends HTMLElement {
|
|
|
519
519
|
}
|
|
520
520
|
_updateTabCounts() {
|
|
521
521
|
const o = {
|
|
522
|
-
signals:
|
|
523
|
-
components:
|
|
522
|
+
signals: h.count,
|
|
523
|
+
components: b.count,
|
|
524
524
|
routes: p.count,
|
|
525
525
|
api: g.count
|
|
526
526
|
};
|
|
@@ -578,14 +578,14 @@ class F extends HTMLElement {
|
|
|
578
578
|
function V() {
|
|
579
579
|
typeof customElements < "u" && !customElements.get("tina4-debug") && customElements.define("tina4-debug", F);
|
|
580
580
|
}
|
|
581
|
-
let u = null,
|
|
581
|
+
let u = null, _ = !1;
|
|
582
582
|
function W() {
|
|
583
|
-
|
|
584
|
-
(t, o) =>
|
|
585
|
-
(t) =>
|
|
583
|
+
_ || (_ = !0, C(
|
|
584
|
+
(t, o) => h.add(t, o),
|
|
585
|
+
(t) => h.onUpdate(t)
|
|
586
586
|
), S(
|
|
587
|
-
(t) =>
|
|
588
|
-
(t) =>
|
|
587
|
+
(t) => b.onMount(t),
|
|
588
|
+
(t) => b.onUnmount(t)
|
|
589
589
|
), p.setGetRoutes(R), A.on("change", (t) => {
|
|
590
590
|
p.onNavigate(t);
|
|
591
591
|
}), x.intercept("request", (t) => (g.onRequest(t), t)), x.intercept("response", (t) => (g.onResponse(t), t)), typeof document < "u" && (V(), u = document.createElement("tina4-debug"), document.body.appendChild(u), document.addEventListener("keydown", (t) => {
|
package/dist/index.d.ts
CHANGED
|
@@ -18,4 +18,6 @@ export { ws } from './ws/ws';
|
|
|
18
18
|
export type { SocketStatus, SocketOptions, ManagedSocket } from './ws/ws';
|
|
19
19
|
export { sse } from './sse/sse';
|
|
20
20
|
export type { StreamStatus, StreamOptions, ManagedStream } from './sse/sse';
|
|
21
|
+
export { persist, clearPersistedKeys } from './storage/persist';
|
|
22
|
+
export type { PersistOptions, PersistSerializer, PersistedSignal } from './storage/persist';
|
|
21
23
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +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"}
|
|
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;AAG5E,OAAO,EAAE,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAChE,YAAY,EAAE,cAAc,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC"}
|
package/dist/signal.cjs.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";let
|
|
1
|
+
"use strict";let f=null,s=null,i=null;function S(n){i=n}function w(){return i}let _=null,b=null;const a=[],E=512;function y(n,o){if(_=n,b=o,n)for(const e of a){const t=e.ref.deref();t&&(t._debugInfo||(t._debugInfo={label:e.label,createdAt:e.createdAt,updateCount:0,subs:e.subs}),n(t,e.label))}a.length=0}let l=0;const g=new Set;function p(n,o){let e=n;const t=new Set,r={_t4:!0,get value(){if(f&&(t.add(f),s)){const u=f;s.push(()=>t.delete(u))}return e},set value(u){if(Object.is(u,e))return;const d=e;if(e=u,r._debugInfo&&r._debugInfo.updateCount++,b&&b(r,d,u),l>0)for(const c of t)g.add(c);else{let c;for(const v of[...t])try{v()}catch(C){c===void 0&&(c=C)}if(c!==void 0)throw c}},_subscribe(u){return t.add(u),()=>{t.delete(u)}},peek(){return e}};return _?(r._debugInfo={label:o,createdAt:Date.now(),updateCount:0,subs:t},_(r,o)):a.length<E&&a.push({ref:new WeakRef(r),label:o,createdAt:Date.now(),subs:t}),r}function I(n){const o=p(void 0);return h(()=>{o.value=n()}),{_t4:!0,get value(){return o.value},set value(e){throw new Error("[tina4] computed signals are read-only")},_subscribe(e){return o._subscribe(e)},peek(){return o.peek()}}}function h(n){let o=!1,e=[];const t=()=>{if(o)return;for(const c of e)c();e=[];const u=f,d=s;f=t,s=e;try{n()}finally{f=u,s=d}};t();const r=()=>{o=!0;for(const u of e)u();e=[]};return i&&i.push(r),r}function k(n){l++;try{n()}finally{if(l--,l===0){const o=[...g];g.clear();let e;for(const t of o)try{t()}catch(r){e===void 0&&(e=r)}if(e!==void 0)throw e}}}function A(n){return n!==null&&typeof n=="object"&&n._t4===!0}exports.__setDebugSignalHooks=y;exports._getEffectCollector=w;exports._setEffectCollector=S;exports.batch=k;exports.computed=I;exports.effect=h;exports.isSignal=A;exports.signal=p;
|
package/dist/signal.es.js
CHANGED
|
@@ -1,125 +1,141 @@
|
|
|
1
|
-
let
|
|
2
|
-
function
|
|
3
|
-
i =
|
|
1
|
+
let f = null, c = null, i = null;
|
|
2
|
+
function C(n) {
|
|
3
|
+
i = n;
|
|
4
4
|
}
|
|
5
|
-
function
|
|
5
|
+
function S() {
|
|
6
6
|
return i;
|
|
7
7
|
}
|
|
8
|
-
let
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
let b = null, _ = null;
|
|
9
|
+
const a = [], v = 512;
|
|
10
|
+
function E(n, o) {
|
|
11
|
+
if (b = n, _ = o, n)
|
|
12
|
+
for (const e of a) {
|
|
13
|
+
const t = e.ref.deref();
|
|
14
|
+
t && (t._debugInfo || (t._debugInfo = {
|
|
15
|
+
label: e.label,
|
|
16
|
+
createdAt: e.createdAt,
|
|
17
|
+
updateCount: 0,
|
|
18
|
+
subs: e.subs
|
|
19
|
+
}), n(t, e.label));
|
|
20
|
+
}
|
|
21
|
+
a.length = 0;
|
|
11
22
|
}
|
|
12
23
|
let l = 0;
|
|
13
24
|
const p = /* @__PURE__ */ new Set();
|
|
14
|
-
function
|
|
15
|
-
let e =
|
|
16
|
-
const
|
|
25
|
+
function w(n, o) {
|
|
26
|
+
let e = n;
|
|
27
|
+
const t = /* @__PURE__ */ new Set(), r = {
|
|
17
28
|
_t4: !0,
|
|
18
29
|
get value() {
|
|
19
|
-
if (
|
|
20
|
-
const
|
|
21
|
-
|
|
30
|
+
if (f && (t.add(f), c)) {
|
|
31
|
+
const u = f;
|
|
32
|
+
c.push(() => t.delete(u));
|
|
22
33
|
}
|
|
23
34
|
return e;
|
|
24
35
|
},
|
|
25
|
-
set value(
|
|
26
|
-
if (Object.is(
|
|
27
|
-
const
|
|
28
|
-
if (e =
|
|
29
|
-
for (const s of
|
|
36
|
+
set value(u) {
|
|
37
|
+
if (Object.is(u, e)) return;
|
|
38
|
+
const d = e;
|
|
39
|
+
if (e = u, r._debugInfo && r._debugInfo.updateCount++, _ && _(r, d, u), l > 0)
|
|
40
|
+
for (const s of t) p.add(s);
|
|
30
41
|
else {
|
|
31
42
|
let s;
|
|
32
|
-
for (const
|
|
43
|
+
for (const g of [...t])
|
|
33
44
|
try {
|
|
34
|
-
|
|
35
|
-
} catch (
|
|
36
|
-
s === void 0 && (s =
|
|
45
|
+
g();
|
|
46
|
+
} catch (h) {
|
|
47
|
+
s === void 0 && (s = h);
|
|
37
48
|
}
|
|
38
49
|
if (s !== void 0) throw s;
|
|
39
50
|
}
|
|
40
51
|
},
|
|
41
|
-
_subscribe(
|
|
42
|
-
return
|
|
43
|
-
|
|
52
|
+
_subscribe(u) {
|
|
53
|
+
return t.add(u), () => {
|
|
54
|
+
t.delete(u);
|
|
44
55
|
};
|
|
45
56
|
},
|
|
46
57
|
peek() {
|
|
47
58
|
return e;
|
|
48
59
|
}
|
|
49
60
|
};
|
|
50
|
-
return
|
|
61
|
+
return b ? (r._debugInfo = { label: o, createdAt: Date.now(), updateCount: 0, subs: t }, b(r, o)) : a.length < v && a.push({
|
|
62
|
+
ref: new WeakRef(r),
|
|
63
|
+
label: o,
|
|
64
|
+
createdAt: Date.now(),
|
|
65
|
+
subs: t
|
|
66
|
+
}), r;
|
|
51
67
|
}
|
|
52
|
-
function
|
|
53
|
-
const
|
|
54
|
-
return
|
|
55
|
-
|
|
68
|
+
function I(n) {
|
|
69
|
+
const o = w(void 0);
|
|
70
|
+
return y(() => {
|
|
71
|
+
o.value = n();
|
|
56
72
|
}), {
|
|
57
73
|
_t4: !0,
|
|
58
74
|
get value() {
|
|
59
|
-
return
|
|
75
|
+
return o.value;
|
|
60
76
|
},
|
|
61
77
|
set value(e) {
|
|
62
78
|
throw new Error("[tina4] computed signals are read-only");
|
|
63
79
|
},
|
|
64
80
|
_subscribe(e) {
|
|
65
|
-
return
|
|
81
|
+
return o._subscribe(e);
|
|
66
82
|
},
|
|
67
83
|
peek() {
|
|
68
|
-
return
|
|
84
|
+
return o.peek();
|
|
69
85
|
}
|
|
70
86
|
};
|
|
71
87
|
}
|
|
72
|
-
function
|
|
73
|
-
let
|
|
74
|
-
const
|
|
75
|
-
if (
|
|
88
|
+
function y(n) {
|
|
89
|
+
let o = !1, e = [];
|
|
90
|
+
const t = () => {
|
|
91
|
+
if (o) return;
|
|
76
92
|
for (const s of e) s();
|
|
77
93
|
e = [];
|
|
78
|
-
const
|
|
79
|
-
|
|
94
|
+
const u = f, d = c;
|
|
95
|
+
f = t, c = e;
|
|
80
96
|
try {
|
|
81
|
-
|
|
97
|
+
n();
|
|
82
98
|
} finally {
|
|
83
|
-
|
|
99
|
+
f = u, c = d;
|
|
84
100
|
}
|
|
85
101
|
};
|
|
86
|
-
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
for (const
|
|
102
|
+
t();
|
|
103
|
+
const r = () => {
|
|
104
|
+
o = !0;
|
|
105
|
+
for (const u of e) u();
|
|
90
106
|
e = [];
|
|
91
107
|
};
|
|
92
|
-
return i && i.push(
|
|
108
|
+
return i && i.push(r), r;
|
|
93
109
|
}
|
|
94
|
-
function
|
|
110
|
+
function A(n) {
|
|
95
111
|
l++;
|
|
96
112
|
try {
|
|
97
|
-
|
|
113
|
+
n();
|
|
98
114
|
} finally {
|
|
99
115
|
if (l--, l === 0) {
|
|
100
|
-
const
|
|
116
|
+
const o = [...p];
|
|
101
117
|
p.clear();
|
|
102
118
|
let e;
|
|
103
|
-
for (const
|
|
119
|
+
for (const t of o)
|
|
104
120
|
try {
|
|
105
|
-
|
|
106
|
-
} catch (
|
|
107
|
-
e === void 0 && (e =
|
|
121
|
+
t();
|
|
122
|
+
} catch (r) {
|
|
123
|
+
e === void 0 && (e = r);
|
|
108
124
|
}
|
|
109
125
|
if (e !== void 0) throw e;
|
|
110
126
|
}
|
|
111
127
|
}
|
|
112
128
|
}
|
|
113
|
-
function k(
|
|
114
|
-
return
|
|
129
|
+
function k(n) {
|
|
130
|
+
return n !== null && typeof n == "object" && n._t4 === !0;
|
|
115
131
|
}
|
|
116
132
|
export {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
133
|
+
S as _,
|
|
134
|
+
C as a,
|
|
135
|
+
A as b,
|
|
136
|
+
I as c,
|
|
137
|
+
E as d,
|
|
138
|
+
y as e,
|
|
123
139
|
k as i,
|
|
124
|
-
|
|
140
|
+
w as s
|
|
125
141
|
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tina4js/storage — Persistent signals.
|
|
3
|
+
*
|
|
4
|
+
* Read STORAGE.md before using this module. localStorage is XSS-readable;
|
|
5
|
+
* never put credentials, tokens, personal data, or secrets here.
|
|
6
|
+
*/
|
|
7
|
+
export { persist, clearPersistedKeys } from './persist';
|
|
8
|
+
export type { PersistOptions, PersistSerializer, PersistedSignal } from './persist';
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/storage/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AACxD,YAAY,EAAE,cAAc,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tina4-js/storage — Persistent signals.
|
|
3
|
+
*
|
|
4
|
+
* Wrap a signal with persist() to read its initial value from localStorage
|
|
5
|
+
* (or sessionStorage) and write every change back. The value survives a
|
|
6
|
+
* page refresh. Opt-in per signal. Zero dependencies.
|
|
7
|
+
*
|
|
8
|
+
* SAFE FOR: theme, language, sidebar state, last-used filters, onboarding
|
|
9
|
+
* flags, draft text the user expects back, guest cart contents.
|
|
10
|
+
*
|
|
11
|
+
* NEVER STORE: auth tokens, JWTs, session IDs, API keys, passwords, personal
|
|
12
|
+
* data, payment details, permission flags, secrets, OTP seeds,
|
|
13
|
+
* or any server-of-record state. localStorage is XSS-readable.
|
|
14
|
+
* See STORAGE.md for the full dangers list.
|
|
15
|
+
*/
|
|
16
|
+
import { type Signal } from '../core/signal';
|
|
17
|
+
export interface PersistSerializer<T> {
|
|
18
|
+
/** Parse a stored string back into a value. */
|
|
19
|
+
read(raw: string): T;
|
|
20
|
+
/** Serialize a value into a string that storage can hold. */
|
|
21
|
+
write(value: T): string;
|
|
22
|
+
}
|
|
23
|
+
export interface PersistOptions<T> {
|
|
24
|
+
/** Storage key. Required. */
|
|
25
|
+
key: string;
|
|
26
|
+
/** 'local' (default) survives a refresh; 'session' lives until the tab closes. */
|
|
27
|
+
storage?: 'local' | 'session';
|
|
28
|
+
/** JSON by default. Provide for Date, Map, Set, or any non-JSON shape. */
|
|
29
|
+
serializer?: PersistSerializer<T>;
|
|
30
|
+
/** Stored-shape version. Defaults to 1. */
|
|
31
|
+
version?: number;
|
|
32
|
+
/** Convert an older stored value into the current shape. */
|
|
33
|
+
migrate?: (oldValue: unknown, oldVersion: number | undefined) => T;
|
|
34
|
+
/** Subscribe to the storage event so other tabs see writes. Opt-in. */
|
|
35
|
+
syncTabs?: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Silence the credential-shape warning. Use only when you are certain
|
|
38
|
+
* the key or value is a coincidence (e.g. tokenColor for a UI palette).
|
|
39
|
+
*/
|
|
40
|
+
silenceCredentialWarning?: boolean;
|
|
41
|
+
}
|
|
42
|
+
export interface PersistedSignal<T> extends Signal<T> {
|
|
43
|
+
/** Remove the key from storage. The signal keeps its current in-memory value. */
|
|
44
|
+
clear(): void;
|
|
45
|
+
/** Stop watching storage events and stop the write effect. */
|
|
46
|
+
dispose(): void;
|
|
47
|
+
}
|
|
48
|
+
/** @internal Reset the warning cache. Tests only. */
|
|
49
|
+
export declare function _resetWarnedKeys(): void;
|
|
50
|
+
/**
|
|
51
|
+
* Wrap a signal so its value is read from storage on creation and written
|
|
52
|
+
* back on every change. Survives a page refresh.
|
|
53
|
+
*
|
|
54
|
+
* @param source - The signal to persist. Its initial value is overwritten
|
|
55
|
+
* by whatever is in storage, if anything.
|
|
56
|
+
* @param options - Persistence options. `key` is required.
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* const theme = persist(signal('light'), { key: 'theme' });
|
|
60
|
+
* theme.value = 'dark'; // survives a refresh
|
|
61
|
+
*/
|
|
62
|
+
export declare function persist<T>(source: Signal<T>, options: PersistOptions<T>): PersistedSignal<T>;
|
|
63
|
+
/**
|
|
64
|
+
* Remove a list of persisted keys at once. Wire this to your logout handler
|
|
65
|
+
* so persisted state does not leak to the next user on the device.
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* function logout() {
|
|
69
|
+
* api.post('/auth/logout');
|
|
70
|
+
* clearPersistedKeys(['cart', 'lastFilter', 'draftReply']);
|
|
71
|
+
* window.location.reload();
|
|
72
|
+
* }
|
|
73
|
+
*/
|
|
74
|
+
export declare function clearPersistedKeys(keys: string[], kind?: 'local' | 'session'): void;
|
|
75
|
+
//# sourceMappingURL=persist.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"persist.d.ts","sourceRoot":"","sources":["../../src/storage/persist.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAU,KAAK,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAErD,MAAM,WAAW,iBAAiB,CAAC,CAAC;IAClC,+CAA+C;IAC/C,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,CAAC,CAAC;IACrB,6DAA6D;IAC7D,KAAK,CAAC,KAAK,EAAE,CAAC,GAAG,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,cAAc,CAAC,CAAC;IAC/B,6BAA6B;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,kFAAkF;IAClF,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9B,0EAA0E;IAC1E,UAAU,CAAC,EAAE,iBAAiB,CAAC,CAAC,CAAC,CAAC;IAClC,2CAA2C;IAC3C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,4DAA4D;IAC5D,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,GAAG,SAAS,KAAK,CAAC,CAAC;IACnE,uEAAuE;IACvE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;OAGG;IACH,wBAAwB,CAAC,EAAE,OAAO,CAAC;CACpC;AAED,MAAM,WAAW,eAAe,CAAC,CAAC,CAAE,SAAQ,MAAM,CAAC,CAAC,CAAC;IACnD,iFAAiF;IACjF,KAAK,IAAI,IAAI,CAAC;IACd,8DAA8D;IAC9D,OAAO,IAAI,IAAI,CAAC;CACjB;AAyDD,qDAAqD;AACrD,wBAAgB,gBAAgB,IAAI,IAAI,CAEvC;AAqBD;;;;;;;;;;;GAWG;AACH,wBAAgB,OAAO,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC,GAAG,eAAe,CAAC,CAAC,CAAC,CA+I5F;AAYD;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,IAAI,GAAE,OAAO,GAAG,SAAmB,GAAG,IAAI,CAW5F"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const O=require("./signal.cjs.js"),p={read:t=>JSON.parse(t),write:t=>JSON.stringify(t)},w=/(token|password|passwd|secret|api[_-]?key|apikey|auth(?!or)|credential|jwt|bearer|otp|seed|private[_-]?key|session[_-]?id)/i,T=/^[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{20,}$/,N=/^[A-Za-z0-9+/_=-]{40,}$/,h=new Set;function S(t,r){if(w.test(t))return`key name "${t}" looks like a credential`;if(typeof r=="string"){if(T.test(r))return"value looks like a JWT";if(r.length>=40&&N.test(r))return"value looks like a long base64 / token"}if(r&&typeof r=="object"&&!Array.isArray(r)){for(const e of Object.keys(r))if(w.test(e))return`object contains a credential-shape field "${e}"`}return null}function b(t,r){h.has(r)||(h.add(r),console.warn(`[tina4 persist] ${t} (key: ${JSON.stringify(r)}). localStorage is XSS-readable and never appropriate for credentials, tokens, passwords, personal data, or secrets. See STORAGE.md.`))}function E(t){if(typeof globalThis>"u")return null;try{const r=t==="local"?globalThis.localStorage:globalThis.sessionStorage;return!r||typeof r.getItem!="function"?null:r}catch{return null}}function J(t,r){var k;const{key:e,storage:y="local",serializer:a=p,version:l=1,migrate:u,syncTabs:$=!1,silenceCredentialWarning:v=!1}=r;if(!e||typeof e!="string")throw new Error("[tina4 persist] options.key is required and must be a string");const f=E(y);if(!f)return m(t,()=>{},()=>{});try{const s=f.getItem(e);if(s!==null){let i,o;try{const n=JSON.parse(s);n&&typeof n=="object"&&"value"in n?(i=n.v,o=n.value):o=n}catch{o=a===p?s:a.read(s)}if(i===l||i===void 0){const n=a===p?o:a.read(typeof o=="string"?o:JSON.stringify(o));t.value=n}else if(u)try{t.value=u(o,i)}catch(n){console.warn(`[tina4 persist] migrate() threw for key "${e}":`,n)}else console.warn(`[tina4 persist] stored version ${i} does not match current ${l} for key "${e}", and no migrate() was provided. Discarding the stored value.`)}}catch(s){console.warn(`[tina4 persist] failed to read key "${e}":`,s)}if(!v){const s=S(e,t.peek());s&&b(s,e)}const A=O.effect(()=>{const s=t.value;if(!v){const i=S(e,s);i&&b(i,e)}try{const o=JSON.stringify(a===p?{v:l,value:s}:{v:l,value:a.write(s)});f.setItem(e,o)}catch(i){console.warn(`[tina4 persist] failed to write key "${e}":`,i)}});let g=null;if($&&typeof globalThis<"u"&&"addEventListener"in globalThis){const s=i=>{const o=i;if(o.storageArea===f&&o.key===e&&o.newValue!==null)try{const n=JSON.parse(o.newValue),c=n&&typeof n=="object"&&"v"in n?n.v:void 0,d=c!==void 0?n.value:n;c!==void 0&&c!==l&&u?t.value=u(d,c):(c===l||c===void 0)&&(t.value=a===p?d:a.read(typeof d=="string"?d:JSON.stringify(d)))}catch(n){console.warn(`[tina4 persist] failed to parse storage event for key "${e}":`,n)}};(k=globalThis.addEventListener)==null||k.call(globalThis,"storage",s),g=()=>{var i;(i=globalThis.removeEventListener)==null||i.call(globalThis,"storage",s)}}return m(t,()=>{try{f.removeItem(e)}catch(s){console.warn(`[tina4 persist] failed to clear key "${e}":`,s)}},()=>{A(),g&&g()})}function m(t,r,e){return Object.assign(t,{clear:r,dispose:e})}function _(t,r="local"){const e=E(r);if(e)for(const y of t)try{e.removeItem(y)}catch(a){console.warn(`[tina4 persist] failed to clear key "${y}":`,a)}}exports.clearPersistedKeys=_;exports.persist=J;
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { e as O } from "./signal.es.js";
|
|
2
|
+
const p = {
|
|
3
|
+
read: (t) => JSON.parse(t),
|
|
4
|
+
write: (t) => JSON.stringify(t)
|
|
5
|
+
}, w = /(token|password|passwd|secret|api[_-]?key|apikey|auth(?!or)|credential|jwt|bearer|otp|seed|private[_-]?key|session[_-]?id)/i, T = /^[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{20,}$/, N = /^[A-Za-z0-9+/_=-]{40,}$/, h = /* @__PURE__ */ new Set();
|
|
6
|
+
function S(t, r) {
|
|
7
|
+
if (w.test(t))
|
|
8
|
+
return `key name "${t}" looks like a credential`;
|
|
9
|
+
if (typeof r == "string") {
|
|
10
|
+
if (T.test(r)) return "value looks like a JWT";
|
|
11
|
+
if (r.length >= 40 && N.test(r))
|
|
12
|
+
return "value looks like a long base64 / token";
|
|
13
|
+
}
|
|
14
|
+
if (r && typeof r == "object" && !Array.isArray(r)) {
|
|
15
|
+
for (const e of Object.keys(r))
|
|
16
|
+
if (w.test(e))
|
|
17
|
+
return `object contains a credential-shape field "${e}"`;
|
|
18
|
+
}
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
function b(t, r) {
|
|
22
|
+
h.has(r) || (h.add(r), console.warn(
|
|
23
|
+
`[tina4 persist] ${t} (key: ${JSON.stringify(r)}). localStorage is XSS-readable and never appropriate for credentials, tokens, passwords, personal data, or secrets. See STORAGE.md.`
|
|
24
|
+
));
|
|
25
|
+
}
|
|
26
|
+
function E(t) {
|
|
27
|
+
if (typeof globalThis > "u") return null;
|
|
28
|
+
try {
|
|
29
|
+
const r = t === "local" ? globalThis.localStorage : globalThis.sessionStorage;
|
|
30
|
+
return !r || typeof r.getItem != "function" ? null : r;
|
|
31
|
+
} catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function _(t, r) {
|
|
36
|
+
var k;
|
|
37
|
+
const {
|
|
38
|
+
key: e,
|
|
39
|
+
storage: y = "local",
|
|
40
|
+
serializer: a = p,
|
|
41
|
+
version: l = 1,
|
|
42
|
+
migrate: g,
|
|
43
|
+
syncTabs: $ = !1,
|
|
44
|
+
silenceCredentialWarning: v = !1
|
|
45
|
+
} = r;
|
|
46
|
+
if (!e || typeof e != "string")
|
|
47
|
+
throw new Error("[tina4 persist] options.key is required and must be a string");
|
|
48
|
+
const f = E(y);
|
|
49
|
+
if (!f)
|
|
50
|
+
return m(t, () => {
|
|
51
|
+
}, () => {
|
|
52
|
+
});
|
|
53
|
+
try {
|
|
54
|
+
const s = f.getItem(e);
|
|
55
|
+
if (s !== null) {
|
|
56
|
+
let o, i;
|
|
57
|
+
try {
|
|
58
|
+
const n = JSON.parse(s);
|
|
59
|
+
n && typeof n == "object" && "value" in n ? (o = n.v, i = n.value) : i = n;
|
|
60
|
+
} catch {
|
|
61
|
+
i = a === p ? s : a.read(s);
|
|
62
|
+
}
|
|
63
|
+
if (o === l || o === void 0) {
|
|
64
|
+
const n = a === p ? i : a.read(typeof i == "string" ? i : JSON.stringify(i));
|
|
65
|
+
t.value = n;
|
|
66
|
+
} else if (g)
|
|
67
|
+
try {
|
|
68
|
+
t.value = g(i, o);
|
|
69
|
+
} catch (n) {
|
|
70
|
+
console.warn(`[tina4 persist] migrate() threw for key "${e}":`, n);
|
|
71
|
+
}
|
|
72
|
+
else
|
|
73
|
+
console.warn(
|
|
74
|
+
`[tina4 persist] stored version ${o} does not match current ${l} for key "${e}", and no migrate() was provided. Discarding the stored value.`
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
} catch (s) {
|
|
78
|
+
console.warn(`[tina4 persist] failed to read key "${e}":`, s);
|
|
79
|
+
}
|
|
80
|
+
if (!v) {
|
|
81
|
+
const s = S(e, t.peek());
|
|
82
|
+
s && b(s, e);
|
|
83
|
+
}
|
|
84
|
+
const A = O(() => {
|
|
85
|
+
const s = t.value;
|
|
86
|
+
if (!v) {
|
|
87
|
+
const o = S(e, s);
|
|
88
|
+
o && b(o, e);
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
const i = JSON.stringify(a === p ? { v: l, value: s } : { v: l, value: a.write(s) });
|
|
92
|
+
f.setItem(e, i);
|
|
93
|
+
} catch (o) {
|
|
94
|
+
console.warn(`[tina4 persist] failed to write key "${e}":`, o);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
let u = null;
|
|
98
|
+
if ($ && typeof globalThis < "u" && "addEventListener" in globalThis) {
|
|
99
|
+
const s = (o) => {
|
|
100
|
+
const i = o;
|
|
101
|
+
if (i.storageArea === f && i.key === e && i.newValue !== null)
|
|
102
|
+
try {
|
|
103
|
+
const n = JSON.parse(i.newValue), c = n && typeof n == "object" && "v" in n ? n.v : void 0, d = c !== void 0 ? n.value : n;
|
|
104
|
+
c !== void 0 && c !== l && g ? t.value = g(d, c) : (c === l || c === void 0) && (t.value = a === p ? d : a.read(typeof d == "string" ? d : JSON.stringify(d)));
|
|
105
|
+
} catch (n) {
|
|
106
|
+
console.warn(`[tina4 persist] failed to parse storage event for key "${e}":`, n);
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
(k = globalThis.addEventListener) == null || k.call(globalThis, "storage", s), u = () => {
|
|
110
|
+
var o;
|
|
111
|
+
(o = globalThis.removeEventListener) == null || o.call(globalThis, "storage", s);
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
return m(
|
|
115
|
+
t,
|
|
116
|
+
() => {
|
|
117
|
+
try {
|
|
118
|
+
f.removeItem(e);
|
|
119
|
+
} catch (s) {
|
|
120
|
+
console.warn(`[tina4 persist] failed to clear key "${e}":`, s);
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
() => {
|
|
124
|
+
A(), u && u();
|
|
125
|
+
}
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
function m(t, r, e) {
|
|
129
|
+
return Object.assign(t, { clear: r, dispose: e });
|
|
130
|
+
}
|
|
131
|
+
function L(t, r = "local") {
|
|
132
|
+
const e = E(r);
|
|
133
|
+
if (e)
|
|
134
|
+
for (const y of t)
|
|
135
|
+
try {
|
|
136
|
+
e.removeItem(y);
|
|
137
|
+
} catch (a) {
|
|
138
|
+
console.warn(`[tina4 persist] failed to clear key "${y}":`, a);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
export {
|
|
142
|
+
L as clearPersistedKeys,
|
|
143
|
+
_ as persist
|
|
144
|
+
};
|
package/dist/tina4.cjs.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./signal.cjs.js"),
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./signal.cjs.js"),s=require("./core.cjs.js"),i=require("./component.cjs.js"),r=require("./index.cjs.js"),a=require("./api.cjs.js"),n=require("./pwa.cjs.js"),o=require("./ws.cjs.js"),c=require("./sse.cjs.js"),t=require("./storage.cjs.js");exports.batch=e.batch;exports.computed=e.computed;exports.effect=e.effect;exports.isSignal=e.isSignal;exports.signal=e.signal;exports.html=s.html;exports.Tina4Element=i.Tina4Element;exports.navigate=r.navigate;exports.route=r.route;exports.router=r.router;exports.api=a.api;exports.pwa=n.pwa;exports.ws=o.ws;exports.sse=c.sse;exports.clearPersistedKeys=t.clearPersistedKeys;exports.persist=t.persist;
|
package/dist/tina4.es.js
CHANGED
|
@@ -1,24 +1,27 @@
|
|
|
1
|
-
import { b as
|
|
1
|
+
import { b as o, c as s, e as a, i as t, s as p } from "./signal.es.js";
|
|
2
2
|
import { html as f } from "./core.es.js";
|
|
3
|
-
import { T as
|
|
3
|
+
import { T as x } 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
|
-
import { pwa as
|
|
7
|
-
import { ws as
|
|
8
|
-
import { sse as
|
|
6
|
+
import { pwa as h } from "./pwa.es.js";
|
|
7
|
+
import { ws as T } from "./ws.es.js";
|
|
8
|
+
import { sse as y } from "./sse.es.js";
|
|
9
|
+
import { clearPersistedKeys as K, persist as P } from "./storage.es.js";
|
|
9
10
|
export {
|
|
10
|
-
|
|
11
|
+
x as Tina4Element,
|
|
11
12
|
b as api,
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
o as batch,
|
|
14
|
+
K as clearPersistedKeys,
|
|
15
|
+
s as computed,
|
|
16
|
+
a as effect,
|
|
15
17
|
f as html,
|
|
16
|
-
|
|
18
|
+
t as isSignal,
|
|
17
19
|
c as navigate,
|
|
18
|
-
|
|
20
|
+
P as persist,
|
|
21
|
+
h as pwa,
|
|
19
22
|
l as route,
|
|
20
23
|
g as router,
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
+
p as signal,
|
|
25
|
+
y as sse,
|
|
26
|
+
T as ws
|
|
24
27
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tina4js",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.7",
|
|
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",
|
|
@@ -39,6 +39,10 @@
|
|
|
39
39
|
"./sse": {
|
|
40
40
|
"import": "./dist/sse.es.js",
|
|
41
41
|
"types": "./dist/sse/index.d.ts"
|
|
42
|
+
},
|
|
43
|
+
"./storage": {
|
|
44
|
+
"import": "./dist/storage.es.js",
|
|
45
|
+
"types": "./dist/storage/index.d.ts"
|
|
42
46
|
}
|
|
43
47
|
},
|
|
44
48
|
"sideEffects": false,
|
package/readme.md
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
<p align="center">
|
|
13
13
|
<a href="https://www.npmjs.com/package/tina4js"><img src="https://img.shields.io/npm/v/tina4js?color=7b1fa2&label=npm" alt="npm"></a>
|
|
14
|
-
<img src="https://img.shields.io/badge/tests-
|
|
14
|
+
<img src="https://img.shields.io/badge/tests-303%20passing-brightgreen" alt="Tests">
|
|
15
15
|
<img src="https://img.shields.io/badge/core-1.5KB%20gzipped-blue" alt="Core size">
|
|
16
16
|
<img src="https://img.shields.io/badge/dependencies-0-brightgreen" alt="Zero Deps">
|
|
17
17
|
<a href="https://tina4.com/js"><img src="https://img.shields.io/badge/docs-tina4.com%2Fjs-7b1fa2" alt="Docs"></a>
|
|
@@ -58,9 +58,11 @@ Every module is built from scratch -- no node_modules bloat, no third-party runt
|
|
|
58
58
|
| **API** | 1.49 KB | Fetch client with auth (Bearer + formToken + FreshToken rotation), interceptors, per-request headers/params |
|
|
59
59
|
| **WebSocket** | 0.91 KB | Signal-driven status, auto-reconnect with exponential backoff, pipe() to signal, JSON auto-parse |
|
|
60
60
|
| **PWA** | 1.16 KB | Service worker + manifest generation, cache strategies (network-first, cache-first, stale-while-revalidate) |
|
|
61
|
+
| **SSE** | 1.33 KB | Server-Sent Events and NDJSON streaming, dual-mode (EventSource + fetch), reactive status signal |
|
|
62
|
+
| **Storage** | 1.67 KB | `persist()` wrapper for signals — survives a page refresh, opt-in per signal, SSR-safe, with loud warnings on credential-shaped keys |
|
|
61
63
|
| **Debug** | 5.11 KB | Dev overlay (Ctrl+Shift+D) -- signals, components, routes, API panels |
|
|
62
64
|
|
|
63
|
-
**
|
|
65
|
+
**303 tests across 14 test files. Zero dependencies. Under 3KB for the full core.**
|
|
64
66
|
|
|
65
67
|
For full documentation visit **[tina4.com/javascript](https://tina4.com/js)**.
|
|
66
68
|
|