@torpor/build 1.0.0 → 1.0.2

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.
Files changed (174) hide show
  1. package/dist/{flattenHeaders-C_YYLdOq.mjs → Server-BnjLJleZ.mjs} +2 -12
  2. package/dist/Server-BnjLJleZ.mjs.map +1 -0
  3. package/dist/{Server-C_IKav_e.d.mts → Server-Cler2w5O.d.mts} +3 -48
  4. package/dist/Server-Cler2w5O.d.mts.map +1 -0
  5. package/dist/ServerEvent-CTx3Q-hk.d.mts +48 -0
  6. package/dist/ServerEvent-CTx3Q-hk.d.mts.map +1 -0
  7. package/dist/{Site-CMa153FA.d.mts → Site-CE0_o5Ci.d.mts} +3 -2
  8. package/dist/Site-CE0_o5Ci.d.mts.map +1 -0
  9. package/dist/bin/index.js +1 -1
  10. package/dist/clientEntry.d.mts +1 -0
  11. package/dist/clientEntry.mjs +59 -0
  12. package/dist/clientEntry.mjs.map +1 -0
  13. package/dist/clientEntryDev.d.mts +1 -0
  14. package/dist/clientEntryDev.mjs +7 -0
  15. package/dist/clientEntryDev.mjs.map +1 -0
  16. package/dist/dev.d.mts +3 -0
  17. package/dist/dev.mjs +3 -0
  18. package/dist/endpoint-EULRGCKA.mjs +108 -0
  19. package/dist/endpoint-EULRGCKA.mjs.map +1 -0
  20. package/dist/flattenHeaders-DM5qsSqO.mjs +13 -0
  21. package/dist/flattenHeaders-DM5qsSqO.mjs.map +1 -0
  22. package/dist/index.d.mts +1 -1
  23. package/dist/load-BntzvmwL.mjs +228 -0
  24. package/dist/load-BntzvmwL.mjs.map +1 -0
  25. package/dist/nav.mjs +1 -225
  26. package/dist/nav.mjs.map +1 -1
  27. package/dist/openapi.d.mts +1 -1
  28. package/dist/run.d.mts +1 -1
  29. package/dist/run.d.mts.map +1 -1
  30. package/dist/run.mjs +1 -1
  31. package/dist/{runOpenApi-BUi1RH1l.mjs → runOpenApi-C8eI2Fh0.mjs} +54 -16
  32. package/dist/runOpenApi-C8eI2Fh0.mjs.map +1 -0
  33. package/dist/server/Server.d.mts +2 -0
  34. package/dist/server/Server.mjs +2 -0
  35. package/dist/server.d.mts +2 -1
  36. package/dist/server.d.mts.map +1 -1
  37. package/dist/server.mjs +2 -1
  38. package/dist/server.mjs.map +1 -1
  39. package/dist/serverEntry-1qY3wZig.mjs +351 -0
  40. package/dist/serverEntry-1qY3wZig.mjs.map +1 -0
  41. package/dist/serverEntry-BAO8m03V.d.mts +6 -0
  42. package/dist/serverEntry-BAO8m03V.d.mts.map +1 -0
  43. package/dist/serverEntry.d.mts +2 -0
  44. package/dist/serverEntry.mjs +2 -0
  45. package/dist/test.d.mts +2 -2
  46. package/dist/test.mjs +2 -104
  47. package/dist/test.mjs.map +1 -1
  48. package/package.json +9 -8
  49. package/dist/Server-C_IKav_e.d.mts.map +0 -1
  50. package/dist/Site-CMa153FA.d.mts.map +0 -1
  51. package/dist/flattenHeaders-C_YYLdOq.mjs.map +0 -1
  52. package/dist/runOpenApi-BUi1RH1l.mjs.map +0 -1
  53. package/src/bin/index.ts +0 -22
  54. package/src/dev.ts +0 -10
  55. package/src/form/formDataToRecord.ts +0 -23
  56. package/src/form/readForm.ts +0 -71
  57. package/src/form.ts +0 -4
  58. package/src/index.ts +0 -82
  59. package/src/nav/api.test-d.ts +0 -79
  60. package/src/nav/api.ts +0 -104
  61. package/src/nav/formSubmit.ts +0 -70
  62. package/src/nav/load.ts +0 -9
  63. package/src/nav/loadData.ts +0 -134
  64. package/src/nav/navigate.ts +0 -189
  65. package/src/nav/reload.ts +0 -9
  66. package/src/nav/route.ts +0 -38
  67. package/src/nav.ts +0 -6
  68. package/src/openapi/docsHtml.ts +0 -25
  69. package/src/openapi/document.ts +0 -154
  70. package/src/openapi/plugin.ts +0 -78
  71. package/src/openapi/types.ts +0 -82
  72. package/src/openapi.ts +0 -14
  73. package/src/response/TypedResponse.ts +0 -17
  74. package/src/response/badRequest.ts +0 -31
  75. package/src/response/created.ts +0 -31
  76. package/src/response/forbidden.ts +0 -21
  77. package/src/response/found.ts +0 -30
  78. package/src/response/methodNotAllowed.ts +0 -18
  79. package/src/response/movedPermanently.ts +0 -25
  80. package/src/response/notFound.ts +0 -24
  81. package/src/response/notModified.ts +0 -34
  82. package/src/response/ok.ts +0 -38
  83. package/src/response/permanentRedirect.ts +0 -30
  84. package/src/response/response.ts +0 -22
  85. package/src/response/seeOther.ts +0 -19
  86. package/src/response/serverError.ts +0 -24
  87. package/src/response/temporaryRedirect.ts +0 -31
  88. package/src/response/transfer.ts +0 -14
  89. package/src/response/unauthorized.ts +0 -21
  90. package/src/response/unprocessable.ts +0 -30
  91. package/src/response.ts +0 -37
  92. package/src/run/depCache.ts +0 -175
  93. package/src/run/devPlugin.ts +0 -128
  94. package/src/run/prepareTemplate.ts +0 -42
  95. package/src/run/run.ts +0 -86
  96. package/src/run/runBuild.ts +0 -128
  97. package/src/run/runDev.ts +0 -177
  98. package/src/run/runOpenApi.ts +0 -52
  99. package/src/run/runPreview.ts +0 -84
  100. package/src/run/staleTorpCopies.ts +0 -100
  101. package/src/run.ts +0 -7
  102. package/src/schema.ts +0 -7
  103. package/src/server/CookieHelper.ts +0 -46
  104. package/src/server/HeaderHelper.ts +0 -25
  105. package/src/server/Server.ts +0 -168
  106. package/src/server/ServerEvent.ts +0 -62
  107. package/src/server/connect/connectMiddleware.ts +0 -85
  108. package/src/server/connect/flattenHeaders.ts +0 -22
  109. package/src/server/connect/nodeMessageToNodeResponse.ts +0 -89
  110. package/src/server/connect/requestToNodeMessage.ts +0 -29
  111. package/src/server/contentType.ts +0 -85
  112. package/src/server/types/HttpMethod.ts +0 -12
  113. package/src/server/types/MiddlewareFunction.ts +0 -22
  114. package/src/server/types/ServerFunction.ts +0 -7
  115. package/src/server.ts +0 -7
  116. package/src/site/Router.ts +0 -175
  117. package/src/site/Site.ts +0 -388
  118. package/src/site/checkLayoutSlots.ts +0 -114
  119. package/src/site/checkRoutes.ts +0 -435
  120. package/src/site/clientEntry.ts +0 -121
  121. package/src/site/clientEntryDev.ts +0 -3
  122. package/src/site/defaultAdapter.ts +0 -11
  123. package/src/site/layoutSlots.ts +0 -53
  124. package/src/site/manifest.ts +0 -173
  125. package/src/site/serverEntry.ts +0 -648
  126. package/src/state/$page.ts +0 -25
  127. package/src/state/$serverPage.ts +0 -22
  128. package/src/state/client.ts +0 -15
  129. package/src/state.ts +0 -3
  130. package/src/test/runTest.ts +0 -538
  131. package/src/test.ts +0 -3
  132. package/src/types/Adapter.ts +0 -20
  133. package/src/types/ClientState.ts +0 -8
  134. package/src/types/InternalState.ts +0 -7
  135. package/src/types/Jsonify.ts +0 -20
  136. package/src/types/LayoutHandler.ts +0 -5
  137. package/src/types/LayoutPath.ts +0 -10
  138. package/src/types/ManifestRoute.ts +0 -6
  139. package/src/types/PageData.test-d.ts +0 -136
  140. package/src/types/PageData.ts +0 -35
  141. package/src/types/PageEndPoint.ts +0 -55
  142. package/src/types/PageForm.test-d.ts +0 -78
  143. package/src/types/PageForm.ts +0 -29
  144. package/src/types/PageLoadEvent.ts +0 -26
  145. package/src/types/PageLoadReturn.ts +0 -13
  146. package/src/types/PageProps.ts +0 -14
  147. package/src/types/PageServerAction.ts +0 -13
  148. package/src/types/PageServerEndPoint.test-d.ts +0 -105
  149. package/src/types/PageServerEndPoint.ts +0 -110
  150. package/src/types/PageServerLoad.ts +0 -15
  151. package/src/types/PageState.ts +0 -8
  152. package/src/types/ParseRouteParams.test-d.ts +0 -84
  153. package/src/types/ParseRouteParams.ts +0 -64
  154. package/src/types/Route.ts +0 -30
  155. package/src/types/RouteHandler.ts +0 -23
  156. package/src/types/RouteLayoutHandler.ts +0 -5
  157. package/src/types/RouteMatchResult.ts +0 -7
  158. package/src/types/RouteType.ts +0 -19
  159. package/src/types/ServerEndPoint.test-d.ts +0 -77
  160. package/src/types/ServerEndPoint.ts +0 -169
  161. package/src/types/ServerHook.ts +0 -18
  162. package/src/types/ServerLoadEvent.ts +0 -82
  163. package/src/types/ServerRequest.ts +0 -15
  164. package/src/types/SitePlugin.ts +0 -24
  165. package/src/types/StandardSchema.ts +0 -109
  166. package/src/utils/pathToRegex.ts +0 -17
  167. package/src/utils/pathTrie.ts +0 -182
  168. package/src/utils/searchParamsToRecord.ts +0 -18
  169. package/src/utils/torporPackages.ts +0 -150
  170. package/src/utils/tsconfigAliases.ts +0 -90
  171. package/src/validation/ValidationError.ts +0 -18
  172. package/src/validation/endpoint.ts +0 -83
  173. package/src/validation/validate.ts +0 -26
  174. package/src/vite-env.d.ts +0 -2
