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.
@@ -0,0 +1,223 @@
1
+ /**
2
+ * Partition Management Tools
3
+ * Handles partition listing, inspection, and orphaned partition recovery
4
+ */
5
+
6
+ export const partitionTools = [
7
+ {
8
+ name: 'resourceListPartitions',
9
+ description: 'List all partitions defined for a resource',
10
+ inputSchema: {
11
+ type: 'object',
12
+ properties: {
13
+ resourceName: {
14
+ type: 'string',
15
+ description: 'Name of the resource'
16
+ }
17
+ },
18
+ required: ['resourceName']
19
+ }
20
+ },
21
+ {
22
+ name: 'resourceListPartitionValues',
23
+ description: 'List unique values for a specific partition field',
24
+ inputSchema: {
25
+ type: 'object',
26
+ properties: {
27
+ resourceName: {
28
+ type: 'string',
29
+ description: 'Name of the resource'
30
+ },
31
+ partitionName: {
32
+ type: 'string',
33
+ description: 'Name of the partition'
34
+ },
35
+ limit: {
36
+ type: 'number',
37
+ description: 'Maximum number of values to return',
38
+ default: 1000
39
+ }
40
+ },
41
+ required: ['resourceName', 'partitionName']
42
+ }
43
+ },
44
+ {
45
+ name: 'dbFindOrphanedPartitions',
46
+ description: 'Find partitions that reference fields no longer in the schema',
47
+ inputSchema: {
48
+ type: 'object',
49
+ properties: {
50
+ resourceName: {
51
+ type: 'string',
52
+ description: 'Name of specific resource to check (optional - checks all if not provided)'
53
+ }
54
+ },
55
+ required: []
56
+ }
57
+ },
58
+ {
59
+ name: 'dbRemoveOrphanedPartitions',
60
+ description: 'Remove orphaned partitions from resource configuration',
61
+ inputSchema: {
62
+ type: 'object',
63
+ properties: {
64
+ resourceName: {
65
+ type: 'string',
66
+ description: 'Name of the resource'
67
+ },
68
+ dryRun: {
69
+ type: 'boolean',
70
+ description: 'Preview changes without applying them',
71
+ default: true
72
+ }
73
+ },
74
+ required: ['resourceName']
75
+ }
76
+ }
77
+ ];
78
+
79
+ export function createPartitionHandlers(server) {
80
+ return {
81
+ async resourceListPartitions(args, database) {
82
+ server.ensureConnected(database);
83
+ const { resourceName } = args;
84
+ const resource = server.getResource(database, resourceName);
85
+
86
+ const partitions = resource.config.partitions || {};
87
+
88
+ return {
89
+ success: true,
90
+ resource: resourceName,
91
+ partitions: Object.keys(partitions),
92
+ count: Object.keys(partitions).length,
93
+ details: partitions
94
+ };
95
+ },
96
+
97
+ async resourceListPartitionValues(args, database) {
98
+ server.ensureConnected(database);
99
+ const { resourceName, partitionName, limit = 1000 } = args;
100
+ const resource = server.getResource(database, resourceName);
101
+
102
+ if (!resource.config.partitions || !resource.config.partitions[partitionName]) {
103
+ throw new Error(`Partition '${partitionName}' not found in resource '${resourceName}'`);
104
+ }
105
+
106
+ try {
107
+ // List all objects with this partition prefix
108
+ const prefix = `${database.keyPrefix}resource=${resourceName}/partition=${partitionName}/`;
109
+
110
+ const response = await database.client.listObjectsV2({
111
+ Bucket: database.bucket,
112
+ Prefix: prefix,
113
+ MaxKeys: limit
114
+ });
115
+
116
+ // Extract unique partition values from keys
117
+ const partitionValues = new Set();
118
+
119
+ for (const obj of response.Contents || []) {
120
+ // Parse partition values from key
121
+ const keyParts = obj.Key.split('/');
122
+ const partitionPart = keyParts.find(part => part.startsWith('partition='));
123
+ if (partitionPart) {
124
+ const valuesPart = keyParts.slice(keyParts.indexOf(partitionPart) + 1).find(part => !part.startsWith('id='));
125
+ if (valuesPart) {
126
+ partitionValues.add(valuesPart);
127
+ }
128
+ }
129
+ }
130
+
131
+ return {
132
+ success: true,
133
+ resource: resourceName,
134
+ partition: partitionName,
135
+ values: Array.from(partitionValues),
136
+ count: partitionValues.size
137
+ };
138
+ } catch (error) {
139
+ return {
140
+ success: false,
141
+ error: error.message,
142
+ resource: resourceName,
143
+ partition: partitionName
144
+ };
145
+ }
146
+ },
147
+
148
+ async dbFindOrphanedPartitions(args, database) {
149
+ server.ensureConnected(database);
150
+ const { resourceName } = args;
151
+
152
+ const orphanedByResource = {};
153
+ const resourcesToCheck = resourceName
154
+ ? [resourceName]
155
+ : Object.keys(database.resources || {});
156
+
157
+ for (const name of resourcesToCheck) {
158
+ const resource = database.resources[name];
159
+ if (resource && resource.findOrphanedPartitions) {
160
+ const orphaned = resource.findOrphanedPartitions();
161
+ if (Object.keys(orphaned).length > 0) {
162
+ orphanedByResource[name] = orphaned;
163
+ }
164
+ }
165
+ }
166
+
167
+ return {
168
+ success: true,
169
+ orphanedPartitions: orphanedByResource,
170
+ affectedResources: Object.keys(orphanedByResource),
171
+ count: Object.keys(orphanedByResource).length,
172
+ hasIssues: Object.keys(orphanedByResource).length > 0
173
+ };
174
+ },
175
+
176
+ async dbRemoveOrphanedPartitions(args, database) {
177
+ server.ensureConnected(database);
178
+ const { resourceName, dryRun = true } = args;
179
+ const resource = server.getResource(database, resourceName);
180
+
181
+ if (!resource.removeOrphanedPartitions) {
182
+ throw new Error(`Resource '${resourceName}' does not support removeOrphanedPartitions method`);
183
+ }
184
+
185
+ // Find orphaned partitions first
186
+ const orphaned = resource.findOrphanedPartitions();
187
+
188
+ if (Object.keys(orphaned).length === 0) {
189
+ return {
190
+ success: true,
191
+ message: 'No orphaned partitions found',
192
+ resource: resourceName,
193
+ dryRun
194
+ };
195
+ }
196
+
197
+ if (dryRun) {
198
+ return {
199
+ success: true,
200
+ message: 'Dry run - no changes made',
201
+ resource: resourceName,
202
+ orphanedPartitions: orphaned,
203
+ wouldRemove: Object.keys(orphaned),
204
+ dryRun: true
205
+ };
206
+ }
207
+
208
+ // Actually remove
209
+ const removed = resource.removeOrphanedPartitions();
210
+
211
+ // Save metadata
212
+ await database.uploadMetadataFile();
213
+
214
+ return {
215
+ success: true,
216
+ message: `Removed ${Object.keys(removed).length} orphaned partition(s)`,
217
+ resource: resourceName,
218
+ removedPartitions: removed,
219
+ dryRun: false
220
+ };
221
+ }
222
+ };
223
+ }
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Query & Filtering Tools
3
+ * Provides advanced query and search capabilities
4
+ */
5
+
6
+ export const queryTools = [
7
+ {
8
+ name: 'resourceQuery',
9
+ description: 'Query documents with complex filters and conditions',
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 (e.g., {status: "active", age: {$gt: 18}})'
20
+ },
21
+ limit: {
22
+ type: 'number',
23
+ description: 'Maximum number of results',
24
+ default: 100
25
+ },
26
+ offset: {
27
+ type: 'number',
28
+ description: 'Number of results to skip',
29
+ default: 0
30
+ }
31
+ },
32
+ required: ['resourceName', 'filters']
33
+ }
34
+ },
35
+ {
36
+ name: 'resourceSearch',
37
+ description: 'Search for documents by text in specific fields',
38
+ inputSchema: {
39
+ type: 'object',
40
+ properties: {
41
+ resourceName: {
42
+ type: 'string',
43
+ description: 'Name of the resource'
44
+ },
45
+ searchText: {
46
+ type: 'string',
47
+ description: 'Text to search for'
48
+ },
49
+ fields: {
50
+ type: 'array',
51
+ items: { type: 'string' },
52
+ description: 'Fields to search in (if not specified, searches all string fields)'
53
+ },
54
+ caseSensitive: {
55
+ type: 'boolean',
56
+ description: 'Case-sensitive search',
57
+ default: false
58
+ },
59
+ limit: {
60
+ type: 'number',
61
+ description: 'Maximum number of results',
62
+ default: 100
63
+ }
64
+ },
65
+ required: ['resourceName', 'searchText']
66
+ }
67
+ }
68
+ ];
69
+
70
+ export function createQueryHandlers(server) {
71
+ return {
72
+ async resourceQuery(args, database) {
73
+ server.ensureConnected(database);
74
+ const { resourceName, filters, limit = 100, offset = 0 } = args;
75
+ const resource = server.getResource(database, resourceName);
76
+
77
+ try {
78
+ // Use the query method from resource
79
+ const results = await resource.query(filters, { limit, offset });
80
+
81
+ return {
82
+ success: true,
83
+ data: results,
84
+ count: results.length,
85
+ filters,
86
+ pagination: {
87
+ limit,
88
+ offset,
89
+ hasMore: results.length === limit
90
+ }
91
+ };
92
+ } catch (error) {
93
+ return {
94
+ success: false,
95
+ error: error.message,
96
+ filters
97
+ };
98
+ }
99
+ },
100
+
101
+ async resourceSearch(args, database) {
102
+ server.ensureConnected(database);
103
+ const { resourceName, searchText, fields, caseSensitive = false, limit = 100 } = args;
104
+ const resource = server.getResource(database, resourceName);
105
+
106
+ try {
107
+ // Get all documents and filter in memory
108
+ const allDocs = await resource.list({ limit: limit * 2 }); // Fetch more to ensure we have enough after filtering
109
+
110
+ const searchString = caseSensitive ? searchText : searchText.toLowerCase();
111
+
112
+ // Determine fields to search
113
+ let searchFields = fields;
114
+ if (!searchFields || searchFields.length === 0) {
115
+ // Auto-detect string fields
116
+ searchFields = Object.keys(resource.attributes || {}).filter(key => {
117
+ const attr = resource.attributes[key];
118
+ const type = typeof attr === 'string' ? attr.split('|')[0] : attr.type;
119
+ return type === 'string';
120
+ });
121
+ }
122
+
123
+ // Filter documents
124
+ const results = allDocs.filter(doc => {
125
+ return searchFields.some(field => {
126
+ const value = doc[field];
127
+ if (!value) return false;
128
+ const valueString = caseSensitive ? String(value) : String(value).toLowerCase();
129
+ return valueString.includes(searchString);
130
+ });
131
+ }).slice(0, limit);
132
+
133
+ return {
134
+ success: true,
135
+ data: results,
136
+ count: results.length,
137
+ searchText,
138
+ searchFields,
139
+ caseSensitive
140
+ };
141
+ } catch (error) {
142
+ return {
143
+ success: false,
144
+ error: error.message,
145
+ searchText
146
+ };
147
+ }
148
+ }
149
+ };
150
+ }
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Resource Management Tools
3
+ * Handles resource creation and listing
4
+ */
5
+
6
+ export const resourceManagementTools = [
7
+ {
8
+ name: 'dbCreateResource',
9
+ description: 'Create a new resource (collection/table) in the database',
10
+ inputSchema: {
11
+ type: 'object',
12
+ properties: {
13
+ name: {
14
+ type: 'string',
15
+ description: 'Resource name'
16
+ },
17
+ attributes: {
18
+ type: 'object',
19
+ description: 'Schema attributes definition (e.g., {"name": "string|required", "age": "number"})'
20
+ },
21
+ behavior: {
22
+ type: 'string',
23
+ description: 'Resource behavior',
24
+ enum: ['user-managed', 'body-only', 'body-overflow', 'enforce-limits', 'truncate-data'],
25
+ default: 'user-managed'
26
+ },
27
+ timestamps: {
28
+ type: 'boolean',
29
+ description: 'Enable automatic timestamps',
30
+ default: false
31
+ },
32
+ partitions: {
33
+ type: 'object',
34
+ description: 'Partition configuration'
35
+ },
36
+ paranoid: {
37
+ type: 'boolean',
38
+ description: 'Enable paranoid mode (soft deletes)',
39
+ default: true
40
+ }
41
+ },
42
+ required: ['name', 'attributes']
43
+ }
44
+ },
45
+ {
46
+ name: 'dbListResources',
47
+ description: 'List all resources in the database',
48
+ inputSchema: {
49
+ type: 'object',
50
+ properties: {},
51
+ required: []
52
+ }
53
+ }
54
+ ];
55
+
56
+ export function createResourceManagementHandlers(server) {
57
+ return {
58
+ async dbCreateResource(args, database) {
59
+ server.ensureConnected(database);
60
+
61
+ const { name, attributes, behavior = 'user-managed', timestamps = false, partitions, paranoid = true } = args;
62
+
63
+ const resource = await database.createResource({
64
+ name,
65
+ attributes,
66
+ behavior,
67
+ timestamps,
68
+ partitions,
69
+ paranoid
70
+ });
71
+
72
+ return {
73
+ success: true,
74
+ resource: {
75
+ name: resource.name,
76
+ behavior: resource.behavior,
77
+ attributes: resource.attributes,
78
+ partitions: resource.config.partitions,
79
+ timestamps: resource.config.timestamps
80
+ }
81
+ };
82
+ },
83
+
84
+ async dbListResources(args, database) {
85
+ server.ensureConnected(database);
86
+
87
+ const resourceList = await database.listResources();
88
+
89
+ return {
90
+ success: true,
91
+ resources: resourceList,
92
+ count: resourceList.length
93
+ };
94
+ }
95
+ };
96
+ }