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.
- package/README.md +139 -43
- package/dist/s3db.cjs +72425 -38970
- package/dist/s3db.cjs.map +1 -1
- package/dist/s3db.es.js +72177 -38764
- package/dist/s3db.es.js.map +1 -1
- package/mcp/lib/base-handler.js +157 -0
- package/mcp/lib/handlers/connection-handler.js +280 -0
- package/mcp/lib/handlers/query-handler.js +533 -0
- package/mcp/lib/handlers/resource-handler.js +428 -0
- package/mcp/lib/tool-registry.js +336 -0
- package/mcp/lib/tools/connection-tools.js +161 -0
- package/mcp/lib/tools/query-tools.js +267 -0
- package/mcp/lib/tools/resource-tools.js +404 -0
- package/package.json +94 -49
- package/src/clients/memory-client.class.js +346 -191
- package/src/clients/memory-storage.class.js +300 -84
- package/src/clients/s3-client.class.js +7 -6
- package/src/concerns/geo-encoding.js +19 -2
- package/src/concerns/ip.js +59 -9
- package/src/concerns/money.js +8 -1
- package/src/concerns/password-hashing.js +49 -8
- package/src/concerns/plugin-storage.js +186 -18
- package/src/concerns/storage-drivers/filesystem-driver.js +284 -0
- package/src/database.class.js +139 -29
- package/src/errors.js +332 -42
- package/src/plugins/api/auth/oidc-auth.js +66 -17
- package/src/plugins/api/auth/strategies/base-strategy.class.js +74 -0
- package/src/plugins/api/auth/strategies/factory.class.js +63 -0
- package/src/plugins/api/auth/strategies/global-strategy.class.js +44 -0
- package/src/plugins/api/auth/strategies/path-based-strategy.class.js +83 -0
- package/src/plugins/api/auth/strategies/path-rules-strategy.class.js +118 -0
- package/src/plugins/api/concerns/failban-manager.js +106 -57
- package/src/plugins/api/concerns/opengraph-helper.js +116 -0
- package/src/plugins/api/concerns/route-context.js +601 -0
- package/src/plugins/api/concerns/state-machine.js +288 -0
- package/src/plugins/api/index.js +180 -41
- package/src/plugins/api/routes/auth-routes.js +198 -30
- package/src/plugins/api/routes/resource-routes.js +19 -4
- package/src/plugins/api/server/health-manager.class.js +163 -0
- package/src/plugins/api/server/middleware-chain.class.js +310 -0
- package/src/plugins/api/server/router.class.js +472 -0
- package/src/plugins/api/server.js +280 -1303
- package/src/plugins/api/utils/custom-routes.js +17 -5
- package/src/plugins/api/utils/guards.js +76 -17
- package/src/plugins/api/utils/openapi-generator-cached.class.js +133 -0
- package/src/plugins/api/utils/openapi-generator.js +7 -6
- package/src/plugins/api/utils/template-engine.js +77 -3
- package/src/plugins/audit.plugin.js +30 -8
- package/src/plugins/backup.plugin.js +110 -14
- package/src/plugins/cache/cache.class.js +22 -5
- package/src/plugins/cache/filesystem-cache.class.js +116 -19
- package/src/plugins/cache/memory-cache.class.js +211 -57
- package/src/plugins/cache/multi-tier-cache.class.js +371 -0
- package/src/plugins/cache/partition-aware-filesystem-cache.class.js +168 -47
- package/src/plugins/cache/redis-cache.class.js +552 -0
- package/src/plugins/cache/s3-cache.class.js +17 -8
- package/src/plugins/cache.plugin.js +176 -61
- package/src/plugins/cloud-inventory/drivers/alibaba-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/aws-driver.js +60 -29
- package/src/plugins/cloud-inventory/drivers/azure-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/base-driver.js +16 -2
- package/src/plugins/cloud-inventory/drivers/cloudflare-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/digitalocean-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/hetzner-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/linode-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/mongodb-atlas-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/vultr-driver.js +8 -1
- package/src/plugins/cloud-inventory/index.js +29 -8
- package/src/plugins/cloud-inventory/registry.js +64 -42
- package/src/plugins/cloud-inventory.plugin.js +240 -138
- package/src/plugins/concerns/plugin-dependencies.js +54 -0
- package/src/plugins/concerns/resource-names.js +100 -0
- package/src/plugins/consumers/index.js +10 -2
- package/src/plugins/consumers/sqs-consumer.js +12 -2
- package/src/plugins/cookie-farm-suite.plugin.js +278 -0
- package/src/plugins/cookie-farm.errors.js +73 -0
- package/src/plugins/cookie-farm.plugin.js +869 -0
- package/src/plugins/costs.plugin.js +7 -1
- package/src/plugins/eventual-consistency/analytics.js +94 -19
- package/src/plugins/eventual-consistency/config.js +15 -7
- package/src/plugins/eventual-consistency/consolidation.js +29 -11
- package/src/plugins/eventual-consistency/garbage-collection.js +3 -1
- package/src/plugins/eventual-consistency/helpers.js +39 -14
- package/src/plugins/eventual-consistency/install.js +21 -2
- package/src/plugins/eventual-consistency/utils.js +32 -10
- package/src/plugins/fulltext.plugin.js +38 -11
- package/src/plugins/geo.plugin.js +61 -9
- package/src/plugins/identity/concerns/config.js +61 -0
- package/src/plugins/identity/concerns/mfa-manager.js +15 -2
- package/src/plugins/identity/concerns/rate-limit.js +124 -0
- package/src/plugins/identity/concerns/resource-schemas.js +9 -1
- package/src/plugins/identity/concerns/token-generator.js +29 -4
- package/src/plugins/identity/drivers/auth-driver.interface.js +76 -0
- package/src/plugins/identity/drivers/client-credentials-driver.js +127 -0
- package/src/plugins/identity/drivers/index.js +18 -0
- package/src/plugins/identity/drivers/password-driver.js +122 -0
- package/src/plugins/identity/email-service.js +17 -2
- package/src/plugins/identity/index.js +413 -69
- package/src/plugins/identity/oauth2-server.js +413 -30
- package/src/plugins/identity/oidc-discovery.js +16 -8
- package/src/plugins/identity/rsa-keys.js +115 -35
- package/src/plugins/identity/server.js +166 -45
- package/src/plugins/identity/session-manager.js +53 -7
- package/src/plugins/identity/ui/pages/mfa-verification.js +17 -15
- package/src/plugins/identity/ui/routes.js +363 -255
- package/src/plugins/importer/index.js +153 -20
- package/src/plugins/index.js +9 -2
- package/src/plugins/kubernetes-inventory/index.js +6 -0
- package/src/plugins/kubernetes-inventory/k8s-driver.js +867 -0
- package/src/plugins/kubernetes-inventory/resource-types.js +274 -0
- package/src/plugins/kubernetes-inventory.plugin.js +980 -0
- package/src/plugins/metrics.plugin.js +64 -16
- package/src/plugins/ml/base-model.class.js +25 -15
- package/src/plugins/ml/regression-model.class.js +1 -1
- package/src/plugins/ml.errors.js +57 -25
- package/src/plugins/ml.plugin.js +28 -4
- package/src/plugins/namespace.js +210 -0
- package/src/plugins/plugin.class.js +180 -8
- package/src/plugins/puppeteer/console-monitor.js +729 -0
- package/src/plugins/puppeteer/cookie-manager.js +492 -0
- package/src/plugins/puppeteer/network-monitor.js +816 -0
- package/src/plugins/puppeteer/performance-manager.js +746 -0
- package/src/plugins/puppeteer/proxy-manager.js +478 -0
- package/src/plugins/puppeteer/stealth-manager.js +556 -0
- package/src/plugins/puppeteer.errors.js +81 -0
- package/src/plugins/puppeteer.plugin.js +1327 -0
- package/src/plugins/queue-consumer.plugin.js +69 -14
- package/src/plugins/recon/behaviors/uptime-behavior.js +691 -0
- package/src/plugins/recon/concerns/command-runner.js +148 -0
- package/src/plugins/recon/concerns/diff-detector.js +372 -0
- package/src/plugins/recon/concerns/fingerprint-builder.js +307 -0
- package/src/plugins/recon/concerns/process-manager.js +338 -0
- package/src/plugins/recon/concerns/report-generator.js +478 -0
- package/src/plugins/recon/concerns/security-analyzer.js +571 -0
- package/src/plugins/recon/concerns/target-normalizer.js +68 -0
- package/src/plugins/recon/config/defaults.js +321 -0
- package/src/plugins/recon/config/resources.js +370 -0
- package/src/plugins/recon/index.js +778 -0
- package/src/plugins/recon/managers/dependency-manager.js +174 -0
- package/src/plugins/recon/managers/scheduler-manager.js +179 -0
- package/src/plugins/recon/managers/storage-manager.js +745 -0
- package/src/plugins/recon/managers/target-manager.js +274 -0
- package/src/plugins/recon/stages/asn-stage.js +314 -0
- package/src/plugins/recon/stages/certificate-stage.js +84 -0
- package/src/plugins/recon/stages/dns-stage.js +107 -0
- package/src/plugins/recon/stages/dnsdumpster-stage.js +362 -0
- package/src/plugins/recon/stages/fingerprint-stage.js +71 -0
- package/src/plugins/recon/stages/google-dorks-stage.js +440 -0
- package/src/plugins/recon/stages/http-stage.js +89 -0
- package/src/plugins/recon/stages/latency-stage.js +148 -0
- package/src/plugins/recon/stages/massdns-stage.js +302 -0
- package/src/plugins/recon/stages/osint-stage.js +1373 -0
- package/src/plugins/recon/stages/ports-stage.js +169 -0
- package/src/plugins/recon/stages/screenshot-stage.js +94 -0
- package/src/plugins/recon/stages/secrets-stage.js +514 -0
- package/src/plugins/recon/stages/subdomains-stage.js +295 -0
- package/src/plugins/recon/stages/tls-audit-stage.js +78 -0
- package/src/plugins/recon/stages/vulnerability-stage.js +78 -0
- package/src/plugins/recon/stages/web-discovery-stage.js +113 -0
- package/src/plugins/recon/stages/whois-stage.js +349 -0
- package/src/plugins/recon.plugin.js +75 -0
- package/src/plugins/recon.plugin.js.backup +2635 -0
- package/src/plugins/relation.errors.js +87 -14
- package/src/plugins/replicator.plugin.js +514 -137
- package/src/plugins/replicators/base-replicator.class.js +89 -1
- package/src/plugins/replicators/bigquery-replicator.class.js +66 -22
- package/src/plugins/replicators/dynamodb-replicator.class.js +22 -15
- package/src/plugins/replicators/mongodb-replicator.class.js +22 -15
- package/src/plugins/replicators/mysql-replicator.class.js +52 -17
- package/src/plugins/replicators/planetscale-replicator.class.js +30 -4
- package/src/plugins/replicators/postgres-replicator.class.js +62 -27
- package/src/plugins/replicators/s3db-replicator.class.js +25 -18
- package/src/plugins/replicators/schema-sync.helper.js +3 -3
- package/src/plugins/replicators/sqs-replicator.class.js +8 -2
- package/src/plugins/replicators/turso-replicator.class.js +23 -3
- package/src/plugins/replicators/webhook-replicator.class.js +42 -4
- package/src/plugins/s3-queue.plugin.js +464 -65
- package/src/plugins/scheduler.plugin.js +20 -6
- package/src/plugins/state-machine.plugin.js +40 -9
- package/src/plugins/tfstate/README.md +126 -126
- package/src/plugins/tfstate/base-driver.js +28 -4
- package/src/plugins/tfstate/errors.js +65 -10
- package/src/plugins/tfstate/filesystem-driver.js +52 -8
- package/src/plugins/tfstate/index.js +163 -90
- package/src/plugins/tfstate/s3-driver.js +64 -6
- package/src/plugins/ttl.plugin.js +72 -17
- package/src/plugins/vector/distances.js +18 -12
- package/src/plugins/vector/kmeans.js +26 -4
- package/src/resource.class.js +115 -19
- package/src/testing/factory.class.js +20 -3
- package/src/testing/seeder.class.js +7 -1
- package/src/clients/memory-client.md +0 -917
- 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
|
+
}
|