@axium/server 0.9.0 → 0.10.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 (70) hide show
  1. package/{web/api/index.ts → dist/api/index.d.ts} +0 -2
  2. package/dist/api/index.js +5 -0
  3. package/dist/api/metadata.d.ts +1 -0
  4. package/dist/api/metadata.js +28 -0
  5. package/dist/api/passkeys.d.ts +1 -0
  6. package/dist/api/passkeys.js +50 -0
  7. package/dist/api/register.d.ts +1 -0
  8. package/dist/api/register.js +70 -0
  9. package/dist/api/session.d.ts +1 -0
  10. package/dist/api/session.js +31 -0
  11. package/dist/api/users.d.ts +1 -0
  12. package/dist/api/users.js +244 -0
  13. package/dist/apps.d.ts +0 -5
  14. package/dist/apps.js +2 -9
  15. package/dist/auth.d.ts +9 -21
  16. package/dist/auth.js +12 -18
  17. package/dist/cli.js +65 -26
  18. package/dist/config.d.ts +15 -8
  19. package/dist/config.js +38 -15
  20. package/dist/database.js +4 -0
  21. package/dist/io.d.ts +6 -4
  22. package/dist/io.js +26 -19
  23. package/dist/plugins.d.ts +4 -2
  24. package/dist/plugins.js +15 -14
  25. package/dist/requests.d.ts +11 -0
  26. package/dist/requests.js +58 -0
  27. package/dist/routes.d.ts +12 -13
  28. package/dist/routes.js +21 -22
  29. package/dist/serve.d.ts +7 -0
  30. package/dist/serve.js +11 -0
  31. package/dist/state.d.ts +4 -0
  32. package/dist/state.js +22 -0
  33. package/dist/sveltekit.d.ts +8 -0
  34. package/dist/sveltekit.js +90 -0
  35. package/package.json +10 -5
  36. package/svelte.config.js +36 -0
  37. package/web/hooks.server.ts +8 -3
  38. package/web/lib/Dialog.svelte +0 -1
  39. package/web/lib/FormDialog.svelte +0 -1
  40. package/web/lib/icons/Icon.svelte +2 -7
  41. package/web/template.html +18 -0
  42. package/web/tsconfig.json +3 -2
  43. package/web/api/metadata.ts +0 -35
  44. package/web/api/passkeys.ts +0 -56
  45. package/web/api/readme.md +0 -1
  46. package/web/api/register.ts +0 -83
  47. package/web/api/schemas.ts +0 -22
  48. package/web/api/session.ts +0 -33
  49. package/web/api/users.ts +0 -351
  50. package/web/api/utils.ts +0 -66
  51. package/web/app.html +0 -14
  52. package/web/auth.ts +0 -8
  53. package/web/index.server.ts +0 -1
  54. package/web/index.ts +0 -1
  55. package/web/lib/auth.ts +0 -12
  56. package/web/lib/index.ts +0 -5
  57. package/web/routes/+layout.svelte +0 -6
  58. package/web/routes/[...path]/+page.server.ts +0 -13
  59. package/web/routes/[appId]/[...page]/+page.server.ts +0 -14
  60. package/web/routes/api/[...path]/+server.ts +0 -49
  61. package/web/utils.ts +0 -26
  62. /package/{web/lib → assets}/icons/light.svg +0 -0
  63. /package/{web/lib → assets}/icons/regular.svg +0 -0
  64. /package/{web/lib → assets}/icons/solid.svg +0 -0
  65. /package/{web/lib → assets}/styles.css +0 -0
  66. /package/{web/routes → routes}/_axium/default/+page.svelte +0 -0
  67. /package/{web/routes → routes}/account/+page.svelte +0 -0
  68. /package/{web/routes → routes}/login/+page.svelte +0 -0
  69. /package/{web/routes → routes}/logout/+page.svelte +0 -0
  70. /package/{web/routes → routes}/register/+page.svelte +0 -0