@@ -1,189 +0,0 @@
1
- import { clearLayoutSlot, fillLayoutSlot, hydrate } from "@torpor/view";
2
- import { type Component, type SlotRender } from "@torpor/view";
3
- import { mount, unmount } from "@torpor/view";
4
- import $page from "../state/$page";
5
- import client from "../state/client";
6
- import type LayoutPath from "../types/LayoutPath";
7
- import type PageEndPoint from "../types/PageEndPoint";
8
- import type PageServerEndPoint from "../types/PageServerEndPoint";
9
- import formSubmit from "./formSubmit";
10
- import loadData from "./loadData";
11
-
12
- // @ts-ignore
13
- export default async function navigate(url: URL, withHydration = false): Promise<boolean> {
14
- let parent = document.getElementById("app");
15
- if (!parent) {
16
- // TODO: 500
17
- console.log("500");
18
- return false;
19
- }
20
-
21
- const path = url.pathname;
22
- const query = url.searchParams;
23
-
24
- //console.log(`navigating to '${path}'${query.size ? ` with ${query}` : ""}`);
25
-
26
- const route = client.router.match(path, query);
27
- if (!route) {
28
- // TODO: 404
29
- console.log("404");
30
- return false;
31
- }
32
-
33
- // Update $page before building the components
34
- $page.url = url;
35
- if (path.endsWith("/_error")) {
36
- $page.status = parseInt(query.get("status") ?? "404");
37
- $page.error = { message: query.get("message") ?? "" };
38
- // Make it look a bit classier by removing the query
39
- window.history.replaceState({}, "", url.toString().split("?")[0]);
40
- } else {
41
- $page.status = 200;
42
- }
43
- const handler = route.handler;
44
- const params = route.params || {};
45
-
46
- // There must be a client endpoint with a component
47
- const clientEndPoint: PageEndPoint | undefined = (await handler.endPoint()).default;
48
- if (!clientEndPoint?.component) {
49
- // TODO: 404
50
- console.log("404");
51
- return false;
52
- }
53
-
54
- // There may be a server endpoint
55
- const serverEndPoint: PageServerEndPoint | undefined =
56
- handler.serverEndPoint && (await handler.serverEndPoint())?.default;
57
-
58
- let newLayoutStack: LayoutPath[] = [];
59
-
60
- // Pass the data into $props
61
- // TODO: Don't load if this is the first time -- it should have been passed
62
- // to us, somehow...
63
- const data = await loadData(
64
- handler,
65
- params,
66
- path,
67
- query,
68
- newLayoutStack,
69
- clientEndPoint,
70
- serverEndPoint,
71
- );
72
- // We may have form data in a hidden input -- not sure if this is the best
73
- // way to do it
74
- let formInput = document.getElementById("t-form-data") as HTMLInputElement;
75
- let form: Record<string, string> | undefined;
76
- if (formInput) {
77
- form = JSON.parse(formInput.value);
78
- $page.form = form;
79
- formInput.remove();
80
- }
81
- let $props: Record<string, any> = { data };
82
-
83
- // We may have form data in $page.form, either from the hidden input
84
- // (above), or added via onformsubmit (below)
85
- $props.form = $page.form;
86
-
87
- // Add some special context for submitting Forms client-side
88
- const $context = {
89
- TorporBuildContext: { onformsubmit: formSubmit },
90
- };
91
-
92
- client.layoutStack.push({ path: route.handler.path, data: {}, reuse: false, slotRegion: null });
93
- client.layoutStack = newLayoutStack;
94
- let layoutStack = client.layoutStack;
95
-
96
- // If there are layouts, work our way upwards, pushing each component into
97
- // the default slot of its parent
98
- // TODO: There's probably a nicer way to do this with reducers or something
99
- let component = clientEndPoint.component as Component;
100
- let slots: Record<string, SlotRender> | undefined = undefined;
101
- let reused = false;
102
- // The index of the innermost reused layout. Its slotRegion is the region
103
- // that must be cleared and refilled (it contains the next layout's or the
104
- // page's content). This is usually the last entry of the stack, but not
105
- // when an outer layout is reused while an inner one is new — e.g.
106
- // navigating between sections that share the root layout.
107
- let reusedIndex = -1;
108
- if (handler.layouts) {
109
- let slotFunctions: SlotRender[] = [];
110
- // The last slot function will render the client component
111
- slotFunctions[handler.layouts.length] = function clientComponent(parent, anchor) {
112
- let i = layoutStack.length - 1;
113
- layoutStack[i].slotRegion = fillLayoutSlot(
114
- clientEndPoint.component!,
115
- slotFunctions[i + 1],
116
- parent,
117
- anchor,
118
- $props,
119
- $context,
120
- );
121
- };
122
-
123
- // Each earlier slot function will render a layout
124
- for (let i = handler.layouts.length - 1; i >= 0; i--) {
125
- const layoutEndPoint: PageEndPoint | undefined = (await handler.layouts[i].endPoint())
126
- ?.default;
127
- if (layoutEndPoint?.component) {
128
- if (layoutStack[i].reuse) {
129
- // Reuse this layout — clear and refill its slot (done in
130
- // the try block below so a failure doesn't leave the slot
131
- // half-cleared)
132
- component = slotFunctions[i + 1] as Component;
133
- reusedIndex = i;
134
- reused = true;
135
- break;
136
- } else if (i === 0) {
137
- component = layoutEndPoint.component as Component;
138
- slots = { _: slotFunctions[i + 1] };
139
- } else {
140
- slotFunctions[i] = function layoutComponent(parent, anchor, _, $context) {
141
- layoutStack[i - 1].slotRegion = fillLayoutSlot(
142
- layoutEndPoint.component!,
143
- slotFunctions[i + 1],
144
- parent,
145
- anchor,
146
- $props,
147
- $context,
148
- );
149
- };
150
- }
151
- }
152
- }
153
- }
154
-
155
- try {
156
- if (reused) {
157
- // The layout is being reused — clear the old slot content, then
158
- // call the slot function directly to fill it with the new page.
159
- // We must not go through `mount` here: the slot's container still
160
- // holds the layout's own children (e.g. a header), which `mount`
161
- // refuses to mount into. Both the clear and the fill are inside
162
- // the try so that a failure doesn't leave the slot half-cleared.
163
- const slotRegion = layoutStack[reusedIndex].slotRegion;
164
- parent = slotRegion.startNode!.parentNode as HTMLElement;
165
- clearLayoutSlot(slotRegion);
166
- component(parent, null);
167
- } else if (withHydration) {
168
- hydrate(parent, component, $props, slots);
169
- } else {
170
- // The layout chain changed (or there was no previous layout to
171
- // reuse): tear down the previous UI entirely — disposing its
172
- // region tree and clearing `#app` — so `mount` starts fresh.
173
- // Without this, `mount` throws because `#app` still holds the
174
- // previous render's children, and it would reuse a stale root
175
- // region.
176
- unmount(parent);
177
- mount(parent, component, $props, slots);
178
- }
179
- } catch (error) {
180
- // TODO: Show a proper Error component
181
- parent.innerHTML = '<span style="color: red">Script syntax error</span><p>' + error + "</p>";
182
- console.log(error);
183
- }
184
-
185
- // Reset prefetched data on each navigation
186
- client.prefetchedData = {};
187
-
188
- return true;
189
- }
package/src/nav/reload.ts DELETED
@@ -1,9 +0,0 @@
1
- import navigate from "./navigate";
2
-
3
- /**
4
- * Reloads data for the currently loaded page and rerenders it.
5
- */
6
- export default async function reload(): Promise<boolean> {
7
- const url = new URL(document.location.href);
8
- return await navigate(url);
9
- }
package/src/nav/route.ts DELETED
@@ -1,38 +0,0 @@
1
- import type { ExactRouteParams, ParseRouteParams } from "../types/ParseRouteParams";
2
-
3
- /**
4
- * Builds a route path in a type-safe manner, filling in dynamic segments, e.g.
5
- * `route("/posts/[id]", { id: 5 })` returns `/posts/5`. The params object is
6
- * checked for exact keys: missing or unknown params error at compile time.
7
- *
8
- * NOTE: The rest args conditional must stay INLINE — routing it through a type
9
- * alias defeats `Params` inference, and excess keys stop being checked
10
- * @param path The route path
11
- * @param params The route params, required if the path has dynamic segments
12
- * @returns The path with the params filled in, URI-encoded
13
- */
14
- export default function route<
15
- Route extends string,
16
- Params extends ParseRouteParams<Route> = ParseRouteParams<Route>,
17
- >(
18
- path: Route,
19
- ...args: string extends Route
20
- ? [params?: Record<string, string>]
21
- : keyof ParseRouteParams<Route> extends never
22
- ? []
23
- : [params: Params & ExactRouteParams<Params, ParseRouteParams<Route>>]
24
- ): string {
25
- const params = ((args as unknown[])[0] ?? {}) as Record<string, string>;
26
- return path.replace(/\[(\.\.\.)?([^\]]+)\]/g, (_, splat: string | undefined, name: string) => {
27
- const value = params[name];
28
- if (value === undefined) {
29
- throw new Error(`Missing param '${name}' for route '${path}'`);
30
- }
31
- return splat
32
- ? value
33
- .split("/")
34
- .map((segment) => encodeURIComponent(segment))
35
- .join("/")
36
- : encodeURIComponent(String(value));
37
- });
38
- }
package/src/nav.ts DELETED
@@ -1,6 +0,0 @@
1
- import makeApi from "./nav/api";
2
- import load from "./nav/load";
3
- import reload from "./nav/reload";
4
- import route from "./nav/route";
5
-
6
- export { load, reload, route, makeApi };
@@ -1,25 +0,0 @@
1
- /**
2
- * A standalone HTML page loading Swagger UI from a CDN, pointed at the
3
- * OpenAPI document endpoint.
4
- */
5
- export default function openApiDocsHtml(docPath: string): string {
6
- return `<!doctype html>
7
- <html lang="en">
8
- <head>
9
- <meta charset="utf-8" />
10
- <meta name="viewport" content="width=device-width, initial-scale=1" />
11
- <title>API Docs</title>
12
- <link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css" />
13
- </head>
14
- <body>
15
- <div id="swagger-ui"></div>
16
- <script src="https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js" crossorigin></script>
17
- <script>
18
- window.addEventListener("DOMContentLoaded", () => {
19
- SwaggerUIBundle({ url: ${JSON.stringify(docPath)}, dom_id: "#swagger-ui" });
20
- });
21
- </script>
22
- </body>
23
- </html>
24
- `;
25
- }
@@ -1,154 +0,0 @@
1
- import type { StandardSchemaV1 } from "../types/StandardSchema";
2
- import type { OpenApiPluginOptions, OpenApiRouteEntry, JsonSchema } from "./types";
3
-
4
- // The endpoint handler names, in the order they appear in docs
5
- const METHODS = ["get", "post", "patch", "put", "del", "options", "head"] as const;
6
- type Method = (typeof METHODS)[number];
7
-
8
- // Handlers whose schema validates the query string; all others validate the
9
- // json request body
10
- const QUERY_METHODS: readonly string[] = ["get", "head"];
11
- const BODY_METHODS: readonly string[] = ["post", "patch", "put", "del", "options"];
12
-
13
- /**
14
- * Builds an OpenAPI 3.1 document from a list of +server endpoints.
15
- *
16
- * Each endpoint's route path (`/api/posts/[id]`) is converted to OpenAPI
17
- * syntax (`/api/posts/{id}`); its `params` schema becomes path parameters,
18
- * its `get`/`head` schema becomes query parameters, and its `post`/`patch`/
19
- * `put`/`del`/`options` schema becomes a json request body. Responses are
20
- * stubbed: a `200` for every handler, plus a `422` when the handler declares
21
- * an input schema (validation errors are returned automatically).
22
- *
23
- * Throws if any endpoint declares a schema but the options have no
24
- * `toJsonSchema` converter.
25
- */
26
- export default function buildOpenApiDocument(
27
- entries: OpenApiRouteEntry[],
28
- options: OpenApiPluginOptions,
29
- ): Record<string, unknown> {
30
- if (!options.toJsonSchema) {
31
- const withSchema = entries.find(hasSchemas);
32
- if (withSchema) {
33
- throw new Error(
34
- `The endpoint at ${withSchema.path} declares schema(s), but the openApi() plugin ` +
35
- `was not given a "toJsonSchema" converter. Pass one, e.g. ` +
36
- `openApi({ toJsonSchema: (s) => z.toJSONSchema(s) })`,
37
- );
38
- }
39
- }
40
-
41
- const paths: Record<string, Record<string, unknown>> = {};
42
- for (const entry of entries) {
43
- const pathItem = buildPathItem(entry, options);
44
- if (pathItem) {
45
- paths[openApiPath(entry.path)] = pathItem;
46
- }
47
- }
48
-
49
- return {
50
- openapi: "3.1.0",
51
- info: {
52
- title: options.title ?? "API",
53
- version: options.version ?? "1.0.0",
54
- },
55
- paths,
56
- };
57
- }
58
-
59
- function hasSchemas(entry: OpenApiRouteEntry): boolean {
60
- const schema = entry.endPoint?.schema;
61
- if (!schema) return false;
62
- return METHODS.some((method) => schema[method]) || !!schema.params;
63
- }
64
-
65
- function buildPathItem(
66
- entry: OpenApiRouteEntry,
67
- options: OpenApiPluginOptions,
68
- ): Record<string, unknown> | undefined {
69
- const endPoint = entry.endPoint;
70
- const pathItem: Record<string, unknown> = {};
71
- let count = 0;
72
-
73
- for (const method of METHODS) {
74
- if (typeof endPoint[method] !== "function") {
75
- continue;
76
- }
77
- count++;
78
-
79
- const schema = endPoint.schema?.[method];
80
- const paramsSchema = endPoint.schema?.params;
81
-
82
- const parameters: Record<string, unknown>[] = [];
83
- if (paramsSchema) {
84
- parameters.push(...flattenObject(convert(paramsSchema, options), "path"));
85
- }
86
- if (schema && QUERY_METHODS.includes(method)) {
87
- parameters.push(...flattenObject(convert(schema, options), "query"));
88
- }
89
-
90
- const responses: Record<string, unknown> = { "200": { description: "OK" } };
91
- if (schema) {
92
- responses["422"] = { description: "Validation failed" };
93
- }
94
-
95
- pathItem[method === "del" ? "delete" : method] = {
96
- operationId: operationId(method, entry.path),
97
- ...(parameters.length ? { parameters } : {}),
98
- ...(schema && BODY_METHODS.includes(method)
99
- ? {
100
- requestBody: {
101
- required: true,
102
- content: { "application/json": { schema: convert(schema, options) } },
103
- },
104
- }
105
- : {}),
106
- responses,
107
- };
108
- }
109
-
110
- return count > 0 ? pathItem : undefined;
111
- }
112
-
113
- function convert(schema: StandardSchemaV1, options: OpenApiPluginOptions): JsonSchema {
114
- return options.toJsonSchema!(schema);
115
- }
116
-
117
- /**
118
- * Flattens a converted object schema into OpenAPI parameters. Path
119
- * parameters are always required; query parameters take their `required`
120
- * flag from the schema's `required` array.
121
- */
122
- function flattenObject(
123
- jsonSchema: JsonSchema,
124
- location: "path" | "query",
125
- ): Record<string, unknown>[] {
126
- const properties = jsonSchema.properties;
127
- if (!properties || typeof properties !== "object") {
128
- return [];
129
- }
130
- const required = Array.isArray(jsonSchema.required) ? jsonSchema.required : [];
131
- return Object.entries(properties as Record<string, JsonSchema>).map(([name, propSchema]) => ({
132
- name,
133
- in: location,
134
- required: location === "path" ? true : required.includes(name),
135
- schema: propSchema,
136
- }));
137
- }
138
-
139
- /**
140
- * Converts a route path to OpenAPI syntax: `/api/posts/[id]` becomes
141
- * `/api/posts/{id}`.
142
- */
143
- function openApiPath(routePath: string): string {
144
- return routePath.replace(/\[\.\.\.([^\]]+)\]/g, "{$1}").replace(/\[([^\]]+)\]/g, "{$1}");
145
- }
146
-
147
- function operationId(method: Method, routePath: string): string {
148
- const segments = openApiPath(routePath)
149
- .split(/[^a-zA-Z0-9]+/)
150
- .filter((s) => s.length > 0)
151
- .map((s) => s.charAt(0).toUpperCase() + s.slice(1));
152
- const verb = method === "del" ? "delete" : method;
153
- return verb + (segments.join("") || "Root");
154
- }
@@ -1,78 +0,0 @@
1
- import type SitePlugin from "../types/SitePlugin";
2
- import openApiDocsHtml from "./docsHtml";
3
- import type { OpenApiPluginOptions, ResolvedOpenApiOptions } from "./types";
4
-
5
- /**
6
- * The `site.pluginState` key the openApi plugin stores its resolved options
7
- * under. A registry symbol (`Symbol.for`), so that the user's config, the
8
- * manifest plugin and the generated runtime code all resolve to the same
9
- * key even when they are loaded as separate module instances.
10
- */
11
- export const OPEN_API_STATE_KEY: symbol = Symbol.for("@torpor/build/openapi");
12
-
13
- /**
14
- * Serves an OpenAPI 3.1 document for the site's `+server` endpoints, plus an
15
- * optional interactive docs page.
16
- *
17
- * ```ts
18
- * import { Site } from "@torpor/build";
19
- * import { openApi } from "@torpor/build/openapi";
20
- * import { z } from "zod";
21
- *
22
- * const site = new Site();
23
- * site.addRouteFolder("src/routes");
24
- * site.plugins = [
25
- * openApi({
26
- * title: "My API",
27
- * toJsonSchema: (s) => z.toJSONSchema(s as z.ZodType, { io: "input" }),
28
- * }),
29
- * ];
30
- * export default site;
31
- * ```
32
- *
33
- * The document is served at `/openapi.json` (configurable with `path`) and
34
- * the docs page at `/docs` (set `docs: false` to disable). Endpoints are
35
- * picked up from every `+server.ts` route: its `params` schema becomes path
36
- * parameters, `get`/`head` schemas become query parameters, and
37
- * `post`/`patch`/`put`/`del`/`options` schemas become request bodies.
38
- *
39
- * The resolved options are stored in `site.pluginState` under
40
- * `OPEN_API_STATE_KEY`, which the manifest plugin reads at build time to
41
- * generate the document endpoint.
42
- */
43
- export function openApi(options: OpenApiPluginOptions = {}): SitePlugin {
44
- return (site) => {
45
- const docPath = withLeadingSlash(options.path ?? "/openapi.json");
46
- if (site.routes.some((route) => route.path === docPath)) {
47
- throw new Error(
48
- `There is already a route registered at ${docPath}, so the OpenAPI document can't be ` +
49
- `served there. Pass a different "path" to the openApi() plugin`,
50
- );
51
- }
52
-
53
- const resolved: ResolvedOpenApiOptions = {
54
- path: docPath,
55
- docs: options.docs === false ? undefined : withLeadingSlash(options.docs ?? "/docs"),
56
- title: options.title ?? "API",
57
- version: options.version ?? "1.0.0",
58
- toJsonSchema: options.toJsonSchema,
59
- };
60
- site.pluginState.set(OPEN_API_STATE_KEY, resolved);
61
-
62
- if (resolved.docs) {
63
- const html = openApiDocsHtml(docPath);
64
- site.addRoute(resolved.docs, {
65
- server: {
66
- get: () =>
67
- new Response(html, {
68
- headers: { "Content-Type": "text/html; charset=utf-8" },
69
- }),
70
- },
71
- });
72
- }
73
- };
74
- }
75
-
76
- function withLeadingSlash(pathName: string): string {
77
- return pathName.startsWith("/") ? pathName : "/" + pathName;
78
- }
@@ -1,82 +0,0 @@
1
- import type { StandardSchemaV1 } from "../types/StandardSchema";
2
-
3
- /**
4
- * A JSON Schema object, as produced by a schema library's converter (e.g.
5
- * `z.toJSONSchema`, valibot's `toJsonSchema`, arktype's `toJsonSchema`).
6
- */
7
- export type JsonSchema = Record<string, unknown>;
8
-
9
- /**
10
- * Converts a Standard Schema (zod, valibot, arktype, ...) into a JSON Schema
11
- * object, for inclusion in an OpenAPI document. Supplied to the openApi
12
- * plugin, since each schema library has its own conversion function:
13
- *
14
- * ```ts
15
- * // zod
16
- * openApi({ toJsonSchema: (s) => z.toJSONSchema(s as z.ZodType, { io: "input" }) })
17
- * // arktype
18
- * openApi({ toJsonSchema: (s) => (s as Type).toJsonSchema() })
19
- * ```
20
- */
21
- export type ToJsonSchema = (schema: StandardSchemaV1) => JsonSchema;
22
-
23
- /**
24
- * Options for the openApi plugin.
25
- */
26
- export type OpenApiPluginOptions = {
27
- /**
28
- * The path to serve the OpenAPI document at. Defaults to `/openapi.json`.
29
- */
30
- path?: string;
31
- /**
32
- * The path to serve interactive API docs at, or `false` to disable the
33
- * docs page. Defaults to `/docs`.
34
- */
35
- docs?: string | false;
36
- /**
37
- * The API title for the document's `info` section. Defaults to `"API"`.
38
- */
39
- title?: string;
40
- /**
41
- * The API version for the document's `info` section. Defaults to
42
- * `"1.0.0"`.
43
- */
44
- version?: string;
45
- /**
46
- * Converts an endpoint's schemas into JSON Schema. Required for endpoints
47
- * that declare schemas; endpoints without schemas are documented with
48
- * their paths and methods only.
49
- */
50
- toJsonSchema?: ToJsonSchema;
51
- };
52
-
53
- /**
54
- * OpenApi options with defaults applied. Created by the openApi plugin and
55
- * consumed by the document builder.
56
- */
57
- export type ResolvedOpenApiOptions = {
58
- path: string;
59
- /** Undefined when the docs page is disabled */
60
- docs: string | undefined;
61
- title: string;
62
- version: string;
63
- toJsonSchema: ToJsonSchema | undefined;
64
- };
65
-
66
- /**
67
- * A ServerEndPoint shape, loosely typed so that endpoint modules loaded from
68
- * anywhere can be passed to the document builder.
69
- */
70
- export type OpenApiEndPoint = {
71
- schema?: Record<string, StandardSchemaV1 | undefined> | undefined;
72
- [method: string]: unknown;
73
- };
74
-
75
- /**
76
- * A single API endpoint to include in an OpenAPI document: its route path
77
- * (with `[param]` segments) and its default-exported endpoint object.
78
- */
79
- export type OpenApiRouteEntry = {
80
- path: string;
81
- endPoint: OpenApiEndPoint;
82
- };
package/src/openapi.ts DELETED
@@ -1,14 +0,0 @@
1
- import buildOpenApiDocument from "./openapi/document";
2
- import openApiDocsHtml from "./openapi/docsHtml";
3
- import { OPEN_API_STATE_KEY, openApi } from "./openapi/plugin";
4
-
5
- export { buildOpenApiDocument, openApi, openApiDocsHtml, OPEN_API_STATE_KEY };
6
-
7
- export type {
8
- JsonSchema,
9
- OpenApiEndPoint,
10
- OpenApiPluginOptions,
11
- OpenApiRouteEntry,
12
- ResolvedOpenApiOptions,
13
- ToJsonSchema,
14
- } from "./openapi/types";
@@ -1,17 +0,0 @@
1
- /**
2
- * A Response whose JSON body type is known, as returned by e.g. `ok({ ... })`.
3
- * Purely a compile-time marker: `__body` is never set at runtime.
4
- */
5
- export default interface TypedResponse<T> extends Response {
6
- /**
7
- * Phantom property carrying the body type. Required so that plain
8
- * `Response` values do not match `TypedResponse<infer T>`.
9
- */
10
- readonly __body: T;
11
- }
12
-
13
- /**
14
- * A Response that does not carry typed JSON data (e.g. redirects, errors,
15
- * plain-text bodies). Loads may return these alongside typed data responses.
16
- */
17
- export type UntypedResponse = Response & { readonly __body?: never };
@@ -1,31 +0,0 @@
1
- import response from "./response";
2
- import type { Jsonify } from "../types/Jsonify";
3
- import type TypedResponse from "./TypedResponse";
4
-
5
- type BadRequestResponse<T extends object | string | undefined> = T extends object
6
- ? TypedResponse<Jsonify<T>>
7
- : Response;
8
-
9
- /**
10
- * 400 Bad Request
11
- *
12
- * The HTTP 400 Bad Request client error response status code indicates that the
13
- * server would not process the request due to something the server considered
14
- * to be a client error. The reason for a 400 response is typically due to
15
- * malformed request syntax, invalid request message framing, or deceptive
16
- * request routing.
17
- *
18
- * Clients that receive a 400 response should expect that repeating the request
19
- * without modification will fail with the same error.
20
- *
21
- * An object body is typed: it becomes the page's `$props.form` (with its JSON
22
- * form) after a form submit, so validation errors can be surfaced with their
23
- * field types via `PageForm`.
24
- *
25
- * See https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/400
26
- */
27
- export default function badRequest<T extends object | string | undefined>(
28
- body?: T,
29
- ): BadRequestResponse<T> {
30
- return response(400, body ?? "Bad request") as BadRequestResponse<T>;
31
- }
@@ -1,31 +0,0 @@
1
- import type { Jsonify } from "../types/Jsonify";
2
- import type TypedResponse from "./TypedResponse";
3
- import response from "./response";
4
-
5
- type CreatedResponse<T extends object | string | undefined> = T extends object
6
- ? TypedResponse<Jsonify<T>>
7
- : Response;
8
-
9
- /**
10
- * 201 Created
11
- *
12
- * The HTTP 201 Created successful response status code indicates that the HTTP
13
- * request has led to the creation of a resource. This status code is commonly
14
- * sent as the result of a POST request.
15
- *
16
- * The new resource, or a description and link to the new resource, is created
17
- * before the response is returned. The newly-created items can be returned in
18
- * the body of the response message, but must be locatable by the URL of the
19
- * initiating request or by the URL in the value of the Location header provided
20
- * with the response.
21
- *
22
- * An object body is typed: the client sees its JSON form through
23
- * `makeApi`-created callers.
24
- *
25
- * See https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/201
26
- */
27
- export default function created<T extends object | string | undefined>(
28
- body?: T,
29
- ): CreatedResponse<T> {
30
- return response(201, body) as CreatedResponse<T>;
31
- }