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,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bulk Operations Tools
|
|
3
|
+
* Handles bulk updates and upserts for efficient batch processing
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export const bulkTools = [
|
|
7
|
+
{
|
|
8
|
+
name: 'resourceUpdateMany',
|
|
9
|
+
description: 'Update multiple documents matching a query filter',
|
|
10
|
+
inputSchema: {
|
|
11
|
+
type: 'object',
|
|
12
|
+
properties: {
|
|
13
|
+
resourceName: {
|
|
14
|
+
type: 'string',
|
|
15
|
+
description: 'Name of the resource'
|
|
16
|
+
},
|
|
17
|
+
filters: {
|
|
18
|
+
type: 'object',
|
|
19
|
+
description: 'Query filters to select documents'
|
|
20
|
+
},
|
|
21
|
+
updates: {
|
|
22
|
+
type: 'object',
|
|
23
|
+
description: 'Updates to apply to matching documents'
|
|
24
|
+
},
|
|
25
|
+
limit: {
|
|
26
|
+
type: 'number',
|
|
27
|
+
description: 'Maximum number of documents to update',
|
|
28
|
+
default: 1000
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
required: ['resourceName', 'filters', 'updates']
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: 'resourceBulkUpsert',
|
|
36
|
+
description: 'Upsert multiple documents (insert if not exists, update if exists)',
|
|
37
|
+
inputSchema: {
|
|
38
|
+
type: 'object',
|
|
39
|
+
properties: {
|
|
40
|
+
resourceName: {
|
|
41
|
+
type: 'string',
|
|
42
|
+
description: 'Name of the resource'
|
|
43
|
+
},
|
|
44
|
+
data: {
|
|
45
|
+
type: 'array',
|
|
46
|
+
description: 'Array of documents to upsert (must include id field)'
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
required: ['resourceName', 'data']
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
export function createBulkHandlers(server) {
|
|
55
|
+
return {
|
|
56
|
+
async resourceUpdateMany(args, database) {
|
|
57
|
+
server.ensureConnected(database);
|
|
58
|
+
const { resourceName, filters, updates, limit = 1000 } = args;
|
|
59
|
+
const resource = server.getResource(database, resourceName);
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
// Query documents matching filters
|
|
63
|
+
const docs = await resource.query(filters, { limit });
|
|
64
|
+
|
|
65
|
+
// Update each document
|
|
66
|
+
const updatePromises = docs.map(doc =>
|
|
67
|
+
resource.update(doc.id, updates)
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
const results = await Promise.all(updatePromises);
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
success: true,
|
|
74
|
+
updatedCount: results.length,
|
|
75
|
+
filters,
|
|
76
|
+
updates,
|
|
77
|
+
data: results
|
|
78
|
+
};
|
|
79
|
+
} catch (error) {
|
|
80
|
+
return {
|
|
81
|
+
success: false,
|
|
82
|
+
error: error.message,
|
|
83
|
+
filters,
|
|
84
|
+
updates
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
async resourceBulkUpsert(args, database) {
|
|
90
|
+
server.ensureConnected(database);
|
|
91
|
+
const { resourceName, data } = args;
|
|
92
|
+
const resource = server.getResource(database, resourceName);
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
// Upsert each document
|
|
96
|
+
const upsertPromises = data.map(doc => resource.upsert(doc));
|
|
97
|
+
const results = await Promise.all(upsertPromises);
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
success: true,
|
|
101
|
+
upsertedCount: results.length,
|
|
102
|
+
data: results
|
|
103
|
+
};
|
|
104
|
+
} catch (error) {
|
|
105
|
+
return {
|
|
106
|
+
success: false,
|
|
107
|
+
error: error.message
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Connection Management Tools
|
|
3
|
+
* Handles database connection, disconnection, and status checks
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export const connectionTools = [
|
|
7
|
+
{
|
|
8
|
+
name: 'dbConnect',
|
|
9
|
+
description: 'Connect to an S3DB database with automatic costs tracking and configurable cache (memory or filesystem)',
|
|
10
|
+
inputSchema: {
|
|
11
|
+
type: 'object',
|
|
12
|
+
properties: {
|
|
13
|
+
connectionString: {
|
|
14
|
+
type: 'string',
|
|
15
|
+
description: 'S3DB connection string (e.g., s3://key:secret@bucket/path)'
|
|
16
|
+
},
|
|
17
|
+
verbose: {
|
|
18
|
+
type: 'boolean',
|
|
19
|
+
description: 'Enable verbose logging',
|
|
20
|
+
default: false
|
|
21
|
+
},
|
|
22
|
+
parallelism: {
|
|
23
|
+
type: 'number',
|
|
24
|
+
description: 'Number of parallel operations',
|
|
25
|
+
default: 10
|
|
26
|
+
},
|
|
27
|
+
passphrase: {
|
|
28
|
+
type: 'string',
|
|
29
|
+
description: 'Passphrase for encryption',
|
|
30
|
+
default: 'secret'
|
|
31
|
+
},
|
|
32
|
+
versioningEnabled: {
|
|
33
|
+
type: 'boolean',
|
|
34
|
+
description: 'Enable resource versioning',
|
|
35
|
+
default: false
|
|
36
|
+
},
|
|
37
|
+
enableCache: {
|
|
38
|
+
type: 'boolean',
|
|
39
|
+
description: 'Enable cache for improved performance',
|
|
40
|
+
default: true
|
|
41
|
+
},
|
|
42
|
+
enableCosts: {
|
|
43
|
+
type: 'boolean',
|
|
44
|
+
description: 'Enable costs tracking for S3 operations',
|
|
45
|
+
default: true
|
|
46
|
+
},
|
|
47
|
+
cacheDriver: {
|
|
48
|
+
type: 'string',
|
|
49
|
+
description: 'Cache driver type: "memory" or "filesystem"',
|
|
50
|
+
enum: ['memory', 'filesystem'],
|
|
51
|
+
default: 'memory'
|
|
52
|
+
},
|
|
53
|
+
cacheMaxSize: {
|
|
54
|
+
type: 'number',
|
|
55
|
+
description: 'Maximum number of items in memory cache (memory driver only)',
|
|
56
|
+
default: 1000
|
|
57
|
+
},
|
|
58
|
+
cacheTtl: {
|
|
59
|
+
type: 'number',
|
|
60
|
+
description: 'Cache time-to-live in milliseconds',
|
|
61
|
+
default: 300000
|
|
62
|
+
},
|
|
63
|
+
cacheDirectory: {
|
|
64
|
+
type: 'string',
|
|
65
|
+
description: 'Directory path for filesystem cache (filesystem driver only)',
|
|
66
|
+
default: './cache'
|
|
67
|
+
},
|
|
68
|
+
cachePrefix: {
|
|
69
|
+
type: 'string',
|
|
70
|
+
description: 'Prefix for cache files (filesystem driver only)',
|
|
71
|
+
default: 'cache'
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
required: ['connectionString']
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
name: 'dbDisconnect',
|
|
79
|
+
description: 'Disconnect from the S3DB database',
|
|
80
|
+
inputSchema: {
|
|
81
|
+
type: 'object',
|
|
82
|
+
properties: {},
|
|
83
|
+
required: []
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
name: 'dbStatus',
|
|
88
|
+
description: 'Get the current database connection status',
|
|
89
|
+
inputSchema: {
|
|
90
|
+
type: 'object',
|
|
91
|
+
properties: {},
|
|
92
|
+
required: []
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
export function createConnectionHandlers(server) {
|
|
98
|
+
return {
|
|
99
|
+
async dbConnect(args, database, { S3db, CachePlugin, CostsPlugin, FilesystemCache }) {
|
|
100
|
+
const {
|
|
101
|
+
connectionString,
|
|
102
|
+
verbose = false,
|
|
103
|
+
parallelism = 10,
|
|
104
|
+
passphrase = 'secret',
|
|
105
|
+
versioningEnabled = false,
|
|
106
|
+
enableCache = true,
|
|
107
|
+
enableCosts = true,
|
|
108
|
+
cacheDriver = 'memory',
|
|
109
|
+
cacheMaxSize = 1000,
|
|
110
|
+
cacheTtl = 300000,
|
|
111
|
+
cacheDirectory = './cache',
|
|
112
|
+
cachePrefix = 'cache'
|
|
113
|
+
} = args;
|
|
114
|
+
|
|
115
|
+
if (database && database.isConnected()) {
|
|
116
|
+
return { success: false, message: 'Database is already connected' };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const plugins = [];
|
|
120
|
+
|
|
121
|
+
// Costs plugin
|
|
122
|
+
const costsEnabled = enableCosts !== false && process.env.S3DB_COSTS_ENABLED !== 'false';
|
|
123
|
+
if (costsEnabled) {
|
|
124
|
+
plugins.push(CostsPlugin);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Cache plugin
|
|
128
|
+
const cacheEnabled = enableCache !== false && process.env.S3DB_CACHE_ENABLED !== 'false';
|
|
129
|
+
|
|
130
|
+
if (cacheEnabled) {
|
|
131
|
+
const cacheMaxSizeEnv = process.env.S3DB_CACHE_MAX_SIZE ? parseInt(process.env.S3DB_CACHE_MAX_SIZE) : cacheMaxSize;
|
|
132
|
+
const cacheTtlEnv = process.env.S3DB_CACHE_TTL ? parseInt(process.env.S3DB_CACHE_TTL) : cacheTtl;
|
|
133
|
+
const cacheDriverEnv = process.env.S3DB_CACHE_DRIVER || cacheDriver;
|
|
134
|
+
const cacheDirectoryEnv = process.env.S3DB_CACHE_DIRECTORY || cacheDirectory;
|
|
135
|
+
const cachePrefixEnv = process.env.S3DB_CACHE_PREFIX || cachePrefix;
|
|
136
|
+
|
|
137
|
+
let cacheConfig = {
|
|
138
|
+
includePartitions: true
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
if (cacheDriverEnv === 'filesystem') {
|
|
142
|
+
cacheConfig.driver = new FilesystemCache({
|
|
143
|
+
directory: cacheDirectoryEnv,
|
|
144
|
+
prefix: cachePrefixEnv,
|
|
145
|
+
ttl: cacheTtlEnv,
|
|
146
|
+
enableCompression: true,
|
|
147
|
+
enableStats: verbose,
|
|
148
|
+
enableCleanup: true,
|
|
149
|
+
cleanupInterval: 300000,
|
|
150
|
+
createDirectory: true
|
|
151
|
+
});
|
|
152
|
+
} else {
|
|
153
|
+
cacheConfig.driver = 'memory';
|
|
154
|
+
cacheConfig.memoryOptions = {
|
|
155
|
+
maxSize: cacheMaxSizeEnv,
|
|
156
|
+
ttl: cacheTtlEnv,
|
|
157
|
+
enableStats: verbose
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
plugins.push(new CachePlugin(cacheConfig));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const newDatabase = new S3db({
|
|
165
|
+
connectionString,
|
|
166
|
+
verbose,
|
|
167
|
+
parallelism,
|
|
168
|
+
passphrase,
|
|
169
|
+
versioningEnabled,
|
|
170
|
+
plugins
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
await newDatabase.connect();
|
|
174
|
+
|
|
175
|
+
return {
|
|
176
|
+
success: true,
|
|
177
|
+
message: 'Connected to S3DB database',
|
|
178
|
+
database: newDatabase,
|
|
179
|
+
status: {
|
|
180
|
+
connected: newDatabase.isConnected(),
|
|
181
|
+
bucket: newDatabase.bucket,
|
|
182
|
+
keyPrefix: newDatabase.keyPrefix,
|
|
183
|
+
version: newDatabase.s3dbVersion,
|
|
184
|
+
plugins: {
|
|
185
|
+
costs: costsEnabled,
|
|
186
|
+
cache: cacheEnabled,
|
|
187
|
+
cacheDriver: cacheEnabled ? cacheDriverEnv : null,
|
|
188
|
+
cacheDirectory: cacheEnabled && cacheDriverEnv === 'filesystem' ? cacheDirectoryEnv : null,
|
|
189
|
+
cacheMaxSize: cacheEnabled && cacheDriverEnv === 'memory' ? cacheMaxSizeEnv : null,
|
|
190
|
+
cacheTtl: cacheEnabled ? cacheTtlEnv : null
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
},
|
|
195
|
+
|
|
196
|
+
async dbDisconnect(args, database) {
|
|
197
|
+
if (!database || !database.isConnected()) {
|
|
198
|
+
return { success: false, message: 'No database connection to disconnect' };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
await database.disconnect();
|
|
202
|
+
|
|
203
|
+
return {
|
|
204
|
+
success: true,
|
|
205
|
+
message: 'Disconnected from S3DB database',
|
|
206
|
+
clearDatabase: true
|
|
207
|
+
};
|
|
208
|
+
},
|
|
209
|
+
|
|
210
|
+
async dbStatus(args, database) {
|
|
211
|
+
if (!database) {
|
|
212
|
+
return {
|
|
213
|
+
connected: false,
|
|
214
|
+
message: 'No database instance created'
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return {
|
|
219
|
+
connected: database.isConnected(),
|
|
220
|
+
bucket: database.bucket,
|
|
221
|
+
keyPrefix: database.keyPrefix,
|
|
222
|
+
version: database.s3dbVersion,
|
|
223
|
+
resourceCount: Object.keys(database.resources || {}).length,
|
|
224
|
+
resources: Object.keys(database.resources || {})
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
}
|