@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.
- package/CHANGELOG.md +16 -0
- package/README.md +55 -6
- package/dist/jobs/index.d.ts +138 -4
- package/dist/jobs/index.d.ts.map +1 -1
- package/dist/jobs/index.js +161 -1
- package/dist/jobs/index.js.map +1 -1
- package/dist/outbox/index.d.ts +5 -0
- package/dist/outbox/index.d.ts.map +1 -1
- package/dist/outbox/index.js +59 -3
- package/dist/outbox/index.js.map +1 -1
- package/dist/providers/instrumentation.d.ts +1 -1
- package/dist/providers/instrumentation.d.ts.map +1 -1
- package/dist/providers/instrumentation.js.map +1 -1
- package/dist/server/hooks/auth.d.ts +50 -65
- package/dist/server/hooks/auth.d.ts.map +1 -1
- package/dist/server/hooks/auth.js +44 -55
- package/dist/server/hooks/auth.js.map +1 -1
- package/dist/server/hooks/index.d.ts +1 -1
- package/dist/server/hooks/index.d.ts.map +1 -1
- package/dist/server/hooks/index.js.map +1 -1
- package/dist/server/http.d.ts +52 -0
- package/dist/server/http.d.ts.map +1 -1
- package/dist/server/http.js +20 -1
- package/dist/server/http.js.map +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/server.d.ts +54 -13
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +56 -35
- package/dist/server/server.js.map +1 -1
- package/dist/testing/index.d.ts +4 -0
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +8 -0
- package/dist/testing/index.js.map +1 -1
- package/dist/uploads/client.d.ts +278 -0
- package/dist/uploads/client.d.ts.map +1 -0
- package/dist/uploads/client.js +428 -0
- package/dist/uploads/client.js.map +1 -0
- package/dist/uploads/index.d.ts +361 -0
- package/dist/uploads/index.d.ts.map +1 -0
- package/dist/uploads/index.js +543 -0
- package/dist/uploads/index.js.map +1 -0
- package/package.json +11 -2
- package/src/jobs/index.ts +326 -5
- package/src/outbox/index.ts +83 -3
- package/src/providers/instrumentation.ts +7 -1
- package/src/server/hooks/auth.ts +89 -162
- package/src/server/hooks/index.ts +1 -5
- package/src/server/http.ts +79 -0
- package/src/server/index.ts +1 -0
- package/src/server/server.ts +191 -23
- package/src/testing/index.ts +11 -0
- package/src/uploads/client.ts +861 -0
- package/src/uploads/index.ts +1067 -0
package/src/server/hooks/auth.ts
CHANGED
|
@@ -1,205 +1,132 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
|
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
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
64
|
+
* Mark a route as intentionally public.
|
|
81
65
|
*/
|
|
82
|
-
|
|
66
|
+
public: () => RouteHook<Ctx, Record<string, never>>;
|
|
83
67
|
/**
|
|
84
|
-
*
|
|
68
|
+
* Resolve auth when present and add optional auth fields to the handler ctx.
|
|
85
69
|
*/
|
|
86
|
-
|
|
70
|
+
optional: () => RouteHook<Ctx, Partial<AddedCtx>>;
|
|
87
71
|
/**
|
|
88
|
-
*
|
|
72
|
+
* Require auth and add authenticated fields to the handler ctx.
|
|
89
73
|
*/
|
|
90
|
-
|
|
91
|
-
args: AuthHookUnauthorizedArgs<Ctx, Session>,
|
|
92
|
-
) => MaybePromise<HttpResponse>;
|
|
74
|
+
required: () => RouteHook<Ctx, AddedCtx>;
|
|
93
75
|
};
|
|
94
76
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
|
92
|
+
* Create route-scoped authentication hooks.
|
|
129
93
|
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
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 -
|
|
137
|
-
*
|
|
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
|
|
141
|
-
options
|
|
142
|
-
):
|
|
143
|
-
|
|
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
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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
|
-
|
|
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
|
|
15
|
-
type CtxWithAuthPort,
|
|
11
|
+
type AuthRouteHooks,
|
|
16
12
|
createAuthHooks,
|
|
17
13
|
} from "./auth";
|
|
18
14
|
export {
|
package/src/server/http.ts
CHANGED
|
@@ -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.
|