@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 +133 -130
- package/dist/entities/gql-entity.d.ts +2 -1
- package/dist/entities/gql-entity.js +9 -8
- package/dist/queries/gql-to-sql-mapper.js +3 -5
- package/package.json +1 -1
- package/src/entities/gql-entity.ts +14 -21
- package/src/entities/index.ts +1 -1
- package/src/queries/alias.ts +5 -1
- package/src/queries/filter-processor.ts +24 -21
- package/src/queries/gql-to-sql-mapper.ts +19 -8
- package/src/queries/relationship-handler.ts +5 -1
- package/src/queries/sql-builder.ts +4 -1
- package/src/query-manager.ts +13 -2
- package/src/types/gql-types.ts +28 -26
- package/src/types/sql-types.ts +22 -24
- package/src/types/utils.ts +2 -3
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
|
-
|
|
64
|
-
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
|
85
|
-
|
|
86
|
-
| `BookGQL.FilterInput`
|
|
87
|
-
| `BookGQL.PaginationInput`
|
|
88
|
-
| `BookGQL.OrderBy`
|
|
89
|
-
| `BookGQL.FieldsResolver`
|
|
90
|
-
| `BookGQL.gqlEntityName`
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
|
153
|
-
|
|
154
|
-
| `type`
|
|
155
|
-
| `generateFilter`
|
|
156
|
-
| `options`
|
|
157
|
-
| `alias`
|
|
158
|
-
| `array`
|
|
159
|
-
| `relatedEntityName` | `() => string`
|
|
160
|
-
| `enum`
|
|
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
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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
|
|
216
|
-
|
|
217
|
-
| `type`
|
|
218
|
-
| `options`
|
|
219
|
-
| `requires`
|
|
220
|
-
| `resolve`
|
|
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 {
|
|
231
|
-
|
|
232
|
-
@GQLEntityClass(
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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
|
|
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:
|
|
253
|
-
refFields: ['
|
|
254
|
-
fields: ['
|
|
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
|
|
261
|
-
|
|
262
|
-
| `type`
|
|
263
|
-
| `options`
|
|
264
|
-
| `mapping.refEntity` | `new () => TRef`
|
|
265
|
-
| `mapping.refFields` | `keyof TRef \| Array<keyof TRef>` | Column(s) on the ref entity to match against
|
|
266
|
-
| `mapping.fields`
|
|
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 `
|
|
272
|
+
For `kingdom: { mapping: { refEntity: Kingdom, refFields: 'id', fields: 'kingdomId' } }`:
|
|
273
273
|
|
|
274
274
|
```sql
|
|
275
|
-
select e_a1.id, e_a1.
|
|
275
|
+
select e_a1.id, e_a1.kingdom_id, f_j1.value as "kingdom"
|
|
276
276
|
from (
|
|
277
|
-
select e_a1.id, e_a1.
|
|
278
|
-
from
|
|
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.
|
|
285
|
-
from
|
|
286
|
-
where e_a1.
|
|
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
|
|
321
|
-
|
|
322
|
-
| `_eq`
|
|
323
|
-
| `_ne`
|
|
324
|
-
| `_in`
|
|
325
|
-
| `_nin`
|
|
326
|
-
| `_like`
|
|
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`
|
|
330
|
-
| `_or`
|
|
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
|
|
349
|
-
|
|
350
|
-
| **m:1** (many-to-one)
|
|
351
|
-
| **1:1** (one-to-one)
|
|
352
|
-
| **1:m** (one-to-many)
|
|
353
|
-
| **m:m** (many-to-many) | Pivot table subquery + `json_agg`
|
|
354
|
-
| **custom `mapping`**
|
|
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
|
|
363
|
-
|
|
364
|
-
| `
|
|
365
|
-
| `
|
|
366
|
-
| `D3GOP_LOG_TYPE`
|
|
367
|
-
| `D3GOP_DEFAULT_QUERY_LIMIT`
|
|
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 `
|
|
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
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
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
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
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
|
|
416
|
-
|
|
417
|
-
| `GQLtoSQLMapper`
|
|
418
|
-
| `GQLQueryManager`
|
|
419
|
-
| `FilterProcessor`
|
|
420
|
-
| `RelationshipHandler` | Generates JOIN SQL for ORM-declared relations
|
|
421
|
-
| `SQLBuilder`
|
|
422
|
-
| `AliasManager`
|
|
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
|
|
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
|
-
|
|
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['
|
|
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
|
|
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 =
|
|
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
|
@@ -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
|
|
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['
|
|
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
|
|
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>(
|
|
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 },
|
package/src/entities/index.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export * from './gql-entity';
|
|
1
|
+
export * from './gql-entity';
|
package/src/queries/alias.ts
CHANGED
|
@@ -6,7 +6,11 @@ export enum AliasType {
|
|
|
6
6
|
value = 'value',
|
|
7
7
|
}
|
|
8
8
|
export class Alias {
|
|
9
|
-
constructor(
|
|
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(
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
438
|
-
|
|
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
|
-
|
|
642
|
+
gqlFieldNameKey.slice(0, -fieldOperation.length)
|
|
640
643
|
: // Id: { _eq }
|
|
641
|
-
|
|
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
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
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>(
|
|
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 {
|
|
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 =
|
|
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 =
|
|
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(
|
|
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
|
package/src/query-manager.ts
CHANGED
|
@@ -5,7 +5,11 @@ import {
|
|
|
5
5
|
simplifyParsedResolveInfoFragmentWithType,
|
|
6
6
|
} from 'graphql-parse-resolve-info';
|
|
7
7
|
import knex from 'knex';
|
|
8
|
-
import {
|
|
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>(
|
|
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>(
|
package/src/types/gql-types.ts
CHANGED
|
@@ -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> =
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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> =
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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\]
|
package/src/types/sql-types.ts
CHANGED
|
@@ -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> =
|
|
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
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
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
|
-
|
|
293
|
-
|
|
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
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
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
|
}>;
|
package/src/types/utils.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
export type DefinedType<T> = Exclude<T, undefined | null | never>;
|
|
2
|
-
export type ExtractArrayType<T> =
|
|
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>
|