@axium/server 0.7.6 → 0.9.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 (57) hide show
  1. package/dist/apps.d.ts +15 -0
  2. package/dist/apps.js +20 -0
  3. package/dist/auth.d.ts +53 -30
  4. package/dist/auth.js +103 -130
  5. package/dist/cli.js +176 -42
  6. package/dist/config.d.ts +57 -312
  7. package/dist/config.js +65 -31
  8. package/dist/database.d.ts +31 -40
  9. package/dist/database.js +165 -62
  10. package/dist/io.js +6 -2
  11. package/dist/plugins.d.ts +8 -24
  12. package/dist/plugins.js +10 -14
  13. package/dist/routes.d.ts +55 -0
  14. package/dist/routes.js +54 -0
  15. package/package.json +11 -16
  16. package/web/api/index.ts +7 -0
  17. package/web/api/metadata.ts +35 -0
  18. package/web/api/passkeys.ts +56 -0
  19. package/web/api/readme.md +1 -0
  20. package/web/api/register.ts +83 -0
  21. package/web/api/schemas.ts +22 -0
  22. package/web/api/session.ts +33 -0
  23. package/web/api/users.ts +351 -0
  24. package/web/api/utils.ts +66 -0
  25. package/web/auth.ts +1 -5
  26. package/web/hooks.server.ts +12 -1
  27. package/web/index.server.ts +0 -1
  28. package/web/lib/ClipboardCopy.svelte +42 -0
  29. package/web/lib/Dialog.svelte +3 -6
  30. package/web/lib/FormDialog.svelte +61 -14
  31. package/web/lib/Toast.svelte +8 -1
  32. package/web/lib/UserCard.svelte +1 -1
  33. package/web/lib/auth.ts +12 -0
  34. package/web/lib/icons/Icon.svelte +7 -13
  35. package/web/lib/index.ts +0 -2
  36. package/web/lib/styles.css +18 -1
  37. package/web/routes/+layout.svelte +1 -1
  38. package/web/routes/[...path]/+page.server.ts +13 -0
  39. package/web/routes/[appId]/[...page]/+page.server.ts +14 -0
  40. package/web/routes/_axium/default/+page.svelte +11 -0
  41. package/web/routes/account/+page.svelte +291 -0
  42. package/web/routes/api/[...path]/+server.ts +49 -0
  43. package/web/routes/login/+page.svelte +25 -0
  44. package/web/routes/logout/+page.svelte +13 -0
  45. package/web/routes/register/+page.svelte +21 -0
  46. package/web/tsconfig.json +2 -1
  47. package/web/utils.ts +9 -15
  48. package/web/actions.ts +0 -58
  49. package/web/lib/Account.svelte +0 -76
  50. package/web/lib/SignUp.svelte +0 -20
  51. package/web/lib/account.css +0 -36
  52. package/web/routes/+page.server.ts +0 -16
  53. package/web/routes/+page.svelte +0 -10
  54. package/web/routes/name/+page.server.ts +0 -5
  55. package/web/routes/name/+page.svelte +0 -20
  56. package/web/routes/signup/+page.server.ts +0 -10
  57. package/web/routes/signup/+page.svelte +0 -15
@@ -0,0 +1,21 @@
1
+ <script lang="ts">
2
+ import FormDialog from '$lib/FormDialog.svelte';
3
+ import { register } from '@axium/client/user';
4
+
5
+ let { dialog = $bindable(), pageMode = true } = $props();
6
+ </script>
7
+
8
+ <svelte:head>
9
+ <title>Sign Up</title>
10
+ </svelte:head>
11
+
12
+ <FormDialog bind:dialog submitText="Register" submit={register} {pageMode}>
13
+ <div>
14
+ <label for="name">Display Name</label>
15
+ <input name="name" type="text" required />
16
+ </div>
17
+ <div>
18
+ <label for="email">Email</label>
19
+ <input name="email" type="email" required />
20
+ </div>
21
+ </FormDialog>
package/web/tsconfig.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "extends": "../.svelte-kit/tsconfig.json",
3
3
  "compilerOptions": {
4
- "target": "ES2021"
4
+ "target": "ES2021",
5
+ "lib": ["ESNext", "DOM", "DOM.Iterable"]
5
6
  },
