@scale-sense/storage-local 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,15 @@
1
+ import { ScaleSenseStorage, ScaleSensePersistedState } from '@scale-sense/react';
2
+
3
+ declare const DEFAULT_STORAGE_KEY: "scale-sense-storage";
4
+ declare const DEFAULT_STORAGE_VERSION: 10;
5
+ /** Synchronous read for store bootstrap (matches zustand persist hydration). */
6
+ declare function readLocalStoragePersistedState(options?: {
7
+ key?: string;
8
+ version?: number;
9
+ }): Partial<ScaleSensePersistedState> | null;
10
+ declare function createLocalStorageAdapter(options?: {
11
+ key?: string;
12
+ version?: number;
13
+ }): ScaleSenseStorage;
14
+
15
+ export { DEFAULT_STORAGE_KEY, DEFAULT_STORAGE_VERSION, createLocalStorageAdapter, readLocalStoragePersistedState };
package/dist/index.js ADDED
@@ -0,0 +1,109 @@
1
+ // src/createLocalStorageAdapter.ts
2
+ import {
3
+ CONVERSION_CARD_KINDS,
4
+ parseHistoryEntry,
5
+ parseHistorySortMode,
6
+ SOURCE_KINDS
7
+ } from "@scale-sense/core";
8
+ var DEFAULT_STORAGE_KEY = "scale-sense-storage";
9
+ var DEFAULT_STORAGE_VERSION = 10;
10
+ var DEFAULT_VISIBLE_SOURCE_KINDS = [
11
+ "westernNumeric",
12
+ "chineseWords"
13
+ ];
14
+ function defaultHidden() {
15
+ return Object.fromEntries(
16
+ SOURCE_KINDS.map((k) => [k, !DEFAULT_VISIBLE_SOURCE_KINDS.includes(k)])
17
+ );
18
+ }
19
+ function defaultConversionHidden() {
20
+ return Object.fromEntries(
21
+ CONVERSION_CARD_KINDS.map((k) => [k, true])
22
+ );
23
+ }
24
+ function migratePersistedState(persistedState) {
25
+ const state = persistedState;
26
+ const nextState = state.cardHidden ? state : {
27
+ ...state,
28
+ cardHidden: state.cardCollapsed ?? defaultHidden()
29
+ };
30
+ const migratedHistory = (nextState.history ?? []).map((h) => parseHistoryEntry(h)).filter((h) => h !== null);
31
+ const savedCustomUnits = Array.isArray(nextState.savedCustomUnits) ? nextState.savedCustomUnits.filter(
32
+ (u) => typeof u === "string"
33
+ ) : [];
34
+ return {
35
+ ...nextState,
36
+ conversionCardHidden: nextState.conversionCardHidden ?? defaultConversionHidden(),
37
+ history: migratedHistory,
38
+ historySortMode: parseHistorySortMode(nextState.historySortMode),
39
+ savedCustomUnits
40
+ };
41
+ }
42
+ function readEnvelope(raw) {
43
+ try {
44
+ return JSON.parse(raw);
45
+ } catch {
46
+ return null;
47
+ }
48
+ }
49
+ function loadPersistedState(key, version) {
50
+ if (typeof localStorage === "undefined") return null;
51
+ const raw = localStorage.getItem(key);
52
+ if (!raw) return null;
53
+ const envelope = readEnvelope(raw);
54
+ if (!envelope) return null;
55
+ const storedVersion = envelope.version ?? 0;
56
+ const innerState = envelope.state ?? envelope;
57
+ if (storedVersion !== version) {
58
+ return migratePersistedState(innerState);
59
+ }
60
+ return innerState;
61
+ }
62
+ function readLocalStoragePersistedState(options) {
63
+ const key = options?.key ?? DEFAULT_STORAGE_KEY;
64
+ const version = options?.version ?? DEFAULT_STORAGE_VERSION;
65
+ return loadPersistedState(key, version);
66
+ }
67
+ function createLocalStorageAdapter(options) {
68
+ const key = options?.key ?? DEFAULT_STORAGE_KEY;
69
+ const version = options?.version ?? DEFAULT_STORAGE_VERSION;
70
+ return {
71
+ async load() {
72
+ return loadPersistedState(key, version);
73
+ },
74
+ async save(state) {
75
+ if (typeof localStorage === "undefined") return;
76
+ const envelope = {
77
+ state: {
78
+ history: state.history,
79
+ historySortMode: state.historySortMode,
80
+ cardHidden: state.cardHidden,
81
+ conversionCardHidden: state.conversionCardHidden,
82
+ sourceKind: state.sourceKind,
83
+ rawInput: state.rawInput,
84
+ savedCustomUnits: state.savedCustomUnits
85
+ },
86
+ version
87
+ };
88
+ localStorage.setItem(key, JSON.stringify(envelope));
89
+ },
90
+ subscribe(listener) {
91
+ if (typeof window === "undefined") {
92
+ return () => {
93
+ };
94
+ }
95
+ const handler = (event) => {
96
+ if (event.key === key) listener();
97
+ };
98
+ window.addEventListener("storage", handler);
99
+ return () => window.removeEventListener("storage", handler);
100
+ }
101
+ };
102
+ }
103
+ export {
104
+ DEFAULT_STORAGE_KEY,
105
+ DEFAULT_STORAGE_VERSION,
106
+ createLocalStorageAdapter,
107
+ readLocalStoragePersistedState
108
+ };
109
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/createLocalStorageAdapter.ts"],"sourcesContent":["import type { ConversionCardKind, SourceKind } from '@scale-sense/core'\nimport {\n CONVERSION_CARD_KINDS,\n type HistoryEntry,\n parseHistoryEntry,\n parseHistorySortMode,\n SOURCE_KINDS,\n} from '@scale-sense/core'\nimport type {\n ScaleSensePersistedState,\n ScaleSenseStorage,\n} from '@scale-sense/react'\n\nexport const DEFAULT_STORAGE_KEY = 'scale-sense-storage' as const\nexport const DEFAULT_STORAGE_VERSION = 10 as const\n\nconst DEFAULT_VISIBLE_SOURCE_KINDS: SourceKind[] = [\n 'westernNumeric',\n 'chineseWords',\n]\n\nfunction defaultHidden(): Record<SourceKind, boolean> {\n return Object.fromEntries(\n SOURCE_KINDS.map((k) => [k, !DEFAULT_VISIBLE_SOURCE_KINDS.includes(k)]),\n ) as Record<SourceKind, boolean>\n}\n\nfunction defaultConversionHidden(): Record<ConversionCardKind, boolean> {\n return Object.fromEntries(\n CONVERSION_CARD_KINDS.map((k) => [k, true]),\n ) as Record<ConversionCardKind, boolean>\n}\n\ntype PersistedEnvelope = {\n state?: unknown\n version?: number\n}\n\n/**\n * Zustand-compatible migration for persisted state (version 10).\n * Ported from the commercial app useAppStore persist migrate block.\n */\nfunction migratePersistedState(\n persistedState: unknown,\n): ScaleSensePersistedState {\n const state = persistedState as {\n cardCollapsed?: Record<SourceKind, boolean>\n cardHidden?: Record<SourceKind, boolean>\n conversionCardHidden?: Record<ConversionCardKind, boolean>\n savedCustomUnits?: unknown\n historySortMode?: unknown\n history?: unknown[]\n sourceKind?: SourceKind\n rawInput?: string\n }\n\n const nextState = state.cardHidden\n ? state\n : {\n ...state,\n cardHidden: state.cardCollapsed ?? defaultHidden(),\n }\n\n const migratedHistory = (nextState.history ?? [])\n .map((h) => parseHistoryEntry(h))\n .filter((h): h is HistoryEntry => h !== null)\n\n const savedCustomUnits = Array.isArray(nextState.savedCustomUnits)\n ? nextState.savedCustomUnits.filter(\n (u): u is string => typeof u === 'string',\n )\n : []\n\n return {\n ...nextState,\n conversionCardHidden:\n nextState.conversionCardHidden ?? defaultConversionHidden(),\n history: migratedHistory,\n historySortMode: parseHistorySortMode(nextState.historySortMode),\n savedCustomUnits,\n } as ScaleSensePersistedState\n}\n\nfunction readEnvelope(raw: string): PersistedEnvelope | null {\n try {\n return JSON.parse(raw) as PersistedEnvelope\n } catch {\n return null\n }\n}\n\nfunction loadPersistedState(\n key: string,\n version: number,\n): Partial<ScaleSensePersistedState> | null {\n if (typeof localStorage === 'undefined') return null\n\n const raw = localStorage.getItem(key)\n if (!raw) return null\n\n const envelope = readEnvelope(raw)\n if (!envelope) return null\n\n const storedVersion = envelope.version ?? 0\n const innerState = envelope.state ?? envelope\n\n if (storedVersion !== version) {\n return migratePersistedState(innerState)\n }\n\n return innerState as Partial<ScaleSensePersistedState>\n}\n\n/** Synchronous read for store bootstrap (matches zustand persist hydration). */\nexport function readLocalStoragePersistedState(options?: {\n key?: string\n version?: number\n}): Partial<ScaleSensePersistedState> | null {\n const key = options?.key ?? DEFAULT_STORAGE_KEY\n const version = options?.version ?? DEFAULT_STORAGE_VERSION\n return loadPersistedState(key, version)\n}\n\nexport function createLocalStorageAdapter(options?: {\n key?: string\n version?: number\n}): ScaleSenseStorage {\n const key = options?.key ?? DEFAULT_STORAGE_KEY\n const version = options?.version ?? DEFAULT_STORAGE_VERSION\n\n return {\n async load() {\n return loadPersistedState(key, version)\n },\n\n async save(state) {\n if (typeof localStorage === 'undefined') return\n\n const envelope: PersistedEnvelope = {\n state: {\n history: state.history,\n historySortMode: state.historySortMode,\n cardHidden: state.cardHidden,\n conversionCardHidden: state.conversionCardHidden,\n sourceKind: state.sourceKind,\n rawInput: state.rawInput,\n savedCustomUnits: state.savedCustomUnits,\n },\n version,\n }\n\n localStorage.setItem(key, JSON.stringify(envelope))\n },\n\n subscribe(listener) {\n if (typeof window === 'undefined') {\n return () => {}\n }\n\n const handler = (event: StorageEvent) => {\n if (event.key === key) listener()\n }\n\n window.addEventListener('storage', handler)\n return () => window.removeEventListener('storage', handler)\n },\n }\n}\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@scale-sense/storage-local",
3
+ "version": "0.1.0",
4
+ "description": "localStorage adapter for ScaleSense persisted state",
5
+ "license": "MIT",
6
+ "publishConfig": {
7
+ "access": "public"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://github.com/scale-sense/scale-sense-sdk.git",
12
+ "directory": "packages/storage-local"
13
+ },
14
+ "type": "module",
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "main": "./dist/index.js",
19
+ "module": "./dist/index.js",
20
+ "types": "./dist/index.d.ts",
21
+ "exports": {
22
+ ".": {
23
+ "types": "./dist/index.d.ts",
24
+ "import": "./dist/index.js"
25
+ }
26
+ },
27
+ "scripts": {
28
+ "build": "tsup",
29
+ "lint": "biome check ."
30
+ },
31
+ "dependencies": {
32
+ "@scale-sense/core": "workspace:*",
33
+ "@scale-sense/react": "workspace:*"
34
+ },
35
+ "devDependencies": {
36
+ "tsup": "^8.5.0",
37
+ "typescript": "~5.9.3"
38
+ }
39
+ }