@primate/core 0.6.3 → 0.7.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 (187) hide show
  1. package/lib/private/App.d.ts +76 -149
  2. package/lib/private/App.js +22 -6
  3. package/lib/private/Flags.d.ts +5 -3
  4. package/lib/private/Flags.js +4 -2
  5. package/lib/private/app/Facade.d.ts +77 -156
  6. package/lib/private/app/Facade.js +4 -1
  7. package/lib/private/build/App.d.ts +4 -2
  8. package/lib/private/build/App.js +13 -4
  9. package/lib/private/build/client/index.js +24 -10
  10. package/lib/private/build/client/plugin/routes.d.ts +4 -0
  11. package/lib/private/build/client/plugin/routes.js +77 -0
  12. package/lib/private/build/hook.js +14 -6
  13. package/lib/private/build/index.d.ts +3 -2
  14. package/lib/private/build/index.js +9 -15
  15. package/lib/private/build/preclient/index.d.ts +3 -0
  16. package/lib/private/build/preclient/index.js +69 -0
  17. package/lib/private/build/preclient/plugin/routes.d.ts +4 -0
  18. package/lib/private/build/preclient/plugin/routes.js +44 -0
  19. package/lib/private/build/server/index.js +8 -5
  20. package/lib/private/build/server/plugin/assets.js +3 -3
  21. package/lib/private/build/server/plugin/native-addons.js +6 -8
  22. package/lib/private/build/server/plugin/node-imports.js +5 -7
  23. package/lib/private/build/server/plugin/route-client.d.ts +4 -0
  24. package/lib/private/build/server/plugin/route-client.js +111 -0
  25. package/lib/private/build/server/plugin/route.js +3 -10
  26. package/lib/private/build/server/plugin/virtual-pages.js +3 -3
  27. package/lib/private/build/server/plugin/virtual-routes.d.ts +2 -1
  28. package/lib/private/build/server/plugin/virtual-routes.js +27 -8
  29. package/lib/private/build/server/plugin/wasm.js +3 -2
  30. package/lib/private/build/shared/intercept.d.ts +4 -0
  31. package/lib/private/build/shared/intercept.js +29 -0
  32. package/lib/private/client/Data.d.ts +1 -1
  33. package/lib/private/client/boot.js +2 -2
  34. package/lib/private/client/create-form.d.ts +11 -1
  35. package/lib/private/client/create-form.js +21 -3
  36. package/lib/private/client/index.d.ts +2 -2
  37. package/lib/private/client/navigate.d.ts +1 -0
  38. package/lib/private/client/navigate.js +7 -6
  39. package/lib/private/client/submit.d.ts +2 -1
  40. package/lib/private/client/submit.js +8 -7
  41. package/lib/private/client/{http.d.ts → transport.d.ts} +3 -3
  42. package/lib/private/client/{http.js → transport.js} +7 -9
  43. package/lib/private/config/schema.d.ts +5 -13
  44. package/lib/private/config/schema.js +1 -5
  45. package/lib/private/db/DB.d.ts +3 -1
  46. package/lib/private/db/MemoryDB.d.ts +5 -2
  47. package/lib/private/db/MemoryDB.js +33 -19
  48. package/lib/private/db/SQLDB.d.ts +23 -0
  49. package/lib/private/db/SQLDB.js +2 -0
  50. package/lib/private/db/errors.d.ts +74 -7
  51. package/lib/private/db/errors.js +31 -7
  52. package/lib/private/db/migrate/apply.js +8 -9
  53. package/lib/private/db/migrate/bundle.js +2 -2
  54. package/lib/private/db/migrate/create.js +18 -20
  55. package/lib/private/db/migrate/status.js +9 -10
  56. package/lib/private/db/migrate/store.d.ts +3 -3
  57. package/lib/private/db/migrate/store.js +5 -5
  58. package/lib/private/db/test.js +256 -115
  59. package/lib/private/db/testSQL.d.ts +50 -0
  60. package/lib/private/db/testSQL.js +196 -0
  61. package/lib/private/errors.d.ts +66 -9
  62. package/lib/private/errors.js +37 -16
  63. package/lib/private/frontend.d.ts +4 -4
  64. package/lib/private/frontend.js +11 -8
  65. package/lib/private/i18n/errors.d.ts +7 -1
  66. package/lib/private/i18n/errors.js +1 -1
  67. package/lib/private/i18n/index/types.d.ts +1 -1
  68. package/lib/private/i18n/locale.d.ts +1 -1
  69. package/lib/private/i18n/module.js +6 -5
  70. package/lib/private/index.d.ts +10 -2
  71. package/lib/private/index.js +13 -1
  72. package/lib/private/logger.d.ts +24 -0
  73. package/lib/private/logger.js +66 -0
  74. package/lib/private/module/Setup.d.ts +3 -0
  75. package/lib/private/module/create.d.ts +2 -1
  76. package/lib/private/module/create.js +4 -0
  77. package/lib/private/paths.d.ts +2 -3
  78. package/lib/private/paths.js +6 -12
  79. package/lib/private/request/ContentType.d.ts +3 -0
  80. package/lib/private/request/ContentType.js +2 -0
  81. package/lib/private/request/RequestBag.d.ts +2 -18
  82. package/lib/private/request/RequestBag.js +4 -16
  83. package/lib/private/request/RequestBody.d.ts +20 -28
  84. package/lib/private/request/RequestBody.js +63 -86
  85. package/lib/private/request/RequestFacade.d.ts +2 -2
  86. package/lib/private/request/handle.js +2 -2
  87. package/lib/private/request/parse.js +2 -4
  88. package/lib/private/request/route.js +15 -8
  89. package/lib/private/response/binary.d.ts +2 -2
  90. package/lib/private/response/binary.js +6 -6
  91. package/lib/private/response/error.js +6 -2
  92. package/lib/private/response/json.js +2 -2
  93. package/lib/private/response/null.d.ts +3 -0
  94. package/lib/private/response/null.js +6 -0
  95. package/lib/private/response/redirect.js +4 -3
  96. package/lib/private/response/respond.js +4 -4
  97. package/lib/private/response/sse.js +2 -2
  98. package/lib/private/response/text.js +2 -2
  99. package/lib/private/route/ContentTypeMap.d.ts +10 -0
  100. package/lib/private/route/ContentTypeMap.js +2 -0
  101. package/lib/private/route/Handler.d.ts +3 -2
  102. package/lib/private/route/NarrowedRequest.d.ts +23 -0
  103. package/lib/private/route/NarrowedRequest.js +2 -0
  104. package/lib/private/route/Options.d.ts +6 -1
  105. package/lib/private/route/Path.d.ts +2 -2
  106. package/lib/private/route/hook.d.ts +3 -1
  107. package/lib/private/route/hook.js +1 -2
  108. package/lib/private/route/router.d.ts +7 -11
  109. package/lib/private/route/router.js +13 -20
  110. package/lib/private/route.client.d.ts +36 -0
  111. package/lib/private/route.client.js +8 -0
  112. package/lib/private/route.d.ts +21 -5
  113. package/lib/private/route.js +21 -5
  114. package/lib/private/serve/App.d.ts +1 -2
  115. package/lib/private/serve/App.js +64 -58
  116. package/lib/private/serve/Init.d.ts +2 -0
  117. package/lib/private/serve/dev-module.js +2 -3
  118. package/lib/private/serve/index.js +5 -6
  119. package/lib/private/server/TAG.d.ts +1 -1
  120. package/lib/private/server/TAG.js +1 -1
  121. package/lib/private/session/index.d.ts +1 -1
  122. package/lib/private/session/index.js +3 -2
  123. package/lib/private/session/module.js +3 -3
  124. package/lib/private/session/schema.d.ts +3 -3
  125. package/lib/private/session/schema.js +4 -3
  126. package/lib/private/store/ExtractRelation.d.ts +7 -0
  127. package/lib/private/store/ExtractRelation.js +2 -0
  128. package/lib/private/store/ExtractSchema.d.ts +10 -0
  129. package/lib/private/{orm → store}/ForeignKey.d.ts +1 -1
  130. package/lib/private/store/Init.d.ts +13 -0
  131. package/lib/private/store/Init.js +2 -0
  132. package/lib/private/{orm/store.d.ts → store/Store.d.ts} +50 -50
  133. package/lib/private/{orm/store.js → store/Store.js} +163 -107
  134. package/lib/private/store/StoreInput.d.ts +11 -0
  135. package/lib/private/store/key.d.ts +8 -0
  136. package/lib/private/store/key.js +8 -0
  137. package/lib/private/{orm → store}/parse.d.ts +3 -3
  138. package/lib/private/{orm → store}/parse.js +7 -2
  139. package/lib/private/store/relation.d.ts +29 -0
  140. package/lib/private/store/relation.js +26 -0
  141. package/lib/private/store.d.ts +24 -0
  142. package/lib/private/store.js +10 -0
  143. package/lib/public/db/errors.d.ts +1 -1
  144. package/lib/public/db/errors.js +1 -1
  145. package/lib/public/db/testSQL.d.ts +2 -0
  146. package/lib/public/db/testSQL.js +2 -0
  147. package/lib/public/db.d.ts +1 -0
  148. package/lib/public/index.d.ts +1 -0
  149. package/lib/public/index.js +1 -1
  150. package/lib/public/response.d.ts +6 -6
  151. package/lib/public/response.js +4 -1
  152. package/lib/public/route.client.d.ts +2 -0
  153. package/lib/public/route.client.js +2 -0
  154. package/lib/public/store.d.ts +2 -0
  155. package/lib/public/store.js +2 -0
  156. package/package.json +24 -17
  157. package/lib/private/bye.d.ts +0 -3
  158. package/lib/private/bye.js +0 -4
  159. package/lib/private/log.d.ts +0 -20
  160. package/lib/private/log.js +0 -47
  161. package/lib/private/orm/ExtractSchema.d.ts +0 -9
  162. package/lib/private/orm/StoreInput.d.ts +0 -10
  163. package/lib/private/orm/key.d.ts +0 -8
  164. package/lib/private/orm/key.js +0 -8
  165. package/lib/private/orm/relation.d.ts +0 -43
  166. package/lib/private/orm/relation.js +0 -26
  167. package/lib/private/request/Verb.d.ts +0 -4
  168. package/lib/private/request/Verb.js +0 -2
  169. package/lib/private/request/verbs.d.ts +0 -3
  170. package/lib/private/request/verbs.js +0 -12
  171. package/lib/private/route/wrap.d.ts +0 -2
  172. package/lib/private/route/wrap.js +0 -12
  173. package/lib/public/log.d.ts +0 -2
  174. package/lib/public/log.js +0 -2
  175. package/lib/public/orm/key.d.ts +0 -2
  176. package/lib/public/orm/key.js +0 -2
  177. package/lib/public/orm/relation.d.ts +0 -2
  178. package/lib/public/orm/relation.js +0 -2
  179. package/lib/public/orm/store.d.ts +0 -2
  180. package/lib/public/orm/store.js +0 -2
  181. package/lib/public/request/verbs.d.ts +0 -2
  182. package/lib/public/request/verbs.js +0 -2
  183. /package/lib/private/{orm → store}/ExtractSchema.js +0 -0
  184. /package/lib/private/{orm → store}/ForeignKey.js +0 -0
  185. /package/lib/private/{orm → store}/PrimaryKey.d.ts +0 -0
  186. /package/lib/private/{orm → store}/PrimaryKey.js +0 -0
  187. /package/lib/private/{orm → store}/StoreInput.js +0 -0