6
7
  "include": ["**/*.ts", "**/*.svelte"]
7
8
  }
package/web/utils.ts CHANGED
@@ -1,21 +1,15 @@
1
- import type { Session } from '@auth/sveltekit';
2
1
  import type { ActionFailure, RequestEvent } from '@sveltejs/kit';
3
- import { fail, redirect } from '@sveltejs/kit';
4
- import type * as z from 'zod';
5
- import { fromError } from 'zod-validation-error';
6
- import config from '../dist/config.js';
7
-
8
- export async function loadSession(event: RequestEvent): Promise<{ session: Session }> {
9
- const session = await event.locals.auth();
10
- if (!session) redirect(307, '/auth/signin');
11
- if (!session.user.name && event.url.pathname != config.web.prefix + '/name') redirect(307, config.web.prefix + '/name');
12
- return { session };
13
- }
2
+ import { fail } from '@sveltejs/kit';
3
+ import z from 'zod/v4';
14
4
 
15
5
  // eslint-disable-next-line @typescript-eslint/no-empty-object-type
16
- export interface FormFail<S extends z.AnyZodObject, E extends object = object> extends ActionFailure<z.infer<S> & { error: string } & E> {}
6
+ export interface FormFail<S extends z.ZodType, E extends object = object> extends ActionFailure<z.infer<S> & { error: string } & E> {}
17
7
 
18
- export async function parseForm<S extends z.AnyZodObject, E extends object = object>(event: RequestEvent, schema: S, errorData?: E): Promise<[z.infer<S>, FormFail<S, E> | null]> {
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]> {
19
13
  const formData = Object.fromEntries(await event.request.formData());
20
14
  const { data, error, success } = schema.safeParse(formData);
21
15
 
@@ -26,7 +20,7 @@ export async function parseForm<S extends z.AnyZodObject, E extends object = obj
26
20
  fail(400, {
27
21
  ...data,
28
22
  ...errorData,
29
- error: fromError(error, { prefix: null }).toString(),
23
+ error: z.prettifyError(error),
30
24
  }),
31
25
  ];
32
26
  }
