Skip to content

Commit 69a43e9

Browse files
wmertensJerryWu1234
andcommitted
refactor(router): make async request store a regular export
Co-Authored-By: Jerry_wu <409187100@qq.com>
1 parent bdc690d commit 69a43e9

File tree

7 files changed

+126
-122
lines changed

7 files changed

+126
-122
lines changed

packages/qwik-router/global.d.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,7 @@ declare module '*?compiled-string' {
88

99
type RequestEventInternal =
1010
import('./middleware/request-handler/request-event').RequestEventInternal;
11-
type AsyncStore = import('node:async_hooks').AsyncLocalStorage<RequestEventInternal>;
1211
type SerializationStrategy = import('@qwik.dev/core/internal').SerializationStrategy;
13-
14-
declare var qcAsyncRequestStore: AsyncStore | undefined;
1512
declare var _qwikActionsMap: Map<string, ActionInternal> | undefined;
1613

1714
type ExperimentalFeatures = import('@qwik.dev/core/optimizer').ExperimentalFeatures;

packages/qwik-router/src/middleware/request-handler/async-request-store.ts

Whitespace-only changes.

packages/qwik-router/src/middleware/request-handler/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export { requestHandler } from './request-handler';
1+
export { requestHandler, _asyncRequestStore } from './request-handler';
22

33
export { getErrorHtml } from './error-handler';
44
export { getNotFound } from './not-found-paths';

packages/qwik-router/src/middleware/request-handler/middleware.request-handler.api.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
```ts
66

77
import type { Action } from '@qwik.dev/router';
8+
import type { AsyncLocalStorage } from 'node:async_hooks';
89
import type { EnvGetter as EnvGetter_2 } from '@qwik.dev/router/middleware/request-handler';
910
import type { FailReturn } from '@qwik.dev/router';
1011
import type { Loader as Loader_2 } from '@qwik.dev/router';
@@ -24,6 +25,11 @@ import type { ValueOrPromise } from '@qwik.dev/core';
2425
export class AbortMessage {
2526
}
2627

28+
// Warning: (ae-forgotten-export) The symbol "RequestEventInternal" needs to be exported by the entry point index.d.ts
29+
//
30+
// @internal (undocumented)
31+
export let _asyncRequestStore: AsyncLocalStorage<RequestEventInternal> | undefined;
32+
2733
// Warning: (ae-forgotten-export) The symbol "CacheControlOptions" needs to be exported by the entry point index.d.ts
2834
//
2935
// @public (undocumented)
@@ -240,8 +246,6 @@ export interface ServerRequestEvent<T = unknown> {
240246
// @public (undocumented)
241247
export type ServerRequestMode = 'dev' | 'static' | 'server';
242248

243-
// Warning: (ae-forgotten-export) The symbol "RequestEventInternal" needs to be exported by the entry point index.d.ts
244-
//
245249
// @public (undocumented)
246250
export type ServerResponseHandler<T = any> = (status: number, headers: Headers, cookies: Cookie, resolve: (response: T) => void, requestEv: RequestEventInternal) => WritableStream<Uint8Array>;
247251

packages/qwik-router/src/middleware/request-handler/request-handler.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,33 @@
1+
import { isServer } from '@qwik.dev/core/build';
12
import type { Render } from '@qwik.dev/core/server';
3+
import type { AsyncLocalStorage } from 'node:async_hooks';
24
import { loadRoute } from '../../runtime/src/routing';
35
import type { QwikRouterConfig, RebuildRouteInfoInternal } from '../../runtime/src/types';
6+
import type { RequestEventInternal } from './request-event';
47
import { renderQwikMiddleware, resolveRequestHandlers } from './resolve-request-handlers';
58
import type { ServerRenderOptions, ServerRequestEvent } from './types';
69
import { getRouteMatchPathname, runQwikRouter, type QwikRouterRun } from './user-response';
710

11+
/** @internal */
12+
export let _asyncRequestStore: AsyncLocalStorage<RequestEventInternal> | undefined;
13+
if (isServer) {
14+
// TODO when we drop cjs support, await this
15+
import('node:async_hooks')
16+
.then((module) => {
17+
_asyncRequestStore = new module.AsyncLocalStorage();
18+
})
19+
.catch((err) => {
20+
console.warn(
21+
'\n=====================\n' +
22+
' Qwik Router Warning:\n' +
23+
' AsyncLocalStorage is not available, continuing without it.\n' +
24+
' This impacts concurrent async server calls, where they lose access to the ServerRequestEv object.\n' +
25+
'=====================\n\n',
26+
err
27+
);
28+
});
29+
}
30+
831
/**
932
* We need to delay importing the config until the first request, because vite also imports from
1033
* this file and @qwik-router-config doesn't exist from the vite config before the build.

packages/qwik-router/src/middleware/request-handler/user-response.ts

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
RedirectMessage,
1717
RewriteMessage,
1818
ServerError,
19+
_asyncRequestStore,
1920
} from '@qwik.dev/router/middleware/request-handler';
2021

2122
export interface QwikRouterRun<T> {
@@ -36,20 +37,6 @@ export interface QwikRouterRun<T> {
3637
completion: Promise<RedirectMessage | Error | undefined>;
3738
}
3839

39-
let asyncStore: AsyncStore | undefined;
40-
import('node:async_hooks')
41-
.then((module) => {
42-
const AsyncLocalStorage = module.AsyncLocalStorage;
43-
asyncStore = new AsyncLocalStorage<RequestEventInternal>();
44-
globalThis.qcAsyncRequestStore = asyncStore;
45-
})
46-
.catch((err) => {
47-
console.warn(
48-
'AsyncLocalStorage not available, continuing without it. This might impact concurrent server calls.',
49-
err
50-
);
51-
});
52-
5340
export function runQwikRouter<T>(
5441
serverRequestEv: ServerRequestEvent<T>,
5542
loadedRoute: LoadedRoute | null,
@@ -70,8 +57,8 @@ export function runQwikRouter<T>(
7057
return {
7158
response: responsePromise,
7259
requestEv,
73-
completion: asyncStore
74-
? asyncStore.run(requestEv, runNext, requestEv, rebuildRouteInfo, resolve!)
60+
completion: _asyncRequestStore
61+
? _asyncRequestStore.run(requestEv, runNext, requestEv, rebuildRouteInfo, resolve!)
7562
: runNext(requestEv, rebuildRouteInfo, resolve!),
7663
};
7764
}

packages/qwik-router/src/runtime/src/server-functions.ts

Lines changed: 93 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,11 @@ import {
1414
_deserialize,
1515
_getContextElement,
1616
_getContextEvent,
17-
_serialize,
18-
_UNINITIALIZED,
1917
_resolveContextWithoutSequentialScope,
18+
_serialize,
2019
type SerializationStrategy,
2120
} from '@qwik.dev/core/internal';
22-
21+
import { _asyncRequestStore } from '@qwik.dev/router/middleware/request-handler';
2322
import * as v from 'valibot';
2423
import { z } from 'zod';
2524
import type { RequestEventLoader } from '../../middleware/request-handler/types';
@@ -30,6 +29,8 @@ import {
3029
QFN_KEY,
3130
} from './constants';
3231
import { RouteStateContext } from './contexts';
32+
import { deepFreeze } from './deepFreeze';
33+
import type { FormSubmitCompletedDetail } from './form-component';
3334
import type {
3435
ActionConstructor,
3536
ActionConstructorQRL,
@@ -63,9 +64,6 @@ import type {
6364
} from './types';
6465
import { useAction, useLocation, useQwikRouterEnv } from './use-functions';
6566

66-
import type { FormSubmitCompletedDetail } from './form-component';
67-
import { deepFreeze } from './deepFreeze';
68-
6967
/** @internal */
7068
export const routeActionQrl = ((
7169
actionQrl: QRL<(form: JSONObject, event: RequestEventAction) => unknown>,
@@ -411,107 +409,102 @@ export const serverQrl = <T extends ServerFunction>(
411409
const origin = options?.origin || '';
412410
const fetchOptions = options?.fetchOptions || {};
413411

414-
function rpc() {
415-
return $(async function (this: RequestEventBase, ...args: Parameters<T>) {
416-
// move to ServerConfig
417-
const abortSignal =
418-
args.length > 0 && args[0] instanceof AbortSignal
419-
? (args.shift() as AbortSignal)
420-
: undefined;
421-
422-
if (isServer) {
423-
// Running during SSR, we can call the function directly
424-
let requestEvent = globalThis.qcAsyncRequestStore?.getStore() as RequestEvent | undefined;
412+
return $(async function (this: RequestEventBase | undefined, ...args: Parameters<T>) {
413+
// move to ServerConfig
414+
const abortSignal =
415+
args.length > 0 && args[0] instanceof AbortSignal ? (args.shift() as AbortSignal) : undefined;
416+
417+
if (isServer) {
418+
// Running during SSR, we can call the function directly
419+
let requestEvent = _asyncRequestStore?.getStore() as RequestEvent | undefined;
420+
421+
if (!requestEvent) {
422+
const contexts = [useQwikRouterEnv()?.ev, this, _getContextEvent()] as RequestEvent[];
423+
requestEvent = contexts.find(
424+
(v) =>
425+
v &&
426+
Object.prototype.hasOwnProperty.call(v, 'sharedMap') &&
427+
Object.prototype.hasOwnProperty.call(v, 'cookie')
428+
);
429+
}
425430

426-
if (!requestEvent) {
427-
const contexts = [useQwikRouterEnv()?.ev, this, _getContextEvent()] as RequestEvent[];
428-
requestEvent = contexts.find(
429-
(v) =>
430-
v &&
431-
Object.prototype.hasOwnProperty.call(v, 'sharedMap') &&
432-
Object.prototype.hasOwnProperty.call(v, 'cookie')
433-
);
431+
return qrl.apply(requestEvent, isDev ? deepFreeze(args) : args);
432+
} else {
433+
// Running on the client, we need to call the function via HTTP
434+
const ctxElm = _getContextElement();
435+
const filteredArgs = args.map((arg: unknown) => {
436+
if (arg instanceof SubmitEvent && arg.target instanceof HTMLFormElement) {
437+
return new FormData(arg.target);
438+
} else if (arg instanceof Event) {
439+
return null;
440+
} else if (arg instanceof Node) {
441+
return null;
434442
}
435-
436-
return qrl.apply(requestEvent, isDev ? deepFreeze(args) : args);
443+
return arg;
444+
});
445+
const qrlHash = qrl.getHash();
446+
// Handled by `pureServerFunction` middleware
447+
let query = '';
448+
const config = {
449+
...fetchOptions,
450+
method,
451+
headers: {
452+
...headers,
453+
'Content-Type': 'application/qwik-json',
454+
Accept: 'application/json, application/qwik-json, text/qwik-json-stream, text/plain',
455+
// Required so we don't call accidentally
456+
'X-QRL': qrlHash,
457+
},
458+
signal: abortSignal,
459+
};
460+
const body = await _serialize([qrl, ...filteredArgs]);
461+
if (method === 'GET') {
462+
query += `&${QDATA_KEY}=${encodeURIComponent(body)}`;
437463
} else {
438-
// Running on the client, we need to call the function via HTTP
439-
const ctxElm = _getContextElement();
440-
const filteredArgs = args.map((arg: unknown) => {
441-
if (arg instanceof SubmitEvent && arg.target instanceof HTMLFormElement) {
442-
return new FormData(arg.target);
443-
} else if (arg instanceof Event) {
444-
return null;
445-
} else if (arg instanceof Node) {
446-
return null;
447-
}
448-
return arg;
449-
});
450-
const qrlHash = qrl.getHash();
451-
// Handled by `pureServerFunction` middleware
452-
let query = '';
453-
const config = {
454-
...fetchOptions,
455-
method,
456-
headers: {
457-
...headers,
458-
'Content-Type': 'application/qwik-json',
459-
Accept: 'application/json, application/qwik-json, text/qwik-json-stream, text/plain',
460-
// Required so we don't call accidentally
461-
'X-QRL': qrlHash,
462-
},
463-
signal: abortSignal,
464-
};
465-
const body = await _serialize([qrl, ...filteredArgs]);
466-
if (method === 'GET') {
467-
query += `&${QDATA_KEY}=${encodeURIComponent(body)}`;
468-
} else {
469-
// PatrickJS: sorry Ryan Florence I prefer const still
470-
config.body = body;
471-
}
472-
const res = await fetch(`${origin}?${QFN_KEY}=${qrlHash}${query}`, config);
473-
474-
const contentType = res.headers.get('Content-Type');
475-
if (res.ok && contentType === 'text/qwik-json-stream' && res.body) {
476-
return (async function* () {
477-
try {
478-
for await (const result of deserializeStream(
479-
res.body!,
480-
ctxElm ?? document.documentElement,
481-
abortSignal
482-
)) {
483-
yield result;
484-
}
485-
} finally {
486-
if (!abortSignal?.aborted) {
487-
await res.body!.cancel();
488-
}
464+
// PatrickJS: sorry Ryan Florence I prefer const still
465+
config.body = body;
466+
}
467+
const res = await fetch(`${origin}?${QFN_KEY}=${qrlHash}${query}`, config);
468+
469+
const contentType = res.headers.get('Content-Type');
470+
if (res.ok && contentType === 'text/qwik-json-stream' && res.body) {
471+
return (async function* () {
472+
try {
473+
for await (const result of deserializeStream(
474+
res.body!,
475+
ctxElm ?? document.documentElement,
476+
abortSignal
477+
)) {
478+
yield result;
479+
}
480+
} finally {
481+
if (!abortSignal?.aborted) {
482+
await res.body!.cancel();
489483
}
490-
})();
491-
} else if (contentType === 'application/qwik-json') {
492-
const str = await res.text();
493-
const [obj] = _deserialize(str, ctxElm ?? document.documentElement);
494-
if (res.status >= 400) {
495-
throw obj;
496-
}
497-
return obj;
498-
} else if (contentType === 'application/json') {
499-
const obj = await res.json();
500-
if (res.status >= 400) {
501-
throw obj;
502-
}
503-
return obj;
504-
} else if (contentType === 'text/plain' || contentType === 'text/html') {
505-
const str = await res.text();
506-
if (res.status >= 400) {
507-
throw str;
508484
}
509-
return str;
485+
})();
486+
} else if (contentType === 'application/qwik-json') {
487+
const str = await res.text();
488+
const [obj] = _deserialize(str, ctxElm ?? document.documentElement);
489+
if (res.status >= 400) {
490+
throw obj;
491+
}
492+
return obj;
493+
} else if (contentType === 'application/json') {
494+
const obj = await res.json();
495+
if (res.status >= 400) {
496+
throw obj;
510497
}
498+
return obj;
499+
} else if (contentType === 'text/plain' || contentType === 'text/html') {
500+
const str = await res.text();
501+
if (res.status >= 400) {
502+
throw str;
503+
}
504+
return str;
511505
}
512-
}) as ServerQRL<T>;
513-
}
514-
return rpc();
506+
}
507+
}) as ServerQRL<T>;
515508
};
516509

517510
/** @public */

0 commit comments

Comments
 (0)