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 +49 -0
- package/index.js +2 -0
- package/package.json +1 -1
- package/src/core/gridQueryBuilder.js +38 -1
- package/src/core/index.js +1 -1
- package/tests/unit/gridQueryBuilder.test.js +145 -0
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
package/package.json
CHANGED
|
@@ -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(
|
|
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", () => {
|