Skip to content

Commit

Permalink
fix: loader$ dx (QwikDev#2777)
Browse files Browse the repository at this point in the history
  • Loading branch information
manucorporat authored Feb 1, 2023
1 parent 7826ffa commit 0db03ed
Show file tree
Hide file tree
Showing 12 changed files with 266 additions and 178 deletions.
4 changes: 4 additions & 0 deletions packages/eslint-plugin-qwik/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { validLexicalScope } from './src/validLexicalScope';
import { noUseAfterAwait } from './src/noUseAfterAwait';
import { singleJsxRoot } from './src/singleJsxRoot';
import { loaderLocation } from './src/loaderLocation';

export const rules = {
'no-use-after-await': noUseAfterAwait,
'valid-lexical-scope': validLexicalScope,
'single-jsx-root': singleJsxRoot,
'loader-location': loaderLocation,
};

export const configs = {
Expand All @@ -14,6 +16,7 @@ export const configs = {
rules: {
'qwik/no-use-after-await': 'error',
'qwik/valid-lexical-scope': 'error',
'qwik/loader-location': 'error',
'qwik/single-jsx-root': 'warn',
},
},
Expand All @@ -23,6 +26,7 @@ export const configs = {
'qwik/valid-lexical-scope': 'error',
'qwik/no-use-after-await': 'error',
'qwik/single-jsx-root': 'error',
'qwik/loader-location': 'error',
},
},
};
37 changes: 37 additions & 0 deletions packages/eslint-plugin-qwik/src/loaderLocation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/* eslint-disable no-console */
import type { Rule } from 'eslint';
import type { CallExpression } from 'estree';

