@raiyyan/brisk 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (206) hide show
  1. package/AGENTS.md +535 -0
  2. package/LICENSE +21 -0
  3. package/README.md +1745 -2
  4. package/dist/bin/brisk.d.ts +3 -0
  5. package/dist/bin/brisk.d.ts.map +1 -0
  6. package/dist/bin/brisk.js +13 -0
  7. package/dist/bin/brisk.js.map +1 -0
  8. package/dist/src/cli/agents.d.ts +7 -0
  9. package/dist/src/cli/agents.d.ts.map +1 -0
  10. package/dist/src/cli/agents.js +55 -0
  11. package/dist/src/cli/agents.js.map +1 -0
  12. package/dist/src/cli/cli.d.ts +5 -0
  13. package/dist/src/cli/cli.d.ts.map +1 -0
  14. package/dist/src/cli/cli.js +113 -0
  15. package/dist/src/cli/cli.js.map +1 -0
  16. package/dist/src/cli/client.d.ts +12 -0
  17. package/dist/src/cli/client.d.ts.map +1 -0
  18. package/dist/src/cli/client.js +76 -0
  19. package/dist/src/cli/client.js.map +1 -0
  20. package/dist/src/cli/init.d.ts +16 -0
  21. package/dist/src/cli/init.d.ts.map +1 -0
  22. package/dist/src/cli/init.js +149 -0
  23. package/dist/src/cli/init.js.map +1 -0
  24. package/dist/src/cli/names.d.ts +14 -0
  25. package/dist/src/cli/names.d.ts.map +1 -0
  26. package/dist/src/cli/names.js +25 -0
  27. package/dist/src/cli/names.js.map +1 -0
  28. package/dist/src/cli/render.d.ts +30 -0
  29. package/dist/src/cli/render.d.ts.map +1 -0
  30. package/dist/src/cli/render.js +101 -0
  31. package/dist/src/cli/render.js.map +1 -0
  32. package/dist/src/cli/system.d.ts +35 -0
  33. package/dist/src/cli/system.d.ts.map +1 -0
  34. package/dist/src/cli/system.js +81 -0
  35. package/dist/src/cli/system.js.map +1 -0
  36. package/dist/src/client/client.d.ts +64 -0
  37. package/dist/src/client/client.d.ts.map +1 -0
  38. package/dist/src/client/client.js +170 -0
  39. package/dist/src/client/client.js.map +1 -0
  40. package/dist/src/client/index.d.ts +8 -0
  41. package/dist/src/client/index.d.ts.map +1 -0
  42. package/dist/src/client/index.js +7 -0
  43. package/dist/src/client/index.js.map +1 -0
  44. package/dist/src/config/env.d.ts +28 -0
  45. package/dist/src/config/env.d.ts.map +1 -0
  46. package/dist/src/config/env.js +35 -0
  47. package/dist/src/config/env.js.map +1 -0
  48. package/dist/src/config/logger.d.ts +32 -0
  49. package/dist/src/config/logger.d.ts.map +1 -0
  50. package/dist/src/config/logger.js +31 -0
  51. package/dist/src/config/logger.js.map +1 -0
  52. package/dist/src/di/container.d.ts +194 -0
  53. package/dist/src/di/container.d.ts.map +1 -0
  54. package/dist/src/di/container.js +225 -0
  55. package/dist/src/di/container.js.map +1 -0
  56. package/dist/src/di/index.d.ts +8 -0
  57. package/dist/src/di/index.d.ts.map +1 -0
  58. package/dist/src/di/index.js +8 -0
  59. package/dist/src/di/index.js.map +1 -0
  60. package/dist/src/errors/http-errors.d.ts +295 -0
  61. package/dist/src/errors/http-errors.d.ts.map +1 -0
  62. package/dist/src/errors/http-errors.js +311 -0
  63. package/dist/src/errors/http-errors.js.map +1 -0
  64. package/dist/src/errors/index.d.ts +8 -0
  65. package/dist/src/errors/index.d.ts.map +1 -0
  66. package/dist/src/errors/index.js +8 -0
  67. package/dist/src/errors/index.js.map +1 -0
  68. package/dist/src/http/app.d.ts +229 -0
  69. package/dist/src/http/app.d.ts.map +1 -0
  70. package/dist/src/http/app.js +45 -0
  71. package/dist/src/http/app.js.map +1 -0
  72. package/dist/src/http/context.d.ts +66 -0
  73. package/dist/src/http/context.d.ts.map +1 -0
  74. package/dist/src/http/context.js +96 -0
  75. package/dist/src/http/context.js.map +1 -0
  76. package/dist/src/http/controller.d.ts +195 -0
  77. package/dist/src/http/controller.d.ts.map +1 -0
  78. package/dist/src/http/controller.js +290 -0
  79. package/dist/src/http/controller.js.map +1 -0
  80. package/dist/src/http/error-handler.d.ts +86 -0
  81. package/dist/src/http/error-handler.d.ts.map +1 -0
  82. package/dist/src/http/error-handler.js +147 -0
  83. package/dist/src/http/error-handler.js.map +1 -0
  84. package/dist/src/http/instance.d.ts +14 -0
  85. package/dist/src/http/instance.d.ts.map +1 -0
  86. package/dist/src/http/instance.js +377 -0
  87. package/dist/src/http/instance.js.map +1 -0
  88. package/dist/src/http/middleware.d.ts +141 -0
  89. package/dist/src/http/middleware.d.ts.map +1 -0
  90. package/dist/src/http/middleware.js +160 -0
  91. package/dist/src/http/middleware.js.map +1 -0
  92. package/dist/src/http/openapi.d.ts +34 -0
  93. package/dist/src/http/openapi.d.ts.map +1 -0
  94. package/dist/src/http/openapi.js +151 -0
  95. package/dist/src/http/openapi.js.map +1 -0
  96. package/dist/src/http/path-params.d.ts +24 -0
  97. package/dist/src/http/path-params.d.ts.map +1 -0
  98. package/dist/src/http/path-params.js +2 -0
  99. package/dist/src/http/path-params.js.map +1 -0
  100. package/dist/src/http/routes.d.ts +141 -0
  101. package/dist/src/http/routes.d.ts.map +1 -0
  102. package/dist/src/http/routes.js +2 -0
  103. package/dist/src/http/routes.js.map +1 -0
  104. package/dist/src/http/server.d.ts +23 -0
  105. package/dist/src/http/server.d.ts.map +1 -0
  106. package/dist/src/http/server.js +22 -0
  107. package/dist/src/http/server.js.map +1 -0
  108. package/dist/src/http/validation.d.ts +16 -0
  109. package/dist/src/http/validation.d.ts.map +1 -0
  110. package/dist/src/http/validation.js +51 -0
  111. package/dist/src/http/validation.js.map +1 -0
  112. package/dist/src/index.d.ts +22 -0
  113. package/dist/src/index.d.ts.map +1 -0
  114. package/dist/src/index.js +19 -0
  115. package/dist/src/index.js.map +1 -0
  116. package/dist/src/testing/index.d.ts +7 -0
  117. package/dist/src/testing/index.d.ts.map +1 -0
  118. package/dist/src/testing/index.js +7 -0
  119. package/dist/src/testing/index.js.map +1 -0
  120. package/dist/src/testing/test-client.d.ts +67 -0
  121. package/dist/src/testing/test-client.d.ts.map +1 -0
  122. package/dist/src/testing/test-client.js +78 -0
  123. package/dist/src/testing/test-client.js.map +1 -0
  124. package/package.json +89 -3
  125. package/starters/minimal/.env.example +3 -0
  126. package/starters/minimal/.oxfmtrc.json +5 -0
  127. package/starters/minimal/.oxlintrc.json +14 -0
  128. package/starters/minimal/AGENTS.md +23 -0
  129. package/starters/minimal/CLAUDE.md +2 -0
  130. package/starters/minimal/README.md +53 -0
  131. package/starters/minimal/_gitignore +3 -0
  132. package/starters/minimal/package.json +29 -0
  133. package/starters/minimal/src/app.ts +14 -0
  134. package/starters/minimal/src/env.ts +7 -0
  135. package/starters/minimal/src/features/index.ts +8 -0
  136. package/starters/minimal/src/features/notes/notes.controller.ts +38 -0
  137. package/starters/minimal/src/features/notes/notes.repository.ts +36 -0
  138. package/starters/minimal/src/features/notes/notes.schema.ts +24 -0
  139. package/starters/minimal/src/features/notes/notes.service.ts +33 -0
  140. package/starters/minimal/src/main.ts +4 -0
  141. package/starters/minimal/src/schemas.ts +26 -0
  142. package/starters/minimal/templates/feature/src/features/index.ts.controllers.insert +1 -0
  143. package/starters/minimal/templates/feature/src/features/index.ts.imports.insert +1 -0
  144. package/starters/minimal/templates/feature/src/features/{{kebab}}/{{kebab}}.controller.ts +38 -0
  145. package/starters/minimal/templates/feature/src/features/{{kebab}}/{{kebab}}.repository.ts +36 -0
  146. package/starters/minimal/templates/feature/src/features/{{kebab}}/{{kebab}}.schema.ts +24 -0
  147. package/starters/minimal/templates/feature/src/features/{{kebab}}/{{kebab}}.service.ts +33 -0
  148. package/starters/minimal/templates/feature/test/{{kebab}}.service.test.ts +20 -0
  149. package/starters/minimal/templates/feature/test/{{kebab}}.test.ts +35 -0
  150. package/starters/minimal/templates/middleware/src/middlewares/{{kebab}}.ts +12 -0
  151. package/starters/minimal/test/notes.service.test.ts +20 -0
  152. package/starters/minimal/test/notes.test.ts +35 -0
  153. package/starters/minimal/tsconfig.json +17 -0
  154. package/starters/minimal/vitest.config.ts +9 -0
  155. package/starters/sqlite-auth/.dockerignore +8 -0
  156. package/starters/sqlite-auth/.env.example +9 -0
  157. package/starters/sqlite-auth/.oxfmtrc.json +5 -0
  158. package/starters/sqlite-auth/.oxlintrc.json +14 -0
  159. package/starters/sqlite-auth/AGENTS.md +23 -0
  160. package/starters/sqlite-auth/CLAUDE.md +2 -0
  161. package/starters/sqlite-auth/Dockerfile +23 -0
  162. package/starters/sqlite-auth/README.md +93 -0
  163. package/starters/sqlite-auth/_gitignore +5 -0
  164. package/starters/sqlite-auth/compose.yaml +14 -0
  165. package/starters/sqlite-auth/package.json +35 -0
  166. package/starters/sqlite-auth/public/.gitkeep +0 -0
  167. package/starters/sqlite-auth/src/app.ts +39 -0
  168. package/starters/sqlite-auth/src/auth/tokens.ts +41 -0
  169. package/starters/sqlite-auth/src/database/clock.ts +4 -0
  170. package/starters/sqlite-auth/src/database/database.ts +29 -0
  171. package/starters/sqlite-auth/src/database/errors.ts +11 -0
  172. package/starters/sqlite-auth/src/database/migrations.ts +29 -0
  173. package/starters/sqlite-auth/src/env.ts +17 -0
  174. package/starters/sqlite-auth/src/features/auth/auth.controller.ts +28 -0
  175. package/starters/sqlite-auth/src/features/index.ts +12 -0
  176. package/starters/sqlite-auth/src/features/notes/notes.controller.ts +39 -0
  177. package/starters/sqlite-auth/src/features/notes/notes.repository.ts +47 -0
  178. package/starters/sqlite-auth/src/features/notes/notes.schema.ts +24 -0
  179. package/starters/sqlite-auth/src/features/notes/notes.service.ts +33 -0
  180. package/starters/sqlite-auth/src/features/users/admin-seed.ts +21 -0
  181. package/starters/sqlite-auth/src/features/users/users.controller.ts +42 -0
  182. package/starters/sqlite-auth/src/features/users/users.errors.ts +13 -0
  183. package/starters/sqlite-auth/src/features/users/users.repository.ts +77 -0
  184. package/starters/sqlite-auth/src/features/users/users.schema.ts +35 -0
  185. package/starters/sqlite-auth/src/features/users/users.service.ts +80 -0
  186. package/starters/sqlite-auth/src/main.ts +4 -0
  187. package/starters/sqlite-auth/src/middlewares/authenticate.ts +29 -0
  188. package/starters/sqlite-auth/src/schemas.ts +26 -0
  189. package/starters/sqlite-auth/templates/feature/src/database/migrations.ts.migrations.insert +5 -0
  190. package/starters/sqlite-auth/templates/feature/src/features/index.ts.controllers.insert +1 -0
  191. package/starters/sqlite-auth/templates/feature/src/features/index.ts.imports.insert +1 -0
  192. package/starters/sqlite-auth/templates/feature/src/features/{{kebab}}/{{kebab}}.controller.ts +39 -0
  193. package/starters/sqlite-auth/templates/feature/src/features/{{kebab}}/{{kebab}}.repository.ts +47 -0
  194. package/starters/sqlite-auth/templates/feature/src/features/{{kebab}}/{{kebab}}.schema.ts +24 -0
  195. package/starters/sqlite-auth/templates/feature/src/features/{{kebab}}/{{kebab}}.service.ts +33 -0
  196. package/starters/sqlite-auth/templates/feature/test/{{kebab}}.service.test.ts +20 -0
  197. package/starters/sqlite-auth/templates/feature/test/{{kebab}}.test.ts +41 -0
  198. package/starters/sqlite-auth/templates/middleware/src/middlewares/{{kebab}}.ts +12 -0
  199. package/starters/sqlite-auth/test/auth.test.ts +18 -0
  200. package/starters/sqlite-auth/test/helpers.ts +13 -0
  201. package/starters/sqlite-auth/test/notes.service.test.ts +20 -0
  202. package/starters/sqlite-auth/test/notes.test.ts +41 -0
  203. package/starters/sqlite-auth/test/users.service.test.ts +25 -0
  204. package/starters/sqlite-auth/test/users.test.ts +164 -0
  205. package/starters/sqlite-auth/tsconfig.json +17 -0
  206. package/starters/sqlite-auth/vitest.config.ts +15 -0
