@stacksjs/bun-router 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/dist/chunk-03m2j6h7.js +2 -0
  2. package/dist/chunk-08cw7ejr.js +3 -0
  3. package/dist/chunk-0e4g0fyv.js +62 -0
  4. package/dist/chunk-0mmn02yw.js +2 -0
  5. package/dist/chunk-126c9qm3.js +2 -0
  6. package/dist/chunk-12p13817.js +2 -0
  7. package/dist/chunk-14v65c37.js +4 -0
  8. package/dist/{chunk-s3dn1kwx.js → chunk-1b6a7t3v.js} +2 -2
  9. package/dist/chunk-1wenkmk0.js +2 -0
  10. package/dist/chunk-2152npfd.js +2 -0
  11. package/dist/chunk-26w23krx.js +2 -0
  12. package/dist/chunk-2g8m379z.js +10 -0
  13. package/dist/{chunk-5mxby3fd.js → chunk-2rr178et.js} +2 -2
  14. package/dist/chunk-2w8f3swz.js +29 -0
  15. package/dist/chunk-3y4gaz7v.js +82 -0
  16. package/dist/chunk-4r1x4afz.js +12 -0
  17. package/dist/chunk-4wyw66zk.js +2 -0
  18. package/dist/chunk-4z5sf8ag.js +2 -0
  19. package/dist/{chunk-krkchy8v.js → chunk-58tfvayh.js} +3 -3
  20. package/dist/{chunk-ex5fkjah.js → chunk-5dp83qcx.js} +43 -43
  21. package/dist/chunk-5f0d9j9a.js +2 -0
  22. package/dist/{chunk-zhx0hxdd.js → chunk-5z52b0fc.js} +7 -7
  23. package/dist/{chunk-kc659vhx.js → chunk-5zrnk2kq.js} +3 -3
  24. package/dist/chunk-6dmqtdnq.js +2 -0
  25. package/dist/{chunk-5s1pr62n.js → chunk-6e91956t.js} +2 -2
  26. package/dist/{chunk-jrf5a2ap.js → chunk-6rv8w5jk.js} +1 -1
  27. package/dist/{chunk-5n0ckcmc.js → chunk-6x1feysm.js} +2 -2
  28. package/dist/chunk-7y72hm1x.js +2 -0
  29. package/dist/{chunk-zetqzgf3.js → chunk-8hbhfh6b.js} +6 -6
  30. package/dist/{chunk-5ncn73tp.js → chunk-8kfzygg5.js} +2 -2
  31. package/dist/chunk-8wydx2we.js +2 -0
  32. package/dist/chunk-8zjnjvv7.js +2 -0
  33. package/dist/chunk-988h4bzr.js +2 -0
  34. package/dist/chunk-9b6a58ac.js +2 -0
  35. package/dist/chunk-a8fcaywb.js +2 -0
  36. package/dist/chunk-bfjwn7yg.js +2 -0
  37. package/dist/chunk-bp2522mm.js +2 -0
  38. package/dist/chunk-bswe62q4.js +48 -0
  39. package/dist/chunk-c3v796c8.js +943 -0
  40. package/dist/chunk-ccqmwdry.js +2 -0
  41. package/dist/chunk-ck0crwnx.js +2 -0
  42. package/dist/chunk-cqgkxcb4.js +2 -0
  43. package/dist/chunk-dntnakjy.js +2 -0
  44. package/dist/{chunk-67wgad4f.js → chunk-ettdpnrw.js} +2 -2
  45. package/dist/{chunk-csfrpsp7.js → chunk-ezqwq17j.js} +10 -10
  46. package/dist/chunk-ezx3qsez.js +2 -0
  47. package/dist/{chunk-73k79n7q.js → chunk-f72nnjy4.js} +360 -360
  48. package/dist/chunk-fna5n0ey.js +246 -0
  49. package/dist/chunk-fqhckdt8.js +2 -0
  50. package/dist/chunk-fr6hbx25.js +2 -0
  51. package/dist/{chunk-vr10sbbh.js → chunk-g9cwng6w.js} +1 -1
  52. package/dist/chunk-gd3za5x0.js +2 -0
  53. package/dist/chunk-gf28608k.js +2 -0
  54. package/dist/chunk-h1dj6v8q.js +3 -0
  55. package/dist/chunk-hjxe7k2g.js +2 -0
  56. package/dist/chunk-hrkgznbn.js +2 -0
  57. package/dist/chunk-j6h5v0br.js +2 -0
  58. package/dist/{chunk-0gjq8ttx.js → chunk-mgb01qdv.js} +2 -2
  59. package/dist/chunk-n24c9whp.js +2 -0
  60. package/dist/{chunk-kxp888dn.js → chunk-n2j24b0h.js} +244 -244
  61. package/dist/{chunk-vv3gb1m5.js → chunk-nhmtg01n.js} +2 -2
  62. package/dist/chunk-p0qhpnqh.js +70 -0
  63. package/dist/chunk-p0w1x8y2.js +3 -0
  64. package/dist/{chunk-9ght4n4r.js → chunk-p3dk1w2w.js} +11 -11
  65. package/dist/{chunk-6sjvhy5f.js → chunk-q4e9pxs7.js} +2 -2
  66. package/dist/chunk-q5dv1kyy.js +2 -0
  67. package/dist/chunk-qnqgf9tq.js +2 -0
  68. package/dist/chunk-qvwy1808.js +898 -0
  69. package/dist/{chunk-5s0pnsd6.js → chunk-r18f77vc.js} +4 -4
  70. package/dist/chunk-r63za1mr.js +2 -0
  71. package/dist/{chunk-6s1kbajm.js → chunk-rft8gw92.js} +1 -1
  72. package/dist/chunk-ryvmyskt.js +4211 -0
  73. package/dist/chunk-sa6mx308.js +2 -0
  74. package/dist/chunk-va0pc7hx.js +2 -0
  75. package/dist/chunk-vcp8dnjq.js +412 -0
  76. package/dist/chunk-vg1erq1q.js +2 -0
  77. package/dist/{chunk-hmgedrmr.js → chunk-w0jv49ch.js} +3 -3
  78. package/dist/{chunk-0t5vz87j.js → chunk-wm805tte.js} +19 -19
  79. package/dist/chunk-xj9h6mc9.js +2 -0
  80. package/dist/chunk-ytvzwrq3.js +51 -0
  81. package/dist/{chunk-w4r464v5.js → chunk-zde7ebc4.js} +2 -2
  82. package/dist/chunk-zmqsv2xy.js +2 -0
  83. package/dist/cli.js +57 -66
  84. package/dist/container/index.js +1 -1
  85. package/dist/index.d.ts +1 -0
  86. package/dist/index.js +29 -29
  87. package/dist/typed/client.d.ts +89 -0
  88. package/dist/typed/contract.d.ts +34 -0
  89. package/dist/typed/endpoint.d.ts +90 -0
  90. package/dist/typed/index.d.ts +14 -0
  91. package/dist/typed/router.d.ts +141 -0
  92. package/package.json +1 -1
  93. package/dist/chunk-30rkn2ym.js +0 -2
  94. package/dist/chunk-38cmrard.js +0 -2
  95. package/dist/chunk-3qna6dwd.js +0 -2
  96. package/dist/chunk-4d5fmta0.js +0 -2
  97. package/dist/chunk-4x3b4b4q.js +0 -2
  98. package/dist/chunk-6xed4va1.js +0 -2
  99. package/dist/chunk-748n6ydj.js +0 -2
  100. package/dist/chunk-7rsf8tes.js +0 -898
  101. package/dist/chunk-8aspmtf1.js +0 -2
  102. package/dist/chunk-8yfdg595.js +0 -2
  103. package/dist/chunk-90eze7av.js +0 -3
  104. package/dist/chunk-9dvwyej7.js +0 -2
  105. package/dist/chunk-a2w45krb.js +0 -2
  106. package/dist/chunk-a547f2ab.js +0 -48
  107. package/dist/chunk-bqdycvtc.js +0 -2
  108. package/dist/chunk-cd03rk3x.js +0 -2
  109. package/dist/chunk-ct0m17q5.js +0 -2
  110. package/dist/chunk-dhbvvr5h.js +0 -2
  111. package/dist/chunk-dhptcsya.js +0 -943
  112. package/dist/chunk-dq93zag9.js +0 -2
  113. package/dist/chunk-edrtesh7.js +0 -2
  114. package/dist/chunk-ejcws37w.js +0 -2
  115. package/dist/chunk-ex890p45.js +0 -51
  116. package/dist/chunk-f69cpnhw.js +0 -2
  117. package/dist/chunk-fjf53b44.js +0 -2
  118. package/dist/chunk-fjzye4qj.js +0 -2
  119. package/dist/chunk-gfd23b6t.js +0 -2
  120. package/dist/chunk-habv0vef.js +0 -12
  121. package/dist/chunk-hz6fc788.js +0 -2
  122. package/dist/chunk-jff6j9y3.js +0 -10
  123. package/dist/chunk-jmeb7n8k.js +0 -70
  124. package/dist/chunk-jy2jhcpy.js +0 -246
  125. package/dist/chunk-m249t3je.js +0 -3
  126. package/dist/chunk-m9yabnjx.js +0 -2
  127. package/dist/chunk-mekrq9wd.js +0 -2
  128. package/dist/chunk-mmxv535f.js +0 -82
  129. package/dist/chunk-mypn7jss.js +0 -2
  130. package/dist/chunk-n18x1k3c.js +0 -2
  131. package/dist/chunk-nkddjzsa.js +0 -2
  132. package/dist/chunk-pjmhqmr9.js +0 -2
  133. package/dist/chunk-qyrnsaq1.js +0 -2
  134. package/dist/chunk-r19ff2qm.js +0 -62
  135. package/dist/chunk-r338fssq.js +0 -2
  136. package/dist/chunk-s36z58x2.js +0 -29
  137. package/dist/chunk-s79dd325.js +0 -471
  138. package/dist/chunk-ta38j9em.js +0 -2
  139. package/dist/chunk-tpfgntwj.js +0 -4211
  140. package/dist/chunk-vw8rwv6e.js +0 -2
  141. package/dist/chunk-wcrqs5z9.js +0 -2
  142. package/dist/chunk-x6d1rmwa.js +0 -2
  143. package/dist/chunk-x7ft15zz.js +0 -2
  144. package/dist/chunk-x8trwn7j.js +0 -3
  145. package/dist/chunk-xhepdj1f.js +0 -2
  146. package/dist/chunk-xv2em0q4.js +0 -4
  147. package/dist/chunk-ypw6g0te.js +0 -2
  148. package/dist/chunk-z8szsm21.js +0 -2
  149. package/dist/chunk-zfbh14qe.js +0 -2
  150. /package/dist/{chunk-a3wahyg7.js → chunk-nf3kv7kv.js} +0 -0
