idb-refined 0.0.1 → 0.0.3
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 +24 -78
- package/dist/cleanWhenTooLarge.d.ts +2 -1
- package/dist/cleanWhenTooLarge.js +3 -1
- package/dist/client.d.ts +21 -0
- package/dist/client.js +84 -0
- package/dist/index.d.ts +2 -13
- package/dist/index.js +1 -7
- package/dist/putWithEviction.d.ts +7 -1
- package/dist/putWithEviction.js +15 -1
- package/package.json +13 -12
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# idb-refined
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Minimal IndexedDB client on top of [idb](https://www.npmjs.com/package/idb). Exposes **add**, **update**, **delete**, and **removeDb**. Init, schema, cleanup and eviction run automatically.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
@@ -12,99 +12,45 @@ npm install idb-refined
|
|
|
12
12
|
|
|
13
13
|
## API
|
|
14
14
|
|
|
15
|
-
|
|
|
16
|
-
|
|
17
|
-
| **
|
|
18
|
-
| **
|
|
19
|
-
| **
|
|
20
|
-
| **
|
|
21
|
-
| **
|
|
22
|
-
| **
|
|
23
|
-
| **deleteDB(name)** | Re-export of idb’s `deleteDB`. |
|
|
15
|
+
| Export | Purpose |
|
|
16
|
+
|--------|---------|
|
|
17
|
+
| **createClient(options)** | Returns `{ set, get, update, delete, deleteDb }`. Options: `dbName` (required), `storeName` (optional). |
|
|
18
|
+
| **set(value)** | Store a value. Value must have an `id` property. Expiry and eviction run automatically. |
|
|
19
|
+
| **get(key)** | Get a value by key. Returns `undefined` if not found. |
|
|
20
|
+
| **update(key, value)** | Update an existing entry by key. |
|
|
21
|
+
| **delete(key)** | Delete an entry by key. |
|
|
22
|
+
| **deleteDb()** | Close the DB and delete it from disk. |
|
|
24
23
|
|
|
25
|
-
|
|
24
|
+
For details, see **[Advanced documentation](docs/advanced.md)**. Run the **[example](example/)** in the browser (see `example/README.md`).
|
|
26
25
|
|
|
27
|
-
|
|
28
|
-
- **Cache with max size:** Use `putWithEviction` or call `cleanWhenTooLarge` after adding.
|
|
29
|
-
- **Cache with TTL + max size:** `putWithEviction` + periodic `cleanOldEntries` for expired.
|
|
30
|
-
- **Log / event buffer:** `putWithEviction` with `dateKey: 'createdAt'` and `maxCount`.
|
|
31
|
-
- **Simple key-value:** initDb, `db.get` / `db.put` / `deleteByKey`; optional cleanup when too large.
|
|
32
|
-
|
|
33
|
-
## Examples
|
|
34
|
-
|
|
35
|
-
### Cache with TTL and max size
|
|
36
|
-
|
|
37
|
-
```ts
|
|
38
|
-
import { initDb, putWithEviction, cleanOldEntries, deleteByKey } from "idb-refined";
|
|
39
|
-
|
|
40
|
-
const db = await initDb("my-cache", {
|
|
41
|
-
schema: {
|
|
42
|
-
stores: {
|
|
43
|
-
cache: { keyPath: "id", indexes: ["expiresAt"] },
|
|
44
|
-
},
|
|
45
|
-
},
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
// Add item; if store has more than 1000 entries, evict oldest
|
|
49
|
-
await putWithEviction(
|
|
50
|
-
db,
|
|
51
|
-
"cache",
|
|
52
|
-
{ id: "k1", data: "v1", expiresAt: Date.now() + 3600 },
|
|
53
|
-
{ dateKey: "expiresAt", maxCount: 1000 }
|
|
54
|
-
);
|
|
55
|
-
|
|
56
|
-
// Periodic: remove expired
|
|
57
|
-
await cleanOldEntries(db, "cache", {
|
|
58
|
-
dateKey: "expiresAt",
|
|
59
|
-
before: Date.now(),
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
// Delete one
|
|
63
|
-
await deleteByKey(db, "cache", "k1");
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
### Log buffer (cap size)
|
|
26
|
+
## Example
|
|
67
27
|
|
|
68
28
|
```ts
|
|
69
|
-
|
|
70
|
-
db,
|
|
71
|
-
"logs",
|
|
72
|
-
{ id: generateId(), message, createdAt: Date.now() },
|
|
73
|
-
{ dateKey: "createdAt", maxCount: 5000 }
|
|
74
|
-
);
|
|
75
|
-
```
|
|
29
|
+
import { createClient } from "idb-refined";
|
|
76
30
|
|
|
77
|
-
|
|
31
|
+
// Optional: type the stored value for set/get/update
|
|
32
|
+
type User = { id: string; name: string; createdAt?: number; expiresAt?: number };
|
|
33
|
+
const { set, get, update, delete: del, deleteDb } = createClient<User>({ dbName: "my-app" });
|
|
78
34
|
|
|
79
|
-
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
console.log(`Evicted ${deleted} entries`);
|
|
35
|
+
await set({ id: "1", name: "Alice" });
|
|
36
|
+
const value = await get("1"); // User | undefined
|
|
37
|
+
await update("1", { name: "Alice Updated" });
|
|
38
|
+
await del("1");
|
|
39
|
+
await deleteDb();
|
|
85
40
|
```
|
|
86
41
|
|
|
87
42
|
## Requirements
|
|
88
43
|
|
|
89
|
-
-
|
|
90
|
-
|
|
91
|
-
```ts
|
|
92
|
-
schema: {
|
|
93
|
-
stores: {
|
|
94
|
-
cache: { keyPath: "id", indexes: ["expiresAt"] },
|
|
95
|
-
},
|
|
96
|
-
}
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
- Eviction is **count-based** only (no byte-size or quota check).
|
|
44
|
+
- Values must include an `id` property (used as the store key).
|
|
45
|
+
- The library uses a single store (default name `"store"`) with indexes on `expiresAt` and `createdAt`. Cleanup and eviction run on set.
|
|
100
46
|
|
|
101
47
|
## Releasing
|
|
102
48
|
|
|
103
49
|
1. Bump version: `pnpm version patch` (or `minor` / `major`).
|
|
104
50
|
2. Commit and push: `git push && git push --tags`.
|
|
105
|
-
3. Pushing a tag matching `v*`
|
|
51
|
+
3. Pushing a tag matching `v*` triggers the [Publish to npm](.github/workflows/publish.yml) workflow.
|
|
106
52
|
|
|
107
|
-
**Required:** Add an `NPM_TOKEN` secret in the repo (Settings → Secrets and variables → Actions).
|
|
53
|
+
**Required:** Add an `NPM_TOKEN` secret in the repo (Settings → Secrets and variables → Actions).
|
|
108
54
|
|
|
109
55
|
## License
|
|
110
56
|
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { IDBPDatabase } from "idb";
|
|
2
2
|
export interface CleanWhenTooLargeOptions {
|
|
3
3
|
dateKey: string;
|
|
4
|
-
|
|
4
|
+
/** Target max entries after eviction. */
|
|
5
|
+
maxCount?: number;
|
|
5
6
|
}
|
|
6
7
|
/**
|
|
7
8
|
* Evict oldest entries (by dateKey) until store count <= maxCount.
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
const DEFAULT_MAX_COUNT = 1000;
|
|
1
2
|
/**
|
|
2
3
|
* Evict oldest entries (by dateKey) until store count <= maxCount.
|
|
3
4
|
* Requires an index on dateKey. Returns the number of entries deleted.
|
|
4
5
|
*/
|
|
5
6
|
export async function cleanWhenTooLarge(db, storeName, options) {
|
|
6
|
-
const { dateKey
|
|
7
|
+
const { dateKey } = options;
|
|
8
|
+
const maxCount = options.maxCount ?? DEFAULT_MAX_COUNT;
|
|
7
9
|
const count = await db.count(storeName);
|
|
8
10
|
if (count <= maxCount)
|
|
9
11
|
return 0;
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface CreateClientOptions {
|
|
2
|
+
dbName: string;
|
|
3
|
+
storeName?: string;
|
|
4
|
+
}
|
|
5
|
+
/** Stored value must have `id`. `createdAt` and `expiresAt` are set by the client if missing. */
|
|
6
|
+
export type StoredValue = Record<string, unknown> & {
|
|
7
|
+
id: IDBValidKey;
|
|
8
|
+
};
|
|
9
|
+
export interface IdbRefinedClient<T extends StoredValue = StoredValue> {
|
|
10
|
+
add: (value: T) => Promise<void>;
|
|
11
|
+
get: (key: IDBValidKey) => Promise<T | undefined>;
|
|
12
|
+
update: (key: IDBValidKey, value: Partial<T>) => Promise<void>;
|
|
13
|
+
delete: (key: IDBValidKey) => Promise<void>;
|
|
14
|
+
removeDb: () => Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Create a client that exposes add, get, update, delete, removeDb.
|
|
18
|
+
* Init, schema, cleanup and eviction run automatically.
|
|
19
|
+
* @template T - Stored value shape (must include `id`). Omit for a generic client.
|
|
20
|
+
*/
|
|
21
|
+
export declare function createClient<T extends StoredValue = StoredValue>(options: CreateClientOptions): IdbRefinedClient<T>;
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { deleteDB } from "idb";
|
|
2
|
+
import { initDb } from "./initDb.js";
|
|
3
|
+
import { cleanOldEntries } from "./cleanOldEntries.js";
|
|
4
|
+
import { cleanWhenTooLarge } from "./cleanWhenTooLarge.js";
|
|
5
|
+
const DEFAULT_STORE_NAME = "store";
|
|
6
|
+
const DEFAULT_KEY_PATH = "id";
|
|
7
|
+
const DATE_INDEXES = ["expiresAt", "createdAt"];
|
|
8
|
+
const DEFAULT_TTL_MS = 3600 * 1000;
|
|
9
|
+
const DEFAULT_MAX_COUNT = 1000;
|
|
10
|
+
const dbCache = new Map();
|
|
11
|
+
function getDefaultSchema(storeName) {
|
|
12
|
+
return {
|
|
13
|
+
stores: {
|
|
14
|
+
[storeName]: {
|
|
15
|
+
keyPath: DEFAULT_KEY_PATH,
|
|
16
|
+
indexes: DATE_INDEXES,
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
async function getDb(dbName, storeName) {
|
|
22
|
+
let db = dbCache.get(dbName);
|
|
23
|
+
if (db != null)
|
|
24
|
+
return db;
|
|
25
|
+
const schema = getDefaultSchema(storeName);
|
|
26
|
+
db = await initDb(dbName, { schema });
|
|
27
|
+
dbCache.set(dbName, db);
|
|
28
|
+
return db;
|
|
29
|
+
}
|
|
30
|
+
function setExpiryFields(value) {
|
|
31
|
+
const now = Date.now();
|
|
32
|
+
if (value.createdAt === undefined)
|
|
33
|
+
value.createdAt = now;
|
|
34
|
+
if (value.expiresAt === undefined)
|
|
35
|
+
value.expiresAt = now + DEFAULT_TTL_MS;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Create a client that exposes add, get, update, delete, removeDb.
|
|
39
|
+
* Init, schema, cleanup and eviction run automatically.
|
|
40
|
+
* @template T - Stored value shape (must include `id`). Omit for a generic client.
|
|
41
|
+
*/
|
|
42
|
+
export function createClient(options) {
|
|
43
|
+
const { dbName } = options;
|
|
44
|
+
const storeName = options.storeName ?? DEFAULT_STORE_NAME;
|
|
45
|
+
return {
|
|
46
|
+
async add(value) {
|
|
47
|
+
const db = await getDb(dbName, storeName);
|
|
48
|
+
setExpiryFields(value);
|
|
49
|
+
await db.put(storeName, value);
|
|
50
|
+
const count = await db.count(storeName);
|
|
51
|
+
if (count > DEFAULT_MAX_COUNT) {
|
|
52
|
+
await cleanWhenTooLarge(db, storeName, {
|
|
53
|
+
dateKey: "createdAt",
|
|
54
|
+
maxCount: DEFAULT_MAX_COUNT,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
await cleanOldEntries(db, storeName, {
|
|
58
|
+
dateKey: "expiresAt",
|
|
59
|
+
before: Date.now(),
|
|
60
|
+
});
|
|
61
|
+
},
|
|
62
|
+
async get(key) {
|
|
63
|
+
const db = await getDb(dbName, storeName);
|
|
64
|
+
return (await db.get(storeName, key));
|
|
65
|
+
},
|
|
66
|
+
async update(key, value) {
|
|
67
|
+
const db = await getDb(dbName, storeName);
|
|
68
|
+
const withKey = { ...value, [DEFAULT_KEY_PATH]: key };
|
|
69
|
+
await db.put(storeName, withKey);
|
|
70
|
+
},
|
|
71
|
+
async delete(key) {
|
|
72
|
+
const db = await getDb(dbName, storeName);
|
|
73
|
+
await db.delete(storeName, key);
|
|
74
|
+
},
|
|
75
|
+
async removeDb() {
|
|
76
|
+
const db = dbCache.get(dbName);
|
|
77
|
+
if (db != null) {
|
|
78
|
+
db.close();
|
|
79
|
+
dbCache.delete(dbName);
|
|
80
|
+
}
|
|
81
|
+
await deleteDB(dbName);
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,13 +1,2 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export type {
|
|
3
|
-
export { cleanOldEntries } from "./cleanOldEntries.js";
|
|
4
|
-
export type { CleanOldEntriesOptions } from "./cleanOldEntries.js";
|
|
5
|
-
export { cleanWhenTooLarge } from "./cleanWhenTooLarge.js";
|
|
6
|
-
export type { CleanWhenTooLargeOptions } from "./cleanWhenTooLarge.js";
|
|
7
|
-
export { putWithEviction } from "./putWithEviction.js";
|
|
8
|
-
export type { PutWithEvictionOptions } from "./putWithEviction.js";
|
|
9
|
-
export { deleteByKey, clearStore } from "./delete.js";
|
|
10
|
-
export { deleteDB } from "idb";
|
|
11
|
-
export type { IDBPDatabase, DBSchema } from "idb";
|
|
12
|
-
export type { SchemaDef, StoreDef } from "./schema.js";
|
|
13
|
-
export { fingerprint, applySchema } from "./schema.js";
|
|
1
|
+
export { createClient } from "./client.js";
|
|
2
|
+
export type { CreateClientOptions, IdbRefinedClient, StoredValue, } from "./client.js";
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export { cleanOldEntries } from "./cleanOldEntries.js";
|
|
3
|
-
export { cleanWhenTooLarge } from "./cleanWhenTooLarge.js";
|
|
4
|
-
export { putWithEviction } from "./putWithEviction.js";
|
|
5
|
-
export { deleteByKey, clearStore } from "./delete.js";
|
|
6
|
-
export { deleteDB } from "idb";
|
|
7
|
-
export { fingerprint, applySchema } from "./schema.js";
|
|
1
|
+
export { createClient } from "./client.js";
|
|
@@ -2,10 +2,16 @@ import type { IDBPDatabase } from "idb";
|
|
|
2
2
|
export interface PutWithEvictionOptions {
|
|
3
3
|
key?: IDBValidKey;
|
|
4
4
|
dateKey: string;
|
|
5
|
-
|
|
5
|
+
/** Evict oldest when store count exceeds this. */
|
|
6
|
+
maxCount?: number;
|
|
7
|
+
/** Absolute expiry timestamp (ms). If set, used for the dateKey field on the value. */
|
|
8
|
+
expiresAt?: number;
|
|
9
|
+
/** Relative expiry in seconds. Used when expiresAt is not set; then dateKey = now + ttlSeconds * 1000. */
|
|
10
|
+
ttlSeconds?: number;
|
|
6
11
|
}
|
|
7
12
|
/**
|
|
8
13
|
* Put a value in the store, then if count > maxCount evict oldest entries (by dateKey).
|
|
14
|
+
* Optionally set the dateKey field from expiresAt (absolute ms) or ttlSeconds (relative seconds).
|
|
9
15
|
* Value must include the keyPath field, or pass options.key.
|
|
10
16
|
*/
|
|
11
17
|
export declare function putWithEviction<T = unknown>(db: IDBPDatabase<unknown>, storeName: string, value: T, options: PutWithEvictionOptions): Promise<void>;
|
package/dist/putWithEviction.js
CHANGED
|
@@ -1,10 +1,24 @@
|
|
|
1
1
|
import { cleanWhenTooLarge } from "./cleanWhenTooLarge.js";
|
|
2
|
+
const DEFAULT_TTL_SECONDS = 3600;
|
|
3
|
+
const DEFAULT_MAX_COUNT = 1000;
|
|
2
4
|
/**
|
|
3
5
|
* Put a value in the store, then if count > maxCount evict oldest entries (by dateKey).
|
|
6
|
+
* Optionally set the dateKey field from expiresAt (absolute ms) or ttlSeconds (relative seconds).
|
|
4
7
|
* Value must include the keyPath field, or pass options.key.
|
|
5
8
|
*/
|
|
6
9
|
export async function putWithEviction(db, storeName, value, options) {
|
|
7
|
-
const { key, dateKey,
|
|
10
|
+
const { key, dateKey, expiresAt, ttlSeconds } = options;
|
|
11
|
+
const maxCount = options.maxCount ?? DEFAULT_MAX_COUNT;
|
|
12
|
+
if (typeof value === "object" && value !== null) {
|
|
13
|
+
const valueRecord = value;
|
|
14
|
+
if (expiresAt !== undefined) {
|
|
15
|
+
valueRecord[dateKey] = expiresAt;
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
valueRecord[dateKey] =
|
|
19
|
+
Date.now() + (ttlSeconds ?? DEFAULT_TTL_SECONDS) * 1000;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
8
22
|
await db.put(storeName, value, key);
|
|
9
23
|
const count = await db.count(storeName);
|
|
10
24
|
if (count > maxCount) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "idb-refined",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "Thin TypeScript IndexedDB helper: initDb (auto-version), clean by date/size, add with eviction, delete helpers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -16,6 +16,16 @@
|
|
|
16
16
|
"files": [
|
|
17
17
|
"dist"
|
|
18
18
|
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsc -p tsconfig.build.json",
|
|
21
|
+
"lint": "eslint src",
|
|
22
|
+
"lint:fix": "eslint src --fix",
|
|
23
|
+
"format": "prettier --write \"src/**/*.ts\"",
|
|
24
|
+
"format:check": "prettier --check \"src/**/*.ts\"",
|
|
25
|
+
"test": "vitest run",
|
|
26
|
+
"test:watch": "vitest",
|
|
27
|
+
"prepare": "husky"
|
|
28
|
+
},
|
|
19
29
|
"lint-staged": {
|
|
20
30
|
"src/**/*.ts": [
|
|
21
31
|
"eslint --fix",
|
|
@@ -46,14 +56,5 @@
|
|
|
46
56
|
"browser",
|
|
47
57
|
"storage"
|
|
48
58
|
],
|
|
49
|
-
"license": "MIT"
|
|
50
|
-
|
|
51
|
-
"build": "tsc -p tsconfig.build.json",
|
|
52
|
-
"lint": "eslint src",
|
|
53
|
-
"lint:fix": "eslint src --fix",
|
|
54
|
-
"format": "prettier --write \"src/**/*.ts\"",
|
|
55
|
-
"format:check": "prettier --check \"src/**/*.ts\"",
|
|
56
|
-
"test": "vitest run",
|
|
57
|
-
"test:watch": "vitest"
|
|
58
|
-
}
|
|
59
|
-
}
|
|
59
|
+
"license": "MIT"
|
|
60
|
+
}
|