graphile-settings 4.1.1 → 4.3.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.
Files changed (101) hide show
  1. package/esm/index.d.ts +1 -2
  2. package/esm/index.js +2 -4
  3. package/esm/plugins/index.d.ts +4 -13
  4. package/esm/plugins/index.js +5 -21
  5. package/esm/presets/constructive-preset.d.ts +4 -0
  6. package/esm/presets/constructive-preset.js +17 -9
  7. package/esm/upload-resolver.d.ts +43 -0
  8. package/esm/upload-resolver.js +168 -0
  9. package/index.d.ts +1 -2
  10. package/index.js +4 -7
  11. package/package.json +14 -6
  12. package/plugins/index.d.ts +4 -13
  13. package/plugins/index.js +32 -44
  14. package/presets/constructive-preset.d.ts +4 -0
  15. package/presets/constructive-preset.js +26 -18
  16. package/upload-resolver.d.ts +43 -0
  17. package/upload-resolver.js +175 -0
  18. package/esm/plugins/conflict-detector.d.ts +0 -7
  19. package/esm/plugins/conflict-detector.js +0 -67
  20. package/esm/plugins/custom-inflector.d.ts +0 -9
  21. package/esm/plugins/custom-inflector.js +0 -382
  22. package/esm/plugins/enable-all-filter-columns.d.ts +0 -60
  23. package/esm/plugins/enable-all-filter-columns.js +0 -85
  24. package/esm/plugins/inflector-logger.d.ts +0 -7
  25. package/esm/plugins/inflector-logger.js +0 -215
  26. package/esm/plugins/many-to-many-preset.d.ts +0 -62
  27. package/esm/plugins/many-to-many-preset.js +0 -86
  28. package/esm/plugins/meta-schema/cache.d.ts +0 -4
  29. package/esm/plugins/meta-schema/cache.js +0 -7
  30. package/esm/plugins/meta-schema/constraint-meta-builders.d.ts +0 -13
  31. package/esm/plugins/meta-schema/constraint-meta-builders.js +0 -51
  32. package/esm/plugins/meta-schema/graphql-meta-field.d.ts +0 -4
  33. package/esm/plugins/meta-schema/graphql-meta-field.js +0 -201
  34. package/esm/plugins/meta-schema/inflection-utils.d.ts +0 -4
  35. package/esm/plugins/meta-schema/inflection-utils.js +0 -20
  36. package/esm/plugins/meta-schema/name-meta-builders.d.ts +0 -4
  37. package/esm/plugins/meta-schema/name-meta-builders.js +0 -38
  38. package/esm/plugins/meta-schema/plugin.d.ts +0 -2
  39. package/esm/plugins/meta-schema/plugin.js +0 -23
  40. package/esm/plugins/meta-schema/relation-meta-builders.d.ts +0 -8
  41. package/esm/plugins/meta-schema/relation-meta-builders.js +0 -115
  42. package/esm/plugins/meta-schema/table-meta-builder.d.ts +0 -2
  43. package/esm/plugins/meta-schema/table-meta-builder.js +0 -69
  44. package/esm/plugins/meta-schema/table-meta-context.d.ts +0 -13
  45. package/esm/plugins/meta-schema/table-meta-context.js +0 -11
  46. package/esm/plugins/meta-schema/table-resource-utils.d.ts +0 -12
  47. package/esm/plugins/meta-schema/table-resource-utils.js +0 -50
  48. package/esm/plugins/meta-schema/type-mappings.d.ts +0 -3
  49. package/esm/plugins/meta-schema/type-mappings.js +0 -75
  50. package/esm/plugins/meta-schema/types.d.ts +0 -206
  51. package/esm/plugins/meta-schema/types.js +0 -1
  52. package/esm/plugins/meta-schema.d.ts +0 -19
  53. package/esm/plugins/meta-schema.js +0 -20
  54. package/esm/plugins/minimal-preset.d.ts +0 -7
  55. package/esm/plugins/minimal-preset.js +0 -42
  56. package/esm/plugins/pg-type-mappings.d.ts +0 -41
  57. package/esm/plugins/pg-type-mappings.js +0 -122
  58. package/esm/plugins/primary-key-only.d.ts +0 -96
  59. package/esm/plugins/primary-key-only.js +0 -143
  60. package/plugins/conflict-detector.d.ts +0 -7
  61. package/plugins/conflict-detector.js +0 -70
  62. package/plugins/custom-inflector.d.ts +0 -9
  63. package/plugins/custom-inflector.js +0 -385
  64. package/plugins/enable-all-filter-columns.d.ts +0 -60
  65. package/plugins/enable-all-filter-columns.js +0 -88
  66. package/plugins/inflector-logger.d.ts +0 -7
  67. package/plugins/inflector-logger.js +0 -218
  68. package/plugins/many-to-many-preset.d.ts +0 -62
  69. package/plugins/many-to-many-preset.js +0 -89
  70. package/plugins/meta-schema/cache.d.ts +0 -4
  71. package/plugins/meta-schema/cache.js +0 -12
  72. package/plugins/meta-schema/constraint-meta-builders.d.ts +0 -13
  73. package/plugins/meta-schema/constraint-meta-builders.js +0 -58
  74. package/plugins/meta-schema/graphql-meta-field.d.ts +0 -4
  75. package/plugins/meta-schema/graphql-meta-field.js +0 -204
  76. package/plugins/meta-schema/inflection-utils.d.ts +0 -4
  77. package/plugins/meta-schema/inflection-utils.js +0 -25
  78. package/plugins/meta-schema/name-meta-builders.d.ts +0 -4
  79. package/plugins/meta-schema/name-meta-builders.js +0 -43
  80. package/plugins/meta-schema/plugin.d.ts +0 -2
  81. package/plugins/meta-schema/plugin.js +0 -26
  82. package/plugins/meta-schema/relation-meta-builders.d.ts +0 -8
  83. package/plugins/meta-schema/relation-meta-builders.js +0 -120
  84. package/plugins/meta-schema/table-meta-builder.d.ts +0 -2
  85. package/plugins/meta-schema/table-meta-builder.js +0 -72
  86. package/plugins/meta-schema/table-meta-context.d.ts +0 -13
  87. package/plugins/meta-schema/table-meta-context.js +0 -15
  88. package/plugins/meta-schema/table-resource-utils.d.ts +0 -12
  89. package/plugins/meta-schema/table-resource-utils.js +0 -60
  90. package/plugins/meta-schema/type-mappings.d.ts +0 -3
  91. package/plugins/meta-schema/type-mappings.js +0 -79
  92. package/plugins/meta-schema/types.d.ts +0 -206
  93. package/plugins/meta-schema/types.js +0 -2
  94. package/plugins/meta-schema.d.ts +0 -19
  95. package/plugins/meta-schema.js +0 -20
  96. package/plugins/minimal-preset.d.ts +0 -7
  97. package/plugins/minimal-preset.js +0 -45
  98. package/plugins/pg-type-mappings.d.ts +0 -41
  99. package/plugins/pg-type-mappings.js +0 -128
  100. package/plugins/primary-key-only.d.ts +0 -96
  101. package/plugins/primary-key-only.js +0 -147
