dynamo-query-engine 1.0.7 → 1.0.8

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.7",
3
+ "version": "1.0.8",
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",
@@ -35,7 +35,11 @@ export async function resolveExpand({
35
35
  field,
36
36
  args = {},
37
37
  }) {
38
+ console.log(`[expandResolver] resolveExpand called for field: ${field}`);
39
+ console.log(`[expandResolver] parentItems count: ${parentItems?.length || 0}`);
40
+
38
41
  if (!parentItems || parentItems.length === 0) {
42
+ console.log(`[expandResolver] No parent items, skipping expand for field: ${field}`);
39
43
  return;
40
44
  }
41
45
 
@@ -52,11 +56,13 @@ export async function resolveExpand({
52
56
  const policy = expandPolicy[field];
53
57
 
54
58
  if (!policy) {
59
+ console.error(`[expandResolver] Field '${field}' does not have expand configuration`);
55
60
  throw new Error(
56
61
  `Field '${field}' does not have expand configuration in parent model`
57
62
  );
58
63
  }
59
64
 
65
+
60
66
  // Get target model from registry
61
67
  const targetModel = modelRegistry.get(policy.type);
62
68
 
@@ -77,12 +83,20 @@ export async function resolveExpand({
77
83
  const expandPromises = parentItems.map(async (parent) => {
78
84
  try {
79
85
  // Get partition key value from parent
80
- // This should be either parent.pk or the first field (usually tenantId)
81
- const partitionKeyValue = parent.pk || parent[Object.keys(parent)[0]];
86
+ // First, determine the target model's hash key name
87
+ const targetAttributes = targetModel.schema.getAttributes();
88
+ const targetHashKeyName = Object.keys(targetAttributes).find(
89
+ (key) => targetAttributes[key].hashKey === true
90
+ );
91
+
92
+
93
+ // Get the partition key value from the parent using the target's hash key name
94
+ const partitionKeyValue = parent[targetHashKeyName];
82
95
 
96
+
83
97
  if (!partitionKeyValue) {
84
98
  console.warn(
85
- `Cannot expand '${field}' for parent: missing partition key value`
99
+ `[expandResolver] Cannot expand '${field}' for parent: missing partition key value`
86
100
  );
87
101
  parent[expandPropertyName] = policy.relation === "ONE" ? null : [];
88
102
  return;
@@ -96,7 +110,7 @@ export async function resolveExpand({
96
110
 
97
111
  if (!foreignKeyValue) {
98
112
  console.warn(
99
- `Cannot expand '${field}' for parent: missing foreign key value '${policy.foreignKey}'`
113
+ `[expandResolver] Cannot expand '${field}' for parent: missing foreign key value '${policy.foreignKey}'`
100
114
  );
101
115
  parent[expandPropertyName] = null;
102
116
  return;
@@ -110,7 +124,7 @@ export async function resolveExpand({
110
124
 
111
125
  if (!rangeKeyName) {
112
126
  console.warn(
113
- `Cannot expand '${field}': target model '${policy.type}' has no range key defined`
127
+ `[expandResolver] Cannot expand '${field}': target model '${policy.type}' has no range key defined`
114
128
  );
115
129
  parent[expandPropertyName] = null;
116
130
  return;
@@ -121,11 +135,12 @@ export async function resolveExpand({
121
135
  items: [
122
136
  {
123
137
  field: rangeKeyName,
124
- operator: "equals",
138
+ operator: "eq",
125
139
  value: foreignKeyValue,
126
140
  },
127
141
  ],
128
142
  });
143
+
129
144
 
130
145
  const { query } = buildGridQuery({
131
146
  model: targetModel,
@@ -155,6 +170,7 @@ export async function resolveExpand({
155
170
  __typename: graphqlTypeName,
156
171
  }));
157
172
 
173
+
158
174
  // Assign results to parent
159
175
  if (policy.relation === "ONE") {
160
176
  parent[expandPropertyName] = resultsWithTypename.length > 0 ? resultsWithTypename[0] : null;
@@ -162,7 +178,8 @@ export async function resolveExpand({
162
178
  parent[expandPropertyName] = resultsWithTypename;
163
179
  }
164
180
  } catch (error) {
165
- console.error(`Error expanding '${field}' for parent:`, error);
181
+ console.error(`[expandResolver] Error expanding '${field}' for parent:`, error);
182
+ console.error(`[expandResolver] Error stack:`, error.stack);
166
183
  // Gracefully handle errors - set empty result
167
184
  parent[expandPropertyName] = policy.relation === "ONE" ? null : [];
168
185
  }
@@ -193,19 +210,102 @@ function createGraphQLToSchemaFieldMap(schema) {
193
210
  return map;
194
211
  }
195
212
 
213
+ /**
214
+ * Parse AppSync selectionSetList to extract requested expand fields
215
+ * @param {string[]} selectionSetList - AppSync selectionSetList array
216
+ * @param {string} parentPath - Parent path prefix (e.g., "rows")
217
+ * @returns {Set<string>} Set of field names that were requested
218
+ */
219
+ function parseAppSyncSelectionSet(selectionSetList, parentPath = "rows") {
220
+ const requestedFields = new Set();
221
+ const prefix = parentPath + "/";
222
+
223
+ for (const path of selectionSetList) {
224
+ if (path.startsWith(prefix)) {
225
+ const afterPrefix = path.substring(prefix.length);
226
+ const firstSlash = afterPrefix.indexOf("/");
227
+
228
+ if (firstSlash === -1) {
229
+ // This is a direct child field (e.g., "rows/unitId")
230
+ requestedFields.add(afterPrefix);
231
+ } else {
232
+ // This is a nested field (e.g., "rows/Unit/name")
233
+ const fieldName = afterPrefix.substring(0, firstSlash);
234
+ requestedFields.add(fieldName);
235
+ }
236
+ }
237
+ }
238
+
239
+ return requestedFields;
240
+ }
241
+
196
242
  /**
197
243
  * Resolve multiple expands from GraphQL resolve info
198
244
  * @param {Array} parentItems - Parent items to expand
199
245
  * @param {*} parentModel - Parent Dynamoose model
200
- * @param {Object} fieldsByTypeName - Fields by type name from graphql-parse-resolve-info
246
+ * @param {Object|string[]} fieldsByTypeName - Fields by type name from graphql-parse-resolve-info OR AppSync selectionSetList
247
+ * @param {Object} [options] - Additional options
248
+ * @param {boolean} [options.isAppSync] - Whether to parse AppSync selectionSetList format
249
+ * @param {string} [options.parentPath] - Parent path for AppSync (default: "rows")
201
250
  * @returns {Promise<void>}
202
251
  */
203
252
  export async function resolveExpands(
204
253
  parentItems,
205
254
  parentModel,
206
- fieldsByTypeName
255
+ fieldsByTypeName,
256
+ options = {}
207
257
  ) {
258
+ const { isAppSync = false, parentPath = "rows" } = options;
259
+
260
+ // Handle AppSync format
261
+ if (isAppSync) {
262
+ const selectionSetList = fieldsByTypeName;
263
+
264
+ if (!selectionSetList || selectionSetList.length === 0) {
265
+ console.log(`[expandResolver] No selectionSetList, skipping`);
266
+ return;
267
+ }
268
+
269
+ // Parse AppSync selection set to find requested fields
270
+ const requestedFields = parseAppSyncSelectionSet(selectionSetList, parentPath);
271
+
272
+ // Get expand policy to filter only expandable fields
273
+ const expandPolicy = extractExpandPolicy(parentModel.schema);
274
+
275
+ // Create mapping from GraphQL field names to schema field names
276
+ const graphqlToSchemaMap = createGraphQLToSchemaFieldMap(parentModel.schema);
277
+
278
+ // Resolve all expands in parallel
279
+ const expandPromises = Array.from(requestedFields)
280
+ .map((graphqlFieldName) => {
281
+ // Map GraphQL field name to schema field name
282
+ const schemaFieldName = graphqlToSchemaMap[graphqlFieldName];
283
+
284
+ // Check if this field is expandable
285
+ if (!schemaFieldName || !expandPolicy[schemaFieldName]) {
286
+ console.log(`[expandResolver] Field '${graphqlFieldName}' is not expandable (schemaFieldName: ${schemaFieldName}, has policy: ${!!expandPolicy[schemaFieldName]})`);
287
+ return null;
288
+ }
289
+
290
+ console.log(`[expandResolver] Expanding field '${graphqlFieldName}' (schema: ${schemaFieldName})`);
291
+ return resolveExpand({
292
+ parentItems,
293
+ parentModel,
294
+ field: schemaFieldName,
295
+ args: {},
296
+ });
297
+ })
298
+ .filter(Boolean); // Remove null entries
299
+
300
+ console.log(`[expandResolver] Executing ${expandPromises.length} expand promises`);
301
+ await Promise.all(expandPromises);
302
+ console.log(`[expandResolver] All expands completed`);
303
+ return;
304
+ }
305
+
306
+ // Handle standard graphql-parse-resolve-info format
208
307
  if (!fieldsByTypeName || Object.keys(fieldsByTypeName).length === 0) {
308
+ console.log(`[expandResolver] No fieldsByTypeName, skipping`);
209
309
  return;
210
310
  }
211
311
 
@@ -214,12 +314,13 @@ export async function resolveExpands(
214
314
  const fields = fieldsByTypeName[typeName];
215
315
 
216
316
  if (!fields) {
317
+ console.log(`[expandResolver] No fields found for type ${typeName}`);
217
318
  return;
218
319
  }
219
320
 
220
321
  // Get expand policy to filter only expandable fields
221
322
  const expandPolicy = extractExpandPolicy(parentModel.schema);
222
-
323
+
223
324
  // Create mapping from GraphQL field names to schema field names
224
325
  const graphqlToSchemaMap = createGraphQLToSchemaFieldMap(parentModel.schema);
225
326
 
@@ -231,6 +332,7 @@ export async function resolveExpands(
231
332
 
232
333
  // Check if this field is expandable
233
334
  if (!schemaFieldName || !expandPolicy[schemaFieldName]) {
335
+ console.log(`[expandResolver] Field '${graphqlFieldName}' is not expandable (schemaFieldName: ${schemaFieldName}, has policy: ${!!expandPolicy[schemaFieldName]})`);
234
336
  return null;
235
337
  }
236
338