@delphington/persistent-buckets 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.
- package/LICENSE +21 -0
- package/README.md +48 -0
- package/dist/bucket.d.ts +69 -0
- package/dist/bucket.d.ts.map +1 -0
- package/dist/bucket.js +119 -0
- package/dist/bucket.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/persistence.d.ts +55 -0
- package/dist/persistence.d.ts.map +1 -0
- package/dist/persistence.js +104 -0
- package/dist/persistence.js.map +1 -0
- package/dist/storage.d.ts +18 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +37 -0
- package/dist/storage.js.map +1 -0
- package/dist/text.d.ts +5 -0
- package/dist/text.d.ts.map +1 -0
- package/dist/text.js +39 -0
- package/dist/text.js.map +1 -0
- package/package.json +57 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Delph
|
|
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
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# persistent-buckets
|
|
2
|
+
|
|
3
|
+
Versioned `localStorage` buckets backed by shared Vue refs.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { defineBucket } from '@delphington/persistent-buckets';
|
|
9
|
+
|
|
10
|
+
type Preferences = {
|
|
11
|
+
theme: 'light' | 'dark';
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const preferences = defineBucket<Preferences>({
|
|
15
|
+
key: 'example:preferences',
|
|
16
|
+
version: 0,
|
|
17
|
+
initial: { theme: 'light' },
|
|
18
|
+
migrate(version, stored) {
|
|
19
|
+
if (version !== 0) throw new Error('Unsupported preferences version');
|
|
20
|
+
return stored as Preferences;
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`state` is a shared Vue ref. Changes must be saved explicitly:
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
const state = preferences.state;
|
|
29
|
+
state.value.theme = 'dark';
|
|
30
|
+
preferences.save();
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Bucket modules self-register when they are imported. Import every bucket at the application's entry point so aggregate operations always use the complete set:
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import './persistence/buckets/history';
|
|
37
|
+
import './persistence/buckets/preferences';
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Vite applications can instead eagerly import a bucket directory:
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
import.meta.glob('./persistence/buckets/*.ts', { eager: true });
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Only imported buckets are included by `persistence.dump()`, `persistence.restore()`, `persistence.reset()`, and `persistence.prune()`.
|
|
47
|
+
|
|
48
|
+
Bucket data must be JSON-serializable. If `localStorage` is unavailable, data is kept in memory and `persistence.ephemeral` is `true`.
|
package/dist/bucket.d.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { type Ref } from 'vue';
|
|
2
|
+
/** Defines a bucket */
|
|
3
|
+
export type BucketDefinition<T> = {
|
|
4
|
+
/** A unique localStorage key. */
|
|
5
|
+
readonly key: string;
|
|
6
|
+
/** The schema version written by `save`. */
|
|
7
|
+
readonly version: number;
|
|
8
|
+
/** The value returned when no valid stored value exists. */
|
|
9
|
+
readonly initial: T;
|
|
10
|
+
/**
|
|
11
|
+
* Validates stored data and migrates older versions to the current shape.
|
|
12
|
+
* @param version The schema version of the stored data
|
|
13
|
+
* @param stored The untrusted value read from storage
|
|
14
|
+
* @returns Data in the current bucket shape
|
|
15
|
+
*/
|
|
16
|
+
migrate: (version: number, stored: unknown) => T;
|
|
17
|
+
/**
|
|
18
|
+
* Removes obsolete data in place.
|
|
19
|
+
* @returns The number of removed records
|
|
20
|
+
*/
|
|
21
|
+
prune?: (data: T) => number;
|
|
22
|
+
};
|
|
23
|
+
/** A user-operatable bucket */
|
|
24
|
+
export type Bucket<T> = {
|
|
25
|
+
/** The bucket's key */
|
|
26
|
+
readonly key: string;
|
|
27
|
+
readonly state: Ref<T>;
|
|
28
|
+
dump: () => PersistedBucket<T>;
|
|
29
|
+
prune: () => number;
|
|
30
|
+
reset: () => T;
|
|
31
|
+
restore: (stored: unknown) => T;
|
|
32
|
+
save: () => void;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Record of what a bucket is for syncronise operations across all buckets
|
|
36
|
+
*/
|
|
37
|
+
export type RegisteredBucket = {
|
|
38
|
+
/** The bucket's storage key */
|
|
39
|
+
readonly key: string;
|
|
40
|
+
/** The function to dump/export the bucket */
|
|
41
|
+
dump: () => PersistedBucket<unknown>;
|
|
42
|
+
/** The function which parses an unknown JSON object, migrates if necessary */
|
|
43
|
+
parse: (stored: unknown) => unknown;
|
|
44
|
+
/** The user-provided callback function of how to prune records (for operations like soft-deletion handling) */
|
|
45
|
+
prune: () => number;
|
|
46
|
+
/** Clears the bucket */
|
|
47
|
+
reset: () => unknown;
|
|
48
|
+
/** Restores the bucket's state to the provided value */
|
|
49
|
+
restore: (stored: unknown) => unknown;
|
|
50
|
+
/** Same as restore */
|
|
51
|
+
restoreData: (data: unknown) => unknown;
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* The versioned envelope used to store a bucket.
|
|
55
|
+
*/
|
|
56
|
+
export type PersistedBucket<T> = {
|
|
57
|
+
version: number;
|
|
58
|
+
data: T;
|
|
59
|
+
};
|
|
60
|
+
export declare const buckets: Map<string, RegisteredBucket>;
|
|
61
|
+
/**
|
|
62
|
+
* Creates a persistent bucket and registers it for aggregate operations.
|
|
63
|
+
* `state` returns one shared Vue ref. Mutating that ref does not write to
|
|
64
|
+
* storage until `save()` is called.
|
|
65
|
+
* @param bucket The bucket definition
|
|
66
|
+
* @returns Operations for the defined bucket
|
|
67
|
+
*/
|
|
68
|
+
export declare function defineBucket<T>(bucket: BucketDefinition<T>): Bucket<T>;
|
|
69
|
+
//# sourceMappingURL=bucket.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bucket.d.ts","sourceRoot":"","sources":["../src/bucket.ts"],"names":[],"mappings":"AAAA,OAAO,EAAO,KAAK,GAAG,EAAE,MAAM,KAAK,CAAC;AAIpC,uBAAuB;AACvB,MAAM,MAAM,gBAAgB,CAAC,CAAC,IAAI;IAChC,iCAAiC;IACjC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IAErB,4CAA4C;IAC5C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAEzB,4DAA4D;IAC5D,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;IAEpB;;;;;OAKG;IACH,OAAO,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,KAAK,CAAC,CAAC;IAEjD;;;OAGG;IACH,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,CAAC;CAC7B,CAAC;AAEF,+BAA+B;AAC/B,MAAM,MAAM,MAAM,CAAC,CAAC,IAAI;IACtB,uBAAuB;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;IACvB,IAAI,EAAE,MAAM,eAAe,CAAC,CAAC,CAAC,CAAC;IAC/B,KAAK,EAAE,MAAM,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC,CAAC;IACf,OAAO,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,CAAC,CAAC;IAChC,IAAI,EAAE,MAAM,IAAI,CAAC;CAClB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,+BAA+B;IAC/B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,6CAA6C;IAC7C,IAAI,EAAE,MAAM,eAAe,CAAC,OAAO,CAAC,CAAC;IACrC,8EAA8E;IAC9E,KAAK,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC;IACpC,+GAA+G;IAC/G,KAAK,EAAE,MAAM,MAAM,CAAC;IACpB,wBAAwB;IACxB,KAAK,EAAE,MAAM,OAAO,CAAC;IACrB,wDAAwD;IACxD,OAAO,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC;IACtC,sBAAsB;IACtB,WAAW,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,OAAO,CAAC;CACzC,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,eAAe,CAAC,CAAC,IAAI;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,CAAC,CAAC;CACT,CAAC;AAEF,eAAO,MAAM,OAAO,+BAAsC,CAAC;AAY3D;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAoHtE"}
|
package/dist/bucket.js
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { ref } from 'vue';
|
|
2
|
+
import { storage } from './storage';
|
|
3
|
+
import { bytesToBase64Url, base64UrlToBytes, gzip, gunzip } from './text';
|
|
4
|
+
export const buckets = new Map();
|
|
5
|
+
function isPersistedBucket(value) {
|
|
6
|
+
return (typeof value === 'object' &&
|
|
7
|
+
value !== null &&
|
|
8
|
+
'version' in value &&
|
|
9
|
+
'data' in value &&
|
|
10
|
+
typeof value.version === 'number');
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Creates a persistent bucket and registers it for aggregate operations.
|
|
14
|
+
* `state` returns one shared Vue ref. Mutating that ref does not write to
|
|
15
|
+
* storage until `save()` is called.
|
|
16
|
+
* @param bucket The bucket definition
|
|
17
|
+
* @returns Operations for the defined bucket
|
|
18
|
+
*/
|
|
19
|
+
export function defineBucket(bucket) {
|
|
20
|
+
const state = ref(_load());
|
|
21
|
+
/**
|
|
22
|
+
* returns a copy of the initial data
|
|
23
|
+
*/
|
|
24
|
+
function initial() {
|
|
25
|
+
return deepcopy(bucket.initial);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Loads the bucket data, if the data can't be loaded for any reason, loads the initial data
|
|
29
|
+
* @returns the loaded bucket data
|
|
30
|
+
*/
|
|
31
|
+
function _load() {
|
|
32
|
+
try {
|
|
33
|
+
const raw = storage.getItem(bucket.key);
|
|
34
|
+
if (raw === null)
|
|
35
|
+
return initial();
|
|
36
|
+
const parsed = JSON.parse(raw);
|
|
37
|
+
if (!isPersistedBucket(parsed))
|
|
38
|
+
return initial();
|
|
39
|
+
return bucket.migrate(parsed.version, parsed.data);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return initial();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function _save(data) {
|
|
46
|
+
try {
|
|
47
|
+
const value = JSON.stringify({
|
|
48
|
+
version: bucket.version,
|
|
49
|
+
data: data,
|
|
50
|
+
});
|
|
51
|
+
storage.setItem(bucket.key, value);
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
state.value = _load();
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function save() {
|
|
59
|
+
_save(state.value);
|
|
60
|
+
}
|
|
61
|
+
function prune() {
|
|
62
|
+
if (!bucket.prune)
|
|
63
|
+
return 0;
|
|
64
|
+
const data = deepcopy(state.value);
|
|
65
|
+
const removed = bucket.prune(data);
|
|
66
|
+
if (removed === 0)
|
|
67
|
+
return 0;
|
|
68
|
+
_save(data);
|
|
69
|
+
state.value = data;
|
|
70
|
+
return removed;
|
|
71
|
+
}
|
|
72
|
+
function dump() {
|
|
73
|
+
return {
|
|
74
|
+
version: bucket.version,
|
|
75
|
+
data: _load(),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function parse(stored) {
|
|
79
|
+
if (!isPersistedBucket(stored))
|
|
80
|
+
throw new Error(`Invalid persisted bucket ${bucket.key}`);
|
|
81
|
+
return bucket.migrate(stored.version, stored.data);
|
|
82
|
+
}
|
|
83
|
+
function restoreData(data) {
|
|
84
|
+
_save(data);
|
|
85
|
+
state.value = data;
|
|
86
|
+
return data;
|
|
87
|
+
}
|
|
88
|
+
function restore(stored) {
|
|
89
|
+
return restoreData(parse(stored));
|
|
90
|
+
}
|
|
91
|
+
function reset() {
|
|
92
|
+
storage.removeItem(bucket.key);
|
|
93
|
+
const value = _load();
|
|
94
|
+
state.value = value;
|
|
95
|
+
return value;
|
|
96
|
+
}
|
|
97
|
+
buckets.set(bucket.key, {
|
|
98
|
+
key: bucket.key,
|
|
99
|
+
dump,
|
|
100
|
+
parse,
|
|
101
|
+
prune,
|
|
102
|
+
reset,
|
|
103
|
+
restore,
|
|
104
|
+
restoreData: (data) => restoreData(data),
|
|
105
|
+
});
|
|
106
|
+
return {
|
|
107
|
+
key: bucket.key,
|
|
108
|
+
state,
|
|
109
|
+
dump,
|
|
110
|
+
prune,
|
|
111
|
+
reset,
|
|
112
|
+
restore,
|
|
113
|
+
save,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
function deepcopy(data) {
|
|
117
|
+
return JSON.parse(JSON.stringify(data));
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=bucket.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bucket.js","sourceRoot":"","sources":["../src/bucket.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,KAAK,MAAM,EAAE,MAAM,UAAU,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAe,MAAM,UAAU,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { type PersistedBucket } from './bucket';
|
|
2
|
+
/**
|
|
3
|
+
* The portable representation of all registered buckets.
|
|
4
|
+
*/
|
|
5
|
+
export type PersistedDump = {
|
|
6
|
+
version: 0;
|
|
7
|
+
exportedAt: string;
|
|
8
|
+
buckets: Record<string, PersistedBucket<unknown>>;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Reads and exports every registered bucket from the active storage backend.
|
|
12
|
+
*/
|
|
13
|
+
declare function dump(): PersistedDump;
|
|
14
|
+
/**
|
|
15
|
+
* Resets every registered bucket to its initial value.
|
|
16
|
+
*/
|
|
17
|
+
declare function reset(): void;
|
|
18
|
+
/**
|
|
19
|
+
* Prunes every registered bucket.
|
|
20
|
+
* @returns The total number of removed records
|
|
21
|
+
*/
|
|
22
|
+
declare function prune(): number;
|
|
23
|
+
/**
|
|
24
|
+
* Restores known buckets from a persistence dump. Registered buckets omitted
|
|
25
|
+
* from the dump are reset, and unknown buckets in the dump are ignored. If a
|
|
26
|
+
* write fails, the function attempts to restore the previous data.
|
|
27
|
+
* @param data The untrusted persistence dump to restore
|
|
28
|
+
*/
|
|
29
|
+
declare function restore(data: unknown): void;
|
|
30
|
+
/**
|
|
31
|
+
* Exports registered buckets as gzip-compressed text when compression is
|
|
32
|
+
* available, or as formatted JSON otherwise.
|
|
33
|
+
* @param prefix The namespace prepended to compressed exports
|
|
34
|
+
*/
|
|
35
|
+
declare function exportText(prefix?: string): Promise<string>;
|
|
36
|
+
/**
|
|
37
|
+
* Restores registered buckets from compressed export text or plain JSON.
|
|
38
|
+
* @param value The exported text to import
|
|
39
|
+
* @param prefix The namespace used by compressed exports
|
|
40
|
+
*/
|
|
41
|
+
declare function importText(value: string, prefix?: string): Promise<void>;
|
|
42
|
+
export declare const persistence: {
|
|
43
|
+
/**
|
|
44
|
+
* if true, we've had to fall back to a shim with no persistence
|
|
45
|
+
*/
|
|
46
|
+
ephemeral: boolean;
|
|
47
|
+
dump: typeof dump;
|
|
48
|
+
reset: typeof reset;
|
|
49
|
+
restore: typeof restore;
|
|
50
|
+
prune: typeof prune;
|
|
51
|
+
exportText: typeof exportText;
|
|
52
|
+
importText: typeof importText;
|
|
53
|
+
};
|
|
54
|
+
export {};
|
|
55
|
+
//# sourceMappingURL=persistence.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"persistence.d.ts","sourceRoot":"","sources":["../src/persistence.ts"],"names":[],"mappings":"AACA,OAAO,EAAW,KAAK,eAAe,EAAyB,MAAM,UAAU,CAAC;AAOhF;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,OAAO,EAAE,CAAC,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC;CACnD,CAAC;AAEF;;GAEG;AACH,iBAAS,IAAI,IAAI,aAAa,CAQ7B;AAED;;GAEG;AACH,iBAAS,KAAK,SAEb;AAED;;;GAGG;AACH,iBAAS,KAAK,WAKb;AAED;;;;;GAKG;AACH,iBAAS,OAAO,CAAC,IAAI,EAAE,OAAO,QAkC7B;AAED;;;;GAIG;AACH,iBAAe,UAAU,CAAC,MAAM,SAAY,mBAQ3C;AAED;;;;GAIG;AACH,iBAAe,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,SAAY,iBAQ1D;AAED,eAAO,MAAM,WAAW;IACtB;;OAEG;;;;;;;;CAUJ,CAAC"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { storage, MemoryStorage } from './storage';
|
|
2
|
+
import { buckets } from './bucket';
|
|
3
|
+
import { bytesToBase64Url, base64UrlToBytes, gzip, gunzip } from './text';
|
|
4
|
+
function isRecord(value) {
|
|
5
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Reads and exports every registered bucket from the active storage backend.
|
|
9
|
+
*/
|
|
10
|
+
function dump() {
|
|
11
|
+
return {
|
|
12
|
+
version: 0,
|
|
13
|
+
exportedAt: new Date().toISOString(),
|
|
14
|
+
buckets: Object.fromEntries([...buckets.values()].map((bucket) => [bucket.key, bucket.dump()])),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Resets every registered bucket to its initial value.
|
|
19
|
+
*/
|
|
20
|
+
function reset() {
|
|
21
|
+
for (const bucket of buckets.values())
|
|
22
|
+
bucket.reset();
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Prunes every registered bucket.
|
|
26
|
+
* @returns The total number of removed records
|
|
27
|
+
*/
|
|
28
|
+
function prune() {
|
|
29
|
+
return [...buckets.values()].reduce((removed, bucket) => removed + bucket.prune(), 0);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Restores known buckets from a persistence dump. Registered buckets omitted
|
|
33
|
+
* from the dump are reset, and unknown buckets in the dump are ignored. If a
|
|
34
|
+
* write fails, the function attempts to restore the previous data.
|
|
35
|
+
* @param data The untrusted persistence dump to restore
|
|
36
|
+
*/
|
|
37
|
+
function restore(data) {
|
|
38
|
+
if (!isRecord(data) || data.version !== 0 || !isRecord(data.buckets))
|
|
39
|
+
throw new Error('Invalid persistence dump');
|
|
40
|
+
const restored = Object.entries(data.buckets)
|
|
41
|
+
.map(([key, stored]) => {
|
|
42
|
+
const bucket = buckets.get(key);
|
|
43
|
+
if (!bucket)
|
|
44
|
+
return;
|
|
45
|
+
return [bucket, bucket.parse(stored)];
|
|
46
|
+
})
|
|
47
|
+
.filter((entry) => entry !== undefined);
|
|
48
|
+
const previous = [...buckets.values()].map((bucket) => [bucket, bucket.dump()]);
|
|
49
|
+
try {
|
|
50
|
+
reset();
|
|
51
|
+
for (const [bucket, value] of restored)
|
|
52
|
+
bucket.restoreData(value);
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
try {
|
|
56
|
+
reset();
|
|
57
|
+
for (const [bucket, value] of previous)
|
|
58
|
+
bucket.restore(value);
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
throw new Error('Import failed and previous data could not be restored');
|
|
62
|
+
}
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Exports registered buckets as gzip-compressed text when compression is
|
|
68
|
+
* available, or as formatted JSON otherwise.
|
|
69
|
+
* @param prefix The namespace prepended to compressed exports
|
|
70
|
+
*/
|
|
71
|
+
async function exportText(prefix = 'buckets') {
|
|
72
|
+
const data = dump();
|
|
73
|
+
const json = JSON.stringify(data);
|
|
74
|
+
const compressed = await gzip(json);
|
|
75
|
+
if (!compressed)
|
|
76
|
+
return JSON.stringify(data, null, 2);
|
|
77
|
+
return `${prefix}:gzip:${bytesToBase64Url(compressed)}`;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Restores registered buckets from compressed export text or plain JSON.
|
|
81
|
+
* @param value The exported text to import
|
|
82
|
+
* @param prefix The namespace used by compressed exports
|
|
83
|
+
*/
|
|
84
|
+
async function importText(value, prefix = 'buckets') {
|
|
85
|
+
const text = value.trim();
|
|
86
|
+
const header = `${prefix}:gzip:`;
|
|
87
|
+
const json = text.startsWith(header)
|
|
88
|
+
? await gunzip(base64UrlToBytes(text.slice(header.length)))
|
|
89
|
+
: text;
|
|
90
|
+
restore(JSON.parse(json));
|
|
91
|
+
}
|
|
92
|
+
export const persistence = {
|
|
93
|
+
/**
|
|
94
|
+
* if true, we've had to fall back to a shim with no persistence
|
|
95
|
+
*/
|
|
96
|
+
ephemeral: storage instanceof MemoryStorage,
|
|
97
|
+
dump,
|
|
98
|
+
reset,
|
|
99
|
+
restore,
|
|
100
|
+
prune,
|
|
101
|
+
exportText,
|
|
102
|
+
importText,
|
|
103
|
+
};
|
|
104
|
+
//# sourceMappingURL=persistence.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"persistence.js","sourceRoot":"","sources":["../src/persistence.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A in-memory storage backend for when localStorage is not available
|
|
3
|
+
*/
|
|
4
|
+
export declare class MemoryStorage implements Storage {
|
|
5
|
+
private items;
|
|
6
|
+
getItem(key: string): string | null;
|
|
7
|
+
setItem(key: string, value: string): void;
|
|
8
|
+
removeItem(key: string): void;
|
|
9
|
+
clear(): void;
|
|
10
|
+
key(index: number): string | null;
|
|
11
|
+
get length(): number;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* The active storage backend. This falls back to in-memory storage when
|
|
15
|
+
* localStorage cannot be accessed.
|
|
16
|
+
*/
|
|
17
|
+
export declare const storage: MemoryStorage | Storage;
|
|
18
|
+
//# sourceMappingURL=storage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,qBAAa,aAAc,YAAW,OAAO;IAC3C,OAAO,CAAC,KAAK,CAA6B;IAE1C,OAAO,CAAC,GAAG,EAAE,MAAM;IAInB,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;IAIlC,UAAU,CAAC,GAAG,EAAE,MAAM;IAItB,KAAK;IAIL,GAAG,CAAC,KAAK,EAAE,MAAM;IAIjB,IAAI,MAAM,WAET;CACF;AAED;;;GAGG;AACH,eAAO,MAAM,OAAO,yBAMhB,CAAC"}
|
package/dist/storage.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A in-memory storage backend for when localStorage is not available
|
|
3
|
+
*/
|
|
4
|
+
export class MemoryStorage {
|
|
5
|
+
items = new Map();
|
|
6
|
+
getItem(key) {
|
|
7
|
+
return this.items.get(key) ?? null;
|
|
8
|
+
}
|
|
9
|
+
setItem(key, value) {
|
|
10
|
+
this.items.set(key, value);
|
|
11
|
+
}
|
|
12
|
+
removeItem(key) {
|
|
13
|
+
this.items.delete(key);
|
|
14
|
+
}
|
|
15
|
+
clear() {
|
|
16
|
+
this.items.clear();
|
|
17
|
+
}
|
|
18
|
+
key(index) {
|
|
19
|
+
return [...this.items.keys()][index] ?? null;
|
|
20
|
+
}
|
|
21
|
+
get length() {
|
|
22
|
+
return this.items.size;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The active storage backend. This falls back to in-memory storage when
|
|
27
|
+
* localStorage cannot be accessed.
|
|
28
|
+
*/
|
|
29
|
+
export const storage = (() => {
|
|
30
|
+
try {
|
|
31
|
+
return globalThis.localStorage ?? new MemoryStorage();
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return new MemoryStorage();
|
|
35
|
+
}
|
|
36
|
+
})();
|
|
37
|
+
//# sourceMappingURL=storage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.js","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,OAAO,aAAa;IAChB,KAAK,GAAG,IAAI,GAAG,EAAkB,CAAC;IAE1C,OAAO,CAAC,GAAW;QACjB,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC;IACrC,CAAC;IAED,OAAO,CAAC,GAAW,EAAE,KAAa;QAChC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IAC7B,CAAC;IAED,UAAU,CAAC,GAAW;QACpB,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACzB,CAAC;IAED,KAAK;QACH,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;IACrB,CAAC;IAED,GAAG,CAAC,KAAa;QACf,OAAO,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC;IAC/C,CAAC;IAED,IAAI,MAAM;QACR,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;IACzB,CAAC;CACF;AAED;;;GAGG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,GAAG,EAAE;IAC3B,IAAI,CAAC;QACH,OAAO,UAAU,CAAC,YAAY,IAAI,IAAI,aAAa,EAAE,CAAC;IACxD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,aAAa,EAAE,CAAC;IAC7B,CAAC;AACH,CAAC,CAAC,EAAE,CAAC"}
|
package/dist/text.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare function bytesToBase64Url(bytes: Uint8Array): string;
|
|
2
|
+
export declare function base64UrlToBytes(value: string): Uint8Array<ArrayBuffer>;
|
|
3
|
+
export declare function gzip(value: string): Promise<Uint8Array<ArrayBuffer> | null>;
|
|
4
|
+
export declare function gunzip(bytes: Uint8Array): Promise<string>;
|
|
5
|
+
//# sourceMappingURL=text.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text.d.ts","sourceRoot":"","sources":["../src/text.ts"],"names":[],"mappings":"AAAA,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,UAAU,UAWjD;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,2BAW7C;AAED,wBAAsB,IAAI,CAAC,KAAK,EAAE,MAAM,2CAOvC;AAED,wBAAsB,MAAM,CAAC,KAAK,EAAE,UAAU,mBAS7C"}
|
package/dist/text.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export function bytesToBase64Url(bytes) {
|
|
2
|
+
let binary = '';
|
|
3
|
+
const chunkSize = 0x8000;
|
|
4
|
+
for (let i = 0; i < bytes.length; i += chunkSize)
|
|
5
|
+
binary += String.fromCharCode(...bytes.subarray(i, i + chunkSize));
|
|
6
|
+
return btoa(binary)
|
|
7
|
+
.replaceAll('+', '-')
|
|
8
|
+
.replaceAll('/', '_')
|
|
9
|
+
.replaceAll('=', '');
|
|
10
|
+
}
|
|
11
|
+
export function base64UrlToBytes(value) {
|
|
12
|
+
const base64 = value
|
|
13
|
+
.replaceAll('-', '+')
|
|
14
|
+
.replaceAll('_', '/')
|
|
15
|
+
.padEnd(Math.ceil(value.length / 4) * 4, '=');
|
|
16
|
+
const binary = atob(base64);
|
|
17
|
+
const bytes = new Uint8Array(binary.length);
|
|
18
|
+
for (let i = 0; i < binary.length; i++)
|
|
19
|
+
bytes[i] = binary.charCodeAt(i);
|
|
20
|
+
return bytes;
|
|
21
|
+
}
|
|
22
|
+
export async function gzip(value) {
|
|
23
|
+
if (typeof CompressionStream === 'undefined')
|
|
24
|
+
return null;
|
|
25
|
+
const stream = new Blob([value])
|
|
26
|
+
.stream()
|
|
27
|
+
.pipeThrough(new CompressionStream('gzip'));
|
|
28
|
+
return new Uint8Array(await new Response(stream).arrayBuffer());
|
|
29
|
+
}
|
|
30
|
+
export async function gunzip(bytes) {
|
|
31
|
+
if (typeof DecompressionStream === 'undefined')
|
|
32
|
+
throw new Error('Compressed imports are not supported by this browser');
|
|
33
|
+
const buffer = new Uint8Array(bytes).buffer;
|
|
34
|
+
const stream = new Blob([buffer])
|
|
35
|
+
.stream()
|
|
36
|
+
.pipeThrough(new DecompressionStream('gzip'));
|
|
37
|
+
return await new Response(stream).text();
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=text.js.map
|
package/dist/text.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text.js","sourceRoot":"","sources":["../src/text.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,gBAAgB,CAAC,KAAiB;IAChD,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,MAAM,SAAS,GAAG,MAAM,CAAC;IAEzB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,SAAS;QAC9C,MAAM,IAAI,MAAM,CAAC,YAAY,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC;IAErE,OAAO,IAAI,CAAC,MAAM,CAAC;SAChB,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC;SACpB,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC;SACpB,UAAU,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;AACzB,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,KAAa;IAC5C,MAAM,MAAM,GAAG,KAAK;SACjB,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC;SACpB,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC;SACpB,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC;IAChD,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC;IAC5B,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAE5C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE;QAAE,KAAK,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;IAExE,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,KAAa;IACtC,IAAI,OAAO,iBAAiB,KAAK,WAAW;QAAE,OAAO,IAAI,CAAC;IAE1D,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC;SAC7B,MAAM,EAAE;SACR,WAAW,CAAC,IAAI,iBAAiB,CAAC,MAAM,CAAC,CAAC,CAAC;IAC9C,OAAO,IAAI,UAAU,CAAC,MAAM,IAAI,QAAQ,CAAC,MAAM,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;AAClE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,KAAiB;IAC5C,IAAI,OAAO,mBAAmB,KAAK,WAAW;QAC5C,MAAM,IAAI,KAAK,CAAC,sDAAsD,CAAC,CAAC;IAE1E,MAAM,MAAM,GAAG,IAAI,UAAU,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC;IAC5C,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC;SAC9B,MAAM,EAAE;SACR,WAAW,CAAC,IAAI,mBAAmB,CAAC,MAAM,CAAC,CAAC,CAAC;IAChD,OAAO,MAAM,IAAI,QAAQ,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;AAC3C,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@delphington/persistent-buckets",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Versioned localStorage buckets with shared Vue state",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Delph",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/Delph/persistent-buckets.git"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/Delph/persistent-buckets#readme",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"localstorage",
|
|
14
|
+
"persistence",
|
|
15
|
+
"vue",
|
|
16
|
+
"typescript"
|
|
17
|
+
],
|
|
18
|
+
"type": "module",
|
|
19
|
+
"private": false,
|
|
20
|
+
"sideEffects": false,
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public"
|
|
23
|
+
},
|
|
24
|
+
"packageManager": "bun@1.2.12",
|
|
25
|
+
"files": [
|
|
26
|
+
"dist",
|
|
27
|
+
"LICENSE",
|
|
28
|
+
"README.md"
|
|
29
|
+
],
|
|
30
|
+
"main": "./dist/index.js",
|
|
31
|
+
"types": "./dist/index.d.ts",
|
|
32
|
+
"exports": {
|
|
33
|
+
".": {
|
|
34
|
+
"types": "./dist/index.d.ts",
|
|
35
|
+
"import": "./dist/index.js"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "tsc -p tsconfig.build.json",
|
|
40
|
+
"prepack": "bun run check",
|
|
41
|
+
"check": "bun run format:check && bun run typecheck && bun run test && bun run build",
|
|
42
|
+
"format": "prettier . --write",
|
|
43
|
+
"format:check": "prettier . --check",
|
|
44
|
+
"test": "vitest run",
|
|
45
|
+
"test:watch": "vitest",
|
|
46
|
+
"typecheck": "tsc --noEmit"
|
|
47
|
+
},
|
|
48
|
+
"peerDependencies": {
|
|
49
|
+
"vue": "^3.5.0"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"prettier": "^3.9.6",
|
|
53
|
+
"typescript": "~6.0.2",
|
|
54
|
+
"vitest": "^4.1.11",
|
|
55
|
+
"vue": "^3.5.40"
|
|
56
|
+
}
|
|
57
|
+
}
|