graphile-pg-aggregates 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 (63) hide show
  1. package/AddAggregateTypesPlugin.d.ts +38 -0
  2. package/AddAggregateTypesPlugin.js +268 -0
  3. package/AddConnectionAggregatesPlugin.d.ts +2 -0
  4. package/AddConnectionAggregatesPlugin.js +56 -0
  5. package/AddConnectionGroupedAggregatesPlugin.d.ts +10 -0
  6. package/AddConnectionGroupedAggregatesPlugin.js +118 -0
  7. package/AddGroupByAggregateEnumValuesForAttributesPlugin.d.ts +10 -0
  8. package/AddGroupByAggregateEnumValuesForAttributesPlugin.js +116 -0
  9. package/AddGroupByAggregateEnumsPlugin.d.ts +2 -0
  10. package/AddGroupByAggregateEnumsPlugin.js +51 -0
  11. package/AddHavingAggregateTypesPlugin.d.ts +22 -0
  12. package/AddHavingAggregateTypesPlugin.js +449 -0
  13. package/AggregateSpecsPlugin.d.ts +1 -0
  14. package/AggregateSpecsPlugin.js +206 -0
  15. package/AggregatesSmartTagsPlugin.d.ts +8 -0
  16. package/AggregatesSmartTagsPlugin.js +65 -0
  17. package/EXPORTABLE.d.ts +1 -0
  18. package/EXPORTABLE.js +15 -0
  19. package/FilterRelationalAggregatesPlugin.d.ts +74 -0
  20. package/FilterRelationalAggregatesPlugin.js +443 -0
  21. package/InflectionPlugin.d.ts +67 -0
  22. package/InflectionPlugin.js +71 -0
  23. package/LICENSE +23 -0
  24. package/OrderByAggregatesPlugin.d.ts +31 -0
  25. package/OrderByAggregatesPlugin.js +205 -0
  26. package/README.md +128 -0
  27. package/esm/AddAggregateTypesPlugin.d.ts +38 -0
  28. package/esm/AddAggregateTypesPlugin.js +265 -0
  29. package/esm/AddConnectionAggregatesPlugin.d.ts +2 -0
  30. package/esm/AddConnectionAggregatesPlugin.js +53 -0
  31. package/esm/AddConnectionGroupedAggregatesPlugin.d.ts +10 -0
  32. package/esm/AddConnectionGroupedAggregatesPlugin.js +115 -0
  33. package/esm/AddGroupByAggregateEnumValuesForAttributesPlugin.d.ts +10 -0
  34. package/esm/AddGroupByAggregateEnumValuesForAttributesPlugin.js +113 -0
  35. package/esm/AddGroupByAggregateEnumsPlugin.d.ts +2 -0
  36. package/esm/AddGroupByAggregateEnumsPlugin.js +48 -0
  37. package/esm/AddHavingAggregateTypesPlugin.d.ts +22 -0
  38. package/esm/AddHavingAggregateTypesPlugin.js +446 -0
  39. package/esm/AggregateSpecsPlugin.d.ts +1 -0
  40. package/esm/AggregateSpecsPlugin.js +203 -0
  41. package/esm/AggregatesSmartTagsPlugin.d.ts +8 -0
  42. package/esm/AggregatesSmartTagsPlugin.js +62 -0
  43. package/esm/EXPORTABLE.d.ts +1 -0
  44. package/esm/EXPORTABLE.js +12 -0
  45. package/esm/FilterRelationalAggregatesPlugin.d.ts +74 -0
  46. package/esm/FilterRelationalAggregatesPlugin.js +440 -0
  47. package/esm/InflectionPlugin.d.ts +67 -0
  48. package/esm/InflectionPlugin.js +68 -0
  49. package/esm/OrderByAggregatesPlugin.d.ts +31 -0
  50. package/esm/OrderByAggregatesPlugin.js +202 -0
  51. package/esm/index.d.ts +42 -0
  52. package/esm/index.js +37 -0
  53. package/esm/interfaces.d.ts +145 -0
  54. package/esm/interfaces.js +44 -0
  55. package/esm/utils.d.ts +23 -0
  56. package/esm/utils.js +22 -0
  57. package/index.d.ts +42 -0
  58. package/index.js +40 -0
  59. package/interfaces.d.ts +145 -0
  60. package/interfaces.js +47 -0
  61. package/package.json +61 -0
  62. package/utils.d.ts +23 -0
  63. package/utils.js +25 -0
