@matter/nodejs 0.16.6-alpha.0-20260121-bc6d62766 → 0.16.6
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/cjs/config.d.ts +6 -0
- package/dist/cjs/config.d.ts.map +1 -1
- package/dist/cjs/config.js +14 -1
- package/dist/cjs/config.js.map +1 -1
- package/dist/cjs/environment/NodeJsEnvironment.d.ts +2 -1
- package/dist/cjs/environment/NodeJsEnvironment.d.ts.map +1 -1
- package/dist/cjs/environment/NodeJsEnvironment.js +28 -10
- package/dist/cjs/environment/NodeJsEnvironment.js.map +1 -1
- package/dist/cjs/storage/StorageFactory.d.ts +52 -0
- package/dist/cjs/storage/StorageFactory.d.ts.map +1 -0
- package/dist/cjs/storage/StorageFactory.js +215 -0
- package/dist/cjs/storage/StorageFactory.js.map +6 -0
- package/dist/cjs/storage/fs/StorageBackendDisk.d.ts.map +1 -0
- package/dist/cjs/storage/{StorageBackendDisk.js.map → fs/StorageBackendDisk.js.map} +1 -1
- package/dist/cjs/storage/fs/StorageBackendJsonFile.d.ts.map +1 -0
- package/dist/cjs/storage/{StorageBackendJsonFile.js.map → fs/StorageBackendJsonFile.js.map} +1 -1
- package/dist/cjs/storage/fs/index.d.ts +8 -0
- package/dist/cjs/storage/fs/index.d.ts.map +1 -0
- package/dist/cjs/storage/fs/index.js +25 -0
- package/dist/cjs/storage/fs/index.js.map +6 -0
- package/dist/cjs/storage/index.d.ts +3 -2
- package/dist/cjs/storage/index.d.ts.map +1 -1
- package/dist/cjs/storage/index.js +3 -2
- package/dist/cjs/storage/index.js.map +1 -1
- package/dist/cjs/storage/sqlite/SqliteStorage.d.ts +105 -0
- package/dist/cjs/storage/sqlite/SqliteStorage.d.ts.map +1 -0
- package/dist/cjs/storage/sqlite/SqliteStorage.js +439 -0
- package/dist/cjs/storage/sqlite/SqliteStorage.js.map +6 -0
- package/dist/cjs/storage/sqlite/SqliteStorageError.d.ts +19 -0
- package/dist/cjs/storage/sqlite/SqliteStorageError.d.ts.map +1 -0
- package/dist/cjs/storage/sqlite/SqliteStorageError.js +38 -0
- package/dist/cjs/storage/sqlite/SqliteStorageError.js.map +6 -0
- package/dist/cjs/storage/sqlite/SqliteTypes.d.ts +67 -0
- package/dist/cjs/storage/sqlite/SqliteTypes.d.ts.map +1 -0
- package/dist/cjs/storage/sqlite/SqliteTypes.js +35 -0
- package/dist/cjs/storage/sqlite/SqliteTypes.js.map +6 -0
- package/dist/cjs/storage/sqlite/SqliteUtil.d.ts +33 -0
- package/dist/cjs/storage/sqlite/SqliteUtil.d.ts.map +1 -0
- package/dist/cjs/storage/sqlite/SqliteUtil.js +64 -0
- package/dist/cjs/storage/sqlite/SqliteUtil.js.map +6 -0
- package/dist/cjs/storage/sqlite/index.d.ts +10 -0
- package/dist/cjs/storage/sqlite/index.d.ts.map +1 -0
- package/dist/cjs/storage/sqlite/index.js +34 -0
- package/dist/cjs/storage/sqlite/index.js.map +6 -0
- package/dist/cjs/storage/sqlite/platform/BunSqlite.d.ts +17 -0
- package/dist/cjs/storage/sqlite/platform/BunSqlite.d.ts.map +1 -0
- package/dist/cjs/storage/sqlite/platform/BunSqlite.js +62 -0
- package/dist/cjs/storage/sqlite/platform/BunSqlite.js.map +6 -0
- package/dist/cjs/storage/sqlite/platform/NodeJsSqlite.d.ts +12 -0
- package/dist/cjs/storage/sqlite/platform/NodeJsSqlite.d.ts.map +1 -0
- package/dist/cjs/storage/sqlite/platform/NodeJsSqlite.js +49 -0
- package/dist/cjs/storage/sqlite/platform/NodeJsSqlite.js.map +6 -0
- package/dist/cjs/storage/sqlite/platform/PlatformSqlite.d.ts +8 -0
- package/dist/cjs/storage/sqlite/platform/PlatformSqlite.d.ts.map +1 -0
- package/dist/cjs/storage/sqlite/platform/PlatformSqlite.js +54 -0
- package/dist/cjs/storage/sqlite/platform/PlatformSqlite.js.map +6 -0
- package/dist/cjs/util/runtimeChecks.d.ts +14 -0
- package/dist/cjs/util/runtimeChecks.d.ts.map +1 -0
- package/dist/cjs/util/runtimeChecks.js +45 -0
- package/dist/cjs/util/runtimeChecks.js.map +6 -0
- package/dist/esm/config.d.ts +6 -0
- package/dist/esm/config.d.ts.map +1 -1
- package/dist/esm/config.js +14 -1
- package/dist/esm/config.js.map +1 -1
- package/dist/esm/environment/NodeJsEnvironment.d.ts +2 -1
- package/dist/esm/environment/NodeJsEnvironment.d.ts.map +1 -1
- package/dist/esm/environment/NodeJsEnvironment.js +29 -10
- package/dist/esm/environment/NodeJsEnvironment.js.map +1 -1
- package/dist/esm/storage/StorageFactory.d.ts +52 -0
- package/dist/esm/storage/StorageFactory.d.ts.map +1 -0
- package/dist/esm/storage/StorageFactory.js +195 -0
- package/dist/esm/storage/StorageFactory.js.map +6 -0
- package/dist/esm/storage/fs/StorageBackendDisk.d.ts.map +1 -0
- package/dist/esm/storage/{StorageBackendDisk.js.map → fs/StorageBackendDisk.js.map} +1 -1
- package/dist/esm/storage/fs/StorageBackendJsonFile.d.ts.map +1 -0
- package/dist/esm/storage/{StorageBackendJsonFile.js.map → fs/StorageBackendJsonFile.js.map} +1 -1
- package/dist/esm/storage/fs/index.d.ts +8 -0
- package/dist/esm/storage/fs/index.d.ts.map +1 -0
- package/dist/esm/storage/fs/index.js +8 -0
- package/dist/esm/storage/fs/index.js.map +6 -0
- package/dist/esm/storage/index.d.ts +3 -2
- package/dist/esm/storage/index.d.ts.map +1 -1
- package/dist/esm/storage/index.js +3 -2
- package/dist/esm/storage/index.js.map +1 -1
- package/dist/esm/storage/sqlite/SqliteStorage.d.ts +105 -0
- package/dist/esm/storage/sqlite/SqliteStorage.d.ts.map +1 -0
- package/dist/esm/storage/sqlite/SqliteStorage.js +423 -0
- package/dist/esm/storage/sqlite/SqliteStorage.js.map +6 -0
- package/dist/esm/storage/sqlite/SqliteStorageError.d.ts +19 -0
- package/dist/esm/storage/sqlite/SqliteStorageError.d.ts.map +1 -0
- package/dist/esm/storage/sqlite/SqliteStorageError.js +18 -0
- package/dist/esm/storage/sqlite/SqliteStorageError.js.map +6 -0
- package/dist/esm/storage/sqlite/SqliteTypes.d.ts +67 -0
- package/dist/esm/storage/sqlite/SqliteTypes.d.ts.map +1 -0
- package/dist/esm/storage/sqlite/SqliteTypes.js +15 -0
- package/dist/esm/storage/sqlite/SqliteTypes.js.map +6 -0
- package/dist/esm/storage/sqlite/SqliteUtil.d.ts +33 -0
- package/dist/esm/storage/sqlite/SqliteUtil.d.ts.map +1 -0
- package/dist/esm/storage/sqlite/SqliteUtil.js +44 -0
- package/dist/esm/storage/sqlite/SqliteUtil.js.map +6 -0
- package/dist/esm/storage/sqlite/index.d.ts +10 -0
- package/dist/esm/storage/sqlite/index.d.ts.map +1 -0
- package/dist/esm/storage/sqlite/index.js +14 -0
- package/dist/esm/storage/sqlite/index.js.map +6 -0
- package/dist/esm/storage/sqlite/platform/BunSqlite.d.ts +17 -0
- package/dist/esm/storage/sqlite/platform/BunSqlite.d.ts.map +1 -0
- package/dist/esm/storage/sqlite/platform/BunSqlite.js +32 -0
- package/dist/esm/storage/sqlite/platform/BunSqlite.js.map +6 -0
- package/dist/esm/storage/sqlite/platform/NodeJsSqlite.d.ts +12 -0
- package/dist/esm/storage/sqlite/platform/NodeJsSqlite.d.ts.map +1 -0
- package/dist/esm/storage/sqlite/platform/NodeJsSqlite.js +29 -0
- package/dist/esm/storage/sqlite/platform/NodeJsSqlite.js.map +6 -0
- package/dist/esm/storage/sqlite/platform/PlatformSqlite.d.ts +8 -0
- package/dist/esm/storage/sqlite/platform/PlatformSqlite.d.ts.map +1 -0
- package/dist/esm/storage/sqlite/platform/PlatformSqlite.js +24 -0
- package/dist/esm/storage/sqlite/platform/PlatformSqlite.js.map +6 -0
- package/dist/esm/util/runtimeChecks.d.ts +14 -0
- package/dist/esm/util/runtimeChecks.d.ts.map +1 -0
- package/dist/esm/util/runtimeChecks.js +25 -0
- package/dist/esm/util/runtimeChecks.js.map +6 -0
- package/package.json +10 -10
- package/src/config.ts +18 -4
- package/src/environment/NodeJsEnvironment.ts +41 -12
- package/src/net/NodeJsHttpEndpoint.ts +1 -1
- package/src/storage/StorageFactory.ts +310 -0
- package/src/storage/fs/index.ts +8 -0
- package/src/storage/index.ts +3 -2
- package/src/storage/sqlite/SqliteStorage.ts +572 -0
- package/src/storage/sqlite/SqliteStorageError.ts +23 -0
- package/src/storage/sqlite/SqliteTypes.ts +75 -0
- package/src/storage/sqlite/SqliteUtil.ts +61 -0
- package/src/storage/sqlite/index.ts +10 -0
- package/src/storage/sqlite/platform/BunSqlite.ts +40 -0
- package/src/storage/sqlite/platform/NodeJsSqlite.ts +35 -0
- package/src/storage/sqlite/platform/PlatformSqlite.ts +52 -0
- package/src/util/runtimeChecks.ts +31 -0
- package/dist/cjs/storage/StorageBackendDisk.d.ts.map +0 -1
- package/dist/cjs/storage/StorageBackendJsonFile.d.ts.map +0 -1
- package/dist/esm/storage/StorageBackendDisk.d.ts.map +0 -1
- package/dist/esm/storage/StorageBackendJsonFile.d.ts.map +0 -1
- /package/dist/cjs/storage/{StorageBackendDisk.d.ts → fs/StorageBackendDisk.d.ts} +0 -0
- /package/dist/cjs/storage/{StorageBackendDisk.js → fs/StorageBackendDisk.js} +0 -0
- /package/dist/cjs/storage/{StorageBackendJsonFile.d.ts → fs/StorageBackendJsonFile.d.ts} +0 -0
- /package/dist/cjs/storage/{StorageBackendJsonFile.js → fs/StorageBackendJsonFile.js} +0 -0
- /package/dist/esm/storage/{StorageBackendDisk.d.ts → fs/StorageBackendDisk.d.ts} +0 -0
- /package/dist/esm/storage/{StorageBackendDisk.js → fs/StorageBackendDisk.js} +0 -0
- /package/dist/esm/storage/{StorageBackendJsonFile.d.ts → fs/StorageBackendJsonFile.d.ts} +0 -0
- /package/dist/esm/storage/{StorageBackendJsonFile.js → fs/StorageBackendJsonFile.js} +0 -0
- /package/src/storage/{StorageBackendDisk.ts → fs/StorageBackendDisk.ts} +0 -0
- /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
|
+
}
|