dynamo-query-engine 1.0.3 → 1.0.4

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/README.md CHANGED
@@ -28,6 +28,8 @@ npm install dynamoose graphql graphql-parse-resolve-info
28
28
 
29
29
  ### 1. Define Models with Grid Configuration
30
30
 
31
+ > **Note**: The library supports any hash key field name (e.g., `pk`, `tenantId`, `userId`, etc.). It automatically detects the hash key from your schema.
32
+
31
33
  ```javascript
32
34
  import dynamoose from "dynamoose";
33
35
 
@@ -79,6 +81,39 @@ const UserSchema = new dynamoose.Schema({
79
81
  export const UserModel = dynamoose.model("Users", UserSchema);
80
82
  ```
81
83
 
84
+ **Example with custom hash key name:**
85
+
86
+ ```javascript
87
+ // The library works with any hash key field name
88
+ const ReservationSchema = new dynamoose.Schema({
89
+ tenantId: {
90
+ type: String,
91
+ hashKey: true, // Automatically detected by getHashKey()
92
+ },
93
+ reservationId: {
94
+ type: String,
95
+ rangeKey: true, // Automatically detected by getRangeKey()
96
+ },
97
+ // ... other fields
98
+ });
99
+
100
+ export const ReservationModel = dynamoose.model("Reservations", ReservationSchema);
101
+ ```
102
+
103
+ **Extracting keys programmatically:**
104
+
105
+ ```javascript
106
+ import { getHashKey, getRangeKey } from "dynamo-query-engine";
107
+
108
+ // Get the hash key field name from your schema
109
+ const hashKeyField = getHashKey(ReservationModel.schema);
110
+ console.log(hashKeyField); // "tenantId"
111
+
112
+ // Get the range key field name (or null if none exists)
113
+ const rangeKeyField = getRangeKey(ReservationModel.schema);
114
+ console.log(rangeKeyField); // "reservationId"
115
+ ```
116
+
82
117
  ### 2. Register Models
83
118
 
84
119
  ```javascript
@@ -265,6 +300,20 @@ Registers a Dynamoose model.
265
300
 
266
301
  Retrieves a registered model.
267
302
 
303
+ #### `getHashKey(schema)`
304
+
305
+ Extracts the hash key (partition key) field name from a Dynamoose schema.
306
+
307
+ **Returns:** `string` - The hash key field name
308
+
309
+ **Throws:** Error if no hash key is found in the schema
310
+
311
+ #### `getRangeKey(schema)`
312
+
313
+ Extracts the range key (sort key) field name from a Dynamoose schema.
314
+
315
+ **Returns:** `string | null` - The range key field name, or null if no range key exists
316
+
268
317
  ## Architecture
269
318
 
270
319
  ```
package/index.js CHANGED
@@ -9,6 +9,8 @@ export { modelRegistry, ModelRegistry } from "./src/core/modelRegistry.js";
9
9
  export {
10
10
  buildGridQuery,
11
11
  extractGridConfig,
12
+ getHashKey,
13
+ getRangeKey,
12
14
  } from "./src/core/gridQueryBuilder.js";
