dynamo-query-engine 1.0.4 → 1.0.5
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/package.json
CHANGED
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
validateFilterField,
|
|
10
10
|
validateSortField,
|
|
11
11
|
validateFilterOperator,
|
|
12
|
-
validatePaginationModel,
|
|
13
12
|
} from "../utils/validation.js";
|
|
14
13
|
import { decodeCursor, validateCursor } from "../utils/cursor.js";
|
|
15
14
|
|
|
@@ -18,13 +17,13 @@ import { decodeCursor, validateCursor } from "../utils/cursor.js";
|
|
|
18
17
|
* @param {Object} params - Query parameters
|
|
19
18
|
* @param {FilterModel} params.filterModel - Filter model
|
|
20
19
|
* @param {SortModel} params.sortModel - Sort model
|
|
21
|
-
* @param {number} params.
|
|
20
|
+
* @param {number} params.limit - Query limit
|
|
22
21
|
* @returns {string} SHA256 hash of the query parameters
|
|
23
22
|
*/
|
|
24
|
-
function hashQuery({ filterModel, sortModel,
|
|
23
|
+
function hashQuery({ filterModel, sortModel, limit }) {
|
|
25
24
|
return crypto
|
|
26
25
|
.createHash("sha256")
|
|
27
|
-
.update(JSON.stringify({ filterModel, sortModel,
|
|
26
|
+
.update(JSON.stringify({ filterModel, sortModel, limit }))
|
|
28
27
|
.digest("hex");
|
|
29
28
|
}
|
|
30
29
|
|
|
@@ -199,7 +198,7 @@ export function buildGridQuery({
|
|
|
199
198
|
partitionKeyValue,
|
|
200
199
|
filterModel,
|
|
201
200
|
sortModel,
|
|
202
|
-
|
|
201
|
+
limit = 10,
|
|
203
202
|
cursor,
|
|
204
203
|
}) {
|
|
205
204
|
// Validate inputs
|
|
@@ -209,7 +208,21 @@ export function buildGridQuery({
|
|
|
209
208
|
if (!partitionKeyValue) {
|
|
210
209
|
throw new Error("partitionKeyValue is required");
|
|
211
210
|
}
|
|
212
|
-
|
|
211
|
+
|
|
212
|
+
// Validate and normalize limit
|
|
213
|
+
if (limit !== undefined && limit !== null) {
|
|
214
|
+
const numLimit = typeof limit === "string" ? Number(limit) : limit;
|
|
215
|
+
if (Number.isNaN(numLimit) || numLimit < 1) {
|
|
216
|
+
throw new Error("limit must be a positive number");
|
|
217
|
+
}
|
|
218
|
+
if (numLimit > 1000) {
|
|
219
|
+
throw new Error("limit cannot exceed 1000 (DynamoDB limit)");
|
|
220
|
+
}
|
|
221
|
+
limit = numLimit;
|
|
222
|
+
} else {
|
|
223
|
+
limit = 10;
|
|
224
|
+
}
|
|
225
|
+
|
|
213
226
|
validateSingleSort(sortModel);
|
|
214
227
|
|
|
215
228
|
// Extract grid config from schema
|
|
@@ -225,7 +238,7 @@ export function buildGridQuery({
|
|
|
225
238
|
const queryHash = hashQuery({
|
|
226
239
|
filterModel,
|
|
227
240
|
sortModel,
|
|
228
|
-
|
|
241
|
+
limit,
|
|
229
242
|
});
|
|
230
243
|
|
|
231
244
|
// Initialize query with partition key
|
|
@@ -242,8 +255,8 @@ export function buildGridQuery({
|
|
|
242
255
|
// Apply sort
|
|
243
256
|
query = applySort(query, sortModel, gridConfig);
|
|
244
257
|
|
|
245
|
-
// Apply
|
|
246
|
-
query = query.limit(
|
|
258
|
+
// Apply limit
|
|
259
|
+
query = query.limit(limit);
|
|
247
260
|
|
|
248
261
|
// Handle cursor for pagination
|
|
249
262
|
if (cursor) {
|
|
@@ -189,7 +189,7 @@ describe("GridQueryBuilder", () => {
|
|
|
189
189
|
const { query } = buildGridQuery({
|
|
190
190
|
model: mockModel,
|
|
191
191
|
partitionKeyValue: "ORG#123",
|
|
192
|
-
|
|
192
|
+
limit: 20,
|
|
193
193
|
});
|
|
194
194
|
|
|
195
195
|
expect(mockModel.query).toHaveBeenCalledWith("pk");
|
|
@@ -203,7 +203,7 @@ describe("GridQueryBuilder", () => {
|
|
|
203
203
|
buildGridQuery({
|
|
204
204
|
model: null,
|
|
205
205
|
partitionKeyValue: "ORG#123",
|
|
206
|
-
|
|
206
|
+
limit: 20,
|
|
207
207
|
});
|
|
208
208
|
}).toThrow("model is required");
|
|
209
209
|
});
|
|
@@ -213,18 +213,19 @@ describe("GridQueryBuilder", () => {
|
|
|
213
213
|
buildGridQuery({
|
|
214
214
|
model: mockModel,
|
|
215
215
|
partitionKeyValue: null,
|
|
216
|
-
|
|
216
|
+
limit: 20,
|
|
217
217
|
});
|
|
218
218
|
}).toThrow("partitionKeyValue is required");
|
|
219
219
|
});
|
|
220
220
|
|
|
221
|
-
it("should
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
221
|
+
it("should use default limit when not provided", () => {
|
|
222
|
+
const { query } = buildGridQuery({
|
|
223
|
+
model: mockModel,
|
|
224
|
+
partitionKeyValue: "ORG#123",
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
expect(mockQuery.limit).toHaveBeenCalledWith(10);
|
|
228
|
+
expect(query).toBe(mockQuery);
|
|
228
229
|
});
|
|
229
230
|
|
|
230
231
|
it("should use custom hash key name from schema", () => {
|
|
@@ -248,7 +249,7 @@ describe("GridQueryBuilder", () => {
|
|
|
248
249
|
buildGridQuery({
|
|
249
250
|
model: customMockModel,
|
|
250
251
|
partitionKeyValue: "tenant-123",
|
|
251
|
-
|
|
252
|
+
limit: 20,
|
|
252
253
|
});
|
|
253
254
|
|
|
254
255
|
// Should query with 'tenantId' not 'pk'
|
|
@@ -277,7 +278,7 @@ describe("GridQueryBuilder", () => {
|
|
|
277
278
|
model: mockModel,
|
|
278
279
|
partitionKeyValue: "ORG#123",
|
|
279
280
|
filterModel,
|
|
280
|
-
|
|
281
|
+
limit: 20,
|
|
281
282
|
});
|
|
282
283
|
|
|
283
284
|
expect(mockQuery.where).toHaveBeenCalledWith("status");
|
|
@@ -295,7 +296,7 @@ describe("GridQueryBuilder", () => {
|
|
|
295
296
|
model: mockModel,
|
|
296
297
|
partitionKeyValue: "ORG#123",
|
|
297
298
|
filterModel,
|
|
298
|
-
|
|
299
|
+
limit: 20,
|
|
299
300
|
});
|
|
300
301
|
|
|
301
302
|
expect(mockQuery.where).toHaveBeenCalledWith("createdAt");
|
|
@@ -317,7 +318,7 @@ describe("GridQueryBuilder", () => {
|
|
|
317
318
|
model: mockModel,
|
|
318
319
|
partitionKeyValue: "ORG#123",
|
|
319
320
|
filterModel,
|
|
320
|
-
|
|
321
|
+
limit: 20,
|
|
321
322
|
});
|
|
322
323
|
|
|
323
324
|
expect(mockQuery.between).toHaveBeenCalledWith("2024-01-01", "2024-12-31");
|
|
@@ -332,7 +333,7 @@ describe("GridQueryBuilder", () => {
|
|
|
332
333
|
model: mockModel,
|
|
333
334
|
partitionKeyValue: "ORG#123",
|
|
334
335
|
filterModel,
|
|
335
|
-
|
|
336
|
+
limit: 20,
|
|
336
337
|
});
|
|
337
338
|
|
|
338
339
|
expect(mockQuery.using).toHaveBeenCalledWith("status-createdAt-index");
|
|
@@ -348,7 +349,7 @@ describe("GridQueryBuilder", () => {
|
|
|
348
349
|
model: mockModel,
|
|
349
350
|
partitionKeyValue: "ORG#123",
|
|
350
351
|
filterModel,
|
|
351
|
-
|
|
352
|
+
limit: 20,
|
|
352
353
|
});
|
|
353
354
|
}).toThrow("Filtering not allowed");
|
|
354
355
|
});
|
|
@@ -363,7 +364,7 @@ describe("GridQueryBuilder", () => {
|
|
|
363
364
|
model: mockModel,
|
|
364
365
|
partitionKeyValue: "ORG#123",
|
|
365
366
|
filterModel,
|
|
366
|
-
|
|
367
|
+
limit: 20,
|
|
367
368
|
});
|
|
368
369
|
}).toThrow("not allowed");
|
|
369
370
|
});
|
|
@@ -387,7 +388,7 @@ describe("GridQueryBuilder", () => {
|
|
|
387
388
|
model: mockModel,
|
|
388
389
|
partitionKeyValue: "ORG#123",
|
|
389
390
|
sortModel,
|
|
390
|
-
|
|
391
|
+
limit: 20,
|
|
391
392
|
});
|
|
392
393
|
|
|
393
394
|
expect(mockQuery.sort).toHaveBeenCalledWith("descending");
|
|
@@ -400,7 +401,7 @@ describe("GridQueryBuilder", () => {
|
|
|
400
401
|
model: mockModel,
|
|
401
402
|
partitionKeyValue: "ORG#123",
|
|
402
403
|
sortModel,
|
|
403
|
-
|
|
404
|
+
limit: 20,
|
|
404
405
|
});
|
|
405
406
|
|
|
406
407
|
// Ascending is default, so sort() should not be called
|
|
@@ -418,7 +419,7 @@ describe("GridQueryBuilder", () => {
|
|
|
418
419
|
model: mockModel,
|
|
419
420
|
partitionKeyValue: "ORG#123",
|
|
420
421
|
sortModel,
|
|
421
|
-
|
|
422
|
+
limit: 20,
|
|
422
423
|
});
|
|
423
424
|
}).toThrow(/sorting by one field/);
|
|
424
425
|
});
|
|
@@ -431,13 +432,13 @@ describe("GridQueryBuilder", () => {
|
|
|
431
432
|
model: mockModel,
|
|
432
433
|
partitionKeyValue: "ORG#123",
|
|
433
434
|
sortModel,
|
|
434
|
-
|
|
435
|
+
limit: 20,
|
|
435
436
|
});
|
|
436
437
|
}).toThrow("Sorting not allowed");
|
|
437
438
|
});
|
|
438
439
|
});
|
|
439
440
|
|
|
440
|
-
describe("buildGridQuery -
|
|
441
|
+
describe("buildGridQuery - Limit and Cursor", () => {
|
|
441
442
|
let mockModel;
|
|
442
443
|
let mockQuery;
|
|
443
444
|
|
|
@@ -448,11 +449,11 @@ describe("GridQueryBuilder", () => {
|
|
|
448
449
|
mockModel.query = vi.fn(() => mockQuery);
|
|
449
450
|
});
|
|
450
451
|
|
|
451
|
-
it("should apply
|
|
452
|
+
it("should apply limit", () => {
|
|
452
453
|
buildGridQuery({
|
|
453
454
|
model: mockModel,
|
|
454
455
|
partitionKeyValue: "ORG#123",
|
|
455
|
-
|
|
456
|
+
limit: 50,
|
|
456
457
|
});
|
|
457
458
|
|
|
458
459
|
expect(mockQuery.limit).toHaveBeenCalledWith(50);
|
|
@@ -465,7 +466,7 @@ describe("GridQueryBuilder", () => {
|
|
|
465
466
|
const firstResult = buildGridQuery({
|
|
466
467
|
model: mockModel,
|
|
467
468
|
partitionKeyValue: "ORG#123",
|
|
468
|
-
|
|
469
|
+
limit: 20,
|
|
469
470
|
});
|
|
470
471
|
|
|
471
472
|
// Create cursor with the actual queryHash
|
|
@@ -477,7 +478,7 @@ describe("GridQueryBuilder", () => {
|
|
|
477
478
|
buildGridQuery({
|
|
478
479
|
model: mockModel,
|
|
479
480
|
partitionKeyValue: "ORG#123",
|
|
480
|
-
|
|
481
|
+
limit: 20,
|
|
481
482
|
cursor,
|
|
482
483
|
});
|
|
483
484
|
|
|
@@ -496,7 +497,7 @@ describe("GridQueryBuilder", () => {
|
|
|
496
497
|
buildGridQuery({
|
|
497
498
|
model: mockModel,
|
|
498
499
|
partitionKeyValue: "ORG#123",
|
|
499
|
-
|
|
500
|
+
limit: 50, // Different limit
|
|
500
501
|
cursor,
|
|
501
502
|
});
|
|
502
503
|
}).toThrow("Cursor does not match");
|
|
@@ -519,7 +520,7 @@ describe("GridQueryBuilder", () => {
|
|
|
519
520
|
items: [{ field: "status", operator: "eq", value: "active" }],
|
|
520
521
|
},
|
|
521
522
|
sortModel: [{ field: "createdAt", sort: "desc" }],
|
|
522
|
-
|
|
523
|
+
limit: 20,
|
|
523
524
|
};
|
|
524
525
|
|
|
525
526
|
const result1 = buildGridQuery(params);
|
|
@@ -532,7 +533,7 @@ describe("GridQueryBuilder", () => {
|
|
|
532
533
|
const baseParams = {
|
|
533
534
|
model: mockModel,
|
|
534
535
|
partitionKeyValue: "ORG#123",
|
|
535
|
-
|
|
536
|
+
limit: 20,
|
|
536
537
|
};
|
|
537
538
|
|
|
538
539
|
const result1 = buildGridQuery({
|
|
@@ -552,7 +553,7 @@ describe("GridQueryBuilder", () => {
|
|
|
552
553
|
expect(result1.queryHash).not.toBe(result2.queryHash);
|
|
553
554
|
});
|
|
554
555
|
|
|
555
|
-
it("should return different hash when
|
|
556
|
+
it("should return different hash when limit changes", () => {
|
|
556
557
|
const baseParams = {
|
|
557
558
|
model: mockModel,
|
|
558
559
|
partitionKeyValue: "ORG#123",
|
|
@@ -560,12 +561,12 @@ describe("GridQueryBuilder", () => {
|
|
|
560
561
|
|
|
561
562
|
const result1 = buildGridQuery({
|
|
562
563
|
...baseParams,
|
|
563
|
-
|
|
564
|
+
limit: 20,
|
|
564
565
|
});
|
|
565
566
|
|
|
566
567
|
const result2 = buildGridQuery({
|
|
567
568
|
...baseParams,
|
|
568
|
-
|
|
569
|
+
limit: 50,
|
|
569
570
|
});
|
|
570
571
|
|
|
571
572
|
expect(result1.queryHash).not.toBe(result2.queryHash);
|
|
@@ -583,7 +584,7 @@ describe("GridQueryBuilder", () => {
|
|
|
583
584
|
mockModel.query = vi.fn(() => mockQuery);
|
|
584
585
|
});
|
|
585
586
|
|
|
586
|
-
it("should handle query with filter, sort, and
|
|
587
|
+
it("should handle query with filter, sort, and limit", () => {
|
|
587
588
|
buildGridQuery({
|
|
588
589
|
model: mockModel,
|
|
589
590
|
partitionKeyValue: "ORG#123",
|
|
@@ -591,7 +592,7 @@ describe("GridQueryBuilder", () => {
|
|
|
591
592
|
items: [{ field: "status", operator: "eq", value: "active" }],
|
|
592
593
|
},
|
|
593
594
|
sortModel: [{ field: "createdAt", sort: "desc" }],
|
|
594
|
-
|
|
595
|
+
limit: 25,
|
|
595
596
|
});
|
|
596
597
|
|
|
597
598
|
// Verify all operations were applied
|
|
@@ -609,7 +610,7 @@ describe("GridQueryBuilder", () => {
|
|
|
609
610
|
partitionKeyValue: "ORG#123",
|
|
610
611
|
filterModel: { items: [] },
|
|
611
612
|
sortModel: [],
|
|
612
|
-
|
|
613
|
+
limit: 20,
|
|
613
614
|
});
|
|
614
615
|
|
|
615
616
|
expect(query).toBeDefined();
|