tina4js 1.2.4 → 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/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- 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/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"}
|
|
@@ -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
|
|