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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dynamo-query-engine",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "Type-safe DynamoDB query builder for GraphQL with support for filtering, sorting, pagination, and relation expansion",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -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.pageSize - Page size
20
+ * @param {number} params.limit - Query limit
22
21
  * @returns {string} SHA256 hash of the query parameters
23
22
  */
24
- function hashQuery({ filterModel, sortModel, pageSize }) {
23
+ function hashQuery({ filterModel, sortModel, limit }) {
25
24
  return crypto
26
25
  .createHash("sha256")
27
- .update(JSON.stringify({ filterModel, sortModel, pageSize }))
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
- paginationModel,
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
- validatePaginationModel(paginationModel);
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
- pageSize: paginationModel.pageSize,
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 pagination limit
246
- query = query.limit(paginationModel.pageSize);
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
- paginationModel: { pageSize: 20 },
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
- paginationModel: { pageSize: 20 },
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
- paginationModel: { pageSize: 20 },
216
+ limit: 20,
217
217
  });
218
218
  }).toThrow("partitionKeyValue is required");
219
219
  });
220
220
 
221
- it("should throw error when paginationModel is missing", () => {
222
- expect(() => {
223
- buildGridQuery({
224
- model: mockModel,
225
- partitionKeyValue: "ORG#123",
226
- });
227
- }).toThrow("paginationModel is required");
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
- paginationModel: { pageSize: 20 },
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
- paginationModel: { pageSize: 20 },
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
- paginationModel: { pageSize: 20 },
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
- paginationModel: { pageSize: 20 },
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
- paginationModel: { pageSize: 20 },
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
- paginationModel: { pageSize: 20 },
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
- paginationModel: { pageSize: 20 },
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
- paginationModel: { pageSize: 20 },
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
- paginationModel: { pageSize: 20 },
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
- paginationModel: { pageSize: 20 },
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
- paginationModel: { pageSize: 20 },
435
+ limit: 20,
435
436
  });
436
437
  }).toThrow("Sorting not allowed");
437
438
  });
438
439
  });
439
440
 
440
- describe("buildGridQuery - Pagination", () => {
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 page size limit", () => {
452
+ it("should apply limit", () => {
452
453
  buildGridQuery({
453
454
  model: mockModel,
454
455
  partitionKeyValue: "ORG#123",
455
- paginationModel: { pageSize: 50 },
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
- paginationModel: { pageSize: 20 },
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
- paginationModel: { pageSize: 20 },
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
- paginationModel: { pageSize: 50 }, // Different page size
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
- paginationModel: { pageSize: 20 },
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
- paginationModel: { pageSize: 20 },
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 page size changes", () => {
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
- paginationModel: { pageSize: 20 },
564
+ limit: 20,
564
565
  });
565
566
 
566
567
  const result2 = buildGridQuery({
567
568
  ...baseParams,
568
- paginationModel: { pageSize: 50 },
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 pagination", () => {
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
- paginationModel: { pageSize: 25 },
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
- paginationModel: { pageSize: 20 },
613
+ limit: 20,
613
614
  });
614
615
 
615
616
  expect(query).toBeDefined();