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
@@ -0,0 +1,12 @@
1
+ import '../augmentations';
2
+ import type { GraphileConfig } from 'graphile-config';
3
+ /**
4
+ * ConnectionFilterTypesPlugin
5
+ *
6
+ * Registers the filter types in the schema:
7
+ * 1. Per-table filter types (e.g. UserFilter) - registered for every codec with attributes
8
+ * 2. Per-scalar operator types (e.g. StringFilter, IntFilter) - registered for each scalar type
9
+ *
10
+ * Also adds `connectionFilterOperatorsDigest` and `escapeLikeWildcards` to the build object.
11
+ */
12
+ export declare const ConnectionFilterTypesPlugin: GraphileConfig.Plugin;
@@ -0,0 +1,228 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ConnectionFilterTypesPlugin = void 0;
4
+ require("../augmentations");
5
+ const version = '1.0.0';
6
+ /**
7
+ * Determines if a codec is suitable for scalar/enum/domain/array filtering.
8
+ * Excludes void, composite types, anonymous types, and polymorphic types.
9
+ */
10
+ function isSuitableForFiltering(build, codec) {
11
+ return (codec !== build.dataplanPg.TYPES.void &&
12
+ !codec.attributes &&
13
+ !codec.isAnonymous &&
14
+ !codec.polymorphism &&
15
+ (!codec.arrayOfCodec || isSuitableForFiltering(build, codec.arrayOfCodec)) &&
16
+ (!codec.domainOfCodec || isSuitableForFiltering(build, codec.domainOfCodec)));
17
+ }
18
+ /**
19
+ * ConnectionFilterTypesPlugin
20
+ *
21
+ * Registers the filter types in the schema:
22
+ * 1. Per-table filter types (e.g. UserFilter) - registered for every codec with attributes
23
+ * 2. Per-scalar operator types (e.g. StringFilter, IntFilter) - registered for each scalar type
24
+ *
25
+ * Also adds `connectionFilterOperatorsDigest` and `escapeLikeWildcards` to the build object.
26
+ */
27
+ exports.ConnectionFilterTypesPlugin = {
28
+ name: 'ConnectionFilterTypesPlugin',
29
+ version,
30
+ description: 'Registers connection filter input types for tables and scalars',
31
+ schema: {
32
+ behaviorRegistry: {
33
+ add: {
34
+ filterProc: {
35
+ description: 'Can this function be filtered?',
36
+ entities: ['pgResource'],
37
+ },
38
+ filter: {
39
+ description: 'Can this table be filtered?',
40
+ entities: ['pgResource'],
41
+ },
42
+ },
43
+ },
44
+ entityBehavior: {
45
+ pgCodec: 'filter',
46
+ pgResource: {
47
+ inferred(behavior, entity, build) {
48
+ if (entity.parameters) {
49
+ return [
50
+ behavior,
51
+ build.options.connectionFilterSetofFunctions
52
+ ? 'filterProc'
53
+ : '-filterProc',
54
+ ];
55
+ }
56
+ else {
57
+ return ['filter', behavior];
58
+ }
59
+ },
60
+ },
61
+ },
62
+ hooks: {
63
+ build(build) {
64
+ const { inflection, options: { connectionFilterAllowedFieldTypes, connectionFilterArrays, }, EXPORTABLE, } = build;
65
+ // Add connectionFilterOperatorsDigest to the build object
66
+ build.connectionFilterOperatorsDigest = (codec) => {
67
+ const finalBuild = build;
68
+ const { dataplanPg: { getInnerCodec, TYPES, isEnumCodec }, } = finalBuild;
69
+ if (!isSuitableForFiltering(finalBuild, codec)) {
70
+ return null;
71
+ }
72
+ // Unwrap to the simple type
73
+ const pgSimpleCodec = getInnerCodec(codec);
74
+ if (!pgSimpleCodec)
75
+ return null;
76
+ if (pgSimpleCodec.polymorphism ||
77
+ pgSimpleCodec.attributes ||
78
+ pgSimpleCodec.isAnonymous) {
79
+ return null;
80
+ }
81
+ // json type has no valid operators (use jsonb instead)
82
+ if (pgSimpleCodec === TYPES.json) {
83
+ return null;
84
+ }
85
+ const itemCodec = codec.arrayOfCodec ?? codec;
86
+ const fieldTypeName = build.getGraphQLTypeNameByPgCodec(itemCodec, 'output');
87
+ if (!fieldTypeName)
88
+ return null;
89
+ const fieldTypeMeta = build.getTypeMetaByName(fieldTypeName);
90
+ if (!fieldTypeMeta)
91
+ return null;
92
+ const fieldInputTypeName = build.getGraphQLTypeNameByPgCodec(itemCodec, 'input');
93
+ if (!fieldInputTypeName)
94
+ return null;
95
+ const fieldInputTypeMeta = build.getTypeMetaByName(fieldInputTypeName);
96
+ if (!fieldInputTypeMeta)
97
+ return null;
98
+ // Skip unrecognized types that PostGraphile maps to String
99
+ const namedTypeName = fieldTypeName;
100
+ const namedInputTypeName = fieldInputTypeName;
101
+ const actualStringCodecs = [
102
+ TYPES.bpchar,
103
+ TYPES.char,
104
+ TYPES.name,
105
+ TYPES.text,
106
+ TYPES.varchar,
107
+ TYPES.citext,
108
+ ];
109
+ if (namedInputTypeName === 'String' &&
110
+ !actualStringCodecs.includes(pgSimpleCodec)) {
111
+ return null;
112
+ }
113
+ // Respect connectionFilterAllowedFieldTypes
114
+ if (connectionFilterAllowedFieldTypes &&
115
+ !connectionFilterAllowedFieldTypes.includes(namedTypeName)) {
116
+ return null;
117
+ }
118
+ // Respect connectionFilterArrays
119
+ const isArray = !!codec.arrayOfCodec;
120
+ if (isArray && !connectionFilterArrays) {
121
+ return null;
122
+ }
123
+ const listType = !!(codec.arrayOfCodec ||
124
+ codec.domainOfCodec?.arrayOfCodec ||
125
+ codec.rangeOfCodec?.arrayOfCodec);
126
+ const operatorsTypeName = listType
127
+ ? inflection.filterFieldListType(namedTypeName)
128
+ : inflection.filterFieldType(namedTypeName);
129
+ const rangeElementInputTypeName = codec.rangeOfCodec && !codec.rangeOfCodec.arrayOfCodec
130
+ ? build.getGraphQLTypeNameByPgCodec(codec.rangeOfCodec, 'input')
131
+ : null;
132
+ const domainBaseTypeName = codec.domainOfCodec && !codec.domainOfCodec.arrayOfCodec
133
+ ? build.getGraphQLTypeNameByPgCodec(codec.domainOfCodec, 'output')
134
+ : null;
135
+ return {
136
+ isList: listType,
137
+ operatorsTypeName,
138
+ relatedTypeName: namedTypeName,
139
+ inputTypeName: fieldInputTypeName,
140
+ rangeElementInputTypeName,
141
+ domainBaseTypeName,
142
+ };
143
+ };
144
+ // Add escapeLikeWildcards helper
145
+ build.escapeLikeWildcards = EXPORTABLE(() => function (input) {
146
+ if ('string' !== typeof input) {
147
+ throw new Error('Non-string input was provided to escapeLikeWildcards');
148
+ }
149
+ return input.split('%').join('\\%').split('_').join('\\_');
150
+ }, []);
151
+ return build;
152
+ },
153
+ init: {
154
+ after: ['PgCodecs'],
155
+ callback(_, build) {
156
+ const { inflection } = build;
157
+ // Register per-table filter types (e.g. UserFilter)
158
+ for (const pgCodec of build.allPgCodecs) {
159
+ if (!pgCodec.attributes) {
160
+ continue;
161
+ }
162
+ const nodeTypeName = build.getGraphQLTypeNameByPgCodec(pgCodec, 'output');
163
+ if (!nodeTypeName) {
164
+ continue;
165
+ }
166
+ const filterTypeName = inflection.filterType(nodeTypeName);
167
+ build.registerInputObjectType(filterTypeName, {
168
+ pgCodec,
169
+ isPgConnectionFilter: true,
170
+ }, () => ({
171
+ description: `A filter to be used against \`${nodeTypeName}\` object types. All fields are combined with a logical \u2018and.\u2019`,
172
+ }), 'ConnectionFilterTypesPlugin');
173
+ }
174
+ // Register per-scalar operator types (e.g. StringFilter, IntFilter)
175
+ const codecsByFilterTypeName = {};
176
+ for (const codec of build.allPgCodecs) {
177
+ const digest = build.connectionFilterOperatorsDigest(codec);
178
+ if (!digest)
179
+ continue;
180
+ const { isList, operatorsTypeName, relatedTypeName, inputTypeName, rangeElementInputTypeName, domainBaseTypeName, } = digest;
181
+ if (!codecsByFilterTypeName[operatorsTypeName]) {
182
+ codecsByFilterTypeName[operatorsTypeName] = {
183
+ isList,
184
+ relatedTypeName,
185
+ pgCodecs: [codec],
186
+ inputTypeName,
187
+ rangeElementInputTypeName,
188
+ domainBaseTypeName,
189
+ };
190
+ }
191
+ else {
192
+ // Validate consistency
193
+ for (const key of [
194
+ 'isList',
195
+ 'relatedTypeName',
196
+ 'inputTypeName',
197
+ 'rangeElementInputTypeName',
198
+ ]) {
199
+ if (digest[key] !==
200
+ codecsByFilterTypeName[operatorsTypeName][key]) {
201
+ throw new Error(`${key} mismatch: existing codecs (${codecsByFilterTypeName[operatorsTypeName].pgCodecs
202
+ .map((c) => c.name)
203
+ .join(', ')}) had ${key} = ${codecsByFilterTypeName[operatorsTypeName][key]}, but ${codec.name} instead has ${key} = ${digest[key]}`);
204
+ }
205
+ }
206
+ codecsByFilterTypeName[operatorsTypeName].pgCodecs.push(codec);
207
+ }
208
+ }
209
+ for (const [operatorsTypeName, { isList, relatedTypeName, pgCodecs, inputTypeName, rangeElementInputTypeName, domainBaseTypeName, },] of Object.entries(codecsByFilterTypeName)) {
210
+ build.registerInputObjectType(operatorsTypeName, {
211
+ pgConnectionFilterOperators: {
212
+ isList,
213
+ pgCodecs,
214
+ inputTypeName,
215
+ rangeElementInputTypeName,
216
+ domainBaseTypeName,
217
+ },
218
+ }, () => ({
219
+ name: operatorsTypeName,
220
+ description: `A filter to be used against ${relatedTypeName}${isList ? ' List' : ''} fields. All fields are combined with a logical \u2018and.\u2019`,
221
+ }), 'ConnectionFilterTypesPlugin');
222
+ }
223
+ return _;
224
+ },
225
+ },
226
+ },
227
+ },
228
+ };
@@ -0,0 +1,11 @@
1
+ export { ConnectionFilterInflectionPlugin } from './ConnectionFilterInflectionPlugin';
2
+ export { ConnectionFilterTypesPlugin } from './ConnectionFilterTypesPlugin';
3
+ export { ConnectionFilterArgPlugin } from './ConnectionFilterArgPlugin';
4
+ export { ConnectionFilterAttributesPlugin } from './ConnectionFilterAttributesPlugin';
5
+ export { ConnectionFilterOperatorsPlugin } from './ConnectionFilterOperatorsPlugin';
6
+ export { ConnectionFilterCustomOperatorsPlugin } from './ConnectionFilterCustomOperatorsPlugin';
7
+ export { ConnectionFilterLogicalOperatorsPlugin } from './ConnectionFilterLogicalOperatorsPlugin';
8
+ export { ConnectionFilterComputedAttributesPlugin } from './ConnectionFilterComputedAttributesPlugin';
9
+ export { ConnectionFilterForwardRelationsPlugin } from './ConnectionFilterForwardRelationsPlugin';
10
+ export { ConnectionFilterBackwardRelationsPlugin } from './ConnectionFilterBackwardRelationsPlugin';
11
+ export { makeApplyFromOperatorSpec } from './operatorApply';
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.makeApplyFromOperatorSpec = exports.ConnectionFilterBackwardRelationsPlugin = exports.ConnectionFilterForwardRelationsPlugin = exports.ConnectionFilterComputedAttributesPlugin = exports.ConnectionFilterLogicalOperatorsPlugin = exports.ConnectionFilterCustomOperatorsPlugin = exports.ConnectionFilterOperatorsPlugin = exports.ConnectionFilterAttributesPlugin = exports.ConnectionFilterArgPlugin = exports.ConnectionFilterTypesPlugin = exports.ConnectionFilterInflectionPlugin = void 0;
4
+ var ConnectionFilterInflectionPlugin_1 = require("./ConnectionFilterInflectionPlugin");
5
+ Object.defineProperty(exports, "ConnectionFilterInflectionPlugin", { enumerable: true, get: function () { return ConnectionFilterInflectionPlugin_1.ConnectionFilterInflectionPlugin; } });
6
+ var ConnectionFilterTypesPlugin_1 = require("./ConnectionFilterTypesPlugin");
7
+ Object.defineProperty(exports, "ConnectionFilterTypesPlugin", { enumerable: true, get: function () { return ConnectionFilterTypesPlugin_1.ConnectionFilterTypesPlugin; } });
8
+ var ConnectionFilterArgPlugin_1 = require("./ConnectionFilterArgPlugin");
9
+ Object.defineProperty(exports, "ConnectionFilterArgPlugin", { enumerable: true, get: function () { return ConnectionFilterArgPlugin_1.ConnectionFilterArgPlugin; } });
10
+ var ConnectionFilterAttributesPlugin_1 = require("./ConnectionFilterAttributesPlugin");
11
+ Object.defineProperty(exports, "ConnectionFilterAttributesPlugin", { enumerable: true, get: function () { return ConnectionFilterAttributesPlugin_1.ConnectionFilterAttributesPlugin; } });
12
+ var ConnectionFilterOperatorsPlugin_1 = require("./ConnectionFilterOperatorsPlugin");
13
+ Object.defineProperty(exports, "ConnectionFilterOperatorsPlugin", { enumerable: true, get: function () { return ConnectionFilterOperatorsPlugin_1.ConnectionFilterOperatorsPlugin; } });
14
+ var ConnectionFilterCustomOperatorsPlugin_1 = require("./ConnectionFilterCustomOperatorsPlugin");
15
+ Object.defineProperty(exports, "ConnectionFilterCustomOperatorsPlugin", { enumerable: true, get: function () { return ConnectionFilterCustomOperatorsPlugin_1.ConnectionFilterCustomOperatorsPlugin; } });
16
+ var ConnectionFilterLogicalOperatorsPlugin_1 = require("./ConnectionFilterLogicalOperatorsPlugin");
17
+ Object.defineProperty(exports, "ConnectionFilterLogicalOperatorsPlugin", { enumerable: true, get: function () { return ConnectionFilterLogicalOperatorsPlugin_1.ConnectionFilterLogicalOperatorsPlugin; } });
18
+ var ConnectionFilterComputedAttributesPlugin_1 = require("./ConnectionFilterComputedAttributesPlugin");
19
+ Object.defineProperty(exports, "ConnectionFilterComputedAttributesPlugin", { enumerable: true, get: function () { return ConnectionFilterComputedAttributesPlugin_1.ConnectionFilterComputedAttributesPlugin; } });
20
+ var ConnectionFilterForwardRelationsPlugin_1 = require("./ConnectionFilterForwardRelationsPlugin");
21
+ Object.defineProperty(exports, "ConnectionFilterForwardRelationsPlugin", { enumerable: true, get: function () { return ConnectionFilterForwardRelationsPlugin_1.ConnectionFilterForwardRelationsPlugin; } });
22
+ var ConnectionFilterBackwardRelationsPlugin_1 = require("./ConnectionFilterBackwardRelationsPlugin");
23
+ Object.defineProperty(exports, "ConnectionFilterBackwardRelationsPlugin", { enumerable: true, get: function () { return ConnectionFilterBackwardRelationsPlugin_1.ConnectionFilterBackwardRelationsPlugin; } });
24
+ var operatorApply_1 = require("./operatorApply");
25
+ Object.defineProperty(exports, "makeApplyFromOperatorSpec", { enumerable: true, get: function () { return operatorApply_1.makeApplyFromOperatorSpec; } });
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Creates an `apply` function for a filter operator field.
3
+ *
4
+ * This is the core execution logic: given a PgCondition ($where) and an input value,
5
+ * it resolves the SQL identifier, SQL value, and calls the operator's resolve function
6
+ * to produce a SQL WHERE clause fragment, then applies it via `$where.where(fragment)`.
7
+ *
8
+ * The `pgFilterAttribute` extension on $where provides the context about which
9
+ * column is being filtered, including the attribute name, codec, and any expression.
10
+ */
11
+ export declare function makeApplyFromOperatorSpec(build: any, _typeName: string, fieldName: string, spec: any, _type: any): any;
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.makeApplyFromOperatorSpec = makeApplyFromOperatorSpec;
4
+ /**
5
+ * Creates an `apply` function for a filter operator field.
6
+ *
7
+ * This is the core execution logic: given a PgCondition ($where) and an input value,
8
+ * it resolves the SQL identifier, SQL value, and calls the operator's resolve function
9
+ * to produce a SQL WHERE clause fragment, then applies it via `$where.where(fragment)`.
10
+ *
11
+ * The `pgFilterAttribute` extension on $where provides the context about which
12
+ * column is being filtered, including the attribute name, codec, and any expression.
13
+ */
14
+ function makeApplyFromOperatorSpec(build, _typeName, fieldName, spec, _type) {
15
+ const { sql, dataplanPg: { TYPES, sqlValueWithCodec }, EXPORTABLE, } = build;
16
+ const { resolve, resolveInput, resolveSqlIdentifier, resolveSqlValue, resolveInputCodec, } = spec;
17
+ const { options: { connectionFilterAllowNullInput } } = build;
18
+ return EXPORTABLE((connectionFilterAllowNullInput, fieldName, resolve, resolveInput, resolveInputCodec, resolveSqlIdentifier, resolveSqlValue, sql, sqlValueWithCodec) => function ($where, value) {
19
+ if (!$where.extensions?.pgFilterAttribute) {
20
+ throw new Error("Planning error: expected 'pgFilterAttribute' to be present on the $where plan's extensions; your extensions to the connection filter plugin do not implement the required interfaces.");
21
+ }
22
+ if (value === undefined) {
23
+ return;
24
+ }
25
+ const { fieldName: parentFieldName, attributeName, attribute, codec, expression, } = $where.extensions.pgFilterAttribute;
26
+ // Determine the SQL expression for the column
27
+ const sourceAlias = attribute
28
+ ? attribute.expression
29
+ ? attribute.expression($where.alias)
30
+ : sql `${$where.alias}.${sql.identifier(attributeName)}`
31
+ : expression
32
+ ? expression
33
+ : $where.alias;
34
+ const sourceCodec = codec ?? attribute.codec;
35
+ // Optionally override the SQL identifier (e.g. cast citext to text)
36
+ const [sqlIdentifier, identifierCodec] = resolveSqlIdentifier
37
+ ? resolveSqlIdentifier(sourceAlias, sourceCodec)
38
+ : [sourceAlias, sourceCodec];
39
+ // Handle null input
40
+ if (connectionFilterAllowNullInput && value === null) {
41
+ return;
42
+ }
43
+ if (!connectionFilterAllowNullInput && value === null) {
44
+ throw Object.assign(new Error('Null literals are forbidden in filter argument input.'), {});
45
+ }
46
+ // Optionally transform the input value
47
+ const resolvedInput = resolveInput ? resolveInput(value) : value;
48
+ // Determine the input codec
49
+ const inputCodec = resolveInputCodec
50
+ ? resolveInputCodec(codec ?? attribute.codec)
51
+ : codec ?? attribute.codec;
52
+ // Generate the SQL value
53
+ const sqlValue = resolveSqlValue
54
+ ? resolveSqlValue($where, value, inputCodec)
55
+ : sqlValueWithCodec(resolvedInput, inputCodec);
56
+ // Generate the WHERE clause fragment and apply it
57
+ const fragment = resolve(sqlIdentifier, sqlValue, value, $where, {
58
+ fieldName: parentFieldName ?? null,
59
+ operatorName: fieldName,
60
+ });
61
+ $where.where(fragment);
62
+ }, [
63
+ connectionFilterAllowNullInput,
64
+ fieldName,
65
+ resolve,
66
+ resolveInput,
67
+ resolveInputCodec,
68
+ resolveSqlIdentifier,
69
+ resolveSqlValue,
70
+ sql,
71
+ sqlValueWithCodec,
72
+ ]);
73
+ }
package/preset.d.ts ADDED
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Connection Filter Preset
3
+ *
4
+ * Provides a convenient preset factory for including connection filtering
5
+ * in PostGraphile v5.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import { ConnectionFilterPreset } from 'graphile-connection-filter';
10
+ *
11
+ * const preset = {
12
+ * extends: [
13
+ * ConnectionFilterPreset(),
14
+ * // or with options:
15
+ * ConnectionFilterPreset({
16
+ * connectionFilterLogicalOperators: true,
17
+ * connectionFilterArrays: true,
18
+ * }),
19
+ * ],
20
+ * };
21
+ * ```
22
+ */
23
+ import './augmentations';
24
+ import type { GraphileConfig } from 'graphile-config';
25
+ import type { ConnectionFilterOptions } from './types';
26
+ /**
27
+ * Creates a preset that includes the connection filter plugins with the given options.
28
+ *
29
+ * All plugins are always included. Use the schema options to control behavior:
30
+ * - `connectionFilterLogicalOperators: false` to exclude and/or/not
31
+ * - `connectionFilterArrays: false` to exclude array type filters
32
+ * - `connectionFilterAllowNullInput: true` to allow null literals
33
+ */
34
+ export declare function ConnectionFilterPreset(options?: ConnectionFilterOptions): GraphileConfig.Preset;
35
+ export default ConnectionFilterPreset;
package/preset.js ADDED
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ /**
3
+ * Connection Filter Preset
4
+ *
5
+ * Provides a convenient preset factory for including connection filtering
6
+ * in PostGraphile v5.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * import { ConnectionFilterPreset } from 'graphile-connection-filter';
11
+ *
12
+ * const preset = {
13
+ * extends: [
14
+ * ConnectionFilterPreset(),
15
+ * // or with options:
16
+ * ConnectionFilterPreset({
17
+ * connectionFilterLogicalOperators: true,
18
+ * connectionFilterArrays: true,
19
+ * }),
20
+ * ],
21
+ * };
22
+ * ```
23
+ */
24
+ Object.defineProperty(exports, "__esModule", { value: true });
25
+ exports.ConnectionFilterPreset = ConnectionFilterPreset;
26
+ require("./augmentations");
27
+ const plugins_1 = require("./plugins");
28
+ /**
29
+ * Default schema options for the connection filter.
30
+ */
31
+ const defaultSchemaOptions = {
32
+ connectionFilterArgumentName: 'where',
33
+ connectionFilterArrays: true,
34
+ connectionFilterLogicalOperators: true,
35
+ connectionFilterAllowNullInput: false,
36
+ connectionFilterSetofFunctions: true,
37
+ connectionFilterComputedColumns: true,
38
+ connectionFilterRelations: false,
39
+ connectionFilterRelationsRequireIndex: true,
40
+ };
41
+ /**
42
+ * Creates a preset that includes the connection filter plugins with the given options.
43
+ *
44
+ * All plugins are always included. Use the schema options to control behavior:
45
+ * - `connectionFilterLogicalOperators: false` to exclude and/or/not
46
+ * - `connectionFilterArrays: false` to exclude array type filters
47
+ * - `connectionFilterAllowNullInput: true` to allow null literals
48
+ */
49
+ function ConnectionFilterPreset(options = {}) {
50
+ const mergedOptions = { ...defaultSchemaOptions, ...options };
51
+ const plugins = [
52
+ plugins_1.ConnectionFilterInflectionPlugin,
53
+ plugins_1.ConnectionFilterTypesPlugin,
54
+ plugins_1.ConnectionFilterArgPlugin,
55
+ plugins_1.ConnectionFilterAttributesPlugin,
56
+ plugins_1.ConnectionFilterOperatorsPlugin,
57
+ plugins_1.ConnectionFilterCustomOperatorsPlugin,
58
+ ];
59
+ // Logical operators plugin always included — checks
60
+ // build.options.connectionFilterLogicalOperators at runtime
61
+ plugins.push(plugins_1.ConnectionFilterLogicalOperatorsPlugin);
62
+ // Computed columns are opt-in (disabled by default)
63
+ if (mergedOptions.connectionFilterComputedColumns) {
64
+ plugins.push(plugins_1.ConnectionFilterComputedAttributesPlugin);
65
+ }
66
+ // Relation filter plugins are always included but check
67
+ // build.options.connectionFilterRelations at runtime
68
+ plugins.push(plugins_1.ConnectionFilterForwardRelationsPlugin);
69
+ plugins.push(plugins_1.ConnectionFilterBackwardRelationsPlugin);
70
+ return {
71
+ plugins,
72
+ schema: mergedOptions,
73
+ };
74
+ }
75
+ exports.default = ConnectionFilterPreset;
package/types.d.ts ADDED
@@ -0,0 +1,146 @@
1
+ import type { SQL } from 'pg-sql2';
2
+ import type { GraphQLInputType } from 'graphql';
3
+ /**
4
+ * Symbol used to store the filter operator registry on the build object.
5
+ */
6
+ export declare const $$filters: unique symbol;
7
+ /**
8
+ * Specification for a filter operator.
9
+ *
10
+ * This is the contract that satellite plugins (PostGIS, search, pgvector, etc.)
11
+ * use when calling `addConnectionFilterOperator`.
12
+ */
13
+ export interface ConnectionFilterOperatorSpec {
14
+ /** Human-readable description for the GraphQL schema. */
15
+ description?: string;
16
+ /**
17
+ * Core resolve function: given the SQL identifier for the column and the SQL
18
+ * value for the user input, return a SQL fragment for the WHERE clause.
19
+ */
20
+ resolve: (sqlIdentifier: SQL, sqlValue: SQL, input: unknown, $where: any, details: {
21
+ fieldName: string | null;
22
+ operatorName: string;
23
+ }) => SQL;
24
+ /**
25
+ * Optional: transform the user input before encoding to SQL.
26
+ * e.g. wrapping a LIKE pattern with `%`.
27
+ */
28
+ resolveInput?: (input: unknown) => unknown;
29
+ /**
30
+ * Optional: override the codec used for encoding the input value to SQL.
31
+ * Receives the column's codec and returns the codec to use for the input.
32
+ */
33
+ resolveInputCodec?: (codec: any) => any;
34
+ /**
35
+ * Optional: override the SQL identifier and its codec.
36
+ * Useful for casting columns (e.g. citext -> text for case-sensitive matching).
37
+ */
38
+ resolveSqlIdentifier?: (sqlIdentifier: SQL, codec: any) => [SQL, any];
39
+ /**
40
+ * Optional: completely override how the SQL value is generated.
41
+ * Receives the $where plan, the raw input value, and the resolved input codec.
42
+ */
43
+ resolveSqlValue?: ($where: any, value: unknown, inputCodec: any) => SQL;
44
+ /**
45
+ * Optional: override the GraphQL input type for this operator.
46
+ * Receives the default field input type and returns the type to use.
47
+ */
48
+ resolveType?: (fieldType: GraphQLInputType) => GraphQLInputType;
49
+ }
50
+ /**
51
+ * A single operator registration returned by a factory function.
52
+ * Declares which type(s) the operator applies to, its name, and its spec.
53
+ */
54
+ export interface ConnectionFilterOperatorRegistration {
55
+ /** The GraphQL type name(s) this operator applies to (e.g. 'String', 'Int', or an array). */
56
+ typeNames: string | string[];
57
+ /** The operator field name (e.g. 'similarTo', 'matches', 'intersects'). */
58
+ operatorName: string;
59
+ /** The operator specification (resolve function, description, etc.). */
60
+ spec: ConnectionFilterOperatorSpec;
61
+ }
62
+ /**
63
+ * A factory function that receives the build object and returns operator registrations.
64
+ * This is the declarative replacement for `build.addConnectionFilterOperator()`.
65
+ *
66
+ * Satellite plugins declare these in their preset's `connectionFilterOperatorFactories`
67
+ * option. The connection filter plugin processes them during its own init hook,
68
+ * eliminating timing dependencies between plugins.
69
+ *
70
+ * @example
71
+ * ```typescript
72
+ * const myFactory: ConnectionFilterOperatorFactory = (build) => [
73
+ * {
74
+ * typeNames: 'String',
75
+ * operatorName: 'similarTo',
76
+ * spec: {
77
+ * description: 'Fuzzy match',
78
+ * resolve: (i, v) => build.sql`similarity(${i}, ${v}) > 0.3`,
79
+ * },
80
+ * },
81
+ * ];
82
+ * ```
83
+ */
84
+ export type ConnectionFilterOperatorFactory = (build: any) => ConnectionFilterOperatorRegistration[];
85
+ /**
86
+ * Configuration options for the connection filter preset.
87
+ */
88
+ export interface ConnectionFilterOptions {
89
+ /** The GraphQL argument name for the filter. Default: 'where' */
90
+ connectionFilterArgumentName?: string;
91
+ /** Allow filtering on array columns. Default: true */
92
+ connectionFilterArrays?: boolean;
93
+ /** Include logical operators (and/or/not). Default: true */
94
+ connectionFilterLogicalOperators?: boolean;
95
+ /** Allow null literals in filter input. Default: false */
96
+ connectionFilterAllowNullInput?: boolean;
97
+ /** Restrict which field types can be filtered. Default: all types */
98
+ connectionFilterAllowedFieldTypes?: string[];
99
+ /** Restrict which operators are available. Default: all operators */
100
+ connectionFilterAllowedOperators?: string[];
101
+ /** Rename operators. Keys are default names, values are custom names. */
102
+ connectionFilterOperatorNames?: Record<string, string>;
103
+ /** Allow filtering on setof functions. Default: true */
104
+ connectionFilterSetofFunctions?: boolean;
105
+ /** Allow filtering on computed columns. Default: true */
106
+ connectionFilterComputedColumns?: boolean;
107
+ /** Allow filtering on related tables via FK relationships. Default: false */
108
+ connectionFilterRelations?: boolean;
109
+ /** Only create relation filter fields for FKs with supporting indexes.
110
+ * Prevents EXISTS subqueries that would cause sequential scans on large tables.
111
+ * Default: true */
112
+ connectionFilterRelationsRequireIndex?: boolean;
113
+ /**
114
+ * Declarative operator factories. Each factory receives the build object and
115
+ * returns an array of operator registrations. This replaces the imperative
116
+ * `build.addConnectionFilterOperator()` API.
117
+ *
118
+ * Satellite plugins (PostGIS, search, pg_trgm, etc.) declare their operators
119
+ * here instead of calling `addConnectionFilterOperator` in an init hook.
120
+ * The connection filter plugin processes all factories during its own init,
121
+ * eliminating timing/ordering dependencies.
122
+ */
123
+ connectionFilterOperatorFactories?: ConnectionFilterOperatorFactory[];
124
+ }
125
+ /**
126
+ * Digest of operator type information for a given codec.
127
+ * Used to determine which filter type (e.g. StringFilter, IntFilter) to create.
128
+ */
129
+ export interface ConnectionFilterOperatorsDigest {
130
+ isList: boolean;
131
+ operatorsTypeName: string;
132
+ relatedTypeName: string;
133
+ inputTypeName: string;
134
+ rangeElementInputTypeName: string | null;
135
+ domainBaseTypeName: string | null;
136
+ }
137
+ /**
138
+ * Scope metadata stored on operator filter types (e.g. StringFilter, IntFilter).
139
+ */
140
+ export interface PgConnectionFilterOperatorsScope {
141
+ isList: boolean;
142
+ pgCodecs: any[];
143
+ inputTypeName: string;
144
+ rangeElementInputTypeName: string | null;
145
+ domainBaseTypeName: string | null;
146
+ }
package/types.js ADDED
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.$$filters = void 0;
4
+ /**
5
+ * Symbol used to store the filter operator registry on the build object.
6
+ */
7
+ exports.$$filters = Symbol('connectionFilters');