Skip to content

Commit 14ec7b5

Browse files
committed
WIP actions support
1 parent 54bfae0 commit 14ec7b5

File tree

12 files changed

+173
-113
lines changed

12 files changed

+173
-113
lines changed

PR-TODO-DO-NOT-MERGE.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# TODO
2+
3+
- fix failing e2e tests
4+
- dev mode support for q-loader-data and other things that read the manifest for qwik-router-config
5+
- remove every q-data.json load
6+
- implement eTag option for loaders
7+
- implement expires option for loaders
8+
- changes the cache-control max-age
9+
- use on client side to determine if the data is stale before fetching
10+
- during SSR, store page create time and loader expires deltas
11+
- cache q-loader-data responses in the browser

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

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,25 @@
1-
import { _serialize, type ValueOrPromise } from '@qwik.dev/core/internal';
1+
import { _serialize, _UNINITIALIZED, type ValueOrPromise } from '@qwik.dev/core/internal';
22
import type {
33
ActionInternal,
44
JSONObject,
5+
LoaderInternal,
56
RequestEvent,
67
RequestHandler,
78
} from '../../../runtime/src/types';
8-
import { getRequestActions, getRequestMode, type RequestEventInternal } from '../request-event';
9+
import {
10+
getRequestActions,
11+
getRequestLoaders,
12+
getRequestMode,
13+
type RequestEventInternal,
14+
} from '../request-event';
915
import { measure, verifySerializable } from '../resolve-request-handlers';
1016
import { IsQAction, QActionId } from '../user-response';
1117
import { runValidators } from './validator-utils';
1218

