@zerolith/state 1.6.0 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/production/stored.js +1 -1
- package/dist/production/stored.js.map +1 -1
- package/dist/stored.d.ts.map +1 -1
- package/dist/stored.js +33 -13
- package/dist/stored.js.map +1 -1
- package/package.json +3 -3
- package/src/stored.ts +39 -14
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{batch as e,diagnostic as t,effect as n,hasOwner as r,onDispose as i,report as a,signal as o}from"zerolith";const s=new Set,c=new Map,l=new WeakMap;export function onOutsideWrite(e,t){let n=l.get(e);return n===void 0&&(n=new Set,l.set(e,n)),n.add(t),()=>{n.delete(t)}}function u(e,n){try{if(typeof window>`u`)throw TypeError(`no window`);let t=e===`session`?window.sessionStorage:window.localStorage;return t.getItem(``),t}catch{s.has(e)||(s.add(e),a(t(`state_storage_unavailable`,`warning`,{storage:e,key:n})));return}}function d(e,n,r,i){if(e===null)return r;let o,s;try{o=JSON.parse(e),s=i===void 0||i(o)}catch{s=!1}return s?o:(a(t(`state_storage_unreadable`,`warning`,{key:n})),r)}function f(e){try{return JSON.stringify(e)}catch{return}}export function stored(s,p,m={}){let h=m.storage??`local`,g=u(h,s),_=o(g===void 0?p:d(g.getItem(s),s,p,m.validate),m.equals===void 0?void 0:{equals:m.equals});if(g===void 0)return _;let v=`${h}:${s}`,y=(c.get(v)??0)+1;c.set(v,y),y>1&&a(t(`state_key_duplicate`,`warning`,{storage:h,key:s}));let b=!0,x;n(()=>{let e=_.get();if(b){b=!1;return}if(x!==void 0&&Object.is(e,x.value)){x=void 0;return}let n=f(e);if(n===void 0&&e!==void 0)a(t(`state_value_not_json`,`warning`,{key:s}));else try{n===void 0?g.removeItem(s):g.setItem(s,n)}catch{a(t(`state_storage_unavailable`,`warning`,{storage:h,key:s}))}});let S=t=>{if(t.storageArea!==g||t.key!==null&&t.key!==s)return;let n=d(t.newValue,s,p,m.validate);x={value:n};try{e(()=>{try{_.set(n)}finally{for(let e of l.get(_)??[])e()}})}finally{x=void 0}};return window.addEventListener(`storage`,S),r()&&i(()=>{window.removeEventListener(`storage`,S);let e=(c.get(v)??1)-1;e>0?c.set(v,e):c.delete(v)}),_}
|
|
2
2
|
//# sourceMappingURL=stored.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stored.js","sources":["../../src/stored.ts"],"sourcesContent":["/**\n * A signal kept in the browser's storage under a key, as JSON, and in step\n * across tabs through the `storage` event.\n */\nimport { type Signal, diagnostic, effect, hasOwner, onDispose, report, signal } from \"zerolith\";\n\n/** Options for `stored`. */\nexport interface StoredOptions<T>\n{\n /** Which storage keeps the value: `localStorage`, shared across tabs and kept across sessions, or `sessionStorage`, this tab's alone. Defaults to local. */\n storage?: \"local\" | \"session\";\n /** When two values count as the same, so a set of an equal value changes nothing; the signal's own option. */\n equals?: (previous: T, next: T) => boolean;\n /**\n * Whether a kept value, once parsed, is one the signal can hold. A value\n * it refuses, or throws on, is read as `initial` and reported as\n * `state_storage_unreadable`, on creation and when another tab writes the\n * key, so data of another shape, such as another app's under the same\n * key, never reaches the page.\n */\n validate?: (value: unknown) => boolean;\n}\n\n/** The storage kinds reported unavailable on this page, each once. */\nconst unavailable = new Set<string>();\n\n/** How many stored signals are live for each key of each storage, so a second one for a key is reported. */\nconst live = new Map<string, number>();\n\n/** What runs after a stored signal takes a value another tab wrote, keyed by the signal. */\nconst outside = new WeakMap<Signal<unknown>, Set<() => void>>();\n\n/**\n * Runs `listener` each time `source` takes a value another tab wrote, which\n * only a stored signal does, and returns the function that stops it. The\n * history uses it; the package does not export it.\n */\nexport function onOutsideWrite(source: Signal<unknown>, listener: () => void): () => void\n{\n let listeners = outside.get(source);\n if (listeners === undefined)\n {\n listeners = new Set();\n outside.set(source, listeners);\n }\n listeners.add(listener);\n return () =>\n {\n listeners.delete(listener);\n };\n}\n\n/** The storage of a kind, or undefined where the page has none or refuses it, which is reported once per kind with the first key asked for. */\nfunction storageOf(kind: \"local\" | \"session\", key: string): Storage | undefined\n{\n try\n {\n const store = kind === \"session\" ? globalThis.sessionStorage : globalThis.localStorage;\n store.getItem(\"\");\n return store;\n }\n catch\n {\n if (!unavailable.has(kind))\n {\n unavailable.add(kind);\n report(diagnostic(\"state_storage_unavailable\", \"warning\", { storage: kind, key }));\n }\n return undefined;\n }\n}\n\n/** The value kept as `raw` under `key`, or `initial` where nothing is kept, or where what is kept does not parse or `validate` refuses it, which is reported. */\nfunction parse<T>(raw: string | null, key: string, initial: T, validate: ((value: unknown) => boolean) | undefined): T\n{\n if (raw === null)\n {\n return initial;\n }\n let value: unknown;\n let accepted: boolean;\n try\n {\n value = JSON.parse(raw);\n accepted = validate === undefined || validate(value);\n }\n catch\n {\n accepted = false;\n }\n if (!accepted)\n {\n report(diagnostic(\"state_storage_unreadable\", \"warning\", { key }));\n return initial;\n }\n return value as T;\n}\n\n/** The JSON for `next`, or undefined for a value JSON cannot hold: a BigInt or a cycle, which throws, and a function or a symbol, which it leaves out. */\nfunction toJson(next: unknown): string | undefined\n{\n try\n {\n return JSON.stringify(next);\n }\n catch\n {\n return undefined;\n }\n}\n\n/**\n * A writable signal kept in storage under `key` as JSON: read back on\n * creation, written on every set, and set again when another tab writes\n * the key, so every tab shows the same value. A missing value takes\n * `initial` quietly; one that does not parse, or that the `validate`\n * option refuses, takes `initial` and reports `state_storage_unreadable`. Setting `undefined` removes the key, so it\n * reads back as `initial`; a value JSON cannot hold, such as a BigInt,\n * stays in memory and reports `state_value_not_json`. Where the page has\n * no storage, as on the server, the signal is a plain one and\n * `state_storage_unavailable` is reported once, as it is for each write\n * the storage refuses. Create each key once and share the signal: two\n * stored signals for one key in the same tab do not hear each other,\n * since the storage event tells other tabs alone, and the second reports\n * `state_key_duplicate`. The write and the listener for other tabs are\n * disposed with the enclosing owner when there is one, after which the\n * signal keeps its value in memory alone; created in plain code, they\n * live for the page.\n */\nexport function stored<T>(key: string, initial: T, options: StoredOptions<T> = {}): Signal<T>\n{\n const kind = options.storage ?? \"local\";\n const store = storageOf(kind, key);\n const value = signal<T>(store === undefined ? initial : parse(store.getItem(key), key, initial, options.validate), options.equals === undefined ? undefined : { equals: options.equals });\n if (store === undefined)\n {\n return value;\n }\n const id = `${kind}:${key}`;\n const count = (live.get(id) ?? 0) + 1;\n live.set(id, count);\n if (count > 1)\n {\n report(diagnostic(\"state_key_duplicate\", \"warning\", { storage: kind, key }));\n }\n let first = true;\n let echo = false;\n effect(() =>\n {\n const next = value.get();\n if (first || echo)\n {\n first = false;\n return;\n }\n const json = toJson(next);\n if (json === undefined && next !== undefined)\n {\n report(diagnostic(\"state_value_not_json\", \"warning\", { key }));\n return;\n }\n try\n {\n if (json === undefined)\n {\n store.removeItem(key);\n }\n else\n {\n store.setItem(key, json);\n }\n }\n catch\n {\n report(diagnostic(\"state_storage_unavailable\", \"warning\", { storage: kind, key }));\n }\n });\n const onStorage = (event: StorageEvent): void =>\n {\n if (event.storageArea !== store || (event.key !== null && event.key !== key))\n {\n return;\n }\n echo = true;\n try\n {\n value.set(parse(event.newValue, key, initial, options.validate));\n }\n finally\n {\n echo = false;\n }\n for (const listener of outside.get(value as Signal<unknown>) ?? [])\n {\n listener();\n }\n };\n addEventListener(\"storage\", onStorage);\n if (hasOwner())\n {\n onDispose(() =>\n {\n removeEventListener(\"storage\", onStorage);\n const left = (live.get(id) ?? 1) - 1;\n if (left > 0)\n {\n live.set(id, left);\n }\n else\n {\n live.delete(id);\n }\n });\n }\n return value;\n}\n"],"names":["unavailable","live","outside","listeners","source","listener","storageOf","store","kind","report","diagnostic","parse","raw","initial","value","accepted","validate","toJson","next","options","key","signal","id","count","first","echo","effect","json","onStorage","event","hasOwner","onDispose","left"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"stored.js","sources":["../../src/stored.ts"],"sourcesContent":["/**\n * A signal kept in the browser's storage under a key, as JSON, and in step\n * across tabs through the `storage` event.\n */\nimport { type Signal, batch, diagnostic, effect, hasOwner, onDispose, report, signal } from \"zerolith\";\n\n/** Options for `stored`. */\nexport interface StoredOptions<T>\n{\n /** Which storage keeps the value: `localStorage`, shared across tabs and kept across sessions, or `sessionStorage`, this tab's alone. Defaults to local. */\n storage?: \"local\" | \"session\";\n /** When two values count as the same, so a set of an equal value changes nothing; the signal's own option. */\n equals?: (previous: T, next: T) => boolean;\n /**\n * Whether a kept value, once parsed, is one the signal can hold. A value\n * it refuses, or throws on, is read as `initial` and reported as\n * `state_storage_unreadable`, on creation and when another tab writes the\n * key, so data of another shape, such as another app's under the same\n * key, never reaches the page.\n */\n validate?: (value: unknown) => boolean;\n}\n\n/** The storage kinds reported unavailable on this page, each once. */\nconst unavailable = new Set<string>();\n\n/** How many stored signals are live for each key of each storage, so a second one for a key is reported. */\nconst live = new Map<string, number>();\n\n/** What runs after a stored signal takes a value another tab wrote, keyed by the signal. */\nconst outside = new WeakMap<Signal<unknown>, Set<() => void>>();\n\n/**\n * Runs `listener` each time `source` takes a value another tab wrote, which\n * only a stored signal does, and returns the function that stops it. The\n * history uses it; the package does not export it.\n */\nexport function onOutsideWrite(source: Signal<unknown>, listener: () => void): () => void\n{\n let listeners = outside.get(source);\n if (listeners === undefined)\n {\n listeners = new Set();\n outside.set(source, listeners);\n }\n listeners.add(listener);\n return () =>\n {\n listeners.delete(listener);\n };\n}\n\n/**\n * The storage of a kind, or undefined where the page has none or refuses it,\n * which is reported once per kind with the first key asked for. A runtime\n * with no window has none, even with Web Storage globals, as Node has from\n * 25: there the storage is one store for the whole process, so a value one\n * request set would be read back by the next.\n */\nfunction storageOf(kind: \"local\" | \"session\", key: string): Storage | undefined\n{\n try\n {\n if (typeof window === \"undefined\")\n {\n throw new TypeError(\"no window\");\n }\n const store = kind === \"session\" ? window.sessionStorage : window.localStorage;\n store.getItem(\"\");\n return store;\n }\n catch\n {\n if (!unavailable.has(kind))\n {\n unavailable.add(kind);\n report(diagnostic(\"state_storage_unavailable\", \"warning\", { storage: kind, key }));\n }\n return undefined;\n }\n}\n\n/** The value kept as `raw` under `key`, or `initial` where nothing is kept, or where what is kept does not parse or `validate` refuses it, which is reported. */\nfunction parse<T>(raw: string | null, key: string, initial: T, validate: ((value: unknown) => boolean) | undefined): T\n{\n if (raw === null)\n {\n return initial;\n }\n let value: unknown;\n let accepted: boolean;\n try\n {\n value = JSON.parse(raw);\n accepted = validate === undefined || validate(value);\n }\n catch\n {\n accepted = false;\n }\n if (!accepted)\n {\n report(diagnostic(\"state_storage_unreadable\", \"warning\", { key }));\n return initial;\n }\n return value as T;\n}\n\n/** The JSON for `next`, or undefined for a value JSON cannot hold: a BigInt or a cycle, which throws, and a function or a symbol, which it leaves out. */\nfunction toJson(next: unknown): string | undefined\n{\n try\n {\n return JSON.stringify(next);\n }\n catch\n {\n return undefined;\n }\n}\n\n/**\n * A writable signal kept in storage under `key` as JSON: read back on\n * creation, written on every set, and set again when another tab writes\n * the key, so every tab shows the same value. A missing value takes\n * `initial` quietly; one that does not parse, or that the `validate`\n * option refuses, takes `initial` and reports `state_storage_unreadable`. Setting `undefined` removes the key, so it\n * reads back as `initial`; a value JSON cannot hold, such as a BigInt,\n * stays in memory and reports `state_value_not_json`. Where the page has\n * no storage, as on the server, the signal is a plain one and\n * `state_storage_unavailable` is reported once, as it is for each write\n * the storage refuses. Create each key once and share the signal: two\n * stored signals for one key in the same tab do not hear each other,\n * since the storage event tells other tabs alone, and the second reports\n * `state_key_duplicate`. The write and the listener for other tabs are\n * disposed with the enclosing owner when there is one, after which the\n * signal keeps its value in memory alone; created in plain code, they\n * live for the page.\n */\nexport function stored<T>(key: string, initial: T, options: StoredOptions<T> = {}): Signal<T>\n{\n const kind = options.storage ?? \"local\";\n const store = storageOf(kind, key);\n const value = signal<T>(store === undefined ? initial : parse(store.getItem(key), key, initial, options.validate), options.equals === undefined ? undefined : { equals: options.equals });\n if (store === undefined)\n {\n return value;\n }\n const id = `${kind}:${key}`;\n const count = (live.get(id) ?? 0) + 1;\n live.set(id, count);\n if (count > 1)\n {\n report(diagnostic(\"state_key_duplicate\", \"warning\", { storage: kind, key }));\n }\n let first = true;\n let incoming: { value: T } | undefined;\n effect(() =>\n {\n const next = value.get();\n if (first)\n {\n first = false;\n return;\n }\n if (incoming !== undefined && Object.is(next, incoming.value))\n {\n incoming = undefined;\n return;\n }\n const json = toJson(next);\n if (json === undefined && next !== undefined)\n {\n report(diagnostic(\"state_value_not_json\", \"warning\", { key }));\n return;\n }\n try\n {\n if (json === undefined)\n {\n store.removeItem(key);\n }\n else\n {\n store.setItem(key, json);\n }\n }\n catch\n {\n report(diagnostic(\"state_storage_unavailable\", \"warning\", { storage: kind, key }));\n }\n });\n const onStorage = (event: StorageEvent): void =>\n {\n if (event.storageArea !== store || (event.key !== null && event.key !== key))\n {\n return;\n }\n const next = parse(event.newValue, key, initial, options.validate);\n incoming = { value: next };\n try\n {\n batch(() =>\n {\n try\n {\n value.set(next);\n }\n finally\n {\n for (const listener of outside.get(value as Signal<unknown>) ?? [])\n {\n listener();\n }\n }\n });\n }\n finally\n {\n incoming = undefined;\n }\n };\n window.addEventListener(\"storage\", onStorage);\n if (hasOwner())\n {\n onDispose(() =>\n {\n window.removeEventListener(\"storage\", onStorage);\n const left = (live.get(id) ?? 1) - 1;\n if (left > 0)\n {\n live.set(id, left);\n }\n else\n {\n live.delete(id);\n }\n });\n }\n return value;\n}\n"],"names":["unavailable","live","outside","listeners","source","listener","storageOf","store","kind","report","diagnostic","parse","raw","initial","value","accepted","validate","toJson","next","options","key","signal","id","count","first","incoming","effect","json","onStorage","event","batch","hasOwner","onDispose","left"],"mappings":"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"}
|
package/dist/stored.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stored.d.ts","sourceRoot":"","sources":["../src/stored.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,KAAK,MAAM,
|
|
1
|
+
{"version":3,"file":"stored.d.ts","sourceRoot":"","sources":["../src/stored.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,KAAK,MAAM,EAAkE,MAAM,UAAU,CAAC;AAEvG,4BAA4B;AAC5B,MAAM,WAAW,aAAa,CAAC,CAAC;IAE5B,4JAA4J;IAC5J,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9B,8GAA8G;IAC9G,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC;IAC3C;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC;CAC1C;AAWD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,CAAC,OAAO,CAAC,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAaxF;AAuED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,MAAM,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,GAAE,aAAa,CAAC,CAAC,CAAM,GAAG,MAAM,CAAC,CAAC,CAAC,CAqG5F"}
|
package/dist/stored.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* A signal kept in the browser's storage under a key, as JSON, and in step
|
|
3
3
|
* across tabs through the `storage` event.
|
|
4
4
|
*/
|
|
5
|
-
import { diagnostic, effect, hasOwner, onDispose, report, signal } from "zerolith";
|
|
5
|
+
import { batch, diagnostic, effect, hasOwner, onDispose, report, signal } from "zerolith";
|
|
6
6
|
/** The storage kinds reported unavailable on this page, each once. */
|
|
7
7
|
const unavailable = new Set();
|
|
8
8
|
/** How many stored signals are live for each key of each storage, so a second one for a key is reported. */
|
|
@@ -25,10 +25,19 @@ export function onOutsideWrite(source, listener) {
|
|
|
25
25
|
listeners.delete(listener);
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
|
-
/**
|
|
28
|
+
/**
|
|
29
|
+
* The storage of a kind, or undefined where the page has none or refuses it,
|
|
30
|
+
* which is reported once per kind with the first key asked for. A runtime
|
|
31
|
+
* with no window has none, even with Web Storage globals, as Node has from
|
|
32
|
+
* 25: there the storage is one store for the whole process, so a value one
|
|
33
|
+
* request set would be read back by the next.
|
|
34
|
+
*/
|
|
29
35
|
function storageOf(kind, key) {
|
|
30
36
|
try {
|
|
31
|
-
|
|
37
|
+
if (typeof window === "undefined") {
|
|
38
|
+
throw new TypeError("no window");
|
|
39
|
+
}
|
|
40
|
+
const store = kind === "session" ? window.sessionStorage : window.localStorage;
|
|
32
41
|
store.getItem("");
|
|
33
42
|
return store;
|
|
34
43
|
}
|
|
@@ -101,13 +110,17 @@ export function stored(key, initial, options = {}) {
|
|
|
101
110
|
report(diagnostic("state_key_duplicate", "warning", { storage: kind, key }));
|
|
102
111
|
}
|
|
103
112
|
let first = true;
|
|
104
|
-
let
|
|
113
|
+
let incoming;
|
|
105
114
|
effect(() => {
|
|
106
115
|
const next = value.get();
|
|
107
|
-
if (first
|
|
116
|
+
if (first) {
|
|
108
117
|
first = false;
|
|
109
118
|
return;
|
|
110
119
|
}
|
|
120
|
+
if (incoming !== undefined && Object.is(next, incoming.value)) {
|
|
121
|
+
incoming = undefined;
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
111
124
|
const json = toJson(next);
|
|
112
125
|
if (json === undefined && next !== undefined) {
|
|
113
126
|
report(diagnostic("state_value_not_json", "warning", { key }));
|
|
@@ -129,21 +142,28 @@ export function stored(key, initial, options = {}) {
|
|
|
129
142
|
if (event.storageArea !== store || (event.key !== null && event.key !== key)) {
|
|
130
143
|
return;
|
|
131
144
|
}
|
|
132
|
-
|
|
145
|
+
const next = parse(event.newValue, key, initial, options.validate);
|
|
146
|
+
incoming = { value: next };
|
|
133
147
|
try {
|
|
134
|
-
|
|
148
|
+
batch(() => {
|
|
149
|
+
try {
|
|
150
|
+
value.set(next);
|
|
151
|
+
}
|
|
152
|
+
finally {
|
|
153
|
+
for (const listener of outside.get(value) ?? []) {
|
|
154
|
+
listener();
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
});
|
|
135
158
|
}
|
|
136
159
|
finally {
|
|
137
|
-
|
|
138
|
-
}
|
|
139
|
-
for (const listener of outside.get(value) ?? []) {
|
|
140
|
-
listener();
|
|
160
|
+
incoming = undefined;
|
|
141
161
|
}
|
|
142
162
|
};
|
|
143
|
-
addEventListener("storage", onStorage);
|
|
163
|
+
window.addEventListener("storage", onStorage);
|
|
144
164
|
if (hasOwner()) {
|
|
145
165
|
onDispose(() => {
|
|
146
|
-
removeEventListener("storage", onStorage);
|
|
166
|
+
window.removeEventListener("storage", onStorage);
|
|
147
167
|
const left = (live.get(id) ?? 1) - 1;
|
|
148
168
|
if (left > 0) {
|
|
149
169
|
live.set(id, left);
|
package/dist/stored.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stored.js","sourceRoot":"","sources":["../src/stored.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAe,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"stored.js","sourceRoot":"","sources":["../src/stored.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zerolith/state",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0",
|
|
4
4
|
"description": "Signals with a memory for zerolith: a signal kept in storage and across tabs, undo and redo, debounced and throttled signals, and a row selector",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "molexxxx",
|
|
@@ -44,9 +44,9 @@
|
|
|
44
44
|
"build": "tsc -p tsconfig.build.json && node ../../scripts/production.ts"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
47
|
-
"zerolith": "^1.
|
|
47
|
+
"zerolith": "^1.8.0"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
|
-
"zerolith": "^1.
|
|
50
|
+
"zerolith": "^1.8.0"
|
|
51
51
|
}
|
|
52
52
|
}
|
package/src/stored.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* A signal kept in the browser's storage under a key, as JSON, and in step
|
|
3
3
|
* across tabs through the `storage` event.
|
|
4
4
|
*/
|
|
5
|
-
import { type Signal, diagnostic, effect, hasOwner, onDispose, report, signal } from "zerolith";
|
|
5
|
+
import { type Signal, batch, diagnostic, effect, hasOwner, onDispose, report, signal } from "zerolith";
|
|
6
6
|
|
|
7
7
|
/** Options for `stored`. */
|
|
8
8
|
export interface StoredOptions<T>
|
|
@@ -50,12 +50,22 @@ export function onOutsideWrite(source: Signal<unknown>, listener: () => void): (
|
|
|
50
50
|
};
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
/**
|
|
53
|
+
/**
|
|
54
|
+
* The storage of a kind, or undefined where the page has none or refuses it,
|
|
55
|
+
* which is reported once per kind with the first key asked for. A runtime
|
|
56
|
+
* with no window has none, even with Web Storage globals, as Node has from
|
|
57
|
+
* 25: there the storage is one store for the whole process, so a value one
|
|
58
|
+
* request set would be read back by the next.
|
|
59
|
+
*/
|
|
54
60
|
function storageOf(kind: "local" | "session", key: string): Storage | undefined
|
|
55
61
|
{
|
|
56
62
|
try
|
|
57
63
|
{
|
|
58
|
-
|
|
64
|
+
if (typeof window === "undefined")
|
|
65
|
+
{
|
|
66
|
+
throw new TypeError("no window");
|
|
67
|
+
}
|
|
68
|
+
const store = kind === "session" ? window.sessionStorage : window.localStorage;
|
|
59
69
|
store.getItem("");
|
|
60
70
|
return store;
|
|
61
71
|
}
|
|
@@ -144,15 +154,20 @@ export function stored<T>(key: string, initial: T, options: StoredOptions<T> = {
|
|
|
144
154
|
report(diagnostic("state_key_duplicate", "warning", { storage: kind, key }));
|
|
145
155
|
}
|
|
146
156
|
let first = true;
|
|
147
|
-
let
|
|
157
|
+
let incoming: { value: T } | undefined;
|
|
148
158
|
effect(() =>
|
|
149
159
|
{
|
|
150
160
|
const next = value.get();
|
|
151
|
-
if (first
|
|
161
|
+
if (first)
|
|
152
162
|
{
|
|
153
163
|
first = false;
|
|
154
164
|
return;
|
|
155
165
|
}
|
|
166
|
+
if (incoming !== undefined && Object.is(next, incoming.value))
|
|
167
|
+
{
|
|
168
|
+
incoming = undefined;
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
156
171
|
const json = toJson(next);
|
|
157
172
|
if (json === undefined && next !== undefined)
|
|
158
173
|
{
|
|
@@ -181,26 +196,36 @@ export function stored<T>(key: string, initial: T, options: StoredOptions<T> = {
|
|
|
181
196
|
{
|
|
182
197
|
return;
|
|
183
198
|
}
|
|
184
|
-
|
|
199
|
+
const next = parse(event.newValue, key, initial, options.validate);
|
|
200
|
+
incoming = { value: next };
|
|
185
201
|
try
|
|
186
202
|
{
|
|
187
|
-
|
|
203
|
+
batch(() =>
|
|
204
|
+
{
|
|
205
|
+
try
|
|
206
|
+
{
|
|
207
|
+
value.set(next);
|
|
208
|
+
}
|
|
209
|
+
finally
|
|
210
|
+
{
|
|
211
|
+
for (const listener of outside.get(value as Signal<unknown>) ?? [])
|
|
212
|
+
{
|
|
213
|
+
listener();
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
});
|
|
188
217
|
}
|
|
189
218
|
finally
|
|
190
219
|
{
|
|
191
|
-
|
|
192
|
-
}
|
|
193
|
-
for (const listener of outside.get(value as Signal<unknown>) ?? [])
|
|
194
|
-
{
|
|
195
|
-
listener();
|
|
220
|
+
incoming = undefined;
|
|
196
221
|
}
|
|
197
222
|
};
|
|
198
|
-
addEventListener("storage", onStorage);
|
|
223
|
+
window.addEventListener("storage", onStorage);
|
|
199
224
|
if (hasOwner())
|
|
200
225
|
{
|
|
201
226
|
onDispose(() =>
|
|
202
227
|
{
|
|
203
|
-
removeEventListener("storage", onStorage);
|
|
228
|
+
window.removeEventListener("storage", onStorage);
|
|
204
229
|
const left = (live.get(id) ?? 1) - 1;
|
|
205
230
|
if (left > 0)
|
|
206
231
|
{
|