@matter/nodejs 0.16.6-alpha.0-20260121-bc6d62766 → 0.16.6-alpha.0-20260122-b5154bcb4

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.
Files changed (150) hide show
  1. package/dist/cjs/config.d.ts +6 -0
  2. package/dist/cjs/config.d.ts.map +1 -1
  3. package/dist/cjs/config.js +14 -1
  4. package/dist/cjs/config.js.map +1 -1
  5. package/dist/cjs/environment/NodeJsEnvironment.d.ts +2 -1
  6. package/dist/cjs/environment/NodeJsEnvironment.d.ts.map +1 -1
  7. package/dist/cjs/environment/NodeJsEnvironment.js +28 -10
  8. package/dist/cjs/environment/NodeJsEnvironment.js.map +1 -1
  9. package/dist/cjs/storage/StorageFactory.d.ts +52 -0
  10. package/dist/cjs/storage/StorageFactory.d.ts.map +1 -0
  11. package/dist/cjs/storage/StorageFactory.js +215 -0
  12. package/dist/cjs/storage/StorageFactory.js.map +6 -0
  13. package/dist/cjs/storage/fs/StorageBackendDisk.d.ts.map +1 -0
  14. package/dist/cjs/storage/{StorageBackendDisk.js.map → fs/StorageBackendDisk.js.map} +1 -1
  15. package/dist/cjs/storage/fs/StorageBackendJsonFile.d.ts.map +1 -0
  16. package/dist/cjs/storage/{StorageBackendJsonFile.js.map → fs/StorageBackendJsonFile.js.map} +1 -1
  17. package/dist/cjs/storage/fs/index.d.ts +8 -0
  18. package/dist/cjs/storage/fs/index.d.ts.map +1 -0
  19. package/dist/cjs/storage/fs/index.js +25 -0
  20. package/dist/cjs/storage/fs/index.js.map +6 -0
  21. package/dist/cjs/storage/index.d.ts +3 -2
  22. package/dist/cjs/storage/index.d.ts.map +1 -1
  23. package/dist/cjs/storage/index.js +3 -2
  24. package/dist/cjs/storage/index.js.map +1 -1
  25. package/dist/cjs/storage/sqlite/SqliteStorage.d.ts +105 -0
  26. package/dist/cjs/storage/sqlite/SqliteStorage.d.ts.map +1 -0
  27. package/dist/cjs/storage/sqlite/SqliteStorage.js +439 -0
  28. package/dist/cjs/storage/sqlite/SqliteStorage.js.map +6 -0
  29. package/dist/cjs/storage/sqlite/SqliteStorageError.d.ts +19 -0
  30. package/dist/cjs/storage/sqlite/SqliteStorageError.d.ts.map +1 -0
  31. package/dist/cjs/storage/sqlite/SqliteStorageError.js +38 -0
  32. package/dist/cjs/storage/sqlite/SqliteStorageError.js.map +6 -0
  33. package/dist/cjs/storage/sqlite/SqliteTypes.d.ts +67 -0
  34. package/dist/cjs/storage/sqlite/SqliteTypes.d.ts.map +1 -0
  35. package/dist/cjs/storage/sqlite/SqliteTypes.js +35 -0
  36. package/dist/cjs/storage/sqlite/SqliteTypes.js.map +6 -0
  37. package/dist/cjs/storage/sqlite/SqliteUtil.d.ts +33 -0
  38. package/dist/cjs/storage/sqlite/SqliteUtil.d.ts.map +1 -0
  39. package/dist/cjs/storage/sqlite/SqliteUtil.js +64 -0
  40. package/dist/cjs/storage/sqlite/SqliteUtil.js.map +6 -0
  41. package/dist/cjs/storage/sqlite/index.d.ts +10 -0
  42. package/dist/cjs/storage/sqlite/index.d.ts.map +1 -0
  43. package/dist/cjs/storage/sqlite/index.js +34 -0
  44. package/dist/cjs/storage/sqlite/index.js.map +6 -0
  45. package/dist/cjs/storage/sqlite/platform/BunSqlite.d.ts +17 -0
  46. package/dist/cjs/storage/sqlite/platform/BunSqlite.d.ts.map +1 -0
  47. package/dist/cjs/storage/sqlite/platform/BunSqlite.js +62 -0
  48. package/dist/cjs/storage/sqlite/platform/BunSqlite.js.map +6 -0
  49. package/dist/cjs/storage/sqlite/platform/NodeJsSqlite.d.ts +12 -0
  50. package/dist/cjs/storage/sqlite/platform/NodeJsSqlite.d.ts.map +1 -0
  51. package/dist/cjs/storage/sqlite/platform/NodeJsSqlite.js +49 -0
  52. package/dist/cjs/storage/sqlite/platform/NodeJsSqlite.js.map +6 -0
  53. package/dist/cjs/storage/sqlite/platform/PlatformSqlite.d.ts +8 -0
  54. package/dist/cjs/storage/sqlite/platform/PlatformSqlite.d.ts.map +1 -0
  55. package/dist/cjs/storage/sqlite/platform/PlatformSqlite.js +54 -0
  56. package/dist/cjs/storage/sqlite/platform/PlatformSqlite.js.map +6 -0
  57. package/dist/cjs/util/runtimeChecks.d.ts +14 -0
  58. package/dist/cjs/util/runtimeChecks.d.ts.map +1 -0
  59. package/dist/cjs/util/runtimeChecks.js +45 -0
  60. package/dist/cjs/util/runtimeChecks.js.map +6 -0
  61. package/dist/esm/config.d.ts +6 -0
  62. package/dist/esm/config.d.ts.map +1 -1
  63. package/dist/esm/config.js +14 -1
  64. package/dist/esm/config.js.map +1 -1
  65. package/dist/esm/environment/NodeJsEnvironment.d.ts +2 -1
  66. package/dist/esm/environment/NodeJsEnvironment.d.ts.map +1 -1
  67. package/dist/esm/environment/NodeJsEnvironment.js +29 -10
  68. package/dist/esm/environment/NodeJsEnvironment.js.map +1 -1
  69. package/dist/esm/storage/StorageFactory.d.ts +52 -0
  70. package/dist/esm/storage/StorageFactory.d.ts.map +1 -0
  71. package/dist/esm/storage/StorageFactory.js +195 -0
  72. package/dist/esm/storage/StorageFactory.js.map +6 -0
  73. package/dist/esm/storage/fs/StorageBackendDisk.d.ts.map +1 -0
  74. package/dist/esm/storage/{StorageBackendDisk.js.map → fs/StorageBackendDisk.js.map} +1 -1
  75. package/dist/esm/storage/fs/StorageBackendJsonFile.d.ts.map +1 -0
  76. package/dist/esm/storage/{StorageBackendJsonFile.js.map → fs/StorageBackendJsonFile.js.map} +1 -1
  77. package/dist/esm/storage/fs/index.d.ts +8 -0
  78. package/dist/esm/storage/fs/index.d.ts.map +1 -0
  79. package/dist/esm/storage/fs/index.js +8 -0
  80. package/dist/esm/storage/fs/index.js.map +6 -0
  81. package/dist/esm/storage/index.d.ts +3 -2
  82. package/dist/esm/storage/index.d.ts.map +1 -1
  83. package/dist/esm/storage/index.js +3 -2
  84. package/dist/esm/storage/index.js.map +1 -1
  85. package/dist/esm/storage/sqlite/SqliteStorage.d.ts +105 -0
  86. package/dist/esm/storage/sqlite/SqliteStorage.d.ts.map +1 -0
  87. package/dist/esm/storage/sqlite/SqliteStorage.js +423 -0
  88. package/dist/esm/storage/sqlite/SqliteStorage.js.map +6 -0
  89. package/dist/esm/storage/sqlite/SqliteStorageError.d.ts +19 -0
  90. package/dist/esm/storage/sqlite/SqliteStorageError.d.ts.map +1 -0
  91. package/dist/esm/storage/sqlite/SqliteStorageError.js +18 -0
  92. package/dist/esm/storage/sqlite/SqliteStorageError.js.map +6 -0
  93. package/dist/esm/storage/sqlite/SqliteTypes.d.ts +67 -0
  94. package/dist/esm/storage/sqlite/SqliteTypes.d.ts.map +1 -0
  95. package/dist/esm/storage/sqlite/SqliteTypes.js +15 -0
  96. package/dist/esm/storage/sqlite/SqliteTypes.js.map +6 -0
  97. package/dist/esm/storage/sqlite/SqliteUtil.d.ts +33 -0
  98. package/dist/esm/storage/sqlite/SqliteUtil.d.ts.map +1 -0
  99. package/dist/esm/storage/sqlite/SqliteUtil.js +44 -0
  100. package/dist/esm/storage/sqlite/SqliteUtil.js.map +6 -0
  101. package/dist/esm/storage/sqlite/index.d.ts +10 -0
  102. package/dist/esm/storage/sqlite/index.d.ts.map +1 -0
  103. package/dist/esm/storage/sqlite/index.js +14 -0
  104. package/dist/esm/storage/sqlite/index.js.map +6 -0
  105. package/dist/esm/storage/sqlite/platform/BunSqlite.d.ts +17 -0
  106. package/dist/esm/storage/sqlite/platform/BunSqlite.d.ts.map +1 -0
  107. package/dist/esm/storage/sqlite/platform/BunSqlite.js +32 -0
  108. package/dist/esm/storage/sqlite/platform/BunSqlite.js.map +6 -0
  109. package/dist/esm/storage/sqlite/platform/NodeJsSqlite.d.ts +12 -0
  110. package/dist/esm/storage/sqlite/platform/NodeJsSqlite.d.ts.map +1 -0
  111. package/dist/esm/storage/sqlite/platform/NodeJsSqlite.js +29 -0
  112. package/dist/esm/storage/sqlite/platform/NodeJsSqlite.js.map +6 -0
  113. package/dist/esm/storage/sqlite/platform/PlatformSqlite.d.ts +8 -0
  114. package/dist/esm/storage/sqlite/platform/PlatformSqlite.d.ts.map +1 -0
  115. package/dist/esm/storage/sqlite/platform/PlatformSqlite.js +24 -0
  116. package/dist/esm/storage/sqlite/platform/PlatformSqlite.js.map +6 -0
  117. package/dist/esm/util/runtimeChecks.d.ts +14 -0
  118. package/dist/esm/util/runtimeChecks.d.ts.map +1 -0
  119. package/dist/esm/util/runtimeChecks.js +25 -0
  120. package/dist/esm/util/runtimeChecks.js.map +6 -0
  121. package/package.json +10 -10
  122. package/src/config.ts +18 -4
  123. package/src/environment/NodeJsEnvironment.ts +41 -12
  124. package/src/net/NodeJsHttpEndpoint.ts +1 -1
  125. package/src/storage/StorageFactory.ts +310 -0
  126. package/src/storage/fs/index.ts +8 -0
  127. package/src/storage/index.ts +3 -2
  128. package/src/storage/sqlite/SqliteStorage.ts +572 -0
  129. package/src/storage/sqlite/SqliteStorageError.ts +23 -0
  130. package/src/storage/sqlite/SqliteTypes.ts +75 -0
  131. package/src/storage/sqlite/SqliteUtil.ts +61 -0
  132. package/src/storage/sqlite/index.ts +10 -0
  133. package/src/storage/sqlite/platform/BunSqlite.ts +40 -0
  134. package/src/storage/sqlite/platform/NodeJsSqlite.ts +35 -0
  135. package/src/storage/sqlite/platform/PlatformSqlite.ts +52 -0
  136. package/src/util/runtimeChecks.ts +31 -0
  137. package/dist/cjs/storage/StorageBackendDisk.d.ts.map +0 -1
  138. package/dist/cjs/storage/StorageBackendJsonFile.d.ts.map +0 -1
  139. package/dist/esm/storage/StorageBackendDisk.d.ts.map +0 -1
  140. package/dist/esm/storage/StorageBackendJsonFile.d.ts.map +0 -1
  141. /package/dist/cjs/storage/{StorageBackendDisk.d.ts → fs/StorageBackendDisk.d.ts} +0 -0
  142. /package/dist/cjs/storage/{StorageBackendDisk.js → fs/StorageBackendDisk.js} +0 -0
  143. /package/dist/cjs/storage/{StorageBackendJsonFile.d.ts → fs/StorageBackendJsonFile.d.ts} +0 -0
  144. /package/dist/cjs/storage/{StorageBackendJsonFile.js → fs/StorageBackendJsonFile.js} +0 -0
  145. /package/dist/esm/storage/{StorageBackendDisk.d.ts → fs/StorageBackendDisk.d.ts} +0 -0
  146. /package/dist/esm/storage/{StorageBackendDisk.js → fs/StorageBackendDisk.js} +0 -0
  147. /package/dist/esm/storage/{StorageBackendJsonFile.d.ts → fs/StorageBackendJsonFile.d.ts} +0 -0
  148. /package/dist/esm/storage/{StorageBackendJsonFile.js → fs/StorageBackendJsonFile.js} +0 -0
  149. /package/src/storage/{StorageBackendDisk.ts → fs/StorageBackendDisk.ts} +0 -0
  150. /package/src/storage/{StorageBackendJsonFile.ts → fs/StorageBackendJsonFile.ts} +0 -0
