s3db.js 11.3.2 → 12.0.0

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.
Files changed (82) hide show
  1. package/README.md +102 -8
  2. package/dist/s3db.cjs.js +36664 -15480
  3. package/dist/s3db.cjs.js.map +1 -1
  4. package/dist/s3db.d.ts +57 -0
  5. package/dist/s3db.es.js +36661 -15531
  6. package/dist/s3db.es.js.map +1 -1
  7. package/mcp/entrypoint.js +58 -0
  8. package/mcp/tools/documentation.js +434 -0
  9. package/mcp/tools/index.js +4 -0
  10. package/package.json +27 -6
  11. package/src/behaviors/user-managed.js +13 -6
  12. package/src/client.class.js +41 -46
  13. package/src/concerns/base62.js +85 -0
  14. package/src/concerns/dictionary-encoding.js +294 -0
  15. package/src/concerns/geo-encoding.js +256 -0
  16. package/src/concerns/high-performance-inserter.js +34 -30
  17. package/src/concerns/ip.js +325 -0
  18. package/src/concerns/metadata-encoding.js +345 -66
  19. package/src/concerns/money.js +193 -0
  20. package/src/concerns/partition-queue.js +7 -4
  21. package/src/concerns/plugin-storage.js +39 -19
  22. package/src/database.class.js +76 -74
  23. package/src/errors.js +0 -4
  24. package/src/plugins/api/auth/api-key-auth.js +88 -0
  25. package/src/plugins/api/auth/basic-auth.js +154 -0
  26. package/src/plugins/api/auth/index.js +112 -0
  27. package/src/plugins/api/auth/jwt-auth.js +169 -0
  28. package/src/plugins/api/index.js +539 -0
  29. package/src/plugins/api/middlewares/index.js +15 -0
  30. package/src/plugins/api/middlewares/validator.js +185 -0
  31. package/src/plugins/api/routes/auth-routes.js +241 -0
  32. package/src/plugins/api/routes/resource-routes.js +304 -0
  33. package/src/plugins/api/server.js +350 -0
  34. package/src/plugins/api/utils/error-handler.js +147 -0
  35. package/src/plugins/api/utils/openapi-generator.js +1240 -0
  36. package/src/plugins/api/utils/response-formatter.js +218 -0
  37. package/src/plugins/backup/streaming-exporter.js +132 -0
  38. package/src/plugins/backup.plugin.js +103 -50
  39. package/src/plugins/cache/s3-cache.class.js +95 -47
  40. package/src/plugins/cache.plugin.js +107 -9
  41. package/src/plugins/concerns/plugin-dependencies.js +313 -0
  42. package/src/plugins/concerns/prometheus-formatter.js +255 -0
  43. package/src/plugins/consumers/rabbitmq-consumer.js +4 -0
  44. package/src/plugins/consumers/sqs-consumer.js +4 -0
  45. package/src/plugins/costs.plugin.js +255 -39
  46. package/src/plugins/eventual-consistency/helpers.js +15 -1
  47. package/src/plugins/geo.plugin.js +873 -0
  48. package/src/plugins/importer/index.js +1020 -0
  49. package/src/plugins/index.js +11 -0
  50. package/src/plugins/metrics.plugin.js +163 -4
  51. package/src/plugins/queue-consumer.plugin.js +6 -27
  52. package/src/plugins/relation.errors.js +139 -0
  53. package/src/plugins/relation.plugin.js +1242 -0
  54. package/src/plugins/replicators/bigquery-replicator.class.js +180 -8
  55. package/src/plugins/replicators/dynamodb-replicator.class.js +383 -0
  56. package/src/plugins/replicators/index.js +28 -3
  57. package/src/plugins/replicators/mongodb-replicator.class.js +391 -0
  58. package/src/plugins/replicators/mysql-replicator.class.js +558 -0
  59. package/src/plugins/replicators/planetscale-replicator.class.js +409 -0
  60. package/src/plugins/replicators/postgres-replicator.class.js +182 -7
  61. package/src/plugins/replicators/s3db-replicator.class.js +1 -12
  62. package/src/plugins/replicators/schema-sync.helper.js +601 -0
  63. package/src/plugins/replicators/sqs-replicator.class.js +11 -9
  64. package/src/plugins/replicators/turso-replicator.class.js +416 -0
  65. package/src/plugins/replicators/webhook-replicator.class.js +612 -0
  66. package/src/plugins/state-machine.plugin.js +122 -68
  67. package/src/plugins/tfstate/README.md +745 -0
  68. package/src/plugins/tfstate/base-driver.js +80 -0
  69. package/src/plugins/tfstate/errors.js +112 -0
  70. package/src/plugins/tfstate/filesystem-driver.js +129 -0
  71. package/src/plugins/tfstate/index.js +2660 -0
  72. package/src/plugins/tfstate/s3-driver.js +192 -0
  73. package/src/plugins/ttl.plugin.js +536 -0
  74. package/src/resource.class.js +14 -10
  75. package/src/s3db.d.ts +57 -0
  76. package/src/schema.class.js +366 -32
  77. package/SECURITY.md +0 -76
  78. package/src/partition-drivers/base-partition-driver.js +0 -106
  79. package/src/partition-drivers/index.js +0 -66
  80. package/src/partition-drivers/memory-partition-driver.js +0 -289
  81. package/src/partition-drivers/sqs-partition-driver.js +0 -337
  82. package/src/partition-drivers/sync-partition-driver.js +0 -38