13
15
  export {
14
16
  resolveExpand,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dynamo-query-engine",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
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",
@@ -46,6 +46,40 @@ export function extractGridConfig(schema) {
46
46
  return gridConfig;
47
47
  }
48
48
 
49
+ /**
50
+ * Get the hash key (partition key) field name from Dynamoose schema
51
+ * @param {*} schema - Dynamoose schema
52
+ * @returns {string} Hash key field name
53
+ */
54
+ export function getHashKey(schema) {
55
+ const attributes = schema.getAttributes();
56
+
57
+ for (const [field, definition] of Object.entries(attributes)) {
58
+ if (definition.hashKey === true) {
59
+ return field;
60
+ }
61
+ }
62
+
63
+ throw new Error("No hash key found in schema");
64
+ }
65
+
66
+ /**
67
+ * Get the range key (sort key) field name from Dynamoose schema
68
+ * @param {*} schema - Dynamoose schema
69
+ * @returns {string|null} Range key field name, or null if no range key exists
70
+ */
71
+ export function getRangeKey(schema) {
72
+ const attributes = schema.getAttributes();
73
+
74
+ for (const [field, definition] of Object.entries(attributes)) {
75
+ if (definition.rangeKey === true) {
76
+ return field;
77
+ }
78
+ }
79
+
80
+ return null;
81
+ }
82
+
49
83
  /**
50
84
  * Determine which GSI to use based on filter model
51
85
  * @param {FilterModel} filterModel - Filter model
@@ -181,6 +215,9 @@ export function buildGridQuery({
181
215
  // Extract grid config from schema
182
216
  const gridConfig = extractGridConfig(model.schema);
183
217
 
218
+ // Get the hash key field name from schema
219
+ const hashKeyField = getHashKey(model.schema);
220
+
184
221
  // Determine which index to use
185
222
  const index = determineIndex(filterModel, gridConfig);
186
223
 
@@ -192,7 +229,7 @@ export function buildGridQuery({
192
229
  });
193
230
 
194
231
  // Initialize query with partition key
195
- let query = model.query("pk").eq(partitionKeyValue);
232
+ let query = model.query(hashKeyField).eq(partitionKeyValue);
196
233
 
197
234
  // Use GSI if needed
198
235
  if (index) {
package/src/core/index.js CHANGED
@@ -3,7 +3,7 @@
3
3
  */
4
4
 
5
5
  export { modelRegistry, ModelRegistry } from "./modelRegistry.js";
6
- export { buildGridQuery, extractGridConfig } from "./gridQueryBuilder.js";
6
+ export { buildGridQuery, extractGridConfig, getHashKey, getRangeKey } from "./gridQueryBuilder.js";
7
7
  export {
8
8
  resolveExpand,
9
9
  resolveExpands,
@@ -6,6 +6,8 @@ import { describe, it, expect, beforeEach, vi } from "vitest";
6
6
  import {
7
7
  buildGridQuery,
8
8
  extractGridConfig,
9
+ getHashKey,
10
+ getRangeKey,
9
11
  } from "../../src/core/gridQueryBuilder.js";
10
12
  import {
11
13
  createMockSchema,
@@ -58,6 +60,120 @@ describe("GridQueryBuilder", () => {
58
60
  });
59
61
  });
60
62
 
63
+ describe("getHashKey", () => {
64
+ it("should extract hash key field named 'pk'", () => {
65
+ const attributes = {
66
+ pk: { type: String, hashKey: true },
67
+ sk: { type: String, rangeKey: true },
68
+ };
69
+
70
+ const schema = createMockSchema(attributes);
71
+ const hashKey = getHashKey(schema);
72
+
73
+ expect(hashKey).toBe("pk");
74
+ });
75
+
76
+ it("should extract hash key field named 'tenantId'", () => {
77
+ const attributes = {
78
+ tenantId: { type: String, hashKey: true },
79
+ reservationId: { type: String, rangeKey: true },
80
+ };
81
+
82
+ const schema = createMockSchema(attributes);
83
+ const hashKey = getHashKey(schema);
84
+
85
+ expect(hashKey).toBe("tenantId");
86
+ });
87
+
88
+ it("should extract hash key field with any name", () => {
89
+ const attributes = {
90
+ customHashKey: { type: String, hashKey: true },
91
+ otherField: { type: String },
92
+ };
93
+
94
+ const schema = createMockSchema(attributes);
95
+ const hashKey = getHashKey(schema);
96
+
97
+ expect(hashKey).toBe("customHashKey");
98
+ });
99
+
100
+ it("should throw error when no hash key found", () => {
101
+ const attributes = {
102
+ field1: { type: String },
103
+ field2: { type: String },
104
+ };
105
+
106
+ const schema = createMockSchema(attributes);
107
+
108
+ expect(() => {
109
+ getHashKey(schema);
110
+ }).toThrow("No hash key found in schema");
111
+ });
112
+ });
113
+
114
+ describe("getRangeKey", () => {
115
+ it("should extract range key field named 'sk'", () => {
116
+ const attributes = {
117
+ pk: { type: String, hashKey: true },
118
+ sk: { type: String, rangeKey: true },
119
+ };
120
+
121
+ const schema = createMockSchema(attributes);
122
+ const rangeKey = getRangeKey(schema);
123
+
124
+ expect(rangeKey).toBe("sk");
125
+ });
126
+
127
+ it("should extract range key field named 'createdAt'", () => {
128
+ const attributes = {
129
+ pk: { type: String, hashKey: true },
130
+ createdAt: { type: String, rangeKey: true },
131
+ };
132
+
133
+ const schema = createMockSchema(attributes);
134
+ const rangeKey = getRangeKey(schema);
135
+
136
+ expect(rangeKey).toBe("createdAt");
137
+ });
138
+
139
+ it("should extract range key field named 'reservationId'", () => {
140
+ const attributes = {
141
+ tenantId: { type: String, hashKey: true },
142
+ reservationId: { type: String, rangeKey: true },
143
+ };
144
+
145
+ const schema = createMockSchema(attributes);
146
+ const rangeKey = getRangeKey(schema);
147
+
148
+ expect(rangeKey).toBe("reservationId");
149
+ });
150
+
151
+ it("should return null when no range key exists", () => {
152
+ const attributes = {
153
+ pk: { type: String, hashKey: true },
154
+ field1: { type: String },
155
+ field2: { type: String },
156
+ };
157
+
158
+ const schema = createMockSchema(attributes);
159
+ const rangeKey = getRangeKey(schema);
160
+
161
+ expect(rangeKey).toBeNull();
162
+ });
163
+
164
+ it("should work with custom range key names", () => {
165
+ const attributes = {
166
+ customHashKey: { type: String, hashKey: true },
167
+ customSortKey: { type: String, rangeKey: true },
168
+ };
169
+
170
+ const schema = createMockSchema(attributes);
171
+ const rangeKey = getRangeKey(schema);
172
+
173
+ expect(rangeKey).toBe("customSortKey");
174
+ });
175
+ });
176
+
61
177
  describe("buildGridQuery - Basic", () => {
62
178
  let mockModel;
63
179
  let mockQuery;
@@ -110,6 +226,35 @@ describe("GridQueryBuilder", () => {
110
226
  });
111
227
  }).toThrow("paginationModel is required");
112
228
  });
229
+
230
+ it("should use custom hash key name from schema", () => {
231
+ // Create schema with custom hash key name (tenantId instead of pk)
232
+ const customAttributes = {
233
+ tenantId: { type: String, hashKey: true },
234
+ reservationId: { type: String, rangeKey: true },
235
+ status: {
236
+ type: String,
237
+ query: {
238
+ filter: { type: "attribute", operators: ["eq"] },
239
+ },
240
+ },
241
+ };
242
+
243
+ const customSchema = createMockSchema(customAttributes);
244
+ const customMockQuery = createMockQuery([]);
245
+ const customMockModel = createMockModel(customSchema, []);
246
+ customMockModel.query = vi.fn(() => customMockQuery);
247
+
248
+ buildGridQuery({
249
+ model: customMockModel,
250
+ partitionKeyValue: "tenant-123",
251
+ paginationModel: { pageSize: 20 },
252
+ });
253
+
254
+ // Should query with 'tenantId' not 'pk'
255
+ expect(customMockModel.query).toHaveBeenCalledWith("tenantId");
256
+ expect(customMockQuery.eq).toHaveBeenCalledWith("tenant-123");
257
+ });
113
258
  });
114
259
 
115
260
  describe("buildGridQuery - Filtering", () => {