@unikvs/indexeddb 0.0.12 → 0.0.13
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 +1 -1
- package/dist/index.d.ts +30 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +208 -0
- package/dist/index.js.map +1 -0
- package/package.json +18 -23
- package/src/index.ts +2 -0
- package/src/indexeddb.ts +235 -91
- package/dist/src/indexeddb.d.ts +0 -69
- package/dist/src/indexeddb.d.ts.map +0 -1
- package/dist/src/indexeddb.js +0 -119
package/README.md
CHANGED
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { IStorage } from "@unikvs/core";
|
|
2
|
+
//#region ../src/indexeddb.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* [API Reference](https://tai-kun.github.io/unikvs/packages/indexeddb#usage)
|
|
5
|
+
*/
|
|
6
|
+
type IndexeddbOptions = {};
|
|
7
|
+
/**
|
|
8
|
+
* [API Reference](https://tai-kun.github.io/unikvs/packages/indexeddb#usage)
|
|
9
|
+
*/
|
|
10
|
+
declare class Indexeddb implements IStorage {
|
|
11
|
+
#private;
|
|
12
|
+
readonly name: string;
|
|
13
|
+
/**
|
|
14
|
+
* [API Reference](https://tai-kun.github.io/unikvs/packages/indexeddb#usage)
|
|
15
|
+
*/
|
|
16
|
+
constructor(dbName?: string, storeName?: string, _options?: IndexeddbOptions);
|
|
17
|
+
get isOpen(): boolean;
|
|
18
|
+
open(c: IStorage.OpenContext): Promise<void>;
|
|
19
|
+
close(c: IStorage.CloseContext): Promise<void>;
|
|
20
|
+
write(c: IStorage.WriteContext, data: any): Promise<void>;
|
|
21
|
+
read(c: IStorage.ReadContext): Promise<any>;
|
|
22
|
+
exists(c: IStorage.ExistsContext): Promise<boolean>;
|
|
23
|
+
delete(c: IStorage.DeleteContext): Promise<void>;
|
|
24
|
+
clear(c: IStorage.ClearContext): Promise<void>;
|
|
25
|
+
getWritable(c: IStorage.GetWritableContext): WritableStream<Uint8Array>;
|
|
26
|
+
getReadable(c: IStorage.GetReadableContext): ReadableStream<Uint8Array>;
|
|
27
|
+
}
|
|
28
|
+
//#endregion
|
|
29
|
+
export { Indexeddb, type IndexeddbOptions };
|
|
30
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/indexeddb.ts"],"mappings":";;;;;KAMY;;;;cAuDS,qBAAqB;;WAuBxB;;;;EAKhB,YACE,iBACA,oBACA,WAAU;MASD;EAgBX,KAAkB,GAAG,SAAS,cAAc;EA8C5C,MAAmB,GAAG,SAAS,eAAe;EAc9C,MAAmB,GAAG,SAAS,cAAc,YAAY;EAmBzD,KAAkB,GAAG,SAAS,cAAc;EA6B5C,OAAoB,GAAG,SAAS,gBAAgB;EAahD,OAAoB,GAAG,SAAS,gBAAgB;EAShD,MAAmB,GAAG,SAAS,eAAe;EAS9C,YAAmB,GAAG,SAAS,qBAAqB,eAAe;EAkCnE,YAAmB,GAAG,SAAS,qBAAqB,eAAe"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { openDB } from "idb";
|
|
2
|
+
//#region \0@oxc-project+runtime@0.154.0/helpers/esm/checkPrivateRedeclaration.js
|
|
3
|
+
function _checkPrivateRedeclaration(e, t) {
|
|
4
|
+
if (t.has(e)) throw new TypeError("Cannot initialize the same private elements twice on an object");
|
|
5
|
+
}
|
|
6
|
+
//#endregion
|
|
7
|
+
//#region \0@oxc-project+runtime@0.154.0/helpers/esm/classPrivateMethodInitSpec.js
|
|
8
|
+
function _classPrivateMethodInitSpec(e, a) {
|
|
9
|
+
_checkPrivateRedeclaration(e, a), a.add(e);
|
|
10
|
+
}
|
|
11
|
+
//#endregion
|
|
12
|
+
//#region \0@oxc-project+runtime@0.154.0/helpers/esm/classPrivateFieldInitSpec.js
|
|
13
|
+
function _classPrivateFieldInitSpec(e, t, a) {
|
|
14
|
+
_checkPrivateRedeclaration(e, t), t.set(e, a);
|
|
15
|
+
}
|
|
16
|
+
//#endregion
|
|
17
|
+
//#region \0@oxc-project+runtime@0.154.0/helpers/esm/assertClassBrand.js
|
|
18
|
+
function _assertClassBrand(e, t, n) {
|
|
19
|
+
if ("function" == typeof e ? e === t : e.has(t)) return arguments.length < 3 ? t : n;
|
|
20
|
+
throw new TypeError("Private element is not present on this object");
|
|
21
|
+
}
|
|
22
|
+
//#endregion
|
|
23
|
+
//#region \0@oxc-project+runtime@0.154.0/helpers/esm/classPrivateFieldSet2.js
|
|
24
|
+
function _classPrivateFieldSet2(s, a, r) {
|
|
25
|
+
return s.set(_assertClassBrand(s, a), r), r;
|
|
26
|
+
}
|
|
27
|
+
//#endregion
|
|
28
|
+
//#region \0@oxc-project+runtime@0.154.0/helpers/esm/classPrivateFieldGet2.js
|
|
29
|
+
function _classPrivateFieldGet2(s, a) {
|
|
30
|
+
return s.get(_assertClassBrand(s, a));
|
|
31
|
+
}
|
|
32
|
+
//#endregion
|
|
33
|
+
//#region ../src/indexeddb.ts
|
|
34
|
+
function isStoredBlob(value) {
|
|
35
|
+
if (typeof value !== "object" || value === null) return false;
|
|
36
|
+
return value["unikvsBlob"] === true;
|
|
37
|
+
}
|
|
38
|
+
async function toStoredBlob(signal, data) {
|
|
39
|
+
const buffer = await data.arrayBuffer();
|
|
40
|
+
signal.throwIfAborted();
|
|
41
|
+
const isFile = typeof File !== "undefined" && data instanceof File;
|
|
42
|
+
return {
|
|
43
|
+
name: isFile ? data.name : "",
|
|
44
|
+
type: data.type,
|
|
45
|
+
buffer,
|
|
46
|
+
unikvsBlob: true,
|
|
47
|
+
unikvsFile: isFile,
|
|
48
|
+
lastModified: isFile ? data.lastModified : 0
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function fromStoredBlob(stored) {
|
|
52
|
+
if (stored.unikvsFile) return new File([stored.buffer], stored.name, {
|
|
53
|
+
type: stored.type,
|
|
54
|
+
lastModified: stored.lastModified
|
|
55
|
+
});
|
|
56
|
+
return new Blob([stored.buffer], { type: stored.type });
|
|
57
|
+
}
|
|
58
|
+
var _db = /* @__PURE__ */ new WeakMap();
|
|
59
|
+
var _opening = /* @__PURE__ */ new WeakMap();
|
|
60
|
+
var _dbName = /* @__PURE__ */ new WeakMap();
|
|
61
|
+
var _storeName = /* @__PURE__ */ new WeakMap();
|
|
62
|
+
var _Indexeddb_brand = /* @__PURE__ */ new WeakSet();
|
|
63
|
+
/**
|
|
64
|
+
* [API Reference](https://tai-kun.github.io/unikvs/packages/indexeddb#usage)
|
|
65
|
+
*/
|
|
66
|
+
var Indexeddb = class {
|
|
67
|
+
/**
|
|
68
|
+
* [API Reference](https://tai-kun.github.io/unikvs/packages/indexeddb#usage)
|
|
69
|
+
*/
|
|
70
|
+
constructor(dbName = "unikvs_db", storeName = "kvs_store", _options = {}) {
|
|
71
|
+
_classPrivateMethodInitSpec(this, _Indexeddb_brand);
|
|
72
|
+
_classPrivateFieldInitSpec(this, _db, void 0);
|
|
73
|
+
_classPrivateFieldInitSpec(this, _opening, void 0);
|
|
74
|
+
_classPrivateFieldInitSpec(this, _dbName, void 0);
|
|
75
|
+
_classPrivateFieldInitSpec(this, _storeName, void 0);
|
|
76
|
+
this.name = "Indexeddb";
|
|
77
|
+
_classPrivateFieldSet2(_db, this, null);
|
|
78
|
+
_classPrivateFieldSet2(_opening, this, null);
|
|
79
|
+
_classPrivateFieldSet2(_dbName, this, dbName);
|
|
80
|
+
_classPrivateFieldSet2(_storeName, this, storeName);
|
|
81
|
+
}
|
|
82
|
+
get isOpen() {
|
|
83
|
+
return _classPrivateFieldGet2(_db, this) !== null;
|
|
84
|
+
}
|
|
85
|
+
async open(c) {
|
|
86
|
+
c.signal.throwIfAborted();
|
|
87
|
+
if (_classPrivateFieldGet2(_db, this) !== null) return;
|
|
88
|
+
if (_classPrivateFieldGet2(_opening, this) !== null) {
|
|
89
|
+
await _classPrivateFieldGet2(_opening, this);
|
|
90
|
+
c.signal.throwIfAborted();
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const opening = openDB(_classPrivateFieldGet2(_dbName, this), 1, { upgrade: (upgraded) => {
|
|
94
|
+
if (!upgraded.objectStoreNames.contains(_classPrivateFieldGet2(_storeName, this))) upgraded.createObjectStore(_classPrivateFieldGet2(_storeName, this));
|
|
95
|
+
} });
|
|
96
|
+
_classPrivateFieldSet2(_opening, this, opening);
|
|
97
|
+
let opened = null;
|
|
98
|
+
try {
|
|
99
|
+
opened = await opening;
|
|
100
|
+
c.signal.throwIfAborted();
|
|
101
|
+
_classPrivateFieldSet2(_db, this, opened);
|
|
102
|
+
opened = null;
|
|
103
|
+
} catch (ex) {
|
|
104
|
+
if (opened !== null) try {
|
|
105
|
+
opened.close();
|
|
106
|
+
} catch {}
|
|
107
|
+
throw ex;
|
|
108
|
+
} finally {
|
|
109
|
+
_classPrivateFieldSet2(_opening, this, null);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
async close(c) {
|
|
113
|
+
c.signal.throwIfAborted();
|
|
114
|
+
const db = _classPrivateFieldGet2(_db, this);
|
|
115
|
+
if (db === null) throw new TypeError("Indexeddb is not open. Call open() first.");
|
|
116
|
+
await Promise.resolve();
|
|
117
|
+
db.close();
|
|
118
|
+
_classPrivateFieldSet2(_db, this, null);
|
|
119
|
+
}
|
|
120
|
+
async write(c, data) {
|
|
121
|
+
c.signal.throwIfAborted();
|
|
122
|
+
const db = _assertClassBrand(_Indexeddb_brand, this, _getDb).call(this);
|
|
123
|
+
if (typeof Blob !== "undefined" && data instanceof Blob) {
|
|
124
|
+
const stored = await toStoredBlob(c.signal, data);
|
|
125
|
+
await db.put(_classPrivateFieldGet2(_storeName, this), stored, c.key);
|
|
126
|
+
c.signal.throwIfAborted();
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
await db.put(_classPrivateFieldGet2(_storeName, this), data, c.key);
|
|
130
|
+
c.signal.throwIfAborted();
|
|
131
|
+
}
|
|
132
|
+
async read(c) {
|
|
133
|
+
c.signal.throwIfAborted();
|
|
134
|
+
const db = _assertClassBrand(_Indexeddb_brand, this, _getDb).call(this);
|
|
135
|
+
const value = await db.get(_classPrivateFieldGet2(_storeName, this), c.key);
|
|
136
|
+
c.signal.throwIfAborted();
|
|
137
|
+
if (isStoredBlob(value)) return fromStoredBlob(value);
|
|
138
|
+
if (value === void 0) {
|
|
139
|
+
const count = await db.count(_classPrivateFieldGet2(_storeName, this), c.key);
|
|
140
|
+
c.signal.throwIfAborted();
|
|
141
|
+
if (count === 0) throw new DOMException("A requested file or directory could not be found at the time an operation was processed.", "NotFoundError");
|
|
142
|
+
}
|
|
143
|
+
return value;
|
|
144
|
+
}
|
|
145
|
+
async exists(c) {
|
|
146
|
+
c.signal.throwIfAborted();
|
|
147
|
+
const count = await _assertClassBrand(_Indexeddb_brand, this, _getDb).call(this).count(_classPrivateFieldGet2(_storeName, this), c.key);
|
|
148
|
+
c.signal.throwIfAborted();
|
|
149
|
+
return count > 0;
|
|
150
|
+
}
|
|
151
|
+
async delete(c) {
|
|
152
|
+
c.signal.throwIfAborted();
|
|
153
|
+
await _assertClassBrand(_Indexeddb_brand, this, _getDb).call(this).delete(_classPrivateFieldGet2(_storeName, this), c.key);
|
|
154
|
+
c.signal.throwIfAborted();
|
|
155
|
+
}
|
|
156
|
+
async clear(c) {
|
|
157
|
+
c.signal.throwIfAborted();
|
|
158
|
+
await _assertClassBrand(_Indexeddb_brand, this, _getDb).call(this).clear(_classPrivateFieldGet2(_storeName, this));
|
|
159
|
+
c.signal.throwIfAborted();
|
|
160
|
+
}
|
|
161
|
+
getWritable(c) {
|
|
162
|
+
c.signal.throwIfAborted();
|
|
163
|
+
const chunks = [];
|
|
164
|
+
return new WritableStream({
|
|
165
|
+
abort: () => {
|
|
166
|
+
chunks.length = 0;
|
|
167
|
+
},
|
|
168
|
+
close: async () => {
|
|
169
|
+
c.signal.throwIfAborted();
|
|
170
|
+
const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
|
171
|
+
const merged = new Uint8Array(totalLength);
|
|
172
|
+
let offset = 0;
|
|
173
|
+
for (const chunk of chunks) {
|
|
174
|
+
merged.set(chunk, offset);
|
|
175
|
+
offset += chunk.length;
|
|
176
|
+
}
|
|
177
|
+
await _assertClassBrand(_Indexeddb_brand, this, _getDb).call(this).put(_classPrivateFieldGet2(_storeName, this), merged, c.key);
|
|
178
|
+
c.signal.throwIfAborted();
|
|
179
|
+
},
|
|
180
|
+
write: (chunk) => {
|
|
181
|
+
c.signal.throwIfAborted();
|
|
182
|
+
chunks.push(chunk);
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
getReadable(c) {
|
|
187
|
+
c.signal.throwIfAborted();
|
|
188
|
+
return new ReadableStream({ pull: async (controller) => {
|
|
189
|
+
try {
|
|
190
|
+
const data = await this.read(c);
|
|
191
|
+
c.signal.throwIfAborted();
|
|
192
|
+
controller.enqueue(data);
|
|
193
|
+
controller.close();
|
|
194
|
+
} catch (ex) {
|
|
195
|
+
controller.error(ex);
|
|
196
|
+
}
|
|
197
|
+
} });
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
function _getDb() {
|
|
201
|
+
const db = _classPrivateFieldGet2(_db, this);
|
|
202
|
+
if (db === null) throw new TypeError("Indexeddb is not open. Call open() first.");
|
|
203
|
+
return db;
|
|
204
|
+
}
|
|
205
|
+
//#endregion
|
|
206
|
+
export { Indexeddb };
|
|
207
|
+
|
|
208
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/indexeddb.ts"],"sourcesContent":["import type { IStorage } from \"@unikvs/core\";\nimport { type IDBPDatabase, openDB } from \"idb\";\n\n/**\n * [API Reference](https://tai-kun.github.io/unikvs/packages/indexeddb#usage)\n */\nexport type IndexeddbOptions = {};\n\n// WebKit で `Blob` を直接保存できない場合があるため、中間形式で保持します。\ntype StoredBlob = {\n readonly name: string;\n readonly type: string;\n readonly buffer: ArrayBuffer;\n readonly unikvsBlob: true;\n readonly unikvsFile: boolean;\n readonly lastModified: number;\n};\n\n// 保存した中間形式かどうかを判定します。\nfunction isStoredBlob(value: unknown): value is StoredBlob {\n if (typeof value !== \"object\" || value === null) {\n return false;\n }\n\n return (value as Record<string, unknown>)[\"unikvsBlob\"] === true;\n}\n\n// `Blob` を IndexedDB に保存できる中間形式に変換します。\n// `File` の名前と更新時刻も保持します。\nasync function toStoredBlob(signal: AbortSignal, data: Blob): Promise<StoredBlob> {\n const buffer = await data.arrayBuffer();\n\n signal.throwIfAborted();\n\n const isFile = typeof File !== \"undefined\" && data instanceof File;\n\n return {\n name: isFile ? data.name : \"\",\n type: data.type,\n buffer,\n unikvsBlob: true,\n unikvsFile: isFile,\n lastModified: isFile ? data.lastModified : 0,\n };\n}\n\n// 中間形式を `Blob` または `File` に戻します。\nfunction fromStoredBlob(stored: StoredBlob): Blob {\n if (stored.unikvsFile) {\n return new File([stored.buffer], stored.name, {\n type: stored.type,\n lastModified: stored.lastModified,\n });\n }\n\n return new Blob([stored.buffer], { type: stored.type });\n}\n\n/**\n * [API Reference](https://tai-kun.github.io/unikvs/packages/indexeddb#usage)\n */\nexport default class Indexeddb implements IStorage {\n /**\n * IndexedDB のデータベースインスタンスを保持します。\n *\n * ストレージがオープンされるまで null です。\n */\n #db: IDBPDatabase | null;\n\n /**\n * 並行 open を束ねるための進行中 Promise です。\n */\n #opening: Promise<IDBPDatabase> | null;\n\n /**\n * 使用するデータベース名です。\n */\n readonly #dbName: string;\n\n /**\n * 使用するオブジェクトストア名です。\n */\n readonly #storeName: string;\n\n public readonly name: string;\n\n /**\n * [API Reference](https://tai-kun.github.io/unikvs/packages/indexeddb#usage)\n */\n public constructor(\n dbName = \"unikvs_db\",\n storeName = \"kvs_store\",\n _options: IndexeddbOptions = {},\n ) {\n this.name = \"Indexeddb\";\n this.#db = null;\n this.#opening = null;\n this.#dbName = dbName;\n this.#storeName = storeName;\n }\n\n public get isOpen(): boolean {\n return this.#db !== null;\n }\n\n // 開いているデータベースを返します。\n // 未オープン時は `TypeError` を投げます。\n #getDb(): IDBPDatabase {\n const db = this.#db;\n\n if (db === null) {\n throw new TypeError(\"Indexeddb is not open. Call open() first.\");\n }\n\n return db;\n }\n\n public async open(c: IStorage.OpenContext): Promise<void> {\n c.signal.throwIfAborted();\n\n if (this.#db !== null) {\n return;\n }\n\n if (this.#opening !== null) {\n await this.#opening;\n c.signal.throwIfAborted();\n\n return;\n }\n\n const opening = openDB(this.#dbName, 1, {\n upgrade: (upgraded) => {\n // オブジェクトストアが存在しない場合は作成します。\n if (!upgraded.objectStoreNames.contains(this.#storeName)) {\n upgraded.createObjectStore(this.#storeName);\n }\n },\n });\n this.#opening = opening;\n\n let opened: IDBPDatabase | null = null;\n\n try {\n opened = await opening;\n c.signal.throwIfAborted();\n this.#db = opened;\n opened = null;\n } catch (ex) {\n if (opened !== null) {\n try {\n opened.close();\n } catch {\n // 閉鎖時の失敗は無視します。\n }\n }\n\n throw ex;\n } finally {\n this.#opening = null;\n }\n }\n\n public async close(c: IStorage.CloseContext): Promise<void> {\n c.signal.throwIfAborted();\n\n const db = this.#db;\n\n if (db === null) {\n throw new TypeError(\"Indexeddb is not open. Call open() first.\");\n }\n\n await Promise.resolve();\n db.close();\n this.#db = null;\n }\n\n public async write(c: IStorage.WriteContext, data: any): Promise<void> {\n c.signal.throwIfAborted();\n\n const db = this.#getDb();\n\n if (typeof Blob !== \"undefined\" && data instanceof Blob) {\n const stored = await toStoredBlob(c.signal, data);\n await db.put(this.#storeName, stored, c.key);\n\n c.signal.throwIfAborted();\n\n return;\n }\n\n await db.put(this.#storeName, data, c.key);\n\n c.signal.throwIfAborted();\n }\n\n public async read(c: IStorage.ReadContext): Promise<any> {\n c.signal.throwIfAborted();\n\n const db = this.#getDb();\n const value = await db.get(this.#storeName, c.key);\n\n c.signal.throwIfAborted();\n\n if (isStoredBlob(value)) {\n return fromStoredBlob(value);\n }\n\n if (value === undefined) {\n const count = await db.count(this.#storeName, c.key);\n\n c.signal.throwIfAborted();\n\n // 存在しない場合は DOMException (NotFoundError) を投げます。\n if (count === 0) {\n throw new DOMException(\n \"A requested file or directory could not be found at the time an operation was processed.\",\n \"NotFoundError\",\n );\n }\n }\n\n return value;\n }\n\n public async exists(c: IStorage.ExistsContext): Promise<boolean> {\n c.signal.throwIfAborted();\n\n const db = this.#getDb();\n\n // 件数が 1 であれば存在し、0 であれば存在しません。\n const count = await db.count(this.#storeName, c.key);\n\n c.signal.throwIfAborted();\n\n return count > 0;\n }\n\n public async delete(c: IStorage.DeleteContext): Promise<void> {\n c.signal.throwIfAborted();\n\n const db = this.#getDb();\n await db.delete(this.#storeName, c.key);\n\n c.signal.throwIfAborted();\n }\n\n public async clear(c: IStorage.ClearContext): Promise<void> {\n c.signal.throwIfAborted();\n\n const db = this.#getDb();\n await db.clear(this.#storeName);\n\n c.signal.throwIfAborted();\n }\n\n public getWritable(c: IStorage.GetWritableContext): WritableStream<Uint8Array> {\n c.signal.throwIfAborted();\n\n const chunks: Uint8Array[] = [];\n\n return new WritableStream<Uint8Array>({\n abort: (): void => {\n chunks.length = 0;\n },\n close: async (): Promise<void> => {\n // 保存は close のときだけ行うため、中断時はここまでのチャンクを破棄できます。\n c.signal.throwIfAborted();\n\n const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);\n const merged = new Uint8Array(totalLength);\n let offset = 0;\n\n for (const chunk of chunks) {\n merged.set(chunk, offset);\n offset += chunk.length;\n }\n\n const db = this.#getDb();\n await db.put(this.#storeName, merged, c.key);\n\n c.signal.throwIfAborted();\n },\n write: (chunk): void => {\n c.signal.throwIfAborted();\n chunks.push(chunk);\n },\n });\n }\n\n public getReadable(c: IStorage.GetReadableContext): ReadableStream<Uint8Array> {\n c.signal.throwIfAborted();\n\n return new ReadableStream<Uint8Array>({\n pull: async (controller: ReadableStreamDefaultController<Uint8Array>): Promise<void> => {\n try {\n const data = await this.read(c as unknown as IStorage.ReadContext);\n\n c.signal.throwIfAborted();\n controller.enqueue(data as Uint8Array);\n controller.close();\n } catch (ex) {\n controller.error(ex);\n }\n },\n });\n }\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,49 +1,44 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unikvs/indexeddb",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.13",
|
|
4
4
|
"description": "Storage plugin that stores arbitrary data in the browser IndexedDB.",
|
|
5
|
-
"homepage": "https://
|
|
5
|
+
"homepage": "https://tai-kun.github.io/unikvs/",
|
|
6
6
|
"license": "MIT",
|
|
7
|
-
"author": "tai-kun",
|
|
8
7
|
"repository": {
|
|
9
|
-
"url": "https://github.com/tai-kun/unikvs"
|
|
8
|
+
"url": "https://github.com/tai-kun/unikvs",
|
|
9
|
+
"directory": "packages/indexeddb"
|
|
10
10
|
},
|
|
11
11
|
"files": [
|
|
12
12
|
"dist",
|
|
13
13
|
"src",
|
|
14
14
|
"package.json",
|
|
15
|
-
"
|
|
16
|
-
"
|
|
15
|
+
"LICENSE",
|
|
16
|
+
"README.md"
|
|
17
17
|
],
|
|
18
18
|
"type": "module",
|
|
19
19
|
"sideEffects": false,
|
|
20
|
-
"main": "./dist/src/index.js",
|
|
21
|
-
"types": "./dist/src/index.d.ts",
|
|
22
20
|
"exports": {
|
|
23
21
|
".": {
|
|
24
|
-
"types": "./dist/
|
|
25
|
-
"default": "./dist/
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"default": "./dist/index.js"
|
|
26
24
|
}
|
|
27
25
|
},
|
|
28
26
|
"dependencies": {
|
|
29
|
-
"@unikvs/core": "0.0.
|
|
30
|
-
"idb": "8.0.
|
|
27
|
+
"@unikvs/core": "0.0.13",
|
|
28
|
+
"idb": "^8.0.4"
|
|
31
29
|
},
|
|
32
30
|
"devDependencies": {
|
|
33
|
-
"@
|
|
34
|
-
"@
|
|
35
|
-
"@
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"npm-check-updates": "20.0.2",
|
|
39
|
-
"playwright": "1.63.0",
|
|
40
|
-
"vite": "8.3.0",
|
|
41
|
-
"vitest": "4.1.11"
|
|
31
|
+
"@unikvs/testing": "0.0.0",
|
|
32
|
+
"@vitest/browser-playwright": "5.0.5",
|
|
33
|
+
"@vitest/coverage-istanbul": "5.0.5",
|
|
34
|
+
"playwright": "1.64.1",
|
|
35
|
+
"vite-plus": "1.3.0"
|
|
42
36
|
},
|
|
43
37
|
"peerDependencies": {
|
|
44
|
-
"@logtape/logtape": "2.0.0"
|
|
38
|
+
"@logtape/logtape": "^2.0.0"
|
|
45
39
|
},
|
|
40
|
+
"readme": "README.md",
|
|
46
41
|
"scripts": {
|
|
47
|
-
"build": "
|
|
42
|
+
"build": "vp -C .config pack"
|
|
48
43
|
}
|
|
49
44
|
}
|
package/src/index.ts
ADDED
package/src/indexeddb.ts
CHANGED
|
@@ -1,5 +1,60 @@
|
|
|
1
1
|
import type { IStorage } from "@unikvs/core";
|
|
2
|
-
import {
|
|
2
|
+
import { type IDBPDatabase, openDB } from "idb";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* [API Reference](https://tai-kun.github.io/unikvs/packages/indexeddb#usage)
|
|
6
|
+
*/
|
|
7
|
+
export type IndexeddbOptions = {};
|
|
8
|
+
|
|
9
|
+
// WebKit で `Blob` を直接保存できない場合があるため、中間形式で保持します。
|
|
10
|
+
type StoredBlob = {
|
|
11
|
+
readonly name: string;
|
|
12
|
+
readonly type: string;
|
|
13
|
+
readonly buffer: ArrayBuffer;
|
|
14
|
+
readonly unikvsBlob: true;
|
|
15
|
+
readonly unikvsFile: boolean;
|
|
16
|
+
readonly lastModified: number;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// 保存した中間形式かどうかを判定します。
|
|
20
|
+
function isStoredBlob(value: unknown): value is StoredBlob {
|
|
21
|
+
if (typeof value !== "object" || value === null) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return (value as Record<string, unknown>)["unikvsBlob"] === true;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// `Blob` を IndexedDB に保存できる中間形式に変換します。
|
|
29
|
+
// `File` の名前と更新時刻も保持します。
|
|
30
|
+
async function toStoredBlob(signal: AbortSignal, data: Blob): Promise<StoredBlob> {
|
|
31
|
+
const buffer = await data.arrayBuffer();
|
|
32
|
+
|
|
33
|
+
signal.throwIfAborted();
|
|
34
|
+
|
|
35
|
+
const isFile = typeof File !== "undefined" && data instanceof File;
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
name: isFile ? data.name : "",
|
|
39
|
+
type: data.type,
|
|
40
|
+
buffer,
|
|
41
|
+
unikvsBlob: true,
|
|
42
|
+
unikvsFile: isFile,
|
|
43
|
+
lastModified: isFile ? data.lastModified : 0,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// 中間形式を `Blob` または `File` に戻します。
|
|
48
|
+
function fromStoredBlob(stored: StoredBlob): Blob {
|
|
49
|
+
if (stored.unikvsFile) {
|
|
50
|
+
return new File([stored.buffer], stored.name, {
|
|
51
|
+
type: stored.type,
|
|
52
|
+
lastModified: stored.lastModified,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return new Blob([stored.buffer], { type: stored.type });
|
|
57
|
+
}
|
|
3
58
|
|
|
4
59
|
/**
|
|
5
60
|
* [API Reference](https://tai-kun.github.io/unikvs/packages/indexeddb#usage)
|
|
@@ -10,154 +65,243 @@ export default class Indexeddb implements IStorage {
|
|
|
10
65
|
*
|
|
11
66
|
* ストレージがオープンされるまで null です。
|
|
12
67
|
*/
|
|
13
|
-
|
|
68
|
+
#db: IDBPDatabase | null;
|
|
14
69
|
|
|
15
70
|
/**
|
|
16
|
-
*
|
|
71
|
+
* 並行 open を束ねるための進行中 Promise です。
|
|
17
72
|
*/
|
|
18
|
-
|
|
73
|
+
#opening: Promise<IDBPDatabase> | null;
|
|
19
74
|
|
|
20
75
|
/**
|
|
21
|
-
*
|
|
76
|
+
* 使用するデータベース名です。
|
|
22
77
|
*/
|
|
23
|
-
|
|
78
|
+
readonly #dbName: string;
|
|
24
79
|
|
|
25
80
|
/**
|
|
26
|
-
*
|
|
81
|
+
* 使用するオブジェクトストア名です。
|
|
27
82
|
*/
|
|
83
|
+
readonly #storeName: string;
|
|
84
|
+
|
|
28
85
|
public readonly name: string;
|
|
29
86
|
|
|
30
87
|
/**
|
|
31
88
|
* [API Reference](https://tai-kun.github.io/unikvs/packages/indexeddb#usage)
|
|
32
89
|
*/
|
|
33
|
-
public constructor(
|
|
90
|
+
public constructor(
|
|
91
|
+
dbName = "unikvs_db",
|
|
92
|
+
storeName = "kvs_store",
|
|
93
|
+
_options: IndexeddbOptions = {},
|
|
94
|
+
) {
|
|
34
95
|
this.name = "Indexeddb";
|
|
35
|
-
this
|
|
36
|
-
this
|
|
37
|
-
this
|
|
96
|
+
this.#db = null;
|
|
97
|
+
this.#opening = null;
|
|
98
|
+
this.#dbName = dbName;
|
|
99
|
+
this.#storeName = storeName;
|
|
38
100
|
}
|
|
39
101
|
|
|
40
|
-
/**
|
|
41
|
-
* [API Reference](https://tai-kun.github.io/unikvs/packages/indexeddb#usage)
|
|
42
|
-
*/
|
|
43
102
|
public get isOpen(): boolean {
|
|
44
|
-
return this
|
|
103
|
+
return this.#db !== null;
|
|
45
104
|
}
|
|
46
105
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
106
|
+
// 開いているデータベースを返します。
|
|
107
|
+
// 未オープン時は `TypeError` を投げます。
|
|
108
|
+
#getDb(): IDBPDatabase {
|
|
109
|
+
const db = this.#db;
|
|
110
|
+
|
|
111
|
+
if (db === null) {
|
|
112
|
+
throw new TypeError("Indexeddb is not open. Call open() first.");
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return db;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
public async open(c: IStorage.OpenContext): Promise<void> {
|
|
119
|
+
c.signal.throwIfAborted();
|
|
120
|
+
|
|
121
|
+
if (this.#db !== null) {
|
|
52
122
|
return;
|
|
53
123
|
}
|
|
54
124
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
125
|
+
if (this.#opening !== null) {
|
|
126
|
+
await this.#opening;
|
|
127
|
+
c.signal.throwIfAborted();
|
|
128
|
+
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const opening = openDB(this.#dbName, 1, {
|
|
133
|
+
upgrade: (upgraded) => {
|
|
134
|
+
// オブジェクトストアが存在しない場合は作成します。
|
|
135
|
+
if (!upgraded.objectStoreNames.contains(this.#storeName)) {
|
|
136
|
+
upgraded.createObjectStore(this.#storeName);
|
|
60
137
|
}
|
|
61
138
|
},
|
|
62
139
|
});
|
|
140
|
+
this.#opening = opening;
|
|
141
|
+
|
|
142
|
+
let opened: IDBPDatabase | null = null;
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
opened = await opening;
|
|
146
|
+
c.signal.throwIfAborted();
|
|
147
|
+
this.#db = opened;
|
|
148
|
+
opened = null;
|
|
149
|
+
} catch (ex) {
|
|
150
|
+
if (opened !== null) {
|
|
151
|
+
try {
|
|
152
|
+
opened.close();
|
|
153
|
+
} catch {
|
|
154
|
+
// 閉鎖時の失敗は無視します。
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
throw ex;
|
|
159
|
+
} finally {
|
|
160
|
+
this.#opening = null;
|
|
161
|
+
}
|
|
63
162
|
}
|
|
64
163
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
164
|
+
public async close(c: IStorage.CloseContext): Promise<void> {
|
|
165
|
+
c.signal.throwIfAborted();
|
|
166
|
+
|
|
167
|
+
const db = this.#db;
|
|
168
|
+
|
|
169
|
+
if (db === null) {
|
|
170
|
+
throw new TypeError("Indexeddb is not open. Call open() first.");
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
await Promise.resolve();
|
|
174
|
+
db.close();
|
|
175
|
+
this.#db = null;
|
|
71
176
|
}
|
|
72
177
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
178
|
+
public async write(c: IStorage.WriteContext, data: any): Promise<void> {
|
|
179
|
+
c.signal.throwIfAborted();
|
|
180
|
+
|
|
181
|
+
const db = this.#getDb();
|
|
182
|
+
|
|
183
|
+
if (typeof Blob !== "undefined" && data instanceof Blob) {
|
|
184
|
+
const stored = await toStoredBlob(c.signal, data);
|
|
185
|
+
await db.put(this.#storeName, stored, c.key);
|
|
186
|
+
|
|
187
|
+
c.signal.throwIfAborted();
|
|
188
|
+
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
await db.put(this.#storeName, data, c.key);
|
|
193
|
+
|
|
194
|
+
c.signal.throwIfAborted();
|
|
79
195
|
}
|
|
80
196
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
197
|
+
public async read(c: IStorage.ReadContext): Promise<any> {
|
|
198
|
+
c.signal.throwIfAborted();
|
|
199
|
+
|
|
200
|
+
const db = this.#getDb();
|
|
201
|
+
const value = await db.get(this.#storeName, c.key);
|
|
202
|
+
|
|
203
|
+
c.signal.throwIfAborted();
|
|
204
|
+
|
|
205
|
+
if (isStoredBlob(value)) {
|
|
206
|
+
return fromStoredBlob(value);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (value === undefined) {
|
|
210
|
+
const count = await db.count(this.#storeName, c.key);
|
|
211
|
+
|
|
212
|
+
c.signal.throwIfAborted();
|
|
213
|
+
|
|
214
|
+
// 存在しない場合は DOMException (NotFoundError) を投げます。
|
|
215
|
+
if (count === 0) {
|
|
216
|
+
throw new DOMException(
|
|
217
|
+
"A requested file or directory could not be found at the time an operation was processed.",
|
|
218
|
+
"NotFoundError",
|
|
219
|
+
);
|
|
220
|
+
}
|
|
92
221
|
}
|
|
93
222
|
|
|
94
|
-
return
|
|
223
|
+
return value;
|
|
95
224
|
}
|
|
96
225
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
226
|
+
public async exists(c: IStorage.ExistsContext): Promise<boolean> {
|
|
227
|
+
c.signal.throwIfAborted();
|
|
228
|
+
|
|
229
|
+
const db = this.#getDb();
|
|
230
|
+
|
|
231
|
+
// 件数が 1 であれば存在し、0 であれば存在しません。
|
|
232
|
+
const count = await db.count(this.#storeName, c.key);
|
|
233
|
+
|
|
234
|
+
c.signal.throwIfAborted();
|
|
102
235
|
|
|
103
|
-
// count() はキーが存在すれば 1 を、存在しなければ 0 を返します
|
|
104
|
-
const count = await this.db!.count(this.storeName, key);
|
|
105
236
|
return count > 0;
|
|
106
237
|
}
|
|
107
238
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
239
|
+
public async delete(c: IStorage.DeleteContext): Promise<void> {
|
|
240
|
+
c.signal.throwIfAborted();
|
|
241
|
+
|
|
242
|
+
const db = this.#getDb();
|
|
243
|
+
await db.delete(this.#storeName, c.key);
|
|
113
244
|
|
|
114
|
-
|
|
245
|
+
c.signal.throwIfAborted();
|
|
115
246
|
}
|
|
116
247
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
await
|
|
248
|
+
public async clear(c: IStorage.ClearContext): Promise<void> {
|
|
249
|
+
c.signal.throwIfAborted();
|
|
250
|
+
|
|
251
|
+
const db = this.#getDb();
|
|
252
|
+
await db.clear(this.#storeName);
|
|
253
|
+
|
|
254
|
+
c.signal.throwIfAborted();
|
|
122
255
|
}
|
|
123
256
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
public getWritable(args: Pick<IStorage.GetWritableArgs, "key">): WritableStream<Uint8Array> {
|
|
128
|
-
const { key } = args;
|
|
257
|
+
public getWritable(c: IStorage.GetWritableContext): WritableStream<Uint8Array> {
|
|
258
|
+
c.signal.throwIfAborted();
|
|
259
|
+
|
|
129
260
|
const chunks: Uint8Array[] = [];
|
|
130
261
|
|
|
131
|
-
return new WritableStream({
|
|
132
|
-
|
|
133
|
-
chunks.
|
|
262
|
+
return new WritableStream<Uint8Array>({
|
|
263
|
+
abort: (): void => {
|
|
264
|
+
chunks.length = 0;
|
|
134
265
|
},
|
|
135
|
-
close: async () => {
|
|
136
|
-
|
|
137
|
-
|
|
266
|
+
close: async (): Promise<void> => {
|
|
267
|
+
// 保存は close のときだけ行うため、中断時はここまでのチャンクを破棄できます。
|
|
268
|
+
c.signal.throwIfAborted();
|
|
269
|
+
|
|
270
|
+
const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
|
271
|
+
const merged = new Uint8Array(totalLength);
|
|
138
272
|
let offset = 0;
|
|
139
273
|
|
|
140
274
|
for (const chunk of chunks) {
|
|
141
|
-
|
|
275
|
+
merged.set(chunk, offset);
|
|
142
276
|
offset += chunk.length;
|
|
143
277
|
}
|
|
144
278
|
|
|
145
|
-
|
|
279
|
+
const db = this.#getDb();
|
|
280
|
+
await db.put(this.#storeName, merged, c.key);
|
|
281
|
+
|
|
282
|
+
c.signal.throwIfAborted();
|
|
283
|
+
},
|
|
284
|
+
write: (chunk): void => {
|
|
285
|
+
c.signal.throwIfAborted();
|
|
286
|
+
chunks.push(chunk);
|
|
146
287
|
},
|
|
147
288
|
});
|
|
148
289
|
}
|
|
149
290
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
291
|
+
public getReadable(c: IStorage.GetReadableContext): ReadableStream<Uint8Array> {
|
|
292
|
+
c.signal.throwIfAborted();
|
|
293
|
+
|
|
294
|
+
return new ReadableStream<Uint8Array>({
|
|
295
|
+
pull: async (controller: ReadableStreamDefaultController<Uint8Array>): Promise<void> => {
|
|
296
|
+
try {
|
|
297
|
+
const data = await this.read(c as unknown as IStorage.ReadContext);
|
|
298
|
+
|
|
299
|
+
c.signal.throwIfAborted();
|
|
300
|
+
controller.enqueue(data as Uint8Array);
|
|
301
|
+
controller.close();
|
|
302
|
+
} catch (ex) {
|
|
303
|
+
controller.error(ex);
|
|
304
|
+
}
|
|
161
305
|
},
|
|
162
306
|
});
|
|
163
307
|
}
|
package/dist/src/indexeddb.d.ts
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import type { IStorage } from "@unikvs/core";
|
|
2
|
-
/**
|
|
3
|
-
* [API Reference](https://tai-kun.github.io/unikvs/packages/indexeddb#usage)
|
|
4
|
-
*/
|
|
5
|
-
export default class Indexeddb implements IStorage {
|
|
6
|
-
/**
|
|
7
|
-
* IndexedDB のデータベースインスタンスを保持します。
|
|
8
|
-
*
|
|
9
|
-
* ストレージがオープンされるまで null です。
|
|
10
|
-
*/
|
|
11
|
-
private db;
|
|
12
|
-
/**
|
|
13
|
-
* 使用するデータベース名です。
|
|
14
|
-
*/
|
|
15
|
-
private readonly dbName;
|
|
16
|
-
/**
|
|
17
|
-
* 使用するオブジェクトストア名です。
|
|
18
|
-
*/
|
|
19
|
-
private readonly storeName;
|
|
20
|
-
/**
|
|
21
|
-
* [API Reference](https://tai-kun.github.io/unikvs/packages/indexeddb#usage)
|
|
22
|
-
*/
|
|
23
|
-
readonly name: string;
|
|
24
|
-
/**
|
|
25
|
-
* [API Reference](https://tai-kun.github.io/unikvs/packages/indexeddb#usage)
|
|
26
|
-
*/
|
|
27
|
-
constructor(dbName?: string, storeName?: string);
|
|
28
|
-
/**
|
|
29
|
-
* [API Reference](https://tai-kun.github.io/unikvs/packages/indexeddb#usage)
|
|
30
|
-
*/
|
|
31
|
-
get isOpen(): boolean;
|
|
32
|
-
/**
|
|
33
|
-
* [API Reference](https://tai-kun.github.io/unikvs/packages/indexeddb#usage)
|
|
34
|
-
*/
|
|
35
|
-
open(): Promise<void>;
|
|
36
|
-
/**
|
|
37
|
-
* [API Reference](https://tai-kun.github.io/unikvs/packages/indexeddb#usage)
|
|
38
|
-
*/
|
|
39
|
-
close(): Promise<void>;
|
|
40
|
-
/**
|
|
41
|
-
* [API Reference](https://tai-kun.github.io/unikvs/packages/indexeddb#data)
|
|
42
|
-
*/
|
|
43
|
-
write(args: Pick<IStorage.WriteArgs<any>, "key" | "data">): Promise<void>;
|
|
44
|
-
/**
|
|
45
|
-
* [API Reference](https://tai-kun.github.io/unikvs/packages/indexeddb#data)
|
|
46
|
-
*/
|
|
47
|
-
read(args: Pick<IStorage.ReadArgs, "key">): Promise<any>;
|
|
48
|
-
/**
|
|
49
|
-
* [API Reference](https://tai-kun.github.io/unikvs/packages/indexeddb#usage)
|
|
50
|
-
*/
|
|
51
|
-
exists(args: Pick<IStorage.ExistsArgs, "key">): Promise<boolean>;
|
|
52
|
-
/**
|
|
53
|
-
* [API Reference](https://tai-kun.github.io/unikvs/packages/indexeddb#usage)
|
|
54
|
-
*/
|
|
55
|
-
delete(args: Pick<IStorage.DeleteArgs, "key">): Promise<void>;
|
|
56
|
-
/**
|
|
57
|
-
* [API Reference](https://tai-kun.github.io/unikvs/packages/indexeddb#usage)
|
|
58
|
-
*/
|
|
59
|
-
clear(): Promise<void>;
|
|
60
|
-
/**
|
|
61
|
-
* [API Reference](https://tai-kun.github.io/unikvs/packages/indexeddb#data)
|
|
62
|
-
*/
|
|
63
|
-
getWritable(args: Pick<IStorage.GetWritableArgs, "key">): WritableStream<Uint8Array>;
|
|
64
|
-
/**
|
|
65
|
-
* [API Reference](https://tai-kun.github.io/unikvs/packages/indexeddb#data)
|
|
66
|
-
*/
|
|
67
|
-
getReadable(args: Pick<IStorage.GetReadableArgs, "key">): ReadableStream<Uint8Array>;
|
|
68
|
-
}
|
|
69
|
-
//# sourceMappingURL=indexeddb.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"indexeddb.d.ts","sourceRoot":"","sources":["../../src/indexeddb.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAG7C;;GAEG;AACH,MAAM,CAAC,OAAO,OAAO,SAAU,YAAW,QAAQ;IAChD;;;;OAIG;IACH,OAAO,CAAC,EAAE,CAAsB;IAEhC;;OAEG;IACH,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAEhC;;OAEG;IACH,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IAEnC;;OAEG;IACH,SAAgB,IAAI,EAAE,MAAM,CAAC;IAE7B;;OAEG;IACH,YAAmB,MAAM,GAAE,MAAoB,EAAE,SAAS,GAAE,MAAoB,EAK/E;IAED;;OAEG;IACH,IAAW,MAAM,IAAI,OAAO,CAE3B;IAED;;OAEG;IACU,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAajC;IAED;;OAEG;IACU,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAGlC;IAED;;OAEG;IACU,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAGrF;IAED;;OAEG;IACU,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,KAAK,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,CAWpE;IAED;;OAEG;IACU,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,UAAU,EAAE,KAAK,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAM5E;IAED;;OAEG;IACU,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,UAAU,EAAE,KAAK,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAIzE;IAED;;OAEG;IACU,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAElC;IAED;;OAEG;IACI,WAAW,CAAC,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,eAAe,EAAE,KAAK,CAAC,GAAG,cAAc,CAAC,UAAU,CAAC,CAqB1F;IAED;;OAEG;IACI,WAAW,CAAC,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,eAAe,EAAE,KAAK,CAAC,GAAG,cAAc,CAAC,UAAU,CAAC,CAU1F;CACF"}
|
package/dist/src/indexeddb.js
DELETED
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
import { openDB } from "idb";
|
|
2
|
-
/**
|
|
3
|
-
* [API Reference](https://tai-kun.github.io/unikvs/packages/indexeddb#usage)
|
|
4
|
-
*/
|
|
5
|
-
export default class Indexeddb {
|
|
6
|
-
/**
|
|
7
|
-
* [API Reference](https://tai-kun.github.io/unikvs/packages/indexeddb#usage)
|
|
8
|
-
*/
|
|
9
|
-
constructor(dbName = "unikvs_db", storeName = "kvs_store") {
|
|
10
|
-
this.name = "Indexeddb";
|
|
11
|
-
this.db = null;
|
|
12
|
-
this.dbName = dbName;
|
|
13
|
-
this.storeName = storeName;
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* [API Reference](https://tai-kun.github.io/unikvs/packages/indexeddb#usage)
|
|
17
|
-
*/
|
|
18
|
-
get isOpen() {
|
|
19
|
-
return this.db !== null;
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* [API Reference](https://tai-kun.github.io/unikvs/packages/indexeddb#usage)
|
|
23
|
-
*/
|
|
24
|
-
async open() {
|
|
25
|
-
if (this.db) {
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
this.db = await openDB(this.dbName, 1, {
|
|
29
|
-
upgrade: (db) => {
|
|
30
|
-
// オブジェクトストアが存在しない場合は作成します
|
|
31
|
-
if (!db.objectStoreNames.contains(this.storeName)) {
|
|
32
|
-
db.createObjectStore(this.storeName);
|
|
33
|
-
}
|
|
34
|
-
},
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* [API Reference](https://tai-kun.github.io/unikvs/packages/indexeddb#usage)
|
|
39
|
-
*/
|
|
40
|
-
async close() {
|
|
41
|
-
this.db.close();
|
|
42
|
-
this.db = null;
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* [API Reference](https://tai-kun.github.io/unikvs/packages/indexeddb#data)
|
|
46
|
-
*/
|
|
47
|
-
async write(args) {
|
|
48
|
-
const { key, data } = args;
|
|
49
|
-
await this.db.put(this.storeName, data, key);
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* [API Reference](https://tai-kun.github.io/unikvs/packages/indexeddb#data)
|
|
53
|
-
*/
|
|
54
|
-
async read(args) {
|
|
55
|
-
const { key } = args;
|
|
56
|
-
// Opfs の挙動に合わせて、存在しない場合は DOMException (NotFoundError) を投げます
|
|
57
|
-
if (!(await this.exists({ key }))) {
|
|
58
|
-
throw new DOMException(`A requested file or directory could not be found at the time an operation was processed.`, "NotFoundError");
|
|
59
|
-
}
|
|
60
|
-
return await this.db.get(this.storeName, key);
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* [API Reference](https://tai-kun.github.io/unikvs/packages/indexeddb#usage)
|
|
64
|
-
*/
|
|
65
|
-
async exists(args) {
|
|
66
|
-
const { key } = args;
|
|
67
|
-
// count() はキーが存在すれば 1 を、存在しなければ 0 を返します
|
|
68
|
-
const count = await this.db.count(this.storeName, key);
|
|
69
|
-
return count > 0;
|
|
70
|
-
}
|
|
71
|
-
/**
|
|
72
|
-
* [API Reference](https://tai-kun.github.io/unikvs/packages/indexeddb#usage)
|
|
73
|
-
*/
|
|
74
|
-
async delete(args) {
|
|
75
|
-
const { key } = args;
|
|
76
|
-
await this.db.delete(this.storeName, key);
|
|
77
|
-
}
|
|
78
|
-
/**
|
|
79
|
-
* [API Reference](https://tai-kun.github.io/unikvs/packages/indexeddb#usage)
|
|
80
|
-
*/
|
|
81
|
-
async clear() {
|
|
82
|
-
await this.db.clear(this.storeName);
|
|
83
|
-
}
|
|
84
|
-
/**
|
|
85
|
-
* [API Reference](https://tai-kun.github.io/unikvs/packages/indexeddb#data)
|
|
86
|
-
*/
|
|
87
|
-
getWritable(args) {
|
|
88
|
-
const { key } = args;
|
|
89
|
-
const chunks = [];
|
|
90
|
-
return new WritableStream({
|
|
91
|
-
write: (chunk) => {
|
|
92
|
-
chunks.push(chunk);
|
|
93
|
-
},
|
|
94
|
-
close: async () => {
|
|
95
|
-
const totalLength = chunks.reduce((acc, chunk) => acc + chunk.length, 0);
|
|
96
|
-
const result = new Uint8Array(totalLength);
|
|
97
|
-
let offset = 0;
|
|
98
|
-
for (const chunk of chunks) {
|
|
99
|
-
result.set(chunk, offset);
|
|
100
|
-
offset += chunk.length;
|
|
101
|
-
}
|
|
102
|
-
await this.db.put(this.storeName, result, key);
|
|
103
|
-
},
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
/**
|
|
107
|
-
* [API Reference](https://tai-kun.github.io/unikvs/packages/indexeddb#data)
|
|
108
|
-
*/
|
|
109
|
-
getReadable(args) {
|
|
110
|
-
const { key } = args;
|
|
111
|
-
return new ReadableStream({
|
|
112
|
-
pull: async (controller) => {
|
|
113
|
-
const data = await this.read({ key });
|
|
114
|
-
controller.enqueue(data);
|
|
115
|
-
controller.close();
|
|
116
|
-
},
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
}
|