@alfe.ai/openclaw-database 0.0.3 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,2 @@
1
+ const require_plugin = require("./plugin2.cjs");
2
+ module.exports = require_plugin.plugin;
@@ -0,0 +1,2 @@
1
+ import plugin from "./plugin.cjs";
2
+ export { plugin as default };
@@ -0,0 +1,2 @@
1
+ const require_plugin = require("./plugin2.cjs");
2
+ module.exports = require_plugin.plugin;
@@ -0,0 +1,19 @@
1
+ //#region src/plugin.d.ts
2
+ interface PluginApi {
3
+ logger: {
4
+ info: (...args: unknown[]) => void;
5
+ warn: (...args: unknown[]) => void;
6
+ error: (...args: unknown[]) => void;
7
+ debug: (...args: unknown[]) => void;
8
+ };
9
+ registerTool(tool: unknown): void;
10
+ }
11
+ declare const plugin: {
12
+ id: string;
13
+ name: string;
14
+ version: string;
15
+ activate(api: PluginApi): void;
16
+ deactivate(api: PluginApi): Promise<void>;
17
+ };
18
+ //#endregion
19
+ export { plugin as default };
package/dist/plugin.d.ts CHANGED
@@ -2,6 +2,7 @@
2
2
  interface PluginApi {
3
3
  logger: {
4
4
  info: (...args: unknown[]) => void;
5
+ warn: (...args: unknown[]) => void;
5
6
  error: (...args: unknown[]) => void;
6
7
  debug: (...args: unknown[]) => void;
7
8
  };
@@ -11,7 +12,7 @@ declare const plugin: {
11
12
  id: string;
12
13
  name: string;
13
14
  version: string;
14
- activate(api: PluginApi): Promise<void>;
15
+ activate(api: PluginApi): void;
15
16
  deactivate(api: PluginApi): Promise<void>;
16
17
  };
17
18
  //#endregion
@@ -0,0 +1,525 @@
1
+ let mongodb = require("mongodb");
2
+ let _alfe_ai_config = require("@alfe.ai/config");
3
+ //#region src/tools.ts
4
+ const DEFAULT_DB = "org_default";
5
+ function str(required = true) {
6
+ return {
7
+ type: "string",
8
+ ...required ? {} : { default: "" }
9
+ };
10
+ }
11
+ function optStr() {
12
+ return {
13
+ type: "string",
14
+ default: ""
15
+ };
16
+ }
17
+ function registerTools(api, _client, _databases, audit, lazyInit) {
18
+ async function db(name) {
19
+ const { mongoClient, databases } = lazyInit ? await lazyInit() : {
20
+ mongoClient: _client ?? (() => {
21
+ throw new Error("MongoDB client not initialized");
22
+ })(),
23
+ databases: _databases
24
+ };
25
+ const dbName = name ?? DEFAULT_DB;
26
+ if (!databases.includes(dbName)) throw new Error(`Access denied: database "${dbName}" is not in the allowed list`);
27
+ return mongoClient.db(dbName);
28
+ }
29
+ api.registerTool({
30
+ name: "db_find",
31
+ description: "Find documents matching a filter",
32
+ label: "db_find",
33
+ parameters: {
34
+ type: "object",
35
+ properties: {
36
+ database: optStr(),
37
+ collection: str(),
38
+ filter: {
39
+ type: "object",
40
+ default: {}
41
+ },
42
+ sort: {
43
+ type: "object",
44
+ default: {}
45
+ },
46
+ limit: {
47
+ type: "number",
48
+ default: 20
49
+ },
50
+ skip: {
51
+ type: "number",
52
+ default: 0
53
+ },
54
+ projection: {
55
+ type: "object",
56
+ default: {}
57
+ }
58
+ },
59
+ required: ["collection"]
60
+ },
61
+ execute: async (_id, p) => {
62
+ const docs = await (await db(p.database)).collection(p.collection).find(p.filter).sort(p.sort).project(p.projection).skip(p.skip || 0).limit(p.limit || 20).toArray();
63
+ return {
64
+ documents: docs,
65
+ count: docs.length
66
+ };
67
+ }
68
+ });
69
+ api.registerTool({
70
+ name: "db_find_one",
71
+ description: "Find a single document matching a filter",
72
+ label: "db_find_one",
73
+ parameters: {
74
+ type: "object",
75
+ properties: {
76
+ database: optStr(),
77
+ collection: str(),
78
+ filter: {
79
+ type: "object",
80
+ default: {}
81
+ },
82
+ projection: {
83
+ type: "object",
84
+ default: {}
85
+ }
86
+ },
87
+ required: ["collection"]
88
+ },
89
+ execute: async (_id, p) => {
90
+ return await (await db(p.database)).collection(p.collection).findOne(p.filter, { projection: p.projection }) ?? { _not_found: true };
91
+ }
92
+ });
93
+ api.registerTool({
94
+ name: "db_insert",
95
+ description: "Insert one or more documents",
96
+ label: "db_insert",
97
+ parameters: {
98
+ type: "object",
99
+ properties: {
100
+ database: optStr(),
101
+ collection: str(),
102
+ documents: {
103
+ type: "array",
104
+ items: { type: "object" }
105
+ }
106
+ },
107
+ required: ["collection", "documents"]
108
+ },
109
+ execute: async (_id, p) => {
110
+ const docs = p.documents;
111
+ const result = await (await db(p.database)).collection(p.collection).insertMany(docs);
112
+ audit({
113
+ database: p.database || DEFAULT_DB,
114
+ collection: p.collection,
115
+ operation: "insert",
116
+ summary: `Inserted ${String(docs.length)} document(s)`
117
+ });
118
+ return {
119
+ insertedCount: result.insertedCount,
120
+ insertedIds: Object.values(result.insertedIds).map(String)
121
+ };
122
+ }
123
+ });
124
+ api.registerTool({
125
+ name: "db_update",
126
+ description: "Update documents matching a filter",
127
+ label: "db_update",
128
+ parameters: {
129
+ type: "object",
130
+ properties: {
131
+ database: optStr(),
132
+ collection: str(),
133
+ filter: { type: "object" },
134
+ update: { type: "object" },
135
+ upsert: {
136
+ type: "boolean",
137
+ default: false
138
+ }
139
+ },
140
+ required: [
141
+ "collection",
142
+ "filter",
143
+ "update"
144
+ ]
145
+ },
146
+ execute: async (_id, p) => {
147
+ const result = await (await db(p.database)).collection(p.collection).updateMany(p.filter, p.update, { upsert: p.upsert || false });
148
+ audit({
149
+ database: p.database || DEFAULT_DB,
150
+ collection: p.collection,
151
+ operation: "update",
152
+ summary: `Modified ${String(result.modifiedCount)}`
153
+ });
154
+ return {
155
+ matchedCount: result.matchedCount,
156
+ modifiedCount: result.modifiedCount,
157
+ upsertedId: result.upsertedId ? String(result.upsertedId) : null
158
+ };
159
+ }
160
+ });
161
+ api.registerTool({
162
+ name: "db_delete",
163
+ description: "Delete documents matching a filter",
164
+ label: "db_delete",
165
+ parameters: {
166
+ type: "object",
167
+ properties: {
168
+ database: optStr(),
169
+ collection: str(),
170
+ filter: { type: "object" }
171
+ },
172
+ required: ["collection", "filter"]
173
+ },
174
+ execute: async (_id, p) => {
175
+ const result = await (await db(p.database)).collection(p.collection).deleteMany(p.filter);
176
+ audit({
177
+ database: p.database || DEFAULT_DB,
178
+ collection: p.collection,
179
+ operation: "delete",
180
+ summary: `Deleted ${String(result.deletedCount)}`
181
+ });
182
+ return { deletedCount: result.deletedCount };
183
+ }
184
+ });
185
+ api.registerTool({
186
+ name: "db_count",
187
+ description: "Count documents matching a filter",
188
+ label: "db_count",
189
+ parameters: {
190
+ type: "object",
191
+ properties: {
192
+ database: optStr(),
193
+ collection: str(),
194
+ filter: {
195
+ type: "object",
196
+ default: {}
197
+ }
198
+ },
199
+ required: ["collection"]
200
+ },
201
+ execute: async (_id, p) => {
202
+ return { count: await (await db(p.database)).collection(p.collection).countDocuments(p.filter) };
203
+ }
204
+ });
205
+ api.registerTool({
206
+ name: "db_aggregate",
207
+ description: "Run an aggregation pipeline",
208
+ label: "db_aggregate",
209
+ parameters: {
210
+ type: "object",
211
+ properties: {
212
+ database: optStr(),
213
+ collection: str(),
214
+ pipeline: {
215
+ type: "array",
216
+ items: { type: "object" }
217
+ }
218
+ },
219
+ required: ["collection", "pipeline"]
220
+ },
221
+ execute: async (_id, p) => {
222
+ const results = await (await db(p.database)).collection(p.collection).aggregate(p.pipeline).toArray();
223
+ return {
224
+ results,
225
+ count: results.length
226
+ };
227
+ }
228
+ });
229
+ api.registerTool({
230
+ name: "db_list_databases",
231
+ description: "List databases the agent can access",
232
+ label: "db_list_databases",
233
+ parameters: {
234
+ type: "object",
235
+ properties: {}
236
+ },
237
+ execute: async () => {
238
+ const { databases } = lazyInit ? await lazyInit() : { databases: _databases };
239
+ return { databases };
240
+ }
241
+ });
242
+ api.registerTool({
243
+ name: "db_list_collections",
244
+ description: "List collections in a database",
245
+ label: "db_list_collections",
246
+ parameters: {
247
+ type: "object",
248
+ properties: { database: optStr() }
249
+ },
250
+ execute: async (_id, p) => {
251
+ return { collections: (await (await db(p.database)).listCollections().toArray()).map((c) => ({
252
+ name: c.name,
253
+ type: c.type
254
+ })) };
255
+ }
256
+ });
257
+ api.registerTool({
258
+ name: "db_create_collection",
259
+ description: "Create a new collection",
260
+ label: "db_create_collection",
261
+ parameters: {
262
+ type: "object",
263
+ properties: {
264
+ database: optStr(),
265
+ collection: str()
266
+ },
267
+ required: ["collection"]
268
+ },
269
+ execute: async (_id, p) => {
270
+ await (await db(p.database)).createCollection(p.collection);
271
+ audit({
272
+ database: p.database || DEFAULT_DB,
273
+ collection: p.collection,
274
+ operation: "create_collection"
275
+ });
276
+ return { success: true };
277
+ }
278
+ });
279
+ api.registerTool({
280
+ name: "db_drop_collection",
281
+ description: "Drop a collection",
282
+ label: "db_drop_collection",
283
+ parameters: {
284
+ type: "object",
285
+ properties: {
286
+ database: optStr(),
287
+ collection: str()
288
+ },
289
+ required: ["collection"]
290
+ },
291
+ execute: async (_id, p) => {
292
+ await (await db(p.database)).dropCollection(p.collection);
293
+ audit({
294
+ database: p.database || DEFAULT_DB,
295
+ collection: p.collection,
296
+ operation: "drop_collection"
297
+ });
298
+ return { success: true };
299
+ }
300
+ });
301
+ api.registerTool({
302
+ name: "db_collection_schema",
303
+ description: "Inspect a collection's schema by sampling documents",
304
+ label: "db_collection_schema",
305
+ parameters: {
306
+ type: "object",
307
+ properties: {
308
+ database: optStr(),
309
+ collection: str()
310
+ },
311
+ required: ["collection"]
312
+ },
313
+ execute: async (_id, p) => {
314
+ const sample = await (await db(p.database)).collection(p.collection).find().limit(100).toArray();
315
+ const fieldTypes = /* @__PURE__ */ new Map();
316
+ for (const doc of sample) for (const [key, value] of Object.entries(doc)) {
317
+ const types = fieldTypes.get(key) ?? /* @__PURE__ */ new Set();
318
+ types.add(value === null ? "null" : typeof value);
319
+ fieldTypes.set(key, types);
320
+ }
321
+ return {
322
+ sampleSize: sample.length,
323
+ fields: [...fieldTypes.entries()].map(([name, types]) => ({
324
+ name,
325
+ types: [...types]
326
+ }))
327
+ };
328
+ }
329
+ });
330
+ api.registerTool({
331
+ name: "db_collection_stats",
332
+ description: "Get collection doc count, size, and indexes",
333
+ label: "db_collection_stats",
334
+ parameters: {
335
+ type: "object",
336
+ properties: {
337
+ database: optStr(),
338
+ collection: str()
339
+ },
340
+ required: ["collection"]
341
+ },
342
+ execute: async (_id, p) => {
343
+ const coll = (await db(p.database)).collection(p.collection);
344
+ const count = await coll.countDocuments();
345
+ const indexes = await coll.indexes();
346
+ return {
347
+ count,
348
+ indexCount: indexes.length,
349
+ indexes: indexes.map((i) => ({
350
+ name: i.name,
351
+ key: i.key
352
+ }))
353
+ };
354
+ }
355
+ });
356
+ api.registerTool({
357
+ name: "db_create_index",
358
+ description: "Create an index on a collection",
359
+ label: "db_create_index",
360
+ parameters: {
361
+ type: "object",
362
+ properties: {
363
+ database: optStr(),
364
+ collection: str(),
365
+ keys: { type: "object" },
366
+ options: {
367
+ type: "object",
368
+ default: {}
369
+ }
370
+ },
371
+ required: ["collection", "keys"]
372
+ },
373
+ execute: async (_id, p) => {
374
+ const indexName = await (await db(p.database)).collection(p.collection).createIndex(p.keys, p.options);
375
+ audit({
376
+ database: p.database || DEFAULT_DB,
377
+ collection: p.collection,
378
+ operation: "create_index",
379
+ summary: indexName
380
+ });
381
+ return { indexName };
382
+ }
383
+ });
384
+ api.registerTool({
385
+ name: "db_list_indexes",
386
+ description: "List indexes on a collection",
387
+ label: "db_list_indexes",
388
+ parameters: {
389
+ type: "object",
390
+ properties: {
391
+ database: optStr(),
392
+ collection: str()
393
+ },
394
+ required: ["collection"]
395
+ },
396
+ execute: async (_id, p) => {
397
+ return { indexes: (await (await db(p.database)).collection(p.collection).indexes()).map((i) => ({
398
+ name: i.name,
399
+ key: i.key,
400
+ unique: i.unique
401
+ })) };
402
+ }
403
+ });
404
+ api.registerTool({
405
+ name: "db_drop_index",
406
+ description: "Drop an index by name",
407
+ label: "db_drop_index",
408
+ parameters: {
409
+ type: "object",
410
+ properties: {
411
+ database: optStr(),
412
+ collection: str(),
413
+ indexName: str()
414
+ },
415
+ required: ["collection", "indexName"]
416
+ },
417
+ execute: async (_id, p) => {
418
+ await (await db(p.database)).collection(p.collection).dropIndex(p.indexName);
419
+ audit({
420
+ database: p.database || DEFAULT_DB,
421
+ collection: p.collection,
422
+ operation: "drop_index",
423
+ summary: p.indexName
424
+ });
425
+ return { success: true };
426
+ }
427
+ });
428
+ }
429
+ //#endregion
430
+ //#region src/plugin.ts
431
+ let mongoClient = null;
432
+ let initPromise = null;
433
+ async function fetchCredentials(apiUrl, apiKey) {
434
+ const res = await fetch(`${apiUrl}/agents/database/register`, {
435
+ method: "POST",
436
+ headers: {
437
+ "Content-Type": "application/json",
438
+ Authorization: `Bearer ${apiKey}`
439
+ }
440
+ });
441
+ if (!res.ok) throw new Error(`Failed to register database credentials: ${String(res.status)} ${res.statusText}`);
442
+ return (await res.json()).data;
443
+ }
444
+ /**
445
+ * Lazy initializer — fetches credentials and connects to MongoDB on first use.
446
+ * Returns a shared promise so concurrent calls don't duplicate work.
447
+ */
448
+ function ensureInitialized(apiUrl, apiKey) {
449
+ if (initPromise) return initPromise;
450
+ initPromise = (async () => {
451
+ const credentials = await fetchCredentials(apiUrl, apiKey);
452
+ if (!credentials.connectionString) throw new Error("No connection string returned — cluster may still be provisioning");
453
+ const url = new URL(credentials.connectionString);
454
+ url.username = credentials.username;
455
+ url.password = credentials.password;
456
+ const client = new mongodb.MongoClient(url.toString(), {
457
+ maxPoolSize: 3,
458
+ minPoolSize: 1,
459
+ serverSelectionTimeoutMS: 5e3
460
+ });
461
+ await client.connect();
462
+ mongoClient = client;
463
+ return {
464
+ mongoClient: client,
465
+ databases: credentials.databases,
466
+ apiUrl,
467
+ apiKey
468
+ };
469
+ })();
470
+ initPromise.catch(() => {
471
+ initPromise = null;
472
+ });
473
+ return initPromise;
474
+ }
475
+ function reportAudit(apiUrl, apiKey, entry) {
476
+ fetch(`${apiUrl}/agents/database/audit`, {
477
+ method: "POST",
478
+ headers: {
479
+ "Content-Type": "application/json",
480
+ Authorization: `Bearer ${apiKey}`
481
+ },
482
+ body: JSON.stringify(entry)
483
+ }).catch(() => {});
484
+ }
485
+ const plugin = {
486
+ id: "@alfe.ai/openclaw-database",
487
+ name: "Database",
488
+ version: "0.0.1",
489
+ activate(api) {
490
+ const log = api.logger;
491
+ log.info("Database plugin activating...");
492
+ let apiUrl;
493
+ let apiKey;
494
+ try {
495
+ const config = (0, _alfe_ai_config.resolveConfig)();
496
+ apiUrl = config.apiUrl;
497
+ apiKey = config.apiKey;
498
+ } catch (err) {
499
+ log.error(`Database plugin: failed to resolve config — ${err instanceof Error ? err.message : String(err)}`);
500
+ return;
501
+ }
502
+ ensureInitialized(apiUrl, apiKey).then(({ databases }) => {
503
+ log.info(`Database plugin connected — ${String(databases.length)} databases available`);
504
+ }).catch((err) => {
505
+ log.warn(`Database plugin: background init failed (will retry on first tool use) — ${err instanceof Error ? err.message : String(err)}`);
506
+ });
507
+ registerTools(api, null, [], (entry) => {
508
+ reportAudit(apiUrl, apiKey, entry);
509
+ }, () => ensureInitialized(apiUrl, apiKey));
510
+ },
511
+ async deactivate(api) {
512
+ api.logger.info("Database plugin deactivating...");
513
+ if (mongoClient) {
514
+ await mongoClient.close();
515
+ mongoClient = null;
516
+ }
517
+ }
518
+ };
519
+ //#endregion
520
+ Object.defineProperty(exports, "plugin", {
521
+ enumerable: true,
522
+ get: function() {
523
+ return plugin;
524
+ }
525
+ });
package/dist/plugin2.js CHANGED
@@ -14,11 +14,17 @@ function optStr() {
14
14
  default: ""
15
15
  };
16
16
  }
