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.
- package/LICENSE +23 -0
- package/README.md +107 -0
- package/augmentations.d.ts +104 -0
- package/augmentations.js +11 -0
- package/esm/augmentations.d.ts +104 -0
- package/esm/augmentations.js +9 -0
- package/esm/index.d.ts +55 -0
- package/esm/index.js +56 -0
- package/esm/plugins/ConnectionFilterArgPlugin.d.ts +13 -0
- package/esm/plugins/ConnectionFilterArgPlugin.js +96 -0
- package/esm/plugins/ConnectionFilterAttributesPlugin.d.ts +14 -0
- package/esm/plugins/ConnectionFilterAttributesPlugin.js +79 -0
- package/esm/plugins/ConnectionFilterBackwardRelationsPlugin.d.ts +33 -0
- package/esm/plugins/ConnectionFilterBackwardRelationsPlugin.js +398 -0
- package/esm/plugins/ConnectionFilterComputedAttributesPlugin.d.ts +19 -0
- package/esm/plugins/ConnectionFilterComputedAttributesPlugin.js +133 -0
- package/esm/plugins/ConnectionFilterCustomOperatorsPlugin.d.ts +35 -0
- package/esm/plugins/ConnectionFilterCustomOperatorsPlugin.js +129 -0
- package/esm/plugins/ConnectionFilterForwardRelationsPlugin.d.ts +28 -0
- package/esm/plugins/ConnectionFilterForwardRelationsPlugin.js +168 -0
- package/esm/plugins/ConnectionFilterInflectionPlugin.d.ts +11 -0
- package/esm/plugins/ConnectionFilterInflectionPlugin.js +27 -0
- package/esm/plugins/ConnectionFilterLogicalOperatorsPlugin.d.ts +15 -0
- package/esm/plugins/ConnectionFilterLogicalOperatorsPlugin.js +86 -0
- package/esm/plugins/ConnectionFilterOperatorsPlugin.d.ts +21 -0
- package/esm/plugins/ConnectionFilterOperatorsPlugin.js +677 -0
- package/esm/plugins/ConnectionFilterTypesPlugin.d.ts +12 -0
- package/esm/plugins/ConnectionFilterTypesPlugin.js +225 -0
- package/esm/plugins/index.d.ts +11 -0
- package/esm/plugins/index.js +11 -0
- package/esm/plugins/operatorApply.d.ts +11 -0
- package/esm/plugins/operatorApply.js +70 -0
- package/esm/preset.d.ts +35 -0
- package/esm/preset.js +72 -0
- package/esm/types.d.ts +146 -0
- package/esm/types.js +4 -0
- package/esm/utils.d.ts +44 -0
- package/esm/utils.js +112 -0
- package/index.d.ts +55 -0
- package/index.js +77 -0
- package/package.json +58 -0
- package/plugins/ConnectionFilterArgPlugin.d.ts +13 -0
- package/plugins/ConnectionFilterArgPlugin.js +99 -0
- package/plugins/ConnectionFilterAttributesPlugin.d.ts +14 -0
- package/plugins/ConnectionFilterAttributesPlugin.js +82 -0
- package/plugins/ConnectionFilterBackwardRelationsPlugin.d.ts +33 -0
- package/plugins/ConnectionFilterBackwardRelationsPlugin.js +401 -0
- package/plugins/ConnectionFilterComputedAttributesPlugin.d.ts +19 -0
- package/plugins/ConnectionFilterComputedAttributesPlugin.js +136 -0
- package/plugins/ConnectionFilterCustomOperatorsPlugin.d.ts +35 -0
- package/plugins/ConnectionFilterCustomOperatorsPlugin.js +132 -0
- package/plugins/ConnectionFilterForwardRelationsPlugin.d.ts +28 -0
- package/plugins/ConnectionFilterForwardRelationsPlugin.js +171 -0
- package/plugins/ConnectionFilterInflectionPlugin.d.ts +11 -0
- package/plugins/ConnectionFilterInflectionPlugin.js +30 -0
- package/plugins/ConnectionFilterLogicalOperatorsPlugin.d.ts +15 -0
- package/plugins/ConnectionFilterLogicalOperatorsPlugin.js +89 -0
- package/plugins/ConnectionFilterOperatorsPlugin.d.ts +21 -0
- package/plugins/ConnectionFilterOperatorsPlugin.js +680 -0
- package/plugins/ConnectionFilterTypesPlugin.d.ts +12 -0
- package/plugins/ConnectionFilterTypesPlugin.js +228 -0
- package/plugins/index.d.ts +11 -0
- package/plugins/index.js +25 -0
- package/plugins/operatorApply.d.ts +11 -0
- package/plugins/operatorApply.js +73 -0
- package/preset.d.ts +35 -0
- package/preset.js +75 -0
- package/types.d.ts +146 -0
- package/types.js +7 -0
- package/utils.d.ts +44 -0
- 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;
|