@unikvs/indexeddb 0.0.13 → 0.0.14
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/dist/index.d.ts +72 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +96 -0
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/indexeddb.ts +137 -19
package/dist/index.d.ts
CHANGED
|
@@ -9,20 +9,92 @@ type IndexeddbOptions = {};
|
|
|
9
9
|
*/
|
|
10
10
|
declare class Indexeddb implements IStorage {
|
|
11
11
|
#private;
|
|
12
|
+
/**
|
|
13
|
+
* ストレージ名です。常に `Indexeddb` を返します。
|
|
14
|
+
*/
|
|
12
15
|
readonly name: string;
|
|
13
16
|
/**
|
|
14
17
|
* [API Reference](https://tai-kun.github.io/unikvs/packages/indexeddb#usage)
|
|
15
18
|
*/
|
|
16
19
|
constructor(dbName?: string, storeName?: string, _options?: IndexeddbOptions);
|
|
20
|
+
/**
|
|
21
|
+
* ストレージが開いているかどうかを返します。
|
|
22
|
+
*
|
|
23
|
+
* @returns データベース接続があれば true を返します。
|
|
24
|
+
*/
|
|
17
25
|
get isOpen(): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* データベースを開きます。並行した呼び出しは一つの接続処理に束ねます。
|
|
28
|
+
*
|
|
29
|
+
* @param c 中断を知らせるシグナルを持つコンテキストです。
|
|
30
|
+
*
|
|
31
|
+
* @returns 完了を知らせるプロミスです。
|
|
32
|
+
*/
|
|
18
33
|
open(c: IStorage.OpenContext): Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* データベース接続を閉じます。
|
|
36
|
+
*
|
|
37
|
+
* @param c 中断を知らせるシグナルを持つコンテキストです。
|
|
38
|
+
*
|
|
39
|
+
* @returns 完了を知らせるプロミスです。
|
|
40
|
+
*/
|
|
19
41
|
close(c: IStorage.CloseContext): Promise<void>;
|
|
42
|
+
/**
|
|
43
|
+
* 指定したキーに値を保存します。Blob は中間形式に変換して保存します。
|
|
44
|
+
*
|
|
45
|
+
* @param c 書き込み対象のキーを持つコンテキストです。
|
|
46
|
+
* @param data 保存する値です。
|
|
47
|
+
*
|
|
48
|
+
* @returns 完了を知らせるプロミスです。
|
|
49
|
+
*/
|
|
20
50
|
write(c: IStorage.WriteContext, data: any): Promise<void>;
|
|
51
|
+
/**
|
|
52
|
+
* 指定したキーの値を読み出します。中間形式は Blob または File に戻します。
|
|
53
|
+
*
|
|
54
|
+
* @param c 読み出すキーを持つコンテキストです。
|
|
55
|
+
*
|
|
56
|
+
* @returns 保存されている値です。
|
|
57
|
+
*/
|
|
21
58
|
read(c: IStorage.ReadContext): Promise<any>;
|
|
59
|
+
/**
|
|
60
|
+
* 指定したキーが存在するかどうかを返します。
|
|
61
|
+
*
|
|
62
|
+
* @param c 確認するキーを持つコンテキストです。
|
|
63
|
+
*
|
|
64
|
+
* @returns キーが存在すれば true を返します。
|
|
65
|
+
*/
|
|
22
66
|
exists(c: IStorage.ExistsContext): Promise<boolean>;
|
|
67
|
+
/**
|
|
68
|
+
* 指定したキーを削除します。
|
|
69
|
+
*
|
|
70
|
+
* @param c 削除するキーを持つコンテキストです。
|
|
71
|
+
*
|
|
72
|
+
* @returns 完了を知らせるプロミスです。
|
|
73
|
+
*/
|
|
23
74
|
delete(c: IStorage.DeleteContext): Promise<void>;
|
|
75
|
+
/**
|
|
76
|
+
* オブジェクトストアのすべての値を削除します。
|
|
77
|
+
*
|
|
78
|
+
* @param c 中断を知らせるシグナルを持つコンテキストです。
|
|
79
|
+
*
|
|
80
|
+
* @returns 完了を知らせるプロミスです。
|
|
81
|
+
*/
|
|
24
82
|
clear(c: IStorage.ClearContext): Promise<void>;
|
|
83
|
+
/**
|
|
84
|
+
* Uint8Array を書き込むためのストリームを作成します。close 時にまとめて保存します。
|
|
85
|
+
*
|
|
86
|
+
* @param c 書き込み対象のキーを持つコンテキストです。
|
|
87
|
+
*
|
|
88
|
+
* @returns Uint8Array を受け付ける書き込みストリームです。
|
|
89
|
+
*/
|
|
25
90
|
getWritable(c: IStorage.GetWritableContext): WritableStream<Uint8Array>;
|
|
91
|
+
/**
|
|
92
|
+
* 保存済みの値を読み出すストリームを作成します。
|
|
93
|
+
*
|
|
94
|
+
* @param c 読み出すキーを持つコンテキストです。
|
|
95
|
+
*
|
|
96
|
+
* @returns Uint8Array を送出する読み取りストリームです。
|
|
97
|
+
*/
|
|
26
98
|
getReadable(c: IStorage.GetReadableContext): ReadableStream<Uint8Array>;
|
|
27
99
|
}
|
|
28
100
|
//#endregion
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/indexeddb.ts"],"mappings":";;;;;KAMY;;;;
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/indexeddb.ts"],"mappings":";;;;;KAMY;;;;cAkGS,qBAAqB;;;;;WAIxB;;;;EA2BhB,YACE,iBACA,oBACA,WAAU;;;;;;MAcD;;;;;;;;EAWX,KAAkB,GAAG,SAAS,cAAc;;;;;;;;EAqD5C,MAAmB,GAAG,SAAS,eAAe;;;;;;;;;EAsB9C,MAAmB,GAAG,SAAS,cAAc,YAAY;;;;;;;;EA0BzD,KAAkB,GAAG,SAAS,cAAc;;;;;;;;EAoC5C,OAAoB,GAAG,SAAS,gBAAgB;;;;;;;;EAoBhD,OAAoB,GAAG,SAAS,gBAAgB;;;;;;;;EAgBhD,MAAmB,GAAG,SAAS,eAAe;;;;;;;;EAgB9C,YAAmB,GAAG,SAAS,qBAAqB,eAAe;;;;;;;;EAyCnE,YAAmB,GAAG,SAAS,qBAAqB,eAAe"}
|
package/dist/index.js
CHANGED
|
@@ -31,10 +31,25 @@ function _classPrivateFieldGet2(s, a) {
|
|
|
31
31
|
}
|
|
32
32
|
//#endregion
|
|
33
33
|
//#region ../src/indexeddb.ts
|
|
34
|
+
/**
|
|
35
|
+
* 保存した中間形式かどうかを判定します。
|
|
36
|
+
*
|
|
37
|
+
* @param value 判定する値です。
|
|
38
|
+
*
|
|
39
|
+
* @returns 中間形式であれば true を返します。
|
|
40
|
+
*/
|
|
34
41
|
function isStoredBlob(value) {
|
|
35
42
|
if (typeof value !== "object" || value === null) return false;
|
|
36
43
|
return value["unikvsBlob"] === true;
|
|
37
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* Blob を IndexedDB に保存できる中間形式に変換します。File の名前と更新時刻も保持します。
|
|
47
|
+
*
|
|
48
|
+
* @param signal 中断を知らせるシグナルです。
|
|
49
|
+
* @param data 変換する Blob です。
|
|
50
|
+
*
|
|
51
|
+
* @returns 中間形式に変換した値です。
|
|
52
|
+
*/
|
|
38
53
|
async function toStoredBlob(signal, data) {
|
|
39
54
|
const buffer = await data.arrayBuffer();
|
|
40
55
|
signal.throwIfAborted();
|
|
@@ -48,6 +63,13 @@ async function toStoredBlob(signal, data) {
|
|
|
48
63
|
lastModified: isFile ? data.lastModified : 0
|
|
49
64
|
};
|
|
50
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* 中間形式を Blob または File に戻します。
|
|
68
|
+
*
|
|
69
|
+
* @param stored 中間形式の値です。
|
|
70
|
+
*
|
|
71
|
+
* @returns 復元した Blob または File です。
|
|
72
|
+
*/
|
|
51
73
|
function fromStoredBlob(stored) {
|
|
52
74
|
if (stored.unikvsFile) return new File([stored.buffer], stored.name, {
|
|
53
75
|
type: stored.type,
|
|
@@ -79,9 +101,21 @@ var Indexeddb = class {
|
|
|
79
101
|
_classPrivateFieldSet2(_dbName, this, dbName);
|
|
80
102
|
_classPrivateFieldSet2(_storeName, this, storeName);
|
|
81
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* ストレージが開いているかどうかを返します。
|
|
106
|
+
*
|
|
107
|
+
* @returns データベース接続があれば true を返します。
|
|
108
|
+
*/
|
|
82
109
|
get isOpen() {
|
|
83
110
|
return _classPrivateFieldGet2(_db, this) !== null;
|
|
84
111
|
}
|
|
112
|
+
/**
|
|
113
|
+
* データベースを開きます。並行した呼び出しは一つの接続処理に束ねます。
|
|
114
|
+
*
|
|
115
|
+
* @param c 中断を知らせるシグナルを持つコンテキストです。
|
|
116
|
+
*
|
|
117
|
+
* @returns 完了を知らせるプロミスです。
|
|
118
|
+
*/
|
|
85
119
|
async open(c) {
|
|
86
120
|
c.signal.throwIfAborted();
|
|
87
121
|
if (_classPrivateFieldGet2(_db, this) !== null) return;
|
|
@@ -109,6 +143,13 @@ var Indexeddb = class {
|
|
|
109
143
|
_classPrivateFieldSet2(_opening, this, null);
|
|
110
144
|
}
|
|
111
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* データベース接続を閉じます。
|
|
148
|
+
*
|
|
149
|
+
* @param c 中断を知らせるシグナルを持つコンテキストです。
|
|
150
|
+
*
|
|
151
|
+
* @returns 完了を知らせるプロミスです。
|
|
152
|
+
*/
|
|
112
153
|
async close(c) {
|
|
113
154
|
c.signal.throwIfAborted();
|
|
114
155
|
const db = _classPrivateFieldGet2(_db, this);
|
|
@@ -117,6 +158,14 @@ var Indexeddb = class {
|
|
|
117
158
|
db.close();
|
|
118
159
|
_classPrivateFieldSet2(_db, this, null);
|
|
119
160
|
}
|
|
161
|
+
/**
|
|
162
|
+
* 指定したキーに値を保存します。Blob は中間形式に変換して保存します。
|
|
163
|
+
*
|
|
164
|
+
* @param c 書き込み対象のキーを持つコンテキストです。
|
|
165
|
+
* @param data 保存する値です。
|
|
166
|
+
*
|
|
167
|
+
* @returns 完了を知らせるプロミスです。
|
|
168
|
+
*/
|
|
120
169
|
async write(c, data) {
|
|
121
170
|
c.signal.throwIfAborted();
|
|
122
171
|
const db = _assertClassBrand(_Indexeddb_brand, this, _getDb).call(this);
|
|
@@ -129,6 +178,13 @@ var Indexeddb = class {
|
|
|
129
178
|
await db.put(_classPrivateFieldGet2(_storeName, this), data, c.key);
|
|
130
179
|
c.signal.throwIfAborted();
|
|
131
180
|
}
|
|
181
|
+
/**
|
|
182
|
+
* 指定したキーの値を読み出します。中間形式は Blob または File に戻します。
|
|
183
|
+
*
|
|
184
|
+
* @param c 読み出すキーを持つコンテキストです。
|
|
185
|
+
*
|
|
186
|
+
* @returns 保存されている値です。
|
|
187
|
+
*/
|
|
132
188
|
async read(c) {
|
|
133
189
|
c.signal.throwIfAborted();
|
|
134
190
|
const db = _assertClassBrand(_Indexeddb_brand, this, _getDb).call(this);
|
|
@@ -142,22 +198,50 @@ var Indexeddb = class {
|
|
|
142
198
|
}
|
|
143
199
|
return value;
|
|
144
200
|
}
|
|
201
|
+
/**
|
|
202
|
+
* 指定したキーが存在するかどうかを返します。
|
|
203
|
+
*
|
|
204
|
+
* @param c 確認するキーを持つコンテキストです。
|
|
205
|
+
*
|
|
206
|
+
* @returns キーが存在すれば true を返します。
|
|
207
|
+
*/
|
|
145
208
|
async exists(c) {
|
|
146
209
|
c.signal.throwIfAborted();
|
|
147
210
|
const count = await _assertClassBrand(_Indexeddb_brand, this, _getDb).call(this).count(_classPrivateFieldGet2(_storeName, this), c.key);
|
|
148
211
|
c.signal.throwIfAborted();
|
|
149
212
|
return count > 0;
|
|
150
213
|
}
|
|
214
|
+
/**
|
|
215
|
+
* 指定したキーを削除します。
|
|
216
|
+
*
|
|
217
|
+
* @param c 削除するキーを持つコンテキストです。
|
|
218
|
+
*
|
|
219
|
+
* @returns 完了を知らせるプロミスです。
|
|
220
|
+
*/
|
|
151
221
|
async delete(c) {
|
|
152
222
|
c.signal.throwIfAborted();
|
|
153
223
|
await _assertClassBrand(_Indexeddb_brand, this, _getDb).call(this).delete(_classPrivateFieldGet2(_storeName, this), c.key);
|
|
154
224
|
c.signal.throwIfAborted();
|
|
155
225
|
}
|
|
226
|
+
/**
|
|
227
|
+
* オブジェクトストアのすべての値を削除します。
|
|
228
|
+
*
|
|
229
|
+
* @param c 中断を知らせるシグナルを持つコンテキストです。
|
|
230
|
+
*
|
|
231
|
+
* @returns 完了を知らせるプロミスです。
|
|
232
|
+
*/
|
|
156
233
|
async clear(c) {
|
|
157
234
|
c.signal.throwIfAborted();
|
|
158
235
|
await _assertClassBrand(_Indexeddb_brand, this, _getDb).call(this).clear(_classPrivateFieldGet2(_storeName, this));
|
|
159
236
|
c.signal.throwIfAborted();
|
|
160
237
|
}
|
|
238
|
+
/**
|
|
239
|
+
* Uint8Array を書き込むためのストリームを作成します。close 時にまとめて保存します。
|
|
240
|
+
*
|
|
241
|
+
* @param c 書き込み対象のキーを持つコンテキストです。
|
|
242
|
+
*
|
|
243
|
+
* @returns Uint8Array を受け付ける書き込みストリームです。
|
|
244
|
+
*/
|
|
161
245
|
getWritable(c) {
|
|
162
246
|
c.signal.throwIfAborted();
|
|
163
247
|
const chunks = [];
|
|
@@ -183,6 +267,13 @@ var Indexeddb = class {
|
|
|
183
267
|
}
|
|
184
268
|
});
|
|
185
269
|
}
|
|
270
|
+
/**
|
|
271
|
+
* 保存済みの値を読み出すストリームを作成します。
|
|
272
|
+
*
|
|
273
|
+
* @param c 読み出すキーを持つコンテキストです。
|
|
274
|
+
*
|
|
275
|
+
* @returns Uint8Array を送出する読み取りストリームです。
|
|
276
|
+
*/
|
|
186
277
|
getReadable(c) {
|
|
187
278
|
c.signal.throwIfAborted();
|
|
188
279
|
return new ReadableStream({ pull: async (controller) => {
|
|
@@ -197,6 +288,11 @@ var Indexeddb = class {
|
|
|
197
288
|
} });
|
|
198
289
|
}
|
|
199
290
|
};
|
|
291
|
+
/**
|
|
292
|
+
* 開いているデータベースを返します。未オープン時はエラーを投げます。
|
|
293
|
+
*
|
|
294
|
+
* @returns 開いているデータベースです。
|
|
295
|
+
*/
|
|
200
296
|
function _getDb() {
|
|
201
297
|
const db = _classPrivateFieldGet2(_db, this);
|
|
202
298
|
if (db === null) throw new TypeError("Indexeddb is not open. Call open() first.");
|
package/dist/index.js.map
CHANGED
|
@@ -1 +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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"}
|
|
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/**\n * WebKit で Blob を直接保存できない場合があるため、中間形式で保持します。\n */\ntype StoredBlob = {\n /**\n * File のときに保持するファイル名です。\n */\n readonly name: string;\n\n /**\n * 元の Blob の MIME 種別です。\n */\n readonly type: string;\n\n /**\n * Blob のバイト列です。\n */\n readonly buffer: ArrayBuffer;\n\n /**\n * 中間形式であることを示す目印です。\n */\n readonly unikvsBlob: true;\n\n /**\n * File 由来かどうかを示す目印です。\n */\n readonly unikvsFile: boolean;\n\n /**\n * File のときに保持する更新時刻です。\n */\n readonly lastModified: number;\n};\n\n/**\n * 保存した中間形式かどうかを判定します。\n *\n * @param value 判定する値です。\n *\n * @returns 中間形式であれば true を返します。\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/**\n * Blob を IndexedDB に保存できる中間形式に変換します。File の名前と更新時刻も保持します。\n *\n * @param signal 中断を知らせるシグナルです。\n * @param data 変換する Blob です。\n *\n * @returns 中間形式に変換した値です。\n */\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/**\n * 中間形式を Blob または File に戻します。\n *\n * @param stored 中間形式の値です。\n *\n * @returns 復元した Blob または File です。\n */\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 public readonly name: string;\n\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 /**\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 /**\n * ストレージが開いているかどうかを返します。\n *\n * @returns データベース接続があれば true を返します。\n */\n public get isOpen(): boolean {\n return this.#db !== null;\n }\n\n /**\n * データベースを開きます。並行した呼び出しは一つの接続処理に束ねます。\n *\n * @param c 中断を知らせるシグナルを持つコンテキストです。\n *\n * @returns 完了を知らせるプロミスです。\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 /**\n * データベース接続を閉じます。\n *\n * @param c 中断を知らせるシグナルを持つコンテキストです。\n *\n * @returns 完了を知らせるプロミスです。\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 /**\n * 指定したキーに値を保存します。Blob は中間形式に変換して保存します。\n *\n * @param c 書き込み対象のキーを持つコンテキストです。\n * @param data 保存する値です。\n *\n * @returns 完了を知らせるプロミスです。\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 /**\n * 指定したキーの値を読み出します。中間形式は Blob または File に戻します。\n *\n * @param c 読み出すキーを持つコンテキストです。\n *\n * @returns 保存されている値です。\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 /**\n * 指定したキーが存在するかどうかを返します。\n *\n * @param c 確認するキーを持つコンテキストです。\n *\n * @returns キーが存在すれば true を返します。\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 /**\n * 指定したキーを削除します。\n *\n * @param c 削除するキーを持つコンテキストです。\n *\n * @returns 完了を知らせるプロミスです。\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 /**\n * オブジェクトストアのすべての値を削除します。\n *\n * @param c 中断を知らせるシグナルを持つコンテキストです。\n *\n * @returns 完了を知らせるプロミスです。\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 /**\n * Uint8Array を書き込むためのストリームを作成します。close 時にまとめて保存します。\n *\n * @param c 書き込み対象のキーを持つコンテキストです。\n *\n * @returns Uint8Array を受け付ける書き込みストリームです。\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 /**\n * 保存済みの値を読み出すストリームを作成します。\n *\n * @param c 読み出すキーを持つコンテキストです。\n *\n * @returns Uint8Array を送出する読み取りストリームです。\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 /**\n * 開いているデータベースを返します。未オープン時はエラーを投げます。\n *\n * @returns 開いているデータベースです。\n */\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"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unikvs/indexeddb",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.14",
|
|
4
4
|
"description": "Storage plugin that stores arbitrary data in the browser IndexedDB.",
|
|
5
5
|
"homepage": "https://tai-kun.github.io/unikvs/",
|
|
6
6
|
"license": "MIT",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
}
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@unikvs/core": "0.0.
|
|
27
|
+
"@unikvs/core": "0.0.14",
|
|
28
28
|
"idb": "^8.0.4"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
package/src/indexeddb.ts
CHANGED
|
@@ -6,17 +6,48 @@ import { type IDBPDatabase, openDB } from "idb";
|
|
|
6
6
|
*/
|
|
7
7
|
export type IndexeddbOptions = {};
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
/**
|
|
10
|
+
* WebKit で Blob を直接保存できない場合があるため、中間形式で保持します。
|
|
11
|
+
*/
|
|
10
12
|
type StoredBlob = {
|
|
13
|
+
/**
|
|
14
|
+
* File のときに保持するファイル名です。
|
|
15
|
+
*/
|
|
11
16
|
readonly name: string;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 元の Blob の MIME 種別です。
|
|
20
|
+
*/
|
|
12
21
|
readonly type: string;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Blob のバイト列です。
|
|
25
|
+
*/
|
|
13
26
|
readonly buffer: ArrayBuffer;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 中間形式であることを示す目印です。
|
|
30
|
+
*/
|
|
14
31
|
readonly unikvsBlob: true;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* File 由来かどうかを示す目印です。
|
|
35
|
+
*/
|
|
15
36
|
readonly unikvsFile: boolean;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* File のときに保持する更新時刻です。
|
|
40
|
+
*/
|
|
16
41
|
readonly lastModified: number;
|
|
17
42
|
};
|
|
18
43
|
|
|
19
|
-
|
|
44
|
+
/**
|
|
45
|
+
* 保存した中間形式かどうかを判定します。
|
|
46
|
+
*
|
|
47
|
+
* @param value 判定する値です。
|
|
48
|
+
*
|
|
49
|
+
* @returns 中間形式であれば true を返します。
|
|
50
|
+
*/
|
|
20
51
|
function isStoredBlob(value: unknown): value is StoredBlob {
|
|
21
52
|
if (typeof value !== "object" || value === null) {
|
|
22
53
|
return false;
|
|
@@ -25,8 +56,14 @@ function isStoredBlob(value: unknown): value is StoredBlob {
|
|
|
25
56
|
return (value as Record<string, unknown>)["unikvsBlob"] === true;
|
|
26
57
|
}
|
|
27
58
|
|
|
28
|
-
|
|
29
|
-
|
|
59
|
+
/**
|
|
60
|
+
* Blob を IndexedDB に保存できる中間形式に変換します。File の名前と更新時刻も保持します。
|
|
61
|
+
*
|
|
62
|
+
* @param signal 中断を知らせるシグナルです。
|
|
63
|
+
* @param data 変換する Blob です。
|
|
64
|
+
*
|
|
65
|
+
* @returns 中間形式に変換した値です。
|
|
66
|
+
*/
|
|
30
67
|
async function toStoredBlob(signal: AbortSignal, data: Blob): Promise<StoredBlob> {
|
|
31
68
|
const buffer = await data.arrayBuffer();
|
|
32
69
|
|
|
@@ -44,7 +81,13 @@ async function toStoredBlob(signal: AbortSignal, data: Blob): Promise<StoredBlob
|
|
|
44
81
|
};
|
|
45
82
|
}
|
|
46
83
|
|
|
47
|
-
|
|
84
|
+
/**
|
|
85
|
+
* 中間形式を Blob または File に戻します。
|
|
86
|
+
*
|
|
87
|
+
* @param stored 中間形式の値です。
|
|
88
|
+
*
|
|
89
|
+
* @returns 復元した Blob または File です。
|
|
90
|
+
*/
|
|
48
91
|
function fromStoredBlob(stored: StoredBlob): Blob {
|
|
49
92
|
if (stored.unikvsFile) {
|
|
50
93
|
return new File([stored.buffer], stored.name, {
|
|
@@ -60,6 +103,11 @@ function fromStoredBlob(stored: StoredBlob): Blob {
|
|
|
60
103
|
* [API Reference](https://tai-kun.github.io/unikvs/packages/indexeddb#usage)
|
|
61
104
|
*/
|
|
62
105
|
export default class Indexeddb implements IStorage {
|
|
106
|
+
/**
|
|
107
|
+
* ストレージ名です。常に `Indexeddb` を返します。
|
|
108
|
+
*/
|
|
109
|
+
public readonly name: string;
|
|
110
|
+
|
|
63
111
|
/**
|
|
64
112
|
* IndexedDB のデータベースインスタンスを保持します。
|
|
65
113
|
*
|
|
@@ -82,8 +130,6 @@ export default class Indexeddb implements IStorage {
|
|
|
82
130
|
*/
|
|
83
131
|
readonly #storeName: string;
|
|
84
132
|
|
|
85
|
-
public readonly name: string;
|
|
86
|
-
|
|
87
133
|
/**
|
|
88
134
|
* [API Reference](https://tai-kun.github.io/unikvs/packages/indexeddb#usage)
|
|
89
135
|
*/
|
|
@@ -99,22 +145,22 @@ export default class Indexeddb implements IStorage {
|
|
|
99
145
|
this.#storeName = storeName;
|
|
100
146
|
}
|
|
101
147
|
|
|
148
|
+
/**
|
|
149
|
+
* ストレージが開いているかどうかを返します。
|
|
150
|
+
*
|
|
151
|
+
* @returns データベース接続があれば true を返します。
|
|
152
|
+
*/
|
|
102
153
|
public get isOpen(): boolean {
|
|
103
154
|
return this.#db !== null;
|
|
104
155
|
}
|
|
105
156
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
return db;
|
|
116
|
-
}
|
|
117
|
-
|
|
157
|
+
/**
|
|
158
|
+
* データベースを開きます。並行した呼び出しは一つの接続処理に束ねます。
|
|
159
|
+
*
|
|
160
|
+
* @param c 中断を知らせるシグナルを持つコンテキストです。
|
|
161
|
+
*
|
|
162
|
+
* @returns 完了を知らせるプロミスです。
|
|
163
|
+
*/
|
|
118
164
|
public async open(c: IStorage.OpenContext): Promise<void> {
|
|
119
165
|
c.signal.throwIfAborted();
|
|
120
166
|
|
|
@@ -161,6 +207,13 @@ export default class Indexeddb implements IStorage {
|
|
|
161
207
|
}
|
|
162
208
|
}
|
|
163
209
|
|
|
210
|
+
/**
|
|
211
|
+
* データベース接続を閉じます。
|
|
212
|
+
*
|
|
213
|
+
* @param c 中断を知らせるシグナルを持つコンテキストです。
|
|
214
|
+
*
|
|
215
|
+
* @returns 完了を知らせるプロミスです。
|
|
216
|
+
*/
|
|
164
217
|
public async close(c: IStorage.CloseContext): Promise<void> {
|
|
165
218
|
c.signal.throwIfAborted();
|
|
166
219
|
|
|
@@ -175,6 +228,14 @@ export default class Indexeddb implements IStorage {
|
|
|
175
228
|
this.#db = null;
|
|
176
229
|
}
|
|
177
230
|
|
|
231
|
+
/**
|
|
232
|
+
* 指定したキーに値を保存します。Blob は中間形式に変換して保存します。
|
|
233
|
+
*
|
|
234
|
+
* @param c 書き込み対象のキーを持つコンテキストです。
|
|
235
|
+
* @param data 保存する値です。
|
|
236
|
+
*
|
|
237
|
+
* @returns 完了を知らせるプロミスです。
|
|
238
|
+
*/
|
|
178
239
|
public async write(c: IStorage.WriteContext, data: any): Promise<void> {
|
|
179
240
|
c.signal.throwIfAborted();
|
|
180
241
|
|
|
@@ -194,6 +255,13 @@ export default class Indexeddb implements IStorage {
|
|
|
194
255
|
c.signal.throwIfAborted();
|
|
195
256
|
}
|
|
196
257
|
|
|
258
|
+
/**
|
|
259
|
+
* 指定したキーの値を読み出します。中間形式は Blob または File に戻します。
|
|
260
|
+
*
|
|
261
|
+
* @param c 読み出すキーを持つコンテキストです。
|
|
262
|
+
*
|
|
263
|
+
* @returns 保存されている値です。
|
|
264
|
+
*/
|
|
197
265
|
public async read(c: IStorage.ReadContext): Promise<any> {
|
|
198
266
|
c.signal.throwIfAborted();
|
|
199
267
|
|
|
@@ -223,6 +291,13 @@ export default class Indexeddb implements IStorage {
|
|
|
223
291
|
return value;
|
|
224
292
|
}
|
|
225
293
|
|
|
294
|
+
/**
|
|
295
|
+
* 指定したキーが存在するかどうかを返します。
|
|
296
|
+
*
|
|
297
|
+
* @param c 確認するキーを持つコンテキストです。
|
|
298
|
+
*
|
|
299
|
+
* @returns キーが存在すれば true を返します。
|
|
300
|
+
*/
|
|
226
301
|
public async exists(c: IStorage.ExistsContext): Promise<boolean> {
|
|
227
302
|
c.signal.throwIfAborted();
|
|
228
303
|
|
|
@@ -236,6 +311,13 @@ export default class Indexeddb implements IStorage {
|
|
|
236
311
|
return count > 0;
|
|
237
312
|
}
|
|
238
313
|
|
|
314
|
+
/**
|
|
315
|
+
* 指定したキーを削除します。
|
|
316
|
+
*
|
|
317
|
+
* @param c 削除するキーを持つコンテキストです。
|
|
318
|
+
*
|
|
319
|
+
* @returns 完了を知らせるプロミスです。
|
|
320
|
+
*/
|
|
239
321
|
public async delete(c: IStorage.DeleteContext): Promise<void> {
|
|
240
322
|
c.signal.throwIfAborted();
|
|
241
323
|
|
|
@@ -245,6 +327,13 @@ export default class Indexeddb implements IStorage {
|
|
|
245
327
|
c.signal.throwIfAborted();
|
|
246
328
|
}
|
|
247
329
|
|
|
330
|
+
/**
|
|
331
|
+
* オブジェクトストアのすべての値を削除します。
|
|
332
|
+
*
|
|
333
|
+
* @param c 中断を知らせるシグナルを持つコンテキストです。
|
|
334
|
+
*
|
|
335
|
+
* @returns 完了を知らせるプロミスです。
|
|
336
|
+
*/
|
|
248
337
|
public async clear(c: IStorage.ClearContext): Promise<void> {
|
|
249
338
|
c.signal.throwIfAborted();
|
|
250
339
|
|
|
@@ -254,6 +343,13 @@ export default class Indexeddb implements IStorage {
|
|
|
254
343
|
c.signal.throwIfAborted();
|
|
255
344
|
}
|
|
256
345
|
|
|
346
|
+
/**
|
|
347
|
+
* Uint8Array を書き込むためのストリームを作成します。close 時にまとめて保存します。
|
|
348
|
+
*
|
|
349
|
+
* @param c 書き込み対象のキーを持つコンテキストです。
|
|
350
|
+
*
|
|
351
|
+
* @returns Uint8Array を受け付ける書き込みストリームです。
|
|
352
|
+
*/
|
|
257
353
|
public getWritable(c: IStorage.GetWritableContext): WritableStream<Uint8Array> {
|
|
258
354
|
c.signal.throwIfAborted();
|
|
259
355
|
|
|
@@ -288,6 +384,13 @@ export default class Indexeddb implements IStorage {
|
|
|
288
384
|
});
|
|
289
385
|
}
|
|
290
386
|
|
|
387
|
+
/**
|
|
388
|
+
* 保存済みの値を読み出すストリームを作成します。
|
|
389
|
+
*
|
|
390
|
+
* @param c 読み出すキーを持つコンテキストです。
|
|
391
|
+
*
|
|
392
|
+
* @returns Uint8Array を送出する読み取りストリームです。
|
|
393
|
+
*/
|
|
291
394
|
public getReadable(c: IStorage.GetReadableContext): ReadableStream<Uint8Array> {
|
|
292
395
|
c.signal.throwIfAborted();
|
|
293
396
|
|
|
@@ -305,4 +408,19 @@ export default class Indexeddb implements IStorage {
|
|
|
305
408
|
},
|
|
306
409
|
});
|
|
307
410
|
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* 開いているデータベースを返します。未オープン時はエラーを投げます。
|
|
414
|
+
*
|
|
415
|
+
* @returns 開いているデータベースです。
|
|
416
|
+
*/
|
|
417
|
+
#getDb(): IDBPDatabase {
|
|
418
|
+
const db = this.#db;
|
|
419
|
+
|
|
420
|
+
if (db === null) {
|
|
421
|
+
throw new TypeError("Indexeddb is not open. Call open() first.");
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
return db;
|
|
425
|
+
}
|
|
308
426
|
}
|