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,38 @@
1
+ declare global {
2
+ namespace GraphileBuild {
3
+ interface BehaviorStrings {
4
+ 'resource:aggregates': true;
5
+ aggregates: true;
6
+ aggregate: true;
7
+ 'sum:resource:aggregates': true;
8
+ 'distinctCount:resource:aggregates': true;
9
+ 'min:resource:aggregates': true;
10
+ 'max:resource:aggregates': true;
11
+ 'average:resource:aggregates': true;
12
+ 'stddevSample:resource:aggregates': true;
13
+ 'stddevPopulation:resource:aggregates': true;
14
+ 'varianceSample:resource:aggregates': true;
15
+ 'variancePopulation:resource:aggregates': true;
16
+ 'sum:resource:aggregate': true;
17
+ 'distinctCount:resource:aggregate': true;
18
+ 'min:resource:aggregate': true;
19
+ 'max:resource:aggregate': true;
20
+ 'average:resource:aggregate': true;
21
+ 'stddevSample:resource:aggregate': true;
22
+ 'stddevPopulation:resource:aggregate': true;
23
+ 'varianceSample:resource:aggregate': true;
24
+ 'variancePopulation:resource:aggregate': true;
25
+ 'sum:attribute:aggregate': true;
26
+ 'distinctCount:attribute:aggregate': true;
27
+ 'min:attribute:aggregate': true;
28
+ 'max:attribute:aggregate': true;
29
+ 'average:attribute:aggregate': true;
30
+ 'stddevSample:attribute:aggregate': true;
31
+ 'stddevPopulation:attribute:aggregate': true;
32
+ 'varianceSample:attribute:aggregate': true;
33
+ 'variancePopulation:attribute:aggregate': true;
34
+ }
35
+ }
36
+ }
37
+ declare const Plugin: GraphileConfig.Plugin;
38
+ export { Plugin as PgAggregatesAddAggregateTypesPlugin };
@@ -0,0 +1,268 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PgAggregatesAddAggregateTypesPlugin = void 0;
4
+ const EXPORTABLE_1 = require("./EXPORTABLE");
5
+ const utils_1 = require("./utils");
6
+ const version = '1.0.0';
7
+ const isSuitableSource = (build, resource) => {
8
+ if (resource.parameters || !resource.codec.attributes) {
9
+ return false;
10
+ }
11
+ if (!build.behavior.pgResourceMatches(resource, 'select')) {
12
+ return false;
13
+ }
14
+ return !!build.behavior.pgResourceMatches(resource, 'resource:aggregates');
15
+ };
16
+ const pgAggregatesPlanKeys = (0, EXPORTABLE_1.EXPORTABLE)(() => (lambda, $pgSelectSingle) => {
17
+ const $pgSelect = $pgSelectSingle.getClassStep();
18
+ const $groupDetails = $pgSelect.getGroupDetails();
19
+ return lambda([$groupDetails, $pgSelectSingle], ([groupDetails, item]) => {
20
+ if (groupDetails.indicies.length === 0 || item == null) {
21
+ return null;
22
+ }
23
+ else {
24
+ return groupDetails.indicies.map(({ index }) => item[index]);
25
+ }
26
+ });
27
+ }, [], 'pgAggregatesPlanKeys');
28
+ const pgAggregatesPlanAggregates = (0, EXPORTABLE_1.EXPORTABLE)(() => function plan($pgSelectSingle) {
29
+ return $pgSelectSingle;
30
+ }, [], 'pgAggregatesPlanAggregates');
31
+ const pgAggregatesPlanAggregateAttribute = (0, EXPORTABLE_1.EXPORTABLE)(() => (attributeCodec, attributeName, codec, spec, sql, $pgSelectSingle) => {
32
+ // Note this expression is just an sql fragment, so you
33
+ // could add CASE statements, function calls, or whatever
34
+ // you need here
35
+ const sqlAttribute = sql.fragment `${$pgSelectSingle.getClassStep().alias}.${sql.identifier(attributeName)}`;
36
+ const sqlAggregate = spec.sqlAggregateWrap(sqlAttribute, attributeCodec);
37
+ return $pgSelectSingle.select(sqlAggregate, codec);
38
+ }, [], 'pgAggregatesPlanAggregateAttribute');
39
+ const pgAggregatesPlanComputedColumnAggregates = (0, EXPORTABLE_1.EXPORTABLE)(() => (codec, computedAttributeResource, makeArgs, pgFromExpression, spec, targetCodec, $pgSelectSingle, fieldArgs) => {
40
+ // Because we require that the computed attribute is
41
+ // evaluated inline, we have to convert it to an
42
+ // expression here; this is only needed because of the
43
+ // aggregation.
44
+ const src = pgFromExpression($pgSelectSingle, computedAttributeResource.from, computedAttributeResource.parameters, [
45
+ {
46
+ placeholder: $pgSelectSingle.getClassStep().alias,
47
+ position: 0
48
+ },
49
+ ...makeArgs(fieldArgs)
50
+ ]);
51
+ const sqlAggregate = spec.sqlAggregateWrap(src, codec);
52
+ return $pgSelectSingle.select(sqlAggregate, targetCodec);
53
+ }, [], 'pgAggregatesPlanComputedColumnAggregates');
54
+ const Plugin = {
55
+ name: 'PgAggregatesAddAggregateTypesPlugin',
56
+ description: `\
57
+ Creates the FooAggregates type for each suitable resource, creates the 'sum' \
58
+ and similar fields on this type, and the entries within these types for \
59
+ attributes and computed columns.`,
60
+ version,
61
+ provides: ['aggregates'],
62
+ // Create the aggregates type for each table
63
+ schema: {
64
+ behaviorRegistry: {
65
+ add: {
66
+ aggregates: {
67
+ description: 'For collection resources (e.g. returning a setof records)',
68
+ entities: ['pgResource']
69
+ },
70
+ aggregate: {
71
+ description: 'for computed column resources (e.g. returning a scalar)',
72
+ entities: ['pgResource']
73
+ }
74
+ }
75
+ },
76
+ entityBehavior: {
77
+ // `aggregates` - for collection resources (e.g. returning a setof records)
78
+ // `aggregate` - for computed column resources (e.g. returning a scalar)
79
+ pgResource: ['select', 'aggregates', 'aggregate'],
80
+ pgCodecAttribute: 'aggregate'
81
+ },
82
+ hooks: {
83
+ init(init, build, _context) {
84
+ const { graphql: { GraphQLList, GraphQLString }, grafast: { lambda }, dataplanPg: { assertPgClassSingleStep }, inflection, input: { pgRegistry: { pgResources } }, EXPORTABLE } = build;
85
+ // TODO: should we be using the codec rather than the source here? What if two sources share the same codec?
86
+ for (const resource of Object.values(pgResources)) {
87
+ if (!isSuitableSource(build, resource)) {
88
+ continue;
89
+ }
90
+ /* const AggregateContainerType = */
91
+ build.registerObjectType(inflection.aggregateContainerType({ resource: resource }), {
92
+ isPgAggregateContainerType: true,
93
+ pgTypeResource: resource
94
+ }, () => ({
95
+ assertStep: assertPgClassSingleStep,
96
+ fields: {
97
+ keys: {
98
+ type: new GraphQLList(GraphQLString),
99
+ plan: EXPORTABLE((lambda, pgAggregatesPlanKeys) => function plan($pgSelectSingle) {
100
+ return pgAggregatesPlanKeys(lambda, $pgSelectSingle);
101
+ }, [lambda, pgAggregatesPlanKeys])
102
+ }
103
+ }
104
+ }), `@graphile/pg-aggregates aggregate container type for ${resource.name}`);
105
+ for (const aggregateSpec of build.pgAggregateSpecs) {
106
+ const aggregateTypeName = inflection.aggregateType({
107
+ resource: resource,
108
+ aggregateSpec
109
+ });
110
+ if (!build.behavior.pgResourceMatches(resource, `${aggregateSpec.id}:resource:aggregates`)) {
111
+ continue;
112
+ }
113
+ build.registerObjectType(aggregateTypeName, {
114
+ isPgAggregateType: true,
115
+ pgAggregateSpec: aggregateSpec,
116
+ pgTypeResource: resource
117
+ }, () => ({}), `${aggregateTypeName} aggregate type for '${resource.name}' source`);
118
+ }
119
+ }
120
+ return init;
121
+ },
122
+ GraphQLObjectType_fields(inFields, build, context) {
123
+ let fields = inFields;
124
+ const { inflection, sql, graphql: { GraphQLNonNull, isOutputType }, dataplanPg: { pgFromExpression }, EXPORTABLE } = build;
125
+ const { fieldWithHooks, Self, scope: { isPgAggregateContainerType, isPgAggregateType, pgTypeResource: resource, pgAggregateSpec: spec } } = context;
126
+ if (!resource || !isSuitableSource(build, resource)) {
127
+ return fields;
128
+ }
129
+ // Hook the '*Aggregates' type for each source to add the "sum" operation
130
+ if (isPgAggregateContainerType) {
131
+ fields = build.extend(fields, build.pgAggregateSpecs.reduce((memo, aggregateSpec) => {
132
+ return build.recoverable(memo, () => {
133
+ if (!build.behavior.pgResourceMatches(resource, `${aggregateSpec.id}:resource:aggregates`)) {
134
+ return memo;
135
+ }
136
+ const aggregateTypeName = inflection.aggregateType({
137
+ resource: resource,
138
+ aggregateSpec
139
+ });
140
+ const AggregateType = build.getTypeByName(aggregateTypeName);
141
+ if (!AggregateType || !isOutputType(AggregateType)) {
142
+ return memo;
143
+ }
144
+ const fieldName = inflection.aggregatesField({ aggregateSpec });
145
+ return build.extend(memo, {
146
+ [fieldName]: fieldWithHooks({
147
+ fieldName,
148
+ isPgAggregateField: true,
149
+ pgAggregateSpec: aggregateSpec,
150
+ pgFieldResource: resource
151
+ }, () => ({
152
+ description: `${aggregateSpec.HumanLabel} aggregates across the matching connection (ignoring before/after/first/last/offset)`,
153
+ type: AggregateType,
154
+ plan: pgAggregatesPlanAggregates
155
+ }))
156
+ }, `Adding aggregates field to ${Self.name}`);
157
+ });
158
+ }, Object.create(null)), 'Adding sum operation to aggregate type');
159
+ }
160
+ // Hook the sum aggregates type to add fields for each numeric source attribute
161
+ if (isPgAggregateType && spec) {
162
+ fields = build.extend(fields,
163
+ // Figure out the attributes that we're allowed to do a `SUM(...)` of
164
+ Object.entries(resource.codec.attributes).reduce((memo, [attributeName, attribute]) => {
165
+ if (!build.behavior.pgCodecAttributeMatches([resource.codec, attributeName], `${spec.id}:attribute:aggregate`)) {
166
+ return memo;
167
+ }
168
+ if ((spec.shouldApplyToEntity &&
169
+ !spec.shouldApplyToEntity({
170
+ type: 'attribute',
171
+ codec: resource.codec,
172
+ attributeName
173
+ })) ||
174
+ !spec.isSuitableType(attribute.codec)) {
175
+ return memo;
176
+ }
177
+ const codec = spec.pgTypeCodecModifier
178
+ ? spec.pgTypeCodecModifier(attribute.codec)
179
+ : attribute.codec;
180
+ const Type = build.getGraphQLTypeByPgCodec(codec, 'output');
181
+ if (!Type) {
182
+ return memo;
183
+ }
184
+ const fieldName = inflection.attribute({
185
+ attributeName,
186
+ codec: resource.codec
187
+ });
188
+ return build.extend(memo, {
189
+ [fieldName]: fieldWithHooks({
190
+ fieldName,
191
+ // In case anyone wants to hook us, describe ourselves
192
+ isPgConnectionAggregateField: true
193
+ //pgFieldIntrospection: attr,
194
+ //TODO: add more details here
195
+ }, () => {
196
+ const attributeCodec = attribute.codec;
197
+ return {
198
+ description: `${spec.HumanLabel} of ${fieldName} across the matching connection`,
199
+ type: spec.isNonNull ? new GraphQLNonNull(Type) : Type,
200
+ plan: EXPORTABLE((attributeCodec, attributeName, codec, pgAggregatesPlanAggregateAttribute, spec, sql) => function plan($pgSelectSingle) {
201
+ return pgAggregatesPlanAggregateAttribute(attributeCodec, attributeName, codec, spec, sql, $pgSelectSingle);
202
+ }, [
203
+ attributeCodec,
204
+ attributeName,
205
+ codec,
206
+ pgAggregatesPlanAggregateAttribute,
207
+ spec,
208
+ sql
209
+ ])
210
+ };
211
+ })
212
+ }, `Add attribute '${attributeName}' compatible with this aggregate`);
213
+ }, Object.create(null)), 'Add attributes compatible with this aggregate');
214
+ const computedAttributeSources = (0, utils_1.getComputedAttributeResources)(build, resource);
215
+ fields = build.extend(fields, computedAttributeSources.reduce((memo, computedAttributeResource) => {
216
+ if (!build.behavior.pgResourceMatches(computedAttributeResource, `${spec.id}:resource:aggregate`)) {
217
+ return memo;
218
+ }
219
+ const codec = computedAttributeResource.codec;
220
+ if ((spec.shouldApplyToEntity &&
221
+ !spec.shouldApplyToEntity({
222
+ type: 'computedAttribute',
223
+ resource: computedAttributeResource
224
+ })) ||
225
+ !spec.isSuitableType(codec)) {
226
+ return memo;
227
+ }
228
+ const fieldName = inflection.computedAttributeField({
229
+ resource: computedAttributeResource
230
+ });
231
+ const targetCodec = spec.pgTypeCodecModifier?.(codec) ?? codec;
232
+ const targetType = build.getGraphQLTypeByPgCodec(targetCodec, 'output');
233
+ if (!targetType) {
234
+ return memo;
235
+ }
236
+ return build.extend(memo, {
237
+ [fieldName]: fieldWithHooks({
238
+ fieldName
239
+ }, () => {
240
+ const { makeFieldArgs, makeArgs } = build.pgGetArgDetailsFromParameters(computedAttributeResource, computedAttributeResource.parameters, 1);
241
+ return {
242
+ type: targetType,
243
+ description: `${spec.HumanLabel} of this field across the matching connection.${computedAttributeResource.description
244
+ ? `\n\n---\n\n${computedAttributeResource.description}`
245
+ : ''}`,
246
+ args: makeFieldArgs(),
247
+ plan: EXPORTABLE((codec, computedAttributeResource, makeArgs, pgAggregatesPlanComputedColumnAggregates, pgFromExpression, spec, targetCodec) => function plan($pgSelectSingle, fieldArgs) {
248
+ return pgAggregatesPlanComputedColumnAggregates(codec, computedAttributeResource, makeArgs, pgFromExpression, spec, targetCodec, $pgSelectSingle, fieldArgs);
249
+ }, [
250
+ codec,
251
+ computedAttributeResource,
252
+ makeArgs,
253
+ pgAggregatesPlanComputedColumnAggregates,
254
+ pgFromExpression,
255
+ spec,
256
+ targetCodec
257
+ ])
258
+ };
259
+ })
260
+ }, '');
261
+ }, Object.create(null)), '');
262
+ }
263
+ return fields;
264
+ }
265
+ }
266
+ }
267
+ };
268
+ exports.PgAggregatesAddAggregateTypesPlugin = Plugin;
@@ -0,0 +1,2 @@
1
+ declare const Plugin: GraphileConfig.Plugin;
2
+ export { Plugin as PgAggregatesAddConnectionAggregatesPlugin };
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PgAggregatesAddConnectionAggregatesPlugin = void 0;
4
+ const EXPORTABLE_1 = require("./EXPORTABLE");
5
+ const version = '1.0.0';
6
+ const pgAggregatesCloneSubplanWithoutPaginationSingle = (0, EXPORTABLE_1.EXPORTABLE)(() => function plan($connection) {
7
+ return $connection.cloneSubplanWithoutPagination('aggregate').single();
8
+ }, [], 'pgAggregatesCloneSubplanWithoutPaginationSingle');
9
+ const Plugin = {
10
+ name: 'PgAggregatesAddConnectionAggregatesPlugin',
11
+ description: 'Adds the `aggregates` field to connections.',
12
+ version,
13
+ provides: ['aggregates'],
14
+ schema: {
15
+ hooks: {
16
+ // Hook all connections to add the 'aggregates' field
17
+ GraphQLObjectType_fields(fields, build, context) {
18
+ const { inflection } = build;
19
+ const { fieldWithHooks, scope: { pgCodec, pgTypeResource, isConnectionType, isPgConnectionRelated } } = context;
20
+ const table = pgTypeResource ??
21
+ Object.values(build.input.pgRegistry.pgResources).find((s) => s.codec === pgCodec && !s.parameters);
22
+ // If it's not a table connection, abort
23
+ if (!isConnectionType ||
24
+ !isPgConnectionRelated ||
25
+ !table ||
26
+ table.parameters ||
27
+ !table.codec.attributes) {
28
+ return fields;
29
+ }
30
+ if (!build.behavior.pgResourceMatches(table, `resource:aggregates`)) {
31
+ return fields;
32
+ }
33
+ const AggregateContainerType = build.getTypeByName(inflection.aggregateContainerType({ resource: table }));
34
+ if (!AggregateContainerType ||
35
+ Object.keys(AggregateContainerType.getFields()).length === 0) {
36
+ // No aggregates for this connection, abort
37
+ return fields;
38
+ }
39
+ const fieldName = inflection.aggregatesContainerField({
40
+ resource: table
41
+ });
42
+ return {
43
+ ...fields,
44
+ [fieldName]: fieldWithHooks({ fieldName }, () => {
45
+ return {
46
+ description: `Aggregates across the matching connection (ignoring before/after/first/last/offset)`,
47
+ type: AggregateContainerType,
48
+ plan: pgAggregatesCloneSubplanWithoutPaginationSingle
49
+ };
50
+ })
51
+ };
52
+ }
53
+ }
54
+ }
55
+ };
56
+ exports.PgAggregatesAddConnectionAggregatesPlugin = Plugin;
@@ -0,0 +1,10 @@
1
+ declare global {
2
+ namespace GraphileBuild {
3
+ interface BehaviorStrings {
4
+ 'resource:groupedAggregates': true;
5
+ groupedAggregates: true;
6
+ }
7
+ }
8
+ }
9
+ declare const Plugin: GraphileConfig.Plugin;
10
+ export { Plugin as PgAggregatesAddConnectionGroupedAggregatesPlugin };
@@ -0,0 +1,118 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PgAggregatesAddConnectionGroupedAggregatesPlugin = void 0;
4
+ const EXPORTABLE_1 = require("./EXPORTABLE");
5
+ const version = '1.0.0';
6
+ function isValidEnum(build, enumType) {
7
+ try {
8
+ if (!enumType) {
9
+ return false;
10
+ }
11
+ if (!(enumType instanceof build.graphql.GraphQLEnumType)) {
12
+ return false;
13
+ }
14
+ if (Object.keys(enumType.getValues()).length === 0) {
15
+ return false;
16
+ }
17
+ }
18
+ catch {
19
+ return false;
20
+ }
21
+ return true;
22
+ }
23
+ const pgAggregateCloneSubplanWithoutPaginationAsAggregate = (0, EXPORTABLE_1.EXPORTABLE)(() => function plan($connection) {
24
+ return $connection.cloneSubplanWithoutPagination('aggregate');
25
+ }, [], 'pgAggregateCloneSubplanWithoutPaginationAsAggregate');
26
+ const pgAggregatesApplyGroupedAggregate = (0, EXPORTABLE_1.EXPORTABLE)(() => function (_$parent, $pgSelect, input) {
27
+ return input.apply($pgSelect);
28
+ }, [], 'pgAggregatesApplyGroupedAggregate');
29
+ const pgAggregatesApplyConditionsToGroupedAggregates = (0, EXPORTABLE_1.EXPORTABLE)(() => function applyPlan(_$parent, $pgSelect, input) {
30
+ return input.apply($pgSelect, (queryBuilder) => queryBuilder.havingBuilder());
31
+ }, [], 'pgAggregatesApplyConditionsToGroupedAggregates');
32
+ const Plugin = {
33
+ name: 'PgAggregatesAddConnectionGroupedAggregatesPlugin',
34
+ description: 'Adds the groupedAggregates field to connections.',
35
+ version,
36
+ provides: ['aggregates'],
37
+ schema: {
38
+ behaviorRegistry: {
39
+ add: {
40
+ 'resource:groupedAggregates': {
41
+ description: 'Should we enable grouped aggregates on this resource?',
42
+ entities: ['pgResource']
43
+ },
44
+ groupedAggregates: {
45
+ description: 'Should we enable grouped aggregates on this resource?',
46
+ entities: ['pgResource']
47
+ }
48
+ }
49
+ },
50
+ entityBehavior: {
51
+ pgResource: [
52
+ 'resource:groupedAggregates',
53
+ 'resource:groupedAggregates:having'
54
+ ]
55
+ },
56
+ hooks: {
57
+ GraphQLObjectType_fields(fields, build, context) {
58
+ const { graphql: { GraphQLList, GraphQLNonNull }, inflection } = build;
59
+ const { fieldWithHooks, scope: { pgCodec, pgTypeResource, isConnectionType, isPgConnectionRelated } } = context;
60
+ const table = pgTypeResource ??
61
+ Object.values(build.input.pgRegistry.pgResources).find((s) => s.codec === pgCodec && !s.parameters);
62
+ // If it's not a table connection, abort
63
+ if (!isConnectionType ||
64
+ !isPgConnectionRelated ||
65
+ !table ||
66
+ table.parameters ||
67
+ !table.codec.attributes) {
68
+ return fields;
69
+ }
70
+ if (!build.behavior.pgResourceMatches(table, `resource:groupedAggregates`)) {
71
+ return fields;
72
+ }
73
+ const AggregateContainerType = build.getTypeByName(inflection.aggregateContainerType({ resource: table }));
74
+ if (!AggregateContainerType ||
75
+ Object.keys(AggregateContainerType.getFields()).length === 0) {
76
+ // No aggregates for this connection, abort
77
+ return fields;
78
+ }
79
+ const fieldName = inflection.groupedAggregatesContainerField({
80
+ resource: table
81
+ });
82
+ const TableGroupByType = build.getTypeByName(inflection.aggregateGroupByType({ resource: table }));
83
+ const TableHavingInputType = build.getTypeByName(inflection.aggregateHavingInputType({ resource: table }));
84
+ const tableTypeName = inflection.tableType(table.codec);
85
+ if (!TableGroupByType || !isValidEnum(build, TableGroupByType)) {
86
+ return fields;
87
+ }
88
+ return {
89
+ ...fields,
90
+ [fieldName]: fieldWithHooks({ fieldName }, () => {
91
+ return {
92
+ description: `Grouped aggregates across the matching connection (ignoring before/after/first/last/offset)`,
93
+ type: new GraphQLList(new GraphQLNonNull(AggregateContainerType)),
94
+ args: {
95
+ groupBy: {
96
+ type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(TableGroupByType))),
97
+ description: build.wrapDescription(`The method to use when grouping \`${tableTypeName}\` for these aggregates.`, 'arg'),
98
+ applyPlan: pgAggregatesApplyGroupedAggregate
99
+ },
100
+ ...(TableHavingInputType
101
+ ? {
102
+ having: {
103
+ type: TableHavingInputType,
104
+ description: build.wrapDescription(`Conditions on the grouped aggregates.`, 'arg'),
105
+ applyPlan: pgAggregatesApplyConditionsToGroupedAggregates
106
+ }
107
+ }
108
+ : null)
109
+ },
110
+ plan: pgAggregateCloneSubplanWithoutPaginationAsAggregate
111
+ };
112
+ })
113
+ };
114
+ }
115
+ }
116
+ }
117
+ };
118
+ exports.PgAggregatesAddConnectionGroupedAggregatesPlugin = Plugin;
@@ -0,0 +1,10 @@
1
+ declare global {
2
+ namespace GraphileBuild {
3
+ interface BehaviorStrings {
4
+ 'attribute:groupBy': true;
5
+ groupBy: true;
6
+ }
7
+ }
8
+ }
9
+ declare const Plugin: GraphileConfig.Plugin;
10
+ export { Plugin as PgAggregatesAddGroupByAggregateEnumValuesForAttributesPlugin };
@@ -0,0 +1,116 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PgAggregatesAddGroupByAggregateEnumValuesForAttributesPlugin = void 0;
4
+ const EXPORTABLE_1 = require("./EXPORTABLE");
5
+ const version = '1.0.0';
6
+ const applyGroupByAggregateSpec = (0, EXPORTABLE_1.EXPORTABLE)(() => (sql, aggregateGroupBySpec, attributeName, attrCodec, qb) => {
7
+ qb.groupBy({
8
+ fragment: aggregateGroupBySpec.sqlWrap(sql `${qb.alias}.${sql.identifier(attributeName)}`),
9
+ codec: aggregateGroupBySpec.sqlWrapCodec(attrCodec)
10
+ });
11
+ }, [], 'applyGroupByAggregateSpec');
12
+ const applyGroupByAttribute = (0, EXPORTABLE_1.EXPORTABLE)(() => (sql, attributeName, attrCodec, qb) => {
13
+ qb.groupBy({
14
+ fragment: sql.fragment `${qb.alias}.${sql.identifier(attributeName)}`,
15
+ codec: attrCodec
16
+ });
17
+ }, [], 'applyGroupByAttribute');
18
+ const Plugin = {
19
+ name: 'PgAggregatesAddGroupByAggregateEnumValuesForAttributesPlugin',
20
+ description: 'Adds values representing table attributes to the enum that defines the groupedAggregates groupings.',
21
+ version,
22
+ provides: ['aggregates'],
23
+ // Now add group by attributes
24
+ schema: {
25
+ behaviorRegistry: {
26
+ add: {
27
+ 'attribute:groupBy': {
28
+ description: 'Can we group by this attribute when aggregating?',
29
+ entities: ['pgCodecAttribute']
30
+ },
31
+ groupBy: {
32
+ description: 'Can we group by this attribute when aggregating?',
33
+ entities: ['pgCodecAttribute']
34
+ }
35
+ }
36
+ },
37
+ entityBehavior: {
38
+ pgCodecAttribute: ['attribute:groupBy']
39
+ },
40
+ hooks: {
41
+ GraphQLEnumType_values(values, build, context) {
42
+ const { extend, inflection, sql, pgAggregateGroupBySpecs, EXPORTABLE } = build;
43
+ const { scope: { isPgAggregateGroupEnum, pgTypeResource: table } } = context;
44
+ if (!isPgAggregateGroupEnum ||
45
+ !table ||
46
+ table.parameters ||
47
+ !table.codec.attributes) {
48
+ return values;
49
+ }
50
+ return extend(values, Object.entries(table.codec.attributes).reduce((memo, [attributeName, attribute]) => {
51
+ // Grouping requires ordering.
52
+ if (!build.behavior.pgCodecAttributeMatches([table.codec, attributeName], 'orderBy')) {
53
+ return memo;
54
+ }
55
+ if (!build.behavior.pgCodecAttributeMatches([table.codec, attributeName], `attribute:groupBy`)) {
56
+ return memo;
57
+ }
58
+ const unique = !!table.uniques.find((u) => u.attributes.length === 1 && u.attributes[0] === attributeName);
59
+ if (unique)
60
+ return memo; // No point grouping by something that's unique.
61
+ const fieldName = inflection.aggregateGroupByAttributeEnum({
62
+ resource: table,
63
+ attributeName
64
+ });
65
+ const attrCodec = attribute.codec;
66
+ memo = extend(memo, {
67
+ [fieldName]: {
68
+ extensions: {
69
+ grafast: {
70
+ apply: EXPORTABLE((applyGroupByAttribute, attrCodec, attributeName, sql) => function ($pgSelect) {
71
+ applyGroupByAttribute(sql, attributeName, attrCodec, $pgSelect);
72
+ }, [applyGroupByAttribute, attrCodec, attributeName, sql])
73
+ }
74
+ }
75
+ }
76
+ }, `Adding groupBy enum value for ${table.name}.${attributeName}.`);
77
+ // Derivatives of this attribute
78
+ pgAggregateGroupBySpecs.forEach((aggregateGroupBySpec) => {
79
+ if ((!aggregateGroupBySpec.shouldApplyToEntity ||
80
+ aggregateGroupBySpec.shouldApplyToEntity({
81
+ type: 'attribute',
82
+ codec: table.codec,
83
+ attributeName
84
+ })) &&
85
+ aggregateGroupBySpec.isSuitableType(attribute.codec)) {
86
+ const fieldName = inflection.aggregateGroupByAttributeDerivativeEnum({
87
+ resource: table,
88
+ attributeName,
89
+ aggregateGroupBySpec
90
+ });
91
+ memo = extend(memo, {
92
+ [fieldName]: {
93
+ extensions: {
94
+ grafast: {
95
+ apply: EXPORTABLE((aggregateGroupBySpec, applyGroupByAggregateSpec, attrCodec, attributeName, sql) => function (qb) {
96
+ applyGroupByAggregateSpec(sql, aggregateGroupBySpec, attributeName, attrCodec, qb);
97
+ }, [
98
+ aggregateGroupBySpec,
99
+ applyGroupByAggregateSpec,
100
+ attrCodec,
101
+ attributeName,
102
+ sql
103
+ ])
104
+ }
105
+ }
106
+ }
107
+ }, `Adding groupBy enum value for '${aggregateGroupBySpec.id}' derivative of ${table.name}.${attributeName}.`);
108
+ }
109
+ });
110
+ return memo;
111
+ }, Object.create(null)), `Adding group by values for attributes from table '${table.name}'`);
112
+ }
113
+ }
114
+ }
115
+ };
116
+ exports.PgAggregatesAddGroupByAggregateEnumValuesForAttributesPlugin = Plugin;
@@ -0,0 +1,2 @@
1
+ declare const Plugin: GraphileConfig.Plugin;
2
+ export { Plugin as PgAggregatesAddGroupByAggregateEnumsPlugin };