17
- function registerTools(api, client, databases, audit) {
18
- function db(name) {
17
+ function registerTools(api, _client, _databases, audit, lazyInit) {
18
+ async function db(name) {
19
+ const { mongoClient, databases } = lazyInit ? await lazyInit() : {
20
+ mongoClient: _client ?? (() => {
21
+ throw new Error("MongoDB client not initialized");
22
+ })(),
23
+ databases: _databases
24
+ };
19
25
  const dbName = name ?? DEFAULT_DB;
20
26
  if (!databases.includes(dbName)) throw new Error(`Access denied: database "${dbName}" is not in the allowed list`);
21
- return client.db(dbName);
27
+ return mongoClient.db(dbName);
22
28
  }
23
29
  api.registerTool({
24
30
  name: "db_find",
@@ -53,7 +59,7 @@ function registerTools(api, client, databases, audit) {
53
59
  required: ["collection"]
54
60
  },
55
61
  execute: async (_id, p) => {
56
- const docs = await db(p.database).collection(p.collection).find(p.filter).sort(p.sort).project(p.projection).skip(p.skip || 0).limit(p.limit || 20).toArray();
62
+ const docs = await (await db(p.database)).collection(p.collection).find(p.filter).sort(p.sort).project(p.projection).skip(p.skip || 0).limit(p.limit || 20).toArray();
57
63
  return {
58
64
  documents: docs,
59
65
  count: docs.length
@@ -81,7 +87,7 @@ function registerTools(api, client, databases, audit) {
81
87
  required: ["collection"]
82
88
  },
83
89
  execute: async (_id, p) => {
84
- return await db(p.database).collection(p.collection).findOne(p.filter, { projection: p.projection }) ?? { _not_found: true };
90
+ return await (await db(p.database)).collection(p.collection).findOne(p.filter, { projection: p.projection }) ?? { _not_found: true };
85
91
  }
86
92
  });
87
93
  api.registerTool({
@@ -102,7 +108,7 @@ function registerTools(api, client, databases, audit) {
102
108
  },
103
109
  execute: async (_id, p) => {
104
110
  const docs = p.documents;
105
- const result = await db(p.database).collection(p.collection).insertMany(docs);
111
+ const result = await (await db(p.database)).collection(p.collection).insertMany(docs);
106
112
  audit({
107
113
  database: p.database || DEFAULT_DB,
108
114
  collection: p.collection,
@@ -138,7 +144,7 @@ function registerTools(api, client, databases, audit) {
138
144
  ]
139
145
  },
140
146
  execute: async (_id, p) => {
141
- const result = await db(p.database).collection(p.collection).updateMany(p.filter, p.update, { upsert: p.upsert || false });
147
+ const result = await (await db(p.database)).collection(p.collection).updateMany(p.filter, p.update, { upsert: p.upsert || false });
142
148
  audit({
143
149
  database: p.database || DEFAULT_DB,
144
150
  collection: p.collection,
@@ -166,7 +172,7 @@ function registerTools(api, client, databases, audit) {
166
172
  required: ["collection", "filter"]
167
173
  },
168
174
  execute: async (_id, p) => {
169
- const result = await db(p.database).collection(p.collection).deleteMany(p.filter);
175
+ const result = await (await db(p.database)).collection(p.collection).deleteMany(p.filter);
170
176
  audit({
171
177
  database: p.database || DEFAULT_DB,
172
178
  collection: p.collection,
@@ -193,7 +199,7 @@ function registerTools(api, client, databases, audit) {
193
199
  required: ["collection"]
194
200
  },
195
201
  execute: async (_id, p) => {
196
- return { count: await db(p.database).collection(p.collection).countDocuments(p.filter) };
202
+ return { count: await (await db(p.database)).collection(p.collection).countDocuments(p.filter) };
197
203
  }
198
204
  });
199
205
  api.registerTool({
@@ -213,7 +219,7 @@ function registerTools(api, client, databases, audit) {
213
219
  required: ["collection", "pipeline"]
214
220
  },
215
221
  execute: async (_id, p) => {
216
- const results = await db(p.database).collection(p.collection).aggregate(p.pipeline).toArray();
222
+ const results = await (await db(p.database)).collection(p.collection).aggregate(p.pipeline).toArray();
217
223
  return {
218
224
  results,
219
225
  count: results.length
@@ -228,7 +234,10 @@ function registerTools(api, client, databases, audit) {
228
234
  type: "object",
229
235
  properties: {}
230
236
  },
231
- execute: () => Promise.resolve({ databases })
237
+ execute: async () => {
238
+ const { databases } = lazyInit ? await lazyInit() : { databases: _databases };
239
+ return { databases };
240
+ }
232
241
  });
233
242
  api.registerTool({
234
243
  name: "db_list_collections",
@@ -239,7 +248,7 @@ function registerTools(api, client, databases, audit) {
239
248
  properties: { database: optStr() }
240
249
  },
241
250
  execute: async (_id, p) => {
242
- return { collections: (await db(p.database).listCollections().toArray()).map((c) => ({
251
+ return { collections: (await (await db(p.database)).listCollections().toArray()).map((c) => ({
243
252
  name: c.name,
244
253
  type: c.type
245
254
  })) };
@@ -258,7 +267,7 @@ function registerTools(api, client, databases, audit) {
258
267
  required: ["collection"]
259
268
  },
260
269
  execute: async (_id, p) => {
261
- await db(p.database).createCollection(p.collection);
270
+ await (await db(p.database)).createCollection(p.collection);
262
271
  audit({
263
272
  database: p.database || DEFAULT_DB,
264
273
  collection: p.collection,
@@ -280,7 +289,7 @@ function registerTools(api, client, databases, audit) {
280
289
  required: ["collection"]
281
290
  },
282
291
  execute: async (_id, p) => {
283
- await db(p.database).dropCollection(p.collection);
292
+ await (await db(p.database)).dropCollection(p.collection);
284
293
  audit({
285
294
  database: p.database || DEFAULT_DB,
286
295
  collection: p.collection,
@@ -302,7 +311,7 @@ function registerTools(api, client, databases, audit) {
302
311
  required: ["collection"]
303
312
  },
304
313
  execute: async (_id, p) => {
305
- const sample = await db(p.database).collection(p.collection).find().limit(100).toArray();
314
+ const sample = await (await db(p.database)).collection(p.collection).find().limit(100).toArray();
306
315
  const fieldTypes = /* @__PURE__ */ new Map();
307
316
  for (const doc of sample) for (const [key, value] of Object.entries(doc)) {
308
317
  const types = fieldTypes.get(key) ?? /* @__PURE__ */ new Set();
@@ -331,7 +340,7 @@ function registerTools(api, client, databases, audit) {
331
340
  required: ["collection"]
332
341
  },
333
342
  execute: async (_id, p) => {
334
- const coll = db(p.database).collection(p.collection);
343
+ const coll = (await db(p.database)).collection(p.collection);
335
344
  const count = await coll.countDocuments();
336
345
  const indexes = await coll.indexes();
337
346
  return {
@@ -362,7 +371,7 @@ function registerTools(api, client, databases, audit) {
362
371
  required: ["collection", "keys"]
363
372
  },
364
373
  execute: async (_id, p) => {
365
- const indexName = await db(p.database).collection(p.collection).createIndex(p.keys, p.options);
374
+ const indexName = await (await db(p.database)).collection(p.collection).createIndex(p.keys, p.options);
366
375
  audit({
367
376
  database: p.database || DEFAULT_DB,
368
377
  collection: p.collection,
@@ -385,7 +394,7 @@ function registerTools(api, client, databases, audit) {
385
394
  required: ["collection"]
386
395
  },
387
396
  execute: async (_id, p) => {
388
- return { indexes: (await db(p.database).collection(p.collection).indexes()).map((i) => ({
397
+ return { indexes: (await (await db(p.database)).collection(p.collection).indexes()).map((i) => ({
389
398
  name: i.name,
390
399
  key: i.key,
391
400
  unique: i.unique
@@ -406,7 +415,7 @@ function registerTools(api, client, databases, audit) {
406
415
  required: ["collection", "indexName"]
407
416
  },
408
417
  execute: async (_id, p) => {
409
- await db(p.database).collection(p.collection).dropIndex(p.indexName);
418
+ await (await db(p.database)).collection(p.collection).dropIndex(p.indexName);
410
419
  audit({
411
420
  database: p.database || DEFAULT_DB,
412
421
  collection: p.collection,
@@ -420,6 +429,7 @@ function registerTools(api, client, databases, audit) {
420
429
  //#endregion
421
430
  //#region src/plugin.ts
422
431
  let mongoClient = null;
432
+ let initPromise = null;
423
433
  async function fetchCredentials(apiUrl, apiKey) {
424
434
  const res = await fetch(`${apiUrl}/agents/database/register`, {
425
435
  method: "POST",
@@ -431,6 +441,37 @@ async function fetchCredentials(apiUrl, apiKey) {
431
441
  if (!res.ok) throw new Error(`Failed to register database credentials: ${String(res.status)} ${res.statusText}`);
432
442
  return (await res.json()).data;
433
443
  }
444
+ /**
445
+ * Lazy initializer — fetches credentials and connects to MongoDB on first use.
446
+ * Returns a shared promise so concurrent calls don't duplicate work.
447
+ */
448
+ function ensureInitialized(apiUrl, apiKey) {
449
+ if (initPromise) return initPromise;
450
+ initPromise = (async () => {
451
+ const credentials = await fetchCredentials(apiUrl, apiKey);
452
+ if (!credentials.connectionString) throw new Error("No connection string returned — cluster may still be provisioning");
453
+ const url = new URL(credentials.connectionString);
454
+ url.username = credentials.username;
455
+ url.password = credentials.password;
456
+ const client = new MongoClient(url.toString(), {
457
+ maxPoolSize: 3,
458
+ minPoolSize: 1,
459
+ serverSelectionTimeoutMS: 5e3
460
+ });
461
+ await client.connect();
462
+ mongoClient = client;
463
+ return {
464
+ mongoClient: client,
465
+ databases: credentials.databases,
466
+ apiUrl,
467
+ apiKey
468
+ };
469
+ })();
470
+ initPromise.catch(() => {
471
+ initPromise = null;
472
+ });
473
+ return initPromise;
474
+ }
434
475
  function reportAudit(apiUrl, apiKey, entry) {
435
476
  fetch(`${apiUrl}/agents/database/audit`, {
436
477
  method: "POST",
@@ -445,37 +486,27 @@ const plugin = {
445
486
  id: "@alfe.ai/openclaw-database",
446
487
  name: "Database",
447
488
  version: "0.0.1",
448
- async activate(api) {
489
+ activate(api) {
449
490
  const log = api.logger;
450
491
  log.info("Database plugin activating...");
451
492
  let apiUrl;
452
493
  let apiKey;
453
494
  try {
454
- const config = await resolveConfig();
495
+ const config = resolveConfig();
455
496
  apiUrl = config.apiUrl;
456
497
  apiKey = config.apiKey;
457
498
  } catch (err) {
458
499
  log.error(`Database plugin: failed to resolve config — ${err instanceof Error ? err.message : String(err)}`);
459
500
  return;
460
501
  }
461
- const credentials = await fetchCredentials(apiUrl, apiKey);
462
- if (!credentials.connectionString) {
463
- log.error("Database plugin: no connection string returned — cluster may still be provisioning");
464
- return;
465
- }
466
- const url = new URL(credentials.connectionString);
467
- url.username = credentials.username;
468
- url.password = credentials.password;
469
- mongoClient = new MongoClient(url.toString(), {
470
- maxPoolSize: 3,
471
- minPoolSize: 1,
472
- serverSelectionTimeoutMS: 5e3
502
+ ensureInitialized(apiUrl, apiKey).then(({ databases }) => {
503
+ log.info(`Database plugin connected — ${String(databases.length)} databases available`);
504
+ }).catch((err) => {
505
+ log.warn(`Database plugin: background init failed (will retry on first tool use) — ${err instanceof Error ? err.message : String(err)}`);
473
506
  });
474
- await mongoClient.connect();
475
- log.info(`Database plugin connected — ${String(credentials.databases.length)} databases available`);
476
- registerTools(api, mongoClient, credentials.databases, (entry) => {
507
+ registerTools(api, null, [], (entry) => {
477
508
  reportAudit(apiUrl, apiKey, entry);
478
- });
509
+ }, () => ensureInitialized(apiUrl, apiKey));
479
510
  },
480
511
  async deactivate(api) {
481
512
  api.logger.info("Database plugin deactivating...");
package/package.json CHANGED
@@ -1,18 +1,20 @@
1
1
  {
2
2
  "name": "@alfe.ai/openclaw-database",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "description": "OpenClaw database plugin — MongoDB access for agents via MCP tools",
5
5
  "type": "module",
6
6
  "main": "./dist/plugin.js",
7
7
  "types": "./dist/index.d.ts",
8
8
  "exports": {
9
9
  ".": {
10
- "import": "./dist/index.js",
11
- "types": "./dist/index.d.ts"
10
+ "types": "./dist/index.d.ts",
11
+ "require": "./dist/index.cjs",
12
+ "import": "./dist/index.js"
12
13
  },
13
14
  "./plugin": {
14
- "import": "./dist/plugin.js",
15
- "types": "./dist/plugin.d.ts"
15
+ "types": "./dist/plugin.d.ts",
16
+ "require": "./dist/plugin.cjs",
17
+ "import": "./dist/plugin.js"
16
18
  }
17
19
  },
18
20
  "openclaw": {
@@ -26,7 +28,7 @@
26
28
  ],
27
29
  "dependencies": {
28
30
  "mongodb": "^6.12.0",
29
- "@alfe.ai/config": "0.0.5"
31
+ "@alfe.ai/config": "0.0.7"
30
32
  },
31
33
  "license": "UNLICENSED",
32
34
  "scripts": {