@@ -1,33 +0,0 @@
1
- import { authenticate } from '$lib/auth';
2
- import type { Result } from '@axium/core/api';
3
- import { connect, database as db } from '@axium/server/database';
4
- import { addRoute } from '@axium/server/routes';
5
- import { error } from '@sveltejs/kit';
6
- import { omit } from 'utilium';
7
- import { getToken, stripUser } from './utils';
8
-
9
- addRoute({
10
- path: '/api/session',
11
- async GET(event): Result<'GET', 'session'> {
12
- const result = await authenticate(event);
13
-
14
- if (!result) error(404, 'Session does not exist');
15
-
16
- return {
17
- ...omit(result, 'token'),
18
- user: stripUser(result.user, true),
19
- };
20
- },
21
- async DELETE(event): Result<'DELETE', 'session'> {
22
- const token = getToken(event);
23
- connect();
24
- const result = await db
25
- .deleteFrom('sessions')
26
- .where('sessions.token', '=', token)
27
- .returningAll()
28
- .executeTakeFirstOrThrow()
29
- .catch((e: Error) => (e.message == 'no result' ? error(404, 'Session does not exist') : error(400, 'Invalid session')));
30
-
31
- return omit(result, 'token');
32
- },
33
- });
package/web/api/users.ts DELETED
@@ -1,351 +0,0 @@
1
- /** Register a new passkey for a new or existing user. */
2
- import type { Result } from '@axium/core/api';
3
- import { LogoutSessions, PasskeyAuthenticationResponse, UserAuthOptions } from '@axium/core/schemas';
4
- import { UserChangeable, type User } from '@axium/core/user';
5
- import {
6
- createPasskey,
7
- createVerification,
8
- getPasskey,
9
- getPasskeysByUserId,
10
- getSessions,
11
- getUser,
12
- useVerification,
13
- } from '@axium/server/auth';
14
- import { config } from '@axium/server/config';
15
- import { connect, database as db } from '@axium/server/database';
16
- import { addRoute } from '@axium/server/routes';
17
- import {
18
- generateAuthenticationOptions,
19
- generateRegistrationOptions,
20
- verifyAuthenticationResponse,
21
- verifyRegistrationResponse,
22
- } from '@simplewebauthn/server';
23
- import { error, type RequestEvent } from '@sveltejs/kit';
24
- import { omit, pick } from 'utilium';
25
- import z from 'zod/v4';
26
- import { PasskeyRegistration } from './schemas.js';
27
- import { checkAuth, createSessionData, parseBody, stripUser, withError } from './utils.js';
28
-
29
- interface UserAuth {
30
- data: string;
31
- type: UserAuthOptions['type'];
32
- }
33
-
34
- const challenges = new Map<string, UserAuth>();
35
-
36
- const params = { id: z.uuid() };
37
-
38
- /**
39
- * Resolve a user's UUID using their email (in the future this might also include handles)
40
- */
41
- addRoute({
42
- path: '/api/user_id',
43
- async POST(event): Result<'POST', 'user_id'> {
44
- const { value } = await parseBody(event, z.object({ using: z.literal('email'), value: z.email() }));
45
-
46
- connect();
47
- const { id } = await db.selectFrom('users').select('id').where('email', '=', value).executeTakeFirst();
48
- return { id };
49
- },
50
- });
51
-
52
- addRoute({
53
- path: '/api/users/:id',
54
- params,
55
- async GET(event): Result<'GET', 'users/:id'> {
56
- const { id: userId } = event.params;
57
-
58
- const authed = await checkAuth(event, userId)
59
- .then(() => true)
60
- .catch(() => false);
61
-
62
- return stripUser(await getUser(userId), authed);
63
- },
64
- async PATCH(event): Result<'PATCH', 'users/:id'> {
65
- const { id: userId } = event.params;
66
- const body: UserChangeable & Pick<User, 'emailVerified'> = await parseBody(event, UserChangeable);
67
-
68
- await checkAuth(event, userId);
69
-
70
- const user = await getUser(userId);
71
- if (!user) error(404, { message: 'User does not exist' });
72
-
73
- if ('email' in body) body.emailVerified = null;
74
-
75
- const result = await db
76
- .updateTable('users')
77
- .set(body)
78
- .where('id', '=', userId)
79
- .returningAll()
80
- .executeTakeFirstOrThrow()
81
- .catch(withError('Failed to update user'));
82
-
83
- return stripUser(result, true);
84
- },
85
- async DELETE(event): Result<'DELETE', 'users/:id'> {
86
- const { id: userId } = event.params;
87
-
88
- await checkAuth(event, userId, true);
89
-
90
- const user = await getUser(userId);
91
- if (!user) error(404, { message: 'User does not exist' });
92
-
93
- const result = await db
94
- .deleteFrom('users')
95
- .where('id', '=', userId)
96
- .returningAll()
97
- .executeTakeFirstOrThrow()
98
- .catch(withError('Failed to delete user'));
99
-
100
- return result;
101
- },
102
- });
103
-
104
- addRoute({
105
- path: '/api/users/:id/full',
106
- params,
107
- async GET(event): Result<'GET', 'users/:id/full'> {
108
- const { id: userId } = event.params;
109
-
110
- await checkAuth(event, userId);
111
-
112
- const user = stripUser(await getUser(userId), true);
113
-
114
- const sessions = await getSessions(userId);
115
-
116
- return {
117
- ...user,
118
- sessions: sessions.map(s => omit(s, 'token')),
119
- };
120
- },
121
- });
122
-
123
- addRoute({
124
- path: '/api/users/:id/auth',
125
- params,
126
- async OPTIONS(event): Result<'OPTIONS', 'users/:id/auth'> {
127
- const { id: userId } = event.params;
128
- const { type } = await parseBody(event, UserAuthOptions);
129
-
130
- const user = await getUser(userId);
131
- if (!user) error(404, { message: 'User does not exist' });
132
-
133
- const passkeys = await getPasskeysByUserId(userId);
134
-
135
- if (!passkeys) error(409, { message: 'No passkeys exists for this user' });
136
-
137
- const options = await generateAuthenticationOptions({
138
- rpID: config.auth.rp_id,
139
- allowCredentials: passkeys.map(passkey => pick(passkey, 'id', 'transports')),
140
- });
141
-
142
- challenges.set(userId, { data: options.challenge, type });
143
-
144
- return options;
145
- },
146
- async POST(event: RequestEvent): Result<'POST', 'users/:id/auth'> {
147
- const { id: userId } = event.params;
148
- const response = await parseBody(event, PasskeyAuthenticationResponse);
149
-
150
- const auth = challenges.get(userId);
151
- if (!auth) error(404, { message: 'No challenge found for this user' });
152
- const { data: expectedChallenge, type } = auth;
153
- challenges.delete(userId);
154
-
155
- const user = await getUser(userId);
156
- if (!user) error(404, { message: 'User does not exist' });
157
-
158
- const passkey = await getPasskey(response.id);
159
- if (!passkey) error(404, { message: 'Passkey does not exist' });
160
-
161
- if (passkey.userId !== userId) error(403, { message: 'Passkey does not belong to this user' });
162
-
163
- const { verified } = await verifyAuthenticationResponse({
164
- response,
165
- credential: passkey,
166
- expectedChallenge,
167
- expectedOrigin: config.auth.origin,
168
- expectedRPID: config.auth.rp_id,
169
- }).catch(withError('Verification failed', 400));
170
-
171
- if (!verified) error(401, { message: 'Verification failed' });
172
-
173
- switch (type) {
174
- case 'login':
175
- return await createSessionData(event, userId);
176
- case 'action':
177
- if ((Date.now() - passkey.createdAt.getTime()) / 60_000 < config.auth.passkey_probation)
178
- error(403, { message: 'You can not authorize sensitive actions with a newly created passkey' });
179
-
180
- return await createSessionData(event, userId, true);
181
- }
182
- },
183
- });
184
-
185
- // Map of user ID => challenge
186
- const registrations = new Map<string, string>();
187
-
188
- addRoute({
189
- path: '/api/users/:id/passkeys',
190
- params,
191
- /**
192
- * Get passkey registration options for a user.
193
- */
194
- async OPTIONS(event: RequestEvent): Result<'OPTIONS', 'users/:id/passkeys'> {
195
- const { id: userId } = event.params;
196
-
197
- const user = await getUser(userId);
198
- if (!user) error(404, { message: 'User does not exist' });
199
-
200
- const existing = await getPasskeysByUserId(userId);
201
-
202
- await checkAuth(event, userId);
203
-
204
- const options = await generateRegistrationOptions({
205
- rpName: config.auth.rp_name,
206
- rpID: config.auth.rp_id,
207
- userName: userId,
208
- userDisplayName: user.email,
209
- attestationType: 'none',
210
- excludeCredentials: existing.map(passkey => pick(passkey, 'id', 'transports')),
211
- authenticatorSelection: {
212
- residentKey: 'preferred',
213
- userVerification: 'preferred',
214
- authenticatorAttachment: 'platform',
215
- },
216
- });
217
-
218
- registrations.set(userId, options.challenge);
219
-
220
- return options;
221
- },
222
-
223
- /**
224
- * Get passkeys for a user.
225
- */
226
- async GET(event: RequestEvent): Result<'GET', 'users/:id/passkeys'> {
227
- const { id: userId } = event.params;
228
-
229
- const user = await getUser(userId);
230
- if (!user) error(404, { message: 'User does not exist' });
231
-
232
- await checkAuth(event, userId);
233
-
234
- const passkeys = await getPasskeysByUserId(userId);
235
-
236
- return passkeys.map(p => omit(p, 'publicKey', 'counter'));
237
- },
238
-
239
- /**
240
- * Register a new passkey for an existing user.
241
- */
242
- async PUT(event: RequestEvent): Result<'PUT', 'users/:id/passkeys'> {
243
- const { id: userId } = event.params;
244
- const response = await parseBody(event, PasskeyRegistration);
245
-
246
- const user = await getUser(userId);
247
- if (!user) error(404, { message: 'User does not exist' });
248
-
249
- await checkAuth(event, userId);
250
-
251
- const expectedChallenge = registrations.get(userId);
252
- if (!expectedChallenge) error(404, { message: 'No registration challenge found for this user' });
253
- registrations.delete(userId);
254
-
255
- const { verified, registrationInfo } = await verifyRegistrationResponse({
256
- response,
257
- expectedChallenge,
258
- expectedOrigin: config.auth.origin,
259
- }).catch(withError('Verification failed', 400));
260
-
261
- if (!verified || !registrationInfo) error(401, { message: 'Verification failed' });
262
-
263
- const passkey = await createPasskey({
264
- transports: [],
265
- ...registrationInfo.credential,
266
- userId,
267
- deviceType: registrationInfo.credentialDeviceType,
268
- backedUp: registrationInfo.credentialBackedUp,
269
- }).catch(withError('Failed to create passkey'));
270
-
271
- return omit(passkey, 'publicKey', 'counter');
272
- },
273
- });
274
-
275
- addRoute({
276
- path: '/api/users/:id/sessions',
277
- params,
278
- async GET(event): Result<'POST', 'users/:id/sessions'> {
279
- const { id: userId } = event.params;
280
-
281
- await checkAuth(event, userId);
282
-
283
- return (await getSessions(userId).catch(e => error(503, 'Failed to get sessions' + (config.debug ? ': ' + e : '')))).map(s =>
284
- omit(s, 'token')
285
- );
286
- },
287
- async DELETE(event: RequestEvent): Result<'DELETE', 'users/:id/sessions'> {
288
- const { id: userId } = event.params;
289
- const body = await parseBody(event, LogoutSessions);
290
-
291
- await checkAuth(event, userId, body.confirm_all);
292
-
293
- const query = body.confirm_all ? db.deleteFrom('sessions') : db.deleteFrom('sessions').where('sessions.id', 'in', body.id);
294
-
295
- const result = await query
296
- .where('sessions.userId', '=', userId)
297
- .returningAll()
298
- .execute()
299
- .catch(withError('Failed to delete one or more sessions'));
300
-
301
- return result.map(s => omit(s, 'token'));
302
- },
303
- });
304
-
305
- addRoute({
306
- path: '/api/users/:id/verify_email',
307
- params,
308
- async OPTIONS(event): Result<'OPTIONS', 'users/:id/verify_email'> {
309
- const { id: userId } = event.params;
310
-
311
- if (!config.auth.email_verification) return { enabled: false };
312
-
313
- await checkAuth(event, userId);
314
-
315
- const user = await getUser(userId);
316
- if (!user) error(404, { message: 'User does not exist' });
317
-
318
- if (!config.auth.email_verification) return { enabled: false };
319
-
320
- return { enabled: true };
321
- },
322
- async GET(event): Result<'GET', 'users/:id/verify_email'> {
323
- const { id: userId } = event.params;
324
-
325
- await checkAuth(event, userId);
326
-
327
- const user = await getUser(userId);
328
- if (!user) error(404, { message: 'User does not exist' });
329
-
330
- if (user.emailVerified) error(409, { message: 'Email already verified' });
331
-
332
- const verification = await createVerification('verify_email', userId, config.auth.verification_timeout * 60);
333
-
334
- return omit(verification, 'token', 'role');
335
- },
336
- async POST(event: RequestEvent): Result<'POST', 'users/:id/verify_email'> {
337
- const { id: userId } = event.params;
338
- const { token } = await parseBody(event, z.object({ token: z.string() }));
339
-
340
- await checkAuth(event, userId);
341
-
342
- const user = await getUser(userId);
343
- if (!user) error(404, { message: 'User does not exist' });
344
-
345
- if (user.emailVerified) error(409, { message: 'Email already verified' });
346
-
347
- await useVerification('verify_email', userId, token).catch(withError('Invalid or expired verification token', 400));
348
-
349
- return {};
350
- },
351
- });
package/web/api/utils.ts DELETED
@@ -1,66 +0,0 @@
1
- import { userProtectedFields, userPublicFields, type User } from '@axium/core/user';
2
- import type { NewSessionResponse } from '@axium/core/api';
3
- import { createSession, getSessionAndUser, type UserInternal } from '@axium/server/auth';
4
- import { config } from '@axium/server/config';
5
- import { error, type RequestEvent } from '@sveltejs/kit';
6
- import { pick } from 'utilium';
7
- import z from 'zod/v4';
8
-
9
- export async function parseBody<const Schema extends z.ZodType, const Result extends z.infer<Schema> = z.infer<Schema>>(
10
- event: RequestEvent,
11
- schema: Schema
12
- ): Promise<Result> {
13
- const contentType = event.request.headers.get('content-type');
14
- if (!contentType || !contentType.includes('application/json')) error(415, { message: 'Invalid content type' });
15
-
16
- const body: unknown = await event.request.json().catch(() => error(415, { message: 'Invalid JSON' }));
17
-
18
- try {
19
- return schema.parse(body) as Result;
20
- } catch (e: any) {
21
- error(400, { message: z.prettifyError(e) });
22
- }
23
- }
24
-
25
- export function getToken(event: RequestEvent, sensitive: boolean = false): string | undefined {
26
- const header_token = event.request.headers.get('Authorization')?.replace('Bearer ', '');
27
- if (header_token) return header_token;
28
-
29
- if (config.debug || config.api.cookie_auth) {
30
- return event.cookies.get(sensitive ? 'elevated_token' : 'session_token');
31
- }
32
- }
33
-
34
- export async function checkAuth(event: RequestEvent, userId: string, sensitive: boolean = false): Promise<void> {
35
- const token = getToken(event, sensitive);
36
-
37
- if (!token) throw error(401, { message: 'Missing token' });
38
-
39
- const { user, elevated } = await getSessionAndUser(token).catch(() => error(401, { message: 'Invalid or expired session' }));
40
-
41
- if (user?.id !== userId /* && !user.isAdmin */) error(403, { message: 'User ID mismatch' });
42
-
43
- if (!elevated && sensitive) error(403, 'This token can not be used for sensitive actions');
44
- }
45
-
46
- export async function createSessionData(event: RequestEvent, userId: string, elevated: boolean = false): Promise<NewSessionResponse> {
47
- const { token } = await createSession(userId, elevated);
48
-
49
- if (elevated) {
50
- event.cookies.set('elevated_token', token, { httpOnly: true, path: '/', expires: new Date(Date.now() + 10 * 60_000) });
51
- return { userId, token: '[[redacted:elevated]]' };
52
- } else {
53
- event.cookies.set('session_token', token, { httpOnly: config.auth.secure_cookies, path: '/' });
54
- return { userId, token };
55
- }
56
- }
57
-
58
- export function stripUser(user: UserInternal, includeProtected: boolean = false): User {
59
- return pick(user, ...userPublicFields, ...(includeProtected ? userProtectedFields : []));
60
- }
61
-
62
- export function withError(text: string, code: number = 500) {
63
- return function (e: Error) {
64
- error(code, { message: text + (config.debug ? `: ${e.message}` : '') });
65
- };
66
- }
package/web/app.html DELETED
@@ -1,14 +0,0 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="utf-8" />
5
- <link rel="icon" href="%sveltekit.assets%/favicon.svg" />
6
- <meta name="viewport" content="width=device-width, initial-scale=1" />
7
- <meta name="color-scheme" content="dark light" />
8
- %sveltekit.head%
9
- </head>
10
-
11
- <body>
12
- <div style="display: contents">%sveltekit.body%</div>
13
- </body>
14
- </html>
package/web/auth.ts DELETED
@@ -1,8 +0,0 @@
1
- import { allLogLevels } from 'logzen';
2
- import { createWriteStream } from 'node:fs';
3
- import { join } from 'node:path/posix';
4
- import config from '../dist/config.js';
5
- import { findDir, logger } from '../dist/io.js';
6
-
7
- logger.attach(createWriteStream(join(findDir(false), 'server.log')), { output: allLogLevels });
8
- await config.loadDefaults();
@@ -1 +0,0 @@
1
- export * from './utils.js';
package/web/index.ts DELETED
@@ -1 +0,0 @@
1
- export * from './lib/index.js';
package/web/lib/auth.ts DELETED
@@ -1,12 +0,0 @@
1
- import type { SessionInternal, UserInternal } from '@axium/server/auth';
2
- import { getSessionAndUser } from '@axium/server/auth';
3
- import type { RequestEvent } from '@sveltejs/kit';
4
-
5
- export async function authenticate(event: RequestEvent): Promise<(SessionInternal & { user: UserInternal | null }) | null> {
6
- const maybe_header = event.request.headers.get('Authorization');
7
- const token = maybe_header?.startsWith('Bearer ') ? maybe_header.slice(7) : event.cookies.get('session_token');
8
-
9
- if (!token) return null;
10
-
11
- return await getSessionAndUser(token).catch(() => null);
12
- }
package/web/lib/index.ts DELETED
@@ -1,5 +0,0 @@
1
- export { default as Dialog } from './Dialog.svelte';
2
- export { default as FormDialog } from './FormDialog.svelte';
3
- export * from './icons/index.js';
4
- export { default as Toast } from './Toast.svelte';
5
- export { default as UserCard } from './UserCard.svelte';
@@ -1,6 +0,0 @@
1
- <script lang="ts">
2
- import '$lib/styles.css';
3
- const { children } = $props();
4
- </script>
5
-
6
- {@render children()}
@@ -1,13 +0,0 @@
1
- import { error, redirect, type LoadEvent } from '@sveltejs/kit';
2
- import { resolveRoute } from '@axium/server/routes';
3
-
4
- export async function load(event: LoadEvent) {
5
- const route = resolveRoute(event);
6
-
7
- if (!route && event.url.pathname === '/') redirect(303, '/_axium/default');
8
- if (!route) error(404);
9
-
10
- if (route.server == true) error(409, 'This is a server route, not a page route');
11
-
12
- return await route.load(event);
13
- }
@@ -1,14 +0,0 @@
1
- import { error, type LoadEvent } from '@sveltejs/kit';
2
- import { apps } from '@axium/server/apps';
3
-
4
- export async function load(event: LoadEvent) {
5
- const app = apps.get(event.params.appId);
6
-
7
- if (!app) error(404);
8
-
9
- const route = app.resolveRoute(event);
10
-
11
- if (!route) error(404);
12
-
13
- return await route.load(event);
14
- }
@@ -1,49 +0,0 @@
1
- import type { RequestMethod } from '@axium/core/requests';
2
- import { resolveRoute } from '@axium/server/routes';
3
- import { config } from '@axium/server/config';
4
- import { error, json, type RequestEvent, type RequestHandler } from '@sveltejs/kit';
5
- import z from 'zod/v4';
6
-
7
- function handler(method: RequestMethod): RequestHandler {
8
- return async function (event: RequestEvent): Promise<Response> {
9
- const _warnings: string[] = [];
10
- if (!event.request.headers.get('Accept')?.includes('application/json')) {
11
- _warnings.push('Only application/json is supported');
12
- event.request.headers.set('Accept', 'application/json');
13
- }
14
-
15
- const route = resolveRoute(event);
16
-
17
- if (!route) error(404, 'Route not found');
18
- if (!route.server) error(503, 'Route is not a server route');
19
-
20
- if (config.debug) console.log(event.request.method, route.path);
21
-
22
- for (const [key, type] of Object.entries(route.params || {})) {
23
- if (!type) continue;
24
-
25
- try {
26
- event.params[key] = type.parse(event.params[key]) as any;
27
- } catch (e: any) {
28
- error(400, `Invalid parameter: ${z.prettifyError(e)}`);
29
- }
30
- }
31
-
32
- if (typeof route[method] != 'function') error(405, `Method ${method} not allowed for ${route.path}`);
33
-
34
- const result: object & { _warnings?: string[] } = await route[method](event);
35
-
36
- result._warnings ||= [];
37
- result._warnings.push(..._warnings);
38
-
39
- return json(result);
40
- };
41
- }
42
-
43
- export const HEAD = handler('HEAD');
44
- export const GET = handler('GET');
45
- export const POST = handler('POST');
46
- export const PUT = handler('PUT');
47
- export const DELETE = handler('DELETE');
48
- export const PATCH = handler('PATCH');
49
- export const OPTIONS = handler('OPTIONS');
package/web/utils.ts DELETED
@@ -1,26 +0,0 @@
1
- import type { ActionFailure, RequestEvent } from '@sveltejs/kit';
2
- import { fail } from '@sveltejs/kit';
3
- import z from 'zod/v4';
4
-
5
- // eslint-disable-next-line @typescript-eslint/no-empty-object-type
6
- export interface FormFail<S extends z.ZodType, E extends object = object> extends ActionFailure<z.infer<S> & { error: string } & E> {}
7
-
8
- export async function parseForm<S extends z.ZodObject, E extends object = object>(
9
- event: RequestEvent,
10
- schema: S,
11
- errorData?: E
12
- ): Promise<[z.infer<S>, FormFail<S, E> | null]> {
13
- const formData = Object.fromEntries(await event.request.formData());
14
- const { data, error, success } = schema.safeParse(formData);
15
-
16
- if (success) return [data, null];
17
-
18
- return [
19
- data,
20
- fail(400, {
21
- ...data,
22
- ...errorData,
23
- error: z.prettifyError(error),
24
- }),
25
- ];
26
- }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes