@nice-code/util 0.2.9 → 0.2.11
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/README.md +159 -1
- package/build/index.js +204 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1 +1,159 @@
|
|
|
1
|
-
# @nice-code/
|
|
1
|
+
# @nice-code/util
|
|
2
|
+
|
|
3
|
+
Typed storage adapters for browser, Cloudflare Durable Objects, and in-memory use.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bun add @nice-code/util
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Typed storage
|
|
14
|
+
|
|
15
|
+
`createTypedStorage` wraps a `StorageAdapter` and adds full TypeScript key and value inference based on a schema type parameter.
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { createTypedWebLocalStorage } from "@nice-code/util";
|
|
19
|
+
|
|
20
|
+
// Define the shape of your storage
|
|
21
|
+
interface IAppStorage {
|
|
22
|
+
user_id: string;
|
|
23
|
+
theme: "light" | "dark";
|
|
24
|
+
last_seen: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const storage = createTypedWebLocalStorage<IAppStorage>(localStorage, "app:");
|
|
28
|
+
|
|
29
|
+
// All keys autocomplete; values are typed
|
|
30
|
+
await storage.setJson("theme", "dark");
|
|
31
|
+
const theme = await storage.getJson("theme"); // "light" | "dark" | undefined
|
|
32
|
+
const userId = await storage.getJsonOrDef("user_id", "guest"); // string
|
|
33
|
+
await storage.removeItem("last_seen");
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
All methods are async and key-prefixed if a prefix is provided.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Available adapters
|
|
41
|
+
|
|
42
|
+
### Browser — `localStorage` / `sessionStorage`
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
import {
|
|
46
|
+
createTypedWebLocalStorage,
|
|
47
|
+
createTypedWebSessionStorage,
|
|
48
|
+
} from "@nice-code/util";
|
|
49
|
+
|
|
50
|
+
const local = createTypedWebLocalStorage<IAppStorage>(localStorage, "prefix:");
|
|
51
|
+
const session = createTypedWebSessionStorage<IAppStorage>(sessionStorage);
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Cloudflare Durable Objects
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
import { createDurableObjectTypedStorage } from "@nice-code/util";
|
|
58
|
+
|
|
59
|
+
// Inside a Durable Object class
|
|
60
|
+
export class MyDO {
|
|
61
|
+
private storage: ITypedStorage<IDOStorage>;
|
|
62
|
+
|
|
63
|
+
constructor(state: DurableObjectState) {
|
|
64
|
+
this.storage = createDurableObjectTypedStorage<IDOStorage>(state.storage, "do:");
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### In-memory (testing / SSR)
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
import {
|
|
73
|
+
createTypedMemoryStorage_string,
|
|
74
|
+
createTypedMemoryStorage_json,
|
|
75
|
+
} from "@nice-code/util";
|
|
76
|
+
|
|
77
|
+
// String-serialized (like localStorage)
|
|
78
|
+
const store = createTypedMemoryStorage_string<IAppStorage>();
|
|
79
|
+
|
|
80
|
+
// JSON-native (no serialization overhead)
|
|
81
|
+
const jsonStore = createTypedMemoryStorage_json<IAppStorage>();
|
|
82
|
+
|
|
83
|
+
// Pass in your own Map to share state between instances
|
|
84
|
+
const shared = new Map<string, string>();
|
|
85
|
+
const a = createTypedMemoryStorage_string<IAppStorage>(shared);
|
|
86
|
+
const b = createTypedMemoryStorage_string<IAppStorage>(shared);
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## `ITypedStorage<T>` interface
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
interface ITypedStorage<T extends Record<string, any>> {
|
|
95
|
+
getJson<K extends keyof T & string>(key: K): Promise<T[K] | undefined>;
|
|
96
|
+
getJsonOrDef<K extends keyof T & string>(key: K, defVal: T[K]): Promise<T[K]>;
|
|
97
|
+
setJson<K extends keyof T & string>(key: K, val: T[K]): Promise<void>;
|
|
98
|
+
removeItem<K extends keyof T & string>(key: K): Promise<void>;
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## `StorageAdapter` — low-level
|
|
105
|
+
|
|
106
|
+
Use `StorageAdapter` directly when you need a getter/setter pair or want to build a custom typed storage on top.
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
import { StorageAdapter } from "@nice-code/util";
|
|
110
|
+
import { createMemoryStorageMethods_string } from "@nice-code/util";
|
|
111
|
+
|
|
112
|
+
const adapter = new StorageAdapter({
|
|
113
|
+
methods: createMemoryStorageMethods_string(),
|
|
114
|
+
keyPrefix: "myapp:",
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
await adapter.setJson("token", { value: "abc", exp: 9999 });
|
|
118
|
+
const token = await adapter.getJson<{ value: string; exp: number }>("token");
|
|
119
|
+
const safe = await adapter.getJsonOrDef("token", { value: "", exp: 0 });
|
|
120
|
+
|
|
121
|
+
// Convenient getter/setter pair for a single key
|
|
122
|
+
const { get, set } = adapter.createJsonGetterSetter<string>("theme");
|
|
123
|
+
await set("dark");
|
|
124
|
+
const theme = await get(); // string | undefined
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## Custom adapter
|
|
130
|
+
|
|
131
|
+
Implement `TStorageAdapterMethods` to wrap any storage backend:
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
import type { IStorageAdapterMethods_String } from "@nice-code/util";
|
|
135
|
+
import { EStorageAdapterType } from "@nice-code/util";
|
|
136
|
+
|
|
137
|
+
const redisMethods: IStorageAdapterMethods_String = {
|
|
138
|
+
type: EStorageAdapterType.string,
|
|
139
|
+
getItem: async (key) => redis.get(key),
|
|
140
|
+
setItem: async (key, value) => { await redis.set(key, value); },
|
|
141
|
+
removeItem: async (key) => { await redis.del(key); },
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const storage = createTypedStorage<IMySchema>({
|
|
145
|
+
storageAdapter: new StorageAdapter({ methods: redisMethods, keyPrefix: "app:" }),
|
|
146
|
+
});
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## TypeScript utilities
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
import type { StringKeys } from "@nice-code/util";
|
|
155
|
+
|
|
156
|
+
// Extracts string keys from a type
|
|
157
|
+
type Keys = StringKeys<{ a: string; b: number; 0: boolean }>;
|
|
158
|
+
// → "a" | "b"
|
|
159
|
+
```
|
package/build/index.js
CHANGED
|
@@ -1,8 +1,204 @@
|
|
|
1
|
-
// src/
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
// src/storage_adapter/storage_adapter.types.ts
|
|
2
|
+
var EStorageAdapterType;
|
|
3
|
+
((EStorageAdapterType2) => {
|
|
4
|
+
EStorageAdapterType2["string"] = "string";
|
|
5
|
+
EStorageAdapterType2["json"] = "json";
|
|
6
|
+
})(EStorageAdapterType ||= {});
|
|
7
|
+
|
|
8
|
+
// src/storage_adapter/StorageAdapter.ts
|
|
9
|
+
class StorageAdapter {
|
|
10
|
+
implementation;
|
|
11
|
+
keyPrefix;
|
|
12
|
+
constructor({ methods, keyPrefix }) {
|
|
13
|
+
this.implementation = methods;
|
|
14
|
+
this.keyPrefix = keyPrefix ?? "";
|
|
15
|
+
}
|
|
16
|
+
getPrefixedKey(key) {
|
|
17
|
+
return `${this.keyPrefix}${key}`;
|
|
18
|
+
}
|
|
19
|
+
async removeItem(key) {
|
|
20
|
+
await this.implementation.removeItem(this.getPrefixedKey(key));
|
|
21
|
+
}
|
|
22
|
+
async setJson(key, value) {
|
|
23
|
+
if (this.implementation.type === "string" /* string */) {
|
|
24
|
+
await this.implementation.setItem(this.getPrefixedKey(key), JSON.stringify(value));
|
|
25
|
+
} else {
|
|
26
|
+
await this.implementation.setItem(this.getPrefixedKey(key), value);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
async getJson(key) {
|
|
30
|
+
if (this.implementation.type === "string" /* string */) {
|
|
31
|
+
const val = await this.implementation.getItem(this.getPrefixedKey(key));
|
|
32
|
+
if (val == null || val === "undefined" || val === "null") {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
return JSON.parse(val);
|
|
36
|
+
} else {
|
|
37
|
+
const val = await this.implementation.getItem(this.getPrefixedKey(key));
|
|
38
|
+
if (val == null || val === "undefined" || val === "null") {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
return val;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
async getJsonOrDef(key, defVal) {
|
|
45
|
+
if (this.implementation.type === "string" /* string */) {
|
|
46
|
+
const val2 = await this.implementation.getItem(this.getPrefixedKey(key));
|
|
47
|
+
if (val2 == null || val2 === "undefined" || val2 === "null") {
|
|
48
|
+
return defVal;
|
|
49
|
+
}
|
|
50
|
+
return JSON.parse(val2);
|
|
51
|
+
}
|
|
52
|
+
const val = await this.implementation.getItem(this.getPrefixedKey(key));
|
|
53
|
+
if (val == null || val === "undefined" || val === "null") {
|
|
54
|
+
return defVal;
|
|
55
|
+
}
|
|
56
|
+
return val;
|
|
57
|
+
}
|
|
58
|
+
createJsonGetterSetter(key) {
|
|
59
|
+
return {
|
|
60
|
+
get: () => this.getJson(key),
|
|
61
|
+
set: (value) => this.setJson(key, value)
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// src/storage_adapter/typed_storage/createTypedStorage.ts
|
|
66
|
+
function createTypedStorage({
|
|
67
|
+
storageAdapter
|
|
68
|
+
}) {
|
|
69
|
+
const getJson = async (key) => {
|
|
70
|
+
return storageAdapter.getJson(key);
|
|
71
|
+
};
|
|
72
|
+
const getJsonOrDef = async (key, defVal) => {
|
|
73
|
+
return await storageAdapter.getJson(key) ?? defVal;
|
|
74
|
+
};
|
|
75
|
+
const setJson = async (key, val) => {
|
|
76
|
+
return storageAdapter.setJson(key, val);
|
|
77
|
+
};
|
|
78
|
+
const removeItem = async (key) => {
|
|
79
|
+
await storageAdapter.removeItem(key);
|
|
80
|
+
};
|
|
81
|
+
return {
|
|
82
|
+
getJson,
|
|
83
|
+
getJsonOrDef,
|
|
84
|
+
setJson,
|
|
85
|
+
removeItem
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// src/storage_adapter/specific/browser/browser_storage.ts
|
|
90
|
+
function createWebLocalStorageMethods(_localStorage) {
|
|
91
|
+
return {
|
|
92
|
+
type: "string" /* string */,
|
|
93
|
+
getItem: async (key) => _localStorage.getItem(key),
|
|
94
|
+
setItem: async (key, value) => {
|
|
95
|
+
_localStorage.setItem(key, value);
|
|
96
|
+
},
|
|
97
|
+
removeItem: async (key) => {
|
|
98
|
+
_localStorage.removeItem(key);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function createTypedWebLocalStorage(_localStorage, keyPrefix) {
|
|
103
|
+
return createTypedStorage({
|
|
104
|
+
storageAdapter: new StorageAdapter({
|
|
105
|
+
methods: createWebLocalStorageMethods(_localStorage),
|
|
106
|
+
keyPrefix
|
|
107
|
+
})
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
function createWebSessionStorageMethods(_sessionStorage) {
|
|
111
|
+
return {
|
|
112
|
+
type: "string" /* string */,
|
|
113
|
+
getItem: async (key) => _sessionStorage.getItem(key),
|
|
114
|
+
setItem: async (key, value) => {
|
|
115
|
+
_sessionStorage.setItem(key, value);
|
|
116
|
+
},
|
|
117
|
+
removeItem: async (key) => {
|
|
118
|
+
_sessionStorage.removeItem(key);
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
function createTypedWebSessionStorage(_sessionStorage, keyPrefix) {
|
|
123
|
+
return createTypedStorage({
|
|
124
|
+
storageAdapter: new StorageAdapter({
|
|
125
|
+
methods: createWebSessionStorageMethods(_sessionStorage),
|
|
126
|
+
keyPrefix
|
|
127
|
+
})
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
// src/storage_adapter/specific/durable_object/durable_object_storage.ts
|
|
131
|
+
function createDurableObjectStorageMethods(durableObjectStorage) {
|
|
132
|
+
return {
|
|
133
|
+
type: "json" /* json */,
|
|
134
|
+
getItem: (key) => durableObjectStorage.get(key),
|
|
135
|
+
setItem: (key, value) => durableObjectStorage.put(key, value),
|
|
136
|
+
removeItem: async (key) => {
|
|
137
|
+
await durableObjectStorage.delete(key);
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
function createDurableObjectTypedStorage(durableObjectStorage, keyPrefix) {
|
|
142
|
+
return createTypedStorage({
|
|
143
|
+
storageAdapter: new StorageAdapter({
|
|
144
|
+
methods: createDurableObjectStorageMethods(durableObjectStorage),
|
|
145
|
+
keyPrefix
|
|
146
|
+
})
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
// src/storage_adapter/specific/memory/memory_storage.ts
|
|
150
|
+
function createMemoryStorageMethods_string(memoryStorageMap = new Map) {
|
|
151
|
+
return {
|
|
152
|
+
type: "string" /* string */,
|
|
153
|
+
getItem: async (key) => memoryStorageMap.get(key) ?? null,
|
|
154
|
+
setItem: async (key, value) => {
|
|
155
|
+
memoryStorageMap.set(key, value);
|
|
156
|
+
},
|
|
157
|
+
removeItem: async (key) => {
|
|
158
|
+
memoryStorageMap.delete(key);
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
function createTypedMemoryStorage_string(memoryStorageMap = new Map, keyPrefix) {
|
|
163
|
+
return createTypedStorage({
|
|
164
|
+
storageAdapter: new StorageAdapter({
|
|
165
|
+
methods: createMemoryStorageMethods_string(memoryStorageMap),
|
|
166
|
+
keyPrefix
|
|
167
|
+
})
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
function createMemoryStorageMethods_json(memoryStorageMap = new Map) {
|
|
171
|
+
return {
|
|
172
|
+
type: "json" /* json */,
|
|
173
|
+
getItem: async (key) => memoryStorageMap.get(key),
|
|
174
|
+
setItem: async (key, value) => {
|
|
175
|
+
memoryStorageMap.set(key, value);
|
|
176
|
+
},
|
|
177
|
+
removeItem: async (key) => {
|
|
178
|
+
memoryStorageMap.delete(key);
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
function createTypedMemoryStorage_json(memoryStorageMap = new Map, keyPrefix) {
|
|
183
|
+
return createTypedStorage({
|
|
184
|
+
storageAdapter: new StorageAdapter({
|
|
185
|
+
methods: createMemoryStorageMethods_json(memoryStorageMap),
|
|
186
|
+
keyPrefix
|
|
187
|
+
})
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
export {
|
|
191
|
+
createWebSessionStorageMethods,
|
|
192
|
+
createWebLocalStorageMethods,
|
|
193
|
+
createTypedWebSessionStorage,
|
|
194
|
+
createTypedWebLocalStorage,
|
|
195
|
+
createTypedStorage,
|
|
196
|
+
createTypedMemoryStorage_string,
|
|
197
|
+
createTypedMemoryStorage_json,
|
|
198
|
+
createMemoryStorageMethods_string,
|
|
199
|
+
createMemoryStorageMethods_json,
|
|
200
|
+
createDurableObjectTypedStorage,
|
|
201
|
+
createDurableObjectStorageMethods,
|
|
202
|
+
StorageAdapter,
|
|
203
|
+
EStorageAdapterType
|
|
204
|
+
};
|