@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 CHANGED
@@ -1,3 +1,3 @@
1
1
  # @unikvs/indexeddb
2
2
 
3
- [Documentation](https://tai-kun.github.io/unikvs/packages/indexeddb)
3
+ [Document](https://tai-kun.github.io/unikvs/)
@@ -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.12",
3
+ "version": "0.0.13",
4
4
  "description": "Storage plugin that stores arbitrary data in the browser IndexedDB.",
5
- "homepage": "https://github.com/tai-kun/unikvs",
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
- "README.md",
16
- "LICENSE"
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/src/index.d.ts",
25
- "default": "./dist/src/index.js"
22
+ "types": "./dist/index.d.ts",
23
+ "default": "./dist/index.js"
26
24
  }
27
25
  },
28
26
  "dependencies": {
29
- "@unikvs/core": "0.0.12",
30
- "idb": "8.0.3"
27
+ "@unikvs/core": "0.0.13",
28
+ "idb": "^8.0.4"
31
29
  },
32
30
  "devDependencies": {
33
- "@tsconfig/node24": "24.0.5",
34
- "@tsconfig/strictest": "2.0.8",
35
- "@types/node": "24.13.6",
36
- "@vitest/browser": "4.1.11",
37
- "@vitest/browser-playwright": "4.1.11",
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": "tsc --project ./.config/tsconfig.build.json"
42
+ "build": "vp -C .config pack"
48
43
  }
49
44
  }
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export type * from "./indexeddb.js";
2
+ export { default as Indexeddb } from "./indexeddb.js";
package/src/indexeddb.ts CHANGED
@@ -1,5 +1,60 @@
1
1
  import type { IStorage } from "@unikvs/core";
2
- import { openDB, type IDBPDatabase } from "idb";
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
- private db: IDBPDatabase | null;
68
+ #db: IDBPDatabase | null;
14
69
 
15
70
  /**
16
- * 使用するデータベース名です。
71
+ * 並行 open を束ねるための進行中 Promise です。
17
72
  */
18
- private readonly dbName: string;
73
+ #opening: Promise<IDBPDatabase> | null;
19
74
 
20
75
  /**
21
- * 使用するオブジェクトストア名です。
76
+ * 使用するデータベース名です。
22
77
  */
23
- private readonly storeName: string;
78
+ readonly #dbName: string;
24
79
 
25
80
  /**
26
- * [API Reference](https://tai-kun.github.io/unikvs/packages/indexeddb#usage)
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(dbName: string = "unikvs_db", storeName: string = "kvs_store") {
90
+ public constructor(
91
+ dbName = "unikvs_db",
92
+ storeName = "kvs_store",
93
+ _options: IndexeddbOptions = {},
94
+ ) {
34
95
  this.name = "Indexeddb";
35
- this.db = null;
36
- this.dbName = dbName;
37
- this.storeName = storeName;
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.db !== null;
103
+ return this.#db !== null;
45
104
  }
46
105
 
47
- /**
48
- * [API Reference](https://tai-kun.github.io/unikvs/packages/indexeddb#usage)
49
- */
50
- public async open(): Promise<void> {
51
- if (this.db) {
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
- this.db = await openDB(this.dbName, 1, {
56
- upgrade: (db) => {
57
- // オブジェクトストアが存在しない場合は作成します
58
- if (!db.objectStoreNames.contains(this.storeName)) {
59
- db.createObjectStore(this.storeName);
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
- * [API Reference](https://tai-kun.github.io/unikvs/packages/indexeddb#usage)
67
- */
68
- public async close(): Promise<void> {
69
- this.db!.close();
70
- this.db = null;
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
- * [API Reference](https://tai-kun.github.io/unikvs/packages/indexeddb#data)
75
- */
76
- public async write(args: Pick<IStorage.WriteArgs<any>, "key" | "data">): Promise<void> {
77
- const { key, data } = args;
78
- await this.db!.put(this.storeName, data, key);
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
- * [API Reference](https://tai-kun.github.io/unikvs/packages/indexeddb#data)
83
- */
84
- public async read(args: Pick<IStorage.ReadArgs, "key">): Promise<any> {
85
- const { key } = args;
86
- // Opfs の挙動に合わせて、存在しない場合は DOMException (NotFoundError) を投げます
87
- if (!(await this.exists({ key }))) {
88
- throw new DOMException(
89
- `A requested file or directory could not be found at the time an operation was processed.`,
90
- "NotFoundError",
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 await this.db!.get(this.storeName, key);
223
+ return value;
95
224
  }
96
225
 
97
- /**
98
- * [API Reference](https://tai-kun.github.io/unikvs/packages/indexeddb#usage)
99
- */
100
- public async exists(args: Pick<IStorage.ExistsArgs, "key">): Promise<boolean> {
101
- const { key } = args;
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
- * [API Reference](https://tai-kun.github.io/unikvs/packages/indexeddb#usage)
110
- */
111
- public async delete(args: Pick<IStorage.DeleteArgs, "key">): Promise<void> {
112
- const { key } = args;
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
- await this.db!.delete(this.storeName, key);
245
+ c.signal.throwIfAborted();
115
246
  }
116
247
 
117
- /**
118
- * [API Reference](https://tai-kun.github.io/unikvs/packages/indexeddb#usage)
119
- */
120
- public async clear(): Promise<void> {
121
- await this.db!.clear(this.storeName);
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
- * [API Reference](https://tai-kun.github.io/unikvs/packages/indexeddb#data)
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
- write: (chunk) => {
133
- chunks.push(chunk);
262
+ return new WritableStream<Uint8Array>({
263
+ abort: (): void => {
264
+ chunks.length = 0;
134
265
  },
135
- close: async () => {
136
- const totalLength = chunks.reduce((acc, chunk) => acc + chunk.length, 0);
137
- const result = new Uint8Array(totalLength);
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
- result.set(chunk, offset);
275
+ merged.set(chunk, offset);
142
276
  offset += chunk.length;
143
277
  }
144
278
 
145
- await this.db!.put(this.storeName, result, key);
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
- * [API Reference](https://tai-kun.github.io/unikvs/packages/indexeddb#data)
152
- */
153
- public getReadable(args: Pick<IStorage.GetReadableArgs, "key">): ReadableStream<Uint8Array> {
154
- const { key } = args;
155
-
156
- return new ReadableStream({
157
- pull: async (controller) => {
158
- const data = await this.read({ key });
159
- controller.enqueue(data);
160
- controller.close();
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
  }
@@ -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"}
@@ -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
- }