@@ -0,0 +1,160 @@
1
+ import { HandlerContext, requestMemo } from "./context.js";
2
+ import { parseInput, Pending, settle } from "./validation.js";
3
+ export function defineMiddleware(optionsOrFn, maybeFn) {
4
+ if (typeof optionsOrFn === "function")
5
+ return new DefinedMiddleware(optionsOrFn, {});
6
+ if (typeof maybeFn !== "function") {
7
+ throw new TypeError("defineMiddleware takes (fn) or (options, fn)");
8
+ }
9
+ return new DefinedMiddleware(maybeFn, optionsOrFn);
10
+ }
11
+ const EMPTY = Object.freeze({});
12
+ /**
13
+ * Runs an Express middleware as a brisk middleware, in order with the others in `use`: a body parser such as
14
+ * multer then runs after authentication, on the one route that needs it. An error it passes to `next` goes
15
+ * to the app's error handler; when it answers the request itself, the route stops there.
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * upload: route.post(
20
+ * "/:taskId/attachments",
21
+ * { use: [authenticate, fromExpress(multer({ limits: { fileSize: 5_000_000 } }).single("file"))] },
22
+ * ({ req, params }) => attachments.save(params.taskId, req.file),
23
+ * ),
24
+ * ```
25
+ */
26
+ export function fromExpress(handler) {
27
+ return new ExpressMiddleware(handler);
28
+ }
29
+ /**
30
+ * An Express middleware wrapped by {@link fromExpress}. In a route's `use` it runs in order with the others;
31
+ * in the app's `use` the app registers `handler` itself as a plain Express layer, at no extra cost.
32
+ */
33
+ export class ExpressMiddleware {
34
+ handler;
35
+ constructor(handler) {
36
+ this.handler = handler;
37
+ }
38
+ run({ req, res }) {
39
+ return new Promise((resolve, reject) => {
40
+ const answered = () => resolve(EMPTY);
41
+ res.once("finish", answered);
42
+ const next = (error) => {
43
+ res.off("finish", answered);
44
+ if (error === undefined || error === "route" || error === "router")
45
+ resolve(EMPTY);
46
+ else
47
+ reject(error);
48
+ };
49
+ Promise.resolve(this.handler(req, res, next)).catch(reject);
50
+ });
51
+ }
52
+ }
53
+ /**
54
+ * A middleware made by {@link defineMiddleware}. Its result is memoized in the request's own context: a plain
55
+ * value when `fn` is synchronous, so sync middlewares never allocate a promise on the request path, and a
56
+ * promise (replaced by its value once settled) when `fn` is async.
57
+ */
58
+ class DefinedMiddleware {
59
+ security;
60
+ #fn;
61
+ #params;
62
+ #query;
63
+ constructor(fn, options) {
64
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- fn's ctx type was checked by the overload that declared it
65
+ this.#fn = fn;
66
+ this.#params = options.params;
67
+ this.#query = options.query;
68
+ this.security = options.security;
69
+ }
70
+ run(ctx) {
71
+ try {
72
+ const out = this.resolve(ctx);
73
+ return out instanceof Promise ? out : Promise.resolve(out);
74
+ }
75
+ catch (error) {
76
+ return Promise.reject(error);
77
+ }
78
+ }
79
+ resolve(ctx) {
80
+ const memo = requestMemo(ctx);
81
+ const cached = memo.get(this);
82
+ if (cached instanceof Promise)
83
+ return cached.then((settled) => this.#output(settled));
84
+ if (cached !== undefined)
85
+ return this.#output(cached);
86
+ const out = this.#call(ctx);
87
+ if (!(out instanceof Promise)) {
88
+ const value = this.#output(out);
89
+ memo.set(this, value);
90
+ return value;
91
+ }
92
+ const pending = out.then((settled) => {
93
+ const value = this.#output(settled);
94
+ memo.set(this, value);
95
+ return value;
96
+ });
97
+ memo.set(this, pending);
98
+ return pending;
99
+ }
100
+ /** Calls `fn`, first validating the input it declared onto a ctx of its own. */
101
+ #call(ctx) {
102
+ if (!this.#params && !this.#query)
103
+ return this.#fn(ctx);
104
+ const input = new HandlerContext(ctx);
105
+ const params = parseInput(this.#params, ctx.req.params, "params");
106
+ const query = parseInput(this.#query, ctx.req.query, "query");
107
+ if (params instanceof Pending || query instanceof Pending) {
108
+ return Promise.all([settle(params), settle(query)]).then(([p, q]) => {
109
+ input.params = p;
110
+ input.query = q;
111
+ return this.#fn(input);
112
+ });
113
+ }
114
+ input.params = params;
115
+ input.query = query;
116
+ return this.#fn(input);
117
+ }
118
+ /** What `fn` returned, as the output its signature declared (`{}` for a check-only middleware). */
119
+ #output(out) {
120
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- fn is typed to return Out (or nothing)
121
+ return (typeof out === "object" && out !== null ? out : EMPTY);
122
+ }
123
+ }
124
+ /**
125
+ * Runs `list` in order against the request's context and writes each output onto `target`. Stays synchronous
126
+ * while the middlewares are, and returns a promise only from the first async one on.
127
+ */
128
+ export function applyMiddlewares(base, target, list, from = 0) {
129
+ for (let index = from; index < list.length; index++) {
130
+ const middleware = list[index];
131
+ if (!middleware)
132
+ continue;
133
+ const out = resolveMiddleware(middleware, base);
134
+ if (out instanceof Promise) {
135
+ return out.then((value) => {
136
+ Object.assign(target, value);
137
+ return applyMiddlewares(base, target, list, index + 1);
138
+ });
139
+ }
140
+ Object.assign(target, out);
141
+ }
142
+ return undefined;
143
+ }
144
+ /**
145
+ * Runs one middleware for the request, reusing its memoized result: a value when it stays synchronous, a
146
+ * promise otherwise.
147
+ */
148
+ export function resolveMiddleware(middleware, ctx) {
149
+ return isDefined(middleware) ? middleware.resolve(ctx) : middleware.run(ctx);
150
+ }
151
+ /** The security schemes a list of middlewares enforces, merged; `undefined` when none does. */
152
+ export function securityOf(middlewares) {
153
+ const entries = middlewares.flatMap((middleware) => Object.entries(middleware.security ?? {}));
154
+ return entries.length > 0 ? Object.fromEntries(entries) : undefined;
155
+ }
156
+ /** `instanceof` on a generic class narrows to `any` type arguments; this keeps the output typed as `object`. */
157
+ function isDefined(middleware) {
158
+ return middleware instanceof DefinedMiddleware;
159
+ }
160
+ //# sourceMappingURL=middleware.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../../src/http/middleware.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ import type { Endpoint } from "./controller.ts";
2
+ /**
3
+ * The OpenAPI document an app serves: `openapi: { title: "Shop API", version: "1.0.0" }`.
4
+ */
5
+ export interface OpenApiOptions {
6
+ title: string;
7
+ version: string;
8
+ description?: string;
9
+ /** Base URLs of the API, such as `[{ url: "https://api.example.com" }]`. Default: same origin. */
10
+ servers?: readonly {
11
+ url: string;
12
+ description?: string;
13
+ }[];
14
+ /** Where the document is served. Default `/openapi.json`; `false` to only build it with `app.openapi()`. */
15
+ path?: string | false;
16
+ }
17
+ /** An endpoint as the document lists it: the controller's name is its tag. */
18
+ export interface DocumentedEndpoint extends Endpoint {
19
+ tag: string;
20
+ }
21
+ type JsonObject = Record<string, unknown>;
22
+ /**
23
+ * Builds an OpenAPI 3.1 document from the routes' Zod schemas. Requests are documented as clients send them
24
+ * (`io: "input"`) and responses as clients receive them (`io: "output"`). A named route's dotted name is its
25
+ * `operationId`, such as `users.get`: the same name the typed client uses.
26
+ */
27
+ export declare function buildOpenApi(endpoints: readonly DocumentedEndpoint[], options: OpenApiOptions): JsonObject;
28
+ /**
29
+ * Converts an Express path: `:id` and `*path` become `{id}` and `{path}`. OpenAPI has no optional segments, so
30
+ * a `{/:slug}` group gives two paths, one with it and one without.
31
+ */
32
+ export declare function openApiPaths(path: string): string[];
33
+ export {};
34
+ //# sourceMappingURL=openapi.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openapi.d.ts","sourceRoot":"","sources":["../../../src/http/openapi.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAEhD;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kGAAkG;IAClG,OAAO,CAAC,EAAE,SAAS;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC3D,4GAA4G;IAC5G,IAAI,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;CACvB;AAED,8EAA8E;AAC9E,MAAM,WAAW,kBAAmB,SAAQ,QAAQ;IAClD,GAAG,EAAE,MAAM,CAAC;CACb;AAED,KAAK,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAmB1C;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,SAAS,kBAAkB,EAAE,EAAE,OAAO,EAAE,cAAc,GAAG,UAAU,CAwB1G;AAyCD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAOnD"}
@@ -0,0 +1,151 @@
1
+ import { STATUS_CODES } from "node:http";
2
+ import { z } from "zod";
3
+ const ERROR_RESPONSE = {
4
+ type: "object",
5
+ required: ["error"],
6
+ properties: {
7
+ error: {
8
+ type: "object",
9
+ required: ["code", "message", "requestId"],
10
+ properties: {
11
+ code: { type: "string", examples: ["NOT_FOUND"] },
12
+ message: { type: "string" },
13
+ details: {},
14
+ requestId: { type: "string" },
15
+ },
16
+ },
17
+ },
18
+ };
19
+ /**
20
+ * Builds an OpenAPI 3.1 document from the routes' Zod schemas. Requests are documented as clients send them
21
+ * (`io: "input"`) and responses as clients receive them (`io: "output"`). A named route's dotted name is its
22
+ * `operationId`, such as `users.get`: the same name the typed client uses.
23
+ */
24
+ export function buildOpenApi(endpoints, options) {
25
+ const paths = {};
26
+ const securitySchemes = {};
27
+ for (const endpoint of endpoints) {
28
+ Object.assign(securitySchemes, endpoint.security);
29
+ for (const path of openApiPaths(endpoint.path)) {
30
+ const item = (paths[path] ??= {});
31
+ item[endpoint.method] = operation(endpoint, path);
32
+ }
33
+ }
34
+ return {
35
+ openapi: "3.1.1",
36
+ info: {
37
+ title: options.title,
38
+ version: options.version,
39
+ ...(options.description === undefined ? {} : { description: options.description }),
40
+ },
41
+ ...(options.servers ? { servers: options.servers } : {}),
42
+ paths,
43
+ components: {
44
+ schemas: { ErrorResponse: ERROR_RESPONSE },
45
+ ...(Object.keys(securitySchemes).length > 0 ? { securitySchemes } : {}),
46
+ },
47
+ };
48
+ }
49
+ function operation(endpoint, path) {
50
+ const { schemas } = endpoint;
51
+ const parameters = [
52
+ ...pathParameters(path, schemas.params),
53
+ ...fieldParameters(schemas.query, "query"),
54
+ ...fieldParameters(schemas.headers, "header"),
55
+ ];
56
+ const success = String(endpoint.status ?? (endpoint.method === "post" ? 201 : 200));
57
+ const responses = {
58
+ [success]: schemas.response
59
+ ? {
60
+ description: describe(success),
61
+ content: { "application/json": { schema: jsonSchema(schemas.response, "output") } },
62
+ }
63
+ : { description: describe(success) },
64
+ default: {
65
+ description: "Error",
66
+ content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } },
67
+ },
68
+ };
69
+ return {
70
+ tags: [endpoint.tag],
71
+ operationId: endpoint.name === undefined ? undefined : `${endpoint.tag}.${endpoint.name}`,
72
+ ...(endpoint.security
73
+ ? { security: [Object.fromEntries(Object.keys(endpoint.security).map((name) => [name, []]))] }
74
+ : {}),
75
+ ...(parameters.length > 0 ? { parameters } : {}),
76
+ ...(schemas.body
77
+ ? {
78
+ requestBody: {
79
+ required: true,
80
+ content: { "application/json": { schema: jsonSchema(schemas.body, "input") } },
81
+ },
82
+ }
83
+ : {}),
84
+ responses,
85
+ };
86
+ }
87
+ /**
88
+ * Converts an Express path: `:id` and `*path` become `{id}` and `{path}`. OpenAPI has no optional segments, so
89
+ * a `{/:slug}` group gives two paths, one with it and one without.
90
+ */
91
+ export function openApiPaths(path) {
92
+ const group = /\{([^{}]*)\}/.exec(path);
93
+ if (group) {
94
+ const [whole, inner = ""] = group;
95
+ return [...openApiPaths(path.replace(whole, inner)), ...openApiPaths(path.replace(whole, ""))];
96
+ }
97
+ return [path.replace(/[:*]([\w$]+)/g, "{$1}")];
98
+ }
99
+ function pathParameters(path, schema) {
100
+ const properties = schema ? objectProperties(jsonSchema(schema, "input")) : {};
101
+ return [...path.matchAll(/\{([\w$]+)\}/g)].map(([, name = ""]) => ({
102
+ name,
103
+ in: "path",
104
+ required: true,
105
+ schema: properties[name] ?? { type: "string" },
106
+ }));
107
+ }
108
+ function fieldParameters(schema, location) {
109
+ if (!schema)
110
+ return [];
111
+ const json = jsonSchema(schema, "input");
112
+ const required = new Set(Array.isArray(json["required"]) ? json["required"] : []);
113
+ return Object.entries(objectProperties(json)).map(([name, property]) => ({
114
+ name,
115
+ in: location,
116
+ required: required.has(name),
117
+ schema: property,
118
+ }));
119
+ }
120
+ function jsonSchema(schema, io) {
121
+ const { $schema: _dialect, ...json } = z.toJSONSchema(schema, { io, unrepresentable: "any" });
122
+ return withoutFormatPatterns(json);
123
+ }
124
+ /**
125
+ * Removes `pattern` wherever `format` already names the type (`email`, `uuid`, `date-time`). Zod emits both,
126
+ * and docs UIs such as Swagger UI build example values from the regex: several KB of noise for an email.
127
+ */
128
+ function withoutFormatPatterns(json) {
129
+ const result = {};
130
+ for (const [key, value] of Object.entries(json)) {
131
+ if (key === "pattern" && typeof json["format"] === "string")
132
+ continue;
133
+ result[key] = clean(value);
134
+ }
135
+ return result;
136
+ }
137
+ function clean(value) {
138
+ if (Array.isArray(value))
139
+ return value.map(clean);
140
+ if (typeof value !== "object" || value === null)
141
+ return value;
142
+ return withoutFormatPatterns({ ...value });
143
+ }
144
+ function objectProperties(json) {
145
+ const properties = json["properties"];
146
+ return typeof properties === "object" && properties !== null ? { ...properties } : {};
147
+ }
148
+ function describe(status) {
149
+ return STATUS_CODES[Number(status)] ?? "Success";
150
+ }
151
+ //# sourceMappingURL=openapi.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openapi.js","sourceRoot":"","sources":["../../../src/http/openapi.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAuBxB,MAAM,cAAc,GAAe;IACjC,IAAI,EAAE,QAAQ;IACd,QAAQ,EAAE,CAAC,OAAO,CAAC;IACnB,UAAU,EAAE;QACV,KAAK,EAAE;YACL,IAAI,EAAE,QAAQ;YACd,QAAQ,EAAE,CAAC,MAAM,EAAE,SAAS,EAAE,WAAW,CAAC;YAC1C,UAAU,EAAE;gBACV,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,WAAW,CAAC,EAAE;gBACjD,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;gBAC3B,OAAO,EAAE,EAAE;gBACX,SAAS,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;aAC9B;SACF;KACF;CACF,CAAC;AAEF;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,SAAwC,EAAE,OAAuB;IAC5F,MAAM,KAAK,GAA+B,EAAE,CAAC;IAC7C,MAAM,eAAe,GAA4B,EAAE,CAAC;IACpD,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;QACjC,MAAM,CAAC,MAAM,CAAC,eAAe,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAClD,KAAK,MAAM,IAAI,IAAI,YAAY,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;YAC/C,MAAM,IAAI,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;YAClC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,SAAS,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QACpD,CAAC;IACH,CAAC;IACD,OAAO;QACL,OAAO,EAAE,OAAO;QAChB,IAAI,EAAE;YACJ,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,GAAG,CAAC,OAAO,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,OAAO,CAAC,WAAW,EAAE,CAAC;SACnF;QACD,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACxD,KAAK;QACL,UAAU,EAAE;YACV,OAAO,EAAE,EAAE,aAAa,EAAE,cAAc,EAAE;YAC1C,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,eAAe,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACxE;KACF,CAAC;AACJ,CAAC;AAED,SAAS,SAAS,CAAC,QAA4B,EAAE,IAAY;IAC3D,MAAM,EAAE,OAAO,EAAE,GAAG,QAAQ,CAAC;IAC7B,MAAM,UAAU,GAAG;QACjB,GAAG,cAAc,CAAC,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC;QACvC,GAAG,eAAe,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC;QAC1C,GAAG,eAAe,CAAC,OAAO,CAAC,OAAO,EAAE,QAAQ,CAAC;KAC9C,CAAC;IACF,MAAM,OAAO,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IACpF,MAAM,SAAS,GAAe;QAC5B,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,QAAQ;YACzB,CAAC,CAAC;gBACE,WAAW,EAAE,QAAQ,CAAC,OAAO,CAAC;gBAC9B,OAAO,EAAE,EAAE,kBAAkB,EAAE,EAAE,MAAM,EAAE,UAAU,CAAC,OAAO,CAAC,QAAQ,EAAE,QAAQ,CAAC,EAAE,EAAE;aACpF;YACH,CAAC,CAAC,EAAE,WAAW,EAAE,QAAQ,CAAC,OAAO,CAAC,EAAE;QACtC,OAAO,EAAE;YACP,WAAW,EAAE,OAAO;YACpB,OAAO,EAAE,EAAE,kBAAkB,EAAE,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,oCAAoC,EAAE,EAAE,EAAE;SAC5F;KACF,CAAC;IACF,OAAO;QACL,IAAI,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC;QACpB,WAAW,EAAE,QAAQ,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,GAAG,IAAI,QAAQ,CAAC,IAAI,EAAE;QACzF,GAAG,CAAC,QAAQ,CAAC,QAAQ;YACnB,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE;YAC9F,CAAC,CAAC,EAAE,CAAC;QACP,GAAG,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAChD,GAAG,CAAC,OAAO,CAAC,IAAI;YACd,CAAC,CAAC;gBACE,WAAW,EAAE;oBACX,QAAQ,EAAE,IAAI;oBACd,OAAO,EAAE,EAAE,kBAAkB,EAAE,EAAE,MAAM,EAAE,UAAU,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,EAAE,EAAE;iBAC/E;aACF;YACH,CAAC,CAAC,EAAE,CAAC;QACP,SAAS;KACV,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,MAAM,KAAK,GAAG,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACxC,IAAI,KAAK,EAAE,CAAC;QACV,MAAM,CAAC,KAAK,EAAE,KAAK,GAAG,EAAE,CAAC,GAAG,KAAK,CAAC;QAClC,OAAO,CAAC,GAAG,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,EAAE,GAAG,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;IACjG,CAAC;IACD,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,eAAe,EAAE,MAAM,CAAC,CAAC,CAAC;AACjD,CAAC;AAED,SAAS,cAAc,CAAC,IAAY,EAAE,MAA6B;IACjE,MAAM,UAAU,GAAG,MAAM,CAAC,CAAC,CAAC,gBAAgB,CAAC,UAAU,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAC/E,OAAO,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;QACjE,IAAI;QACJ,EAAE,EAAE,MAAM;QACV,QAAQ,EAAE,IAAI;QACd,MAAM,EAAE,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE;KAC/C,CAAC,CAAC,CAAC;AACN,CAAC;AAED,SAAS,eAAe,CAAC,MAA6B,EAAE,QAA4B;IAClF,IAAI,CAAC,MAAM;QAAE,OAAO,EAAE,CAAC;IACvB,MAAM,IAAI,GAAG,UAAU,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACzC,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAClF,OAAO,MAAM,CAAC,OAAO,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,QAAQ,CAAC,EAAE,EAAE,CAAC,CAAC;QACvE,IAAI;QACJ,EAAE,EAAE,QAAQ;QACZ,QAAQ,EAAE,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC;QAC5B,MAAM,EAAE,QAAQ;KACjB,CAAC,CAAC,CAAC;AACN,CAAC;AAED,SAAS,UAAU,CAAC,MAAiB,EAAE,EAAsB;IAC3D,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,GAAG,CAAC,CAAC,YAAY,CAAC,MAAM,EAAE,EAAE,EAAE,EAAE,eAAe,EAAE,KAAK,EAAE,CAAC,CAAC;IAC9F,OAAO,qBAAqB,CAAC,IAAI,CAAC,CAAC;AACrC,CAAC;AAED;;;GAGG;AACH,SAAS,qBAAqB,CAAC,IAAgB;IAC7C,MAAM,MAAM,GAAe,EAAE,CAAC;IAC9B,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QAChD,IAAI,GAAG,KAAK,SAAS,IAAI,OAAO,IAAI,CAAC,QAAQ,CAAC,KAAK,QAAQ;YAAE,SAAS;QACtE,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC;IAC7B,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,KAAK,CAAC,KAAc;IAC3B,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IAClD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAC9D,OAAO,qBAAqB,CAAC,EAAE,GAAG,KAAK,EAAE,CAAC,CAAC;AAC7C,CAAC;AAED,SAAS,gBAAgB,CAAC,IAAgB;IACxC,MAAM,UAAU,GAAG,IAAI,CAAC,YAAY,CAAC,CAAC;IACtC,OAAO,OAAO,UAAU,KAAK,QAAQ,IAAI,UAAU,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,GAAG,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;AACxF,CAAC;AAED,SAAS,QAAQ,CAAC,MAAc;IAC9B,OAAO,YAAY,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,SAAS,CAAC;AACnD,CAAC"}
@@ -0,0 +1,24 @@
1
+ /** The union of characters in `S`: `CharsOf<"ab">` is `"a" | "b"`. Tail-recursive, so long strings are fine. */
2
+ type CharsOf<S extends string, Found = never> = S extends `${infer C}${infer Rest}` ? CharsOf<Rest, Found | C> : Found;
3
+ /** Characters a path parameter name may contain, as Express 5 (path-to-regexp v8) reads them. */
4
+ type NameChar = CharsOf<"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_$">;
5
+ /** Splits `"id/rest"` into the parameter name and what follows it: `["id", "/rest"]`. */
6
+ type ReadName<S extends string, Name extends string = ""> = S extends `${infer C}${infer Rest}` ? C extends NameChar ? ReadName<Rest, `${Name}${C}`> : [Name, S] : [Name, S];
7
+ /** Rewrites `*name` as `:*name`, so every parameter starts with `:`. */
8
+ type MarkWildcards<S extends string> = S extends `${infer Before}*${infer After}` ? `${Before}:*${MarkWildcards<After>}` : S;
9
+ /** Parameters outside optional groups. A `*name` wildcard is an array of segments, as Express 5 gives it. */
10
+ type RequiredParams<S extends string> = S extends `${string}:${infer After}` ? After extends `*${infer Wildcard}` ? Named<Wildcard, string[]> : Named<After, string> : {};
11
+ type Named<S extends string, Value> = ReadName<S> extends [infer Name extends string, infer Rest extends string] ? {
12
+ [K in Name]: Value;
13
+ } & RequiredParams<Rest> : never;
14
+ type GroupedParams<S extends string> = S extends `${infer Before}{${infer Inside}}${infer After}` ? RequiredParams<Before> & Partial<RequiredParams<Inside>> & GroupedParams<After> : RequiredParams<S>;
15
+ /**
16
+ * The parameters of an Express 5 path, typed from the string.
17
+ *
18
+ * - `"/orgs/:orgId/users/:id"` gives `{ orgId: string; id: string }`
19
+ * - `"/files/*path"` gives `{ path: string[] }`
20
+ * - `"/posts{/:slug}"` gives `{ slug?: string }`
21
+ */
22
+ export type PathParams<S extends string> = GroupedParams<MarkWildcards<S>>;
23
+ export {};
24
+ //# sourceMappingURL=path-params.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path-params.d.ts","sourceRoot":"","sources":["../../../src/http/path-params.ts"],"names":[],"mappings":"AAAA,gHAAgH;AAChH,KAAK,OAAO,CAAC,CAAC,SAAS,MAAM,EAAE,KAAK,GAAG,KAAK,IAAI,CAAC,SAAS,GAAG,MAAM,CAAC,GAAG,MAAM,IAAI,EAAE,GAC/E,OAAO,CAAC,IAAI,EAAE,KAAK,GAAG,CAAC,CAAC,GACxB,KAAK,CAAC;AAEV,iGAAiG;AACjG,KAAK,QAAQ,GAAG,OAAO,CAAC,kEAAkE,CAAC,CAAC;AAE5F,yFAAyF;AACzF,KAAK,QAAQ,CAAC,CAAC,SAAS,MAAM,EAAE,IAAI,SAAS,MAAM,GAAG,EAAE,IAAI,CAAC,SAAS,GAAG,MAAM,CAAC,GAAG,MAAM,IAAI,EAAE,GAC3F,CAAC,SAAS,QAAQ,GAChB,QAAQ,CAAC,IAAI,EAAE,GAAG,IAAI,GAAG,CAAC,EAAE,CAAC,GAC7B,CAAC,IAAI,EAAE,CAAC,CAAC,GACX,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;AAEd,wEAAwE;AACxE,KAAK,aAAa,CAAC,CAAC,SAAS,MAAM,IAAI,CAAC,SAAS,GAAG,MAAM,MAAM,IAAI,MAAM,KAAK,EAAE,GAC7E,GAAG,MAAM,KAAK,aAAa,CAAC,KAAK,CAAC,EAAE,GACpC,CAAC,CAAC;AAEN,6GAA6G;AAC7G,KAAK,cAAc,CAAC,CAAC,SAAS,MAAM,IAAI,CAAC,SAAS,GAAG,MAAM,IAAI,MAAM,KAAK,EAAE,GACxE,KAAK,SAAS,IAAI,MAAM,QAAQ,EAAE,GAChC,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,CAAC,GACzB,KAAK,CAAC,KAAK,EAAE,MAAM,CAAC,GACtB,EAAE,CAAC;AAEP,KAAK,KAAK,CAAC,CAAC,SAAS,MAAM,EAAE,KAAK,IAChC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,MAAM,IAAI,SAAS,MAAM,EAAE,MAAM,IAAI,SAAS,MAAM,CAAC,GACtE;KAAG,CAAC,IAAI,IAAI,GAAG,KAAK;CAAE,GAAG,cAAc,CAAC,IAAI,CAAC,GAC7C,KAAK,CAAC;AAEZ,KAAK,aAAa,CAAC,CAAC,SAAS,MAAM,IAAI,CAAC,SAAS,GAAG,MAAM,MAAM,IAAI,MAAM,MAAM,IAAI,MAAM,KAAK,EAAE,GAC7F,cAAc,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,GAAG,aAAa,CAAC,KAAK,CAAC,GAC/E,cAAc,CAAC,CAAC,CAAC,CAAC;AAEtB;;;;;;GAMG;AACH,MAAM,MAAM,UAAU,CAAC,CAAC,SAAS,MAAM,IAAI,aAAa,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=path-params.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path-params.js","sourceRoot":"","sources":["../../../src/http/path-params.ts"],"names":[],"mappings":""}
@@ -0,0 +1,141 @@
1
+ import type { z } from "zod";
2
+ import type { PathParams } from "./path-params.ts";
3
+ /** An HTTP method a route can declare. */
4
+ export type HttpMethod = "get" | "post" | "put" | "patch" | "delete";
5
+ /**
6
+ * A declared route, as `route.get(...)` and the other route methods return it. At runtime it holds the method
7
+ * and full path; its `~types` field exists only for the compiler, carrying what a client sends and receives.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * type Ping = Route<{}, { pong: true }>; // no input, resolves to { pong: true }
12
+ * ```
13
+ */
14
+ export interface Route<Input = unknown, Output = unknown> {
15
+ readonly method: HttpMethod;
16
+ readonly path: string;
17
+ readonly "~types"?: {
18
+ input: Input;
19
+ output: Output;
20
+ };
21
+ }
22
+ /** What a controller's setup returns: named routes, nested for groups. */
23
+ export interface Routes {
24
+ readonly [name: string]: Route | Routes;
25
+ }
26
+ /**
27
+ * An app's routes as data: the typed client's map from names to method and path.
28
+ *
29
+ * @example
30
+ * ```ts
31
+ * const manifest = { users: { get: ["GET", "/api/users/:id"] } } as const satisfies Manifest;
32
+ * ```
33
+ */
34
+ export interface Manifest {
35
+ readonly [name: string]: readonly [method: string, path: string] | Manifest;
36
+ }
37
+ /** A field that is left out when its type has no keys, and optional when every key is optional. */
38
+ type Field<K extends string, T> = [keyof T] extends [never] ? {} : {} extends T ? {
39
+ [P in K]?: T;
40
+ } : {
41
+ [P in K]: T;
42
+ };
43
+ type SchemaInput<S, Fallback> = S extends z.ZodType ? z.input<S> : Fallback;
44
+ /** A value a URL can carry. */
45
+ type UrlValue = string | number | boolean;
46
+ /** A value the client can write into a URL: it turns numbers, booleans, bigints and dates into text. */
47
+ type UrlScalar = UrlValue | bigint | Date;
48
+ type SchemaOutput<S, Fallback> = S extends z.ZodType ? z.output<S> : Fallback;
49
+ /**
50
+ * Path and query fields as a client writes them: the schema's input, or its output when that is something a
51
+ * URL can carry, so \`z.stringbool()\` takes \`true\` and \`z.coerce.number()\` takes \`2\`. A coercing schema's
52
+ * \`unknown\` input accepts any URL value.
53
+ */
54
+ type UrlFields<In, Out> = {
55
+ [K in keyof In]: unknown extends In[K] ? UrlValue | UrlValue[] : In[K] | (K extends keyof Out ? Extract<Out[K], UrlScalar | readonly UrlScalar[] | undefined> : never);
56
+ };
57
+ /**
58
+ * Headers a client sends: the typed ones from the route's schema, plus any others, such as the
59
+ * `authorization` a middleware reads.
60
+ */
61
+ type HeadersField<H> = H extends z.ZodType ? Field<"headers", z.input<H> & Record<string, string>> : {
62
+ headers?: Record<string, string>;
63
+ };
64
+ /** What a client sends: path params, query, body and headers, each typed by its schema. */
65
+ export type RouteInput<Path extends string, P, Q, B, H> = Field<"params", UrlFields<SchemaInput<P, PathParams<Path>>, SchemaOutput<P, {}>>> & Field<"query", UrlFields<SchemaInput<Q, {}>, SchemaOutput<Q, {}>>> & Field<"body", SchemaInput<B, {}>> & HeadersField<H>;
66
+ /** A Node stream, recognized by shape so these types never need `@types/node`. */
67
+ interface StreamLike {
68
+ pipe(...args: never[]): unknown;
69
+ read(...args: never[]): unknown;
70
+ }
71
+ /**
72
+ * What a value becomes after `JSON.stringify` and `JSON.parse`: dates become strings, and functions and
73
+ * `undefined` fields disappear.
74
+ *
75
+ * @example
76
+ * ```ts
77
+ * type Sent = Jsonify<{ at: Date; run(): void; note?: string }>; // { at: string; note?: string }
78
+ * ```
79
+ */
80
+ export type Jsonify<T> = T extends {
81
+ toJSON(): infer J;
82
+ } ? Jsonify<J> : T extends string | number | boolean | null ? T : T extends readonly (infer E)[] ? Jsonify<E>[] : T extends object ? {
83
+ [K in keyof T as T[K] extends (...args: never[]) => unknown ? never : K]: Jsonify<T[K]>;
84
+ } : never;
85
+ /**
86
+ * What a client gets back: the response schema's output, or else the handler's JSON-serialized result. A
87
+ * stream or bytes come back as the raw `Response`, and nothing as `undefined` (204).
88
+ */
89
+ export type RouteOutput<R, Returned> = R extends z.ZodType ? z.output<R> : [Returned] extends [undefined | void] ? undefined : Returned extends StreamLike | Uint8Array ? Response : Jsonify<Returned>;
90
+ /** What `defineController` returns: carries its routes' types, read by {@link ApiOf}. */
91
+ export interface ControllerTypes<R> {
92
+ readonly "~routes"?: R;
93
+ }
94
+ /**
95
+ * An app, or a map of controllers, reduced to its named routes: the shape of the typed client.
96
+ *
97
+ * @example
98
+ * ```ts
99
+ * type Api = ApiOf<typeof app>; // { users: { get: Route<...>, create: Route<...> } }
100
+ * ```
101
+ */
102
+ export type ApiOf<T> = "~controllers" extends keyof T ? T extends {
103
+ readonly "~controllers"?: infer C;
104
+ } ? RoutesOf<NonNullable<C>> : never : RoutesOf<T>;
105
+ /** Controllers whose setup returned named routes; others have no place in the client. */
106
+ type RoutesOf<C> = {
107
+ [K in keyof C as C[K] extends ControllerTypes<Routes> ? K : never]: C[K] extends ControllerTypes<infer R> ? R : never;
108
+ };
109
+ /**
110
+ * A typed client for routes: every route becomes a function that takes its input and resolves to its output.
111
+ *
112
+ * @example
113
+ * ```ts
114
+ * const user = await api.users.get({ params: { id } }); // typed from the route's schemas
115
+ * ```
116
+ */
117
+ export type Client<T> = {
118
+ readonly [K in keyof T]: T[K] extends Route<infer I, infer O> ? Call<I, O> : Client<T[K]>;
119
+ };
120
+ /** A body sent as it is, without JSON encoding: a file, a form, bytes or a stream. */
121
+ export type RawBody = FormData | Blob | URLSearchParams | ArrayBuffer | ArrayBufferView | ReadableStream | string;
122
+ /** The input of {@link Call.raw}: the route's own input, with any body `fetch` accepts. */
123
+ type RawInput<I> = Omit<I, "body"> & {
124
+ body?: RawBody | (I extends {
125
+ body?: infer B;
126
+ } ? B : never);
127
+ };
128
+ type Send<I, R> = {} extends I ? (input?: I, init?: RequestInit) => Promise<R> : (input: I, init?: RequestInit) => Promise<R>;
129
+ /**
130
+ * One route as a function. It resolves to the route's output and rejects with `ApiError` on a non-2xx
131
+ * response; `init` passes fetch options such as `signal`.
132
+ */
133
+ export type Call<I, O> = Send<I, O> & {
134
+ /**
135
+ * The same request, resolving to the raw `Response` whatever its status: for downloads, uploads
136
+ * (`body: formData`) and anything that is not JSON. The client's headers are still applied.
137
+ */
138
+ readonly raw: Send<RawInput<I>, Response>;
139
+ };
140
+ export {};
141
+ //# sourceMappingURL=routes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../../src/http/routes.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=routes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.js","sourceRoot":"","sources":["../../../src/http/routes.ts"],"names":[],"mappings":""}
@@ -0,0 +1,23 @@
1
+ import type { Server as HttpServerType } from "node:http";
2
+ import type { Server as HttpsServerType } from "node:https";
3
+ /** The Node server an app listens on: plain HTTP, or HTTPS when you pass your own. */
4
+ export type NodeServer = HttpServerType | HttpsServerType;
5
+ /**
6
+ * The listening server, injectable once the app listens. Attach WebSockets (`ws`, Socket.IO) to it in a
7
+ * provider's `onReady`; it does not exist before then, so never inject it in a class field or `onInit`.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * export class ChatGateway implements OnReady, OnDrain {
12
+ * #wss?: WebSocketServer;
13
+ * onReady() {
14
+ * this.#wss = new WebSocketServer({ server: inject(HttpServer) });
15
+ * }
16
+ * onDrain() {
17
+ * for (const client of this.#wss?.clients ?? []) client.close(1001);
18
+ * }
19
+ * }
20
+ * ```
21
+ */
22
+ export declare const HttpServer: import("../di/container.ts").InjectionToken<NodeServer>;
23
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../src/http/server.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,IAAI,cAAc,EAAE,MAAM,WAAW,CAAC;AAC1D,OAAO,KAAK,EAAE,MAAM,IAAI,eAAe,EAAE,MAAM,YAAY,CAAC;AAG5D,sFAAsF;AACtF,MAAM,MAAM,UAAU,GAAG,cAAc,GAAG,eAAe,CAAC;AAE1D;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,UAAU,yDAIrB,CAAC"}
@@ -0,0 +1,22 @@
1
+ import { defineToken } from "../di/container.js";
2
+ /**
3
+ * The listening server, injectable once the app listens. Attach WebSockets (`ws`, Socket.IO) to it in a
4
+ * provider's `onReady`; it does not exist before then, so never inject it in a class field or `onInit`.
5
+ *
6
+ * @example
7
+ * ```ts
8
+ * export class ChatGateway implements OnReady, OnDrain {
9
+ * #wss?: WebSocketServer;
10
+ * onReady() {
11
+ * this.#wss = new WebSocketServer({ server: inject(HttpServer) });
12
+ * }
13
+ * onDrain() {
14
+ * for (const client of this.#wss?.clients ?? []) client.close(1001);
15
+ * }
16
+ * }
17
+ * ```
18
+ */
19
+ export const HttpServer = /* @__PURE__ */ defineToken("HttpServer", () => {
20
+ throw new Error("HttpServer exists only once the app listens: inject it in onReady(), not in a field or onInit().");
21
+ });
22
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../../src/http/server.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAKjD;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,eAAe,CAAC,WAAW,CAAa,YAAY,EAAE,GAAG,EAAE;IACnF,MAAM,IAAI,KAAK,CACb,kGAAkG,CACnG,CAAC;AACJ,CAAC,CAAC,CAAC"}
@@ -0,0 +1,16 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * Zod parsing shared by routes and middlewares. Every parse is synchronous, the fast path, unless a schema has
4
+ * async refinements: then it switches to `safeParseAsync` and the value comes back wrapped in {@link Pending}.
5
+ */
6
+ /** An input whose schema needs async validation; never a value a schema can produce. */
7
+ export declare class Pending {
8
+ readonly promise: Promise<unknown>;
9
+ constructor(promise: Promise<unknown>);
10
+ }
11
+ export declare const settle: (value: unknown) => unknown;
12
+ export declare function parseInput(schema: z.ZodType | undefined, value: unknown, location: string): unknown;
13
+ /** A result that breaks its own response schema is a server bug: a 500, never a 400. */
14
+ export declare function serializeOutput(schema: z.ZodType | undefined, value: unknown): unknown;
15
+ export declare function safeParse(schema: z.ZodType, value: unknown): z.ZodSafeParseResult<unknown> | Promise<z.ZodSafeParseResult<unknown>>;
16
+ //# sourceMappingURL=validation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../../src/http/validation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;GAGG;AAEH,wFAAwF;AACxF,qBAAa,OAAO;IAClB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;IACnC,YAAY,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,EAEpC;CACF;AAED,eAAO,MAAM,MAAM,UAAW,OAAO,KAAG,OAA6D,CAAC;AAEtG,wBAAgB,UAAU,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,GAAG,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAMnG;AAOD,wFAAwF;AACxF,wBAAgB,eAAe,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,GAAG,SAAS,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAItF;AAOD,wBAAgB,SAAS,CACvB,MAAM,EAAE,CAAC,CAAC,OAAO,EACjB,KAAK,EAAE,OAAO,GACb,CAAC,CAAC,kBAAkB,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC,CAOxE"}