dynamo-query-engine 1.0.4 → 1.0.6

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.6",
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",
@@ -4,9 +4,7 @@
4
4
 
5
5
  import "../types/jsdoc.js";
6
6
  import { modelRegistry } from "./modelRegistry.js";
7
- import { buildGridQuery } from "./gridQueryBuilder.js";
8
- import { validateExpandField } from "../utils/validation.js";
9
- import { extractGridConfig } from "./gridQueryBuilder.js";
7
+ import { buildGridQuery, extractGridConfig } from "./gridQueryBuilder.js";
10
8
 
11
9
  /**
12
10
  * Extract expand policy from schema
@@ -62,6 +60,15 @@ export async function resolveExpand({
62
60
  // Get target model from registry
63
61
  const targetModel = modelRegistry.get(policy.type);
64
62
 
63
+ // Get the GraphQL type name from the schema (if defined)
64
+ const attributes = parentModel.schema.getAttributes();
65
+ const fieldConfig = attributes[field];
66
+ const graphqlTypeName = fieldConfig?.graphql?.type || policy.type;
67
+
68
+ // Determine which property name to use for storing the expanded data
69
+ // If graphql.type is defined, use it as the property name, otherwise use the field name
70
+ const expandPropertyName = fieldConfig?.graphql?.type || field;
71
+
65
72
  // Determine limit based on args and policy
66
73
  const requestedLimit = args.pagination?.pageSize ?? policy.defaultLimit;
67
74
  const limit = Math.min(requestedLimit, policy.maxLimit);
@@ -77,7 +84,7 @@ export async function resolveExpand({
77
84
  console.warn(
78
85
  `Cannot expand '${field}' for parent: missing partition key value`
79
86
  );
80
- parent[field] = [];
87
+ parent[expandPropertyName] = [];
81
88
  return;
82
89
  }
83
90
 
@@ -94,16 +101,22 @@ export async function resolveExpand({
94
101
  // Execute query
95
102
  const results = await query.exec();
96
103
 
104
+ // Add __typename to each result
105
+ const resultsWithTypename = results.map((item) => ({
106
+ ...item,
107
+ __typename: graphqlTypeName,
108
+ }));
109
+
97
110
  // Assign results to parent
98
111
  if (policy.relation === "ONE") {
99
- parent[field] = results.length > 0 ? results[0] : null;
112
+ parent[expandPropertyName] = resultsWithTypename.length > 0 ? resultsWithTypename[0] : null;
100
113
  } else {
101
- parent[field] = results;
114
+ parent[expandPropertyName] = resultsWithTypename;
102
115
  }
103
116
  } catch (error) {
104
117
  console.error(`Error expanding '${field}' for parent:`, error);
105
118
  // Gracefully handle errors - set empty result
106
- parent[field] = policy.relation === "ONE" ? null : [];
119
+ parent[expandPropertyName] = policy.relation === "ONE" ? null : [];
107
120
  }
108
121
  });
109
122
 
@@ -111,6 +124,27 @@ export async function resolveExpand({
111
124
  await Promise.all(expandPromises);
112
125
  }
113
126
 
127
+ /**
128
+ * Create a mapping from GraphQL field names to schema field names
129
+ * @param {*} schema - Dynamoose schema
130
+ * @returns {Object.<string, string>} Map of GraphQL field name to schema field name
131
+ */
132
+ function createGraphQLToSchemaFieldMap(schema) {
133
+ const attributes = schema.getAttributes();
134
+ const map = {};
135
+
136
+ for (const [fieldName, config] of Object.entries(attributes)) {
137
+ // If the field has a graphql.type config, map from graphql type to field name
138
+ if (config?.graphql?.type) {
139
+ map[config.graphql.type] = fieldName;
140
+ }
141
+ // Also keep the original field name mapping (for backward compatibility)
142
+ map[fieldName] = fieldName;
143
+ }
144
+
145
+ return map;
146
+ }
147
+
114
148
  /**
115
149
  * Resolve multiple expands from GraphQL resolve info
116
150
  * @param {Array} parentItems - Parent items to expand
@@ -137,18 +171,29 @@ export async function resolveExpands(
137
171
 
138
172
  // Get expand policy to filter only expandable fields
139
173
  const expandPolicy = extractExpandPolicy(parentModel.schema);
174
+
175
+ // Create mapping from GraphQL field names to schema field names
176
+ const graphqlToSchemaMap = createGraphQLToSchemaFieldMap(parentModel.schema);
140
177
 
141
178
  // Resolve all expands in parallel
142
179
  const expandPromises = Object.entries(fields)
143
- .filter(([fieldName]) => expandPolicy[fieldName])
144
- .map(([fieldName, fieldInfo]) =>
145
- resolveExpand({
180
+ .map(([graphqlFieldName, fieldInfo]) => {
181
+ // Map GraphQL field name to schema field name
182
+ const schemaFieldName = graphqlToSchemaMap[graphqlFieldName];
183
+
184
+ // Check if this field is expandable
185
+ if (!schemaFieldName || !expandPolicy[schemaFieldName]) {
186
+ return null;
187
+ }
188
+
189
+ return resolveExpand({
146
190
  parentItems,
147
191
  parentModel,
148
- field: fieldName,
192
+ field: schemaFieldName,
149
193
  args: fieldInfo.args || {},
150
- })
151
- );
194
+ });
195
+ })
196
+ .filter(Boolean); // Remove null entries
152
197
 
153
198
  await Promise.all(expandPromises);
154
199
  }
@@ -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) {
@@ -0,0 +1,339 @@
1
+ /**
2
+ * @fileoverview Tests for expand resolver with GraphQL type names
3
+ */
4
+
5
+ import { describe, it, expect, beforeEach, vi } from "vitest";
6
+ import { resolveExpand, resolveExpands } from "../../src/core/expandResolver.js";
7
+ import { modelRegistry } from "../../src/core/modelRegistry.js";
8
+ import { createMockSchema, createMockModel, createMockQuery } from "../mocks/dynamoose.mock.js";
9
+
10
+ describe("ExpandResolver", () => {
11
+ describe("resolveExpand with GraphQL type names", () => {
12
+ let parentModel;
13
+ let targetModel;
14
+ let parentItems;
15
+
16
+ beforeEach(() => {
17
+ // Clear registry
18
+ modelRegistry.clear();
19
+
20
+ // Create parent schema with unitId field that has graphql.type config
21
+ const parentSchema = createMockSchema({
22
+ tenantId: {
23
+ type: String,
24
+ hashKey: true,
25
+ },
26
+ reservationId: {
27
+ type: String,
28
+ rangeKey: true,
29
+ },
30
+ unitId: {
31
+ type: String,
32
+ query: {
33
+ expand: {
34
+ type: "Unit",
35
+ relation: "ONE",
36
+ defaultLimit: 1,
37
+ maxLimit: 1,
38
+ },
39
+ },
40
+ graphql: {
41
+ type: "Unit", // GraphQL type name
42
+ },
43
+ },
44
+ });
45
+
46
+ // Create target schema (Unit)
47
+ const targetSchema = createMockSchema({
48
+ tenantId: {
49
+ type: String,
50
+ hashKey: true,
51
+ },
52
+ unitId: {
53
+ type: String,
54
+ rangeKey: true,
55
+ },
56
+ name: {
57
+ type: String,
58
+ },
59
+ });
60
+
61
+ // Create parent model
62
+ parentModel = createMockModel(parentSchema);
63
+
64
+ // Create target model with mock query result
65
+ const unitData = [
66
+ {
67
+ tenantId: "tenant-1",
68
+ unitId: "unit-1",
69
+ name: "Unit A",
70
+ },
71
+ ];
72
+ targetModel = createMockModel(targetSchema, unitData);
73
+
74
+ // Register target model
75
+ modelRegistry.register("Unit", targetModel);
76
+
77
+ // Create parent items
78
+ parentItems = [
79
+ {
80
+ tenantId: "tenant-1",
81
+ reservationId: "res-1",
82
+ unitId: "unit-1",
83
+ },
84
+ ];
85
+ });
86
+
87
+ it("should store expanded data under GraphQL type name (Unit) not field name (unitId)", async () => {
88
+ await resolveExpand({
89
+ parentItems,
90
+ parentModel,
91
+ field: "unitId",
92
+ });
93
+
94
+ // The expanded data should be under 'Unit', not 'unitId'
95
+ expect(parentItems[0].Unit).toBeDefined();
96
+ expect(parentItems[0].Unit).toEqual({
97
+ tenantId: "tenant-1",
98
+ unitId: "unit-1",
99
+ name: "Unit A",
100
+ __typename: "Unit",
101
+ });
102
+ });
103
+
104
+ it("should add __typename field to expanded data", async () => {
105
+ await resolveExpand({
106
+ parentItems,
107
+ parentModel,
108
+ field: "unitId",
109
+ });
110
+
111
+ expect(parentItems[0].Unit.__typename).toBe("Unit");
112
+ });
113
+
114
+ it("should handle ONE relation correctly", async () => {
115
+ await resolveExpand({
116
+ parentItems,
117
+ parentModel,
118
+ field: "unitId",
119
+ });
120
+
121
+ // ONE relation should return a single object, not an array
122
+ expect(parentItems[0].Unit).toBeDefined();
123
+ expect(Array.isArray(parentItems[0].Unit)).toBe(false);
124
+ });
125
+
126
+ it("should handle MANY relation correctly", async () => {
127
+ // Clear and re-register with different config
128
+ modelRegistry.clear();
129
+
130
+ // Update parent schema for MANY relation
131
+ const parentSchemaMany = createMockSchema({
132
+ tenantId: {
133
+ type: String,
134
+ hashKey: true,
135
+ },
136
+ reservationId: {
137
+ type: String,
138
+ rangeKey: true,
139
+ },
140
+ unitId: {
141
+ type: String,
142
+ query: {
143
+ expand: {
144
+ type: "Unit",
145
+ relation: "MANY",
146
+ defaultLimit: 10,
147
+ maxLimit: 100,
148
+ },
149
+ },
150
+ graphql: {
151
+ type: "Units", // Plural for MANY relation
152
+ },
153
+ },
154
+ });
155
+
156
+ const unitsData = [
157
+ { tenantId: "tenant-1", unitId: "unit-1", name: "Unit A" },
158
+ { tenantId: "tenant-1", unitId: "unit-2", name: "Unit B" },
159
+ ];
160
+
161
+ const parentModelMany = createMockModel(parentSchemaMany);
162
+ const targetModelMany = createMockModel(targetModel.schema, unitsData);
163
+
164
+ modelRegistry.register("Unit", targetModelMany);
165
+
166
+ await resolveExpand({
167
+ parentItems,
168
+ parentModel: parentModelMany,
169
+ field: "unitId",
170
+ });
171
+
172
+ // MANY relation should return an array
173
+ expect(parentItems[0].Units).toBeDefined();
174
+ expect(Array.isArray(parentItems[0].Units)).toBe(true);
175
+ expect(parentItems[0].Units).toHaveLength(2);
176
+ expect(parentItems[0].Units[0].__typename).toBe("Units");
177
+ expect(parentItems[0].Units[1].__typename).toBe("Units");
178
+ });
179
+
180
+ it("should fallback to field name when graphql.type is not defined", async () => {
181
+ // Clear and re-register with different config
182
+ modelRegistry.clear();
183
+
184
+ // Create schema without graphql.type config
185
+ const parentSchemaNoGraphQL = createMockSchema({
186
+ tenantId: {
187
+ type: String,
188
+ hashKey: true,
189
+ },
190
+ reservationId: {
191
+ type: String,
192
+ rangeKey: true,
193
+ },
194
+ unitId: {
195
+ type: String,
196
+ query: {
197
+ expand: {
198
+ type: "Unit",
199
+ relation: "ONE",
200
+ defaultLimit: 1,
201
+ maxLimit: 1,
202
+ },
203
+ },
204
+ // No graphql config
205
+ },
206
+ });
207
+
208
+ const unitData = [
209
+ { tenantId: "tenant-1", unitId: "unit-1", name: "Unit A" },
210
+ ];
211
+
212
+ const parentModelNoGraphQL = createMockModel(parentSchemaNoGraphQL);
213
+ const targetModelNoGraphQL = createMockModel(targetModel.schema, unitData);
214
+
215
+ modelRegistry.register("Unit", targetModelNoGraphQL);
216
+
217
+ await resolveExpand({
218
+ parentItems,
219
+ parentModel: parentModelNoGraphQL,
220
+ field: "unitId",
221
+ });
222
+
223
+ // Should fallback to field name 'unitId'
224
+ expect(parentItems[0].unitId).toBeDefined();
225
+ expect(parentItems[0].unitId.__typename).toBe("Unit");
226
+ });
227
+ });
228
+
229
+ describe("resolveExpands with GraphQL field mapping", () => {
230
+ let parentModel;
231
+ let targetModel;
232
+ let parentItems;
233
+
234
+ beforeEach(() => {
235
+ modelRegistry.clear();
236
+
237
+ const parentSchema = createMockSchema({
238
+ tenantId: {
239
+ type: String,
240
+ hashKey: true,
241
+ },
242
+ reservationId: {
243
+ type: String,
244
+ rangeKey: true,
245
+ },
246
+ unitId: {
247
+ type: String,
248
+ query: {
249
+ expand: {
250
+ type: "Unit",
251
+ relation: "ONE",
252
+ defaultLimit: 1,
253
+ maxLimit: 1,
254
+ },
255
+ },
256
+ graphql: {
257
+ type: "Unit",
258
+ },
259
+ },
260
+ });
261
+
262
+ const targetSchema = createMockSchema({
263
+ tenantId: {
264
+ type: String,
265
+ hashKey: true,
266
+ },
267
+ unitId: {
268
+ type: String,
269
+ rangeKey: true,
270
+ },
271
+ name: {
272
+ type: String,
273
+ },
274
+ });
275
+
276
+ const unitData = [
277
+ { tenantId: "tenant-1", unitId: "unit-1", name: "Unit A" },
278
+ ];
279
+
280
+ parentModel = createMockModel(parentSchema);
281
+ targetModel = createMockModel(targetSchema, unitData);
282
+
283
+ modelRegistry.register("Unit", targetModel);
284
+
285
+ parentItems = [
286
+ {
287
+ tenantId: "tenant-1",
288
+ reservationId: "res-1",
289
+ unitId: "unit-1",
290
+ },
291
+ ];
292
+ });
293
+
294
+ it("should map GraphQL field name (Unit) to schema field name (unitId)", async () => {
295
+ // Simulate GraphQL resolve info with field name 'Unit'
296
+ const fieldsByTypeName = {
297
+ Reservation: {
298
+ Unit: {
299
+ // GraphQL field name
300
+ fieldsByTypeName: {
301
+ Unit: {
302
+ unitId: {},
303
+ name: {},
304
+ },
305
+ },
306
+ },
307
+ },
308
+ };
309
+
310
+ await resolveExpands(parentItems, parentModel, fieldsByTypeName);
311
+
312
+ // Should map 'Unit' -> 'unitId' and store result under 'Unit'
313
+ expect(parentItems[0].Unit).toBeDefined();
314
+ expect(parentItems[0].Unit.__typename).toBe("Unit");
315
+ });
316
+
317
+ it("should skip non-expandable fields", async () => {
318
+ const fieldsByTypeName = {
319
+ Reservation: {
320
+ reservationId: {}, // Not an expandable field
321
+ Unit: {
322
+ fieldsByTypeName: {
323
+ Unit: {
324
+ unitId: {},
325
+ name: {},
326
+ },
327
+ },
328
+ },
329
+ },
330
+ };
331
+
332
+ await resolveExpands(parentItems, parentModel, fieldsByTypeName);
333
+
334
+ // Only Unit should be expanded
335
+ expect(parentItems[0].Unit).toBeDefined();
336
+ expect(parentItems[0].reservationId).toBe("res-1"); // Original value unchanged
337
+ });
338
+ });
339
+ });
@@ -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();