@ductape/sdk 0.0.4-v42 → 0.0.4-v43
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/dist/apps/services/app.service.d.ts +10 -0
- package/dist/apps/services/app.service.js +22 -0
- package/dist/apps/services/app.service.js.map +1 -1
- package/dist/database/adapters/base.adapter.d.ts +176 -0
- package/dist/database/adapters/base.adapter.js +31 -0
- package/dist/database/adapters/base.adapter.js.map +1 -0
- package/dist/database/adapters/dynamodb.adapter.d.ts +83 -0
- package/dist/database/adapters/dynamodb.adapter.js +1237 -0
- package/dist/database/adapters/dynamodb.adapter.js.map +1 -0
- package/dist/database/adapters/mongodb.adapter.d.ts +70 -0
- package/dist/database/adapters/mongodb.adapter.js +717 -0
- package/dist/database/adapters/mongodb.adapter.js.map +1 -0
- package/dist/database/adapters/mysql.adapter.d.ts +141 -0
- package/dist/database/adapters/mysql.adapter.js +1221 -0
- package/dist/database/adapters/mysql.adapter.js.map +1 -0
- package/dist/database/adapters/postgresql.adapter.d.ts +142 -0
- package/dist/database/adapters/postgresql.adapter.js +1288 -0
- package/dist/database/adapters/postgresql.adapter.js.map +1 -0
- package/dist/database/database.service.d.ts +190 -0
- package/dist/database/database.service.js +552 -0
- package/dist/database/database.service.js.map +1 -0
- package/dist/database/index.d.ts +18 -0
- package/dist/database/index.js +98 -0
- package/dist/database/index.js.map +1 -0
- package/dist/database/types/aggregation.types.d.ts +202 -0
- package/dist/database/types/aggregation.types.js +21 -0
- package/dist/database/types/aggregation.types.js.map +1 -0
- package/dist/database/types/connection.types.d.ts +132 -0
- package/dist/database/types/connection.types.js +6 -0
- package/dist/database/types/connection.types.js.map +1 -0
- package/dist/database/types/database.types.d.ts +173 -0
- package/dist/database/types/database.types.js +73 -0
- package/dist/database/types/database.types.js.map +1 -0
- package/dist/database/types/index.d.ts +12 -0
- package/dist/database/types/index.js +37 -0
- package/dist/database/types/index.js.map +1 -0
- package/dist/database/types/index.types.d.ts +220 -0
- package/dist/database/types/index.types.js +27 -0
- package/dist/database/types/index.types.js.map +1 -0
- package/dist/database/types/migration.types.d.ts +205 -0
- package/dist/database/types/migration.types.js +44 -0
- package/dist/database/types/migration.types.js.map +1 -0
- package/dist/database/types/query.types.d.ts +274 -0
- package/dist/database/types/query.types.js +57 -0
- package/dist/database/types/query.types.js.map +1 -0
- package/dist/database/types/result.types.d.ts +218 -0
- package/dist/database/types/result.types.js +6 -0
- package/dist/database/types/result.types.js.map +1 -0
- package/dist/database/types/schema.types.d.ts +190 -0
- package/dist/database/types/schema.types.js +69 -0
- package/dist/database/types/schema.types.js.map +1 -0
- package/dist/database/utils/helpers.d.ts +66 -0
- package/dist/database/utils/helpers.js +501 -0
- package/dist/database/utils/helpers.js.map +1 -0
- package/dist/database/utils/migration.utils.d.ts +151 -0
- package/dist/database/utils/migration.utils.js +476 -0
- package/dist/database/utils/migration.utils.js.map +1 -0
- package/dist/database/utils/transaction.d.ts +64 -0
- package/dist/database/utils/transaction.js +130 -0
- package/dist/database/utils/transaction.js.map +1 -0
- package/dist/database/validators/connection.validator.d.ts +20 -0
- package/dist/database/validators/connection.validator.js +267 -0
- package/dist/database/validators/connection.validator.js.map +1 -0
- package/dist/database/validators/query.validator.d.ts +31 -0
- package/dist/database/validators/query.validator.js +305 -0
- package/dist/database/validators/query.validator.js.map +1 -0
- package/dist/database/validators/schema.validator.d.ts +31 -0
- package/dist/database/validators/schema.validator.js +334 -0
- package/dist/database/validators/schema.validator.js.map +1 -0
- package/dist/index.d.ts +25 -4
- package/dist/index.js +36 -4
- package/dist/index.js.map +1 -1
- package/dist/processor/services/processor.service.js +10 -8
- package/dist/processor/services/processor.service.js.map +1 -1
- package/dist/types/processor.types.d.ts +2 -2
- package/package.json +3 -1
|
@@ -0,0 +1,1237 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* DynamoDB Database Adapter
|
|
4
|
+
* Implements database operations for Amazon DynamoDB
|
|
5
|
+
*/
|
|
6
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
13
|
+
}) : (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
o[k2] = m[k];
|
|
16
|
+
}));
|
|
17
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
18
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
19
|
+
}) : function(o, v) {
|
|
20
|
+
o["default"] = v;
|
|
21
|
+
});
|
|
22
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
23
|
+
var ownKeys = function(o) {
|
|
24
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
25
|
+
var ar = [];
|
|
26
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
27
|
+
return ar;
|
|
28
|
+
};
|
|
29
|
+
return ownKeys(o);
|
|
30
|
+
};
|
|
31
|
+
return function (mod) {
|
|
32
|
+
if (mod && mod.__esModule) return mod;
|
|
33
|
+
var result = {};
|
|
34
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
35
|
+
__setModuleDefault(result, mod);
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
})();
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
exports.DynamoDBAdapter = void 0;
|
|
41
|
+
const base_adapter_1 = require("./base.adapter");
|
|
42
|
+
const database_types_1 = require("../types/database.types");
|
|
43
|
+
const schema_types_1 = require("../types/schema.types");
|
|
44
|
+
const migration_types_1 = require("../types/migration.types");
|
|
45
|
+
/**
|
|
46
|
+
* DynamoDB Connection wrapper
|
|
47
|
+
*/
|
|
48
|
+
class DynamoDBConnection {
|
|
49
|
+
constructor(id, client, docClient) {
|
|
50
|
+
this.id = id;
|
|
51
|
+
this.type = database_types_1.DatabaseType.DYNAMODB;
|
|
52
|
+
this.status = database_types_1.ConnectionStatus.CONNECTED;
|
|
53
|
+
this.client = client;
|
|
54
|
+
this.docClient = docClient;
|
|
55
|
+
}
|
|
56
|
+
async connect() {
|
|
57
|
+
this.status = database_types_1.ConnectionStatus.CONNECTED;
|
|
58
|
+
}
|
|
59
|
+
async disconnect() {
|
|
60
|
+
this.client.destroy();
|
|
61
|
+
this.status = database_types_1.ConnectionStatus.DISCONNECTED;
|
|
62
|
+
}
|
|
63
|
+
isConnected() {
|
|
64
|
+
return this.status === database_types_1.ConnectionStatus.CONNECTED;
|
|
65
|
+
}
|
|
66
|
+
getClient() {
|
|
67
|
+
return this.docClient;
|
|
68
|
+
}
|
|
69
|
+
getRawClient() {
|
|
70
|
+
return this.client;
|
|
71
|
+
}
|
|
72
|
+
getType() {
|
|
73
|
+
return this.type;
|
|
74
|
+
}
|
|
75
|
+
getConfig() {
|
|
76
|
+
throw new Error('Config not stored in connection');
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* DynamoDB Database Adapter
|
|
81
|
+
*/
|
|
82
|
+
class DynamoDBAdapter extends base_adapter_1.BaseDatabaseAdapter {
|
|
83
|
+
constructor() {
|
|
84
|
+
super(...arguments);
|
|
85
|
+
this.type = database_types_1.DatabaseType.DYNAMODB;
|
|
86
|
+
}
|
|
87
|
+
// ==================== Connection Methods ====================
|
|
88
|
+
async connect(config) {
|
|
89
|
+
var _a, _b, _c, _d;
|
|
90
|
+
try {
|
|
91
|
+
// Dynamic import of AWS SDK v3
|
|
92
|
+
const { DynamoDBClient } = await Promise.resolve().then(() => __importStar(require('@aws-sdk/client-dynamodb')));
|
|
93
|
+
const { DynamoDBDocumentClient } = await Promise.resolve().then(() => __importStar(require('@aws-sdk/lib-dynamodb')));
|
|
94
|
+
const clientConfig = {
|
|
95
|
+
region: ((_a = config.options) === null || _a === void 0 ? void 0 : _a.region) || 'us-east-1',
|
|
96
|
+
};
|
|
97
|
+
// Add credentials if provided
|
|
98
|
+
if (((_b = config.options) === null || _b === void 0 ? void 0 : _b.accessKeyId) && ((_c = config.options) === null || _c === void 0 ? void 0 : _c.secretAccessKey)) {
|
|
99
|
+
clientConfig.credentials = {
|
|
100
|
+
accessKeyId: config.options.accessKeyId,
|
|
101
|
+
secretAccessKey: config.options.secretAccessKey,
|
|
102
|
+
sessionToken: config.options.sessionToken,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
// Add endpoint for local development
|
|
106
|
+
if ((_d = config.options) === null || _d === void 0 ? void 0 : _d.endpoint) {
|
|
107
|
+
clientConfig.endpoint = config.options.endpoint;
|
|
108
|
+
}
|
|
109
|
+
const client = new DynamoDBClient(clientConfig);
|
|
110
|
+
const docClient = DynamoDBDocumentClient.from(client);
|
|
111
|
+
return new DynamoDBConnection(`dynamodb-${Date.now()}`, client, docClient);
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
throw new database_types_1.DatabaseError(database_types_1.DatabaseErrorType.CONNECTION_ERROR, `Failed to connect to DynamoDB: ${error.message}`, error);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
async disconnect(connection) {
|
|
118
|
+
await connection.disconnect();
|
|
119
|
+
}
|
|
120
|
+
async testConnection(connection) {
|
|
121
|
+
const startTime = Date.now();
|
|
122
|
+
try {
|
|
123
|
+
const { ListTablesCommand } = await Promise.resolve().then(() => __importStar(require('@aws-sdk/client-dynamodb')));
|
|
124
|
+
const client = connection.getRawClient();
|
|
125
|
+
await client.send(new ListTablesCommand({ Limit: 1 }));
|
|
126
|
+
return {
|
|
127
|
+
connected: true,
|
|
128
|
+
message: 'DynamoDB connection successful',
|
|
129
|
+
databaseType: 'DynamoDB',
|
|
130
|
+
responseTime: Date.now() - startTime,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
return {
|
|
135
|
+
connected: false,
|
|
136
|
+
message: `DynamoDB connection failed: ${error.message}`,
|
|
137
|
+
databaseType: 'DynamoDB',
|
|
138
|
+
responseTime: Date.now() - startTime,
|
|
139
|
+
error: error.message,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
// ==================== Transaction Methods ====================
|
|
144
|
+
async beginTransaction(connection, options) {
|
|
145
|
+
// DynamoDB transactions are handled differently - store items for batch execution
|
|
146
|
+
return {
|
|
147
|
+
items: [],
|
|
148
|
+
connection,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
async commitTransaction(connection, transaction) {
|
|
152
|
+
// Execute transactional write
|
|
153
|
+
if (transaction.items.length === 0) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
const { TransactWriteCommand } = await Promise.resolve().then(() => __importStar(require('@aws-sdk/lib-dynamodb')));
|
|
157
|
+
const docClient = connection.getClient();
|
|
158
|
+
await docClient.send(new TransactWriteCommand({
|
|
159
|
+
TransactItems: transaction.items,
|
|
160
|
+
}));
|
|
161
|
+
}
|
|
162
|
+
async rollbackTransaction(connection, transaction) {
|
|
163
|
+
// DynamoDB transactions are atomic - no explicit rollback needed
|
|
164
|
+
// Just clear the items
|
|
165
|
+
transaction.items = [];
|
|
166
|
+
}
|
|
167
|
+
async createSavepoint(connection, transaction, savepointName) {
|
|
168
|
+
// DynamoDB doesn't support savepoints
|
|
169
|
+
throw new database_types_1.DatabaseError(database_types_1.DatabaseErrorType.QUERY_ERROR, 'DynamoDB does not support savepoints');
|
|
170
|
+
}
|
|
171
|
+
async rollbackToSavepoint(connection, transaction, savepointName) {
|
|
172
|
+
throw new database_types_1.DatabaseError(database_types_1.DatabaseErrorType.QUERY_ERROR, 'DynamoDB does not support savepoints');
|
|
173
|
+
}
|
|
174
|
+
async releaseSavepoint(connection, transaction, savepointName) {
|
|
175
|
+
throw new database_types_1.DatabaseError(database_types_1.DatabaseErrorType.QUERY_ERROR, 'DynamoDB does not support savepoints');
|
|
176
|
+
}
|
|
177
|
+
// ==================== Query Methods ====================
|
|
178
|
+
async query(connection, options) {
|
|
179
|
+
const startTime = Date.now();
|
|
180
|
+
try {
|
|
181
|
+
const { ScanCommand, QueryCommand } = await Promise.resolve().then(() => __importStar(require('@aws-sdk/lib-dynamodb')));
|
|
182
|
+
const docClient = connection.getClient();
|
|
183
|
+
let result;
|
|
184
|
+
// If there's a partition key in where clause, use Query, otherwise use Scan
|
|
185
|
+
const hasPartitionKey = options.where && this.hasPartitionKeyInWhere(options.where);
|
|
186
|
+
if (hasPartitionKey) {
|
|
187
|
+
const params = this.buildQueryParams(options);
|
|
188
|
+
result = await docClient.send(new QueryCommand(params));
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
const params = this.buildScanParams(options);
|
|
192
|
+
result = await docClient.send(new ScanCommand(params));
|
|
193
|
+
}
|
|
194
|
+
const executionTime = Date.now() - startTime;
|
|
195
|
+
return {
|
|
196
|
+
data: (result.Items || []),
|
|
197
|
+
count: result.Count || 0,
|
|
198
|
+
executionTime,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
catch (error) {
|
|
202
|
+
throw new database_types_1.DatabaseError(database_types_1.DatabaseErrorType.QUERY_ERROR, `DynamoDB query failed: ${error.message}`, error);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
async insert(connection, options) {
|
|
206
|
+
const startTime = Date.now();
|
|
207
|
+
try {
|
|
208
|
+
const { PutCommand, BatchWriteCommand } = await Promise.resolve().then(() => __importStar(require('@aws-sdk/lib-dynamodb')));
|
|
209
|
+
const docClient = connection.getClient();
|
|
210
|
+
const dataArray = Array.isArray(options.data) ? options.data : [options.data];
|
|
211
|
+
if (dataArray.length === 1) {
|
|
212
|
+
// Single item insert
|
|
213
|
+
await docClient.send(new PutCommand({
|
|
214
|
+
TableName: options.table,
|
|
215
|
+
Item: dataArray[0],
|
|
216
|
+
}));
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
// Batch insert (max 25 items per batch)
|
|
220
|
+
const batches = this.chunkArray(dataArray, 25);
|
|
221
|
+
for (const batch of batches) {
|
|
222
|
+
await docClient.send(new BatchWriteCommand({
|
|
223
|
+
RequestItems: {
|
|
224
|
+
[options.table]: batch.map((item) => ({
|
|
225
|
+
PutRequest: { Item: item },
|
|
226
|
+
})),
|
|
227
|
+
},
|
|
228
|
+
}));
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
const executionTime = Date.now() - startTime;
|
|
232
|
+
return {
|
|
233
|
+
insertedCount: dataArray.length,
|
|
234
|
+
insertedIds: [], // DynamoDB doesn't auto-generate IDs
|
|
235
|
+
data: undefined,
|
|
236
|
+
executionTime,
|
|
237
|
+
success: true,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
catch (error) {
|
|
241
|
+
throw new database_types_1.DatabaseError(database_types_1.DatabaseErrorType.QUERY_ERROR, `DynamoDB insert failed: ${error.message}`, error);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
async update(connection, options) {
|
|
245
|
+
const startTime = Date.now();
|
|
246
|
+
try {
|
|
247
|
+
const { UpdateCommand } = await Promise.resolve().then(() => __importStar(require('@aws-sdk/lib-dynamodb')));
|
|
248
|
+
const docClient = connection.getClient();
|
|
249
|
+
// DynamoDB requires a key to update - extract from where clause
|
|
250
|
+
const key = this.extractKeyFromWhere(options.where);
|
|
251
|
+
// Build update expression
|
|
252
|
+
const { updateExpression, expressionAttributeNames, expressionAttributeValues } = this.buildUpdateExpression(options.data);
|
|
253
|
+
await docClient.send(new UpdateCommand({
|
|
254
|
+
TableName: options.table,
|
|
255
|
+
Key: key,
|
|
256
|
+
UpdateExpression: updateExpression,
|
|
257
|
+
ExpressionAttributeNames: expressionAttributeNames,
|
|
258
|
+
ExpressionAttributeValues: expressionAttributeValues,
|
|
259
|
+
}));
|
|
260
|
+
const executionTime = Date.now() - startTime;
|
|
261
|
+
return {
|
|
262
|
+
updatedCount: 1, // DynamoDB updates one item at a time
|
|
263
|
+
matchedCount: 1,
|
|
264
|
+
data: undefined,
|
|
265
|
+
executionTime,
|
|
266
|
+
success: true,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
catch (error) {
|
|
270
|
+
throw new database_types_1.DatabaseError(database_types_1.DatabaseErrorType.QUERY_ERROR, `DynamoDB update failed: ${error.message}`, error);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
async delete(connection, options) {
|
|
274
|
+
const startTime = Date.now();
|
|
275
|
+
try {
|
|
276
|
+
const { DeleteCommand } = await Promise.resolve().then(() => __importStar(require('@aws-sdk/lib-dynamodb')));
|
|
277
|
+
const docClient = connection.getClient();
|
|
278
|
+
// DynamoDB requires a key to delete - extract from where clause
|
|
279
|
+
const key = this.extractKeyFromWhere(options.where);
|
|
280
|
+
await docClient.send(new DeleteCommand({
|
|
281
|
+
TableName: options.table,
|
|
282
|
+
Key: key,
|
|
283
|
+
}));
|
|
284
|
+
const executionTime = Date.now() - startTime;
|
|
285
|
+
return {
|
|
286
|
+
deletedCount: 1,
|
|
287
|
+
data: undefined,
|
|
288
|
+
executionTime,
|
|
289
|
+
success: true,
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
catch (error) {
|
|
293
|
+
throw new database_types_1.DatabaseError(database_types_1.DatabaseErrorType.QUERY_ERROR, `DynamoDB delete failed: ${error.message}`, error);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
async upsert(connection, options) {
|
|
297
|
+
const startTime = Date.now();
|
|
298
|
+
try {
|
|
299
|
+
const { PutCommand } = await Promise.resolve().then(() => __importStar(require('@aws-sdk/lib-dynamodb')));
|
|
300
|
+
const docClient = connection.getClient();
|
|
301
|
+
const data = Array.isArray(options.data) ? options.data[0] : options.data;
|
|
302
|
+
// PutItem automatically does upsert in DynamoDB
|
|
303
|
+
await docClient.send(new PutCommand({
|
|
304
|
+
TableName: options.table,
|
|
305
|
+
Item: data,
|
|
306
|
+
}));
|
|
307
|
+
const executionTime = Date.now() - startTime;
|
|
308
|
+
return {
|
|
309
|
+
insertedCount: 0, // Can't distinguish in DynamoDB
|
|
310
|
+
updatedCount: 0,
|
|
311
|
+
affectedCount: 1,
|
|
312
|
+
data: undefined,
|
|
313
|
+
executionTime,
|
|
314
|
+
success: true,
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
catch (error) {
|
|
318
|
+
throw new database_types_1.DatabaseError(database_types_1.DatabaseErrorType.QUERY_ERROR, `DynamoDB upsert failed: ${error.message}`, error);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
async executeRaw(connection, options) {
|
|
322
|
+
var _a;
|
|
323
|
+
const startTime = Date.now();
|
|
324
|
+
try {
|
|
325
|
+
const { ExecuteStatementCommand } = await Promise.resolve().then(() => __importStar(require('@aws-sdk/client-dynamodb')));
|
|
326
|
+
const client = connection.getRawClient();
|
|
327
|
+
// DynamoDB PartiQL support
|
|
328
|
+
const result = await client.send(new ExecuteStatementCommand({
|
|
329
|
+
Statement: options.query,
|
|
330
|
+
Parameters: options.params,
|
|
331
|
+
}));
|
|
332
|
+
const executionTime = Date.now() - startTime;
|
|
333
|
+
return {
|
|
334
|
+
rows: (result.Items || []),
|
|
335
|
+
rowCount: ((_a = result.Items) === null || _a === void 0 ? void 0 : _a.length) || 0,
|
|
336
|
+
executionTime,
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
catch (error) {
|
|
340
|
+
throw new database_types_1.DatabaseError(database_types_1.DatabaseErrorType.QUERY_ERROR, `DynamoDB raw query failed: ${error.message}`, error);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
// ==================== Aggregation Methods ====================
|
|
344
|
+
async count(connection, options) {
|
|
345
|
+
try {
|
|
346
|
+
const { ScanCommand } = await Promise.resolve().then(() => __importStar(require('@aws-sdk/lib-dynamodb')));
|
|
347
|
+
const docClient = connection.getClient();
|
|
348
|
+
const params = {
|
|
349
|
+
TableName: options.table,
|
|
350
|
+
Select: 'COUNT',
|
|
351
|
+
};
|
|
352
|
+
if (options.where) {
|
|
353
|
+
const { filterExpression, expressionAttributeNames, expressionAttributeValues } = this.buildFilterExpression(options.where);
|
|
354
|
+
params.FilterExpression = filterExpression;
|
|
355
|
+
params.ExpressionAttributeNames = expressionAttributeNames;
|
|
356
|
+
params.ExpressionAttributeValues = expressionAttributeValues;
|
|
357
|
+
}
|
|
358
|
+
const result = await docClient.send(new ScanCommand(params));
|
|
359
|
+
return result.Count || 0;
|
|
360
|
+
}
|
|
361
|
+
catch (error) {
|
|
362
|
+
throw new database_types_1.DatabaseError(database_types_1.DatabaseErrorType.QUERY_ERROR, `DynamoDB count failed: ${error.message}`, error);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
async sum(connection, options) {
|
|
366
|
+
// DynamoDB doesn't support aggregations natively - need to scan and calculate
|
|
367
|
+
try {
|
|
368
|
+
const result = await this.query(connection, {
|
|
369
|
+
table: options.table,
|
|
370
|
+
where: options.where,
|
|
371
|
+
env: options.env,
|
|
372
|
+
product: options.product,
|
|
373
|
+
database: options.database,
|
|
374
|
+
});
|
|
375
|
+
return result.data.reduce((sum, item) => sum + (item[options.column] || 0), 0);
|
|
376
|
+
}
|
|
377
|
+
catch (error) {
|
|
378
|
+
throw new database_types_1.DatabaseError(database_types_1.DatabaseErrorType.QUERY_ERROR, `DynamoDB sum failed: ${error.message}`, error);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
async avg(connection, options) {
|
|
382
|
+
try {
|
|
383
|
+
const result = await this.query(connection, {
|
|
384
|
+
table: options.table,
|
|
385
|
+
where: options.where,
|
|
386
|
+
env: options.env,
|
|
387
|
+
product: options.product,
|
|
388
|
+
database: options.database,
|
|
389
|
+
});
|
|
390
|
+
const sum = result.data.reduce((total, item) => total + (item[options.column] || 0), 0);
|
|
391
|
+
return result.data.length > 0 ? sum / result.data.length : 0;
|
|
392
|
+
}
|
|
393
|
+
catch (error) {
|
|
394
|
+
throw new database_types_1.DatabaseError(database_types_1.DatabaseErrorType.QUERY_ERROR, `DynamoDB avg failed: ${error.message}`, error);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
async min(connection, options) {
|
|
398
|
+
try {
|
|
399
|
+
const result = await this.query(connection, {
|
|
400
|
+
table: options.table,
|
|
401
|
+
where: options.where,
|
|
402
|
+
env: options.env,
|
|
403
|
+
product: options.product,
|
|
404
|
+
database: options.database,
|
|
405
|
+
});
|
|
406
|
+
if (result.data.length === 0)
|
|
407
|
+
return 0;
|
|
408
|
+
return result.data.reduce((min, item) => {
|
|
409
|
+
const value = item[options.column];
|
|
410
|
+
return value < min ? value : min;
|
|
411
|
+
}, result.data[0][options.column]);
|
|
412
|
+
}
|
|
413
|
+
catch (error) {
|
|
414
|
+
throw new database_types_1.DatabaseError(database_types_1.DatabaseErrorType.QUERY_ERROR, `DynamoDB min failed: ${error.message}`, error);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
async max(connection, options) {
|
|
418
|
+
try {
|
|
419
|
+
const result = await this.query(connection, {
|
|
420
|
+
table: options.table,
|
|
421
|
+
where: options.where,
|
|
422
|
+
env: options.env,
|
|
423
|
+
product: options.product,
|
|
424
|
+
database: options.database,
|
|
425
|
+
});
|
|
426
|
+
if (result.data.length === 0)
|
|
427
|
+
return 0;
|
|
428
|
+
return result.data.reduce((max, item) => {
|
|
429
|
+
const value = item[options.column];
|
|
430
|
+
return value > max ? value : max;
|
|
431
|
+
}, result.data[0][options.column]);
|
|
432
|
+
}
|
|
433
|
+
catch (error) {
|
|
434
|
+
throw new database_types_1.DatabaseError(database_types_1.DatabaseErrorType.QUERY_ERROR, `DynamoDB max failed: ${error.message}`, error);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
async groupBy(connection, options) {
|
|
438
|
+
try {
|
|
439
|
+
// DynamoDB doesn't support GROUP BY - need to scan and group manually
|
|
440
|
+
const result = await this.query(connection, {
|
|
441
|
+
table: options.table,
|
|
442
|
+
where: options.where,
|
|
443
|
+
env: options.env,
|
|
444
|
+
product: options.product,
|
|
445
|
+
database: options.database,
|
|
446
|
+
});
|
|
447
|
+
const groups = new Map();
|
|
448
|
+
result.data.forEach((item) => {
|
|
449
|
+
// Create group key
|
|
450
|
+
const groupKey = options.groupBy.map((col) => item[col]).join('|');
|
|
451
|
+
if (!groups.has(groupKey)) {
|
|
452
|
+
const groupData = {};
|
|
453
|
+
options.groupBy.forEach((col) => {
|
|
454
|
+
groupData[col] = item[col];
|
|
455
|
+
});
|
|
456
|
+
groups.set(groupKey, groupData);
|
|
457
|
+
}
|
|
458
|
+
// Accumulate aggregations
|
|
459
|
+
if (options.aggregate) {
|
|
460
|
+
const groupData = groups.get(groupKey);
|
|
461
|
+
Object.entries(options.aggregate).forEach(([alias, agg]) => {
|
|
462
|
+
const value = item[agg.column];
|
|
463
|
+
if (!groupData[alias]) {
|
|
464
|
+
groupData[alias] = agg.function === 'count' ? 0 : 0;
|
|
465
|
+
}
|
|
466
|
+
switch (agg.function.toLowerCase()) {
|
|
467
|
+
case 'count':
|
|
468
|
+
groupData[alias]++;
|
|
469
|
+
break;
|
|
470
|
+
case 'sum':
|
|
471
|
+
groupData[alias] += value || 0;
|
|
472
|
+
break;
|
|
473
|
+
case 'avg':
|
|
474
|
+
if (!groupData[`${alias}_sum`])
|
|
475
|
+
groupData[`${alias}_sum`] = 0;
|
|
476
|
+
if (!groupData[`${alias}_count`])
|
|
477
|
+
groupData[`${alias}_count`] = 0;
|
|
478
|
+
groupData[`${alias}_sum`] += value || 0;
|
|
479
|
+
groupData[`${alias}_count`]++;
|
|
480
|
+
groupData[alias] = groupData[`${alias}_sum`] / groupData[`${alias}_count`];
|
|
481
|
+
break;
|
|
482
|
+
}
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
});
|
|
486
|
+
return Array.from(groups.values());
|
|
487
|
+
}
|
|
488
|
+
catch (error) {
|
|
489
|
+
throw new database_types_1.DatabaseError(database_types_1.DatabaseErrorType.QUERY_ERROR, `DynamoDB groupBy failed: ${error.message}`, error);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
async aggregate(connection, options) {
|
|
493
|
+
try {
|
|
494
|
+
const result = await this.query(connection, {
|
|
495
|
+
table: options.table,
|
|
496
|
+
where: options.where,
|
|
497
|
+
env: options.env,
|
|
498
|
+
product: options.product,
|
|
499
|
+
database: options.database,
|
|
500
|
+
});
|
|
501
|
+
const aggregateResult = {};
|
|
502
|
+
Object.entries(options.operations).forEach(([alias, agg]) => {
|
|
503
|
+
const values = result.data.map((item) => item[agg.column]);
|
|
504
|
+
switch (agg.function.toLowerCase()) {
|
|
505
|
+
case 'count':
|
|
506
|
+
aggregateResult[alias] = result.data.length;
|
|
507
|
+
break;
|
|
508
|
+
case 'sum':
|
|
509
|
+
aggregateResult[alias] = values.reduce((sum, val) => sum + (val || 0), 0);
|
|
510
|
+
break;
|
|
511
|
+
case 'avg':
|
|
512
|
+
const sum = values.reduce((s, val) => s + (val || 0), 0);
|
|
513
|
+
aggregateResult[alias] = values.length > 0 ? sum / values.length : 0;
|
|
514
|
+
break;
|
|
515
|
+
case 'min':
|
|
516
|
+
aggregateResult[alias] = Math.min(...values.filter((v) => v !== undefined));
|
|
517
|
+
break;
|
|
518
|
+
case 'max':
|
|
519
|
+
aggregateResult[alias] = Math.max(...values.filter((v) => v !== undefined));
|
|
520
|
+
break;
|
|
521
|
+
}
|
|
522
|
+
});
|
|
523
|
+
return aggregateResult;
|
|
524
|
+
}
|
|
525
|
+
catch (error) {
|
|
526
|
+
throw new database_types_1.DatabaseError(database_types_1.DatabaseErrorType.QUERY_ERROR, `DynamoDB aggregate failed: ${error.message}`, error);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
// ==================== Schema Methods ====================
|
|
530
|
+
async createTable(connection, schema, options) {
|
|
531
|
+
const startTime = Date.now();
|
|
532
|
+
try {
|
|
533
|
+
const { CreateTableCommand } = await Promise.resolve().then(() => __importStar(require('@aws-sdk/client-dynamodb')));
|
|
534
|
+
const client = connection.getRawClient();
|
|
535
|
+
// Extract key schema from columns
|
|
536
|
+
const keySchema = [];
|
|
537
|
+
const attributeDefinitions = [];
|
|
538
|
+
schema.columns.forEach((col) => {
|
|
539
|
+
if (col.primaryKey) {
|
|
540
|
+
keySchema.push({
|
|
541
|
+
AttributeName: col.name,
|
|
542
|
+
KeyType: 'HASH', // Partition key
|
|
543
|
+
});
|
|
544
|
+
attributeDefinitions.push({
|
|
545
|
+
AttributeName: col.name,
|
|
546
|
+
AttributeType: this.mapColumnTypeToDynamoDB(col.type),
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
});
|
|
550
|
+
// If no sort key specified, DynamoDB only needs partition key
|
|
551
|
+
await client.send(new CreateTableCommand({
|
|
552
|
+
TableName: schema.name,
|
|
553
|
+
KeySchema: keySchema,
|
|
554
|
+
AttributeDefinitions: attributeDefinitions,
|
|
555
|
+
BillingMode: 'PAY_PER_REQUEST', // On-demand billing
|
|
556
|
+
}));
|
|
557
|
+
const executionTime = Date.now() - startTime;
|
|
558
|
+
return {
|
|
559
|
+
success: true,
|
|
560
|
+
operation: 'create',
|
|
561
|
+
table: schema.name,
|
|
562
|
+
executionTime,
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
catch (error) {
|
|
566
|
+
return {
|
|
567
|
+
success: false,
|
|
568
|
+
operation: 'create',
|
|
569
|
+
table: schema.name,
|
|
570
|
+
error: error.message,
|
|
571
|
+
executionTime: Date.now() - startTime,
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
async dropTable(connection, tableName) {
|
|
576
|
+
const startTime = Date.now();
|
|
577
|
+
try {
|
|
578
|
+
const { DeleteTableCommand } = await Promise.resolve().then(() => __importStar(require('@aws-sdk/client-dynamodb')));
|
|
579
|
+
const client = connection.getRawClient();
|
|
580
|
+
await client.send(new DeleteTableCommand({
|
|
581
|
+
TableName: tableName,
|
|
582
|
+
}));
|
|
583
|
+
return {
|
|
584
|
+
success: true,
|
|
585
|
+
operation: 'drop',
|
|
586
|
+
table: tableName,
|
|
587
|
+
executionTime: Date.now() - startTime,
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
catch (error) {
|
|
591
|
+
return {
|
|
592
|
+
success: false,
|
|
593
|
+
operation: 'drop',
|
|
594
|
+
table: tableName,
|
|
595
|
+
error: error.message,
|
|
596
|
+
executionTime: Date.now() - startTime,
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
async alterTable(connection, tableName, alterations, options) {
|
|
601
|
+
// DynamoDB doesn't support schema alterations in the traditional sense
|
|
602
|
+
// Can only modify throughput, streams, TTL, etc.
|
|
603
|
+
throw new database_types_1.DatabaseError(database_types_1.DatabaseErrorType.SCHEMA_ERROR, 'DynamoDB does not support traditional table alterations. You can only modify table settings.');
|
|
604
|
+
}
|
|
605
|
+
async getTableSchema(connection, tableName) {
|
|
606
|
+
try {
|
|
607
|
+
const { DescribeTableCommand } = await Promise.resolve().then(() => __importStar(require('@aws-sdk/client-dynamodb')));
|
|
608
|
+
const client = connection.getRawClient();
|
|
609
|
+
const result = await client.send(new DescribeTableCommand({
|
|
610
|
+
TableName: tableName,
|
|
611
|
+
}));
|
|
612
|
+
const table = result.Table;
|
|
613
|
+
const columns = table.KeySchema.map((key) => {
|
|
614
|
+
const attr = table.AttributeDefinitions.find((a) => a.AttributeName === key.AttributeName);
|
|
615
|
+
return {
|
|
616
|
+
name: key.AttributeName,
|
|
617
|
+
type: this.mapDynamoDBTypeToColumnType((attr === null || attr === void 0 ? void 0 : attr.AttributeType) || 'S'),
|
|
618
|
+
primaryKey: key.KeyType === 'HASH',
|
|
619
|
+
nullable: false,
|
|
620
|
+
};
|
|
621
|
+
});
|
|
622
|
+
return {
|
|
623
|
+
name: tableName,
|
|
624
|
+
columns,
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
catch (error) {
|
|
628
|
+
throw new database_types_1.DatabaseError(database_types_1.DatabaseErrorType.SCHEMA_ERROR, `Failed to get DynamoDB table schema: ${error.message}`, error);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
async listTables(connection) {
|
|
632
|
+
try {
|
|
633
|
+
const { ListTablesCommand } = await Promise.resolve().then(() => __importStar(require('@aws-sdk/client-dynamodb')));
|
|
634
|
+
const client = connection.getRawClient();
|
|
635
|
+
const result = await client.send(new ListTablesCommand({}));
|
|
636
|
+
return result.TableNames || [];
|
|
637
|
+
}
|
|
638
|
+
catch (error) {
|
|
639
|
+
throw new database_types_1.DatabaseError(database_types_1.DatabaseErrorType.SCHEMA_ERROR, `Failed to list DynamoDB tables: ${error.message}`, error);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
async tableExists(connection, tableName) {
|
|
643
|
+
try {
|
|
644
|
+
const { DescribeTableCommand } = await Promise.resolve().then(() => __importStar(require('@aws-sdk/client-dynamodb')));
|
|
645
|
+
const client = connection.getRawClient();
|
|
646
|
+
await client.send(new DescribeTableCommand({
|
|
647
|
+
TableName: tableName,
|
|
648
|
+
}));
|
|
649
|
+
return true;
|
|
650
|
+
}
|
|
651
|
+
catch (error) {
|
|
652
|
+
if (error.name === 'ResourceNotFoundException') {
|
|
653
|
+
return false;
|
|
654
|
+
}
|
|
655
|
+
throw new database_types_1.DatabaseError(database_types_1.DatabaseErrorType.SCHEMA_ERROR, `Failed to check DynamoDB table existence: ${error.message}`, error);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
// ==================== Index Methods ====================
|
|
659
|
+
async createIndex(connection, options) {
|
|
660
|
+
const startTime = Date.now();
|
|
661
|
+
try {
|
|
662
|
+
const { UpdateTableCommand } = await Promise.resolve().then(() => __importStar(require('@aws-sdk/client-dynamodb')));
|
|
663
|
+
const client = connection.getRawClient();
|
|
664
|
+
// Build GSI definition
|
|
665
|
+
const keySchema = options.index.columns.map((col, idx) => ({
|
|
666
|
+
AttributeName: col.name,
|
|
667
|
+
KeyType: idx === 0 ? 'HASH' : 'RANGE',
|
|
668
|
+
}));
|
|
669
|
+
const attributeDefinitions = options.index.columns.map((col) => ({
|
|
670
|
+
AttributeName: col.name,
|
|
671
|
+
AttributeType: 'S', // Default to String
|
|
672
|
+
}));
|
|
673
|
+
await client.send(new UpdateTableCommand({
|
|
674
|
+
TableName: options.table,
|
|
675
|
+
AttributeDefinitions: attributeDefinitions,
|
|
676
|
+
GlobalSecondaryIndexUpdates: [
|
|
677
|
+
{
|
|
678
|
+
Create: {
|
|
679
|
+
IndexName: options.index.name,
|
|
680
|
+
KeySchema: keySchema,
|
|
681
|
+
Projection: {
|
|
682
|
+
ProjectionType: 'ALL',
|
|
683
|
+
},
|
|
684
|
+
},
|
|
685
|
+
},
|
|
686
|
+
],
|
|
687
|
+
}));
|
|
688
|
+
return {
|
|
689
|
+
success: true,
|
|
690
|
+
operation: 'create',
|
|
691
|
+
indexName: options.index.name,
|
|
692
|
+
table: options.table,
|
|
693
|
+
executionTime: Date.now() - startTime,
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
catch (error) {
|
|
697
|
+
return {
|
|
698
|
+
success: false,
|
|
699
|
+
operation: 'create',
|
|
700
|
+
indexName: options.index.name,
|
|
701
|
+
table: options.table,
|
|
702
|
+
error: error.message,
|
|
703
|
+
executionTime: Date.now() - startTime,
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
async dropIndex(connection, options) {
|
|
708
|
+
const startTime = Date.now();
|
|
709
|
+
try {
|
|
710
|
+
const { UpdateTableCommand } = await Promise.resolve().then(() => __importStar(require('@aws-sdk/client-dynamodb')));
|
|
711
|
+
const client = connection.getRawClient();
|
|
712
|
+
await client.send(new UpdateTableCommand({
|
|
713
|
+
TableName: options.table,
|
|
714
|
+
GlobalSecondaryIndexUpdates: [
|
|
715
|
+
{
|
|
716
|
+
Delete: {
|
|
717
|
+
IndexName: options.indexName,
|
|
718
|
+
},
|
|
719
|
+
},
|
|
720
|
+
],
|
|
721
|
+
}));
|
|
722
|
+
return {
|
|
723
|
+
success: true,
|
|
724
|
+
operation: 'drop',
|
|
725
|
+
indexName: options.indexName,
|
|
726
|
+
table: options.table,
|
|
727
|
+
executionTime: Date.now() - startTime,
|
|
728
|
+
};
|
|
729
|
+
}
|
|
730
|
+
catch (error) {
|
|
731
|
+
return {
|
|
732
|
+
success: false,
|
|
733
|
+
operation: 'drop',
|
|
734
|
+
indexName: options.indexName,
|
|
735
|
+
table: options.table,
|
|
736
|
+
error: error.message,
|
|
737
|
+
executionTime: Date.now() - startTime,
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
async listIndexes(connection, options) {
|
|
742
|
+
var _a, _b;
|
|
743
|
+
try {
|
|
744
|
+
const { DescribeTableCommand } = await Promise.resolve().then(() => __importStar(require('@aws-sdk/client-dynamodb')));
|
|
745
|
+
const client = connection.getRawClient();
|
|
746
|
+
const result = await client.send(new DescribeTableCommand({
|
|
747
|
+
TableName: options.table,
|
|
748
|
+
}));
|
|
749
|
+
const indexes = [];
|
|
750
|
+
// Add GSIs
|
|
751
|
+
if ((_a = result.Table) === null || _a === void 0 ? void 0 : _a.GlobalSecondaryIndexes) {
|
|
752
|
+
result.Table.GlobalSecondaryIndexes.forEach((gsi) => {
|
|
753
|
+
indexes.push({
|
|
754
|
+
name: gsi.IndexName,
|
|
755
|
+
table: options.table,
|
|
756
|
+
columns: gsi.KeySchema.map((key) => key.AttributeName),
|
|
757
|
+
columnDetails: gsi.KeySchema.map((key) => ({
|
|
758
|
+
name: key.AttributeName,
|
|
759
|
+
order: 'ASC',
|
|
760
|
+
})),
|
|
761
|
+
unique: false,
|
|
762
|
+
primaryKey: false,
|
|
763
|
+
type: 'GSI',
|
|
764
|
+
});
|
|
765
|
+
});
|
|
766
|
+
}
|
|
767
|
+
// Add LSIs
|
|
768
|
+
if ((_b = result.Table) === null || _b === void 0 ? void 0 : _b.LocalSecondaryIndexes) {
|
|
769
|
+
result.Table.LocalSecondaryIndexes.forEach((lsi) => {
|
|
770
|
+
indexes.push({
|
|
771
|
+
name: lsi.IndexName,
|
|
772
|
+
table: options.table,
|
|
773
|
+
columns: lsi.KeySchema.map((key) => key.AttributeName),
|
|
774
|
+
columnDetails: lsi.KeySchema.map((key) => ({
|
|
775
|
+
name: key.AttributeName,
|
|
776
|
+
order: 'ASC',
|
|
777
|
+
})),
|
|
778
|
+
unique: false,
|
|
779
|
+
primaryKey: false,
|
|
780
|
+
type: 'LSI',
|
|
781
|
+
});
|
|
782
|
+
});
|
|
783
|
+
}
|
|
784
|
+
return indexes;
|
|
785
|
+
}
|
|
786
|
+
catch (error) {
|
|
787
|
+
throw new database_types_1.DatabaseError(database_types_1.DatabaseErrorType.INDEX_ERROR, `Failed to list DynamoDB indexes: ${error.message}`, error);
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
async getIndexStatistics(connection, tableName, indexName) {
|
|
791
|
+
var _a;
|
|
792
|
+
try {
|
|
793
|
+
const { DescribeTableCommand } = await Promise.resolve().then(() => __importStar(require('@aws-sdk/client-dynamodb')));
|
|
794
|
+
const client = connection.getRawClient();
|
|
795
|
+
const result = await client.send(new DescribeTableCommand({
|
|
796
|
+
TableName: tableName,
|
|
797
|
+
}));
|
|
798
|
+
const statistics = [];
|
|
799
|
+
if ((_a = result.Table) === null || _a === void 0 ? void 0 : _a.GlobalSecondaryIndexes) {
|
|
800
|
+
result.Table.GlobalSecondaryIndexes.forEach((gsi) => {
|
|
801
|
+
if (!indexName || gsi.IndexName === indexName) {
|
|
802
|
+
statistics.push({
|
|
803
|
+
indexName: gsi.IndexName,
|
|
804
|
+
table: tableName,
|
|
805
|
+
size: gsi.IndexSizeBytes || 0,
|
|
806
|
+
tuplesRead: gsi.ItemCount || 0,
|
|
807
|
+
});
|
|
808
|
+
}
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
return statistics;
|
|
812
|
+
}
|
|
813
|
+
catch (error) {
|
|
814
|
+
throw new database_types_1.DatabaseError(database_types_1.DatabaseErrorType.INDEX_ERROR, `Failed to get DynamoDB index statistics: ${error.message}`, error);
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
// ==================== Migration Methods ====================
|
|
818
|
+
/**
|
|
819
|
+
* Execute a migration operation
|
|
820
|
+
* Note: DynamoDB is schema-less, so column operations are skipped
|
|
821
|
+
*/
|
|
822
|
+
async executeMigrationOperation(connection, operation) {
|
|
823
|
+
var _a, _b, _c, _d;
|
|
824
|
+
const { CreateTableCommand, DeleteTableCommand, UpdateTableCommand, ExecuteStatementCommand } = await Promise.resolve().then(() => __importStar(require('@aws-sdk/client-dynamodb')));
|
|
825
|
+
const client = connection.getRawClient();
|
|
826
|
+
switch (operation.type) {
|
|
827
|
+
case 'create_table':
|
|
828
|
+
if ((_a = operation.params) === null || _a === void 0 ? void 0 : _a.schema) {
|
|
829
|
+
const schema = operation.params.schema;
|
|
830
|
+
const keySchema = [];
|
|
831
|
+
const attributeDefinitions = [];
|
|
832
|
+
// Extract partition and sort keys from schema
|
|
833
|
+
schema.columns.forEach((col) => {
|
|
834
|
+
if (col.primaryKey) {
|
|
835
|
+
keySchema.push({
|
|
836
|
+
AttributeName: col.name,
|
|
837
|
+
KeyType: 'HASH',
|
|
838
|
+
});
|
|
839
|
+
attributeDefinitions.push({
|
|
840
|
+
AttributeName: col.name,
|
|
841
|
+
AttributeType: this.mapColumnTypeToDynamoDB(col.type),
|
|
842
|
+
});
|
|
843
|
+
}
|
|
844
|
+
});
|
|
845
|
+
await client.send(new CreateTableCommand({
|
|
846
|
+
TableName: schema.name,
|
|
847
|
+
KeySchema: keySchema,
|
|
848
|
+
AttributeDefinitions: attributeDefinitions,
|
|
849
|
+
BillingMode: 'PAY_PER_REQUEST',
|
|
850
|
+
}));
|
|
851
|
+
return { executed: true, command: `CreateTable: ${schema.name}` };
|
|
852
|
+
}
|
|
853
|
+
return { executed: false };
|
|
854
|
+
case 'drop_table':
|
|
855
|
+
if (operation.table) {
|
|
856
|
+
await client.send(new DeleteTableCommand({
|
|
857
|
+
TableName: operation.table,
|
|
858
|
+
}));
|
|
859
|
+
return { executed: true, command: `DropTable: ${operation.table}` };
|
|
860
|
+
}
|
|
861
|
+
return { executed: false };
|
|
862
|
+
case 'add_index':
|
|
863
|
+
if (operation.table && ((_b = operation.params) === null || _b === void 0 ? void 0 : _b.indexName) && ((_c = operation.params) === null || _c === void 0 ? void 0 : _c.columns)) {
|
|
864
|
+
const keySchema = operation.params.columns.map((col, idx) => ({
|
|
865
|
+
AttributeName: col,
|
|
866
|
+
KeyType: idx === 0 ? 'HASH' : 'RANGE',
|
|
867
|
+
}));
|
|
868
|
+
const attributeDefinitions = operation.params.columns.map((col) => ({
|
|
869
|
+
AttributeName: col,
|
|
870
|
+
AttributeType: 'S', // Default to String
|
|
871
|
+
}));
|
|
872
|
+
await client.send(new UpdateTableCommand({
|
|
873
|
+
TableName: operation.table,
|
|
874
|
+
AttributeDefinitions: attributeDefinitions,
|
|
875
|
+
GlobalSecondaryIndexUpdates: [
|
|
876
|
+
{
|
|
877
|
+
Create: {
|
|
878
|
+
IndexName: operation.params.indexName,
|
|
879
|
+
KeySchema: keySchema,
|
|
880
|
+
Projection: {
|
|
881
|
+
ProjectionType: 'ALL',
|
|
882
|
+
},
|
|
883
|
+
},
|
|
884
|
+
},
|
|
885
|
+
],
|
|
886
|
+
}));
|
|
887
|
+
return { executed: true, command: `AddIndex: ${operation.params.indexName}` };
|
|
888
|
+
}
|
|
889
|
+
return { executed: false };
|
|
890
|
+
case 'drop_index':
|
|
891
|
+
if (operation.table && ((_d = operation.params) === null || _d === void 0 ? void 0 : _d.indexName)) {
|
|
892
|
+
await client.send(new UpdateTableCommand({
|
|
893
|
+
TableName: operation.table,
|
|
894
|
+
GlobalSecondaryIndexUpdates: [
|
|
895
|
+
{
|
|
896
|
+
Delete: {
|
|
897
|
+
IndexName: operation.params.indexName,
|
|
898
|
+
},
|
|
899
|
+
},
|
|
900
|
+
],
|
|
901
|
+
}));
|
|
902
|
+
return { executed: true, command: `DropIndex: ${operation.params.indexName}` };
|
|
903
|
+
}
|
|
904
|
+
return { executed: false };
|
|
905
|
+
case 'raw_sql':
|
|
906
|
+
if (operation.sql) {
|
|
907
|
+
// Use PartiQL for raw queries
|
|
908
|
+
await client.send(new ExecuteStatementCommand({
|
|
909
|
+
Statement: operation.sql,
|
|
910
|
+
}));
|
|
911
|
+
return { executed: true, command: `PartiQL: ${operation.sql.substring(0, 50)}...` };
|
|
912
|
+
}
|
|
913
|
+
return { executed: false };
|
|
914
|
+
// Schema-less operations - skip silently for DynamoDB
|
|
915
|
+
case 'add_column':
|
|
916
|
+
case 'drop_column':
|
|
917
|
+
case 'modify_column':
|
|
918
|
+
case 'rename_column':
|
|
919
|
+
case 'alter_table':
|
|
920
|
+
return { executed: false, command: `Skipped (schema-less): ${operation.type}` };
|
|
921
|
+
// Constraint operations - not applicable to DynamoDB
|
|
922
|
+
case 'add_constraint':
|
|
923
|
+
case 'drop_constraint':
|
|
924
|
+
return { executed: false, command: `Skipped (no constraints): ${operation.type}` };
|
|
925
|
+
default:
|
|
926
|
+
return { executed: false };
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
async runMigration(connection, migration, options) {
|
|
930
|
+
const startTime = Date.now();
|
|
931
|
+
const statements = [];
|
|
932
|
+
try {
|
|
933
|
+
// DynamoDB doesn't support transactions for schema changes
|
|
934
|
+
// Execute operations sequentially
|
|
935
|
+
for (const operation of migration.up) {
|
|
936
|
+
const result = await this.executeMigrationOperation(connection, operation);
|
|
937
|
+
if (result.command) {
|
|
938
|
+
statements.push(result.command);
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
// Record migration in history table
|
|
942
|
+
await this.recordMigration(connection, migration);
|
|
943
|
+
return {
|
|
944
|
+
tag: migration.tag,
|
|
945
|
+
status: migration_types_1.MigrationStatus.COMPLETED,
|
|
946
|
+
direction: migration_types_1.MigrationDirection.UP,
|
|
947
|
+
executedAt: new Date(),
|
|
948
|
+
duration: Date.now() - startTime,
|
|
949
|
+
statements,
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
catch (error) {
|
|
953
|
+
return {
|
|
954
|
+
tag: migration.tag,
|
|
955
|
+
status: migration_types_1.MigrationStatus.FAILED,
|
|
956
|
+
direction: migration_types_1.MigrationDirection.UP,
|
|
957
|
+
executedAt: new Date(),
|
|
958
|
+
duration: Date.now() - startTime,
|
|
959
|
+
error: error.message,
|
|
960
|
+
statements,
|
|
961
|
+
};
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
async rollbackMigration(connection, migration, options) {
|
|
965
|
+
const startTime = Date.now();
|
|
966
|
+
const statements = [];
|
|
967
|
+
try {
|
|
968
|
+
// Execute rollback operations
|
|
969
|
+
for (const operation of migration.down) {
|
|
970
|
+
const result = await this.executeMigrationOperation(connection, operation);
|
|
971
|
+
if (result.command) {
|
|
972
|
+
statements.push(result.command);
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
// Remove migration from history table
|
|
976
|
+
await this.removeMigration(connection, migration.tag);
|
|
977
|
+
return {
|
|
978
|
+
tag: migration.tag,
|
|
979
|
+
status: migration_types_1.MigrationStatus.ROLLED_BACK,
|
|
980
|
+
direction: migration_types_1.MigrationDirection.DOWN,
|
|
981
|
+
executedAt: new Date(),
|
|
982
|
+
duration: Date.now() - startTime,
|
|
983
|
+
statements,
|
|
984
|
+
};
|
|
985
|
+
}
|
|
986
|
+
catch (error) {
|
|
987
|
+
return {
|
|
988
|
+
tag: migration.tag,
|
|
989
|
+
status: migration_types_1.MigrationStatus.FAILED,
|
|
990
|
+
direction: migration_types_1.MigrationDirection.DOWN,
|
|
991
|
+
executedAt: new Date(),
|
|
992
|
+
duration: Date.now() - startTime,
|
|
993
|
+
error: error.message,
|
|
994
|
+
statements,
|
|
995
|
+
};
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
async getMigrationHistory(connection, options) {
|
|
999
|
+
try {
|
|
1000
|
+
const { ScanCommand } = await Promise.resolve().then(() => __importStar(require('@aws-sdk/lib-dynamodb')));
|
|
1001
|
+
const docClient = connection.getClient();
|
|
1002
|
+
// Ensure migration table exists
|
|
1003
|
+
await this.ensureMigrationTable(connection);
|
|
1004
|
+
const result = await docClient.send(new ScanCommand({
|
|
1005
|
+
TableName: '_ductape_migrations',
|
|
1006
|
+
}));
|
|
1007
|
+
return (result.Items || []).map((item) => ({
|
|
1008
|
+
tag: item.tag,
|
|
1009
|
+
name: item.name,
|
|
1010
|
+
env: options.env,
|
|
1011
|
+
product: options.product,
|
|
1012
|
+
database: options.database,
|
|
1013
|
+
status: migration_types_1.MigrationStatus.COMPLETED,
|
|
1014
|
+
direction: migration_types_1.MigrationDirection.UP,
|
|
1015
|
+
appliedAt: new Date(item.executed_at),
|
|
1016
|
+
}));
|
|
1017
|
+
}
|
|
1018
|
+
catch (error) {
|
|
1019
|
+
throw new database_types_1.DatabaseError(database_types_1.DatabaseErrorType.MIGRATION_ERROR, `Failed to get DynamoDB migration history: ${error.message}`, error);
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
/**
|
|
1023
|
+
* Ensure migration tracking table exists
|
|
1024
|
+
*/
|
|
1025
|
+
async ensureMigrationTable(connection) {
|
|
1026
|
+
try {
|
|
1027
|
+
const { CreateTableCommand, DescribeTableCommand } = await Promise.resolve().then(() => __importStar(require('@aws-sdk/client-dynamodb')));
|
|
1028
|
+
const client = connection.getRawClient();
|
|
1029
|
+
// Check if table exists
|
|
1030
|
+
try {
|
|
1031
|
+
await client.send(new DescribeTableCommand({
|
|
1032
|
+
TableName: '_ductape_migrations',
|
|
1033
|
+
}));
|
|
1034
|
+
return; // Table exists
|
|
1035
|
+
}
|
|
1036
|
+
catch (error) {
|
|
1037
|
+
if (error.name !== 'ResourceNotFoundException') {
|
|
1038
|
+
throw error;
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
// Create migration table
|
|
1042
|
+
await client.send(new CreateTableCommand({
|
|
1043
|
+
TableName: '_ductape_migrations',
|
|
1044
|
+
KeySchema: [
|
|
1045
|
+
{
|
|
1046
|
+
AttributeName: 'tag',
|
|
1047
|
+
KeyType: 'HASH',
|
|
1048
|
+
},
|
|
1049
|
+
],
|
|
1050
|
+
AttributeDefinitions: [
|
|
1051
|
+
{
|
|
1052
|
+
AttributeName: 'tag',
|
|
1053
|
+
AttributeType: 'S',
|
|
1054
|
+
},
|
|
1055
|
+
],
|
|
1056
|
+
BillingMode: 'PAY_PER_REQUEST',
|
|
1057
|
+
}));
|
|
1058
|
+
// Wait for table to be active
|
|
1059
|
+
await new Promise((resolve) => setTimeout(resolve, 5000));
|
|
1060
|
+
}
|
|
1061
|
+
catch (error) {
|
|
1062
|
+
throw new database_types_1.DatabaseError(database_types_1.DatabaseErrorType.MIGRATION_ERROR, `Failed to ensure migration table: ${error.message}`, error);
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
/**
|
|
1066
|
+
* Record migration execution
|
|
1067
|
+
*/
|
|
1068
|
+
async recordMigration(connection, migration) {
|
|
1069
|
+
try {
|
|
1070
|
+
const { PutCommand } = await Promise.resolve().then(() => __importStar(require('@aws-sdk/lib-dynamodb')));
|
|
1071
|
+
const docClient = connection.getClient();
|
|
1072
|
+
await this.ensureMigrationTable(connection);
|
|
1073
|
+
await docClient.send(new PutCommand({
|
|
1074
|
+
TableName: '_ductape_migrations',
|
|
1075
|
+
Item: {
|
|
1076
|
+
tag: migration.tag,
|
|
1077
|
+
name: migration.name,
|
|
1078
|
+
executed_at: new Date().toISOString(),
|
|
1079
|
+
},
|
|
1080
|
+
}));
|
|
1081
|
+
}
|
|
1082
|
+
catch (error) {
|
|
1083
|
+
throw new database_types_1.DatabaseError(database_types_1.DatabaseErrorType.MIGRATION_ERROR, `Failed to record migration: ${error.message}`, error);
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
/**
|
|
1087
|
+
* Remove migration from history
|
|
1088
|
+
*/
|
|
1089
|
+
async removeMigration(connection, tag) {
|
|
1090
|
+
try {
|
|
1091
|
+
const { DeleteCommand } = await Promise.resolve().then(() => __importStar(require('@aws-sdk/lib-dynamodb')));
|
|
1092
|
+
const docClient = connection.getClient();
|
|
1093
|
+
await docClient.send(new DeleteCommand({
|
|
1094
|
+
TableName: '_ductape_migrations',
|
|
1095
|
+
Key: { tag },
|
|
1096
|
+
}));
|
|
1097
|
+
}
|
|
1098
|
+
catch (error) {
|
|
1099
|
+
throw new database_types_1.DatabaseError(database_types_1.DatabaseErrorType.MIGRATION_ERROR, `Failed to remove migration: ${error.message}`, error);
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
// ==================== Helper Methods ====================
|
|
1103
|
+
hasPartitionKeyInWhere(where) {
|
|
1104
|
+
// Simple check - in production would need table metadata
|
|
1105
|
+
return where && typeof where === 'object' && Object.keys(where).length > 0;
|
|
1106
|
+
}
|
|
1107
|
+
buildQueryParams(options) {
|
|
1108
|
+
const params = {
|
|
1109
|
+
TableName: options.table,
|
|
1110
|
+
};
|
|
1111
|
+
if (options.where) {
|
|
1112
|
+
const { keyConditionExpression, expressionAttributeNames, expressionAttributeValues } = this.buildKeyConditionExpression(options.where);
|
|
1113
|
+
params.KeyConditionExpression = keyConditionExpression;
|
|
1114
|
+
params.ExpressionAttributeNames = expressionAttributeNames;
|
|
1115
|
+
params.ExpressionAttributeValues = expressionAttributeValues;
|
|
1116
|
+
}
|
|
1117
|
+
if (options.limit) {
|
|
1118
|
+
params.Limit = options.limit;
|
|
1119
|
+
}
|
|
1120
|
+
return params;
|
|
1121
|
+
}
|
|
1122
|
+
buildScanParams(options) {
|
|
1123
|
+
const params = {
|
|
1124
|
+
TableName: options.table,
|
|
1125
|
+
};
|
|
1126
|
+
if (options.where) {
|
|
1127
|
+
const { filterExpression, expressionAttributeNames, expressionAttributeValues } = this.buildFilterExpression(options.where);
|
|
1128
|
+
params.FilterExpression = filterExpression;
|
|
1129
|
+
params.ExpressionAttributeNames = expressionAttributeNames;
|
|
1130
|
+
params.ExpressionAttributeValues = expressionAttributeValues;
|
|
1131
|
+
}
|
|
1132
|
+
if (options.limit) {
|
|
1133
|
+
params.Limit = options.limit;
|
|
1134
|
+
}
|
|
1135
|
+
return params;
|
|
1136
|
+
}
|
|
1137
|
+
buildKeyConditionExpression(where) {
|
|
1138
|
+
const names = {};
|
|
1139
|
+
const values = {};
|
|
1140
|
+
const conditions = [];
|
|
1141
|
+
Object.entries(where).forEach(([key, value], idx) => {
|
|
1142
|
+
names[`#${key}`] = key;
|
|
1143
|
+
values[`:val${idx}`] = value;
|
|
1144
|
+
conditions.push(`#${key} = :val${idx}`);
|
|
1145
|
+
});
|
|
1146
|
+
return {
|
|
1147
|
+
keyConditionExpression: conditions.join(' AND '),
|
|
1148
|
+
expressionAttributeNames: names,
|
|
1149
|
+
expressionAttributeValues: values,
|
|
1150
|
+
};
|
|
1151
|
+
}
|
|
1152
|
+
buildFilterExpression(where) {
|
|
1153
|
+
const { keyConditionExpression, expressionAttributeNames, expressionAttributeValues, } = this.buildKeyConditionExpression(where);
|
|
1154
|
+
return {
|
|
1155
|
+
filterExpression: keyConditionExpression,
|
|
1156
|
+
expressionAttributeNames,
|
|
1157
|
+
expressionAttributeValues,
|
|
1158
|
+
};
|
|
1159
|
+
}
|
|
1160
|
+
buildUpdateExpression(data) {
|
|
1161
|
+
const names = {};
|
|
1162
|
+
const values = {};
|
|
1163
|
+
const updates = [];
|
|
1164
|
+
Object.entries(data).forEach(([key, value], idx) => {
|
|
1165
|
+
names[`#${key}`] = key;
|
|
1166
|
+
values[`:val${idx}`] = value;
|
|
1167
|
+
updates.push(`#${key} = :val${idx}`);
|
|
1168
|
+
});
|
|
1169
|
+
return {
|
|
1170
|
+
updateExpression: `SET ${updates.join(', ')}`,
|
|
1171
|
+
expressionAttributeNames: names,
|
|
1172
|
+
expressionAttributeValues: values,
|
|
1173
|
+
};
|
|
1174
|
+
}
|
|
1175
|
+
extractKeyFromWhere(where) {
|
|
1176
|
+
// Simple implementation - extract key fields from where clause
|
|
1177
|
+
return where || {};
|
|
1178
|
+
}
|
|
1179
|
+
chunkArray(array, size) {
|
|
1180
|
+
const chunks = [];
|
|
1181
|
+
for (let i = 0; i < array.length; i += size) {
|
|
1182
|
+
chunks.push(array.slice(i, i + size));
|
|
1183
|
+
}
|
|
1184
|
+
return chunks;
|
|
1185
|
+
}
|
|
1186
|
+
mapColumnTypeToDynamoDB(type) {
|
|
1187
|
+
// DynamoDB only supports S (String), N (Number), and B (Binary) for keys
|
|
1188
|
+
switch (type) {
|
|
1189
|
+
case schema_types_1.ColumnType.INTEGER:
|
|
1190
|
+
case schema_types_1.ColumnType.BIGINT:
|
|
1191
|
+
case schema_types_1.ColumnType.SMALLINT:
|
|
1192
|
+
case schema_types_1.ColumnType.DECIMAL:
|
|
1193
|
+
case schema_types_1.ColumnType.NUMERIC:
|
|
1194
|
+
case schema_types_1.ColumnType.FLOAT:
|
|
1195
|
+
case schema_types_1.ColumnType.DOUBLE:
|
|
1196
|
+
case schema_types_1.ColumnType.REAL:
|
|
1197
|
+
return 'N';
|
|
1198
|
+
case schema_types_1.ColumnType.BLOB:
|
|
1199
|
+
case schema_types_1.ColumnType.BINARY:
|
|
1200
|
+
return 'B';
|
|
1201
|
+
default:
|
|
1202
|
+
return 'S';
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
mapDynamoDBTypeToColumnType(dynamoType) {
|
|
1206
|
+
switch (dynamoType) {
|
|
1207
|
+
case 'N':
|
|
1208
|
+
return schema_types_1.ColumnType.DECIMAL;
|
|
1209
|
+
case 'B':
|
|
1210
|
+
return schema_types_1.ColumnType.BINARY;
|
|
1211
|
+
default:
|
|
1212
|
+
return schema_types_1.ColumnType.STRING;
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
// ==================== Utility Methods ====================
|
|
1216
|
+
escapeIdentifier(identifier) {
|
|
1217
|
+
// DynamoDB doesn't require escaping identifiers
|
|
1218
|
+
return identifier;
|
|
1219
|
+
}
|
|
1220
|
+
escapeValue(value) {
|
|
1221
|
+
if (value === null || value === undefined) {
|
|
1222
|
+
return 'null';
|
|
1223
|
+
}
|
|
1224
|
+
if (typeof value === 'string') {
|
|
1225
|
+
return `"${value.replace(/"/g, '\\"')}"`;
|
|
1226
|
+
}
|
|
1227
|
+
if (typeof value === 'object') {
|
|
1228
|
+
return JSON.stringify(value);
|
|
1229
|
+
}
|
|
1230
|
+
return String(value);
|
|
1231
|
+
}
|
|
1232
|
+
async getDatabaseVersion(connection) {
|
|
1233
|
+
return 'DynamoDB (AWS Managed)';
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
exports.DynamoDBAdapter = DynamoDBAdapter;
|
|
1237
|
+
//# sourceMappingURL=dynamodb.adapter.js.map
|