bun-query-builder 0.1.11 → 0.1.13

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