13-
export function actionHandler(routeActions: ActionInternal[]): RequestHandler {
19+
export function actionHandler(
20+
routeActions: ActionInternal[],
21+
routeLoaders: LoaderInternal[]
22+
): RequestHandler {
1423
return async (requestEvent: RequestEvent) => {
1524
const requestEv = requestEvent as RequestEventInternal;
1625

@@ -25,7 +34,6 @@ export function actionHandler(routeActions: ActionInternal[]): RequestHandler {
2534
const actionId = requestEv.sharedMap.get(QActionId);
2635

2736
// Execute just this action
28-
const actions = getRequestActions(requestEv);
2937
const isDev = getRequestMode(requestEv) === 'dev';
3038
const method = requestEv.method;
3139

@@ -35,13 +43,20 @@ export function actionHandler(routeActions: ActionInternal[]): RequestHandler {
3543
);
3644
}
3745
if (method === 'POST') {
46+
const actions = getRequestActions(requestEv);
3847
let action: ActionInternal | undefined;
3948
for (const routeAction of routeActions) {
4049
if (routeAction.__id === actionId) {
4150
action = routeAction;
42-
break;
51+
} else {
52+
// actions can use other actions
53+
actions[routeAction.__id] = _UNINITIALIZED;
4354
}
44-
// TODO: do we need to initialize the rest with _UNINITIALIZED?
55+
}
56+
// actions can use loaders
57+
const loaders = getRequestLoaders(requestEv);
58+
for (const routeLoader of routeLoaders) {
59+
loaders[routeLoader.__id] = _UNINITIALIZED;
4560
}
4661
if (!action) {
4762
const serverActionsMap = globalThis._qwikActionsMap as
@@ -68,7 +83,7 @@ export function actionHandler(routeActions: ActionInternal[]): RequestHandler {
6883
};
6984
}
7085

71-
async function executeAction(
86+
export async function executeAction(
7287
action: ActionInternal,
7388
actions: Record<string, ValueOrPromise<unknown> | undefined>,
7489
requestEv: RequestEventInternal,

packages/qwik-router/src/middleware/request-handler/handlers/action-handler.unit.ts

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ vi.mock('../resolve-request-handlers', () => ({
2020
}));
2121

2222
vi.mock('../request-event', () => ({
23+
getRequestLoaders: vi.fn(),
24+
getRequestLoaderSerializationStrategyMap: vi.fn(),
2325
getRequestActions: vi.fn(),
2426
getRequestMode: vi.fn(),
2527
RequestEvQwikSerializer: Symbol('RequestEvQwikSerializer'),
@@ -122,7 +124,7 @@ describe('actionHandler', () => {
122124

123125
describe('when not a QAction request', () => {
124126
it('should return early without processing', async () => {
125-
const handler = actionHandler([mockAction]);
127+
const handler = actionHandler([mockAction], []);
126128

127129
await handler(mockRequestEvent);
128130

@@ -140,7 +142,7 @@ describe('actionHandler', () => {
140142
};
141143
mockEvent.sharedMap.set(IsQAction, true);
142144

143-
const handler = actionHandler([mockAction]);
145+
const handler = actionHandler([mockAction], []);
144146

145147
await handler(mockEvent);
146148

@@ -157,7 +159,7 @@ describe('actionHandler', () => {
157159
};
158160
mockEvent.sharedMap.set(IsQAction, true);
159161

160-
const handler = actionHandler([mockAction]);
162+
const handler = actionHandler([mockAction], []);
161163

162164
await handler(mockEvent);
163165

@@ -175,7 +177,7 @@ describe('actionHandler', () => {
175177
mockEvent.sharedMap.set(IsQAction, true);
176178
mockEvent.sharedMap.set(QActionId, mockActionId);
177179

178-
const handler = actionHandler([mockAction]);
180+
const handler = actionHandler([mockAction], []);
179181

180182
await handler(mockEvent);
181183

@@ -194,7 +196,7 @@ describe('actionHandler', () => {
194196
mockEvent.sharedMap.set(IsQAction, true);
195197
mockEvent.sharedMap.set(QActionId, mockActionId);
196198

197-
const handler = actionHandler([mockAction]);
199+
const handler = actionHandler([mockAction], []);
198200

199201
await handler(mockEvent);
200202

@@ -208,7 +210,7 @@ describe('actionHandler', () => {
208210
mockRequestEvent.sharedMap.set(IsQAction, true);
209211
mockRequestEvent.sharedMap.set(QActionId, 'non-existent-action');
210212

211-
const handler = actionHandler([mockAction]);
213+
const handler = actionHandler([mockAction], []);
212214

213215
await handler(mockRequestEvent);
214216

@@ -235,7 +237,7 @@ describe('actionHandler', () => {
235237
data,
236238
});
237239

238-
const handler = actionHandler([mockAction]);
240+
const handler = actionHandler([mockAction], []);
239241

240242
await handler(mockRequestEvent);
241243

@@ -256,7 +258,7 @@ describe('actionHandler', () => {
256258
});
257259

258260
it('should execute action and return serialized data', async () => {
259-
const handler = actionHandler([mockAction]);
261+
const handler = actionHandler([mockAction], []);
260262

261263
await handler(mockRequestEvent);
262264

@@ -284,7 +286,7 @@ describe('actionHandler', () => {
284286
it('should measure execution time in dev mode', async () => {
285287
vi.mocked(getRequestMode).mockReturnValue('dev');
286288

287-
const handler = actionHandler([mockAction]);
289+
const handler = actionHandler([mockAction], []);
288290

289291
await handler(mockRequestEvent);
290292

@@ -295,7 +297,7 @@ describe('actionHandler', () => {
295297
it('should not measure execution time in production mode', async () => {
296298
vi.mocked(getRequestMode).mockReturnValue('server');
297299

298-
const handler = actionHandler([mockAction]);
300+
const handler = actionHandler([mockAction], []);
299301

300302
await handler(mockRequestEvent);
301303

@@ -306,7 +308,7 @@ describe('actionHandler', () => {
306308
it('should not return serialized data when client does not accept JSON', async () => {
307309
mockRequestEvent.request.headers.set('accept', 'text/html');
308310

309-
const handler = actionHandler([mockAction]);
311+
const handler = actionHandler([mockAction], []);
310312

311313
await handler(mockRequestEvent);
312314

@@ -338,7 +340,7 @@ describe('actionHandler', () => {
338340
});
339341

340342
it('should call fail method and store the result', async () => {
341-
const handler = actionHandler([mockAction]);
343+
const handler = actionHandler([mockAction], []);
342344

343345
await handler(mockRequestEvent);
344346

@@ -356,7 +358,7 @@ describe('actionHandler', () => {
356358
error: 'Validation failed',
357359
});
358360

359-
const handler = actionHandler([mockAction]);
361+
const handler = actionHandler([mockAction], []);
360362

361363
await handler(mockRequestEvent);
362364

@@ -376,7 +378,7 @@ describe('actionHandler', () => {
376378
});
377379

378380
it('should throw an error', async () => {
379-
const handler = actionHandler([mockAction]);
381+
const handler = actionHandler([mockAction], []);
380382

381383
await expect(handler(mockRequestEvent)).rejects.toThrow(
382384
`Expected request data for the action id ${mockActionId} to be an object`
@@ -389,7 +391,7 @@ describe('actionHandler', () => {
389391
parseBody: vi.fn().mockResolvedValue(null),
390392
};
391393

392-
const handler = actionHandler([mockAction]);
394+
const handler = actionHandler([mockAction], []);
393395

394396
await expect(handler(mockEvent)).rejects.toThrow(
395397
`Expected request data for the action id ${mockActionId} to be an object`
@@ -414,7 +416,7 @@ describe('actionHandler', () => {
414416
});
415417

416418
it('should propagate the error', async () => {
417-
const handler = actionHandler([mockAction]);
419+
const handler = actionHandler([mockAction], []);
418420

419421
await expect(handler(mockRequestEvent)).rejects.toThrow('Action execution failed');
420422
});
@@ -425,7 +427,7 @@ describe('actionHandler', () => {
425427
mockRequestEvent.sharedMap.set(IsQAction, true);
426428
mockRequestEvent.sharedMap.set(QActionId, mockActionId);
427429

428-
const handler = actionHandler([]);
430+
const handler = actionHandler([], []);
429431

430432
await handler(mockRequestEvent);
431433

@@ -437,7 +439,7 @@ describe('actionHandler', () => {
437439
mockRequestEvent.sharedMap.set(IsQAction, true);
438440
mockRequestEvent.sharedMap.set(QActionId, 'non-existent-action');
439441

440-
const handler = actionHandler([mockAction]);
442+
const handler = actionHandler([mockAction], []);
441443

442444
await handler(mockRequestEvent);
443445

@@ -458,7 +460,7 @@ describe('actionHandler', () => {
458460
});
459461
(mockAction.__qrl.call as Mock).mockResolvedValue({ result: 'success' });
460462

461-
const handler = actionHandler([mockAction]);
463+
const handler = actionHandler([mockAction], []);
462464

463465
await handler(mockEvent);
464466

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

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import qwikRouterConfig from '@qwik-router-config';
22
import { _serialize, _UNINITIALIZED } from '@qwik.dev/core/internal';
3-
import type { LoaderInternal, RequestHandler } from '../../../runtime/src/types';
3+
import type { ActionInternal, LoaderInternal, RequestHandler } from '../../../runtime/src/types';
44
import { getPathnameForDynamicRoute } from '../../../utils/pathname';
55
import {
6+
getRequestActions,
67
getRequestLoaders,
78
getRequestLoaderSerializationStrategyMap,
89
getRequestMode,
@@ -71,7 +72,10 @@ export function loaderDataHandler(routeLoaders: LoaderInternal[]): RequestHandle
7172
};
7273
}
7374

74-
export function loaderHandler(routeLoaders: LoaderInternal[]): RequestHandler {
75+
export function loaderHandler(
76+
routeLoaders: LoaderInternal[],
77+
routeActions: ActionInternal[]
78+
): RequestHandler {
7579
return async (requestEvent: RequestEvent) => {
7680
const requestEv = requestEvent as RequestEventInternal;
7781

@@ -94,10 +98,17 @@ export function loaderHandler(routeLoaders: LoaderInternal[]): RequestHandler {
9498
if (routeLoader.__id === loaderId) {
9599
loader = routeLoader;
96100
} else if (!loaders[routeLoader.__id]) {
101+
// loaders can use other loaders
97102
loaders[routeLoader.__id] = _UNINITIALIZED;
98103
}
99104
}
100105

106+
// loaders can use actions
107+
const actions = getRequestActions(requestEv);
108+
for (const routeAction of routeActions) {
109+
actions[routeAction.__id] = _UNINITIALIZED;
110+
}
111+
101112
if (!loader) {
102113
requestEv.json(404, { error: 'Loader not found' });
103114
return;

0 commit comments

Comments
 (0)