@constructive-io/graphql-codegen 4.15.1 → 4.15.3

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.
@@ -429,7 +429,9 @@ function buildEntityProperties(table) {
429
429
  if ((0, utils_1.isRelationField)(field.name, table))
430
430
  continue;
431
431
  const fieldType = typeof field.type === 'string' ? field.type : field.type.gqlType;
432
- const tsType = scalarToInputTs(fieldType);
432
+ const isArray = typeof field.type !== 'string' && field.type.isArray;
433
+ const baseTsType = scalarToInputTs(fieldType);
434
+ const tsType = isArray ? `${baseTsType}[]` : baseTsType;
433
435
  const isNullable = field.name !== 'id' && field.name !== 'nodeId';
434
436
  properties.push({
435
437
  name: field.name,
@@ -712,8 +714,8 @@ function generateEntitySelectTypes(tables, tableByName) {
712
714
  /**
713
715
  * Map field type to filter type
714
716
  */
715
- function getFilterTypeForField(fieldType) {
716
- return (0, scalars_1.scalarToFilterType)(fieldType) ?? 'StringFilter';
717
+ function getFilterTypeForField(fieldType, isArray = false) {
718
+ return (0, scalars_1.scalarToFilterType)(fieldType, isArray) ?? 'StringFilter';
717
719
  }
718
720
  /**
719
721
  * Build properties for a table filter interface
@@ -723,9 +725,10 @@ function buildTableFilterProperties(table) {
723
725
  const properties = [];
724
726
  for (const field of table.fields) {
725
727
  const fieldType = typeof field.type === 'string' ? field.type : field.type.gqlType;
728
+ const isArray = typeof field.type !== 'string' && field.type.isArray;
726
729
  if ((0, utils_1.isRelationField)(field.name, table))
727
730
  continue;
728
- const filterType = getFilterTypeForField(fieldType);
731
+ const filterType = getFilterTypeForField(fieldType, isArray);
729
732
  properties.push({ name: field.name, type: filterType, optional: true });
730
733
  }
731
734
  // Add logical operators
@@ -822,6 +825,16 @@ function generateTableConditionTypes(tables, typeRegistry) {
822
825
  * from VectorSearchPlugin).
823
826
  */
824
827
  function buildOrderByValues(table, typeRegistry) {
828
+ // When the schema's orderBy enum is available, use it as the source of truth
829
+ // instead of naively generating values for every field.
830
+ if (typeRegistry) {
831
+ const orderByTypeName = (0, utils_1.getOrderByTypeName)(table);
832
+ const orderByType = typeRegistry.get(orderByTypeName);
833
+ if (orderByType?.kind === 'ENUM' && orderByType.enumValues) {
834
+ return [...orderByType.enumValues];
835
+ }
836
+ }
837
+ // Fallback: derive from table fields when schema enum is not available
825
838
  const values = ['PRIMARY_KEY_ASC', 'PRIMARY_KEY_DESC', 'NATURAL'];
826
839
  for (const field of table.fields) {
827
840
  if ((0, utils_1.isRelationField)(field.name, table))
@@ -830,20 +843,6 @@ function buildOrderByValues(table, typeRegistry) {
830
843
  values.push(`${upperSnake}_ASC`);
831
844
  values.push(`${upperSnake}_DESC`);
832
845
  }
833
- // Merge any additional values from the schema's orderBy enum type
834
- // (e.g., plugin-added values like EMBEDDING_DISTANCE_ASC/DESC)
835
- if (typeRegistry) {
836
- const orderByTypeName = (0, utils_1.getOrderByTypeName)(table);
837
- const orderByType = typeRegistry.get(orderByTypeName);
838
- if (orderByType?.kind === 'ENUM' && orderByType.enumValues) {
839
- const existingValues = new Set(values);
840
- for (const value of orderByType.enumValues) {
841
- if (!existingValues.has(value)) {
842
- values.push(value);
843
- }
844
- }
845
- }
846
- }
847
846
  return values;
848
847
  }
849
848
  /**
@@ -877,7 +876,9 @@ function buildCreateDataFieldsFromTable(table) {
877
876
  if ((0, utils_1.isRelationField)(field.name, table))
878
877
  continue;
879
878
  const fieldType = typeof field.type === 'string' ? field.type : field.type.gqlType;
880
- const tsType = scalarToInputTs(fieldType);
879
+ const isArray = typeof field.type !== 'string' && field.type.isArray;
880
+ const baseTsType = scalarToInputTs(fieldType);
881
+ const tsType = isArray ? `${baseTsType}[]` : baseTsType;
881
882
  const isOptional = !field.name.endsWith('Id');
882
883
  fields.push({ name: field.name, type: tsType, optional: isOptional });
883
884
  }
@@ -958,9 +959,34 @@ function buildCreateInputInterface(table, typeRegistry) {
958
959
  return t.exportNamedDeclaration(interfaceDecl);
959
960
  }
960
961
  /**
961
- * Build Patch type properties
962
+ * Build Patch type properties.
963
+ *
964
+ * Prefers reading from the GraphQL schema's Patch input type when available
965
+ * (via typeRegistry), which correctly excludes computed/virtual fields.
966
+ * Falls back to deriving from table fields if the schema type is not found.
962
967
  */
963
- function buildPatchProperties(table) {
968
+ function buildPatchProperties(table, typeRegistry) {
969
+ // Try to read from the schema's Patch input type first
970
+ if (typeRegistry) {
971
+ const patchTypeName = (0, utils_1.getPatchTypeName)(table);
972
+ const patchType = typeRegistry.get(patchTypeName);
973
+ if (patchType?.kind === 'INPUT_OBJECT' &&
974
+ patchType.inputFields) {
975
+ const properties = [];
976
+ for (const field of patchType.inputFields) {
977
+ if (EXCLUDED_MUTATION_FIELDS.includes(field.name))
978
+ continue;
979
+ const tsType = typeRefToTs(field.type);
980
+ properties.push({
981
+ name: field.name,
982
+ type: `${tsType} | null`,
983
+ optional: true,
984
+ });
985
+ }
986
+ return properties;
987
+ }
988
+ }
989
+ // Fallback: derive from table fields
964
990
  const properties = [];
965
991
  for (const field of table.fields) {
966
992
  if (EXCLUDED_MUTATION_FIELDS.includes(field.name))
@@ -968,7 +994,9 @@ function buildPatchProperties(table) {
968
994
  if ((0, utils_1.isRelationField)(field.name, table))
969
995
  continue;
970
996
  const fieldType = typeof field.type === 'string' ? field.type : field.type.gqlType;
971
- const tsType = scalarToInputTs(fieldType);
997
+ const isArray = typeof field.type !== 'string' && field.type.isArray;
998
+ const baseTsType = scalarToInputTs(fieldType);
999
+ const tsType = isArray ? `${baseTsType}[]` : baseTsType;
972
1000
  properties.push({
973
1001
  name: field.name,
974
1002
  type: `${tsType} | null`,
@@ -991,7 +1019,7 @@ function generateCrudInputTypes(table, typeRegistry) {
991
1019
  // Create input
992
1020
  statements.push(buildCreateInputInterface(table, typeRegistry));
993
1021
  // Patch interface
994
- statements.push(createExportedInterface(patchName, buildPatchProperties(table)));
1022
+ statements.push(createExportedInterface(patchName, buildPatchProperties(table, typeRegistry)));
995
1023
  // Update input - v5 uses entity-specific patch field names (e.g., "userPatch")
996
1024
  const patchFieldName = table.query?.patchFieldName ?? (0, utils_1.lcFirst)(typeName) + 'Patch';
997
1025
  statements.push(createExportedInterface(`Update${typeName}Input`, [
package/core/generate.js CHANGED
@@ -50,6 +50,7 @@ const fs = __importStar(require("node:fs"));
50
50
  const node_path_1 = __importDefault(require("node:path"));
51
51
  const graphql_1 = require("graphql");
52
52
  const core_1 = require("@pgpmjs/core");
53
+ const pg_cache_1 = require("pg-cache");
53
54
  const pgsql_client_1 = require("pgsql-client");
54
55
  const pgsql_seed_1 = require("pgsql-seed");
55
56
  const config_1 = require("../types/config");
@@ -681,6 +682,10 @@ async function generateMulti(options) {
681
682
  finally {
682
683
  for (const shared of sharedSources.values()) {
683
684
  const keepDb = Object.values(configs).some((c) => c.db?.keepDb);
685
+ // Release pg-cache pool for this ephemeral database before dropping
686
+ // deployPgpm() caches connections that must be closed first
687
+ pg_cache_1.pgCache.delete(shared.ephemeralDb.config.database);
688
+ await pg_cache_1.pgCache.waitForDisposals();
684
689
  shared.ephemeralDb.teardown({ keepDb });
685
690
  }
686
691
  }
@@ -136,6 +136,10 @@ class PgpmModuleSchemaSource {
136
136
  return { introspection };
137
137
  }
138
138
  finally {
139
+ // Release pg-cache pool for this ephemeral database before dropping
140
+ // deployPgpm() and getPgPool() cache connections that must be closed first
141
+ pg_cache_1.pgCache.delete(dbConfig.database);
142
+ await pg_cache_1.pgCache.waitForDisposals();
139
143
  // Clean up the ephemeral database
140
144
  teardown({ keepDb });
141
145
  if (keepDb) {
@@ -15,7 +15,7 @@ import { pluralize } from 'inflekt';
15
15
  import { addJSDocComment, addLineComment, generateCode } from '../babel-ast';
16
16
  import { SCALAR_NAMES, scalarToFilterType, scalarToTsType } from '../scalars';
17
17
  import { getTypeBaseName } from '../type-resolver';
18
- import { getCreateInputTypeName, getConditionTypeName, getFilterTypeName, getGeneratedFileHeader, getOrderByTypeName, getPrimaryKeyInfo, getTableNames, isRelationField, lcFirst, stripSmartComments, } from '../utils';
18
+ import { getCreateInputTypeName, getConditionTypeName, getFilterTypeName, getGeneratedFileHeader, getOrderByTypeName, getPatchTypeName, getPrimaryKeyInfo, getTableNames, isRelationField, lcFirst, stripSmartComments, } from '../utils';
19
19
  // ============================================================================
20
20
  // Constants
21
21
  // ============================================================================
@@ -391,7 +391,9 @@ function buildEntityProperties(table) {
391
391
  if (isRelationField(field.name, table))
392
392
  continue;
393
393
  const fieldType = typeof field.type === 'string' ? field.type : field.type.gqlType;
394
- const tsType = scalarToInputTs(fieldType);
394
+ const isArray = typeof field.type !== 'string' && field.type.isArray;
395
+ const baseTsType = scalarToInputTs(fieldType);
396
+ const tsType = isArray ? `${baseTsType}[]` : baseTsType;
395
397
  const isNullable = field.name !== 'id' && field.name !== 'nodeId';
396
398
  properties.push({
397
399
  name: field.name,
@@ -674,8 +676,8 @@ function generateEntitySelectTypes(tables, tableByName) {
674
676
  /**
675
677
  * Map field type to filter type
676
678
  */
677
- function getFilterTypeForField(fieldType) {
678
- return scalarToFilterType(fieldType) ?? 'StringFilter';
679
+ function getFilterTypeForField(fieldType, isArray = false) {
680
+ return scalarToFilterType(fieldType, isArray) ?? 'StringFilter';
679
681
  }
680
682
  /**
681
683
  * Build properties for a table filter interface
@@ -685,9 +687,10 @@ function buildTableFilterProperties(table) {
685
687
  const properties = [];
686
688
  for (const field of table.fields) {
687
689
  const fieldType = typeof field.type === 'string' ? field.type : field.type.gqlType;
690
+ const isArray = typeof field.type !== 'string' && field.type.isArray;
688
691
  if (isRelationField(field.name, table))
689
692
  continue;
690
- const filterType = getFilterTypeForField(fieldType);
693
+ const filterType = getFilterTypeForField(fieldType, isArray);
691
694
  properties.push({ name: field.name, type: filterType, optional: true });
692
695
  }
693
696
  // Add logical operators
@@ -784,6 +787,16 @@ function generateTableConditionTypes(tables, typeRegistry) {
784
787
  * from VectorSearchPlugin).
785
788
  */
786
789
  function buildOrderByValues(table, typeRegistry) {
790
+ // When the schema's orderBy enum is available, use it as the source of truth
791
+ // instead of naively generating values for every field.
792
+ if (typeRegistry) {
793
+ const orderByTypeName = getOrderByTypeName(table);
794
+ const orderByType = typeRegistry.get(orderByTypeName);
795
+ if (orderByType?.kind === 'ENUM' && orderByType.enumValues) {
796
+ return [...orderByType.enumValues];
797
+ }
798
+ }
799
+ // Fallback: derive from table fields when schema enum is not available
787
800
  const values = ['PRIMARY_KEY_ASC', 'PRIMARY_KEY_DESC', 'NATURAL'];
788
801
  for (const field of table.fields) {
789
802
  if (isRelationField(field.name, table))
@@ -792,20 +805,6 @@ function buildOrderByValues(table, typeRegistry) {
792
805
  values.push(`${upperSnake}_ASC`);
793
806
  values.push(`${upperSnake}_DESC`);
794
807
  }
795
- // Merge any additional values from the schema's orderBy enum type
796
- // (e.g., plugin-added values like EMBEDDING_DISTANCE_ASC/DESC)
797
- if (typeRegistry) {
798
- const orderByTypeName = getOrderByTypeName(table);
799
- const orderByType = typeRegistry.get(orderByTypeName);
800
- if (orderByType?.kind === 'ENUM' && orderByType.enumValues) {
801
- const existingValues = new Set(values);
802
- for (const value of orderByType.enumValues) {
803
- if (!existingValues.has(value)) {
804
- values.push(value);
805
- }
806
- }
807
- }
808
- }
809
808
  return values;
810
809
  }
811
810
  /**
@@ -839,7 +838,9 @@ function buildCreateDataFieldsFromTable(table) {
839
838
  if (isRelationField(field.name, table))
840
839
  continue;
841
840
  const fieldType = typeof field.type === 'string' ? field.type : field.type.gqlType;
842
- const tsType = scalarToInputTs(fieldType);
841
+ const isArray = typeof field.type !== 'string' && field.type.isArray;
842
+ const baseTsType = scalarToInputTs(fieldType);
843
+ const tsType = isArray ? `${baseTsType}[]` : baseTsType;
843
844
  const isOptional = !field.name.endsWith('Id');
844
845
  fields.push({ name: field.name, type: tsType, optional: isOptional });
845
846
  }
@@ -920,9 +921,34 @@ function buildCreateInputInterface(table, typeRegistry) {
920
921
  return t.exportNamedDeclaration(interfaceDecl);
921
922
  }
922
923
  /**
923
- * Build Patch type properties
924
+ * Build Patch type properties.
925
+ *
926
+ * Prefers reading from the GraphQL schema's Patch input type when available
927
+ * (via typeRegistry), which correctly excludes computed/virtual fields.
928
+ * Falls back to deriving from table fields if the schema type is not found.
924
929
  */
925
- function buildPatchProperties(table) {
930
+ function buildPatchProperties(table, typeRegistry) {
931
+ // Try to read from the schema's Patch input type first
932
+ if (typeRegistry) {
933
+ const patchTypeName = getPatchTypeName(table);
934
+ const patchType = typeRegistry.get(patchTypeName);
935
+ if (patchType?.kind === 'INPUT_OBJECT' &&
936
+ patchType.inputFields) {
937
+ const properties = [];
938
+ for (const field of patchType.inputFields) {
939
+ if (EXCLUDED_MUTATION_FIELDS.includes(field.name))
940
+ continue;
941
+ const tsType = typeRefToTs(field.type);
942
+ properties.push({
943
+ name: field.name,
944
+ type: `${tsType} | null`,
945
+ optional: true,
946
+ });
947
+ }
948
+ return properties;
949
+ }
950
+ }
951
+ // Fallback: derive from table fields
926
952
  const properties = [];
927
953
  for (const field of table.fields) {
928
954
  if (EXCLUDED_MUTATION_FIELDS.includes(field.name))
@@ -930,7 +956,9 @@ function buildPatchProperties(table) {
930
956
  if (isRelationField(field.name, table))
931
957
  continue;
932
958
  const fieldType = typeof field.type === 'string' ? field.type : field.type.gqlType;
933
- const tsType = scalarToInputTs(fieldType);
959
+ const isArray = typeof field.type !== 'string' && field.type.isArray;
960
+ const baseTsType = scalarToInputTs(fieldType);
961
+ const tsType = isArray ? `${baseTsType}[]` : baseTsType;
934
962
  properties.push({
935
963
  name: field.name,
936
964
  type: `${tsType} | null`,
@@ -953,7 +981,7 @@ function generateCrudInputTypes(table, typeRegistry) {
953
981
  // Create input
954
982
  statements.push(buildCreateInputInterface(table, typeRegistry));
955
983
  // Patch interface
956
- statements.push(createExportedInterface(patchName, buildPatchProperties(table)));
984
+ statements.push(createExportedInterface(patchName, buildPatchProperties(table, typeRegistry)));
957
985
  // Update input - v5 uses entity-specific patch field names (e.g., "userPatch")
958
986
  const patchFieldName = table.query?.patchFieldName ?? lcFirst(typeName) + 'Patch';
959
987
  statements.push(createExportedInterface(`Update${typeName}Input`, [
@@ -8,6 +8,7 @@ import * as fs from 'node:fs';
8
8
  import path from 'node:path';
9
9
  import { buildClientSchema, printSchema } from 'graphql';
10
10
  import { PgpmPackage } from '@pgpmjs/core';
11
+ import { pgCache } from 'pg-cache';
11
12
  import { createEphemeralDb } from 'pgsql-client';
12
13
  import { deployPgpm } from 'pgsql-seed';
13
14
  import { getConfigOptions } from '../types/config';
@@ -639,6 +640,10 @@ export async function generateMulti(options) {
639
640
  finally {
640
641
  for (const shared of sharedSources.values()) {
641
642
  const keepDb = Object.values(configs).some((c) => c.db?.keepDb);
643
+ // Release pg-cache pool for this ephemeral database before dropping
644
+ // deployPgpm() caches connections that must be closed first
645
+ pgCache.delete(shared.ephemeralDb.config.database);
646
+ await pgCache.waitForDisposals();
642
647
  shared.ephemeralDb.teardown({ keepDb });
643
648
  }
644
649
  }
@@ -9,7 +9,7 @@
9
9
  */
10
10
  import { PgpmPackage } from '@pgpmjs/core';
11
11
  import { buildSchema, introspectionFromSchema } from 'graphql';
12
- import { getPgPool } from 'pg-cache';
12
+ import { getPgPool, pgCache } from 'pg-cache';
13
13
  import { createEphemeralDb } from 'pgsql-client';
14
14
  import { deployPgpm } from 'pgsql-seed';
15
15
  import { buildSchemaSDL } from 'graphile-schema';
@@ -131,6 +131,10 @@ export class PgpmModuleSchemaSource {
131
131
  return { introspection };
132
132
  }
133
133
  finally {
134
+ // Release pg-cache pool for this ephemeral database before dropping
135
+ // deployPgpm() and getPgPool() cache connections that must be closed first
136
+ pgCache.delete(dbConfig.database);
137
+ await pgCache.waitForDisposals();
134
138
  // Clean up the ephemeral database
135
139
  teardown({ keepDb });
136
140
  if (keepDb) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@constructive-io/graphql-codegen",
3
- "version": "4.15.1",
3
+ "version": "4.15.3",
4
4
  "description": "GraphQL SDK generator for Constructive databases with React Query hooks",
5
5
  "keywords": [
6
6
  "graphql",
@@ -56,15 +56,15 @@
56
56
  "@0no-co/graphql.web": "^1.1.2",
57
57
  "@babel/generator": "^7.29.1",
58
58
  "@babel/types": "^7.29.0",
59
- "@constructive-io/graphql-query": "^3.6.3",
59
+ "@constructive-io/graphql-query": "^3.6.5",
60
60
  "@constructive-io/graphql-types": "^3.3.4",
61
61
  "@inquirerer/utils": "^3.3.4",
62
- "@pgpmjs/core": "^6.6.5",
62
+ "@pgpmjs/core": "^6.7.1",
63
63
  "ajv": "^8.18.0",
64
64
  "deepmerge": "^4.3.1",
65
65
  "find-and-require-package-json": "^0.9.1",
66
66
  "gql-ast": "^3.3.3",
67
- "graphile-schema": "^1.6.3",
67
+ "graphile-schema": "^1.6.5",
68
68
  "graphql": "16.13.0",
69
69
  "inflekt": "^0.3.3",
70
70
  "inquirerer": "^4.7.0",
@@ -73,8 +73,8 @@
73
73
  "oxfmt": "^0.40.0",
74
74
  "pg-cache": "^3.3.4",
75
75
  "pg-env": "^1.7.3",
76
- "pgsql-client": "^3.5.5",
77
- "pgsql-seed": "^2.5.5",
76
+ "pgsql-client": "^3.5.7",
77
+ "pgsql-seed": "^2.5.7",
78
78
  "undici": "^7.24.3"
79
79
  },
80
80
  "peerDependencies": {
@@ -101,5 +101,5 @@
101
101
  "tsx": "^4.21.0",
102
102
  "typescript": "^5.9.3"
103
103
  },
104
- "gitHead": "9c322f47ca08b5b853fcb395fe2bfc224f8c4c27"
104
+ "gitHead": "c18ef8e002d958001fe69fff3758d1f89613eb2b"
105
105
  }