offdex 1.0.7 → 2.0.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/LICENSE +21 -0
- package/README.md +64 -64
- package/package.json +14 -4
- package/src/ObjectStore/index.js +10 -67
- package/src/index.d.ts +29 -47
- package/src/types.d.ts +1 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 offdex contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,68 +1,68 @@
|
|
|
1
|
-
# Offdex
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
## Why use Offdex
|
|
6
|
-
|
|
7
|
-
- Zero schema/versioning overhead: one object store keyed by `
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
1
|
+
# Offdex
|
|
2
|
+
|
|
3
|
+
Keyed object storage for the browser. Offdex wraps IndexedDB with a tiny, unbiased API so you can drop in `{ key: "<string>", ...data }` objects and have them shared across tabs, workers, and sessions without thinking about schema versions.
|
|
4
|
+
|
|
5
|
+
## Why use Offdex
|
|
6
|
+
|
|
7
|
+
- Zero schema/versioning overhead: one object store keyed by `key`, no migrations to manage.
|
|
8
|
+
- Minimal wrapper: no hooks or proxies in the storage API, just data in and data out.
|
|
9
|
+
- Works everywhere IndexedDB works: tabs, workers, and other browser runtimes share the same underlying database.
|
|
10
|
+
- Offline by default: IndexedDB persists across reloads and disconnects.
|
|
11
|
+
- Typed surface: ships with TypeScript definitions for easy adoption.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install offdex
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Quick start
|
|
20
|
+
|
|
21
|
+
```js
|
|
22
|
+
import { storage } from "offdex";
|
|
23
|
+
|
|
23
24
|
const profile = {
|
|
24
|
-
|
|
25
|
+
key: "profile:ada",
|
|
25
26
|
name: "Ada Lovelace",
|
|
26
27
|
role: "analyst",
|
|
27
28
|
};
|
|
28
|
-
|
|
29
|
-
await storage.put(profile);
|
|
30
|
-
|
|
31
|
-
const again = await storage.get(profile.
|
|
32
|
-
// -> {
|
|
33
|
-
|
|
34
|
-
await storage.delete(profile.
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
## API
|
|
38
|
-
|
|
39
|
-
### `storage`
|
|
40
|
-
|
|
41
|
-
- Ready-to-use singleton instance shared across every import in the same origin. Uses the `offdex` database and `objects` store under the hood.
|
|
42
|
-
|
|
43
|
-
### `class ObjectStore`
|
|
44
|
-
|
|
45
|
-
- `constructor()` — opens (or creates) the `offdex` database with the `objects` store. Use this only if you need a separate instance.
|
|
46
|
-
- `put(object: {
|
|
47
|
-
- `
|
|
48
|
-
- `
|
|
49
|
-
- `
|
|
50
|
-
- `
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
- `
|
|
56
|
-
- `
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
- `StoredObject`
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
-
|
|
67
|
-
-
|
|
68
|
-
- There is no schema migration system; keep your stored objects backward compatible or manage migrations externally if you need them.
|
|
29
|
+
|
|
30
|
+
await storage.put(profile);
|
|
31
|
+
|
|
32
|
+
const again = await storage.get(profile.key);
|
|
33
|
+
// -> { key: "profile:ada", name: "Ada Lovelace", role: "analyst" }
|
|
34
|
+
|
|
35
|
+
await storage.delete(profile.key);
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## API
|
|
39
|
+
|
|
40
|
+
### `storage`
|
|
41
|
+
|
|
42
|
+
- Ready-to-use singleton instance shared across every import in the same origin. Uses the `offdex` database and `objects` store under the hood.
|
|
43
|
+
|
|
44
|
+
### `class ObjectStore`
|
|
45
|
+
|
|
46
|
+
- `constructor()` — opens (or creates) the `offdex` database with the `objects` store. Use this only if you need a separate instance.
|
|
47
|
+
- `put(object: { key: string } & Record<string, unknown>): Promise<void>` - upserts an object keyed by `key`.
|
|
48
|
+
- `putAll(objects: { key: string } & Record<string, unknown>[]): Promise<void>` - upserts multiple objects in a single transaction.
|
|
49
|
+
- `get(key: string): Promise<{ key: string } & Record<string, unknown> | undefined>` - fetches by `key`, returning `undefined` when missing.
|
|
50
|
+
- `delete(key: string): Promise<void>` - removes an object by `key`.
|
|
51
|
+
- `getAllMatches(queryOrFilter: StorageQuery | (object) => boolean): Promise<object[]>` - returns objects that pass a query or predicate.
|
|
52
|
+
- `deleteAllMatches(queryOrFilter: StorageQuery | (object) => boolean): Promise<void>` — deletes objects that pass a query or predicate.
|
|
53
|
+
|
|
54
|
+
### Other exports
|
|
55
|
+
|
|
56
|
+
- `StorageQuery` — helper for simple equality-based queries.
|
|
57
|
+
- `ObservableValue` — observable wrapper around a single value.
|
|
58
|
+
- `ObservableObject` — wraps an object in observable values keyed by its properties.
|
|
59
|
+
|
|
60
|
+
### Types
|
|
61
|
+
|
|
62
|
+
- `StoredObject` - `{ key: string } & Record<string, unknown>`.
|
|
63
|
+
|
|
64
|
+
## Notes
|
|
65
|
+
|
|
66
|
+
- Runs in any environment that exposes `indexedDB` (secure contexts in modern browsers).
|
|
67
|
+
- Data is shared per origin; open multiple tabs or workers and you will see the same store.
|
|
68
|
+
- There is no schema migration system; keep your stored objects backward compatible or manage migrations externally if you need them.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "offdex",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "Minimal, unbiased IndexedDB object-store wrapper keyed by a string. Shared across browser threads, offline-persistent, simple API.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"indexeddb",
|
|
7
7
|
"offline",
|
|
@@ -10,16 +10,25 @@
|
|
|
10
10
|
"web-storage",
|
|
11
11
|
"object-store",
|
|
12
12
|
"key-value",
|
|
13
|
-
"
|
|
13
|
+
"key",
|
|
14
14
|
"persist",
|
|
15
15
|
"threads",
|
|
16
16
|
"workers",
|
|
17
|
-
"shared"
|
|
17
|
+
"shared",
|
|
18
|
+
"minimal"
|
|
18
19
|
],
|
|
19
20
|
"license": "MIT",
|
|
20
21
|
"type": "module",
|
|
21
22
|
"main": "src/index.js",
|
|
22
23
|
"types": "src/index.d.ts",
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "git+https://github.com/jortsupetterson/offdex.git"
|
|
27
|
+
},
|
|
28
|
+
"bugs": {
|
|
29
|
+
"url": "https://github.com/jortsupetterson/offdex/issues"
|
|
30
|
+
},
|
|
31
|
+
"homepage": "https://github.com/jortsupetterson/offdex#readme",
|
|
23
32
|
"exports": {
|
|
24
33
|
".": {
|
|
25
34
|
"types": "./src/index.d.ts",
|
|
@@ -29,6 +38,7 @@
|
|
|
29
38
|
},
|
|
30
39
|
"files": [
|
|
31
40
|
"src",
|
|
41
|
+
"LICENSE",
|
|
32
42
|
"README.md"
|
|
33
43
|
],
|
|
34
44
|
"sideEffects": false
|
package/src/ObjectStore/index.js
CHANGED
|
@@ -12,7 +12,7 @@ export class ObjectStore {
|
|
|
12
12
|
request.addEventListener("upgradeneeded", () => {
|
|
13
13
|
const db = request.result;
|
|
14
14
|
if (!db.objectStoreNames.contains(ObjectStore.#store)) {
|
|
15
|
-
db.createObjectStore(ObjectStore.#store, { keyPath: "
|
|
15
|
+
db.createObjectStore(ObjectStore.#store, { keyPath: "key" });
|
|
16
16
|
}
|
|
17
17
|
});
|
|
18
18
|
|
|
@@ -48,19 +48,16 @@ export class ObjectStore {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
/**
|
|
51
|
-
* @param {
|
|
52
|
-
* @param {(propertyName: string, oldValue: any, newValue: any) => void | boolean} [onSetEvent]
|
|
53
|
-
* @param {(propertyName: string, deletedValue: any) => void | boolean} [onDeleteEvent]
|
|
51
|
+
* @param {string} key
|
|
54
52
|
* @returns {Promise<import("../types").StoredObject | undefined>}
|
|
55
53
|
*/
|
|
56
|
-
async get(
|
|
54
|
+
async get(key) {
|
|
57
55
|
const db = await this.instance;
|
|
58
|
-
const objectStoreInstance = this;
|
|
59
56
|
|
|
60
57
|
return new Promise((resolve, reject) => {
|
|
61
58
|
const transaction = db.transaction(ObjectStore.#store, "readonly");
|
|
62
59
|
const store = transaction.objectStore(ObjectStore.#store);
|
|
63
|
-
const request = store.get(
|
|
60
|
+
const request = store.get(key);
|
|
64
61
|
|
|
65
62
|
transaction.oncomplete = () => {
|
|
66
63
|
if (!request.result) {
|
|
@@ -68,32 +65,7 @@ export class ObjectStore {
|
|
|
68
65
|
return;
|
|
69
66
|
}
|
|
70
67
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const handler = {
|
|
74
|
-
set(targetObject, propertyName, newValue) {
|
|
75
|
-
const oldValue = targetObject[propertyName];
|
|
76
|
-
if (onSetEvent) {
|
|
77
|
-
const shouldUpdate = onSetEvent(propertyName, oldValue, newValue);
|
|
78
|
-
if (shouldUpdate === false) return true;
|
|
79
|
-
}
|
|
80
|
-
targetObject[propertyName] = newValue;
|
|
81
|
-
void objectStoreInstance.put(targetObject);
|
|
82
|
-
return true;
|
|
83
|
-
},
|
|
84
|
-
deleteProperty(targetObject, propertyName) {
|
|
85
|
-
const deletedValue = targetObject[propertyName];
|
|
86
|
-
if (onDeleteEvent) {
|
|
87
|
-
const shouldDelete = onDeleteEvent(propertyName, deletedValue);
|
|
88
|
-
if (shouldDelete === false) return true;
|
|
89
|
-
}
|
|
90
|
-
delete targetObject[propertyName];
|
|
91
|
-
void objectStoreInstance.put(targetObject);
|
|
92
|
-
return true;
|
|
93
|
-
},
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
resolve(new Proxy(target, handler));
|
|
68
|
+
resolve(request.result);
|
|
97
69
|
};
|
|
98
70
|
|
|
99
71
|
transaction.onerror = () =>
|
|
@@ -102,15 +74,15 @@ export class ObjectStore {
|
|
|
102
74
|
}
|
|
103
75
|
|
|
104
76
|
/**
|
|
105
|
-
* @param {
|
|
77
|
+
* @param {string} key
|
|
106
78
|
* @returns {Promise<void>}
|
|
107
79
|
*/
|
|
108
|
-
async delete(
|
|
80
|
+
async delete(key) {
|
|
109
81
|
const db = await this.instance;
|
|
110
82
|
return new Promise((resolve, reject) => {
|
|
111
83
|
const transaction = db.transaction(ObjectStore.#store, "readwrite");
|
|
112
84
|
const store = transaction.objectStore(ObjectStore.#store);
|
|
113
|
-
store.delete(
|
|
85
|
+
store.delete(key);
|
|
114
86
|
transaction.oncomplete = () => resolve();
|
|
115
87
|
transaction.onerror = () =>
|
|
116
88
|
reject(new Error(`{offdex} ObjectStore: ${transaction.error}`));
|
|
@@ -139,11 +111,9 @@ export class ObjectStore {
|
|
|
139
111
|
|
|
140
112
|
/**
|
|
141
113
|
* @param {import("../StorageQuery/index.js").StorageQuery | ((object: import("../types").StoredObject) => boolean)} queryOrPredicate
|
|
142
|
-
* @param {(propertyName: string, oldValue: any, newValue: any) => void | boolean} [onSetEvent]
|
|
143
|
-
* @param {(propertyName: string, deletedValue: any) => void | boolean} [onDeleteEvent]
|
|
144
114
|
* @returns {Promise<import("../types").StoredObject[]>}
|
|
145
115
|
*/
|
|
146
|
-
async getAllMatches(queryOrPredicate
|
|
116
|
+
async getAllMatches(queryOrPredicate) {
|
|
147
117
|
const db = await this.instance;
|
|
148
118
|
const objectStoreInstance = this;
|
|
149
119
|
|
|
@@ -166,34 +136,7 @@ export class ObjectStore {
|
|
|
166
136
|
const value = cursor.value;
|
|
167
137
|
|
|
168
138
|
if (predicate(value)) {
|
|
169
|
-
|
|
170
|
-
set(targetObject, propertyName, newValue) {
|
|
171
|
-
const oldValue = targetObject[propertyName];
|
|
172
|
-
if (onSetEvent) {
|
|
173
|
-
const shouldUpdate = onSetEvent(
|
|
174
|
-
propertyName,
|
|
175
|
-
oldValue,
|
|
176
|
-
newValue
|
|
177
|
-
);
|
|
178
|
-
if (shouldUpdate === false) return true;
|
|
179
|
-
}
|
|
180
|
-
targetObject[propertyName] = newValue;
|
|
181
|
-
void objectStoreInstance.put(targetObject);
|
|
182
|
-
return true;
|
|
183
|
-
},
|
|
184
|
-
deleteProperty(targetObject, propertyName) {
|
|
185
|
-
const deletedValue = targetObject[propertyName];
|
|
186
|
-
if (onDeleteEvent) {
|
|
187
|
-
const shouldDelete = onDeleteEvent(propertyName, deletedValue);
|
|
188
|
-
if (shouldDelete === false) return true;
|
|
189
|
-
}
|
|
190
|
-
delete targetObject[propertyName];
|
|
191
|
-
void objectStoreInstance.put(targetObject);
|
|
192
|
-
return true;
|
|
193
|
-
},
|
|
194
|
-
};
|
|
195
|
-
|
|
196
|
-
results.push(new Proxy(value, handler));
|
|
139
|
+
results.push(value);
|
|
197
140
|
}
|
|
198
141
|
|
|
199
142
|
cursor.continue();
|
package/src/index.d.ts
CHANGED
|
@@ -1,54 +1,36 @@
|
|
|
1
|
-
export type
|
|
2
|
-
|
|
3
|
-
export
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
) => void
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export class ObservableValue<T = unknown> {
|
|
23
|
-
constructor(value: T, fallback?: T);
|
|
24
|
-
get(): T;
|
|
25
|
-
set(newValue: T): void;
|
|
26
|
-
reset(): void;
|
|
27
|
-
observe(event: "set" | "reset", callback: (value: T) => void): () => void;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export class ObservableObject<T extends StoredObject = StoredObject> {
|
|
31
|
-
constructor(object: T);
|
|
32
|
-
[key: string]: ObservableValue<unknown>;
|
|
33
|
-
}
|
|
34
|
-
|
|
1
|
+
export type StoredObject = { key: string } & Record<string, unknown>;
|
|
2
|
+
|
|
3
|
+
export class StorageQuery {
|
|
4
|
+
constructor(args?: Record<string, unknown>);
|
|
5
|
+
args: Record<string, unknown>;
|
|
6
|
+
matches(object: StoredObject): boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export class ObservableValue<T = unknown> {
|
|
10
|
+
constructor(value: T, fallback?: T);
|
|
11
|
+
get(): T;
|
|
12
|
+
set(newValue: T): void;
|
|
13
|
+
reset(): void;
|
|
14
|
+
observe(event: "set" | "reset", callback: (value: T) => void): () => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class ObservableObject<T extends StoredObject = StoredObject> {
|
|
18
|
+
constructor(object: T);
|
|
19
|
+
[key: string]: ObservableValue<unknown>;
|
|
20
|
+
}
|
|
21
|
+
|
|
35
22
|
export class ObjectStore {
|
|
36
23
|
constructor();
|
|
37
24
|
put(object: StoredObject): Promise<void>;
|
|
38
|
-
get(
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
onDeleteEvent?: OnDeleteHandler
|
|
42
|
-
): Promise<StoredObject | undefined>;
|
|
43
|
-
delete(id: UUIDv4): Promise<void>;
|
|
25
|
+
get(key: string): Promise<StoredObject | undefined>;
|
|
26
|
+
delete(key: string): Promise<void>;
|
|
27
|
+
putAll(objects: StoredObject[]): Promise<void>;
|
|
44
28
|
getAllMatches(
|
|
45
|
-
queryOrFilter: StorageQuery | ((object: StoredObject) => boolean)
|
|
46
|
-
onSetEvent?: OnSetHandler,
|
|
47
|
-
onDeleteEvent?: OnDeleteHandler
|
|
29
|
+
queryOrFilter: StorageQuery | ((object: StoredObject) => boolean)
|
|
48
30
|
): Promise<StoredObject[]>;
|
|
49
31
|
deleteAllMatches(
|
|
50
32
|
queryOrFilter: StorageQuery | ((object: StoredObject) => boolean)
|
|
51
|
-
): Promise<void>;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export const storage: ObjectStore;
|
|
33
|
+
): Promise<void>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const storage: ObjectStore;
|
package/src/types.d.ts
CHANGED