@dav3/gql-of-power 1.1.0 → 1.2.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/README.md CHANGED
@@ -60,8 +60,8 @@ import { ID, Int } from 'type-graphql';
60
60
 
61
61
  // --- Author ---
62
62
  const authorFields = defineFields(Author, {
63
- id: { type: () => ID, generateFilter: true },
64
- name: { type: () => String, generateFilter: true },
63
+ id: { type: () => ID, generateFilter: true },
64
+ name: { type: () => String, generateFilter: true },
65
65
  });
66
66
 
67
67
  @GQLEntityClass(Author, authorFields)
@@ -69,10 +69,10 @@ export class AuthorGQL extends GQLEntityBase {}
69
69
 
70
70
  // --- Book ---
71
71
  const bookFields = defineFields(Book, {
72
- id: { type: () => ID, generateFilter: true },
73
- title: { type: () => String, generateFilter: true },
74
- publishedYear: { type: () => Int, generateFilter: true },
75
- author: { type: () => AuthorGQL, options: { nullable: true } },
72
+ id: { type: () => ID, generateFilter: true },
73
+ title: { type: () => String, generateFilter: true },
74
+ publishedYear: { type: () => Int, generateFilter: true },
75
+ author: { type: () => AuthorGQL, options: { nullable: true } },
76
76
  });
77
77
 
78
78
  @GQLEntityClass(Book, bookFields)
@@ -81,14 +81,14 @@ export class BookGQL extends GQLEntityBase {}
81
81
 
82
82
  The decorator attaches generated statics to the class:
83
83
 
84
- | Static | Purpose |
85
- |--------|---------|
86
- | `BookGQL.FilterInput` | Generated filter input type |
87
- | `BookGQL.PaginationInput` | Generated pagination input type |
88
- | `BookGQL.OrderBy` | Generated order-by input type |
89
- | `BookGQL.FieldsResolver` | Auto-generated field resolver class |
90
- | `BookGQL.gqlEntityName` | GQL type name (with suffix if configured) |
91
- | `BookGQL.relatedEntityName` | ORM entity class name (`'Book'`) |
84
+ | Static | Purpose |
85
+ | --------------------------- | ----------------------------------------- |
86
+ | `BookGQL.FilterInput` | Generated filter input type |
87
+ | `BookGQL.PaginationInput` | Generated pagination input type |
88
+ | `BookGQL.OrderBy` | Generated order-by input type |
89
+ | `BookGQL.FieldsResolver` | Auto-generated field resolver class |
90
+ | `BookGQL.gqlEntityName` | GQL type name (with suffix if configured) |
91
+ | `BookGQL.relatedEntityName` | ORM entity class name (`'Book'`) |
92
92
 
93
93
  ### 2. Write Resolvers
94
94
 
@@ -99,14 +99,14 @@ import { GraphQLResolveInfo } from 'graphql';
99
99
 
100
100
  @GQLResolver(BookGQL)
101
101
  export class BookResolver {
102
- @Query(() => [BookGQL])
103
- async books(
104
- @Arg('filter', () => BookGQL.FilterInput, { nullable: true }) filter: any,
105
- @Arg('pagination', () => BookGQL.PaginationInput, { nullable: true }) pagination: any,
106
- @Info() info: GraphQLResolveInfo,
107
- ) {
108
- return queryManager.getQueryResultsForInfo(metadataProvider, BookGQL, info, filter, pagination);
109
- }
102
+ @Query(() => [BookGQL])
103
+ async books(
104
+ @Arg('filter', () => BookGQL.FilterInput, { nullable: true }) filter: any,
105
+ @Arg('pagination', () => BookGQL.PaginationInput, { nullable: true }) pagination: any,
106
+ @Info() info: GraphQLResolveInfo
107
+ ) {
108
+ return queryManager.getQueryResultsForInfo(metadataProvider, BookGQL, info, filter, pagination);
109
+ }
110
110
  }
111
111
  ```
112
112
 
@@ -118,10 +118,10 @@ import { getAutoResolvers } from '@dav3/gql-of-power';
118
118
  import '../entities'; // trigger @GQLEntityClass decoration
119
119
 
120
120
  const schema = buildSchemaSync({
121
- resolvers: [
122
- BookResolver,
123
- ...getAutoResolvers(), // FieldsResolver for every @GQLEntityClass entity
124
- ],
121
+ resolvers: [
122
+ BookResolver,
123
+ ...getAutoResolvers(), // FieldsResolver for every @GQLEntityClass entity
124
+ ],
125
125
  });
126
126
  ```
127
127
 
@@ -141,23 +141,23 @@ Typed wrapper that constrains config keys to `keyof T`. Identity function at run
141
141
 
142
142
  ```typescript
143
143
  const fields = defineFields(Book, {
144
- id: { type: () => ID, generateFilter: true },
145
- title: { type: () => String, generateFilter: true },
146
- typo: { type: () => String }, // TS error — 'typo' is not keyof Book ✗
144
+ id: { type: () => ID, generateFilter: true },
145
+ title: { type: () => String, generateFilter: true },
146
+ typo: { type: () => String }, // TS error — 'typo' is not keyof Book ✗
147
147
  });
148
148
  ```
149
149
 
150
150
  #### Field options
151
151
 
152
- | Option | Type | Purpose |
153
- |--------|------|---------|
154
- | `type` | `() => GraphQLType` | GraphQL return type (required) |
155
- | `generateFilter` | `boolean` | Generate filter input fields for this property |
156
- | `options` | `FieldOptions` | type-graphql field options (nullable, description, etc.) |
157
- | `alias` | `string` | Override the GQL field name |
158
- | `array` | `true` | Mark as array return type |
159
- | `relatedEntityName` | `() => string` | ORM entity name for array relation fields (auto-derived when using `@GQLEntityClass`) |
160
- | `enum` | `EnumData` | Register an enum type |
152
+ | Option | Type | Purpose |
153
+ | ------------------- | ------------------- | ------------------------------------------------------------------------------------- |
154
+ | `type` | `() => GraphQLType` | GraphQL return type (required) |
155
+ | `generateFilter` | `boolean` | Generate filter input fields for this property |
156
+ | `options` | `FieldOptions` | type-graphql field options (nullable, description, etc.) |
157
+ | `alias` | `string` | Override the GQL field name |
158
+ | `array` | `true` | Mark as array return type |
159
+ | `relatedEntityName` | `() => string` | ORM entity name for array relation fields (auto-derived when using `@GQLEntityClass`) |
160
+ | `enum` | `EnumData` | Register an enum type |
161
161
 
162
162
  ### `@GQLEntityClass(OrmClass, fields, extra?)`
163
163
 
