graphile-connection-filter 1.1.0

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 (71) hide show
  1. package/LICENSE +23 -0
  2. package/README.md +107 -0
  3. package/augmentations.d.ts +104 -0
  4. package/augmentations.js +11 -0
  5. package/esm/augmentations.d.ts +104 -0
  6. package/esm/augmentations.js +9 -0
  7. package/esm/index.d.ts +55 -0
  8. package/esm/index.js +56 -0
  9. package/esm/plugins/ConnectionFilterArgPlugin.d.ts +13 -0
  10. package/esm/plugins/ConnectionFilterArgPlugin.js +96 -0
  11. package/esm/plugins/ConnectionFilterAttributesPlugin.d.ts +14 -0
  12. package/esm/plugins/ConnectionFilterAttributesPlugin.js +79 -0
  13. package/esm/plugins/ConnectionFilterBackwardRelationsPlugin.d.ts +33 -0
  14. package/esm/plugins/ConnectionFilterBackwardRelationsPlugin.js +398 -0
  15. package/esm/plugins/ConnectionFilterComputedAttributesPlugin.d.ts +19 -0
  16. package/esm/plugins/ConnectionFilterComputedAttributesPlugin.js +133 -0
  17. package/esm/plugins/ConnectionFilterCustomOperatorsPlugin.d.ts +35 -0
  18. package/esm/plugins/ConnectionFilterCustomOperatorsPlugin.js +129 -0
  19. package/esm/plugins/ConnectionFilterForwardRelationsPlugin.d.ts +28 -0
  20. package/esm/plugins/ConnectionFilterForwardRelationsPlugin.js +168 -0
  21. package/esm/plugins/ConnectionFilterInflectionPlugin.d.ts +11 -0
  22. package/esm/plugins/ConnectionFilterInflectionPlugin.js +27 -0
  23. package/esm/plugins/ConnectionFilterLogicalOperatorsPlugin.d.ts +15 -0
  24. package/esm/plugins/ConnectionFilterLogicalOperatorsPlugin.js +86 -0
  25. package/esm/plugins/ConnectionFilterOperatorsPlugin.d.ts +21 -0
  26. package/esm/plugins/ConnectionFilterOperatorsPlugin.js +677 -0
  27. package/esm/plugins/ConnectionFilterTypesPlugin.d.ts +12 -0
  28. package/esm/plugins/ConnectionFilterTypesPlugin.js +225 -0
  29. package/esm/plugins/index.d.ts +11 -0
  30. package/esm/plugins/index.js +11 -0
  31. package/esm/plugins/operatorApply.d.ts +11 -0
  32. package/esm/plugins/operatorApply.js +70 -0
  33. package/esm/preset.d.ts +35 -0
  34. package/esm/preset.js +72 -0
  35. package/esm/types.d.ts +146 -0
  36. package/esm/types.js +4 -0
  37. package/esm/utils.d.ts +44 -0
  38. package/esm/utils.js +112 -0
  39. package/index.d.ts +55 -0
  40. package/index.js +77 -0
  41. package/package.json +58 -0
  42. package/plugins/ConnectionFilterArgPlugin.d.ts +13 -0
  43. package/plugins/ConnectionFilterArgPlugin.js +99 -0
  44. package/plugins/ConnectionFilterAttributesPlugin.d.ts +14 -0
  45. package/plugins/ConnectionFilterAttributesPlugin.js +82 -0
  46. package/plugins/ConnectionFilterBackwardRelationsPlugin.d.ts +33 -0
  47. package/plugins/ConnectionFilterBackwardRelationsPlugin.js +401 -0
  48. package/plugins/ConnectionFilterComputedAttributesPlugin.d.ts +19 -0
  49. package/plugins/ConnectionFilterComputedAttributesPlugin.js +136 -0
  50. package/plugins/ConnectionFilterCustomOperatorsPlugin.d.ts +35 -0
  51. package/plugins/ConnectionFilterCustomOperatorsPlugin.js +132 -0
  52. package/plugins/ConnectionFilterForwardRelationsPlugin.d.ts +28 -0
  53. package/plugins/ConnectionFilterForwardRelationsPlugin.js +171 -0
  54. package/plugins/ConnectionFilterInflectionPlugin.d.ts +11 -0
  55. package/plugins/ConnectionFilterInflectionPlugin.js +30 -0
  56. package/plugins/ConnectionFilterLogicalOperatorsPlugin.d.ts +15 -0
  57. package/plugins/ConnectionFilterLogicalOperatorsPlugin.js +89 -0
  58. package/plugins/ConnectionFilterOperatorsPlugin.d.ts +21 -0
  59. package/plugins/ConnectionFilterOperatorsPlugin.js +680 -0
  60. package/plugins/ConnectionFilterTypesPlugin.d.ts +12 -0
  61. package/plugins/ConnectionFilterTypesPlugin.js +228 -0
  62. package/plugins/index.d.ts +11 -0
  63. package/plugins/index.js +25 -0
  64. package/plugins/operatorApply.d.ts +11 -0
  65. package/plugins/operatorApply.js +73 -0
  66. package/preset.d.ts +35 -0
  67. package/preset.js +75 -0
  68. package/types.d.ts +146 -0
  69. package/types.js +7 -0
  70. package/utils.d.ts +44 -0
  71. package/utils.js +119 -0
