s3db.js 11.2.6 → 11.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +138 -0
- package/dist/s3db.cjs.js +2 -2
- package/dist/s3db.es.js +2 -2
- package/mcp/.env.example +117 -0
- package/mcp/{server.js → entrypoint.js} +1941 -683
- package/mcp/tools/bulk.js +112 -0
- package/mcp/tools/connection.js +228 -0
- package/mcp/tools/crud.js +579 -0
- package/mcp/tools/debugging.js +299 -0
- package/mcp/tools/export-import.js +281 -0
- package/mcp/tools/index.js +67 -0
- package/mcp/tools/partitions.js +223 -0
- package/mcp/tools/query.js +150 -0
- package/mcp/tools/resources.js +96 -0
- package/mcp/tools/stats.js +281 -0
- package/package.json +17 -7
- package/src/database.class.js +1 -1
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stats & Monitoring Tools
|
|
3
|
+
* Provides database statistics, cache metrics, and resource stats
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export const statsTools = [
|
|
7
|
+
{
|
|
8
|
+
name: 'dbGetStats',
|
|
9
|
+
description: 'Get database statistics including costs and cache performance',
|
|
10
|
+
inputSchema: {
|
|
11
|
+
type: 'object',
|
|
12
|
+
properties: {},
|
|
13
|
+
required: []
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
name: 'dbClearCache',
|
|
18
|
+
description: 'Clear all cached data or cache for specific resource',
|
|
19
|
+
inputSchema: {
|
|
20
|
+
type: 'object',
|
|
21
|
+
properties: {
|
|
22
|
+
resourceName: {
|
|
23
|
+
type: 'string',
|
|
24
|
+
description: 'Name of specific resource to clear cache (optional - if not provided, clears all cache)'
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
required: []
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
name: 'resourceGetStats',
|
|
32
|
+
description: 'Get detailed statistics for a specific resource',
|
|
33
|
+
inputSchema: {
|
|
34
|
+
type: 'object',
|
|
35
|
+
properties: {
|
|
36
|
+
resourceName: {
|
|
37
|
+
type: 'string',
|
|
38
|
+
description: 'Name of the resource'
|
|
39
|
+
},
|
|
40
|
+
includePartitionStats: {
|
|
41
|
+
type: 'boolean',
|
|
42
|
+
description: 'Include partition statistics',
|
|
43
|
+
default: true
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
required: ['resourceName']
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: 'cacheGetStats',
|
|
51
|
+
description: 'Get detailed cache statistics including hit/miss ratios and memory usage',
|
|
52
|
+
inputSchema: {
|
|
53
|
+
type: 'object',
|
|
54
|
+
properties: {
|
|
55
|
+
resourceName: {
|
|
56
|
+
type: 'string',
|
|
57
|
+
description: 'Get stats for specific resource (optional - gets all if not provided)'
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
required: []
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
export function createStatsHandlers(server) {
|
|
66
|
+
return {
|
|
67
|
+
async dbGetStats(args, database) {
|
|
68
|
+
server.ensureConnected(database);
|
|
69
|
+
|
|
70
|
+
const stats = {
|
|
71
|
+
database: {
|
|
72
|
+
connected: database.isConnected(),
|
|
73
|
+
bucket: database.bucket,
|
|
74
|
+
keyPrefix: database.keyPrefix,
|
|
75
|
+
version: database.s3dbVersion,
|
|
76
|
+
resourceCount: Object.keys(database.resources || {}).length,
|
|
77
|
+
resources: Object.keys(database.resources || {})
|
|
78
|
+
},
|
|
79
|
+
costs: null,
|
|
80
|
+
cache: null
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// Get costs from client if available
|
|
84
|
+
if (database.client && database.client.costs) {
|
|
85
|
+
stats.costs = {
|
|
86
|
+
total: database.client.costs.total,
|
|
87
|
+
totalRequests: database.client.costs.requests.total,
|
|
88
|
+
requestsByType: { ...database.client.costs.requests },
|
|
89
|
+
eventsByType: { ...database.client.costs.events },
|
|
90
|
+
estimatedCostUSD: database.client.costs.total
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Get cache stats from plugins if available
|
|
95
|
+
try {
|
|
96
|
+
const cachePlugin = database.pluginList?.find(p => p.constructor.name === 'CachePlugin');
|
|
97
|
+
if (cachePlugin && cachePlugin.driver) {
|
|
98
|
+
const cacheSize = await cachePlugin.driver.size();
|
|
99
|
+
const cacheKeys = await cachePlugin.driver.keys();
|
|
100
|
+
|
|
101
|
+
stats.cache = {
|
|
102
|
+
enabled: true,
|
|
103
|
+
driver: cachePlugin.driver.constructor.name,
|
|
104
|
+
size: cacheSize,
|
|
105
|
+
maxSize: cachePlugin.driver.maxSize || 'unlimited',
|
|
106
|
+
ttl: cachePlugin.driver.ttl || 'no expiration',
|
|
107
|
+
keyCount: cacheKeys.length,
|
|
108
|
+
sampleKeys: cacheKeys.slice(0, 5) // First 5 keys as sample
|
|
109
|
+
};
|
|
110
|
+
} else {
|
|
111
|
+
stats.cache = { enabled: false };
|
|
112
|
+
}
|
|
113
|
+
} catch (error) {
|
|
114
|
+
stats.cache = { enabled: false, error: error.message };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
success: true,
|
|
119
|
+
stats
|
|
120
|
+
};
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
async dbClearCache(args, database) {
|
|
124
|
+
server.ensureConnected(database);
|
|
125
|
+
const { resourceName } = args;
|
|
126
|
+
|
|
127
|
+
try {
|
|
128
|
+
const cachePlugin = database.pluginList?.find(p => p.constructor.name === 'CachePlugin');
|
|
129
|
+
if (!cachePlugin || !cachePlugin.driver) {
|
|
130
|
+
return {
|
|
131
|
+
success: false,
|
|
132
|
+
message: 'Cache is not enabled or available'
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (resourceName) {
|
|
137
|
+
// Clear cache for specific resource
|
|
138
|
+
const resource = server.getResource(database, resourceName);
|
|
139
|
+
await cachePlugin.clearCacheForResource(resource);
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
success: true,
|
|
143
|
+
message: `Cache cleared for resource: ${resourceName}`
|
|
144
|
+
};
|
|
145
|
+
} else {
|
|
146
|
+
// Clear all cache
|
|
147
|
+
await cachePlugin.driver.clear();
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
success: true,
|
|
151
|
+
message: 'All cache cleared'
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
} catch (error) {
|
|
155
|
+
return {
|
|
156
|
+
success: false,
|
|
157
|
+
message: `Failed to clear cache: ${error.message}`
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
|
|
162
|
+
async resourceGetStats(args, database) {
|
|
163
|
+
server.ensureConnected(database);
|
|
164
|
+
const { resourceName, includePartitionStats = true } = args;
|
|
165
|
+
const resource = server.getResource(database, resourceName);
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
const stats = {
|
|
169
|
+
success: true,
|
|
170
|
+
resource: resourceName,
|
|
171
|
+
totalDocuments: await resource.count(),
|
|
172
|
+
schema: {
|
|
173
|
+
attributeCount: Object.keys(resource.attributes || {}).length,
|
|
174
|
+
attributes: Object.keys(resource.attributes || {})
|
|
175
|
+
},
|
|
176
|
+
configuration: {
|
|
177
|
+
behavior: resource.behavior,
|
|
178
|
+
timestamps: resource.config.timestamps,
|
|
179
|
+
paranoid: resource.config.paranoid,
|
|
180
|
+
asyncPartitions: resource.config.asyncPartitions
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
// Partition stats
|
|
185
|
+
if (includePartitionStats && resource.config.partitions) {
|
|
186
|
+
stats.partitions = {
|
|
187
|
+
count: Object.keys(resource.config.partitions).length,
|
|
188
|
+
details: {}
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
for (const [partitionName, partitionConfig] of Object.entries(resource.config.partitions)) {
|
|
192
|
+
try {
|
|
193
|
+
const partitionCount = await resource.count({ partition: partitionName });
|
|
194
|
+
stats.partitions.details[partitionName] = {
|
|
195
|
+
fields: Object.keys(partitionConfig.fields || {}),
|
|
196
|
+
documentCount: partitionCount
|
|
197
|
+
};
|
|
198
|
+
} catch (error) {
|
|
199
|
+
stats.partitions.details[partitionName] = {
|
|
200
|
+
fields: Object.keys(partitionConfig.fields || {}),
|
|
201
|
+
error: error.message
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return stats;
|
|
208
|
+
} catch (error) {
|
|
209
|
+
return {
|
|
210
|
+
success: false,
|
|
211
|
+
error: error.message,
|
|
212
|
+
resource: resourceName
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
|
|
217
|
+
async cacheGetStats(args, database) {
|
|
218
|
+
server.ensureConnected(database);
|
|
219
|
+
const { resourceName } = args;
|
|
220
|
+
|
|
221
|
+
try {
|
|
222
|
+
const cachePlugin = database.pluginList?.find(p => p.constructor.name === 'CachePlugin');
|
|
223
|
+
|
|
224
|
+
if (!cachePlugin || !cachePlugin.driver) {
|
|
225
|
+
return {
|
|
226
|
+
success: false,
|
|
227
|
+
message: 'Cache is not enabled or available'
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const allKeys = await cachePlugin.driver.keys();
|
|
232
|
+
const cacheSize = await cachePlugin.driver.size();
|
|
233
|
+
|
|
234
|
+
const stats = {
|
|
235
|
+
success: true,
|
|
236
|
+
enabled: true,
|
|
237
|
+
driver: cachePlugin.driver.constructor.name,
|
|
238
|
+
totalKeys: allKeys.length,
|
|
239
|
+
totalSize: cacheSize,
|
|
240
|
+
config: {
|
|
241
|
+
maxSize: cachePlugin.driver.maxSize || 'unlimited',
|
|
242
|
+
ttl: cachePlugin.driver.ttl || 'no expiration'
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
// Resource-specific stats if requested
|
|
247
|
+
if (resourceName) {
|
|
248
|
+
const resourceKeys = allKeys.filter(key => key.includes(`resource=${resourceName}`));
|
|
249
|
+
stats.resource = {
|
|
250
|
+
name: resourceName,
|
|
251
|
+
keys: resourceKeys.length,
|
|
252
|
+
sampleKeys: resourceKeys.slice(0, 5)
|
|
253
|
+
};
|
|
254
|
+
} else {
|
|
255
|
+
// Group by resource
|
|
256
|
+
const byResource = {};
|
|
257
|
+
for (const key of allKeys) {
|
|
258
|
+
const match = key.match(/resource=([^/]+)/);
|
|
259
|
+
if (match) {
|
|
260
|
+
const res = match[1];
|
|
261
|
+
byResource[res] = (byResource[res] || 0) + 1;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
stats.byResource = byResource;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Memory stats for memory cache
|
|
268
|
+
if (cachePlugin.driver.constructor.name === 'MemoryCache' && cachePlugin.driver.getMemoryStats) {
|
|
269
|
+
stats.memory = cachePlugin.driver.getMemoryStats();
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return stats;
|
|
273
|
+
} catch (error) {
|
|
274
|
+
return {
|
|
275
|
+
success: false,
|
|
276
|
+
error: error.message
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "s3db.js",
|
|
3
|
-
"version": "11.2
|
|
3
|
+
"version": "11.3.2",
|
|
4
4
|
"description": "Use AWS S3, the world's most reliable document storage, as a database with this ORM.",
|
|
5
5
|
"main": "dist/s3db.cjs.js",
|
|
6
6
|
"module": "dist/s3db.es.js",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"bin": {
|
|
11
11
|
"s3db.js": "./bin/cli.js",
|
|
12
12
|
"s3db": "./bin/cli.js",
|
|
13
|
-
"s3db-mcp": "./mcp/
|
|
13
|
+
"s3db-mcp": "./mcp/entrypoint.js"
|
|
14
14
|
},
|
|
15
15
|
"repository": {
|
|
16
16
|
"type": "git",
|
|
@@ -30,7 +30,13 @@
|
|
|
30
30
|
"cloud-database",
|
|
31
31
|
"metadata-encoding",
|
|
32
32
|
"s3-database",
|
|
33
|
-
"serverless"
|
|
33
|
+
"serverless",
|
|
34
|
+
"mcp",
|
|
35
|
+
"model-context-protocol",
|
|
36
|
+
"mcp-server",
|
|
37
|
+
"claude",
|
|
38
|
+
"ai-tools",
|
|
39
|
+
"llm"
|
|
34
40
|
],
|
|
35
41
|
"type": "module",
|
|
36
42
|
"sideEffects": false,
|
|
@@ -52,7 +58,7 @@
|
|
|
52
58
|
"dist/",
|
|
53
59
|
"src/",
|
|
54
60
|
"bin/cli.js",
|
|
55
|
-
"mcp/
|
|
61
|
+
"mcp/",
|
|
56
62
|
"README.md",
|
|
57
63
|
"PLUGINS.md",
|
|
58
64
|
"SECURITY.md",
|
|
@@ -129,8 +135,11 @@
|
|
|
129
135
|
"https://github.com/sponsors/forattini-dev"
|
|
130
136
|
],
|
|
131
137
|
"scripts": {
|
|
132
|
-
"build": "
|
|
133
|
-
"build:
|
|
138
|
+
"build": "npm run build:core",
|
|
139
|
+
"build:core": "rollup -c",
|
|
140
|
+
"build:cli": "echo '✅ CLI uses ES modules directly from bin/cli.js (no build needed)'",
|
|
141
|
+
"build:mcp": "echo '✅ MCP uses ES modules directly from mcp/entrypoint.js (no build needed)'",
|
|
142
|
+
"build:all": "npm run build:core && npm run build:cli && npm run build:mcp",
|
|
134
143
|
"build:binaries": "./scripts/scripts/build-binaries.sh",
|
|
135
144
|
"dev": "rollup -c -w",
|
|
136
145
|
"test": "pnpm run test:js && pnpm run test:ts",
|
|
@@ -145,6 +154,7 @@
|
|
|
145
154
|
"benchmark:partitions": "node docs/benchmarks/partitions-matrix.js",
|
|
146
155
|
"release:check": "./scripts/pre-release-check.sh",
|
|
147
156
|
"validate:types": "pnpm run test:ts && echo 'TypeScript definitions are valid!'",
|
|
148
|
-
"test:ts:runtime": "tsx tests/typescript/types-runtime-simple.ts"
|
|
157
|
+
"test:ts:runtime": "tsx tests/typescript/types-runtime-simple.ts",
|
|
158
|
+
"test:mcp": "node mcp/entrypoint.js --help"
|
|
149
159
|
}
|
|
150
160
|
}
|
package/src/database.class.js
CHANGED
|
@@ -1006,7 +1006,7 @@ export class Database extends EventEmitter {
|
|
|
1006
1006
|
autoDecrypt: config.autoDecrypt !== undefined ? config.autoDecrypt : true,
|
|
1007
1007
|
hooks: hooks || {},
|
|
1008
1008
|
versioningEnabled: this.versioningEnabled,
|
|
1009
|
-
strictValidation: this.strictValidation,
|
|
1009
|
+
strictValidation: config.strictValidation !== undefined ? config.strictValidation : this.strictValidation,
|
|
1010
1010
|
map: config.map,
|
|
1011
1011
|
idGenerator: config.idGenerator,
|
|
1012
1012
|
idSize: config.idSize,
|