@axium/server 0.7.6 → 0.8.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 (56) hide show
  1. package/dist/apps.d.ts +15 -0
  2. package/dist/apps.js +20 -0
  3. package/dist/auth.d.ts +63 -30
  4. package/dist/auth.js +110 -129
  5. package/dist/cli.js +33 -6
  6. package/dist/config.d.ts +241 -61
  7. package/dist/config.js +26 -2
  8. package/dist/database.d.ts +28 -37
  9. package/dist/database.js +124 -50
  10. package/dist/io.js +6 -2
  11. package/dist/plugins.d.ts +7 -24
  12. package/dist/plugins.js +9 -14
  13. package/dist/routes.d.ts +55 -0
  14. package/dist/routes.js +54 -0
  15. package/package.json +7 -15
  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 +340 -0
  24. package/web/api/utils.ts +66 -0
  25. package/web/auth.ts +1 -5
  26. package/web/hooks.server.ts +6 -1
  27. package/web/index.server.ts +0 -1
  28. package/web/lib/Dialog.svelte +3 -6
  29. package/web/lib/FormDialog.svelte +53 -14
  30. package/web/lib/Toast.svelte +8 -1
  31. package/web/lib/UserCard.svelte +1 -1
  32. package/web/lib/auth.ts +12 -0
  33. package/web/lib/icons/Icon.svelte +5 -7
  34. package/web/lib/index.ts +0 -2
  35. package/web/lib/styles.css +12 -1
  36. package/web/routes/+layout.svelte +1 -1
  37. package/web/routes/[...path]/+page.server.ts +13 -0
  38. package/web/routes/[appId]/[...page]/+page.server.ts +14 -0
  39. package/web/routes/_axium/default/+page.svelte +11 -0
  40. package/web/routes/account/+page.svelte +224 -0
  41. package/web/routes/api/[...path]/+server.ts +49 -0
  42. package/web/routes/login/+page.svelte +25 -0
  43. package/web/routes/logout/+page.svelte +13 -0
  44. package/web/routes/register/+page.svelte +21 -0
  45. package/web/tsconfig.json +2 -1
  46. package/web/utils.ts +9 -15
  47. package/web/actions.ts +0 -58
  48. package/web/lib/Account.svelte +0 -76
  49. package/web/lib/SignUp.svelte +0 -20
  50. package/web/lib/account.css +0 -36
  51. package/web/routes/+page.server.ts +0 -16
  52. package/web/routes/+page.svelte +0 -10
  53. package/web/routes/name/+page.server.ts +0 -5
  54. package/web/routes/name/+page.svelte +0 -20
  55. package/web/routes/signup/+page.server.ts +0 -10
  56. package/web/routes/signup/+page.svelte +0 -15
@@ -1,52 +1,41 @@
1
- import type { AdapterAccountType as db } from '@auth/core/adapters';
1
+ import type { Preferences } from '@axium/core';
2
2
  import { Kysely, type GeneratedAlways } from 'kysely';
