graphile-settings 3.1.1 → 4.0.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 (102) hide show
  1. package/README.md +170 -85
  2. package/esm/index.d.ts +37 -0
  3. package/esm/index.js +54 -103
  4. package/esm/plugins/conflict-detector.d.ts +7 -0
  5. package/esm/plugins/conflict-detector.js +67 -0
  6. package/esm/plugins/custom-inflector.d.ts +9 -0
  7. package/esm/plugins/custom-inflector.js +382 -0
  8. package/esm/plugins/enable-all-filter-columns.d.ts +60 -0
  9. package/esm/plugins/enable-all-filter-columns.js +85 -0
  10. package/esm/plugins/index.d.ts +19 -0
  11. package/esm/plugins/index.js +26 -0
  12. package/esm/plugins/inflector-logger.d.ts +7 -0
  13. package/esm/plugins/inflector-logger.js +215 -0
  14. package/esm/plugins/many-to-many-preset.d.ts +62 -0
  15. package/esm/plugins/many-to-many-preset.js +86 -0
  16. package/esm/plugins/meta-schema/cache.d.ts +4 -0
  17. package/esm/plugins/meta-schema/cache.js +7 -0
  18. package/esm/plugins/meta-schema/constraint-meta-builders.d.ts +13 -0
  19. package/esm/plugins/meta-schema/constraint-meta-builders.js +51 -0
  20. package/esm/plugins/meta-schema/graphql-meta-field.d.ts +4 -0
  21. package/esm/plugins/meta-schema/graphql-meta-field.js +201 -0
  22. package/esm/plugins/meta-schema/inflection-utils.d.ts +4 -0
  23. package/esm/plugins/meta-schema/inflection-utils.js +20 -0
  24. package/esm/plugins/meta-schema/name-meta-builders.d.ts +4 -0
  25. package/esm/plugins/meta-schema/name-meta-builders.js +38 -0
  26. package/esm/plugins/meta-schema/plugin.d.ts +2 -0
  27. package/esm/plugins/meta-schema/plugin.js +23 -0
  28. package/esm/plugins/meta-schema/relation-meta-builders.d.ts +8 -0
  29. package/esm/plugins/meta-schema/relation-meta-builders.js +115 -0
  30. package/esm/plugins/meta-schema/table-meta-builder.d.ts +2 -0
  31. package/esm/plugins/meta-schema/table-meta-builder.js +69 -0
  32. package/esm/plugins/meta-schema/table-meta-context.d.ts +13 -0
  33. package/esm/plugins/meta-schema/table-meta-context.js +11 -0
  34. package/esm/plugins/meta-schema/table-resource-utils.d.ts +12 -0
  35. package/esm/plugins/meta-schema/table-resource-utils.js +50 -0
  36. package/esm/plugins/meta-schema/type-mappings.d.ts +3 -0
  37. package/esm/plugins/meta-schema/type-mappings.js +75 -0
  38. package/esm/plugins/meta-schema/types.d.ts +206 -0
  39. package/esm/plugins/meta-schema/types.js +1 -0
  40. package/esm/plugins/meta-schema.d.ts +19 -0
  41. package/esm/plugins/meta-schema.js +20 -0
  42. package/esm/plugins/minimal-preset.d.ts +7 -0
  43. package/esm/plugins/minimal-preset.js +42 -0
  44. package/esm/plugins/pg-type-mappings.d.ts +41 -0
  45. package/esm/plugins/pg-type-mappings.js +122 -0
  46. package/esm/plugins/primary-key-only.d.ts +96 -0
  47. package/esm/plugins/primary-key-only.js +143 -0
  48. package/esm/presets/constructive-preset.d.ts +40 -0
  49. package/esm/presets/constructive-preset.js +137 -0
  50. package/esm/presets/index.d.ts +7 -0
  51. package/esm/presets/index.js +7 -0
  52. package/index.d.ts +37 -3
  53. package/index.js +56 -129
  54. package/package.json +16 -22
  55. package/plugins/conflict-detector.d.ts +7 -0
  56. package/plugins/conflict-detector.js +70 -0
  57. package/plugins/custom-inflector.d.ts +9 -0
  58. package/plugins/custom-inflector.js +385 -0
  59. package/plugins/enable-all-filter-columns.d.ts +60 -0
  60. package/plugins/enable-all-filter-columns.js +88 -0
  61. package/plugins/index.d.ts +19 -0
  62. package/plugins/index.js +56 -0
  63. package/plugins/inflector-logger.d.ts +7 -0
  64. package/plugins/inflector-logger.js +218 -0
  65. package/plugins/many-to-many-preset.d.ts +62 -0
  66. package/plugins/many-to-many-preset.js +89 -0
  67. package/plugins/meta-schema/cache.d.ts +4 -0
  68. package/plugins/meta-schema/cache.js +12 -0
  69. package/plugins/meta-schema/constraint-meta-builders.d.ts +13 -0
  70. package/plugins/meta-schema/constraint-meta-builders.js +58 -0
  71. package/plugins/meta-schema/graphql-meta-field.d.ts +4 -0
  72. package/plugins/meta-schema/graphql-meta-field.js +204 -0
  73. package/plugins/meta-schema/inflection-utils.d.ts +4 -0
  74. package/plugins/meta-schema/inflection-utils.js +25 -0
  75. package/plugins/meta-schema/name-meta-builders.d.ts +4 -0
  76. package/plugins/meta-schema/name-meta-builders.js +43 -0
  77. package/plugins/meta-schema/plugin.d.ts +2 -0
  78. package/plugins/meta-schema/plugin.js +26 -0
  79. package/plugins/meta-schema/relation-meta-builders.d.ts +8 -0
  80. package/plugins/meta-schema/relation-meta-builders.js +120 -0
  81. package/plugins/meta-schema/table-meta-builder.d.ts +2 -0
  82. package/plugins/meta-schema/table-meta-builder.js +72 -0
  83. package/plugins/meta-schema/table-meta-context.d.ts +13 -0
  84. package/plugins/meta-schema/table-meta-context.js +15 -0
  85. package/plugins/meta-schema/table-resource-utils.d.ts +12 -0
  86. package/plugins/meta-schema/table-resource-utils.js +60 -0
  87. package/plugins/meta-schema/type-mappings.d.ts +3 -0
  88. package/plugins/meta-schema/type-mappings.js +79 -0
  89. package/plugins/meta-schema/types.d.ts +206 -0
  90. package/plugins/meta-schema/types.js +2 -0
  91. package/plugins/meta-schema.d.ts +19 -0
  92. package/plugins/meta-schema.js +20 -0
  93. package/plugins/minimal-preset.d.ts +7 -0
  94. package/plugins/minimal-preset.js +45 -0
  95. package/plugins/pg-type-mappings.d.ts +41 -0
  96. package/plugins/pg-type-mappings.js +128 -0
  97. package/plugins/primary-key-only.d.ts +96 -0
  98. package/plugins/primary-key-only.js +147 -0
  99. package/presets/constructive-preset.d.ts +40 -0
  100. package/presets/constructive-preset.js +140 -0
  101. package/presets/index.d.ts +7 -0
  102. package/presets/index.js +11 -0