@@ -1,143 +0,0 @@
1
- /**
2
- * PrimaryKeyOnlyPlugin - Disables non-primary-key unique constraint lookups for
3
- * BOTH queries AND mutations.
4
- *
5
- * WHY THIS EXISTS:
6
- * PostGraphile v5 creates fields for EVERY unique constraint on a table:
7
- *
8
- * QUERIES (PgRowByUniquePlugin):
9
- * - `user(id)`, `userByEmail(email)`, `userByUsername(username)`
10
- *
11
- * MUTATIONS (PgMutationUpdateDeletePlugin):
12
- * - `updateUser`, `updateUserByEmail`, `updateUserByUsername`
13
- * - `deleteUser`, `deleteUserByEmail`, `deleteUserByUsername`
14
- *
15
- * For code generation (React Query, etc.), this creates unnecessary complexity.
16
- * The same operations can be done using the primary key lookup or filters.
17
- * Standardizing on primary keys reduces the API surface and generated code.
18
- *
19
- * SOURCE CODE REFERENCES:
20
- *
21
- * 1. Query fields (PgRowByUniquePlugin):
22
- * https://github.com/graphile/crystal/blob/924b2515c6bd30e5905ac1419a25244b40c8bb4d/graphile-build/graphile-build-pg/src/plugins/PgRowByUniquePlugin.ts#L42-L257
23
- *
24
- * The behavior check for queries:
25
- * ```typescript
26
- * const fieldBehaviorScope = "query:resource:single";
27
- * if (!build.behavior.pgResourceUniqueMatches([resource, unique], fieldBehaviorScope)) {
28
- * return memo; // Skip this field
29
- * }
30
- * ```
31
- *
32
- * 2. Mutation fields (PgMutationUpdateDeletePlugin):
33
- * https://github.com/graphile/crystal/blob/924b2515c6bd30e5905ac1419a25244b40c8bb4d/graphile-build/graphile-build-pg/src/plugins/PgMutationUpdateDeletePlugin.ts
34
- *
35
- * The behavior check for mutations:
36
- * ```typescript
37
- * const constraintMode = `constraint:${mode}`; // "constraint:resource:update" or "constraint:resource:delete"
38
- * ...resource.uniques.filter((unique) => {
39
- * return build.behavior.pgResourceUniqueMatches([resource, unique], constraintMode);
40
- * })
41
- * ```
42
- *
43
- * OUR FIX:
44
- * We use the behavior system's OVERRIDE phase (not inferred) to disable these behaviors.
45
- * The override phase runs AFTER the behavior multiplication/preferences system processes
46
- * behaviors, giving us the final say on what's enabled/disabled.
47
- *
48
- * Behaviors we control:
49
- * - `-single` - Disables query lookups (userByEmail, etc.)
50
- * - `-constraint:resource:update` - Disables updateByX mutations
51
- * - `-constraint:resource:delete` - Disables deleteByX mutations
52
- *
53
- * CONFIGURATION OPTIONS:
54
- *
55
- * 1. `disableAllUniqueLookups: false` (default - PrimaryKeyOnlyPreset):
56
- * - Primary key: query lookup + mutations enabled
57
- * - Non-primary-key: everything disabled
58
- * Result: `user(id)`, `updateUser`, `deleteUser` only
59
- *
60
- * 2. `disableAllUniqueLookups: true` (NoUniqueLookupPreset):
61
- * - Primary key: query lookup DISABLED, mutations ENABLED
62
- * - Non-primary-key: everything disabled
63
- * Result: No query lookups (use filters), but `updateUser`, `deleteUser` still work
64
- */
65
- /**
66
- * Creates a plugin that controls unique constraint lookup behavior.
67
- *
68
- * @param options - Configuration options
69
- * @param options.disableAllUniqueLookups - If true, disables ALL unique lookups including primary keys
70
- */
71
- export function createUniqueLookupPlugin(options = {}) {
72
- const { disableAllUniqueLookups = false } = options;
73
- return {
74
- name: 'UniqueLookupPlugin',
75
- version: '1.0.0',
76
- description: disableAllUniqueLookups
77
- ? 'Disables all unique constraint lookups (use filters instead)'
78
- : 'Disables non-primary-key unique constraint lookups to reduce API surface',
79
- schema: {
80
- entityBehavior: {
81
- pgResourceUnique: {
82
- // Use 'override' phase instead of 'inferred' - override runs AFTER
83
- // the behavior multiplication/preferences system processes behaviors,
84
- // so it has the final say on what behaviors are enabled/disabled.
85
- override: {
86
- provides: ['uniqueLookupControl'],
87
- callback(behavior, [_resource, unique]) {
88
- if (disableAllUniqueLookups) {
89
- // Disable ALL unique QUERY lookups - users must use filters
90
- // But KEEP primary key mutations (updateX, deleteX)
91
- if (unique.isPrimary) {
92
- // Primary key: only disable query lookups, keep mutations
93
- return [behavior, '-single'];
94
- }
95
- // Non-primary-key: disable everything (queries and mutations)
96
- return [
97
- behavior,
98
- '-single',
99
- '-constraint:resource:update',
100
- '-constraint:resource:delete',
101
- ];
102
- }
103
- // Only allow primary key lookups (both queries and mutations)
104
- if (!unique.isPrimary) {
105
- // Disable non-primary-key unique constraint lookups for queries and mutations
106
- return [
107
- behavior,
108
- '-single',
109
- '-constraint:resource:update',
110
- '-constraint:resource:delete',
111
- ];
112
- }
113
- return behavior;
114
- },
115
- },
116
- },
117
- },
118
- },
119
- };
120
- }
121
- // Default plugin instance (primary key only)
122
- export const PrimaryKeyOnlyPlugin = createUniqueLookupPlugin({
123
- disableAllUniqueLookups: false,
124
- });
125
- // Plugin that disables ALL unique lookups
126
- export const NoUniqueLookupPlugin = createUniqueLookupPlugin({
127
- disableAllUniqueLookups: true,
128
- });
129
- /**
130
- * Preset that keeps only primary key lookups.
131
- * Use this in your main preset's `extends` array.
132
- */
133
- export const PrimaryKeyOnlyPreset = {
134
- plugins: [PrimaryKeyOnlyPlugin],
135
- };
136
- /**
137
- * Preset that disables ALL unique lookups (including primary keys).
138
- * Users must use collection queries with filters instead.
139
- * Use this in your main preset's `extends` array.
140
- */
141
- export const NoUniqueLookupPreset = {
142
- plugins: [NoUniqueLookupPlugin],
143
- };
@@ -1,7 +0,0 @@
1
- import type { GraphileConfig } from 'graphile-config';
2
- export declare const ConflictDetectorPlugin: GraphileConfig.Plugin;
3
- /**
4
- * Preset that includes the conflict detector plugin.
5
- */
6
- export declare const ConflictDetectorPreset: GraphileConfig.Preset;
7
- export default ConflictDetectorPlugin;
@@ -1,70 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ConflictDetectorPreset = exports.ConflictDetectorPlugin = void 0;
4
- exports.ConflictDetectorPlugin = {
5
- name: 'ConflictDetectorPlugin',
6
- version: '1.0.0',
7
- schema: {
8
- hooks: {
9
- build(build) {
10
- // Track codecs by their GraphQL name to detect conflicts
11
- const codecsByName = new Map();
12
- // Get configured schemas from pgServices to only check relevant codecs
13
- const configuredSchemas = new Set();
14
- const pgServices = build.resolvedPreset?.pgServices ?? [];
15
- for (const service of pgServices) {
16
- for (const schema of service.schemas ?? ['public']) {
17
- configuredSchemas.add(schema);
18
- }
19
- }
20
- // Iterate through all codecs to find tables
21
- for (const codec of Object.values(build.input.pgRegistry.pgCodecs)) {
22
- // Skip non-table codecs (those without attributes or anonymous ones)
23
- if (!codec.attributes || codec.isAnonymous)
24
- continue;
25
- // Get the schema name from the codec's extensions
26
- const pgExtensions = codec.extensions?.pg;
27
- const schemaName = pgExtensions?.schemaName || 'unknown';
28
- const tableName = codec.name;
29
- // Skip codecs from schemas not in the configured list
30
- if (configuredSchemas.size > 0 && !configuredSchemas.has(schemaName)) {
31
- continue;
32
- }
33
- // Get the GraphQL name that would be generated
34
- const graphqlName = build.inflection.tableType(codec);
35
- const info = {
36
- name: graphqlName,
37
- schemaName,
38
- tableName,
39
- };
40
- if (!codecsByName.has(graphqlName)) {
41
- codecsByName.set(graphqlName, []);
42
- }
43
- codecsByName.get(graphqlName).push(info);
44
- }
45
- // Check for conflicts and log warnings
46
- for (const [graphqlName, codecs] of codecsByName) {
47
- if (codecs.length > 1) {
48
- const locations = codecs
49
- .map((c) => `${c.schemaName}.${c.tableName}`)
50
- .join(', ');
51
- console.warn(`\nNAMING CONFLICT DETECTED: GraphQL type "${graphqlName}" would be generated from multiple tables:\n` +
52
- ` Tables: ${locations}\n` +
53
- ` Resolution options:\n` +
54
- ` 1. Add @name smart tag to one table: COMMENT ON TABLE schema.table IS E'@name UniqueTypeName';\n` +
55
- ` 2. Rename one of the tables in the database\n` +
56
- ` 3. Exclude one table from the schema using @omit smart tag\n`);
57
- }
58
- }
59
- return build;
60
- },
61
- },
62
- },
63
- };
64
- /**
65
- * Preset that includes the conflict detector plugin.
66
- */
67
- exports.ConflictDetectorPreset = {
68
- plugins: [exports.ConflictDetectorPlugin],
69
- };
70
- exports.default = exports.ConflictDetectorPlugin;
@@ -1,9 +0,0 @@
1
- import type { GraphileConfig } from 'graphile-config';
2
- export declare const InflektPlugin: GraphileConfig.Plugin;
3
- /**
4
- * Preset that includes the inflekt-based inflector plugin.
5
- * Use this in your main preset's `extends` array.
6
- */
7
- export declare const InflektPreset: GraphileConfig.Preset;
8
- export declare const CustomInflectorPlugin: GraphileConfig.Plugin;
9
- export declare const CustomInflectorPreset: GraphileConfig.Preset;
@@ -1,385 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.CustomInflectorPreset = exports.CustomInflectorPlugin = exports.InflektPreset = exports.InflektPlugin = void 0;
4
- const inflekt_1 = require("inflekt");
5
- /**
6
- * Custom inflector plugin for Constructive using the inflekt library.
7
- *
8
- * This plugin provides inflection rules based on the inflekt package from dev-utils.
9
- * It gives us full control over naming conventions and handles Latin plural suffixes
10
- * correctly (e.g., "schemata" -> "schema" instead of "schematum").
11
- *
12
- * Key features:
13
- * - Uses inflekt for pluralization/singularization with PostGraphile-compatible Latin handling
14
- * - Simplifies field names (allUsers -> users, postsByAuthorId -> posts)
15
- * - Customizable opposite name mappings for relations
16
- */
17
- /**
18
- * Custom opposite name mappings for relations.
19
- * For example, if you have a `parent_id` column, this determines
20
- * what the reverse relation should be called.
21
- *
22
- * Add your own mappings here as needed.
23
- */
24
- const CUSTOM_OPPOSITES = {
25
- parent: 'child',
26
- child: 'parent',
27
- author: 'authored',
28
- editor: 'edited',
29
- reviewer: 'reviewed',
30
- owner: 'owned',
31
- creator: 'created',
32
- updater: 'updated',
33
- };
34
- /**
35
- * Extract base name from attribute names like "author_id" -> "author"
36
- */
37
- function getBaseName(attributeName) {
38
- const matches = attributeName.match(/^(.+?)(_row_id|_id|_uuid|_fk|_pk|RowId|Id|Uuid|UUID|Fk|Pk)$/);
39
- if (matches) {
40
- return matches[1];
41
- }
42
- return null;
43
- }
44
- /**
45
- * Check if a base name matches another name (singularized)
46
- */
47
- function baseNameMatches(baseName, otherName) {
48
- const singularizedName = (0, inflekt_1.singularize)(otherName);
49
- return (0, inflekt_1.camelize)(baseName, true) === (0, inflekt_1.camelize)(singularizedName, true);
50
- }
51
- /**
52
- * Get the opposite name for a relation base name
53
- */
54
- function getOppositeBaseName(baseName) {
55
- return CUSTOM_OPPOSITES[baseName] || null;
56
- }
57
- /**
58
- * Returns true if array1 and array2 have the same length and values
59
- */
60
- function arraysMatch(array1, array2, comparator = (v1, v2) => v1 === v2) {
61
- if (array1 === array2)
62
- return true;
63
- const l = array1.length;
64
- if (l !== array2.length)
65
- return false;
66
- for (let i = 0; i < l; i++) {
67
- if (!comparator(array1[i], array2[i]))
68
- return false;
69
- }
70
- return true;
71
- }
72
- exports.InflektPlugin = {
73
- name: 'InflektPlugin',
74
- version: '1.0.0',
75
- inflection: {
76
- replace: {
77
- /**
78
- * Remove schema prefixes from all schemas.
79
- *
80
- * WHY THIS EXISTS:
81
- * PostGraphile v5's default `_schemaPrefix` inflector only removes the prefix
82
- * for the FIRST schema in the pgServices.schemas array. All other schemas get
83
- * prefixed with their schema name (e.g., "services_public_api" -> "servicesPublicApi").
84
- *
85
- * This is problematic for multi-schema setups where you want clean, consistent
86
- * naming across all schemas.
87
- *
88
- * SOURCE CODE REFERENCE:
89
- * https://github.com/graphile/crystal/blob/924b2515c6bd30e5905ac1419a25244b40c8bb4d/graphile-build/graphile-build-pg/src/plugins/PgTablesPlugin.ts#L261-L271
90
- *
91
- * The relevant v5 code:
92
- * ```typescript
93
- * _schemaPrefix(options, { pgNamespace, serviceName }) {
94
- * const pgService = options.pgServices?.find((db) => db.name === serviceName);
95
- * const databasePrefix = serviceName === "main" ? "" : `${serviceName}_`;
96
- * const schemaPrefix =
97
- * pgNamespace.nspname === pgService?.schemas?.[0] // <-- Only first schema!
98
- * ? ""
99
- * : `${pgNamespace.nspname}_`;
100
- * return `${databasePrefix}${schemaPrefix}`;
101
- * }
102
- * ```
103
- *
104
- * OUR FIX:
105
- * We override this to always return an empty string, giving clean names for
106
- * all schemas. Use the ConflictDetectorPlugin to detect naming conflicts.
107
- *
108
- * WARNING: This may cause naming conflicts if you have tables with the
109
- * same name in different schemas. Use @name smart tags to disambiguate.
110
- */
111
- _schemaPrefix(_previous, _options, _details) {
112
- return '';
113
- },
114
- /**
115
- * Keep `id` columns as `id` instead of renaming to `rowId`.
116
- *
117
- * WHY THIS EXISTS:
118
- * PostGraphile v5's default `_attributeName` inflector renames any column
119
- * named "id" to "row_id" to avoid conflicts with the Relay Global Object
120
- * Identification spec's `id` field. Since we don't use Relay/Node (we use
121
- * UUIDs), there's no conflict to avoid.
122
- *
123
- * NOTE: Disabling NodePlugin does NOT fix this! The renaming happens in
124
- * PgAttributesPlugin which is a core plugin we need for basic column
125
- * functionality.
126
- *
127
- * SOURCE CODE REFERENCE:
128
- * https://github.com/graphile/crystal/blob/924b2515c6bd30e5905ac1419a25244b40c8bb4d/graphile-build/graphile-build-pg/src/plugins/PgAttributesPlugin.ts#L289-L298
129
- *
130
- * The relevant v5 code:
131
- * ```typescript
132
- * _attributeName(options, { attributeName, codec, skipRowId }) {
133
- * const attribute = codec.attributes[attributeName];
134
- * const name = attribute.extensions?.tags?.name || attributeName;
135
- * // Avoid conflict with 'id' field used for Relay.
136
- * const nonconflictName =
137
- * !skipRowId && name.toLowerCase() === "id" && !codec.isAnonymous
138
- * ? "row_id" // <-- This renames id to row_id!
139
- * : name;
140
- * return this.coerceToGraphQLName(nonconflictName);
141
- * }
142
- * ```
143
- *
144
- * OUR FIX:
145
- * We override this to always use the original attribute name, never
146
- * renaming `id` to `row_id`. Since we use UUIDs and don't use Relay,
147
- * there's no naming conflict.
148
- */
149
- _attributeName(_previous, _options, details) {
150
- const attribute = details.codec.attributes[details.attributeName];
151
- const name = attribute?.extensions?.tags?.name || details.attributeName;
152
- return this.coerceToGraphQLName(name);
153
- },
154
- /**
155
- * Fix capitalized plurals (e.g., "Table1S" -> "Table1s")
156
- */
157
- camelCase(previous, _preset, str) {
158
- const original = previous(str);
159
- return (0, inflekt_1.fixCapitalisedPlural)(original);
160
- },
161
- upperCamelCase(previous, _preset, str) {
162
- const original = previous(str);
163
- return (0, inflekt_1.fixCapitalisedPlural)(original);
164
- },
165
- /**
166
- * Use inflekt's singularize/pluralize which only changes the last word
167
- */
168
- pluralize(_previous, _preset, str) {
169
- return (0, inflekt_1.pluralizeLast)(str);
170
- },
171
- singularize(_previous, _preset, str) {
172
- return (0, inflekt_1.singularizeLast)(str);
173
- },
174
- /**
175
- * Simplify root query connection fields (allUsers -> users)
176
- */
177
- allRowsConnection(_previous, _options, resource) {
178
- const resourceName = this._singularizedResourceName(resource);
179
- return (0, inflekt_1.camelize)((0, inflekt_1.distinctPluralize)(resourceName), true);
180
- },
181
- /**
182
- * Simplify root query list fields
183
- */
184
- allRowsList(_previous, _options, resource) {
185
- const resourceName = this._singularizedResourceName(resource);
186
- return (0, inflekt_1.camelize)((0, inflekt_1.distinctPluralize)(resourceName), true) + 'List';
187
- },
188
- /**
189
- * Simplify single relation field names (userByAuthorId -> author)
190
- */
191
- singleRelation(previous, _options, details) {
192
- const { registry, codec, relationName } = details;
193
- const relation = registry.pgRelations[codec.name]?.[relationName];
194
- if (typeof relation.extensions?.tags?.fieldName === 'string') {
195
- return relation.extensions.tags.fieldName;
196
- }
197
- // Try to extract base name from the local attribute
198
- if (relation.localAttributes.length === 1) {
199
- const attributeName = relation.localAttributes[0];
200
- const baseName = getBaseName(attributeName);
201
- if (baseName) {
202
- return (0, inflekt_1.camelize)(baseName, true);
203
- }
204
- }
205
- // Fall back to the remote resource name
206
- const foreignPk = relation.remoteResource.uniques.find((u) => u.isPrimary);
207
- if (foreignPk &&
208
- arraysMatch(foreignPk.attributes, relation.remoteAttributes)) {
209
- return (0, inflekt_1.camelize)(this._singularizedCodecName(relation.remoteResource.codec), true);
210
- }
211
- return previous(details);
212
- },
213
- /**
214
- * Simplify backwards single relation field names
215
- */
216
- singleRelationBackwards(previous, _options, details) {
217
- const { registry, codec, relationName } = details;
218
- const relation = registry.pgRelations[codec.name]?.[relationName];
219
- if (typeof relation.extensions?.tags?.foreignSingleFieldName === 'string') {
220
- return relation.extensions.tags.foreignSingleFieldName;
221
- }
222
- if (typeof relation.extensions?.tags?.foreignFieldName === 'string') {
223
- return relation.extensions.tags.foreignFieldName;
224
- }
225
- // Try to extract base name from the remote attribute
226
- if (relation.remoteAttributes.length === 1) {
227
- const attributeName = relation.remoteAttributes[0];
228
- const baseName = getBaseName(attributeName);
229
- if (baseName) {
230
- const oppositeBaseName = getOppositeBaseName(baseName);
231
- if (oppositeBaseName) {
232
- return (0, inflekt_1.camelize)(`${oppositeBaseName}_${this._singularizedCodecName(relation.remoteResource.codec)}`, true);
233
- }
234
- if (baseNameMatches(baseName, codec.name)) {
235
- return (0, inflekt_1.camelize)(this._singularizedCodecName(relation.remoteResource.codec), true);
236
- }
237
- }
238
- }
239
- return previous(details);
240
- },
241
- /**
242
- * Simplify many relation field names (postsByAuthorId -> posts)
243
- */
244
- _manyRelation(previous, _options, details) {
245
- const { registry, codec, relationName } = details;
246
- const relation = registry.pgRelations[codec.name]?.[relationName];
247
- const baseOverride = relation.extensions?.tags.foreignFieldName;
248
- if (typeof baseOverride === 'string') {
249
- return baseOverride;
250
- }
251
- // Try to extract base name from the remote attribute
252
- if (relation.remoteAttributes.length === 1) {
253
- const attributeName = relation.remoteAttributes[0];
254
- const baseName = getBaseName(attributeName);
255
- if (baseName) {
256
- const oppositeBaseName = getOppositeBaseName(baseName);
257
- if (oppositeBaseName) {
258
- return (0, inflekt_1.camelize)(`${oppositeBaseName}_${(0, inflekt_1.distinctPluralize)(this._singularizedCodecName(relation.remoteResource.codec))}`, true);
259
- }
260
- if (baseNameMatches(baseName, codec.name)) {
261
- return (0, inflekt_1.camelize)((0, inflekt_1.distinctPluralize)(this._singularizedCodecName(relation.remoteResource.codec)), true);
262
- }
263
- }
264
- }
265
- // Fall back to pluralized remote resource name
266
- const pk = relation.remoteResource.uniques.find((u) => u.isPrimary);
267
- if (pk && arraysMatch(pk.attributes, relation.remoteAttributes)) {
268
- return (0, inflekt_1.camelize)((0, inflekt_1.distinctPluralize)(this._singularizedCodecName(relation.remoteResource.codec)), true);
269
- }
270
- return previous(details);
271
- },
272
- /**
273
- * Simplify many-to-many relation field names with conflict detection.
274
- *
275
- * Default pg-many-to-many naming: tagsByPostTagPostIdAndTagId
276
- * Our simplified naming: tags
277
- *
278
- * Falls back to verbose naming if:
279
- * - Smart tag override exists (manyToManyFieldName)
280
- * - There's a direct relation to the same target table (would conflict)
281
- * - There are multiple many-to-many relations to the same target table
282
- */
283
- _manyToManyRelation(previous, _options, details) {
284
- const { leftTable, rightTable, junctionTable, rightRelationName } = details;
285
- const junctionRightRelation = junctionTable.getRelation(rightRelationName);
286
- const baseOverride = junctionRightRelation.extensions?.tags?.manyToManyFieldName;
287
- if (typeof baseOverride === 'string') {
288
- return baseOverride;
289
- }
290
- const simpleName = (0, inflekt_1.camelize)((0, inflekt_1.distinctPluralize)(this._singularizedCodecName(rightTable.codec)), true);
291
- const leftRelations = leftTable.getRelations();
292
- let hasDirectRelation = false;
293
- let manyToManyCount = 0;
294
- for (const [_relName, rel] of Object.entries(leftRelations)) {
295
- if (rel.remoteResource?.codec?.name === rightTable.codec.name) {
296
- if (!rel.isReferencee) {
297
- hasDirectRelation = true;
298
- }
299
- }
300
- if (rel.isReferencee &&
301
- rel.remoteResource?.codec?.name !== rightTable.codec.name) {
302
- const junctionRelations = rel.remoteResource?.getRelations?.() || {};
303
- for (const [_jRelName, jRel] of Object.entries(junctionRelations)) {
304
- if (!jRel.isReferencee &&
305
- jRel.remoteResource?.codec?.name === rightTable.codec.name) {
306
- manyToManyCount++;
307
- }
308
- }
309
- }
310
- }
311
- if (hasDirectRelation || manyToManyCount > 1) {
312
- return previous(details);
313
- }
314
- return simpleName;
315
- },
316
- /**
317
- * Shorten primary key lookups (userById -> user)
318
- */
319
- rowByUnique(previous, _options, details) {
320
- const { unique, resource } = details;
321
- if (typeof unique.extensions?.tags?.fieldName === 'string') {
322
- return unique.extensions?.tags?.fieldName;
323
- }
324
- if (unique.isPrimary) {
325
- return (0, inflekt_1.camelize)(this._singularizedCodecName(resource.codec), true);
326
- }
327
- return previous(details);
328
- },
329
- /**
330
- * Shorten update mutation names
331
- */
332
- updateByKeysField(previous, _options, details) {
333
- const { resource, unique } = details;
334
- if (typeof unique.extensions?.tags.updateFieldName === 'string') {
335
- return unique.extensions.tags.updateFieldName;
336
- }
337
- if (unique.isPrimary) {
338
- return (0, inflekt_1.camelize)(`update_${this._singularizedCodecName(resource.codec)}`, true);
339
- }
340
- return previous(details);
341
- },
342
- /**
343
- * Shorten delete mutation names
344
- */
345
- deleteByKeysField(previous, _options, details) {
346
- const { resource, unique } = details;
347
- if (typeof unique.extensions?.tags.deleteFieldName === 'string') {
348
- return unique.extensions.tags.deleteFieldName;
349
- }
350
- if (unique.isPrimary) {
351
- return (0, inflekt_1.camelize)(`delete_${this._singularizedCodecName(resource.codec)}`, true);
352
- }
353
- return previous(details);
354
- },
355
- /**
356
- * Uppercase enum values to match GraphQL CONSTANT_CASE convention.
357
- *
358
- * WHY THIS EXISTS:
359
- * In PostGraphile v4, custom PostgreSQL enum values (e.g., 'app', 'core', 'module')
360
- * were automatically uppercased to CONSTANT_CASE ('APP', 'CORE', 'MODULE').
361
- * In PostGraphile v5, the default `enumValue` inflector preserves the original
362
- * PostgreSQL casing via `coerceToGraphQLName(value)`, resulting in lowercase
363
- * enum values in the GraphQL schema.
364
- *
365
- * OUR FIX:
366
- * We call the previous inflector to retain all special character handling
367
- * (asterisks, symbols, etc.), then uppercase the result to restore v4 behavior.
368
- */
369
- enumValue(previous, _options, value, codec) {
370
- const result = previous(value, codec);
371
- return result.toUpperCase();
372
- },
373
- },
374
- },
375
- };
376
- /**
377
- * Preset that includes the inflekt-based inflector plugin.
378
- * Use this in your main preset's `extends` array.
379
- */
380
- exports.InflektPreset = {
381
- plugins: [exports.InflektPlugin],
382
- };
383
- // Re-export for backwards compatibility
384
- exports.CustomInflectorPlugin = exports.InflektPlugin;
385
- exports.CustomInflectorPreset = exports.InflektPreset;
@@ -1,60 +0,0 @@
1
- import type { GraphileConfig } from 'graphile-config';
2
- /**
3
- * EnableAllFilterColumnsPlugin - Enables filtering on ALL columns, not just indexed ones.
4
- *
5
- * WHY THIS EXISTS:
6
- * PostGraphile v5's `PgIndexBehaviorsPlugin` restricts filtering to only indexed columns
7
- * by default. This is a performance optimization - filtering on non-indexed columns can
8
- * cause slow table scans. However, for development and flexibility, we want to allow
9
- * filtering on all columns and let developers/DBAs decide which columns need indexes.
10
- *
11
- * SOURCE CODE REFERENCE:
12
- * PgIndexBehaviorsPlugin marks non-indexed columns with `extensions.isIndexed = false`
13
- * and then adds `-filterBy` behavior to remove them from filters:
14
- * https://github.com/graphile/crystal/blob/924b2515c6bd30e5905ac1419a25244b40c8bb4d/graphile-build/graphile-build-pg/src/plugins/PgIndexBehaviorsPlugin.ts
15
- *
16
- * The relevant v5 code (from PgIndexBehaviorsPlugin):
17
- * ```typescript
18
- * entityBehavior: {
19
- * pgCodecAttribute: {
20
- * inferred: {
21
- * after: ["inferred"],
22
- * provides: ["postInferred"],
23
- * callback(behavior, [codec, attributeName]) {
24
- * const newBehavior = [behavior];
25
- * const attr = codec.attributes[attributeName];
26
- * if (attr.extensions?.isIndexed === false) {
27
- * newBehavior.push("-filterBy", "-orderBy"); // <-- This removes filterBy!
28
- * }
29
- * return newBehavior;
30
- * },
31
- * },
32
- * },
33
- * },
34
- * ```
35
- *
36
- * OUR FIX:
37
- * We add a behavior callback that runs AFTER PgIndexBehaviorsPlugin's "postInferred" phase
38
- * and adds `+attribute:filterBy` back to ALL columns, regardless of index status.
39
- *
40
- * This means:
41
- * - All columns will appear in the connection filter's filter argument
42
- * - Developers can filter by any column
43
- * - It's the developer's/DBA's responsibility to add indexes for frequently filtered columns
44
- *
45
- * PERFORMANCE WARNING:
46
- * Filtering on non-indexed columns can cause full table scans, which may be slow on large
47
- * tables. Monitor your query performance and add indexes as needed. You can check which
48
- * columns are indexed by querying pg_indexes or using EXPLAIN ANALYZE on your queries.
49
- *
50
- * To identify non-indexed columns being filtered, you can:
51
- * 1. Enable slow query logging in PostgreSQL (log_min_duration_statement)
52
- * 2. Use EXPLAIN ANALYZE on queries to see if they're doing sequential scans
53
- * 3. Check pg_stat_user_tables for seq_scan counts
54
- */
55
- export declare const EnableAllFilterColumnsPlugin: GraphileConfig.Plugin;
56
- /**
57
- * Preset that includes the EnableAllFilterColumnsPlugin.
58
- * Add this to your main preset's `extends` array.
59
- */
60
- export declare const EnableAllFilterColumnsPreset: GraphileConfig.Preset;