@@ -0,0 +1,62 @@
1
+ const version = '1.0.0';
2
+ const EMPTY_OBJECT = Object.freeze({});
3
+ export const PgAggregatesSmartTagsPlugin = {
4
+ name: 'PgAggregatesSmartTagsPlugin',
5
+ description: 'For compatibility with PostGraphile v4 schemas, this plugin attempts to convert `@aggregates` V4 smart tags to V5 behaviors',
6
+ version,
7
+ before: [
8
+ 'PgAggregatesAddAggregateTypesPlugin',
9
+ 'PgAggregatesFilterRelationalAggregatesPlugin',
10
+ 'PgAggregatesOrderByAggregatesPlugin'
11
+ ],
12
+ provides: ['smart-tags'],
13
+ gather: {
14
+ namespace: 'pgV4AggregatesSmartTags',
15
+ initialCache() {
16
+ return EMPTY_OBJECT;
17
+ },
18
+ initialState() {
19
+ return EMPTY_OBJECT;
20
+ },
21
+ helpers: {},
22
+ hooks: {
23
+ // Run in the 'introspection' phase before anything uses the tags
24
+ pgIntrospection_introspection(info, event) {
25
+ for (const pgClass of event.introspection.classes) {
26
+ processTags(pgClass.getTags());
27
+ }
28
+ }
29
+ }
30
+ }
31
+ };
32
+ function processTags(tags) {
33
+ switch (tags?.aggregates) {
34
+ case 'on':
35
+ addBehaviorToTags(tags, '+aggregates +aggregates:filterBy +aggregates:orderBy +aggregate +aggregate:filterBy +aggregate:orderBy');
36
+ break;
37
+ case 'off':
38
+ addBehaviorToTags(tags, '-aggregates -aggregates:filterBy -aggregates:orderBy -aggregate -aggregate:filterBy -aggregate:orderBy');
39
+ break;
40
+ }
41
+ }
42
+ function addBehaviorToTags(tags, behavior, prepend = false) {
43
+ if (Array.isArray(tags.behavior)) {
44
+ if (prepend) {
45
+ tags.behavior = [behavior, ...tags.behavior];
46
+ }
47
+ else {
48
+ tags.behavior = [...tags.behavior, behavior];
49
+ }
50
+ }
51
+ else if (typeof tags.behavior === 'string') {
52
+ tags.behavior = prepend
53
+ ? [behavior, tags.behavior]
54
+ : [tags.behavior, behavior];
55
+ }
56
+ else if (!tags.behavior) {
57
+ tags.behavior = [behavior];
58
+ }
59
+ else {
60
+ throw new Error(`Did not understand tags.behavior - it wasn't an array or a string`);
61
+ }
62
+ }
@@ -0,0 +1 @@
1
+ export declare function EXPORTABLE<T, TScope extends any[]>(factory: (...args: TScope) => T, args: [...TScope], nameHint?: string): T;
@@ -0,0 +1,12 @@
1
+ export function EXPORTABLE(factory, args, nameHint) {
2
+ const fn = factory(...args);
3
+ if (((typeof fn === 'object' && fn !== null) || typeof fn === 'function') &&
4
+ !('$exporter$factory' in fn)) {
5
+ Object.defineProperties(fn, {
6
+ $exporter$args: { value: args },
7
+ $exporter$factory: { value: factory },
8
+ $exporter$name: { writable: true, value: nameHint }
9
+ });
10
+ }
11
+ return fn;
12
+ }
@@ -0,0 +1,74 @@
1
+ import type { PgCodec, PgConditionCapableParent, PgWhereConditionSpec, sql } from '@dataplan/pg';
2
+ import type { Modifier } from 'grafast';
3
+ import type { AggregateSpec } from './interfaces';
4
+ type PgSQL = typeof sql;
5
+ type SQL = ReturnType<PgSQL>;
6
+ declare global {
7
+ namespace DataplanPg {
8
+ interface PgConditionExtensions {
9
+ pgFilterAttribute?: {
10
+ fieldName?: string;
11
+ codec: PgCodec;
12
+ expression?: SQL;
13
+ };
14
+ }
15
+ }
16
+ namespace GraphileBuild {
17
+ interface Build {
18
+ PgAggregateCondition: PgAggregateConditionClass;
19
+ PgAggregateConditionExpression: PgAggregateConditionExpressionClass;
20
+ }
21
+ interface BehaviorStrings {
22
+ 'resource:aggregates:filterBy': true;
23
+ 'aggregates:filterBy': true;
24
+ 'aggregate:filterBy': true;
25
+ 'sum:resource:aggregates:filterBy': true;
26
+ 'distinctCount:resource:aggregates:filterBy': true;
27
+ 'min:resource:aggregates:filterBy': true;
28
+ 'max:resource:aggregates:filterBy': true;
29
+ 'average:resource:aggregates:filterBy': true;
30
+ 'stddevSample:resource:aggregates:filterBy': true;
31
+ 'stddevPopulation:resource:aggregates:filterBy': true;
32
+ 'varianceSample:resource:aggregates:filterBy': true;
33
+ 'variancePopulation:resource:aggregates:filterBy': true;
34
+ 'sum:attribute:aggregate:filterBy': true;
35
+ 'distinctCount:attribute:aggregate:filterBy': true;
36
+ 'min:attribute:aggregate:filterBy': true;
37
+ 'max:attribute:aggregate:filterBy': true;
38
+ 'average:attribute:aggregate:filterBy': true;
39
+ 'stddevSample:attribute:aggregate:filterBy': true;
40
+ 'stddevPopulation:attribute:aggregate:filterBy': true;
41
+ 'varianceSample:attribute:aggregate:filterBy': true;
42
+ 'variancePopulation:attribute:aggregate:filterBy': true;
43
+ }
44
+ }
45
+ }
46
+ export declare const Plugin: GraphileConfig.Plugin;
47
+ export { Plugin as PgAggregatesFilterRelationalAggregatesPlugin };
48
+ interface PgAggregateCondition<TParentStep extends PgConditionCapableParent> extends Modifier<TParentStep> {
49
+ sql: PgSQL;
50
+ tableExpression: SQL;
51
+ alias: SQL;
52
+ conditions: PgWhereConditionSpec<any>[];
53
+ expressions: SQL[];
54
+ where(condition: PgWhereConditionSpec<any>): void;
55
+ expression(expression: SQL): void;
56
+ forAggregate(spec: AggregateSpec): PgAggregateConditionExpression;
57
+ apply(): void;
58
+ }
59
+ interface PgAggregateConditionClass {
60
+ new <TParentStep extends PgConditionCapableParent>($parent: TParentStep, options: {
61
+ sql: PgSQL;
62
+ tableExpression: SQL;
63
+ alias?: string;
64
+ }, pgWhereConditionSpecListToSQL: GraphileBuild.Build['dataplanPg']['pgWhereConditionSpecListToSQL']): PgAggregateCondition<TParentStep>;
65
+ }
66
+ interface PgAggregateConditionExpression extends Modifier<PgAggregateCondition<any>> {
67
+ alias: SQL;
68
+ conditions: PgWhereConditionSpec<any>[];
69
+ where(condition: PgWhereConditionSpec<any>): void;
70
+ apply(): void;
71
+ }
72
+ interface PgAggregateConditionExpressionClass {
73
+ new ($parent: PgAggregateCondition<any>, spec: AggregateSpec, pgWhereConditionSpecListToSQL: GraphileBuild.Build['dataplanPg']['pgWhereConditionSpecListToSQL']): PgAggregateConditionExpression;
74
+ }
@@ -0,0 +1,440 @@
1
+ import { EXPORTABLE } from './EXPORTABLE';
2
+ const version = '1.0.0';
3
+ const pgAggregateApplyAttributeOrder = EXPORTABLE(() => (PgCondition, sql, spec, attributeName, attrCodec, rawAttrCodec, $parent, input) => {
4
+ if (input == null)
5
+ return;
6
+ const $col = new PgCondition($parent);
7
+ $col.extensions.pgFilterAttribute = {
8
+ codec: attrCodec,
9
+ expression: spec.sqlAggregateWrap(sql `${$col.alias}.${sql.identifier(attributeName)}`, rawAttrCodec)
10
+ };
11
+ return $col;
12
+ }, [], 'pgAggregateApplyAttributeOrder');
13
+ const pgAggregateApplyComputedAttributeOrder = EXPORTABLE(() => (PgCondition, sql, spec, proc, attrCodec, $parent, input) => {
14
+ if (input == null)
15
+ return;
16
+ const $col = new PgCondition($parent);
17
+ const sqlComputedAttributeCall = sql.query `${typeof proc.from === 'function'
18
+ ? proc.from({ placeholder: $col.alias })
19
+ : proc.from}`;
20
+ $col.extensions.pgFilterAttribute = {
21
+ codec: attrCodec,
22
+ expression: spec.sqlAggregateWrap(sqlComputedAttributeCall, proc.codec)
23
+ };
24
+ return $col;
25
+ }, [], 'pgAggregateApplyComputedAttributeOrder');
26
+ const pgAggregateApplyForeignCondition = EXPORTABLE(() => function (PgCondition, $subquery, input) {
27
+ if (input == null)
28
+ return;
29
+ // Enable all the helpers
30
+ const $condition = new PgCondition($subquery, false, 'AND');
31
+ return $condition;
32
+ }, [], 'pgAggregateApplyForeignCondition');
33
+ const pgAggregatesApply = EXPORTABLE(() => (PgAggregateCondition, pgWhereConditionSpecListToSQL, sql, $where, input) => {
34
+ if (input == null)
35
+ return;
36
+ // Constructive fork: read from _manyRelation (set by our
37
+ // ConnectionFilterBackwardRelationsPlugin) instead of upstream's
38
+ // $where.extensions.pgFilterRelation.
39
+ const rel = $where._manyRelation;
40
+ if (!rel) {
41
+ throw new Error(`Invalid use of aggregates filter: '_manyRelation' expected on $where. ` +
42
+ `Ensure graphile-connection-filter backward relations plugin has tagged $where.`);
43
+ }
44
+ const { localAttributes, remoteAttributes, foreignTableExpression, foreignTable } = rel;
45
+ const $subQuery = new PgAggregateCondition($where, {
46
+ sql,
47
+ tableExpression: foreignTableExpression,
48
+ alias: foreignTable.name
49
+ }, pgWhereConditionSpecListToSQL);
50
+ localAttributes.forEach((localAttribute, i) => {
51
+ const remoteAttribute = remoteAttributes[i];
52
+ $subQuery.where(sql `${$where.alias}.${sql.identifier(localAttribute)} = ${$subQuery.alias}.${sql.identifier(remoteAttribute)}`);
53
+ });
54
+ return $subQuery;
55
+ }, [], 'pgAggregatesApply');
56
+ export const Plugin = {
57
+ name: 'PgAggregatesFilterRelationalAggregatesPlugin',
58
+ description: `\
59
+ Adds the ability to filter a collection by aggregates on relationships via \
60
+ graphile-connection-filter, e.g. filtering all players based on \
61
+ the sum of their points scored.`,
62
+ version,
63
+ // This has to run AFTER any plugins that provide `build.pgAggregateSpecs`
64
+ // otherwise we might add codecs to build.allPgCodecs before all the relevant
65
+ // codecs/specs are available.
66
+ after: ['PgBasicsPlugin', 'PgCodecsPlugin', 'aggregates'],
67
+ provides: ['codecs'],
68
+ before: ['ConnectionFilterArgPlugin'],
69
+ inflection: {
70
+ add: {
71
+ // TODO: rename this!
72
+ filterTableAggregateType(_preset, foreignTable, spec) {
73
+ const foreignTableTypeName = this.tableType(foreignTable.codec);
74
+ return this.filterType(foreignTableTypeName + this.upperCamelCase(spec.id) + 'Aggregate');
75
+ }
76
+ }
77
+ },
78
+ schema: {
79
+ behaviorRegistry: {
80
+ add: {
81
+ 'aggregates:filterBy': {
82
+ description: 'Can we filter by the details of this resource whilst aggregating the resource?',
83
+ entities: ['pgResource']
84
+ },
85
+ 'aggregate:filterBy': {
86
+ description: 'Can we filter using the value of this attribute',
87
+ entities: ['pgCodecAttribute']
88
+ }
89
+ }
90
+ },
91
+ entityBehavior: {
92
+ pgResource: ['aggregates:filterBy', 'aggregate:filterBy'],
93
+ pgCodecAttribute: ['aggregate:filterBy']
94
+ },
95
+ hooks: {
96
+ build(build) {
97
+ const { EXPORTABLE, grafast: { Modifier } } = build;
98
+ if (!build.allPgCodecs) {
99
+ throw new Error('PgAggregatesFilterRelationalAggregatesPlugin must run after build.allPgCodecs has been established');
100
+ }
101
+ if (!build.pgAggregateSpecs) {
102
+ throw new Error('PgAggregatesFilterRelationalAggregatesPlugin must run after build.pgAggregateSpecs has been established');
103
+ }
104
+ // Add aggregate derivative codecs to `allPgCodecs`
105
+ for (const spec of build.pgAggregateSpecs) {
106
+ if (!spec.pgTypeCodecModifier) {
107
+ continue;
108
+ }
109
+ for (const existingCodec of build.allPgCodecs) {
110
+ if (spec.isSuitableType(existingCodec)) {
111
+ const codec = spec.pgTypeCodecModifier(existingCodec);
112
+ if (!build.allPgCodecs.has(codec)) {
113
+ build.allPgCodecs.add(codec);
114
+ }
115
+ }
116
+ }
117
+ }
118
+ const PgAggregateConditionExpression = EXPORTABLE((Modifier) => class PgAggregateConditionExpression extends Modifier {
119
+ spec;
120
+ pgWhereConditionSpecListToSQL;
121
+ alias;
122
+ conditions = [];
123
+ constructor(parent, spec, pgWhereConditionSpecListToSQL) {
124
+ super(parent);
125
+ this.spec = spec;
126
+ this.pgWhereConditionSpecListToSQL = pgWhereConditionSpecListToSQL;
127
+ this.alias = parent.alias;
128
+ }
129
+ where(condition) {
130
+ this.conditions.push(condition);
131
+ }
132
+ apply() {
133
+ const sqlCondition = this.pgWhereConditionSpecListToSQL(this.alias, this.conditions);
134
+ if (sqlCondition) {
135
+ this.parent.expression(sqlCondition);
136
+ }
137
+ }
138
+ }, [Modifier]);
139
+ const PgAggregateCondition = EXPORTABLE((Modifier, PgAggregateConditionExpression) => class PgAggregateCondition extends Modifier {
140
+ pgWhereConditionSpecListToSQL;
141
+ sql;
142
+ tableExpression;
143
+ alias;
144
+ conditions = [];
145
+ expressions = [];
146
+ constructor(parent, options, pgWhereConditionSpecListToSQL) {
147
+ super(parent);
148
+ this.pgWhereConditionSpecListToSQL = pgWhereConditionSpecListToSQL;
149
+ const { sql, tableExpression, alias } = options;
150
+ this.sql = sql;
151
+ this.alias = sql.identifier(Symbol(alias ?? 'aggregate'));
152
+ this.tableExpression = tableExpression;
153
+ }
154
+ where(condition) {
155
+ this.conditions.push(condition);
156
+ }
157
+ expression(expression) {
158
+ this.expressions.push(expression);
159
+ }
160
+ forAggregate(spec) {
161
+ return new PgAggregateConditionExpression(this, spec, this.pgWhereConditionSpecListToSQL);
162
+ }
163
+ apply() {
164
+ const { sql } = this;
165
+ const sqlCondition = this.pgWhereConditionSpecListToSQL(this.alias, this.conditions);
166
+ const where = sqlCondition
167
+ ? sql `where ${sqlCondition}`
168
+ : sql.blank;
169
+ const boolExpr = this.expressions.length === 0
170
+ ? sql.true
171
+ : sql.parens(sql.join(this.expressions.map((expr) => sql.parens(expr)), '\nand\n'));
172
+ const subquery = sql `(${sql.indent `\
173
+ select ${boolExpr}
174
+ from ${this.tableExpression} as ${this.alias}
175
+ ${where}`}
176
+ group by ())`;
177
+ return this.parent.where(subquery);
178
+ }
179
+ }, [Modifier, PgAggregateConditionExpression]);
180
+ return build.extend(build, {
181
+ PgAggregateCondition,
182
+ PgAggregateConditionExpression
183
+ }, 'Adding step classes from postgraphile-plugin-connection-filter');
184
+ },
185
+ init(_, build) {
186
+ const { inflection, dataplanPg: { PgCondition }, EXPORTABLE } = build;
187
+ if (!inflection.filterType) {
188
+ // Filter plugin is not enabled
189
+ return _;
190
+ }
191
+ // Register the aggregate filter type for each table
192
+ for (const foreignTable of Object.values(build.input.pgRegistry.pgResources)) {
193
+ if (foreignTable.parameters || !foreignTable.codec.attributes) {
194
+ continue;
195
+ }
196
+ if (!build.behavior.pgResourceMatches(foreignTable, 'resource:aggregates:filterBy')) {
197
+ continue;
198
+ }
199
+ const foreignTableTypeName = inflection.tableType(foreignTable.codec);
200
+ const foreignTableFilterTypeName = inflection.filterType(foreignTableTypeName);
201
+ const foreignTableAggregateFilterTypeName = inflection.filterType(foreignTableTypeName + 'Aggregates');
202
+ build.recoverable(null, () => {
203
+ const filterFieldName = 'where';
204
+ build.registerInputObjectType(foreignTableAggregateFilterTypeName, {
205
+ pgResource: foreignTable,
206
+ isPgConnectionAggregateFilter: true
207
+ }, () => {
208
+ return {
209
+ description: `A filter to be used against aggregates of \`${foreignTableTypeName}\` object types.`,
210
+ fields: () => {
211
+ const type = build.getTypeByName(foreignTableFilterTypeName);
212
+ if (!type) {
213
+ return {};
214
+ }
215
+ return {
216
+ [filterFieldName]: {
217
+ description: `A filter that must pass for the relevant \`${foreignTableTypeName}\` object to be included within the aggregate.`,
218
+ type,
219
+ apply: EXPORTABLE((PgCondition, pgAggregateApplyForeignCondition) => ($subquery, input) => pgAggregateApplyForeignCondition(PgCondition, $subquery, input), [PgCondition, pgAggregateApplyForeignCondition], `${filterFieldName}Apply`)
220
+ // No need to auto-apply since we're applied manually via `fieldArgs.apply($subQuery)` below.
221
+ }
222
+ };
223
+ }
224
+ };
225
+ }, 'Adding aggregate filter input type');
226
+ });
227
+ // Register the aggregate spec filter type for each aggreage spec for each source
228
+ for (const spec of build.pgAggregateSpecs) {
229
+ if (!build.behavior.pgResourceMatches(foreignTable, `${spec.id}:resource:aggregates:filterBy`)) {
230
+ continue;
231
+ }
232
+ const filterTypeName = inflection.filterTableAggregateType(foreignTable, spec);
233
+ build.registerInputObjectType(filterTypeName, {
234
+ isPgConnectionAggregateAggregateFilter: true,
235
+ pgConnectionAggregateFilterAggregateSpec: spec,
236
+ pgTypeResource: foreignTable
237
+ }, () => ({}), `Add '${spec.id}' aggregate filter type for '${foreignTableTypeName}'`);
238
+ }
239
+ }
240
+ return _;
241
+ },
242
+ // This hook adds 'aggregates' under a "backwards" relation, siblings of
243
+ // every, some, none.
244
+ // See https://github.com/graphile-contrib/postgraphile-plugin-connection-filter/blob/6223cdb1d2ac5723aecdf55f735a18f8e2b98683/src/PgConnectionArgFilterBackwardRelationsPlugin.ts#L374
245
+ GraphQLInputObjectType_fields(inFields, build, context) {
246
+ let fields = inFields;
247
+ const { extend, inflection, sql, pgAggregateSpecs, dataplanPg: { PgCondition, pgWhereConditionSpecListToSQL }, PgAggregateCondition, EXPORTABLE } = build;
248
+ if (!inflection.filterType) {
249
+ // Filter plugin is not enabled
250
+ return inFields;
251
+ }
252
+ const { fieldWithHooks, scope: { foreignTable, isPgConnectionFilterMany, isPgConnectionAggregateFilter, pgResource, isPgConnectionAggregateAggregateFilter, pgConnectionAggregateFilterAggregateSpec: spec, pgTypeResource } } = context;
253
+ // Add 'aggregates' field to relation filters, next to `every`/`some`/`none`
254
+ fields = (() => {
255
+ if (!isPgConnectionFilterMany || !foreignTable)
256
+ return fields;
257
+ const foreignTableTypeName = inflection.tableType(foreignTable.codec);
258
+ const foreignTableAggregateFilterTypeName = inflection.filterType(foreignTableTypeName + 'Aggregates');
259
+ const fieldName = 'aggregates';
260
+ const AggregateType = build.getTypeByName(foreignTableAggregateFilterTypeName);
261
+ if (!AggregateType) {
262
+ return fields;
263
+ }
264
+ return build.extend(fields, {
265
+ [fieldName]: fieldWithHooks({
266
+ fieldName,
267
+ isPgConnectionFilterAggregatesField: true
268
+ }, {
269
+ description: `Aggregates across related \`${foreignTableTypeName}\` match the filter criteria.`,
270
+ type: AggregateType,
271
+ apply: EXPORTABLE((PgAggregateCondition, pgAggregatesApply, pgWhereConditionSpecListToSQL, sql) => function ($where, input) {
272
+ return pgAggregatesApply(PgAggregateCondition, pgWhereConditionSpecListToSQL, sql, $where, input);
273
+ }, [
274
+ PgAggregateCondition,
275
+ pgAggregatesApply,
276
+ pgWhereConditionSpecListToSQL,
277
+ sql
278
+ ])
279
+ // No need to auto-apply, postgraphile-plugin-connection-filter explicitly calls fieldArgs.apply()
280
+ })
281
+ }, "Adding 'aggregates' filter field on relation");
282
+ })();
283
+ // This hook adds our various aggregates to the 'aggregates' input defined in `AggregateType` above
284
+ fields = (() => {
285
+ if (!isPgConnectionAggregateFilter ||
286
+ !pgResource ||
287
+ pgResource.parameters ||
288
+ !pgResource.codec.attributes) {
289
+ return fields;
290
+ }
291
+ const foreignTable = pgResource;
292
+ const foreignTableTypeName = inflection.tableType(foreignTable.codec);
293
+ return pgAggregateSpecs.reduce((memo, spec) => {
294
+ const filterTypeName = inflection.filterTableAggregateType(foreignTable, spec);
295
+ const fieldName = inflection.camelCase(spec.id);
296
+ const type = build.getTypeByName(filterTypeName);
297
+ if (!type) {
298
+ return memo;
299
+ }
300
+ return extend(memo, {
301
+ [fieldName]: fieldWithHooks({ fieldName }, () => ({
302
+ type,
303
+ description: `${spec.HumanLabel} aggregate over matching \`${foreignTableTypeName}\` objects.`,
304
+ apply: EXPORTABLE((spec) => function ($subquery, input) {
305
+ if (input == null)
306
+ return;
307
+ return $subquery.forAggregate(spec);
308
+ }, [spec])
309
+ }))
310
+ }, `Adding aggregate '${spec.id}' filter input for '${pgResource.name}'. `);
311
+ }, fields);
312
+ })();
313
+ // This hook adds matching attributes to the relevant aggregate types.
314
+ fields = (() => {
315
+ if (!isPgConnectionAggregateAggregateFilter ||
316
+ !spec ||
317
+ !pgTypeResource ||
318
+ pgTypeResource.parameters ||
319
+ !pgTypeResource.codec.attributes) {
320
+ return fields;
321
+ }
322
+ const table = pgTypeResource;
323
+ const attributes = table.codec.attributes;
324
+ fields = extend(fields, {
325
+ ...Object.entries(attributes).reduce((memo, [attributeName, attribute]) => {
326
+ if (!build.behavior.pgCodecAttributeMatches([table.codec, attributeName], `${spec.id}:attribute:aggregate:filterBy`)) {
327
+ return memo;
328
+ }
329
+ if ((spec.shouldApplyToEntity &&
330
+ !spec.shouldApplyToEntity({
331
+ type: 'attribute',
332
+ codec: table.codec,
333
+ attributeName: attributeName
334
+ })) ||
335
+ !spec.isSuitableType(attribute.codec)) {
336
+ return memo;
337
+ }
338
+ const rawAttrCodec = attribute.codec;
339
+ const attrCodec = spec.pgTypeCodecModifier
340
+ ? spec.pgTypeCodecModifier(attribute.codec)
341
+ : attribute.codec;
342
+ const fieldName = inflection.attribute({
343
+ codec: table.codec,
344
+ attributeName
345
+ });
346
+ const digest = build.connectionFilterOperatorsDigest(attrCodec);
347
+ if (!digest) {
348
+ return memo;
349
+ }
350
+ const OperatorsType = build.getTypeByName(digest.operatorsTypeName);
351
+ if (!OperatorsType) {
352
+ return memo;
353
+ }
354
+ return build.extend(memo, {
355
+ [fieldName]: {
356
+ type: OperatorsType,
357
+ apply: EXPORTABLE((PgCondition, attrCodec, attributeName, pgAggregateApplyAttributeOrder, rawAttrCodec, spec, sql) => function ($parent, input) {
358
+ return pgAggregateApplyAttributeOrder(PgCondition, sql, spec, attributeName, attrCodec, rawAttrCodec, $parent, input);
359
+ }, [
360
+ PgCondition,
361
+ attrCodec,
362
+ attributeName,
363
+ pgAggregateApplyAttributeOrder,
364
+ rawAttrCodec,
365
+ spec,
366
+ sql
367
+ ])
368
+ // No need to auto-apply since we're called via `fieldArgs.apply($subquery.forAggregate(spec))` above
369
+ }
370
+ }, `Add aggregate '${attributeName}' filter for source '${table.name}' for spec '${spec.id}'`);
371
+ }, Object.create(null))
372
+ }, `Adding per-attribute '${spec.id}' aggregate filters for '${pgTypeResource.name}'`);
373
+ const computedAttributeResources = Object.values(build.input.pgRegistry.pgResources).filter((s) => isComputedScalarAttributeResource(s) &&
374
+ s.parameters[0].codec === table.codec &&
375
+ s.parameters.slice(1).every((p) => p.optional));
376
+ fields = extend(fields, {
377
+ ...computedAttributeResources.reduce((memo, proc) => {
378
+ if ((spec.shouldApplyToEntity &&
379
+ !spec.shouldApplyToEntity({
380
+ type: 'computedAttribute',
381
+ resource: proc
382
+ })) ||
383
+ !spec.isSuitableType(proc.codec)) {
384
+ return memo;
385
+ }
386
+ const fieldName = inflection.computedAttributeField({
387
+ resource: proc
388
+ });
389
+ const attrCodec = spec.pgTypeCodecModifier
390
+ ? spec.pgTypeCodecModifier(proc.codec)
391
+ : proc.codec;
392
+ const digest = build.connectionFilterOperatorsDigest(attrCodec);
393
+ if (!digest) {
394
+ return memo;
395
+ }
396
+ const OperatorsType = build.getTypeByName(digest.operatorsTypeName);
397
+ if (!OperatorsType) {
398
+ return memo;
399
+ }
400
+ return build.extend(memo, {
401
+ [fieldName]: {
402
+ type: OperatorsType,
403
+ apply: EXPORTABLE((PgCondition, attrCodec, pgAggregateApplyComputedAttributeOrder, proc, spec, sql) => function apply($parent, input) {
404
+ return pgAggregateApplyComputedAttributeOrder(PgCondition, sql, spec, proc, attrCodec, $parent, input);
405
+ }, [
406
+ PgCondition,
407
+ attrCodec,
408
+ pgAggregateApplyComputedAttributeOrder,
409
+ proc,
410
+ spec,
411
+ sql
412
+ ])
413
+ }
414
+ }, `Add computed aggregate '${fieldName}' filter for source '${table.name}' for spec '${spec.id}'`);
415
+ }, Object.create(null))
416
+ }, `Adding per-computed-column '${spec.id}' aggregate filters for '${pgTypeResource.name}'`);
417
+ return fields;
418
+ })();
419
+ return fields;
420
+ }
421
+ }
422
+ }
423
+ };
424
+ export { Plugin as PgAggregatesFilterRelationalAggregatesPlugin };
425
+ function isComputedScalarAttributeResource(s) {
426
+ if (!s.parameters || s.parameters.length < 1) {
427
+ return false;
428
+ }
429
+ if (s.codec.attributes) {
430
+ return false;
431
+ }
432
+ if (!s.isUnique) {
433
+ return false;
434
+ }
435
+ const firstParameter = s.parameters[0];
436
+ if (!firstParameter?.codec.attributes) {
437
+ return false;
438
+ }
439
+ return true;
440
+ }
@@ -0,0 +1,67 @@
1
+ import type { PgCodecWithAttributes, PgRegistry, PgResource } from '@dataplan/pg';
2
+ import type { AggregateGroupBySpec, AggregateSpec, CORE_HAVING_FILTER_SPECS } from './interfaces';
3
+ declare global {
4
+ namespace GraphileBuild {
5
+ interface Inflection {
6
+ aggregateContainerType(this: Inflection, details: {
7
+ resource: PgResource<any, any, any, any, any>;
8
+ }): string;
9
+ aggregateType(this: Inflection, details: {
10
+ resource: PgResource<any, any, any, any, any>;
11
+ aggregateSpec: AggregateSpec;
12
+ }): string;
13
+ aggregatesContainerField(this: Inflection, details: {
14
+ resource: PgResource<any, any, any, any, any>;
15
+ }): string;
16
+ groupedAggregatesContainerField(this: Inflection, details: {
17
+ resource: PgResource<any, any, any, any, any>;
18
+ }): string;
19
+ aggregatesField(this: Inflection, details: {
20
+ aggregateSpec: AggregateSpec;
21
+ }): string;
22
+ aggregateGroupByType(this: Inflection, details: {
23
+ resource: PgResource<any, any, any, any, any>;
24
+ }): string;
25
+ aggregateGroupByAttributeEnum(this: Inflection, details: {
26
+ resource: PgResource<any, any, any, any, any>;
27
+ attributeName: string;
28
+ }): string;
29
+ aggregateHavingInputType(this: Inflection, details: {
30
+ resource: PgResource<any, any, any, any, any>;
31
+ }): string;
32
+ aggregateHavingAggregateInputType(this: Inflection, details: {
33
+ resource: PgResource<any, any, any, any, any>;
34
+ aggregateSpec: AggregateSpec;
35
+ }): string;
36
+ aggregateHavingAggregateComputedAttributeInputType(this: Inflection, details: {
37
+ resource: PgResource<any, any, any, any, any>;
38
+ aggregateSpec: AggregateSpec;
39
+ computedAttributeResource: PgResource<any, any, any, any, any>;
40
+ }): string;
41
+ aggregateHavingAggregateComputedAttributeArgsInputType(this: Inflection, details: {
42
+ resource: PgResource<any, any, any, any, any>;
43
+ aggregateSpec: AggregateSpec;
44
+ computedAttributeResource: PgResource<any, any, any, any, any>;
45
+ }): string;
46
+ aggregateGroupByAttributeDerivativeEnum(this: Inflection, details: {
47
+ resource: PgResource<any, any, any, any, any>;
48
+ attributeName: string;
49
+ aggregateGroupBySpec: AggregateGroupBySpec;
50
+ }): string;
51
+ orderByCountOfManyRelationByKeys(this: Inflection, details: {
52
+ registry: PgRegistry;
53
+ codec: PgCodecWithAttributes;
54
+ relationName: string;
55
+ }): string;
56
+ orderByAttributeAggregateOfManyRelationByKeys(this: Inflection, details: {
57
+ registry: PgRegistry;
58
+ codec: PgCodecWithAttributes;
59
+ relationName: string;
60
+ aggregateSpec: AggregateSpec;
61
+ attributeName: string;
62
+ }): string;
63
+ aggregateHavingFilterInputType(this: Inflection, spec: (typeof CORE_HAVING_FILTER_SPECS)[number]): string;
64
+ }
65
+ }
66
+ }
67
+ export declare const PgAggregatesInflectorsPlugin: GraphileConfig.Plugin;