@@ -0,0 +1,572 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2022-2026 Matter.js Authors
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import {
8
+ type Bytes,
9
+ type CloneableStorage,
10
+ type SupportedStorageTypes,
11
+ fromJson,
12
+ Storage,
13
+ toJson,
14
+ } from "@matter/general";
15
+
16
+ import { SqliteStorageError } from "./SqliteStorageError.js";
17
+ import type { DatabaseCreator, DatabaseLike, SafeUint8Array, SqlRunnable } from "./SqliteTypes.js";
18
+ import { SqliteTransaction as Transaction } from "./SqliteTypes.js";
19
+ import { buildContextKeyLog, buildContextKeyPair, buildContextPath, escapeGlob } from "./SqliteUtil.js";
20
+
21
+ /**
22
+ * Type of Key-Value store table
23
+ *
24
+ * T means JSON or BLOB type
25
+ */
26
+ type KVStoreType<T extends string | SafeUint8Array = string | SafeUint8Array> = {
27
+ context: string;
28
+ key: string;
29
+ value_type: T extends string ? "json" : "blob";
30
+ value_json: T extends string ? string : null;
31
+ value_blob: T extends SafeUint8Array ? SafeUint8Array : null;
32
+ };
33
+
34
+ /**
35
+ * SQLRunnable with
36
+ *
37
+ * `I`: keyof KVStoreType -> KVStoreType
38
+ * `O`: keyof KVStoreType -> KVStoreType
39
+ */
40
+ type SqlRunnableKV<
41
+ I extends keyof KVStoreType<string> | void,
42
+ O extends keyof KVStoreType<string> | void,
43
+ > = SqlRunnable<
44
+ I extends keyof KVStoreType<string> ? Pick<KVStoreType<string>, I> : void,
45
+ O extends keyof KVStoreType<string> ? Pick<KVStoreType<string>, O> : void
46
+ >;
47
+
48
+ /**
49
+ * SQLite implementation of `StorageBackendDisk.ts`
50
+ *
51
+ * `DatabaseCreator` is need to use (sqlite).
52
+ *
53
+ * Supports `node:sqlite`, `bun:sqlite`. (maybe also `better-sqlite3` support)
54
+ */
55
+ export class SqliteStorage extends Storage implements CloneableStorage {
56
+ public static readonly memoryPath = ":memory:";
57
+ public static readonly defaultTableName = "kvstore";
58
+
59
+ protected isInitialized = false;
60
+ #inTransaction = false;
61
+
62
+ // internal values
63
+ protected readonly database: DatabaseLike;
64
+ protected readonly dbPath: string;
65
+ protected readonly tableName: string;
66
+ protected readonly clearOnInit: boolean;
67
+ protected readonly databaseCreator: DatabaseCreator;
68
+
69
+ // queries
70
+ readonly #queryInit: SqlRunnable<void, void>;
71
+ readonly #queryGet: SqlRunnableKV<"context" | "key", "value_json">;
72
+ readonly #queryGetRaw: SqlRunnable<void, KVStoreType>;
73
+ readonly #querySet: SqlRunnableKV<"context" | "key" | "value_json", void>;
74
+ readonly #querySetRaw: SqlRunnable<KVStoreType, void>;
75
+ readonly #queryDelete: SqlRunnableKV<"context" | "key", void>;
76
+ readonly #queryKeys: SqlRunnableKV<"context", "key">;
77
+ readonly #queryValues: SqlRunnable<{ context: string }, { key: string; value_json: string }>;
78
+ readonly #queryContextSub: SqlRunnable<{ contextGlob: string }, { context: string }>;
79
+ readonly #queryClear: SqlRunnable<void, void>;
80
+ readonly #queryClearAll: SqlRunnable<{ context: string; contextGlob: string }, void>;
81
+ readonly #queryHas: SqlRunnable<{ context: string; key: string }, { has_record: 1 }>;
82
+ readonly #queryOpenBlob: SqlRunnable<
83
+ Pick<KVStoreType, "context" | "key">,
84
+ Pick<KVStoreType, "value_type" | "value_json" | "value_blob">
85
+ >;
86
+ readonly #queryWriteBlob: SqlRunnable<Pick<KVStoreType, "context" | "key" | "value_blob">, void>;
87
+
88
+ /**
89
+ * Create sqlite-based disk
90
+ *
91
+ * @param args.databaseCreator database instance creator
92
+ * @param args.path Database path (treats `null` as `:memory:`, DO NOT input `:memory:` directly)
93
+ * @param args.clear Clear on init
94
+ * @param args.tableName table name
95
+ */
96
+ constructor(args: { databaseCreator: DatabaseCreator; path: string | null; tableName?: string; clear?: boolean }) {
97
+ super();
98
+ const { databaseCreator, path, tableName, clear } = args;
99
+
100
+ this.dbPath = path === null ? SqliteStorage.memoryPath : path;
101
+ this.databaseCreator = databaseCreator;
102
+ this.database = databaseCreator(this.dbPath);
103
+
104
+ // tableName is vulnerable
105
+ // DO NOT USE FROM USER'S INPUT
106
+ this.tableName = tableName ?? SqliteStorage.defaultTableName;
107
+ this.clearOnInit = clear ?? false;
108
+
109
+ // ═════════════════════════════════════════════════════════════
110
+ // Query Preparation
111
+ // ═════════════════════════════════════════════════════════════
112
+
113
+ // ─────────────────────────────────────────────────────────────
114
+ // Schema Initialization
115
+ // ─────────────────────────────────────────────────────────────
116
+ this.#queryInit = this.database.prepare(`
117
+ CREATE TABLE IF NOT EXISTS ${this.tableName} (
118
+ context TEXT NOT NULL,
119
+ key TEXT NOT NULL,
120
+ value_type TEXT CHECK(value_type IN ('json', 'blob')),
121
+ value_json TEXT,
122
+ value_blob BLOB,
123
+ CONSTRAINT PKPair PRIMARY KEY (context, key)
124
+ ) STRICT
125
+ `);
126
+ this.#queryInit.run(); // Run once (prepare requires existing database in bun.js)
127
+
128
+ // ─────────────────────────────────────────────────────────────
129
+ // Read Operations
130
+ // ─────────────────────────────────────────────────────────────
131
+ this.#queryGet = this.database.prepare(`
132
+ SELECT value_json FROM ${this.tableName} WHERE
133
+ context=$context AND
134
+ key=$key AND
135
+ value_type='json'
136
+ `);
137
+
138
+ this.#queryGetRaw = this.database.prepare(`
139
+ SELECT * FROM ${this.tableName}
140
+ `);
141
+
142
+ this.#queryHas = this.database.prepare(`
143
+ SELECT EXISTS(
144
+ SELECT 1 FROM ${this.tableName}
145
+ WHERE context=$context AND key=$key
146
+ ) as has_record
147
+ `);
148
+
149
+ // ─────────────────────────────────────────────────────────────
150
+ // Write Operations
151
+ // ─────────────────────────────────────────────────────────────
152
+ this.#querySet = this.database.prepare(`
153
+ INSERT INTO ${this.tableName}
154
+ (context, key, value_type, value_json, value_blob)
155
+ VALUES($context, $key, 'json', $value_json, NULL)
156
+ ON CONFLICT(context, key)
157
+ DO UPDATE SET
158
+ value_type = 'json',
159
+ value_json = excluded.value_json,
160
+ value_blob = NULL
161
+ `);
162
+
163
+ this.#querySetRaw = this.database.prepare(`
164
+ INSERT INTO ${this.tableName}
165
+ (context, key, value_type, value_json, value_blob)
166
+ VALUES($context, $key, $value_type, $value_json, $value_blob)
167
+ ON CONFLICT(context, key)
168
+ DO UPDATE SET
169
+ value_type = excluded.value_type,
170
+ value_json = excluded.value_json,
171
+ value_blob = excluded.value_blob
172
+ `);
173
+
174
+ // ─────────────────────────────────────────────────────────────
175
+ // Delete Operations
176
+ // ─────────────────────────────────────────────────────────────
177
+ this.#queryDelete = this.database.prepare(`
178
+ DELETE FROM ${this.tableName} WHERE
179
+ context=$context AND
180
+ key=$key
181
+ `);
182
+
183
+ this.#queryClear = this.database.prepare(`
184
+ DELETE FROM ${this.tableName}
185
+ `);
186
+
187
+ this.#queryClearAll = this.database.prepare(`
188
+ DELETE FROM ${this.tableName} WHERE
189
+ context=$context OR context GLOB $contextGlob
190
+ `);
191
+
192
+ // ─────────────────────────────────────────────────────────────
193
+ // Context & Key Queries
194
+ // ─────────────────────────────────────────────────────────────
195
+ this.#queryKeys = this.database.prepare(`
196
+ SELECT DISTINCT key FROM ${this.tableName} WHERE
197
+ context=$context
198
+ `);
199
+
200
+ this.#queryValues = this.database.prepare(`
201
+ SELECT key, value_json FROM ${this.tableName} WHERE
202
+ context=$context AND
203
+ value_type='json'
204
+ `);
205
+
206
+ this.#queryContextSub = this.database.prepare(`
207
+ SELECT DISTINCT context FROM ${this.tableName} WHERE
208
+ context GLOB $contextGlob
209
+ `);
210
+
211
+ // ─────────────────────────────────────────────────────────────
212
+ // Blob Operations
213
+ // ─────────────────────────────────────────────────────────────
214
+ this.#queryOpenBlob = this.database.prepare(`
215
+ SELECT value_type, value_json, value_blob FROM ${this.tableName} WHERE
216
+ context=$context AND
217
+ key=$key
218
+ `);
219
+
220
+ this.#queryWriteBlob = this.database.prepare(`
221
+ INSERT INTO ${this.tableName}
222
+ (context, key, value_type, value_json, value_blob)
223
+ VALUES($context, $key, 'blob', NULL, $value_blob)
224
+ ON CONFLICT(context, key)
225
+ DO UPDATE SET
226
+ value_type = 'blob',
227
+ value_json = NULL,
228
+ value_blob = excluded.value_blob
229
+ `);
230
+ }
231
+
232
+ /**
233
+ * Manual transaction control
234
+ *
235
+ * Use this for explicit transaction management across multiple operations.
236
+ * Internal methods like `set()` will automatically detect and use external transactions.
237
+ *
238
+ * TODO: Sync transaction to native matter.js API
239
+ */
240
+ public transaction(mode: Transaction) {
241
+ switch (mode) {
242
+ case Transaction.BEGIN:
243
+ if (this.#inTransaction) {
244
+ throw new SqliteStorageError("transaction", "BEGIN", "Transaction is in progress.");
245
+ }
246
+ this.database.exec("BEGIN IMMEDIATE TRANSACTION");
247
+ this.#inTransaction = true;
248
+ break;
249
+
250
+ case Transaction.COMMIT:
251
+ if (!this.#inTransaction) {
252
+ throw new SqliteStorageError("transaction", "COMMIT", "No transaction in progress.");
253
+ }
254
+ this.database.exec("COMMIT");
255
+ this.#inTransaction = false;
256
+ break;
257
+
258
+ case Transaction.ROLLBACK:
259
+ if (!this.#inTransaction) {
260
+ return;
261
+ }
262
+ this.database.exec("ROLLBACK");
263
+ this.#inTransaction = false;
264
+ break;
265
+ }
266
+ }
267
+
268
+ protected withAnyTransaction<T>(callback: () => T) {
269
+ if (this.#inTransaction) {
270
+ // Use external transaction
271
+ return callback();
272
+ }
273
+ // Use internal transaction
274
+ this.transaction(Transaction.BEGIN);
275
+ try {
276
+ const result = callback();
277
+ this.transaction(Transaction.COMMIT);
278
+ return result;
279
+ } catch (err) {
280
+ this.transaction(Transaction.ROLLBACK);
281
+ throw err;
282
+ }
283
+ }
284
+
285
+ override get initialized() {
286
+ return this.isInitialized;
287
+ }
288
+
289
+ override async initialize(): Promise<void> {
290
+ if (this.clearOnInit) {
291
+ await this.clear(false);
292
+ }
293
+ this.isInitialized = true;
294
+ }
295
+
296
+ public clone(): Storage {
297
+ const clonedStorage = new SqliteStorage({
298
+ databaseCreator: this.databaseCreator,
299
+ path: null,
300
+ tableName: this.tableName,
301
+ clear: false,
302
+ });
303
+
304
+ const rawData = this.getRawAll();
305
+ clonedStorage.setRaw(rawData);
306
+ clonedStorage.isInitialized = true;
307
+ return clonedStorage;
308
+ }
309
+
310
+ override close() {
311
+ this.isInitialized = false;
312
+ this.database.close();
313
+ }
314
+
315
+ override get<T extends SupportedStorageTypes>(contexts: string[], key: string): T | null | undefined {
316
+ const queryResult = this.#queryGet.get(buildContextKeyPair(contexts, key));
317
+ // Bun returns null, NodeJs returns undefined
318
+ if (queryResult == null) {
319
+ return undefined;
320
+ }
321
+ if (queryResult.value_json === null) {
322
+ // Shouldn't be happened. (Confused with BLOB?)
323
+ this.delete(contexts, key);
324
+
325
+ throw new SqliteStorageError(
326
+ "get",
327
+ buildContextKeyLog(contexts, key),
328
+ "path has null json-value! (expected non-null value)",
329
+ );
330
+ }
331
+
332
+ return fromJson(queryResult.value_json) as T | null;
333
+ }
334
+
335
+ protected getRawAll() {
336
+ return this.#queryGetRaw.all().filter(v => v != null);
337
+ }
338
+
339
+ override set(contexts: string[], key: string, value: SupportedStorageTypes): void;
340
+ override set(contexts: string[], values: Record<string, SupportedStorageTypes>): void;
341
+ override set(
342
+ contexts: string[],
343
+ keyOrValues: string | Record<string, SupportedStorageTypes>,
344
+ value?: SupportedStorageTypes,
345
+ ) {
346
+ if (typeof keyOrValues === "string") {
347
+ if (value === undefined) {
348
+ // If user called set(contexts, key),
349
+ // indented behavior should be error instead of setting `undefined JSON`.
350
+ throw new SqliteStorageError(
351
+ "set",
352
+ buildContextKeyLog(contexts, keyOrValues),
353
+ "Use null instead of undefined if you want to store null value!",
354
+ );
355
+ }
356
+ this.setValue(contexts, keyOrValues, toJson(value));
357
+ } else {
358
+ // use internal/external transaction
359
+ this.withAnyTransaction(() => {
360
+ for (const [key, value] of Object.entries(keyOrValues)) {
361
+ this.setValue(contexts, key, toJson(value ?? null));
362
+ }
363
+ });
364
+ }
365
+ }
366
+
367
+ /**
368
+ * Set [contexts, key] to value
369
+ * @param contexts Context
370
+ * @param key Key
371
+ * @param value Value
372
+ * @returns
373
+ */
374
+ protected setValue(contexts: string[], key: string, value: string) {
375
+ const { changes } = this.#querySet.run({
376
+ ...buildContextKeyPair(contexts, key),
377
+ value_json: value,
378
+ });
379
+ if (Number(changes) <= 0) {
380
+ throw new SqliteStorageError(
381
+ "set",
382
+ buildContextKeyLog(contexts, key),
383
+ `Something went wrong! Value wasn't changed.`,
384
+ );
385
+ }
386
+ }
387
+
388
+ /**
389
+ * Set Raw data. (for copy)
390
+ */
391
+ protected setRaw(rawData: KVStoreType[]) {
392
+ if (rawData.length <= 0) {
393
+ return;
394
+ }
395
+ if (rawData.length === 1) {
396
+ const raw = rawData[0];
397
+ const { changes } = this.#querySetRaw.run({
398
+ context: raw.context,
399
+ key: raw.key,
400
+ value_type: raw.value_type,
401
+ value_json: raw.value_json,
402
+ value_blob: raw.value_blob,
403
+ });
404
+ if (Number(changes) <= 0) {
405
+ throw new SqliteStorageError(
406
+ "setraw",
407
+ `${raw.context}$${raw.key}`,
408
+ `Something went wrong! Value wasn't changed.`,
409
+ );
410
+ }
411
+ return;
412
+ }
413
+
414
+ this.withAnyTransaction(() => {
415
+ for (const raw of rawData) {
416
+ const { changes } = this.#querySetRaw.run({
417
+ context: raw.context,
418
+ key: raw.key,
419
+ value_type: raw.value_type,
420
+ value_json: raw.value_json,
421
+ value_blob: raw.value_blob,
422
+ });
423
+ if (Number(changes) <= 0) {
424
+ throw new SqliteStorageError(
425
+ "setraw",
426
+ `${raw.context}$${raw.key}`,
427
+ `Something went wrong! Value wasn't changed.`,
428
+ );
429
+ }
430
+ }
431
+ });
432
+ }
433
+
434
+ override delete(contexts: string[], key: string) {
435
+ this.#queryDelete.run(buildContextKeyPair(contexts, key));
436
+ }
437
+
438
+ override keys(contexts: string[]) {
439
+ const queryResults = this.#queryKeys
440
+ .all({
441
+ context: buildContextPath(contexts),
442
+ })
443
+ .filter(v => v != null);
444
+
445
+ return queryResults.map(v => v.key);
446
+ }
447
+
448
+ override values(contexts: string[]) {
449
+ const queryResults = this.#queryValues
450
+ .all({
451
+ context: buildContextPath(contexts),
452
+ })
453
+ .filter(v => v != null);
454
+
455
+ const record = Object.create(null) as Record<string, SupportedStorageTypes>;
456
+
457
+ for (const element of queryResults) {
458
+ record[element.key] = fromJson(element.value_json);
459
+ }
460
+
461
+ return record;
462
+ }
463
+
464
+ /**
465
+ * Return sub contexts of context
466
+ * (search nested depth, return 1 depth of them)
467
+ * @param contexts context path
468
+ * @returns sub contexts
469
+ */
470
+ override contexts(contexts: string[]): string[] {
471
+ const parentCtx = buildContextPath(contexts);
472
+ let subContexts: string[];
473
+
474
+ if (contexts.length === 0) {
475
+ // Query all root contexts (may include nested ones)
476
+ const allContexts = this.#queryContextSub.all({ contextGlob: "*" }).filter(v => v != null);
477
+
478
+ subContexts = allContexts.map(v => {
479
+ const firstDotIndex = v.context.indexOf(".");
480
+ if (firstDotIndex < 0) {
481
+ // root
482
+ return v.context;
483
+ }
484
+ return v.context.substring(0, firstDotIndex);
485
+ });
486
+ } else {
487
+ // Query all sub-contexts (may include deeply nested ones)
488
+ const allSubContexts = this.#queryContextSub
489
+ .all({
490
+ contextGlob: escapeGlob(parentCtx) + ".*",
491
+ })
492
+ .filter(v => v != null);
493
+
494
+ subContexts = allSubContexts.map(v => {
495
+ const subKey = v.context.substring(parentCtx.length + 1);
496
+ const dotIndex = subKey.indexOf(".");
497
+
498
+ if (dotIndex < 0) {
499
+ // direct child
500
+ return subKey;
501
+ }
502
+ return subKey.substring(0, dotIndex);
503
+ });
504
+ }
505
+
506
+ // Remove duplicates and empty values
507
+ return [...new Set(subContexts.filter(c => c != null && c.trim().length > 0))];
508
+ }
509
+
510
+ /**
511
+ * Should be implement to platform specific class
512
+ * when `completely = true`
513
+ *
514
+ * basic cleanup query for here.
515
+ */
516
+ public async clear(_completely?: boolean) {
517
+ this.#queryClear.run();
518
+ }
519
+
520
+ override clearAll(contexts: string[]) {
521
+ // Match StorageBackendDisk behavior: if contexts is empty, do nothing
522
+ if (contexts.length === 0) {
523
+ return;
524
+ }
525
+ const contextPath = buildContextPath(contexts);
526
+
527
+ // Delete the context itself and all sub-contexts
528
+ this.#queryClearAll.run({
529
+ context: contextPath,
530
+ contextGlob: escapeGlob(contextPath) + ".*",
531
+ });
532
+ }
533
+
534
+ override has(contexts: string[], key: string) {
535
+ const result = this.#queryHas.get(buildContextKeyPair(contexts, key));
536
+ return result?.has_record === 1;
537
+ }
538
+
539
+ override openBlob(contexts: string[], key: string): Blob {
540
+ const queryResult = this.#queryOpenBlob.get(buildContextKeyPair(contexts, key));
541
+ if (queryResult == null) {
542
+ return new Blob();
543
+ }
544
+ if (queryResult.value_type === "blob" && queryResult.value_blob != null) {
545
+ return new Blob([new Uint8Array(queryResult.value_blob)]);
546
+ }
547
+ if (queryResult.value_type === "json" && queryResult.value_json != null) {
548
+ return new Blob([queryResult.value_json]);
549
+ }
550
+
551
+ // Corrupted context$key
552
+ this.delete(contexts, key);
553
+ return new Blob();
554
+ }
555
+
556
+ override async writeBlobFromStream(contexts: string[], key: string, stream: ReadableStream<Bytes>) {
557
+ const arrayBuffer = await new Response(stream).arrayBuffer();
558
+ const bytes = new Uint8Array(arrayBuffer);
559
+
560
+ const queryResult = this.#queryWriteBlob.run({
561
+ ...buildContextKeyPair(contexts, key),
562
+ value_blob: bytes,
563
+ });
564
+ if (Number(queryResult.changes) <= 0) {
565
+ throw new SqliteStorageError(
566
+ "writeBlob",
567
+ buildContextKeyLog(contexts, key),
568
+ `Something went wrong! Value wasn't changed.`,
569
+ );
570
+ }
571
+ }
572
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2022-2026 Matter.js Authors
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import { StorageError } from "@matter/general";
8
+
9
+ /**
10
+ * StorageError with
11
+ *
12
+ * `methodType`: Method type of Storage
13
+ * `contextKey`: Context$Key
14
+ */
15
+ export class SqliteStorageError extends StorageError {
16
+ constructor(
17
+ public readonly methodType: string,
18
+ public readonly contextKey: string,
19
+ public readonly mainReason: string,
20
+ ) {
21
+ super(`[${methodType.toUpperCase()}] ${contextKey}: ${mainReason}`);
22
+ }
23
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2022-2026 Matter.js Authors
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ /**
8
+ * Stores various utility types used by sqlite disk.
9
+ */
10
+
11
+ // bytes
12
+ export type SafeUint8Array = Uint8Array<ArrayBuffer>;
13
+ // TEXT, BLOB, NUMBER, null
14
+ export type SqliteDataType = null | number | bigint | string | SafeUint8Array;
15
+ // Key-Value of SQLiteDataType
16
+ export type SqliteResultType = Record<string, SqliteDataType>;
17
+
18
+ /**
19
+ * DatabaseLike
20
+ *
21
+ * compatible with `node:sqlite`(type mismatch), `bun:sqlite`
22
+ */
23
+ export interface DatabaseLike {
24
+ prepare<O extends SqliteResultType | void>(query: string): SqlRunnableSimple<O> & SqlRunnableParam<any, O>;
25
+ exec(sql: string): void;
26
+ close(): void;
27
+ }
28
+
29
+ export type DatabaseCreator = (path: string) => DatabaseLike;
30
+
31
+ /**
32
+ * Defines `I` -> `O` Runnable
33
+ *
34
+ * `I` type is treated as Input.
35
+ *
36
+ * `O` type is treated as Output.
37
+ *
38
+ * `void` type is used for no input/output.
39
+ */
40
+ export type SqlRunnable<
41
+ I extends SqliteResultType | void,
42
+ O extends SqliteResultType | void,
43
+ > = I extends SqliteResultType ? SqlRunnableParam<I, O> : SqlRunnableSimple<O>;
44
+
45
+ /**
46
+ * Database method with no parameter.
47
+ *
48
+ * (`I` must be void)
49
+ */
50
+ interface SqlRunnableSimple<O extends SqliteResultType | void> {
51
+ run(): { changes: number | bigint };
52
+ get(): O | null | undefined;
53
+ all(): Array<O | undefined>; // Bun uses Array<T | undefined>
54
+ }
55
+
56
+ /**
57
+ * Database method with parameter.
58
+ */
59
+ interface SqlRunnableParam<I extends SqliteResultType, O extends SqliteResultType | void> {
60
+ run(arg: I): { changes: number | bigint };
61
+ get(arg: I): O | null | undefined;
62
+ all(arg: I): Array<O | undefined>; // Bun uses Array<T | undefined>
63
+ }
64
+
65
+ /**
66
+ * SQLite Transaction mode
67
+ *
68
+ * TODO: Move transaction control to higher level (matter.js Transaction API)
69
+ * and remove this
70
+ */
71
+ export enum SqliteTransaction {
72
+ BEGIN,
73
+ COMMIT,
74
+ ROLLBACK,
75
+ }