@constructive-io/graphql-query 3.8.1 → 3.9.1

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.
@@ -185,10 +185,10 @@ function buildCleanTable(entityName, entityType, typeMap, queryFields, mutationF
185
185
  mutationOps.create ||
186
186
  mutationOps.update ||
187
187
  mutationOps.delete);
188
- // Infer primary key from mutation inputs
189
- const constraints = inferConstraints(entityName, typeMap);
188
+ // Infer primary key from mutation inputs (pass mutation ops for composite PK input type derivation)
189
+ const constraints = inferConstraints(entityName, typeMap, mutationOps);
190
190
  // Infer the patch field name from UpdateXxxInput (e.g., "userPatch")
191
- const patchFieldName = inferPatchFieldName(entityName, typeMap);
191
+ const patchFieldName = inferPatchFieldName(entityName, typeMap, mutationOps);
192
192
  // Build inflection map from discovered types
193
193
  const inflection = buildInflection(entityName, typeMap, entityToConnection);
194
194
  // Combine query operations with fallbacks for UI purposes
@@ -462,21 +462,23 @@ function matchMutationOperations(entityName, mutationFields) {
462
462
  if (field.name === expectedCreate) {
463
463
  create = field.name;
464
464
  }
465
- // Match update (could be updateUser or updateUserById)
466
- if (field.name === expectedUpdate ||
467
- field.name === `${expectedUpdate}ById`) {
468
- // Prefer non-ById version
469
- if (!update || field.name === expectedUpdate) {
470
- update = field.name;
471
- }
465
+ // Match update (could be updateUser, updateUserById, or updateUserByFooAndBar for composite PKs)
466
+ if (field.name === expectedUpdate) {
467
+ update = field.name;
472
468
  }
473
- // Match delete (could be deleteUser or deleteUserById)
474
- if (field.name === expectedDelete ||
475
- field.name === `${expectedDelete}ById`) {
476
- // Prefer non-ById version
477
- if (!del || field.name === expectedDelete) {
478
- del = field.name;
479
- }
469
+ else if (!update &&
470
+ (field.name === `${expectedUpdate}ById` ||
471
+ field.name.startsWith(`${expectedUpdate}By`))) {
472
+ update = field.name;
473
+ }
474
+ // Match delete (could be deleteUser, deleteUserById, or deleteUserByFooAndBar for composite PKs)
475
+ if (field.name === expectedDelete) {
476
+ del = field.name;
477
+ }
478
+ else if (!del &&
479
+ (field.name === `${expectedDelete}ById` ||
480
+ field.name.startsWith(`${expectedDelete}By`))) {
481
+ del = field.name;
480
482
  }
481
483
  }
482
484
  return { create, update, delete: del };
@@ -489,25 +491,29 @@ function matchMutationOperations(entityName, mutationFields) {
489
491
  *
490
492
  * Primary key can be inferred from Update/Delete mutation input types,
491
493
  * which typically have an 'id' field or similar.
494
+ *
495
+ * For composite PK tables (e.g. junction tables), PostGraphile generates
496
+ * mutations like `deletePostTagByPostIdAndTagId` with input type
497
+ * `DeletePostTagByPostIdAndTagIdInput`. We derive input type names from
498
+ * the actual matched mutation names when available.
492
499
  */
493
- function inferConstraints(entityName, typeMap) {
500
+ function inferConstraints(entityName, typeMap, mutations) {
494
501
  const primaryKey = [];
495
- // Try to find Update or Delete input type to extract PK
496
- const updateInputName = `Update${entityName}Input`;
497
- const deleteInputName = `Delete${entityName}Input`;
502
+ const deleteInputName = inputTypeFromMutation(mutations?.delete, `Delete${entityName}Input`);
503
+ const updateInputName = inputTypeFromMutation(mutations?.update, `Update${entityName}Input`);
498
504
  const updateInput = typeMap.get(updateInputName);
499
505
  const deleteInput = typeMap.get(deleteInputName);
500
- const keyInputField = inferPrimaryKeyFromInputObject(updateInput) ||
501
- inferPrimaryKeyFromInputObject(deleteInput);
502
- if (keyInputField) {
506
+ // Prefer Delete input (fewer non-PK fields) over Update input
507
+ const keyFields = inferPrimaryKeyFromInputObject(deleteInput).length > 0
508
+ ? inferPrimaryKeyFromInputObject(deleteInput)
509
+ : inferPrimaryKeyFromInputObject(updateInput);
510
+ if (keyFields.length > 0) {
503
511
  primaryKey.push({
504
512
  name: 'primary',
505
- fields: [
506
- {
507
- name: keyInputField.name,
508
- type: convertToCleanFieldType(keyInputField.type),
509
- },
510
- ],
513
+ fields: keyFields.map((f) => ({
514
+ name: f.name,
515
+ type: convertToCleanFieldType(f.type),
516
+ })),
511
517
  });
512
518
  }
513
519
  // If no PK found from inputs, try to find 'id' field in entity type
@@ -535,22 +541,23 @@ function inferConstraints(entityName, typeMap) {
535
541
  };
536
542
  }
537
543
  /**
538
- * Infer a single-row lookup key from an Update/Delete input object.
544
+ * Infer primary key fields from an Update/Delete input object.
539
545
  *
540
546
  * Priority:
541
547
  * 1. Canonical keys: id, nodeId, rowId
542
- * 2. Single non-patch/non-clientMutationId scalar-ish field
548
+ * 2. All non-patch/non-clientMutationId fields (supports composite keys)
543
549
  *
544
- * If multiple possible key fields remain, return null to avoid guessing.
550
+ * Returns all candidate key fields, enabling composite PK detection
551
+ * for junction tables like PostTag(postId, tagId).
545
552
  */
546
553
  function inferPrimaryKeyFromInputObject(inputType) {
547
554
  const inputFields = inputType?.inputFields ?? [];
548
555
  if (inputFields.length === 0)
549
- return null;
556
+ return [];
550
557
  const canonicalKey = inputFields.find((field) => field.name === 'id' || field.name === 'nodeId' || field.name === 'rowId');
551
558
  if (canonicalKey)
552
- return canonicalKey;
553
- const candidates = inputFields.filter((field) => {
559
+ return [canonicalKey];
560
+ return inputFields.filter((field) => {
554
561
  if (field.name === 'clientMutationId')
555
562
  return false;
556
563
  const baseTypeName = getBaseTypeName(field.type);
@@ -562,7 +569,6 @@ function inferPrimaryKeyFromInputObject(inputType) {
562
569
  return false;
563
570
  return true;
564
571
  });
565
- return candidates.length === 1 ? candidates[0] : null;
566
572
  }
567
573
  /**
568
574
  * Infer the patch field name from an Update input type.
@@ -573,8 +579,8 @@ function inferPrimaryKeyFromInputObject(inputType) {
573
579
  *
574
580
  * Pattern: {lcFirst(entityTypeName)}Patch
575
581
  */
576
- function inferPatchFieldName(entityName, typeMap) {
577
- const updateInputName = `Update${entityName}Input`;
582
+ function inferPatchFieldName(entityName, typeMap, mutations) {
583
+ const updateInputName = inputTypeFromMutation(mutations?.update, `Update${entityName}Input`);
578
584
  const updateInput = typeMap.get(updateInputName);
579
585
  const inputFields = updateInput?.inputFields ?? [];
580
586
  // Find the field whose type name ends in 'Patch'
@@ -679,6 +685,15 @@ function findOrderByType(entityName, pluralName, typeMap) {
679
685
  // ============================================================================
680
686
  // Utility Functions
681
687
  // ============================================================================
688
+ /**
689
+ * Derive the input type name for a mutation.
690
+ * PostGraphile always generates input types as ${PascalCaseMutationName}Input.
691
+ * When the actual mutation name is known (e.g. from introspection), we derive
692
+ * from it directly. Otherwise we fall back to the conventional ${Verb}${Entity}Input.
693
+ */
694
+ function inputTypeFromMutation(mutationName, fallback) {
695
+ return mutationName ? ucFirst(mutationName) + 'Input' : fallback;
696
+ }
682
697
  /**
683
698
  * Build a map of type name → IntrospectionType for efficient lookup
684
699
  */
@@ -188,10 +188,10 @@ function buildCleanTable(entityName, entityType, typeMap, queryFields, mutationF
188
188
  mutationOps.create ||
189
189
  mutationOps.update ||
190
190
  mutationOps.delete);
191
- // Infer primary key from mutation inputs
192
- const constraints = inferConstraints(entityName, typeMap);
191
+ // Infer primary key from mutation inputs (pass mutation ops for composite PK input type derivation)
192
+ const constraints = inferConstraints(entityName, typeMap, mutationOps);
193
193
  // Infer the patch field name from UpdateXxxInput (e.g., "userPatch")
194
- const patchFieldName = inferPatchFieldName(entityName, typeMap);
194
+ const patchFieldName = inferPatchFieldName(entityName, typeMap, mutationOps);
195
195
  // Build inflection map from discovered types
196
196
  const inflection = buildInflection(entityName, typeMap, entityToConnection);
197
197
  // Combine query operations with fallbacks for UI purposes
@@ -465,21 +465,23 @@ function matchMutationOperations(entityName, mutationFields) {
465
465
  if (field.name === expectedCreate) {
466
466
  create = field.name;
467
467
  }
468
- // Match update (could be updateUser or updateUserById)
469
- if (field.name === expectedUpdate ||
470
- field.name === `${expectedUpdate}ById`) {
471
- // Prefer non-ById version
472
- if (!update || field.name === expectedUpdate) {
473
- update = field.name;
474
- }
468
+ // Match update (could be updateUser, updateUserById, or updateUserByFooAndBar for composite PKs)
469
+ if (field.name === expectedUpdate) {
470
+ update = field.name;
475
471
  }
476
- // Match delete (could be deleteUser or deleteUserById)
477
- if (field.name === expectedDelete ||
478
- field.name === `${expectedDelete}ById`) {
479
- // Prefer non-ById version
480
- if (!del || field.name === expectedDelete) {
481
- del = field.name;
482
- }
472
+ else if (!update &&
473
+ (field.name === `${expectedUpdate}ById` ||
474
+ field.name.startsWith(`${expectedUpdate}By`))) {
475
+ update = field.name;
476
+ }
477
+ // Match delete (could be deleteUser, deleteUserById, or deleteUserByFooAndBar for composite PKs)
478
+ if (field.name === expectedDelete) {
479
+ del = field.name;
480
+ }
481
+ else if (!del &&
482
+ (field.name === `${expectedDelete}ById` ||
483
+ field.name.startsWith(`${expectedDelete}By`))) {
484
+ del = field.name;
483
485
  }
484
486
  }
485
487
  return { create, update, delete: del };
@@ -492,25 +494,29 @@ function matchMutationOperations(entityName, mutationFields) {
492
494
  *
493
495
  * Primary key can be inferred from Update/Delete mutation input types,
494
496
  * which typically have an 'id' field or similar.
497
+ *
498
+ * For composite PK tables (e.g. junction tables), PostGraphile generates
499
+ * mutations like `deletePostTagByPostIdAndTagId` with input type
500
+ * `DeletePostTagByPostIdAndTagIdInput`. We derive input type names from
501
+ * the actual matched mutation names when available.
495
502
  */
496
- function inferConstraints(entityName, typeMap) {
503
+ function inferConstraints(entityName, typeMap, mutations) {
497
504
  const primaryKey = [];
498
- // Try to find Update or Delete input type to extract PK
499
- const updateInputName = `Update${entityName}Input`;
500
- const deleteInputName = `Delete${entityName}Input`;
505
+ const deleteInputName = inputTypeFromMutation(mutations?.delete, `Delete${entityName}Input`);
506
+ const updateInputName = inputTypeFromMutation(mutations?.update, `Update${entityName}Input`);
501
507
  const updateInput = typeMap.get(updateInputName);
502
508
  const deleteInput = typeMap.get(deleteInputName);
503
- const keyInputField = inferPrimaryKeyFromInputObject(updateInput) ||
504
- inferPrimaryKeyFromInputObject(deleteInput);
505
- if (keyInputField) {
509
+ // Prefer Delete input (fewer non-PK fields) over Update input
510
+ const keyFields = inferPrimaryKeyFromInputObject(deleteInput).length > 0
511
+ ? inferPrimaryKeyFromInputObject(deleteInput)
512
+ : inferPrimaryKeyFromInputObject(updateInput);
513
+ if (keyFields.length > 0) {
506
514
  primaryKey.push({
507
515
  name: 'primary',
508
- fields: [
509
- {
510
- name: keyInputField.name,
511
- type: convertToCleanFieldType(keyInputField.type),
512
- },
513
- ],
516
+ fields: keyFields.map((f) => ({
517
+ name: f.name,
518
+ type: convertToCleanFieldType(f.type),
519
+ })),
514
520
  });
515
521
  }
516
522
  // If no PK found from inputs, try to find 'id' field in entity type
@@ -538,22 +544,23 @@ function inferConstraints(entityName, typeMap) {
538
544
  };
539
545
  }
540
546
  /**
541
- * Infer a single-row lookup key from an Update/Delete input object.
547
+ * Infer primary key fields from an Update/Delete input object.
542
548
  *
543
549
  * Priority:
544
550
  * 1. Canonical keys: id, nodeId, rowId
545
- * 2. Single non-patch/non-clientMutationId scalar-ish field
551
+ * 2. All non-patch/non-clientMutationId fields (supports composite keys)
546
552
  *
547
- * If multiple possible key fields remain, return null to avoid guessing.
553
+ * Returns all candidate key fields, enabling composite PK detection
554
+ * for junction tables like PostTag(postId, tagId).
548
555
  */
549
556
  function inferPrimaryKeyFromInputObject(inputType) {
550
557
  const inputFields = inputType?.inputFields ?? [];
551
558
  if (inputFields.length === 0)
552
- return null;
559
+ return [];
553
560
  const canonicalKey = inputFields.find((field) => field.name === 'id' || field.name === 'nodeId' || field.name === 'rowId');
554
561
  if (canonicalKey)
555
- return canonicalKey;
556
- const candidates = inputFields.filter((field) => {
562
+ return [canonicalKey];
563
+ return inputFields.filter((field) => {
557
564
  if (field.name === 'clientMutationId')
558
565
  return false;
559
566
  const baseTypeName = (0, introspection_1.getBaseTypeName)(field.type);
@@ -565,7 +572,6 @@ function inferPrimaryKeyFromInputObject(inputType) {
565
572
  return false;
566
573
  return true;
567
574
  });
568
- return candidates.length === 1 ? candidates[0] : null;
569
575
  }
570
576
  /**
571
577
  * Infer the patch field name from an Update input type.
@@ -576,8 +582,8 @@ function inferPrimaryKeyFromInputObject(inputType) {
576
582
  *
577
583
  * Pattern: {lcFirst(entityTypeName)}Patch
578
584
  */
579
- function inferPatchFieldName(entityName, typeMap) {
580
- const updateInputName = `Update${entityName}Input`;
585
+ function inferPatchFieldName(entityName, typeMap, mutations) {
586
+ const updateInputName = inputTypeFromMutation(mutations?.update, `Update${entityName}Input`);
581
587
  const updateInput = typeMap.get(updateInputName);
582
588
  const inputFields = updateInput?.inputFields ?? [];
583
589
  // Find the field whose type name ends in 'Patch'
@@ -682,6 +688,15 @@ function findOrderByType(entityName, pluralName, typeMap) {
682
688
  // ============================================================================
683
689
  // Utility Functions
684
690
  // ============================================================================
691
+ /**
692
+ * Derive the input type name for a mutation.
693
+ * PostGraphile always generates input types as ${PascalCaseMutationName}Input.
694
+ * When the actual mutation name is known (e.g. from introspection), we derive
695
+ * from it directly. Otherwise we fall back to the conventional ${Verb}${Entity}Input.
696
+ */
697
+ function inputTypeFromMutation(mutationName, fallback) {
698
+ return mutationName ? (0, inflekt_1.ucFirst)(mutationName) + 'Input' : fallback;
699
+ }
685
700
  /**
686
701
  * Build a map of type name → IntrospectionType for efficient lookup
687
702
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@constructive-io/graphql-query",
3
- "version": "3.8.1",
3
+ "version": "3.9.1",
4
4
  "description": "Constructive GraphQL Query",
5
5
  "author": "Constructive <developers@constructive.io>",
6
6
  "main": "index.js",
@@ -34,7 +34,7 @@
34
34
  "grafast": "1.0.0-rc.9",
35
35
  "graphile-build-pg": "5.0.0-rc.8",
36
36
  "graphile-config": "1.0.0-rc.6",
37
- "graphile-settings": "^4.12.1",
37
+ "graphile-settings": "^4.12.2",
38
38
  "graphql": "16.13.0",
39
39
  "inflection": "^3.0.0",
40
40
  "inflekt": "^0.3.3",
@@ -51,5 +51,5 @@
51
51
  "devDependencies": {
52
52
  "makage": "^0.1.10"
53
53
  },
54
- "gitHead": "b0ff4ed0025af0e5df91d8e3535c347e4cde6438"
54
+ "gitHead": "bfc62d3bf8b8374dea67536cfea9d16cbe0575ac"
55
55
  }