@torpor/build 0.4.14 → 1.0.1

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 (193) hide show
  1. package/README.md +20 -2
  2. package/dist/Jsonify-Dg6gChd3.d.mts +10 -0
  3. package/dist/Jsonify-Dg6gChd3.d.mts.map +1 -0
  4. package/dist/ParseRouteParams-DBmqlmLh.d.mts +42 -0
  5. package/dist/ParseRouteParams-DBmqlmLh.d.mts.map +1 -0
  6. package/dist/{RouteType-B6BMOyYw.mjs → Router-C4ADUfhv.mjs} +30 -42
  7. package/dist/Router-C4ADUfhv.mjs.map +1 -0
  8. package/dist/Server-BnjLJleZ.mjs +104 -0
  9. package/dist/Server-BnjLJleZ.mjs.map +1 -0
  10. package/dist/Server-Cler2w5O.d.mts +61 -0
  11. package/dist/Server-Cler2w5O.d.mts.map +1 -0
  12. package/dist/ServerEvent-CTx3Q-hk.d.mts +48 -0
  13. package/dist/ServerEvent-CTx3Q-hk.d.mts.map +1 -0
  14. package/dist/{ServerEvent-CxPwA4lH.mjs → ServerEvent-Dljn5i7A.mjs} +37 -11
  15. package/dist/ServerEvent-Dljn5i7A.mjs.map +1 -0
  16. package/dist/Site-CE0_o5Ci.d.mts +566 -0
  17. package/dist/Site-CE0_o5Ci.d.mts.map +1 -0
  18. package/dist/StandardSchema-D28L8_zZ.d.mts +100 -0
  19. package/dist/StandardSchema-D28L8_zZ.d.mts.map +1 -0
  20. package/dist/TypedResponse-6lznqN4U.d.mts +22 -0
  21. package/dist/TypedResponse-6lznqN4U.d.mts.map +1 -0
  22. package/dist/{_page-BQs4WvCO.mjs → _page-4cQKs_Dn.mjs} +3 -5
  23. package/dist/_page-4cQKs_Dn.mjs.map +1 -0
  24. package/dist/bin/index.d.mts +1 -1
  25. package/dist/bin/index.js +15 -11
  26. package/dist/bin/index.js.map +1 -1
  27. package/dist/clientEntry.d.mts +1 -0
  28. package/dist/clientEntry.mjs +59 -0
  29. package/dist/clientEntry.mjs.map +1 -0
  30. package/dist/clientEntryDev.d.mts +1 -0
  31. package/dist/clientEntryDev.mjs +7 -0
  32. package/dist/clientEntryDev.mjs.map +1 -0
  33. package/dist/dev.d.mts +3 -0
  34. package/dist/dev.mjs +3 -0
  35. package/dist/endpoint-EULRGCKA.mjs +108 -0
  36. package/dist/endpoint-EULRGCKA.mjs.map +1 -0
  37. package/dist/flattenHeaders-DM5qsSqO.mjs +13 -0
  38. package/dist/flattenHeaders-DM5qsSqO.mjs.map +1 -0
  39. package/dist/form.d.mts +35 -0
  40. package/dist/form.d.mts.map +1 -0
  41. package/dist/form.mjs +48 -0
  42. package/dist/form.mjs.map +1 -0
  43. package/dist/index.d.mts +72 -156
  44. package/dist/index.d.mts.map +1 -1
  45. package/dist/index.mjs +158 -26
  46. package/dist/index.mjs.map +1 -1
  47. package/dist/load-BntzvmwL.mjs +228 -0
  48. package/dist/load-BntzvmwL.mjs.map +1 -0
  49. package/dist/nav.d.mts +58 -5
  50. package/dist/nav.d.mts.map +1 -1
  51. package/dist/nav.mjs +63 -209
  52. package/dist/nav.mjs.map +1 -1
  53. package/dist/openapi.d.mts +144 -0
  54. package/dist/openapi.d.mts.map +1 -0
  55. package/dist/openapi.mjs +2 -0
  56. package/dist/pathTrie-D4Ax2Mu9.mjs +160 -0
  57. package/dist/pathTrie-D4Ax2Mu9.mjs.map +1 -0
  58. package/dist/plugin-D8Www11B.mjs +202 -0
  59. package/dist/plugin-D8Www11B.mjs.map +1 -0
  60. package/dist/{response-C5TtAsh1.mjs → response-Bg4w2S1q.mjs} +16 -39
  61. package/dist/response-Bg4w2S1q.mjs.map +1 -0
  62. package/dist/response.d.mts +309 -289
  63. package/dist/response.d.mts.map +1 -1
  64. package/dist/response.mjs +3 -4
  65. package/dist/run.d.mts +11 -4
  66. package/dist/run.d.mts.map +1 -1
  67. package/dist/run.mjs +2 -8
  68. package/dist/runOpenApi-DlOgF2Il.mjs +1419 -0
  69. package/dist/runOpenApi-DlOgF2Il.mjs.map +1 -0
  70. package/dist/schema.d.mts +32 -0
  71. package/dist/schema.d.mts.map +1 -0
  72. package/dist/schema.mjs +2 -0
  73. package/dist/server/Server.d.mts +2 -0
  74. package/dist/server/Server.mjs +2 -0
  75. package/dist/server.d.mts +11 -8
  76. package/dist/server.d.mts.map +1 -1
  77. package/dist/server.mjs +133 -4
  78. package/dist/server.mjs.map +1 -0
  79. package/dist/serverEntry-1qY3wZig.mjs +351 -0
  80. package/dist/serverEntry-1qY3wZig.mjs.map +1 -0
  81. package/dist/serverEntry-BAO8m03V.d.mts +6 -0
  82. package/dist/serverEntry-BAO8m03V.d.mts.map +1 -0
  83. package/dist/serverEntry.d.mts +2 -0
  84. package/dist/serverEntry.mjs +2 -0
  85. package/dist/state.d.mts.map +1 -1
  86. package/dist/state.mjs +2 -3
  87. package/dist/test.d.mts +8 -10
  88. package/dist/test.d.mts.map +1 -1
  89. package/dist/test.mjs +181 -79
  90. package/dist/test.mjs.map +1 -1
  91. package/dist/{seeOther-B4Yhu9iq.mjs → unprocessable-DAZbrDeR.mjs} +34 -13
  92. package/dist/unprocessable-DAZbrDeR.mjs.map +1 -0
  93. package/dist/validate-Ok4krlkX.mjs +40 -0
  94. package/dist/validate-Ok4krlkX.mjs.map +1 -0
  95. package/package.json +30 -18
  96. package/dist/RouteType-B6BMOyYw.mjs.map +0 -1
  97. package/dist/Server-CF5wDJp6.d.mts +0 -78
  98. package/dist/Server-CF5wDJp6.d.mts.map +0 -1
  99. package/dist/ServerEvent-CxPwA4lH.mjs.map +0 -1
  100. package/dist/Site-DgC6WWn1.d.mts +0 -78
  101. package/dist/Site-DgC6WWn1.d.mts.map +0 -1
  102. package/dist/_page-BQs4WvCO.mjs.map +0 -1
  103. package/dist/connectMiddleware-D7nehjj3.mjs +0 -252
  104. package/dist/connectMiddleware-D7nehjj3.mjs.map +0 -1
  105. package/dist/pathToRegex-TUAMc3zS.mjs +0 -11
  106. package/dist/pathToRegex-TUAMc3zS.mjs.map +0 -1
  107. package/dist/response-C5TtAsh1.mjs.map +0 -1
  108. package/dist/run-BPFtSYA3.mjs +0 -332
  109. package/dist/run-BPFtSYA3.mjs.map +0 -1
  110. package/dist/seeOther-B4Yhu9iq.mjs.map +0 -1
  111. package/src/bin/index.ts +0 -11
  112. package/src/index.ts +0 -30
  113. package/src/nav/formSubmit.ts +0 -56
  114. package/src/nav/load.ts +0 -9
  115. package/src/nav/loadData.ts +0 -134
  116. package/src/nav/navigate.ts +0 -164
  117. package/src/nav/reload.ts +0 -9
  118. package/src/nav.ts +0 -4
  119. package/src/response/badRequest.ts +0 -19
  120. package/src/response/created.ts +0 -20
  121. package/src/response/forbidden.ts +0 -21
  122. package/src/response/found.ts +0 -30
  123. package/src/response/methodNotAllowed.ts +0 -18
  124. package/src/response/movedPermanently.ts +0 -25
  125. package/src/response/notFound.ts +0 -24
  126. package/src/response/notModified.ts +0 -34
  127. package/src/response/ok.ts +0 -29
  128. package/src/response/permanentRedirect.ts +0 -30
  129. package/src/response/response.ts +0 -22
  130. package/src/response/seeOther.ts +0 -19
  131. package/src/response/serverError.ts +0 -24
  132. package/src/response/temporaryRedirect.ts +0 -31
  133. package/src/response/transfer.ts +0 -14
  134. package/src/response/unauthorized.ts +0 -21
  135. package/src/response/unprocessable.ts +0 -18
  136. package/src/response.ts +0 -37
  137. package/src/run/prepareTemplate.ts +0 -39
  138. package/src/run/run.ts +0 -62
  139. package/src/run/runBuild.ts +0 -77
  140. package/src/run/runDev.ts +0 -89
  141. package/src/run/runPreview.ts +0 -93
  142. package/src/run.ts +0 -6
  143. package/src/server/CookieHelper.ts +0 -36
  144. package/src/server/HeaderHelper.ts +0 -25
  145. package/src/server/Routerx.ts +0 -72
  146. package/src/server/Server.ts +0 -144
  147. package/src/server/ServerEvent.ts +0 -39
  148. package/src/server/connect/bufferToArrayBuffer.ts +0 -8
  149. package/src/server/connect/connectMiddleware.ts +0 -81
  150. package/src/server/connect/flattenHeaders.ts +0 -44
  151. package/src/server/connect/nodeMessageToNodeResponse.ts +0 -89
  152. package/src/server/connect/readableToBuffer.ts +0 -16
  153. package/src/server/connect/requestToNodeMessage.ts +0 -26
  154. package/src/server/contentType.ts +0 -85
  155. package/src/server/types/HttpMethod.ts +0 -12
  156. package/src/server/types/MiddlewareFunction.ts +0 -8
  157. package/src/server/types/ServerFunction.ts +0 -7
  158. package/src/server.ts +0 -7
  159. package/src/site/Router.ts +0 -164
  160. package/src/site/Site.ts +0 -214
  161. package/src/site/clientEntry.ts +0 -108
  162. package/src/site/clientEntryDev.ts +0 -3
  163. package/src/site/defaultAdapter.ts +0 -11
  164. package/src/site/manifest.ts +0 -54
  165. package/src/site/serverEntry.ts +0 -439
  166. package/src/state/$page.ts +0 -24
  167. package/src/state/client.ts +0 -15
  168. package/src/state.ts +0 -3
  169. package/src/test/runTest.ts +0 -350
  170. package/src/test.ts +0 -3
  171. package/src/types/Adapter.ts +0 -8
  172. package/src/types/ClientState.ts +0 -8
  173. package/src/types/InternalState.ts +0 -7
  174. package/src/types/LayoutHandler.ts +0 -5
  175. package/src/types/LayoutPath.ts +0 -10
  176. package/src/types/ManifestRoute.ts +0 -6
  177. package/src/types/PageEndPoint.ts +0 -39
  178. package/src/types/PageLoadEvent.ts +0 -14
  179. package/src/types/PageServerAction.ts +0 -7
  180. package/src/types/PageServerEndPoint.ts +0 -16
  181. package/src/types/PageServerLoad.ts +0 -7
  182. package/src/types/PageState.ts +0 -8
  183. package/src/types/Route.ts +0 -11
  184. package/src/types/RouteHandler.ts +0 -16
  185. package/src/types/RouteLayoutHandler.ts +0 -5
  186. package/src/types/RouteMatchResult.ts +0 -7
  187. package/src/types/RouteType.ts +0 -19
  188. package/src/types/ServerEndPoint.ts +0 -37
  189. package/src/types/ServerHook.ts +0 -11
  190. package/src/types/ServerLoadEvent.ts +0 -37
  191. package/src/types/ServerRequest.ts +0 -7
  192. package/src/utils/pathToRegex.ts +0 -15
  193. package/src/vite-env.d.ts +0 -2