@@ -195,29 +195,29 @@ Provide a GraphQL `@FieldResolver` function. The library fetches field(s) listed
195
195
 
196
196
  ```typescript
197
197
  @GQLEntityClass(Fellowship, fields, {
198
- customFields: {
199
- firstMember: {
200
- type: () => GraphQLJSON,
201
- options: { nullable: true },
202
- requires: 'id', // ensure 'id' is fetched even if client didn't request it
203
- resolveDecorators: [Root(), Ctx()],
204
- resolve: (root: Fellowship, ctx: any) => {
205
- return memberDataLoader.load(root.id);
206
- },
207
- },
208
- },
198
+ customFields: {
199
+ firstMember: {
200
+ type: () => GraphQLJSON,
201
+ options: { nullable: true },
202
+ requires: 'id', // ensure 'id' is fetched even if client didn't request it
203
+ resolveDecorators: [Root(), Ctx()],
204
+ resolve: (root: Fellowship, ctx: any) => {
205
+ return memberDataLoader.load(root.id);
206
+ },
207
+ },
208
+ },
209
209
  })
210
210
  export class FellowshipGQL extends GQLEntityBase {}
211
211
  ```
212
212
 
213
213
  #### `resolve` field options
214
214
 
215
- | Option | Purpose |
216
- |--------|---------|
217
- | `type` | GraphQL return type |
218
- | `options` | type-graphql field options |
219
- | `requires` | Field name(s) to ensure are fetched from DB |
220
- | `resolve` | The resolver function (required) |
215
+ | Option | Purpose |
216
+ | ------------------- | ---------------------------------------------------------------------- |
217
+ | `type` | GraphQL return type |
218
+ | `options` | type-graphql field options |
219
+ | `requires` | Field name(s) to ensure are fetched from DB |
220
+ | `resolve` | The resolver function (required) |
221
221
  | `resolveDecorators` | type-graphql parameter decorators in order (`[Root(), Ctx(), Info()]`) |
222
222
 
223
223
  ### Strategy 2: `mapping` — automatic SQL JOIN
@@ -227,63 +227,63 @@ Provide a `FieldMappingConfig`. The library generates a SQL `LEFT JOIN LATERAL`
227
227
  Use this when the foreign key exists as a plain column on the entity (not declared as an ORM relation).
228
228
 
229
229
  ```typescript
230
- import { CrmAccount } from './orm-entities';
231
-
232
- @GQLEntityClass(Job, fields, {
233
- customFields: {
234
- account: {
235
- type: () => CrmAccountGQL,
236
- options: { nullable: true },
237
- mapping: {
238
- refEntity: CrmAccount, // ORM entity class to JOIN to
239
- refFields: 'id', // column(s) on CrmAccount — keyof CrmAccount ✓
240
- fields: 'crmAccountId', // column(s) on Job — keyof Job ✓
241
- },
242
- },
243
- },
230
+ import { Kingdom } from './orm-entities';
231
+
232
+ @GQLEntityClass(Hobbit, fields, {
233
+ customFields: {
234
+ kingdom: {
235
+ type: () => KingdomGQL,
236
+ options: { nullable: true },
237
+ mapping: {
238
+ refEntity: Kingdom, // ORM entity class to JOIN to
239
+ refFields: 'id', // column(s) on Kingdom — keyof Kingdom ✓
240
+ fields: 'kingdomId', // column(s) on Hobbit — keyof Hobbit ✓
241
+ },
242
+ },
243
+ },
244
244
  })
245
- export class JobGQL extends GQLEntityBase {}
245
+ export class HobbitGQL extends GQLEntityBase {}
246
246
  ```
247
247
 
248
248
  Composite FK — use arrays (must have the same length):
249
249
 
250
250
  ```typescript
251
251
  mapping: {
252
- refEntity: OrderLine,
253
- refFields: ['tenantId', 'externalId'],
254
- fields: ['tenantId', 'lineExternalId'],
252
+ refEntity: Weapon,
253
+ refFields: ['realmId', 'weaponCode'],
254
+ fields: ['realmId', 'weaponExternalId'],
255
255
  }
256
256
  ```
257
257
 
258
258
  #### `mapping` field options
259
259
 
260
- | Option | Type | Purpose |
261
- |--------|------|---------|
262
- | `type` | `() => GraphQLType` | GraphQL return type |
263
- | `options` | `FieldOptions` | type-graphql field options (nullable, etc.) |
264
- | `mapping.refEntity` | `new () => TRef` | ORM entity class to JOIN to (must be in the metadata provider) |
265
- | `mapping.refFields` | `keyof TRef \| Array<keyof TRef>` | Column(s) on the ref entity to match against |
266
- | `mapping.fields` | `keyof T \| Array<keyof T>` | Column(s) on the owner entity to match from |
260
+ | Option | Type | Purpose |
261
+ | ------------------- | --------------------------------- | -------------------------------------------------------------- |
262
+ | `type` | `() => GraphQLType` | GraphQL return type |
263
+ | `options` | `FieldOptions` | type-graphql field options (nullable, etc.) |
264
+ | `mapping.refEntity` | `new () => TRef` | ORM entity class to JOIN to (must be in the metadata provider) |
265
+ | `mapping.refFields` | `keyof TRef \| Array<keyof TRef>` | Column(s) on the ref entity to match against |
266
+ | `mapping.fields` | `keyof T \| Array<keyof T>` | Column(s) on the owner entity to match from |
267
267
 
268
268
  > **Note**: `resolve` and `mapping` are mutually exclusive — TypeScript enforces this via a discriminated union. `resolveDecorators` and `requires` are only valid on the `resolve` branch.
269
269
 
270
270
  #### Generated SQL
271
271
 
272
- For `account: { mapping: { refEntity: CrmAccount, refFields: 'id', fields: 'crmAccountId' } }`:
272
+ For `kingdom: { mapping: { refEntity: Kingdom, refFields: 'id', fields: 'kingdomId' } }`:
273
273
 
274
274
  ```sql
275
- select e_a1.id, e_a1.crm_account_id, f_j1.value as "account"
275
+ select e_a1.id, e_a1.kingdom_id, f_j1.value as "kingdom"
276
276
  from (
277
- select e_a1.id, e_a1.crm_account_id
278
- from job as e_a1
277
+ select e_a1.id, e_a1.kingdom_id
278
+ from hobbit as e_a1
279
279
  where true
280
280
  ) as e_a1
281
281
  left outer join lateral (
282
282
  select row_to_json(f_j1)::jsonb as value
283
283
  from (
284
- select f_j1.id, f_j1.account_name
285
- from "crm_account" as f_j1
286
- where e_a1.crm_account_id = f_j1.id
284
+ select f_j1.id, f_j1.name
285
+ from kingdom as f_j1
286
+ where e_a1.kingdom_id = f_j1.id
287
287
  ) as f_j1
288
288
  ) as f_j1 on true
289
289
  ```