package/mcp/entrypoint.js CHANGED
@@ -47,6 +47,35 @@ class S3dbMCPServer {
47
47
  this.server.setRequestHandler(ListToolsRequestSchema, async () => {
48
48
  return {
49
49
  tools: [
50
+ // 📖 DOCUMENTATION TOOLS (for AI agents)
51
+ {
52
+ name: 's3dbQueryDocs',
53
+ description: 'Search s3db.js documentation to answer questions about features, plugins, best practices, and usage. Use this tool to help AI agents understand how to use s3db.js effectively.',
54
+ inputSchema: {
55
+ type: 'object',
56
+ properties: {
57
+ query: {
58
+ type: 'string',
59
+ description: 'Natural language question about s3db.js (e.g., "How do I use GeoPlugin?", "What is the best caching strategy?", "How do partitions work?")'
60
+ },
61
+ maxResults: {
62
+ type: 'number',
63
+ description: 'Maximum number of documentation files to return',
64
+ default: 5
65
+ }
66
+ },
67
+ required: ['query']
68
+ }
69
+ },
70
+ {
71
+ name: 's3dbListTopics',
72
+ description: 'List all available documentation topics and their categories',
73
+ inputSchema: {
74
+ type: 'object',
75
+ properties: {},
76
+ required: []
77
+ }
78
+ },
50
79
  {
51
80
  name: 'dbConnect',
52
81
  description: 'Connect to an S3DB database with automatic costs tracking and configurable cache (memory or filesystem)',
@@ -877,6 +906,15 @@ class S3dbMCPServer {
877
906
  let result;
878
907
 
879
908
  switch (name) {
909
+ // Documentation tools
910
+ case 's3dbQueryDocs':
911
+ result = await this.handleS3dbQueryDocs(args);
912
+ break;
913
+
914
+ case 's3dbListTopics':
915
+ result = await this.handleS3dbListTopics(args);
916
+ break;
917
+
880
918
  case 'dbConnect':
881
919
  result = await this.handleDbConnect(args);
882
920
  break;
@@ -1135,6 +1173,26 @@ class S3dbMCPServer {
1135
1173
  });
1136
1174
  }
1137
1175
 
1176
+ // 📖 DOCUMENTATION TOOLS HANDLERS
1177
+
1178
+ async handleS3dbQueryDocs(args) {
1179
+ const { query, maxResults = 5 } = args;
1180
+
1181
+ // Import the documentation handler dynamically
1182
+ const { createDocumentationHandlers } = await import('./tools/documentation.js');
1183
+ const handlers = createDocumentationHandlers(this);
1184
+
1185
+ return await handlers.s3dbQueryDocs(args);
1186
+ }
1187
+
1188
+ async handleS3dbListTopics(args) {
1189
+ // Import the documentation handler dynamically
1190
+ const { createDocumentationHandlers } = await import('./tools/documentation.js');
1191
+ const handlers = createDocumentationHandlers(this);
1192
+
1193
+ return await handlers.s3dbListTopics(args);
1194
+ }
1195
+
1138
1196
  // Database connection handlers
1139
1197
  async handleDbConnect(args) {
1140
1198
  const {
@@ -0,0 +1,434 @@
1
+ /**
2
+ * Documentation Tools
3
+ * Provides intelligent documentation retrieval for AI agents
4
+ */
5
+
6
+ import { readFileSync, readdirSync, statSync } from 'fs';
7
+ import { join, dirname } from 'path';
8
+ import { fileURLToPath } from 'url';
9
+
10
+ const __filename = fileURLToPath(import.meta.url);
11
+ const __dirname = dirname(__filename);
12
+ const PROJECT_ROOT = join(__dirname, '../..');
13
+
14
+ /**
15
+ * Documentation index - maps topics to file paths and sections
16
+ */
17
+ const DOCUMENTATION_INDEX = {
18
+ // Core concepts
19
+ 'getting started': ['README.md', 'docs/schema.md'],
20
+ 'installation': ['README.md'],
21
+ 'connection': ['docs/client.md', 'CLAUDE.md'],
22
+ 'database': ['CLAUDE.md', 'docs/schema.md'],
23
+ 'resource': ['CLAUDE.md', 'docs/schema.md'],
24
+ 'schema': ['docs/schema.md', 'CLAUDE.md'],
25
+ 'validation': ['docs/schema.md', 'CLAUDE.md'],
26
+
27
+ // Data operations
28
+ 'insert': ['CLAUDE.md'],
29
+ 'update': ['CLAUDE.md'],
30
+ 'delete': ['CLAUDE.md'],
31
+ 'query': ['CLAUDE.md'],
32
+ 'list': ['CLAUDE.md'],
33
+ 'get': ['CLAUDE.md'],
34
+ 'count': ['CLAUDE.md'],
35
+ 'crud': ['CLAUDE.md'],
36
+
37
+ // Partitioning
38
+ 'partition': ['CLAUDE.md', 'docs/benchmarks/partitions.md'],
39
+ 'partitioning': ['CLAUDE.md', 'docs/benchmarks/partitions.md'],
40
+ 'orphaned partition': ['CLAUDE.md'],
41
+ 'partition migration': ['CLAUDE.md', 'docs/mcp.md'],
42
+
43
+ // Plugins
44
+ 'plugin': ['docs/plugins/README.md', 'CLAUDE.md'],
45
+ 'cache': ['docs/plugins/cache.md', 'CLAUDE.md'],
46
+ 'caching': ['docs/plugins/cache.md', 'CLAUDE.md'],
47
+ 'audit': ['docs/plugins/audit.md', 'CLAUDE.md'],
48
+ 'replicator': ['docs/plugins/replicator.md', 'CLAUDE.md'],
49
+ 'backup': ['docs/plugins/backup.md', 'CLAUDE.md'],
50
+ 'geo': ['docs/plugins/geo.md', 'CLAUDE.md'],
51
+ 'geospatial': ['docs/plugins/geo.md', 'CLAUDE.md'],
52
+ 'location': ['docs/plugins/geo.md'],
53
+ 'metrics': ['docs/plugins/metrics.md', 'CLAUDE.md'],
54
+ 'costs': ['docs/plugins/costs.md', 'CLAUDE.md'],
55
+ 'eventual consistency': ['docs/plugins/eventual-consistency.md', 'CLAUDE.md'],
56
+ 'fulltext': ['docs/plugins/fulltext.md'],
57
+ 'search': ['docs/plugins/fulltext.md'],
58
+ 'queue': ['docs/plugins/queue-consumer.md', 'docs/plugins/s3-queue.md'],
59
+
60
+ // Performance & Optimization
61
+ 'performance': ['CLAUDE.md', 'docs/benchmarks/README.md'],
62
+ 'optimization': ['CLAUDE.md', 'docs/benchmarks/README.md'],
63
+ 'benchmark': ['docs/benchmarks/README.md'],
64
+ 'compression': ['CLAUDE.md'],
65
+ 'encoding': ['CLAUDE.md', 'docs/benchmarks/smart-encoding.md'],
66
+
67
+ // MCP specific
68
+ 'mcp': ['docs/mcp.md'],
69
+ 'model context protocol': ['docs/mcp.md'],
70
+ 'ai agent': ['docs/mcp.md'],
71
+ 'claude desktop': ['docs/mcp.md'],
72
+
73
+ // Advanced features
74
+ 'encryption': ['CLAUDE.md'],
75
+ 'secret': ['CLAUDE.md'],
76
+ 'vector': ['docs/plugins/vector.md'],
77
+ 'embedding': ['CLAUDE.md', 'docs/plugins/vector.md'],
78
+ 'versioning': ['CLAUDE.md'],
79
+ 'hooks': ['CLAUDE.md'],
80
+ 'behavior': ['CLAUDE.md'],
81
+ 'metadata': ['CLAUDE.md'],
82
+
83
+ // Troubleshooting
84
+ 'error': ['CLAUDE.md', 'docs/mcp.md'],
85
+ 'troubleshooting': ['docs/mcp.md'],
86
+ 'recovery': ['CLAUDE.md']
87
+ };
88
+
89
+ /**
90
+ * Get all markdown files in docs directory
91
+ */
92
+ function getAllDocFiles() {
93
+ const files = [];
94
+
95
+ function walkDir(dir) {
96
+ try {
97
+ const items = readdirSync(dir);
98
+ for (const item of items) {
99
+ const fullPath = join(dir, item);
100
+ try {
101
+ const stat = statSync(fullPath);
102
+ if (stat.isDirectory()) {
103
+ walkDir(fullPath);
104
+ } else if (item.endsWith('.md')) {
105
+ const relativePath = fullPath.replace(PROJECT_ROOT + '/', '');
106
+ files.push(relativePath);
107
+ }
108
+ } catch (err) {
109
+ // Skip files we can't access
110
+ continue;
111
+ }
112
+ }
113
+ } catch (err) {
114
+ // Skip directories we can't access
115
+ return;
116
+ }
117
+ }
118
+
119
+ walkDir(join(PROJECT_ROOT, 'docs'));
120
+
121
+ // Add root-level important docs
122
+ try {
123
+ ['README.md', 'CLAUDE.md'].forEach(file => {
124
+ const fullPath = join(PROJECT_ROOT, file);
125
+ try {
126
+ if (statSync(fullPath).isFile()) {
127
+ files.push(file);
128
+ }
129
+ } catch (err) {
130
+ // Skip if file doesn't exist
131
+ }
132
+ });
133
+ } catch (err) {
134
+ // Skip if error accessing root files
135
+ }
136
+
137
+ return files;
138
+ }
139
+
140
+ /**
141
+ * Search for query terms in documentation
142
+ */
143
+ function searchDocumentation(query) {
144
+ const queryLower = query.toLowerCase();
145
+ const queryTerms = queryLower.split(/\s+/).filter(t => t.length > 2);
146
+
147
+ // Find relevant files from index
148
+ const relevantFiles = new Set();
149
+
150
+ // Check index first
151
+ for (const [topic, files] of Object.entries(DOCUMENTATION_INDEX)) {
152
+ if (queryLower.includes(topic) || queryTerms.some(term => topic.includes(term))) {
153
+ files.forEach(f => relevantFiles.add(f));
154
+ }
155
+ }
156
+
157
+ // If no matches in index, search all docs
158
+ if (relevantFiles.size === 0) {
159
+ getAllDocFiles().forEach(f => relevantFiles.add(f));
160
+ }
161
+
162
+ const results = [];
163
+
164
+ for (const filePath of relevantFiles) {
165
+ try {
166
+ const fullPath = join(PROJECT_ROOT, filePath);
167
+ const content = readFileSync(fullPath, 'utf-8');
168
+
169
+ // Calculate relevance score
170
+ let score = 0;
171
+ const contentLower = content.toLowerCase();
172
+
173
+ // Exact query match
174
+ if (contentLower.includes(queryLower)) {
175
+ score += 100;
176
+ }
177
+
178
+ // Individual term matches
179
+ queryTerms.forEach(term => {
180
+ const regex = new RegExp(`\\b${term}\\b`, 'gi');
181
+ const matches = content.match(regex);
182
+ if (matches) {
183
+ score += matches.length * 10;
184
+ }
185
+ });
186
+
187
+ // Boost for certain file types
188
+ if (filePath === 'CLAUDE.md') score *= 1.5;
189
+ if (filePath.includes('plugins/')) score *= 1.2;
190
+
191
+ if (score > 0) {
192
+ // Extract relevant sections
193
+ const sections = extractRelevantSections(content, queryTerms, filePath);
194
+
195
+ results.push({
196
+ file: filePath,
197
+ score,
198
+ sections
199
+ });
200
+ }
201
+ } catch (error) {
202
+ // Skip files that can't be read
203
+ continue;
204
+ }
205
+ }
206
+
207
+ // Sort by relevance
208
+ results.sort((a, b) => b.score - a.score);
209
+
210
+ return results;
211
+ }
212
+
213
+ /**
214
+ * Extract relevant sections from markdown content
215
+ */
216
+ function extractRelevantSections(content, queryTerms, filePath) {
217
+ const lines = content.split('\n');
218
+ const sections = [];
219
+ let currentSection = null;
220
+ let currentContent = [];
221
+ let sectionScore = 0;
222
+
223
+ for (let i = 0; i < lines.length; i++) {
224
+ const line = lines[i];
225
+
226
+ // Detect section headers
227
+ if (line.match(/^#{1,4}\s/)) {
228
+ // Save previous section if it has content
229
+ if (currentSection && currentContent.length > 0 && sectionScore > 0) {
230
+ sections.push({
231
+ header: currentSection,
232
+ content: currentContent.join('\n').trim(),
233
+ score: sectionScore
234
+ });
235
+ }
236
+
237
+ // Start new section
238
+ currentSection = line;
239
+ currentContent = [line];
240
+ sectionScore = 0;
241
+
242
+ // Score the header
243
+ const headerLower = line.toLowerCase();
244
+ queryTerms.forEach(term => {
245
+ if (headerLower.includes(term)) {
246
+ sectionScore += 50;
247
+ }
248
+ });
249
+ } else if (currentSection) {
250
+ currentContent.push(line);
251
+
252
+ // Score the content
253
+ const lineLower = line.toLowerCase();
254
+ queryTerms.forEach(term => {
255
+ if (lineLower.includes(term)) {
256
+ sectionScore += 5;
257
+ }
258
+ });
259
+ }
260
+ }
261
+
262
+ // Save last section
263
+ if (currentSection && currentContent.length > 0 && sectionScore > 0) {
264
+ sections.push({
265
+ header: currentSection,
266
+ content: currentContent.join('\n').trim(),
267
+ score: sectionScore
268
+ });
269
+ }
270
+
271
+ // Sort sections by score and limit
272
+ sections.sort((a, b) => b.score - a.score);
273
+
274
+ // Return top sections (limit to avoid huge responses)
275
+ return sections.slice(0, 3);
276
+ }
277
+
278
+ /**
279
+ * Format search results for display
280
+ */
281
+ function formatResults(results, query, maxResults = 5) {
282
+ if (results.length === 0) {
283
+ return {
284
+ query,
285
+ found: false,
286
+ message: 'No documentation found for this query. Try rephrasing or asking about: plugins, cache, partitions, schema, CRUD operations, or MCP integration.',
287
+ suggestions: [
288
+ 'How do I use the CachePlugin?',
289
+ 'What are partitions and when should I use them?',
290
+ 'How do I create a resource with schema validation?',
291
+ 'How does the MCP server work?',
292
+ 'What plugins are available?'
293
+ ]
294
+ };
295
+ }
296
+
297
+ const topResults = results.slice(0, maxResults);
298
+
299
+ const formatted = {
300
+ query,
301
+ found: true,
302
+ resultCount: results.length,
303
+ showing: topResults.length,
304
+ results: []
305
+ };
306
+
307
+ for (const result of topResults) {
308
+ const formattedResult = {
309
+ file: result.file,
310
+ relevanceScore: result.score,
311
+ sections: []
312
+ };
313
+
314
+ for (const section of result.sections) {
315
+ // Limit section content length
316
+ let content = section.content;
317
+ if (content.length > 1500) {
318
+ content = content.substring(0, 1500) + '\n\n... (truncated)';
319
+ }
320
+
321
+ formattedResult.sections.push({
322
+ header: section.header,
323
+ content: content
324
+ });
325
+ }
326
+
327
+ formatted.results.push(formattedResult);
328
+ }
329
+
330
+ return formatted;
331
+ }
332
+
333
+ export const documentationTools = [
334
+ {
335
+ name: 's3dbQueryDocs',
336
+ description: 'Search s3db.js documentation to answer questions about features, plugins, best practices, and usage. Use this tool to help AI agents understand how to use s3db.js effectively.',
337
+ inputSchema: {
338
+ type: 'object',
339
+ properties: {
340
+ query: {
341
+ type: 'string',
342
+ description: 'Natural language question about s3db.js (e.g., "How do I use GeoPlugin?", "What is the best caching strategy?", "How do partitions work?")'
343
+ },
344
+ maxResults: {
345
+ type: 'number',
346
+ description: 'Maximum number of documentation files to return',
347
+ default: 5
348
+ }
349
+ },
350
+ required: ['query']
351
+ }
352
+ },
353
+ {
354
+ name: 's3dbListTopics',
355
+ description: 'List all available documentation topics and their categories',
356
+ inputSchema: {
357
+ type: 'object',
358
+ properties: {},
359
+ required: []
360
+ }
361
+ }
362
+ ];
363
+
364
+ export function createDocumentationHandlers(server) {
365
+ return {
366
+ async s3dbQueryDocs(args) {
367
+ const { query, maxResults = 5 } = args;
368
+
369
+ try {
370
+ const results = searchDocumentation(query);
371
+ const formatted = formatResults(results, query, maxResults);
372
+
373
+ return {
374
+ success: true,
375
+ ...formatted
376
+ };
377
+ } catch (error) {
378
+ return {
379
+ success: false,
380
+ query,
381
+ error: error.message,
382
+ suggestion: 'Try rephrasing your question or use s3dbListTopics to see available topics'
383
+ };
384
+ }
385
+ },
386
+
387
+ async s3dbListTopics(args) {
388
+ const topics = {};
389
+
390
+ // Organize by category
391
+ topics.core = ['getting started', 'installation', 'connection', 'database', 'resource', 'schema', 'validation'];
392
+ topics.operations = ['insert', 'update', 'delete', 'query', 'list', 'get', 'count', 'crud'];
393
+ topics.partitioning = ['partition', 'partitioning', 'orphaned partition', 'partition migration'];
394
+ topics.plugins = ['plugin', 'cache', 'audit', 'replicator', 'backup', 'geo', 'metrics', 'costs', 'eventual consistency', 'fulltext', 'queue'];
395
+ topics.performance = ['performance', 'optimization', 'benchmark', 'compression', 'encoding'];
396
+ topics.mcp = ['mcp', 'model context protocol', 'ai agent', 'claude desktop'];
397
+ topics.advanced = ['encryption', 'secret', 'vector', 'embedding', 'versioning', 'hooks', 'behavior', 'metadata'];
398
+ topics.troubleshooting = ['error', 'troubleshooting', 'recovery'];
399
+
400
+ const allFiles = getAllDocFiles();
401
+
402
+ return {
403
+ success: true,
404
+ message: 'Use s3dbQueryDocs with any of these topics to get detailed documentation',
405
+ categories: topics,
406
+ availableFiles: allFiles,
407
+ totalTopics: Object.keys(DOCUMENTATION_INDEX).length,
408
+ totalFiles: allFiles.length,
409
+ examples: [
410
+ {
411
+ query: 'How do I use the CachePlugin?',
412
+ description: 'Learn about caching strategies and configuration'
413
+ },
414
+ {
415
+ query: 'What are partitions?',
416
+ description: 'Understand partitioning for performance optimization'
417
+ },
418
+ {
419
+ query: 'How do I handle orphaned partitions?',
420
+ description: 'Recovery workflow for partition issues'
421
+ },
422
+ {
423
+ query: 'What plugins are available?',
424
+ description: 'Complete list of available plugins and their purposes'
425
+ },
426
+ {
427
+ query: 'How does MCP integration work?',
428
+ description: 'Setting up and using the MCP server with AI agents'
429
+ }
430
+ ]
431
+ };
432
+ }
433
+ };
434
+ }
@@ -12,12 +12,14 @@ import { partitionTools, createPartitionHandlers } from './partitions.js';
12
12
  import { bulkTools, createBulkHandlers } from './bulk.js';
13
13
  import { exportImportTools, createExportImportHandlers } from './export-import.js';
14
14
  import { statsTools, createStatsHandlers } from './stats.js';
15
+ import { documentationTools, createDocumentationHandlers } from './documentation.js';
15
16
 
16
17
  /**
17
18
  * Get all tool definitions
18
19
  */
19
20
  export function getAllTools() {
20
21
  return [
22
+ ...documentationTools,
21
23
  ...connectionTools,
22
24
  ...resourceManagementTools,
23
25
  ...crudTools,
@@ -37,6 +39,7 @@ export function getAllTools() {
37
39
  */
38
40
  export function createAllHandlers(server) {
39
41
  return {
42
+ ...createDocumentationHandlers(server),
40
43
  ...createConnectionHandlers(server),
41
44
  ...createResourceManagementHandlers(server),
42
45
  ...createCrudHandlers(server),
@@ -54,6 +57,7 @@ export function createAllHandlers(server) {
54
57
  */
55
58
  export function getToolsByCategory() {
56
59
  return {
60
+ documentation: documentationTools,
57
61
  connection: connectionTools,
58
62
  resources: resourceManagementTools,
59
63
  crud: crudTools,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "s3db.js",
3
- "version": "11.3.2",
3
+ "version": "12.0.0",
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",
@@ -72,6 +72,7 @@
72
72
  "dotenv": "^17.2.3",
73
73
  "fastest-validator": "^1.19.1",
74
74
  "flat": "^6.0.1",
75
+ "glob": "^11.0.3",
75
76
  "json-stable-stringify": "^1.3.0",
76
77
  "lodash-es": "^4.17.21",
77
78
  "nanoid": "5.1.6"
@@ -79,9 +80,12 @@
79
80
  "peerDependencies": {
80
81
  "@aws-sdk/client-sqs": "^3.0.0",
81
82
  "@google-cloud/bigquery": "^7.0.0",
83
+ "@hono/node-server": "^1.0.0",
84
+ "@hono/swagger-ui": "^0.5.0",
82
85
  "amqplib": "^0.10.8",
83
- "pg": "^8.0.0",
84
- "uuid": "^9.0.0"
86
+ "hono": "^4.0.0",
87
+ "node-cron": "^4.0.0",
88
+ "pg": "^8.0.0"
85
89
  },
86
90
  "peerDependenciesMeta": {
87
91
  "@aws-sdk/client-sqs": {
@@ -90,19 +94,30 @@
90
94
  "@google-cloud/bigquery": {
91
95
  "optional": true
92
96
  },
93
- "pg": {
97
+ "@hono/node-server": {
94
98
  "optional": true
95
99
  },
96
- "uuid": {
100
+ "@hono/swagger-ui": {
101
+ "optional": true
102
+ },
103
+ "pg": {
97
104
  "optional": true
98
105
  },
99
106
  "amqplib": {
100
107
  "optional": true
108
+ },
109
+ "hono": {
110
+ "optional": true
111
+ },
112
+ "node-cron": {
113
+ "optional": true
101
114
  }
102
115
  },
103
116
  "devDependencies": {
117
+ "@aws-sdk/client-sqs": "^3.0.0",
104
118
  "@babel/core": "^7.28.4",
105
119
  "@babel/preset-env": "^7.28.3",
120
+ "@google-cloud/bigquery": "^7.0.0",
106
121
  "@rollup/plugin-commonjs": "^28.0.6",
107
122
  "@rollup/plugin-json": "^6.1.0",
108
123
  "@rollup/plugin-node-resolve": "^16.0.2",
@@ -110,6 +125,7 @@
110
125
  "@rollup/plugin-terser": "^0.4.4",
111
126
  "@types/node": "24.7.0",
112
127
  "@xenova/transformers": "^2.17.2",
128
+ "amqplib": "^0.10.8",
113
129
  "babel-loader": "^10.0.0",
114
130
  "chalk": "^5.6.2",
115
131
  "cli-table3": "^0.6.5",
@@ -117,8 +133,10 @@
117
133
  "esbuild": "^0.25.10",
118
134
  "inquirer": "^12.9.6",
119
135
  "jest": "^30.2.0",
136
+ "node-cron": "^4.0.0",
120
137
  "node-loader": "^2.1.0",
121
138
  "ora": "^9.0.0",
139
+ "pg": "^8.0.0",
122
140
  "pkg": "^5.8.1",
123
141
  "rollup": "^4.52.4",
124
142
  "rollup-plugin-copy": "^3.5.0",
@@ -128,6 +146,7 @@
128
146
  "rollup-plugin-terser": "^7.0.2",
129
147
  "tsx": "^4.20.6",
130
148
  "typescript": "5.9.3",
149
+ "uuid": "^13.0.0",
131
150
  "webpack": "^5.102.1",
132
151
  "webpack-cli": "^6.0.1"
133
152
  },
@@ -155,6 +174,8 @@
155
174
  "release:check": "./scripts/pre-release-check.sh",
156
175
  "validate:types": "pnpm run test:ts && echo 'TypeScript definitions are valid!'",
157
176
  "test:ts:runtime": "tsx tests/typescript/types-runtime-simple.ts",
158
- "test:mcp": "node mcp/entrypoint.js --help"
177
+ "test:mcp": "node mcp/entrypoint.js --help",
178
+ "install:peers": "pnpm add -D @aws-sdk/client-sqs @google-cloud/bigquery amqplib node-cron pg",
179
+ "install:peers:script": "./scripts/install-peer-deps.sh"
159
180
  }
160
181
  }
@@ -1,6 +1,7 @@
1
1
  import { calculateTotalSize } from '../concerns/calculator.js';
2
2
  import { calculateEffectiveLimit } from '../concerns/calculator.js';
3
3
  import { S3_METADATA_LIMIT_BYTES } from './enforce-limits.js';
4
+ import tryFn from '../concerns/try-fn.js';
4
5
 
5
6
  /**
6
7
  * User Managed Behavior Configuration Documentation
@@ -149,15 +150,21 @@ export async function handleUpsert({ resource, id, data, mappedData, originalDat
149
150
  export async function handleGet({ resource, metadata, body }) {
150
151
  // If body contains data, parse it and merge with metadata
151
152
  if (body && body.trim() !== '') {
152
- try {
153
+ const [ok, error, result] = tryFn(() => {
153
154
  const bodyData = JSON.parse(body);
154
155
  // Merge body data with metadata, with metadata taking precedence
155
- const mergedData = {
156
- ...bodyData,
157
- ...metadata
156
+ return {
157
+ metadata: {
158
+ ...bodyData,
159
+ ...metadata
160
+ },
161
+ body
158
162
  };
159
- return { metadata: mergedData, body };
160
- } catch (error) {
163
+ });
164
+
165
+ if (ok) {
166
+ return result;
167
+ } else {
161
168
  // If parsing fails, return original metadata and body
162
169
  return { metadata, body };
163
170
  }