package/dist/nav.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"nav.mjs","names":["CLIENT_SYMBOL: unique symbol","client: ClientState","client","layoutEndPoint: PageEndPoint | undefined","layoutServerEndPoint: PageServerEndPoint | undefined","client","clientEndPoint: PageEndPoint | undefined","serverEndPoint: PageServerEndPoint | undefined","newLayoutStack: LayoutPath[]","form: Record<string, string> | undefined","$props: Record<string, any>","$page","slots: Record<string, SlotRender> | undefined","slotFunctions: SlotRender[]","parent","layoutEndPoint: PageEndPoint | undefined","$context"],"sources":["../src/state/client.ts","../src/nav/reload.ts","../src/nav/formSubmit.ts","../src/nav/loadData.ts","../src/nav/navigate.ts","../src/nav/load.ts"],"sourcesContent":["import type ClientState from \"../types/ClientState\";\n\nconst CLIENT_SYMBOL: unique symbol = Symbol.for(\"torp.Client\");\n\nconst client: ClientState =\n\t// @ts-ignore\n\t(globalThis[CLIENT_SYMBOL] ??= {\n\t\t// @ts-ignore This will definitely, 100% get set\n\t\trouter: null,\n\t\tlayoutStack: [],\n\t\t// TODO: Probably expire after 30 seconds or something?\n\t\tprefetchedData: {},\n\t});\n\nexport default client;\n","import navigate from \"./navigate\";\n\n/**\n * Reloads data for the currently loaded page and rerenders it.\n */\nexport default async function reload(): Promise<boolean> {\n\tconst url = new URL(document.location.href);\n\treturn await navigate(url);\n}\n","import $page from \"../state/$page\";\nimport load from \"./load\";\nimport reload from \"./reload\";\n\nexport default async function formSubmit(e: SubmitEvent): Promise<void> {\n\t// We'll take it from here\n\te.preventDefault();\n\n\t// Reset $page on submit and link navigation\n\t// TODO: Should we put data in $page.data??\n\t$page.form = undefined;\n\n\t// Perform a POST fetch to the form's action location\n\tconst form = e.target as HTMLFormElement;\n\tconst button = e.submitter as HTMLButtonElement;\n\t// HACK: submit buttons have their formAction set to the current page if not\n\t// set, so we can't just check whether it's empty and fallback to form action\n\t//const action = button?.formAction ?? form.action;\n\tlet action = form.action || document.URL;\n\tif (button && button.formAction && button.formAction !== document.URL) {\n\t\taction = button.formAction;\n\t}\n\tconst options = {\n\t\tbody: new FormData(form),\n\t\tmethod: \"POST\",\n\t\tmode: \"cors\",\n\t\tcredentials: \"same-origin\",\n\t\t// Add a special header which tells the server that we want the raw\n\t\t// error response (if applicable) rather than the reloaded page that you\n\t\t// would get if submitting a page without javascript\n\t\theaders: {\n\t\t\t\"x-torpor-form-submit\": \"\",\n\t\t},\n\t};\n\tconst response = await fetch(\n\t\taction,\n\t\t// @ts-ignore -- don't know what it's complaining about here\n\t\toptions,\n\t);\n\n\tif (response.status === 200 && response.headers.has(\"x-torpor-form-redirect\")) {\n\t\t// TODO: Should do the correct type of redirect e.g. seeOther etc\n\t\tconst redirect = response.headers.get(\"location\")!;\n\t\tawait load(redirect);\n\t\twindow.location.href = redirect;\n\t} else if (\n\t\t(response.status >= 200 && response.status <= 299) ||\n\t\t(response.status >= 400 && response.status <= 499)\n\t) {\n\t\t// Put the response into `$page.form`, and it will be put into `$props` in reload\n\t\tif (response.headers.get(\"content-type\") === \"application/json\") {\n\t\t\t$page.form = await response.json();\n\t\t}\n\t\tawait reload();\n\t}\n}\n","import client from \"../state/client\";\nimport type LayoutPath from \"../types/LayoutPath\";\nimport type PageEndPoint from \"../types/PageEndPoint\";\nimport type PageServerEndPoint from \"../types/PageServerEndPoint\";\nimport type RouteHandler from \"../types/RouteHandler\";\n\nexport default async function loadData(\n\thandler: RouteHandler,\n\tparams: Record<string, string>,\n\tpath: string,\n\tquery: URLSearchParams,\n\tnewLayoutStack: LayoutPath[],\n\tclientEndPoint: PageEndPoint | undefined,\n\tserverEndPoint: PageServerEndPoint | undefined,\n\tprefetch = false,\n): Promise<Record<string, string> | void> {\n\tlet data = {};\n\tif (handler.layouts) {\n\t\tlet layoutStack = client.layoutStack;\n\t\tfor (let [i, layout] of handler.layouts.entries()) {\n\t\t\tlet layoutPath = layout.path;\n\t\t\tfor (let key in params) {\n\t\t\t\tlayoutPath = layoutPath.replace(`[${key}]`, params[key]);\n\t\t\t}\n\t\t\tif (layoutStack.at(i)?.path === layoutPath) {\n\t\t\t\t// We've already loaded this layout, we can just re-use its data and UI\n\t\t\t\tif (!prefetch) {\n\t\t\t\t\tlayoutStack[i].reuse = true;\n\t\t\t\t}\n\t\t\t\tnewLayoutStack[i] = layoutStack[i];\n\t\t\t\tObject.assign(data, layoutStack[i].data);\n\t\t\t} else {\n\t\t\t\tconst stackLayout = { path: layoutPath, data: {}, reuse: false, slotRegion: null };\n\t\t\t\tconst layoutEndPoint: PageEndPoint | undefined = (await layout.endPoint())?.default;\n\t\t\t\tconst layoutServerEndPoint: PageServerEndPoint | undefined =\n\t\t\t\t\tlayout.serverEndPoint && (await layout.serverEndPoint())?.default;\n\t\t\t\tconst layoutResponse = await loadClientAndServerData(\n\t\t\t\t\tstackLayout.data,\n\t\t\t\t\tdocument.location.origin + layoutPath,\n\t\t\t\t\tquery,\n\t\t\t\t\tparams,\n\t\t\t\t\tlayoutEndPoint,\n\t\t\t\t\tlayoutServerEndPoint,\n\t\t\t\t);\n\t\t\t\tif (layoutResponse?.ok === false) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tObject.assign(data, stackLayout.data);\n\t\t\t\tnewLayoutStack.push(stackLayout);\n\t\t\t}\n\t\t}\n\t}\n\tlet endPointResponse = await loadClientAndServerData(\n\t\tdata,\n\t\tdocument.location.origin + path,\n\t\tquery,\n\t\tparams,\n\t\tclientEndPoint,\n\t\tserverEndPoint,\n\t);\n\tif (endPointResponse?.ok === false) {\n\t\treturn;\n\t}\n\treturn data;\n}\n\nasync function loadClientAndServerData(\n\tdata: Record<string, any>,\n\tlocation: string,\n\tquery: URLSearchParams,\n\tparams: Record<string, string>,\n\tclientEndPoint?: PageEndPoint,\n\tserverEndPoint?: PageServerEndPoint,\n): Promise<Response | undefined | void> {\n\tlet prefetchedData = client.prefetchedData;\n\n\tif (clientEndPoint?.load) {\n\t\tconst clientKey = location + (query.size > 0 ? `?${query}` : \"\");\n\t\tif (prefetchedData[clientKey]) {\n\t\t\tObject.assign(data, prefetchedData[clientKey]);\n\t\t} else {\n\t\t\tconst clientUrl = new URL(document.location.href);\n\t\t\tfor (let [name, value] of query) {\n\t\t\t\tclientUrl.searchParams.append(name, value);\n\t\t\t}\n\t\t\tconst clientParams = buildClientParams(clientUrl, params, data);\n\t\t\tconst clientResponse = await clientEndPoint.load(clientParams);\n\t\t\tif (clientResponse) {\n\t\t\t\tif (clientResponse.ok) {\n\t\t\t\t\tif (clientResponse.headers.get(\"Content-Type\")?.includes(\"application/json\")) {\n\t\t\t\t\t\tconst clientData = await clientResponse.json();\n\t\t\t\t\t\tObject.assign(data, clientData);\n\t\t\t\t\t\tprefetchedData[clientKey] = clientData;\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\treturn clientResponse;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif (serverEndPoint?.load) {\n\t\tconst serverLocation = location.replace(/\\/$/, \"\") + \"/~server\";\n\t\tconst serverKey = serverLocation + (query.size > 0 ? `?${query}` : \"\");\n\t\tif (prefetchedData[serverKey]) {\n\t\t\tObject.assign(data, prefetchedData[serverKey]);\n\t\t} else {\n\t\t\tconst serverUrl = new URL(serverLocation);\n\t\t\tfor (let [name, value] of query) {\n\t\t\t\tserverUrl.searchParams.append(name, value);\n\t\t\t}\n\t\t\tconst serverResponse = await fetch(serverUrl);\n\t\t\tif (serverResponse) {\n\t\t\t\tif (serverResponse.ok) {\n\t\t\t\t\tif (serverResponse.headers.get(\"Content-Type\")?.includes(\"application/json\")) {\n\t\t\t\t\t\tconst serverData = await serverResponse.json();\n\t\t\t\t\t\tObject.assign(data, serverData);\n\t\t\t\t\t\tprefetchedData[serverKey] = serverData;\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\treturn serverResponse;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunction buildClientParams(url: URL, params: Record<string, string>, data: Record<string, any>) {\n\treturn {\n\t\turl,\n\t\tparams,\n\t\tdata,\n\t};\n}\n","import { clearLayoutSlot, fillLayoutSlot, hydrate } from \"@torpor/view\";\nimport { type Component, type SlotRender } from \"@torpor/view\";\nimport { mount } from \"@torpor/view\";\nimport $page from \"../state/$page\";\nimport client from \"../state/client\";\nimport type LayoutPath from \"../types/LayoutPath\";\nimport type PageEndPoint from \"../types/PageEndPoint\";\nimport type PageServerEndPoint from \"../types/PageServerEndPoint\";\nimport formSubmit from \"./formSubmit\";\nimport loadData from \"./loadData\";\n\n// @ts-ignore\nexport default async function navigate(url: URL, withHydration = false): Promise<boolean> {\n\tlet parent = document.getElementById(\"app\");\n\tif (!parent) {\n\t\t// TODO: 500\n\t\tconsole.log(\"500\");\n\t\treturn false;\n\t}\n\n\tconst path = url.pathname;\n\tconst query = url.searchParams;\n\n\t//console.log(`navigating to '${path}'${query.size ? ` with ${query}` : \"\"}`);\n\n\tconst route = client.router.match(path, query);\n\tif (!route) {\n\t\t// TODO: 404\n\t\tconsole.log(\"404\");\n\t\treturn false;\n\t}\n\n\t// Update $page before building the components\n\t$page.url = url;\n\tif (path.endsWith(\"/_error\")) {\n\t\t$page.status = parseInt(query.get(\"status\") ?? \"404\");\n\t\t$page.error = { message: query.get(\"message\") ?? \"\" };\n\t\t// Make it look a bit classier by removing the query\n\t\twindow.history.replaceState({}, \"\", url.toString().split(\"?\")[0]);\n\t} else {\n\t\t$page.status = 200;\n\t}\n\tconst handler = route.handler;\n\tconst params = route.params || {};\n\n\t// There must be a client endpoint with a component\n\tconst clientEndPoint: PageEndPoint | undefined = (await handler.endPoint()).default;\n\tif (!clientEndPoint?.component) {\n\t\t// TODO: 404\n\t\tconsole.log(\"404\");\n\t\treturn false;\n\t}\n\n\t// There may be a server endpoint\n\tconst serverEndPoint: PageServerEndPoint | undefined =\n\t\thandler.serverEndPoint && (await handler.serverEndPoint())?.default;\n\n\tlet newLayoutStack: LayoutPath[] = [];\n\n\t// Pass the data into $props\n\t// TODO: Don't load if this is the first time -- it should have been passed\n\t// to us, somehow...\n\tconst data = await loadData(\n\t\thandler,\n\t\tparams,\n\t\tpath,\n\t\tquery,\n\t\tnewLayoutStack,\n\t\tclientEndPoint,\n\t\tserverEndPoint,\n\t);\n\t// We may have form data in a hidden input -- not sure if this is the best\n\t// way to do it\n\tlet formInput = document.getElementById(\"t-form-data\") as HTMLInputElement;\n\tlet form: Record<string, string> | undefined;\n\tif (formInput) {\n\t\tform = JSON.parse(formInput.value);\n\t\t$page.form = form;\n\t\tformInput.remove();\n\t}\n\tlet $props: Record<string, any> = { data };\n\n\t// We may have form data in $page.form, either from the hidden input\n\t// (above), or added via onformsubmit (below)\n\t$props.form = $page.form;\n\n\t// Add some special context for submitting Forms client-side\n\tconst $context = {\n\t\tTorporBuildContext: { onformsubmit: formSubmit },\n\t};\n\n\tclient.layoutStack.push({ path: route.handler.path, data: {}, reuse: false, slotRegion: null });\n\tclient.layoutStack = newLayoutStack;\n\tlet layoutStack = client.layoutStack;\n\n\t// If there are layouts, work our way upwards, pushing each component into\n\t// the default slot of its parent\n\t// TODO: There's probably a nicer way to do this with reducers or something\n\tlet component = clientEndPoint.component as Component;\n\tlet slots: Record<string, SlotRender> | undefined = undefined;\n\tif (handler.layouts) {\n\t\tlet slotFunctions: SlotRender[] = [];\n\t\t// The last slot function will render the client component\n\t\tslotFunctions[handler.layouts.length] = function clientComponent(parent, anchor) {\n\t\t\tlet i = layoutStack.length - 1;\n\t\t\tlayoutStack[i].slotRegion = fillLayoutSlot(\n\t\t\t\tclientEndPoint.component!,\n\t\t\t\tslotFunctions[i + 1],\n\t\t\t\tparent,\n\t\t\t\tanchor,\n\t\t\t\t$props,\n\t\t\t\t$context,\n\t\t\t);\n\t\t};\n\n\t\t// Each earlier slot function will render a layout\n\t\tfor (let i = handler.layouts.length - 1; i >= 0; i--) {\n\t\t\tconst layoutEndPoint: PageEndPoint | undefined = (await handler.layouts[i].endPoint())\n\t\t\t\t?.default;\n\t\t\tif (layoutEndPoint?.component) {\n\t\t\t\tif (layoutStack[i].reuse) {\n\t\t\t\t\t// Set the parent to add the new content to (from the old\n\t\t\t\t\t// content), clear the range under this point, and set the\n\t\t\t\t\t// component to the slot function within this layout\n\t\t\t\t\tparent = layoutStack[i].slotRegion.startNode.parentNode as HTMLElement;\n\t\t\t\t\tclearLayoutSlot(layoutStack[i].slotRegion);\n\t\t\t\t\tcomponent = slotFunctions[i + 1];\n\t\t\t\t\tbreak;\n\t\t\t\t} else if (i === 0) {\n\t\t\t\t\tcomponent = layoutEndPoint.component as Component;\n\t\t\t\t\tslots = { _: slotFunctions[i + 1] };\n\t\t\t\t} else {\n\t\t\t\t\tslotFunctions[i] = function layoutComponent(parent, anchor, _, $context) {\n\t\t\t\t\t\tlayoutStack[i - 1].slotRegion = fillLayoutSlot(\n\t\t\t\t\t\t\tlayoutEndPoint.component!,\n\t\t\t\t\t\t\tslotFunctions[i + 1],\n\t\t\t\t\t\t\tparent,\n\t\t\t\t\t\t\tanchor,\n\t\t\t\t\t\t\t$props,\n\t\t\t\t\t\t\t$context,\n\t\t\t\t\t\t);\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif (withHydration) {\n\t\thydrate(parent, component, $props, slots);\n\t} else {\n\t\ttry {\n\t\t\tmount(parent, component, $props, slots);\n\t\t} catch (error) {\n\t\t\t// TODO: Show a proper Error component\n\t\t\tparent.innerHTML = '<span style=\"color: red\">Script syntax error</span><p>' + error + \"</p>\";\n\t\t\tconsole.log(error);\n\t\t}\n\t}\n\n\t// Reset prefetched data on each navigation\n\tclient.prefetchedData = {};\n\n\treturn true;\n}\n","import navigate from \"./navigate\";\n\n/**\n * Loads and renders the page at the supplied path.\n */\nexport default async function load(path: string): Promise<boolean> {\n\tconst url = new URL(path, document.location.href);\n\treturn await navigate(url);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"nav.mjs","names":[],"sources":["../src/nav/route.ts","../src/nav/api.ts"],"sourcesContent":["import type { ExactRouteParams, ParseRouteParams } from \"../types/ParseRouteParams\";\n\n/**\n * Builds a route path in a type-safe manner, filling in dynamic segments, e.g.\n * `route(\"/posts/[id]\", { id: 5 })` returns `/posts/5`. The params object is\n * checked for exact keys: missing or unknown params error at compile time.\n *\n * NOTE: The rest args conditional must stay INLINE — routing it through a type\n * alias defeats `Params` inference, and excess keys stop being checked\n * @param path The route path\n * @param params The route params, required if the path has dynamic segments\n * @returns The path with the params filled in, URI-encoded\n */\nexport default function route<\n\tRoute extends string,\n\tParams extends ParseRouteParams<Route> = ParseRouteParams<Route>,\n>(\n\tpath: Route,\n\t...args: string extends Route\n\t\t? [params?: Record<string, string>]\n\t\t: keyof ParseRouteParams<Route> extends never\n\t\t\t? []\n\t\t\t: [params: Params & ExactRouteParams<Params, ParseRouteParams<Route>>]\n): string {\n\tconst params = ((args as unknown[])[0] ?? {}) as Record<string, string>;\n\treturn path.replace(/\\[(\\.\\.\\.)?([^\\]]+)\\]/g, (_, splat: string | undefined, name: string) => {\n\t\tconst value = params[name];\n\t\tif (value === undefined) {\n\t\t\tthrow new Error(`Missing param '${name}' for route '${path}'`);\n\t\t}\n\t\treturn splat\n\t\t\t? value\n\t\t\t\t\t.split(\"/\")\n\t\t\t\t\t.map((segment) => encodeURIComponent(segment))\n\t\t\t\t\t.join(\"/\")\n\t\t\t: encodeURIComponent(String(value));\n\t});\n}\n","import type { ExactRouteParams, ParseRouteParams } from \"../types/ParseRouteParams\";\nimport type TypedResponse from \"../response/TypedResponse\";\nimport route from \"./route\";\n\n/**\n * The value a client call resolves to: the endpoint's typed JSON body when it\n * returns a typed response, or the raw Response when the body isn't JSON.\n * Untyped endpoints resolve to `unknown`.\n */\ntype ApiResult<Handler> = Handler extends (event: any) => any\n\t? Awaited<ReturnType<Handler>> extends TypedResponse<infer Body>\n\t\t? Body | Response\n\t\t: unknown\n\t: never;\n\n/**\n * The body a client call accepts: the endpoint event's annotated JSON body\n * type (from `ServerLoadEvent<Route, Body>`), or a loose value when the\n * handler doesn't annotate one.\n */\ntype ApiRequestBody<Handler> = Handler extends (event: infer Event) => any\n\t? Event extends { json: () => Promise<infer Body> }\n\t\t? unknown extends Body\n\t\t\t? BodyInit | object\n\t\t\t: Body\n\t\t: BodyInit | object\n\t: BodyInit | object;\n\n/** The callable methods of an endpoint, one per handler it defines. */\nexport type ApiMethods<Endpoint extends object> = {\n\t[Method in string & keyof Endpoint as NonNullable<Endpoint[Method]> extends (event: any) => any\n\t\t? Method\n\t\t: never]: (\n\t\tbody?: ApiRequestBody<NonNullable<Endpoint[Method]>>,\n\t\tinit?: RequestInit,\n\t) => Promise<ApiResult<NonNullable<Endpoint[Method]>>>;\n};\n\nconst METHODS = [\"get\", \"post\", \"patch\", \"put\", \"del\", \"options\", \"head\"] as const;\n\n/**\n * Creates typed client callers for a `+server` endpoint, e.g.\n *\n * ```ts\n * import type endpoint from \"@/routes/api/posts/[id]/+server\";\n * const post = makeApi<\"/api/posts/[id]\", typeof endpoint>(\"/api/posts/[id]\");\n * const data = await post.get(); // typed from the endpoint's responses\n * ```\n *\n * Method names match the endpoint's handlers (`del` for DELETE). Params are\n * filled in and URI-encoded from the route path, and JSON responses are\n * parsed; non-JSON responses resolve to the raw Response. Failed requests\n * (non-2xx) throw.\n *\n * @param path The endpoint's route path\n * @param params The route params, required if the path has dynamic segments\n * @returns An object with one caller per handler defined by the endpoint\n */\nexport default function makeApi<\n\tRoute extends string,\n\tEndpoint extends object,\n\tParams extends ParseRouteParams<Route> = ParseRouteParams<Route>,\n>(\n\tpath: Route,\n\t// NOTE: The rest args conditional must stay INLINE — routing it through a\n\t// type alias defeats `Params` inference, and excess keys stop being checked\n\t...args: string extends Route\n\t\t? [params?: Record<string, string>]\n\t\t: keyof ParseRouteParams<Route> extends never\n\t\t\t? []\n\t\t\t: [params: Params & ExactRouteParams<Params, ParseRouteParams<Route>>]\n): ApiMethods<Endpoint> {\n\tconst url = route(path as string, ...(args as [params?: Record<string, string>]));\n\tconst api: Record<string, (body?: unknown, init?: RequestInit) => Promise<unknown>> = {};\n\tfor (const method of METHODS) {\n\t\tapi[method] = (body, init) => request(method, url, body, init);\n\t}\n\treturn api as ApiMethods<Endpoint>;\n}\n\nasync function request(\n\tmethod: string,\n\turl: string,\n\tbody: unknown,\n\tinit?: RequestInit,\n): Promise<unknown> {\n\t// `del` is the endpoint handler name for DELETE requests\n\tconst httpMethod = method === \"del\" ? \"delete\" : method;\n\tconst options: RequestInit = { method: httpMethod.toUpperCase(), ...init };\n\tif (body !== undefined && options.body === undefined && method !== \"get\" && method !== \"head\") {\n\t\tif (typeof body === \"object\") {\n\t\t\toptions.headers ??= { \"Content-Type\": \"application/json\" };\n\t\t\toptions.body = JSON.stringify(body);\n\t\t} else {\n\t\t\toptions.body = body as BodyInit;\n\t\t}\n\t}\n\tconst response = await fetch(url, options);\n\tif (!response.ok) {\n\t\tthrow new Error(`API ${method.toUpperCase()} ${url} failed: ${response.status}`);\n\t}\n\tconst contentType = response.headers.get(\"Content-Type\") ?? \"\";\n\treturn contentType.includes(\"application/json\") ? await response.json() : response;\n}\n"],"mappings":";;;;;;;;;;;;;AAaA,SAAwB,MAIvB,MACA,GAAG,MAKM;CACT,MAAM,SAAW,KAAmB,MAAM,CAAC;CAC3C,OAAO,KAAK,QAAQ,2BAA2B,GAAG,OAA2B,SAAiB;EAC7F,MAAM,QAAQ,OAAO;EACrB,IAAI,UAAU,KAAA,GACb,MAAM,IAAI,MAAM,kBAAkB,KAAK,eAAe,KAAK,EAAE;EAE9D,OAAO,QACJ,MACC,MAAM,GAAG,CAAC,CACV,KAAK,YAAY,mBAAmB,OAAO,CAAC,CAAC,CAC7C,KAAK,GAAG,IACT,mBAAmB,OAAO,KAAK,CAAC;CACpC,CAAC;AACF;;;ACCA,MAAM,UAAU;CAAC;CAAO;CAAQ;CAAS;CAAO;CAAO;CAAW;AAAM;;;;;;;;;;;;;;;;;;;AAoBxE,SAAwB,QAKvB,MAGA,GAAG,MAKoB;CACvB,MAAM,MAAM,MAAM,MAAgB,GAAI,IAA0C;CAChF,MAAM,MAAgF,CAAC;CACvF,KAAK,MAAM,UAAU,SACpB,IAAI,WAAW,MAAM,SAAS,QAAQ,QAAQ,KAAK,MAAM,IAAI;CAE9D,OAAO;AACR;AAEA,eAAe,QACd,QACA,KACA,MACA,MACmB;CAGnB,MAAM,UAAuB;EAAE,SADZ,WAAW,QAAQ,WAAW,OAAA,CACC,YAAY;EAAG,GAAG;CAAK;CACzE,IAAI,SAAS,KAAA,KAAa,QAAQ,SAAS,KAAA,KAAa,WAAW,SAAS,WAAW,QACtF,IAAI,OAAO,SAAS,UAAU;EAC7B,QAAQ,YAAY,EAAE,gBAAgB,mBAAmB;EACzD,QAAQ,OAAO,KAAK,UAAU,IAAI;CACnC,OACC,QAAQ,OAAO;CAGjB,MAAM,WAAW,MAAM,MAAM,KAAK,OAAO;CACzC,IAAI,CAAC,SAAS,IACb,MAAM,IAAI,MAAM,OAAO,OAAO,YAAY,EAAE,GAAG,IAAI,WAAW,SAAS,QAAQ;CAGhF,QADoB,SAAS,QAAQ,IAAI,cAAc,KAAK,GAAA,CACzC,SAAS,kBAAkB,IAAI,MAAM,SAAS,KAAK,IAAI;AAC3E"}
@@ -0,0 +1,144 @@
1
+ import { v as SitePlugin } from "./Site-CE0_o5Ci.mjs";
2
+ import { t as StandardSchemaV1 } from "./StandardSchema-D28L8_zZ.mjs";
3
+ //#region src/openapi/types.d.ts
4
+ /**
5
+ * A JSON Schema object, as produced by a schema library's converter (e.g.
6
+ * `z.toJSONSchema`, valibot's `toJsonSchema`, arktype's `toJsonSchema`).
7
+ */
8
+ type JsonSchema = Record<string, unknown>;
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
+ type ToJsonSchema = (schema: StandardSchemaV1) => JsonSchema;
22
+ /**
23
+ * Options for the openApi plugin.
24
+ */
25
+ type OpenApiPluginOptions = {
26
+ /**
27
+ * The path to serve the OpenAPI document at. Defaults to `/openapi.json`.
28
+ */
29
+ path?: string;
30
+ /**
31
+ * The path to serve interactive API docs at, or `false` to disable the
32
+ * docs page. Defaults to `/docs`.
33
+ */
34
+ docs?: string | false;
35
+ /**
36
+ * The API title for the document's `info` section. Defaults to `"API"`.
37
+ */
38
+ title?: string;
39
+ /**
40
+ * The API version for the document's `info` section. Defaults to
41
+ * `"1.0.0"`.
42
+ */
43
+ version?: string;
44
+ /**
45
+ * Converts an endpoint's schemas into JSON Schema. Required for endpoints
46
+ * that declare schemas; endpoints without schemas are documented with
47
+ * their paths and methods only.
48
+ */
49
+ toJsonSchema?: ToJsonSchema;
50
+ };
51
+ /**
52
+ * OpenApi options with defaults applied. Created by the openApi plugin and
53
+ * consumed by the document builder.
54
+ */
55
+ type ResolvedOpenApiOptions = {
56
+ path: string;
57
+ /** Undefined when the docs page is disabled */
58
+ docs: string | undefined;
59
+ title: string;
60
+ version: string;
61
+ toJsonSchema: ToJsonSchema | undefined;
62
+ };
63
+ /**
64
+ * A ServerEndPoint shape, loosely typed so that endpoint modules loaded from
65
+ * anywhere can be passed to the document builder.
66
+ */
67
+ type OpenApiEndPoint = {
68
+ schema?: Record<string, StandardSchemaV1 | undefined> | undefined;
69
+ [method: string]: unknown;
70
+ };
71
+ /**
72
+ * A single API endpoint to include in an OpenAPI document: its route path
73
+ * (with `[param]` segments) and its default-exported endpoint object.
74
+ */
75
+ type OpenApiRouteEntry = {
76
+ path: string;
77
+ endPoint: OpenApiEndPoint;
78
+ };
79
+ //#endregion
80
+ //#region src/openapi/document.d.ts
81
+ /**
82
+ * Builds an OpenAPI 3.1 document from a list of +server endpoints.
83
+ *
84
+ * Each endpoint's route path (`/api/posts/[id]`) is converted to OpenAPI
85
+ * syntax (`/api/posts/{id}`); its `params` schema becomes path parameters,
86
+ * its `get`/`head` schema becomes query parameters, and its `post`/`patch`/
87
+ * `put`/`del`/`options` schema becomes a json request body. Responses are
88
+ * stubbed: a `200` for every handler, plus a `422` when the handler declares
89
+ * an input schema (validation errors are returned automatically).
90
+ *
91
+ * Throws if any endpoint declares a schema but the options have no
92
+ * `toJsonSchema` converter.
93
+ */
94
+ declare function buildOpenApiDocument(entries: OpenApiRouteEntry[], options: OpenApiPluginOptions): Record<string, unknown>;
95
+ //#endregion
96
+ //#region src/openapi/docsHtml.d.ts
97
+ /**
98
+ * A standalone HTML page loading Swagger UI from a CDN, pointed at the
99
+ * OpenAPI document endpoint.
100
+ */
101
+ declare function openApiDocsHtml(docPath: string): string;
102
+ //#endregion
103
+ //#region src/openapi/plugin.d.ts
104
+ /**
105
+ * The `site.pluginState` key the openApi plugin stores its resolved options
106
+ * under. A registry symbol (`Symbol.for`), so that the user's config, the
107
+ * manifest plugin and the generated runtime code all resolve to the same
108
+ * key even when they are loaded as separate module instances.
109
+ */
110
+ declare const OPEN_API_STATE_KEY: symbol;
111
+ /**
112
+ * Serves an OpenAPI 3.1 document for the site's `+server` endpoints, plus an
113
+ * optional interactive docs page.
114
+ *
115
+ * ```ts
116
+ * import { Site } from "@torpor/build";
117
+ * import { openApi } from "@torpor/build/openapi";
118
+ * import { z } from "zod";
119
+ *
120
+ * const site = new Site();
121
+ * site.addRouteFolder("src/routes");
122
+ * site.plugins = [
123
+ * openApi({
124
+ * title: "My API",
125
+ * toJsonSchema: (s) => z.toJSONSchema(s as z.ZodType, { io: "input" }),
126
+ * }),
127
+ * ];
128
+ * export default site;
129
+ * ```
130
+ *
131
+ * The document is served at `/openapi.json` (configurable with `path`) and
132
+ * the docs page at `/docs` (set `docs: false` to disable). Endpoints are
133
+ * picked up from every `+server.ts` route: its `params` schema becomes path
134
+ * parameters, `get`/`head` schemas become query parameters, and
135
+ * `post`/`patch`/`put`/`del`/`options` schemas become request bodies.
136
+ *
137
+ * The resolved options are stored in `site.pluginState` under
138
+ * `OPEN_API_STATE_KEY`, which the manifest plugin reads at build time to
139
+ * generate the document endpoint.
140
+ */
141
+ declare function openApi(options?: OpenApiPluginOptions): SitePlugin;
142
+ //#endregion
143
+ export { type JsonSchema, OPEN_API_STATE_KEY, type OpenApiEndPoint, type OpenApiPluginOptions, type OpenApiRouteEntry, type ResolvedOpenApiOptions, type ToJsonSchema, buildOpenApiDocument, openApi, openApiDocsHtml };
144
+ //# sourceMappingURL=openapi.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openapi.d.mts","names":[],"sources":["../src/openapi/types.ts","../src/openapi/document.ts","../src/openapi/docsHtml.ts","../src/openapi/plugin.ts"],"mappings":";;;;;;;KAMY,aAAa;;;;;;;;;;;;;KAcb,gBAAgB,QAAQ,qBAAqB;;;;KAK7C;;;;EAIX;;;;;EAKA;;;;EAIA;;;;;EAKA;;;;;;EAMA,eAAe;;;;;;KAOJ;EACX;;EAEA;EACA;EACA;EACA,cAAc;;;;;;KAOH;EACX,SAAS,eAAe;;;;;;;KAQb;EACX;EACA,UAAU;;;;;;;;;;;;;;;;;iBCvDa,qBACvB,SAAS,qBACT,SAAS,uBACP;;;;;;;iBCxBqB,gBAAgB;;;;;;;;;cCM3B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAgCG,QAAQ,UAAS,uBAA4B"}
@@ -0,0 +1,2 @@
1
+ import { i as buildOpenApiDocument, n as openApi, r as openApiDocsHtml, t as OPEN_API_STATE_KEY } from "./plugin-D8Www11B.mjs";
2
+ export { OPEN_API_STATE_KEY, buildOpenApiDocument, openApi, openApiDocsHtml };
@@ -0,0 +1,160 @@
1
+ //#region src/utils/pathTrie.ts
2
+ /**
3
+ * A simple trie router for path patterns.
4
+ *
5
+ * Pattern segments:
6
+ * - `posts` — static segment
7
+ * - `[id]` — matches one non-empty path segment, captured as `params.id`
8
+ * - `[...path]` — matches the rest of the path (at least one segment),
9
+ * captured as `params.path`
10
+ * - `*` — matches the rest of the path (possibly nothing), not captured
11
+ * - `*.css` — a segment with an embedded `*` matches one path segment
12
+ *
13
+ * `*` and `[...]` must be the last segment of a pattern. Matching priority at
14
+ * each level is static, then param, then glob, then rest. Trailing slashes are
15
+ * ignored, so `/posts` and `/posts/` match the same route.
16
+ */
17
+ var PathTrie = class {
18
+ #root = new TrieNode();
19
+ /**
20
+ * Registers a value at a path pattern. The first registration of an exact
21
+ * pattern wins, matching the previous regex-scanning router.
22
+ */
23
+ insert(pattern, value) {
24
+ let node = this.#root;
25
+ const segments = splitPath(pattern);
26
+ for (let i = 0; i < segments.length; i++) {
27
+ const segment = segments[i];
28
+ if (segment === "*") {
29
+ if (i !== segments.length - 1) throw new Error(`The '*' wildcard must be the last segment of a path: ${pattern}`);
30
+ node.rest ??= {
31
+ name: void 0,
32
+ allowEmpty: true,
33
+ child: new TrieNode()
34
+ };
35
+ node = node.rest.child;
36
+ } else if (segment.startsWith("[...") && segment.endsWith("]")) {
37
+ if (i !== segments.length - 1) throw new Error(`The '[...x]' splat must be the last segment of a path: ${pattern}`);
38
+ node.rest ??= {
39
+ name: segment.slice(4, -1),
40
+ allowEmpty: false,
41
+ child: new TrieNode()
42
+ };
43
+ node = node.rest.child;
44
+ } else if (segment.startsWith("[") && segment.endsWith("]")) {
45
+ node.param ??= {
46
+ name: segment.slice(1, -1),
47
+ child: new TrieNode()
48
+ };
49
+ node = node.param.child;
50
+ } else if (segment.includes("*")) {
51
+ node.glob ??= {
52
+ regex: globToRegex(segment),
53
+ child: new TrieNode()
54
+ };
55
+ node = node.glob.child;
56
+ } else {
57
+ node.static ??= /* @__PURE__ */ new Map();
58
+ let child = node.static.get(segment);
59
+ if (!child) {
60
+ child = new TrieNode();
61
+ node.static.set(segment, child);
62
+ }
63
+ node = child;
64
+ }
65
+ }
66
+ node.value ??= value;
67
+ }
68
+ /**
69
+ * Matches a request path against the trie. Returns the registered value
70
+ * and any captured params, or undefined when nothing matches.
71
+ */
72
+ match(path) {
73
+ let trailingSlash = path === "/";
74
+ while (path.length > 1 && path.endsWith("/")) {
75
+ path = path.slice(0, -1);
76
+ trailingSlash = true;
77
+ }
78
+ const segments = path === "/" ? [] : splitPath(path);
79
+ return this.#matchNode(this.#root, segments, 0, {}, trailingSlash);
80
+ }
81
+ #matchNode(node, segments, i, params, trailingSlash) {
82
+ if (i === segments.length) {
83
+ if (node.value !== void 0) return {
84
+ value: node.value,
85
+ params: orUndefined(params)
86
+ };
87
+ const rest = node.rest;
88
+ if (rest?.allowEmpty && trailingSlash && rest.child.value !== void 0) return {
89
+ value: rest.child.value,
90
+ params: orUndefined(params)
91
+ };
92
+ return;
93
+ }
94
+ const segment = segments[i];
95
+ const stat = node.static?.get(segment);
96
+ if (stat) {
97
+ const match = this.#matchNode(stat, segments, i + 1, params, trailingSlash);
98
+ if (match) return match;
99
+ }
100
+ if (node.param && segment !== "") {
101
+ const match = this.#matchNode(node.param.child, segments, i + 1, {
102
+ ...params,
103
+ [node.param.name]: segment
104
+ }, trailingSlash);
105
+ if (match) return match;
106
+ }
107
+ if (node.glob?.regex.test(segment)) {
108
+ const glob = node.glob;
109
+ const match = this.#matchNode(glob.child, segments, i + 1, params, trailingSlash);
110
+ if (match) return match;
111
+ }
112
+ const rest = node.rest;
113
+ if (rest?.child.value !== void 0) {
114
+ if (rest.name !== void 0) return {
115
+ value: rest.child.value,
116
+ params: orUndefined({
117
+ ...params,
118
+ [rest.name]: segments.slice(i).join("/")
119
+ })
120
+ };
121
+ return {
122
+ value: rest.child.value,
123
+ params: orUndefined(params)
124
+ };
125
+ }
126
+ }
127
+ };
128
+ var TrieNode = class {
129
+ value = void 0;
130
+ static;
131
+ param;
132
+ glob;
133
+ rest;
134
+ };
135
+ /**
136
+ * Splits a path or pattern into segments, dropping the leading empty segment
137
+ * and any trailing empty (from a trailing slash). `/posts/5` becomes
138
+ * `["posts", "5"]` and `/` becomes `[]`.
139
+ */
140
+ function splitPath(path) {
141
+ const segments = path.split("/");
142
+ if (segments[0] === "") segments.shift();
143
+ if (segments.length && segments[segments.length - 1] === "") segments.pop();
144
+ return segments;
145
+ }
146
+ /**
147
+ * Compiles a pattern segment with an embedded `*` (e.g. `*.css`) into a
148
+ * regex that matches a single path segment.
149
+ */
150
+ function globToRegex(segment) {
151
+ const source = segment.split("*").map((part) => part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join(".*");
152
+ return new RegExp(`^${source}$`);
153
+ }
154
+ function orUndefined(params) {
155
+ return Object.keys(params).length ? params : void 0;
156
+ }
157
+ //#endregion
158
+ export { PathTrie as t };
159
+
160
+ //# sourceMappingURL=pathTrie-D4Ax2Mu9.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pathTrie-D4Ax2Mu9.mjs","names":["#root","#matchNode"],"sources":["../src/utils/pathTrie.ts"],"sourcesContent":["/**\n * A simple trie router for path patterns.\n *\n * Pattern segments:\n * - `posts` — static segment\n * - `[id]` — matches one non-empty path segment, captured as `params.id`\n * - `[...path]` — matches the rest of the path (at least one segment),\n * captured as `params.path`\n * - `*` — matches the rest of the path (possibly nothing), not captured\n * - `*.css` — a segment with an embedded `*` matches one path segment\n *\n * `*` and `[...]` must be the last segment of a pattern. Matching priority at\n * each level is static, then param, then glob, then rest. Trailing slashes are\n * ignored, so `/posts` and `/posts/` match the same route.\n */\nexport default class PathTrie<T> {\n\t#root = new TrieNode<T>();\n\n\t/**\n\t * Registers a value at a path pattern. The first registration of an exact\n\t * pattern wins, matching the previous regex-scanning router.\n\t */\n\tinsert(pattern: string, value: T): void {\n\t\tlet node = this.#root;\n\t\tconst segments = splitPath(pattern);\n\t\tfor (let i = 0; i < segments.length; i++) {\n\t\t\tconst segment = segments[i];\n\t\t\tif (segment === \"*\") {\n\t\t\t\tif (i !== segments.length - 1) {\n\t\t\t\t\tthrow new Error(`The '*' wildcard must be the last segment of a path: ${pattern}`);\n\t\t\t\t}\n\t\t\t\tnode.rest ??= { name: undefined, allowEmpty: true, child: new TrieNode() };\n\t\t\t\tnode = node.rest.child;\n\t\t\t} else if (segment.startsWith(\"[...\") && segment.endsWith(\"]\")) {\n\t\t\t\tif (i !== segments.length - 1) {\n\t\t\t\t\tthrow new Error(`The '[...x]' splat must be the last segment of a path: ${pattern}`);\n\t\t\t\t}\n\t\t\t\tnode.rest ??= {\n\t\t\t\t\tname: segment.slice(4, -1),\n\t\t\t\t\tallowEmpty: false,\n\t\t\t\t\tchild: new TrieNode(),\n\t\t\t\t};\n\t\t\t\tnode = node.rest.child;\n\t\t\t} else if (segment.startsWith(\"[\") && segment.endsWith(\"]\")) {\n\t\t\t\tnode.param ??= { name: segment.slice(1, -1), child: new TrieNode() };\n\t\t\t\tnode = node.param.child;\n\t\t\t} else if (segment.includes(\"*\")) {\n\t\t\t\tnode.glob ??= { regex: globToRegex(segment), child: new TrieNode() };\n\t\t\t\tnode = node.glob.child;\n\t\t\t} else {\n\t\t\t\tnode.static ??= new Map();\n\t\t\t\tlet child = node.static.get(segment);\n\t\t\t\tif (!child) {\n\t\t\t\t\tchild = new TrieNode();\n\t\t\t\t\tnode.static.set(segment, child);\n\t\t\t\t}\n\t\t\t\tnode = child;\n\t\t\t}\n\t\t}\n\t\tnode.value ??= value;\n\t}\n\n\t/**\n\t * Matches a request path against the trie. Returns the registered value\n\t * and any captured params, or undefined when nothing matches.\n\t */\n\tmatch(path: string): PathMatch<T> | undefined {\n\t\t// Trailing slashes are optional: `/posts/` matches `/posts`. Whether\n\t\t// there WAS a trailing separator still matters for a trailing bare\n\t\t// `*`, which requires one (e.g. `/assets/*` matches `/assets/` but\n\t\t// not `/assets`)\n\t\tlet trailingSlash = path === \"/\";\n\t\twhile (path.length > 1 && path.endsWith(\"/\")) {\n\t\t\tpath = path.slice(0, -1);\n\t\t\ttrailingSlash = true;\n\t\t}\n\t\tconst segments = path === \"/\" ? [] : splitPath(path);\n\t\treturn this.#matchNode(this.#root, segments, 0, {}, trailingSlash);\n\t}\n\n\t#matchNode(\n\t\tnode: TrieNode<T>,\n\t\tsegments: string[],\n\t\ti: number,\n\t\tparams: Record<string, string>,\n\t\ttrailingSlash: boolean,\n\t): PathMatch<T> | undefined {\n\t\tif (i === segments.length) {\n\t\t\tif (node.value !== undefined) {\n\t\t\t\treturn { value: node.value, params: orUndefined(params) };\n\t\t\t}\n\t\t\t// A trailing bare `*` also matches nothing, when the path ended\n\t\t\t// with its separator (e.g. `/assets/*` matches `/assets/`)\n\t\t\tconst rest = node.rest;\n\t\t\tif (rest?.allowEmpty && trailingSlash && rest.child.value !== undefined) {\n\t\t\t\treturn { value: rest.child.value, params: orUndefined(params) };\n\t\t\t}\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst segment = segments[i];\n\n\t\t// Static segments win over params\n\t\tconst stat = node.static?.get(segment);\n\t\tif (stat) {\n\t\t\tconst match = this.#matchNode(stat, segments, i + 1, params, trailingSlash);\n\t\t\tif (match) return match;\n\t\t}\n\n\t\tif (node.param && segment !== \"\") {\n\t\t\tconst match = this.#matchNode(node.param.child, segments, i + 1, {\n\t\t\t\t...params,\n\t\t\t\t[node.param.name]: segment,\n\t\t\t}, trailingSlash);\n\t\t\tif (match) return match;\n\t\t}\n\n\t\tif (node.glob?.regex.test(segment)) {\n\t\t\tconst glob = node.glob;\n\t\t\tconst match = this.#matchNode(glob.child, segments, i + 1, params, trailingSlash);\n\t\t\tif (match) return match;\n\t\t}\n\n\t\t// The rest matcher consumes all remaining segments\n\t\tconst rest = node.rest;\n\t\tif (rest?.child.value !== undefined) {\n\t\t\t// Only build the captured value (and a new params object) when the\n\t\t\t// pattern actually names it — a bare `*` catch-all shouldn't pay\n\t\t\t// for a slice+join on every request\n\t\t\tif (rest.name !== undefined) {\n\t\t\t\treturn {\n\t\t\t\t\tvalue: rest.child.value,\n\t\t\t\t\tparams: orUndefined({ ...params, [rest.name]: segments.slice(i).join(\"/\") }),\n\t\t\t\t};\n\t\t\t}\n\t\t\treturn { value: rest.child.value, params: orUndefined(params) };\n\t\t}\n\n\t\treturn undefined;\n\t}\n}\n\nexport interface PathMatch<T> {\n\tvalue: T;\n\tparams: Record<string, string> | undefined;\n}\n\nclass TrieNode<T> {\n\tvalue: T | undefined = undefined;\n\tstatic: Map<string, TrieNode<T>> | undefined;\n\tparam: { name: string; child: TrieNode<T> } | undefined;\n\tglob: { regex: RegExp; child: TrieNode<T> } | undefined;\n\trest: { name: string | undefined; allowEmpty: boolean; child: TrieNode<T> } | undefined;\n}\n\n/**\n * Splits a path or pattern into segments, dropping the leading empty segment\n * and any trailing empty (from a trailing slash). `/posts/5` becomes\n * `[\"posts\", \"5\"]` and `/` becomes `[]`.\n */\nfunction splitPath(path: string): string[] {\n\tconst segments = path.split(\"/\");\n\tif (segments[0] === \"\") segments.shift();\n\tif (segments.length && segments[segments.length - 1] === \"\") segments.pop();\n\treturn segments;\n}\n\n/**\n * Compiles a pattern segment with an embedded `*` (e.g. `*.css`) into a\n * regex that matches a single path segment.\n */\nfunction globToRegex(segment: string): RegExp {\n\tconst source = segment\n\t\t.split(\"*\")\n\t\t.map((part) => part.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\"))\n\t\t.join(\".*\");\n\treturn new RegExp(`^${source}$`);\n}\n\nfunction orUndefined(params: Record<string, string>): Record<string, string> | undefined {\n\treturn Object.keys(params).length ? params : undefined;\n}\n"],"mappings":"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"}
@@ -0,0 +1,202 @@
1
+ //#region src/openapi/document.ts
2
+ const METHODS = [
3
+ "get",
4
+ "post",
5
+ "patch",
6
+ "put",
7
+ "del",
8
+ "options",
9
+ "head"
10
+ ];
11
+ const QUERY_METHODS = ["get", "head"];
12
+ const BODY_METHODS = [
13
+ "post",
14
+ "patch",
15
+ "put",
16
+ "del",
17
+ "options"
18
+ ];
19
+ /**
20
+ * Builds an OpenAPI 3.1 document from a list of +server endpoints.
21
+ *
22
+ * Each endpoint's route path (`/api/posts/[id]`) is converted to OpenAPI
23
+ * syntax (`/api/posts/{id}`); its `params` schema becomes path parameters,
24
+ * its `get`/`head` schema becomes query parameters, and its `post`/`patch`/
25
+ * `put`/`del`/`options` schema becomes a json request body. Responses are
26
+ * stubbed: a `200` for every handler, plus a `422` when the handler declares
27
+ * an input schema (validation errors are returned automatically).
28
+ *
29
+ * Throws if any endpoint declares a schema but the options have no
30
+ * `toJsonSchema` converter.
31
+ */
32
+ function buildOpenApiDocument(entries, options) {
33
+ if (!options.toJsonSchema) {
34
+ const withSchema = entries.find(hasSchemas);
35
+ if (withSchema) throw new Error(`The endpoint at ${withSchema.path} declares schema(s), but the openApi() plugin was not given a "toJsonSchema" converter. Pass one, e.g. openApi({ toJsonSchema: (s) => z.toJSONSchema(s) })`);
36
+ }
37
+ const paths = {};
38
+ for (const entry of entries) {
39
+ const pathItem = buildPathItem(entry, options);
40
+ if (pathItem) paths[openApiPath(entry.path)] = pathItem;
41
+ }
42
+ return {
43
+ openapi: "3.1.0",
44
+ info: {
45
+ title: options.title ?? "API",
46
+ version: options.version ?? "1.0.0"
47
+ },
48
+ paths
49
+ };
50
+ }
51
+ function hasSchemas(entry) {
52
+ const schema = entry.endPoint?.schema;
53
+ if (!schema) return false;
54
+ return METHODS.some((method) => schema[method]) || !!schema.params;
55
+ }
56
+ function buildPathItem(entry, options) {
57
+ const endPoint = entry.endPoint;
58
+ const pathItem = {};
59
+ let count = 0;
60
+ for (const method of METHODS) {
61
+ if (typeof endPoint[method] !== "function") continue;
62
+ count++;
63
+ const schema = endPoint.schema?.[method];
64
+ const paramsSchema = endPoint.schema?.params;
65
+ const parameters = [];
66
+ if (paramsSchema) parameters.push(...flattenObject(convert(paramsSchema, options), "path"));
67
+ if (schema && QUERY_METHODS.includes(method)) parameters.push(...flattenObject(convert(schema, options), "query"));
68
+ const responses = { "200": { description: "OK" } };
69
+ if (schema) responses["422"] = { description: "Validation failed" };
70
+ pathItem[method === "del" ? "delete" : method] = {
71
+ operationId: operationId(method, entry.path),
72
+ ...parameters.length ? { parameters } : {},
73
+ ...schema && BODY_METHODS.includes(method) ? { requestBody: {
74
+ required: true,
75
+ content: { "application/json": { schema: convert(schema, options) } }
76
+ } } : {},
77
+ responses
78
+ };
79
+ }
80
+ return count > 0 ? pathItem : void 0;
81
+ }
82
+ function convert(schema, options) {
83
+ return options.toJsonSchema(schema);
84
+ }
85
+ /**
86
+ * Flattens a converted object schema into OpenAPI parameters. Path
87
+ * parameters are always required; query parameters take their `required`
88
+ * flag from the schema's `required` array.
89
+ */
90
+ function flattenObject(jsonSchema, location) {
91
+ const properties = jsonSchema.properties;
92
+ if (!properties || typeof properties !== "object") return [];
93
+ const required = Array.isArray(jsonSchema.required) ? jsonSchema.required : [];
94
+ return Object.entries(properties).map(([name, propSchema]) => ({
95
+ name,
96
+ in: location,
97
+ required: location === "path" ? true : required.includes(name),
98
+ schema: propSchema
99
+ }));
100
+ }
101
+ /**
102
+ * Converts a route path to OpenAPI syntax: `/api/posts/[id]` becomes
103
+ * `/api/posts/{id}`.
104
+ */
105
+ function openApiPath(routePath) {
106
+ return routePath.replace(/\[\.\.\.([^\]]+)\]/g, "{$1}").replace(/\[([^\]]+)\]/g, "{$1}");
107
+ }
108
+ function operationId(method, routePath) {
109
+ const segments = openApiPath(routePath).split(/[^a-zA-Z0-9]+/).filter((s) => s.length > 0).map((s) => s.charAt(0).toUpperCase() + s.slice(1));
110
+ return (method === "del" ? "delete" : method) + (segments.join("") || "Root");
111
+ }
112
+ //#endregion
113
+ //#region src/openapi/docsHtml.ts
114
+ /**
115
+ * A standalone HTML page loading Swagger UI from a CDN, pointed at the
116
+ * OpenAPI document endpoint.
117
+ */
118
+ function openApiDocsHtml(docPath) {
119
+ return `<!doctype html>
120
+ <html lang="en">
121
+ <head>
122
+ <meta charset="utf-8" />
123
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
124
+ <title>API Docs</title>
125
+ <link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css" />
126
+ </head>
127
+ <body>
128
+ <div id="swagger-ui"></div>
129
+ <script src="https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js" crossorigin><\/script>
130
+ <script>
131
+ window.addEventListener("DOMContentLoaded", () => {
132
+ SwaggerUIBundle({ url: ${JSON.stringify(docPath)}, dom_id: "#swagger-ui" });
133
+ });
134
+ <\/script>
135
+ </body>
136
+ </html>
137
+ `;
138
+ }
139
+ //#endregion
140
+ //#region src/openapi/plugin.ts
141
+ /**
142
+ * The `site.pluginState` key the openApi plugin stores its resolved options
143
+ * under. A registry symbol (`Symbol.for`), so that the user's config, the
144
+ * manifest plugin and the generated runtime code all resolve to the same
145
+ * key even when they are loaded as separate module instances.
146
+ */
147
+ const OPEN_API_STATE_KEY = Symbol.for("@torpor/build/openapi");
148
+ /**
149
+ * Serves an OpenAPI 3.1 document for the site's `+server` endpoints, plus an
150
+ * optional interactive docs page.
151
+ *
152
+ * ```ts
153
+ * import { Site } from "@torpor/build";
154
+ * import { openApi } from "@torpor/build/openapi";
155
+ * import { z } from "zod";
156
+ *
157
+ * const site = new Site();
158
+ * site.addRouteFolder("src/routes");
159
+ * site.plugins = [
160
+ * openApi({
161
+ * title: "My API",
162
+ * toJsonSchema: (s) => z.toJSONSchema(s as z.ZodType, { io: "input" }),
163
+ * }),
164
+ * ];
165
+ * export default site;
166
+ * ```
167
+ *
168
+ * The document is served at `/openapi.json` (configurable with `path`) and
169
+ * the docs page at `/docs` (set `docs: false` to disable). Endpoints are
170
+ * picked up from every `+server.ts` route: its `params` schema becomes path
171
+ * parameters, `get`/`head` schemas become query parameters, and
172
+ * `post`/`patch`/`put`/`del`/`options` schemas become request bodies.
173
+ *
174
+ * The resolved options are stored in `site.pluginState` under
175
+ * `OPEN_API_STATE_KEY`, which the manifest plugin reads at build time to
176
+ * generate the document endpoint.
177
+ */
178
+ function openApi(options = {}) {
179
+ return (site) => {
180
+ const docPath = withLeadingSlash(options.path ?? "/openapi.json");
181
+ if (site.routes.some((route) => route.path === docPath)) throw new Error(`There is already a route registered at ${docPath}, so the OpenAPI document can't be served there. Pass a different "path" to the openApi() plugin`);
182
+ const resolved = {
183
+ path: docPath,
184
+ docs: options.docs === false ? void 0 : withLeadingSlash(options.docs ?? "/docs"),
185
+ title: options.title ?? "API",
186
+ version: options.version ?? "1.0.0",
187
+ toJsonSchema: options.toJsonSchema
188
+ };
189
+ site.pluginState.set(OPEN_API_STATE_KEY, resolved);
190
+ if (resolved.docs) {
191
+ const html = openApiDocsHtml(docPath);
192
+ site.addRoute(resolved.docs, { server: { get: () => new Response(html, { headers: { "Content-Type": "text/html; charset=utf-8" } }) } });
193
+ }
194
+ };
195
+ }
196
+ function withLeadingSlash(pathName) {
197
+ return pathName.startsWith("/") ? pathName : "/" + pathName;
198
+ }
199
+ //#endregion
200
+ export { buildOpenApiDocument as i, openApi as n, openApiDocsHtml as r, OPEN_API_STATE_KEY as t };
201
+
202
+ //# sourceMappingURL=plugin-D8Www11B.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin-D8Www11B.mjs","names":[],"sources":["../src/openapi/document.ts","../src/openapi/docsHtml.ts","../src/openapi/plugin.ts"],"sourcesContent":["import type { StandardSchemaV1 } from \"../types/StandardSchema\";\nimport type { OpenApiPluginOptions, OpenApiRouteEntry, JsonSchema } from \"./types\";\n\n// The endpoint handler names, in the order they appear in docs\nconst METHODS = [\"get\", \"post\", \"patch\", \"put\", \"del\", \"options\", \"head\"] as const;\ntype Method = (typeof METHODS)[number];\n\n// Handlers whose schema validates the query string; all others validate the\n// json request body\nconst QUERY_METHODS: readonly string[] = [\"get\", \"head\"];\nconst BODY_METHODS: readonly string[] = [\"post\", \"patch\", \"put\", \"del\", \"options\"];\n\n/**\n * Builds an OpenAPI 3.1 document from a list of +server endpoints.\n *\n * Each endpoint's route path (`/api/posts/[id]`) is converted to OpenAPI\n * syntax (`/api/posts/{id}`); its `params` schema becomes path parameters,\n * its `get`/`head` schema becomes query parameters, and its `post`/`patch`/\n * `put`/`del`/`options` schema becomes a json request body. Responses are\n * stubbed: a `200` for every handler, plus a `422` when the handler declares\n * an input schema (validation errors are returned automatically).\n *\n * Throws if any endpoint declares a schema but the options have no\n * `toJsonSchema` converter.\n */\nexport default function buildOpenApiDocument(\n\tentries: OpenApiRouteEntry[],\n\toptions: OpenApiPluginOptions,\n): Record<string, unknown> {\n\tif (!options.toJsonSchema) {\n\t\tconst withSchema = entries.find(hasSchemas);\n\t\tif (withSchema) {\n\t\t\tthrow new Error(\n\t\t\t\t`The endpoint at ${withSchema.path} declares schema(s), but the openApi() plugin ` +\n\t\t\t\t\t`was not given a \"toJsonSchema\" converter. Pass one, e.g. ` +\n\t\t\t\t\t`openApi({ toJsonSchema: (s) => z.toJSONSchema(s) })`,\n\t\t\t);\n\t\t}\n\t}\n\n\tconst paths: Record<string, Record<string, unknown>> = {};\n\tfor (const entry of entries) {\n\t\tconst pathItem = buildPathItem(entry, options);\n\t\tif (pathItem) {\n\t\t\tpaths[openApiPath(entry.path)] = pathItem;\n\t\t}\n\t}\n\n\treturn {\n\t\topenapi: \"3.1.0\",\n\t\tinfo: {\n\t\t\ttitle: options.title ?? \"API\",\n\t\t\tversion: options.version ?? \"1.0.0\",\n\t\t},\n\t\tpaths,\n\t};\n}\n\nfunction hasSchemas(entry: OpenApiRouteEntry): boolean {\n\tconst schema = entry.endPoint?.schema;\n\tif (!schema) return false;\n\treturn METHODS.some((method) => schema[method]) || !!schema.params;\n}\n\nfunction buildPathItem(\n\tentry: OpenApiRouteEntry,\n\toptions: OpenApiPluginOptions,\n): Record<string, unknown> | undefined {\n\tconst endPoint = entry.endPoint;\n\tconst pathItem: Record<string, unknown> = {};\n\tlet count = 0;\n\n\tfor (const method of METHODS) {\n\t\tif (typeof endPoint[method] !== \"function\") {\n\t\t\tcontinue;\n\t\t}\n\t\tcount++;\n\n\t\tconst schema = endPoint.schema?.[method];\n\t\tconst paramsSchema = endPoint.schema?.params;\n\n\t\tconst parameters: Record<string, unknown>[] = [];\n\t\tif (paramsSchema) {\n\t\t\tparameters.push(...flattenObject(convert(paramsSchema, options), \"path\"));\n\t\t}\n\t\tif (schema && QUERY_METHODS.includes(method)) {\n\t\t\tparameters.push(...flattenObject(convert(schema, options), \"query\"));\n\t\t}\n\n\t\tconst responses: Record<string, unknown> = { \"200\": { description: \"OK\" } };\n\t\tif (schema) {\n\t\t\tresponses[\"422\"] = { description: \"Validation failed\" };\n\t\t}\n\n\t\tpathItem[method === \"del\" ? \"delete\" : method] = {\n\t\t\toperationId: operationId(method, entry.path),\n\t\t\t...(parameters.length ? { parameters } : {}),\n\t\t\t...(schema && BODY_METHODS.includes(method)\n\t\t\t\t? {\n\t\t\t\t\t\trequestBody: {\n\t\t\t\t\t\t\trequired: true,\n\t\t\t\t\t\t\tcontent: { \"application/json\": { schema: convert(schema, options) } },\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n\t\t\t\t: {}),\n\t\t\tresponses,\n\t\t};\n\t}\n\n\treturn count > 0 ? pathItem : undefined;\n}\n\nfunction convert(schema: StandardSchemaV1, options: OpenApiPluginOptions): JsonSchema {\n\treturn options.toJsonSchema!(schema);\n}\n\n/**\n * Flattens a converted object schema into OpenAPI parameters. Path\n * parameters are always required; query parameters take their `required`\n * flag from the schema's `required` array.\n */\nfunction flattenObject(\n\tjsonSchema: JsonSchema,\n\tlocation: \"path\" | \"query\",\n): Record<string, unknown>[] {\n\tconst properties = jsonSchema.properties;\n\tif (!properties || typeof properties !== \"object\") {\n\t\treturn [];\n\t}\n\tconst required = Array.isArray(jsonSchema.required) ? jsonSchema.required : [];\n\treturn Object.entries(properties as Record<string, JsonSchema>).map(([name, propSchema]) => ({\n\t\tname,\n\t\tin: location,\n\t\trequired: location === \"path\" ? true : required.includes(name),\n\t\tschema: propSchema,\n\t}));\n}\n\n/**\n * Converts a route path to OpenAPI syntax: `/api/posts/[id]` becomes\n * `/api/posts/{id}`.\n */\nfunction openApiPath(routePath: string): string {\n\treturn routePath.replace(/\\[\\.\\.\\.([^\\]]+)\\]/g, \"{$1}\").replace(/\\[([^\\]]+)\\]/g, \"{$1}\");\n}\n\nfunction operationId(method: Method, routePath: string): string {\n\tconst segments = openApiPath(routePath)\n\t\t.split(/[^a-zA-Z0-9]+/)\n\t\t.filter((s) => s.length > 0)\n\t\t.map((s) => s.charAt(0).toUpperCase() + s.slice(1));\n\tconst verb = method === \"del\" ? \"delete\" : method;\n\treturn verb + (segments.join(\"\") || \"Root\");\n}\n","/**\n * A standalone HTML page loading Swagger UI from a CDN, pointed at the\n * OpenAPI document endpoint.\n */\nexport default function openApiDocsHtml(docPath: string): string {\n\treturn `<!doctype html>\n<html lang=\"en\">\n<head>\n\t<meta charset=\"utf-8\" />\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n\t<title>API Docs</title>\n\t<link rel=\"stylesheet\" href=\"https://unpkg.com/swagger-ui-dist@5/swagger-ui.css\" />\n</head>\n<body>\n\t<div id=\"swagger-ui\"></div>\n\t<script src=\"https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js\" crossorigin></script>\n\t<script>\n\t\twindow.addEventListener(\"DOMContentLoaded\", () => {\n\t\t\tSwaggerUIBundle({ url: ${JSON.stringify(docPath)}, dom_id: \"#swagger-ui\" });\n\t\t});\n\t</script>\n</body>\n</html>\n`;\n}\n","import type SitePlugin from \"../types/SitePlugin\";\nimport openApiDocsHtml from \"./docsHtml\";\nimport type { OpenApiPluginOptions, ResolvedOpenApiOptions } from \"./types\";\n\n/**\n * The `site.pluginState` key the openApi plugin stores its resolved options\n * under. A registry symbol (`Symbol.for`), so that the user's config, the\n * manifest plugin and the generated runtime code all resolve to the same\n * key even when they are loaded as separate module instances.\n */\nexport const OPEN_API_STATE_KEY: symbol = Symbol.for(\"@torpor/build/openapi\");\n\n/**\n * Serves an OpenAPI 3.1 document for the site's `+server` endpoints, plus an\n * optional interactive docs page.\n *\n * ```ts\n * import { Site } from \"@torpor/build\";\n * import { openApi } from \"@torpor/build/openapi\";\n * import { z } from \"zod\";\n *\n * const site = new Site();\n * site.addRouteFolder(\"src/routes\");\n * site.plugins = [\n * \topenApi({\n * \t\ttitle: \"My API\",\n * \t\ttoJsonSchema: (s) => z.toJSONSchema(s as z.ZodType, { io: \"input\" }),\n * \t}),\n * ];\n * export default site;\n * ```\n *\n * The document is served at `/openapi.json` (configurable with `path`) and\n * the docs page at `/docs` (set `docs: false` to disable). Endpoints are\n * picked up from every `+server.ts` route: its `params` schema becomes path\n * parameters, `get`/`head` schemas become query parameters, and\n * `post`/`patch`/`put`/`del`/`options` schemas become request bodies.\n *\n * The resolved options are stored in `site.pluginState` under\n * `OPEN_API_STATE_KEY`, which the manifest plugin reads at build time to\n * generate the document endpoint.\n */\nexport function openApi(options: OpenApiPluginOptions = {}): SitePlugin {\n\treturn (site) => {\n\t\tconst docPath = withLeadingSlash(options.path ?? \"/openapi.json\");\n\t\tif (site.routes.some((route) => route.path === docPath)) {\n\t\t\tthrow new Error(\n\t\t\t\t`There is already a route registered at ${docPath}, so the OpenAPI document can't be ` +\n\t\t\t\t\t`served there. Pass a different \"path\" to the openApi() plugin`,\n\t\t\t);\n\t\t}\n\n\t\tconst resolved: ResolvedOpenApiOptions = {\n\t\t\tpath: docPath,\n\t\t\tdocs: options.docs === false ? undefined : withLeadingSlash(options.docs ?? \"/docs\"),\n\t\t\ttitle: options.title ?? \"API\",\n\t\t\tversion: options.version ?? \"1.0.0\",\n\t\t\ttoJsonSchema: options.toJsonSchema,\n\t\t};\n\t\tsite.pluginState.set(OPEN_API_STATE_KEY, resolved);\n\n\t\tif (resolved.docs) {\n\t\t\tconst html = openApiDocsHtml(docPath);\n\t\t\tsite.addRoute(resolved.docs, {\n\t\t\t\tserver: {\n\t\t\t\t\tget: () =>\n\t\t\t\t\t\tnew Response(html, {\n\t\t\t\t\t\t\theaders: { \"Content-Type\": \"text/html; charset=utf-8\" },\n\t\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\t};\n}\n\nfunction withLeadingSlash(pathName: string): string {\n\treturn pathName.startsWith(\"/\") ? pathName : \"/\" + pathName;\n}\n"],"mappings":";AAIA,MAAM,UAAU;CAAC;CAAO;CAAQ;CAAS;CAAO;CAAO;CAAW;AAAM;AAKxE,MAAM,gBAAmC,CAAC,OAAO,MAAM;AACvD,MAAM,eAAkC;CAAC;CAAQ;CAAS;CAAO;CAAO;AAAS;;;;;;;;;;;;;;AAejF,SAAwB,qBACvB,SACA,SAC0B;CAC1B,IAAI,CAAC,QAAQ,cAAc;EAC1B,MAAM,aAAa,QAAQ,KAAK,UAAU;EAC1C,IAAI,YACH,MAAM,IAAI,MACT,mBAAmB,WAAW,KAAK,2JAGpC;CAEF;CAEA,MAAM,QAAiD,CAAC;CACxD,KAAK,MAAM,SAAS,SAAS;EAC5B,MAAM,WAAW,cAAc,OAAO,OAAO;EAC7C,IAAI,UACH,MAAM,YAAY,MAAM,IAAI,KAAK;CAEnC;CAEA,OAAO;EACN,SAAS;EACT,MAAM;GACL,OAAO,QAAQ,SAAS;GACxB,SAAS,QAAQ,WAAW;EAC7B;EACA;CACD;AACD;AAEA,SAAS,WAAW,OAAmC;CACtD,MAAM,SAAS,MAAM,UAAU;CAC/B,IAAI,CAAC,QAAQ,OAAO;CACpB,OAAO,QAAQ,MAAM,WAAW,OAAO,OAAO,KAAK,CAAC,CAAC,OAAO;AAC7D;AAEA,SAAS,cACR,OACA,SACsC;CACtC,MAAM,WAAW,MAAM;CACvB,MAAM,WAAoC,CAAC;CAC3C,IAAI,QAAQ;CAEZ,KAAK,MAAM,UAAU,SAAS;EAC7B,IAAI,OAAO,SAAS,YAAY,YAC/B;EAED;EAEA,MAAM,SAAS,SAAS,SAAS;EACjC,MAAM,eAAe,SAAS,QAAQ;EAEtC,MAAM,aAAwC,CAAC;EAC/C,IAAI,cACH,WAAW,KAAK,GAAG,cAAc,QAAQ,cAAc,OAAO,GAAG,MAAM,CAAC;EAEzE,IAAI,UAAU,cAAc,SAAS,MAAM,GAC1C,WAAW,KAAK,GAAG,cAAc,QAAQ,QAAQ,OAAO,GAAG,OAAO,CAAC;EAGpE,MAAM,YAAqC,EAAE,OAAO,EAAE,aAAa,KAAK,EAAE;EAC1E,IAAI,QACH,UAAU,SAAS,EAAE,aAAa,oBAAoB;EAGvD,SAAS,WAAW,QAAQ,WAAW,UAAU;GAChD,aAAa,YAAY,QAAQ,MAAM,IAAI;GAC3C,GAAI,WAAW,SAAS,EAAE,WAAW,IAAI,CAAC;GAC1C,GAAI,UAAU,aAAa,SAAS,MAAM,IACvC,EACA,aAAa;IACZ,UAAU;IACV,SAAS,EAAE,oBAAoB,EAAE,QAAQ,QAAQ,QAAQ,OAAO,EAAE,EAAE;GACrE,EACD,IACC,CAAC;GACJ;EACD;CACD;CAEA,OAAO,QAAQ,IAAI,WAAW,KAAA;AAC/B;AAEA,SAAS,QAAQ,QAA0B,SAA2C;CACrF,OAAO,QAAQ,aAAc,MAAM;AACpC;;;;;;AAOA,SAAS,cACR,YACA,UAC4B;CAC5B,MAAM,aAAa,WAAW;CAC9B,IAAI,CAAC,cAAc,OAAO,eAAe,UACxC,OAAO,CAAC;CAET,MAAM,WAAW,MAAM,QAAQ,WAAW,QAAQ,IAAI,WAAW,WAAW,CAAC;CAC7E,OAAO,OAAO,QAAQ,UAAwC,CAAC,CAAC,KAAK,CAAC,MAAM,iBAAiB;EAC5F;EACA,IAAI;EACJ,UAAU,aAAa,SAAS,OAAO,SAAS,SAAS,IAAI;EAC7D,QAAQ;CACT,EAAE;AACH;;;;;AAMA,SAAS,YAAY,WAA2B;CAC/C,OAAO,UAAU,QAAQ,uBAAuB,MAAM,CAAC,CAAC,QAAQ,iBAAiB,MAAM;AACxF;AAEA,SAAS,YAAY,QAAgB,WAA2B;CAC/D,MAAM,WAAW,YAAY,SAAS,CAAC,CACrC,MAAM,eAAe,CAAC,CACtB,QAAQ,MAAM,EAAE,SAAS,CAAC,CAAC,CAC3B,KAAK,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,YAAY,IAAI,EAAE,MAAM,CAAC,CAAC;CAEnD,QADa,WAAW,QAAQ,WAAW,WAC5B,SAAS,KAAK,EAAE,KAAK;AACrC;;;;;;;ACrJA,SAAwB,gBAAgB,SAAyB;CAChE,OAAO;;;;;;;;;;;;;4BAaoB,KAAK,UAAU,OAAO,EAAE;;;;;;AAMpD;;;;;;;;;ACdA,MAAa,qBAA6B,OAAO,IAAI,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgC5E,SAAgB,QAAQ,UAAgC,CAAC,GAAe;CACvE,QAAQ,SAAS;EAChB,MAAM,UAAU,iBAAiB,QAAQ,QAAQ,eAAe;EAChE,IAAI,KAAK,OAAO,MAAM,UAAU,MAAM,SAAS,OAAO,GACrD,MAAM,IAAI,MACT,0CAA0C,QAAQ,iGAEnD;EAGD,MAAM,WAAmC;GACxC,MAAM;GACN,MAAM,QAAQ,SAAS,QAAQ,KAAA,IAAY,iBAAiB,QAAQ,QAAQ,OAAO;GACnF,OAAO,QAAQ,SAAS;GACxB,SAAS,QAAQ,WAAW;GAC5B,cAAc,QAAQ;EACvB;EACA,KAAK,YAAY,IAAI,oBAAoB,QAAQ;EAEjD,IAAI,SAAS,MAAM;GAClB,MAAM,OAAO,gBAAgB,OAAO;GACpC,KAAK,SAAS,SAAS,MAAM,EAC5B,QAAQ,EACP,WACC,IAAI,SAAS,MAAM,EAClB,SAAS,EAAE,gBAAgB,2BAA2B,EACvD,CAAC,EACH,EACD,CAAC;EACF;CACD;AACD;AAEA,SAAS,iBAAiB,UAA0B;CACnD,OAAO,SAAS,WAAW,GAAG,IAAI,WAAW,MAAM;AACpD"}