@@ -317,17 +317,17 @@ filter: {
317
317
 
318
318
  ### Filter operations
319
319
 
320
- | Operation | Meaning |
321
- |-----------|---------|
322
- | `_eq` | Equal |
323
- | `_ne` | Not equal |
324
- | `_in` | In array |
325
- | `_nin` | Not in array |
326
- | `_like` | ILIKE (case-insensitive contains) |
320
+ | Operation | Meaning |
321
+ | -------------- | ------------------------------------ |
322
+ | `_eq` | Equal |
323
+ | `_ne` | Not equal |
324
+ | `_in` | In array |
325
+ | `_nin` | Not in array |
326
+ | `_like` | ILIKE (case-insensitive contains) |
327
327
  | `_gt` / `_gte` | Greater than / greater than or equal |
328
- | `_lt` / `_lte` | Less than / less than or equal |
329
- | `_and` | Logical AND |
330
- | `_or` | Logical OR (generates UNION ALL) |
328
+ | `_lt` / `_lte` | Less than / less than or equal |
329
+ | `_and` | Logical AND |
330
+ | `_or` | Logical OR (generates UNION ALL) |
331
331
 
332
332
  ---
333
333
 
@@ -345,13 +345,13 @@ pagination: {
345
345
 
346
346
  ## Relationship Handling
347
347
 
348
- | Relationship | SQL Strategy |
349
- |--------------|-------------|
350
- | **m:1** (many-to-one) | `LEFT JOIN LATERAL` + `row_to_json` |
351
- | **1:1** (one-to-one) | `LEFT JOIN LATERAL` + `row_to_json` |
352
- | **1:m** (one-to-many) | `LEFT JOIN LATERAL` + `json_agg` |
353
- | **m:m** (many-to-many) | Pivot table subquery + `json_agg` |
354
- | **custom `mapping`** | `LEFT JOIN LATERAL` + `row_to_json` (same as m:1) |
348
+ | Relationship | SQL Strategy |
349
+ | ---------------------- | ------------------------------------------------- |
350
+ | **m:1** (many-to-one) | `LEFT JOIN LATERAL` + `row_to_json` |
351
+ | **1:1** (one-to-one) | `LEFT JOIN LATERAL` + `row_to_json` |
352
+ | **1:m** (one-to-many) | `LEFT JOIN LATERAL` + `json_agg` |
353
+ | **m:m** (many-to-many) | Pivot table subquery + `json_agg` |
354
+ | **custom `mapping`** | `LEFT JOIN LATERAL` + `row_to_json` (same as m:1) |
355
355
 
356
356
  ---
357
357
 
@@ -359,15 +359,15 @@ pagination: {
359
359
 
360
360
  ### Environment Variables
361
361
 
362
- | Variable | Purpose |
363
- |----------|---------|
364
- | `D3GOP_SORT_SUFFIX` | Suffix appended to all generated GQL type names and the Sort enum (e.g. `'V2'` → `BookV2`, `SortV2`) |
365
- | `D3GOP_TYPES_SUFFIX` | Fallback suffix if `D3GOP_SORT_SUFFIX` is not set |
366
- | `D3GOP_LOG_TYPE` | Logging level: `debug` or `disabled` |
367
- | `D3GOP_DEFAULT_QUERY_LIMIT` | Default query limit when pagination is not specified (default: `3000`) |
368
- | `D3GOP_USE_STRING_FOR_JSONB` | Toggle between JSONB and string concatenation for JSON aggregation |
362
+ | Variable | Purpose |
363
+ | ---------------------------- | ------------------------------------------------------------------------------------------- |
364
+ | `D3GOP_TYPES_SUFFIX` | Suffix appended to all generated GQL entity type names (e.g. `'V2'` → `BookV2`, `AuthorV2`) |
365
+ | `D3GOP_SORT_SUFFIX` | Suffix appended to sort/pagination types only (e.g. `'V2'` → `SortV2`, `BookV2OrderBy`) |
366
+ | `D3GOP_LOG_TYPE` | Logging level: `debug` or `disabled` |
367
+ | `D3GOP_DEFAULT_QUERY_LIMIT` | Default query limit when pagination is not specified (default: `3000`) |
368
+ | `D3GOP_USE_STRING_FOR_JSONB` | Toggle between JSONB and string concatenation for JSON aggregation |
369
369
 
370
- > **Type name collision**: If you have both v1 (`createGQLTypes`) and v2 (`@GQLEntityClass`) entities in the same schema, set `D3GOP_SORT_SUFFIX` / `D3GOP_TYPES_SUFFIX` so v2 entity names are distinct (e.g. `Job` → `JobV2`). No `setGlobalConfig()` call is required — the env var is read automatically.
370
+ > **Type name collision**: If you have both v1 (`createGQLTypes`) and v2 (`@GQLEntityClass`) entities in the same schema, set `D3GOP_TYPES_SUFFIX` so v2 entity names are distinct (e.g. `Hobbit` → `HobbitV2`). Use `D3GOP_SORT_SUFFIX` separately if sort/pagination types also need a suffix. No `setGlobalConfig()` call is required — the env vars are read automatically.
371
371
 
372
372
  ### Programmatic config
373
373
 
@@ -376,6 +376,9 @@ import { setGlobalConfig } from '@dav3/gql-of-power';
376
376
 
377
377
  // Call before any @GQLEntityClass decorators run (i.e. before importing entity files)
378
378
  setGlobalConfig({ gqlTypesSuffix: 'V2' });
379
+
380
+ // Optionally set a separate suffix for sort/pagination types
381
+ setGlobalConfig({ gqlTypesSuffix: 'V2', gqlSortSuffix: 'V2' });
379
382
  ```
380
383
 
381
384
  ---
@@ -387,20 +390,20 @@ const queryManager = new GQLQueryManager();
387
390
 
388
391
  // From a GraphQL resolver — fields are parsed from resolve info automatically
389
392
  const results = await queryManager.getQueryResultsForInfo(
390
- metadataProvider,
391
- BookGQL, // @GQLEntityClass-decorated class or plain ORM class
392
- info, // GraphQLResolveInfo
393
- filter,
394
- pagination
393
+ metadataProvider,
394
+ BookGQL, // @GQLEntityClass-decorated class or plain ORM class
395
+ info, // GraphQLResolveInfo
396
+ filter,
397
+ pagination
395
398
  );
396
399
 
397
400
  // With explicit field selection (useful for testing or non-resolver contexts)
398
401
  const results = await queryManager.getQueryResultsForFields(
399
- metadataProvider,
400
- BookGQL,
401
- { id: {}, title: {} },
402
- filter,
403
- pagination
402
+ metadataProvider,
403
+ BookGQL,
404
+ { id: {}, title: {} },
405
+ filter,
406
+ pagination
404
407
  );
405
408
  ```
406
409
 
@@ -412,14 +415,14 @@ const results = await queryManager.getQueryResultsForFields(
412
415
 
413
416
  ### Core Components
414
417
 
415
- | Component | Purpose |
416
- |-----------|---------|
417
- | `GQLtoSQLMapper` | Transforms GraphQL field selections into SQL with proper joins |
418
- | `GQLQueryManager` | Orchestrates query building and execution |
419
- | `FilterProcessor` | Translates GQL filter inputs to SQL WHERE clauses |
420
- | `RelationshipHandler` | Generates JOIN SQL for ORM-declared relations |
421
- | `SQLBuilder` | Assembles final SQL strings and JSON aggregations |
422
- | `AliasManager` | Manages incremental SQL aliases to prevent naming conflicts |
418
+ | Component | Purpose |
419
+ | --------------------- | -------------------------------------------------------------- |
420
+ | `GQLtoSQLMapper` | Transforms GraphQL field selections into SQL with proper joins |
421
+ | `GQLQueryManager` | Orchestrates query building and execution |
422
+ | `FilterProcessor` | Translates GQL filter inputs to SQL WHERE clauses |
423
+ | `RelationshipHandler` | Generates JOIN SQL for ORM-declared relations |
424
+ | `SQLBuilder` | Assembles final SQL strings and JSON aggregations |
425
+ | `AliasManager` | Manages incremental SQL aliases to prevent naming conflicts |
423
426
 
424
427
  ### Query Flow
425
428
 
@@ -2,7 +2,8 @@ import { getMetadataStorage } from 'type-graphql';
2
2
  import { CustomFieldsSettings, FieldSettings, FieldsSettings, GQLEntityFilterInputFieldType, GQLEntityPaginationInputType, RelatedFieldSettings } from '../types';
3
3
  import { AccessControlEntry } from '../types/access-control';
4
4
  export declare const setGlobalConfig: (config: {
5
- gqlTypesSuffix: string;
5
+ gqlTypesSuffix?: string;
6
+ gqlSortSuffix?: string;
6
7
  }) => void;
7
8
  export declare const getFieldsOptionsFor: (name: string) => Record<string, string>;
8
9
  export declare const getFieldByAlias: (entityName: string | undefined, alias: string) => string;
@@ -27,9 +27,13 @@ const aclMap = {};
27
27
  const autoResolverRegistry = new Map();
28
28
  // ─── Global config ───────────────────────────────────────────────────────────
29
29
  let gqlTypesSuffix = '';
30
+ let gqlSortSuffix = '';
30
31
  let sortEnumRegistered = false;
31
32
  const setGlobalConfig = (config) => {
32
- gqlTypesSuffix = config.gqlTypesSuffix;
33
+ if (config.gqlTypesSuffix !== undefined)
34
+ gqlTypesSuffix = config.gqlTypesSuffix;
35
+ if (config.gqlSortSuffix !== undefined)
36
+ gqlSortSuffix = config.gqlSortSuffix;
33
37
  };
34
38
  exports.setGlobalConfig = setGlobalConfig;
35
39
  // ─── Public accessors ────────────────────────────────────────────────────────
@@ -41,7 +45,7 @@ const getCustomFieldsFor = (name) => CustomFieldsMap[name] ?? {};
41
45
  exports.getCustomFieldsFor = getCustomFieldsFor;
42
46
  const getACLFor = (name) => aclMap[name] ?? {};
43
47
  exports.getACLFor = getACLFor;
44
- const getGQLEntityNameFor = (name) => `${name}${gqlTypesSuffix || process.env['D3GOP_SORT_SUFFIX'] || process.env['D3GOP_TYPES_SUFFIX'] || ''}`;
48
+ const getGQLEntityNameFor = (name) => `${name}${gqlTypesSuffix || process.env['D3GOP_TYPES_SUFFIX'] || ''}`;
45
49
  exports.getGQLEntityNameFor = getGQLEntityNameFor;
46
50
  const getGQLEntityNameForClass = (classType) => (0, exports.getGQLEntityNameFor)(classType.name);
47
51
  exports.getGQLEntityNameForClass = getGQLEntityNameForClass;
@@ -67,18 +71,15 @@ function getAutoResolvers() {
67
71
  }
68
72
  // ─── Sort enum deferred registration ────────────────────────────────────────
69
73
  /**
70
- * Registers the Sort enum with type-graphql using the current suffix.
74
+ * Registers the Sort enum with type-graphql using the current sort suffix.
71
75
  * Deferred from module load so that setGlobalConfig() can be called first,
72
- * or falls back to the D3GOP_SORT_SUFFIX / D3GOP_TYPES_SUFFIX env variable.
76
+ * or falls back to the D3GOP_SORT_SUFFIX env variable.
73
77
  * Safe to call multiple times — only registers once.
74
78
  */
75
79
  function ensureSortRegistered() {
76
80
  if (sortEnumRegistered)
77
81
  return;
78
- const suffix = gqlTypesSuffix ||
79
- process.env['D3GOP_SORT_SUFFIX'] ||
80
- process.env['D3GOP_TYPES_SUFFIX'] ||
81
- '';
82
+ const suffix = gqlSortSuffix || process.env['D3GOP_SORT_SUFFIX'] || '';
82
83
  (0, type_graphql_1.registerEnumType)(types_1.Sort, { name: `Sort${suffix}` });
83
84
  sortEnumRegistered = true;
84
85
  }
@@ -127,7 +127,7 @@ class GQLtoSQLMapper {
127
127
  const decoratorAlias = (0, entities_1.getFieldByAlias)(entityMetadata.name, gqlFieldNameKey);
128
128
  const fieldName = decoratorAlias !== gqlFieldNameKey
129
129
  ? decoratorAlias
130
- : name ?? gqlFieldNameKey;
130
+ : (name ?? gqlFieldNameKey);
131
131
  variables_1.logger.log(logPrefix, '- mapFilter for', gqlFieldNameKey, 'alias for', fieldName
132
132
  // 'keys:',
133
133
  // ...mappings.keys()
@@ -180,7 +180,7 @@ class GQLtoSQLMapper {
180
180
  mapCustomField(customFieldProps, mapping, latestAlias, gqlFieldName, mappings, fieldsByTypeName, ownerMetadata) {
181
181
  // mapping strategy: generate a SQL JOIN to the reference entity
182
182
  if (customFieldProps && 'mapping' in customFieldProps && customFieldProps.mapping) {
183
- const { refEntity, refFields: rawRefFields, fields: rawLocalFields } = customFieldProps.mapping;
183
+ const { refEntity, refFields: rawRefFields, fields: rawLocalFields, } = customFieldProps.mapping;
184
184
  // Normalise single string → array for uniform handling
185
185
  const refFields = Array.isArray(rawRefFields) ? rawRefFields : [rawRefFields];
186
186
  const localFields = Array.isArray(rawLocalFields) ? rawLocalFields : [rawLocalFields];
@@ -222,9 +222,7 @@ class GQLtoSQLMapper {
222
222
  // JSON aggregate — single object (not array), same pattern as RelationshipHandler.mapManyToOne
223
223
  mapping.json.push(`${joinAlias.toColumnName('value')} as "${gqlFieldName}"`);
224
224
  const selectFields = [
225
- ...new Set(refSqlCols
226
- .map((sqlCol) => joinAlias.toColumnName(sqlCol))
227
- .concat(Array.from(refSelect))),
225
+ ...new Set(refSqlCols.map((sqlCol) => joinAlias.toColumnName(sqlCol)).concat(Array.from(refSelect))),
228
226
  ];
229
227
  const jsonSQL = sql_builder_1.SQLBuilder.generateJsonSelectStatement(joinAlias.toString()); // single object
230
228
  const subFromSQL = `(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dav3/gql-of-power",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -38,10 +38,12 @@ const autoResolverRegistry = new Map<string, new () => any>();
38
38
  // ─── Global config ───────────────────────────────────────────────────────────
39
39
 
40
40
  let gqlTypesSuffix = '';
41
+ let gqlSortSuffix = '';
41
42
  let sortEnumRegistered = false;
42
43
 
43
- export const setGlobalConfig = (config: { gqlTypesSuffix: string }) => {
44
- gqlTypesSuffix = config.gqlTypesSuffix;
44
+ export const setGlobalConfig = (config: { gqlTypesSuffix?: string; gqlSortSuffix?: string }) => {
45
+ if (config.gqlTypesSuffix !== undefined) gqlTypesSuffix = config.gqlTypesSuffix;
46
+ if (config.gqlSortSuffix !== undefined) gqlSortSuffix = config.gqlSortSuffix;
45
47
  };
46
48
 
47
49
  // ─── Public accessors ────────────────────────────────────────────────────────
@@ -55,7 +57,7 @@ export const getCustomFieldsFor = (name: string) => CustomFieldsMap[name] ?? {};
55
57
  export const getACLFor = (name: string) => aclMap[name] ?? {};
56
58
 
57
59
  export const getGQLEntityNameFor = (name: string) =>
58
- `${name}${gqlTypesSuffix || process.env['D3GOP_SORT_SUFFIX'] || process.env['D3GOP_TYPES_SUFFIX'] || ''}`;
60
+ `${name}${gqlTypesSuffix || process.env['D3GOP_TYPES_SUFFIX'] || ''}`;
59
61
  export const getGQLEntityNameForClass = <T>(classType: new () => T) =>
60
62
  getGQLEntityNameFor(classType.name);
61
63
  export const getGQLEntityFieldResolverName = (gqlEntityName: string) =>
@@ -83,18 +85,14 @@ export function getAutoResolvers(): Array<new () => any> {
83
85
  // ─── Sort enum deferred registration ────────────────────────────────────────
84
86
 
85
87
  /**
86
- * Registers the Sort enum with type-graphql using the current suffix.
88
+ * Registers the Sort enum with type-graphql using the current sort suffix.
87
89
  * Deferred from module load so that setGlobalConfig() can be called first,
88
- * or falls back to the D3GOP_SORT_SUFFIX / D3GOP_TYPES_SUFFIX env variable.
90
+ * or falls back to the D3GOP_SORT_SUFFIX env variable.
89
91
  * Safe to call multiple times — only registers once.
90
92
  */
91
93
  function ensureSortRegistered() {
92
94
  if (sortEnumRegistered) return;
93
- const suffix =
94
- gqlTypesSuffix ||
95
- process.env['D3GOP_SORT_SUFFIX'] ||
96
- process.env['D3GOP_TYPES_SUFFIX'] ||
97
- '';
95
+ const suffix = gqlSortSuffix || process.env['D3GOP_SORT_SUFFIX'] || '';
98
96
  registerEnumType(Sort, { name: `Sort${suffix}` });
99
97
  sortEnumRegistered = true;
100
98
  }
@@ -268,7 +266,9 @@ export function GQLEntityClass<T extends Object, K>(
268
266
  * attempt to derive it from the decorated type class's `.relatedEntityName` static.
269
267
  * This allows `defineFields` consumers to skip the redundant `relatedEntityName` boilerplate.
270
268
  */
271
- function _resolveRelatedEntityNames<T>(fields: Partial<FieldsSettings<T>>): Partial<FieldsSettings<T>> {
269
+ function _resolveRelatedEntityNames<T>(
270
+ fields: Partial<FieldsSettings<T>>
271
+ ): Partial<FieldsSettings<T>> {
272
272
  const resolved: Partial<FieldsSettings<T>> = {};
273
273
  for (const [fieldName, fieldOptions] of Object.entries(fields)) {
274
274
  if (!fieldOptions) {
@@ -386,14 +386,7 @@ export function createGQLEntity<T extends Object, K>(
386
386
  ObjectType(gqlEntityName)(GQLEntity);
387
387
 
388
388
  function buildResolvers() {
389
- return _buildResolversForEntity(
390
- GQLEntity,
391
- gqlEntityName,
392
- fields,
393
- opts,
394
- metadata,
395
- customFields
396
- );
389
+ return _buildResolversForEntity(GQLEntity, gqlEntityName, fields, opts, metadata, customFields);
397
390
  }
398
391
 
399
392
  return {
@@ -671,7 +664,7 @@ export function createGQLEntityFilters<T, K>(
671
664
  ? options.filter(
672
665
  ({ appliesToArray }) =>
673
666
  (!appliesToArray && includeNotArrays) || (appliesToArray && getFilterType)
674
- )
667
+ )
675
668
  : [];
676
669
 
677
670
  if (canFilterForField && applicableOptions.length > 0) {
@@ -744,7 +737,7 @@ export function createGQLEntityFilters<T, K>(
744
737
  TypeMap[getGQLEntityNameFor(fieldOptions.relatedEntityName()) + 'FilterInput'] ??
745
738
  GQLEntityFilterInputField
746
739
  );
747
- }
740
+ }
748
741
  : () => GQLEntityFilterInputField,
749
742
  options: { ...fieldOptions.options, nullable: true },
750
743
  typeOptions: { nullable: true },
@@ -1 +1 @@
1
- export * from './gql-entity';
1
+ export * from './gql-entity';
@@ -6,7 +6,11 @@ export enum AliasType {
6
6
  value = 'value',
7
7
  }
8
8
  export class Alias {
9
- constructor(private type: AliasType, private index: number, private prefix: string) {}
9
+ constructor(
10
+ private type: AliasType,
11
+ private index: number,
12
+ private prefix: string
13
+ ) {}
10
14
 
11
15
  public toString() {
12
16
  return `${this.type[0]}_${this.prefix}${this.index}`;
@@ -97,16 +97,19 @@ export class FilterProcessor extends ClassOperations {
97
97
  // Handle nested field operations like { id: { _in }, id: { _eq }, ... }
98
98
  const fieldOperations =
99
99
  typeof filterValue === 'object'
100
- ? keys(filterValue ?? ({} as Partial<typeof filterValue>)).reduce((acc, key) => {
101
- const k = keys(FieldOperations).find((k) => k.toString() === key.toString());
102
- if (k) {
103
- acc.push({
104
- fieldOperation: k,
105
- value: (filterValue as any)[key],
106
- });
107
- }
108
- return acc;
109
- }, [] as Array<{ fieldOperation: keyof FieldOperationsType; value: any }>)
100
+ ? keys(filterValue ?? ({} as Partial<typeof filterValue>)).reduce(
101
+ (acc, key) => {
102
+ const k = keys(FieldOperations).find((k) => k.toString() === key.toString());
103
+ if (k) {
104
+ acc.push({
105
+ fieldOperation: k,
106
+ value: (filterValue as any)[key],
107
+ });
108
+ }
109
+ return acc;
110
+ },
111
+ [] as Array<{ fieldOperation: keyof FieldOperationsType; value: any }>
112
+ )
110
113
  : ([] as Array<{ fieldOperation: keyof FieldOperationsType; value: any }>);
111
114
 
112
115
  if (fieldOperation) {
@@ -434,8 +437,8 @@ export class FilterProcessor extends ClassOperations {
434
437
  ? properties[gqlFieldNameKey as keyof EntityMetadata<T>['properties']]
435
438
  ? gqlFieldNameKey
436
439
  : properties[lowercasedFirstFieldNameKey as keyof EntityMetadata<T>['properties']]
437
- ? lowercasedFirstFieldNameKey
438
- : null
440
+ ? lowercasedFirstFieldNameKey
441
+ : null
439
442
  : null;
440
443
 
441
444
  const gqlFieldName = (customFieldProps?.requires as string) ?? fieldNameKey;
@@ -636,9 +639,9 @@ export class FilterProcessor extends ClassOperations {
636
639
  const fieldNameBeforeOperation =
637
640
  gqlFieldNameKey.indexOf(fieldOperation) > 0
638
641
  ? // id_eq
639
- gqlFieldNameKey.slice(0, -fieldOperation.length)
642
+ gqlFieldNameKey.slice(0, -fieldOperation.length)
640
643
  : // Id: { _eq }
641
- gqlFieldNameKey.charAt(0).toLowerCase() + gqlFieldNameKey.slice(1);
644
+ gqlFieldNameKey.charAt(0).toLowerCase() + gqlFieldNameKey.slice(1);
642
645
 
643
646
  if (fieldNameBeforeOperation) {
644
647
  const fieldProps = properties[fieldNameBeforeOperation as keyof typeof properties];
@@ -902,7 +905,7 @@ export class FilterProcessor extends ClassOperations {
902
905
  outerJoin,
903
906
  whereSQL,
904
907
  whereWithValues
905
- );
908
+ );
906
909
 
907
910
  const existsSQL = `exists (${subquery})`.replaceAll(/[ \n\t]+/gi, ' ');
908
911
 
@@ -971,10 +974,10 @@ export class FilterProcessor extends ClassOperations {
971
974
 
972
975
  logger.log('FilterProcessor - mapFilterManyToOne: whereSQL', alias.toString(), unionAll);
973
976
 
974
- const subquery =
975
- unionAll.length > 0
976
- ? unionAll.map((q) => `(${q})`).join(' union all ')
977
- : this.buildManyToOneJoin(
977
+ const subquery =
978
+ unionAll.length > 0
979
+ ? unionAll.map((q) => `(${q})`).join(' union all ')
980
+ : this.buildManyToOneJoin(
978
981
  [],
979
982
  alias,
980
983
  referenceField.tableName,
@@ -982,7 +985,7 @@ export class FilterProcessor extends ClassOperations {
982
985
  outerJoin,
983
986
  whereSQL,
984
987
  whereWithValues
985
- );
988
+ );
986
989
 
987
990
  const existsSQL = `exists (${subquery})`.replaceAll(/[ \n\t]+/gi, ' ');
988
991
 
@@ -1075,7 +1078,7 @@ export class FilterProcessor extends ClassOperations {
1075
1078
  outerJoin,
1076
1079
  whereSQL,
1077
1080
  whereWithValues
1078
- );
1081
+ );
1079
1082
 
1080
1083
  const existsSQL = `exists (${subquery})`.replaceAll(/[ \n\t]+/gi, ' ');
1081
1084
 
@@ -225,7 +225,7 @@ export class GQLtoSQLMapper {
225
225
  name: pk,
226
226
  alias: pk,
227
227
  },
228
- },
228
+ },
229
229
  definedFields
230
230
  ) ?? definedFields;
231
231
 
@@ -253,7 +253,7 @@ export class GQLtoSQLMapper {
253
253
  const fieldName =
254
254
  decoratorAlias !== gqlFieldNameKey
255
255
  ? decoratorAlias
256
- : (name as string | undefined) ?? gqlFieldNameKey;
256
+ : ((name as string | undefined) ?? gqlFieldNameKey);
257
257
  logger.log(
258
258
  logPrefix,
259
259
  '- mapFilter for',
@@ -295,7 +295,15 @@ export class GQLtoSQLMapper {
295
295
  );
296
296
 
297
297
  if (!fieldProps) {
298
- return this.mapCustomField<T>(customFieldProps, mapping, alias, gqlFieldName, mappings, fieldsByTypeName, entityMetadata);
298
+ return this.mapCustomField<T>(
299
+ customFieldProps,
300
+ mapping,
301
+ alias,
302
+ gqlFieldName,
303
+ mappings,
304
+ fieldsByTypeName,
305
+ entityMetadata
306
+ );
299
307
  } else {
300
308
  this.mapField<T>(
301
309
  fieldName,
@@ -359,7 +367,11 @@ export class GQLtoSQLMapper {
359
367
  ) {
360
368
  // mapping strategy: generate a SQL JOIN to the reference entity
361
369
  if (customFieldProps && 'mapping' in customFieldProps && customFieldProps.mapping) {
362
- const { refEntity, refFields: rawRefFields, fields: rawLocalFields } = customFieldProps.mapping;
370
+ const {
371
+ refEntity,
372
+ refFields: rawRefFields,
373
+ fields: rawLocalFields,
374
+ } = customFieldProps.mapping;
363
375
 
364
376
  // Normalise single string → array for uniform handling
365
377
  const refFields = Array.isArray(rawRefFields) ? rawRefFields : [rawRefFields];
@@ -397,7 +409,8 @@ export class GQLtoSQLMapper {
397
409
 
398
410
  // fieldsByTypeName is keyed by the GQL type name which may have a suffix (e.g. 'AuthorV2').
399
411
  // Resolve via getGQLEntityNameFor so the suffix is applied consistently.
400
- const subFields = fieldsByTypeName?.[getGQLEntityNameFor(refEntityName)] ?? fieldsByTypeName?.[refEntityName];
412
+ const subFields =
413
+ fieldsByTypeName?.[getGQLEntityNameFor(refEntityName)] ?? fieldsByTypeName?.[refEntityName];
401
414
  const newMappings = this.recursiveMap({
402
415
  entityMetadata: refMetadata,
403
416
  fields: subFields,
@@ -424,9 +437,7 @@ export class GQLtoSQLMapper {
424
437
 
425
438
  const selectFields = [
426
439
  ...new Set(
427
- refSqlCols
428
- .map((sqlCol) => joinAlias.toColumnName(sqlCol))
429
- .concat(Array.from(refSelect))
440
+ refSqlCols.map((sqlCol) => joinAlias.toColumnName(sqlCol)).concat(Array.from(refSelect))
430
441
  ),
431
442
  ];
432
443
 
@@ -289,7 +289,11 @@ export class RelationshipHandler {
289
289
  outerJoin.length > 0 && json.length > 0
290
290
  ? `( select ${refAlias}.*, ${json.join(', ')} from ${innerSubquery} ${outerJoin.join(' \n')} ) as ${refAlias}`
291
291
  : `${innerSubquery} ${outerJoin.join(' \n')}`;
292
- const leftOuterJoin = `left outer join lateral ( select ${jsonSQL} as value from ${fromBody} ) as ${refAlias} on true`.replaceAll(/[ \n\t]+/gi, ' ');
292
+ const leftOuterJoin =
293
+ `left outer join lateral ( select ${jsonSQL} as value from ${fromBody} ) as ${refAlias} on true`.replaceAll(
294
+ /[ \n\t]+/gi,
295
+ ' '
296
+ );
293
297
 
294
298
  mapping.json.push(`${alias.toColumnName('value')} as "${gqlFieldName}"`);
295
299
  mapping.outerJoin.push(leftOuterJoin);
@@ -182,7 +182,10 @@ export class SQLBuilder {
182
182
  ): string {
183
183
  if (joins.length > 0 && jsonColumns.length > 0) {
184
184
  const innerBody = `( select ${alias}.*, ${jsonColumns.join(', ')} from ${fromSQL} ${joins.join(' \n')} ) as ${alias}`;
185
- return `left outer join lateral ( select ${jsonSelect} as value from ${innerBody} ) as ${alias} on true`.replaceAll(/[ \n\t]+/gi, ' ');
185
+ return `left outer join lateral ( select ${jsonSelect} as value from ${innerBody} ) as ${alias} on true`.replaceAll(
186
+ /[ \n\t]+/gi,
187
+ ' '
188
+ );
186
189
  }
187
190
  return `left outer join lateral (
188
191
  select ${jsonSelect} as value
@@ -5,7 +5,11 @@ import {
5
5
  simplifyParsedResolveInfoFragmentWithType,
6
6
  } from 'graphql-parse-resolve-info';
7
7
  import knex from 'knex';
8
- import { getCustomFieldsFor, getGQLEntityNameFor, getGQLEntityNameForClass } from './entities/gql-entity';
8
+ import {
9
+ getCustomFieldsFor,
10
+ getGQLEntityNameFor,
11
+ getGQLEntityNameForClass,
12
+ } from './entities/gql-entity';
9
13
  import { GQLtoSQLMapper } from './queries/gql-to-sql-mapper';
10
14
  import {
11
15
  DatabaseDriver,
@@ -60,7 +64,14 @@ export class GQLQueryManager {
60
64
  throw new Error(`Entity ${entityName} not found in metadata`);
61
65
  }
62
66
  const fields = getGQLFields(info) as FieldSelection<T>;
63
- return this.getQueryResultsForFields<K, T>(provider, entity, fields, filter, pagination, entityName);
67
+ return this.getQueryResultsForFields<K, T>(
68
+ provider,
69
+ entity,
70
+ fields,
71
+ filter,
72
+ pagination,
73
+ entityName
74
+ );
64
75
  }
65
76
 
66
77
  async getQueryResultsForFields<K extends { _____name: string }, T>(
@@ -30,36 +30,38 @@ export type FieldOperationsType<T> = Partial<
30
30
  * - \[_in\]: typeof T\[field\]\[\]
31
31
  * - etc...
32
32
  */
33
- export type FieldValuesObjectOperationsType<T> = DefinedType<T> extends Array<infer K>
34
- ? {
35
- [k in string &
36
- keyof Pick<
37
- FieldOperationsClass<DefinedType<K>>,
38
- '_in' | '_nin' | '_contains' | '_eq' | '_overlap'
39
- >]?: FieldOperationsClass<DefinedType<K>>[k] | null;
40
- }
41
- : {
42
- [k in string & keyof FieldOperationsClass<DefinedType<T>>]?:
43
- | FieldOperationsClass<DefinedType<T>>[k]
44
- | null;
45
- };
33
+ export type FieldValuesObjectOperationsType<T> =
34
+ DefinedType<T> extends Array<infer K>
35
+ ? {
36
+ [k in string &
37
+ keyof Pick<
38
+ FieldOperationsClass<DefinedType<K>>,
39
+ '_in' | '_nin' | '_contains' | '_eq' | '_overlap'
40
+ >]?: FieldOperationsClass<DefinedType<K>>[k] | null;
41
+ }
42
+ : {
43
+ [k in string & keyof FieldOperationsClass<DefinedType<T>>]?:
44
+ | FieldOperationsClass<DefinedType<T>>[k]
45
+ | null;
46
+ };
46
47
 
47
48
  /**
48
49
  * { \[field\]: { _eq: typeof T\[field\], ... } }
49
50
  */
50
- export type FieldValuesObjectType<T> = DefinedType<DefinedType<T>> extends Array<infer K>
51
- ? Partial<FieldValuesObjectType<DefinedType<K>>>
52
- : DefinedType<T> extends Primitives
53
- ? Partial<FieldValuesObjectOperationsType<DefinedType<T>>>
54
- : {
55
- [key in string & keyof DefinedType<T>]?: DefinedType<DefinedType<T>[key]> extends Array<
56
- infer K
57
- >
58
- ? K extends Primitives
59
- ? Partial<FieldValuesObjectOperationsType<ExtractArrayType<DefinedType<K>>>>
60
- : Partial<GQLEntityFilterInputFieldType<DefinedType<K>>>
61
- : Partial<GQLEntityFilterInputFieldType<DefinedType<DefinedType<T>[key]>>>;
62
- };
51
+ export type FieldValuesObjectType<T> =
52
+ DefinedType<DefinedType<T>> extends Array<infer K>
53
+ ? Partial<FieldValuesObjectType<DefinedType<K>>>
54
+ : DefinedType<T> extends Primitives
55
+ ? Partial<FieldValuesObjectOperationsType<DefinedType<T>>>
56
+ : {
57
+ [key in string & keyof DefinedType<T>]?: DefinedType<DefinedType<T>[key]> extends Array<
58
+ infer K
59
+ >
60
+ ? K extends Primitives
61
+ ? Partial<FieldValuesObjectOperationsType<ExtractArrayType<DefinedType<K>>>>
62
+ : Partial<GQLEntityFilterInputFieldType<DefinedType<K>>>
63
+ : Partial<GQLEntityFilterInputFieldType<DefinedType<DefinedType<T>[key]>>>;
64
+ };
63
65
 
64
66
  /**
65
67
  * - \[_field\]: T\[field\]
@@ -236,9 +236,8 @@ export type GQLArgumentsFilterAndPagination<T> =
236
236
  };
237
237
 
238
238
  export type ActualValueType<T> = Exclude<T, undefined | null>;
239
- export type ExtractType<T> = ActualValueType<T> extends Array<infer K>
240
- ? ActualValueType<K>
241
- : ActualValueType<T>;
239
+ export type ExtractType<T> =
240
+ ActualValueType<T> extends Array<infer K> ? ActualValueType<K> : ActualValueType<T>;
242
241
 
243
242
  export type FieldsDetailsMap<T> = {
244
243
  [key in keyof ExtractArrayType<T>]?: FieldDetails<ExtractArrayType<T>, key>;
@@ -263,19 +262,19 @@ export type Fields<
263
262
  *
264
263
  */
265
264
  ArrayIdentifierFieldName extends string = 'getItems',
266
- SingleRecordIdentifierFieldName extends string = 'getEntity'
265
+ SingleRecordIdentifierFieldName extends string = 'getEntity',
267
266
  > = Partial<{
268
267
  [key in string & keyof NonNullable<T>]: NonNullable<NonNullable<T>[key]> extends Array<infer E>
269
268
  ?
270
269
  | Fields<NonNullable<E>, ArrayIdentifierFieldName, SingleRecordIdentifierFieldName>
271
270
  | GQLArgumentsFilterAndPagination<NonNullable<E>>
272
271
  : NonNullable<NonNullable<T>[key]> extends infer K
273
- ? K extends infer E
274
- ? ArrayIdentifierFieldName extends keyof NonNullable<E>
275
- ? NonNullable<E>[ArrayIdentifierFieldName] extends () => infer F
276
- ? F extends Array<infer G>
277
- ? Partial<
278
- | Fields<G, ArrayIdentifierFieldName, SingleRecordIdentifierFieldName> &
272
+ ? K extends infer E
273
+ ? ArrayIdentifierFieldName extends keyof NonNullable<E>
274
+ ? NonNullable<E>[ArrayIdentifierFieldName] extends () => infer F
275
+ ? F extends Array<infer G>
276
+ ? Partial<
277
+ Fields<G, ArrayIdentifierFieldName, SingleRecordIdentifierFieldName> &
279
278
  (
280
279
  | {
281
280
  [key in string & keyof typeof FieldOperations]?: Fields<
@@ -286,13 +285,12 @@ export type Fields<
286
285
  }
287
286
  | GQLArgumentsFilterAndPagination<G>
288
287
  )
289
- >
288
+ >
289
+ : {}
290
290
  : {}
291
- : {}
292
- : SingleRecordIdentifierFieldName extends keyof NonNullable<K>
293
- ? NonNullable<K>[SingleRecordIdentifierFieldName] extends () => infer F
294
- ?
295
- | Fields<NonNullable<F>, ArrayIdentifierFieldName, SingleRecordIdentifierFieldName> &
291
+ : SingleRecordIdentifierFieldName extends keyof NonNullable<K>
292
+ ? NonNullable<K>[SingleRecordIdentifierFieldName] extends () => infer F
293
+ ? Fields<NonNullable<F>, ArrayIdentifierFieldName, SingleRecordIdentifierFieldName> &
296
294
  (
297
295
  | Partial<{
298
296
  [Key in string & keyof F as `${Capitalize<Key>}`]: {
@@ -305,12 +303,12 @@ export type Fields<
305
303
  }>
306
304
  | GQLArgumentsFilterAndPagination<F>
307
305
  )
308
- : Fields<NonNullable<K>, ArrayIdentifierFieldName, SingleRecordIdentifierFieldName>
309
- : NonNullable<T>[key] | GQLArgumentsFilterAndPagination<NonNullable<T>[key]>
310
- : Fields<
311
- NonNullable<NonNullable<T>[key]>,
312
- ArrayIdentifierFieldName,
313
- SingleRecordIdentifierFieldName
314
- >
315
- : {};
306
+ : Fields<NonNullable<K>, ArrayIdentifierFieldName, SingleRecordIdentifierFieldName>
307
+ : NonNullable<T>[key] | GQLArgumentsFilterAndPagination<NonNullable<T>[key]>
308
+ : Fields<
309
+ NonNullable<NonNullable<T>[key]>,
310
+ ArrayIdentifierFieldName,
311
+ SingleRecordIdentifierFieldName
312
+ >
313
+ : {};
316
314
  }>;
@@ -1,7 +1,6 @@
1
1
  export type DefinedType<T> = Exclude<T, undefined | null | never>;
2
- export type ExtractArrayType<T> = DefinedType<T> extends Array<infer K>
3
- ? ExtractArrayType<DefinedType<K>>
4
- : DefinedType<T>;
2
+ export type ExtractArrayType<T> =
3
+ DefinedType<T> extends Array<infer K> ? ExtractArrayType<DefinedType<K>> : DefinedType<T>;
5
4
 
6
5
  export type OmitArrays<T> = {
7
6
  [K in keyof DefinedType<T> as DefinedType<DefinedType<T>[K]> extends Array<infer L>