@@ -0,0 +1,89 @@
1
+ /**
2
+ * A client that knows what every endpoint takes and returns, with no
3
+ * generation step between changing a route and seeing the type change.
4
+ *
5
+ * Reads the route map straight out of `createTypedRouter()`'s type, so editing
6
+ * a handler's return shape or its `validations` is a compile error at every
7
+ * call site that disagrees - immediately, with no CLI run and no generated
8
+ * file that could still be holding yesterday's answer.
9
+ *
10
+ * ```ts
11
+ * import type { AppRoutes } from './routes'
12
+ * import { createTypedClient } from '@stacksjs/bun-router'
13
+ *
14
+ * const client = createTypedClient<AppRoutes>({ baseUrl: 'https://api.example.com' })
15
+ *
16
+ * const projects = await client.get('/v1/projects')
17
+ * const one = await client.get('/v1/projects/{id}', { params: { id: '42' } })
18
+ * const created = await client.post('/v1/projects', { name: 'apollo', budget: 1200 })
19
+ * ```
20
+ *
21
+ * A path the API does not serve is a compile error. A body that does not match
22
+ * the handler's declared input is a compile error. The awaited result is the
23
+ * handler's own return type.
24
+ *
25
+ * ## What it does not promise
26
+ *
27
+ * The output type is the handler's return type as TypeScript sees it, not a
28
+ * model of what JSON does to it - a `Date` in a response body arrives as a
29
+ * string, and the type will still say `Date`. A handler that returns a
30
+ * `Response` or a stream has taken over the wire format itself and is typed
31
+ * `unknown`, which is the honest answer.
32
+ *
33
+ * It speaks HTTP and nothing else: no router import, no server code, nothing
34
+ * that stops it being bundled for a browser.
35
+ */
36
+ import type { PathsForMethod, RouteMapOf, TypedRouteMap } from './contract';
37
+ /** A response the server refused. Carries the parsed body when there was one. */
38
+ export declare class TypedClientError extends Error {
39
+ readonly status: number;
40
+ readonly response: Response;
41
+ readonly body: unknown;
42
+ constructor(response: Response, body: unknown);
43
+ }
44
+ export interface TypedClientOptions {
45
+ /** Where the API lives. A trailing slash is fine. */
46
+ baseUrl: string;
47
+ /**
48
+ * Headers on every request. A function is called per request, so an auth
49
+ * token that rotates does not have to be a new client.
50
+ */
51
+ headers?: Record<string, string> | (() => Record<string, string> | Promise<Record<string, string>>);
52
+ /** Swap the fetch implementation - a test double, or one that retries. */
53
+ fetch?: typeof globalThis.fetch;
54
+ credentials?: RequestInit['credentials'];
55
+ /**
56
+ * Called instead of throwing when the server answers 4xx/5xx. Return a value
57
+ * to have it become the call's result; throw to keep the default behaviour.
58
+ */
59
+ onError?: (error: TypedClientError) => unknown;
60
+ }
61
+ /** Per-call options. `params` is required when the path has any. */
62
+ export interface TypedRequestOptions<TParams extends Record<string, string>> {
63
+ params?: TParams;
64
+ query?: Record<string, string | number | boolean | undefined>;
65
+ headers?: Record<string, string>;
66
+ signal?: AbortSignal;
67
+ }
68
+ type Route<T, M extends string, P extends string> = RouteMapOf<T> extends infer R ? (R extends TypedRouteMap ? (`${M} ${P}` extends keyof R ? R[`${M} ${P}`] : never) : never) : never;
69
+ type Input<T, M extends string, P extends string> = Route<T, M, P>['input'];
70
+ type Output<T, M extends string, P extends string> = Route<T, M, P>['output'];
71
+ type Params<T, M extends string, P extends string> = Route<T, M, P>['params'];
72
+ type Paths<T, M extends string> = RouteMapOf<T> extends infer R ? (R extends TypedRouteMap ? PathsForMethod<R, M> : never) : never;
73
+ export interface TypedClient<T> {
74
+ get: <P extends Paths<T, 'GET'>>(path: P, options?: TypedRequestOptions<Params<T, 'GET', P>>) => Promise<Output<T, 'GET', P>>;
75
+ post: <P extends Paths<T, 'POST'>>(path: P, body: Input<T, 'POST', P>, options?: TypedRequestOptions<Params<T, 'POST', P>>) => Promise<Output<T, 'POST', P>>;
76
+ put: <P extends Paths<T, 'PUT'>>(path: P, body: Input<T, 'PUT', P>, options?: TypedRequestOptions<Params<T, 'PUT', P>>) => Promise<Output<T, 'PUT', P>>;
77
+ patch: <P extends Paths<T, 'PATCH'>>(path: P, body: Input<T, 'PATCH', P>, options?: TypedRequestOptions<Params<T, 'PATCH', P>>) => Promise<Output<T, 'PATCH', P>>;
78
+ delete: <P extends Paths<T, 'DELETE'>>(path: P, options?: TypedRequestOptions<Params<T, 'DELETE', P>>) => Promise<Output<T, 'DELETE', P>>;
79
+ /**
80
+ * The untyped escape hatch, for when you need the `Response` itself -
81
+ * a redirect to follow by hand, a header to read, a body to stream.
82
+ */
83
+ raw: (method: string, path: string, init?: RequestInit & {
84
+ params?: Record<string, string>;
85
+ query?: Record<string, unknown>;
86
+ }) => Promise<Response>;
87
+ }
88
+ export declare function createTypedClient<T>(options: TypedClientOptions): TypedClient<T>;
89
+ export {};
@@ -0,0 +1,34 @@
1
+ /**
2
+ * The contract between a typed router and a typed client.
3
+ *
4
+ * One route map, keyed `"METHOD /path"`, describing what each endpoint takes,
5
+ * what it answers with, and what its path needs. `createTypedRouter()`
6
+ * accumulates one of these into its own type as routes are registered;
7
+ * `createTypedClient()` reads it back. Nothing else needs to know it exists.
8
+ *
9
+ * It lives in its own module, with no imports, because both halves depend on
10
+ * it and neither should have to depend on the other.
11
+ */
12
+ /** One route: what a client sends, what it gets back, what the path needs. */
13
+ export interface TypedRoute {
14
+ input: unknown;
15
+ output: unknown;
16
+ params: Record<string, string>;
17
+ }
18
+ /** A whole API, keyed `"METHOD /path"` (e.g. `"GET /v1/projects"`). */
19
+ export type TypedRouteMap = Record<string, TypedRoute>;
20
+ /**
21
+ * The route map behind a typed router, or the map itself.
22
+ *
23
+ * `createTypedRouter()` accumulates its map into a phantom `__routes`
24
+ * property, so `typeof api` carries it and this reads it back out. Passing a
25
+ * map type directly works too, which is what an application that would rather
26
+ * name its API explicitly will export.
27
+ */
28
+ export type RouteMapOf<T> = T extends {
29
+ __routes?: infer R;
30
+ } ? ([R] extends [TypedRouteMap | undefined] ? NonNullable<R> : never) : (T extends TypedRouteMap ? T : never);
31
+ /** The paths one method serves, as a union of path literals. */
32
+ export type PathsForMethod<R extends TypedRouteMap, M extends string> = Extract<keyof R, `${M} ${string}`> extends `${M} ${infer P}` ? P : never;
33
+ /** The HTTP methods a typed router registers. */
34
+ export type TypedMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Reading a handler's input and output types.
3
+ *
4
+ * A typed route map is only as good as what can be inferred about the handler
5
+ * behind it, and handlers come in more than one shape. Three are understood
6
+ * here, in order:
7
+ *
8
+ * 1. A plain function. Its return type is the output; the input is unknown,
9
+ * because a function that reads `req.body` says nothing about what it
10
+ * expects to find there.
11
+ * 2. An object with `handle()`. Same output inference, and if it also
12
+ * carries a `validations` map, the input comes from that - the same
13
+ * object the validator runs, so the two cannot drift.
14
+ * 3. Anything declaring an explicit `__input` phantom, which wins over
15
+ * `validations`. The escape hatch for a handler that validates somewhere
16
+ * this cannot see.
17
+ *
18
+ * Shape 2 is why a framework built on this router does not need its own
19
+ * builder: an "action" with `handle` and `validations` is structurally exactly
20
+ * what this reads.
21
+ */
22
+ /** A validation rule, as `validations` maps are written. */
23
+ export interface TypedRule<T = unknown> {
24
+ /**
25
+ * Names the value the rule accepts. Matches the `Validator<T>` shape that
26
+ * ts-validation (and anything else with a `test(value: T)` predicate) uses,
27
+ * which is how the input type is read without depending on a validator
28
+ * library.
29
+ */
30
+ test?: (value: T) => boolean;
31
+ validate: (value: any) => any;
32
+ }
33
+ /** A `validations:` map: one rule per field, optionally with a message. */
34
+ export type TypedValidations = Record<string, {
35
+ rule: TypedRule<any>;
36
+ message?: unknown;
37
+ }>;
38
+ /** What a single rule accepts. */
39
+ type RuleInput<R> = R extends {
40
+ test: (value: infer T) => boolean;
41
+ } ? T : unknown;
42
+ /** The body shape a `validations` map describes. */
43
+ export type InputOfValidations<V> = V extends TypedValidations ? {
44
+ [K in keyof V]: RuleInput<V[K]['rule']>;
45
+ } : Record<string, unknown>;
46
+ /**
47
+ * What a client has to send to reach this handler.
48
+ *
49
+ * An explicit `__input` wins; otherwise `validations`; otherwise unknown-ish,
50
+ * which is the honest answer rather than a guess.
51
+ */
52
+ export type InputOf<H> = H extends {
53
+ __input?: infer I;
54
+ } ? ([I] extends [undefined] ? InputOfHandlerValidations<H> : NonNullable<I>) : InputOfHandlerValidations<H>;
55
+ type InputOfHandlerValidations<H> = H extends {
56
+ validations?: infer V;
57
+ } ? ([V] extends [undefined] ? Record<string, unknown> : InputOfValidations<NonNullable<V>>) : Record<string, unknown>;
58
+ /**
59
+ * What a client gets back.
60
+ *
61
+ * A handler that returns a `Response` or a stream has taken over the wire
62
+ * format itself, so its shape is genuinely unknown here - saying so is more
63
+ * useful than pretending the client knows.
64
+ */
65
+ export type OutputOf<H> = H extends {
66
+ handle: (..._args: any[]) => infer R;
67
+ } ? NormalizeOutput<Awaited<R>> : (H extends (..._args: any[]) => infer R ? NormalizeOutput<Awaited<R>> : unknown);
68
+ type NormalizeOutput<R> = [R] extends [Response | ReadableStream] ? unknown : R;
69
+ /** Anything this module can read types out of. */
70
+ export type TypedHandlerLike = ((..._args: any[]) => any) | {
71
+ handle: (..._args: any[]) => any;
72
+ };
73
+ /**
74
+ * Declare a handler's input and output explicitly.
75
+ *
76
+ * For the case shape 1 cannot cover: a plain function that validates its own
77
+ * body, where the router has no `validations` map to read. The returned value
78
+ * is the same function with an `__input` phantom attached, so it registers
79
+ * exactly as it would have.
80
+ *
81
+ * @example
82
+ * const createUser = defineEndpoint<{ name: string }, { id: number }>(async (req) => {
83
+ * const body = await req.json()
84
+ * return { id: 1 }
85
+ * })
86
+ */
87
+ export declare function defineEndpoint<TInput, TOutput>(handle: (..._args: any[]) => TOutput | Promise<TOutput>): ((..._args: any[]) => TOutput | Promise<TOutput>) & {
88
+ __input?: TInput;
89
+ };
90
+ export {};
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Typed routes and the client that reads them.
3
+ *
4
+ * Three modules, deliberately separate: the contract both halves agree on, the
5
+ * builder that produces it, and the client that consumes it. The client
6
+ * imports only the contract, so it stays bundleable for a browser.
7
+ */
8
+ export type { PathsForMethod, RouteMapOf, TypedMethod, TypedRoute, TypedRouteMap } from './contract';
9
+ export { createTypedClient, TypedClientError } from './client';
10
+ export type { TypedClient, TypedClientOptions, TypedRequestOptions } from './client';
11
+ export { defineEndpoint } from './endpoint';
12
+ export type { InputOf, InputOfValidations, OutputOf, TypedHandlerLike, TypedRule, TypedValidations } from './endpoint';
13
+ export { createTypedRouter } from './router';
14
+ export type { RoutesOf, TypedRouteParams, TypedRegistrar, TypedRouteOptions, TypedRouter } from './router';
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Routes a TypeScript consumer can see the shape of, with no generation step.
3
+ *
4
+ * ## What this is for
5
+ *
6
+ * `router.get('/v1/projects', handler)` tells the compiler nothing a client
7
+ * could use. The path is a string, the handler returns a `Response`, and the
8
+ * only route-aware client you could offer was one generated from an OpenAPI
9
+ * document by running a CLI command - which has to be re-run, and whose output
10
+ * goes stale the moment a route changes.
11
+ *
12
+ * This builder registers through the router it is given, exactly as you would
13
+ * by hand, while accumulating a route map into its own type as you chain:
14
+ *
15
+ * ```ts
16
+ * export const api = createTypedRouter(router)
17
+ * .get('/v1/projects', listProjects)
18
+ * .post('/v1/projects', createProject)
19
+ *
20
+ * export type AppRoutes = typeof api
21
+ * ```
22
+ *
23
+ * ```ts
24
+ * // any TypeScript consumer, same repo or a package away
25
+ * import { createTypedClient } from '@stacksjs/bun-router'
26
+ *
27
+ * const client = createTypedClient<AppRoutes>({ baseUrl: 'https://api.example.com' })
28
+ * const projects = await client.get('/v1/projects') // typed, no CLI step
29
+ * ```
30
+ *
31
+ * Registration goes through the ordinary router, so there is exactly one
32
+ * runtime dispatch path and the difference from a hand-written route is
33
+ * entirely at compile time.
34
+ *
35
+ * ## What it reads
36
+ *
37
+ * See `./endpoint.ts`. A plain function gives its return type; an object with
38
+ * `handle()` gives the same, plus its input from a `validations` map when it
39
+ * has one. That second shape is deliberate: a framework built on this router
40
+ * whose "actions" are `{ handle, validations }` objects gets typed routes from
41
+ * this builder without needing one of its own.
42
+ *
43
+ * ## What it is not
44
+ *
45
+ * Not a replacement for `router.get(...)`, which stays exactly as it is for
46
+ * every route that would rather be lazy than inferable, and not a replacement
47
+ * for OpenAPI, which remains the answer for consumers that are not TypeScript
48
+ * in this repo.
49
+ *
50
+ * ## Groups
51
+ *
52
+ * There is deliberately no `.group()`. A prefix applied only at runtime makes
53
+ * every accumulated path type wrong, and one applied only in the type is a
54
+ * second place for the URL to be written down. Write the full path; it is what
55
+ * the client will show you anyway.
56
+ */
57
+ import type { ExtractRouteParams } from '../types';
58
+ import type { TypedRouteMap } from './contract';
59
+ import type { InputOf, OutputOf, TypedHandlerLike } from './endpoint';
60
+ /**
61
+ * A path's params, normalized.
62
+ *
63
+ * `ExtractRouteParams` answers `object` for a path with no parameters, which
64
+ * would let a client pass anything. An empty record says what is meant.
65
+ */
66
+ export type TypedRouteParams<P extends string> = keyof ExtractRouteParams<P> extends never ? Record<string, never> : ExtractRouteParams<P>;
67
+ type Entry<P extends string, H> = {
68
+ input: InputOf<H>;
69
+ output: OutputOf<H>;
70
+ params: TypedRouteParams<P>;
71
+ };
72
+ /**
73
+ * Per-route settings.
74
+ *
75
+ * An argument rather than a chained call, because chaining `.middleware()`
76
+ * would return the route and lose the accumulated map. Applied by calling the
77
+ * matching method on whatever the registrar returned, so a router whose
78
+ * `get()` returns a chainable route supports all of them; see
79
+ * {@link TypedRegistrar}.
80
+ */
81
+ export interface TypedRouteOptions {
82
+ middleware?: string | readonly string[];
83
+ name?: string;
84
+ skipCsrf?: boolean;
85
+ requireCsrf?: boolean;
86
+ rateLimit?: {
87
+ max: number;
88
+ window?: 'second' | 'minute' | 'hour' | 'day' | number;
89
+ };
90
+ }
91
+ /**
92
+ * Anything that can register a route.
93
+ *
94
+ * `Router` satisfies this, and so does any wrapper around it. The return value
95
+ * is whatever that router hands back: when it is a chainable route,
96
+ * {@link TypedRouteOptions} are applied to it.
97
+ */
98
+ export interface TypedRegistrar {
99
+ get: (path: string, handler: any) => unknown;
100
+ post: (path: string, handler: any) => unknown;
101
+ put: (path: string, handler: any) => unknown;
102
+ patch: (path: string, handler: any) => unknown;
103
+ delete: (path: string, handler: any) => unknown;
104
+ }
105
+ /**
106
+ * The builder. Each method returns the same object at runtime and a wider type
107
+ * at compile time, which is what puts the route map in `typeof api` without
108
+ * anything being written down twice.
109
+ */
110
+ export interface TypedRouter<R extends TypedRouteMap = {}> {
111
+ /**
112
+ * Phantom. Never set at runtime; it is where `RouteMapOf` reads the
113
+ * accumulated map from.
114
+ */
115
+ readonly __routes?: R;
116
+ get: <P extends string, H extends TypedHandlerLike>(path: P, handler: H, options?: TypedRouteOptions) => TypedRouter<R & {
117
+ [K in `GET ${P}`]: Entry<P, H>;
118
+ }>;
119
+ post: <P extends string, H extends TypedHandlerLike>(path: P, handler: H, options?: TypedRouteOptions) => TypedRouter<R & {
120
+ [K in `POST ${P}`]: Entry<P, H>;
121
+ }>;
122
+ put: <P extends string, H extends TypedHandlerLike>(path: P, handler: H, options?: TypedRouteOptions) => TypedRouter<R & {
123
+ [K in `PUT ${P}`]: Entry<P, H>;
124
+ }>;
125
+ patch: <P extends string, H extends TypedHandlerLike>(path: P, handler: H, options?: TypedRouteOptions) => TypedRouter<R & {
126
+ [K in `PATCH ${P}`]: Entry<P, H>;
127
+ }>;
128
+ delete: <P extends string, H extends TypedHandlerLike>(path: P, handler: H, options?: TypedRouteOptions) => TypedRouter<R & {
129
+ [K in `DELETE ${P}`]: Entry<P, H>;
130
+ }>;
131
+ }
132
+ /** The accumulated route map, pulled back out of a builder's type. */
133
+ export type RoutesOf<T> = T extends TypedRouter<infer R> ? R : never;
134
+ /**
135
+ * Build a typed route group on top of a router.
136
+ *
137
+ * Routes land in that router's table exactly as if they had been registered by
138
+ * hand; the only difference is that the builder's own type remembers them.
139
+ */
140
+ export declare function createTypedRouter(router: TypedRegistrar): TypedRouter;
141
+ export {};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@stacksjs/bun-router",
3
3
  "type": "module",
