@torpor/build 0.4.13 → 1.0.0
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.md +20 -2
- package/dist/Jsonify-Dg6gChd3.d.mts +10 -0
- package/dist/Jsonify-Dg6gChd3.d.mts.map +1 -0
- package/dist/ParseRouteParams-DBmqlmLh.d.mts +42 -0
- package/dist/ParseRouteParams-DBmqlmLh.d.mts.map +1 -0
- package/dist/{RouteType-B6BMOyYw.mjs → Router-C4ADUfhv.mjs} +30 -42
- package/dist/Router-C4ADUfhv.mjs.map +1 -0
- package/dist/{Server-CF5wDJp6.d.mts → Server-C_IKav_e.d.mts} +44 -16
- package/dist/Server-C_IKav_e.d.mts.map +1 -0
- package/dist/{ServerEvent-CxPwA4lH.mjs → ServerEvent-Dljn5i7A.mjs} +37 -11
- package/dist/ServerEvent-Dljn5i7A.mjs.map +1 -0
- package/dist/Site-CMa153FA.d.mts +565 -0
- package/dist/Site-CMa153FA.d.mts.map +1 -0
- package/dist/StandardSchema-D28L8_zZ.d.mts +100 -0
- package/dist/StandardSchema-D28L8_zZ.d.mts.map +1 -0
- package/dist/TypedResponse-6lznqN4U.d.mts +22 -0
- package/dist/TypedResponse-6lznqN4U.d.mts.map +1 -0
- package/dist/{_page-BQs4WvCO.mjs → _page-4cQKs_Dn.mjs} +3 -5
- package/dist/_page-4cQKs_Dn.mjs.map +1 -0
- package/dist/bin/index.d.mts +1 -1
- package/dist/bin/index.js +15 -11
- package/dist/bin/index.js.map +1 -1
- package/dist/flattenHeaders-C_YYLdOq.mjs +114 -0
- package/dist/flattenHeaders-C_YYLdOq.mjs.map +1 -0
- package/dist/form.d.mts +35 -0
- package/dist/form.d.mts.map +1 -0
- package/dist/form.mjs +48 -0
- package/dist/form.mjs.map +1 -0
- package/dist/index.d.mts +72 -156
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +164 -32
- package/dist/index.mjs.map +1 -1
- package/dist/nav.d.mts +58 -5
- package/dist/nav.d.mts.map +1 -1
- package/dist/nav.mjs +116 -38
- package/dist/nav.mjs.map +1 -1
- package/dist/openapi.d.mts +144 -0
- package/dist/openapi.d.mts.map +1 -0
- package/dist/openapi.mjs +2 -0
- package/dist/pathTrie-D4Ax2Mu9.mjs +160 -0
- package/dist/pathTrie-D4Ax2Mu9.mjs.map +1 -0
- package/dist/plugin-D8Www11B.mjs +202 -0
- package/dist/plugin-D8Www11B.mjs.map +1 -0
- package/dist/{response-C5TtAsh1.mjs → response-Bg4w2S1q.mjs} +16 -39
- package/dist/response-Bg4w2S1q.mjs.map +1 -0
- package/dist/response.d.mts +309 -289
- package/dist/response.d.mts.map +1 -1
- package/dist/response.mjs +3 -4
- package/dist/run.d.mts +11 -4
- package/dist/run.d.mts.map +1 -1
- package/dist/run.mjs +2 -8
- package/dist/runOpenApi-BUi1RH1l.mjs +1381 -0
- package/dist/runOpenApi-BUi1RH1l.mjs.map +1 -0
- package/dist/schema.d.mts +32 -0
- package/dist/schema.d.mts.map +1 -0
- package/dist/schema.mjs +2 -0
- package/dist/server.d.mts +10 -8
- package/dist/server.d.mts.map +1 -1
- package/dist/server.mjs +132 -4
- package/dist/server.mjs.map +1 -0
- package/dist/state.d.mts.map +1 -1
- package/dist/state.mjs +2 -3
- package/dist/test.d.mts +8 -10
- package/dist/test.d.mts.map +1 -1
- package/dist/test.mjs +286 -82
- package/dist/test.mjs.map +1 -1
- package/dist/{seeOther-B4Yhu9iq.mjs → unprocessable-DAZbrDeR.mjs} +34 -13
- package/dist/unprocessable-DAZbrDeR.mjs.map +1 -0
- package/dist/validate-Ok4krlkX.mjs +40 -0
- package/dist/validate-Ok4krlkX.mjs.map +1 -0
- package/package.json +28 -17
- package/src/bin/index.ts +13 -2
- package/src/dev.ts +10 -0
- package/src/form/formDataToRecord.ts +23 -0
- package/src/form/readForm.ts +71 -0
- package/src/form.ts +4 -0
- package/src/index.ts +53 -1
- package/src/nav/api.test-d.ts +79 -0
- package/src/nav/api.ts +104 -0
- package/src/nav/formSubmit.ts +18 -4
- package/src/nav/navigate.ts +40 -15
- package/src/nav/route.ts +38 -0
- package/src/nav.ts +3 -1
- package/src/openapi/docsHtml.ts +25 -0
- package/src/openapi/document.ts +154 -0
- package/src/openapi/plugin.ts +78 -0
- package/src/openapi/types.ts +82 -0
- package/src/openapi.ts +14 -0
- package/src/response/TypedResponse.ts +17 -0
- package/src/response/badRequest.ts +14 -2
- package/src/response/created.ts +13 -2
- package/src/response/found.ts +2 -2
- package/src/response/movedPermanently.ts +2 -2
- package/src/response/notModified.ts +2 -2
- package/src/response/ok.ts +13 -4
- package/src/response/response.ts +4 -4
- package/src/response/unprocessable.ts +14 -2
- package/src/run/depCache.ts +175 -0
- package/src/run/devPlugin.ts +128 -0
- package/src/run/prepareTemplate.ts +6 -3
- package/src/run/run.ts +63 -39
- package/src/run/runBuild.ts +63 -12
- package/src/run/runDev.ts +146 -58
- package/src/run/runOpenApi.ts +52 -0
- package/src/run/runPreview.ts +17 -26
- package/src/run/staleTorpCopies.ts +100 -0
- package/src/run.ts +2 -1
- package/src/schema.ts +7 -0
- package/src/server/CookieHelper.ts +17 -7
- package/src/server/Server.ts +54 -30
- package/src/server/ServerEvent.ts +24 -1
- package/src/server/connect/connectMiddleware.ts +44 -40
- package/src/server/connect/flattenHeaders.ts +1 -23
- package/src/server/connect/requestToNodeMessage.ts +6 -3
- package/src/server/types/MiddlewareFunction.ts +18 -4
- package/src/site/Router.ts +52 -41
- package/src/site/Site.ts +184 -10
- package/src/site/checkLayoutSlots.ts +114 -0
- package/src/site/checkRoutes.ts +435 -0
- package/src/site/clientEntry.ts +22 -9
- package/src/site/layoutSlots.ts +53 -0
- package/src/site/manifest.ts +125 -6
- package/src/site/serverEntry.ts +352 -133
- package/src/state/$page.ts +2 -1
- package/src/state/$serverPage.ts +22 -0
- package/src/test/runTest.ts +306 -118
- package/src/types/Adapter.ts +12 -0
- package/src/types/Jsonify.ts +20 -0
- package/src/types/PageData.test-d.ts +136 -0
- package/src/types/PageData.ts +35 -0
- package/src/types/PageEndPoint.ts +25 -9
- package/src/types/PageForm.test-d.ts +78 -0
- package/src/types/PageForm.ts +29 -0
- package/src/types/PageLoadEvent.ts +16 -4
- package/src/types/PageLoadReturn.ts +13 -0
- package/src/types/PageProps.ts +14 -0
- package/src/types/PageServerAction.ts +8 -2
- package/src/types/PageServerEndPoint.test-d.ts +105 -0
- package/src/types/PageServerEndPoint.ts +99 -5
- package/src/types/PageServerLoad.ts +11 -3
- package/src/types/ParseRouteParams.test-d.ts +84 -0
- package/src/types/ParseRouteParams.ts +64 -0
- package/src/types/Route.ts +20 -1
- package/src/types/RouteHandler.ts +8 -1
- package/src/types/ServerEndPoint.test-d.ts +77 -0
- package/src/types/ServerEndPoint.ts +148 -16
- package/src/types/ServerHook.ts +10 -3
- package/src/types/ServerLoadEvent.ts +48 -3
- package/src/types/ServerRequest.ts +10 -2
- package/src/types/SitePlugin.ts +24 -0
- package/src/types/StandardSchema.ts +109 -0
- package/src/utils/pathToRegex.ts +4 -2
- package/src/utils/pathTrie.ts +182 -0
- package/src/utils/searchParamsToRecord.ts +18 -0
- package/src/utils/torporPackages.ts +150 -0
- package/src/utils/tsconfigAliases.ts +90 -0
- package/src/validation/ValidationError.ts +18 -0
- package/src/validation/endpoint.ts +83 -0
- package/src/validation/validate.ts +26 -0
- package/dist/RouteType-B6BMOyYw.mjs.map +0 -1
- package/dist/Server-CF5wDJp6.d.mts.map +0 -1
- package/dist/ServerEvent-CxPwA4lH.mjs.map +0 -1
- package/dist/Site-DgC6WWn1.d.mts +0 -78
- package/dist/Site-DgC6WWn1.d.mts.map +0 -1
- package/dist/_page-BQs4WvCO.mjs.map +0 -1
- package/dist/connectMiddleware-D7nehjj3.mjs +0 -252
- package/dist/connectMiddleware-D7nehjj3.mjs.map +0 -1
- package/dist/pathToRegex-TUAMc3zS.mjs +0 -11
- package/dist/pathToRegex-TUAMc3zS.mjs.map +0 -1
- package/dist/response-C5TtAsh1.mjs.map +0 -1
- package/dist/run-DiZgkMnG.mjs +0 -332
- package/dist/run-DiZgkMnG.mjs.map +0 -1
- package/dist/seeOther-B4Yhu9iq.mjs.map +0 -1
- package/src/server/Routerx.ts +0 -72
- package/src/server/connect/bufferToArrayBuffer.ts +0 -8
- package/src/server/connect/readableToBuffer.ts +0 -16
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type PageEndPoint from "./PageEndPoint";
|
|
2
|
+
import type PageLoadEvent from "./PageLoadEvent";
|
|
3
|
+
import type PageServerEndPoint from "./PageServerEndPoint";
|
|
4
|
+
import type { ParseRouteParams, RouteArgs, RouteParamsOf } from "./ParseRouteParams";
|
|
5
|
+
import type ServerLoadEvent from "./ServerLoadEvent";
|
|
6
|
+
import route from "../nav/route";
|
|
7
|
+
|
|
8
|
+
type Equals<A, B> =
|
|
9
|
+
(<T>() => T extends A ? 1 : 2) extends <T>() => T extends B ? 1 : 2 ? true : false;
|
|
10
|
+
type Mutual<A, B> = A extends B ? (B extends A ? true : false) : false;
|
|
11
|
+
type Expect<T extends true> = T;
|
|
12
|
+
type IsNever<T> = [T] extends [never] ? true : false;
|
|
13
|
+
|
|
14
|
+
// --- ParseRouteParams ---
|
|
15
|
+
|
|
16
|
+
export type T01 = Expect<Equals<ParseRouteParams<"/about">, unknown>>;
|
|
17
|
+
export type T02 = Expect<Equals<ParseRouteParams<"/">, unknown>>;
|
|
18
|
+
export type T03 = Expect<Equals<ParseRouteParams<"/posts/[id]">, { id: string }>>;
|
|
19
|
+
export type T04 = Expect<
|
|
20
|
+
Mutual<ParseRouteParams<"/users/[userId]/posts/[postId]">, { userId: string; postId: string }>
|
|
21
|
+
>;
|
|
22
|
+
export type T05 = Expect<Equals<ParseRouteParams<"/files/[...path]">, { path: string }>>;
|
|
23
|
+
export type T06 = Expect<Equals<ParseRouteParams<"/api/posts/[id]/~server">, { id: string }>>;
|
|
24
|
+
export type T07 = Expect<IsNever<keyof ParseRouteParams<"/about">>>;
|
|
25
|
+
|
|
26
|
+
export const postParams: ParseRouteParams<"/posts/[id]"> = { id: "5" };
|
|
27
|
+
export const paramValue: string = postParams.id;
|
|
28
|
+
// @ts-expect-error 'nope' is not a param of /posts/[id]
|
|
29
|
+
export const badParam: string = postParams.nope;
|
|
30
|
+
// @ts-expect-error 'id' is required
|
|
31
|
+
export const missingParam: ParseRouteParams<"/posts/[id]"> = {};
|
|
32
|
+
|
|
33
|
+
// --- RouteParamsOf (loose defaults) ---
|
|
34
|
+
|
|
35
|
+
export type T08 = Expect<Equals<RouteParamsOf<undefined>, Record<string, string>>>;
|
|
36
|
+
export type T09 = Expect<Equals<RouteParamsOf<string>, Record<string, string>>>;
|
|
37
|
+
export type T10 = Expect<Equals<RouteParamsOf<"/posts/[id]">, { id: string }>>;
|
|
38
|
+
|
|
39
|
+
// --- RouteArgs ---
|
|
40
|
+
|
|
41
|
+
export type T11 = Expect<Equals<RouteArgs<"/about">, []>>;
|
|
42
|
+
export type T12 = Expect<Equals<RouteArgs<"/posts/[id]">, [params: { id: string }]>>;
|
|
43
|
+
|
|
44
|
+
// --- Typed events ---
|
|
45
|
+
|
|
46
|
+
export const serverEvent: ServerLoadEvent<"/posts/[id]"> = null as never;
|
|
47
|
+
export const serverParam: string = serverEvent.params.id;
|
|
48
|
+
// @ts-expect-error 'nope' is not a param of /posts/[id]
|
|
49
|
+
export const badServerParam: string = serverEvent.params.nope;
|
|
50
|
+
|
|
51
|
+
export const pageEvent: PageLoadEvent<"/users/[userId]"> = null as never;
|
|
52
|
+
export const pageParam: string = pageEvent.params.userId;
|
|
53
|
+
|
|
54
|
+
// --- Usage sites ---
|
|
55
|
+
|
|
56
|
+
export const pageServer: PageServerEndPoint<"/posts/[id]"> = {
|
|
57
|
+
load: (ev) => void ev.params.id,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// @ts-expect-error 'nope' is not a param of /posts/[id]
|
|
61
|
+
export const badServer: PageServerEndPoint<"/posts/[id]"> = { load: (ev) => void ev.params.nope };
|
|
62
|
+
|
|
63
|
+
export const page: PageEndPoint<"/posts/[id]"> = {
|
|
64
|
+
load: (ev) => void ev.params.id,
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// --- Typed link builder ---
|
|
68
|
+
|
|
69
|
+
export const staticRoute: string = route("/about");
|
|
70
|
+
export const dynamicRoute: string = route("/posts/[id]", { id: "5" });
|
|
71
|
+
export const splatRoute: string = route("/files/[...path]", { path: "a/b" });
|
|
72
|
+
// @ts-expect-error missing params
|
|
73
|
+
void route("/posts/[id]");
|
|
74
|
+
// @ts-expect-error params are not accepted for static routes
|
|
75
|
+
void route("/about", {});
|
|
76
|
+
// @ts-expect-error 'nope' is not a param of /posts/[id]
|
|
77
|
+
void route("/posts/[id]", { nope: "1" });
|
|
78
|
+
// @ts-expect-error 'extra' is not a param of /posts/[id]
|
|
79
|
+
void route("/posts/[id]", { id: "5", extra: "x" });
|
|
80
|
+
// @ts-expect-error 'id' is required
|
|
81
|
+
void route("/posts/[id]", {});
|
|
82
|
+
|
|
83
|
+
// Non-literal paths fall back to a loose optional params object
|
|
84
|
+
export const looseRoute: string = route("/posts/[id]" as string, { id: "5" });
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extracts the params object from a route path string.
|
|
3
|
+
*
|
|
4
|
+
* Dynamic segments (`[id]`) and splat segments (`[...path]`) become string
|
|
5
|
+
* properties; static segments are ignored. For example:
|
|
6
|
+
*
|
|
7
|
+
* ```ts
|
|
8
|
+
* type Params = ParseRouteParams<"/posts/[id]/comments/[commentId]">;
|
|
9
|
+
* // { id: string; commentId: string }
|
|
10
|
+
* ```
|
|
11
|
+
*/
|
|
12
|
+
export type ParseRouteParams<Route extends string> = ParseSegments<Route, unknown>;
|
|
13
|
+
|
|
14
|
+
type ParseSegments<Route extends string, Params> = Route extends `${infer Head}/${infer Rest}`
|
|
15
|
+
? ParseSegments<Rest, ParseSegment<Head, Params>>
|
|
16
|
+
: ParseSegment<Route, Params>;
|
|
17
|
+
|
|
18
|
+
type ParseSegment<Segment extends string, Params> = Segment extends `[...${infer Name}]`
|
|
19
|
+
? Params & { [Key in Name]: string }
|
|
20
|
+
: Segment extends `[${infer Name}]`
|
|
21
|
+
? Params & { [Key in Name]: string }
|
|
22
|
+
: Params;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Resolves the params for a route annotation: the parsed params for a literal
|
|
26
|
+
* route path, or a loose record when no route (or a non-literal string) is
|
|
27
|
+
* given.
|
|
28
|
+
*/
|
|
29
|
+
export type RouteParamsOf<Route extends string | undefined> = string extends Route
|
|
30
|
+
? Record<string, string>
|
|
31
|
+
: Route extends string
|
|
32
|
+
? ParseRouteParams<Route>
|
|
33
|
+
: Record<string, string>;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Validates an inferred params object against a route's params shape:
|
|
37
|
+
* resolves to the params type when its keys are exactly the route's keys, and
|
|
38
|
+
* `never` when keys are missing or extra — so wrong keys error at the call
|
|
39
|
+
* site (excess property checks are skipped for deferred conditional rest
|
|
40
|
+
* tuples, but assignability to `never` can't be).
|
|
41
|
+
*/
|
|
42
|
+
export type ExactRouteParams<Params, Shape> = Params extends Shape
|
|
43
|
+
? [Exclude<keyof Params, keyof Shape>] extends [never]
|
|
44
|
+
? Params
|
|
45
|
+
: never
|
|
46
|
+
: never;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The arguments for building a route path: nothing for static routes, or the
|
|
50
|
+
* route's params object for dynamic routes.
|
|
51
|
+
*/
|
|
52
|
+
export type RouteArgs<Route extends string> = string extends Route
|
|
53
|
+
? [params?: Record<string, string>]
|
|
54
|
+
: keyof ParseRouteParams<Route> extends never
|
|
55
|
+
? []
|
|
56
|
+
: [params: ParseRouteParams<Route>];
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* RouteArgs for an optional route annotation, defaulting to a single optional
|
|
60
|
+
* loose params object.
|
|
61
|
+
*/
|
|
62
|
+
export type RouteArgsOf<Route extends string | undefined> = Route extends string
|
|
63
|
+
? RouteArgs<Route>
|
|
64
|
+
: [params?: Record<string, string>];
|
package/src/types/Route.ts
CHANGED
|
@@ -1,11 +1,30 @@
|
|
|
1
|
+
import type PageEndPoint from "./PageEndPoint";
|
|
2
|
+
import type PageServerEndPoint from "./PageServerEndPoint";
|
|
3
|
+
import type ServerEndPoint from "./ServerEndPoint";
|
|
4
|
+
import type ServerHook from "./ServerHook";
|
|
1
5
|
import { type RouteType } from "./RouteType";
|
|
2
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Any endpoint shape that can be stored inline on a Route.
|
|
9
|
+
*/
|
|
10
|
+
export type InlineEndPoint = PageServerEndPoint | ServerEndPoint | ServerHook | PageEndPoint;
|
|
11
|
+
|
|
3
12
|
/**
|
|
4
13
|
* A route that is added to the Site.
|
|
5
14
|
*/
|
|
6
15
|
export default interface Route {
|
|
7
16
|
path: string;
|
|
8
|
-
|
|
17
|
+
/**
|
|
18
|
+
* The file for this route, relative to the site root. Omitted for inline
|
|
19
|
+
* endpoints (see `endPoint`).
|
|
20
|
+
*/
|
|
21
|
+
file?: string;
|
|
22
|
+
/**
|
|
23
|
+
* For inline routes (defined in code rather than by file), the endpoint
|
|
24
|
+
* object itself. The shape depends on the route type
|
|
25
|
+
* (`PageServerEndPoint`, `ServerEndPoint`, `ServerHook`, etc.).
|
|
26
|
+
*/
|
|
27
|
+
endPoint?: InlineEndPoint;
|
|
9
28
|
type: RouteType;
|
|
10
29
|
subFolder?: string;
|
|
11
30
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type RouteLayoutHandler from "./RouteLayoutHandler";
|
|
2
|
+
import type ServerHook from "./ServerHook";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* A handler for a route in the Router.
|
|
@@ -12,5 +13,11 @@ export default interface RouteHandler {
|
|
|
12
13
|
loaded?: boolean;
|
|
13
14
|
layouts?: RouteLayoutHandler[];
|
|
14
15
|
serverEndPoint?: () => Promise<any>;
|
|
15
|
-
|
|
16
|
+
serverHooks?: (() => Promise<any>)[];
|
|
17
|
+
|
|
18
|
+
// The endpoint module and the resolved server hooks are cached on the
|
|
19
|
+
// handler after first use, so warm requests don't pay for the loaders
|
|
20
|
+
modulePromise?: Promise<any>;
|
|
21
|
+
resolvedModule?: any;
|
|
22
|
+
resolvedHooks?: Promise<ServerHook[]>;
|
|
16
23
|
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import ok from "../response/ok";
|
|
2
|
+
import type { ParamsOf, SchemaBody, SchemaQuery } from "./ServerEndPoint";
|
|
3
|
+
import type ServerEndPoint from "./ServerEndPoint";
|
|
4
|
+
import type { StandardSchemaV1 } from "./StandardSchema";
|
|
5
|
+
|
|
6
|
+
type Equals<A, B> =
|
|
7
|
+
(<T>() => T extends A ? 1 : 2) extends <T>() => T extends B ? 1 : 2 ? true : false;
|
|
8
|
+
type Expect<T extends true> = T;
|
|
9
|
+
|
|
10
|
+
// A mock schema library's schema, with input and output types
|
|
11
|
+
declare const postSchema: StandardSchemaV1<{ title: string }, { title: string; upper: string }>;
|
|
12
|
+
declare const querySchema: StandardSchemaV1<{ page: string }, { page: number }>;
|
|
13
|
+
declare const paramsSchema: StandardSchemaV1<{ id: string }, { id: number }>;
|
|
14
|
+
|
|
15
|
+
const schemas: { post: typeof postSchema } = { post: postSchema };
|
|
16
|
+
|
|
17
|
+
// An endpoint declaring a schema for its post handler. The `.upper` access
|
|
18
|
+
// below compiles only when json() is typed from the schema's output
|
|
19
|
+
const endpoint: ServerEndPoint<"/api/posts", typeof schemas> = {
|
|
20
|
+
schema: schemas,
|
|
21
|
+
post: async ({ json }) => ok({ echoed: (await json()).upper }),
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// json() returns the schema's output type
|
|
25
|
+
type PostEvent = Parameters<NonNullable<typeof endpoint.post>>[0];
|
|
26
|
+
export type R01 = Expect<
|
|
27
|
+
Equals<Awaited<ReturnType<PostEvent["json"]>>, { title: string; upper: string }>
|
|
28
|
+
>;
|
|
29
|
+
|
|
30
|
+
// The schema's input type is available via InferInput
|
|
31
|
+
export type R02 = Expect<Equals<StandardSchemaV1.InferInput<typeof postSchema>, { title: string }>>;
|
|
32
|
+
|
|
33
|
+
// Client callers made with makeApi accept the schema's output as their body
|
|
34
|
+
type PostEndPoint = ServerEndPoint<"/api/posts", typeof schemas>;
|
|
35
|
+
export type R03 = Expect<
|
|
36
|
+
Equals<Parameters<Api<PostEndPoint>["post"]>[0], { title: string; upper: string } | undefined>
|
|
37
|
+
>;
|
|
38
|
+
type Api<E extends object> = import("../nav/api").ApiMethods<E>;
|
|
39
|
+
|
|
40
|
+
// The type helpers behind the handler signatures
|
|
41
|
+
export type R04 = Expect<
|
|
42
|
+
Equals<SchemaBody<typeof schemas, "post">, { title: string; upper: string }>
|
|
43
|
+
>;
|
|
44
|
+
export type R05 = Expect<
|
|
45
|
+
Equals<SchemaQuery<typeof schemas, "get">, Record<string, string | string[]>>
|
|
46
|
+
>;
|
|
47
|
+
|
|
48
|
+
// A get handler's schema types the query string instead of the body
|
|
49
|
+
const getEndpoint: ServerEndPoint<"/api/posts", { get: typeof querySchema }> = {
|
|
50
|
+
schema: { get: querySchema },
|
|
51
|
+
get: async ({ query }) => ok({ page: (await query()).page }),
|
|
52
|
+
};
|
|
53
|
+
type GetEvent = Parameters<NonNullable<typeof getEndpoint.get>>[0];
|
|
54
|
+
export type R06 = Expect<Equals<Awaited<ReturnType<GetEvent["query"]>>, { page: number }>>;
|
|
55
|
+
export type R07 = Expect<Equals<Awaited<ReturnType<GetEvent["json"]>>, unknown>>;
|
|
56
|
+
|
|
57
|
+
// A params schema types (and coerces) the event's params
|
|
58
|
+
const paramsEndpoint: ServerEndPoint<"/api/posts/[id]", { params: typeof paramsSchema }> = {
|
|
59
|
+
schema: { params: paramsSchema },
|
|
60
|
+
get: async ({ params }) => ok({ id: params.id }),
|
|
61
|
+
};
|
|
62
|
+
type ParamsEvent = Parameters<NonNullable<typeof paramsEndpoint.get>>[0];
|
|
63
|
+
export type R08 = Expect<Equals<ParamsEvent["params"], { id: number }>>;
|
|
64
|
+
|
|
65
|
+
// Endpoints without schemas keep the loose types
|
|
66
|
+
const plainEndpoint: ServerEndPoint<"/api/posts"> = {
|
|
67
|
+
post: async ({ json }) => ok({ v: await json() }),
|
|
68
|
+
};
|
|
69
|
+
type PlainEvent = Parameters<NonNullable<typeof plainEndpoint.post>>[0];
|
|
70
|
+
export type R09 = Expect<Equals<Awaited<ReturnType<PlainEvent["json"]>>, unknown>>;
|
|
71
|
+
|
|
72
|
+
// The params of a route without a params schema are string record of the
|
|
73
|
+
// route's dynamic segments
|
|
74
|
+
export type R10 = Expect<
|
|
75
|
+
Equals<ParamsOf<ServerEndPointSchemas, "/api/posts/[id]">, { id: string }>
|
|
76
|
+
>;
|
|
77
|
+
type ServerEndPointSchemas = import("./ServerEndPoint").ServerEndPointSchemas;
|
|
@@ -1,37 +1,169 @@
|
|
|
1
|
+
import type { RouteParamsOf } from "./ParseRouteParams";
|
|
2
|
+
import type { FormDataRecord, QueryRecord } from "./ServerLoadEvent";
|
|
3
|
+
import type { StandardSchemaV1 } from "./StandardSchema";
|
|
1
4
|
import type ServerRequest from "./ServerRequest";
|
|
2
5
|
|
|
3
6
|
/**
|
|
4
|
-
*
|
|
7
|
+
* Optional standard schemas for +server endpoints, keyed by handler name.
|
|
8
|
+
* Any schema implementing the Standard Schema interface (zod, valibot,
|
|
9
|
+
* arktype, etc) can be used.
|
|
10
|
+
*
|
|
11
|
+
* - `get`/`head` schemas validate the URL's query string, surfaced through
|
|
12
|
+
* `event.query()`
|
|
13
|
+
* - `post`/`patch`/`put`/`del`/`options` schemas validate the JSON request
|
|
14
|
+
* body, surfaced through `event.json()`
|
|
15
|
+
* - a `params` schema validates (and generally coerces) the route params,
|
|
16
|
+
* surfaced through `event.params`
|
|
17
|
+
*
|
|
18
|
+
* When a request body or query string fails validation, the handler is not
|
|
19
|
+
* called and a 422 response with the schema's issues is returned. When route
|
|
20
|
+
* params fail validation, a 404 response is returned, since the URL can't
|
|
21
|
+
* refer to an existing resource.
|
|
5
22
|
*/
|
|
6
|
-
type
|
|
23
|
+
export type ServerEndPointSchemas = {
|
|
24
|
+
params?: StandardSchemaV1;
|
|
25
|
+
get?: StandardSchemaV1;
|
|
26
|
+
post?: StandardSchemaV1;
|
|
27
|
+
patch?: StandardSchemaV1;
|
|
28
|
+
put?: StandardSchemaV1;
|
|
29
|
+
del?: StandardSchemaV1;
|
|
30
|
+
options?: StandardSchemaV1;
|
|
31
|
+
head?: StandardSchemaV1;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The json body type for a handler, inferred from its schema. Falls back to
|
|
36
|
+
* `unknown` for handlers without a declared schema.
|
|
37
|
+
*/
|
|
38
|
+
export type SchemaBody<Schemas extends ServerEndPointSchemas, Method extends string> = [
|
|
39
|
+
Method,
|
|
40
|
+
] extends [keyof Schemas & string]
|
|
41
|
+
? Schemas[Method] extends StandardSchemaV1
|
|
42
|
+
? StandardSchemaV1.InferOutput<Schemas[Method]>
|
|
43
|
+
: unknown
|
|
44
|
+
: unknown;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The query type for a handler, inferred from its schema. Falls back to a
|
|
48
|
+
* loose record for handlers without a declared schema.
|
|
49
|
+
*/
|
|
50
|
+
export type SchemaQuery<Schemas extends ServerEndPointSchemas, Method extends string> = [
|
|
51
|
+
Method,
|
|
52
|
+
] extends [keyof Schemas & string]
|
|
53
|
+
? Schemas[Method] extends StandardSchemaV1
|
|
54
|
+
? StandardSchemaV1.InferOutput<Schemas[Method]>
|
|
55
|
+
: QueryRecord
|
|
56
|
+
: QueryRecord;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* The route params type for an endpoint, inferred from its `params` schema.
|
|
60
|
+
* Falls back to the route's string params when no schema is declared.
|
|
61
|
+
*/
|
|
62
|
+
export type ParamsOf<
|
|
63
|
+
Schemas extends { params?: StandardSchemaV1 },
|
|
64
|
+
Route extends string | undefined,
|
|
65
|
+
> = Schemas extends { params: StandardSchemaV1 }
|
|
66
|
+
? StandardSchemaV1.InferOutput<Schemas["params"]>
|
|
67
|
+
: RouteParamsOf<Route>;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* For +server. Annotate with a route path to get typed params, e.g.
|
|
71
|
+
* `ServerEndPoint<"/api/posts/[id]">`, and with a schemas object to get
|
|
72
|
+
* typed (and validated) request bodies, query strings and params, e.g.
|
|
73
|
+
* `ServerEndPoint<"/api/posts", typeof schema>`.
|
|
74
|
+
*/
|
|
75
|
+
type ServerEndPoint<
|
|
76
|
+
Route extends string | undefined = undefined,
|
|
77
|
+
Schemas extends ServerEndPointSchemas = ServerEndPointSchemas,
|
|
78
|
+
> = {
|
|
79
|
+
/**
|
|
80
|
+
* Optional standard schemas for validating requests, keyed by handler
|
|
81
|
+
* name (plus a `params` schema for the route params), e.g.
|
|
82
|
+
*
|
|
83
|
+
* ```ts
|
|
84
|
+
* const schema = {
|
|
85
|
+
* params: z.object({ id: z.coerce.number() }),
|
|
86
|
+
* get: z.object({ sort: z.enum(["asc", "desc"]) }),
|
|
87
|
+
* post: z.object({ title: z.string() }),
|
|
88
|
+
* };
|
|
89
|
+
* export default {
|
|
90
|
+
* schema,
|
|
91
|
+
* get: async (event) => ok(await event.query()),
|
|
92
|
+
* post: async (event) => ok((await event.json()).title),
|
|
93
|
+
* } satisfies ServerEndPoint<"/api/posts/[id]", typeof schema>;
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
96
|
+
schema?: Schemas | undefined;
|
|
7
97
|
/**
|
|
8
|
-
* Performs a GET.
|
|
98
|
+
* Performs a GET. Its schema validates the query string.
|
|
9
99
|
*/
|
|
10
|
-
get?: ServerRequest
|
|
100
|
+
get?: ServerRequest<
|
|
101
|
+
Route,
|
|
102
|
+
unknown,
|
|
103
|
+
FormDataRecord,
|
|
104
|
+
SchemaQuery<Schemas, "get">,
|
|
105
|
+
ParamsOf<Schemas, Route>
|
|
106
|
+
>;
|
|
11
107
|
/**
|
|
12
|
-
* Performs a POST.
|
|
108
|
+
* Performs a POST. Its schema validates the JSON request body.
|
|
13
109
|
*/
|
|
14
|
-
post?: ServerRequest
|
|
110
|
+
post?: ServerRequest<
|
|
111
|
+
Route,
|
|
112
|
+
SchemaBody<Schemas, "post">,
|
|
113
|
+
FormDataRecord,
|
|
114
|
+
QueryRecord,
|
|
115
|
+
ParamsOf<Schemas, Route>
|
|
116
|
+
>;
|
|
15
117
|
/**
|
|
16
|
-
* Performs a PATCH.
|
|
118
|
+
* Performs a PATCH. Its schema validates the JSON request body.
|
|
17
119
|
*/
|
|
18
|
-
patch?: ServerRequest
|
|
120
|
+
patch?: ServerRequest<
|
|
121
|
+
Route,
|
|
122
|
+
SchemaBody<Schemas, "patch">,
|
|
123
|
+
FormDataRecord,
|
|
124
|
+
QueryRecord,
|
|
125
|
+
ParamsOf<Schemas, Route>
|
|
126
|
+
>;
|
|
19
127
|
/**
|
|
20
|
-
* Performs a PUT.
|
|
128
|
+
* Performs a PUT. Its schema validates the JSON request body.
|
|
21
129
|
*/
|
|
22
|
-
put?: ServerRequest
|
|
130
|
+
put?: ServerRequest<
|
|
131
|
+
Route,
|
|
132
|
+
SchemaBody<Schemas, "put">,
|
|
133
|
+
FormDataRecord,
|
|
134
|
+
QueryRecord,
|
|
135
|
+
ParamsOf<Schemas, Route>
|
|
136
|
+
>;
|
|
23
137
|
/**
|
|
24
|
-
* Performs a DELETE.
|
|
138
|
+
* Performs a DELETE. Its schema validates the JSON request body.
|
|
25
139
|
*/
|
|
26
|
-
del?: ServerRequest
|
|
140
|
+
del?: ServerRequest<
|
|
141
|
+
Route,
|
|
142
|
+
SchemaBody<Schemas, "del">,
|
|
143
|
+
FormDataRecord,
|
|
144
|
+
QueryRecord,
|
|
145
|
+
ParamsOf<Schemas, Route>
|
|
146
|
+
>;
|
|
27
147
|
/**
|
|
28
|
-
* Performs an OPTIONS request.
|
|
148
|
+
* Performs an OPTIONS request. Its schema validates the JSON request body.
|
|
29
149
|
*/
|
|
30
|
-
options?: ServerRequest
|
|
150
|
+
options?: ServerRequest<
|
|
151
|
+
Route,
|
|
152
|
+
SchemaBody<Schemas, "options">,
|
|
153
|
+
FormDataRecord,
|
|
154
|
+
QueryRecord,
|
|
155
|
+
ParamsOf<Schemas, Route>
|
|
156
|
+
>;
|
|
31
157
|
/**
|
|
32
|
-
* Performs a HEAD request.
|
|
158
|
+
* Performs a HEAD request. Its schema validates the query string.
|
|
33
159
|
*/
|
|
34
|
-
head?: ServerRequest
|
|
160
|
+
head?: ServerRequest<
|
|
161
|
+
Route,
|
|
162
|
+
unknown,
|
|
163
|
+
FormDataRecord,
|
|
164
|
+
SchemaQuery<Schemas, "head">,
|
|
165
|
+
ParamsOf<Schemas, Route>
|
|
166
|
+
>;
|
|
35
167
|
};
|
|
36
168
|
|
|
37
169
|
export default ServerEndPoint;
|
package/src/types/ServerHook.ts
CHANGED
|
@@ -3,9 +3,16 @@ import type ServerLoadEvent from "./ServerLoadEvent";
|
|
|
3
3
|
/**
|
|
4
4
|
* For _hook.server.
|
|
5
5
|
*/
|
|
6
|
-
export default interface ServerHook {
|
|
6
|
+
export default interface ServerHook<Route extends string | undefined = undefined> {
|
|
7
7
|
/**
|
|
8
|
-
* Called
|
|
8
|
+
* Called before each server request is handled. Return a Response to
|
|
9
|
+
* short-circuit the request, skipping the load, action or view rendering
|
|
10
|
+
* (e.g. a redirect for unauthenticated users).
|
|
9
11
|
*/
|
|
10
|
-
|
|
12
|
+
enter?: (event: ServerLoadEvent<Route>) => Promise<Response | void> | Response | void;
|
|
13
|
+
/**
|
|
14
|
+
* Called after the request has been handled, even if the handler threw
|
|
15
|
+
* an error or the enter hook short-circuited it.
|
|
16
|
+
*/
|
|
17
|
+
exit?: (event: ServerLoadEvent<Route>) => Promise<void> | void;
|
|
11
18
|
}
|
|
@@ -1,15 +1,43 @@
|
|
|
1
1
|
import CookieHelper from "../server/CookieHelper";
|
|
2
2
|
import HeaderHelper from "../server/HeaderHelper";
|
|
3
|
+
import type { RouteParamsOf } from "./ParseRouteParams";
|
|
3
4
|
|
|
4
|
-
|
|
5
|
+
/**
|
|
6
|
+
* The values read from a form submission: one value per field, or an array
|
|
7
|
+
* when a field was submitted with multiple values (e.g. a multi select).
|
|
8
|
+
*/
|
|
9
|
+
export type FormDataRecord = Record<string, FormDataEntryValue | FormDataEntryValue[]>;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The values read from a URL's query string: one string per param, or an
|
|
13
|
+
* array when a param was repeated.
|
|
14
|
+
*/
|
|
15
|
+
export type QueryRecord = Record<string, string | string[]>;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The event passed to server functions. Annotate with a route path to get
|
|
19
|
+
* typed params, e.g. `ServerLoadEvent<"/posts/[id]">`, and with a body type
|
|
20
|
+
* to get typed request bodies in API endpoints, e.g.
|
|
21
|
+
* `ServerLoadEvent<"/api/posts", { title: string }>` — which also types the
|
|
22
|
+
* `body` param of client calls made with `makeApi`.
|
|
23
|
+
*/
|
|
24
|
+
export default interface ServerLoadEvent<
|
|
25
|
+
Route extends string | undefined = undefined,
|
|
26
|
+
Body = unknown,
|
|
27
|
+
FormBody = FormDataRecord,
|
|
28
|
+
QueryBody = QueryRecord,
|
|
29
|
+
Params = RouteParamsOf<Route>,
|
|
30
|
+
> {
|
|
5
31
|
/**
|
|
6
32
|
* The URL for the server function.
|
|
7
33
|
*/
|
|
8
34
|
url: URL;
|
|
9
35
|
/**
|
|
10
|
-
* Route params from the URL and route path.
|
|
36
|
+
* Route params from the URL and route path. When the endpoint declares a
|
|
37
|
+
* `params` schema, the values are validated and typed by the schema's
|
|
38
|
+
* output.
|
|
11
39
|
*/
|
|
12
|
-
params:
|
|
40
|
+
params: Params;
|
|
13
41
|
// TODO: Maybe we should find a better name for the data that is set set in
|
|
14
42
|
// pages, and just call this data?
|
|
15
43
|
/**
|
|
@@ -22,6 +50,23 @@ export default interface ServerLoadEvent {
|
|
|
22
50
|
*/
|
|
23
51
|
request: Request;
|
|
24
52
|
//response: ServerResponse;
|
|
53
|
+
/**
|
|
54
|
+
* Reads the request body as JSON, typed by the event's `Body` annotation.
|
|
55
|
+
*/
|
|
56
|
+
json: () => Promise<Body>;
|
|
57
|
+
/**
|
|
58
|
+
* Reads the request body as form data, returning a record of field values.
|
|
59
|
+
* When the endpoint declares a schema for the form, the values are
|
|
60
|
+
* validated and typed by the schema's output.
|
|
61
|
+
*/
|
|
62
|
+
form: () => Promise<FormBody>;
|
|
63
|
+
/**
|
|
64
|
+
* Reads the URL's query string, returning a record of param values. When
|
|
65
|
+
* the endpoint declares a schema for the query (get/head handlers and the
|
|
66
|
+
* load function), the values are validated and typed by the schema's
|
|
67
|
+
* output.
|
|
68
|
+
*/
|
|
69
|
+
query: () => Promise<QueryBody>;
|
|
25
70
|
/**
|
|
26
71
|
* A helper for getting and setting cookie data.
|
|
27
72
|
*/
|
|
@@ -1,7 +1,15 @@
|
|
|
1
|
+
import type { RouteParamsOf } from "./ParseRouteParams";
|
|
2
|
+
import type { FormDataRecord, QueryRecord } from "./ServerLoadEvent";
|
|
1
3
|
import type ServerLoadEvent from "./ServerLoadEvent";
|
|
2
4
|
|
|
3
|
-
type ServerRequest
|
|
4
|
-
|
|
5
|
+
type ServerRequest<
|
|
6
|
+
Route extends string | undefined = undefined,
|
|
7
|
+
Body = unknown,
|
|
8
|
+
FormBody = FormDataRecord,
|
|
9
|
+
QueryBody = QueryRecord,
|
|
10
|
+
Params = RouteParamsOf<Route>,
|
|
11
|
+
> = (
|
|
12
|
+
event: ServerLoadEvent<Route, Body, FormBody, QueryBody, Params>,
|
|
5
13
|
) => Response | undefined | void | Promise<Response | undefined | void>;
|
|
6
14
|
|
|
7
15
|
export default ServerRequest;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type Site from "../site/Site";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A Torpor site plugin. A plugin is a function that receives the Site and
|
|
5
|
+
* sets itself up on it, typically by registering routes (e.g. an OpenAPI
|
|
6
|
+
* document endpoint) or adding configuration.
|
|
7
|
+
*
|
|
8
|
+
* Plugins are added to `site.plugins` in the site config file, and are run
|
|
9
|
+
* once when the config is loaded (for dev and build), and once when the
|
|
10
|
+
* server starts up in production:
|
|
11
|
+
*
|
|
12
|
+
* ```ts
|
|
13
|
+
* import { Site } from "@torpor/build";
|
|
14
|
+
* import { openApi } from "@torpor/build/openapi";
|
|
15
|
+
*
|
|
16
|
+
* const site = new Site();
|
|
17
|
+
* site.addRouteFolder("src/routes");
|
|
18
|
+
* site.plugins = [openApi()];
|
|
19
|
+
* export default site;
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
type SitePlugin = (site: Site) => void | Promise<void>;
|
|
23
|
+
|
|
24
|
+
export default SitePlugin;
|