@beignet/core 0.0.2 → 0.0.3

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 (56) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +55 -6
  3. package/dist/jobs/index.d.ts +138 -4
  4. package/dist/jobs/index.d.ts.map +1 -1
  5. package/dist/jobs/index.js +161 -1
  6. package/dist/jobs/index.js.map +1 -1
  7. package/dist/outbox/index.d.ts +5 -0
  8. package/dist/outbox/index.d.ts.map +1 -1
  9. package/dist/outbox/index.js +59 -3
  10. package/dist/outbox/index.js.map +1 -1
  11. package/dist/providers/instrumentation.d.ts +1 -1
  12. package/dist/providers/instrumentation.d.ts.map +1 -1
  13. package/dist/providers/instrumentation.js.map +1 -1
  14. package/dist/server/hooks/auth.d.ts +50 -65
  15. package/dist/server/hooks/auth.d.ts.map +1 -1
  16. package/dist/server/hooks/auth.js +44 -55
  17. package/dist/server/hooks/auth.js.map +1 -1
  18. package/dist/server/hooks/index.d.ts +1 -1
  19. package/dist/server/hooks/index.d.ts.map +1 -1
  20. package/dist/server/hooks/index.js.map +1 -1
  21. package/dist/server/http.d.ts +52 -0
  22. package/dist/server/http.d.ts.map +1 -1
  23. package/dist/server/http.js +20 -1
  24. package/dist/server/http.js.map +1 -1
  25. package/dist/server/index.d.ts +1 -1
  26. package/dist/server/index.d.ts.map +1 -1
  27. package/dist/server/index.js +1 -1
  28. package/dist/server/index.js.map +1 -1
  29. package/dist/server/server.d.ts +54 -13
  30. package/dist/server/server.d.ts.map +1 -1
  31. package/dist/server/server.js +56 -35
  32. package/dist/server/server.js.map +1 -1
  33. package/dist/testing/index.d.ts +4 -0
  34. package/dist/testing/index.d.ts.map +1 -1
  35. package/dist/testing/index.js +8 -0
  36. package/dist/testing/index.js.map +1 -1
  37. package/dist/uploads/client.d.ts +278 -0
  38. package/dist/uploads/client.d.ts.map +1 -0
  39. package/dist/uploads/client.js +428 -0
  40. package/dist/uploads/client.js.map +1 -0
  41. package/dist/uploads/index.d.ts +361 -0
  42. package/dist/uploads/index.d.ts.map +1 -0
  43. package/dist/uploads/index.js +543 -0
  44. package/dist/uploads/index.js.map +1 -0
  45. package/package.json +11 -2
  46. package/src/jobs/index.ts +326 -5
  47. package/src/outbox/index.ts +83 -3
  48. package/src/providers/instrumentation.ts +7 -1
  49. package/src/server/hooks/auth.ts +89 -162
  50. package/src/server/hooks/index.ts +1 -5
  51. package/src/server/http.ts +79 -0
  52. package/src/server/index.ts +1 -0
  53. package/src/server/server.ts +191 -23
  54. package/src/testing/index.ts +11 -0
  55. package/src/uploads/client.ts +861 -0
  56. package/src/uploads/index.ts +1067 -0
@@ -1,205 +1,132 @@
1
- import {
2
- type AuthPort,
3
- type AuthSession,
4
- AuthUnauthorizedError,
5
- } from "../../ports";
6
- import type { HttpRequestLike, HttpResponse, ServerHook } from "../types";
1
+ import { AuthUnauthorizedError } from "../../ports";
2
+ import type { HttpRequestLike, RouteHook } from "../types";
7
3
 
8
4
  type MaybePromise<T> = T | Promise<T>;
9
5
 
10
6
  /**
11
- * Authentication mode for one matched contract.
12
- */
13
- export type AuthHookMode = "public" | "optional" | "required";
14
-
15
- /**
16
- * Input accepted when resolving auth mode from contract metadata or options.
17
- *
18
- * `true` maps to `"required"`; `false`, `null`, and `undefined` map to
19
- * `"public"`.
20
- */
21
- export type AuthHookModeInput = AuthHookMode | boolean | null | undefined;
22
-
23
- /**
24
- * Minimal context shape required when `createAuthHooks(...)` reads
25
- * `ctx.ports.auth`.
26
- */
27
- export type CtxWithAuthPort = {
28
- ports: {
29
- auth: AuthPort;
30
- };
31
- };
32
-
33
- /**
34
- * Arguments passed to auth hook callbacks.
7
+ * Arguments passed to auth route-hook callbacks.
35
8
  */