3
- import type { Preferences } from './auth.js';
4
- import { type MaybeOutput, type WithOutput } from './io.js';
3
+ import type { VerificationRole } from './auth.js';
4
+ import type { MaybeOutput, WithOutput } from './io.js';
5
+ import type { AuthenticatorTransportFuture, CredentialDeviceType } from '@simplewebauthn/server';
5
6
  export interface Schema {
6
- User: {
7
- id: GeneratedAlways<string>;
8
- name: string | null;
7
+ users: {
8
+ id: GeneratedAlways<string> & string;
9
9
  email: string;
10
- emailVerified: Date | null;
11
- image: string | null;
12
- password: string | null;
13
- salt: string | null;
14
- preferences: Preferences;
15
- };
16
- Account: {
17
- id: GeneratedAlways<string>;
18
- userId: string;
19
- type: db;
20
- provider: string;
21
- providerAccountId: string;
22
- refresh_token?: string;
23
- access_token?: string;
24
- expires_at?: number;
25
- token_type?: Lowercase<string>;
26
- scope?: string;
27
- id_token?: string;
28
- session_state: string | null;
10
+ name: string;
11
+ image?: string | null;
12
+ emailVerified?: Date | null;
13
+ preferences?: Preferences;
29
14
  };
30
- Session: {
15
+ sessions: {
31
16
  id: GeneratedAlways<string>;
17
+ created: GeneratedAlways<Date>;
32
18
  userId: string;
33
- sessionToken: string;
19
+ token: string;
34
20
  expires: Date;
21
+ elevated: boolean;
35
22
  };
36
- VerificationToken: {
37
- identifier: string;
23
+ verifications: {
24
+ userId: string;
38
25
  token: string;
39
26
  expires: Date;
27
+ role: VerificationRole;
40
28
  };
41
- Authenticator: {
42
- credentialID: string;
29
+ passkeys: {
30
+ id: string;
31
+ name: string | null;
32
+ createdAt: GeneratedAlways<Date>;
43
33
  userId: string;
44
- providerAccountId: string;
45
- credentialPublicKey: string;
34
+ publicKey: Uint8Array;
46
35
  counter: number;
47
- credentialDeviceType: string;
48
- credentialBackedUp: boolean;
49
- transports: string | null;
36
+ deviceType: CredentialDeviceType;
37
+ backedUp: boolean;
38
+ transports: AuthenticatorTransportFuture[];
50
39
  };
51
40
  }
52
41
  export interface Database extends Kysely<Schema>, AsyncDisposable {
@@ -55,7 +44,7 @@ export declare let database: Database;
55
44
  export declare function connect(): Database;
56
45
  export interface Stats {
57
46
  users: number;
58
- accounts: number;
47
+ passkeys: number;
59
48
  sessions: number;
60
49
  }
61
50
  export declare function count(table: keyof Schema): Promise<number>;
@@ -69,11 +58,13 @@ export interface InitOptions extends OpOptions {
69
58
  skip: boolean;
70
59
  }
71
60
  export declare function shouldRecreate(opt: InitOptions & WithOutput): boolean;
61
+ export declare function getHBA(opt: OpOptions & WithOutput): Promise<[content: string, writeBack: (newContent: string) => void]>;
72
62
  export interface PluginShortcuts {
73
63
  done: () => void;
74
64
  warnExists: (error: string | Error) => void;
75
65
  }
76
66
  export declare function init(opt: InitOptions): Promise<void>;
67
+ export declare function check(opt: OpOptions): Promise<void>;
77
68
  /**
78
69
  * Completely remove Axium from the database.
79
70
  */
package/dist/database.js CHANGED
@@ -52,6 +52,7 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
52
52
  });
53
53
  import { Kysely, PostgresDialect, sql } from 'kysely';
54
54
  import { randomBytes } from 'node:crypto';
55
+ import { readFileSync, writeFileSync } from 'node:fs';
55
56
  import pg from 'pg';
56
57
  import config from './config.js';
57
58
  import { _fixOutput, run, someWarnings } from './io.js';
@@ -76,15 +77,15 @@ export async function count(table) {
76
77
  }
77
78
  export async function status() {
78
79
  return {
79
- users: await count('User'),
80
- accounts: await count('Account'),
81
- sessions: await count('Session'),
80
+ users: await count('users'),
81
+ passkeys: await count('passkeys'),
82
+ sessions: await count('sessions'),
82
83
  };
83
84
  }
84
85
  export async function statusText() {
85
86
  try {
86
87
  const stats = await status();
87
- return `${stats.users} users, ${stats.accounts} accounts, ${stats.sessions} sessions`;
88
+ return `${stats.users} users, ${stats.passkeys} passkeys, ${stats.sessions} sessions`;
88
89
  }
89
90
  catch (error) {
90
91
  throw typeof error == 'object' && 'message' in error ? error.message : error;
@@ -102,6 +103,30 @@ export function shouldRecreate(opt) {
102
103
  opt.output('warn', 'already exists. Use --skip to skip or --force to re-create.');
103
104
  throw 2;
104
105
  }
106
+ export async function getHBA(opt) {
107
+ const hbaShowResult = await run(opt, 'Finding pg_hba.conf', `sudo -u postgres psql -c "SHOW hba_file"`);
108
+ opt.output('start', 'Resolving pg_hba.conf path');
109
+ const hbaPath = hbaShowResult.match(/^\s*(.+\.conf)\s*$/m)?.[1]?.trim();
110
+ if (!hbaPath) {
111
+ throw 'failed. You will need to add password-based auth for the axium user manually.';
112
+ }
113
+ opt.output('done');
114
+ opt.output('debug', `Found pg_hba.conf at ${hbaPath}`);
115
+ opt.output('start', 'Reading HBA configuration');
116
+ const content = readFileSync(hbaPath, 'utf-8');
117
+ opt.output('done');
118
+ const writeBack = (newContent) => {
119
+ opt.output('start', 'Writing HBA configuration');
120
+ writeFileSync(hbaPath, newContent);
121
+ opt.output('done');
122
+ };
123
+ return [content, writeBack];
124
+ }
125
+ const pgHba = `
126
+ local axium axium md5
127
+ host axium axium 127.0.0.1/32 md5
128
+ host axium axium ::1/128 md5
129
+ `;
105
130
  export async function init(opt) {
106
131
  const env_1 = { stack: [], error: void 0, hasError: false };
107
132
  try {
@@ -110,8 +135,10 @@ export async function init(opt) {
110
135
  config.save({ db: { password: randomBytes(32).toString('base64') } }, true);
111
136
  opt.output('debug', 'Generated password and wrote to global config');
112
137
  }
138
+ await run(opt, 'Checking for sudo', 'which sudo');
139
+ await run(opt, 'Checking for psql', 'which psql');
113
140
  const _sql = (command, message) => run(opt, message, `sudo -u postgres psql -c "${command}"`);
114
- const warnExists = someWarnings(opt.output, [/(schema|relation) "\w+" already exists/, 'already exists.']);
141
+ const warnExists = someWarnings(opt.output, [/(schema|relation) "[\w.]+" already exists/, 'already exists.']);
115
142
  const done = () => opt.output('done');
116
143
  await _sql('CREATE DATABASE axium', 'Creating database').catch(async (error) => {
117
144
  if (error != 'database "axium" already exists')
@@ -134,79 +161,73 @@ export async function init(opt) {
134
161
  await _sql('GRANT ALL PRIVILEGES ON DATABASE axium TO axium', 'Granting database privileges');
135
162
  await _sql('GRANT ALL PRIVILEGES ON SCHEMA public TO axium', 'Granting schema privileges');
136
163
  await _sql('ALTER DATABASE axium OWNER TO axium', 'Setting database owner');
164
+ await getHBA(opt)
165
+ .then(([content, writeBack]) => {
166
+ opt.output('start', 'Checking for Axium HBA configuration');
167
+ if (content.includes(pgHba))
168
+ throw 'already exists.';
169
+ done();
170
+ opt.output('start', 'Adding Axium HBA configuration');
171
+ const newContent = content.replace(/^local\s+all\s+all.*$/m, `$&\n${pgHba}`);
172
+ done();
173
+ writeBack(newContent);
174
+ })
175
+ .catch(e => opt.output('warn', e));
137
176
  await _sql('SELECT pg_reload_conf()', 'Reloading configuration');
138
177
  const db = __addDisposableResource(env_1, connect(), true);
139
- opt.output('start', 'Creating table User');
178
+ opt.output('start', 'Creating table users');
140
179
  await db.schema
141
- .createTable('User')
180
+ .createTable('users')
142
181
  .addColumn('id', 'uuid', col => col.primaryKey().defaultTo(sql `gen_random_uuid()`))
143
182
  .addColumn('name', 'text')
144
183
  .addColumn('email', 'text', col => col.unique().notNull())
145
184
  .addColumn('emailVerified', 'timestamptz')
146
185
  .addColumn('image', 'text')
147
- .addColumn('password', 'text')
148
- .addColumn('salt', 'text')
149
186
  .addColumn('preferences', 'jsonb', col => col.notNull().defaultTo(sql `'{}'::jsonb`))
150
187
  .execute()
151
188
  .then(done)
152
189
  .catch(warnExists);
153
- opt.output('start', 'Creating table Account');
154
- await db.schema
155
- .createTable('Account')
156
- .addColumn('id', 'uuid', col => col.primaryKey().defaultTo(sql `gen_random_uuid()`))
157
- .addColumn('userId', 'uuid', col => col.references('User.id').onDelete('cascade').notNull())
158
- .addColumn('type', 'text', col => col.notNull())
159
- .addColumn('provider', 'text', col => col.notNull())
160
- .addColumn('providerAccountId', 'text', col => col.notNull())
161
- .addColumn('refresh_token', 'text')
162
- .addColumn('access_token', 'text')
163
- .addColumn('expires_at', 'bigint')
164
- .addColumn('token_type', 'text')
165
- .addColumn('scope', 'text')
166
- .addColumn('id_token', 'text')
167
- .addColumn('session_state', 'text')
168
- .execute()
169
- .then(done)
170
- .catch(warnExists);
171
- opt.output('start', 'Creating index for Account.userId');
172
- await db.schema.createIndex('Account_userId_index').on('Account').column('userId').execute().then(done).catch(warnExists);
173
- opt.output('start', 'Creating table Session');
190
+ opt.output('start', 'Creating table sessions');
174
191
  await db.schema
175
- .createTable('Session')
192
+ .createTable('sessions')
176
193
  .addColumn('id', 'uuid', col => col.primaryKey().defaultTo(sql `gen_random_uuid()`))
177
- .addColumn('userId', 'uuid', col => col.references('User.id').onDelete('cascade').notNull())
178
- .addColumn('sessionToken', 'text', col => col.notNull().unique())
194
+ .addColumn('userId', 'uuid', col => col.references('users.id').onDelete('cascade').notNull())
195
+ .addColumn('token', 'text', col => col.notNull().unique())
196
+ .addColumn('created', 'timestamptz', col => col.notNull())
179
197
  .addColumn('expires', 'timestamptz', col => col.notNull())
198
+ .addColumn('elevated', 'boolean', col => col.notNull())
180
199
  .execute()
181
200
  .then(done)
182
201
  .catch(warnExists);
183
- opt.output('start', 'Creating index for Session.userId');
184
- await db.schema.createIndex('Session_userId_index').on('Session').column('userId').execute().then(done).catch(warnExists);
185
- opt.output('start', 'Creating table VerificationToken');
202
+ opt.output('start', 'Creating index for sessions.userId');
203
+ await db.schema.createIndex('sessions_userId_index').on('sessions').column('userId').execute().then(done).catch(warnExists);
204
+ opt.output('start', 'Creating table verifications');
186
205
  await db.schema
187
- .createTable('VerificationToken')
188
- .addColumn('identifier', 'text', col => col.notNull())
206
+ .createTable('verifications')
207
+ .addColumn('userId', 'uuid', col => col.references('users.id').onDelete('cascade').notNull())
189
208
  .addColumn('token', 'text', col => col.notNull().unique())
190
209
  .addColumn('expires', 'timestamptz', col => col.notNull())
210
+ .addColumn('role', 'text', col => col.notNull())
191
211
  .execute()
192
212
  .then(done)
193
213
  .catch(warnExists);
194
- opt.output('start', 'Creating table Authenticator');
214
+ opt.output('start', 'Creating table passkeys');
195
215
  await db.schema
196
- .createTable('Authenticator')
197
- .addColumn('credentialID', 'text', col => col.primaryKey().notNull())
198
- .addColumn('userId', 'uuid', col => col.notNull().references('User.id').onDelete('cascade').onUpdate('cascade'))
199
- .addColumn('providerAccountId', 'text', col => col.notNull())
200
- .addColumn('credentialPublicKey', 'text', col => col.notNull())
216
+ .createTable('passkeys')
217
+ .addColumn('id', 'text', col => col.primaryKey().notNull())
218
+ .addColumn('name', 'text')
219
+ .addColumn('createdAt', 'timestamptz', col => col.notNull().defaultTo(sql `now()`))
220
+ .addColumn('userId', 'uuid', col => col.notNull().references('users.id').onDelete('cascade').onUpdate('cascade'))
221
+ .addColumn('publicKey', 'bytea', col => col.notNull())
201
222
  .addColumn('counter', 'integer', col => col.notNull())
202
- .addColumn('credentialDeviceType', 'text', col => col.notNull())
203
- .addColumn('credentialBackedUp', 'boolean', col => col.notNull())
204
- .addColumn('transports', 'text')
223
+ .addColumn('deviceType', 'text', col => col.notNull())
224
+ .addColumn('backedUp', 'boolean', col => col.notNull())
225
+ .addColumn('transports', sql `text[]`)
205
226
  .execute()
206
227
  .then(done)
207
228
  .catch(warnExists);
208
- opt.output('start', 'Creating index for Authenticator.credentialID');
209
- await db.schema.createIndex('Authenticator_credentialID_key').on('Authenticator').column('credentialID').execute().then(done).catch(warnExists);
229
+ opt.output('start', 'Creating index for passkeys.id');
230
+ await db.schema.createIndex('passkeys_id_key').on('passkeys').column('id').execute().then(done).catch(warnExists);
210
231
  for (const plugin of plugins) {
211
232
  if (!plugin.db_init)
212
233
  continue;
@@ -224,6 +245,47 @@ export async function init(opt) {
224
245
  await result_1;
225
246
  }
226
247
  }
248
+ export async function check(opt) {
249
+ const env_2 = { stack: [], error: void 0, hasError: false };
250
+ try {
251
+ _fixOutput(opt);
252
+ await run(opt, 'Checking for sudo', 'which sudo');
253
+ await run(opt, 'Checking for psql', 'which psql');
254
+ const _sql = (command, message) => run(opt, message, `sudo -u postgres psql -c "${command}"`);
255
+ const done = () => opt.output('done');
256
+ const throwUnlessRows = (text) => {
257
+ if (text.includes('(0 rows)'))
258
+ throw 'missing.';
259
+ return text;
260
+ };
261
+ await _sql(`SELECT 1 FROM pg_database WHERE datname = 'axium'`, 'Checking for database').then(throwUnlessRows);
262
+ await _sql(`SELECT 1 FROM pg_roles WHERE rolname = 'axium'`, 'Checking for user').then(throwUnlessRows);
263
+ opt.output('start', 'Connecting to database');
264
+ const db = __addDisposableResource(env_2, connect(), true);
265
+ done();
266
+ opt.output('start', `Checking users table`);
267
+ await db.selectFrom('users').select(['id', 'email', 'emailVerified', 'image', 'name', 'preferences']).execute().then(done);
268
+ opt.output('start', `Checking sessions table`);
269
+ await db.selectFrom('sessions').select(['id', 'userId', 'token', 'created', 'expires', 'elevated']).execute().then(done);
270
+ opt.output('start', `Checking verifications table`);
271
+ await db.selectFrom('verifications').select(['userId', 'token', 'expires', 'role']).execute().then(done);
272
+ opt.output('start', `Checking passkeys table`);
273
+ await db
274
+ .selectFrom('passkeys')
275
+ .select(['id', 'name', 'createdAt', 'userId', 'publicKey', 'counter', 'deviceType', 'backedUp', 'transports'])
276
+ .execute()
277
+ .then(done);
278
+ }
279
+ catch (e_2) {
280
+ env_2.error = e_2;
281
+ env_2.hasError = true;
282
+ }
283
+ finally {
284
+ const result_2 = __disposeResources(env_2);
285
+ if (result_2)
286
+ await result_2;
287
+ }
288
+ }
227
289
  /**
228
290
  * Completely remove Axium from the database.
229
291
  */
@@ -241,6 +303,18 @@ export async function uninstall(opt) {
241
303
  await _sql('DROP DATABASE axium', 'Dropping database');
242
304
  await _sql('REVOKE ALL PRIVILEGES ON SCHEMA public FROM axium', 'Revoking schema privileges');
243
305
  await _sql('DROP USER axium', 'Dropping user');
306
+ await getHBA(opt)
307
+ .then(([content, writeBack]) => {
308
+ opt.output('start', 'Checking for Axium HBA configuration');
309
+ if (!content.includes(pgHba))
310
+ throw 'missing.';
311
+ opt.output('done');
312
+ opt.output('start', 'Removing Axium HBA configuration');
313
+ const newContent = content.replace(pgHba, '');
314
+ opt.output('done');
315
+ writeBack(newContent);
316
+ })
317
+ .catch(e => opt.output('warn', e));
244
318
  }
245
319
  /**
246
320
  * Removes all data from tables.
@@ -254,7 +328,7 @@ export async function wipe(opt) {
254
328
  opt.output('plugin', plugin.name);
255
329
  await plugin.db_wipe(opt, db);
256
330
  }
257
- for (const table of ['User', 'Account', 'Session', 'VerificationToken', 'Authenticator']) {
331
+ for (const table of ['users', 'passkeys', 'sessions', 'verifications']) {
258
332
  opt.output('start', `Removing data from ${table}`);
259
333
  await db.deleteFrom(table).execute();
260
334
  opt.output('done');
package/dist/io.js CHANGED
@@ -65,7 +65,11 @@ export async function run(opts, message, command) {
65
65
  return value;
66
66
  }
67
67
  catch (error) {
68
- throw error == '[command]' ? stderr?.slice(0, 100) || 'failed.' : typeof error == 'object' && 'message' in error ? error.message : error;
68
+ throw error == '[command]'
69
+ ? stderr?.slice(0, 100) || 'failed.'
70
+ : typeof error == 'object' && 'message' in error
71
+ ? error.message
72
+ : error;
69
73
  }
70
74
  }
71
75
  /** Yet another convenience function */
@@ -149,7 +153,7 @@ export async function restrictedPorts(opt) {
149
153
  opt.output('done');
150
154
  return '/usr/sbin/setcap';
151
155
  });
152
- opt.output('debug', 'Using setup at ' + setcap);
156
+ opt.output('debug', 'Using setcap at ' + setcap);
153
157
  let { node } = opt;
154
158
  node ||= await run(opt, 'Finding node', 'command -v node')
155
159
  .then(e => e.trim())
package/dist/plugins.d.ts CHANGED
@@ -1,32 +1,15 @@
1
- import * as z from 'zod';
1
+ import z from 'zod/v4';
2
+ export declare const fn: z.ZodCustom<(...args: unknown[]) => any, (...args: unknown[]) => any>;
2
3
  export declare const Plugin: z.ZodObject<{
3
4
  id: z.ZodString;
4
5
  name: z.ZodString;
5
6
  version: z.ZodString;
6
7
  description: z.ZodOptional<z.ZodString>;
7
- statusText: z.ZodOptional<z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodUnion<[z.ZodString, z.ZodPromise<z.ZodString>]>>>;
8
- db_init: z.ZodOptional<z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodUnknown>>;
9
- db_remove: z.ZodOptional<z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodUnknown>>;
10
- db_wipe: z.ZodOptional<z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodUnknown>>;
11
- }, "strip", z.ZodTypeAny, {
12
- name: string;
13
- id: string;
14
- version: string;
15
- description?: string | undefined;
16
- statusText?: ((...args: unknown[]) => string | Promise<string>) | undefined;
17
- db_init?: ((...args: unknown[]) => unknown) | undefined;
18
- db_remove?: ((...args: unknown[]) => unknown) | undefined;
19
- db_wipe?: ((...args: unknown[]) => unknown) | undefined;
20
- }, {
21
- name: string;
22
- id: string;
23
- version: string;
24
- description?: string | undefined;
25
- statusText?: ((...args: unknown[]) => string | Promise<string>) | undefined;
26
- db_init?: ((...args: unknown[]) => unknown) | undefined;
27
- db_remove?: ((...args: unknown[]) => unknown) | undefined;
28
- db_wipe?: ((...args: unknown[]) => unknown) | undefined;
29
- }>;
8
+ statusText: z.ZodCustom<z.core.$InferInnerFunctionTypeAsync<z.core.$ZodTuple<[], null>, z.ZodString>, z.core.$InferInnerFunctionTypeAsync<z.core.$ZodTuple<[], null>, z.ZodString>>;
9
+ db_init: z.ZodOptional<z.ZodCustom<(...args: unknown[]) => any, (...args: unknown[]) => any>>;
10
+ db_remove: z.ZodOptional<z.ZodCustom<(...args: unknown[]) => any, (...args: unknown[]) => any>>;
11
+ db_wipe: z.ZodOptional<z.ZodCustom<(...args: unknown[]) => any, (...args: unknown[]) => any>>;
12
+ }, z.core.$strip>;
30
13
  export interface Plugin extends z.infer<typeof Plugin> {
31
14
  }
32
15
  export declare const plugins: Set<Plugin>;
package/dist/plugins.js CHANGED
@@ -1,23 +1,19 @@
1
1
  import * as fs from 'node:fs';
2
2
  import { join, resolve } from 'node:path/posix';
3
3
  import { styleText } from 'node:util';
4
- import * as z from 'zod';
5
- import { fromZodError } from 'zod-validation-error';
4
+ import z from 'zod/v4';
6
5
  import { findDir, output } from './io.js';
7
- import { fileURLToPath } from 'node:url';
6
+ import { zAsyncFunction } from '@axium/core/schemas';
7
+ export const fn = z.custom(data => typeof data === 'function');
8
8
  export const Plugin = z.object({
9
9
  id: z.string(),
10
10
  name: z.string(),
11
11
  version: z.string(),
12
12
  description: z.string().optional(),
13
- statusText: z
14
- .function()
15
- .args()
16
- .returns(z.union([z.string(), z.promise(z.string())]))
17
- .optional(),
18
- db_init: z.function().optional(),
19
- db_remove: z.function().optional(),
20
- db_wipe: z.function().optional(),
13
+ statusText: zAsyncFunction(z.function({ input: [], output: z.string() })),
14
+ db_init: fn.optional(),
15
+ db_remove: fn.optional(),
16
+ db_wipe: fn.optional(),
21
17
  });
22
18
  export const plugins = new Set();
23
19
  export function resolvePlugin(search) {
@@ -32,7 +28,6 @@ export function pluginText(plugin) {
32
28
  plugin.id,
33
29
  `Version: ${plugin.version}`,
34
30
  `Description: ${plugin.description ?? styleText('dim', '(none)')}`,
35
- `Status text integration: ${plugin.statusText ? styleText('whiteBright', 'yes') : styleText('yellow', 'no')}`,
36
31
  `Database integration: ${[plugin.db_init, plugin.db_remove, plugin.db_wipe]
37
32
  .filter(Boolean)
38
33
  .map(fn => fn?.name.slice(3))
@@ -41,8 +36,8 @@ export function pluginText(plugin) {
41
36
  }
42
37
  export async function loadPlugin(specifier) {
43
38
  try {
44
- const plugin = await Plugin.parseAsync(await import(specifier)).catch(e => {
45
- throw fromZodError(e);
39
+ const plugin = await Plugin.parseAsync(await import(/* @vite-ignore */ specifier)).catch(e => {
40
+ throw z.prettifyError(e);
46
41
  });
47
42
  plugins.add(plugin);
48
43
  output.debug(`Loaded plugin: "${plugin.name}" (${plugin.id}) ${plugin.version}`);
@@ -0,0 +1,55 @@
1
+ import type { RequestMethod } from '@axium/core/requests';
2
+ import type { LoadEvent, RequestEvent } from '@sveltejs/kit';
3
+ import type { Component } from 'svelte';
4
+ import type z from 'zod/v4';
5
+ type _Params = Partial<Record<string, string>>;
6
+ export type EndpointHandlers<Params extends _Params = _Params> = Partial<Record<RequestMethod, (event: RequestEvent<Params>) => object | Promise<object>>>;
7
+ export type RouteParamOptions = z.ZodType;
8
+ export interface CommonRouteOptions<Params extends _Params = _Params> {
9
+ path: string;
10
+ params?: {
11
+ [K in keyof Params]?: RouteParamOptions;
12
+ };
13
+ }
14
+ /**
15
+ * A route with server-side handlers for different HTTP methods.
16
+ */
17
+ export interface ServerRouteOptions<Params extends _Params = _Params> extends CommonRouteOptions<Params>, EndpointHandlers<Params> {
18
+ }
19
+ export interface WebRouteOptions extends CommonRouteOptions {
20
+ load?(event: RequestEvent): object | Promise<object>;
21
+ /** the Svelte page */
22
+ page?: Component;
23
+ }
24
+ export type RouteOptions = ServerRouteOptions | WebRouteOptions;
25
+ export interface RouteCommon {
26
+ path: string;
27
+ params?: Record<string, RouteParamOptions>;
28
+ [kBuiltin]: boolean;
29
+ }
30
+ export interface ServerRoute extends RouteCommon, EndpointHandlers {
31
+ server: true;
32
+ }
33
+ export interface WebRoute extends RouteCommon {
34
+ server: false;
35
+ load?(event: LoadEvent): object | Promise<object>;
36
+ page?: Component;
37
+ }
38
+ export type Route = ServerRoute | WebRoute;
39
+ /**
40
+ * @internal
41
+ */
42
+ export declare const routes: Map<string, Route>;
43
+ declare const kBuiltin: unique symbol;
44
+ export declare function addRoute(opt: RouteOptions, _routeMap?: Map<string, Route>): void;
45
+ /**
46
+ * Resolve a request URL into a route.
47
+ * This handles parsing of parameters in the URL.
48
+ */
49
+ export declare function resolveRoute<T extends Route>(event: RequestEvent | LoadEvent, _routeMap?: Map<string, T>): T | undefined;
50
+ /**
51
+ * This function marks all existing routes as built-in.
52
+ * @internal
53
+ */
54
+ export declare function _markDefaults(): void;
55
+ export {};
package/dist/routes.js ADDED
@@ -0,0 +1,54 @@
1
+ /**
2
+ * @internal
3
+ */
4
+ export const routes = new Map();
5
+ const kBuiltin = Symbol('kBuiltin');
6
+ export function addRoute(opt, _routeMap = routes) {
7
+ const route = { ...opt, server: !('page' in opt), [kBuiltin]: false };
8
+ if (!route.path.startsWith('/')) {
9
+ throw new Error(`Route path must start with a slash: ${route.path}`);
10
+ }
11
+ if (route.path.startsWith('/api/') && !route.server) {
12
+ throw new Error(`API routes cannot have a client page: ${route.path}`);
13
+ }
14
+ _routeMap.set(route.path, route);
15
+ }
16
+ /**
17
+ * Resolve a request URL into a route.
18
+ * This handles parsing of parameters in the URL.
19
+ */
20
+ export function resolveRoute(event, _routeMap = routes) {
21
+ const { pathname } = event.url;
22
+ if (_routeMap.has(pathname) && !pathname.split('/').some(p => p.startsWith(':')))
23
+ return _routeMap.get(pathname);
24
+ // Otherwise we must have a parameterized route
25
+ routes: for (const route of _routeMap.values()) {
26
+ const params = {};
27
+ // Split the path and route into parts, zipped together
28
+ const pathParts = pathname.split('/').filter(Boolean);
29
+ for (const routePart of route.path.split('/').filter(Boolean)) {
30
+ const pathPart = pathParts.shift();
31
+ if (!pathPart)
32
+ continue routes;
33
+ if (pathPart == routePart)
34
+ continue;
35
+ if (!routePart.startsWith(':'))
36
+ continue routes;
37
+ params[routePart.slice(1)] = pathPart;
38
+ }
39
+ // we didn't find a match, since an exact match would have been found already
40
+ if (pathParts.length || !Object.keys(params).length)
41
+ continue;
42
+ event.params = params;
43
+ return route;
44
+ }
45
+ }
46
+ /**
47
+ * This function marks all existing routes as built-in.
48
+ * @internal
49
+ */
50
+ export function _markDefaults() {
51
+ for (const route of routes.values()) {
52
+ route[kBuiltin] = true;
53
+ }
54
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axium/server",
3
- "version": "0.7.6",
3
+ "version": "0.8.0",
4
4
  "author": "James Prevett <axium@jamespre.dev> (https://jamespre.dev)",
5
5
  "funding": {
6
6
  "type": "individual",
@@ -21,8 +21,7 @@
21
21
  ".": "./dist/index.js",
22
22
  "./*": "./dist/*",
23
23
  "./web": "./web/index.js",
24
- "./web/*": "./web/*",
25
- "./web/server": "./web/index.server.js"
24
+ "./web/*": "./web/*"
26
25
  },
27
26
  "files": [
28
27
  "dist",
@@ -34,28 +33,21 @@
34
33
  "scripts": {
35
34
  "build": "tsc"
36
35
  },
37
- "peerDependencies": {
38
- "@axium/core": ">=0.0.2"
39
- },
40
36
  "dependencies": {
41
- "@auth/core": "^0.38.0",
42
- "@auth/kysely-adapter": "^1.8.0",
37
+ "@simplewebauthn/server": "^13.1.1",
38
+ "@sveltejs/kit": "^2.20.2",
43
39
  "@types/pg": "^8.11.11",
44
- "bcryptjs": "^3.0.2",
45
40
  "commander": "^13.1.0",
46
41
  "kysely": "^0.27.5",
47
42
  "logzen": "^0.7.0",
48
43
  "mime": "^4.0.7",
49
44
  "pg": "^8.14.1",
50
- "utilium": "^2.3.0",
51
- "zod-validation-error": "^3.4.0"
45
+ "utilium": "^2.3.8",
46
+ "zod": "^3.25.61",
47
+ "@axium/core": ">=0.2.0"
52
48
  },
53
49
  "devDependencies": {
54
- "@auth/sveltekit": "^1.8.0",
55
- "@simplewebauthn/browser": "^9.0.1",
56
- "@simplewebauthn/server": "^9.0.3",
57
50
  "@sveltejs/adapter-node": "^5.2.12",
58
- "@sveltejs/kit": "^2.20.2",
59
51
  "svelte": "^5.25.3",
60
52
  "vite-plugin-mkcert": "^1.17.8"
61
53
  }
@@ -0,0 +1,7 @@
1
+ import './metadata.js';
2
+ import './passkeys.js';
3
+ import './register.js';
4
+ import './session.js';
5
+ import './users.js';
6
+ export * from './schemas.js';
7
+ export * from './utils.js';