bun-query-builder 0.1.12 → 0.1.15

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 (63) hide show
  1. package/README.md +45 -468
  2. package/dist/__tests__/type-narrowing-compile.d.ts +1 -0
  3. package/dist/__tests__/type-narrowing.test.d.ts +1 -0
  4. package/dist/actions/benchmark.d.ts +1 -1
  5. package/dist/actions/cache.d.ts +1 -1
  6. package/dist/actions/console.d.ts +1 -1
  7. package/dist/actions/data.d.ts +2 -1
  8. package/dist/actions/db-info.d.ts +1 -1
  9. package/dist/actions/db-optimize.d.ts +1 -1
  10. package/dist/actions/db-wipe.d.ts +1 -1
  11. package/dist/actions/explain.d.ts +1 -1
  12. package/dist/actions/file.d.ts +1 -1
  13. package/dist/actions/index.d.ts +1 -1
  14. package/dist/actions/inspect.d.ts +1 -1
  15. package/dist/actions/introspect.d.ts +1 -1
  16. package/dist/actions/make-model.d.ts +1 -1
  17. package/dist/actions/migrate-generate.d.ts +1 -1
  18. package/dist/actions/migrate-rollback.d.ts +1 -1
  19. package/dist/actions/migrate-status.d.ts +1 -1
  20. package/dist/actions/migrate.d.ts +1 -1
  21. package/dist/actions/model-show.d.ts +1 -1
  22. package/dist/actions/ping.d.ts +1 -1
  23. package/dist/actions/query-explain-all.d.ts +1 -1
  24. package/dist/actions/relation-diagram.d.ts +1 -1
  25. package/dist/actions/seed.d.ts +1 -1
  26. package/dist/actions/sql.d.ts +1 -1
  27. package/dist/actions/unsafe.d.ts +1 -1
  28. package/dist/actions/validate.d.ts +1 -1
  29. package/dist/actions/wait-ready.d.ts +1 -1
  30. package/dist/bin/cli.js +25785 -0
  31. package/dist/browser.d.ts +118 -44
  32. package/dist/client.d.ts +22 -56
  33. package/dist/config.d.ts +16 -3
  34. package/dist/db.d.ts +5 -4
  35. package/dist/drivers/dynamodb.d.ts +3 -13
  36. package/dist/drivers/index.d.ts +2 -1
  37. package/dist/drivers/mysql.d.ts +3 -9
  38. package/dist/drivers/postgres.d.ts +3 -9
  39. package/dist/drivers/sqlite.d.ts +3 -9
  40. package/dist/dynamodb/client.d.ts +1 -5
  41. package/dist/dynamodb/index.d.ts +7 -28
  42. package/dist/dynamodb/migration-driver.d.ts +2 -23
  43. package/dist/dynamodb/migration-tracker.d.ts +4 -6
  44. package/dist/dynamodb/migrations.d.ts +4 -1
  45. package/dist/dynamodb/model.d.ts +5 -13
  46. package/dist/dynamodb-client.d.ts +3 -23
  47. package/dist/dynamodb-single-table.d.ts +22 -26
  48. package/dist/dynamodb-tooling-adapter.d.ts +2 -33
  49. package/dist/factory.d.ts +3 -3
  50. package/dist/index.d.ts +24 -1
  51. package/dist/loader.d.ts +1 -1
  52. package/dist/meta.d.ts +1 -1
  53. package/dist/migrations.d.ts +4 -4
  54. package/dist/model.d.ts +61 -3
  55. package/dist/orm.d.ts +130 -82
  56. package/dist/schema.d.ts +32 -25
  57. package/dist/seeder.d.ts +3 -1
  58. package/dist/src/browser.js +824 -0
  59. package/dist/src/dynamodb/index.js +2301 -0
  60. package/dist/{index.js → src/index.js} +21466 -20710
  61. package/dist/type-inference.d.ts +326 -0
  62. package/dist/types.d.ts +1 -5
  63. package/package.json +3 -7