export const loaderLocation: Rule.RuleModule = {
meta: {
type: 'problem',
docs: {
description: 'Detect declaration location of loader$',
recommended: true,
url: 'https://github.com/BuilderIO/qwik',
},
messages: {
invalidLoaderLocation:
'loader$() can only be declared in `layout.tsx`, `index.tsx` and `plugin.tsx` inside the `src/routes` directory, instead it was declared in "{{path}}". Please check the docs: https://qwik.builder.io/qwikcity/loader',
},
},
create(context) {
const path = context.getFilename();
const isLayout = /^layout(|!|-.+)\.tsx?$/.test(path);
const isIndex = /^index(|!|-.+)\.tsx?$/.test(path);
const canContainLoader = isIndex || isLayout;
return {
'CallExpression[callee.name=/^loader\\$$/]'(node: CallExpression) {
if (!canContainLoader) {
context.report({
node: node.callee,
messageId: 'invalidLoaderLocation',
data: {
path,
},
});
}
},
};
},
};
12 changes: 6 additions & 6 deletions packages/qwik-city/middleware/request-handler/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
```ts

import type { Action } from '@builder.io/qwik-city';
import type { FailReturn } from '@builder.io/qwik-city';
import type { Loader } from '@builder.io/qwik-city';
import type { QwikCityPlan } from '@builder.io/qwik-city';
import type { Render } from '@builder.io/qwik/server';
import type { RenderOptions } from '@builder.io/qwik/server';
import type { RequestEvent as RequestEvent_2 } from '@builder.io/qwik-city';
import type { ServerAction } from '@builder.io/qwik-city';
import type { ServerLoader } from '@builder.io/qwik-city';

// Warning: (ae-forgotten-export) The symbol "CacheControlOptions" needs to be exported by the entry point index.d.ts
//
Expand Down Expand Up @@ -51,9 +51,9 @@ export interface CookieValue {
// @alpha (undocumented)
export interface GetData {
// (undocumented)
<T>(loader: ServerLoader<T>): Promise<T>;
<T>(loader: Loader<T>): Promise<T>;
// (undocumented)
<T>(loader: ServerAction<T>): Promise<T | undefined>;
<T>(loader: Action<T>): Promise<T | undefined>;
}

// @alpha (undocumented)
Expand All @@ -62,9 +62,9 @@ export function getErrorHtml(status: number, e: any): string;
// @alpha (undocumented)
export interface GetSyncData {
// (undocumented)
<T>(loader: ServerLoader<T>): T;
<T>(loader: Loader<T>): T;
// (undocumented)
<T>(loader: ServerAction<T>): T | undefined;
<T>(loader: Action<T>): T | undefined;
}

// @alpha (undocumented)
Expand Down
15 changes: 4 additions & 11 deletions packages/qwik-city/middleware/request-handler/request-event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,7 @@ import type {
RequestHandler,
RequestEventCommon,
} from './types';
import type {
ServerAction,
ServerActionInternal,
ServerLoader,
ServerLoaderInternal,
} from '../../runtime/src/types';
import type { Action, ActionInternal, Loader, LoaderInternal } from '../../runtime/src/types';
import { Cookie } from './cookie';
import { ErrorResponse } from './error-handler';
import { AbortMessage, RedirectMessage } from './redirect-handler';
Expand Down Expand Up @@ -131,13 +126,11 @@ export function createRequestEvent(
headers.set('Cache-Control', createCacheControl(cacheControl));
},

getData: (loaderOrAction: ServerAction<any> | ServerLoader<any>) => {
getData: (loaderOrAction: Action<any> | Loader<any>) => {
// create user request event, which is a narrowed down request context
const id = (loaderOrAction as ServerLoaderInternal | ServerActionInternal).__qrl.getHash();
const id = (loaderOrAction as LoaderInternal | ActionInternal).__qrl.getHash();

if (
(loaderOrAction as ServerLoaderInternal | ServerActionInternal).__brand === 'server_loader'
) {
if ((loaderOrAction as LoaderInternal | ActionInternal).__brand === 'server_loader') {
if (id in loaders) {
throw new Error('Loader data does not exist');
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import type {
LoadedRoute,
PageModule,
RouteModule,
ServerActionInternal,
ServerLoaderInternal,
ActionInternal,
LoaderInternal,
} from '../../runtime/src/types';

import type { RequestEvent, RequestHandler } from './types';
Expand All @@ -30,7 +30,7 @@ export const resolveRequestHandlers = (
method: string,
renderHandler: RequestHandler
) => {
const serverLoaders: ServerLoaderInternal[] = [];
const serverLoaders: LoaderInternal[] = [];
const requestHandlers: RequestHandler[] = [];
const isPageRoute = !!(route && isLastModulePageRoute(route[1]));
if (serverPlugins) {
Expand All @@ -57,7 +57,7 @@ export const resolveRequestHandlers = (
};

const _resolveRequestHandlers = (
serverLoaders: ServerLoaderInternal[],
serverLoaders: LoaderInternal[],
requestHandlers: RequestHandler[],
routeModules: RouteModule[],
collectActions: boolean,
Expand Down Expand Up @@ -122,7 +122,7 @@ export const checkBrand = (obj: any, brand: string) => {
return obj && typeof obj === 'object' && obj.__brand === brand;
};

export function actionsMiddleware(serverLoaders: ServerLoaderInternal[]) {
export function actionsMiddleware(serverLoaders: LoaderInternal[]) {
return async (requestEv: RequestEventInternal) => {
if (requestEv.headersSent) {
requestEv.exit();
Expand All @@ -133,10 +133,7 @@ export function actionsMiddleware(serverLoaders: ServerLoaderInternal[]) {

if (method === 'POST') {
const selectedAction = requestEv.query.get(QACTION_KEY);
const serverActionsMap = (globalThis as any)._qwikActionsMap as Map<
string,
ServerActionInternal
>;
const serverActionsMap = (globalThis as any)._qwikActionsMap as Map<string, ActionInternal>;
if (selectedAction && serverActionsMap) {
const action = serverActionsMap.get(selectedAction);
if (action) {
Expand Down
10 changes: 5 additions & 5 deletions packages/qwik-city/middleware/request-handler/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Render, RenderOptions } from '@builder.io/qwik/server';
import type { QwikCityPlan, FailReturn, ServerAction, ServerLoader } from '@builder.io/qwik-city';
import type { QwikCityPlan, FailReturn, Action, Loader } from '@builder.io/qwik-city';
import type { ErrorResponse } from './error-handler';
import type { AbortMessage, RedirectMessage } from './redirect-handler';
import type { RequestEventInternal } from './request-event';
Expand Down Expand Up @@ -287,16 +287,16 @@ export interface RequestEventLoader<PLATFORM = unknown> extends RequestEventComm
* @alpha
*/
export interface GetData {
<T>(loader: ServerLoader<T>): Promise<T>;
<T>(loader: ServerAction<T>): Promise<T | undefined>;
<T>(loader: Loader<T>): Promise<T>;
<T>(loader: Action<T>): Promise<T | undefined>;
}

/**
* @alpha
*/
export interface GetSyncData {
<T>(loader: ServerLoader<T>): T;
<T>(loader: ServerAction<T>): T | undefined;
<T>(loader: Loader<T>): T;
<T>(loader: Action<T>): T | undefined;
}

/**
Expand Down
100 changes: 40 additions & 60 deletions packages/qwik-city/runtime/src/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,39 +4,46 @@
```ts

