@awsless/dynamodb-server 0.0.12 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,88 +1,3033 @@
1
- "use strict";
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- var __export = (target, all) => {
7
- for (var name in all)
8
- __defProp(target, name, { get: all[name], enumerable: true });
9
- };
10
- var __copyProps = (to, from, except, desc) => {
11
- if (from && typeof from === "object" || typeof from === "function") {
12
- for (let key of __getOwnPropNames(from))
13
- if (!__hasOwnProp.call(to, key) && key !== except)
14
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
- }
16
- return to;
17
- };
18
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
-
20
- // src/index.ts
21
- var src_exports = {};
22
- __export(src_exports, {
23
- DynamoDBServer: () => DynamoDBServer
24
- });
25
- module.exports = __toCommonJS(src_exports);
26
- var import_client_dynamodb = require("@aws-sdk/client-dynamodb");
27
- var import_lib_dynamodb = require("@aws-sdk/lib-dynamodb");
28
- var import_url_parser = require("@aws-sdk/url-parser");
29
- var import_sleep_await = require("sleep-await");
30
- var import_dynamo_db_local = require("dynamo-db-local");
31
- var DynamoDBServer = class {
1
+ // src/dynamodb-server.ts
2
+ import { DynamoDBClient as DynamoDBClient2 } from "@aws-sdk/client-dynamodb";
3
+ import { DynamoDBDocumentClient } from "@aws-sdk/lib-dynamodb";
4
+
5
+ // src/clock.ts
6
+ var VirtualClock = class {
7
+ offset = 0;
8
+ now() {
9
+ return Date.now() + this.offset;
10
+ }
11
+ nowInSeconds() {
12
+ return Math.floor(this.now() / 1e3);
13
+ }
14
+ advance(ms) {
15
+ this.offset += ms;
16
+ }
17
+ set(timestamp) {
18
+ this.offset = timestamp - Date.now();
19
+ }
20
+ reset() {
21
+ this.offset = 0;
22
+ }
23
+ };
24
+
25
+ // src/java-server.ts
26
+ import { DynamoDBClient, ListTablesCommand } from "@aws-sdk/client-dynamodb";
27
+ import { spawn } from "dynamo-db-local";
28
+ function sleep(ms) {
29
+ return new Promise((resolve) => setTimeout(resolve, ms));
30
+ }
31
+ function createJavaServer(port, region) {
32
+ const childProcess = spawn({ port });
33
+ const getClient = () => new DynamoDBClient({
34
+ endpoint: `http://localhost:${port}`,
35
+ region,
36
+ credentials: {
37
+ accessKeyId: "fake",
38
+ secretAccessKey: "fake"
39
+ }
40
+ });
41
+ const ping = async () => {
42
+ const client = getClient();
43
+ try {
44
+ const response = await client.send(new ListTablesCommand({}));
45
+ return Array.isArray(response.TableNames);
46
+ } catch {
47
+ return false;
48
+ }
49
+ };
50
+ const wait = async (times = 10) => {
51
+ while (times--) {
52
+ if (await ping()) {
53
+ return;
54
+ }
55
+ await sleep(100 * (times + 1));
56
+ }
57
+ throw new Error("DynamoDB server is unavailable");
58
+ };
59
+ return {
60
+ port,
61
+ stop: async () => {
62
+ childProcess.kill();
63
+ },
64
+ ping,
65
+ wait
66
+ };
67
+ }
68
+
69
+ // src/server.ts
70
+ import {
71
+ createServer as createHttpServer
72
+ } from "node:http";
73
+
74
+ // src/errors/index.ts
75
+ var DynamoDBError = class extends Error {
76
+ __type;
77
+ statusCode;
78
+ constructor(type, message, statusCode = 400) {
79
+ super(message);
80
+ this.__type = `com.amazonaws.dynamodb.v20120810#${type}`;
81
+ this.statusCode = statusCode;
82
+ }
83
+ toJSON() {
84
+ return {
85
+ __type: this.__type,
86
+ message: this.message
87
+ };
88
+ }
89
+ };
90
+ var ValidationException = class extends DynamoDBError {
91
+ constructor(message) {
92
+ super("ValidationException", message, 400);
93
+ }
94
+ };
95
+ var ResourceNotFoundException = class extends DynamoDBError {
96
+ constructor(message) {
97
+ super("ResourceNotFoundException", message, 400);
98
+ }
99
+ };
100
+ var ResourceInUseException = class extends DynamoDBError {
101
+ constructor(message) {
102
+ super("ResourceInUseException", message, 400);
103
+ }
104
+ };
105
+ var ConditionalCheckFailedException = class extends DynamoDBError {
106
+ Item;
107
+ constructor(message = "The conditional request failed", item) {
108
+ super("ConditionalCheckFailedException", message, 400);
109
+ this.Item = item;
110
+ }
111
+ toJSON() {
112
+ return {
113
+ __type: this.__type,
114
+ message: this.message,
115
+ ...this.Item && { Item: this.Item }
116
+ };
117
+ }
118
+ };
119
+ var TransactionCanceledException = class extends DynamoDBError {
120
+ CancellationReasons;
121
+ constructor(message, reasons) {
122
+ super("TransactionCanceledException", message, 400);
123
+ this.CancellationReasons = reasons;
124
+ }
125
+ toJSON() {
126
+ return {
127
+ __type: this.__type,
128
+ message: this.message,
129
+ CancellationReasons: this.CancellationReasons
130
+ };
131
+ }
132
+ };
133
+ var TransactionConflictException = class extends DynamoDBError {
134
+ constructor(message = "Transaction is ongoing for the item") {
135
+ super("TransactionConflictException", message, 400);
136
+ }
137
+ };
138
+ var ProvisionedThroughputExceededException = class extends DynamoDBError {
139
+ constructor(message = "The level of configured provisioned throughput for the table was exceeded") {
140
+ super("ProvisionedThroughputExceededException", message, 400);
141
+ }
142
+ };
143
+ var ItemCollectionSizeLimitExceededException = class extends DynamoDBError {
144
+ constructor(message = "Collection size exceeded") {
145
+ super("ItemCollectionSizeLimitExceededException", message, 400);
146
+ }
147
+ };
148
+ var InternalServerError = class extends DynamoDBError {
149
+ constructor(message = "Internal server error") {
150
+ super("InternalServerError", message, 500);
151
+ }
152
+ };
153
+ var SerializationException = class extends DynamoDBError {
154
+ constructor(message) {
155
+ super("SerializationException", message, 400);
156
+ }
157
+ };
158
+ var IdempotentParameterMismatchException = class extends DynamoDBError {
159
+ constructor(message = "The request uses the same client token as a previous, but non-identical request") {
160
+ super("IdempotentParameterMismatchException", message, 400);
161
+ }
162
+ };
163
+
164
+ // src/operations/create-table.ts
165
+ function validateAndCreate(store, input) {
166
+ if (!input.TableName) {
167
+ throw new ValidationException("TableName is required");
168
+ }
169
+ if (!input.KeySchema || input.KeySchema.length === 0) {
170
+ throw new ValidationException("KeySchema is required");
171
+ }
172
+ if (!input.AttributeDefinitions || input.AttributeDefinitions.length === 0) {
173
+ throw new ValidationException("AttributeDefinitions is required");
174
+ }
175
+ const hashKeys = input.KeySchema.filter((k) => k.KeyType === "HASH");
176
+ if (hashKeys.length !== 1) {
177
+ throw new ValidationException("Exactly one hash key is required");
178
+ }
179
+ const rangeKeys = input.KeySchema.filter((k) => k.KeyType === "RANGE");
180
+ if (rangeKeys.length > 1) {
181
+ throw new ValidationException("At most one range key is allowed");
182
+ }
183
+ const definedAttrs = new Set(input.AttributeDefinitions.map((a) => a.AttributeName));
184
+ for (const keyElement of input.KeySchema) {
185
+ if (!definedAttrs.has(keyElement.AttributeName)) {
186
+ throw new ValidationException(
187
+ `Attribute ${keyElement.AttributeName} is specified in KeySchema but not in AttributeDefinitions`
188
+ );
189
+ }
190
+ }
191
+ if (input.LocalSecondaryIndexes) {
192
+ const tableHashKey = hashKeys[0].AttributeName;
193
+ for (const lsi of input.LocalSecondaryIndexes) {
194
+ const lsiHashKey = lsi.KeySchema.find((k) => k.KeyType === "HASH");
195
+ if (!lsiHashKey || lsiHashKey.AttributeName !== tableHashKey) {
196
+ throw new ValidationException(
197
+ `Local secondary index ${lsi.IndexName} must have the same hash key as the table`
198
+ );
199
+ }
200
+ }
201
+ }
202
+ const table = store.createTable({
203
+ TableName: input.TableName,
204
+ KeySchema: input.KeySchema,
205
+ AttributeDefinitions: input.AttributeDefinitions,
206
+ ProvisionedThroughput: input.ProvisionedThroughput,
207
+ BillingMode: input.BillingMode,
208
+ GlobalSecondaryIndexes: input.GlobalSecondaryIndexes,
209
+ LocalSecondaryIndexes: input.LocalSecondaryIndexes,
210
+ StreamSpecification: input.StreamSpecification
211
+ });
212
+ return table.describe();
213
+ }
214
+ function createTable(store, input) {
215
+ const tableDescription = validateAndCreate(store, input);
216
+ return { TableDescription: tableDescription };
217
+ }
218
+
219
+ // src/operations/delete-table.ts
220
+ function deleteTable(store, input) {
221
+ if (!input.TableName) {
222
+ throw new ValidationException("TableName is required");
223
+ }
224
+ const table = store.deleteTable(input.TableName);
225
+ const description = table.describe();
226
+ return {
227
+ TableDescription: {
228
+ ...description,
229
+ TableStatus: "DELETING"
230
+ }
231
+ };
232
+ }
233
+
234
+ // src/operations/describe-table.ts
235
+ function describeTable(store, input) {
236
+ if (!input.TableName) {
237
+ throw new ValidationException("TableName is required");
238
+ }
239
+ const table = store.getTable(input.TableName);
240
+ return {
241
+ Table: table.describe()
242
+ };
243
+ }
244
+
245
+ // src/operations/list-tables.ts
246
+ function listTables(store, input) {
247
+ const result = store.listTables(input.ExclusiveStartTableName, input.Limit);
248
+ return {
249
+ TableNames: result.tableNames,
250
+ LastEvaluatedTableName: result.lastEvaluatedTableName
251
+ };
252
+ }
253
+
254
+ // src/expressions/path.ts
255
+ function parsePath(path, attributeNames) {
256
+ const segments = [];
257
+ let current = "";
258
+ let i = 0;
259
+ while (i < path.length) {
260
+ const char = path[i];
261
+ if (char === ".") {
262
+ if (current) {
263
+ segments.push({ type: "attribute", value: resolveAttributeName(current, attributeNames) });
264
+ current = "";
265
+ }
266
+ i++;
267
+ } else if (char === "[") {
268
+ if (current) {
269
+ segments.push({ type: "attribute", value: resolveAttributeName(current, attributeNames) });
270
+ current = "";
271
+ }
272
+ i++;
273
+ let indexStr = "";
274
+ while (i < path.length && path[i] !== "]") {
275
+ indexStr += path[i];
276
+ i++;
277
+ }
278
+ i++;
279
+ segments.push({ type: "index", value: parseInt(indexStr, 10) });
280
+ } else {
281
+ current += char;
282
+ i++;
283
+ }
284
+ }
285
+ if (current) {
286
+ segments.push({ type: "attribute", value: resolveAttributeName(current, attributeNames) });
287
+ }
288
+ return segments;
289
+ }
290
+ function resolveAttributeName(name, attributeNames) {
291
+ if (name.startsWith("#") && attributeNames) {
292
+ const resolved = attributeNames[name];
293
+ if (resolved !== void 0) {
294
+ return resolved;
295
+ }
296
+ }
297
+ return name;
298
+ }
299
+ function getValueAtPath(item, segments) {
300
+ let current = { M: item };
301
+ for (const segment of segments) {
302
+ if (current === void 0) {
303
+ return void 0;
304
+ }
305
+ if (segment.type === "attribute") {
306
+ if ("M" in current) {
307
+ const map = current.M;
308
+ current = map[segment.value];
309
+ } else {
310
+ return void 0;
311
+ }
312
+ } else if (segment.type === "index") {
313
+ if ("L" in current) {
314
+ const list = current.L;
315
+ current = list[segment.value];
316
+ } else {
317
+ return void 0;
318
+ }
319
+ }
320
+ }
321
+ return current;
322
+ }
323
+ function setValueAtPath(item, segments, value) {
324
+ if (segments.length === 0) {
325
+ return;
326
+ }
327
+ let current = { M: item };
328
+ for (let i = 0; i < segments.length - 1; i++) {
329
+ const segment = segments[i];
330
+ const nextSegment = segments[i + 1];
331
+ if (segment.type === "attribute") {
332
+ if (!("M" in current)) {
333
+ return;
334
+ }
335
+ const map = current.M;
336
+ const attrName = segment.value;
337
+ if (!map[attrName]) {
338
+ if (nextSegment.type === "attribute") {
339
+ map[attrName] = { M: {} };
340
+ } else {
341
+ map[attrName] = { L: [] };
342
+ }
343
+ }
344
+ current = map[attrName];
345
+ } else if (segment.type === "index") {
346
+ if (!("L" in current)) {
347
+ return;
348
+ }
349
+ const list = current.L;
350
+ const idx = segment.value;
351
+ if (!list[idx]) {
352
+ if (nextSegment.type === "attribute") {
353
+ list[idx] = { M: {} };
354
+ } else {
355
+ list[idx] = { L: [] };
356
+ }
357
+ }
358
+ current = list[idx];
359
+ }
360
+ }
361
+ const lastSegment = segments[segments.length - 1];
362
+ if (lastSegment.type === "attribute") {
363
+ if ("M" in current) {
364
+ const map = current.M;
365
+ map[lastSegment.value] = value;
366
+ }
367
+ } else if (lastSegment.type === "index") {
368
+ if ("L" in current) {
369
+ const list = current.L;
370
+ list[lastSegment.value] = value;
371
+ }
372
+ }
373
+ }
374
+ function deleteValueAtPath(item, segments) {
375
+ if (segments.length === 0) {
376
+ return false;
377
+ }
378
+ let current = { M: item };
379
+ for (let i = 0; i < segments.length - 1; i++) {
380
+ const segment = segments[i];
381
+ if (segment.type === "attribute") {
382
+ if (!("M" in current)) {
383
+ return false;
384
+ }
385
+ const map = current.M;
386
+ if (!map[segment.value]) {
387
+ return false;
388
+ }
389
+ current = map[segment.value];
390
+ } else if (segment.type === "index") {
391
+ if (!("L" in current)) {
392
+ return false;
393
+ }
394
+ const list = current.L;
395
+ if (list[segment.value] === void 0) {
396
+ return false;
397
+ }
398
+ current = list[segment.value];
399
+ }
400
+ }
401
+ const lastSegment = segments[segments.length - 1];
402
+ if (lastSegment.type === "attribute") {
403
+ if ("M" in current) {
404
+ const map = current.M;
405
+ delete map[lastSegment.value];
406
+ return true;
407
+ }
408
+ } else if (lastSegment.type === "index") {
409
+ if ("L" in current) {
410
+ const list = current.L;
411
+ list.splice(lastSegment.value, 1);
412
+ return true;
413
+ }
414
+ }
415
+ return false;
416
+ }
417
+
418
+ // src/expressions/condition.ts
419
+ function tokenize(expression) {
420
+ const tokens = [];
421
+ let i = 0;
422
+ const keywords = {
423
+ AND: "AND",
424
+ OR: "OR",
425
+ NOT: "NOT",
426
+ BETWEEN: "BETWEEN",
427
+ IN: "IN"
428
+ };
429
+ const functions = ["attribute_exists", "attribute_not_exists", "attribute_type", "begins_with", "contains", "size"];
430
+ while (i < expression.length) {
431
+ const char = expression[i];
432
+ if (/\s/.test(char)) {
433
+ i++;
434
+ continue;
435
+ }
436
+ if (char === "(") {
437
+ tokens.push({ type: "LPAREN", value: "(" });
438
+ i++;
439
+ continue;
440
+ }
441
+ if (char === ")") {
442
+ tokens.push({ type: "RPAREN", value: ")" });
443
+ i++;
444
+ continue;
445
+ }
446
+ if (char === ",") {
447
+ tokens.push({ type: "COMMA", value: "," });
448
+ i++;
449
+ continue;
450
+ }
451
+ if (char === "=" || char === "<" || char === ">") {
452
+ let op = char;
453
+ if (expression[i + 1] === "=") {
454
+ op += "=";
455
+ i++;
456
+ } else if (char === "<" && expression[i + 1] === ">") {
457
+ op = "<>";
458
+ i++;
459
+ }
460
+ tokens.push({ type: "COMPARATOR", value: op });
461
+ i++;
462
+ continue;
463
+ }
464
+ if (char === ":") {
465
+ let value = ":";
466
+ i++;
467
+ while (i < expression.length && /[a-zA-Z0-9_]/.test(expression[i])) {
468
+ value += expression[i];
469
+ i++;
470
+ }
471
+ tokens.push({ type: "VALUE", value });
472
+ continue;
473
+ }
474
+ if (char === "#") {
475
+ let value = "#";
476
+ i++;
477
+ while (i < expression.length && /[a-zA-Z0-9_]/.test(expression[i])) {
478
+ value += expression[i];
479
+ i++;
480
+ }
481
+ tokens.push({ type: "PATH", value });
482
+ continue;
483
+ }
484
+ if (/[a-zA-Z_]/.test(char)) {
485
+ let word = "";
486
+ while (i < expression.length && /[a-zA-Z0-9_]/.test(expression[i])) {
487
+ word += expression[i];
488
+ i++;
489
+ }
490
+ const upper = word.toUpperCase();
491
+ if (keywords[upper]) {
492
+ tokens.push({ type: keywords[upper], value: upper });
493
+ } else if (functions.includes(word)) {
494
+ tokens.push({ type: "FUNCTION", value: word });
495
+ } else {
496
+ tokens.push({ type: "PATH", value: word });
497
+ }
498
+ continue;
499
+ }
500
+ if (char === "[") {
501
+ let value = "";
502
+ while (i < expression.length && expression[i] !== "]") {
503
+ value += expression[i];
504
+ i++;
505
+ }
506
+ value += "]";
507
+ i++;
508
+ if (tokens.length > 0 && tokens[tokens.length - 1].type === "PATH") {
509
+ tokens[tokens.length - 1].value += value;
510
+ }
511
+ continue;
512
+ }
513
+ if (char === ".") {
514
+ if (tokens.length > 0 && tokens[tokens.length - 1].type === "PATH") {
515
+ i++;
516
+ let pathPart = ".";
517
+ while (i < expression.length && /[a-zA-Z0-9_#\[\]]/.test(expression[i])) {
518
+ pathPart += expression[i];
519
+ i++;
520
+ }
521
+ tokens[tokens.length - 1].value += pathPart;
522
+ } else {
523
+ i++;
524
+ }
525
+ continue;
526
+ }
527
+ i++;
528
+ }
529
+ return tokens;
530
+ }
531
+ function evaluateCondition(expression, item, context) {
532
+ if (!expression || expression.trim() === "") {
533
+ return true;
534
+ }
535
+ const tokens = tokenize(expression);
536
+ let pos = 0;
537
+ function current() {
538
+ return tokens[pos];
539
+ }
540
+ function consume(type) {
541
+ const token = tokens[pos];
542
+ if (!token) {
543
+ throw new ValidationException("Unexpected end of expression");
544
+ }
545
+ if (type && token.type !== type) {
546
+ throw new ValidationException(`Expected ${type} but got ${token.type}`);
547
+ }
548
+ pos++;
549
+ return token;
550
+ }
551
+ function parseExpression() {
552
+ return parseOr();
553
+ }
554
+ function parseOr() {
555
+ let left = parseAnd();
556
+ while (current()?.type === "OR") {
557
+ consume("OR");
558
+ const right = parseAnd();
559
+ left = left || right;
560
+ }
561
+ return left;
562
+ }
563
+ function parseAnd() {
564
+ let left = parseNot();
565
+ while (current()?.type === "AND") {
566
+ consume("AND");
567
+ const right = parseNot();
568
+ left = left && right;
569
+ }
570
+ return left;
571
+ }
572
+ function parseNot() {
573
+ if (current()?.type === "NOT") {
574
+ consume("NOT");
575
+ return !parseNot();
576
+ }
577
+ return parsePrimary();
578
+ }
579
+ function parsePrimary() {
580
+ const token = current();
581
+ if (!token) {
582
+ return true;
583
+ }
584
+ if (token.type === "LPAREN") {
585
+ consume("LPAREN");
586
+ const result = parseExpression();
587
+ consume("RPAREN");
588
+ return result;
589
+ }
590
+ if (token.type === "FUNCTION") {
591
+ return parseFunction();
592
+ }
593
+ if (token.type === "PATH" || token.type === "VALUE") {
594
+ return parseComparison();
595
+ }
596
+ throw new ValidationException(`Unexpected token: ${token.type}`);
597
+ }
598
+ function parseFunction() {
599
+ const funcToken = consume("FUNCTION");
600
+ consume("LPAREN");
601
+ const funcName = funcToken.value;
602
+ if (funcName === "attribute_exists") {
603
+ const pathToken = consume("PATH");
604
+ consume("RPAREN");
605
+ const segments = parsePath(pathToken.value, context.expressionAttributeNames);
606
+ const value = getValueAtPath(item, segments);
607
+ return value !== void 0;
608
+ }
609
+ if (funcName === "attribute_not_exists") {
610
+ const pathToken = consume("PATH");
611
+ consume("RPAREN");
612
+ const segments = parsePath(pathToken.value, context.expressionAttributeNames);
613
+ const value = getValueAtPath(item, segments);
614
+ return value === void 0;
615
+ }
616
+ if (funcName === "attribute_type") {
617
+ const pathToken = consume("PATH");
618
+ consume("COMMA");
619
+ const typeToken = consume("VALUE");
620
+ consume("RPAREN");
621
+ const segments = parsePath(pathToken.value, context.expressionAttributeNames);
622
+ const value = getValueAtPath(item, segments);
623
+ const expectedType = resolveValue(typeToken.value);
624
+ if (!value || !expectedType || !("S" in expectedType)) {
625
+ return false;
626
+ }
627
+ const typeMap = {
628
+ S: "S",
629
+ N: "N",
630
+ B: "B",
631
+ SS: "SS",
632
+ NS: "NS",
633
+ BS: "BS",
634
+ M: "M",
635
+ L: "L",
636
+ NULL: "NULL",
637
+ BOOL: "BOOL"
638
+ };
639
+ const actualType = Object.keys(value)[0];
640
+ return typeMap[expectedType.S] === actualType;
641
+ }
642
+ if (funcName === "begins_with") {
643
+ const pathToken = consume("PATH");
644
+ consume("COMMA");
645
+ const prefixToken = consume("VALUE");
646
+ consume("RPAREN");
647
+ const segments = parsePath(pathToken.value, context.expressionAttributeNames);
648
+ const value = getValueAtPath(item, segments);
649
+ const prefix = resolveValue(prefixToken.value);
650
+ if (!value || !prefix) {
651
+ return false;
652
+ }
653
+ if ("S" in value && "S" in prefix) {
654
+ return value.S.startsWith(prefix.S);
655
+ }
656
+ if ("B" in value && "B" in prefix) {
657
+ return value.B.startsWith(prefix.B);
658
+ }
659
+ return false;
660
+ }
661
+ if (funcName === "contains") {
662
+ const pathToken = consume("PATH");
663
+ consume("COMMA");
664
+ const operandToken = consume("VALUE");
665
+ consume("RPAREN");
666
+ const segments = parsePath(pathToken.value, context.expressionAttributeNames);
667
+ const value = getValueAtPath(item, segments);
668
+ const operand = resolveValue(operandToken.value);
669
+ if (!value || !operand) {
670
+ return false;
671
+ }
672
+ if ("S" in value && "S" in operand) {
673
+ return value.S.includes(operand.S);
674
+ }
675
+ if ("SS" in value && "S" in operand) {
676
+ return value.SS.includes(operand.S);
677
+ }
678
+ if ("NS" in value && "N" in operand) {
679
+ return value.NS.includes(operand.N);
680
+ }
681
+ if ("BS" in value && "B" in operand) {
682
+ return value.BS.includes(operand.B);
683
+ }
684
+ if ("L" in value) {
685
+ return value.L.some((v) => compareValues(v, operand) === 0);
686
+ }
687
+ return false;
688
+ }
689
+ if (funcName === "size") {
690
+ const pathToken = consume("PATH");
691
+ consume("RPAREN");
692
+ const segments = parsePath(pathToken.value, context.expressionAttributeNames);
693
+ const value = getValueAtPath(item, segments);
694
+ if (!value) {
695
+ return false;
696
+ }
697
+ let size = 0;
698
+ if ("S" in value) size = value.S.length;
699
+ else if ("B" in value) size = value.B.length;
700
+ else if ("SS" in value) size = value.SS.length;
701
+ else if ("NS" in value) size = value.NS.length;
702
+ else if ("BS" in value) size = value.BS.length;
703
+ else if ("L" in value) size = value.L.length;
704
+ else if ("M" in value) size = Object.keys(value.M).length;
705
+ const nextToken = current();
706
+ if (nextToken?.type === "COMPARATOR") {
707
+ consume("COMPARATOR");
708
+ const rightToken = consume("VALUE");
709
+ const rightValue = resolveValue(rightToken.value);
710
+ if (!rightValue || !("N" in rightValue)) {
711
+ throw new ValidationException("Size comparison requires numeric operand");
712
+ }
713
+ return compareNumbers(size, parseFloat(rightValue.N), nextToken.value);
714
+ }
715
+ return size > 0;
716
+ }
717
+ throw new ValidationException(`Unknown function: ${funcName}`);
718
+ }
719
+ function parseComparison() {
720
+ const leftToken = consume();
721
+ const leftValue = resolveOperand2(leftToken);
722
+ const nextToken = current();
723
+ if (nextToken?.type === "COMPARATOR") {
724
+ const op = consume("COMPARATOR").value;
725
+ const rightToken = consume();
726
+ const rightValue = resolveOperand2(rightToken);
727
+ return compare(leftValue, rightValue, op);
728
+ }
729
+ if (nextToken?.type === "BETWEEN") {
730
+ consume("BETWEEN");
731
+ const lowToken = consume();
732
+ const lowValue = resolveOperand2(lowToken);
733
+ consume("AND");
734
+ const highToken = consume();
735
+ const highValue = resolveOperand2(highToken);
736
+ if (!leftValue || !lowValue || !highValue) {
737
+ return false;
738
+ }
739
+ return compareValues(leftValue, lowValue) >= 0 && compareValues(leftValue, highValue) <= 0;
740
+ }
741
+ if (nextToken?.type === "IN") {
742
+ consume("IN");
743
+ consume("LPAREN");
744
+ const values = [];
745
+ values.push(resolveOperand2(consume()));
746
+ while (current()?.type === "COMMA") {
747
+ consume("COMMA");
748
+ values.push(resolveOperand2(consume()));
749
+ }
750
+ consume("RPAREN");
751
+ if (!leftValue) {
752
+ return false;
753
+ }
754
+ return values.some((v) => v && compareValues(leftValue, v) === 0);
755
+ }
756
+ return leftValue !== void 0;
757
+ }
758
+ function resolveOperand2(token) {
759
+ if (token.type === "VALUE") {
760
+ return resolveValue(token.value);
761
+ }
762
+ if (token.type === "PATH") {
763
+ const segments = parsePath(token.value, context.expressionAttributeNames);
764
+ return getValueAtPath(item, segments);
765
+ }
766
+ return void 0;
767
+ }
768
+ function resolveValue(ref) {
769
+ if (ref.startsWith(":") && context.expressionAttributeValues) {
770
+ return context.expressionAttributeValues[ref];
771
+ }
772
+ return void 0;
773
+ }
774
+ function compare(left, right, op) {
775
+ if (!left || !right) {
776
+ if (op === "<>") {
777
+ return left !== right;
778
+ }
779
+ return false;
780
+ }
781
+ const cmp = compareValues(left, right);
782
+ switch (op) {
783
+ case "=":
784
+ return cmp === 0;
785
+ case "<>":
786
+ return cmp !== 0;
787
+ case "<":
788
+ return cmp < 0;
789
+ case "<=":
790
+ return cmp <= 0;
791
+ case ">":
792
+ return cmp > 0;
793
+ case ">=":
794
+ return cmp >= 0;
795
+ default:
796
+ return false;
797
+ }
798
+ }
799
+ function compareNumbers(left, right, op) {
800
+ switch (op) {
801
+ case "=":
802
+ return left === right;
803
+ case "<>":
804
+ return left !== right;
805
+ case "<":
806
+ return left < right;
807
+ case "<=":
808
+ return left <= right;
809
+ case ">":
810
+ return left > right;
811
+ case ">=":
812
+ return left >= right;
813
+ default:
814
+ return false;
815
+ }
816
+ }
817
+ return parseExpression();
818
+ }
819
+ function compareValues(a, b) {
820
+ if ("S" in a && "S" in b) {
821
+ return a.S.localeCompare(b.S);
822
+ }
823
+ if ("N" in a && "N" in b) {
824
+ return parseFloat(a.N) - parseFloat(b.N);
825
+ }
826
+ if ("B" in a && "B" in b) {
827
+ return a.B.localeCompare(b.B);
828
+ }
829
+ if ("BOOL" in a && "BOOL" in b) {
830
+ return Number(a.BOOL) - Number(b.BOOL);
831
+ }
832
+ if ("NULL" in a && "NULL" in b) {
833
+ return 0;
834
+ }
835
+ const aStr = JSON.stringify(a);
836
+ const bStr = JSON.stringify(b);
837
+ return aStr.localeCompare(bStr);
838
+ }
839
+
840
+ // src/store/item.ts
841
+ import { createHash } from "crypto";
842
+ function extractKey(item, keySchema) {
843
+ const key = {};
844
+ for (const element of keySchema) {
845
+ const value = item[element.AttributeName];
846
+ if (value) {
847
+ key[element.AttributeName] = value;
848
+ }
849
+ }
850
+ return key;
851
+ }
852
+ function serializeKey(key, keySchema) {
853
+ const parts = [];
854
+ for (const element of keySchema) {
855
+ const value = key[element.AttributeName];
856
+ if (value) {
857
+ parts.push(serializeAttributeValue(value));
858
+ }
859
+ }
860
+ return parts.join("#");
861
+ }
862
+ function serializeAttributeValue(value) {
863
+ if ("S" in value) return `S:${value.S}`;
864
+ if ("N" in value) return `N:${value.N}`;
865
+ if ("B" in value) return `B:${value.B}`;
866
+ if ("SS" in value) return `SS:${value.SS.sort().join(",")}`;
867
+ if ("NS" in value) return `NS:${value.NS.sort().join(",")}`;
868
+ if ("BS" in value) return `BS:${value.BS.sort().join(",")}`;
869
+ if ("BOOL" in value) return `BOOL:${value.BOOL}`;
870
+ if ("NULL" in value) return "NULL";
871
+ if ("L" in value) return `L:${JSON.stringify(value.L)}`;
872
+ if ("M" in value) return `M:${JSON.stringify(value.M)}`;
873
+ return "";
874
+ }
875
+ function getHashKey(keySchema) {
876
+ const hash = keySchema.find((k) => k.KeyType === "HASH");
877
+ if (!hash) {
878
+ throw new Error("No hash key found");
879
+ }
880
+ return hash.AttributeName;
881
+ }
882
+ function getRangeKey(keySchema) {
883
+ const range = keySchema.find((k) => k.KeyType === "RANGE");
884
+ return range?.AttributeName;
885
+ }
886
+ function deepClone(obj) {
887
+ return JSON.parse(JSON.stringify(obj));
888
+ }
889
+ function estimateItemSize(item) {
890
+ return JSON.stringify(item).length;
891
+ }
892
+ function extractRawValue(value) {
893
+ if ("S" in value) return value.S;
894
+ if ("N" in value) return value.N;
895
+ if ("B" in value) return value.B;
896
+ return serializeAttributeValue(value);
897
+ }
898
+ function hashAttributeValue(value) {
899
+ const raw = extractRawValue(value);
900
+ return createHash("md5").update("Outliers" + raw).digest("hex");
901
+ }
902
+
903
+ // src/expressions/key-condition.ts
904
+ function parseKeyCondition(expression, keySchema, context) {
905
+ const hashKeyName = getHashKey(keySchema);
906
+ const rangeKeyName = getRangeKey(keySchema);
907
+ const resolvedNames = context.expressionAttributeNames || {};
908
+ const resolvedValues = context.expressionAttributeValues || {};
909
+ function resolveName(name) {
910
+ if (name.startsWith("#")) {
911
+ const resolved = resolvedNames[name];
912
+ if (resolved === void 0) {
913
+ throw new ValidationException(`Expression attribute name ${name} is not defined`);
914
+ }
915
+ return resolved;
916
+ }
917
+ return name;
918
+ }
919
+ function resolveValue(ref) {
920
+ if (ref.startsWith(":")) {
921
+ const value = resolvedValues[ref];
922
+ if (value === void 0) {
923
+ throw new ValidationException(`Expression attribute value ${ref} is not defined`);
924
+ }
925
+ return value;
926
+ }
927
+ throw new ValidationException(`Invalid value reference: ${ref}`);
928
+ }
929
+ function stripOuterParens(expr) {
930
+ let s = expr.trim();
931
+ while (s.startsWith("(") && s.endsWith(")")) {
932
+ let depth = 0;
933
+ let balanced = true;
934
+ for (let i = 0; i < s.length - 1; i++) {
935
+ if (s[i] === "(") depth++;
936
+ else if (s[i] === ")") depth--;
937
+ if (depth === 0) {
938
+ balanced = false;
939
+ break;
940
+ }
941
+ }
942
+ if (balanced) {
943
+ s = s.slice(1, -1).trim();
944
+ } else {
945
+ break;
946
+ }
947
+ }
948
+ return s;
949
+ }
950
+ const normalizedExpression = stripOuterParens(expression);
951
+ const parts = normalizedExpression.split(/\s+AND\s+/i);
952
+ let hashValue;
953
+ let rangeCondition;
954
+ for (const part of parts) {
955
+ const trimmed = stripOuterParens(part);
956
+ const beginsWithMatch = trimmed.match(/^begins_with\s*\(\s*([#\w]+)\s*,\s*(:\w+)\s*\)$/i);
957
+ if (beginsWithMatch) {
958
+ const attrName = resolveName(beginsWithMatch[1]);
959
+ const value = resolveValue(beginsWithMatch[2]);
960
+ if (attrName === rangeKeyName) {
961
+ rangeCondition = { operator: "begins_with", value };
962
+ } else {
963
+ throw new ValidationException(`begins_with can only be used on sort key`);
964
+ }
965
+ continue;
966
+ }
967
+ const betweenMatch = trimmed.match(/^([#\w]+)\s+BETWEEN\s+(:\w+)\s+AND\s+(:\w+)$/i);
968
+ if (betweenMatch) {
969
+ const attrName = resolveName(betweenMatch[1]);
970
+ const value1 = resolveValue(betweenMatch[2]);
971
+ const value2 = resolveValue(betweenMatch[3]);
972
+ if (attrName === rangeKeyName) {
973
+ rangeCondition = { operator: "BETWEEN", value: value1, value2 };
974
+ } else {
975
+ throw new ValidationException(`BETWEEN can only be used on sort key`);
976
+ }
977
+ continue;
978
+ }
979
+ const comparisonMatch = trimmed.match(/^([#\w]+)\s*(=|<>|<=|>=|<|>)\s*(:\w+)$/);
980
+ if (comparisonMatch) {
981
+ const attrName = resolveName(comparisonMatch[1]);
982
+ const operator = comparisonMatch[2];
983
+ const value = resolveValue(comparisonMatch[3]);
984
+ if (attrName === hashKeyName) {
985
+ if (operator !== "=") {
986
+ throw new ValidationException(`Hash key condition must use = operator`);
987
+ }
988
+ hashValue = value;
989
+ } else if (attrName === rangeKeyName) {
990
+ rangeCondition = { operator, value };
991
+ } else {
992
+ throw new ValidationException(`Key condition references unknown attribute: ${attrName}`);
993
+ }
994
+ continue;
995
+ }
996
+ throw new ValidationException(`Invalid key condition expression: ${trimmed}`);
997
+ }
998
+ if (!hashValue) {
999
+ throw new ValidationException(`Key condition must specify hash key equality`);
1000
+ }
1001
+ return {
1002
+ hashKey: hashKeyName,
1003
+ hashValue,
1004
+ rangeKey: rangeKeyName,
1005
+ rangeCondition
1006
+ };
1007
+ }
1008
+ function matchesKeyCondition(item, condition) {
1009
+ const itemHashValue = item[condition.hashKey];
1010
+ if (!itemHashValue || !attributeEquals(itemHashValue, condition.hashValue)) {
1011
+ return false;
1012
+ }
1013
+ if (condition.rangeCondition && condition.rangeKey) {
1014
+ const itemRangeValue = item[condition.rangeKey];
1015
+ if (!itemRangeValue) {
1016
+ return false;
1017
+ }
1018
+ return matchesRangeCondition(itemRangeValue, condition.rangeCondition);
1019
+ }
1020
+ return true;
1021
+ }
1022
+ function matchesRangeCondition(value, condition) {
1023
+ const cmp = compareValues2(value, condition.value);
1024
+ switch (condition.operator) {
1025
+ case "=":
1026
+ return cmp === 0;
1027
+ case "<":
1028
+ return cmp < 0;
1029
+ case "<=":
1030
+ return cmp <= 0;
1031
+ case ">":
1032
+ return cmp > 0;
1033
+ case ">=":
1034
+ return cmp >= 0;
1035
+ case "BETWEEN":
1036
+ if (!condition.value2) return false;
1037
+ return cmp >= 0 && compareValues2(value, condition.value2) <= 0;
1038
+ case "begins_with":
1039
+ if ("S" in value && "S" in condition.value) {
1040
+ return value.S.startsWith(condition.value.S);
1041
+ }
1042
+ if ("B" in value && "B" in condition.value) {
1043
+ return value.B.startsWith(condition.value.B);
1044
+ }
1045
+ return false;
1046
+ default:
1047
+ return false;
1048
+ }
1049
+ }
1050
+ function attributeEquals(a, b) {
1051
+ if ("S" in a && "S" in b) return a.S === b.S;
1052
+ if ("N" in a && "N" in b) return a.N === b.N;
1053
+ if ("B" in a && "B" in b) return a.B === b.B;
1054
+ return JSON.stringify(a) === JSON.stringify(b);
1055
+ }
1056
+ function compareValues2(a, b) {
1057
+ if ("S" in a && "S" in b) {
1058
+ return a.S.localeCompare(b.S);
1059
+ }
1060
+ if ("N" in a && "N" in b) {
1061
+ return parseFloat(a.N) - parseFloat(b.N);
1062
+ }
1063
+ if ("B" in a && "B" in b) {
1064
+ return a.B.localeCompare(b.B);
1065
+ }
1066
+ return 0;
1067
+ }
1068
+
1069
+ // src/expressions/projection.ts
1070
+ function applyProjection(item, projectionExpression, expressionAttributeNames) {
1071
+ if (!projectionExpression || projectionExpression.trim() === "") {
1072
+ return item;
1073
+ }
1074
+ const paths = projectionExpression.split(",").map((p) => p.trim());
1075
+ const result = {};
1076
+ for (const path of paths) {
1077
+ const segments = parsePath(path, expressionAttributeNames);
1078
+ const value = getValueAtPath(item, segments);
1079
+ if (value !== void 0) {
1080
+ setValueAtPath(result, segments, value);
1081
+ }
1082
+ }
1083
+ return result;
1084
+ }
1085
+
1086
+ // src/expressions/update.ts
1087
+ function parseUpdateExpression(expression) {
1088
+ const actions = [];
1089
+ let remaining = expression.trim();
1090
+ while (remaining.length > 0) {
1091
+ const setMatch = remaining.match(/^SET\s+/i);
1092
+ const removeMatch = remaining.match(/^REMOVE\s+/i);
1093
+ const addMatch = remaining.match(/^ADD\s+/i);
1094
+ const deleteMatch = remaining.match(/^DELETE\s+/i);
1095
+ if (setMatch) {
1096
+ remaining = remaining.slice(setMatch[0].length);
1097
+ const { items, rest } = parseActionList(remaining);
1098
+ for (const item of items) {
1099
+ const eqIdx = item.indexOf("=");
1100
+ if (eqIdx === -1) {
1101
+ throw new ValidationException(`Invalid SET action: ${item}`);
1102
+ }
1103
+ const path = item.slice(0, eqIdx).trim();
1104
+ const valueExpr = item.slice(eqIdx + 1).trim();
1105
+ const ifNotExistsMatch = valueExpr.match(/^if_not_exists\s*\(\s*([^,]+)\s*,\s*(.+)\s*\)$/i);
1106
+ if (ifNotExistsMatch) {
1107
+ actions.push({
1108
+ type: "SET",
1109
+ path,
1110
+ value: valueExpr,
1111
+ operation: "if_not_exists",
1112
+ operands: [ifNotExistsMatch[1].trim(), ifNotExistsMatch[2].trim()]
1113
+ });
1114
+ continue;
1115
+ }
1116
+ const listAppendMatch = valueExpr.match(/^list_append\s*\(\s*([^,]+)\s*,\s*(.+)\s*\)$/i);
1117
+ if (listAppendMatch) {
1118
+ actions.push({
1119
+ type: "SET",
1120
+ path,
1121
+ value: valueExpr,
1122
+ operation: "list_append",
1123
+ operands: [listAppendMatch[1].trim(), listAppendMatch[2].trim()]
1124
+ });
1125
+ continue;
1126
+ }
1127
+ const plusMatch = valueExpr.match(/^(.+?)\s*\+\s*(.+)$/);
1128
+ if (plusMatch) {
1129
+ actions.push({
1130
+ type: "SET",
1131
+ path,
1132
+ value: valueExpr,
1133
+ operation: "plus",
1134
+ operands: [plusMatch[1].trim(), plusMatch[2].trim()]
1135
+ });
1136
+ continue;
1137
+ }
1138
+ const minusMatch = valueExpr.match(/^(.+?)\s*-\s*(.+)$/);
1139
+ if (minusMatch) {
1140
+ actions.push({
1141
+ type: "SET",
1142
+ path,
1143
+ value: valueExpr,
1144
+ operation: "minus",
1145
+ operands: [minusMatch[1].trim(), minusMatch[2].trim()]
1146
+ });
1147
+ continue;
1148
+ }
1149
+ actions.push({ type: "SET", path, value: valueExpr });
1150
+ }
1151
+ remaining = rest;
1152
+ } else if (removeMatch) {
1153
+ remaining = remaining.slice(removeMatch[0].length);
1154
+ const { items, rest } = parseActionList(remaining);
1155
+ for (const item of items) {
1156
+ actions.push({ type: "REMOVE", path: item.trim() });
1157
+ }
1158
+ remaining = rest;
1159
+ } else if (addMatch) {
1160
+ remaining = remaining.slice(addMatch[0].length);
1161
+ const { items, rest } = parseActionList(remaining);
1162
+ for (const item of items) {
1163
+ const parts = item.trim().split(/\s+/);
1164
+ if (parts.length < 2) {
1165
+ throw new ValidationException(`Invalid ADD action: ${item}`);
1166
+ }
1167
+ actions.push({ type: "ADD", path: parts[0], value: parts.slice(1).join(" ") });
1168
+ }
1169
+ remaining = rest;
1170
+ } else if (deleteMatch) {
1171
+ remaining = remaining.slice(deleteMatch[0].length);
1172
+ const { items, rest } = parseActionList(remaining);
1173
+ for (const item of items) {
1174
+ const parts = item.trim().split(/\s+/);
1175
+ if (parts.length < 2) {
1176
+ throw new ValidationException(`Invalid DELETE action: ${item}`);
1177
+ }
1178
+ actions.push({ type: "DELETE", path: parts[0], value: parts.slice(1).join(" ") });
1179
+ }
1180
+ remaining = rest;
1181
+ } else {
1182
+ remaining = remaining.slice(1);
1183
+ }
1184
+ }
1185
+ return actions;
1186
+ }
1187
+ function parseActionList(expression) {
1188
+ const items = [];
1189
+ let current = "";
1190
+ let depth = 0;
1191
+ let i = 0;
1192
+ const stopKeywords = ["SET", "REMOVE", "ADD", "DELETE"];
1193
+ while (i < expression.length) {
1194
+ const char = expression[i];
1195
+ for (const keyword of stopKeywords) {
1196
+ if (expression.slice(i).toUpperCase().startsWith(keyword + " ") && depth === 0 && current.trim()) {
1197
+ items.push(current.trim());
1198
+ return { items, rest: expression.slice(i) };
1199
+ }
1200
+ }
1201
+ if (char === "(") {
1202
+ depth++;
1203
+ current += char;
1204
+ } else if (char === ")") {
1205
+ depth--;
1206
+ current += char;
1207
+ } else if (char === "," && depth === 0) {
1208
+ if (current.trim()) {
1209
+ items.push(current.trim());
1210
+ }
1211
+ current = "";
1212
+ } else {
1213
+ current += char;
1214
+ }
1215
+ i++;
1216
+ }
1217
+ if (current.trim()) {
1218
+ items.push(current.trim());
1219
+ }
1220
+ return { items, rest: "" };
1221
+ }
1222
+ function applyUpdateExpression(item, expression, context) {
1223
+ if (!expression || expression.trim() === "") {
1224
+ return item;
1225
+ }
1226
+ const result = JSON.parse(JSON.stringify(item));
1227
+ const actions = parseUpdateExpression(expression);
1228
+ const removeActions = [];
1229
+ const otherActions = [];
1230
+ for (const action of actions) {
1231
+ if (action.type === "REMOVE") {
1232
+ removeActions.push(action);
1233
+ } else {
1234
+ otherActions.push(action);
1235
+ }
1236
+ }
1237
+ for (const action of otherActions) {
1238
+ switch (action.type) {
1239
+ case "SET":
1240
+ applySetAction(result, action, context);
1241
+ break;
1242
+ case "ADD":
1243
+ applyAddAction(result, action, context);
1244
+ break;
1245
+ case "DELETE":
1246
+ applyDeleteAction(result, action, context);
1247
+ break;
1248
+ }
1249
+ }
1250
+ removeActions.sort((a, b) => {
1251
+ const aSegments = parsePath(a.path, context.expressionAttributeNames);
1252
+ const bSegments = parsePath(b.path, context.expressionAttributeNames);
1253
+ let aLastIdx;
1254
+ let bLastIdx;
1255
+ for (const seg of aSegments) {
1256
+ if (seg.type === "index") {
1257
+ aLastIdx = seg.value;
1258
+ }
1259
+ }
1260
+ for (const seg of bSegments) {
1261
+ if (seg.type === "index") {
1262
+ bLastIdx = seg.value;
1263
+ }
1264
+ }
1265
+ if (aLastIdx !== void 0 && bLastIdx !== void 0) {
1266
+ return bLastIdx - aLastIdx;
1267
+ }
1268
+ return 0;
1269
+ });
1270
+ for (const action of removeActions) {
1271
+ applyRemoveAction(result, action, context);
1272
+ }
1273
+ return result;
1274
+ }
1275
+ function resolveOperand(item, operand, context) {
1276
+ operand = operand.trim();
1277
+ if (operand.startsWith(":")) {
1278
+ return context.expressionAttributeValues?.[operand];
1279
+ }
1280
+ const segments = parsePath(operand, context.expressionAttributeNames);
1281
+ return getValueAtPath(item, segments);
1282
+ }
1283
+ function applySetAction(item, action, context) {
1284
+ const segments = parsePath(action.path, context.expressionAttributeNames);
1285
+ let value;
1286
+ if (action.operation === "if_not_exists") {
1287
+ const existingValue = resolveOperand(item, action.operands[0], context);
1288
+ if (existingValue !== void 0) {
1289
+ value = existingValue;
1290
+ } else {
1291
+ value = resolveOperand(item, action.operands[1], context);
1292
+ }
1293
+ } else if (action.operation === "list_append") {
1294
+ const list1 = resolveOperand(item, action.operands[0], context);
1295
+ const list2 = resolveOperand(item, action.operands[1], context);
1296
+ if (list1 && "L" in list1 && list2 && "L" in list2) {
1297
+ value = { L: [...list1.L, ...list2.L] };
1298
+ } else if (list1 && "L" in list1) {
1299
+ value = list1;
1300
+ } else if (list2 && "L" in list2) {
1301
+ value = list2;
1302
+ }
1303
+ } else if (action.operation === "plus") {
1304
+ const left = resolveOperand(item, action.operands[0], context);
1305
+ const right = resolveOperand(item, action.operands[1], context);
1306
+ if (left && "N" in left && right && "N" in right) {
1307
+ const result = parseFloat(left.N) + parseFloat(right.N);
1308
+ value = { N: String(result) };
1309
+ }
1310
+ } else if (action.operation === "minus") {
1311
+ const left = resolveOperand(item, action.operands[0], context);
1312
+ const right = resolveOperand(item, action.operands[1], context);
1313
+ if (left && "N" in left && right && "N" in right) {
1314
+ const result = parseFloat(left.N) - parseFloat(right.N);
1315
+ value = { N: String(result) };
1316
+ }
1317
+ } else {
1318
+ value = resolveOperand(item, action.value, context);
1319
+ }
1320
+ if (value !== void 0) {
1321
+ setValueAtPath(item, segments, value);
1322
+ }
1323
+ }
1324
+ function applyRemoveAction(item, action, context) {
1325
+ const segments = parsePath(action.path, context.expressionAttributeNames);
1326
+ deleteValueAtPath(item, segments);
1327
+ }
1328
+ function applyAddAction(item, action, context) {
1329
+ const segments = parsePath(action.path, context.expressionAttributeNames);
1330
+ const addValue = resolveOperand(item, action.value, context);
1331
+ const existingValue = getValueAtPath(item, segments);
1332
+ if (!addValue) {
1333
+ return;
1334
+ }
1335
+ if ("N" in addValue) {
1336
+ if (existingValue && "N" in existingValue) {
1337
+ const result = parseFloat(existingValue.N) + parseFloat(addValue.N);
1338
+ setValueAtPath(item, segments, { N: String(result) });
1339
+ } else if (!existingValue) {
1340
+ setValueAtPath(item, segments, addValue);
1341
+ }
1342
+ } else if ("SS" in addValue) {
1343
+ if (existingValue && "SS" in existingValue) {
1344
+ const combined = /* @__PURE__ */ new Set([...existingValue.SS, ...addValue.SS]);
1345
+ setValueAtPath(item, segments, { SS: Array.from(combined) });
1346
+ } else if (!existingValue) {
1347
+ setValueAtPath(item, segments, addValue);
1348
+ }
1349
+ } else if ("NS" in addValue) {
1350
+ if (existingValue && "NS" in existingValue) {
1351
+ const combined = /* @__PURE__ */ new Set([...existingValue.NS, ...addValue.NS]);
1352
+ setValueAtPath(item, segments, { NS: Array.from(combined) });
1353
+ } else if (!existingValue) {
1354
+ setValueAtPath(item, segments, addValue);
1355
+ }
1356
+ } else if ("BS" in addValue) {
1357
+ if (existingValue && "BS" in existingValue) {
1358
+ const combined = /* @__PURE__ */ new Set([...existingValue.BS, ...addValue.BS]);
1359
+ setValueAtPath(item, segments, { BS: Array.from(combined) });
1360
+ } else if (!existingValue) {
1361
+ setValueAtPath(item, segments, addValue);
1362
+ }
1363
+ }
1364
+ }
1365
+ function applyDeleteAction(item, action, context) {
1366
+ const segments = parsePath(action.path, context.expressionAttributeNames);
1367
+ const deleteValue = resolveOperand(item, action.value, context);
1368
+ const existingValue = getValueAtPath(item, segments);
1369
+ if (!deleteValue || !existingValue) {
1370
+ return;
1371
+ }
1372
+ if ("SS" in deleteValue && "SS" in existingValue) {
1373
+ const toDelete = new Set(deleteValue.SS);
1374
+ const remaining = existingValue.SS.filter((s) => !toDelete.has(s));
1375
+ if (remaining.length > 0) {
1376
+ setValueAtPath(item, segments, { SS: remaining });
1377
+ } else {
1378
+ deleteValueAtPath(item, segments);
1379
+ }
1380
+ } else if ("NS" in deleteValue && "NS" in existingValue) {
1381
+ const toDelete = new Set(deleteValue.NS);
1382
+ const remaining = existingValue.NS.filter((n) => !toDelete.has(n));
1383
+ if (remaining.length > 0) {
1384
+ setValueAtPath(item, segments, { NS: remaining });
1385
+ } else {
1386
+ deleteValueAtPath(item, segments);
1387
+ }
1388
+ } else if ("BS" in deleteValue && "BS" in existingValue) {
1389
+ const toDelete = new Set(deleteValue.BS);
1390
+ const remaining = existingValue.BS.filter((b) => !toDelete.has(b));
1391
+ if (remaining.length > 0) {
1392
+ setValueAtPath(item, segments, { BS: remaining });
1393
+ } else {
1394
+ deleteValueAtPath(item, segments);
1395
+ }
1396
+ }
1397
+ }
1398
+
1399
+ // src/operations/put-item.ts
1400
+ function putItem(store, input) {
1401
+ if (!input.TableName) {
1402
+ throw new ValidationException("TableName is required");
1403
+ }
1404
+ if (!input.Item) {
1405
+ throw new ValidationException("Item is required");
1406
+ }
1407
+ const table = store.getTable(input.TableName);
1408
+ const hashKey = table.getHashKeyName();
1409
+ if (!input.Item[hashKey]) {
1410
+ throw new ValidationException(`Missing the key ${hashKey} in the item`);
1411
+ }
1412
+ const rangeKey = table.getRangeKeyName();
1413
+ if (rangeKey && !input.Item[rangeKey]) {
1414
+ throw new ValidationException(`Missing the key ${rangeKey} in the item`);
1415
+ }
1416
+ const existingItem = table.getItem(input.Item);
1417
+ if (input.ConditionExpression) {
1418
+ const conditionMet = evaluateCondition(input.ConditionExpression, existingItem || {}, {
1419
+ expressionAttributeNames: input.ExpressionAttributeNames,
1420
+ expressionAttributeValues: input.ExpressionAttributeValues
1421
+ });
1422
+ if (!conditionMet) {
1423
+ if (input.ReturnValuesOnConditionCheckFailure === "ALL_OLD" && existingItem) {
1424
+ throw new ConditionalCheckFailedException("The conditional request failed", existingItem);
1425
+ }
1426
+ throw new ConditionalCheckFailedException("The conditional request failed");
1427
+ }
1428
+ }
1429
+ const oldItem = table.putItem(input.Item);
1430
+ const output = {};
1431
+ if (input.ReturnValues === "ALL_OLD" && oldItem) {
1432
+ output.Attributes = oldItem;
1433
+ }
1434
+ if (input.ReturnConsumedCapacity && input.ReturnConsumedCapacity !== "NONE") {
1435
+ output.ConsumedCapacity = {
1436
+ TableName: input.TableName,
1437
+ CapacityUnits: 1,
1438
+ WriteCapacityUnits: 1
1439
+ };
1440
+ }
1441
+ return output;
1442
+ }
1443
+
1444
+ // src/operations/get-item.ts
1445
+ function getItem(store, input) {
1446
+ if (!input.TableName) {
1447
+ throw new ValidationException("TableName is required");
1448
+ }
1449
+ if (!input.Key) {
1450
+ throw new ValidationException("Key is required");
1451
+ }
1452
+ const table = store.getTable(input.TableName);
1453
+ const hashKey = table.getHashKeyName();
1454
+ if (!input.Key[hashKey]) {
1455
+ throw new ValidationException(`Missing the key ${hashKey} in the key`);
1456
+ }
1457
+ const rangeKey = table.getRangeKeyName();
1458
+ if (rangeKey && !input.Key[rangeKey]) {
1459
+ throw new ValidationException(`Missing the key ${rangeKey} in the key`);
1460
+ }
1461
+ let item = table.getItem(input.Key);
1462
+ if (item && input.ProjectionExpression) {
1463
+ item = applyProjection(item, input.ProjectionExpression, input.ExpressionAttributeNames);
1464
+ }
1465
+ const output = {};
1466
+ if (item) {
1467
+ output.Item = item;
1468
+ }
1469
+ if (input.ReturnConsumedCapacity && input.ReturnConsumedCapacity !== "NONE") {
1470
+ output.ConsumedCapacity = {
1471
+ TableName: input.TableName,
1472
+ CapacityUnits: 0.5,
1473
+ ReadCapacityUnits: 0.5
1474
+ };
1475
+ }
1476
+ return output;
1477
+ }
1478
+
1479
+ // src/operations/delete-item.ts
1480
+ function deleteItem(store, input) {
1481
+ if (!input.TableName) {
1482
+ throw new ValidationException("TableName is required");
1483
+ }
1484
+ if (!input.Key) {
1485
+ throw new ValidationException("Key is required");
1486
+ }
1487
+ const table = store.getTable(input.TableName);
1488
+ const hashKey = table.getHashKeyName();
1489
+ if (!input.Key[hashKey]) {
1490
+ throw new ValidationException(`Missing the key ${hashKey} in the key`);
1491
+ }
1492
+ const rangeKey = table.getRangeKeyName();
1493
+ if (rangeKey && !input.Key[rangeKey]) {
1494
+ throw new ValidationException(`Missing the key ${rangeKey} in the key`);
1495
+ }
1496
+ const existingItem = table.getItem(input.Key);
1497
+ if (input.ConditionExpression) {
1498
+ const conditionMet = evaluateCondition(input.ConditionExpression, existingItem || {}, {
1499
+ expressionAttributeNames: input.ExpressionAttributeNames,
1500
+ expressionAttributeValues: input.ExpressionAttributeValues
1501
+ });
1502
+ if (!conditionMet) {
1503
+ if (input.ReturnValuesOnConditionCheckFailure === "ALL_OLD" && existingItem) {
1504
+ throw new ConditionalCheckFailedException("The conditional request failed", existingItem);
1505
+ }
1506
+ throw new ConditionalCheckFailedException("The conditional request failed");
1507
+ }
1508
+ }
1509
+ const oldItem = table.deleteItem(input.Key);
1510
+ const output = {};
1511
+ if (input.ReturnValues === "ALL_OLD" && oldItem) {
1512
+ output.Attributes = oldItem;
1513
+ }
1514
+ if (input.ReturnConsumedCapacity && input.ReturnConsumedCapacity !== "NONE") {
1515
+ output.ConsumedCapacity = {
1516
+ TableName: input.TableName,
1517
+ CapacityUnits: 1,
1518
+ WriteCapacityUnits: 1
1519
+ };
1520
+ }
1521
+ return output;
1522
+ }
1523
+
1524
+ // src/operations/update-item.ts
1525
+ function updateItem(store, input) {
1526
+ if (!input.TableName) {
1527
+ throw new ValidationException("TableName is required");
1528
+ }
1529
+ if (!input.Key) {
1530
+ throw new ValidationException("Key is required");
1531
+ }
1532
+ const table = store.getTable(input.TableName);
1533
+ const hashKey = table.getHashKeyName();
1534
+ if (!input.Key[hashKey]) {
1535
+ throw new ValidationException(`Missing the key ${hashKey} in the key`);
1536
+ }
1537
+ const rangeKey = table.getRangeKeyName();
1538
+ if (rangeKey && !input.Key[rangeKey]) {
1539
+ throw new ValidationException(`Missing the key ${rangeKey} in the key`);
1540
+ }
1541
+ const existingItem = table.getItem(input.Key);
1542
+ if (input.ConditionExpression) {
1543
+ const conditionMet = evaluateCondition(input.ConditionExpression, existingItem || {}, {
1544
+ expressionAttributeNames: input.ExpressionAttributeNames,
1545
+ expressionAttributeValues: input.ExpressionAttributeValues
1546
+ });
1547
+ if (!conditionMet) {
1548
+ if (input.ReturnValuesOnConditionCheckFailure === "ALL_OLD" && existingItem) {
1549
+ throw new ConditionalCheckFailedException("The conditional request failed", existingItem);
1550
+ }
1551
+ throw new ConditionalCheckFailedException("The conditional request failed");
1552
+ }
1553
+ }
1554
+ let item = existingItem ? { ...existingItem } : { ...input.Key };
1555
+ if (input.UpdateExpression) {
1556
+ item = applyUpdateExpression(item, input.UpdateExpression, {
1557
+ expressionAttributeNames: input.ExpressionAttributeNames,
1558
+ expressionAttributeValues: input.ExpressionAttributeValues
1559
+ });
1560
+ }
1561
+ for (const [key, value] of Object.entries(input.Key)) {
1562
+ item[key] = value;
1563
+ }
1564
+ table.updateItem(input.Key, item);
1565
+ const output = {};
1566
+ switch (input.ReturnValues) {
1567
+ case "ALL_OLD":
1568
+ if (existingItem) {
1569
+ output.Attributes = existingItem;
1570
+ }
1571
+ break;
1572
+ case "ALL_NEW":
1573
+ output.Attributes = item;
1574
+ break;
1575
+ case "UPDATED_OLD":
1576
+ if (existingItem && input.UpdateExpression) {
1577
+ output.Attributes = getUpdatedAttributes(existingItem, item);
1578
+ }
1579
+ break;
1580
+ case "UPDATED_NEW":
1581
+ if (input.UpdateExpression) {
1582
+ output.Attributes = getUpdatedAttributes(existingItem || {}, item);
1583
+ }
1584
+ break;
1585
+ }
1586
+ if (input.ReturnConsumedCapacity && input.ReturnConsumedCapacity !== "NONE") {
1587
+ output.ConsumedCapacity = {
1588
+ TableName: input.TableName,
1589
+ CapacityUnits: 1,
1590
+ WriteCapacityUnits: 1
1591
+ };
1592
+ }
1593
+ return output;
1594
+ }
1595
+ function getUpdatedAttributes(oldItem, newItem) {
1596
+ const updated = {};
1597
+ for (const [key, newValue] of Object.entries(newItem)) {
1598
+ const oldValue = oldItem[key];
1599
+ if (!oldValue || JSON.stringify(oldValue) !== JSON.stringify(newValue)) {
1600
+ updated[key] = newValue;
1601
+ }
1602
+ }
1603
+ return updated;
1604
+ }
1605
+
1606
+ // src/operations/query.ts
1607
+ function query(store, input) {
1608
+ if (!input.TableName) {
1609
+ throw new ValidationException("TableName is required");
1610
+ }
1611
+ if (!input.KeyConditionExpression) {
1612
+ throw new ValidationException("KeyConditionExpression is required");
1613
+ }
1614
+ const table = store.getTable(input.TableName);
1615
+ let keySchema = table.keySchema;
1616
+ if (input.IndexName) {
1617
+ const indexKeySchema = table.getIndexKeySchema(input.IndexName);
1618
+ if (!indexKeySchema) {
1619
+ throw new ResourceNotFoundException(`Index ${input.IndexName} not found`);
1620
+ }
1621
+ keySchema = indexKeySchema;
1622
+ }
1623
+ const keyCondition = parseKeyCondition(input.KeyConditionExpression, keySchema, {
1624
+ expressionAttributeNames: input.ExpressionAttributeNames,
1625
+ expressionAttributeValues: input.ExpressionAttributeValues
1626
+ });
1627
+ let items;
1628
+ let lastEvaluatedKey;
1629
+ if (input.IndexName) {
1630
+ const result = table.queryIndex(
1631
+ input.IndexName,
1632
+ { [keyCondition.hashKey]: keyCondition.hashValue },
1633
+ {
1634
+ scanIndexForward: input.ScanIndexForward,
1635
+ exclusiveStartKey: input.ExclusiveStartKey
1636
+ }
1637
+ );
1638
+ items = result.items;
1639
+ lastEvaluatedKey = result.lastEvaluatedKey;
1640
+ } else {
1641
+ const result = table.queryByHashKey(
1642
+ { [keyCondition.hashKey]: keyCondition.hashValue },
1643
+ {
1644
+ scanIndexForward: input.ScanIndexForward,
1645
+ exclusiveStartKey: input.ExclusiveStartKey
1646
+ }
1647
+ );
1648
+ items = result.items;
1649
+ lastEvaluatedKey = result.lastEvaluatedKey;
1650
+ }
1651
+ if (keyCondition.rangeCondition) {
1652
+ items = items.filter((item) => matchesKeyCondition(item, keyCondition));
1653
+ }
1654
+ const scannedCount = items.length;
1655
+ if (input.FilterExpression) {
1656
+ items = items.filter(
1657
+ (item) => evaluateCondition(input.FilterExpression, item, {
1658
+ expressionAttributeNames: input.ExpressionAttributeNames,
1659
+ expressionAttributeValues: input.ExpressionAttributeValues
1660
+ })
1661
+ );
1662
+ }
1663
+ if (input.Limit && items.length > input.Limit) {
1664
+ items = items.slice(0, input.Limit);
1665
+ if (items.length > 0) {
1666
+ const lastItem = items[items.length - 1];
1667
+ lastEvaluatedKey = {};
1668
+ const hashKey = table.getHashKeyName();
1669
+ const rangeKey = table.getRangeKeyName();
1670
+ if (lastItem[hashKey]) {
1671
+ lastEvaluatedKey[hashKey] = lastItem[hashKey];
1672
+ }
1673
+ if (rangeKey && lastItem[rangeKey]) {
1674
+ lastEvaluatedKey[rangeKey] = lastItem[rangeKey];
1675
+ }
1676
+ if (input.IndexName) {
1677
+ const indexKeySchema = table.getIndexKeySchema(input.IndexName);
1678
+ if (indexKeySchema) {
1679
+ for (const key of indexKeySchema) {
1680
+ const attrValue = lastItem[key.AttributeName];
1681
+ if (attrValue) {
1682
+ lastEvaluatedKey[key.AttributeName] = attrValue;
1683
+ }
1684
+ }
1685
+ }
1686
+ }
1687
+ }
1688
+ }
1689
+ if (input.ProjectionExpression) {
1690
+ items = items.map((item) => applyProjection(item, input.ProjectionExpression, input.ExpressionAttributeNames));
1691
+ }
1692
+ const output = {
1693
+ Count: items.length,
1694
+ ScannedCount: scannedCount
1695
+ };
1696
+ if (input.Select !== "COUNT") {
1697
+ output.Items = items;
1698
+ }
1699
+ if (lastEvaluatedKey && Object.keys(lastEvaluatedKey).length > 0) {
1700
+ output.LastEvaluatedKey = lastEvaluatedKey;
1701
+ }
1702
+ if (input.ReturnConsumedCapacity && input.ReturnConsumedCapacity !== "NONE") {
1703
+ output.ConsumedCapacity = {
1704
+ TableName: input.TableName,
1705
+ CapacityUnits: Math.max(0.5, scannedCount * 0.5),
1706
+ ReadCapacityUnits: Math.max(0.5, scannedCount * 0.5)
1707
+ };
1708
+ }
1709
+ return output;
1710
+ }
1711
+
1712
+ // src/operations/scan.ts
1713
+ function scan(store, input) {
1714
+ if (!input.TableName) {
1715
+ throw new ValidationException("TableName is required");
1716
+ }
1717
+ const table = store.getTable(input.TableName);
1718
+ let items;
1719
+ let lastEvaluatedKey;
1720
+ if (input.IndexName) {
1721
+ if (!table.hasIndex(input.IndexName)) {
1722
+ throw new ResourceNotFoundException(`Index ${input.IndexName} not found`);
1723
+ }
1724
+ const result = table.scanIndex(input.IndexName, void 0, input.ExclusiveStartKey);
1725
+ items = result.items;
1726
+ lastEvaluatedKey = result.lastEvaluatedKey;
1727
+ } else {
1728
+ const result = table.scan(void 0, input.ExclusiveStartKey);
1729
+ items = result.items;
1730
+ lastEvaluatedKey = result.lastEvaluatedKey;
1731
+ }
1732
+ if (input.TotalSegments !== void 0 && input.Segment !== void 0) {
1733
+ const segmentSize = Math.ceil(items.length / input.TotalSegments);
1734
+ const start = input.Segment * segmentSize;
1735
+ const end = start + segmentSize;
1736
+ items = items.slice(start, end);
1737
+ }
1738
+ const scannedCount = items.length;
1739
+ if (input.FilterExpression) {
1740
+ items = items.filter(
1741
+ (item) => evaluateCondition(input.FilterExpression, item, {
1742
+ expressionAttributeNames: input.ExpressionAttributeNames,
1743
+ expressionAttributeValues: input.ExpressionAttributeValues
1744
+ })
1745
+ );
1746
+ }
1747
+ if (input.Limit && items.length > input.Limit) {
1748
+ items = items.slice(0, input.Limit);
1749
+ if (items.length > 0) {
1750
+ const lastItem = items[items.length - 1];
1751
+ lastEvaluatedKey = {};
1752
+ const hashKey = table.getHashKeyName();
1753
+ const rangeKey = table.getRangeKeyName();
1754
+ if (lastItem[hashKey]) {
1755
+ lastEvaluatedKey[hashKey] = lastItem[hashKey];
1756
+ }
1757
+ if (rangeKey && lastItem[rangeKey]) {
1758
+ lastEvaluatedKey[rangeKey] = lastItem[rangeKey];
1759
+ }
1760
+ if (input.IndexName) {
1761
+ const indexKeySchema = table.getIndexKeySchema(input.IndexName);
1762
+ if (indexKeySchema) {
1763
+ for (const key of indexKeySchema) {
1764
+ const attrValue = lastItem[key.AttributeName];
1765
+ if (attrValue) {
1766
+ lastEvaluatedKey[key.AttributeName] = attrValue;
1767
+ }
1768
+ }
1769
+ }
1770
+ }
1771
+ }
1772
+ }
1773
+ if (input.ProjectionExpression) {
1774
+ items = items.map((item) => applyProjection(item, input.ProjectionExpression, input.ExpressionAttributeNames));
1775
+ }
1776
+ const output = {
1777
+ Count: items.length,
1778
+ ScannedCount: scannedCount
1779
+ };
1780
+ if (input.Select !== "COUNT") {
1781
+ output.Items = items;
1782
+ }
1783
+ if (lastEvaluatedKey && Object.keys(lastEvaluatedKey).length > 0) {
1784
+ output.LastEvaluatedKey = lastEvaluatedKey;
1785
+ }
1786
+ if (input.ReturnConsumedCapacity && input.ReturnConsumedCapacity !== "NONE") {
1787
+ output.ConsumedCapacity = {
1788
+ TableName: input.TableName,
1789
+ CapacityUnits: Math.max(0.5, scannedCount * 0.5),
1790
+ ReadCapacityUnits: Math.max(0.5, scannedCount * 0.5)
1791
+ };
1792
+ }
1793
+ return output;
1794
+ }
1795
+
1796
+ // src/operations/batch-get-item.ts
1797
+ var MAX_BATCH_GET_ITEMS = 100;
1798
+ function batchGetItem(store, input) {
1799
+ if (!input.RequestItems) {
1800
+ throw new ValidationException("RequestItems is required");
1801
+ }
1802
+ let totalKeys = 0;
1803
+ for (const tableRequest of Object.values(input.RequestItems)) {
1804
+ totalKeys += tableRequest.Keys.length;
1805
+ }
1806
+ if (totalKeys > MAX_BATCH_GET_ITEMS) {
1807
+ throw new ValidationException(`Too many items requested for the BatchGetItem call. Max is ${MAX_BATCH_GET_ITEMS}`);
1808
+ }
1809
+ const responses = {};
1810
+ const consumedCapacity = [];
1811
+ for (const [tableName, tableRequest] of Object.entries(input.RequestItems)) {
1812
+ const table = store.getTable(tableName);
1813
+ const items = [];
1814
+ let capacityUnits = 0;
1815
+ for (const key of tableRequest.Keys) {
1816
+ let item = table.getItem(key);
1817
+ if (item) {
1818
+ if (tableRequest.ProjectionExpression) {
1819
+ item = applyProjection(item, tableRequest.ProjectionExpression, tableRequest.ExpressionAttributeNames);
1820
+ }
1821
+ items.push(item);
1822
+ capacityUnits += 0.5;
1823
+ }
1824
+ }
1825
+ if (items.length > 0) {
1826
+ responses[tableName] = items;
1827
+ }
1828
+ if (input.ReturnConsumedCapacity && input.ReturnConsumedCapacity !== "NONE") {
1829
+ consumedCapacity.push({
1830
+ TableName: tableName,
1831
+ CapacityUnits: capacityUnits,
1832
+ ReadCapacityUnits: capacityUnits
1833
+ });
1834
+ }
1835
+ }
1836
+ const output = {};
1837
+ if (Object.keys(responses).length > 0) {
1838
+ output.Responses = responses;
1839
+ }
1840
+ if (input.ReturnConsumedCapacity && input.ReturnConsumedCapacity !== "NONE") {
1841
+ output.ConsumedCapacity = consumedCapacity;
1842
+ }
1843
+ return output;
1844
+ }
1845
+
1846
+ // src/operations/batch-write-item.ts
1847
+ var MAX_BATCH_WRITE_ITEMS = 25;
1848
+ function batchWriteItem(store, input) {
1849
+ if (!input.RequestItems) {
1850
+ throw new ValidationException("RequestItems is required");
1851
+ }
1852
+ let totalItems = 0;
1853
+ for (const tableRequests of Object.values(input.RequestItems)) {
1854
+ totalItems += tableRequests.length;
1855
+ }
1856
+ if (totalItems > MAX_BATCH_WRITE_ITEMS) {
1857
+ throw new ValidationException(`Too many items requested for the BatchWriteItem call. Max is ${MAX_BATCH_WRITE_ITEMS}`);
1858
+ }
1859
+ const consumedCapacity = [];
1860
+ for (const [tableName, requests] of Object.entries(input.RequestItems)) {
1861
+ const table = store.getTable(tableName);
1862
+ let capacityUnits = 0;
1863
+ for (const request of requests) {
1864
+ if ("PutRequest" in request) {
1865
+ const item = request.PutRequest.Item;
1866
+ const hashKey = table.getHashKeyName();
1867
+ if (!item[hashKey]) {
1868
+ throw new ValidationException(`Missing the key ${hashKey} in the item`);
1869
+ }
1870
+ const rangeKey = table.getRangeKeyName();
1871
+ if (rangeKey && !item[rangeKey]) {
1872
+ throw new ValidationException(`Missing the key ${rangeKey} in the item`);
1873
+ }
1874
+ table.putItem(item);
1875
+ capacityUnits += 1;
1876
+ } else if ("DeleteRequest" in request) {
1877
+ const key = request.DeleteRequest.Key;
1878
+ const hashKey = table.getHashKeyName();
1879
+ if (!key[hashKey]) {
1880
+ throw new ValidationException(`Missing the key ${hashKey} in the key`);
1881
+ }
1882
+ const rangeKey = table.getRangeKeyName();
1883
+ if (rangeKey && !key[rangeKey]) {
1884
+ throw new ValidationException(`Missing the key ${rangeKey} in the key`);
1885
+ }
1886
+ table.deleteItem(key);
1887
+ capacityUnits += 1;
1888
+ }
1889
+ }
1890
+ if (input.ReturnConsumedCapacity && input.ReturnConsumedCapacity !== "NONE") {
1891
+ consumedCapacity.push({
1892
+ TableName: tableName,
1893
+ CapacityUnits: capacityUnits,
1894
+ WriteCapacityUnits: capacityUnits
1895
+ });
1896
+ }
1897
+ }
1898
+ const output = {};
1899
+ if (input.ReturnConsumedCapacity && input.ReturnConsumedCapacity !== "NONE") {
1900
+ output.ConsumedCapacity = consumedCapacity;
1901
+ }
1902
+ return output;
1903
+ }
1904
+
1905
+ // src/operations/transact-get-items.ts
1906
+ var MAX_TRANSACT_ITEMS = 100;
1907
+ function transactGetItems(store, input) {
1908
+ if (!input.TransactItems) {
1909
+ throw new ValidationException("TransactItems is required");
1910
+ }
1911
+ if (input.TransactItems.length > MAX_TRANSACT_ITEMS) {
1912
+ throw new ValidationException(`Too many items in the TransactGetItems call. Max is ${MAX_TRANSACT_ITEMS}`);
1913
+ }
1914
+ const responses = [];
1915
+ const consumedCapacity = /* @__PURE__ */ new Map();
1916
+ for (const transactItem of input.TransactItems) {
1917
+ const { Get: getRequest } = transactItem;
1918
+ if (!getRequest.TableName) {
1919
+ throw new ValidationException("TableName is required in Get request");
1920
+ }
1921
+ if (!getRequest.Key) {
1922
+ throw new ValidationException("Key is required in Get request");
1923
+ }
1924
+ const table = store.getTable(getRequest.TableName);
1925
+ let item = table.getItem(getRequest.Key);
1926
+ if (item && getRequest.ProjectionExpression) {
1927
+ item = applyProjection(item, getRequest.ProjectionExpression, getRequest.ExpressionAttributeNames);
1928
+ }
1929
+ responses.push({ Item: item });
1930
+ const current = consumedCapacity.get(getRequest.TableName) || 0;
1931
+ consumedCapacity.set(getRequest.TableName, current + 2);
1932
+ }
1933
+ const output = {
1934
+ Responses: responses
1935
+ };
1936
+ if (input.ReturnConsumedCapacity && input.ReturnConsumedCapacity !== "NONE") {
1937
+ output.ConsumedCapacity = Array.from(consumedCapacity.entries()).map(([tableName, units]) => ({
1938
+ TableName: tableName,
1939
+ CapacityUnits: units,
1940
+ ReadCapacityUnits: units
1941
+ }));
1942
+ }
1943
+ return output;
1944
+ }
1945
+
1946
+ // src/operations/transact-write-items.ts
1947
+ var MAX_TRANSACT_ITEMS2 = 100;
1948
+ var idempotencyTokens = /* @__PURE__ */ new Map();
1949
+ var IDEMPOTENCY_WINDOW_MS = 10 * 60 * 1e3;
1950
+ function transactWriteItems(store, input) {
1951
+ if (!input.TransactItems) {
1952
+ throw new ValidationException("TransactItems is required");
1953
+ }
1954
+ if (input.TransactItems.length > MAX_TRANSACT_ITEMS2) {
1955
+ throw new ValidationException(`Too many items in the TransactWriteItems call. Max is ${MAX_TRANSACT_ITEMS2}`);
1956
+ }
1957
+ if (input.ClientRequestToken) {
1958
+ const cached = idempotencyTokens.get(input.ClientRequestToken);
1959
+ if (cached) {
1960
+ if (Date.now() - cached.timestamp < IDEMPOTENCY_WINDOW_MS) {
1961
+ return cached.result;
1962
+ }
1963
+ idempotencyTokens.delete(input.ClientRequestToken);
1964
+ }
1965
+ }
1966
+ const itemKeys = /* @__PURE__ */ new Set();
1967
+ for (const transactItem of input.TransactItems) {
1968
+ let tableName;
1969
+ let key;
1970
+ if ("ConditionCheck" in transactItem) {
1971
+ tableName = transactItem.ConditionCheck.TableName;
1972
+ key = transactItem.ConditionCheck.Key;
1973
+ } else if ("Put" in transactItem) {
1974
+ tableName = transactItem.Put.TableName;
1975
+ const table2 = store.getTable(tableName);
1976
+ key = extractKey(transactItem.Put.Item, table2.keySchema);
1977
+ } else if ("Delete" in transactItem) {
1978
+ tableName = transactItem.Delete.TableName;
1979
+ key = transactItem.Delete.Key;
1980
+ } else if ("Update" in transactItem) {
1981
+ tableName = transactItem.Update.TableName;
1982
+ key = transactItem.Update.Key;
1983
+ } else {
1984
+ throw new ValidationException("Invalid transaction item");
1985
+ }
1986
+ const table = store.getTable(tableName);
1987
+ const keyString = `${tableName}#${serializeKey(key, table.keySchema)}`;
1988
+ if (itemKeys.has(keyString)) {
1989
+ throw new ValidationException("Transaction request cannot include multiple operations on one item");
1990
+ }
1991
+ itemKeys.add(keyString);
1992
+ }
1993
+ const cancellationReasons = [];
1994
+ let hasCancellation = false;
1995
+ for (const transactItem of input.TransactItems) {
1996
+ const reason = validateTransactionItem(store, transactItem);
1997
+ cancellationReasons.push(reason);
1998
+ if (reason.Code !== "None") {
1999
+ hasCancellation = true;
2000
+ }
2001
+ }
2002
+ if (hasCancellation) {
2003
+ throw new TransactionCanceledException("Transaction cancelled, please refer cancance reasons for specific reasons", cancellationReasons);
2004
+ }
2005
+ const consumedCapacity = /* @__PURE__ */ new Map();
2006
+ for (const transactItem of input.TransactItems) {
2007
+ executeTransactionItem(store, transactItem, consumedCapacity);
2008
+ }
2009
+ const output = {};
2010
+ if (input.ReturnConsumedCapacity && input.ReturnConsumedCapacity !== "NONE") {
2011
+ output.ConsumedCapacity = Array.from(consumedCapacity.entries()).map(([tableName, units]) => ({
2012
+ TableName: tableName,
2013
+ CapacityUnits: units,
2014
+ WriteCapacityUnits: units
2015
+ }));
2016
+ }
2017
+ if (input.ClientRequestToken) {
2018
+ idempotencyTokens.set(input.ClientRequestToken, {
2019
+ timestamp: Date.now(),
2020
+ result: output
2021
+ });
2022
+ }
2023
+ return output;
2024
+ }
2025
+ function validateTransactionItem(store, transactItem) {
2026
+ try {
2027
+ if ("ConditionCheck" in transactItem) {
2028
+ const { ConditionCheck: check } = transactItem;
2029
+ const table = store.getTable(check.TableName);
2030
+ const existingItem = table.getItem(check.Key);
2031
+ const conditionMet = evaluateCondition(check.ConditionExpression, existingItem || {}, {
2032
+ expressionAttributeNames: check.ExpressionAttributeNames,
2033
+ expressionAttributeValues: check.ExpressionAttributeValues
2034
+ });
2035
+ if (!conditionMet) {
2036
+ return {
2037
+ Code: "ConditionalCheckFailed",
2038
+ Message: "The conditional request failed",
2039
+ Item: check.ReturnValuesOnConditionCheckFailure === "ALL_OLD" ? existingItem : void 0
2040
+ };
2041
+ }
2042
+ } else if ("Put" in transactItem) {
2043
+ const { Put: put } = transactItem;
2044
+ if (put.ConditionExpression) {
2045
+ const table = store.getTable(put.TableName);
2046
+ const key = extractKey(put.Item, table.keySchema);
2047
+ const existingItem = table.getItem(key);
2048
+ const conditionMet = evaluateCondition(put.ConditionExpression, existingItem || {}, {
2049
+ expressionAttributeNames: put.ExpressionAttributeNames,
2050
+ expressionAttributeValues: put.ExpressionAttributeValues
2051
+ });
2052
+ if (!conditionMet) {
2053
+ return {
2054
+ Code: "ConditionalCheckFailed",
2055
+ Message: "The conditional request failed",
2056
+ Item: put.ReturnValuesOnConditionCheckFailure === "ALL_OLD" ? existingItem : void 0
2057
+ };
2058
+ }
2059
+ }
2060
+ } else if ("Delete" in transactItem) {
2061
+ const { Delete: del } = transactItem;
2062
+ if (del.ConditionExpression) {
2063
+ const table = store.getTable(del.TableName);
2064
+ const existingItem = table.getItem(del.Key);
2065
+ const conditionMet = evaluateCondition(del.ConditionExpression, existingItem || {}, {
2066
+ expressionAttributeNames: del.ExpressionAttributeNames,
2067
+ expressionAttributeValues: del.ExpressionAttributeValues
2068
+ });
2069
+ if (!conditionMet) {
2070
+ return {
2071
+ Code: "ConditionalCheckFailed",
2072
+ Message: "The conditional request failed",
2073
+ Item: del.ReturnValuesOnConditionCheckFailure === "ALL_OLD" ? existingItem : void 0
2074
+ };
2075
+ }
2076
+ }
2077
+ } else if ("Update" in transactItem) {
2078
+ const { Update: update } = transactItem;
2079
+ if (update.ConditionExpression) {
2080
+ const table = store.getTable(update.TableName);
2081
+ const existingItem = table.getItem(update.Key);
2082
+ const conditionMet = evaluateCondition(update.ConditionExpression, existingItem || {}, {
2083
+ expressionAttributeNames: update.ExpressionAttributeNames,
2084
+ expressionAttributeValues: update.ExpressionAttributeValues
2085
+ });
2086
+ if (!conditionMet) {
2087
+ return {
2088
+ Code: "ConditionalCheckFailed",
2089
+ Message: "The conditional request failed",
2090
+ Item: update.ReturnValuesOnConditionCheckFailure === "ALL_OLD" ? existingItem : void 0
2091
+ };
2092
+ }
2093
+ }
2094
+ }
2095
+ return { Code: "None", Message: null };
2096
+ } catch (error) {
2097
+ if (error instanceof ConditionalCheckFailedException) {
2098
+ return {
2099
+ Code: "ConditionalCheckFailed",
2100
+ Message: error.message,
2101
+ Item: error.Item
2102
+ };
2103
+ }
2104
+ return {
2105
+ Code: "ValidationError",
2106
+ Message: error instanceof Error ? error.message : "Unknown error"
2107
+ };
2108
+ }
2109
+ }
2110
+ function executeTransactionItem(store, transactItem, consumedCapacity) {
2111
+ if ("ConditionCheck" in transactItem) {
2112
+ const tableName = transactItem.ConditionCheck.TableName;
2113
+ const current = consumedCapacity.get(tableName) || 0;
2114
+ consumedCapacity.set(tableName, current + 2);
2115
+ return;
2116
+ }
2117
+ if ("Put" in transactItem) {
2118
+ const { Put: put } = transactItem;
2119
+ const table = store.getTable(put.TableName);
2120
+ table.putItem(put.Item);
2121
+ const current = consumedCapacity.get(put.TableName) || 0;
2122
+ consumedCapacity.set(put.TableName, current + 2);
2123
+ return;
2124
+ }
2125
+ if ("Delete" in transactItem) {
2126
+ const { Delete: del } = transactItem;
2127
+ const table = store.getTable(del.TableName);
2128
+ table.deleteItem(del.Key);
2129
+ const current = consumedCapacity.get(del.TableName) || 0;
2130
+ consumedCapacity.set(del.TableName, current + 2);
2131
+ return;
2132
+ }
2133
+ if ("Update" in transactItem) {
2134
+ const { Update: update } = transactItem;
2135
+ const table = store.getTable(update.TableName);
2136
+ const existingItem = table.getItem(update.Key);
2137
+ let item = existingItem ? { ...existingItem } : { ...update.Key };
2138
+ item = applyUpdateExpression(item, update.UpdateExpression, {
2139
+ expressionAttributeNames: update.ExpressionAttributeNames,
2140
+ expressionAttributeValues: update.ExpressionAttributeValues
2141
+ });
2142
+ for (const [key, value] of Object.entries(update.Key)) {
2143
+ item[key] = value;
2144
+ }
2145
+ table.updateItem(update.Key, item);
2146
+ const current = consumedCapacity.get(update.TableName) || 0;
2147
+ consumedCapacity.set(update.TableName, current + 2);
2148
+ }
2149
+ }
2150
+
2151
+ // src/server.ts
2152
+ var operations = {
2153
+ CreateTable: createTable,
2154
+ DeleteTable: deleteTable,
2155
+ DescribeTable: describeTable,
2156
+ ListTables: listTables,
2157
+ PutItem: putItem,
2158
+ GetItem: getItem,
2159
+ DeleteItem: deleteItem,
2160
+ UpdateItem: updateItem,
2161
+ Query: query,
2162
+ Scan: scan,
2163
+ BatchGetItem: batchGetItem,
2164
+ BatchWriteItem: batchWriteItem,
2165
+ TransactGetItems: transactGetItems,
2166
+ TransactWriteItems: transactWriteItems
2167
+ };
2168
+ function parseTarget(target) {
2169
+ if (!target) return null;
2170
+ const match = target.match(/^DynamoDB_\d+\.(\w+)$/);
2171
+ return match ? match[1] ?? null : null;
2172
+ }
2173
+ function generateUUID() {
2174
+ if (typeof crypto !== "undefined" && crypto.randomUUID) {
2175
+ return crypto.randomUUID();
2176
+ }
2177
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
2178
+ const r = Math.random() * 16 | 0;
2179
+ const v = c === "x" ? r : r & 3 | 8;
2180
+ return v.toString(16);
2181
+ });
2182
+ }
2183
+ function formatError(error) {
2184
+ if (error instanceof DynamoDBError) {
2185
+ return {
2186
+ body: JSON.stringify(error.toJSON()),
2187
+ status: error.statusCode
2188
+ };
2189
+ }
2190
+ return {
2191
+ body: JSON.stringify({
2192
+ __type: "com.amazonaws.dynamodb.v20120810#InternalServerError",
2193
+ message: error instanceof Error ? error.message : "Internal server error"
2194
+ }),
2195
+ status: 500
2196
+ };
2197
+ }
2198
+ async function handleRequest(store, method, target, getBody) {
2199
+ const requestId = generateUUID();
2200
+ if (method !== "POST") {
2201
+ return {
2202
+ body: JSON.stringify({ message: "Method not allowed" }),
2203
+ status: 405,
2204
+ requestId
2205
+ };
2206
+ }
2207
+ const operation = parseTarget(target);
2208
+ if (!operation) {
2209
+ return {
2210
+ body: JSON.stringify({
2211
+ __type: "com.amazon.coral.service#UnknownOperationException",
2212
+ message: "Unknown operation"
2213
+ }),
2214
+ status: 400,
2215
+ requestId
2216
+ };
2217
+ }
2218
+ const handler = operations[operation];
2219
+ if (!handler) {
2220
+ return {
2221
+ body: JSON.stringify({
2222
+ __type: "com.amazon.coral.service#UnknownOperationException",
2223
+ message: `Unknown operation: ${operation}`
2224
+ }),
2225
+ status: 400,
2226
+ requestId
2227
+ };
2228
+ }
2229
+ let body;
2230
+ try {
2231
+ const text = await getBody();
2232
+ body = text ? JSON.parse(text) : {};
2233
+ } catch {
2234
+ const err = formatError(new SerializationException("Could not parse request body"));
2235
+ return { ...err, requestId };
2236
+ }
2237
+ try {
2238
+ const result = handler(store, body);
2239
+ return {
2240
+ body: JSON.stringify(result),
2241
+ status: 200,
2242
+ requestId
2243
+ };
2244
+ } catch (error) {
2245
+ const err = formatError(error);
2246
+ return { ...err, requestId };
2247
+ }
2248
+ }
2249
+ var isBun = typeof globalThis.Bun !== "undefined";
2250
+ function createBunServer(store, port) {
2251
+ const server = Bun.serve({
2252
+ port,
2253
+ async fetch(req) {
2254
+ const result = await handleRequest(store, req.method, req.headers.get("X-Amz-Target"), () => req.text());
2255
+ return new Response(result.body, {
2256
+ status: result.status,
2257
+ headers: {
2258
+ "Content-Type": "application/x-amz-json-1.0",
2259
+ "x-amzn-RequestId": result.requestId
2260
+ }
2261
+ });
2262
+ }
2263
+ });
2264
+ return {
2265
+ port: server.port ?? port,
2266
+ stop: () => server.stop()
2267
+ };
2268
+ }
2269
+ function createNodeServer(store, port) {
2270
+ return new Promise((resolve, reject) => {
2271
+ const server = createHttpServer(async (req, res) => {
2272
+ const getBody = () => {
2273
+ return new Promise((resolve2, reject2) => {
2274
+ let body = "";
2275
+ req.on("data", (chunk) => {
2276
+ body += chunk.toString();
2277
+ });
2278
+ req.on("end", () => resolve2(body));
2279
+ req.on("error", reject2);
2280
+ });
2281
+ };
2282
+ const result = await handleRequest(
2283
+ store,
2284
+ req.method ?? "GET",
2285
+ req.headers["x-amz-target"],
2286
+ getBody
2287
+ );
2288
+ res.writeHead(result.status, {
2289
+ "Content-Type": "application/x-amz-json-1.0",
2290
+ "x-amzn-RequestId": result.requestId
2291
+ });
2292
+ res.end(result.body);
2293
+ });
2294
+ server.on("error", reject);
2295
+ server.listen(port, () => {
2296
+ const address = server.address();
2297
+ const actualPort = typeof address === "object" && address ? address.port : port;
2298
+ resolve({
2299
+ port: actualPort,
2300
+ stop: () => server.close()
2301
+ });
2302
+ });
2303
+ });
2304
+ }
2305
+ function createServer(store, port) {
2306
+ if (isBun) {
2307
+ return createBunServer(store, port);
2308
+ }
2309
+ return createNodeServer(store, port);
2310
+ }
2311
+
2312
+ // src/store/table.ts
2313
+ var sequenceCounter = 0;
2314
+ function generateSequenceNumber() {
2315
+ return String(++sequenceCounter).padStart(21, "0");
2316
+ }
2317
+ function generateEventId() {
2318
+ return crypto.randomUUID().replace(/-/g, "");
2319
+ }
2320
+ var Table = class {
2321
+ name;
2322
+ keySchema;
2323
+ attributeDefinitions;
2324
+ provisionedThroughput;
2325
+ billingMode;
2326
+ createdAt;
2327
+ tableId;
2328
+ streamSpecification;
2329
+ latestStreamArn;
2330
+ latestStreamLabel;
2331
+ ttlSpecification;
2332
+ items = /* @__PURE__ */ new Map();
2333
+ globalSecondaryIndexes = /* @__PURE__ */ new Map();
2334
+ localSecondaryIndexes = /* @__PURE__ */ new Map();
2335
+ streamCallbacks = /* @__PURE__ */ new Set();
2336
+ region;
2337
+ constructor(options, region = "us-east-1") {
2338
+ this.name = options.tableName;
2339
+ this.keySchema = options.keySchema;
2340
+ this.attributeDefinitions = options.attributeDefinitions;
2341
+ this.provisionedThroughput = options.provisionedThroughput;
2342
+ this.billingMode = options.billingMode ?? (options.provisionedThroughput ? "PROVISIONED" : "PAY_PER_REQUEST");
2343
+ this.createdAt = Date.now();
2344
+ this.tableId = crypto.randomUUID();
2345
+ this.region = region;
2346
+ this.streamSpecification = options.streamSpecification;
2347
+ this.ttlSpecification = options.timeToLiveSpecification;
2348
+ if (options.streamSpecification?.StreamEnabled) {
2349
+ this.latestStreamLabel = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
2350
+ this.latestStreamArn = `arn:aws:dynamodb:${region}:000000000000:table/${this.name}/stream/${this.latestStreamLabel}`;
2351
+ }
2352
+ if (options.globalSecondaryIndexes) {
2353
+ for (const gsi of options.globalSecondaryIndexes) {
2354
+ this.globalSecondaryIndexes.set(gsi.IndexName, {
2355
+ keySchema: gsi.KeySchema,
2356
+ projection: gsi.Projection,
2357
+ provisionedThroughput: gsi.ProvisionedThroughput,
2358
+ items: /* @__PURE__ */ new Map()
2359
+ });
2360
+ }
2361
+ }
2362
+ if (options.localSecondaryIndexes) {
2363
+ for (const lsi of options.localSecondaryIndexes) {
2364
+ this.localSecondaryIndexes.set(lsi.IndexName, {
2365
+ keySchema: lsi.KeySchema,
2366
+ projection: lsi.Projection,
2367
+ items: /* @__PURE__ */ new Map()
2368
+ });
2369
+ }
2370
+ }
2371
+ }
2372
+ getHashKeyName() {
2373
+ return getHashKey(this.keySchema);
2374
+ }
2375
+ getRangeKeyName() {
2376
+ return getRangeKey(this.keySchema);
2377
+ }
2378
+ getTtlAttributeName() {
2379
+ if (this.ttlSpecification?.Enabled) {
2380
+ return this.ttlSpecification.AttributeName;
2381
+ }
2382
+ return void 0;
2383
+ }
2384
+ setTtlSpecification(spec) {
2385
+ this.ttlSpecification = spec;
2386
+ }
2387
+ getTtlSpecification() {
2388
+ return this.ttlSpecification;
2389
+ }
2390
+ describe() {
2391
+ const desc = {
2392
+ TableName: this.name,
2393
+ TableStatus: "ACTIVE",
2394
+ CreationDateTime: this.createdAt / 1e3,
2395
+ TableArn: `arn:aws:dynamodb:${this.region}:000000000000:table/${this.name}`,
2396
+ TableId: this.tableId,
2397
+ KeySchema: this.keySchema,
2398
+ AttributeDefinitions: this.attributeDefinitions,
2399
+ ItemCount: this.items.size,
2400
+ TableSizeBytes: this.estimateTableSize()
2401
+ };
2402
+ if (this.billingMode === "PROVISIONED" && this.provisionedThroughput) {
2403
+ desc.ProvisionedThroughput = {
2404
+ ReadCapacityUnits: this.provisionedThroughput.ReadCapacityUnits,
2405
+ WriteCapacityUnits: this.provisionedThroughput.WriteCapacityUnits,
2406
+ NumberOfDecreasesToday: 0
2407
+ };
2408
+ } else {
2409
+ desc.BillingModeSummary = {
2410
+ BillingMode: "PAY_PER_REQUEST"
2411
+ };
2412
+ }
2413
+ if (this.globalSecondaryIndexes.size > 0) {
2414
+ desc.GlobalSecondaryIndexes = this.describeGlobalSecondaryIndexes();
2415
+ }
2416
+ if (this.localSecondaryIndexes.size > 0) {
2417
+ desc.LocalSecondaryIndexes = this.describeLocalSecondaryIndexes();
2418
+ }
2419
+ if (this.streamSpecification) {
2420
+ desc.StreamSpecification = this.streamSpecification;
2421
+ if (this.latestStreamArn) {
2422
+ desc.LatestStreamArn = this.latestStreamArn;
2423
+ desc.LatestStreamLabel = this.latestStreamLabel;
2424
+ }
2425
+ }
2426
+ return desc;
2427
+ }
2428
+ describeGlobalSecondaryIndexes() {
2429
+ const indexes = [];
2430
+ for (const [name, data] of this.globalSecondaryIndexes) {
2431
+ let itemCount = 0;
2432
+ for (const keys of data.items.values()) {
2433
+ itemCount += keys.size;
2434
+ }
2435
+ indexes.push({
2436
+ IndexName: name,
2437
+ KeySchema: data.keySchema,
2438
+ Projection: data.projection,
2439
+ IndexStatus: "ACTIVE",
2440
+ ProvisionedThroughput: data.provisionedThroughput,
2441
+ IndexSizeBytes: 0,
2442
+ ItemCount: itemCount,
2443
+ IndexArn: `arn:aws:dynamodb:${this.region}:000000000000:table/${this.name}/index/${name}`
2444
+ });
2445
+ }
2446
+ return indexes;
2447
+ }
2448
+ describeLocalSecondaryIndexes() {
2449
+ const indexes = [];
2450
+ for (const [name, data] of this.localSecondaryIndexes) {
2451
+ let itemCount = 0;
2452
+ for (const keys of data.items.values()) {
2453
+ itemCount += keys.size;
2454
+ }
2455
+ indexes.push({
2456
+ IndexName: name,
2457
+ KeySchema: data.keySchema,
2458
+ Projection: data.projection,
2459
+ IndexSizeBytes: 0,
2460
+ ItemCount: itemCount,
2461
+ IndexArn: `arn:aws:dynamodb:${this.region}:000000000000:table/${this.name}/index/${name}`
2462
+ });
2463
+ }
2464
+ return indexes;
2465
+ }
2466
+ estimateTableSize() {
2467
+ let size = 0;
2468
+ for (const item of this.items.values()) {
2469
+ size += estimateItemSize(item);
2470
+ }
2471
+ return size;
2472
+ }
2473
+ getItem(key) {
2474
+ const keyString = serializeKey(key, this.keySchema);
2475
+ const item = this.items.get(keyString);
2476
+ return item ? deepClone(item) : void 0;
2477
+ }
2478
+ putItem(item) {
2479
+ const key = extractKey(item, this.keySchema);
2480
+ const keyString = serializeKey(key, this.keySchema);
2481
+ const oldItem = this.items.get(keyString);
2482
+ this.items.set(keyString, deepClone(item));
2483
+ this.updateIndexes(item, oldItem);
2484
+ this.emitStreamRecord(oldItem ? "MODIFY" : "INSERT", key, oldItem, item);
2485
+ return oldItem ? deepClone(oldItem) : void 0;
2486
+ }
2487
+ deleteItem(key) {
2488
+ const keyString = serializeKey(key, this.keySchema);
2489
+ const oldItem = this.items.get(keyString);
2490
+ if (oldItem) {
2491
+ this.items.delete(keyString);
2492
+ this.removeFromIndexes(oldItem);
2493
+ this.emitStreamRecord("REMOVE", key, oldItem, void 0);
2494
+ }
2495
+ return oldItem ? deepClone(oldItem) : void 0;
2496
+ }
2497
+ updateItem(key, updatedItem) {
2498
+ const keyString = serializeKey(key, this.keySchema);
2499
+ const oldItem = this.items.get(keyString);
2500
+ this.items.set(keyString, deepClone(updatedItem));
2501
+ this.updateIndexes(updatedItem, oldItem);
2502
+ this.emitStreamRecord(oldItem ? "MODIFY" : "INSERT", key, oldItem, updatedItem);
2503
+ return oldItem ? deepClone(oldItem) : void 0;
2504
+ }
2505
+ updateIndexes(newItem, oldItem) {
2506
+ if (oldItem) {
2507
+ this.removeFromIndexes(oldItem);
2508
+ }
2509
+ this.addToIndexes(newItem);
2510
+ }
2511
+ addToIndexes(item) {
2512
+ const primaryKey = serializeKey(extractKey(item, this.keySchema), this.keySchema);
2513
+ for (const [, indexData] of this.globalSecondaryIndexes) {
2514
+ const indexKey = this.buildIndexKey(item, indexData.keySchema);
2515
+ if (indexKey) {
2516
+ let keys = indexData.items.get(indexKey);
2517
+ if (!keys) {
2518
+ keys = /* @__PURE__ */ new Set();
2519
+ indexData.items.set(indexKey, keys);
2520
+ }
2521
+ keys.add(primaryKey);
2522
+ }
2523
+ }
2524
+ for (const [, indexData] of this.localSecondaryIndexes) {
2525
+ const indexKey = this.buildIndexKey(item, indexData.keySchema);
2526
+ if (indexKey) {
2527
+ let keys = indexData.items.get(indexKey);
2528
+ if (!keys) {
2529
+ keys = /* @__PURE__ */ new Set();
2530
+ indexData.items.set(indexKey, keys);
2531
+ }
2532
+ keys.add(primaryKey);
2533
+ }
2534
+ }
2535
+ }
2536
+ removeFromIndexes(item) {
2537
+ const primaryKey = serializeKey(extractKey(item, this.keySchema), this.keySchema);
2538
+ for (const [, indexData] of this.globalSecondaryIndexes) {
2539
+ const indexKey = this.buildIndexKey(item, indexData.keySchema);
2540
+ if (indexKey) {
2541
+ const keys = indexData.items.get(indexKey);
2542
+ if (keys) {
2543
+ keys.delete(primaryKey);
2544
+ if (keys.size === 0) {
2545
+ indexData.items.delete(indexKey);
2546
+ }
2547
+ }
2548
+ }
2549
+ }
2550
+ for (const [, indexData] of this.localSecondaryIndexes) {
2551
+ const indexKey = this.buildIndexKey(item, indexData.keySchema);
2552
+ if (indexKey) {
2553
+ const keys = indexData.items.get(indexKey);
2554
+ if (keys) {
2555
+ keys.delete(primaryKey);
2556
+ if (keys.size === 0) {
2557
+ indexData.items.delete(indexKey);
2558
+ }
2559
+ }
2560
+ }
2561
+ }
2562
+ }
2563
+ buildIndexKey(item, keySchema) {
2564
+ const hashAttr = getHashKey(keySchema);
2565
+ if (!item[hashAttr]) {
2566
+ return null;
2567
+ }
2568
+ const rangeAttr = getRangeKey(keySchema);
2569
+ if (rangeAttr && !item[rangeAttr]) {
2570
+ return null;
2571
+ }
2572
+ return serializeKey(extractKey(item, keySchema), keySchema);
2573
+ }
2574
+ scan(limit, exclusiveStartKey) {
2575
+ const hashAttr = this.getHashKeyName();
2576
+ const rangeAttr = this.getRangeKeyName();
2577
+ const allItems = Array.from(this.items.values()).map((item) => deepClone(item));
2578
+ allItems.sort((a, b) => {
2579
+ const hashA = a[hashAttr];
2580
+ const hashB = b[hashAttr];
2581
+ if (hashA && hashB) {
2582
+ const hashCmp = hashAttributeValue(hashA).localeCompare(hashAttributeValue(hashB));
2583
+ if (hashCmp !== 0) return hashCmp;
2584
+ }
2585
+ if (rangeAttr) {
2586
+ const rangeA = a[rangeAttr];
2587
+ const rangeB = b[rangeAttr];
2588
+ if (rangeA && rangeB) {
2589
+ return this.compareAttributes(rangeA, rangeB);
2590
+ }
2591
+ }
2592
+ return 0;
2593
+ });
2594
+ let startIdx = 0;
2595
+ if (exclusiveStartKey) {
2596
+ const startKey = serializeKey(exclusiveStartKey, this.keySchema);
2597
+ startIdx = allItems.findIndex(
2598
+ (item) => serializeKey(extractKey(item, this.keySchema), this.keySchema) === startKey
2599
+ );
2600
+ if (startIdx !== -1) {
2601
+ startIdx++;
2602
+ } else {
2603
+ startIdx = 0;
2604
+ }
2605
+ }
2606
+ const items = limit ? allItems.slice(startIdx, startIdx + limit) : allItems.slice(startIdx);
2607
+ const hasMore = limit ? startIdx + limit < allItems.length : false;
2608
+ const lastItem = items[items.length - 1];
2609
+ return {
2610
+ items,
2611
+ lastEvaluatedKey: hasMore && lastItem ? extractKey(lastItem, this.keySchema) : void 0
2612
+ };
2613
+ }
2614
+ queryByHashKey(hashValue, options) {
2615
+ const hashAttr = this.getHashKeyName();
2616
+ const rangeAttr = this.getRangeKeyName();
2617
+ const matchingItems = [];
2618
+ for (const item of this.items.values()) {
2619
+ const itemHashValue = item[hashAttr];
2620
+ const queryHashValue = hashValue[hashAttr];
2621
+ if (itemHashValue && queryHashValue && this.attributeEquals(itemHashValue, queryHashValue)) {
2622
+ matchingItems.push(deepClone(item));
2623
+ }
2624
+ }
2625
+ if (rangeAttr) {
2626
+ matchingItems.sort((a, b) => {
2627
+ const aVal = a[rangeAttr];
2628
+ const bVal = b[rangeAttr];
2629
+ if (!aVal && !bVal) return 0;
2630
+ if (!aVal) return 1;
2631
+ if (!bVal) return -1;
2632
+ const cmp = this.compareAttributes(aVal, bVal);
2633
+ return options?.scanIndexForward === false ? -cmp : cmp;
2634
+ });
2635
+ }
2636
+ let startIdx = 0;
2637
+ if (options?.exclusiveStartKey) {
2638
+ const startKey = serializeKey(options.exclusiveStartKey, this.keySchema);
2639
+ startIdx = matchingItems.findIndex(
2640
+ (item) => serializeKey(extractKey(item, this.keySchema), this.keySchema) === startKey
2641
+ );
2642
+ if (startIdx !== -1) {
2643
+ startIdx++;
2644
+ } else {
2645
+ startIdx = 0;
2646
+ }
2647
+ }
2648
+ const limit = options?.limit;
2649
+ const sliced = limit ? matchingItems.slice(startIdx, startIdx + limit) : matchingItems.slice(startIdx);
2650
+ const hasMore = limit ? startIdx + limit < matchingItems.length : false;
2651
+ const lastItem = sliced[sliced.length - 1];
2652
+ return {
2653
+ items: sliced,
2654
+ lastEvaluatedKey: hasMore && lastItem ? extractKey(lastItem, this.keySchema) : void 0
2655
+ };
2656
+ }
2657
+ queryIndex(indexName, hashValue, options) {
2658
+ const gsi = this.globalSecondaryIndexes.get(indexName);
2659
+ const lsi = this.localSecondaryIndexes.get(indexName);
2660
+ const indexData = gsi || lsi;
2661
+ if (!indexData) {
2662
+ throw new Error(`Index ${indexName} not found`);
2663
+ }
2664
+ const indexHashAttr = getHashKey(indexData.keySchema);
2665
+ const indexRangeAttr = getRangeKey(indexData.keySchema);
2666
+ const matchingItems = [];
2667
+ for (const item of this.items.values()) {
2668
+ const itemHashValue = item[indexHashAttr];
2669
+ const queryHashValue = hashValue[indexHashAttr];
2670
+ if (!itemHashValue || !queryHashValue || !this.attributeEquals(itemHashValue, queryHashValue)) {
2671
+ continue;
2672
+ }
2673
+ if (indexRangeAttr && !item[indexRangeAttr]) {
2674
+ continue;
2675
+ }
2676
+ matchingItems.push(deepClone(item));
2677
+ }
2678
+ if (indexRangeAttr) {
2679
+ matchingItems.sort((a, b) => {
2680
+ const aVal = a[indexRangeAttr];
2681
+ const bVal = b[indexRangeAttr];
2682
+ if (!aVal && !bVal) return 0;
2683
+ if (!aVal) return 1;
2684
+ if (!bVal) return -1;
2685
+ const cmp = this.compareAttributes(aVal, bVal);
2686
+ return options?.scanIndexForward === false ? -cmp : cmp;
2687
+ });
2688
+ }
2689
+ let startIdx = 0;
2690
+ if (options?.exclusiveStartKey) {
2691
+ const combinedKeySchema = [...indexData.keySchema];
2692
+ const tableRangeKey = this.getRangeKeyName();
2693
+ if (tableRangeKey && !combinedKeySchema.some((k) => k.AttributeName === tableRangeKey)) {
2694
+ combinedKeySchema.push({ AttributeName: tableRangeKey, KeyType: "RANGE" });
2695
+ }
2696
+ const tableHashKey = this.getHashKeyName();
2697
+ if (!combinedKeySchema.some((k) => k.AttributeName === tableHashKey)) {
2698
+ combinedKeySchema.push({ AttributeName: tableHashKey, KeyType: "HASH" });
2699
+ }
2700
+ const startKey = serializeKey(options.exclusiveStartKey, combinedKeySchema);
2701
+ startIdx = matchingItems.findIndex(
2702
+ (item) => serializeKey(extractKey(item, combinedKeySchema), combinedKeySchema) === startKey
2703
+ );
2704
+ if (startIdx !== -1) {
2705
+ startIdx++;
2706
+ } else {
2707
+ startIdx = 0;
2708
+ }
2709
+ }
2710
+ const limit = options?.limit;
2711
+ const sliced = limit ? matchingItems.slice(startIdx, startIdx + limit) : matchingItems.slice(startIdx);
2712
+ const hasMore = limit ? startIdx + limit < matchingItems.length : false;
2713
+ const lastItem = sliced[sliced.length - 1];
2714
+ let lastEvaluatedKey;
2715
+ if (hasMore && lastItem) {
2716
+ lastEvaluatedKey = extractKey(lastItem, this.keySchema);
2717
+ for (const keyElement of indexData.keySchema) {
2718
+ const attrValue = lastItem[keyElement.AttributeName];
2719
+ if (attrValue) {
2720
+ lastEvaluatedKey[keyElement.AttributeName] = attrValue;
2721
+ }
2722
+ }
2723
+ }
2724
+ return {
2725
+ items: sliced,
2726
+ lastEvaluatedKey,
2727
+ indexKeySchema: indexData.keySchema
2728
+ };
2729
+ }
2730
+ scanIndex(indexName, limit, exclusiveStartKey) {
2731
+ const gsi = this.globalSecondaryIndexes.get(indexName);
2732
+ const lsi = this.localSecondaryIndexes.get(indexName);
2733
+ const indexData = gsi || lsi;
2734
+ if (!indexData) {
2735
+ throw new Error(`Index ${indexName} not found`);
2736
+ }
2737
+ const indexHashAttr = getHashKey(indexData.keySchema);
2738
+ const indexRangeAttr = getRangeKey(indexData.keySchema);
2739
+ const matchingItems = [];
2740
+ for (const item of this.items.values()) {
2741
+ if (item[indexHashAttr]) {
2742
+ matchingItems.push(deepClone(item));
2743
+ }
2744
+ }
2745
+ matchingItems.sort((a, b) => {
2746
+ const hashA = a[indexHashAttr];
2747
+ const hashB = b[indexHashAttr];
2748
+ if (hashA && hashB) {
2749
+ const hashCmp = hashAttributeValue(hashA).localeCompare(hashAttributeValue(hashB));
2750
+ if (hashCmp !== 0) return hashCmp;
2751
+ }
2752
+ if (indexRangeAttr) {
2753
+ const rangeA = a[indexRangeAttr];
2754
+ const rangeB = b[indexRangeAttr];
2755
+ if (rangeA && rangeB) {
2756
+ return this.compareAttributes(rangeA, rangeB);
2757
+ }
2758
+ }
2759
+ return 0;
2760
+ });
2761
+ let startIdx = 0;
2762
+ if (exclusiveStartKey) {
2763
+ const startKey = serializeKey(exclusiveStartKey, this.keySchema);
2764
+ startIdx = matchingItems.findIndex(
2765
+ (item) => serializeKey(extractKey(item, this.keySchema), this.keySchema) === startKey
2766
+ );
2767
+ if (startIdx !== -1) {
2768
+ startIdx++;
2769
+ } else {
2770
+ startIdx = 0;
2771
+ }
2772
+ }
2773
+ const sliced = limit ? matchingItems.slice(startIdx, startIdx + limit) : matchingItems.slice(startIdx);
2774
+ const hasMore = limit ? startIdx + limit < matchingItems.length : false;
2775
+ const lastItem = sliced[sliced.length - 1];
2776
+ let lastEvaluatedKey;
2777
+ if (hasMore && lastItem) {
2778
+ lastEvaluatedKey = extractKey(lastItem, this.keySchema);
2779
+ for (const keyElement of indexData.keySchema) {
2780
+ const attrValue = lastItem[keyElement.AttributeName];
2781
+ if (attrValue) {
2782
+ lastEvaluatedKey[keyElement.AttributeName] = attrValue;
2783
+ }
2784
+ }
2785
+ }
2786
+ return {
2787
+ items: sliced,
2788
+ lastEvaluatedKey,
2789
+ indexKeySchema: indexData.keySchema
2790
+ };
2791
+ }
2792
+ hasIndex(indexName) {
2793
+ return this.globalSecondaryIndexes.has(indexName) || this.localSecondaryIndexes.has(indexName);
2794
+ }
2795
+ getIndexKeySchema(indexName) {
2796
+ return this.globalSecondaryIndexes.get(indexName)?.keySchema || this.localSecondaryIndexes.get(indexName)?.keySchema;
2797
+ }
2798
+ attributeEquals(a, b) {
2799
+ if ("S" in a && "S" in b) return a.S === b.S;
2800
+ if ("N" in a && "N" in b) return a.N === b.N;
2801
+ if ("B" in a && "B" in b) return a.B === b.B;
2802
+ return JSON.stringify(a) === JSON.stringify(b);
2803
+ }
2804
+ compareAttributes(a, b) {
2805
+ if ("S" in a && "S" in b) return a.S.localeCompare(b.S);
2806
+ if ("N" in a && "N" in b) return parseFloat(a.N) - parseFloat(b.N);
2807
+ if ("B" in a && "B" in b) return a.B.localeCompare(b.B);
2808
+ return 0;
2809
+ }
2810
+ getAllItems() {
2811
+ return Array.from(this.items.values()).map((item) => deepClone(item));
2812
+ }
2813
+ clear() {
2814
+ this.items.clear();
2815
+ for (const index of this.globalSecondaryIndexes.values()) {
2816
+ index.items.clear();
2817
+ }
2818
+ for (const index of this.localSecondaryIndexes.values()) {
2819
+ index.items.clear();
2820
+ }
2821
+ }
2822
+ onStreamRecord(callback) {
2823
+ this.streamCallbacks.add(callback);
2824
+ return () => {
2825
+ this.streamCallbacks.delete(callback);
2826
+ };
2827
+ }
2828
+ emitStreamRecord(eventName, keys, oldImage, newImage) {
2829
+ if (!this.streamSpecification?.StreamEnabled || this.streamCallbacks.size === 0) {
2830
+ return;
2831
+ }
2832
+ const viewType = this.streamSpecification.StreamViewType || "NEW_AND_OLD_IMAGES";
2833
+ const record = {
2834
+ eventID: generateEventId(),
2835
+ eventName,
2836
+ eventVersion: "1.1",
2837
+ eventSource: "aws:dynamodb",
2838
+ awsRegion: this.region,
2839
+ dynamodb: {
2840
+ ApproximateCreationDateTime: Date.now() / 1e3,
2841
+ Keys: keys,
2842
+ SequenceNumber: generateSequenceNumber(),
2843
+ SizeBytes: estimateItemSize(keys),
2844
+ StreamViewType: viewType
2845
+ }
2846
+ };
2847
+ if (viewType === "NEW_IMAGE" || viewType === "NEW_AND_OLD_IMAGES") {
2848
+ if (newImage) {
2849
+ record.dynamodb.NewImage = deepClone(newImage);
2850
+ }
2851
+ }
2852
+ if (viewType === "OLD_IMAGE" || viewType === "NEW_AND_OLD_IMAGES") {
2853
+ if (oldImage) {
2854
+ record.dynamodb.OldImage = deepClone(oldImage);
2855
+ }
2856
+ }
2857
+ for (const callback of this.streamCallbacks) {
2858
+ try {
2859
+ callback(record);
2860
+ } catch {
2861
+ }
2862
+ }
2863
+ }
2864
+ expireTtlItems(currentTimeSeconds) {
2865
+ const ttlAttr = this.getTtlAttributeName();
2866
+ if (!ttlAttr) {
2867
+ return [];
2868
+ }
2869
+ const expiredItems = [];
2870
+ for (const [keyString, item] of this.items) {
2871
+ const ttlValue = item[ttlAttr];
2872
+ if (ttlValue && "N" in ttlValue) {
2873
+ const ttlTimestamp = parseInt(ttlValue.N, 10);
2874
+ if (ttlTimestamp <= currentTimeSeconds) {
2875
+ expiredItems.push(deepClone(item));
2876
+ const key = extractKey(item, this.keySchema);
2877
+ this.items.delete(keyString);
2878
+ this.removeFromIndexes(item);
2879
+ this.emitStreamRecord("REMOVE", key, item, void 0);
2880
+ }
2881
+ }
2882
+ }
2883
+ return expiredItems;
2884
+ }
2885
+ };
2886
+
2887
+ // src/store/index.ts
2888
+ var TableStore = class {
2889
+ tables = /* @__PURE__ */ new Map();
2890
+ region;
32
2891
  constructor(region = "us-east-1") {
33
2892
  this.region = region;
34
- this.endpoint = (0, import_url_parser.parseUrl)(`http://localhost`);
35
2893
  }
36
- client;
37
- documentClient;
38
- endpoint;
39
- process;
40
- async listen(port) {
41
- if (this.process) {
42
- throw new Error(`DynamoDB server is already listening on port: ${this.endpoint.port}`);
2894
+ createTable(input) {
2895
+ if (this.tables.has(input.TableName)) {
2896
+ throw new ResourceInUseException(`Table already exists: ${input.TableName}`);
43
2897
  }
44
- if (port < 0 || port >= 65536) {
45
- throw new RangeError(`Port should be >= 0 and < 65536. Received ${port}.`);
2898
+ const table = new Table(
2899
+ {
2900
+ tableName: input.TableName,
2901
+ keySchema: input.KeySchema,
2902
+ attributeDefinitions: input.AttributeDefinitions,
2903
+ provisionedThroughput: input.ProvisionedThroughput,
2904
+ billingMode: input.BillingMode,
2905
+ globalSecondaryIndexes: input.GlobalSecondaryIndexes,
2906
+ localSecondaryIndexes: input.LocalSecondaryIndexes,
2907
+ streamSpecification: input.StreamSpecification,
2908
+ timeToLiveSpecification: input.TimeToLiveSpecification
2909
+ },
2910
+ this.region
2911
+ );
2912
+ this.tables.set(input.TableName, table);
2913
+ return table;
2914
+ }
2915
+ getTable(tableName) {
2916
+ const table = this.tables.get(tableName);
2917
+ if (!table) {
2918
+ throw new ResourceNotFoundException(`Requested resource not found: Table: ${tableName} not found`);
46
2919
  }
47
- this.endpoint.port = port;
48
- this.process = await (0, import_dynamo_db_local.spawn)({ port });
2920
+ return table;
49
2921
  }
50
- /** Kill the DynamoDB server. */
51
- async kill() {
52
- if (this.process) {
53
- await this.process.kill();
54
- this.process = void 0;
2922
+ hasTable(tableName) {
2923
+ return this.tables.has(tableName);
2924
+ }
2925
+ deleteTable(tableName) {
2926
+ const table = this.tables.get(tableName);
2927
+ if (!table) {
2928
+ throw new ResourceNotFoundException(`Requested resource not found: Table: ${tableName} not found`);
55
2929
  }
2930
+ this.tables.delete(tableName);
2931
+ return table;
56
2932
  }
57
- /** Ping the DynamoDB server if its ready. */
58
- async ping() {
59
- const client = this.getClient();
60
- const command = new import_client_dynamodb.ListTablesCommand({});
61
- try {
62
- const response = await client.send(command);
63
- return Array.isArray(response.TableNames);
64
- } catch (error) {
65
- return false;
2933
+ listTables(exclusiveStartTableName, limit) {
2934
+ const allNames = Array.from(this.tables.keys()).sort();
2935
+ let startIdx = 0;
2936
+ if (exclusiveStartTableName) {
2937
+ startIdx = allNames.indexOf(exclusiveStartTableName);
2938
+ if (startIdx !== -1) {
2939
+ startIdx++;
2940
+ } else {
2941
+ startIdx = 0;
2942
+ }
66
2943
  }
2944
+ const tableNames = limit ? allNames.slice(startIdx, startIdx + limit) : allNames.slice(startIdx);
2945
+ const hasMore = limit ? startIdx + tableNames.length < allNames.length : false;
2946
+ const lastEvaluatedTableName = hasMore ? tableNames[tableNames.length - 1] : void 0;
2947
+ return { tableNames, lastEvaluatedTableName };
67
2948
  }
68
- /** Ping the DynamoDB server untill its ready. */
69
- async wait(times = 10) {
70
- while (times--) {
71
- if (await this.ping()) {
72
- return;
2949
+ clear() {
2950
+ this.tables.clear();
2951
+ }
2952
+ expireTtlItems(currentTimeSeconds) {
2953
+ for (const table of this.tables.values()) {
2954
+ table.expireTtlItems(currentTimeSeconds);
2955
+ }
2956
+ }
2957
+ };
2958
+
2959
+ // src/dynamodb-server.ts
2960
+ var DynamoDBServer = class {
2961
+ server;
2962
+ javaServer;
2963
+ store;
2964
+ clock;
2965
+ config;
2966
+ endpoint;
2967
+ client;
2968
+ documentClient;
2969
+ streamCallbacks = /* @__PURE__ */ new Map();
2970
+ constructor(config = {}) {
2971
+ this.config = {
2972
+ port: config.port ?? 0,
2973
+ region: config.region ?? "us-east-1",
2974
+ hostname: config.hostname ?? "localhost",
2975
+ engine: config.engine ?? "memory"
2976
+ };
2977
+ this.endpoint = {
2978
+ protocol: "http:",
2979
+ hostname: this.config.hostname,
2980
+ path: "/"
2981
+ };
2982
+ this.store = new TableStore(this.config.region);
2983
+ this.clock = new VirtualClock();
2984
+ }
2985
+ async listen(port) {
2986
+ if (this.server || this.javaServer) {
2987
+ throw new Error("Server is already running");
2988
+ }
2989
+ const listenPort = port ?? this.config.port;
2990
+ if (this.config.engine === "java") {
2991
+ this.javaServer = createJavaServer(listenPort, this.config.region);
2992
+ await this.javaServer.wait();
2993
+ this.config.port = this.javaServer.port;
2994
+ } else {
2995
+ const serverOrPromise = createServer(this.store, listenPort);
2996
+ if (serverOrPromise instanceof Promise) {
2997
+ this.server = await serverOrPromise;
2998
+ } else {
2999
+ this.server = serverOrPromise;
73
3000
  }
74
- await (0, import_sleep_await.sleepAwait)(100 * times);
3001
+ this.config.port = this.server.port;
75
3002
  }
76
- throw new Error("DynamoDB server is unavailable");
3003
+ this.endpoint.port = this.config.port;
3004
+ }
3005
+ async stop() {
3006
+ if (this.server) {
3007
+ this.server.stop();
3008
+ this.server = void 0;
3009
+ }
3010
+ if (this.javaServer) {
3011
+ await this.javaServer.stop();
3012
+ this.javaServer = void 0;
3013
+ }
3014
+ this.client = void 0;
3015
+ this.documentClient = void 0;
3016
+ }
3017
+ get port() {
3018
+ return this.config.port;
3019
+ }
3020
+ get engine() {
3021
+ return this.config.engine;
3022
+ }
3023
+ getEndpoint() {
3024
+ return this.endpoint;
77
3025
  }
78
- /** Get DynamoDBClient connected to dynamodb local. */
79
3026
  getClient() {
80
3027
  if (!this.client) {
81
- this.client = new import_client_dynamodb.DynamoDBClient({
82
- maxAttempts: 3,
3028
+ this.client = new DynamoDBClient2({
83
3029
  endpoint: this.endpoint,
84
- region: this.region,
85
- tls: false,
3030
+ region: this.config.region,
86
3031
  credentials: {
87
3032
  accessKeyId: "fake",
88
3033
  secretAccessKey: "fake"
@@ -91,10 +3036,9 @@ var DynamoDBServer = class {
91
3036
  }
92
3037
  return this.client;
93
3038
  }
94
- /** Get DynamoDBDocumentClient connected to dynamodb local. */
95
3039
  getDocumentClient() {
96
3040
  if (!this.documentClient) {
97
- this.documentClient = import_lib_dynamodb.DynamoDBDocumentClient.from(this.getClient(), {
3041
+ this.documentClient = DynamoDBDocumentClient.from(this.getClient(), {
98
3042
  marshallOptions: {
99
3043
  removeUndefinedValues: true
100
3044
  }
@@ -102,8 +3046,91 @@ var DynamoDBServer = class {
102
3046
  }
103
3047
  return this.documentClient;
104
3048
  }
3049
+ /**
3050
+ * Advance the virtual clock by the specified number of milliseconds.
3051
+ * This triggers TTL processing for expired items.
3052
+ * Only available when using the 'memory' engine.
3053
+ */
3054
+ advanceTime(ms) {
3055
+ if (this.config.engine === "java") {
3056
+ throw new Error("advanceTime is not supported with the Java engine");
3057
+ }
3058
+ this.clock.advance(ms);
3059
+ this.processTTL();
3060
+ }
3061
+ /**
3062
+ * Set the virtual clock to the specified timestamp.
3063
+ * This triggers TTL processing for expired items.
3064
+ * Only available when using the 'memory' engine.
3065
+ */
3066
+ setTime(timestamp) {
3067
+ if (this.config.engine === "java") {
3068
+ throw new Error("setTime is not supported with the Java engine");
3069
+ }
3070
+ this.clock.set(timestamp);
3071
+ this.processTTL();
3072
+ }
3073
+ /**
3074
+ * Get the current virtual clock time.
3075
+ * Only available when using the 'memory' engine.
3076
+ */
3077
+ getTime() {
3078
+ if (this.config.engine === "java") {
3079
+ throw new Error("getTime is not supported with the Java engine");
3080
+ }
3081
+ return this.clock.now();
3082
+ }
3083
+ processTTL() {
3084
+ const currentTimeSeconds = this.clock.nowInSeconds();
3085
+ this.store.expireTtlItems(currentTimeSeconds);
3086
+ }
3087
+ /**
3088
+ * Register a callback for stream records on a specific table.
3089
+ * Only available when using the 'memory' engine.
3090
+ */
3091
+ onStreamRecord(tableName, callback) {
3092
+ if (this.config.engine === "java") {
3093
+ throw new Error("onStreamRecord is not supported with the Java engine");
3094
+ }
3095
+ const table = this.store.getTable(tableName);
3096
+ return table.onStreamRecord(callback);
3097
+ }
3098
+ /**
3099
+ * Reset the server, clearing all tables and data.
3100
+ * Only available when using the 'memory' engine.
3101
+ */
3102
+ reset() {
3103
+ if (this.config.engine === "java") {
3104
+ throw new Error("reset is not supported with the Java engine");
3105
+ }
3106
+ this.store.clear();
3107
+ this.clock.reset();
3108
+ this.streamCallbacks.clear();
3109
+ }
3110
+ /**
3111
+ * Get the internal table store.
3112
+ * Only available when using the 'memory' engine.
3113
+ */
3114
+ getTableStore() {
3115
+ if (this.config.engine === "java") {
3116
+ throw new Error("getTableStore is not supported with the Java engine");
3117
+ }
3118
+ return this.store;
3119
+ }
3120
+ };
3121
+ export {
3122
+ ConditionalCheckFailedException,
3123
+ DynamoDBError,
3124
+ DynamoDBServer,
3125
+ IdempotentParameterMismatchException,
3126
+ InternalServerError,
3127
+ ItemCollectionSizeLimitExceededException,
3128
+ ProvisionedThroughputExceededException,
3129
+ ResourceInUseException,
3130
+ ResourceNotFoundException,
3131
+ SerializationException,
3132
+ TransactionCanceledException,
3133
+ TransactionConflictException,
3134
+ ValidationException,
3135
+ VirtualClock
105
3136
  };
106
- // Annotate the CommonJS export names for ESM import in node:
107
- 0 && (module.exports = {
108
- DynamoDBServer
109
- });