@fluojs/graphql 1.0.0-beta.4 → 1.0.0-beta.5
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.ko.md +2 -1
- package/README.md +2 -1
- package/dist/service.d.ts +1 -0
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +34 -10
- package/dist/transport/transport.d.ts.map +1 -1
- package/dist/transport/transport.js +66 -20
- package/package.json +6 -6
package/README.ko.md
CHANGED
|
@@ -133,7 +133,7 @@ class RequestResolver {
|
|
|
133
133
|
### 프로토콜 지원
|
|
134
134
|
- **HTTP**: 표준 GET/POST 쿼리 및 뮤테이션.
|
|
135
135
|
- **SSE**: Server-Sent Events를 통한 구독(기본값).
|
|
136
|
-
- **WebSockets**:
|
|
136
|
+
- **WebSockets**: 활성 adapter가 upgrade listener를 지원하는 Node HTTP/S 서버를 노출할 때(예: Node HTTP adapter) 사용할 수 있는 선택적 `graphql-ws` 실시간 구독 지원.
|
|
137
137
|
|
|
138
138
|
```typescript
|
|
139
139
|
GraphqlModule.forRoot({
|
|
@@ -154,6 +154,7 @@ GraphqlModule.forRoot({
|
|
|
154
154
|
|
|
155
155
|
- `graphiql`을 명시적으로 켜거나 `introspection: true`를 설정하지 않으면 스키마 introspection은 기본적으로 비활성화됩니다.
|
|
156
156
|
- 문서 depth, field complexity, aggregate query cost에 대한 request validation budget이 기본적으로 보수적인 값으로 활성화됩니다.
|
|
157
|
+
- Streaming GraphQL 응답은 downstream response stream이 닫히거나 오류를 내면 upstream fetch body를 cancel하므로 SSE subscription 리소스를 즉시 해제합니다.
|
|
157
158
|
- WebSocket 구독 경로에는 별도의 전송 budget이 기본 적용됩니다: 동시 연결 `100`, 최대 payload 크기 `64 KiB`, 연결당 활성 operation `25`개입니다.
|
|
158
159
|
- 무제한 WebSocket 동작이 정말 필요할 때만 `subscriptions.websocket.limits = false`를 사용하고, 그 경우에도 동일한 수준의 외부 제어 수단을 마련해야 합니다.
|
|
159
160
|
- 무제한 동작이 꼭 필요할 때만 `limits: false`를 사용하고, 그 경우에는 외부 제어 수단을 함께 두어야 합니다.
|
package/README.md
CHANGED
|
@@ -133,7 +133,7 @@ class RequestResolver {
|
|
|
133
133
|
### Protocol Support
|
|
134
134
|
- **HTTP**: Standard GET/POST queries and mutations.
|
|
135
135
|
- **SSE**: Subscriptions over Server-Sent Events (default).
|
|
136
|
-
- **WebSockets**: Optional `graphql-ws` support for real-time subscriptions.
|
|
136
|
+
- **WebSockets**: Optional `graphql-ws` support for real-time subscriptions when the active adapter exposes a Node HTTP/S server with upgrade listeners (for example, the Node HTTP adapter).
|
|
137
137
|
|
|
138
138
|
```typescript
|
|
139
139
|
GraphqlModule.forRoot({
|
|
@@ -154,6 +154,7 @@ GraphqlModule.forRoot({
|
|
|
154
154
|
|
|
155
155
|
- Schema introspection is disabled by default unless you explicitly enable `graphiql` or set `introspection: true`.
|
|
156
156
|
- Request validation budgets are enabled by default with conservative limits for document depth, field complexity, and aggregate query cost.
|
|
157
|
+
- Streaming GraphQL responses cancel the upstream fetch body when the downstream response stream closes or errors, so SSE subscription resources are released promptly.
|
|
157
158
|
- WebSocket subscriptions use separate transport budgets by default: `100` concurrent connections, `64 KiB` maximum payload size, and `25` active operations per connection.
|
|
158
159
|
- Set `subscriptions.websocket.limits = false` only when you intentionally need unbounded websocket behavior and can enforce equivalent controls elsewhere.
|
|
159
160
|
- Pass `limits: false` only when you intentionally need unbounded behavior and can compensate with external controls.
|
package/dist/service.d.ts
CHANGED
|
@@ -27,6 +27,7 @@ export declare class GraphqlLifecycleService implements OnApplicationBootstrap,
|
|
|
27
27
|
private websocketUpgradeListener;
|
|
28
28
|
private websocketUpgradeServer;
|
|
29
29
|
private executeGraphqlOperation;
|
|
30
|
+
private releaseGraphqlInstanceOfPatch;
|
|
30
31
|
private subscribeGraphqlOperation;
|
|
31
32
|
private yoga;
|
|
32
33
|
private readonly middleware;
|
package/dist/service.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAKA,OAAO,EAAgD,KAAK,sBAAsB,EAAsD,MAAM,cAAc,CAAC;AAE7J,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,KAAK,sBAAsB,EAC3B,KAAK,qBAAqB,EAC3B,MAAM,iBAAiB,CAAC;AA2BzB,OAAO,KAAK,EAEV,oBAAoB,EAGrB,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAKA,OAAO,EAAgD,KAAK,sBAAsB,EAAsD,MAAM,cAAc,CAAC;AAE7J,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,KAAK,sBAAsB,EAC3B,KAAK,qBAAqB,EAC3B,MAAM,iBAAiB,CAAC;AA2BzB,OAAO,KAAK,EAEV,oBAAoB,EAGrB,MAAM,YAAY,CAAC;AAmIpB;;GAEG;AACH,qBACa,yBAAyB;IAEpC,SAAS,IAAI,SAAS;IAKtB,UAAU,IAAI,SAAS;CAGxB;AA+ID;;GAEG;AACH,qBACa,uBAAwB,YAAW,sBAAsB,EAAE,qBAAqB;IA6DzF,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IACjC,OAAO,CAAC,QAAQ,CAAC,eAAe;IAChC,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAC1B,OAAO,CAAC,QAAQ,CAAC,OAAO;IAhExB,OAAO,CAAC,uBAAuB,CAAsC;IACrE,OAAO,CAAC,oBAAoB,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAqC;IACzE,OAAO,CAAC,QAAQ,CAAC,4BAA4B,CAA6C;IAC1F,OAAO,CAAC,mBAAmB,CAAkD;IAC7E,OAAO,CAAC,eAAe,CAA8B;IACrD,OAAO,CAAC,wBAAwB,CAAkC;IAClE,OAAO,CAAC,sBAAsB,CAAgC;IAC9D,OAAO,CAAC,uBAAuB,CAAyD;IACxF,OAAO,CAAC,6BAA6B,CAA2B;IAChE,OAAO,CAAC,yBAAyB,CAAyD;IAC1F,OAAO,CAAC,IAAI,CAAuB;IAEnC,OAAO,CAAC,QAAQ,CAAC,UAAU,CA4CzB;gBAGiB,gBAAgB,EAAE,SAAS,EAC3B,eAAe,EAAE,SAAS,cAAc,EAAE,EAC1C,MAAM,EAAE,iBAAiB,EACzB,OAAO,EAAE,sBAAsB,EACjC,OAAO,EAAE,oBAAoB;IAGxC,sBAAsB,IAAI,OAAO,CAAC,IAAI,CAAC;IAsCvC,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC;IAY5C,OAAO,CAAC,sBAAsB;IAI9B,OAAO,CAAC,2BAA2B;IAInC,OAAO,CAAC,sBAAsB;IAe9B,OAAO,CAAC,aAAa;IAMrB,OAAO,CAAC,qBAAqB;IAI7B,OAAO,CAAC,2BAA2B;IAInC,OAAO,CAAC,kBAAkB;IAiB1B,OAAO,CAAC,oBAAoB;IAmB5B,OAAO,CAAC,mBAAmB;IAyB3B,OAAO,CAAC,0BAA0B;YAuEpB,4BAA4B;IAuC1C,OAAO,CAAC,2BAA2B;IAInC,OAAO,CAAC,oBAAoB;YAkBd,wBAAwB;IAwDtC,OAAO,CAAC,kCAAkC;IAwB1C,OAAO,CAAC,sBAAsB;IAoB9B,OAAO,CAAC,sCAAsC;YAchC,kCAAkC;YAqBlC,sCAAsC;IAkBpD,OAAO,CAAC,6BAA6B;YAYvB,yBAAyB;CAexC"}
|
package/dist/service.js
CHANGED
|
@@ -78,7 +78,8 @@ function closeWebSocketServer(server) {
|
|
|
78
78
|
}
|
|
79
79
|
const graphqlRequestContextStorage = new AsyncLocalStorage();
|
|
80
80
|
const runtimeRequire = createRequire(import.meta.url);
|
|
81
|
-
let
|
|
81
|
+
let graphqlInstanceOfPatchRefCount = 0;
|
|
82
|
+
let restoreGraphqlInstanceOfPatch;
|
|
82
83
|
const allowedCrossRealmGraphqlObjects = new WeakSet();
|
|
83
84
|
|
|
84
85
|
/**
|
|
@@ -149,15 +150,16 @@ function isAllowedCrossRealmGraphqlObject(value, constructor) {
|
|
|
149
150
|
}
|
|
150
151
|
return allowedCrossRealmGraphqlObjects.has(value) && getCrossRealmGraphqlTag(value, constructor) !== undefined;
|
|
151
152
|
}
|
|
152
|
-
function
|
|
153
|
-
if (graphqlInstanceOfPatched) {
|
|
154
|
-
return;
|
|
155
|
-
}
|
|
153
|
+
function installGraphqlInstanceOfPatch() {
|
|
156
154
|
const instanceOfModule = runtimeRequire('graphql/jsutils/instanceOf.js');
|
|
157
|
-
|
|
158
|
-
|
|
155
|
+
if (restoreGraphqlInstanceOfPatch) {
|
|
156
|
+
graphqlInstanceOfPatchRefCount += 1;
|
|
157
|
+
return releaseGraphqlInstanceOfPatch;
|
|
158
|
+
}
|
|
159
|
+
const patchedFrom = instanceOfModule.instanceOf;
|
|
160
|
+
const patchedInstanceOf = (value, constructor) => {
|
|
159
161
|
try {
|
|
160
|
-
if (
|
|
162
|
+
if (patchedFrom(value, constructor)) {
|
|
161
163
|
return true;
|
|
162
164
|
}
|
|
163
165
|
} catch (error) {
|
|
@@ -168,7 +170,26 @@ function patchGraphqlInstanceOf() {
|
|
|
168
170
|
}
|
|
169
171
|
return isAllowedCrossRealmGraphqlObject(value, constructor);
|
|
170
172
|
};
|
|
171
|
-
|
|
173
|
+
instanceOfModule.instanceOf = patchedInstanceOf;
|
|
174
|
+
graphqlInstanceOfPatchRefCount = 1;
|
|
175
|
+
restoreGraphqlInstanceOfPatch = () => {
|
|
176
|
+
if (instanceOfModule.instanceOf !== patchedInstanceOf) {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
instanceOfModule.instanceOf = patchedFrom;
|
|
180
|
+
};
|
|
181
|
+
return releaseGraphqlInstanceOfPatch;
|
|
182
|
+
}
|
|
183
|
+
function releaseGraphqlInstanceOfPatch() {
|
|
184
|
+
if (graphqlInstanceOfPatchRefCount === 0) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
graphqlInstanceOfPatchRefCount -= 1;
|
|
188
|
+
if (graphqlInstanceOfPatchRefCount > 0) {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
restoreGraphqlInstanceOfPatch?.();
|
|
192
|
+
restoreGraphqlInstanceOfPatch = undefined;
|
|
172
193
|
}
|
|
173
194
|
async function loadGraphqlDeps() {
|
|
174
195
|
const graphqlMod = runtimeRequire('graphql');
|
|
@@ -208,6 +229,7 @@ class GraphqlLifecycleService {
|
|
|
208
229
|
websocketUpgradeListener;
|
|
209
230
|
websocketUpgradeServer;
|
|
210
231
|
executeGraphqlOperation;
|
|
232
|
+
releaseGraphqlInstanceOfPatch;
|
|
211
233
|
subscribeGraphqlOperation;
|
|
212
234
|
yoga;
|
|
213
235
|
middleware = {
|
|
@@ -291,6 +313,8 @@ class GraphqlLifecycleService {
|
|
|
291
313
|
this.middlewareRegistered = false;
|
|
292
314
|
this.executeGraphqlOperation = undefined;
|
|
293
315
|
this.graphQLErrorConstructor = undefined;
|
|
316
|
+
this.releaseGraphqlInstanceOfPatch?.();
|
|
317
|
+
this.releaseGraphqlInstanceOfPatch = undefined;
|
|
294
318
|
this.subscribeGraphqlOperation = undefined;
|
|
295
319
|
this.yoga = undefined;
|
|
296
320
|
}
|
|
@@ -312,7 +336,7 @@ class GraphqlLifecycleService {
|
|
|
312
336
|
};
|
|
313
337
|
}
|
|
314
338
|
resolveSchema(deps) {
|
|
315
|
-
|
|
339
|
+
this.releaseGraphqlInstanceOfPatch ??= installGraphqlInstanceOfPatch();
|
|
316
340
|
return resolveSchema(deps, this.options.schema, () => this.createCodeFirstSchema(deps), markAllowedCrossRealmGraphqlObjects);
|
|
317
341
|
}
|
|
318
342
|
createCodeFirstSchema(deps) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../../src/transport/transport.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,iBAAiB,EAA2B,MAAM,cAAc,CAAC;AAEjG;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEnD;AAsED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAUjE;
|
|
1
|
+
{"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../../src/transport/transport.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,iBAAiB,EAA2B,MAAM,cAAc,CAAC;AAEjG;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEnD;AAsED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAUjE;AAgGD;;;;;;GAMG;AACH,wBAAsB,kBAAkB,CAAC,aAAa,EAAE,QAAQ,EAAE,iBAAiB,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CA+BrH"}
|
|
@@ -89,6 +89,71 @@ function readSetCookieValues(headers) {
|
|
|
89
89
|
}
|
|
90
90
|
return values;
|
|
91
91
|
}
|
|
92
|
+
async function writeFetchResponseStream(body, stream) {
|
|
93
|
+
const reader = body.getReader();
|
|
94
|
+
let readerDone = false;
|
|
95
|
+
let cancelPromise;
|
|
96
|
+
let notifyCancellation;
|
|
97
|
+
const cancellationStarted = new Promise(resolve => {
|
|
98
|
+
notifyCancellation = resolve;
|
|
99
|
+
});
|
|
100
|
+
const removeCloseListener = stream.onClose?.(() => {
|
|
101
|
+
if (!readerDone) {
|
|
102
|
+
void cancelUnreadBody().catch(() => {});
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
const cancelUnreadBody = () => {
|
|
106
|
+
if (!cancelPromise) {
|
|
107
|
+
readerDone = true;
|
|
108
|
+
notifyCancellation?.();
|
|
109
|
+
cancelPromise = reader.cancel();
|
|
110
|
+
}
|
|
111
|
+
return cancelPromise;
|
|
112
|
+
};
|
|
113
|
+
const readNextChunk = async () => {
|
|
114
|
+
const readPromise = reader.read();
|
|
115
|
+
readPromise.catch(() => {});
|
|
116
|
+
return await Promise.race([readPromise, cancellationStarted.then(async () => {
|
|
117
|
+
await cancelPromise;
|
|
118
|
+
return {
|
|
119
|
+
done: true,
|
|
120
|
+
value: undefined
|
|
121
|
+
};
|
|
122
|
+
})]);
|
|
123
|
+
};
|
|
124
|
+
try {
|
|
125
|
+
while (true) {
|
|
126
|
+
if (stream.closed) {
|
|
127
|
+
await cancelUnreadBody();
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
const {
|
|
131
|
+
done,
|
|
132
|
+
value
|
|
133
|
+
} = await readNextChunk();
|
|
134
|
+
if (done) {
|
|
135
|
+
readerDone = true;
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
if (stream.closed) {
|
|
139
|
+
await cancelUnreadBody();
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
const canContinue = stream.write(value);
|
|
143
|
+
if (!canContinue && !stream.closed) {
|
|
144
|
+
await stream.waitForDrain?.();
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
} catch (error) {
|
|
148
|
+
await cancelUnreadBody();
|
|
149
|
+
throw error;
|
|
150
|
+
} finally {
|
|
151
|
+
removeCloseListener?.();
|
|
152
|
+
}
|
|
153
|
+
if (!stream.closed) {
|
|
154
|
+
stream.close();
|
|
155
|
+
}
|
|
156
|
+
}
|
|
92
157
|
|
|
93
158
|
/**
|
|
94
159
|
* Write fetch response.
|
|
@@ -113,26 +178,7 @@ export async function writeFetchResponse(fetchResponse, frameworkResponse) {
|
|
|
113
178
|
if (fetchResponse.body && stream) {
|
|
114
179
|
frameworkResponse.committed = true;
|
|
115
180
|
stream.flush?.();
|
|
116
|
-
|
|
117
|
-
while (true) {
|
|
118
|
-
const {
|
|
119
|
-
done,
|
|
120
|
-
value
|
|
121
|
-
} = await reader.read();
|
|
122
|
-
if (done) {
|
|
123
|
-
break;
|
|
124
|
-
}
|
|
125
|
-
if (stream.closed) {
|
|
126
|
-
break;
|
|
127
|
-
}
|
|
128
|
-
const canContinue = stream.write(value);
|
|
129
|
-
if (!canContinue && !stream.closed) {
|
|
130
|
-
await stream.waitForDrain?.();
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
if (!stream.closed) {
|
|
134
|
-
stream.close();
|
|
135
|
-
}
|
|
181
|
+
await writeFetchResponseStream(fetchResponse.body, stream);
|
|
136
182
|
return;
|
|
137
183
|
}
|
|
138
184
|
const buffer = await fetchResponse.arrayBuffer();
|
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"yoga",
|
|
10
10
|
"api"
|
|
11
11
|
],
|
|
12
|
-
"version": "1.0.0-beta.
|
|
12
|
+
"version": "1.0.0-beta.5",
|
|
13
13
|
"private": false,
|
|
14
14
|
"license": "MIT",
|
|
15
15
|
"repository": {
|
|
@@ -41,11 +41,11 @@
|
|
|
41
41
|
"graphql-ws": "^5.16.2",
|
|
42
42
|
"graphql-yoga": "^5.18.1",
|
|
43
43
|
"ws": "^8.18.3",
|
|
44
|
-
"@fluojs/core": "^1.0.0-beta.
|
|
45
|
-
"@fluojs/di": "^1.0.0-beta.
|
|
46
|
-
"@fluojs/http": "^1.0.0-beta.
|
|
47
|
-
"@fluojs/runtime": "^1.0.0-beta.
|
|
48
|
-
"@fluojs/validation": "^1.0.0-beta.
|
|
44
|
+
"@fluojs/core": "^1.0.0-beta.3",
|
|
45
|
+
"@fluojs/di": "^1.0.0-beta.6",
|
|
46
|
+
"@fluojs/http": "^1.0.0-beta.9",
|
|
47
|
+
"@fluojs/runtime": "^1.0.0-beta.9",
|
|
48
|
+
"@fluojs/validation": "^1.0.0-beta.2"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@types/ws": "^8.18.1",
|