4
- "version": "0.1.0",
4
+ "version": "0.1.2",
5
5
  "description": "A fast, type-safe router for Bun.",
6
6
  "author": "Chris Breuer <chris@stacksjs.org>",
7
7
  "license": "MIT",
@@ -1,2 +0,0 @@
1
- // @bun
2
- import{V as a}from"./chunk-a547f2ab.js";import"./chunk-6s1kbajm.js";import"./chunk-m249t3je.js";import"./chunk-tpfgntwj.js";import"./chunk-jff6j9y3.js";import"./chunk-csfrpsp7.js";import"./chunk-0t5vz87j.js";import"./chunk-krkchy8v.js";import"./chunk-brpgjyqg.js";import"./chunk-5mxby3fd.js";import"./chunk-5be9x5cq.js";import"./chunk-jmeb7n8k.js";import"./chunk-kc659vhx.js";import"./chunk-a3wahyg7.js";export{a as buildForProduction};
@@ -1,2 +0,0 @@
1
- // @bun
2
- import{Gp as a,Hp as b}from"./chunk-kc659vhx.js";import"./chunk-a3wahyg7.js";export{b as warnUnbalancedTags,a as findUnbalancedTags};
@@ -1,2 +0,0 @@
1
- // @bun
2
- import{lu as a,mu as b,nu as c,ou as d,pu as e,qu as f,ru as g,su as h,tu as i}from"./chunk-hz6fc788.js";import"./chunk-5n0ckcmc.js";import"./chunk-a3wahyg7.js";export{c as setCachedImageResult,e as processImage,f as optimizeImage,b as getCachedImageResult,i as generateSrcset,h as generateResponsiveVariants,d as clearImageCache,g as applyTransformations,a as ImageProcessorCache};
@@ -1,2 +0,0 @@
1
- // @bun
2
- import{ds as a,es as b,fs as c,gs as d,hs as e,is as f,js as g,ks as h,ls as i,ms as j,ns as k,os as l,ps as m,qs as n,rs as o,ss as p,ts as q}from"./chunk-90eze7av.js";import"./chunk-jy2jhcpy.js";import"./chunk-a3wahyg7.js";export{h as resizeAndConvert,f as processImage,d as isSharpAvailable,m as isImageFile,e as isImageBackendAvailable,l as groupVariantsByFormat,o as getTotalSize,n as getMimeType,g as getImageMetadata,k as getFallbackVariant,i as generateSrcSet,j as generateSizes,p as formatSize,q as ImageProcessingError,a as DEFAULT_WIDTHS,c as DEFAULT_QUALITY,b as DEFAULT_FORMATS};
@@ -1,2 +0,0 @@
1
- // @bun
2
- import{Ao as z,Bo as A,Co as B,Do as C,Eo as D,Fo as E,Go as F,Ho as G,Io as H,Jo as I,Ko as J,Lo as K,bo as a,co as b,do as c,eo as d,fo as e,go as f,ho as g,io as h,jo as i,ko as j,lo as k,mo as l,no as m,oo as n,po as o,qo as p,ro as q,so as r,to as s,uo as t,vo as u,wo as v,xo as w,yo as x,zo as y}from"./chunk-5mxby3fd.js";import"./chunk-a3wahyg7.js";export{o as setAppUrl,H as runMiddleware,r as route,j as resolveLayoutChain,s as resetRoutes,z as registerMiddleware,g as patternToRegex,t as navigateTo,h as matchRoute,E as loadMiddlewareFromDirectory,c as isNonPageRoutePattern,b as isNonPageRoutePath,B as hasMiddleware,K as getRouterScript,D as getMiddlewareNames,A as getMiddleware,d as generateRouteTypes,e as generateRouteManifest,n as formatRoutes,m as findErrorPage,f as filePathToPattern,J as extractLayoutMetadata,I as deriveLayoutGroup,q as defineRoutes,p as defineRoute,v as defineMiddleware,y as createStorageManager,w as createServerCookieManager,l as createRouter,F as createRouteLocation,G as createMiddlewareContext,x as createClientCookieManager,C as clearMiddleware,i as bracketPathToRegex,u as abortNavigation,k as Router,a as NON_PAGE_DIRS};
@@ -1,2 +0,0 @@
1
- // @bun
2
- import{Ke as a,Le as b,Me as c,Ne as d,Oe as e}from"./chunk-vsh4390q.js";import"./chunk-a3wahyg7.js";export{a as processAsyncDirectives,c as isAsyncComponent,e as generateAsyncComponentRuntime,d as generateAsyncComponentCSS,b as defineAsyncComponent};
@@ -1,2 +0,0 @@
1
- // @bun
2
- import{o as a,p as b,q as c,r as d,s as e,t as f}from"./chunk-5ncn73tp.js";import"./chunk-tpfgntwj.js";import"./chunk-jff6j9y3.js";import"./chunk-csfrpsp7.js";import"./chunk-0t5vz87j.js";import"./chunk-krkchy8v.js";import"./chunk-brpgjyqg.js";import"./chunk-5mxby3fd.js";import"./chunk-5be9x5cq.js";import"./chunk-jmeb7n8k.js";import"./chunk-kc659vhx.js";import"./chunk-a3wahyg7.js";export{c as renderSlot,d as processSlots,a as parseSlots,b as parseSlotElement,e as extractSlotContent,f as applySlots};