@@ -0,0 +1,20 @@
1
+ /**
2
+ * PostGraphile v5 Meta Schema Plugin
3
+ *
4
+ * Exposes a `_meta` GraphQL query that provides metadata about tables, fields,
5
+ * constraints, indexes, and relations for code generation tooling.
6
+ */
7
+ import { cachedTablesMeta } from './meta-schema/cache';
8
+ import { MetaSchemaPlugin } from './meta-schema/plugin';
9
+ import { buildFieldMeta, pgTypeToGqlType } from './meta-schema/type-mappings';
10
+ export { MetaSchemaPlugin };
11
+ export const MetaSchemaPreset = {
12
+ plugins: [MetaSchemaPlugin],
13
+ };
14
+ /** @internal Exported for testing only */
15
+ export { pgTypeToGqlType as _pgTypeToGqlType };
16
+ /** @internal Exported for testing only */
17
+ export { buildFieldMeta as _buildFieldMeta };
18
+ /** @internal Exported for testing only */
19
+ export { cachedTablesMeta as _cachedTablesMeta };
20
+ export default MetaSchemaPlugin;
@@ -0,0 +1,7 @@
1
+ import type { GraphileConfig } from 'graphile-config';
2
+ /**
3
+ * Minimal preset with all the PostgreSQL functionality but without Node/Relay features.
4
+ * This keeps `id` columns as `id` (no renaming to `rowId`).
5
+ */
6
+ export declare const MinimalPreset: GraphileConfig.Preset;
7
+ export default MinimalPreset;
@@ -0,0 +1,42 @@
1
+ import { defaultPreset as graphileBuildPreset } from 'graphile-build';
2
+ import { defaultPreset as graphileBuildPgPreset } from 'graphile-build-pg';
3
+ /**
4
+ * Minimal PostGraphile v5 preset without Node/Relay features.
5
+ *
6
+ * This preset builds a clean GraphQL API from PostgreSQL without:
7
+ * - Global Node ID (Relay spec) - so `id` columns stay as `id`
8
+ * - Schema prefixing for naming conflicts
9
+ *
10
+ * We use the default presets from graphile-build and graphile-build-pg
11
+ * and disable all Node-related plugins.
12
+ */
13
+ /**
14
+ * List of Node/Relay-related plugins to disable.
15
+ * These implement the Relay Global Object Identification spec which:
16
+ * - Adds a global `id` field to types
17
+ * - Renames actual `id` columns to `rowId`
18
+ * - Adds Node interface and nodeId lookups
19
+ *
20
+ * Since we use UUIDs, we don't need any of this.
21
+ */
22
+ const NODE_PLUGINS_TO_DISABLE = [
23
+ // Core Node plugins from graphile-build
24
+ 'NodePlugin',
25
+ 'AddNodeInterfaceToSuitableTypesPlugin',
26
+ 'NodeIdCodecBase64JSONPlugin',
27
+ 'NodeIdCodecPipeStringPlugin',
28
+ 'RegisterQueryNodePlugin',
29
+ 'NodeAccessorPlugin',
30
+ // PG-specific Node plugins from graphile-build-pg
31
+ 'PgNodeIdAttributesPlugin',
32
+ 'PgTableNodePlugin',
33
+ ];
34
+ /**
35
+ * Minimal preset with all the PostgreSQL functionality but without Node/Relay features.
36
+ * This keeps `id` columns as `id` (no renaming to `rowId`).
37
+ */
38
+ export const MinimalPreset = {
39
+ extends: [graphileBuildPreset, graphileBuildPgPreset],
40
+ disablePlugins: NODE_PLUGINS_TO_DISABLE,
41
+ };
42
+ export default MinimalPreset;
@@ -0,0 +1,41 @@
1
+ import type { GraphileConfig } from 'graphile-config';
2
+ /**
3
+ * Type mapping configuration for custom PostgreSQL types.
4
+ */
5
+ export interface TypeMapping {
6
+ /** PostgreSQL type name */
7
+ name: string;
8
+ /** PostgreSQL schema/namespace name */
9
+ namespaceName: string;
10
+ /** GraphQL type to map to */
11
+ type: 'String';
12
+ }
13
+ /**
14
+ * Plugin that maps custom PostgreSQL types to GraphQL scalar types.
15
+ *
16
+ * This is useful for domain types or composite types that should be
17
+ * represented as simple scalars (String, JSON) in the GraphQL API.
18
+ *
19
+ * For example, if you have:
20
+ * CREATE DOMAIN email AS text;
21
+ * CREATE TYPE url AS (value text);
22
+ *
23
+ * This plugin will map them to GraphQL String type instead of creating
24
+ * complex object types.
25
+ *
26
+ * The plugin handles both:
27
+ * 1. Domain types (simple aliases) - maps directly to the target scalar
28
+ * 2. Composite types - extracts the first field's value when converting from PG
29
+ */
30
+ export declare const PgTypeMappingsPlugin: GraphileConfig.Plugin;
31
+ /**
32
+ * Preset that includes the PG type mappings plugin.
33
+ *
34
+ * This preset maps common custom PostgreSQL types to GraphQL scalars:
35
+ * - email -> String
36
+ * - hostname -> String
37
+ * - url -> String
38
+ * - origin -> String
39
+ */
40
+ export declare const PgTypeMappingsPreset: GraphileConfig.Preset;
41
+ export default PgTypeMappingsPlugin;
@@ -0,0 +1,122 @@
1
+ import { GraphQLString } from 'graphql';
2
+ import sql from 'pg-sql2';
3
+ /**
4
+ * Default type mappings for common custom PostgreSQL types.
5
+ * These are typically domain types or composite types that should be
6
+ * represented as simple scalars in GraphQL.
7
+ */
8
+ const DEFAULT_MAPPINGS = [
9
+ { name: 'email', namespaceName: 'public', type: 'String' },
10
+ { name: 'hostname', namespaceName: 'public', type: 'String' },
11
+ { name: 'origin', namespaceName: 'public', type: 'String' },
12
+ { name: 'url', namespaceName: 'public', type: 'String' },
13
+ ];
14
+ /**
15
+ * Plugin that maps custom PostgreSQL types to GraphQL scalar types.
16
+ *
17
+ * This is useful for domain types or composite types that should be
18
+ * represented as simple scalars (String, JSON) in the GraphQL API.
19
+ *
20
+ * For example, if you have:
21
+ * CREATE DOMAIN email AS text;
22
+ * CREATE TYPE url AS (value text);
23
+ *
24
+ * This plugin will map them to GraphQL String type instead of creating
25
+ * complex object types.
26
+ *
27
+ * The plugin handles both:
28
+ * 1. Domain types (simple aliases) - maps directly to the target scalar
29
+ * 2. Composite types - extracts the first field's value when converting from PG
30
+ */
31
+ export const PgTypeMappingsPlugin = {
32
+ name: 'PgTypeMappingsPlugin',
33
+ version: '1.0.0',
34
+ gather: {
35
+ hooks: {
36
+ async pgCodecs_findPgCodec(info, event) {
37
+ if (event.pgCodec) {
38
+ return;
39
+ }
40
+ const { pgType: type, serviceName } = event;
41
+ // Find the namespace for this type
42
+ const namespace = await info.helpers.pgIntrospection.getNamespace(serviceName, type.typnamespace);
43
+ if (!namespace) {
44
+ return;
45
+ }
46
+ // Check if this type matches any of our mappings
47
+ const mapping = DEFAULT_MAPPINGS.find(m => m.name === type.typname && m.namespaceName === namespace.nspname);
48
+ if (!mapping) {
49
+ return;
50
+ }
51
+ // Create a codec for this type
52
+ // For composite types, the fromPg function extracts the first field's value
53
+ // For domain types, it just passes through the value
54
+ event.pgCodec = {
55
+ name: type.typname,
56
+ sqlType: sql.identifier(namespace.nspname, type.typname),
57
+ fromPg: (value) => {
58
+ if (value == null) {
59
+ return null;
60
+ }
61
+ // If it's already a scalar, return it
62
+ if (typeof value !== 'object' || Array.isArray(value)) {
63
+ return value;
64
+ }
65
+ // For composite types, extract the first field's value
66
+ const obj = value;
67
+ const keys = Object.keys(obj);
68
+ if (keys.length > 0) {
69
+ return obj[keys[0]];
70
+ }
71
+ return value;
72
+ },
73
+ toPg: (value) => value,
74
+ attributes: undefined,
75
+ executor: null,
76
+ extensions: {
77
+ oid: type._id,
78
+ pg: {
79
+ serviceName,
80
+ schemaName: namespace.nspname,
81
+ name: type.typname,
82
+ },
83
+ tags: {
84
+ // Mark this as a custom mapped type
85
+ pgTypeMappings: mapping.type,
86
+ },
87
+ },
88
+ };
89
+ },
90
+ },
91
+ },
92
+ schema: {
93
+ hooks: {
94
+ init(_, build) {
95
+ const { setGraphQLTypeForPgCodec } = build;
96
+ // Map our custom codecs to GraphQL types
97
+ for (const codec of Object.values(build.input.pgRegistry.pgCodecs)) {
98
+ const mappingType = codec.extensions?.tags?.pgTypeMappings;
99
+ if (mappingType) {
100
+ const gqlTypeName = GraphQLString.name;
101
+ setGraphQLTypeForPgCodec(codec, 'input', gqlTypeName);
102
+ setGraphQLTypeForPgCodec(codec, 'output', gqlTypeName);
103
+ }
104
+ }
105
+ return _;
106
+ },
107
+ },
108
+ },
109
+ };
110
+ /**
111
+ * Preset that includes the PG type mappings plugin.
112
+ *
113
+ * This preset maps common custom PostgreSQL types to GraphQL scalars:
114
+ * - email -> String
115
+ * - hostname -> String
116
+ * - url -> String
117
+ * - origin -> String
118
+ */
119
+ export const PgTypeMappingsPreset = {
120
+ plugins: [PgTypeMappingsPlugin],
121
+ };
122
+ export default PgTypeMappingsPlugin;
@@ -0,0 +1,96 @@
1
+ import type { GraphileConfig } from 'graphile-config';
2
+ /**
3
+ * Configuration options for unique lookup behavior.
4
+ */
5
+ export interface UniqueLookupOptions {
6
+ /**
7
+ * If true, disables ALL unique constraint lookups including primary keys.
8
+ * Users must use collection queries with filters instead.
9
+ * Default: false (primary key lookups are kept)
10
+ */
11
+ disableAllUniqueLookups?: boolean;
12
+ }
13
+ /**
14
+ * PrimaryKeyOnlyPlugin - Disables non-primary-key unique constraint lookups for
15
+ * BOTH queries AND mutations.
16
+ *
17
+ * WHY THIS EXISTS:
18
+ * PostGraphile v5 creates fields for EVERY unique constraint on a table:
19
+ *
20
+ * QUERIES (PgRowByUniquePlugin):
21
+ * - `user(id)`, `userByEmail(email)`, `userByUsername(username)`
22
+ *
23
+ * MUTATIONS (PgMutationUpdateDeletePlugin):
24
+ * - `updateUser`, `updateUserByEmail`, `updateUserByUsername`
25
+ * - `deleteUser`, `deleteUserByEmail`, `deleteUserByUsername`
26
+ *
27
+ * For code generation (React Query, etc.), this creates unnecessary complexity.
28
+ * The same operations can be done using the primary key lookup or filters.
29
+ * Standardizing on primary keys reduces the API surface and generated code.
30
+ *
31
+ * SOURCE CODE REFERENCES:
32
+ *
33
+ * 1. Query fields (PgRowByUniquePlugin):
34
+ * https://github.com/graphile/crystal/blob/924b2515c6bd30e5905ac1419a25244b40c8bb4d/graphile-build/graphile-build-pg/src/plugins/PgRowByUniquePlugin.ts#L42-L257
35
+ *
36
+ * The behavior check for queries:
37
+ * ```typescript
38
+ * const fieldBehaviorScope = "query:resource:single";
39
+ * if (!build.behavior.pgResourceUniqueMatches([resource, unique], fieldBehaviorScope)) {
40
+ * return memo; // Skip this field
41
+ * }
42
+ * ```
43
+ *
44
+ * 2. Mutation fields (PgMutationUpdateDeletePlugin):
45
+ * https://github.com/graphile/crystal/blob/924b2515c6bd30e5905ac1419a25244b40c8bb4d/graphile-build/graphile-build-pg/src/plugins/PgMutationUpdateDeletePlugin.ts
46
+ *
47
+ * The behavior check for mutations:
48
+ * ```typescript
49
+ * const constraintMode = `constraint:${mode}`; // "constraint:resource:update" or "constraint:resource:delete"
50
+ * ...resource.uniques.filter((unique) => {
51
+ * return build.behavior.pgResourceUniqueMatches([resource, unique], constraintMode);
52
+ * })
53
+ * ```
54
+ *
55
+ * OUR FIX:
56
+ * We use the behavior system's OVERRIDE phase (not inferred) to disable these behaviors.
57
+ * The override phase runs AFTER the behavior multiplication/preferences system processes
58
+ * behaviors, giving us the final say on what's enabled/disabled.
59
+ *
60
+ * Behaviors we control:
61
+ * - `-single` - Disables query lookups (userByEmail, etc.)
62
+ * - `-constraint:resource:update` - Disables updateByX mutations
63
+ * - `-constraint:resource:delete` - Disables deleteByX mutations
64
+ *
65
+ * CONFIGURATION OPTIONS:
66
+ *
67
+ * 1. `disableAllUniqueLookups: false` (default - PrimaryKeyOnlyPreset):
68
+ * - Primary key: query lookup + mutations enabled
69
+ * - Non-primary-key: everything disabled
70
+ * Result: `user(id)`, `updateUser`, `deleteUser` only
71
+ *
72
+ * 2. `disableAllUniqueLookups: true` (NoUniqueLookupPreset):
73
+ * - Primary key: query lookup DISABLED, mutations ENABLED
74
+ * - Non-primary-key: everything disabled
75
+ * Result: No query lookups (use filters), but `updateUser`, `deleteUser` still work
76
+ */
77
+ /**
78
+ * Creates a plugin that controls unique constraint lookup behavior.
79
+ *
80
+ * @param options - Configuration options
81
+ * @param options.disableAllUniqueLookups - If true, disables ALL unique lookups including primary keys
82
+ */
83
+ export declare function createUniqueLookupPlugin(options?: UniqueLookupOptions): GraphileConfig.Plugin;
84
+ export declare const PrimaryKeyOnlyPlugin: GraphileConfig.Plugin;
85
+ export declare const NoUniqueLookupPlugin: GraphileConfig.Plugin;
86
+ /**
87
+ * Preset that keeps only primary key lookups.
88
+ * Use this in your main preset's `extends` array.
89
+ */
90
+ export declare const PrimaryKeyOnlyPreset: GraphileConfig.Preset;
91
+ /**
92
+ * Preset that disables ALL unique lookups (including primary keys).
93
+ * Users must use collection queries with filters instead.
94
+ * Use this in your main preset's `extends` array.
95
+ */
96
+ export declare const NoUniqueLookupPreset: GraphileConfig.Preset;
@@ -0,0 +1,143 @@
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
+ };
@@ -0,0 +1,40 @@
1
+ import type { GraphileConfig } from 'graphile-config';
2
+ /**
3
+ * Constructive PostGraphile v5 Preset
4
+ *
5
+ * This is the main preset that combines all our custom plugins and configurations.
6
+ * It provides a clean, opinionated GraphQL API built from PostgreSQL.
7
+ *
8
+ * FEATURES:
9
+ * - No Node/Relay features (keeps `id` as `id`, no global object identification)
10
+ * - Custom inflection using inflekt library
11
+ * - Conflict detection for multi-schema setups
12
+ * - Inflector logging for debugging (enable with INFLECTOR_LOG=1)
13
+ * - Primary key only lookups (no *ByEmail, *ByUsername, etc.)
14
+ * - Connection filter plugin with all columns filterable
15
+ * - Many-to-many relationships (opt-in via @behavior +manyToMany)
16
+ * - Meta schema plugin (_meta query for introspection of tables, fields, indexes)
17
+ * - PG type mappings (maps custom types like email, url to GraphQL scalars)
18
+ *
19
+ * DISABLED PLUGINS:
20
+ * - PgConnectionArgFilterBackwardRelationsPlugin (relation filters bloat the API)
21
+ * - PgConnectionArgFilterForwardRelationsPlugin (relation filters bloat the API)
22
+ *
23
+ * USAGE:
24
+ * ```typescript
25
+ * import { ConstructivePreset } from 'graphile-settings/presets';
26
+ * import { makePgService } from 'postgraphile/adaptors/pg';
27
+ *
28
+ * const preset: GraphileConfig.Preset = {
29
+ * extends: [ConstructivePreset],
30
+ * pgServices: [
31
+ * makePgService({
32
+ * connectionString: DATABASE_URL,
33
+ * schemas: ['public'],
34
+ * }),
35
+ * ],
36
+ * };
37
+ * ```
38
+ */
39
+ export declare const ConstructivePreset: GraphileConfig.Preset;
40
+ export default ConstructivePreset;
@@ -0,0 +1,137 @@
1
+ import { PostGraphileConnectionFilterPreset } from 'postgraphile-plugin-connection-filter';
2
+ import { MinimalPreset } from '../plugins/minimal-preset';
3
+ import { InflektPreset } from '../plugins/custom-inflector';
4
+ import { ConflictDetectorPreset } from '../plugins/conflict-detector';
5
+ import { InflectorLoggerPreset } from '../plugins/inflector-logger';
6
+ import { NoUniqueLookupPreset } from '../plugins/primary-key-only';
7
+ import { EnableAllFilterColumnsPreset } from '../plugins/enable-all-filter-columns';
8
+ import { ManyToManyOptInPreset } from '../plugins/many-to-many-preset';
9
+ import { MetaSchemaPreset } from '../plugins/meta-schema';
10
+ import { PgSearchPreset } from 'graphile-search-plugin';
11
+ import { PgTypeMappingsPreset } from '../plugins/pg-type-mappings';
12
+ /**
13
+ * Constructive PostGraphile v5 Preset
14
+ *
15
+ * This is the main preset that combines all our custom plugins and configurations.
16
+ * It provides a clean, opinionated GraphQL API built from PostgreSQL.
17
+ *
18
+ * FEATURES:
19
+ * - No Node/Relay features (keeps `id` as `id`, no global object identification)
20
+ * - Custom inflection using inflekt library
21
+ * - Conflict detection for multi-schema setups
22
+ * - Inflector logging for debugging (enable with INFLECTOR_LOG=1)
23
+ * - Primary key only lookups (no *ByEmail, *ByUsername, etc.)
24
+ * - Connection filter plugin with all columns filterable
25
+ * - Many-to-many relationships (opt-in via @behavior +manyToMany)
26
+ * - Meta schema plugin (_meta query for introspection of tables, fields, indexes)
27
+ * - PG type mappings (maps custom types like email, url to GraphQL scalars)
28
+ *
29
+ * DISABLED PLUGINS:
30
+ * - PgConnectionArgFilterBackwardRelationsPlugin (relation filters bloat the API)
31
+ * - PgConnectionArgFilterForwardRelationsPlugin (relation filters bloat the API)
32
+ *
33
+ * USAGE:
34
+ * ```typescript
35
+ * import { ConstructivePreset } from 'graphile-settings/presets';
36
+ * import { makePgService } from 'postgraphile/adaptors/pg';
37
+ *
38
+ * const preset: GraphileConfig.Preset = {
39
+ * extends: [ConstructivePreset],
40
+ * pgServices: [
41
+ * makePgService({
42
+ * connectionString: DATABASE_URL,
43
+ * schemas: ['public'],
44
+ * }),
45
+ * ],
46
+ * };
47
+ * ```
48
+ */
49
+ export const ConstructivePreset = {
50
+ extends: [
51
+ MinimalPreset,
52
+ ConflictDetectorPreset,
53
+ InflektPreset,
54
+ InflectorLoggerPreset,
55
+ NoUniqueLookupPreset,
56
+ PostGraphileConnectionFilterPreset,
57
+ EnableAllFilterColumnsPreset,
58
+ ManyToManyOptInPreset,
59
+ MetaSchemaPreset,
60
+ PgSearchPreset({ pgSearchPrefix: 'fullText' }),
61
+ PgTypeMappingsPreset,
62
+ ],
63
+ /**
64
+ * Disable relation filter plugins from postgraphile-plugin-connection-filter.
65
+ *
66
+ * WHY THIS EXISTS:
67
+ * The connection filter plugin includes PgConnectionArgFilterBackwardRelationsPlugin and
68
+ * PgConnectionArgFilterForwardRelationsPlugin which add relation filter fields like
69
+ * `apiExtensions`, `apiExtensionsExist`, `database`, `domains`, etc. to every filter type.
70
+ *
71
+ * The `connectionFilterRelations: false` schema option does NOT work - it's defined in the
72
+ * plugin's TypeScript types but the actual code always includes the plugins regardless.
73
+ * See: https://github.com/graphile-contrib/postgraphile-plugin-connection-filter/blob/master/src/index.ts
74
+ * The comments `//if (connectionFilterRelations)` are just comments, not actual conditional logic.
75
+ *
76
+ * The entityBehavior approach (setting `pgCodecRelation: '-filterBy'`) also doesn't work
77
+ * because the behavior system doesn't properly negate the plugin's default `filterBy` behavior.
78
+ *
79
+ * OUR FIX:
80
+ * We use `disablePlugins` to directly disable the two relation filter plugins.
81
+ * This is the most reliable way to prevent relation filter fields from being generated.
82
+ */
83
+ disablePlugins: [
84
+ 'PgConnectionArgFilterBackwardRelationsPlugin',
85
+ 'PgConnectionArgFilterForwardRelationsPlugin',
86
+ ],
87
+ /**
88
+ * Connection Filter Plugin Configuration
89
+ *
90
+ * These options control what fields appear in the `filter` argument on connections.
91
+ * We disable relation filters to keep the API surface clean and match our v4 behavior.
92
+ *
93
+ * NOTE: By default, PostGraphile v5 only allows filtering on INDEXED columns.
94
+ * We override this with EnableAllFilterColumnsPreset to allow filtering on ALL columns.
95
+ * This gives developers flexibility but requires monitoring for slow queries on
96
+ * non-indexed columns. See the plugin documentation for performance considerations.
97
+ *
98
+ * NOTE: Relation filtering is disabled via `disablePlugins` above.
99
+ *
100
+ * Documentation: https://github.com/graphile-contrib/postgraphile-plugin-connection-filter
101
+ */
102
+ schema: {
103
+ /**
104
+ * connectionFilterRelations: false
105
+ * This option is defined in the plugin's types but does NOT actually work.
106
+ * The relation filter plugins are disabled via `disablePlugins` above.
107
+ * We keep this option set to false for documentation purposes.
108
+ */
109
+ connectionFilterRelations: false,
110
+ /**
111
+ * connectionFilterComputedColumns: false
112
+ * Disables filtering on computed columns (functions that return a value for a row).
113
+ * Computed columns can be expensive to filter on since they may not be indexed.
114
+ * To selectively enable, use `@filterable` smart tag on specific functions.
115
+ */
116
+ connectionFilterComputedColumns: false,
117
+ /**
118
+ * connectionFilterSetofFunctions: false
119
+ * Disables filtering on functions that return `setof` (multiple rows).
120
+ * These can be expensive operations. To selectively enable, use `@filterable` smart tag.
121
+ */
122
+ connectionFilterSetofFunctions: false,
123
+ /**
124
+ * connectionFilterLogicalOperators: true (default)
125
+ * Keeps `and`, `or`, `not` operators for combining filter conditions.
126
+ * Example: filter: { or: [{ name: { eq: "foo" } }, { name: { eq: "bar" } }] }
127
+ */
128
+ connectionFilterLogicalOperators: true,
129
+ /**
130
+ * connectionFilterArrays: true (default)
131
+ * Allows filtering on PostgreSQL array columns.
132
+ * Example: filter: { tags: { contains: ["important"] } }
133
+ */
134
+ connectionFilterArrays: true,
135
+ },
136
+ };
137
+ export default ConstructivePreset;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * PostGraphile v5 Presets
3
+ *
4
+ * This module exports pre-configured presets that combine multiple plugins
5
+ * for common use cases.
6
+ */
7
+ export { ConstructivePreset } from './constructive-preset';
@@ -0,0 +1,7 @@
1
+ /**
2
+ * PostGraphile v5 Presets
3
+ *
4
+ * This module exports pre-configured presets that combine multiple plugins
5
+ * for common use cases.
6
+ */
7
+ export { ConstructivePreset } from './constructive-preset';