@dav3/gql-of-power 1.3.0 → 1.5.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 +168 -0
- package/agents/templates/entity-skeleton.ts.txt +136 -0
- package/dist/entities/gql-entity.d.ts +4 -1
- package/dist/entities/gql-entity.js +342 -283
- package/dist/field-operations.d.ts +102 -0
- package/dist/field-operations.js +95 -0
- package/dist/operations.d.ts +5 -102
- package/dist/operations.js +17 -82
- package/dist/queries/enum-filter-converter.d.ts +29 -0
- package/dist/queries/enum-filter-converter.js +155 -0
- package/dist/queries/filter-processor.d.ts +5 -2
- package/dist/queries/filter-processor.js +58 -53
- package/dist/queries/gql-to-sql-mapper.d.ts +8 -1
- package/dist/queries/gql-to-sql-mapper.js +215 -174
- package/dist/queries/relationship-handler.d.ts +3 -1
- package/dist/queries/relationship-handler.js +7 -6
- package/dist/queries/sql-builder.d.ts +3 -52
- package/dist/queries/sql-builder.js +4 -73
- package/dist/queries/utils.d.ts +1 -2
- package/dist/queries/utils.js +1 -8
- package/dist/query-manager.d.ts +11 -2
- package/dist/query-manager.js +16 -110
- 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 +2 -3
- package/dist/types/sql-types.d.ts +7 -2
- package/dist/types/sql-types.js +2 -0
- package/dist/variables.d.ts +0 -1
- package/dist/variables.js +4 -5
- 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 +14 -11
- package/src/entities/gql-entity.ts +499 -295
- package/src/field-operations.ts +134 -0
- package/src/operations.ts +6 -120
- package/src/queries/enum-filter-converter.ts +179 -0
- package/src/queries/filter-processor.ts +54 -24
- package/src/queries/gql-to-sql-mapper.ts +379 -242
- package/src/queries/relationship-handler.ts +4 -9
- package/src/queries/sql-builder.ts +5 -102
- package/src/queries/utils.ts +2 -11
- package/src/query-manager.ts +38 -121
- package/src/types/access-control.ts +1 -0
- package/src/types/gql-to-sql-types.ts +2 -2
- package/src/types/gql-types.ts +11 -9
- package/src/types/sql-types.ts +11 -5
- package/src/types/utils.ts +3 -3
- package/src/variables.ts +1 -1
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
|
@@ -293,6 +293,68 @@ Returns `null` when the FK column is null (LEFT JOIN).
|
|
|
293
293
|
|
|
294
294
|
---
|
|
295
295
|
|
|
296
|
+
## Three Ways to Define an Entity
|
|
297
|
+
|
|
298
|
+
There are three APIs for registering a GQL entity. They differ in **when** the generated types (FilterInput, PaginationInput, FieldsResolver, etc.) are registered — that difference matters for filter-type resolution (see [Troubleshooting](#troubleshooting)).
|
|
299
|
+
|
|
300
|
+
### Quick comparison
|
|
301
|
+
|
|
302
|
+
| API | When to use | Calls `buildResolvers()`? | FilterInput registered |
|
|
303
|
+
| ------------------- | -------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ | ------------------------ |
|
|
304
|
+
| `@GQLEntityClass` | **Default.** No circular imports between entity files. | ✅ Automatically (at decoration time) | ✅ Yes |
|
|
305
|
+
| `createGQLTypes()` | Same as `@GQLEntityClass` but as a function call (can't decorate a class, or prefer the explicit style). | ✅ Automatically (internally) | ✅ Yes |
|
|
306
|
+
| `createGQLEntity()` | **Circular imports** between entity definitions — you need to defer resolver registration. | ❌ **Deferred** — you must call `.buildResolvers()` yourself | ❌ No, until you call it |
|
|
307
|
+
|
|
308
|
+
### `@GQLEntityClass` — the default (recommended)
|
|
309
|
+
|
|
310
|
+
The decorator registers the entity **and** its FilterInput/FieldsResolver immediately at decoration time. All generated statics (`FilterInput`, `PaginationInput`, `FieldsResolver`) are available on the class.
|
|
311
|
+
|
|
312
|
+
```typescript
|
|
313
|
+
@GQLEntityClass(Book, bookFields)
|
|
314
|
+
export class BookGQL extends GQLEntityBase {}
|
|
315
|
+
// BookGQL.FilterInput is ready. Done.
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
### `createGQLTypes()` — function-call equivalent
|
|
319
|
+
|
|
320
|
+
Same behaviour as the decorator — it calls `createGQLEntity().buildResolvers()` internally (see `gql-entity.ts:1221-1222`). Use it when you can't or don't want to decorate a class.
|
|
321
|
+
|
|
322
|
+
```typescript
|
|
323
|
+
export const BookGQL = createGQLTypes(Book, bookFields);
|
|
324
|
+
// BookGQL.FilterInput is ready. Done.
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### `createGQLEntity()` — for circular imports (advanced)
|
|
328
|
+
|
|
329
|
+
Returns the entity definition **without** registering the FilterInput or FieldsResolver. You must call `.buildResolvers()` yourself before `buildSchema()` runs. This split exists so two entity files can reference each other's `GQLEntity` type thunks without a circular-import deadlock at module-load time.
|
|
330
|
+
|
|
331
|
+
```typescript
|
|
332
|
+
// book.entity.ts
|
|
333
|
+
export const BookEntity = createGQLEntity(Book, bookFields);
|
|
334
|
+
// ↑ FilterInput NOT registered yet
|
|
335
|
+
|
|
336
|
+
// author.entity.ts — can import BookEntity for the thunk even if Book imports Author
|
|
337
|
+
export const AuthorEntity = createGQLEntity(Author, authorFields, {
|
|
338
|
+
customFields: {/* ... uses () => BookEntity.GQLEntity ... */},
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
// schema/index.ts — YOU must call buildResolvers() on EVERY entity
|
|
342
|
+
export const BookGQL = BookEntity.buildResolvers(); // ← registers BookFilterInput
|
|
343
|
+
export const AuthorGQL = AuthorEntity.buildResolvers(); // ← registers AuthorFilterInput
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
> ⚠️ **If you forget a `.buildResolvers()` call**, any filter that references that entity (relationship fields, mapping custom-field filters, `_exists` filters) will throw at schema-build time:
|
|
347
|
+
>
|
|
348
|
+
> ```
|
|
349
|
+
> gql-of-power: FilterInput for referenced entity "Book" (BookFilterInput) is not registered.
|
|
350
|
+
> Make sure Book's buildResolvers() is called (or use createGQLTypes / @GQLEntityClass)
|
|
351
|
+
> before building the schema.
|
|
352
|
+
> ```
|
|
353
|
+
>
|
|
354
|
+
> The error names the missing entity so you know exactly which file to fix.
|
|
355
|
+
|
|
356
|
+
---
|
|
357
|
+
|
|
296
358
|
## Filtering
|
|
297
359
|
|
|
298
360
|
Filter at any nesting level:
|
|
@@ -560,6 +622,88 @@ No `limit` or `offset` parameters are accepted — `LIMIT 1` is always applied.
|
|
|
560
622
|
|
|
561
623
|
---
|
|
562
624
|
|
|
625
|
+
## Troubleshooting
|
|
626
|
+
|
|
627
|
+
### `FilterInput for referenced entity "X" is not registered`
|
|
628
|
+
|
|
629
|
+
This error fires at `buildSchema()` time when a relationship field, mapping custom-field filter, or `_exists` filter references an entity whose FilterInput was never registered.
|
|
630
|
+
|
|
631
|
+
**Cause:** You used `createGQLEntity()` (the deferred API) for entity `X` but forgot to call `X.buildResolvers()` before building the schema. The entity's ObjectType is registered, but its FilterInput is not.
|
|
632
|
+
|
|
633
|
+
**Fix:** Either switch the entity to `@GQLEntityClass` / `createGQLTypes()` (which call `buildResolvers()` automatically), or add the missing call:
|
|
634
|
+
|
|
635
|
+
```typescript
|
|
636
|
+
// schema/index.ts — collect ALL entities and call buildResolvers()
|
|
637
|
+
const BookGQL = BookEntity.buildResolvers();
|
|
638
|
+
const AuthorGQL = AuthorEntity.buildResolvers();
|
|
639
|
+
```
|
|
640
|
+
|
|
641
|
+
Before August 2026 this was a **silent wrong-type bug**: the missing FilterInput fell back to the parent entity's FilterInput, so `Author.books` would generate `AuthorFilterInput` instead of `BookFilterInput`. The library now throws with the message above.
|
|
642
|
+
|
|
643
|
+
### Mapping custom-field filter is missing from the schema
|
|
644
|
+
|
|
645
|
+
If a `customFields` entry with `mapping` doesn't produce its nested filter (e.g. `HomeRegion: { name_eq: '...' }` is absent), check whether `resolve` was **also** set on the same field:
|
|
646
|
+
|
|
647
|
+
```typescript
|
|
648
|
+
customFields: {
|
|
649
|
+
homeRegion: {
|
|
650
|
+
type: () => RegionGQL.GQLEntity,
|
|
651
|
+
mapping: { refEntity: Region, refFields: 'id', fields: 'homeRegionId' },
|
|
652
|
+
generateFilter: true,
|
|
653
|
+
resolve: (root) => ..., // ← THIS makes the filter silently disappear
|
|
654
|
+
},
|
|
655
|
+
},
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
`resolve` and `mapping` are **mutually exclusive** on the same custom field. When both are set, only the resolver registers — the mapping filter is skipped. TypeScript's `CustomFieldSettings<T>` union enforces this at the type level, but `as any` bypasses it. Remove `resolve` (and `resolveDecorators`/`requires`) from a mapping-strategy field.
|
|
659
|
+
|
|
660
|
+
### Filter key is PascalCase, not camelCase
|
|
661
|
+
|
|
662
|
+
Mapping custom-field filters appear in the FilterInput under a **capitalized** key:
|
|
663
|
+
|
|
664
|
+
```graphql
|
|
665
|
+
# ✅ Correct — PascalCase filter key
|
|
666
|
+
persons(filter: { HomeRegion: { name_eq: 'Gondor' } }) { ... }
|
|
667
|
+
|
|
668
|
+
# ❌ Wrong — camelCase doesn't exist on the FilterInput
|
|
669
|
+
persons(filter: { homeRegion: { name_eq: 'Gondor' } })
|
|
670
|
+
# Field "homeRegion" is not defined by type "PersonFilterInput". Did you mean "HomeRegion"?
|
|
671
|
+
```
|
|
672
|
+
|
|
673
|
+
The field **selection** in the query stays camelCase (`homeRegion { ... }`); only the filter key is capitalized. Count-field nested filters follow the same rule (`BookCount: { _gte: 2 }`).
|
|
674
|
+
|
|
675
|
+
### Relationship-field filters aren't statically typed
|
|
676
|
+
|
|
677
|
+
`GQLEntityFilterInputFieldType<T>` surfaces filter operators for scalar fields but does **not** statically include nested relationship sub-filters (they're generated at runtime). Cast the filter object `as any` when using relationship filters:
|
|
678
|
+
|
|
679
|
+
```typescript
|
|
680
|
+
queryManager.getQueryResultsForInfo(provider, PersonGQL, info, {
|
|
681
|
+
fellowship: { id_in: [1, 2] }, // works at runtime, not statically typed
|
|
682
|
+
} as any);
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
This is the library's own test-suite pattern.
|
|
686
|
+
|
|
687
|
+
### `mapNumericEnum` filter values silently fail inside nested/inline filters
|
|
688
|
+
|
|
689
|
+
If a `mapNumericEnum` field filters correctly at the **top level** but silently returns no results (or the wrong rows) when placed inside an inline field-argument filter on a nested relation — e.g.:
|
|
690
|
+
|
|
691
|
+
```graphql
|
|
692
|
+
# Works: top-level filter → 'InProgress' converted to 1
|
|
693
|
+
bearers(filter: { questState: InProgress }) { name }
|
|
694
|
+
|
|
695
|
+
# Silently failed before August 2026: inline filter → 'InProgress' passed as a string
|
|
696
|
+
rings { bearer(filter: { questState: InProgress }) { name } }
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
**Cause:** enum conversion (`convertFilterEnumValues`) was historically applied only to top-level filters in `GQLQueryManager`. Inline field-argument filters (`books(filter: {...})`), count-field filters (`bookCount(filter: {...})`), and multi-level nested filters entered the SQL mapper through a different code path that bypassed conversion entirely. The `mapNumericEnum` string key went straight to SQL as a string instead of being converted to the numeric DB value, so the `WHERE` clause silently matched nothing.
|
|
700
|
+
|
|
701
|
+
**Fix (August 2026):** `handleFieldArguments` and `mapCountField` in the SQL mapper now call `convertFilterEnumValues` on inline filter args before SQL generation. The conversion resolves the target entity's `mapNumericEnum` fields through the parent entity's relation-field registry and recurses to arbitrary depth. This means filters now behave identically at the top level, inside `relation(filter:)`, inside nested `relation(filter: { nested: {...} })`, inside count subqueries, and inside `_or`/`_and` arrays at any level.
|
|
702
|
+
|
|
703
|
+
**If you still see this pattern:** ensure the target entity is decorator-registered (`@GQLEntityClass` / `createGQLTypes`) so its `mapNumericEnum` fields appear in the `MapEnumFieldsMap` registry — conversion silently passes values through for unregistered entities.
|
|
704
|
+
|
|
705
|
+
---
|
|
706
|
+
|
|
563
707
|
## Known Limitations
|
|
564
708
|
|
|
565
709
|
- ⚠️ Class-level `_not` conditions not yet supported
|
|
@@ -568,6 +712,27 @@ No `limit` or `offset` parameters are accepted — `LIMIT 1` is always applied.
|
|
|
568
712
|
|
|
569
713
|
---
|
|
570
714
|
|
|
715
|
+
## Agent Integration
|
|
716
|
+
|
|
717
|
+
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.
|
|
718
|
+
|
|
719
|
+
To install it in a consumer project (one-time setup):
|
|
720
|
+
|
|
721
|
+
```bash
|
|
722
|
+
# Claude Code
|
|
723
|
+
mkdir -p .claude/skills && cp -R node_modules/@dav3/gql-of-power/agents/ .claude/skills/gql-of-power/
|
|
724
|
+
|
|
725
|
+
# Cursor
|
|
726
|
+
mkdir -p .cursor/rules && cp node_modules/@dav3/gql-of-power/agents/SKILL.md .cursor/rules/gql-of-power.mdc
|
|
727
|
+
|
|
728
|
+
# Hermes
|
|
729
|
+
ln -s node_modules/@dav3/gql-of-power/agents ~/.hermes/skills/gql-of-power
|
|
730
|
+
```
|
|
731
|
+
|
|
732
|
+
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.
|
|
733
|
+
|
|
734
|
+
---
|
|
735
|
+
|
|
571
736
|
## Development
|
|
572
737
|
|
|
573
738
|
```bash
|
|
@@ -575,8 +740,11 @@ bun install # Install dependencies
|
|
|
575
740
|
bun run build # Compile TypeScript → dist/
|
|
576
741
|
bun run test # Run all tests
|
|
577
742
|
bun run test:watch # Watch mode
|
|
743
|
+
bun run audit # Codebase-intelligence gate (fallow): dead code, duplication, complexity
|
|
578
744
|
```
|
|
579
745
|
|
|
746
|
+
`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.
|
|
747
|
+
|
|
580
748
|
---
|
|
581
749
|
|
|
582
750
|
## 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 { CountFieldMeta, 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;
|
|
@@ -28,6 +29,8 @@ export declare const clearMapEnumFields: () => void;
|
|
|
28
29
|
export declare const getParseJsonFieldsFor: (name: string) => Set<string>;
|
|
29
30
|
export declare const registerParseJsonField: (gqlEntityName: string, fieldName: string) => void;
|
|
30
31
|
export declare const clearParseJsonFields: () => void;
|
|
32
|
+
export declare const getRelationFieldsFor: (name: string) => Record<string, () => string>;
|
|
33
|
+
export declare const clearRelationFields: () => void;
|
|
31
34
|
export declare const getACLFor: (name: string) => AccessControlEntry<any, any>;
|
|
32
35
|
export declare const getGQLEntityNameFor: (name: string) => string;
|
|
33
36
|
export declare const getGQLEntityNameForClass: <T>(classType: new () => T) => string;
|