import type { Action as Action_2 } from '@builder.io/qwik-city';
import { Component } from '@builder.io/qwik';
import { Cookie } from '@builder.io/qwik-city/middleware/request-handler';
import { CookieOptions } from '@builder.io/qwik-city/middleware/request-handler';
import { CookieValue } from '@builder.io/qwik-city/middleware/request-handler';
import type { FailReturn as FailReturn_2 } from '@builder.io/qwik-city';
import type { GetSyncData } from '@builder.io/qwik-city/middleware/request-handler';
import { JSXNode } from '@builder.io/qwik';
import type { Loader as Loader_2 } from '@builder.io/qwik-city';
import { QRL } from '@builder.io/qwik';
import { QwikIntrinsicElements } from '@builder.io/qwik';
import { QwikJSX } from '@builder.io/qwik';
import { RequestEvent } from '@builder.io/qwik-city/middleware/request-handler';
import { RequestEventCommon } from '@builder.io/qwik-city/middleware/request-handler';
import { RequestEventLoader } from '@builder.io/qwik-city/middleware/request-handler';
import { RequestHandler } from '@builder.io/qwik-city/middleware/request-handler';
import type { ServerAction as ServerAction_2 } from '@builder.io/qwik-city';
import type { ServerLoader as ServerLoader_2 } from '@builder.io/qwik-city';
import type { Signal } from '@builder.io/qwik';
import { ValueOrPromise } from '@builder.io/qwik';
import { z } from 'zod';

// @alpha (undocumented)
export const action$: Action;
export const action$: ActionConstructor;