@@ -1,9 +1,9 @@
1
- import E from "#errors";
2
- import log from "#log";
1
+ import E, { Code } from "#errors";
3
2
  import response_error from "#response/error";
4
3
  import response_json from "#response/json";
5
4
  import respond from "#response/respond";
6
- import { Status } from "@rcompat/http";
5
+ import { CodeError } from "@rcompat/error";
6
+ import http from "@rcompat/http";
7
7
  import ParseError from "pema/ParseError";
8
8
  const result = (request, response) => ({ request, response });
9
9
  function wrap_hook(h) {
@@ -43,24 +43,31 @@ export default async function (app, partial_request) {
43
43
  try {
44
44
  const route = await app.route(partial_request);
45
45
  if (route === undefined) {
46
+ app.log.trace `no route for pathname ${partial_request.url.pathname}`;
46
47
  return response_error()(app, {}, partial_request);
47
48
  }
48
- const { errors, hooks, layouts, handler } = route;
49
+ const { errors, hooks = [], layouts, handler, is_head } = route;
49
50
  errorRoute = errors[0];
50
51
  const internal_hooks = app.route_hooks.map(wrap_hook);
51
52
  const route_hooks = hooks.map(wrap_hook);
52
53
  const last = async (req, _next) => result(req, await handler(req));
53
54
  const { request, response } = await run([...internal_hooks, ...route_hooks, last], route.request);
54
- return await respond(response)(app, {
55
+ const full = await respond(response)(app, {
55
56
  layouts: await Promise.all(layouts.map(layout => layout(request))),
56
57
  }, request);
58
+ return is_head
59
+ ? new Response(null, { headers: full.headers, status: full.status })
60
+ : full;
57
61
  }
58
62
  catch (error) {
59
63
  const request = partial_request;
60
- if (error instanceof ParseError) {
61
- return response_json(error.toJSON(), { status: Status.BAD_REQUEST })(app);
64
+ if (ParseError.is(error)) {
65
+ return response_json(error.toJSON(), { status: http.Status.BAD_REQUEST })(app);
62
66
  }
63
- log.error(error);
67
+ if (CodeError.matches(error, Code.request_content_type_mismatch)) {
68
+ return response_json({ error: error.message }, { status: http.Status.UNSUPPORTED_MEDIA_TYPE })(app);
69
+ }
70
+ app.log.error(error);
64
71
  // the +error.js page itself could fail
65
72
  try {
66
73
  return respond(await errorRoute(request))(app, {}, request);
@@ -1,10 +1,10 @@
1
1
  import type ServeApp from "#serve/App";
2
- import type { StreamSource } from "@rcompat/fs";
2
+ import type { Streamable } from "@rcompat/fs";
3
3
  /**
4
4
  * Stream a binary response.
5
5
  * @param source streamable source
6
6
  * @param options response options
7
7
  * @return Response rendering function
8
8
  */
9
- export default function binary(source: StreamSource, init?: ResponseInit): (app: ServeApp) => Response;
9
+ export default function binary(source: Streamable, init?: ResponseInit): (app: ServeApp) => Response;
10
10
  //# sourceMappingURL=binary.d.ts.map
@@ -1,5 +1,5 @@
1
- import Streamable from "@rcompat/fs/Streamable";
2
- import { MIME } from "@rcompat/http";
1
+ import fs from "@rcompat/fs";
2
+ import http from "@rcompat/http";
3
3
  import is from "@rcompat/is";
4
4
  const encodeRFC5987 = (s) => encodeURIComponent(s).replace(/['()*]/g, c => `%${c.charCodeAt(0).toString(16).toUpperCase()}`);
5
5
  /**
@@ -31,15 +31,15 @@ function toContentDisposition(filename) {
31
31
  */
32
32
  export default function binary(source, init) {
33
33
  return (app) => {
34
- const { headers, ...rest } = app.media(MIME.APPLICATION_OCTET_STREAM, init);
35
- const name = Streamable.named(source) ? source.name : "default.bin";
34
+ const { headers, ...rest } = app.media(http.MIME.APPLICATION_OCTET_STREAM, init);
35
+ const name = fs.isNamedStream(source) ? source.name : "default.bin";
36
36
  const out = new Headers(headers);
37
37
  out.set("Content-Disposition", toContentDisposition(name));
38
38
  if (is.blob(source)) {
39
- source.type && out.set("Content-Type", source.type);
39
+ is.text(source.type) && out.set("Content-Type", source.type);
40
40
  out.set("Content-Length", String(source.size));
41
41
  }
42
- return app.respond(Streamable.stream(source), {
42
+ return app.respond(fs.stream(source), {
43
43
  ...rest, headers: Object.fromEntries(out.entries()),
44
44
  });
45
45
  };
@@ -1,5 +1,8 @@
1
1
  import location from "#location";
2
- import { Status } from "@rcompat/http";
2
+ import http from "@rcompat/http";
3
+ const sse_reload = `<script>
4
+ new EventSource("/esbuild").addEventListener("change", () => location.reload());
5
+ </script>`;
3
6
  /**
4
7
  * Render an error page.
5
8
  *
@@ -11,8 +14,9 @@ import { Status } from "@rcompat/http";
11
14
  export default function error(options) {
12
15
  return app => app.view({
13
16
  body: options?.body ?? "Not Found",
17
+ head: app.mode === "development" ? sse_reload : undefined,
14
18
  page: options?.page ?? location.error_html,
15
- status: options?.status ?? Status.NOT_FOUND,
19
+ status: options?.status ?? http.Status.NOT_FOUND,
16
20
  });
17
21
  }
18
22
  //# sourceMappingURL=error.js.map
@@ -1,10 +1,10 @@
1
1
  import response from "#response";
2
- import { MIME } from "@rcompat/http";
2
+ import http from "@rcompat/http";
3
3
  /**
4
4
  * Issue a JSON response
5
5
  * @param body body object
6
6
  * @param options response options
7
7
  * @return Response rendering function
8
8
  */
9
- export default response(MIME.APPLICATION_JSON, JSON.stringify);
9
+ export default response(http.MIME.APPLICATION_JSON, JSON.stringify);
10
10
  //# sourceMappingURL=json.js.map
@@ -0,0 +1,3 @@
1
+ declare function $null(): () => Response;
2
+ export default $null;
3
+ //# sourceMappingURL=null.d.ts.map
@@ -0,0 +1,6 @@
1
+ import http from "@rcompat/http";
2
+ function $null() {
3
+ return () => new Response(null, { status: http.Status.NO_CONTENT });
4
+ }
5
+ export default $null;
6
+ //# sourceMappingURL=null.js.map
@@ -1,4 +1,5 @@
1
- import { Status } from "@rcompat/http";
1
+ import http from "@rcompat/http";
2
+ import is from "@rcompat/is";
2
3
  function hasCRLF(s) {
3
4
  return /[\r\n]/.test(s);
4
5
  }
@@ -31,7 +32,7 @@ function toSearch(query) {
31
32
  return params;
32
33
  }, new URLSearchParams())
33
34
  .toString();
34
- return search ? "?" + search : "";
35
+ return is.text(search) ? "?" + search : "";
35
36
  }
36
37
  function toNormalized(relative, base) {
37
38
  // base -> URL resolution
@@ -60,6 +61,6 @@ app => app.respond(null, {
60
61
  Location: location,
61
62
  "Cache-Control": "no-cache",
62
63
  },
63
- status: status ?? Status.FOUND,
64
+ status: status ?? http.Status.FOUND,
64
65
  });
65
66
  //# sourceMappingURL=redirect.js.map
@@ -1,19 +1,19 @@
1
1
  import E from "#errors";
2
2
  import binary from "#response/binary";
3
3
  import json from "#response/json";
4
+ import $null from "#response/null";
4
5
  import redirect from "#response/redirect";
5
6
  import text from "#response/text";
6
- import Streamable from "@rcompat/fs/Streamable";
7
- import { Status } from "@rcompat/http";
7
+ import fs from "@rcompat/fs";
8
8
  import is from "@rcompat/is";
9
9
  function match(m) {
10
10
  return m;
11
11
  }
12
12
  // [if, then]
13
13
  const guesses = match([
14
- [is.null, () => () => new Response(null, { status: Status.NO_CONTENT })],
14
+ [is.null, $null],
15
15
  [is.url, value => redirect(value.toString())],
16
- [Streamable.is, value => binary(value)],
16
+ [fs.isStream, value => binary(value)],
17
17
  [is.response, value => _ => value],
18
18
  [is.dict, json],
19
19
  [is.array, json],
@@ -1,5 +1,5 @@
1
1
  import response from "#response";
2
- import { MIME } from "@rcompat/http";
2
+ import http from "@rcompat/http";
3
3
  const encode = (input) => new TextEncoder().encode(input);
4
4
  const handle = (body) => new ReadableStream({
5
5
  cancel() {
@@ -21,5 +21,5 @@ const handle = (body) => new ReadableStream({
21
21
  * @param options response options
22
22
  * @return Response rendering function
23
23
  */
24
- export default response(MIME.TEXT_EVENT_STREAM, handle);
24
+ export default response(http.MIME.TEXT_EVENT_STREAM, handle);
25
25
  //# sourceMappingURL=sse.js.map
@@ -1,11 +1,11 @@
1
1
  import response from "#response";
2
2
  import fn from "@rcompat/fn";
3
- import { MIME } from "@rcompat/http";
3
+ import http from "@rcompat/http";
4
4
  /**
5
5
  * Return a plaintext response
6
6
  * @param body plaintext body
7
7
  * @param options response options
8
8
  * @return Response rendering function
9
9
  */
10
- export default response(MIME.TEXT_PLAIN, fn.identity);
10
+ export default response(http.MIME.TEXT_PLAIN, fn.identity);
11
11
  //# sourceMappingURL=text.js.map
@@ -0,0 +1,10 @@
1
+ import type { Dict, JSONValue } from "@rcompat/type";
2
+ type ContentTypeMap = {
3
+ "application/json": JSONValue;
4
+ "text/plain": string;
5
+ "application/x-www-form-urlencoded": URLSearchParams | Dict<string>;
6
+ "multipart/form-data": FormData | Dict<FormDataEntryValue>;
7
+ "application/octet-stream": Blob;
8
+ };
9
+ export type { ContentTypeMap as default };
10
+ //# sourceMappingURL=ContentTypeMap.d.ts.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=ContentTypeMap.js.map
@@ -1,6 +1,7 @@
1
- import type RequestFacade from "#request/RequestFacade";
2
1
  import type ResponseLike from "#response/ResponseLike";
2
+ import type NarrowedRequest from "#route/NarrowedRequest";
3
+ import type RouteOptions from "#route/Options";
3
4
  import type { MaybePromise } from "@rcompat/type";
4
- type RouteHandler = (request: RequestFacade) => MaybePromise<ResponseLike>;
5
+ type RouteHandler<O extends RouteOptions = RouteOptions> = (request: NarrowedRequest<O>) => MaybePromise<ResponseLike>;
5
6
  export { RouteHandler as default };
6
7
  //# sourceMappingURL=Handler.d.ts.map
@@ -0,0 +1,23 @@
1
+ import type RequestBody from "#request/RequestBody";
2
+ import type RequestFacade from "#request/RequestFacade";
3
+ import type RouteOptions from "#route/Options";
4
+ import type { Unpack } from "@rcompat/type";
5
+ import type { Parsed } from "pema";
6
+ type ContentTypeMethod = {
7
+ "application/json": "json";
8
+ "text/plain": "text";
9
+ "application/x-www-form-urlencoded": "form";
10
+ "multipart/form-data": "multipart";
11
+ "application/octet-stream": "blob";
12
+ };
13
+ type NarrowedBody<O extends RouteOptions, B extends RequestBody> = O extends {
14
+ contentType: infer CT extends keyof ContentTypeMethod;
15
+ body: Parsed<infer T>;
16
+ } ? Omit<B, ContentTypeMethod[CT]> & {
17
+ [K in ContentTypeMethod[CT]]: () => Promise<Unpack<T>>;
18
+ } : B;
19
+ type NarrowedRequest<O extends RouteOptions> = Omit<RequestFacade, "body"> & {
20
+ body: NarrowedBody<O, RequestBody>;
21
+ };
22
+ export type { NarrowedRequest as default };
23
+ //# sourceMappingURL=NarrowedRequest.d.ts.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=NarrowedRequest.js.map
@@ -1,5 +1,10 @@
1
+ import type http from "@rcompat/http";
2
+ import type { Parsed } from "pema";
3
+ type MIME = Omit<typeof http.MIME, "resolve" | "extension">;
4
+ type MIMEValue = MIME[keyof MIME];
1
5
  type RouteOptions = {
2
- parseBody?: boolean;
6
+ contentType?: MIMEValue;
7
+ body?: Parsed<unknown>;
3
8
  };
4
9
  export type { RouteOptions as default };
5
10
  //# sourceMappingURL=Options.d.ts.map
@@ -1,8 +1,8 @@
1
- import type Verb from "#request/Verb";
1
+ import type { Method } from "@rcompat/http";
2
2
  import type RouteHandler from "#route/Handler";
3
3
  import type RouteOptions from "#route/Options";
4
4
  type RoutePath = {
5
- [key in Verb]?: {
5
+ [key in Method]?: {
6
6
  handler: RouteHandler;
7
7
  options: RouteOptions;
8
8
  };
@@ -2,5 +2,7 @@ import type NextRoute from "#module/NextRoute";
2
2
  import type RequestFacade from "#request/RequestFacade";
3
3
  import type ResponseLike from "#response/ResponseLike";
4
4
  import type { MaybePromise } from "@rcompat/type";
5
- export default function hook(fn: (request: RequestFacade, next: NextRoute) => MaybePromise<ResponseLike>): void;
5
+ type HookFn = (request: RequestFacade, next: NextRoute) => MaybePromise<ResponseLike>;
6
+ export default function hook(fn: HookFn): HookFn;
7
+ export {};
6
8
  //# sourceMappingURL=hook.d.ts.map
@@ -1,5 +1,4 @@
1
- import router from "#route/router";
2
1
  export default function hook(fn) {
3
- router.addHook(fn);
2
+ return fn;
4
3
  }
5
4
  //# sourceMappingURL=hook.js.map
@@ -1,15 +1,11 @@
1
1
  import type RequestHook from "#module/RequestHook";
2
- import type Verb from "#request/Verb";
3
2
  import type RouteHandler from "#route/Handler";
4
3
  import type RouteOptions from "#route/Options";
5
- export declare const routes: string[];
4
+ import type { Method } from "@rcompat/http";
6
5
  declare class Router {
7
6
  #private;
8
- push(route: string): void;
9
- pop(): void;
10
- get active(): string | undefined;
11
- add(verb: Verb, handler: RouteHandler, options?: RouteOptions): void;
12
- addHook(fn: RequestHook): void;
7
+ add(path: string, method: Method, handler: RouteHandler, options?: RouteOptions): void;
8
+ addHook(path: string, fn: RequestHook): void;
13
9
  getHooks(path: string): RequestHook[];
14
10
  verifyHook(path: string): void;
15
11
  get(path: string): {
@@ -17,10 +13,6 @@ declare class Router {
17
13
  handler: RouteHandler;
18
14
  options: RouteOptions;
19
15
  } | undefined;
20
- head?: {
21
- handler: RouteHandler;
22
- options: RouteOptions;
23
- } | undefined;
24
16
  get?: {
25
17
  handler: RouteHandler;
26
18
  options: RouteOptions;
@@ -37,6 +29,10 @@ declare class Router {
37
29
  handler: RouteHandler;
38
30
  options: RouteOptions;
39
31
  } | undefined;
32
+ head?: {
33
+ handler: RouteHandler;
34
+ options: RouteOptions;
35
+ } | undefined;
40
36
  connect?: {
41
37
  handler: RouteHandler;
42
38
  options: RouteOptions;
@@ -1,7 +1,5 @@
1
1
  import E from "#errors";
2
2
  import assert from "@rcompat/assert";
3
- const stack = [];
4
- export const routes = stack;
5
3
  function is_hook_file(p) {
6
4
  const basename = p.split("/").at(-1) ?? p;
7
5
  return basename === "+hook" || basename.startsWith("+hook.");
@@ -9,28 +7,23 @@ function is_hook_file(p) {
9
7
  class Router {
10
8
  #routes = {};
11
9
  #hooks = {};
12
- push(route) { stack.push(route); }
13
- pop() { stack.pop(); }
14
- get active() { return stack.at(-1); }
15
- add(verb, handler, options) {
16
- assert.string(verb);
10
+ add(path, method, handler, options) {
11
+ assert.string(path);
12
+ assert.string(method);
17
13
  assert.function(handler);
18
14
  assert.maybe.dict(options);
19
- assert.maybe.boolean(options?.parseBody);
20
- const active = assert.defined(this.active);
21
- if (is_hook_file(active))
22
- throw E.hook_route_functions_not_allowed(active);
23
- const _routes = this.#routes;
24
- if (!(active in _routes))
25
- _routes[active] = {};
26
- _routes[active][verb] = { handler, options: options ?? {} };
15
+ if (is_hook_file(path))
16
+ throw E.hook_route_functions_not_allowed(path);
17
+ if (!(path in this.#routes))
18
+ this.#routes[path] = {};
19
+ this.#routes[path][method] = { handler, options: options ?? {} };
27
20
  }
28
- addHook(fn) {
21
+ addHook(path, fn) {
22
+ assert.string(path);
29
23
  assert.function(fn);
30
- const active = assert.defined(this.active);
31
- if (!is_hook_file(active))
32
- throw E.hook_not_allowed(active);
33
- (this.#hooks[active] ??= []).push(fn);
24
+ if (!is_hook_file(path))
25
+ throw E.hook_not_allowed(path);
26
+ (this.#hooks[path] ??= []).push(fn);
34
27
  }
35
28
  getHooks(path) {
36
29
  return [...(this.#hooks[path] ?? [])];
@@ -0,0 +1,36 @@
1
+ import type ContentTypeMap from "#route/ContentTypeMap";
2
+ import type RouteHandler from "#route/Handler";
3
+ import type RouteOptions from "#route/Options";
4
+ import type { Unpack } from "@rcompat/type";
5
+ import type { Parsed } from "pema";
6
+ type Body<O extends RouteOptions> = O extends {
7
+ contentType: infer _CT extends keyof ContentTypeMap;
8
+ body: infer S extends Parsed<unknown>;
9
+ } ? Unpack<S["infer"]> : O extends {
10
+ contentType: infer CT extends keyof ContentTypeMap;
11
+ } ? ContentTypeMap[CT] : never;
12
+ type MethodMeta = {
13
+ contentType?: string;
14
+ };
15
+ type ClientMethod<O extends RouteOptions> = MethodMeta & (Body<O> extends never ? () => Promise<Response> : (args: {
16
+ body: Body<O>;
17
+ }) => Promise<Response>);
18
+ type ClientRoute<R> = {
19
+ [K in keyof R]: R[K] extends {
20
+ options: infer O extends RouteOptions;
21
+ } ? ClientMethod<O> : () => Promise<Response>;
22
+ };
23
+ type WithResult<O extends RouteOptions> = {
24
+ handler: RouteHandler<O>;
25
+ options: O;
26
+ };
27
+ type RouteHandlers = {
28
+ [key: string]: RouteHandler | WithResult<RouteOptions>;
29
+ };
30
+ declare function route<R extends RouteHandlers>(handlers: R): ClientRoute<R>;
31
+ declare namespace route {
32
+ var _a: <O extends RouteOptions>(options: O, handler: RouteHandler<O>) => WithResult<O>;
33
+ export { _a as with };
34
+ }
35
+ export default route;
36
+ //# sourceMappingURL=route.client.d.ts.map
@@ -0,0 +1,8 @@
1
+ function route(handlers) {
2
+ return {};
3
+ }
4
+ route.with = function (options, handler) {
5
+ return { handler, options };
6
+ };
7
+ export default route;
8
+ //# sourceMappingURL=route.client.js.map
@@ -1,9 +1,25 @@
1
- import type Verb from "#request/Verb";
2
1
  import type RouteHandler from "#route/Handler";
3
2
  import type RouteOptions from "#route/Options";
4
- type Route = {
5
- [key in Verb]: (handler: RouteHandler, options?: RouteOptions) => void;
3
+ import type { Method } from "@rcompat/http";
4
+ declare const BRAND: unique symbol;
5
+ type WithResult<O extends RouteOptions> = {
6
+ [BRAND]: true;
7
+ handler: RouteHandler;
8
+ options: O;
6
9
  };
7
- declare const _default: Route;
8
- export default _default;
10
+ type AnyHandler = RouteHandler | WithResult<RouteOptions>;
11
+ type RouteHandlers = {
12
+ [key in Method]?: AnyHandler;
13
+ };
14
+ declare function route(handlers: RouteHandlers): {
15
+ [k: string]: {
16
+ handler: RouteHandler;
17
+ options: RouteOptions;
18
+ };
19
+ };
20
+ declare namespace route {
21
+ var _a: <O extends RouteOptions>(options: O, handler: RouteHandler<O>) => WithResult<O>;
22
+ export { _a as with };
23
+ }
24
+ export default route;
9
25
  //# sourceMappingURL=route.d.ts.map
@@ -1,6 +1,22 @@
1
- import verbs from "#request/verbs";
2
- import router from "#route/router";
3
- export default Object.fromEntries(verbs.map(verb => [verb, (handler, options) => {
4
- router.add(verb, handler, options);
5
- }]));
1
+ import E from "#errors";
2
+ import is from "@rcompat/is";
3
+ const BRAND = Symbol("route.with");
4
+ function is_with(value) {
5
+ return is.branded(value, BRAND);
6
+ }
7
+ function route(handlers) {
8
+ return Object.fromEntries(Object.entries(handlers).map(([method, value]) => {
9
+ if (is_with(value)) {
10
+ return [method, { handler: value.handler, options: value.options }];
11
+ }
12
+ return [method, { handler: value, options: {} }];
13
+ }));
14
+ }
15
+ route.with = function (options, handler) {
16
+ if (options.body !== undefined && options.contentType === undefined) {
17
+ throw E.build_body_requires_content_type();
18
+ }
19
+ return { [BRAND]: true, handler, options };
20
+ };
21
+ export default route;
6
22
  //# sourceMappingURL=route.js.map
@@ -22,9 +22,7 @@ interface PublishOptions {
22
22
  export default class ServeApp extends App {
23
23
  #private;
24
24
  constructor(rootfile: string, init: ServeInit);
25
- get secure(): boolean;
26
25
  get assets(): Asset[];
27
- get url(): string;
28
26
  get router(): FileRouter;
29
27
  get frontends(): {
30
28
  [k: string]: ViewResponse;
@@ -54,6 +52,7 @@ export default class ServeApp extends App {
54
52
  layouts: RouteHandler[];
55
53
  handler: RouteHandler;
56
54
  request: any;
55
+ is_head: boolean;
57
56
  } | undefined>;
58
57
  }
59
58
  export {};