package/web/actions.ts DELETED
@@ -1,58 +0,0 @@
1
- import { Registration, User } from '@axium/core/schemas';
2
- import type { RequestEvent } from '@sveltejs/kit';
3
- import { fail } from '@sveltejs/kit';
4
- import { adapter, register } from '../dist/auth.js';
5
- import { parseForm } from './utils.js';
6
-
7
- export async function editEmail(event: RequestEvent) {
8
- const session = await event.locals.auth();
9
- if (!session) return fail(401, { error: 'You are not signed in' });
10
-
11
- const [{ email } = {}, error] = await parseForm(event, User.pick({ email: true }));
12
- if (error) return error;
13
-
14
- const user = await adapter.getUserByEmail(session.user.email);
15
- if (!user) return fail(401, { email, error: 'You are not signed in' });
16
-
17
- try {
18
- await adapter.updateUser({ id: user.id, email, image: user.image });
19
- } catch (error: any) {
20
- return fail(400, { email, error: typeof error === 'string' ? error : error.message });
21
- }
22
- return { success: true };
23
- }
24
-
25
- export async function editName(event: RequestEvent) {
26
- const session = await event.locals.auth();
27
- if (!session) return fail(401, { error: 'You are not signed in' });
28
-
29
- const [{ name } = {}, error] = await parseForm(event, User.pick({ name: true }));
30
- if (error) return error;
31
-
32
- const user = await adapter.getUserByEmail(session.user.email);
33
- if (!user) return fail(401, { name, error: 'You are not signed in' });
34
-
35
- try {
36
- await adapter.updateUser({ id: user.id, name, image: user.image });
37
- } catch (error: any) {
38
- return fail(400, { name, error: typeof error === 'string' ? error : error.message });
39
- }
40
- return { success: true };
41
- }
42
-
43
- export async function signup(event: RequestEvent) {
44
- const [data, error] = await parseForm(event, Registration);
45
- if (error) return error;
46
-
47
- try {
48
- const { session } = await register(data);
49
- event.cookies.set('session', session.sessionToken, {
50
- path: '/',
51
- expires: session.expires,
52
- httpOnly: true,
53
- });
54
- return { ...data, success: true, data: session.sessionToken };
55
- } catch (error: any) {
56
- return fail(400, { ...data, error: typeof error === 'string' ? error : error.message });
57
- }
58
- }
@@ -1,76 +0,0 @@
1
- <script lang="ts">
2
- import { goto } from '$app/navigation';
3
- import { getUserImage } from '@axium/core';
4
- import './account.css';
5
- import FormDialog from './FormDialog.svelte';
6
- import Icon from './icons/Icon.svelte';
7
- import './styles.css';
8
-
9
- const { data, children = () => {}, form } = $props();
10
-
11
- const user = $derived(data.user);
12
-
13
- $effect(() => {
14
- if (!user) goto('/auth/signin');
15
- });
16
-
17
- const image = $derived(getUserImage(user));
18
-
19
- let changeEmail = $state(false);
20
- let changeName = $state(false);
21
- </script>
22
-
23
- <div class="Account flex-content">
24
- <img class="pfp" src={image} alt="User profile" />
25
- <p class="greeting">Welcome, {user.name}</p>
26
- <div class="account-section main">
27
- <div class="account-item">
28
- <p class="subtle">Name</p>
29
- <p>{user.name}</p>
30
- <button style:display="contents" class="change" onclick={() => (changeName = true)}><Icon i="chevron-right" /></button>
31
- </div>
32
- <div class="account-item">
33
- <p class="subtle">Email</p>
34
- <p>{user.email}</p>
35
- <button style:display="contents" class="change" onclick={() => (changeEmail = true)}><Icon i="chevron-right" /></button>
36
- </div>
37
- <div class="account-item">
38
- <p class="subtle">User ID <dfn title="This is your UUID."><Icon i="regular/circle-info" /></dfn></p>
39
- <p>{user.id}</p>
40
- </div>
41
- <a class="signout" href="/auth/signout"><button>Sign out</button></a>
42
- </div>
43
- {@render children()}
44
- </div>
45
-
46
- <FormDialog bind:active={changeEmail} {form} submitText="Change" action="?/email">
47
- <div>
48
- <label for="email">Email Address</label>
49
- <input name="email" type="email" value={form?.email || user.email || ''} required />
50
- </div>
51
- </FormDialog>
52
-
53
- <FormDialog bind:active={changeName} {form} submitText="Change" action="?/name">
54
- <div>
55
- <label for="name">What do you want to be called?</label>
56
- <input name="name" type="text" value={form?.name || user.name || ''} required />
57
- </div>
58
- </FormDialog>
59
-
60
- <style>
61
- .pfp {
62
- width: 100px;
63
- height: 100px;
64
- border-radius: 50%;
65
- border: 1px solid #8888;
66
- margin-top: 3em;
67
- }
68
-
69
- .greeting {
70
- font-size: 2em;
71
- }
72
-
73
- .signout {
74
- margin-top: 2em;
75
- }
76
- </style>
@@ -1,20 +0,0 @@
1
- <script lang="ts">
2
- import FormDialog from './FormDialog.svelte';
3
- import './styles.css';
4
- let { form } = $props();
5
- </script>
6
-
7
- <FormDialog pageMode={true} {form} submitText="Sign Up">
8
- <div>
9
- <label for="name">Display Name</label>
10
- <input name="name" type="text" value={form?.name || ''} required />
11
- </div>
12
- <div>
13
- <label for="email">Email</label>
14
- <input name="email" type="email" value={form?.email || ''} required />
15
- </div>
16
- <div>
17
- <label for="password">Password</label>
18
- <input name="password" type="password" />
19
- </div>
20
- </FormDialog>
@@ -1,36 +0,0 @@
1
- .account-section {
2
- width: 50%;
3
- padding-top: 4em;
4
-
5
- > div:has(+ div) {
6
- border-bottom: 1px solid #8888;
7
- }
8
- }
9
-
10
- .account-section .account-item {
11
- display: grid;
12
- grid-template-columns: 10em 1fr 2em;
13
- align-items: center;
14
- width: 100%;
15
- gap: 1em;
16
- text-wrap: nowrap;
17
- padding-bottom: 1em;
18
-
19
- > :first-child {
20
- margin: 0 5em 0 1em;
21
- grid-column: 1;
22
- }
23
-
24
- > :nth-child(2) {
25
- margin: 0;
26
- grid-column: 2;
27
- }
28
-
29
- > :last-child:nth-child(3) {
30
- margin: 0;
31
- display: inline;
32
- grid-column: 3;
33
- font-size: 0.75em;
34
- cursor: pointer;
35
- }
36
- }
@@ -1,16 +0,0 @@
1
- import type { Actions } from '@sveltejs/kit';
2
- import { adapter } from '../../dist/auth.js';
3
- import { editEmail, editName } from '../actions.js';
4
- import { loadSession } from '../utils.js';
5
- import type { PageServerLoadEvent } from './$types.js';
6
-
7
- export async function load(event: PageServerLoadEvent) {
8
- const { session } = await loadSession(event);
9
- const user = await adapter.getUserByEmail(session.user.email);
10
- return { session, user };
11
- }
12
-
13
- export const actions = {
14
- email: editEmail,
15
- name: editName,
16
- } satisfies Actions;
@@ -1,10 +0,0 @@
1
- <script lang="ts">
2
- import Account from '../lib/Account.svelte';
3
- const { data, form } = $props();
4
- </script>
5
-
6
- <svelte:head>
7
- <title>Account</title>
8
- </svelte:head>
9
-
10
- <Account {data} {form}></Account>
@@ -1,5 +0,0 @@
1
- import type { Actions } from '@sveltejs/kit';
2
- import { editName } from '../../actions.js';
3
-
4
- export { loadSession as load } from '../../utils.js';
5
- export const actions = { default: editName } satisfies Actions;
@@ -1,20 +0,0 @@
1
- <script lang="ts">
2
- import { goto } from '$app/navigation';
3
- import FormDialog from '../../lib/FormDialog.svelte';
4
- import '../../lib/styles.css';
5
- let { form, data } = $props();
6
- $effect(() => {
7
- if (form?.success) goto('/');
8
- });
9
- </script>
10
-
11
- <svelte:head>
12
- <title>Edit Name</title>
13
- </svelte:head>
14
-
15
- <FormDialog pageMode={true} {form} submitText="Change">
16
- <div>
17
- <label for="name">What do you want to be called?</label>
18
- <input name="name" type="text" value={form?.name || data.session.user.name || ''} required />
19
- </div>
20
- </FormDialog>
@@ -1,10 +0,0 @@
1
- import { redirect } from '@sveltejs/kit';
2
- import config from '../../../dist/config.js';
3
- import { signup } from '../../actions.js';
4
- import type { Actions } from './$types.js';
5
-
6
- export function load() {
7
- if (!config.auth.credentials) return redirect(307, '/auth/signin');
8
- }
9
-
10
- export const actions = { default: signup } satisfies Actions;
@@ -1,15 +0,0 @@
1
- <script lang="ts">
2
- import { goto } from '$app/navigation';
3
- import SignUp from '../../lib/SignUp.svelte';
4
- import '../../lib/styles.css';
5
- let { form } = $props();
6
- $effect(() => {
7
- if (form?.success) goto('/');
8
- });
9
- </script>
10
-
11
- <svelte:head>
12
- <title>Sign Up</title>
13
- </svelte:head>
14
-
15
- <SignUp {form} />