s3db.js 13.6.0 → 14.0.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.
Files changed (193) hide show
  1. package/README.md +139 -43
  2. package/dist/s3db.cjs +72425 -38970
  3. package/dist/s3db.cjs.map +1 -1
  4. package/dist/s3db.es.js +72177 -38764
  5. package/dist/s3db.es.js.map +1 -1
  6. package/mcp/lib/base-handler.js +157 -0
  7. package/mcp/lib/handlers/connection-handler.js +280 -0
  8. package/mcp/lib/handlers/query-handler.js +533 -0
  9. package/mcp/lib/handlers/resource-handler.js +428 -0
  10. package/mcp/lib/tool-registry.js +336 -0
  11. package/mcp/lib/tools/connection-tools.js +161 -0
  12. package/mcp/lib/tools/query-tools.js +267 -0
  13. package/mcp/lib/tools/resource-tools.js +404 -0
  14. package/package.json +94 -49
  15. package/src/clients/memory-client.class.js +346 -191
  16. package/src/clients/memory-storage.class.js +300 -84
  17. package/src/clients/s3-client.class.js +7 -6
  18. package/src/concerns/geo-encoding.js +19 -2
  19. package/src/concerns/ip.js +59 -9
  20. package/src/concerns/money.js +8 -1
  21. package/src/concerns/password-hashing.js +49 -8
  22. package/src/concerns/plugin-storage.js +186 -18
  23. package/src/concerns/storage-drivers/filesystem-driver.js +284 -0
  24. package/src/database.class.js +139 -29
  25. package/src/errors.js +332 -42
  26. package/src/plugins/api/auth/oidc-auth.js +66 -17
  27. package/src/plugins/api/auth/strategies/base-strategy.class.js +74 -0
  28. package/src/plugins/api/auth/strategies/factory.class.js +63 -0
  29. package/src/plugins/api/auth/strategies/global-strategy.class.js +44 -0
  30. package/src/plugins/api/auth/strategies/path-based-strategy.class.js +83 -0
  31. package/src/plugins/api/auth/strategies/path-rules-strategy.class.js +118 -0
  32. package/src/plugins/api/concerns/failban-manager.js +106 -57
  33. package/src/plugins/api/concerns/opengraph-helper.js +116 -0
  34. package/src/plugins/api/concerns/route-context.js +601 -0
  35. package/src/plugins/api/concerns/state-machine.js +288 -0
  36. package/src/plugins/api/index.js +180 -41
  37. package/src/plugins/api/routes/auth-routes.js +198 -30
  38. package/src/plugins/api/routes/resource-routes.js +19 -4
  39. package/src/plugins/api/server/health-manager.class.js +163 -0
  40. package/src/plugins/api/server/middleware-chain.class.js +310 -0
  41. package/src/plugins/api/server/router.class.js +472 -0
  42. package/src/plugins/api/server.js +280 -1303
  43. package/src/plugins/api/utils/custom-routes.js +17 -5
  44. package/src/plugins/api/utils/guards.js +76 -17
  45. package/src/plugins/api/utils/openapi-generator-cached.class.js +133 -0
  46. package/src/plugins/api/utils/openapi-generator.js +7 -6
  47. package/src/plugins/api/utils/template-engine.js +77 -3
  48. package/src/plugins/audit.plugin.js +30 -8
  49. package/src/plugins/backup.plugin.js +110 -14
  50. package/src/plugins/cache/cache.class.js +22 -5
  51. package/src/plugins/cache/filesystem-cache.class.js +116 -19
  52. package/src/plugins/cache/memory-cache.class.js +211 -57
  53. package/src/plugins/cache/multi-tier-cache.class.js +371 -0
  54. package/src/plugins/cache/partition-aware-filesystem-cache.class.js +168 -47
  55. package/src/plugins/cache/redis-cache.class.js +552 -0
  56. package/src/plugins/cache/s3-cache.class.js +17 -8
  57. package/src/plugins/cache.plugin.js +176 -61
  58. package/src/plugins/cloud-inventory/drivers/alibaba-driver.js +8 -1
  59. package/src/plugins/cloud-inventory/drivers/aws-driver.js +60 -29
  60. package/src/plugins/cloud-inventory/drivers/azure-driver.js +8 -1
  61. package/src/plugins/cloud-inventory/drivers/base-driver.js +16 -2
  62. package/src/plugins/cloud-inventory/drivers/cloudflare-driver.js +8 -1
  63. package/src/plugins/cloud-inventory/drivers/digitalocean-driver.js +8 -1
  64. package/src/plugins/cloud-inventory/drivers/hetzner-driver.js +8 -1
  65. package/src/plugins/cloud-inventory/drivers/linode-driver.js +8 -1
  66. package/src/plugins/cloud-inventory/drivers/mongodb-atlas-driver.js +8 -1
  67. package/src/plugins/cloud-inventory/drivers/vultr-driver.js +8 -1
  68. package/src/plugins/cloud-inventory/index.js +29 -8
  69. package/src/plugins/cloud-inventory/registry.js +64 -42
  70. package/src/plugins/cloud-inventory.plugin.js +240 -138
  71. package/src/plugins/concerns/plugin-dependencies.js +54 -0
  72. package/src/plugins/concerns/resource-names.js +100 -0
  73. package/src/plugins/consumers/index.js +10 -2
  74. package/src/plugins/consumers/sqs-consumer.js +12 -2
  75. package/src/plugins/cookie-farm-suite.plugin.js +278 -0
  76. package/src/plugins/cookie-farm.errors.js +73 -0
  77. package/src/plugins/cookie-farm.plugin.js +869 -0
  78. package/src/plugins/costs.plugin.js +7 -1
  79. package/src/plugins/eventual-consistency/analytics.js +94 -19
  80. package/src/plugins/eventual-consistency/config.js +15 -7
  81. package/src/plugins/eventual-consistency/consolidation.js +29 -11
  82. package/src/plugins/eventual-consistency/garbage-collection.js +3 -1
  83. package/src/plugins/eventual-consistency/helpers.js +39 -14
  84. package/src/plugins/eventual-consistency/install.js +21 -2
  85. package/src/plugins/eventual-consistency/utils.js +32 -10
  86. package/src/plugins/fulltext.plugin.js +38 -11
  87. package/src/plugins/geo.plugin.js +61 -9
  88. package/src/plugins/identity/concerns/config.js +61 -0
  89. package/src/plugins/identity/concerns/mfa-manager.js +15 -2
  90. package/src/plugins/identity/concerns/rate-limit.js +124 -0
  91. package/src/plugins/identity/concerns/resource-schemas.js +9 -1
  92. package/src/plugins/identity/concerns/token-generator.js +29 -4
  93. package/src/plugins/identity/drivers/auth-driver.interface.js +76 -0
  94. package/src/plugins/identity/drivers/client-credentials-driver.js +127 -0
  95. package/src/plugins/identity/drivers/index.js +18 -0
  96. package/src/plugins/identity/drivers/password-driver.js +122 -0
  97. package/src/plugins/identity/email-service.js +17 -2
  98. package/src/plugins/identity/index.js +413 -69
  99. package/src/plugins/identity/oauth2-server.js +413 -30
  100. package/src/plugins/identity/oidc-discovery.js +16 -8
  101. package/src/plugins/identity/rsa-keys.js +115 -35
  102. package/src/plugins/identity/server.js +166 -45
  103. package/src/plugins/identity/session-manager.js +53 -7
  104. package/src/plugins/identity/ui/pages/mfa-verification.js +17 -15
  105. package/src/plugins/identity/ui/routes.js +363 -255
  106. package/src/plugins/importer/index.js +153 -20
  107. package/src/plugins/index.js +9 -2
  108. package/src/plugins/kubernetes-inventory/index.js +6 -0
  109. package/src/plugins/kubernetes-inventory/k8s-driver.js +867 -0
  110. package/src/plugins/kubernetes-inventory/resource-types.js +274 -0
  111. package/src/plugins/kubernetes-inventory.plugin.js +980 -0
  112. package/src/plugins/metrics.plugin.js +64 -16
  113. package/src/plugins/ml/base-model.class.js +25 -15
  114. package/src/plugins/ml/regression-model.class.js +1 -1
  115. package/src/plugins/ml.errors.js +57 -25
  116. package/src/plugins/ml.plugin.js +28 -4
  117. package/src/plugins/namespace.js +210 -0
  118. package/src/plugins/plugin.class.js +180 -8
  119. package/src/plugins/puppeteer/console-monitor.js +729 -0
  120. package/src/plugins/puppeteer/cookie-manager.js +492 -0
  121. package/src/plugins/puppeteer/network-monitor.js +816 -0
  122. package/src/plugins/puppeteer/performance-manager.js +746 -0
  123. package/src/plugins/puppeteer/proxy-manager.js +478 -0
  124. package/src/plugins/puppeteer/stealth-manager.js +556 -0
  125. package/src/plugins/puppeteer.errors.js +81 -0
  126. package/src/plugins/puppeteer.plugin.js +1327 -0
  127. package/src/plugins/queue-consumer.plugin.js +69 -14
  128. package/src/plugins/recon/behaviors/uptime-behavior.js +691 -0
  129. package/src/plugins/recon/concerns/command-runner.js +148 -0
  130. package/src/plugins/recon/concerns/diff-detector.js +372 -0
  131. package/src/plugins/recon/concerns/fingerprint-builder.js +307 -0
  132. package/src/plugins/recon/concerns/process-manager.js +338 -0
  133. package/src/plugins/recon/concerns/report-generator.js +478 -0
  134. package/src/plugins/recon/concerns/security-analyzer.js +571 -0
  135. package/src/plugins/recon/concerns/target-normalizer.js +68 -0
  136. package/src/plugins/recon/config/defaults.js +321 -0
  137. package/src/plugins/recon/config/resources.js +370 -0
  138. package/src/plugins/recon/index.js +778 -0
  139. package/src/plugins/recon/managers/dependency-manager.js +174 -0
  140. package/src/plugins/recon/managers/scheduler-manager.js +179 -0
  141. package/src/plugins/recon/managers/storage-manager.js +745 -0
  142. package/src/plugins/recon/managers/target-manager.js +274 -0
  143. package/src/plugins/recon/stages/asn-stage.js +314 -0
  144. package/src/plugins/recon/stages/certificate-stage.js +84 -0
  145. package/src/plugins/recon/stages/dns-stage.js +107 -0
  146. package/src/plugins/recon/stages/dnsdumpster-stage.js +362 -0
  147. package/src/plugins/recon/stages/fingerprint-stage.js +71 -0
  148. package/src/plugins/recon/stages/google-dorks-stage.js +440 -0
  149. package/src/plugins/recon/stages/http-stage.js +89 -0
  150. package/src/plugins/recon/stages/latency-stage.js +148 -0
  151. package/src/plugins/recon/stages/massdns-stage.js +302 -0
  152. package/src/plugins/recon/stages/osint-stage.js +1373 -0
  153. package/src/plugins/recon/stages/ports-stage.js +169 -0
  154. package/src/plugins/recon/stages/screenshot-stage.js +94 -0
  155. package/src/plugins/recon/stages/secrets-stage.js +514 -0
  156. package/src/plugins/recon/stages/subdomains-stage.js +295 -0
  157. package/src/plugins/recon/stages/tls-audit-stage.js +78 -0
  158. package/src/plugins/recon/stages/vulnerability-stage.js +78 -0
  159. package/src/plugins/recon/stages/web-discovery-stage.js +113 -0
  160. package/src/plugins/recon/stages/whois-stage.js +349 -0
  161. package/src/plugins/recon.plugin.js +75 -0
  162. package/src/plugins/recon.plugin.js.backup +2635 -0
  163. package/src/plugins/relation.errors.js +87 -14
  164. package/src/plugins/replicator.plugin.js +514 -137
  165. package/src/plugins/replicators/base-replicator.class.js +89 -1
  166. package/src/plugins/replicators/bigquery-replicator.class.js +66 -22
  167. package/src/plugins/replicators/dynamodb-replicator.class.js +22 -15
  168. package/src/plugins/replicators/mongodb-replicator.class.js +22 -15
  169. package/src/plugins/replicators/mysql-replicator.class.js +52 -17
  170. package/src/plugins/replicators/planetscale-replicator.class.js +30 -4
  171. package/src/plugins/replicators/postgres-replicator.class.js +62 -27
  172. package/src/plugins/replicators/s3db-replicator.class.js +25 -18
  173. package/src/plugins/replicators/schema-sync.helper.js +3 -3
  174. package/src/plugins/replicators/sqs-replicator.class.js +8 -2
  175. package/src/plugins/replicators/turso-replicator.class.js +23 -3
  176. package/src/plugins/replicators/webhook-replicator.class.js +42 -4
  177. package/src/plugins/s3-queue.plugin.js +464 -65
  178. package/src/plugins/scheduler.plugin.js +20 -6
  179. package/src/plugins/state-machine.plugin.js +40 -9
  180. package/src/plugins/tfstate/README.md +126 -126
  181. package/src/plugins/tfstate/base-driver.js +28 -4
  182. package/src/plugins/tfstate/errors.js +65 -10
  183. package/src/plugins/tfstate/filesystem-driver.js +52 -8
  184. package/src/plugins/tfstate/index.js +163 -90
  185. package/src/plugins/tfstate/s3-driver.js +64 -6
  186. package/src/plugins/ttl.plugin.js +72 -17
  187. package/src/plugins/vector/distances.js +18 -12
  188. package/src/plugins/vector/kmeans.js +26 -4
  189. package/src/resource.class.js +115 -19
  190. package/src/testing/factory.class.js +20 -3
  191. package/src/testing/seeder.class.js +7 -1
  192. package/src/clients/memory-client.md +0 -917
  193. package/src/plugins/cloud-inventory/drivers/mock-drivers.js +0 -449
@@ -0,0 +1,533 @@
1
+ import { BaseHandler } from '../base-handler.js';
2
+
3
+ /**
4
+ * Handler for advanced query operations
5
+ */
6
+ export class QueryHandler extends BaseHandler {
7
+ constructor(database) {
8
+ super(database);
9
+ this.queries = new Map();
10
+ }
11
+
12
+ /**
13
+ * Create a new query builder
14
+ */
15
+ async create(args) {
16
+ this.ensureConnected();
17
+ this.validateParams(args, ['resourceName']);
18
+
19
+ const { resourceName, queryId } = args;
20
+ const id = queryId || this.generateQueryId();
21
+
22
+ const query = {
23
+ id,
24
+ resourceName,
25
+ filters: [],
26
+ sort: [],
27
+ projection: null,
28
+ aggregation: [],
29
+ limit: 100,
30
+ offset: 0,
31
+ createdAt: Date.now()
32
+ };
33
+
34
+ this.queries.set(id, query);
35
+
36
+ return this.formatResponse({
37
+ queryId: id,
38
+ resourceName
39
+ }, {
40
+ message: `Query builder created for resource: ${resourceName}`
41
+ });
42
+ }
43
+
44
+ /**
45
+ * Add filter to query
46
+ */
47
+ async filter(args) {
48
+ this.validateParams(args, ['queryId', 'field', 'operator', 'value']);
49
+
50
+ const { queryId, field, operator, value, combineWith = 'AND' } = args;
51
+ const query = this.getQuery(queryId);
52
+
53
+ const validOperators = [
54
+ 'eq', 'ne', 'gt', 'gte', 'lt', 'lte',
55
+ 'in', 'nin', 'contains', 'startsWith',
56
+ 'endsWith', 'regex', 'exists', 'type'
57
+ ];
58
+
59
+ if (!validOperators.includes(operator)) {
60
+ throw new Error(`Invalid operator: ${operator}. Valid: ${validOperators.join(', ')}`);
61
+ }
62
+
63
+ query.filters.push({
64
+ field,
65
+ operator,
66
+ value,
67
+ combineWith
68
+ });
69
+
70
+ return this.formatResponse({
71
+ queryId,
72
+ filters: query.filters
73
+ });
74
+ }
75
+
76
+ /**
77
+ * Add sorting to query
78
+ */
79
+ async sort(args) {
80
+ this.validateParams(args, ['queryId', 'field']);
81
+
82
+ const { queryId, field, direction = 'asc' } = args;
83
+ const query = this.getQuery(queryId);
84
+
85
+ if (!['asc', 'desc'].includes(direction)) {
86
+ throw new Error(`Invalid sort direction: ${direction}. Use 'asc' or 'desc'`);
87
+ }
88
+
89
+ query.sort.push({ field, direction });
90
+
91
+ return this.formatResponse({
92
+ queryId,
93
+ sort: query.sort
94
+ });
95
+ }
96
+
97
+ /**
98
+ * Set field projection
99
+ */
100
+ async project(args) {
101
+ this.validateParams(args, ['queryId', 'fields']);
102
+
103
+ const { queryId, fields, exclude = false } = args;
104
+ const query = this.getQuery(queryId);
105
+
106
+ query.projection = {
107
+ fields: Array.isArray(fields) ? fields : [fields],
108
+ exclude
109
+ };
110
+
111
+ return this.formatResponse({
112
+ queryId,
113
+ projection: query.projection
114
+ });
115
+ }
116
+
117
+ /**
118
+ * Execute query
119
+ */
120
+ async execute(args) {
121
+ this.ensureConnected();
122
+ this.validateParams(args, ['queryId']);
123
+
124
+ const { queryId, limit, offset, explain = false } = args;
125
+ const query = this.getQuery(queryId);
126
+ const resource = this.getResource(query.resourceName);
127
+
128
+ // Update pagination
129
+ if (limit !== undefined) query.limit = limit;
130
+ if (offset !== undefined) query.offset = offset;
131
+
132
+ // Build execution plan
133
+ const plan = this.buildExecutionPlan(query);
134
+
135
+ if (explain) {
136
+ return this.formatResponse({
137
+ queryId,
138
+ plan,
139
+ estimatedCost: this.estimateQueryCost(plan, resource)
140
+ });
141
+ }
142
+
143
+ // Execute query
144
+ const startTime = Date.now();
145
+ const results = await this.executeQuery(resource, plan);
146
+ const executionTime = Date.now() - startTime;
147
+
148
+ return this.formatResponse({
149
+ documents: results,
150
+ count: results.length
151
+ }, {
152
+ queryId,
153
+ executionTime,
154
+ pagination: {
155
+ limit: query.limit,
156
+ offset: query.offset,
157
+ hasMore: results.length === query.limit
158
+ }
159
+ });
160
+ }
161
+
162
+ /**
163
+ * Perform aggregation
164
+ */
165
+ async aggregate(args) {
166
+ this.ensureConnected();
167
+ this.validateParams(args, ['resourceName', 'pipeline']);
168
+
169
+ const { resourceName, pipeline } = args;
170
+ const resource = this.getResource(resourceName);
171
+
172
+ const results = await this.executeAggregation(resource, pipeline);
173
+
174
+ return this.formatResponse({
175
+ results,
176
+ pipeline
177
+ });
178
+ }
179
+
180
+ /**
181
+ * Build complex query from natural language
182
+ */
183
+ async buildFromText(args) {
184
+ this.validateParams(args, ['resourceName', 'query']);
185
+
186
+ const { resourceName, query } = args;
187
+ const queryId = this.generateQueryId();
188
+
189
+ // Parse natural language query
190
+ const parsed = this.parseNaturalQuery(query);
191
+
192
+ // Create query
193
+ await this.create({ resourceName, queryId });
194
+
195
+ // Apply parsed conditions
196
+ for (const filter of parsed.filters) {
197
+ await this.filter({ queryId, ...filter });
198
+ }
199
+
200
+ if (parsed.sort) {
201
+ await this.sort({ queryId, ...parsed.sort });
202
+ }
203
+
204
+ if (parsed.projection) {
205
+ await this.project({ queryId, ...parsed.projection });
206
+ }
207
+
208
+ return this.formatResponse({
209
+ queryId,
210
+ parsed,
211
+ query: this.getQuery(queryId)
212
+ }, {
213
+ message: 'Query built from text successfully'
214
+ });
215
+ }
216
+
217
+ // Private helper methods
218
+
219
+ private getQuery(queryId) {
220
+ const query = this.queries.get(queryId);
221
+ if (!query) {
222
+ throw new Error(`Query ${queryId} not found`);
223
+ }
224
+ return query;
225
+ }
226
+
227
+ private generateQueryId() {
228
+ return `query_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
229
+ }
230
+
231
+ private buildExecutionPlan(query) {
232
+ return {
233
+ resource: query.resourceName,
234
+ filters: query.filters,
235
+ sort: query.sort,
236
+ projection: query.projection,
237
+ aggregation: query.aggregation,
238
+ limit: query.limit,
239
+ offset: query.offset,
240
+ indexes: this.identifyUsableIndexes(query)
241
+ };
242
+ }
243
+
244
+ private async executeQuery(resource, plan) {
245
+ // Get all documents (in production, this would be optimized)
246
+ let results = await resource.list({ limit: 10000 });
247
+
248
+ // Apply filters
249
+ results = this.applyFilters(results, plan.filters);
250
+
251
+ // Apply sorting
252
+ if (plan.sort && plan.sort.length > 0) {
253
+ results = this.applySorting(results, plan.sort);
254
+ }
255
+
256
+ // Apply projection
257
+ if (plan.projection) {
258
+ results = this.applyProjection(results, plan.projection);
259
+ }
260
+
261
+ // Apply pagination
262
+ results = results.slice(plan.offset, plan.offset + plan.limit);
263
+
264
+ return results;
265
+ }
266
+
267
+ private applyFilters(data, filters) {
268
+ if (!filters || filters.length === 0) return data;
269
+
270
+ return data.filter(item => {
271
+ let result = true;
272
+ let previousCombine = 'AND';
273
+
274
+ for (const filter of filters) {
275
+ const matches = this.evaluateFilter(item, filter);
276
+
277
+ if (previousCombine === 'AND') {
278
+ result = result && matches;
279
+ } else {
280
+ result = result || matches;
281
+ }
282
+
283
+ previousCombine = filter.combineWith || 'AND';
284
+ }
285
+
286
+ return result;
287
+ });
288
+ }
289
+
290
+ private evaluateFilter(item, filter) {
291
+ const value = this.getNestedValue(item, filter.field);
292
+
293
+ switch (filter.operator) {
294
+ case 'eq': return value === filter.value;
295
+ case 'ne': return value !== filter.value;
296
+ case 'gt': return value > filter.value;
297
+ case 'gte': return value >= filter.value;
298
+ case 'lt': return value < filter.value;
299
+ case 'lte': return value <= filter.value;
300
+ case 'in': return Array.isArray(filter.value) && filter.value.includes(value);
301
+ case 'nin': return Array.isArray(filter.value) && !filter.value.includes(value);
302
+ case 'contains': return String(value).includes(filter.value);
303
+ case 'startsWith': return String(value).startsWith(filter.value);
304
+ case 'endsWith': return String(value).endsWith(filter.value);
305
+ case 'regex': return new RegExp(filter.value).test(String(value));
306
+ case 'exists': return filter.value ? value !== undefined : value === undefined;
307
+ case 'type': return typeof value === filter.value;
308
+ default: return true;
309
+ }
310
+ }
311
+
312
+ private getNestedValue(obj, path) {
313
+ return path.split('.').reduce((current, key) => current?.[key], obj);
314
+ }
315
+
316
+ private applySorting(data, sortRules) {
317
+ return [...data].sort((a, b) => {
318
+ for (const rule of sortRules) {
319
+ const aVal = this.getNestedValue(a, rule.field);
320
+ const bVal = this.getNestedValue(b, rule.field);
321
+
322
+ if (aVal === bVal) continue;
323
+
324
+ const comparison = aVal < bVal ? -1 : 1;
325
+ return rule.direction === 'asc' ? comparison : -comparison;
326
+ }
327
+ return 0;
328
+ });
329
+ }
330
+
331
+ private applyProjection(data, projection) {
332
+ return data.map(item => {
333
+ if (projection.exclude) {
334
+ const result = { ...item };
335
+ projection.fields.forEach(field => {
336
+ this.deleteNestedValue(result, field);
337
+ });
338
+ return result;
339
+ } else {
340
+ const result = { id: item.id }; // Always include ID
341
+ projection.fields.forEach(field => {
342
+ const value = this.getNestedValue(item, field);
343
+ if (value !== undefined) {
344
+ this.setNestedValue(result, field, value);
345
+ }
346
+ });
347
+ return result;
348
+ }
349
+ });
350
+ }
351
+
352
+ private setNestedValue(obj, path, value) {
353
+ const keys = path.split('.');
354
+ const lastKey = keys.pop();
355
+ const target = keys.reduce((current, key) => {
356
+ if (!current[key]) current[key] = {};
357
+ return current[key];
358
+ }, obj);
359
+ target[lastKey] = value;
360
+ }
361
+
362
+ private deleteNestedValue(obj, path) {
363
+ const keys = path.split('.');
364
+ const lastKey = keys.pop();
365
+ const target = keys.reduce((current, key) => current?.[key], obj);
366
+ if (target) delete target[lastKey];
367
+ }
368
+
369
+ private async executeAggregation(resource, pipeline) {
370
+ const data = await resource.list({ limit: 10000 });
371
+ let results = data;
372
+
373
+ for (const stage of pipeline) {
374
+ results = await this.executeAggregationStage(results, stage);
375
+ }
376
+
377
+ return results;
378
+ }
379
+
380
+ private async executeAggregationStage(data, stage) {
381
+ switch (stage.stage) {
382
+ case 'match':
383
+ return this.applyFilters(data, stage.params.filters);
384
+
385
+ case 'group':
386
+ return this.groupData(data, stage.params);
387
+
388
+ case 'sort':
389
+ return this.applySorting(data, stage.params.rules);
390
+
391
+ case 'limit':
392
+ return data.slice(0, stage.params.count);
393
+
394
+ case 'count':
395
+ return [{ count: data.length }];
396
+
397
+ case 'sum':
398
+ return [{ sum: data.reduce((acc, item) => acc + (item[stage.params.field] || 0), 0) }];
399
+
400
+ case 'avg':
401
+ const sum = data.reduce((acc, item) => acc + (item[stage.params.field] || 0), 0);
402
+ return [{ avg: sum / data.length }];
403
+
404
+ case 'min':
405
+ const min = Math.min(...data.map(item => item[stage.params.field] || Infinity));
406
+ return [{ min }];
407
+
408
+ case 'max':
409
+ const max = Math.max(...data.map(item => item[stage.params.field] || -Infinity));
410
+ return [{ max }];
411
+
412
+ default:
413
+ return data;
414
+ }
415
+ }
416
+
417
+ private groupData(data, params) {
418
+ const groups = new Map();
419
+
420
+ for (const item of data) {
421
+ const key = this.getNestedValue(item, params.by);
422
+ if (!groups.has(key)) {
423
+ groups.set(key, []);
424
+ }
425
+ groups.get(key).push(item);
426
+ }
427
+
428
+ const results = [];
429
+ for (const [key, items] of groups) {
430
+ const group = { _id: key, count: items.length };
431
+
432
+ // Apply aggregation functions
433
+ if (params.aggregations) {
434
+ for (const agg of params.aggregations) {
435
+ if (agg.type === 'sum') {
436
+ group[agg.name] = items.reduce((acc, item) => acc + (item[agg.field] || 0), 0);
437
+ } else if (agg.type === 'avg') {
438
+ const sum = items.reduce((acc, item) => acc + (item[agg.field] || 0), 0);
439
+ group[agg.name] = sum / items.length;
440
+ }
441
+ }
442
+ }
443
+
444
+ results.push(group);
445
+ }
446
+
447
+ return results;
448
+ }
449
+
450
+ private estimateQueryCost(plan, resource) {
451
+ let cost = 0;
452
+
453
+ // Base cost for scanning
454
+ cost += 0.0004; // LIST operation
455
+
456
+ // Additional costs for complexity
457
+ if (plan.filters.length > 0) cost += 0.0001 * plan.filters.length;
458
+ if (plan.sort.length > 0) cost += 0.0002;
459
+ if (plan.aggregation.length > 0) cost += 0.0003 * plan.aggregation.length;
460
+
461
+ return {
462
+ estimatedCostUSD: cost,
463
+ operations: {
464
+ list: 1,
465
+ filters: plan.filters.length,
466
+ sort: plan.sort.length > 0 ? 1 : 0,
467
+ aggregation: plan.aggregation.length
468
+ }
469
+ };
470
+ }
471
+
472
+ private identifyUsableIndexes(query) {
473
+ // In a real implementation, this would check for actual indexes
474
+ const indexes = [];
475
+
476
+ for (const filter of query.filters) {
477
+ if (filter.operator === 'eq') {
478
+ indexes.push({
479
+ field: filter.field,
480
+ type: 'equality'
481
+ });
482
+ }
483
+ }
484
+
485
+ return indexes;
486
+ }
487
+
488
+ private parseNaturalQuery(text) {
489
+ const parsed = {
490
+ filters: [],
491
+ sort: null,
492
+ projection: null
493
+ };
494
+
495
+ // Simple pattern matching (in production, use NLP)
496
+ const patterns = {
497
+ equals: /(\w+)\s+(?:is|equals?|=)\s+['"]?([^'"]+)['"]?/gi,
498
+ greater: /(\w+)\s+(?:greater than|>)\s+(\d+)/gi,
499
+ less: /(\w+)\s+(?:less than|<)\s+(\d+)/gi,
500
+ contains: /(\w+)\s+contains\s+['"]?([^'"]+)['"]?/gi,
501
+ sort: /sort(?:ed)?\s+by\s+(\w+)\s*(asc|desc)?/i,
502
+ fields: /(?:select|show|return)\s+([\w,\s]+)/i
503
+ };
504
+
505
+ // Extract filters
506
+ let match;
507
+ while ((match = patterns.equals.exec(text))) {
508
+ parsed.filters.push({
509
+ field: match[1],
510
+ operator: 'eq',
511
+ value: match[2]
512
+ });
513
+ }
514
+
515
+ // Extract sorting
516
+ if ((match = patterns.sort.exec(text))) {
517
+ parsed.sort = {
518
+ field: match[1],
519
+ direction: match[2] || 'asc'
520
+ };
521
+ }
522
+
523
+ // Extract projection
524
+ if ((match = patterns.fields.exec(text))) {
525
+ parsed.projection = {
526
+ fields: match[1].split(',').map(f => f.trim()),
527
+ exclude: false
528
+ };
529
+ }
530
+
531
+ return parsed;
532
+ }
533
+ }