package/esm/utils.js ADDED
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Utility functions for the connection filter plugin.
3
+ */
4
+ /**
5
+ * Check if a value is an empty object (no own enumerable keys).
6
+ */
7
+ export function isEmpty(o) {
8
+ return typeof o === 'object' && o !== null && Object.keys(o).length === 0;
9
+ }
10
+ /**
11
+ * Check if a pgResource is a computed scalar attribute function.
12
+ * A computed attribute is a function that:
13
+ * - has parameters
14
+ * - returns a scalar (no attributes on codec)
15
+ * - is unique (returns single row)
16
+ * - first parameter's codec has attributes (i.e. takes a table row)
17
+ */
18
+ export function isComputedScalarAttributeResource(s) {
19
+ if (!s.parameters || s.parameters.length < 1) {
20
+ return false;
21
+ }
22
+ if (s.codec.attributes) {
23
+ return false;
24
+ }
25
+ if (!s.isUnique) {
26
+ return false;
27
+ }
28
+ const firstParameter = s.parameters[0];
29
+ if (!firstParameter?.codec.attributes) {
30
+ return false;
31
+ }
32
+ return true;
33
+ }
34
+ /**
35
+ * Get all computed attribute resources for a given source.
36
+ */
37
+ export function getComputedAttributeResources(build, source) {
38
+ const computedAttributeSources = Object.values(build.input.pgRegistry.pgResources).filter((s) => isComputedScalarAttributeResource(s) &&
39
+ s.parameters[0].codec === source.codec);
40
+ return computedAttributeSources;
41
+ }
42
+ /**
43
+ * Walks from a PgCondition up to the PgSelectQueryBuilder.
44
+ * Uses the .parent property on PgCondition to traverse up the chain,
45
+ * following Benjie's pattern from postgraphile-plugin-fulltext-filter.
46
+ *
47
+ * This is used by satellite plugins (search, BM25, pgvector) that need
48
+ * to access the query builder from within a filter's apply callback
49
+ * to inject SELECT expressions (for ranking/scoring) and ORDER BY clauses.
50
+ *
51
+ * @param build - The Graphile Build object (needs build.dataplanPg.PgCondition)
52
+ * @param $condition - The PgCondition instance from the filter apply callback
53
+ * @returns The PgSelectQueryBuilder if found, or null
54
+ */
55
+ export function getQueryBuilder(build, $condition) {
56
+ const PgCondition = build.dataplanPg?.PgCondition;
57
+ if (!PgCondition)
58
+ return null;
59
+ let current = $condition;
60
+ const { alias } = current;
61
+ // Walk up through nested PgConditions (e.g. and/or/not)
62
+ // Note: PgCondition.parent is protected, so we use bracket notation
63
+ // to access it from outside the class hierarchy.
64
+ while (current &&
65
+ current instanceof PgCondition &&
66
+ current.alias === alias) {
67
+ current = current['parent'];
68
+ }
69
+ // Verify we found a query builder with matching alias
70
+ // Using duck-typing per Benjie's pattern
71
+ if (current &&
72
+ typeof current.selectAndReturnIndex === 'function' &&
73
+ current.alias === alias) {
74
+ return current;
75
+ }
76
+ return null;
77
+ }
78
+ /**
79
+ * Creates an assertion function that validates filter input values.
80
+ *
81
+ * Rejects empty objects in nested contexts (logical operators, relation filters)
82
+ * and optionally rejects null literals based on the connectionFilterAllowNullInput option.
83
+ *
84
+ * Note: Top-level empty filter `{}` is handled in ConnectionFilterArgPlugin's
85
+ * applyPlan — it's treated as "no filter" and skipped, not rejected.
86
+ */
87
+ export function makeAssertAllowed(build) {
88
+ const { options, EXPORTABLE } = build;
89
+ const { connectionFilterAllowNullInput, } = options;
90
+ const assertAllowed = EXPORTABLE((connectionFilterAllowNullInput, isEmpty) => function (value, mode) {
91
+ // Reject empty objects in nested filter contexts (and/or/not, relation filters)
92
+ if (mode === 'object' && isEmpty(value)) {
93
+ throw Object.assign(new Error('Empty objects are forbidden in filter argument input.'), {});
94
+ }
95
+ if (mode === 'list') {
96
+ const arr = value;
97
+ if (arr) {
98
+ const l = arr.length;
99
+ for (let i = 0; i < l; i++) {
100
+ if (isEmpty(arr[i])) {
101
+ throw Object.assign(new Error('Empty objects are forbidden in filter argument input.'), {});
102
+ }
103
+ }
104
+ }
105
+ }
106
+ // For all modes, check null
107
+ if (!connectionFilterAllowNullInput && value === null) {
108
+ throw Object.assign(new Error('Null literals are forbidden in filter argument input.'), {});
109
+ }
110
+ }, [connectionFilterAllowNullInput, isEmpty]);
111
+ return assertAllowed;
112
+ }
package/index.d.ts ADDED
@@ -0,0 +1,55 @@
1
+ /**
2
+ * graphile-connection-filter
3
+ *
4
+ * A PostGraphile v5 native connection filter plugin.
5
+ * Adds advanced filtering capabilities to connection and list fields.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import { ConnectionFilterPreset } from 'graphile-connection-filter';
10
+ *
11
+ * const preset = {
12
+ * extends: [
13
+ * ConnectionFilterPreset(),
14
+ * ],
15
+ * };
16
+ * ```
17
+ *
18
+ * For satellite plugins that need to register custom operators, declare them
19
+ * as factories in the preset's `connectionFilterOperatorFactories` option:
20
+ * ```typescript
21
+ * import type { ConnectionFilterOperatorFactory } from 'graphile-connection-filter';
22
+ *
23
+ * const myOperatorFactory: ConnectionFilterOperatorFactory = (build) => [{
24
+ * typeNames: 'MyType',
25
+ * operatorName: 'myOperator',
26
+ * spec: {
27
+ * description: 'My custom operator',
28
+ * resolve: (sqlIdentifier, sqlValue) => build.sql`${sqlIdentifier} OP ${sqlValue}`,
29
+ * },
30
+ * }];
31
+ *
32
+ * export const MyPreset = {
33
+ * schema: {
34
+ * connectionFilterOperatorFactories: [myOperatorFactory],
35
+ * },
36
+ * plugins: [MyPlugin],
37
+ * };
38
+ * ```
39
+ *
40
+ * For satellite plugins that need to access the query builder from a filter apply:
41
+ * ```typescript
42
+ * import { getQueryBuilder } from 'graphile-connection-filter';
43
+ * // In your filter field's apply callback:
44
+ * const qb = getQueryBuilder(build, $condition);
45
+ * if (qb) {
46
+ * const idx = qb.selectAndReturnIndex(sql`...`);
47
+ * qb.setMeta('key', { selectIndex: idx });
48
+ * }
49
+ * ```
50
+ */
51
+ export { ConnectionFilterPreset } from './preset';
52
+ export { ConnectionFilterInflectionPlugin, ConnectionFilterTypesPlugin, ConnectionFilterArgPlugin, ConnectionFilterAttributesPlugin, ConnectionFilterOperatorsPlugin, ConnectionFilterCustomOperatorsPlugin, ConnectionFilterLogicalOperatorsPlugin, ConnectionFilterComputedAttributesPlugin, ConnectionFilterForwardRelationsPlugin, ConnectionFilterBackwardRelationsPlugin, makeApplyFromOperatorSpec, } from './plugins';
53
+ export type { ConnectionFilterOperatorSpec, ConnectionFilterOperatorRegistration, ConnectionFilterOperatorFactory, ConnectionFilterOptions, ConnectionFilterOperatorsDigest, PgConnectionFilterOperatorsScope, } from './types';
54
+ export { $$filters } from './types';
55
+ export { isEmpty, makeAssertAllowed, getQueryBuilder, isComputedScalarAttributeResource, getComputedAttributeResources, } from './utils';
package/index.js ADDED
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ /**
3
+ * graphile-connection-filter
4
+ *
5
+ * A PostGraphile v5 native connection filter plugin.
6
+ * Adds advanced filtering capabilities to connection and list fields.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * import { ConnectionFilterPreset } from 'graphile-connection-filter';
11
+ *
12
+ * const preset = {
13
+ * extends: [
14
+ * ConnectionFilterPreset(),
15
+ * ],
16
+ * };
17
+ * ```
18
+ *
19
+ * For satellite plugins that need to register custom operators, declare them
20
+ * as factories in the preset's `connectionFilterOperatorFactories` option:
21
+ * ```typescript
22
+ * import type { ConnectionFilterOperatorFactory } from 'graphile-connection-filter';
23
+ *
24
+ * const myOperatorFactory: ConnectionFilterOperatorFactory = (build) => [{
25
+ * typeNames: 'MyType',
26
+ * operatorName: 'myOperator',
27
+ * spec: {
28
+ * description: 'My custom operator',
29
+ * resolve: (sqlIdentifier, sqlValue) => build.sql`${sqlIdentifier} OP ${sqlValue}`,
30
+ * },
31
+ * }];
32
+ *
33
+ * export const MyPreset = {
34
+ * schema: {
35
+ * connectionFilterOperatorFactories: [myOperatorFactory],
36
+ * },
37
+ * plugins: [MyPlugin],
38
+ * };
39
+ * ```
40
+ *
41
+ * For satellite plugins that need to access the query builder from a filter apply:
42
+ * ```typescript
43
+ * import { getQueryBuilder } from 'graphile-connection-filter';
44
+ * // In your filter field's apply callback:
45
+ * const qb = getQueryBuilder(build, $condition);
46
+ * if (qb) {
47
+ * const idx = qb.selectAndReturnIndex(sql`...`);
48
+ * qb.setMeta('key', { selectIndex: idx });
49
+ * }
50
+ * ```
51
+ */
52
+ Object.defineProperty(exports, "__esModule", { value: true });
53
+ exports.getComputedAttributeResources = exports.isComputedScalarAttributeResource = exports.getQueryBuilder = exports.makeAssertAllowed = exports.isEmpty = exports.$$filters = exports.makeApplyFromOperatorSpec = exports.ConnectionFilterBackwardRelationsPlugin = exports.ConnectionFilterForwardRelationsPlugin = exports.ConnectionFilterComputedAttributesPlugin = exports.ConnectionFilterLogicalOperatorsPlugin = exports.ConnectionFilterCustomOperatorsPlugin = exports.ConnectionFilterOperatorsPlugin = exports.ConnectionFilterAttributesPlugin = exports.ConnectionFilterArgPlugin = exports.ConnectionFilterTypesPlugin = exports.ConnectionFilterInflectionPlugin = exports.ConnectionFilterPreset = void 0;
54
+ var preset_1 = require("./preset");
55
+ Object.defineProperty(exports, "ConnectionFilterPreset", { enumerable: true, get: function () { return preset_1.ConnectionFilterPreset; } });
56
+ // Re-export all plugins for granular use
57
+ var plugins_1 = require("./plugins");
58
+ Object.defineProperty(exports, "ConnectionFilterInflectionPlugin", { enumerable: true, get: function () { return plugins_1.ConnectionFilterInflectionPlugin; } });
59
+ Object.defineProperty(exports, "ConnectionFilterTypesPlugin", { enumerable: true, get: function () { return plugins_1.ConnectionFilterTypesPlugin; } });
60
+ Object.defineProperty(exports, "ConnectionFilterArgPlugin", { enumerable: true, get: function () { return plugins_1.ConnectionFilterArgPlugin; } });
61
+ Object.defineProperty(exports, "ConnectionFilterAttributesPlugin", { enumerable: true, get: function () { return plugins_1.ConnectionFilterAttributesPlugin; } });
62
+ Object.defineProperty(exports, "ConnectionFilterOperatorsPlugin", { enumerable: true, get: function () { return plugins_1.ConnectionFilterOperatorsPlugin; } });
63
+ Object.defineProperty(exports, "ConnectionFilterCustomOperatorsPlugin", { enumerable: true, get: function () { return plugins_1.ConnectionFilterCustomOperatorsPlugin; } });
64
+ Object.defineProperty(exports, "ConnectionFilterLogicalOperatorsPlugin", { enumerable: true, get: function () { return plugins_1.ConnectionFilterLogicalOperatorsPlugin; } });
65
+ Object.defineProperty(exports, "ConnectionFilterComputedAttributesPlugin", { enumerable: true, get: function () { return plugins_1.ConnectionFilterComputedAttributesPlugin; } });
66
+ Object.defineProperty(exports, "ConnectionFilterForwardRelationsPlugin", { enumerable: true, get: function () { return plugins_1.ConnectionFilterForwardRelationsPlugin; } });
67
+ Object.defineProperty(exports, "ConnectionFilterBackwardRelationsPlugin", { enumerable: true, get: function () { return plugins_1.ConnectionFilterBackwardRelationsPlugin; } });
68
+ Object.defineProperty(exports, "makeApplyFromOperatorSpec", { enumerable: true, get: function () { return plugins_1.makeApplyFromOperatorSpec; } });
69
+ var types_1 = require("./types");
70
+ Object.defineProperty(exports, "$$filters", { enumerable: true, get: function () { return types_1.$$filters; } });
71
+ // Re-export utilities
72
+ var utils_1 = require("./utils");
73
+ Object.defineProperty(exports, "isEmpty", { enumerable: true, get: function () { return utils_1.isEmpty; } });
74
+ Object.defineProperty(exports, "makeAssertAllowed", { enumerable: true, get: function () { return utils_1.makeAssertAllowed; } });
75
+ Object.defineProperty(exports, "getQueryBuilder", { enumerable: true, get: function () { return utils_1.getQueryBuilder; } });
76
+ Object.defineProperty(exports, "isComputedScalarAttributeResource", { enumerable: true, get: function () { return utils_1.isComputedScalarAttributeResource; } });
77
+ Object.defineProperty(exports, "getComputedAttributeResources", { enumerable: true, get: function () { return utils_1.getComputedAttributeResources; } });
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "graphile-connection-filter",
3
+ "version": "1.1.0",
4
+ "description": "PostGraphile v5 native connection filter plugin - adds advanced filtering to connections",
5
+ "author": "Constructive <developers@constructive.io>",
6
+ "homepage": "https://github.com/constructive-io/constructive",
7
+ "license": "MIT",
8
+ "main": "index.js",
9
+ "module": "esm/index.js",
10
+ "types": "index.d.ts",
11
+ "scripts": {
12
+ "clean": "makage clean",
13
+ "prepack": "npm run build",
14
+ "build": "makage build",
15
+ "build:dev": "makage build --dev",
16
+ "lint": "eslint . --fix",
17
+ "test": "jest",
18
+ "test:watch": "jest --watch"
19
+ },
20
+ "publishConfig": {
21
+ "access": "public",
22
+ "directory": "dist"
23
+ },
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "https://github.com/constructive-io/constructive"
27
+ },
28
+ "keywords": [
29
+ "postgraphile",
30
+ "graphile",
31
+ "constructive",
32
+ "plugin",
33
+ "postgres",
34
+ "graphql",
35
+ "filter",
36
+ "connection-filter",
37
+ "v5"
38
+ ],
39
+ "bugs": {
40
+ "url": "https://github.com/constructive-io/constructive/issues"
41
+ },
42
+ "devDependencies": {
43
+ "@types/node": "^22.19.11",
44
+ "graphile-test": "^4.5.2",
45
+ "makage": "^0.1.10",
46
+ "pgsql-test": "^4.5.2"
47
+ },
48
+ "peerDependencies": {
49
+ "@dataplan/pg": "1.0.0-rc.5",
50
+ "graphile-build": "5.0.0-rc.4",
51
+ "graphile-build-pg": "5.0.0-rc.5",
52
+ "graphile-config": "1.0.0-rc.5",
53
+ "graphql": "^16.9.0",
54
+ "pg-sql2": "5.0.0-rc.4",
55
+ "postgraphile": "5.0.0-rc.7"
56
+ },
57
+ "gitHead": "1ade5f10df8e38a5f87bbd2e2f7ec2ba97267079"
58
+ }
@@ -0,0 +1,13 @@
1
+ import '../augmentations';
2
+ import type { GraphileConfig } from 'graphile-config';
3
+ /**
4
+ * ConnectionFilterArgPlugin
5
+ *
6
+ * Adds the filter argument (configurable name, default 'where') to connection
7
+ * and simple collection fields. Uses `applyPlan` to create a PgCondition that
8
+ * child filter fields can add WHERE clauses to.
9
+ *
10
+ * This runs before PgConnectionArgOrderByPlugin so that filters are applied
11
+ * before ordering (important for e.g. full-text search rank ordering).
12
+ */
13
+ export declare const ConnectionFilterArgPlugin: GraphileConfig.Plugin;
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ConnectionFilterArgPlugin = void 0;
4
+ require("../augmentations");
5
+ const utils_1 = require("../utils");
6
+ const version = '1.0.0';
7
+ /**
8
+ * ConnectionFilterArgPlugin
9
+ *
10
+ * Adds the filter argument (configurable name, default 'where') to connection
11
+ * and simple collection fields. Uses `applyPlan` to create a PgCondition that
12
+ * child filter fields can add WHERE clauses to.
13
+ *
14
+ * This runs before PgConnectionArgOrderByPlugin so that filters are applied
15
+ * before ordering (important for e.g. full-text search rank ordering).
16
+ */
17
+ exports.ConnectionFilterArgPlugin = {
18
+ name: 'ConnectionFilterArgPlugin',
19
+ version,
20
+ description: 'Adds the filter argument to connection and list fields',
21
+ before: ['PgConnectionArgOrderByPlugin'],
22
+ schema: {
23
+ hooks: {
24
+ GraphQLObjectType_fields_field_args(args, build, context) {
25
+ const { extend, inflection, EXPORTABLE, dataplanPg: { PgCondition }, } = build;
26
+ const { scope: { isPgFieldConnection, isPgFieldSimpleCollection, pgFieldResource: resource, pgFieldCodec, fieldName, }, Self, } = context;
27
+ const shouldAddFilter = isPgFieldConnection || isPgFieldSimpleCollection;
28
+ if (!shouldAddFilter)
29
+ return args;
30
+ const codec = pgFieldCodec ?? resource?.codec;
31
+ if (!codec)
32
+ return args;
33
+ // Check behavior: procedures use "filterProc", tables use "filter"
34
+ const desiredBehavior = resource?.parameters
35
+ ? 'filterProc'
36
+ : 'filter';
37
+ if (resource
38
+ ? !build.behavior.pgResourceMatches(resource, desiredBehavior)
39
+ : !build.behavior.pgCodecMatches(codec, desiredBehavior)) {
40
+ return args;
41
+ }
42
+ const returnCodec = codec;
43
+ const nodeType = build.getGraphQLTypeByPgCodec(returnCodec, 'output');
44
+ if (!nodeType)
45
+ return args;
46
+ const nodeTypeName = nodeType.name;
47
+ const filterTypeName = inflection.filterType(nodeTypeName);
48
+ const FilterType = build.getTypeByName(filterTypeName);
49
+ if (!FilterType)
50
+ return args;
51
+ // For setof functions returning scalars, track the codec
52
+ const attributeCodec = resource?.parameters && !resource?.codec.attributes
53
+ ? resource.codec
54
+ : null;
55
+ const argName = build.options.connectionFilterArgumentName || 'where';
56
+ return extend(args, {
57
+ [argName]: {
58
+ description: 'A filter to be used in determining which values should be returned by the collection.',
59
+ type: FilterType,
60
+ ...(isPgFieldConnection
61
+ ? {
62
+ applyPlan: EXPORTABLE((PgCondition, isEmpty, attributeCodec) => function (_, $connection, fieldArg) {
63
+ const $pgSelect = $connection.getSubplan();
64
+ fieldArg.apply($pgSelect, (queryBuilder, value) => {
65
+ // If filter is null/undefined or empty {}, treat as "no filter" — skip
66
+ if (value == null || isEmpty(value))
67
+ return;
68
+ const condition = new PgCondition(queryBuilder);
69
+ if (attributeCodec) {
70
+ condition.extensions.pgFilterAttribute = {
71
+ codec: attributeCodec,
72
+ };
73
+ }
74
+ return condition;
75
+ });
76
+ }, [PgCondition, utils_1.isEmpty, attributeCodec]),
77
+ }
78
+ : {
79
+ applyPlan: EXPORTABLE((PgCondition, isEmpty, attributeCodec) => function (_, $pgSelect, fieldArg) {
80
+ fieldArg.apply($pgSelect, (queryBuilder, value) => {
81
+ // If filter is null/undefined or empty {}, treat as "no filter" — skip
82
+ if (value == null || isEmpty(value))
83
+ return;
84
+ const condition = new PgCondition(queryBuilder);
85
+ if (attributeCodec) {
86
+ condition.extensions.pgFilterAttribute = {
87
+ codec: attributeCodec,
88
+ };
89
+ }
90
+ return condition;
91
+ });
92
+ }, [PgCondition, utils_1.isEmpty, attributeCodec]),
93
+ }),
94
+ },
95
+ }, `Adding connection filter '${argName}' arg to field '${fieldName}' of '${Self.name}'`);
96
+ },
97
+ },
98
+ },
99
+ };
@@ -0,0 +1,14 @@
1
+ import '../augmentations';
2
+ import type { GraphileConfig } from 'graphile-config';
3
+ /**
4
+ * ConnectionFilterAttributesPlugin
5
+ *
6
+ * Adds per-column filter fields to the table filter types.
7
+ * For example, on `UserFilter`, adds fields like `name` (type: StringFilter),
8
+ * `age` (type: IntFilter), etc.
9
+ *
10
+ * Each field's `apply` function creates a new PgCondition with the
11
+ * `pgFilterAttribute` extension set, so downstream operator fields know
12
+ * which column they are operating on.
13
+ */
14
+ export declare const ConnectionFilterAttributesPlugin: GraphileConfig.Plugin;
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ConnectionFilterAttributesPlugin = void 0;
4
+ require("../augmentations");
5
+ const utils_1 = require("../utils");
6
+ const version = '1.0.0';
7
+ /**
8
+ * ConnectionFilterAttributesPlugin
9
+ *
10
+ * Adds per-column filter fields to the table filter types.
11
+ * For example, on `UserFilter`, adds fields like `name` (type: StringFilter),
12
+ * `age` (type: IntFilter), etc.
13
+ *
14
+ * Each field's `apply` function creates a new PgCondition with the
15
+ * `pgFilterAttribute` extension set, so downstream operator fields know
16
+ * which column they are operating on.
17
+ */
18
+ exports.ConnectionFilterAttributesPlugin = {
19
+ name: 'ConnectionFilterAttributesPlugin',
20
+ version,
21
+ description: 'Adds column-based filter fields to connection filter types',
22
+ schema: {
23
+ entityBehavior: {
24
+ pgCodecAttribute: 'attribute:filterBy',
25
+ },
26
+ hooks: {
27
+ GraphQLInputObjectType_fields(inFields, build, context) {
28
+ let fields = inFields;
29
+ const { inflection, connectionFilterOperatorsDigest, dataplanPg: { PgCondition }, EXPORTABLE, } = build;
30
+ const { fieldWithHooks, scope: { pgCodec: rawCodec, isPgConnectionFilter }, } = context;
31
+ if (!isPgConnectionFilter || !rawCodec || !rawCodec.attributes) {
32
+ return fields;
33
+ }
34
+ const codec = rawCodec;
35
+ for (const [attributeName, attribute] of Object.entries(codec.attributes)) {
36
+ if (!build.behavior.pgCodecAttributeMatches([codec, attributeName], 'attribute:filterBy')) {
37
+ continue;
38
+ }
39
+ const fieldName = inflection.attribute({ codec: codec, attributeName });
40
+ const colSpec = { fieldName, attributeName, attribute };
41
+ const digest = connectionFilterOperatorsDigest(attribute.codec);
42
+ if (!digest)
43
+ continue;
44
+ const OperatorsType = build.getTypeByName(digest.operatorsTypeName);
45
+ if (!OperatorsType)
46
+ continue;
47
+ const { connectionFilterAllowNullInput, } = build.options;
48
+ fields = build.extend(fields, {
49
+ [fieldName]: fieldWithHooks({
50
+ fieldName,
51
+ isPgConnectionFilterField: true,
52
+ }, () => ({
53
+ description: `Filter by the object\u2019s \`${fieldName}\` field.`,
54
+ type: OperatorsType,
55
+ apply: EXPORTABLE((PgCondition, colSpec, connectionFilterAllowNullInput, isEmpty) => function (queryBuilder, value) {
56
+ if (value === undefined) {
57
+ return;
58
+ }
59
+ if (isEmpty(value)) {
60
+ throw Object.assign(new Error('Empty objects are forbidden in filter argument input.'), {});
61
+ }
62
+ if (!connectionFilterAllowNullInput &&
63
+ value === null) {
64
+ throw Object.assign(new Error('Null literals are forbidden in filter argument input.'), {});
65
+ }
66
+ const condition = new PgCondition(queryBuilder);
67
+ condition.extensions.pgFilterAttribute = colSpec;
68
+ return condition;
69
+ }, [
70
+ PgCondition,
71
+ colSpec,
72
+ connectionFilterAllowNullInput,
73
+ utils_1.isEmpty,
74
+ ]),
75
+ })),
76
+ }, 'Adding attribute-based filtering');
77
+ }
78
+ return fields;
79
+ },
80
+ },
81
+ },
82
+ };
@@ -0,0 +1,33 @@
1
+ import '../augmentations';
2
+ import type { GraphileConfig } from 'graphile-config';
3
+ /**
4
+ * ConnectionFilterBackwardRelationsPlugin
5
+ *
6
+ * Adds backward relation filter fields to table filter types.
7
+ * A "backward" relation is one where another table has a FK referencing the current table.
8
+ *
9
+ * For unique backward relations (one-to-one), a single filter field is added:
10
+ * ```graphql
11
+ * allClients(filter: {
12
+ * profileByClientId: { bio: { includes: "engineer" } }
13
+ * }) { ... }
14
+ * ```
15
+ *
16
+ * For non-unique backward relations (one-to-many), a "many" filter type is added
17
+ * with `some`, `every`, and `none` sub-fields:
18
+ * ```graphql
19
+ * allClients(filter: {
20
+ * ordersByClientId: { some: { total: { greaterThan: 1000 } } }
21
+ * }) { ... }
22
+ * ```
23
+ *
24
+ * The SQL generated uses EXISTS subqueries:
25
+ * ```sql
26
+ * WHERE EXISTS (
27
+ * SELECT 1 FROM orders
28
+ * WHERE orders.client_id = clients.id
29
+ * AND <nested filter conditions>
30
+ * )
31
+ * ```
32
+ */
33
+ export declare const ConnectionFilterBackwardRelationsPlugin: GraphileConfig.Plugin;