36
9
  export type AuthHookArgs<Ctx> = {
10
+ /**
11
+ * Framework-neutral request.
12
+ */
37
13
  req: HttpRequestLike;
14
+ /**
15
+ * Current route handler context.
16
+ */
38
17
  ctx: Ctx;
18
+ /**
19
+ * Matched contract metadata and schemas.
20
+ */
39
21
  contract: {
40
22
  metadata?: Record<string, unknown>;
41
23
  };
24
+ /**
25
+ * Parsed path parameters.
26
+ */
42
27
  path: unknown;
28
+ /**
29
+ * Parsed query parameters.
30
+ */
43
31
  query: unknown;
32
+ /**
33
+ * Parsed request headers.
34
+ */
44
35
  headers: unknown;
36
+ /**
37
+ * Parsed request body.
38
+ */
45
39
  body: unknown;
46
40
  };
47
41
 
48
42
  /**
49
- * Arguments passed to the auth `assign` callback.
50
- */
51
- export type AuthHookAssignArgs<Ctx, Session> = AuthHookArgs<Ctx> & {
52
- session: Session | null;
53
- };
54
-
55
- /**
56
- * Arguments passed to the auth `unauthorized` callback.
43
+ * Options for route-scoped auth hooks.
57
44
  */
58
- export type AuthHookUnauthorizedArgs<Ctx, Session> = AuthHookArgs<Ctx> & {
59
- session: Session | null;
60
- };
61
-
62
- /**
63
- * Options for `createAuthHooks(...)`.
64
- */
65
- export type AuthHooksOptions<Ctx, Session> = {
45
+ export type AuthHooksOptions<Ctx, AddedCtx extends object> = {
66
46
  /**
67
- * Hook name used in diagnostics.
47
+ * Hook name prefix used in diagnostics.
68
48
  */
69
49
  name?: string;
70
50
  /**
71
- * Resolve whether the current contract is public, optional-auth, or required-auth.
72
- */
73
- mode?: (args: AuthHookArgs<Ctx>) => MaybePromise<AuthHookModeInput>;
74
- /**
75
- * Custom session lookup. Required when context does not expose
76
- * `ctx.ports.auth`.
51
+ * Resolve authenticated context additions for the current request.
52
+ *
53
+ * Return `null` when the request is unauthenticated. Required hooks will
54
+ * reject that request; optional hooks will add no auth context.
77
55
  */
78
- getSession?: (args: AuthHookArgs<Ctx>) => MaybePromise<Session | null>;
56
+ resolve: (args: AuthHookArgs<Ctx>) => MaybePromise<AddedCtx | null>;
57
+ };
58
+
59
+ /**
60
+ * Route-scoped auth hook set.
61
+ */
62
+ export type AuthRouteHooks<Ctx, AddedCtx extends object> = {
79
63
  /**
80
- * Decide whether a resolved session counts as authenticated.
64
+ * Mark a route as intentionally public.
81
65
  */
82
- isAuthenticated?: (session: Session | null) => boolean;
66
+ public: () => RouteHook<Ctx, Record<string, never>>;
83
67
  /**
84
- * Return an updated context with auth/session fields attached.
68
+ * Resolve auth when present and add optional auth fields to the handler ctx.
85
69
  */
86
- assign?: (args: AuthHookAssignArgs<Ctx, Session>) => MaybePromise<Ctx>;
70
+ optional: () => RouteHook<Ctx, Partial<AddedCtx>>;
87
71
  /**
88
- * Return a custom unauthorized response for required-auth routes.
72
+ * Require auth and add authenticated fields to the handler ctx.
89
73
  */
90
- unauthorized?: (
91
- args: AuthHookUnauthorizedArgs<Ctx, Session>,
92
- ) => MaybePromise<HttpResponse>;
74
+ required: () => RouteHook<Ctx, AddedCtx>;
93
75
  };
94
76
 
95
- type InferAuthSession<TAuth> =
96
- TAuth extends AuthPort<infer User, infer SessionMetadata>
97
- ? AuthSession<User, SessionMetadata>
98
- : unknown;
99
-
100
- function modeFromInput(input: AuthHookModeInput): AuthHookMode {
101
- if (input === true || input === "required") return "required";
102
- if (input === "optional") return "optional";
103
- return "public";
104
- }
105
-
106
- function defaultMode<Ctx>(args: AuthHookArgs<Ctx>): AuthHookMode {
107
- return modeFromInput(args.contract.metadata?.auth as AuthHookModeInput);
108
- }
109
-
110
- async function defaultGetSession<Ctx>(
111
- args: AuthHookArgs<Ctx>,
112
- ): Promise<unknown | null> {
113
- const auth = (args.ctx as { ports?: { auth?: AuthPort } }).ports?.auth;
114
- if (!auth) {
115
- throw new Error(
116
- "createAuthHooks requires ctx.ports.auth or an explicit getSession option.",
117
- );
118
- }
119
-
120
- return auth.getSession(args.req);
121
- }
122
-
123
- function defaultIsAuthenticated<Session>(session: Session | null): boolean {
124
- return session !== null;
77
+ function toAuthArgs<Ctx>(
78
+ args: Parameters<RouteHook<Ctx, object>["resolve"]>[0],
79
+ ): AuthHookArgs<Ctx> {
80
+ return {
81
+ req: args.req,
82
+ ctx: args.ctx,
83
+ contract: args.contract,
84
+ path: args.path,
85
+ query: args.query,
86
+ headers: args.headers,
87
+ body: args.body,
88
+ };
125
89
  }
126
90
 
127
91
  /**
128
- * Create metadata-driven authentication hooks.
92
+ * Create route-scoped authentication hooks.
129
93
  *
130
- * By default the hook reads `contract.metadata.auth`: `"required"` or `true`
131
- * requires an authenticated session, `"optional"` attaches a session when one
132
- * exists, and missing/false metadata treats the route as public. The hook
133
- * identifies a user; business authorization still belongs in policies or use
134
- * cases.
94
+ * Use `auth.required()` on routes that require an authenticated actor and
95
+ * `auth.optional()` where handlers can use auth when present. The returned
96
+ * route hooks enrich handler `ctx`; business authorization still belongs in
97
+ * feature policies or use cases.
135
98
  *
136
- * @param options - Optional auth mode, session lookup, context assignment, and
137
- * unauthorized response customization.
138
- * @returns A server hook that runs before route handlers.
99
+ * @param options - Auth resolution callback and optional diagnostic name.
100
+ * @returns Public, optional, and required route-hook factories.
139
101
  */
140
- export function createAuthHooks<Ctx extends CtxWithAuthPort>(
141
- options?: AuthHooksOptions<Ctx, InferAuthSession<Ctx["ports"]["auth"]>>,
142
- ): ServerHook<Ctx, Ctx["ports"]>;
143
- export function createAuthHooks<Ctx, Session>(
144
- options: AuthHooksOptions<Ctx, Session> & {
145
- getSession: (args: AuthHookArgs<Ctx>) => MaybePromise<Session | null>;
146
- },
147
- ): ServerHook<Ctx>;
148
- export function createAuthHooks<Ctx, Session>(
149
- options: AuthHooksOptions<Ctx, Session> = {},
150
- ): ServerHook<Ctx> {
151
- return {
152
- name: options.name ?? "auth",
153
- beforeHandle: async ({
154
- req,
155
- ctx,
156
- contract,
157
- path,
158
- query,
159
- headers,
160
- body,
161
- }) => {
162
- const args: AuthHookArgs<Ctx> = {
163
- req,
164
- ctx,
165
- contract,
166
- path,
167
- query,
168
- headers,
169
- body,
170
- };
171
- const mode = modeFromInput(
172
- options.mode ? await options.mode(args) : defaultMode(args),
173
- );
174
-
175
- if (mode === "public") {
176
- return undefined;
177
- }
178
-
179
- const session = options.getSession
180
- ? await options.getSession(args)
181
- : ((await defaultGetSession(args)) as Session | null);
182
- const isAuthenticated =
183
- options.isAuthenticated?.(session) ?? defaultIsAuthenticated(session);
102
+ export function createAuthHooks<Ctx, AddedCtx extends object>(
103
+ options: AuthHooksOptions<Ctx, AddedCtx>,
104
+ ): AuthRouteHooks<Ctx, AddedCtx> {
105
+ const name = options.name ?? "auth";
184
106
 
185
- if (mode === "required" && !isAuthenticated) {
186
- if (options.unauthorized) {
187
- return {
188
- ctx,
189
- response: await options.unauthorized({ ...args, session }),
190
- };
107
+ return {
108
+ public: () => ({
109
+ name: `${name}.public`,
110
+ resolve: () => undefined,
111
+ }),
112
+ optional: () => ({
113
+ name: `${name}.optional`,
114
+ resolve: async (args) => {
115
+ const additions = await options.resolve(toAuthArgs(args));
116
+
117
+ return additions ?? undefined;
118
+ },
119
+ }),
120
+ required: () => ({
121
+ name: `${name}.required`,
122
+ resolve: async (args) => {
123
+ const additions = await options.resolve(toAuthArgs(args));
124
+ if (!additions) {
125
+ throw new AuthUnauthorizedError();
191
126
  }
192
127
 
193
- throw new AuthUnauthorizedError();
194
- }
195
-
196
- if (!options.assign) {
197
- return undefined;
198
- }
199
-
200
- return {
201
- ctx: await options.assign({ ...args, session }),
202
- };
203
- },
128
+ return additions;
129
+ },
130
+ }),
204
131
  };
205
132
  }
@@ -7,12 +7,8 @@ import type { ServerHook } from "../http";
7
7
 
8
8
  export {
9
9
  type AuthHookArgs,
10
- type AuthHookAssignArgs,
11
- type AuthHookMode,
12
- type AuthHookModeInput,
13
10
  type AuthHooksOptions,
14
- type AuthHookUnauthorizedArgs,
15
- type CtxWithAuthPort,
11
+ type AuthRouteHooks,
16
12
  createAuthHooks,
17
13
  } from "./auth";
18
14
  export {
@@ -168,6 +168,85 @@ export type Handler<Ctx, C extends HttpContractConfig> = (
168
168
  */
169
169
  export type MaybePromise<T> = T | Promise<T>;
170
170
 
171
+ /**
172
+ * Arguments passed to a route-scoped hook after request parsing and context
173
+ * creation.
174
+ */
175
+ export type RouteHookArgs<
176
+ Ctx,
177
+ C extends HttpContractConfig = HttpContractConfig,
178
+ > = HandlerArgs<Ctx, C>;
179
+
180
+ /**
181
+ * Hook that runs only for the route or route group where it is attached.
182
+ *
183
+ * Route hooks are for scoped policy and context enrichment such as
184
+ * authentication, tenant resolution, feature gates, and idempotency. They add
185
+ * fields to the handler context instead of replacing the app context.
186
+ */
187
+ export interface RouteHook<
188
+ Ctx,
189
+ AddedCtx extends object = Record<string, never>,
190
+ > {
191
+ /**
192
+ * Optional name used in diagnostics and devtools.
193
+ */
194
+ name?: string;
195
+ /**
196
+ * Resolve additional context for this route or throw to stop handling.
197
+ */
198
+ resolve: (args: RouteHookArgs<Ctx>) => MaybePromise<AddedCtx | undefined>;
199
+ }
200
+
201
+ /**
202
+ * Builder for route-scoped hooks.
203
+ */
204
+ export type RouteHookBuilder<Ctx> = {
205
+ /**
206
+ * Assign a diagnostic name to the hook.
207
+ */
208
+ name: (name: string) => RouteHookNamedBuilder<Ctx>;
209
+ /**
210
+ * Define the hook resolver.
211
+ */
212
+ resolve: <AddedCtx extends object>(
213
+ resolve: RouteHook<Ctx, AddedCtx>["resolve"],
214
+ ) => RouteHook<Ctx, AddedCtx>;
215
+ };
216
+
217
+ /**
218
+ * Named builder for route-scoped hooks.
219
+ */
220
+ export type RouteHookNamedBuilder<Ctx> = {
221
+ /**
222
+ * Define the hook resolver.
223
+ */
224
+ resolve: <AddedCtx extends object>(
225
+ resolve: RouteHook<Ctx, AddedCtx>["resolve"],
226
+ ) => RouteHook<Ctx, AddedCtx>;
227
+ };
228
+
229
+ /**
230
+ * Define a route-scoped hook.
231
+ *
232
+ * Route hooks enrich handler context for one route or route group. They should
233
+ * throw application/framework errors for denials instead of returning HTTP
234
+ * responses directly.
235
+ */
236
+ export function defineRouteHook<Ctx>(): RouteHookBuilder<Ctx> {
237
+ return {
238
+ name: (name) => ({
239
+ resolve: (resolve) => ({
240
+ name,
241
+ resolve,
242
+ }),
243
+ }),
244
+ resolve: (resolve) => ({
245
+ resolve,
246
+ }),
247
+ };
248
+ }
249
+
171
250
  /**
172
251
  * Hook that runs after a route is matched but before request parsing and
173
252
  * context creation.
@@ -46,6 +46,7 @@ export type {
46
46
  export {
47
47
  contractsFromRoutes,
48
48
  createServer,
49
+ defineRoute,
49
50
  defineRouteGroup,
50
51
  defineRoutes,
51
52
  } from "./server";