clay-server 2.33.1 → 2.34.0-beta.1

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/lib/config.js CHANGED
@@ -2,31 +2,27 @@ var fs = require("fs");
2
2
  var path = require("path");
3
3
  var os = require("os");
4
4
  var net = require("net");
5
+ var execFileSync = require("child_process").execFileSync;
6
+
7
+ function isSafeSystemUserName(name) {
8
+ return typeof name === "string" && /^[a-z_][a-z0-9_-]*[$]?$/.test(name);
9
+ }
5
10
 
6
11
  // When running under sudo, resolve the real user's home directory
7
12
  // so that ~/.clay/ points to the original user's data, not /root/.clay/
8
13
  function getRealHome() {
9
14
  var sudoUser = process.env.SUDO_USER;
10
- if (sudoUser && sudoUser !== "root") {
15
+ if (sudoUser && sudoUser !== "root" && isSafeSystemUserName(sudoUser)) {
11
16
  // 1. Try getent passwd (works on most Linux, may fail with some NSS configs)
12
17
  try {
13
- var entry = require("child_process")
14
- .execSync("getent passwd " + sudoUser, { encoding: "utf8", timeout: 3000 })
15
- .trim();
18
+ var entry = execFileSync("getent", ["passwd", sudoUser], { encoding: "utf8", timeout: 3000 }).trim();
16
19
  var home = entry.split(":")[5];
17
20
  if (home && fs.existsSync(home)) return home;
18
21
  } catch (e) {}
19
- // 2. Try shell expansion of ~USER
20
- try {
21
- var home = require("child_process")
22
- .execSync("eval echo ~" + sudoUser, { encoding: "utf8", timeout: 3000 })
23
- .trim();
24
- if (home && home !== "~" + sudoUser && fs.existsSync(home)) return home;
25
- } catch (e2) {}
26
- // 3. Direct path fallback (GCE, cloud VMs)
22
+ // 2. Direct path fallback (GCE, cloud VMs)
27
23
  var directHome = "/home/" + sudoUser;
28
24
  if (fs.existsSync(directHome)) return directHome;
29
- // 4. SUDO_USER's original HOME (some sudo configs preserve it)
25
+ // 3. SUDO_USER's original HOME (some sudo configs preserve it)
30
26
  if (process.env.SUDO_HOME && fs.existsSync(process.env.SUDO_HOME)) return process.env.SUDO_HOME;
31
27
  }
32
28
  return os.homedir();
package/lib/daemon.js CHANGED
@@ -53,11 +53,11 @@ console.log("[daemon] UID: " + (typeof process.getuid === "function" ? process.g
53
53
 
54
54
  // --- OS users mode: check required system dependencies ---
55
55
  if (config.osUsers) {
56
- var { execSync: checkExec } = require("child_process");
56
+ var checkExec = require("child_process").execFileSync;
57
57
  var missing = [];
58
- try { checkExec("which setfacl", { stdio: "ignore" }); } catch (e) { missing.push("acl (setfacl)"); }
59
- try { checkExec("which git", { stdio: "ignore" }); } catch (e) { missing.push("git"); }
60
- try { checkExec("which useradd", { stdio: "ignore" }); } catch (e) { missing.push("useradd"); }
58
+ try { checkExec("which", ["setfacl"], { stdio: "ignore" }); } catch (e) { missing.push("acl (setfacl)"); }
59
+ try { checkExec("which", ["git"], { stdio: "ignore" }); } catch (e) { missing.push("git"); }
60
+ try { checkExec("which", ["useradd"], { stdio: "ignore" }); } catch (e) { missing.push("useradd"); }
61
61
  if (missing.length > 0) {
62
62
  console.error("[daemon] OS users mode requires missing system packages: " + missing.join(", "));
63
63
  console.error("[daemon] Install with: sudo apt install " + missing.map(function (m) { return m.split(" ")[0]; }).join(" "));
@@ -179,7 +179,7 @@ var relay = createServer({
179
179
  onCreateProject: function (projectName, wsUser) {
180
180
  console.log("[daemon] onCreateProject wsUser:", JSON.stringify(wsUser ? { id: wsUser.id, role: wsUser.role, username: wsUser.username, linuxUser: wsUser.linuxUser } : null));
181
181
  var os = require("os");
182
- var { execSync } = require("child_process");
182
+ var execFileSync = require("child_process").execFileSync;
183
183
  var baseDir;
184
184
  if (config.osUsers) {
185
185
  baseDir = "/var/clay/projects";
@@ -199,21 +199,23 @@ var relay = createServer({
199
199
  if (linuxUser) {
200
200
  var uidGid = null;
201
201
  try {
202
- var passwdLine = execSync("id -u " + linuxUser + " && id -g " + linuxUser, { encoding: "utf8" }).trim().split("\n");
203
- uidGid = { uid: parseInt(passwdLine[0], 10), gid: parseInt(passwdLine[1], 10) };
202
+ uidGid = {
203
+ uid: parseInt(execFileSync("id", ["-u", linuxUser], { encoding: "utf8", stdio: "pipe" }).trim(), 10),
204
+ gid: parseInt(execFileSync("id", ["-g", linuxUser], { encoding: "utf8", stdio: "pipe" }).trim(), 10),
205
+ };
204
206
  } catch (e) {}
205
207
  if (uidGid) {
206
208
  fs.chmodSync(targetDir, 0o700);
207
- execSync("chown -R " + linuxUser + ":" + linuxUser + " " + JSON.stringify(targetDir));
208
- execSync("git init", { cwd: targetDir, uid: uidGid.uid, gid: uidGid.gid, env: { PATH: "/usr/local/bin:/usr/bin:/bin" } });
209
+ execFileSync("chown", ["-R", linuxUser + ":" + linuxUser, targetDir]);
210
+ execFileSync("git", ["init"], { cwd: targetDir, uid: uidGid.uid, gid: uidGid.gid, env: { PATH: "/usr/local/bin:/usr/bin:/bin" } });
209
211
  } else {
210
- execSync("git init", { cwd: targetDir });
212
+ execFileSync("git", ["init"], { cwd: targetDir });
211
213
  }
212
214
  } else {
213
- execSync("git init", { cwd: targetDir });
215
+ execFileSync("git", ["init"], { cwd: targetDir });
214
216
  }
215
217
  } else {
216
- execSync("git init", { cwd: targetDir });
218
+ execFileSync("git", ["init"], { cwd: targetDir });
217
219
  }
218
220
  } catch (e) {
219
221
  try { fs.rmSync(targetDir, { recursive: true, force: true }); } catch (ce) {}
@@ -245,7 +247,8 @@ var relay = createServer({
245
247
  },
246
248
  onCloneProject: function (cloneUrl, wsUser, callback) {
247
249
  var os = require("os");
248
- var { spawn, execSync } = require("child_process");
250
+ var spawn = require("child_process").spawn;
251
+ var execFileSync = require("child_process").execFileSync;
249
252
  var baseDir;
250
253
  if (config.osUsers) {
251
254
  baseDir = "/var/clay/projects";
@@ -262,9 +265,8 @@ var relay = createServer({
262
265
  var spawnOpts = { cwd: baseDir };
263
266
  if (config.osUsers && wsUser && wsUser.linuxUser) {
264
267
  try {
265
- var passwdLine = execSync("id -u " + wsUser.linuxUser + " && id -g " + wsUser.linuxUser, { encoding: "utf8" }).trim().split("\n");
266
- spawnOpts.uid = parseInt(passwdLine[0], 10);
267
- spawnOpts.gid = parseInt(passwdLine[1], 10);
268
+ spawnOpts.uid = parseInt(execFileSync("id", ["-u", wsUser.linuxUser], { encoding: "utf8", stdio: "pipe" }).trim(), 10);
269
+ spawnOpts.gid = parseInt(execFileSync("id", ["-g", wsUser.linuxUser], { encoding: "utf8", stdio: "pipe" }).trim(), 10);
268
270
  spawnOpts.env = Object.assign({}, process.env, {
269
271
  HOME: "/home/" + wsUser.linuxUser,
270
272
  USER: wsUser.linuxUser
@@ -304,7 +306,7 @@ var relay = createServer({
304
306
  if (config.osUsers && wsUser && wsUser.linuxUser) {
305
307
  try {
306
308
  fs.chmodSync(targetDir, 0o700);
307
- execSync("chown -R " + wsUser.linuxUser + ":" + wsUser.linuxUser + " " + JSON.stringify(targetDir));
309
+ execFileSync("chown", ["-R", wsUser.linuxUser + ":" + wsUser.linuxUser, targetDir]);
308
310
  } catch (e) {}
309
311
  }
310
312
  // Register project - creator always becomes owner
@@ -0,0 +1,337 @@
1
+ var fs = require("fs");
2
+ var path = require("path");
3
+ var config = require("./config");
4
+
5
+ var sqlite;
6
+ try {
7
+ sqlite = require("node:sqlite");
8
+ } catch (e) {
9
+ throw new Error("Mate datastores require Node 22.13.0 or newer with node:sqlite available.");
10
+ }
11
+
12
+ function parseNodeVersion(version) {
13
+ var parts = String(version || "").split(".");
14
+ return {
15
+ major: parseInt(parts[0] || "0", 10) || 0,
16
+ minor: parseInt(parts[1] || "0", 10) || 0,
17
+ patch: parseInt(parts[2] || "0", 10) || 0,
18
+ };
19
+ }
20
+
21
+ function assertNodeVersion() {
22
+ var v = parseNodeVersion(process.versions.node);
23
+ if (v.major < 22 || (v.major === 22 && v.minor < 13)) {
24
+ throw new Error("Mate datastores require Node 22.13.0 or newer.");
25
+ }
26
+ }
27
+
28
+ assertNodeVersion();
29
+
30
+ var DatabaseSync = sqlite.DatabaseSync;
31
+
32
+ var MAX_ROWS = 200;
33
+ var MAX_RESULT_BYTES = 1024 * 1024;
34
+ var DB_SIZE_WARNING_BYTES = 100 * 1024 * 1024;
35
+ var PRAGMA_BUSY_TIMEOUT = 5000;
36
+ var CLAY_META_VERSION = "1";
37
+
38
+ var _dbCache = {};
39
+
40
+ function getMateDbPath(opts) {
41
+ if (opts && opts.dbPath) return String(opts.dbPath);
42
+ if (opts && opts.mateDir) return path.join(String(opts.mateDir), "store.db");
43
+ var userId = opts && opts.userId ? String(opts.userId) : "default";
44
+ var mateId = opts && opts.mateId ? String(opts.mateId) : null;
45
+ if (!mateId) throw new Error("Mate datastore requires a mateId.");
46
+ return path.join(config.CONFIG_DIR, "mates", userId, mateId, "store.db");
47
+ }
48
+
49
+ function ensureParentDir(filePath) {
50
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
51
+ }
52
+
53
+ function getDbSizeBytes(dbPath) {
54
+ try {
55
+ return fs.statSync(dbPath).size;
56
+ } catch (e) {
57
+ return 0;
58
+ }
59
+ }
60
+
61
+ function openDatabase(dbPath) {
62
+ var db = new DatabaseSync(dbPath);
63
+ try { db.exec("PRAGMA journal_mode = WAL"); } catch (e) {}
64
+ try { db.exec("PRAGMA foreign_keys = ON"); } catch (e2) {}
65
+ try { db.exec("PRAGMA busy_timeout = " + PRAGMA_BUSY_TIMEOUT); } catch (e3) {}
66
+ return db;
67
+ }
68
+
69
+ function initClayMeta(db) {
70
+ var now = new Date().toISOString();
71
+ db.exec("CREATE TABLE IF NOT EXISTS clay_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL)");
72
+ var stmt = db.prepare("INSERT OR IGNORE INTO clay_meta (key, value) VALUES (?, ?)");
73
+ stmt.run("clay_meta_version", CLAY_META_VERSION);
74
+ stmt.run("created_at", now);
75
+ stmt.run("last_opened_at", now);
76
+ db.prepare("UPDATE clay_meta SET value = ? WHERE key = ?").run(now, "last_opened_at");
77
+ }
78
+
79
+ function ensureMateDatastore(opts) {
80
+ var dbPath = getMateDbPath(opts);
81
+ if (_dbCache[dbPath] && _dbCache[dbPath].db) return _dbCache[dbPath];
82
+ ensureParentDir(dbPath);
83
+ var db = openDatabase(dbPath);
84
+ initClayMeta(db);
85
+ var wrapper = {
86
+ db: db,
87
+ dbPath: dbPath,
88
+ sizeBytes: getDbSizeBytes(dbPath),
89
+ warning: getDbSizeBytes(dbPath) > DB_SIZE_WARNING_BYTES ? "Mate datastore exceeds 100 MB soft warning threshold." : null,
90
+ };
91
+ _dbCache[dbPath] = wrapper;
92
+ return wrapper;
93
+ }
94
+
95
+ function openMateDatastore(opts) {
96
+ return ensureMateDatastore(opts);
97
+ }
98
+
99
+ function closeMateDatastore(handle) {
100
+ var wrapper = unwrapHandle(handle);
101
+ if (!wrapper || !wrapper.db) return;
102
+ try {
103
+ wrapper.db.close();
104
+ } catch (e) {}
105
+ if (wrapper.dbPath && _dbCache[wrapper.dbPath]) delete _dbCache[wrapper.dbPath];
106
+ }
107
+
108
+ function closeAllMateDatastores() {
109
+ var keys = Object.keys(_dbCache);
110
+ for (var i = 0; i < keys.length; i++) {
111
+ closeMateDatastore(_dbCache[keys[i]]);
112
+ }
113
+ }
114
+
115
+ function unwrapHandle(handle) {
116
+ if (!handle) return null;
117
+ if (handle.db && handle.dbPath) return handle;
118
+ return { db: handle, dbPath: null, sizeBytes: 0, warning: null };
119
+ }
120
+
121
+ function normalizeSql(sql) {
122
+ var text = String(sql || "");
123
+ text = text.replace(/^\s*(?:--[^\n]*\n|\/\*[\s\S]*?\*\/\s*)*/g, "");
124
+ return text.trim();
125
+ }
126
+
127
+ function getFirstKeyword(sql) {
128
+ var text = normalizeSql(sql);
129
+ var match = text.match(/^([A-Za-z]+)/);
130
+ return match ? match[1].toUpperCase() : "";
131
+ }
132
+
133
+ function hasMultipleStatements(sql) {
134
+ var text = normalizeSql(sql);
135
+ if (text.indexOf(";") === -1) return false;
136
+ return !/;\s*$/.test(text) || text.slice(0, -1).indexOf(";") !== -1;
137
+ }
138
+
139
+ function isForbiddenSql(sql) {
140
+ var text = normalizeSql(sql).toUpperCase();
141
+ var banned = [
142
+ "ATTACH DATABASE",
143
+ "DETACH DATABASE",
144
+ "LOAD_EXTENSION",
145
+ "LOAD EXTENSION",
146
+ ];
147
+ for (var i = 0; i < banned.length; i++) {
148
+ if (text.indexOf(banned[i]) !== -1) return banned[i];
149
+ }
150
+ if (/PRAGMA\s+(?!table_info|table_xinfo|index_list|index_info|foreign_key_list)/i.test(text)) {
151
+ return "PRAGMA";
152
+ }
153
+ return "";
154
+ }
155
+
156
+ function isReadOnlyQuery(sql) {
157
+ var kw = getFirstKeyword(sql);
158
+ if (kw === "SELECT" || kw === "WITH") return true;
159
+ return false;
160
+ }
161
+
162
+ function isAllowedExec(sql) {
163
+ var kw = getFirstKeyword(sql);
164
+ return kw === "CREATE" || kw === "ALTER" || kw === "DROP" || kw === "INSERT" || kw === "UPDATE" || kw === "DELETE";
165
+ }
166
+
167
+ function bindParams(stmt, params) {
168
+ if (!params) return stmt;
169
+ if (!Array.isArray(params)) return stmt;
170
+ return stmt.run.apply(stmt, params);
171
+ }
172
+
173
+ function runQuery(handle, sql, params, limits) {
174
+ var wrapper = unwrapHandle(handle);
175
+ if (!wrapper || !wrapper.db) {
176
+ return makeError("MATE_DATASTORE_UNAVAILABLE", "Mate datastore is not available.");
177
+ }
178
+ var text = normalizeSql(sql);
179
+ if (!text) return makeError("SQLITE_QUERY_REJECTED", "Query SQL is required.");
180
+ if (hasMultipleStatements(text)) return makeError("SQLITE_QUERY_REJECTED", "Multiple statements are not allowed in query mode.");
181
+ var forbidden = isForbiddenSql(text);
182
+ if (forbidden) return makeError("SQLITE_FORBIDDEN", forbidden + " is not allowed in Mate datastores.");
183
+ if (!isReadOnlyQuery(text)) return makeError("SQLITE_QUERY_REJECTED", "Only SELECT and WITH queries are allowed.");
184
+
185
+ try {
186
+ var stmt = wrapper.db.prepare(text);
187
+ var rows = Array.isArray(params) ? stmt.all.apply(stmt, params) : stmt.all();
188
+ rows = sanitizeValue(rows);
189
+ var rowCount = rows.length;
190
+ var maxRows = limits && typeof limits.maxRows === "number" ? limits.maxRows : MAX_ROWS;
191
+ var truncated = false;
192
+ if (rows.length > maxRows) {
193
+ rows = rows.slice(0, maxRows);
194
+ truncated = true;
195
+ }
196
+ var result = {
197
+ ok: true,
198
+ rows: rows,
199
+ rowCount: rowCount,
200
+ truncated: truncated,
201
+ };
202
+ if (wrapper.warning) result.warning = wrapper.warning;
203
+ if (limits && limits.includeSizeInfo) {
204
+ result.sizeBytes = wrapper.sizeBytes;
205
+ }
206
+ if (Buffer.byteLength(JSON.stringify(result), "utf8") > (limits && limits.maxBytes ? limits.maxBytes : MAX_RESULT_BYTES)) {
207
+ return makeError("SQLITE_RESULT_TOO_LARGE", "Query result exceeds the 1 MB response limit.");
208
+ }
209
+ return result;
210
+ } catch (e) {
211
+ return normalizeSqliteError(e, "SQLITE_EXEC_FAILED", "Query failed.");
212
+ }
213
+ }
214
+
215
+ function runExec(handle, sql, params, limits) {
216
+ var wrapper = unwrapHandle(handle);
217
+ if (!wrapper || !wrapper.db) {
218
+ return makeError("MATE_DATASTORE_UNAVAILABLE", "Mate datastore is not available.");
219
+ }
220
+ var text = normalizeSql(sql);
221
+ if (!text) return makeError("MATE_DATASTORE_BAD_INPUT", "SQL is required.");
222
+ if (hasMultipleStatements(text)) return makeError("SQLITE_EXEC_FAILED", "Multiple statements are not allowed in exec mode.");
223
+ var forbidden = isForbiddenSql(text);
224
+ if (forbidden) return makeError("SQLITE_FORBIDDEN", forbidden + " is not allowed in Mate datastores.");
225
+ if (!isAllowedExec(text)) return makeError("SQLITE_EXEC_FAILED", "Only CREATE, ALTER, DROP, INSERT, UPDATE, and DELETE are allowed in exec mode.");
226
+
227
+ try {
228
+ var stmt = wrapper.db.prepare(text);
229
+ var runResult = Array.isArray(params) ? stmt.run.apply(stmt, params) : stmt.run();
230
+ var result = {
231
+ ok: true,
232
+ changes: typeof runResult.changes === "number" ? runResult.changes : 0,
233
+ lastInsertRowid: typeof runResult.lastInsertRowid !== "undefined" ? sanitizeValue(runResult.lastInsertRowid) : null,
234
+ };
235
+ if (wrapper.warning) result.warning = wrapper.warning;
236
+ if (limits && limits.includeSizeInfo) {
237
+ result.sizeBytes = getDbSizeBytes(wrapper.dbPath);
238
+ }
239
+ if (Buffer.byteLength(JSON.stringify(result), "utf8") > (limits && limits.maxBytes ? limits.maxBytes : MAX_RESULT_BYTES)) {
240
+ return makeError("SQLITE_RESULT_TOO_LARGE", "Execution result exceeds the 1 MB response limit.");
241
+ }
242
+ wrapper.sizeBytes = getDbSizeBytes(wrapper.dbPath);
243
+ if (wrapper.sizeBytes > DB_SIZE_WARNING_BYTES) {
244
+ wrapper.warning = "Mate datastore exceeds 100 MB soft warning threshold.";
245
+ result.warning = wrapper.warning;
246
+ }
247
+ return result;
248
+ } catch (e) {
249
+ return normalizeSqliteError(e, "SQLITE_EXEC_FAILED", "Execution failed.");
250
+ }
251
+ }
252
+
253
+ function listSchemaObjects(handle) {
254
+ var wrapper = unwrapHandle(handle);
255
+ if (!wrapper || !wrapper.db) {
256
+ return makeError("MATE_DATASTORE_UNAVAILABLE", "Mate datastore is not available.");
257
+ }
258
+ try {
259
+ var rows = wrapper.db.prepare(
260
+ "SELECT name, type, sql FROM sqlite_master WHERE type IN ('table', 'view', 'index') AND name NOT LIKE 'sqlite_%' ORDER BY type, name"
261
+ ).all();
262
+ return { ok: true, objects: rows };
263
+ } catch (e) {
264
+ return normalizeSqliteError(e, "SQLITE_EXEC_FAILED", "Failed to list schema objects.");
265
+ }
266
+ }
267
+
268
+ function describeTable(handle, tableName) {
269
+ var wrapper = unwrapHandle(handle);
270
+ if (!wrapper || !wrapper.db) {
271
+ return makeError("MATE_DATASTORE_UNAVAILABLE", "Mate datastore is not available.");
272
+ }
273
+ if (!isSafeIdentifier(tableName)) {
274
+ return makeError("MATE_DATASTORE_BAD_INPUT", "Table name is invalid.");
275
+ }
276
+ try {
277
+ var info = wrapper.db.prepare("SELECT name, type, sql FROM sqlite_master WHERE name = ? AND type IN ('table', 'view')").get(tableName);
278
+ if (!info) return makeError("SQLITE_TABLE_NOT_FOUND", "Table not found.");
279
+ var columns = wrapper.db.prepare("PRAGMA table_info(" + quoteIdentifier(tableName) + ")").all();
280
+ var indexes = wrapper.db.prepare("PRAGMA index_list(" + quoteIdentifier(tableName) + ")").all();
281
+ return {
282
+ ok: true,
283
+ table: tableName,
284
+ columns: columns,
285
+ indexes: indexes,
286
+ createSql: info.sql || null,
287
+ };
288
+ } catch (e) {
289
+ return normalizeSqliteError(e, "SQLITE_EXEC_FAILED", "Failed to describe table.");
290
+ }
291
+ }
292
+
293
+ function isSafeIdentifier(name) {
294
+ return typeof name === "string" && /^[A-Za-z_][A-Za-z0-9_]*$/.test(name);
295
+ }
296
+
297
+ function quoteIdentifier(name) {
298
+ return '"' + String(name).replace(/"/g, '""') + '"';
299
+ }
300
+
301
+ function makeError(code, message) {
302
+ return { ok: false, code: code, message: message };
303
+ }
304
+
305
+ function sanitizeValue(value) {
306
+ if (typeof value === "bigint") return value.toString();
307
+ if (Array.isArray(value)) return value.map(sanitizeValue);
308
+ if (value && typeof value === "object") {
309
+ var out = {};
310
+ var keys = Object.keys(value);
311
+ for (var i = 0; i < keys.length; i++) {
312
+ out[keys[i]] = sanitizeValue(value[keys[i]]);
313
+ }
314
+ return out;
315
+ }
316
+ return value;
317
+ }
318
+
319
+ function normalizeSqliteError(err, fallbackCode, fallbackMessage) {
320
+ var message = err && err.message ? String(err.message) : fallbackMessage;
321
+ var code = fallbackCode;
322
+ if (message.indexOf("no such table") !== -1) code = "SQLITE_TABLE_NOT_FOUND";
323
+ if (message.indexOf("no such column") !== -1) code = "SQLITE_QUERY_REJECTED";
324
+ return { ok: false, code: code, message: message };
325
+ }
326
+
327
+ module.exports = {
328
+ openMateDatastore: openMateDatastore,
329
+ ensureMateDatastore: ensureMateDatastore,
330
+ listSchemaObjects: listSchemaObjects,
331
+ describeTable: describeTable,
332
+ runQuery: runQuery,
333
+ runExec: runExec,
334
+ closeMateDatastore: closeMateDatastore,
335
+ closeAllMateDatastores: closeAllMateDatastores,
336
+ getMateDbPath: getMateDbPath,
337
+ };