@axium/server 0.30.0 → 0.31.1

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/dist/api/admin.js CHANGED
@@ -8,7 +8,7 @@ import { audit, events, getEvents } from '../audit.js';
8
8
  import { requireSession } from '../auth.js';
9
9
  import { config } from '../config.js';
10
10
  import { count, database as db } from '../database.js';
11
- import { error, withError } from '../requests.js';
11
+ import { error, parseSearch, withError } from '../requests.js';
12
12
  import { addRoute } from '../routes.js';
13
13
  async function assertAdmin(route, req) {
14
14
  const admin = await requireSession(req);
@@ -122,13 +122,7 @@ addRoute({
122
122
  async GET(req) {
123
123
  await assertAdmin(this, req);
124
124
  const filter = { severity: Severity.Info };
125
- try {
126
- const search = Object.fromEntries(new URL(req.url).searchParams);
127
- Object.assign(filter, AuditFilter.parse(search));
128
- }
129
- catch (e) {
130
- error(400, e instanceof z.core.$ZodError ? z.prettifyError(e) : 'invalid body');
131
- }
125
+ Object.assign(filter, parseSearch(req, AuditFilter));
132
126
  return await getEvents(filter)
133
127
  .select(eb => jsonObjectFrom(eb.selectFrom('users').whereRef('users.id', '=', 'audit_log.userId').select(['id', 'name'])).as('user'))
134
128
  .execute();
package/dist/api/users.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /** Register a new passkey for a new or existing user. */
2
- import { preferenceDefaults, preferenceSchemas } from '@axium/core';
2
+ import { preferenceDefaults, Preferences } from '@axium/core';
3
3
  import { PasskeyAuthenticationResponse, PasskeyRegistration } from '@axium/core/passkeys';
4
4
  import { LogoutSessions, UserAuthOptions, UserChangeable } from '@axium/core/user';
5
5
  import * as webauthn from '@simplewebauthn/server';
@@ -43,7 +43,7 @@ addRoute({
43
43
  if ('email' in body)
44
44
  body.emailVerified = null;
45
45
  if ('preferences' in body)
46
- body.preferences = Object.assign(structuredClone(preferenceDefaults), await z.object(preferenceSchemas).partial().parseAsync(body.preferences).catch(withError('Invalid preferences', 400)));
46
+ body.preferences = Object.assign(structuredClone(preferenceDefaults), await Preferences.partial().parseAsync(body.preferences).catch(withError('Invalid preferences', 400)));
47
47
  const result = await db
48
48
  .updateTable('users')
49
49
  .set(body)
@@ -591,7 +591,7 @@ export interface CheckOptions extends OpOptions {
591
591
  /**
592
592
  * Checks that a table has the expected column types, nullability, and default values.
593
593
  */
594
- export declare function checkTableTypes<TB extends keyof Schema & string>(tableName: TB, types: Table, opt: CheckOptions): Promise<void>;
594
+ export declare function checkTableTypes<TB extends keyof Schema & string>(tableName: TB, types: Table, opt: CheckOptions, tableMetadata?: kysely.TableMetadata[]): Promise<void>;
595
595
  export declare function clean(opt: Partial<OpOptions>): Promise<void>;
596
596
  export declare function rotatePassword(): Promise<void>;
597
597
  export {};
package/dist/database.js CHANGED
@@ -64,6 +64,10 @@ import config from './config.js';
64
64
  import rawSchema from './db.json' with { type: 'json' };
65
65
  import { dirs, systemDir } from './io.js';
66
66
  pg.types.setTypeParser(pg.types.builtins.INT8, BigInt);
67
+ // @ts-expect-error 2339
68
+ BigInt.prototype.toJSON = function () {
69
+ return Number(this);
70
+ };
67
71
  const sym = Symbol.for('Axium:database');
68
72
  export let database;
69
73
  export function connect() {
@@ -365,8 +369,7 @@ export function* getSchemaFiles() {
365
369
  yield [name, SchemaFile.parse(plugin._db)];
366
370
  }
367
371
  catch (e) {
368
- const text = e instanceof z.core.$ZodError ? z.prettifyError(e) : e instanceof Error ? e.message : String(e);
369
- throw `Invalid database configuration for plugin "${name}":\n${text}`;
372
+ throw `Invalid database configuration for plugin "${name}":\n${io.errorText(e)}`;
370
373
  }
371
374
  }
372
375
  }
@@ -382,10 +385,16 @@ export function getFullSchema(opt = {}) {
382
385
  let currentSchema = { tables: {}, indexes: [] };
383
386
  fullSchema.versions[pluginName] = file.latest;
384
387
  for (const [version, schema] of file.versions.entries()) {
385
- if (schema.delta)
386
- applyDeltaToSchema(currentSchema, schema);
387
- else
388
+ if (!schema.delta)
388
389
  currentSchema = schema;
390
+ else {
391
+ try {
392
+ applyDeltaToSchema(currentSchema, schema);
393
+ }
394
+ catch (e) {
395
+ throw `Failed to apply version ${version - 1}->${version} delta to ${pluginName}: ${io.errorText(e)}`;
396
+ }
397
+ }
389
398
  if (version === file.latest)
390
399
  break;
391
400
  }
@@ -423,20 +432,20 @@ export function setUpgradeInfo(info) {
423
432
  }
424
433
  export function applyTableDeltaToSchema(table, delta) {
425
434
  for (const column of delta.drop_columns) {
426
- if (column in table)
435
+ if (column in table.columns)
427
436
  delete table.columns[column];
428
437
  else
429
- throw `Can't drop column ${column} because it does not exist`;
438
+ throw `can't drop column ${column} because it does not exist`;
430
439
  }
431
440
  for (const [name, column] of Object.entries(delta.add_columns)) {
432
- if (name in table)
433
- throw `Can't add column ${name} because it already exists`;
441
+ if (name in table.columns)
442
+ throw `can't add column ${name} because it already exists`;
434
443
  table.columns[name] = column;
435
444
  }
436
445
  for (const [name, columnDelta] of Object.entries(delta.alter_columns)) {
437
446
  const column = table.columns[name];
438
447
  if (!column)
439
- throw `Can't modify column ${name} because it does not exist`;
448
+ throw `can't modify column ${name} because it does not exist`;
440
449
  if (columnDelta.type)
441
450
  column.type = columnDelta.type;
442
451
  if (columnDelta.default)
@@ -459,11 +468,11 @@ export function applyTableDeltaToSchema(table, delta) {
459
468
  if (table.constraints[name])
460
469
  delete table.constraints[name];
461
470
  else
462
- throw `Can't drop constraint ${name} because it does not exist`;
471
+ throw `can't drop constraint ${name} because it does not exist`;
463
472
  }
464
473
  for (const [name, constraint] of Object.entries(delta.add_constraints)) {
465
474
  if (table.constraints[name])
466
- throw `Can't add constraint ${name} because it already exists`;
475
+ throw `can't add constraint ${name} because it already exists`;
467
476
  table.constraints[name] = constraint;
468
477
  }
469
478
  }
@@ -472,11 +481,11 @@ export function applyDeltaToSchema(schema, delta) {
472
481
  if (tableName in schema.tables)
473
482
  delete schema.tables[tableName];
474
483
  else
475
- throw `Can't drop table ${tableName} because it does not exist`;
484
+ throw `can't drop table ${tableName} because it does not exist`;
476
485
  }
477
486
  for (const [tableName, table] of Object.entries(delta.add_tables)) {
478
487
  if (tableName in schema.tables)
479
- throw `Can't add table ${tableName} because it already exists`;
488
+ throw `can't add table ${tableName} because it already exists`;
480
489
  else
481
490
  schema.tables[tableName] = table;
482
491
  }
@@ -484,7 +493,7 @@ export function applyDeltaToSchema(schema, delta) {
484
493
  if (tableName in schema.tables)
485
494
  applyTableDeltaToSchema(schema.tables[tableName], tableDelta);
486
495
  else
487
- throw `Can't modify table ${tableName} because it does not exist`;
496
+ throw `can't modify table ${tableName} because it does not exist`;
488
497
  }
489
498
  }
490
499
  export function validateDelta(delta) {
@@ -782,10 +791,10 @@ export async function applyDelta(delta, forceAbort = false) {
782
791
  /**
783
792
  * Checks that a table has the expected column types, nullability, and default values.
784
793
  */
785
- export async function checkTableTypes(tableName, types, opt) {
794
+ export async function checkTableTypes(tableName, types, opt, tableMetadata) {
786
795
  io.start(`Checking table ${tableName}`);
787
- const dbTables = opt._metadata || (await database.introspection.getTables());
788
- const table = dbTables.find(t => (t.schema == 'public' ? t.name : `${t.schema}.${t.name}`) === tableName);
796
+ tableMetadata ||= await database.introspection.getTables();
797
+ const table = tableMetadata.find(t => (t.schema == 'public' ? t.name : `${t.schema}.${t.name}`) === tableName);
789
798
  if (!table)
790
799
  throw 'missing.';
791
800
  const columns = Object.fromEntries(table.columns.map(c => [c.name, c]));
package/dist/linking.js CHANGED
@@ -40,7 +40,7 @@ export function linkRoutes(options = {}) {
40
40
  unlinkSync(from);
41
41
  }
42
42
  catch (e) {
43
- io.exit(e && e instanceof Error ? e.message : e.toString());
43
+ io.exit(e);
44
44
  }
45
45
  io.done();
46
46
  io.start('Re-linking ' + text);
@@ -50,7 +50,7 @@ export function linkRoutes(options = {}) {
50
50
  io.done();
51
51
  }
52
52
  catch (e) {
53
- io.exit(e && e instanceof Error ? e.message : e.toString());
53
+ io.exit(e);
54
54
  }
55
55
  }
56
56
  }
@@ -65,7 +65,7 @@ export function unlinkRoutes(options = {}) {
65
65
  io.done();
66
66
  }
67
67
  catch (e) {
68
- io.exit(e && e instanceof Error ? e.message : e.toString());
68
+ io.exit(e);
69
69
  }
70
70
  }
71
71
  }
package/dist/main.js CHANGED
@@ -60,17 +60,17 @@ import { Argument, Option, program } from 'commander';
60
60
  import { access } from 'node:fs/promises';
61
61
  import { join, resolve } from 'node:path/posix';
62
62
  import { createInterface } from 'node:readline/promises';
63
- import { styleText, parseArgs } from 'node:util';
63
+ import { parseArgs, styleText } from 'node:util';
64
64
  import { getByString, isJSON, setByString } from 'utilium';
65
65
  import * as z from 'zod';
66
66
  import $pkg from '../package.json' with { type: 'json' };
67
67
  import { audit, getEvents, styleSeverity } from './audit.js';
68
+ import { diffUpdate, lookupUser, userText } from './cli.js';
68
69
  import config, { configFiles, FileSchema, saveConfigTo } from './config.js';
69
70
  import * as db from './database.js';
70
71
  import { _portActions, _portMethods, restrictedPorts } from './io.js';
71
72
  import { linkRoutes, listRouteLinks, unlinkRoutes } from './linking.js';
72
73
  import { serve } from './serve.js';
73
- import { diffUpdate, lookupUser, userText } from './cli.js';
74
74
  async function rlConfirm(question = 'Is this ok') {
75
75
  const { data, error } = z
76
76
  .stringbool()
@@ -122,13 +122,11 @@ try {
122
122
  .name('axium')
123
123
  .description('Axium server CLI')
124
124
  .configureHelp({ showGlobalOptions: true })
125
- .option('--safe', 'do not execute code from plugins')
125
+ .option('--safe', 'do not execute code from plugins', false)
126
126
  .option('--debug', 'override debug mode')
127
127
  .option('--no-debug', 'override debug mode')
128
- .option('-c, --config <path>', 'path to the config file');
129
- program.on('option:debug', () => config.set({ debug: true }));
130
- noAutoDB = ['init', 'serve', 'check'];
131
- program.hook('preAction', (_, action) => {
128
+ .option('-c, --config <path>', 'path to the config file')
129
+ .hook('preAction', (_, action) => {
132
130
  const opt = action.optsWithGlobals();
133
131
  opt.force && io.warn('--force: Protections disabled.');
134
132
  if (typeof opt.debug == 'boolean') {
@@ -142,23 +140,19 @@ try {
142
140
  if (!noAutoDB.includes(action.name()))
143
141
  throw e;
144
142
  }
145
- });
146
- program.hook('postAction', async (_, action) => {
143
+ })
144
+ .hook('postAction', async (_, action) => {
147
145
  if (!noAutoDB.includes(action.name()))
148
146
  await db.database.destroy();
149
- });
147
+ })
148
+ .on('option:debug', () => config.set({ debug: true }));
149
+ noAutoDB = ['init', 'serve', 'check'];
150
150
  // Options shared by multiple (sub)commands
151
151
  opts = {
152
- // database specific
153
- host: new Option('-H, --host <host>', 'the host of the database.').argParser(value => {
154
- const [hostname, port] = value?.split(':') ?? [];
155
- config.db.host = hostname || config.db.host;
156
- config.db.port = port && Number.isSafeInteger(parseInt(port)) ? parseInt(port) : config.db.port;
157
- }),
158
152
  check: new Option('--check', 'check the database schema after initialization').default(false),
159
153
  force: new Option('-f, --force', 'force the operation').default(false),
160
154
  global: new Option('-g, --global', 'apply the operation globally').default(false),
161
- timeout: new Option('-t, --timeout <ms>', 'how long to wait for commands to complete.').default('1000').argParser(value => {
155
+ timeout: new Option('-t, --timeout <ms>', 'how long to wait for commands to complete.').default(1000).argParser(value => {
162
156
  const timeout = parseInt(value);
163
157
  if (!Number.isSafeInteger(timeout) || timeout < 0)
164
158
  io.warn('Invalid timeout value, using default.');
@@ -166,17 +160,17 @@ try {
166
160
  }),
167
161
  packagesDir: new Option('-p, --packages-dir <dir>', 'the directory to look for packages in'),
168
162
  };
169
- axiumDB = program.command('db').alias('database').description('Manage the database').addOption(opts.timeout).addOption(opts.host);
163
+ axiumDB = program.command('db').alias('database').description('Manage the database').addOption(opts.timeout);
170
164
  axiumDB
171
165
  .command('init')
172
166
  .description('Initialize the database')
173
167
  .addOption(opts.force)
174
- .option('-s, --skip', 'If the user, database, or schema already exists, skip trying to create it.')
168
+ .option('-s, --skip', 'If the user, database, or schema already exists, skip trying to create it.', false)
175
169
  .addOption(opts.check)
176
- .action(async (_localOpts, _) => {
177
- const opt = _.optsWithGlobals();
178
- await db.init(opt).catch(io.handleError);
179
- await dbInitTables().catch(io.handleError);
170
+ .action(async function axium_db_init() {
171
+ const opt = this.optsWithGlobals();
172
+ await db.init(opt).catch(io.exit);
173
+ await dbInitTables().catch(io.exit);
180
174
  });
181
175
  axiumDB
182
176
  .command('status')
@@ -204,9 +198,9 @@ try {
204
198
  io.warn(`Database has existing ${key}. Use --force if you really want to drop the database.`);
205
199
  process.exit(2);
206
200
  }
207
- await db._sql('DROP DATABASE axium', 'Dropping database').catch(io.handleError);
208
- await db._sql('REVOKE ALL PRIVILEGES ON SCHEMA public FROM axium', 'Revoking schema privileges').catch(io.handleError);
209
- await db._sql('DROP USER axium', 'Dropping user').catch(io.handleError);
201
+ await db._sql('DROP DATABASE axium', 'Dropping database').catch(io.exit);
202
+ await db._sql('REVOKE ALL PRIVILEGES ON SCHEMA public FROM axium', 'Revoking schema privileges').catch(io.exit);
203
+ await db._sql('DROP USER axium', 'Dropping user').catch(io.exit);
210
204
  await db
211
205
  .getHBA(opt)
212
206
  .then(([content, writeBack]) => {
@@ -249,7 +243,7 @@ try {
249
243
  console.log(table + ' '.repeat(maxTableName - table.length), '|', plugins);
250
244
  }
251
245
  await rlConfirm('Are you sure you want to wipe these tables and any dependents');
252
- await db.database.deleteFrom(Array.from(tables.keys())).execute().catch(io.handleError);
246
+ await db.database.deleteFrom(Array.from(tables.keys())).execute().catch(io.exit);
253
247
  });
254
248
  axiumDB
255
249
  .command('check')
@@ -258,23 +252,20 @@ try {
258
252
  .action(async (opt) => {
259
253
  const env_2 = { stack: [], error: void 0, hasError: false };
260
254
  try {
261
- await io.run('Checking for sudo', 'which sudo').catch(io.handleError);
262
- await io.run('Checking for psql', 'which psql').catch(io.handleError);
255
+ await io.run('Checking for sudo', 'which sudo').catch(io.exit);
256
+ await io.run('Checking for psql', 'which psql').catch(io.exit);
263
257
  const throwUnlessRows = (text) => {
264
258
  if (text.includes('(0 rows)'))
265
259
  throw 'missing.';
266
260
  return text;
267
261
  };
268
- await db
269
- ._sql(`SELECT 1 FROM pg_database WHERE datname = 'axium'`, 'Checking for database')
270
- .then(throwUnlessRows)
271
- .catch(io.handleError);
272
- await db._sql(`SELECT 1 FROM pg_roles WHERE rolname = 'axium'`, 'Checking for user').then(throwUnlessRows).catch(io.handleError);
262
+ await db._sql(`SELECT 1 FROM pg_database WHERE datname = 'axium'`, 'Checking for database').then(throwUnlessRows).catch(io.exit);
263
+ await db._sql(`SELECT 1 FROM pg_roles WHERE rolname = 'axium'`, 'Checking for user').then(throwUnlessRows).catch(io.exit);
273
264
  io.start('Connecting to database');
274
265
  const _ = __addDisposableResource(env_2, db.connect(), true);
275
266
  io.done();
276
267
  io.start('Getting schema metadata');
277
- const schemas = await db.database.introspection.getSchemas().catch(io.handleError);
268
+ const schemas = await db.database.introspection.getSchemas().catch(io.exit);
278
269
  io.done();
279
270
  io.start('Checking for acl schema');
280
271
  if (!schemas.find(s => s.name == 'acl'))
@@ -284,13 +275,21 @@ try {
284
275
  const tablePromises = await Promise.all([
285
276
  db.database.introspection.getTables(),
286
277
  db.database.withSchema('acl').introspection.getTables(),
287
- ]).catch(io.handleError);
288
- opt._metadata = tablePromises.flat();
289
- const tables = Object.fromEntries(opt._metadata.map(t => [t.schema == 'public' ? t.name : `${t.schema}.${t.name}`, t]));
278
+ ]).catch(io.exit);
279
+ const tableMetadata = tablePromises.flat();
280
+ const tables = Object.fromEntries(tableMetadata.map(t => [t.schema == 'public' ? t.name : `${t.schema}.${t.name}`, t]));
290
281
  io.done();
291
- const schema = db.getFullSchema();
282
+ io.start('Resolving database schemas');
283
+ let schema;
284
+ try {
285
+ schema = db.getFullSchema();
286
+ io.done();
287
+ }
288
+ catch (e) {
289
+ io.exit(e);
290
+ }
292
291
  for (const [name, table] of Object.entries(schema.tables)) {
293
- await db.checkTableTypes(name, table, opt);
292
+ await db.checkTableTypes(name, table, opt, tableMetadata);
294
293
  delete tables[name];
295
294
  }
296
295
  io.start('Checking for extra tables');
@@ -327,13 +326,13 @@ try {
327
326
  .command('schema')
328
327
  .description('Get the JSON schema for the database configuration file')
329
328
  .option('-j, --json', 'values are JSON encoded')
330
- .action((opt) => {
329
+ .action(opt => {
331
330
  try {
332
331
  const schema = z.toJSONSchema(db.SchemaFile, { io: 'input' });
333
332
  console.log(opt.json ? JSON.stringify(schema, null, 4) : schema);
334
333
  }
335
334
  catch (e) {
336
- io.handleError(e instanceof z.core.$ZodError ? z.prettifyError(e) : e);
335
+ io.exit(e);
337
336
  }
338
337
  });
339
338
  axiumDB
@@ -342,7 +341,7 @@ try {
342
341
  .alias('up')
343
342
  .description('Upgrade the database to the latest version')
344
343
  .option('--abort', 'Rollback changes instead of committing them')
345
- .action(async (opt) => {
344
+ .action(async function axium_db_upgrade(opt) {
346
345
  const deltas = [];
347
346
  const info = db.getUpgradeInfo();
348
347
  let empty = true;
@@ -401,7 +400,7 @@ try {
401
400
  io.exit(e);
402
401
  }
403
402
  console.log('Applying delta.');
404
- await db.applyDelta(delta, opt.abort).catch(io.handleError);
403
+ await db.applyDelta(delta, opt.abort).catch(io.exit);
405
404
  info.upgrades.push({ timestamp: new Date(), from, to });
406
405
  db.setUpgradeInfo(info);
407
406
  });
@@ -449,13 +448,13 @@ try {
449
448
  .command('config')
450
449
  .description('Manage the configuration')
451
450
  .addOption(opts.global)
452
- .option('-j, --json', 'values are JSON encoded')
453
- .option('-r, --redact', 'Do not output sensitive values');
451
+ .option('-j, --json', 'values are JSON encoded', false)
452
+ .option('-r, --redact', 'Do not output sensitive values', false);
454
453
  axiumConfig
455
454
  .command('dump')
456
455
  .description('Output the entire current configuration')
457
- .action(() => {
458
- const opt = axiumConfig.optsWithGlobals();
456
+ .action(function axium_config_dump() {
457
+ const opt = this.optsWithGlobals();
459
458
  const value = config.plain();
460
459
  console.log(opt.json ? JSON.stringify(value, configReplacer(opt), 4) : value);
461
460
  });
@@ -463,8 +462,8 @@ try {
463
462
  .command('get')
464
463
  .description('Get a config value')
465
464
  .argument('<key>', 'the key to get')
466
- .action((key) => {
467
- const opt = axiumConfig.optsWithGlobals();
465
+ .action(function axium_config_get(key) {
466
+ const opt = this.optsWithGlobals();
468
467
  const value = getByString(config.plain(), key);
469
468
  console.log(opt.json ? JSON.stringify(value, configReplacer(opt), 4) : value);
470
469
  });
@@ -473,8 +472,8 @@ try {
473
472
  .description('Set a config value. Note setting objects is not supported.')
474
473
  .argument('<key>', 'the key to set')
475
474
  .argument('<value>', 'the value')
476
- .action((key, value) => {
477
- const opt = axiumConfig.optsWithGlobals();
475
+ .action(function axium_config_set(key, value) {
476
+ const opt = this.optsWithGlobals();
478
477
  if (opt.json && !isJSON(value))
479
478
  io.exit('Invalid JSON');
480
479
  const obj = {};
@@ -500,7 +499,7 @@ try {
500
499
  console.log(opt.json ? JSON.stringify(schema, configReplacer(opt), 4) : schema);
501
500
  }
502
501
  catch (e) {
503
- io.handleError(e instanceof z.core.$ZodError ? z.prettifyError(e) : e);
502
+ io.exit(e);
504
503
  }
505
504
  });
506
505
  axiumPlugin = program.command('plugin').alias('plugins').description('Manage plugins').addOption(opts.global);
@@ -510,7 +509,7 @@ try {
510
509
  .description('List loaded plugins')
511
510
  .option('-l, --long', 'use the long listing format')
512
511
  .option('--no-versions', 'do not show plugin versions')
513
- .action((opt) => {
512
+ .action(opt => {
514
513
  if (!plugins.size) {
515
514
  console.log('No plugins loaded.');
516
515
  return;
@@ -557,7 +556,7 @@ try {
557
556
  .addOption(opts.timeout)
558
557
  .addOption(opts.check)
559
558
  .argument('<plugin>', 'the plugin to initialize')
560
- .action(async (search, opt) => {
559
+ .action(async (search) => {
561
560
  const env_3 = { stack: [], error: void 0, hasError: false };
562
561
  try {
563
562
  const plugin = _findPlugin(search);
@@ -582,7 +581,7 @@ try {
582
581
  .description('List apps added by plugins')
583
582
  .option('-l, --long', 'use the long listing format')
584
583
  .option('-b, --builtin', 'include built-in apps')
585
- .action((opt) => {
584
+ .action(opt => {
586
585
  if (!apps.size) {
587
586
  console.log('No apps.');
588
587
  return;
@@ -692,7 +691,6 @@ try {
692
691
  .command('status')
693
692
  .alias('stats')
694
693
  .description('Get information about the server')
695
- .addOption(opts.host)
696
694
  .action(async () => {
697
695
  console.log('Axium Server v' + $pkg.version);
698
696
  console.log(styleText('whiteBright', 'Debug mode:'), config.debug ? styleText('yellow', 'enabled') : 'disabled');
@@ -721,37 +719,37 @@ try {
721
719
  .addOption(new Option('-m, --method <method>', 'the method to use').choices(_portMethods).default('node-cap'))
722
720
  .option('-N, --node <path>', 'the path to the node binary')
723
721
  .action(async (action, opt) => {
724
- await restrictedPorts({ ...opt, action }).catch(io.handleError);
722
+ await restrictedPorts({ ...opt, action }).catch(io.exit);
725
723
  });
726
724
  program
727
725
  .command('init')
728
726
  .description('Install Axium server')
729
727
  .addOption(opts.force)
730
- .addOption(opts.host)
731
728
  .addOption(opts.check)
732
729
  .addOption(opts.packagesDir)
733
- .option('-s, --skip', 'Skip already initialized steps')
730
+ .option('-s, --skip', 'Skip already initialized steps', false)
734
731
  .action(async (opt) => {
735
- await db.init(opt).catch(io.handleError);
736
- await dbInitTables().catch(io.handleError);
737
- await restrictedPorts({ method: 'node-cap', action: 'enable' }).catch(io.handleError);
732
+ await db.init(opt).catch(io.exit);
733
+ await dbInitTables().catch(io.exit);
734
+ await restrictedPorts({ method: 'node-cap', action: 'enable' }).catch(io.exit);
738
735
  });
739
736
  program
740
737
  .command('serve')
741
738
  .description('Start the Axium server')
742
- .option('-p, --port <port>', 'the port to listen on')
739
+ .option('-p, --port <port>', 'the port to listen on', Number.parseInt, config.web.port)
743
740
  .option('--ssl <prefix>', 'the prefix for the cert.pem and key.pem SSL files')
744
741
  .option('-b, --build <path>', 'the path to the handler build')
745
742
  .action(async (opt) => {
743
+ if (opt.port < 1 || opt.port > 65535)
744
+ io.exit('Invalid port');
746
745
  const server = await serve({
747
746
  secure: opt.ssl ? true : config.web.secure,
748
747
  ssl_cert: opt.ssl ? join(opt.ssl, 'cert.pem') : config.web.ssl_cert,
749
748
  ssl_key: opt.ssl ? join(opt.ssl, 'key.pem') : config.web.ssl_key,
750
749
  build: opt.build ? resolve(opt.build) : config.web.build,
751
750
  });
752
- const port = !Number.isNaN(Number.parseInt(opt.port ?? 'NaN')) ? Number.parseInt(opt.port) : config.web.port;
753
- server.listen(port, () => {
754
- console.log('Server is listening on port ' + port);
751
+ server.listen(opt.port, () => {
752
+ console.log('Server is listening on port ' + opt.port);
755
753
  });
756
754
  });
757
755
  program
@@ -761,12 +759,11 @@ try {
761
759
  .addOption(new Option('-l, --list', 'list route links').conflicts('delete'))
762
760
  .option('-d, --delete', 'delete route links')
763
761
  .argument('[name...]', 'List of plugin names to operate on. If not specified, operates on all plugins and built-in routes.')
764
- .action(async function (names) {
762
+ .action(async function axium_link(names) {
765
763
  const opt = this.optsWithGlobals();
766
- if (names.length)
767
- opt.only = names;
764
+ const linkOpts = { only: names };
768
765
  if (opt.list) {
769
- for (const link of listRouteLinks(opt)) {
766
+ for (const link of listRouteLinks(linkOpts)) {
770
767
  const idText = link.id.startsWith('#') ? `(${link.id.slice(1)})` : link.id;
771
768
  const fromColor = await access(link.from)
772
769
  .then(() => 'cyanBright')
@@ -776,10 +773,10 @@ try {
776
773
  return;
777
774
  }
778
775
  if (opt.delete) {
779
- unlinkRoutes(opt);
776
+ unlinkRoutes(linkOpts);
780
777
  return;
781
778
  }
782
- linkRoutes(opt);
779
+ linkRoutes(linkOpts);
783
780
  });
784
781
  program
785
782
  .command('audit')
@@ -32,7 +32,8 @@ export declare function isRedirect(e: unknown): e is Redirect;
32
32
  */
33
33
  export declare function redirect(location: string, status?: number): never;
34
34
  export declare function json(data: object, init?: ResponseInit): Response;
35
- export declare function parseBody<const Schema extends z.ZodType, const Result extends z.infer<Schema> = z.infer<Schema>>(request: Request, schema: Schema): Promise<Result>;
35
+ export declare function parseBody<const Schema extends z.ZodType>(request: Request, schema: Schema): Promise<z.infer<Schema>>;
36
+ export declare function parseSearch<const Schema extends z.ZodType>(request: Request, schema: Schema): z.infer<Schema>;
36
37
  export declare function getToken(request: Request, sensitive?: boolean): string | undefined;
37
38
  export interface CreateSessionOptions {
38
39
  elevated?: boolean;
package/dist/requests.js CHANGED
@@ -44,6 +44,16 @@ export async function parseBody(request, schema) {
44
44
  error(400, e instanceof z.core.$ZodError ? z.prettifyError(e) : 'invalid body');
45
45
  }
46
46
  }
47
+ export function parseSearch(request, schema) {
48
+ const url = new URL(request.url);
49
+ const searchParams = Object.fromEntries(url.searchParams.entries());
50
+ try {
51
+ return schema.parse(searchParams);
52
+ }
53
+ catch (e) {
54
+ error(400, e instanceof z.core.$ZodError ? z.prettifyError(e) : 'invalid query parameters');
55
+ }
56
+ }
47
57
  export function getToken(request, sensitive = false) {
48
58
  const header_token = request.headers.get('Authorization')?.replace('Bearer ', '');
49
59
  if (header_token)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axium/server",
3
- "version": "0.30.0",
3
+ "version": "0.31.1",
4
4
  "author": "James Prevett <axium@jamespre.dev>",
5
5
  "funding": {
6
6
  "type": "individual",
@@ -48,7 +48,7 @@
48
48
  },
49
49
  "peerDependencies": {
50
50
  "@axium/client": ">=0.9.5",
51
- "@axium/core": ">=0.14.0",
51
+ "@axium/core": ">=0.15.0",
52
52
  "kysely": "^0.28.0",
53
53
  "utilium": "^2.6.0",
54
54
  "zod": "^4.0.5"