@@ -0,0 +1,2301 @@
1
+ // @bun
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ function __accessProp(key) {
7
+ return this[key];
8
+ }
9
+ var __toCommonJS = (from) => {
10
+ var entry = (__moduleCache ??= new WeakMap).get(from), desc;
11
+ if (entry)
12
+ return entry;
13
+ entry = __defProp({}, "__esModule", { value: true });
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (var key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(entry, key))
17
+ __defProp(entry, key, {
18
+ get: __accessProp.bind(from, key),
19
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
20
+ });
21
+ }
22
+ __moduleCache.set(from, entry);
23
+ return entry;
24
+ };
25
+ var __moduleCache;
26
+ var __returnValue = (v) => v;
27
+ function __exportSetter(name, newValue) {
28
+ this[name] = __returnValue.bind(null, newValue);
29
+ }
30
+ var __export = (target, all) => {
31
+ for (var name in all)
32
+ __defProp(target, name, {
33
+ get: all[name],
34
+ enumerable: true,
35
+ configurable: true,
36
+ set: __exportSetter.bind(all, name)
37
+ });
38
+ };
39
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
40
+ var __promiseAll = (args) => Promise.all(args);
41
+ var __require = import.meta.require;
42
+
43
+ // src/drivers/dynamodb.ts
44
+ class DynamoDBDriverImpl {
45
+ config;
46
+ entityMappings = new Map;
47
+ constructor(config) {
48
+ this.config = config;
49
+ if (config.entityMappings) {
50
+ for (const mapping of config.entityMappings) {
51
+ this.registerEntity(mapping);
52
+ }
53
+ }
54
+ }
55
+ createTable(definition) {
56
+ return {
57
+ ...definition,
58
+ billingMode: definition.billingMode ?? this.config.defaultBillingMode ?? "PAY_PER_REQUEST"
59
+ };
60
+ }
61
+ deleteTable(tableName) {
62
+ return { tableName };
63
+ }
64
+ registerEntity(mapping) {
65
+ this.entityMappings.set(mapping.entityType, mapping);
66
+ }
67
+ getEntityMapping(entityType) {
68
+ return this.entityMappings.get(entityType);
69
+ }
70
+ buildPrimaryKey(entityType, values) {
71
+ const mapping = this.entityMappings.get(entityType);
72
+ if (!mapping) {
73
+ throw new Error(`No entity mapping found for type: ${entityType}`);
74
+ }
75
+ const pk = this.interpolatePattern(mapping.pkPattern, values);
76
+ const sk = this.interpolatePattern(mapping.skPattern, values);
77
+ return { pk, sk };
78
+ }
79
+ parseEntityFromItem(item) {
80
+ const pk = item.pk || item.PK;
81
+ if (!pk)
82
+ return null;
83
+ for (const [entityType, mapping] of this.entityMappings) {
84
+ const prefix = mapping.pkPattern.split("${")[0];
85
+ if (pk.startsWith(prefix)) {
86
+ return {
87
+ entityType,
88
+ data: this.unmarshall(item)
89
+ };
90
+ }
91
+ }
92
+ return null;
93
+ }
94
+ buildQueryParams(params) {
95
+ return {
96
+ tableName: params.tableName ?? this.config.tableName ?? "",
97
+ keyConditions: params.keyConditions ?? [],
98
+ ...params
99
+ };
100
+ }
101
+ buildScanParams(params) {
102
+ return {
103
+ tableName: params.tableName ?? this.config.tableName ?? "",
104
+ ...params
105
+ };
106
+ }
107
+ buildGetItemParams(params) {
108
+ return {
109
+ tableName: params.tableName ?? this.config.tableName ?? "",
110
+ key: params.key ?? {},
111
+ ...params
112
+ };
113
+ }
114
+ buildPutItemParams(params) {
115
+ return {
116
+ tableName: params.tableName ?? this.config.tableName ?? "",
117
+ item: params.item ?? {},
118
+ ...params
119
+ };
120
+ }
121
+ buildUpdateItemParams(params) {
122
+ return {
123
+ tableName: params.tableName ?? this.config.tableName ?? "",
124
+ key: params.key ?? {},
125
+ updateExpressions: params.updateExpressions ?? {},
126
+ ...params
127
+ };
128
+ }
129
+ buildDeleteItemParams(params) {
130
+ return {
131
+ tableName: params.tableName ?? this.config.tableName ?? "",
132
+ key: params.key ?? {},
133
+ ...params
134
+ };
135
+ }
136
+ buildBatchGetItemParams(params) {
137
+ return {
138
+ requestItems: params.requestItems ?? {}
139
+ };
140
+ }
141
+ buildBatchWriteItemParams(params) {
142
+ return {
143
+ requestItems: params.requestItems ?? {}
144
+ };
145
+ }
146
+ buildTransactWriteParams(params) {
147
+ return {
148
+ transactItems: params.transactItems ?? [],
149
+ ...params
150
+ };
151
+ }
152
+ buildKeyConditionExpression(conditions) {
153
+ return this.buildExpression(conditions, "AND");
154
+ }
155
+ buildFilterExpression(conditions) {
156
+ return this.buildExpression(conditions, "AND");
157
+ }
158
+ buildUpdateExpression(updates) {
159
+ const expressionAttributeNames = {};
160
+ const expressionAttributeValues = {};
161
+ const parts = [];
162
+ let valueIndex = 0;
163
+ if (updates.set && Object.keys(updates.set).length > 0) {
164
+ const setParts = [];
165
+ for (const [key, value] of Object.entries(updates.set)) {
166
+ const nameKey = `#attr${valueIndex}`;
167
+ const valueKey = `:val${valueIndex}`;
168
+ expressionAttributeNames[nameKey] = key;
169
+ expressionAttributeValues[valueKey] = this.marshallValue(value);
170
+ setParts.push(`${nameKey} = ${valueKey}`);
171
+ valueIndex++;
172
+ }
173
+ parts.push(`SET ${setParts.join(", ")}`);
174
+ }
175
+ if (updates.remove && updates.remove.length > 0) {
176
+ const removeParts = [];
177
+ for (const attr of updates.remove) {
178
+ const nameKey = `#attr${valueIndex}`;
179
+ expressionAttributeNames[nameKey] = attr;
180
+ removeParts.push(nameKey);
181
+ valueIndex++;
182
+ }
183
+ parts.push(`REMOVE ${removeParts.join(", ")}`);
184
+ }
185
+ if (updates.add && Object.keys(updates.add).length > 0) {
186
+ const addParts = [];
187
+ for (const [key, value] of Object.entries(updates.add)) {
188
+ const nameKey = `#attr${valueIndex}`;
189
+ const valueKey = `:val${valueIndex}`;
190
+ expressionAttributeNames[nameKey] = key;
191
+ expressionAttributeValues[valueKey] = this.marshallValue(value);
192
+ addParts.push(`${nameKey} ${valueKey}`);
193
+ valueIndex++;
194
+ }
195
+ parts.push(`ADD ${addParts.join(", ")}`);
196
+ }
197
+ if (updates.delete && Object.keys(updates.delete).length > 0) {
198
+ const deleteParts = [];
199
+ for (const [key, value] of Object.entries(updates.delete)) {
200
+ const nameKey = `#attr${valueIndex}`;
201
+ const valueKey = `:val${valueIndex}`;
202
+ expressionAttributeNames[nameKey] = key;
203
+ expressionAttributeValues[valueKey] = this.marshallValue(value);
204
+ deleteParts.push(`${nameKey} ${valueKey}`);
205
+ valueIndex++;
206
+ }
207
+ parts.push(`DELETE ${deleteParts.join(", ")}`);
208
+ }
209
+ return {
210
+ expression: parts.join(" "),
211
+ expressionAttributeNames,
212
+ expressionAttributeValues
213
+ };
214
+ }
215
+ buildProjectionExpression(attributes) {
216
+ const expressionAttributeNames = {};
217
+ const projectionParts = [];
218
+ attributes.forEach((attr, index) => {
219
+ const nameKey = `#proj${index}`;
220
+ expressionAttributeNames[nameKey] = attr;
221
+ projectionParts.push(nameKey);
222
+ });
223
+ return {
224
+ expression: projectionParts.join(", "),
225
+ expressionAttributeNames
226
+ };
227
+ }
228
+ marshall(item) {
229
+ const result = {};
230
+ for (const [key, value] of Object.entries(item)) {
231
+ result[key] = this.marshallValue(value);
232
+ }
233
+ return result;
234
+ }
235
+ unmarshall(item) {
236
+ const result = {};
237
+ for (const [key, value] of Object.entries(item)) {
238
+ result[key] = this.unmarshallValue(value);
239
+ }
240
+ return result;
241
+ }
242
+ interpolatePattern(pattern, values) {
243
+ return pattern.replace(/\$\{(\w+)\}/g, (_, key) => {
244
+ if (!(key in values)) {
245
+ throw new Error(`Missing value for pattern key: ${key}`);
246
+ }
247
+ return String(values[key]);
248
+ });
249
+ }
250
+ buildExpression(conditions, joiner) {
251
+ const expressionAttributeNames = {};
252
+ const expressionAttributeValues = {};
253
+ const parts = [];
254
+ conditions.forEach((condition, index) => {
255
+ const nameKey = `#attr${index}`;
256
+ expressionAttributeNames[nameKey] = condition.attribute;
257
+ let expr;
258
+ switch (condition.operator) {
259
+ case "=":
260
+ case "<":
261
+ case "<=":
262
+ case ">":
263
+ case ">=": {
264
+ const valueKey = `:val${index}`;
265
+ expressionAttributeValues[valueKey] = this.marshallValue(condition.value);
266
+ expr = `${nameKey} ${condition.operator} ${valueKey}`;
267
+ break;
268
+ }
269
+ case "BETWEEN": {
270
+ const valueKey1 = `:val${index}a`;
271
+ const valueKey2 = `:val${index}b`;
272
+ expressionAttributeValues[valueKey1] = this.marshallValue(condition.values?.[0]);
273
+ expressionAttributeValues[valueKey2] = this.marshallValue(condition.values?.[1]);
274
+ expr = `${nameKey} BETWEEN ${valueKey1} AND ${valueKey2}`;
275
+ break;
276
+ }
277
+ case "begins_with": {
278
+ const valueKey = `:val${index}`;
279
+ expressionAttributeValues[valueKey] = this.marshallValue(condition.value);
280
+ expr = `begins_with(${nameKey}, ${valueKey})`;
281
+ break;
282
+ }
283
+ case "contains": {
284
+ const valueKey = `:val${index}`;
285
+ expressionAttributeValues[valueKey] = this.marshallValue(condition.value);
286
+ expr = `contains(${nameKey}, ${valueKey})`;
287
+ break;
288
+ }
289
+ case "attribute_exists":
290
+ expr = `attribute_exists(${nameKey})`;
291
+ break;
292
+ case "attribute_not_exists":
293
+ expr = `attribute_not_exists(${nameKey})`;
294
+ break;
295
+ case "attribute_type": {
296
+ const valueKey = `:val${index}`;
297
+ expressionAttributeValues[valueKey] = { S: condition.value };
298
+ expr = `attribute_type(${nameKey}, ${valueKey})`;
299
+ break;
300
+ }
301
+ case "IN": {
302
+ const valueKeys = (condition.values ?? []).map((_, i) => `:val${index}_${i}`);
303
+ condition.values?.forEach((val, i) => {
304
+ expressionAttributeValues[`:val${index}_${i}`] = this.marshallValue(val);
305
+ });
306
+ expr = `${nameKey} IN (${valueKeys.join(", ")})`;
307
+ break;
308
+ }
309
+ default:
310
+ throw new Error(`Unknown operator: ${condition.operator}`);
311
+ }
312
+ parts.push(expr);
313
+ });
314
+ return {
315
+ expression: parts.join(` ${joiner} `),
316
+ expressionAttributeNames,
317
+ expressionAttributeValues
318
+ };
319
+ }
320
+ marshallValue(value) {
321
+ if (value === null || value === undefined) {
322
+ return { NULL: true };
323
+ }
324
+ if (typeof value === "string") {
325
+ return { S: value };
326
+ }
327
+ if (typeof value === "number") {
328
+ return { N: String(value) };
329
+ }
330
+ if (typeof value === "boolean") {
331
+ return { BOOL: value };
332
+ }
333
+ if (value instanceof Uint8Array || Buffer.isBuffer(value)) {
334
+ return { B: value };
335
+ }
336
+ if (Array.isArray(value)) {
337
+ if (value.length === 0) {
338
+ return { L: [] };
339
+ }
340
+ const _firstType = typeof value[0];
341
+ const isStringSet = value.every((v) => typeof v === "string");
342
+ const isNumberSet = value.every((v) => typeof v === "number");
343
+ if (isStringSet) {
344
+ return { SS: value };
345
+ }
346
+ if (isNumberSet) {
347
+ return { NS: value.map(String) };
348
+ }
349
+ return { L: value.map((v) => this.marshallValue(v)) };
350
+ }
351
+ if (typeof value === "object") {
352
+ const marshalled = {};
353
+ for (const [k, v] of Object.entries(value)) {
354
+ marshalled[k] = this.marshallValue(v);
355
+ }
356
+ return { M: marshalled };
357
+ }
358
+ return { S: String(value) };
359
+ }
360
+ unmarshallValue(value) {
361
+ if (!value || typeof value !== "object") {
362
+ return value;
363
+ }
364
+ if ("S" in value)
365
+ return value.S;
366
+ if ("N" in value)
367
+ return Number(value.N);
368
+ if ("BOOL" in value)
369
+ return value.BOOL;
370
+ if ("NULL" in value)
371
+ return null;
372
+ if ("B" in value)
373
+ return value.B;
374
+ if ("SS" in value)
375
+ return value.SS;
376
+ if ("NS" in value)
377
+ return value.NS.map(Number);
378
+ if ("BS" in value)
379
+ return value.BS;
380
+ if ("L" in value)
381
+ return value.L.map((v) => this.unmarshallValue(v));
382
+ if ("M" in value) {
383
+ const result = {};
384
+ for (const [k, v] of Object.entries(value.M)) {
385
+ result[k] = this.unmarshallValue(v);
386
+ }
387
+ return result;
388
+ }
389
+ return value;
390
+ }
391
+ }
392
+ function createDynamoDBDriver(config) {
393
+ return new DynamoDBDriverImpl(config);
394
+ }
395
+
396
+ // src/dynamodb/client.ts
397
+ async function hmacSha256(key, message) {
398
+ const keyBuffer = key instanceof ArrayBuffer ? key : key.buffer.slice(key.byteOffset, key.byteOffset + key.byteLength);
399
+ const cryptoKey = await crypto.subtle.importKey("raw", keyBuffer, { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
400
+ return crypto.subtle.sign("HMAC", cryptoKey, new TextEncoder().encode(message));
401
+ }
402
+ async function sha256(message) {
403
+ const hashBuffer = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(message));
404
+ return Array.from(new Uint8Array(hashBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
405
+ }
406
+ async function getSigningKey(secretKey, dateStamp, region, service) {
407
+ const kDate = await hmacSha256(new TextEncoder().encode(`AWS4${secretKey}`), dateStamp);
408
+ const kRegion = await hmacSha256(kDate, region);
409
+ const kService = await hmacSha256(kRegion, service);
410
+ return hmacSha256(kService, "aws4_request");
411
+ }
412
+ async function signRequest(method, url, headers, body, credentials, region, service) {
413
+ const now = new Date;
414
+ const amzDate = now.toISOString().replace(/[:-]|\.\d{3}/g, "");
415
+ const dateStamp = amzDate.slice(0, 8);
416
+ const canonicalUri = url.pathname;
417
+ const canonicalQuerystring = url.search.slice(1);
418
+ const signedHeaders = {
419
+ ...headers,
420
+ host: url.host,
421
+ "x-amz-date": amzDate
422
+ };
423
+ if (credentials.sessionToken) {
424
+ signedHeaders["x-amz-security-token"] = credentials.sessionToken;
425
+ }
426
+ const sortedHeaderKeys = Object.keys(signedHeaders).sort();
427
+ const canonicalHeaders = sortedHeaderKeys.map((key) => `${key.toLowerCase()}:${signedHeaders[key].trim()}`).join(`
428
+ `) + `
429
+ `;
430
+ const signedHeadersStr = sortedHeaderKeys.map((k) => k.toLowerCase()).join(";");
431
+ const payloadHash = await sha256(body);
432
+ const canonicalRequest = [
433
+ method,
434
+ canonicalUri,
435
+ canonicalQuerystring,
436
+ canonicalHeaders,
437
+ signedHeadersStr,
438
+ payloadHash
439
+ ].join(`
440
+ `);
441
+ const canonicalRequestHash = await sha256(canonicalRequest);
442
+ const algorithm = "AWS4-HMAC-SHA256";
443
+ const credentialScope = `${dateStamp}/${region}/${service}/aws4_request`;
444
+ const stringToSign = [
445
+ algorithm,
446
+ amzDate,
447
+ credentialScope,
448
+ canonicalRequestHash
449
+ ].join(`
450
+ `);
451
+ const signingKey = await getSigningKey(credentials.secretAccessKey, dateStamp, region, service);
452
+ const signatureBuffer = await hmacSha256(signingKey, stringToSign);
453
+ const signature = Array.from(new Uint8Array(signatureBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
454
+ const authorization = `${algorithm} Credential=${credentials.accessKeyId}/${credentialScope}, SignedHeaders=${signedHeadersStr}, Signature=${signature}`;
455
+ return {
456
+ ...signedHeaders,
457
+ authorization
458
+ };
459
+ }
460
+
461
+ class DynamoDBClient {
462
+ config;
463
+ endpoint;
464
+ credentials;
465
+ constructor(config) {
466
+ this.config = config;
467
+ this.endpoint = config.endpoint ?? `https://dynamodb.${config.region}.amazonaws.com`;
468
+ this.credentials = config.credentials ?? {
469
+ accessKeyId: process.env.AWS_ACCESS_KEY_ID ?? "",
470
+ secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY ?? "",
471
+ sessionToken: process.env.AWS_SESSION_TOKEN
472
+ };
473
+ if (!this.credentials.accessKeyId || !this.credentials.secretAccessKey) {
474
+ throw new Error("AWS credentials not provided. Set credentials in config or AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY environment variables.");
475
+ }
476
+ }
477
+ async execute(operation, input) {
478
+ const url = new URL(this.endpoint);
479
+ const body = JSON.stringify(input);
480
+ const headers = {
481
+ "content-type": "application/x-amz-json-1.0",
482
+ "x-amz-target": `DynamoDB_20120810.${operation}`
483
+ };
484
+ const signedHeaders = await signRequest("POST", url, headers, body, this.credentials, this.config.region, "dynamodb");
485
+ const response = await fetch(url.toString(), {
486
+ method: "POST",
487
+ headers: signedHeaders,
488
+ body
489
+ });
490
+ if (!response.ok) {
491
+ const errorBody = await response.text();
492
+ let errorMessage;
493
+ try {
494
+ const errorJson = JSON.parse(errorBody);
495
+ errorMessage = errorJson.message ?? errorJson.Message ?? errorBody;
496
+ } catch {
497
+ errorMessage = errorBody;
498
+ }
499
+ throw new Error(`DynamoDB ${operation} failed: ${response.status} - ${errorMessage}`);
500
+ }
501
+ return response.json();
502
+ }
503
+ async query(input) {
504
+ return this.execute("Query", input);
505
+ }
506
+ async scan(input) {
507
+ return this.execute("Scan", input);
508
+ }
509
+ async getItem(input) {
510
+ return this.execute("GetItem", input);
511
+ }
512
+ async putItem(input) {
513
+ return this.execute("PutItem", input);
514
+ }
515
+ async updateItem(input) {
516
+ return this.execute("UpdateItem", input);
517
+ }
518
+ async deleteItem(input) {
519
+ return this.execute("DeleteItem", input);
520
+ }
521
+ async batchGetItem(input) {
522
+ return this.execute("BatchGetItem", input);
523
+ }
524
+ async batchWriteItem(input) {
525
+ return this.execute("BatchWriteItem", input);
526
+ }
527
+ async transactWriteItems(input) {
528
+ return this.execute("TransactWriteItems", input);
529
+ }
530
+ async describeTable(tableName) {
531
+ return this.execute("DescribeTable", { TableName: tableName });
532
+ }
533
+ async createTable(input) {
534
+ return this.execute("CreateTable", input);
535
+ }
536
+ async deleteTable(tableName) {
537
+ return this.execute("DeleteTable", { TableName: tableName });
538
+ }
539
+ async listTables(input) {
540
+ return this.execute("ListTables", input ?? {});
541
+ }
542
+ async updateTable(input) {
543
+ return this.execute("UpdateTable", input);
544
+ }
545
+ async updateTimeToLive(input) {
546
+ return this.execute("UpdateTimeToLive", input);
547
+ }
548
+ async describeTimeToLive(tableName) {
549
+ return this.execute("DescribeTimeToLive", { TableName: tableName });
550
+ }
551
+ }
552
+ function createClient(config) {
553
+ return new DynamoDBClient(config);
554
+ }
555
+
556
+ // src/dynamodb/model.ts
557
+ function configureModels(config) {
558
+ globalConfig = { ...globalConfig, ...config };
559
+ globalClient = null;
560
+ globalDriver = null;
561
+ }
562
+ function getClient() {
563
+ if (!globalClient) {
564
+ globalClient = createClient({
565
+ region: globalConfig.region ?? "us-east-1",
566
+ endpoint: globalConfig.endpoint,
567
+ credentials: globalConfig.credentials
568
+ });
569
+ }
570
+ return globalClient;
571
+ }
572
+ function getDriver() {
573
+ if (!globalDriver) {
574
+ globalDriver = createDynamoDBDriver({
575
+ region: globalConfig.region ?? "us-east-1",
576
+ endpoint: globalConfig.endpoint,
577
+ credentials: globalConfig.credentials
578
+ });
579
+ }
580
+ return globalDriver;
581
+ }
582
+
583
+ class Model {
584
+ static tableName = "";
585
+ static pkAttribute = "pk";
586
+ static skAttribute = "sk";
587
+ static pkPrefix = "";
588
+ static skPrefix = "METADATA";
589
+ static entityTypeAttribute = "_et";
590
+ static keyDelimiter = "#";
591
+ static primaryKey = "id";
592
+ static timestamps = true;
593
+ static createdAtField = "createdAt";
594
+ static updatedAtField = "updatedAt";
595
+ _attributes = {};
596
+ _original = {};
597
+ _exists = false;
598
+ constructor(attributes = {}) {
599
+ this._attributes = { ...attributes };
600
+ this._original = { ...attributes };
601
+ for (const [key, value] of Object.entries(attributes)) {
602
+ this[key] = value;
603
+ }
604
+ }
605
+ static query() {
606
+ return new ModelQueryBuilderImpl(this);
607
+ }
608
+ static async find(id) {
609
+ const ModelClass = this;
610
+ const client = getClient();
611
+ const driver = getDriver();
612
+ const pk = `${ModelClass.pkPrefix}${ModelClass.keyDelimiter}${id}`;
613
+ const sk = ModelClass.skPrefix;
614
+ try {
615
+ const result = await client.getItem({
616
+ TableName: ModelClass.tableName,
617
+ Key: driver.marshall({
618
+ [ModelClass.pkAttribute]: pk,
619
+ [ModelClass.skAttribute]: sk
620
+ })
621
+ });
622
+ if (!result.Item) {
623
+ return null;
624
+ }
625
+ const data = driver.unmarshall(result.Item);
626
+ const instance = new ModelClass(data);
627
+ instance._exists = true;
628
+ return instance;
629
+ } catch (error) {
630
+ console.error("Find error:", error);
631
+ return null;
632
+ }
633
+ }
634
+ static async findOrFail(id) {
635
+ const result = await this.find(id);
636
+ if (!result) {
637
+ throw new Error(`${this.name} not found with id: ${id}`);
638
+ }
639
+ return result;
640
+ }
641
+ static async all() {
642
+ return this.query().get();
643
+ }
644
+ static async create(attributes) {
645
+ const ModelClass = this;
646
+ const client = getClient();
647
+ const driver = getDriver();
648
+ const id = attributes[ModelClass.primaryKey];
649
+ if (!id) {
650
+ throw new Error(`${ModelClass.primaryKey} is required`);
651
+ }
652
+ const pk = `${ModelClass.pkPrefix}${ModelClass.keyDelimiter}${id}`;
653
+ const sk = ModelClass.skPrefix;
654
+ const now = new Date().toISOString();
655
+ const item = {
656
+ ...attributes,
657
+ [ModelClass.pkAttribute]: pk,
658
+ [ModelClass.skAttribute]: sk,
659
+ [ModelClass.entityTypeAttribute]: ModelClass.name
660
+ };
661
+ if (ModelClass.timestamps) {
662
+ item[ModelClass.createdAtField] = now;
663
+ item[ModelClass.updatedAtField] = now;
664
+ }
665
+ await client.putItem({
666
+ TableName: ModelClass.tableName,
667
+ Item: driver.marshall(item),
668
+ ConditionExpression: `attribute_not_exists(${ModelClass.pkAttribute})`
669
+ });
670
+ const instance = new ModelClass(item);
671
+ instance._exists = true;
672
+ return instance;
673
+ }
674
+ static async updateOrCreate(attributes, values) {
675
+ const ModelClass = this;
676
+ const id = attributes[ModelClass.primaryKey];
677
+ const existing = await ModelClass.find(id);
678
+ if (existing) {
679
+ await existing.update(values);
680
+ return existing;
681
+ }
682
+ return ModelClass.create({ ...attributes, ...values });
683
+ }
684
+ static where(attribute, operatorOrValue, value) {
685
+ const builder = this.query();
686
+ return builder.where(attribute, operatorOrValue, value);
687
+ }
688
+ static wherePk(value) {
689
+ const builder = this.query();
690
+ return builder.wherePk(value);
691
+ }
692
+ getKey() {
693
+ const ModelClass = this.constructor;
694
+ return this[ModelClass.primaryKey];
695
+ }
696
+ getAttribute(key) {
697
+ return this._attributes[key];
698
+ }
699
+ setAttribute(key, value) {
700
+ this._attributes[key] = value;
701
+ this[key] = value;
702
+ return this;
703
+ }
704
+ getAttributes() {
705
+ return { ...this._attributes };
706
+ }
707
+ isDirty(attribute) {
708
+ if (attribute) {
709
+ return this._attributes[attribute] !== this._original[attribute];
710
+ }
711
+ return JSON.stringify(this._attributes) !== JSON.stringify(this._original);
712
+ }
713
+ getDirty() {
714
+ const dirty = {};
715
+ for (const [key, value] of Object.entries(this._attributes)) {
716
+ if (value !== this._original[key]) {
717
+ dirty[key] = value;
718
+ }
719
+ }
720
+ return dirty;
721
+ }
722
+ async save() {
723
+ if (this._exists) {
724
+ const dirty = this.getDirty();
725
+ if (Object.keys(dirty).length > 0) {
726
+ await this.update(dirty);
727
+ }
728
+ } else {
729
+ const ModelClass = this.constructor;
730
+ const created = await ModelClass.create(this._attributes);
731
+ this._attributes = created._attributes;
732
+ this._original = { ...this._attributes };
733
+ this._exists = true;
734
+ }
735
+ return this;
736
+ }
737
+ async update(values) {
738
+ const ModelClass = this.constructor;
739
+ const client = getClient();
740
+ const driver = getDriver();
741
+ const id = this.getKey();
742
+ const pk = `${ModelClass.pkPrefix}${ModelClass.keyDelimiter}${id}`;
743
+ const sk = ModelClass.skPrefix;
744
+ const now = new Date().toISOString();
745
+ const updateValues = { ...values };
746
+ if (ModelClass.timestamps) {
747
+ updateValues[ModelClass.updatedAtField] = now;
748
+ }
749
+ const setParts = [];
750
+ const exprNames = {};
751
+ const exprValues = {};
752
+ let idx = 0;
753
+ for (const [attr, value] of Object.entries(updateValues)) {
754
+ const nameKey = `#attr${idx}`;
755
+ const valueKey = `:val${idx}`;
756
+ exprNames[nameKey] = attr;
757
+ exprValues[valueKey] = driver.marshall({ v: value }).v;
758
+ setParts.push(`${nameKey} = ${valueKey}`);
759
+ idx++;
760
+ }
761
+ await client.updateItem({
762
+ TableName: ModelClass.tableName,
763
+ Key: driver.marshall({
764
+ [ModelClass.pkAttribute]: pk,
765
+ [ModelClass.skAttribute]: sk
766
+ }),
767
+ UpdateExpression: `SET ${setParts.join(", ")}`,
768
+ ExpressionAttributeNames: exprNames,
769
+ ExpressionAttributeValues: exprValues
770
+ });
771
+ for (const [key, value] of Object.entries(updateValues)) {
772
+ this._attributes[key] = value;
773
+ this[key] = value;
774
+ }
775
+ this._original = { ...this._attributes };
776
+ return this;
777
+ }
778
+ async delete() {
779
+ const ModelClass = this.constructor;
780
+ const client = getClient();
781
+ const driver = getDriver();
782
+ const id = this.getKey();
783
+ const pk = `${ModelClass.pkPrefix}${ModelClass.keyDelimiter}${id}`;
784
+ const sk = ModelClass.skPrefix;
785
+ await client.deleteItem({
786
+ TableName: ModelClass.tableName,
787
+ Key: driver.marshall({
788
+ [ModelClass.pkAttribute]: pk,
789
+ [ModelClass.skAttribute]: sk
790
+ })
791
+ });
792
+ this._exists = false;
793
+ return true;
794
+ }
795
+ async refresh() {
796
+ const ModelClass = this.constructor;
797
+ const fresh = await ModelClass.find(this.getKey());
798
+ if (fresh) {
799
+ this._attributes = fresh._attributes;
800
+ this._original = { ...this._attributes };
801
+ for (const [key, value] of Object.entries(this._attributes)) {
802
+ this[key] = value;
803
+ }
804
+ }
805
+ return this;
806
+ }
807
+ toObject() {
808
+ const ModelClass = this.constructor;
809
+ const obj = { ...this._attributes };
810
+ delete obj[ModelClass.pkAttribute];
811
+ delete obj[ModelClass.skAttribute];
812
+ delete obj[ModelClass.entityTypeAttribute];
813
+ return obj;
814
+ }
815
+ toJSON() {
816
+ return this.toObject();
817
+ }
818
+ }
819
+
820
+ class ModelQueryBuilderImpl {
821
+ ModelClass;
822
+ _pkValue;
823
+ _skCondition;
824
+ _indexName;
825
+ _filterConditions = [];
826
+ _projectionAttrs = [];
827
+ _limitValue;
828
+ _scanForward = true;
829
+ _consistentReadValue = false;
830
+ _startKey;
831
+ constructor(ModelClass) {
832
+ this.ModelClass = ModelClass;
833
+ }
834
+ wherePk(value) {
835
+ this._pkValue = `${this.ModelClass.pkPrefix}${this.ModelClass.keyDelimiter}${value}`;
836
+ return this;
837
+ }
838
+ where(attribute, operatorOrValue, value) {
839
+ if (value === undefined) {
840
+ this._filterConditions.push({ attribute, operator: "=", value: operatorOrValue });
841
+ } else {
842
+ this._filterConditions.push({ attribute, operator: operatorOrValue, value });
843
+ }
844
+ return this;
845
+ }
846
+ whereIn(attribute, values) {
847
+ this._filterConditions.push({ attribute, operator: "IN", values });
848
+ return this;
849
+ }
850
+ whereBetween(attribute, start, end) {
851
+ this._filterConditions.push({ attribute, operator: "BETWEEN", value: start, values: [start, end] });
852
+ return this;
853
+ }
854
+ whereBeginsWith(attribute, prefix) {
855
+ this._filterConditions.push({ attribute, operator: "begins_with", value: prefix });
856
+ return this;
857
+ }
858
+ whereExists(attribute) {
859
+ this._filterConditions.push({ attribute, operator: "attribute_exists" });
860
+ return this;
861
+ }
862
+ whereNotExists(attribute) {
863
+ this._filterConditions.push({ attribute, operator: "attribute_not_exists" });
864
+ return this;
865
+ }
866
+ orderBy(direction) {
867
+ this._scanForward = direction === "asc";
868
+ return this;
869
+ }
870
+ limit(count) {
871
+ this._limitValue = count;
872
+ return this;
873
+ }
874
+ select(...attributes) {
875
+ this._projectionAttrs.push(...attributes);
876
+ return this;
877
+ }
878
+ index(indexName) {
879
+ this._indexName = indexName;
880
+ return this;
881
+ }
882
+ consistentRead() {
883
+ this._consistentReadValue = true;
884
+ return this;
885
+ }
886
+ startFrom(key) {
887
+ this._startKey = key;
888
+ return this;
889
+ }
890
+ buildRequest() {
891
+ const driver = getDriver();
892
+ const request = {
893
+ TableName: this.ModelClass.tableName
894
+ };
895
+ if (this._indexName) {
896
+ request.IndexName = this._indexName;
897
+ }
898
+ const exprNames = {};
899
+ const exprValues = {};
900
+ let idx = 0;
901
+ const keyConditions = [];
902
+ if (this._pkValue) {
903
+ const nameKey = `#pk${idx}`;
904
+ const valueKey = `:pk${idx}`;
905
+ exprNames[nameKey] = this.ModelClass.pkAttribute;
906
+ exprValues[valueKey] = { S: this._pkValue };
907
+ keyConditions.push(`${nameKey} = ${valueKey}`);
908
+ idx++;
909
+ }
910
+ if (this._skCondition) {
911
+ const nameKey = `#sk${idx}`;
912
+ exprNames[nameKey] = this.ModelClass.skAttribute;
913
+ switch (this._skCondition.type) {
914
+ case "eq": {
915
+ const valueKey = `:sk${idx}`;
916
+ exprValues[valueKey] = { S: this._skCondition.value };
917
+ keyConditions.push(`${nameKey} = ${valueKey}`);
918
+ break;
919
+ }
920
+ case "begins_with": {
921
+ const valueKey = `:sk${idx}`;
922
+ exprValues[valueKey] = { S: this._skCondition.value };
923
+ keyConditions.push(`begins_with(${nameKey}, ${valueKey})`);
924
+ break;
925
+ }
926
+ }
927
+ idx++;
928
+ }
929
+ if (keyConditions.length > 0) {
930
+ request.KeyConditionExpression = keyConditions.join(" AND ");
931
+ }
932
+ if (this._filterConditions.length > 0) {
933
+ const filterParts = [];
934
+ for (const cond of this._filterConditions) {
935
+ const nameKey = `#flt${idx}`;
936
+ exprNames[nameKey] = cond.attribute;
937
+ if (cond.operator === "attribute_exists") {
938
+ filterParts.push(`attribute_exists(${nameKey})`);
939
+ } else if (cond.operator === "attribute_not_exists") {
940
+ filterParts.push(`attribute_not_exists(${nameKey})`);
941
+ } else if (cond.operator === "IN" && cond.values) {
942
+ const valueKeys = cond.values.map((_, i) => `:flt${idx}_${i}`);
943
+ cond.values.forEach((val, i) => {
944
+ exprValues[`:flt${idx}_${i}`] = driver.marshall({ v: val }).v;
945
+ });
946
+ filterParts.push(`${nameKey} IN (${valueKeys.join(", ")})`);
947
+ } else if (cond.operator === "BETWEEN" && cond.values) {
948
+ const valueKey1 = `:flt${idx}a`;
949
+ const valueKey2 = `:flt${idx}b`;
950
+ exprValues[valueKey1] = driver.marshall({ v: cond.values[0] }).v;
951
+ exprValues[valueKey2] = driver.marshall({ v: cond.values[1] }).v;
952
+ filterParts.push(`${nameKey} BETWEEN ${valueKey1} AND ${valueKey2}`);
953
+ } else if (cond.operator === "begins_with") {
954
+ const valueKey = `:flt${idx}`;
955
+ exprValues[valueKey] = driver.marshall({ v: cond.value }).v;
956
+ filterParts.push(`begins_with(${nameKey}, ${valueKey})`);
957
+ } else {
958
+ const valueKey = `:flt${idx}`;
959
+ exprValues[valueKey] = driver.marshall({ v: cond.value }).v;
960
+ filterParts.push(`${nameKey} ${cond.operator} ${valueKey}`);
961
+ }
962
+ idx++;
963
+ }
964
+ request.FilterExpression = filterParts.join(" AND ");
965
+ }
966
+ if (this._projectionAttrs.length > 0) {
967
+ const projParts = [];
968
+ for (const attr of this._projectionAttrs) {
969
+ const nameKey = `#proj${idx}`;
970
+ exprNames[nameKey] = attr;
971
+ projParts.push(nameKey);
972
+ idx++;
973
+ }
974
+ request.ProjectionExpression = projParts.join(", ");
975
+ }
976
+ if (Object.keys(exprNames).length > 0) {
977
+ request.ExpressionAttributeNames = exprNames;
978
+ }
979
+ if (Object.keys(exprValues).length > 0) {
980
+ request.ExpressionAttributeValues = exprValues;
981
+ }
982
+ if (this._limitValue !== undefined) {
983
+ request.Limit = this._limitValue;
984
+ }
985
+ request.ScanIndexForward = this._scanForward;
986
+ if (this._consistentReadValue) {
987
+ request.ConsistentRead = true;
988
+ }
989
+ if (this._startKey) {
990
+ request.ExclusiveStartKey = driver.marshall(this._startKey);
991
+ }
992
+ return request;
993
+ }
994
+ async get() {
995
+ const client = getClient();
996
+ const driver = getDriver();
997
+ const request = this.buildRequest();
998
+ const isQuery = this._pkValue !== undefined;
999
+ const response = isQuery ? await client.query(request) : await client.scan(request);
1000
+ const items = (response.Items ?? []).map((item) => {
1001
+ const data = driver.unmarshall(item);
1002
+ const instance = new this.ModelClass(data);
1003
+ instance._exists = true;
1004
+ return instance;
1005
+ });
1006
+ return items;
1007
+ }
1008
+ async first() {
1009
+ this._limitValue = 1;
1010
+ const results = await this.get();
1011
+ return results[0] ?? null;
1012
+ }
1013
+ async count() {
1014
+ const client = getClient();
1015
+ const request = this.buildRequest();
1016
+ request.Select = "COUNT";
1017
+ const isQuery = this._pkValue !== undefined;
1018
+ const response = isQuery ? await client.query(request) : await client.scan(request);
1019
+ return response.Count ?? 0;
1020
+ }
1021
+ async paginate(pageSize, lastKey) {
1022
+ const driver = getDriver();
1023
+ if (lastKey) {
1024
+ this._startKey = lastKey;
1025
+ }
1026
+ this._limitValue = pageSize;
1027
+ const client = getClient();
1028
+ const request = this.buildRequest();
1029
+ const isQuery = this._pkValue !== undefined;
1030
+ const response = isQuery ? await client.query(request) : await client.scan(request);
1031
+ const items = (response.Items ?? []).map((item) => {
1032
+ const data = driver.unmarshall(item);
1033
+ const instance = new this.ModelClass(data);
1034
+ instance._exists = true;
1035
+ return instance;
1036
+ });
1037
+ return {
1038
+ items,
1039
+ lastKey: response.LastEvaluatedKey ? driver.unmarshall(response.LastEvaluatedKey) : undefined
1040
+ };
1041
+ }
1042
+ }
1043
+ var globalConfig, globalClient = null, globalDriver = null;
1044
+ var init_model = __esm(() => {
1045
+ globalConfig = {
1046
+ region: process.env.AWS_REGION ?? "us-east-1"
1047
+ };
1048
+ });
1049
+
1050
+ // src/dynamodb/migrations.ts
1051
+ function hashTableDefinition(definition) {
1052
+ const normalized = JSON.stringify({
1053
+ tableName: definition.tableName,
1054
+ keySchema: definition.keySchema,
1055
+ attributeDefinitions: [...definition.attributeDefinitions || []].sort((a, b) => a.name.localeCompare(b.name)),
1056
+ globalSecondaryIndexes: [...definition.globalSecondaryIndexes || []].sort((a, b) => a.indexName.localeCompare(b.indexName)),
1057
+ localSecondaryIndexes: [...definition.localSecondaryIndexes || []].sort((a, b) => a.indexName.localeCompare(b.indexName)),
1058
+ billingMode: definition.billingMode,
1059
+ ttlAttribute: definition.ttlAttribute,
1060
+ streamSpecification: definition.streamSpecification
1061
+ });
1062
+ let hash = 0;
1063
+ for (let i = 0;i < normalized.length; i++) {
1064
+ const char = normalized.charCodeAt(i);
1065
+ hash = (hash << 5) - hash + char;
1066
+ hash = hash & hash;
1067
+ }
1068
+ return Math.abs(hash).toString(16).padStart(8, "0");
1069
+ }
1070
+ function extractTableDefinition(model) {
1071
+ const schema = extractModelSchema(model);
1072
+ return convertSchemaToDefinition(schema);
1073
+ }
1074
+ function extractModelSchema(ModelClass) {
1075
+ return {
1076
+ tableName: ModelClass.tableName || "",
1077
+ pkAttribute: ModelClass.pkAttribute || "pk",
1078
+ skAttribute: ModelClass.skAttribute || "sk",
1079
+ pkPrefix: ModelClass.pkPrefix || "",
1080
+ skPrefix: ModelClass.skPrefix || "METADATA",
1081
+ entityTypeAttribute: ModelClass.entityTypeAttribute || "_et",
1082
+ timestamps: ModelClass.timestamps !== false,
1083
+ ttlAttribute: ModelClass.ttlAttribute,
1084
+ gsis: ModelClass.gsis,
1085
+ lsis: ModelClass.lsis,
1086
+ billingMode: ModelClass.billingMode || "PAY_PER_REQUEST",
1087
+ provisionedThroughput: ModelClass.provisionedThroughput,
1088
+ streamEnabled: ModelClass.streamEnabled,
1089
+ streamViewType: ModelClass.streamViewType
1090
+ };
1091
+ }
1092
+ function convertSchemaToDefinition(schema) {
1093
+ const attributeDefinitions = [
1094
+ { name: schema.pkAttribute, type: "S" },
1095
+ { name: schema.skAttribute, type: "S" }
1096
+ ];
1097
+ const gsis = [];
1098
+ if (schema.gsis) {
1099
+ for (const gsi of schema.gsis) {
1100
+ if (!attributeDefinitions.some((a) => a.name === gsi.pkAttribute)) {
1101
+ attributeDefinitions.push({ name: gsi.pkAttribute, type: "S" });
1102
+ }
1103
+ if (gsi.skAttribute && !attributeDefinitions.some((a) => a.name === gsi.skAttribute)) {
1104
+ attributeDefinitions.push({ name: gsi.skAttribute, type: "S" });
1105
+ }
1106
+ gsis.push({
1107
+ indexName: gsi.indexName,
1108
+ keySchema: {
1109
+ partitionKey: gsi.pkAttribute,
1110
+ sortKey: gsi.skAttribute
1111
+ },
1112
+ projection: {
1113
+ type: Array.isArray(gsi.projection) ? "INCLUDE" : gsi.projection || "ALL",
1114
+ nonKeyAttributes: Array.isArray(gsi.projection) ? gsi.projection : undefined
1115
+ },
1116
+ provisionedThroughput: gsi.provisionedThroughput
1117
+ });
1118
+ }
1119
+ }
1120
+ return {
1121
+ tableName: schema.tableName,
1122
+ keySchema: {
1123
+ partitionKey: schema.pkAttribute,
1124
+ sortKey: schema.skAttribute
1125
+ },
1126
+ attributeDefinitions,
1127
+ globalSecondaryIndexes: gsis.length > 0 ? gsis : undefined,
1128
+ billingMode: schema.billingMode,
1129
+ provisionedThroughput: schema.provisionedThroughput,
1130
+ ttlAttribute: schema.ttlAttribute,
1131
+ streamSpecification: schema.streamEnabled ? { enabled: true, viewType: schema.streamViewType || "NEW_AND_OLD_IMAGES" } : undefined
1132
+ };
1133
+ }
1134
+ function buildMigrationPlan(current, target) {
1135
+ const operations = [];
1136
+ const tableName = target.tableName;
1137
+ if (!current) {
1138
+ operations.push({
1139
+ type: "CREATE_TABLE",
1140
+ tableName,
1141
+ details: { definition: target }
1142
+ });
1143
+ } else {
1144
+ const pkChanged = current.keySchema.partitionKey !== target.keySchema.partitionKey;
1145
+ const skChanged = current.keySchema.sortKey !== target.keySchema.sortKey;
1146
+ if (pkChanged || skChanged) {
1147
+ console.warn(`[Migration] Table ${tableName}: Key schema changes require table recreation. ` + `Current: pk=${current.keySchema.partitionKey}, sk=${current.keySchema.sortKey}. ` + `Target: pk=${target.keySchema.partitionKey}, sk=${target.keySchema.sortKey}. ` + `This operation is not supported automatically.`);
1148
+ }
1149
+ const currentGSIs = current.globalSecondaryIndexes || [];
1150
+ const targetGSIs = target.globalSecondaryIndexes || [];
1151
+ const currentGSINames = new Set(currentGSIs.map((g) => g.indexName));
1152
+ const targetGSINames = new Set(targetGSIs.map((g) => g.indexName));
1153
+ for (const gsi of targetGSIs) {
1154
+ if (!currentGSINames.has(gsi.indexName)) {
1155
+ operations.push({
1156
+ type: "ADD_GSI",
1157
+ tableName,
1158
+ details: { gsi }
1159
+ });
1160
+ }
1161
+ }
1162
+ for (const gsi of currentGSIs) {
1163
+ if (!targetGSINames.has(gsi.indexName)) {
1164
+ operations.push({
1165
+ type: "DELETE_GSI",
1166
+ tableName,
1167
+ details: { indexName: gsi.indexName }
1168
+ });
1169
+ }
1170
+ }
1171
+ if (current.billingMode !== target.billingMode && target.billingMode) {
1172
+ operations.push({
1173
+ type: "UPDATE_BILLING_MODE",
1174
+ tableName,
1175
+ details: {
1176
+ billingMode: target.billingMode,
1177
+ provisionedThroughput: target.provisionedThroughput
1178
+ }
1179
+ });
1180
+ }
1181
+ if (current.ttlAttribute !== target.ttlAttribute) {
1182
+ operations.push({
1183
+ type: "UPDATE_TTL",
1184
+ tableName,
1185
+ details: {
1186
+ ttlAttribute: target.ttlAttribute || null,
1187
+ enabled: !!target.ttlAttribute
1188
+ }
1189
+ });
1190
+ }
1191
+ const currentStreamEnabled = current.streamSpecification?.enabled;
1192
+ const targetStreamEnabled = target.streamSpecification?.enabled;
1193
+ if (currentStreamEnabled !== targetStreamEnabled) {
1194
+ if (targetStreamEnabled) {
1195
+ operations.push({
1196
+ type: "ENABLE_STREAM",
1197
+ tableName,
1198
+ details: {
1199
+ viewType: target.streamSpecification?.viewType || "NEW_AND_OLD_IMAGES"
1200
+ }
1201
+ });
1202
+ } else {
1203
+ operations.push({
1204
+ type: "DISABLE_STREAM",
1205
+ tableName,
1206
+ details: {}
1207
+ });
1208
+ }
1209
+ }
1210
+ }
1211
+ return {
1212
+ tableName,
1213
+ operations,
1214
+ timestamp: new Date().toISOString(),
1215
+ hash: hashTableDefinition(target)
1216
+ };
1217
+ }
1218
+ function isDefinitionEqual(a, b) {
1219
+ return hashTableDefinition(a) === hashTableDefinition(b);
1220
+ }
1221
+
1222
+ // src/dynamodb/migration-tracker.ts
1223
+ class DynamoDBMigrationTracker {
1224
+ client;
1225
+ initialized = false;
1226
+ constructor(client) {
1227
+ this.client = client;
1228
+ }
1229
+ async ensureMigrationsTable() {
1230
+ if (this.initialized)
1231
+ return;
1232
+ try {
1233
+ await this.client.describeTable(MIGRATIONS_TABLE);
1234
+ this.initialized = true;
1235
+ } catch (error) {
1236
+ if (error.message?.includes("ResourceNotFoundException") || error.message?.includes("not found")) {
1237
+ console.log(`[Migration] Creating migrations table: ${MIGRATIONS_TABLE}`);
1238
+ await this.client.createTable({
1239
+ TableName: MIGRATIONS_TABLE,
1240
+ KeySchema: [
1241
+ { AttributeName: "pk", KeyType: "HASH" },
1242
+ { AttributeName: "sk", KeyType: "RANGE" }
1243
+ ],
1244
+ AttributeDefinitions: [
1245
+ { AttributeName: "pk", AttributeType: "S" },
1246
+ { AttributeName: "sk", AttributeType: "S" }
1247
+ ],
1248
+ BillingMode: "PAY_PER_REQUEST"
1249
+ });
1250
+ await this.waitForTableActive(MIGRATIONS_TABLE);
1251
+ this.initialized = true;
1252
+ } else {
1253
+ throw error;
1254
+ }
1255
+ }
1256
+ }
1257
+ async waitForTableActive(tableName, maxAttempts = 30) {
1258
+ for (let i = 0;i < maxAttempts; i++) {
1259
+ try {
1260
+ const result = await this.client.describeTable(tableName);
1261
+ if (result.Table?.TableStatus === "ACTIVE") {
1262
+ return;
1263
+ }
1264
+ } catch {}
1265
+ await new Promise((resolve) => setTimeout(resolve, 1000));
1266
+ }
1267
+ throw new Error(`Table ${tableName} did not become active within ${maxAttempts} seconds`);
1268
+ }
1269
+ async getLatestState(tableName) {
1270
+ await this.ensureMigrationsTable();
1271
+ const pk = `${PK_PREFIX}#${tableName}`;
1272
+ const result = await this.client.query({
1273
+ TableName: MIGRATIONS_TABLE,
1274
+ KeyConditionExpression: "pk = :pk",
1275
+ ExpressionAttributeValues: {
1276
+ ":pk": { S: pk }
1277
+ },
1278
+ ScanIndexForward: false,
1279
+ Limit: 1
1280
+ });
1281
+ if (!result.Items || result.Items.length === 0) {
1282
+ return null;
1283
+ }
1284
+ return this.unmarshallState(result.Items[0]);
1285
+ }
1286
+ async getHistory(tableName) {
1287
+ await this.ensureMigrationsTable();
1288
+ const pk = `${PK_PREFIX}#${tableName}`;
1289
+ const result = await this.client.query({
1290
+ TableName: MIGRATIONS_TABLE,
1291
+ KeyConditionExpression: "pk = :pk",
1292
+ ExpressionAttributeValues: {
1293
+ ":pk": { S: pk }
1294
+ },
1295
+ ScanIndexForward: false
1296
+ });
1297
+ if (!result.Items) {
1298
+ return [];
1299
+ }
1300
+ return result.Items.map((item) => this.unmarshallState(item));
1301
+ }
1302
+ async recordMigration(tableName, definition, version) {
1303
+ await this.ensureMigrationsTable();
1304
+ const latest = await this.getLatestState(tableName);
1305
+ const newVersion = version ?? (latest ? latest.version + 1 : 1);
1306
+ const pk = `${PK_PREFIX}#${tableName}`;
1307
+ const sk = `${SK_PREFIX}#${String(newVersion).padStart(6, "0")}`;
1308
+ const state = {
1309
+ tableName,
1310
+ hash: hashTableDefinition(definition),
1311
+ definition,
1312
+ appliedAt: new Date().toISOString(),
1313
+ version: newVersion
1314
+ };
1315
+ await this.client.putItem({
1316
+ TableName: MIGRATIONS_TABLE,
1317
+ Item: this.marshallState(state, pk, sk)
1318
+ });
1319
+ console.log(`[Migration] Recorded migration for ${tableName} (version ${newVersion})`);
1320
+ return state;
1321
+ }
1322
+ async listTrackedTables() {
1323
+ await this.ensureMigrationsTable();
1324
+ const result = await this.client.scan({
1325
+ TableName: MIGRATIONS_TABLE,
1326
+ ProjectionExpression: "pk"
1327
+ });
1328
+ if (!result.Items) {
1329
+ return [];
1330
+ }
1331
+ const tableNames = new Set;
1332
+ for (const item of result.Items) {
1333
+ const pk = item.pk?.S || "";
1334
+ const tableName = pk.replace(`${PK_PREFIX}#`, "");
1335
+ if (tableName) {
1336
+ tableNames.add(tableName);
1337
+ }
1338
+ }
1339
+ return Array.from(tableNames);
1340
+ }
1341
+ async hasMigrations(tableName) {
1342
+ const state = await this.getLatestState(tableName);
1343
+ return state !== null;
1344
+ }
1345
+ async deleteMigrationHistory(tableName) {
1346
+ await this.ensureMigrationsTable();
1347
+ const history = await this.getHistory(tableName);
1348
+ const pk = `${PK_PREFIX}#${tableName}`;
1349
+ for (const state of history) {
1350
+ const sk = `${SK_PREFIX}#${String(state.version).padStart(6, "0")}`;
1351
+ await this.client.deleteItem({
1352
+ TableName: MIGRATIONS_TABLE,
1353
+ Key: {
1354
+ pk: { S: pk },
1355
+ sk: { S: sk }
1356
+ }
1357
+ });
1358
+ }
1359
+ console.log(`[Migration] Deleted migration history for ${tableName}`);
1360
+ }
1361
+ marshallState(state, pk, sk) {
1362
+ return {
1363
+ pk: { S: pk },
1364
+ sk: { S: sk },
1365
+ tableName: { S: state.tableName },
1366
+ hash: { S: state.hash },
1367
+ definition: { S: JSON.stringify(state.definition) },
1368
+ appliedAt: { S: state.appliedAt },
1369
+ version: { N: String(state.version) }
1370
+ };
1371
+ }
1372
+ unmarshallState(item) {
1373
+ return {
1374
+ tableName: item.tableName?.S || "",
1375
+ hash: item.hash?.S || "",
1376
+ definition: JSON.parse(item.definition?.S || "{}"),
1377
+ appliedAt: item.appliedAt?.S || "",
1378
+ version: Number(item.version?.N || 0)
1379
+ };
1380
+ }
1381
+ }
1382
+ var MIGRATIONS_TABLE = "_qb_migrations", PK_PREFIX = "MIGRATION", SK_PREFIX = "VERSION";
1383
+ var init_migration_tracker = () => {};
1384
+
1385
+ // src/dynamodb/migration-driver.ts
1386
+ class DynamoDBMigrationDriver {
1387
+ client;
1388
+ tracker;
1389
+ config;
1390
+ constructor(config) {
1391
+ this.config = config;
1392
+ this.client = createClient({
1393
+ region: config.region,
1394
+ endpoint: config.endpoint,
1395
+ credentials: config.credentials
1396
+ });
1397
+ this.tracker = new DynamoDBMigrationTracker(this.client);
1398
+ }
1399
+ async execute(plan) {
1400
+ const result = {
1401
+ tableName: plan.tableName,
1402
+ success: true,
1403
+ operations: []
1404
+ };
1405
+ if (plan.operations.length === 0) {
1406
+ this.log(`[Migration] No changes needed for ${plan.tableName}`);
1407
+ return result;
1408
+ }
1409
+ this.log(`[Migration] Executing ${plan.operations.length} operations for ${plan.tableName}`);
1410
+ try {
1411
+ for (const op of plan.operations) {
1412
+ await this.executeOperation(op);
1413
+ result.operations.push(op.type);
1414
+ }
1415
+ this.log(`[Migration] Successfully applied ${result.operations.length} operations to ${plan.tableName}`);
1416
+ } catch (error) {
1417
+ result.success = false;
1418
+ result.error = error.message;
1419
+ console.error(`[Migration] Failed to execute migration for ${plan.tableName}:`, error);
1420
+ }
1421
+ return result;
1422
+ }
1423
+ async executeOperation(op) {
1424
+ this.log(`[Migration] Executing ${op.type} on ${op.tableName}`);
1425
+ if (this.config.dryRun) {
1426
+ this.log(`[Migration] DRY RUN: Would execute ${op.type}`, op.details);
1427
+ return;
1428
+ }
1429
+ switch (op.type) {
1430
+ case "CREATE_TABLE":
1431
+ await this.createTable(op.details.definition);
1432
+ break;
1433
+ case "DELETE_TABLE":
1434
+ await this.deleteTable(op.tableName);
1435
+ break;
1436
+ case "ADD_GSI":
1437
+ await this.addGSI(op.tableName, op.details.gsi);
1438
+ break;
1439
+ case "DELETE_GSI":
1440
+ await this.deleteGSI(op.tableName, op.details.indexName);
1441
+ break;
1442
+ case "UPDATE_TTL":
1443
+ await this.updateTTL(op.tableName, op.details.ttlAttribute, op.details.enabled);
1444
+ break;
1445
+ case "UPDATE_BILLING_MODE":
1446
+ await this.updateBillingMode(op.tableName, op.details.billingMode, op.details.provisionedThroughput);
1447
+ break;
1448
+ case "ENABLE_STREAM":
1449
+ await this.enableStream(op.tableName, op.details.viewType);
1450
+ break;
1451
+ case "DISABLE_STREAM":
1452
+ await this.disableStream(op.tableName);
1453
+ break;
1454
+ default:
1455
+ throw new Error(`Unknown migration operation type: ${op.type}`);
1456
+ }
1457
+ }
1458
+ async createTable(definition) {
1459
+ const input = {
1460
+ TableName: definition.tableName,
1461
+ KeySchema: [
1462
+ { AttributeName: definition.keySchema.partitionKey, KeyType: "HASH" }
1463
+ ],
1464
+ AttributeDefinitions: definition.attributeDefinitions.map((a) => ({
1465
+ AttributeName: a.name,
1466
+ AttributeType: a.type
1467
+ })),
1468
+ BillingMode: definition.billingMode || "PAY_PER_REQUEST"
1469
+ };
1470
+ if (definition.keySchema.sortKey) {
1471
+ input.KeySchema.push({
1472
+ AttributeName: definition.keySchema.sortKey,
1473
+ KeyType: "RANGE"
1474
+ });
1475
+ }
1476
+ if (definition.billingMode === "PROVISIONED" && definition.provisionedThroughput) {
1477
+ input.ProvisionedThroughput = {
1478
+ ReadCapacityUnits: definition.provisionedThroughput.readCapacityUnits,
1479
+ WriteCapacityUnits: definition.provisionedThroughput.writeCapacityUnits
1480
+ };
1481
+ }
1482
+ if (definition.globalSecondaryIndexes && definition.globalSecondaryIndexes.length > 0) {
1483
+ input.GlobalSecondaryIndexes = definition.globalSecondaryIndexes.map((gsi) => ({
1484
+ IndexName: gsi.indexName,
1485
+ KeySchema: [
1486
+ { AttributeName: gsi.keySchema.partitionKey, KeyType: "HASH" },
1487
+ ...gsi.keySchema.sortKey ? [{ AttributeName: gsi.keySchema.sortKey, KeyType: "RANGE" }] : []
1488
+ ],
1489
+ Projection: {
1490
+ ProjectionType: gsi.projection.type,
1491
+ ...gsi.projection.nonKeyAttributes ? { NonKeyAttributes: gsi.projection.nonKeyAttributes } : {}
1492
+ },
1493
+ ...gsi.provisionedThroughput ? {
1494
+ ProvisionedThroughput: {
1495
+ ReadCapacityUnits: gsi.provisionedThroughput.readCapacityUnits,
1496
+ WriteCapacityUnits: gsi.provisionedThroughput.writeCapacityUnits
1497
+ }
1498
+ } : {}
1499
+ }));
1500
+ }
1501
+ if (definition.localSecondaryIndexes && definition.localSecondaryIndexes.length > 0) {
1502
+ input.LocalSecondaryIndexes = definition.localSecondaryIndexes.map((lsi) => ({
1503
+ IndexName: lsi.indexName,
1504
+ KeySchema: [
1505
+ { AttributeName: definition.keySchema.partitionKey, KeyType: "HASH" },
1506
+ { AttributeName: lsi.sortKey, KeyType: "RANGE" }
1507
+ ],
1508
+ Projection: {
1509
+ ProjectionType: lsi.projection.type,
1510
+ ...lsi.projection.nonKeyAttributes ? { NonKeyAttributes: lsi.projection.nonKeyAttributes } : {}
1511
+ }
1512
+ }));
1513
+ }
1514
+ if (definition.streamSpecification?.enabled) {
1515
+ input.StreamSpecification = {
1516
+ StreamEnabled: true,
1517
+ StreamViewType: definition.streamSpecification.viewType || "NEW_AND_OLD_IMAGES"
1518
+ };
1519
+ }
1520
+ this.log(`[Migration] Creating table: ${definition.tableName}`);
1521
+ await this.client.createTable(input);
1522
+ await this.waitForTableActive(definition.tableName);
1523
+ if (definition.ttlAttribute) {
1524
+ await this.updateTTL(definition.tableName, definition.ttlAttribute, true);
1525
+ }
1526
+ this.log(`[Migration] Table created: ${definition.tableName}`);
1527
+ }
1528
+ async deleteTable(tableName) {
1529
+ this.log(`[Migration] Deleting table: ${tableName}`);
1530
+ await this.client.deleteTable(tableName);
1531
+ this.log(`[Migration] Table deleted: ${tableName}`);
1532
+ }
1533
+ async addGSI(tableName, gsi) {
1534
+ this.log(`[Migration] Adding GSI ${gsi.indexName} to ${tableName}`);
1535
+ const tableInfo = await this.client.describeTable(tableName);
1536
+ const existingAttrs = new Set(tableInfo.Table?.AttributeDefinitions?.map((a) => a.AttributeName) || []);
1537
+ const newAttrs = [];
1538
+ if (!existingAttrs.has(gsi.keySchema.partitionKey)) {
1539
+ newAttrs.push({ AttributeName: gsi.keySchema.partitionKey, AttributeType: "S" });
1540
+ }
1541
+ if (gsi.keySchema.sortKey && !existingAttrs.has(gsi.keySchema.sortKey)) {
1542
+ newAttrs.push({ AttributeName: gsi.keySchema.sortKey, AttributeType: "S" });
1543
+ }
1544
+ const input = {
1545
+ TableName: tableName,
1546
+ AttributeDefinitions: newAttrs.length > 0 ? newAttrs : undefined,
1547
+ GlobalSecondaryIndexUpdates: [{
1548
+ Create: {
1549
+ IndexName: gsi.indexName,
1550
+ KeySchema: [
1551
+ { AttributeName: gsi.keySchema.partitionKey, KeyType: "HASH" },
1552
+ ...gsi.keySchema.sortKey ? [{ AttributeName: gsi.keySchema.sortKey, KeyType: "RANGE" }] : []
1553
+ ],
1554
+ Projection: {
1555
+ ProjectionType: gsi.projection.type,
1556
+ ...gsi.projection.nonKeyAttributes ? { NonKeyAttributes: gsi.projection.nonKeyAttributes } : {}
1557
+ },
1558
+ ...gsi.provisionedThroughput ? {
1559
+ ProvisionedThroughput: {
1560
+ ReadCapacityUnits: gsi.provisionedThroughput.readCapacityUnits,
1561
+ WriteCapacityUnits: gsi.provisionedThroughput.writeCapacityUnits
1562
+ }
1563
+ } : {}
1564
+ }
1565
+ }]
1566
+ };
1567
+ await this.executeUpdateTable(input);
1568
+ await this.waitForGSIActive(tableName, gsi.indexName);
1569
+ this.log(`[Migration] GSI ${gsi.indexName} added to ${tableName}`);
1570
+ }
1571
+ async deleteGSI(tableName, indexName) {
1572
+ this.log(`[Migration] Deleting GSI ${indexName} from ${tableName}`);
1573
+ const input = {
1574
+ TableName: tableName,
1575
+ GlobalSecondaryIndexUpdates: [{
1576
+ Delete: {
1577
+ IndexName: indexName
1578
+ }
1579
+ }]
1580
+ };
1581
+ await this.executeUpdateTable(input);
1582
+ this.log(`[Migration] GSI ${indexName} deleted from ${tableName}`);
1583
+ }
1584
+ async updateTTL(tableName, ttlAttribute, enabled) {
1585
+ this.log(`[Migration] Updating TTL on ${tableName}: ${enabled ? ttlAttribute : "disabled"}`);
1586
+ const input = {
1587
+ TableName: tableName,
1588
+ TimeToLiveSpecification: {
1589
+ Enabled: enabled,
1590
+ AttributeName: ttlAttribute || "ttl"
1591
+ }
1592
+ };
1593
+ await this.executeUpdateTimeToLive(input);
1594
+ this.log(`[Migration] TTL updated on ${tableName}`);
1595
+ }
1596
+ async updateBillingMode(tableName, billingMode, provisionedThroughput) {
1597
+ this.log(`[Migration] Updating billing mode on ${tableName} to ${billingMode}`);
1598
+ const input = {
1599
+ TableName: tableName,
1600
+ BillingMode: billingMode
1601
+ };
1602
+ if (billingMode === "PROVISIONED" && provisionedThroughput) {
1603
+ input.ProvisionedThroughput = {
1604
+ ReadCapacityUnits: provisionedThroughput.readCapacityUnits,
1605
+ WriteCapacityUnits: provisionedThroughput.writeCapacityUnits
1606
+ };
1607
+ }
1608
+ await this.executeUpdateTable(input);
1609
+ await this.waitForTableActive(tableName);
1610
+ this.log(`[Migration] Billing mode updated on ${tableName}`);
1611
+ }
1612
+ async enableStream(tableName, viewType) {
1613
+ this.log(`[Migration] Enabling stream on ${tableName} with view type ${viewType}`);
1614
+ const input = {
1615
+ TableName: tableName,
1616
+ StreamSpecification: {
1617
+ StreamEnabled: true,
1618
+ StreamViewType: viewType
1619
+ }
1620
+ };
1621
+ await this.executeUpdateTable(input);
1622
+ await this.waitForTableActive(tableName);
1623
+ this.log(`[Migration] Stream enabled on ${tableName}`);
1624
+ }
1625
+ async disableStream(tableName) {
1626
+ this.log(`[Migration] Disabling stream on ${tableName}`);
1627
+ const input = {
1628
+ TableName: tableName,
1629
+ StreamSpecification: {
1630
+ StreamEnabled: false
1631
+ }
1632
+ };
1633
+ await this.executeUpdateTable(input);
1634
+ await this.waitForTableActive(tableName);
1635
+ this.log(`[Migration] Stream disabled on ${tableName}`);
1636
+ }
1637
+ async executeUpdateTable(input) {
1638
+ await this.client.updateTable(input);
1639
+ }
1640
+ async executeUpdateTimeToLive(input) {
1641
+ await this.client.updateTimeToLive(input);
1642
+ }
1643
+ async waitForTableActive(tableName, maxAttempts = 60) {
1644
+ this.log(`[Migration] Waiting for table ${tableName} to become active...`);
1645
+ for (let i = 0;i < maxAttempts; i++) {
1646
+ try {
1647
+ const result = await this.client.describeTable(tableName);
1648
+ const status = result.Table?.TableStatus;
1649
+ if (status === "ACTIVE") {
1650
+ return;
1651
+ }
1652
+ this.log(`[Migration] Table status: ${status}, waiting...`);
1653
+ } catch {}
1654
+ await new Promise((resolve) => setTimeout(resolve, 2000));
1655
+ }
1656
+ throw new Error(`Table ${tableName} did not become active within ${maxAttempts * 2} seconds`);
1657
+ }
1658
+ async waitForGSIActive(tableName, indexName, maxAttempts = 120) {
1659
+ this.log(`[Migration] Waiting for GSI ${indexName} to become active...`);
1660
+ for (let i = 0;i < maxAttempts; i++) {
1661
+ try {
1662
+ const result = await this.client.describeTable(tableName);
1663
+ const gsi = result.Table?.GlobalSecondaryIndexes?.find((g) => g.IndexName === indexName);
1664
+ if (gsi?.IndexStatus === "ACTIVE") {
1665
+ return;
1666
+ }
1667
+ const status = gsi?.IndexStatus || "CREATING";
1668
+ this.log(`[Migration] GSI status: ${status}, waiting...`);
1669
+ } catch {}
1670
+ await new Promise((resolve) => setTimeout(resolve, 5000));
1671
+ }
1672
+ throw new Error(`GSI ${indexName} did not become active within ${maxAttempts * 5} seconds`);
1673
+ }
1674
+ async migrateModel(ModelClass) {
1675
+ const definition = extractTableDefinition(ModelClass);
1676
+ let currentDefinition = null;
1677
+ try {
1678
+ const tableInfo = await this.client.describeTable(definition.tableName);
1679
+ if (tableInfo.Table) {
1680
+ currentDefinition = this.tableInfoToDefinition(tableInfo.Table);
1681
+ }
1682
+ } catch (error) {
1683
+ if (!error.message?.includes("ResourceNotFoundException") && !error.message?.includes("not found")) {
1684
+ throw error;
1685
+ }
1686
+ }
1687
+ const plan = buildMigrationPlan(currentDefinition, definition);
1688
+ const result = await this.execute(plan);
1689
+ if (result.success && !this.config.dryRun) {
1690
+ result.state = await this.tracker.recordMigration(definition.tableName, definition);
1691
+ }
1692
+ return result;
1693
+ }
1694
+ async migrateModels(models) {
1695
+ const results = [];
1696
+ for (const ModelClass of models) {
1697
+ try {
1698
+ const result = await this.migrateModel(ModelClass);
1699
+ results.push(result);
1700
+ } catch (error) {
1701
+ results.push({
1702
+ tableName: ModelClass.tableName || "unknown",
1703
+ success: false,
1704
+ operations: [],
1705
+ error: error.message
1706
+ });
1707
+ }
1708
+ }
1709
+ return results;
1710
+ }
1711
+ async getStatus() {
1712
+ const status = new Map;
1713
+ const tables = await this.tracker.listTrackedTables();
1714
+ for (const tableName of tables) {
1715
+ const state = await this.tracker.getLatestState(tableName);
1716
+ status.set(tableName, state);
1717
+ }
1718
+ return status;
1719
+ }
1720
+ tableInfoToDefinition(tableInfo) {
1721
+ const keySchema = {
1722
+ partitionKey: "",
1723
+ sortKey: undefined
1724
+ };
1725
+ for (const key of tableInfo.KeySchema || []) {
1726
+ if (key.KeyType === "HASH") {
1727
+ keySchema.partitionKey = key.AttributeName;
1728
+ } else if (key.KeyType === "RANGE") {
1729
+ keySchema.sortKey = key.AttributeName;
1730
+ }
1731
+ }
1732
+ const attributeDefinitions = (tableInfo.AttributeDefinitions || []).map((a) => ({
1733
+ name: a.AttributeName,
1734
+ type: a.AttributeType
1735
+ }));
1736
+ const gsis = (tableInfo.GlobalSecondaryIndexes || []).map((gsi) => {
1737
+ const gsiKeySchema = {
1738
+ partitionKey: "",
1739
+ sortKey: undefined
1740
+ };
1741
+ for (const key of gsi.KeySchema || []) {
1742
+ if (key.KeyType === "HASH") {
1743
+ gsiKeySchema.partitionKey = key.AttributeName;
1744
+ } else if (key.KeyType === "RANGE") {
1745
+ gsiKeySchema.sortKey = key.AttributeName;
1746
+ }
1747
+ }
1748
+ return {
1749
+ indexName: gsi.IndexName,
1750
+ keySchema: gsiKeySchema,
1751
+ projection: {
1752
+ type: gsi.Projection?.ProjectionType || "ALL",
1753
+ nonKeyAttributes: gsi.Projection?.NonKeyAttributes
1754
+ },
1755
+ provisionedThroughput: gsi.ProvisionedThroughput ? {
1756
+ readCapacityUnits: gsi.ProvisionedThroughput.ReadCapacityUnits,
1757
+ writeCapacityUnits: gsi.ProvisionedThroughput.WriteCapacityUnits
1758
+ } : undefined
1759
+ };
1760
+ });
1761
+ return {
1762
+ tableName: tableInfo.TableName,
1763
+ keySchema,
1764
+ attributeDefinitions,
1765
+ globalSecondaryIndexes: gsis.length > 0 ? gsis : undefined,
1766
+ billingMode: tableInfo.BillingModeSummary?.BillingMode || "PAY_PER_REQUEST",
1767
+ provisionedThroughput: tableInfo.ProvisionedThroughput ? {
1768
+ readCapacityUnits: tableInfo.ProvisionedThroughput.ReadCapacityUnits,
1769
+ writeCapacityUnits: tableInfo.ProvisionedThroughput.WriteCapacityUnits
1770
+ } : undefined,
1771
+ ttlAttribute: tableInfo.TimeToLiveDescription?.AttributeName,
1772
+ streamSpecification: tableInfo.StreamSpecification ? {
1773
+ enabled: tableInfo.StreamSpecification.StreamEnabled,
1774
+ viewType: tableInfo.StreamSpecification.StreamViewType
1775
+ } : undefined
1776
+ };
1777
+ }
1778
+ log(...args) {
1779
+ if (this.config.verbose || this.config.dryRun) {
1780
+ console.log(...args);
1781
+ }
1782
+ }
1783
+ }
1784
+ function createMigrationDriver(config) {
1785
+ return new DynamoDBMigrationDriver(config);
1786
+ }
1787
+ async function migrateModels(models, config) {
1788
+ const driver = createMigrationDriver(config);
1789
+ return driver.migrateModels(models);
1790
+ }
1791
+ var init_migration_driver = __esm(() => {
1792
+ init_migration_tracker();
1793
+ });
1794
+
1795
+ // src/dynamodb/index.ts
1796
+ class EntityQueryBuilder {
1797
+ driver;
1798
+ client;
1799
+ tableName;
1800
+ pkAttribute;
1801
+ skAttribute;
1802
+ entityTypeAttr;
1803
+ delimiter;
1804
+ _entityType;
1805
+ _pkValue;
1806
+ _skCondition;
1807
+ _indexName;
1808
+ _projectionAttrs = [];
1809
+ _filterConditions = [];
1810
+ _limitValue;
1811
+ _scanForward = true;
1812
+ _consistentRead = false;
1813
+ _startKey;
1814
+ constructor(driver, client, tableName, config) {
1815
+ this.driver = driver;
1816
+ this.client = client;
1817
+ this.tableName = tableName;
1818
+ this.pkAttribute = config.pkAttribute;
1819
+ this.skAttribute = config.skAttribute;
1820
+ this.entityTypeAttr = config.entityTypeAttribute;
1821
+ this.delimiter = config.keyDelimiter;
1822
+ }
1823
+ entity(entityType) {
1824
+ this._entityType = entityType;
1825
+ return this;
1826
+ }
1827
+ pk(value) {
1828
+ this._pkValue = value;
1829
+ return this;
1830
+ }
1831
+ get sk() {
1832
+ const self = this;
1833
+ return {
1834
+ equals(value) {
1835
+ self._skCondition = { type: "eq", value };
1836
+ return self;
1837
+ },
1838
+ beginsWith(prefix) {
1839
+ self._skCondition = { type: "begins_with", value: prefix };
1840
+ return self;
1841
+ },
1842
+ between(start, end) {
1843
+ self._skCondition = { type: "between", value: start, value2: end };
1844
+ return self;
1845
+ },
1846
+ lt(value) {
1847
+ self._skCondition = { type: "lt", value };
1848
+ return self;
1849
+ },
1850
+ lte(value) {
1851
+ self._skCondition = { type: "lte", value };
1852
+ return self;
1853
+ },
1854
+ gt(value) {
1855
+ self._skCondition = { type: "gt", value };
1856
+ return self;
1857
+ },
1858
+ gte(value) {
1859
+ self._skCondition = { type: "gte", value };
1860
+ return self;
1861
+ }
1862
+ };
1863
+ }
1864
+ index(indexName) {
1865
+ this._indexName = indexName;
1866
+ return this;
1867
+ }
1868
+ project(...attributes) {
1869
+ this._projectionAttrs.push(...attributes);
1870
+ return this;
1871
+ }
1872
+ filter(attribute, operator, value) {
1873
+ this._filterConditions.push({ attribute, operator, value });
1874
+ return this;
1875
+ }
1876
+ where(attribute, value) {
1877
+ return this.filter(attribute, "=", value);
1878
+ }
1879
+ whereIn(attribute, values) {
1880
+ this._filterConditions.push({ attribute, operator: "IN", values });
1881
+ return this;
1882
+ }
1883
+ limit(count) {
1884
+ this._limitValue = count;
1885
+ return this;
1886
+ }
1887
+ asc() {
1888
+ this._scanForward = true;
1889
+ return this;
1890
+ }
1891
+ desc() {
1892
+ this._scanForward = false;
1893
+ return this;
1894
+ }
1895
+ consistent() {
1896
+ this._consistentRead = true;
1897
+ return this;
1898
+ }
1899
+ startFrom(key) {
1900
+ this._startKey = key;
1901
+ return this;
1902
+ }
1903
+ toRequest() {
1904
+ const request = {
1905
+ TableName: this.tableName
1906
+ };
1907
+ if (this._indexName) {
1908
+ request.IndexName = this._indexName;
1909
+ }
1910
+ const keyConditions = [];
1911
+ const exprNames = {};
1912
+ const exprValues = {};
1913
+ let idx = 0;
1914
+ if (this._pkValue) {
1915
+ const nameKey = `#pk${idx}`;
1916
+ const valueKey = `:pk${idx}`;
1917
+ exprNames[nameKey] = this.pkAttribute;
1918
+ exprValues[valueKey] = { S: this._pkValue };
1919
+ keyConditions.push(`${nameKey} = ${valueKey}`);
1920
+ idx++;
1921
+ }
1922
+ if (this._skCondition) {
1923
+ const nameKey = `#sk${idx}`;
1924
+ exprNames[nameKey] = this.skAttribute;
1925
+ switch (this._skCondition.type) {
1926
+ case "eq": {
1927
+ const valueKey = `:sk${idx}`;
1928
+ exprValues[valueKey] = { S: this._skCondition.value };
1929
+ keyConditions.push(`${nameKey} = ${valueKey}`);
1930
+ break;
1931
+ }
1932
+ case "begins_with": {
1933
+ const valueKey = `:sk${idx}`;
1934
+ exprValues[valueKey] = { S: this._skCondition.value };
1935
+ keyConditions.push(`begins_with(${nameKey}, ${valueKey})`);
1936
+ break;
1937
+ }
1938
+ case "between": {
1939
+ const valueKey1 = `:sk${idx}a`;
1940
+ const valueKey2 = `:sk${idx}b`;
1941
+ exprValues[valueKey1] = { S: this._skCondition.value };
1942
+ exprValues[valueKey2] = { S: this._skCondition.value2 };
1943
+ keyConditions.push(`${nameKey} BETWEEN ${valueKey1} AND ${valueKey2}`);
1944
+ break;
1945
+ }
1946
+ case "lt": {
1947
+ const valueKey = `:sk${idx}`;
1948
+ exprValues[valueKey] = { S: this._skCondition.value };
1949
+ keyConditions.push(`${nameKey} < ${valueKey}`);
1950
+ break;
1951
+ }
1952
+ case "lte": {
1953
+ const valueKey = `:sk${idx}`;
1954
+ exprValues[valueKey] = { S: this._skCondition.value };
1955
+ keyConditions.push(`${nameKey} <= ${valueKey}`);
1956
+ break;
1957
+ }
1958
+ case "gt": {
1959
+ const valueKey = `:sk${idx}`;
1960
+ exprValues[valueKey] = { S: this._skCondition.value };
1961
+ keyConditions.push(`${nameKey} > ${valueKey}`);
1962
+ break;
1963
+ }
1964
+ case "gte": {
1965
+ const valueKey = `:sk${idx}`;
1966
+ exprValues[valueKey] = { S: this._skCondition.value };
1967
+ keyConditions.push(`${nameKey} >= ${valueKey}`);
1968
+ break;
1969
+ }
1970
+ }
1971
+ idx++;
1972
+ }
1973
+ if (keyConditions.length > 0) {
1974
+ request.KeyConditionExpression = keyConditions.join(" AND ");
1975
+ }
1976
+ if (this._filterConditions.length > 0) {
1977
+ const filterParts = [];
1978
+ for (const cond of this._filterConditions) {
1979
+ const nameKey = `#flt${idx}`;
1980
+ exprNames[nameKey] = cond.attribute;
1981
+ if (cond.operator === "IN" && cond.values) {
1982
+ const valueKeys = cond.values.map((_, i) => `:flt${idx}_${i}`);
1983
+ cond.values.forEach((val, i) => {
1984
+ exprValues[`:flt${idx}_${i}`] = this.driver.marshall({ v: val }).v;
1985
+ });
1986
+ filterParts.push(`${nameKey} IN (${valueKeys.join(", ")})`);
1987
+ } else {
1988
+ const valueKey = `:flt${idx}`;
1989
+ exprValues[valueKey] = this.driver.marshall({ v: cond.value }).v;
1990
+ filterParts.push(`${nameKey} ${cond.operator} ${valueKey}`);
1991
+ }
1992
+ idx++;
1993
+ }
1994
+ request.FilterExpression = filterParts.join(" AND ");
1995
+ }
1996
+ if (this._projectionAttrs.length > 0) {
1997
+ const projParts = [];
1998
+ for (const attr of this._projectionAttrs) {
1999
+ const nameKey = `#proj${idx}`;
2000
+ exprNames[nameKey] = attr;
2001
+ projParts.push(nameKey);
2002
+ idx++;
2003
+ }
2004
+ request.ProjectionExpression = projParts.join(", ");
2005
+ }
2006
+ if (Object.keys(exprNames).length > 0) {
2007
+ request.ExpressionAttributeNames = exprNames;
2008
+ }
2009
+ if (Object.keys(exprValues).length > 0) {
2010
+ request.ExpressionAttributeValues = exprValues;
2011
+ }
2012
+ if (this._limitValue !== undefined) {
2013
+ request.Limit = this._limitValue;
2014
+ }
2015
+ request.ScanIndexForward = this._scanForward;
2016
+ if (this._consistentRead) {
2017
+ request.ConsistentRead = true;
2018
+ }
2019
+ if (this._startKey) {
2020
+ request.ExclusiveStartKey = this.driver.marshall(this._startKey);
2021
+ }
2022
+ return request;
2023
+ }
2024
+ async get() {
2025
+ if (!this.client) {
2026
+ throw new Error("DynamoDB client not configured. Call dynamo.connection() first.");
2027
+ }
2028
+ const request = this.toRequest();
2029
+ const isQuery = this._pkValue !== undefined;
2030
+ const response = isQuery ? await this.client.query(request) : await this.client.scan(request);
2031
+ return (response.Items ?? []).map((item) => this.driver.unmarshall(item));
2032
+ }
2033
+ async first() {
2034
+ this._limitValue = 1;
2035
+ const results = await this.get();
2036
+ return results[0];
2037
+ }
2038
+ async getAll() {
2039
+ const allItems = [];
2040
+ let lastKey;
2041
+ do {
2042
+ if (lastKey) {
2043
+ this._startKey = lastKey;
2044
+ }
2045
+ const request = this.toRequest();
2046
+ const isQuery = this._pkValue !== undefined;
2047
+ const response = isQuery ? await this.client.query(request) : await this.client.scan(request);
2048
+ const items = (response.Items ?? []).map((item) => this.driver.unmarshall(item));
2049
+ allItems.push(...items);
2050
+ lastKey = response.LastEvaluatedKey ? this.driver.unmarshall(response.LastEvaluatedKey) : undefined;
2051
+ } while (lastKey);
2052
+ return allItems;
2053
+ }
2054
+ async count() {
2055
+ if (!this.client) {
2056
+ throw new Error("DynamoDB client not configured. Call dynamo.connection() first.");
2057
+ }
2058
+ const request = this.toRequest();
2059
+ request.Select = "COUNT";
2060
+ const isQuery = this._pkValue !== undefined;
2061
+ const response = isQuery ? await this.client.query(request) : await this.client.scan(request);
2062
+ return response.Count ?? 0;
2063
+ }
2064
+ }
2065
+
2066
+ class DynamoClient {
2067
+ driver;
2068
+ client;
2069
+ tableName = "";
2070
+ pkAttribute = "pk";
2071
+ skAttribute = "sk";
2072
+ entityTypeAttr = "_et";
2073
+ delimiter = "#";
2074
+ entityMappings = new Map;
2075
+ connection(config) {
2076
+ const driverConfig = {
2077
+ region: config.region,
2078
+ tableName: config.table,
2079
+ endpoint: config.endpoint,
2080
+ credentials: config.credentials
2081
+ };
2082
+ this.driver = createDynamoDBDriver(driverConfig);
2083
+ this.tableName = config.table;
2084
+ this.pkAttribute = config.pkAttribute ?? "pk";
2085
+ this.skAttribute = config.skAttribute ?? "sk";
2086
+ this.entityTypeAttr = config.entityTypeAttribute ?? "_et";
2087
+ this.delimiter = config.keyDelimiter ?? "#";
2088
+ return this;
2089
+ }
2090
+ setClient(client) {
2091
+ this.client = client;
2092
+ return this;
2093
+ }
2094
+ registerEntity(mapping) {
2095
+ this.entityMappings.set(mapping.entityType, mapping);
2096
+ if (this.driver) {
2097
+ this.driver.registerEntity(mapping);
2098
+ }
2099
+ return this;
2100
+ }
2101
+ entity(entityType) {
2102
+ if (!this.driver) {
2103
+ throw new Error("DynamoDB not configured. Call dynamo.connection() first.");
2104
+ }
2105
+ const builder = new EntityQueryBuilder(this.driver, this.client, this.tableName, {
2106
+ pkAttribute: this.pkAttribute,
2107
+ skAttribute: this.skAttribute,
2108
+ entityTypeAttribute: this.entityTypeAttr,
2109
+ keyDelimiter: this.delimiter
2110
+ });
2111
+ return builder.entity(entityType);
2112
+ }
2113
+ async batchWrite(operations) {
2114
+ if (!this.client) {
2115
+ throw new Error("DynamoDB client not configured. Call setClient() first.");
2116
+ }
2117
+ if (!this.driver) {
2118
+ throw new Error("DynamoDB not configured. Call dynamo.connection() first.");
2119
+ }
2120
+ const requestItems = [];
2121
+ for (const op of operations) {
2122
+ if (op.put) {
2123
+ const item = {
2124
+ ...op.put.item,
2125
+ [this.entityTypeAttr]: op.put.entity
2126
+ };
2127
+ requestItems.push({
2128
+ PutRequest: {
2129
+ Item: this.driver.marshall(item)
2130
+ }
2131
+ });
2132
+ } else if (op.delete) {
2133
+ requestItems.push({
2134
+ DeleteRequest: {
2135
+ Key: this.driver.marshall({
2136
+ [this.pkAttribute]: op.delete.pk,
2137
+ [this.skAttribute]: op.delete.sk
2138
+ })
2139
+ }
2140
+ });
2141
+ }
2142
+ }
2143
+ if (requestItems.length > 0) {
2144
+ await this.client.batchWriteItem({
2145
+ RequestItems: {
2146
+ [this.tableName]: requestItems
2147
+ }
2148
+ });
2149
+ }
2150
+ }
2151
+ async transactWrite(operations) {
2152
+ if (!this.client) {
2153
+ throw new Error("DynamoDB client not configured. Call setClient() first.");
2154
+ }
2155
+ if (!this.driver) {
2156
+ throw new Error("DynamoDB not configured. Call dynamo.connection() first.");
2157
+ }
2158
+ const transactItems = [];
2159
+ for (const op of operations) {
2160
+ if (op.put) {
2161
+ const item = {
2162
+ ...op.put.item,
2163
+ [this.entityTypeAttr]: op.put.entity
2164
+ };
2165
+ const transactItem = {
2166
+ Put: {
2167
+ TableName: this.tableName,
2168
+ Item: this.driver.marshall(item)
2169
+ }
2170
+ };
2171
+ if (op.put.condition) {
2172
+ transactItem.Put.ConditionExpression = op.put.condition;
2173
+ }
2174
+ transactItems.push(transactItem);
2175
+ } else if (op.update) {
2176
+ const key = {
2177
+ [this.pkAttribute]: op.update.pk
2178
+ };
2179
+ if (op.update.sk) {
2180
+ key[this.skAttribute] = op.update.sk;
2181
+ }
2182
+ const updateParts = [];
2183
+ const exprNames = {};
2184
+ const exprValues = {};
2185
+ let idx = 0;
2186
+ if (op.update.set) {
2187
+ const setParts = [];
2188
+ for (const [attr, value] of Object.entries(op.update.set)) {
2189
+ const nameKey = `#set${idx}`;
2190
+ const valueKey = `:set${idx}`;
2191
+ exprNames[nameKey] = attr;
2192
+ exprValues[valueKey] = this.driver.marshall({ v: value }).v;
2193
+ setParts.push(`${nameKey} = ${valueKey}`);
2194
+ idx++;
2195
+ }
2196
+ if (setParts.length > 0) {
2197
+ updateParts.push(`SET ${setParts.join(", ")}`);
2198
+ }
2199
+ }
2200
+ if (op.update.add) {
2201
+ const addParts = [];
2202
+ for (const [attr, value] of Object.entries(op.update.add)) {
2203
+ const nameKey = `#add${idx}`;
2204
+ const valueKey = `:add${idx}`;
2205
+ exprNames[nameKey] = attr;
2206
+ exprValues[valueKey] = { N: String(value) };
2207
+ addParts.push(`${nameKey} ${valueKey}`);
2208
+ idx++;
2209
+ }
2210
+ if (addParts.length > 0) {
2211
+ updateParts.push(`ADD ${addParts.join(", ")}`);
2212
+ }
2213
+ }
2214
+ if (op.update.remove && op.update.remove.length > 0) {
2215
+ const removeParts = [];
2216
+ for (const attr of op.update.remove) {
2217
+ const nameKey = `#rem${idx}`;
2218
+ exprNames[nameKey] = attr;
2219
+ removeParts.push(nameKey);
2220
+ idx++;
2221
+ }
2222
+ updateParts.push(`REMOVE ${removeParts.join(", ")}`);
2223
+ }
2224
+ transactItems.push({
2225
+ Update: {
2226
+ TableName: this.tableName,
2227
+ Key: this.driver.marshall(key),
2228
+ UpdateExpression: updateParts.join(" "),
2229
+ ExpressionAttributeNames: exprNames,
2230
+ ExpressionAttributeValues: exprValues
2231
+ }
2232
+ });
2233
+ } else if (op.delete) {
2234
+ const transactItem = {
2235
+ Delete: {
2236
+ TableName: this.tableName,
2237
+ Key: this.driver.marshall({
2238
+ [this.pkAttribute]: op.delete.pk,
2239
+ [this.skAttribute]: op.delete.sk
2240
+ })
2241
+ }
2242
+ };
2243
+ if (op.delete.condition) {
2244
+ transactItem.Delete.ConditionExpression = op.delete.condition;
2245
+ }
2246
+ transactItems.push(transactItem);
2247
+ } else if (op.conditionCheck) {
2248
+ transactItems.push({
2249
+ ConditionCheck: {
2250
+ TableName: this.tableName,
2251
+ Key: this.driver.marshall({
2252
+ [this.pkAttribute]: op.conditionCheck.pk,
2253
+ [this.skAttribute]: op.conditionCheck.sk
2254
+ }),
2255
+ ConditionExpression: op.conditionCheck.condition
2256
+ }
2257
+ });
2258
+ }
2259
+ }
2260
+ if (transactItems.length > 0) {
2261
+ await this.client.transactWriteItems({
2262
+ TransactItems: transactItems
2263
+ });
2264
+ }
2265
+ }
2266
+ getDriver() {
2267
+ return this.driver;
2268
+ }
2269
+ }
2270
+ function createDynamo() {
2271
+ return new DynamoClient;
2272
+ }
2273
+ var dynamo;
2274
+ var init_dynamodb = __esm(() => {
2275
+ init_model();
2276
+ init_migration_driver();
2277
+ init_migration_tracker();
2278
+ dynamo = new DynamoClient;
2279
+ });
2280
+ init_dynamodb();
2281
+
2282
+ export {
2283
+ migrateModels,
2284
+ isDefinitionEqual,
2285
+ hashTableDefinition,
2286
+ extractTableDefinition,
2287
+ extractModelSchema,
2288
+ dynamo,
2289
+ createMigrationDriver,
2290
+ createDynamo,
2291
+ createClient,
2292
+ convertSchemaToDefinition,
2293
+ configureModels,
2294
+ buildMigrationPlan as buildDynamoDBMigrationPlan,
2295
+ Model,
2296
+ MIGRATIONS_TABLE,
2297
+ EntityQueryBuilder,
2298
+ DynamoDBMigrationTracker,
2299
+ DynamoDBMigrationDriver,
2300
+ DynamoDBClient
2301
+ };