@dav3/gql-of-power 1.2.0 → 1.4.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/.fallowrc.json +23 -0
- package/.prettierignore +13 -0
- package/README.md +164 -14
- package/agents/templates/entity-skeleton.ts.txt +136 -0
- package/dist/entities/gql-entity.d.ts +28 -2
- package/dist/entities/gql-entity.js +575 -171
- package/dist/field-operations.d.ts +102 -0
- package/dist/field-operations.js +95 -0
- package/dist/operations.d.ts +9 -103
- package/dist/operations.js +17 -82
- package/dist/queries/filter-processor.d.ts +86 -5
- package/dist/queries/filter-processor.js +327 -52
- package/dist/queries/gql-to-sql-mapper.d.ts +23 -3
- package/dist/queries/gql-to-sql-mapper.js +338 -132
- package/dist/queries/relationship-handler.d.ts +3 -1
- package/dist/queries/relationship-handler.js +13 -9
- package/dist/queries/sql-builder.d.ts +4 -53
- package/dist/queries/sql-builder.js +8 -74
- package/dist/queries/utils.d.ts +1 -2
- package/dist/queries/utils.js +1 -8
- package/dist/query-manager.d.ts +18 -8
- package/dist/query-manager.js +129 -5
- package/dist/types/gql-to-sql-types.d.ts +1 -1
- package/dist/types/gql-to-sql-types.js +2 -2
- package/dist/types/gql-types.d.ts +13 -3
- package/dist/types/sql-types.d.ts +150 -3
- package/dist/types/sql-types.js +2 -0
- package/dist/variables.d.ts +0 -1
- package/dist/variables.js +13 -10
- package/fallow-baselines/dead-code.json +34 -0
- package/fallow-baselines/dupes.json +10 -0
- package/fallow-baselines/health.json +103 -0
- package/package.json +19 -12
- package/src/entities/gql-entity.ts +774 -173
- package/src/field-operations.ts +134 -0
- package/src/operations.ts +11 -116
- package/src/queries/filter-processor.ts +616 -35
- package/src/queries/gql-to-sql-mapper.ts +604 -161
- package/src/queries/relationship-handler.ts +12 -12
- package/src/queries/sql-builder.ts +12 -103
- package/src/queries/utils.ts +2 -11
- package/src/query-manager.ts +198 -23
- package/src/types/access-control.ts +1 -0
- package/src/types/gql-to-sql-types.ts +2 -2
- package/src/types/gql-types.ts +23 -9
- package/src/types/sql-types.ts +155 -6
- package/src/types/utils.ts +3 -3
- package/src/variables.ts +12 -8
package/.fallowrc.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "./node_modules/fallow/schema.json",
|
|
3
|
+
"entry": [
|
|
4
|
+
"src/index.{ts,tsx,js,jsx}",
|
|
5
|
+
"scripts/update-version.ts",
|
|
6
|
+
"tests/run-sample-tests.ts",
|
|
7
|
+
"tests/regression/generate-sql-snapshots.ts"
|
|
8
|
+
],
|
|
9
|
+
"ignorePatterns": ["dist/**", "coverage/**", "tests/fixtures/**", "examples/**"],
|
|
10
|
+
"ignoreDependencies": [],
|
|
11
|
+
"ignoreExports": [
|
|
12
|
+
{
|
|
13
|
+
"file": "src/types/gql-types.ts",
|
|
14
|
+
"exports": ["*"],
|
|
15
|
+
"reason": "Generated filter/pagination input type shapes consumed via type-graphql decorators, not direct imports."
|
|
16
|
+
}
|
|
17
|
+
],
|
|
18
|
+
"audit": {
|
|
19
|
+
"deadCodeBaseline": "fallow-baselines/dead-code.json",
|
|
20
|
+
"healthBaseline": "fallow-baselines/health.json",
|
|
21
|
+
"dupesBaseline": "fallow-baselines/dupes.json"
|
|
22
|
+
}
|
|
23
|
+
}
|
package/.prettierignore
ADDED
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# ⚔️ GQL of Power
|
|
2
2
|
|
|
3
|
-
> **"One Query to Rule Them All"** — Master your data with the power of a single
|
|
3
|
+
> **"One Query to Rule Them All"** — Master your data with the power of a single optimized SQL query
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
|
|
9
9
|
In the age of microservices and data loaders, a shadow falls upon performance: _the N+1 query problem_. Weary developers traverse the lands of Middleware-earth, orchestrating countless queries to fetch interconnected data.
|
|
10
10
|
|
|
11
|
-
But what if there was another way? What if, with the power of a single query, you could bind all your data together?
|
|
11
|
+
But what if there was another way? What if, with the power of a single SQL query, you could bind all your data together?
|
|
12
12
|
|
|
13
|
-
**GQL of Power** is a TypeScript library that harnesses ancient sorcery to generate perfectly optimized SQL queries from your GraphQL operations. It eliminates the need for data loaders entirely — one query to fetch your complete data structure, no matter how deeply nested your relationships run.
|
|
13
|
+
**GQL of Power** is a TypeScript library that harnesses ancient sorcery to generate perfectly optimized SQL queries from your GraphQL operations. It eliminates the need for data loaders entirely — one SQL query to fetch your complete data structure, no matter how deeply nested your relationships run.
|
|
14
14
|
|
|
15
15
|
Like the One Ring wielding dominion over all other Rings, GQL of Power commands your database with singular authority.
|
|
16
16
|
|
|
@@ -157,6 +157,7 @@ const fields = defineFields(Book, {
|
|
|
157
157
|
| `alias` | `string` | Override the GQL field name |
|
|
158
158
|
| `array` | `true` | Mark as array return type |
|
|
159
159
|
| `relatedEntityName` | `() => string` | ORM entity name for array relation fields (auto-derived when using `@GQLEntityClass`) |
|
|
160
|
+
| `countFieldName` | `string` | Generate a count field for this relationship (see [Count Fields](#count-fields)) |
|
|
160
161
|
| `enum` | `EnumData` | Register an enum type |
|
|
161
162
|
|
|
162
163
|
### `@GQLEntityClass(OrmClass, fields, extra?)`
|
|
@@ -317,17 +318,114 @@ filter: {
|
|
|
317
318
|
|
|
318
319
|
### Filter operations
|
|
319
320
|
|
|
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
|
-
| `_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)
|
|
321
|
+
| Operation | Meaning |
|
|
322
|
+
| -------------- | ------------------------------------------------------ |
|
|
323
|
+
| `_eq` | Equal |
|
|
324
|
+
| `_ne` | Not equal |
|
|
325
|
+
| `_in` | In array |
|
|
326
|
+
| `_nin` | Not in array |
|
|
327
|
+
| `_like` | ILIKE (case-insensitive contains) |
|
|
328
|
+
| `_gt` / `_gte` | Greater than / greater than or equal |
|
|
329
|
+
| `_lt` / `_lte` | Less than / less than or equal |
|
|
330
|
+
| `_and` | Logical AND |
|
|
331
|
+
| `_or` | Logical OR (generates UNION ALL) |
|
|
332
|
+
| `_exists` | Check related entities exist (AND-combined per key) |
|
|
333
|
+
| `_not_exists` | Check no related entities exist (AND-combined per key) |
|
|
334
|
+
|
|
335
|
+
### Existence Filters (`_exists` / `_not_exists`)
|
|
336
|
+
|
|
337
|
+
Check whether related entities match a set of conditions. Each key is a relationship field name; the value is a filter applied to that related entity. Multiple keys are AND-combined.
|
|
338
|
+
|
|
339
|
+
```graphql
|
|
340
|
+
# Authors that have at least one book titled "The Hobbit"
|
|
341
|
+
filter: {
|
|
342
|
+
_exists: {
|
|
343
|
+
books: { title: "The Hobbit" }
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
# Authors that have a book titled "The Hobbit" AND no books in the "Horror" genre
|
|
348
|
+
filter: {
|
|
349
|
+
_and: [
|
|
350
|
+
{ _exists: { books: { title: "The Hobbit" } } }
|
|
351
|
+
{ _not_exists: { books: { genre: "Horror" } } }
|
|
352
|
+
]
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
# Persons who fought in a victory battle AND wrote a book about it
|
|
356
|
+
filter: {
|
|
357
|
+
_exists: {
|
|
358
|
+
battles: { outcome: "Victory" }
|
|
359
|
+
books: { title: { _like: "%War%" } }
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
`_exists` generates `EXISTS (SELECT 1 FROM ... WHERE ...)` subqueries. `_not_exists` generates `NOT EXISTS (...)`. Multiple keys within one `_exists`/`_not_exists` each produce a separate `EXISTS`/`NOT EXISTS` clause, AND-combined in the WHERE. OR across exists conditions is achieved via `_or`.
|
|
365
|
+
|
|
366
|
+
---
|
|
367
|
+
|
|
368
|
+
## Count Fields
|
|
369
|
+
|
|
370
|
+
Add `countFieldName` to any relationship field with `array: true` to auto-generate an Int count field. The count is computed via a correlated `COUNT(*)` subquery — no JOINs in the outer query.
|
|
371
|
+
|
|
372
|
+
### Definition
|
|
373
|
+
|
|
374
|
+
```typescript
|
|
375
|
+
const authorFields = defineFields(Author, {
|
|
376
|
+
id: { type: () => ID, generateFilter: true },
|
|
377
|
+
name: { type: () => String, generateFilter: true },
|
|
378
|
+
books: {
|
|
379
|
+
type: () => BookGQL,
|
|
380
|
+
array: true,
|
|
381
|
+
relatedEntityName: () => 'Book',
|
|
382
|
+
countFieldName: 'bookCount', // generates an `bookCount: Int` field
|
|
383
|
+
},
|
|
384
|
+
});
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
### Querying
|
|
388
|
+
|
|
389
|
+
```graphql
|
|
390
|
+
query {
|
|
391
|
+
authors {
|
|
392
|
+
name
|
|
393
|
+
bookCount # total books
|
|
394
|
+
bookCount(filter: { publishedYear: { _gt: 1950 } }) # filtered count
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
### Filtering by Count
|
|
400
|
+
|
|
401
|
+
The count field is also available as a filter key with numeric operators:
|
|
402
|
+
|
|
403
|
+
```graphql
|
|
404
|
+
# Authors with exactly 4 books
|
|
405
|
+
filter: { bookCount: 4 }
|
|
406
|
+
|
|
407
|
+
# Authors with more than 3 books
|
|
408
|
+
filter: { bookCount_gt: 3 }
|
|
409
|
+
|
|
410
|
+
# Nested object form
|
|
411
|
+
filter: { BookCount: { _gte: 2, _lte: 10 } }
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
Supported operators: `_eq`, `_ne`, `_gt`, `_gte`, `_lt`, `_lte`.
|
|
415
|
+
|
|
416
|
+
### Generated SQL
|
|
417
|
+
|
|
418
|
+
```sql
|
|
419
|
+
-- bookCount (select)
|
|
420
|
+
SELECT ...,
|
|
421
|
+
(SELECT COUNT(*) FROM "books" AS e_w1 WHERE e_w1.author_id = a_1.id) AS "bookCount"
|
|
422
|
+
FROM authors AS a_1 ...
|
|
423
|
+
|
|
424
|
+
-- bookCount_gt (filter)
|
|
425
|
+
WHERE (SELECT COUNT(*) FROM "books" AS e_w1 WHERE e_w1.author_id = a_1.id) > :v_bookCount_gt__1
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
Works for all relationship types (1:m, m:1, m:n). For m:n, the pivot table is included in the subquery.
|
|
331
429
|
|
|
332
430
|
---
|
|
333
431
|
|
|
@@ -409,6 +507,34 @@ const results = await queryManager.getQueryResultsForFields(
|
|
|
409
507
|
|
|
410
508
|
`BookGQL.relatedEntityName` (`'Book'`) is used automatically to look up ORM metadata — no need to pass the ORM class separately.
|
|
411
509
|
|
|
510
|
+
### Singular Queries
|
|
511
|
+
|
|
512
|
+
For fetching a single record, use the singular variants. These enforce `LIMIT 1` internally and return `K | null` instead of `K[]`:
|
|
513
|
+
|
|
514
|
+
```typescript
|
|
515
|
+
// From a GraphQL resolver
|
|
516
|
+
const ring = await queryManager.getQueryResultForInfo(
|
|
517
|
+
metadataProvider,
|
|
518
|
+
RingGQL,
|
|
519
|
+
info,
|
|
520
|
+
{ forgedBy: 'Sauron' },
|
|
521
|
+
[{ forgedYear: 'desc' }] // optional orderBy — controls *which* record is returned
|
|
522
|
+
);
|
|
523
|
+
// ring: Ring | null
|
|
524
|
+
|
|
525
|
+
// With explicit field selection
|
|
526
|
+
const ring = await queryManager.getQueryResultForFields(
|
|
527
|
+
metadataProvider,
|
|
528
|
+
RingGQL,
|
|
529
|
+
{ id: {}, name: {} },
|
|
530
|
+
{ forgedBy: 'Sauron' },
|
|
531
|
+
[{ forgedYear: 'desc' }]
|
|
532
|
+
);
|
|
533
|
+
// ring: Ring | null
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
No `limit` or `offset` parameters are accepted — `LIMIT 1` is always applied.
|
|
537
|
+
|
|
412
538
|
---
|
|
413
539
|
|
|
414
540
|
## Architecture
|
|
@@ -442,6 +568,27 @@ const results = await queryManager.getQueryResultsForFields(
|
|
|
442
568
|
|
|
443
569
|
---
|
|
444
570
|
|
|
571
|
+
## Agent Integration
|
|
572
|
+
|
|
573
|
+
This library ships an **agent skill** (`agents/`) that teaches AI coding assistants (Claude Code, Cursor, Hermes, or any agent reading `AGENTS.md`) how to scaffold gql-of-power entities, relationships, and advanced features correctly — using copy-paste templates, annotated relationship examples, and a decision tree for picking the right relationship type.
|
|
574
|
+
|
|
575
|
+
To install it in a consumer project (one-time setup):
|
|
576
|
+
|
|
577
|
+
```bash
|
|
578
|
+
# Claude Code
|
|
579
|
+
mkdir -p .claude/skills && cp -R node_modules/@dav3/gql-of-power/agents/ .claude/skills/gql-of-power/
|
|
580
|
+
|
|
581
|
+
# Cursor
|
|
582
|
+
mkdir -p .cursor/rules && cp node_modules/@dav3/gql-of-power/agents/SKILL.md .cursor/rules/gql-of-power.mdc
|
|
583
|
+
|
|
584
|
+
# Hermes
|
|
585
|
+
ln -s node_modules/@dav3/gql-of-power/agents ~/.hermes/skills/gql-of-power
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
See [`agents/install.md`](./agents/install.md) for full instructions (including a symlink option that tracks gql-of-power updates, slash-command setup, and a generic `AGENTS.md` snippet). Once installed, ask your agent to "add a gql-of-power entity" and it will follow the library's intended patterns.
|
|
589
|
+
|
|
590
|
+
---
|
|
591
|
+
|
|
445
592
|
## Development
|
|
446
593
|
|
|
447
594
|
```bash
|
|
@@ -449,8 +596,11 @@ bun install # Install dependencies
|
|
|
449
596
|
bun run build # Compile TypeScript → dist/
|
|
450
597
|
bun run test # Run all tests
|
|
451
598
|
bun run test:watch # Watch mode
|
|
599
|
+
bun run audit # Codebase-intelligence gate (fallow): dead code, duplication, complexity
|
|
452
600
|
```
|
|
453
601
|
|
|
602
|
+
`bun run audit` runs the changed-file gate against saved baselines in `.fallow/` and fails only on **new** findings it introduces. Use `bun run audit:full` for the complete report, or `bun run audit:dead-code` / `audit:dupes` / `audit:health` for individual analyses. See `AGENTS.md` for the full audit workflow.
|
|
603
|
+
|
|
454
604
|
---
|
|
455
605
|
|
|
456
606
|
## License
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
// gql-of-power entity skeleton — copy, rename, and adapt.
|
|
2
|
+
// Middle-earth themed example: a "Weapon" entity with a m:1 to its Bearer.
|
|
3
|
+
//
|
|
4
|
+
// This file is a template, not importable code. Place your real files in:
|
|
5
|
+
// schema/entities.ts (ORM class + EntityMetadata)
|
|
6
|
+
// schema/fields.ts (FieldsSettings)
|
|
7
|
+
// graphql/entities.ts (createGQLTypes calls)
|
|
8
|
+
// graphql/resolvers.ts (@Resolver + @Query classes)
|
|
9
|
+
|
|
10
|
+
import 'reflect-metadata';
|
|
11
|
+
import { GraphQLResolveInfo } from 'graphql';
|
|
12
|
+
import {
|
|
13
|
+
createGQLTypes,
|
|
14
|
+
GQLEntityFilterInputFieldType,
|
|
15
|
+
FieldsSettings,
|
|
16
|
+
} from '@dav3/gql-of-power';
|
|
17
|
+
import { EntityMetadata, EntityProperty, ReferenceType } from '@dav3/gql-of-power';
|
|
18
|
+
import { Arg, Info, Int, Query, Resolver } from 'type-graphql';
|
|
19
|
+
import { queryManager, provider } from './your-app-setup'; // <-- your wiring
|
|
20
|
+
|
|
21
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
22
|
+
// 1. ORM ENTITY CLASS (schema/entities.ts)
|
|
23
|
+
// Plain TS class. Property names = JS names. Relationship fields optional.
|
|
24
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
export class Weapon {
|
|
27
|
+
id!: number;
|
|
28
|
+
name!: string;
|
|
29
|
+
damage!: number;
|
|
30
|
+
// m:1 relationship — Weapon holds bearerId
|
|
31
|
+
bearerId?: number;
|
|
32
|
+
bearer?: Person;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export class Person {
|
|
36
|
+
id!: number;
|
|
37
|
+
name!: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
41
|
+
// 2. ENTITY METADATA (schema/entities.ts)
|
|
42
|
+
// SQL reality: table, PKs, column names, relationship join columns.
|
|
43
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
44
|
+
|
|
45
|
+
const prop = (
|
|
46
|
+
type: string,
|
|
47
|
+
name: string,
|
|
48
|
+
fieldNames: string[],
|
|
49
|
+
reference?: {
|
|
50
|
+
referenceType: ReferenceType;
|
|
51
|
+
joinColumns?: string[];
|
|
52
|
+
referencedColumnNames?: string[];
|
|
53
|
+
mappedBy?: string;
|
|
54
|
+
}
|
|
55
|
+
): EntityProperty => ({
|
|
56
|
+
type,
|
|
57
|
+
name,
|
|
58
|
+
fieldNames,
|
|
59
|
+
mappedBy: reference?.mappedBy || '',
|
|
60
|
+
joinColumns: reference?.joinColumns || [],
|
|
61
|
+
referencedColumnNames: reference?.referencedColumnNames || [],
|
|
62
|
+
inverseJoinColumns: [],
|
|
63
|
+
pivotTable: '',
|
|
64
|
+
reference: reference?.referenceType,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
export const WeaponMetadata: EntityMetadata<Weapon> = {
|
|
68
|
+
name: 'Weapon',
|
|
69
|
+
tableName: 'weapons',
|
|
70
|
+
primaryKeys: ['id'],
|
|
71
|
+
properties: {
|
|
72
|
+
id: prop('number', 'id', ['id']),
|
|
73
|
+
name: prop('string', 'name', ['weapon_name']),
|
|
74
|
+
damage: prop('number', 'damage', ['damage']),
|
|
75
|
+
bearerId: prop('number', 'bearerId', ['bearer_id']),
|
|
76
|
+
bearer: prop('Person', 'bearer', ['bearer_id'], {
|
|
77
|
+
referenceType: ReferenceType.MANY_TO_ONE,
|
|
78
|
+
joinColumns: ['bearer_id'],
|
|
79
|
+
referencedColumnNames: ['id'],
|
|
80
|
+
}),
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export const PersonMetadata: EntityMetadata<Person> = {
|
|
85
|
+
name: 'Person',
|
|
86
|
+
tableName: 'persons',
|
|
87
|
+
primaryKeys: ['id'],
|
|
88
|
+
properties: {
|
|
89
|
+
id: prop('number', 'id', ['id']),
|
|
90
|
+
name: prop('string', 'name', ['person_name']),
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
95
|
+
// 3. FIELDS SETTINGS (schema/fields.ts)
|
|
96
|
+
// How each field maps to GraphQL. Pass to createGQLTypes.
|
|
97
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
98
|
+
|
|
99
|
+
// Create PersonGQL first (Weapon references it)
|
|
100
|
+
export const PersonGQL = createGQLTypes(Person, {
|
|
101
|
+
id: { type: () => Number, generateFilter: true },
|
|
102
|
+
name: { type: () => String, generateFilter: true },
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
export const WeaponFields: Partial<FieldsSettings<Weapon>> = {
|
|
106
|
+
id: { type: () => Number, generateFilter: true },
|
|
107
|
+
name: { type: () => String, generateFilter: true },
|
|
108
|
+
damage: { type: () => Int, generateFilter: true },
|
|
109
|
+
bearer: {
|
|
110
|
+
type: () => PersonGQL.GQLEntity,
|
|
111
|
+
options: { nullable: true },
|
|
112
|
+
generateFilter: true,
|
|
113
|
+
relatedEntityName: () => Person.name,
|
|
114
|
+
getFilterType: () => Int,
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
119
|
+
// 4. WIRE + RESOLVE (graphql/entities.ts, graphql/resolvers.ts)
|
|
120
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
121
|
+
|
|
122
|
+
export const WeaponGQL = createGQLTypes(Weapon, WeaponFields);
|
|
123
|
+
|
|
124
|
+
@Resolver(() => WeaponGQL.GQLEntity || Object)
|
|
125
|
+
export class WeaponResolver extends WeaponGQL.FieldsResolver {
|
|
126
|
+
@Query(() => [WeaponGQL.GQLEntity])
|
|
127
|
+
async weapons(
|
|
128
|
+
@Info() info: GraphQLResolveInfo,
|
|
129
|
+
@Arg('filter', () => WeaponGQL.GQLEntityFilterInput || Object, { nullable: true })
|
|
130
|
+
filter?: GQLEntityFilterInputFieldType<Weapon>
|
|
131
|
+
) {
|
|
132
|
+
// Relationship-field filters need `as any` (the type doesn't surface them):
|
|
133
|
+
// { bearer: { name: 'Aragorn' } } as any
|
|
134
|
+
return await queryManager.getQueryResultsForInfo(provider, Weapon, info, filter);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getMetadataStorage } from 'type-graphql';
|
|
2
|
-
import { CustomFieldsSettings, FieldSettings, FieldsSettings,
|
|
2
|
+
import { CountFieldMeta, CustomFieldsSettings, FieldSettings, FieldsSettings, RelatedFieldSettings } from '../types/sql-types';
|
|
3
|
+
import { GQLEntityFilterInputFieldType, GQLEntityPaginationInputType } from '../types/gql-types';
|
|
3
4
|
import { AccessControlEntry } from '../types/access-control';
|
|
4
5
|
export declare const setGlobalConfig: (config: {
|
|
5
6
|
gqlTypesSuffix?: string;
|
|
@@ -8,6 +9,26 @@ export declare const setGlobalConfig: (config: {
|
|
|
8
9
|
export declare const getFieldsOptionsFor: (name: string) => Record<string, string>;
|
|
9
10
|
export declare const getFieldByAlias: (entityName: string | undefined, alias: string) => string;
|
|
10
11
|
export declare const getCustomFieldsFor: (name: string) => CustomFieldsSettings<any>;
|
|
12
|
+
/**
|
|
13
|
+
* Returns the count fields registered for the given GQL entity name.
|
|
14
|
+
* Keyed by the count field name (e.g. 'bookCount'), value is the count field metadata.
|
|
15
|
+
*/
|
|
16
|
+
export declare const getCountFieldsFor: (name: string) => Record<string, CountFieldMeta>;
|
|
17
|
+
/**
|
|
18
|
+
* Manually registers a count field for an entity.
|
|
19
|
+
* Useful for testing without the @GQLEntityClass decorator.
|
|
20
|
+
* In production, count fields are auto-registered by the decorator when `countFieldName` is set.
|
|
21
|
+
*/
|
|
22
|
+
export declare const registerCountField: (gqlEntityName: string, countFieldName: string, relationshipFieldName: string, relatedEntityName: () => string) => void;
|
|
23
|
+
/**
|
|
24
|
+
* Clears all registered count fields. Intended for test teardown.
|
|
25
|
+
*/
|
|
26
|
+
export declare const clearCountFields: () => void;
|
|
27
|
+
export declare const getMapEnumFieldsFor: (name: string) => Record<string, any>;
|
|
28
|
+
export declare const clearMapEnumFields: () => void;
|
|
29
|
+
export declare const getParseJsonFieldsFor: (name: string) => Set<string>;
|
|
30
|
+
export declare const registerParseJsonField: (gqlEntityName: string, fieldName: string) => void;
|
|
31
|
+
export declare const clearParseJsonFields: () => void;
|
|
11
32
|
export declare const getACLFor: (name: string) => AccessControlEntry<any, any>;
|
|
12
33
|
export declare const getGQLEntityNameFor: (name: string) => string;
|
|
13
34
|
export declare const getGQLEntityNameForClass: <T>(classType: new () => T) => string;
|
|
@@ -34,6 +55,7 @@ export type GQLEntityStaticMembers = {
|
|
|
34
55
|
readonly FilterInput: new () => any;
|
|
35
56
|
readonly PaginationInput: new () => any;
|
|
36
57
|
readonly OrderBy: new () => any;
|
|
58
|
+
readonly Input: new () => any;
|
|
37
59
|
readonly FieldsResolver: new () => any;
|
|
38
60
|
readonly gqlEntityName: string;
|
|
39
61
|
readonly relatedEntityName: string;
|
|
@@ -54,6 +76,7 @@ export declare abstract class GQLEntityBase {
|
|
|
54
76
|
static FilterInput: new () => any;
|
|
55
77
|
static PaginationInput: new () => any;
|
|
56
78
|
static OrderBy: new () => any;
|
|
79
|
+
static Input: new () => any;
|
|
57
80
|
static FieldsResolver: new () => any;
|
|
58
81
|
static gqlEntityName: string;
|
|
59
82
|
static relatedEntityName: string;
|
|
@@ -140,12 +163,14 @@ export declare function createGQLEntity<T extends Object, K>(classType: new () =
|
|
|
140
163
|
GQLEntityOrderBy: {
|
|
141
164
|
new (): {};
|
|
142
165
|
};
|
|
166
|
+
GQLEntityInput: new () => any;
|
|
143
167
|
FieldsResolver: {
|
|
144
168
|
new (): {};
|
|
145
169
|
};
|
|
146
170
|
bindFieldResolvers: (_c: any) => void;
|
|
147
171
|
};
|
|
148
172
|
};
|
|
173
|
+
type TypeGQLMetadataStorage = ReturnType<typeof getMetadataStorage>;
|
|
149
174
|
/**
|
|
150
175
|
* Convenience wrapper — creates entity, builds resolvers, and returns everything merged.
|
|
151
176
|
* Equivalent to calling `createGQLEntity(...).buildResolvers()` and merging results.
|
|
@@ -160,6 +185,7 @@ export declare function createGQLTypes<T extends Object, K>(classType: new () =>
|
|
|
160
185
|
GQLEntityOrderBy: {
|
|
161
186
|
new (): {};
|
|
162
187
|
};
|
|
188
|
+
GQLEntityInput: new () => any;
|
|
163
189
|
FieldsResolver: {
|
|
164
190
|
new (): {};
|
|
165
191
|
};
|
|
@@ -177,13 +203,13 @@ export declare function createGQLTypes<T extends Object, K>(classType: new () =>
|
|
|
177
203
|
GQLEntityOrderBy: {
|
|
178
204
|
new (): {};
|
|
179
205
|
};
|
|
206
|
+
GQLEntityInput: new () => any;
|
|
180
207
|
FieldsResolver: {
|
|
181
208
|
new (): {};
|
|
182
209
|
};
|
|
183
210
|
bindFieldResolvers: (_c: any) => void;
|
|
184
211
|
};
|
|
185
212
|
};
|
|
186
|
-
type TypeGQLMetadataStorage = ReturnType<typeof getMetadataStorage>;
|
|
187
213
|
/**
|
|
188
214
|
* Creates filter and sorting metadata for a field. Called during buildResolvers().
|
|
189
215
|
*/
|