// @alpha (undocumented)
export interface Action {
// Warning: (ae-forgotten-export) The symbol "DefaultActionType" needs to be exported by the entry point index.d.ts
export interface Action<RETURN, INPUT = Record<string, any>> {
// (undocumented)
readonly [isServerLoader]?: true;
use(): ActionStore<RETURN, INPUT>;
}

// @alpha (undocumented)
export interface ActionConstructor {
// Warning: (ae-forgotten-export) The symbol "JSONObject" needs to be exported by the entry point index.d.ts
//
// (undocumented)
<O>(actionQrl: (form: DefaultActionType, event: RequestEventLoader) => ValueOrPromise<O>): ServerAction<O>;
<O>(actionQrl: (form: JSONObject, event: RequestEventLoader) => ValueOrPromise<O>): Action<O>;
// Warning: (ae-forgotten-export) The symbol "GetValidatorType" needs to be exported by the entry point index.d.ts
//
// (undocumented)
<O, B extends ZodReturn>(actionQrl: (data: GetValidatorType<B>, event: RequestEventLoader) => ValueOrPromise<O>, options: B): ServerAction<O | FailReturn<z.typeToFlattenedError<GetValidatorType<B>>>, GetValidatorType<B>>;
<O, B extends ZodReturn>(actionQrl: (data: GetValidatorType<B>, event: RequestEventLoader) => ValueOrPromise<O>, options: B): Action<O | FailReturn<z.typeToFlattenedError<GetValidatorType<B>>>, GetValidatorType<B>>;
}

// @alpha (undocumented)
Expand All @@ -45,7 +52,19 @@ export type ActionOptions = z.ZodRawShape;
// Warning: (ae-forgotten-export) The symbol "RequestEventLoader_2" needs to be exported by the entry point index.d.ts
//
// @alpha (undocumented)
export const actionQrl: <B, A>(actionQrl: QRL<(form: DefaultActionType, event: RequestEventLoader_2) => ValueOrPromise<B>>, options?: ZodReturn) => ServerAction<B, A>;
export const actionQrl: <B, A>(actionQrl: QRL<(form: JSONObject, event: RequestEventLoader_2) => ValueOrPromise<B>>, options?: ZodReturn) => Action<B, A>;

// @alpha (undocumented)
export interface ActionStore<RETURN, INPUT> {
readonly actionPath: string;
readonly fail: GetFailReturn<RETURN> | undefined;
readonly formData: FormData | undefined;
readonly isRunning: boolean;
readonly run: (form: INPUT | FormData | SubmitEvent) => Promise<RETURN>;
readonly status?: number;
// Warning: (ae-forgotten-export) The symbol "GetValueReturn" needs to be exported by the entry point index.d.ts
readonly value: GetValueReturn<RETURN> | undefined;
}

// @alpha @deprecated (undocumented)
export const Content: Component< {}>;
Expand Down Expand Up @@ -177,17 +196,11 @@ export const Form: <O, I>({ action, spaReset, reloadDocument, onSubmit$, ...rest

// @alpha (undocumented)
export interface FormProps<O, I> extends Omit<QwikJSX.IntrinsicElements['form'], 'action' | 'method'> {
// (undocumented)
action: ServerActionUse<O, I>;
// (undocumented)
action: ActionStore<O, I>;
onSubmit$?: (event: Event, form: HTMLFormElement) => ValueOrPromise<void>;
// (undocumented)
onSubmitFail$?: (event: CustomEvent<FormSubmitFailDetail<O>>, form: HTMLFormElement) => ValueOrPromise<void>;
// (undocumented)
onSubmitSuccess$?: (event: CustomEvent<FormSubmitSuccessDetail<O>>, form: HTMLFormElement) => ValueOrPromise<void>;
// (undocumented)
reloadDocument?: boolean;
// (undocumented)
spaReset?: boolean;
}

Expand All @@ -203,8 +216,6 @@ export interface FormSubmitFailDetail<T> {
export interface FormSubmitSuccessDetail<T> {
// (undocumented)
status: number;
// Warning: (ae-forgotten-export) The symbol "GetValueReturn" needs to be exported by the entry point index.d.ts
//
// (undocumented)
value: GetValueReturn<T>;
}
Expand All @@ -231,10 +242,20 @@ export interface LinkProps extends AnchorAttributes {
}

// @alpha (undocumented)
export const loader$: <RETURN, PLATFORM = unknown>(first: (event: RequestEventLoader_2<PLATFORM>) => RETURN) => ServerLoader<RETURN>;
export const loader$: <RETURN, PLATFORM = unknown>(first: (event: RequestEventLoader_2<PLATFORM>) => RETURN) => Loader<RETURN>;

// @alpha (undocumented)
export const loaderQrl: <RETURN, PLATFORM = unknown>(loaderQrl: QRL<(event: RequestEventLoader_2<PLATFORM>) => RETURN>) => ServerLoader<RETURN>;
export interface Loader<RETURN> {
// (undocumented)
readonly [isServerLoader]?: true;
use(): LoaderSignal<RETURN>;
}

// @alpha (undocumented)
export const loaderQrl: <RETURN, PLATFORM = unknown>(loaderQrl: QRL<(event: RequestEventLoader_2<PLATFORM>) => RETURN>) => Loader<RETURN>;

// @alpha (undocumented)
export type LoaderSignal<T> = Awaited<T> extends () => ValueOrPromise<infer B> ? Signal<ValueOrPromise<B>> : Signal<Awaited<T>>;

// Warning: (ae-forgotten-export) The symbol "MenuModuleLoader" needs to be exported by the entry point index.d.ts
//
Expand Down Expand Up @@ -332,47 +353,6 @@ export type RouteParams = Record<string, string>;
// @alpha (undocumented)
export const RouterOutlet: Component< {}>;

// @alpha (undocumented)
export interface ServerAction<RETURN, INPUT = Record<string, any>> {
// (undocumented)
readonly [isServerLoader]?: true;
// (undocumented)
use(): ServerActionUse<RETURN, INPUT>;
}

// @alpha (undocumented)
export interface ServerActionUse<RETURN, INPUT> {
// (undocumented)
readonly actionPath: string;
// (undocumented)
readonly fail: GetFailReturn<RETURN> | undefined;
// (undocumented)
readonly formData: FormData | undefined;
// (undocumented)
readonly id: string;
// (undocumented)
readonly isRunning: boolean;
// Warning: (ae-forgotten-export) The symbol "ServerActionExecute" needs to be exported by the entry point index.d.ts
//
// (undocumented)
readonly run: ServerActionExecute<RETURN, INPUT>;
// (undocumented)
readonly status?: number;
// (undocumented)
readonly value: GetValueReturn<RETURN> | undefined;
}

// @alpha (undocumented)
export interface ServerLoader<RETURN> {
// (undocumented)
readonly [isServerLoader]?: true;
// (undocumented)
use(): ServerLoaderUse<RETURN>;
}

// @alpha (undocumented)
export type ServerLoaderUse<T> = Awaited<T> extends () => ValueOrPromise<infer B> ? Signal<ValueOrPromise<B>> : Signal<Awaited<T>>;

// @alpha (undocumented)
export const ServiceWorkerRegister: () => JSXNode<"script">;

Expand Down
Loading

0 comments on commit 0db03ed

Please sign in to comment.