@fluojs/graphql 1.0.0-beta.1 → 1.0.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.ko.md +49 -8
  2. package/README.md +49 -8
  3. package/package.json +3 -3
package/README.ko.md CHANGED
@@ -10,6 +10,7 @@ fluo를 위한 데코레이터 기반 GraphQL 통합 패키지입니다. **Graph
10
10
  - [사용 시점](#사용-시점)
11
11
  - [빠른 시작](#빠른-시작)
12
12
  - [핵심 기능](#핵심-기능)
13
+ - [Resolver Lifecycle 계약](#resolver-lifecycle-계약)
13
14
  - [운영 가드레일](#운영-가드레일)
14
15
  - [공개 API](#공개-api)
15
16
  - [관련 패키지](#관련-패키지)
@@ -37,11 +38,16 @@ import { Module } from '@fluojs/core';
37
38
  import { bootstrapNodeApplication } from '@fluojs/runtime/node';
38
39
  import { GraphqlModule, Query, Resolver, Arg } from '@fluojs/graphql';
39
40
 
41
+ class HelloInput {
42
+ @Arg('name')
43
+ name = '';
44
+ }
45
+
40
46
  @Resolver()
41
47
  class HelloResolver {
42
- @Query()
43
- hello(@Arg('name') name: string): string {
44
- return `Hello, ${name}!`;
48
+ @Query({ input: HelloInput })
49
+ hello(input: HelloInput): string {
50
+ return `Hello, ${input.name}!`;
45
51
  }
46
52
  }
47
53
 
@@ -65,7 +71,7 @@ await app.listen(3000);
65
71
  ## 핵심 기능
66
72
 
67
73
  ### Code-first Resolvers
68
- fluo는 표준 데코레이터를 사용하여 GraphQL 스키마를 정의합니다. `@Resolver`, `@Query`, `@Mutation`, `@Subscription`을 사용하여 클래스 메서드를 GraphQL 작업에 매핑합니다.
74
+ fluo는 표준 데코레이터를 사용하여 GraphQL 스키마를 정의합니다. `@Resolver`, `@Query`, `@Mutation`, `@Subscription`을 사용하여 클래스 메서드를 GraphQL 작업에 매핑합니다. GraphQL 인자는 input DTO 필드에 `@Arg(...)`로 선언하고, resolver 메서드는 작업의 `input` 옵션을 통해 해당 DTO를 받습니다.
69
75
 
70
76
  ### Request-Scoped DataLoaders
71
77
  내장된 DataLoader 통합을 통해 N+1 문제를 효율적으로 해결합니다. Loader는 각 GraphQL 작업마다 자동으로 격리됩니다.
@@ -78,11 +84,46 @@ const userLoader = createDataLoader(async (ids: string[]) => {
78
84
  return ids.map(id => users.find(u => u.id === id));
79
85
  });
80
86
 
87
+ class UserInput {
88
+ @Arg('id')
89
+ id = '';
90
+ }
91
+
81
92
  @Resolver()
82
93
  class UserResolver {
83
- @Query()
84
- async user(@Arg('id') id: string, context: GraphQLContext) {
85
- return userLoader(context).load(id);
94
+ @Query({ input: UserInput })
95
+ async user(input: UserInput, context: GraphQLContext) {
96
+ return userLoader(context).load(input.id);
97
+ }
98
+ }
99
+ ```
100
+
101
+ ## Resolver Lifecycle 계약
102
+
103
+ - Singleton resolver가 기본값이며, 각 operation에서 애플리케이션 컨테이너를 통해 resolve됩니다.
104
+ - Request-scoped provider를 주입하는 resolver는 resolver 자체에도 `@Scope('request')`를 지정해야 합니다. 이렇게 해야 DI lifetime 규칙이 명시적으로 유지되고 singleton-to-request dependency mismatch를 피할 수 있습니다.
105
+ - `@fluojs/graphql`은 HTTP GraphQL 요청 또는 WebSocket subscription operation마다 operation-scoped DI 컨테이너를 하나 만들고, 해당 operation 안의 resolver 호출들이 이를 공유하며, operation 완료 또는 WebSocket operation 종료 시 dispose합니다.
106
+ - Request-scoped DataLoader helper는 같은 `GraphQLContext` operation 경계를 사용하므로 loader cache는 하나의 GraphQL operation 안에서만 공유됩니다.
107
+
108
+ ```typescript
109
+ import { Inject, Scope } from '@fluojs/core';
110
+ import { Query, Resolver } from '@fluojs/graphql';
111
+
112
+ @Scope('request')
113
+ class RequestState {
114
+ private static nextId = 0;
115
+ readonly requestId = `request-${++RequestState.nextId}`;
116
+ }
117
+
118
+ @Inject(RequestState)
119
+ @Scope('request')
120
+ @Resolver()
121
+ class RequestResolver {
122
+ constructor(private readonly state: RequestState) {}
123
+
124
+ @Query('requestId')
125
+ requestId(): string {
126
+ return this.state.requestId;
86
127
  }
87
128
  }
88
129
  ```
@@ -142,7 +183,7 @@ GraphqlModule.forRoot({
142
183
 
143
184
  - `GraphqlModule.forRoot(options)`: GraphQL 통합을 위한 메인 엔트리 포인트.
144
185
  - `Resolver`, `Query`, `Mutation`, `Subscription`: 작업 데코레이터.
145
- - `Arg`: 인자 매핑 데코레이터.
186
+ - `Arg`: Input DTO 필드를 GraphQL 인자로 매핑하는 데코레이터.
146
187
  - `createDataLoader`, `createDataLoaderMap`: DataLoader 팩토리 헬퍼.
147
188
  - `GraphQLContext`: GraphQL 실행 컨텍스트를 위한 타입 정의.
148
189
 
package/README.md CHANGED
@@ -10,6 +10,7 @@ Decorator-based GraphQL integration for fluo. Built on **GraphQL Yoga**, it prov
10
10
  - [When to Use](#when-to-use)
11
11
  - [Quick Start](#quick-start)
12
12
  - [Core Capabilities](#core-capabilities)
13
+ - [Resolver Lifecycle Contracts](#resolver-lifecycle-contracts)
13
14
  - [Operational Guardrails](#operational-guardrails)
14
15
  - [Public API](#public-api)
15
16
  - [Related Packages](#related-packages)
@@ -37,11 +38,16 @@ import { Module } from '@fluojs/core';
37
38
  import { bootstrapNodeApplication } from '@fluojs/runtime/node';
38
39
  import { GraphqlModule, Query, Resolver, Arg } from '@fluojs/graphql';
39
40
 
41
+ class HelloInput {
42
+ @Arg('name')
43
+ name = '';
44
+ }
45
+
40
46
  @Resolver()
41
47
  class HelloResolver {
42
- @Query()
43
- hello(@Arg('name') name: string): string {
44
- return `Hello, ${name}!`;
48
+ @Query({ input: HelloInput })
49
+ hello(input: HelloInput): string {
50
+ return `Hello, ${input.name}!`;
45
51
  }
46
52
  }
47
53
 
@@ -65,7 +71,7 @@ await app.listen(3000);
65
71
  ## Core Capabilities
66
72
 
67
73
  ### Code-first Resolvers
68
- fluo uses standard decorators to define your GraphQL schema. Use `@Resolver`, `@Query`, `@Mutation`, and `@Subscription` to map class methods to GraphQL operations.
74
+ fluo uses standard decorators to define your GraphQL schema. Use `@Resolver`, `@Query`, `@Mutation`, and `@Subscription` to map class methods to GraphQL operations. GraphQL arguments are declared on input DTO fields with `@Arg(...)`, then passed to the resolver method through the operation `input` option.
69
75
 
70
76
  ### Request-Scoped DataLoaders
71
77
  Efficiently solve the N+1 problem with built-in DataLoader integration. Loaders are automatically isolated per GraphQL operation.
@@ -78,11 +84,46 @@ const userLoader = createDataLoader(async (ids: string[]) => {
78
84
  return ids.map(id => users.find(u => u.id === id));
79
85
  });
80
86
 
87
+ class UserInput {
88
+ @Arg('id')
89
+ id = '';
90
+ }
91
+
81
92
  @Resolver()
82
93
  class UserResolver {
83
- @Query()
84
- async user(@Arg('id') id: string, context: GraphQLContext) {
85
- return userLoader(context).load(id);
94
+ @Query({ input: UserInput })
95
+ async user(input: UserInput, context: GraphQLContext) {
96
+ return userLoader(context).load(input.id);
97
+ }
98
+ }
99
+ ```
100
+
101
+ ## Resolver Lifecycle Contracts
102
+
103
+ - Singleton resolvers are the default and are resolved from the application container for every operation.
104
+ - Resolvers that inject request-scoped providers must also be marked with `@Scope('request')`; this keeps DI lifetime rules explicit and avoids singleton-to-request dependency mismatches.
105
+ - `@fluojs/graphql` creates one operation-scoped DI container for each HTTP GraphQL request or websocket subscription operation, shares it across resolver calls in that operation, and disposes it when the operation completes or the websocket operation disconnects.
106
+ - Request-scoped DataLoader helpers use the same `GraphQLContext` operation boundary, so loader caches are shared only within one GraphQL operation.
107
+
108
+ ```typescript
109
+ import { Inject, Scope } from '@fluojs/core';
110
+ import { Query, Resolver } from '@fluojs/graphql';
111
+
112
+ @Scope('request')
113
+ class RequestState {
114
+ private static nextId = 0;
115
+ readonly requestId = `request-${++RequestState.nextId}`;
116
+ }
117
+
118
+ @Inject(RequestState)
119
+ @Scope('request')
120
+ @Resolver()
121
+ class RequestResolver {
122
+ constructor(private readonly state: RequestState) {}
123
+
124
+ @Query('requestId')
125
+ requestId(): string {
126
+ return this.state.requestId;
86
127
  }
87
128
  }
88
129
  ```
@@ -142,7 +183,7 @@ GraphqlModule.forRoot({
142
183
 
143
184
  - `GraphqlModule.forRoot(options)`: Main entry point for GraphQL integration.
144
185
  - `Resolver`, `Query`, `Mutation`, `Subscription`: Operation decorators.
145
- - `Arg`: Argument mapping decorator.
186
+ - `Arg`: Input DTO field-to-GraphQL-argument mapping decorator.
146
187
  - `createDataLoader`, `createDataLoaderMap`: DataLoader factory helpers.
147
188
  - `GraphQLContext`: Type definition for the GraphQL execution context.
148
189
 
package/package.json CHANGED
@@ -9,7 +9,7 @@
9
9
  "yoga",
10
10
  "api"
11
11
  ],
12
- "version": "1.0.0-beta.1",
12
+ "version": "1.0.0-beta.2",
13
13
  "private": false,
14
14
  "license": "MIT",
15
15
  "repository": {
@@ -42,9 +42,9 @@
42
42
  "graphql-yoga": "^5.18.1",
43
43
  "ws": "^8.18.3",
44
44
  "@fluojs/core": "^1.0.0-beta.1",
45
- "@fluojs/runtime": "^1.0.0-beta.1",
46
- "@fluojs/di": "^1.0.0-beta.1",
45
+ "@fluojs/di": "^1.0.0-beta.2",
47
46
  "@fluojs/http": "^1.0.0-beta.1",
47
+ "@fluojs/runtime": "^1.0.0-beta.2",
48
48
  "@fluojs/validation": "^1.0.0-beta.1"
49
49
  },
50
50
  "devDependencies": {