@axium/server 0.16.3 → 0.17.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.
Files changed (102) hide show
  1. package/build/client/_app/immutable/chunks/{DDMLQWfm.js → 7xl0ELIE.js} +1 -1
  2. package/build/client/_app/immutable/chunks/7xl0ELIE.js.br +0 -0
  3. package/build/client/_app/immutable/chunks/7xl0ELIE.js.gz +0 -0
  4. package/build/client/_app/immutable/chunks/BRAdhDz6.js +31 -0
  5. package/build/client/_app/immutable/chunks/BRAdhDz6.js.br +0 -0
  6. package/build/client/_app/immutable/chunks/BRAdhDz6.js.gz +0 -0
  7. package/build/client/_app/immutable/chunks/{BfcZAjMn.js → CX0J-op4.js} +1 -1
  8. package/build/client/_app/immutable/chunks/CX0J-op4.js.br +0 -0
  9. package/build/client/_app/immutable/chunks/CX0J-op4.js.gz +0 -0
  10. package/build/client/_app/immutable/chunks/{C_sHKpBE.js → DJ_KxCMV.js} +1 -1
  11. package/build/client/_app/immutable/chunks/DJ_KxCMV.js.br +0 -0
  12. package/build/client/_app/immutable/chunks/DJ_KxCMV.js.gz +0 -0
  13. package/build/client/_app/immutable/entry/{app.DQs_wJeB.js → app.CQmekYJh.js} +2 -2
  14. package/build/client/_app/immutable/entry/app.CQmekYJh.js.br +0 -0
  15. package/build/client/_app/immutable/entry/app.CQmekYJh.js.gz +0 -0
  16. package/build/client/_app/immutable/entry/start.Dx36W5a3.js +1 -0
  17. package/build/client/_app/immutable/entry/start.Dx36W5a3.js.br +2 -0
  18. package/build/client/_app/immutable/entry/start.Dx36W5a3.js.gz +0 -0
  19. package/build/client/_app/immutable/nodes/{1.D2ng5LTp.js → 1.DYxkhS_I.js} +1 -1
  20. package/build/client/_app/immutable/nodes/1.DYxkhS_I.js.br +0 -0
  21. package/build/client/_app/immutable/nodes/1.DYxkhS_I.js.gz +0 -0
  22. package/build/client/_app/immutable/nodes/{3.DfNZyDBl.js → 3.Cki5Gkis.js} +1 -1
  23. package/build/client/_app/immutable/nodes/3.Cki5Gkis.js.br +0 -0
  24. package/build/client/_app/immutable/nodes/3.Cki5Gkis.js.gz +0 -0
  25. package/build/client/_app/immutable/nodes/{4.CFJZfOMs.js → 4.BKg_Gu-0.js} +1 -1
  26. package/build/client/_app/immutable/nodes/4.BKg_Gu-0.js.br +0 -0
  27. package/build/client/_app/immutable/nodes/4.BKg_Gu-0.js.gz +0 -0
  28. package/build/client/_app/immutable/nodes/5.5undPhf2.js +1 -0
  29. package/build/client/_app/immutable/nodes/5.5undPhf2.js.br +0 -0
  30. package/build/client/_app/immutable/nodes/5.5undPhf2.js.gz +0 -0
  31. package/build/client/_app/immutable/nodes/{6.CqeNzAMh.js → 6.BzQIq2vx.js} +1 -1
  32. package/build/client/_app/immutable/nodes/6.BzQIq2vx.js.br +0 -0
  33. package/build/client/_app/immutable/nodes/6.BzQIq2vx.js.gz +0 -0
  34. package/build/client/_app/version.json +1 -1
  35. package/build/client/_app/version.json.br +0 -0
  36. package/build/client/_app/version.json.gz +0 -0
  37. package/build/server/chunks/{1-CNB-iEOC.js → 1-DzEYA6on.js} +2 -2
  38. package/build/server/chunks/{1-CNB-iEOC.js.map → 1-DzEYA6on.js.map} +1 -1
  39. package/build/server/chunks/{3-D7xQwEuP.js → 3-Ks9jlNYv.js} +3 -3
  40. package/build/server/chunks/{3-D7xQwEuP.js.map → 3-Ks9jlNYv.js.map} +1 -1
  41. package/build/server/chunks/{4-CTLWwpj1.js → 4-CmDUmYYc.js} +3 -3
  42. package/build/server/chunks/{4-CTLWwpj1.js.map → 4-CmDUmYYc.js.map} +1 -1
  43. package/build/server/chunks/{5-uP-phnWG.js → 5-BbXAiLZP.js} +3 -3
  44. package/build/server/chunks/{5-uP-phnWG.js.map → 5-BbXAiLZP.js.map} +1 -1
  45. package/build/server/chunks/{6-C9P6O1MT.js → 6-CyHZCnEe.js} +3 -3
  46. package/build/server/chunks/{6-C9P6O1MT.js.map → 6-CyHZCnEe.js.map} +1 -1
  47. package/build/server/chunks/{Logout-CDjCRZpb.js → Logout-CkwmxNTt.js} +2 -2
  48. package/build/server/chunks/{Logout-CDjCRZpb.js.map → Logout-CkwmxNTt.js.map} +1 -1
  49. package/build/server/chunks/{_page.svelte-oktZpRPO.js → _page.svelte-CVJoQAAB.js} +2 -2
  50. package/build/server/chunks/{_page.svelte-oktZpRPO.js.map → _page.svelte-CVJoQAAB.js.map} +1 -1
  51. package/build/server/chunks/{_page.svelte-CohtrGLZ.js → _page.svelte-D3kXcWlK.js} +2 -2
  52. package/build/server/chunks/{_page.svelte-CohtrGLZ.js.map → _page.svelte-D3kXcWlK.js.map} +1 -1
  53. package/build/server/chunks/{_page.svelte-D5xsy8Qm.js → _page.svelte-DPdvFuWg.js} +3 -3
  54. package/build/server/chunks/{_page.svelte-D5xsy8Qm.js.map → _page.svelte-DPdvFuWg.js.map} +1 -1
  55. package/build/server/chunks/{_page.svelte-C07GMDXE.js → _page.svelte-whmO3TtB.js} +3 -3
  56. package/build/server/chunks/{_page.svelte-C07GMDXE.js.map → _page.svelte-whmO3TtB.js.map} +1 -1
  57. package/build/server/chunks/{user-CvsWrOQL.js → user-DW-tjDe0.js} +16 -1
  58. package/build/server/chunks/{user-CvsWrOQL.js.map → user-DW-tjDe0.js.map} +1 -1
  59. package/build/server/index.js +1 -1
  60. package/build/server/index.js.map +1 -1
  61. package/build/server/manifest.js +6 -6
  62. package/build/server/manifest.js.map +1 -1
  63. package/dist/acl.d.ts +54 -0
  64. package/dist/acl.js +63 -0
  65. package/dist/api/acl.d.ts +1 -0
  66. package/dist/api/acl.js +21 -0
  67. package/dist/api/users.js +16 -12
  68. package/dist/auth.js +1 -0
  69. package/dist/cli.js +13 -7
  70. package/dist/database.d.ts +53 -7
  71. package/dist/database.js +146 -41
  72. package/dist/plugins.d.ts +5 -5
  73. package/dist/routes.d.ts +3 -0
  74. package/dist/routes.js +3 -0
  75. package/package.json +1 -1
  76. package/web/lib/AccessControl.svelte +37 -0
  77. package/web/lib/AccessControlDialog.svelte +12 -0
  78. package/build/client/_app/immutable/chunks/BfcZAjMn.js.br +0 -0
  79. package/build/client/_app/immutable/chunks/BfcZAjMn.js.gz +0 -0
  80. package/build/client/_app/immutable/chunks/C_sHKpBE.js.br +0 -0
  81. package/build/client/_app/immutable/chunks/C_sHKpBE.js.gz +0 -0
  82. package/build/client/_app/immutable/chunks/DDMLQWfm.js.br +0 -0
  83. package/build/client/_app/immutable/chunks/DDMLQWfm.js.gz +0 -0
  84. package/build/client/_app/immutable/chunks/znOOCAZb.js +0 -31
  85. package/build/client/_app/immutable/chunks/znOOCAZb.js.br +0 -0
  86. package/build/client/_app/immutable/chunks/znOOCAZb.js.gz +0 -0
  87. package/build/client/_app/immutable/entry/app.DQs_wJeB.js.br +0 -0
  88. package/build/client/_app/immutable/entry/app.DQs_wJeB.js.gz +0 -0
  89. package/build/client/_app/immutable/entry/start.B5yg2sIq.js +0 -1
  90. package/build/client/_app/immutable/entry/start.B5yg2sIq.js.br +0 -2
  91. package/build/client/_app/immutable/entry/start.B5yg2sIq.js.gz +0 -0
  92. package/build/client/_app/immutable/nodes/1.D2ng5LTp.js.br +0 -0
  93. package/build/client/_app/immutable/nodes/1.D2ng5LTp.js.gz +0 -0
  94. package/build/client/_app/immutable/nodes/3.DfNZyDBl.js.br +0 -0
  95. package/build/client/_app/immutable/nodes/3.DfNZyDBl.js.gz +0 -0
  96. package/build/client/_app/immutable/nodes/4.CFJZfOMs.js.br +0 -0
  97. package/build/client/_app/immutable/nodes/4.CFJZfOMs.js.gz +0 -0
  98. package/build/client/_app/immutable/nodes/5.xLFJGflh.js +0 -1
  99. package/build/client/_app/immutable/nodes/5.xLFJGflh.js.br +0 -0
  100. package/build/client/_app/immutable/nodes/5.xLFJGflh.js.gz +0 -0
  101. package/build/client/_app/immutable/nodes/6.CqeNzAMh.js.br +0 -0
  102. package/build/client/_app/immutable/nodes/6.CqeNzAMh.js.gz +0 -0
package/dist/database.js CHANGED
@@ -77,11 +77,12 @@ export function connect() {
77
77
  return database;
78
78
  }
79
79
  // Helpers
80
- export async function count(table) {
81
- const db = connect();
82
- return (await db.selectFrom(table)
83
- .select(db.fn.countAll().as('count'))
84
- .executeTakeFirstOrThrow()).count;
80
+ export async function count(...tables) {
81
+ return await database
82
+ .selectFrom(tables)
83
+ .select(() => tables.map(t => database.fn.countAll(t).as(t)))
84
+ .$castTo()
85
+ .executeTakeFirstOrThrow();
85
86
  }
86
87
  /**
87
88
  * Select the user with the id from the userId column of a table, placing it in the `user` property.
@@ -92,16 +93,9 @@ export function userFromId(eb) {
92
93
  .$castTo()
93
94
  .as('user');
94
95
  }
95
- export async function status() {
96
- return {
97
- users: await count('users'),
98
- passkeys: await count('passkeys'),
99
- sessions: await count('sessions'),
100
- };
101
- }
102
- export async function statusText() {
96
+ export async function statText() {
103
97
  try {
104
- const stats = await status();
98
+ const stats = await count('users', 'passkeys', 'sessions');
105
99
  return `${stats.users} users, ${stats.passkeys} passkeys, ${stats.sessions} sessions`;
106
100
  }
107
101
  catch (error) {
@@ -152,6 +146,13 @@ const throwUnlessRows = (text) => {
152
146
  throw 'missing.';
153
147
  return text;
154
148
  };
149
+ export async function createIndex(table, column, mod) {
150
+ io.start(`Creating index for ${table}.${column}`);
151
+ let query = database.schema.createIndex(`${table}_${column}_index`).on(table).column(column);
152
+ if (mod)
153
+ query = mod(query);
154
+ await query.execute().then(io.done).catch(warnExists);
155
+ }
155
156
  export async function init(opt) {
156
157
  const env_1 = { stack: [], error: void 0, hasError: false };
157
158
  try {
@@ -195,7 +196,16 @@ export async function init(opt) {
195
196
  })
196
197
  .catch(io.warn);
197
198
  await _sql('SELECT pg_reload_conf()', 'Reloading configuration');
199
+ io.start('Connecting to database');
198
200
  const db = __addDisposableResource(env_1, connect(), true);
201
+ io.done();
202
+ function maybeCheck(table) {
203
+ return (e) => {
204
+ warnExists(e);
205
+ if (opt.check)
206
+ return checkTableTypes(table, expectedTypes[table], opt);
207
+ };
208
+ }
199
209
  io.start('Creating table users');
200
210
  await db.schema
201
211
  .createTable('users')
@@ -211,7 +221,7 @@ export async function init(opt) {
211
221
  .addColumn('registeredAt', 'timestamptz', col => col.notNull().defaultTo(sql `now()`))
212
222
  .execute()
213
223
  .then(io.done)
214
- .catch(warnExists);
224
+ .catch(maybeCheck('users'));
215
225
  io.start('Creating table sessions');
216
226
  await db.schema
217
227
  .createTable('sessions')
@@ -223,9 +233,8 @@ export async function init(opt) {
223
233
  .addColumn('elevated', 'boolean', col => col.notNull())
224
234
  .execute()
225
235
  .then(io.done)
226
- .catch(warnExists);
227
- io.start('Creating index for sessions.userId');
228
- await db.schema.createIndex('sessions_userId_index').on('sessions').column('userId').execute().then(io.done).catch(warnExists);
236
+ .catch(maybeCheck('sessions'));
237
+ await createIndex('sessions', 'id');
229
238
  io.start('Creating table verifications');
230
239
  await db.schema
231
240
  .createTable('verifications')
@@ -235,14 +244,14 @@ export async function init(opt) {
235
244
  .addColumn('role', 'text', col => col.notNull())
236
245
  .execute()
237
246
  .then(io.done)
238
- .catch(warnExists);
247
+ .catch(maybeCheck('verifications'));
239
248
  io.start('Creating table passkeys');
240
249
  await db.schema
241
250
  .createTable('passkeys')
242
251
  .addColumn('id', 'text', col => col.primaryKey().notNull())
243
252
  .addColumn('name', 'text')
244
253
  .addColumn('createdAt', 'timestamptz', col => col.notNull().defaultTo(sql `now()`))
245
- .addColumn('userId', 'uuid', col => col.notNull().references('users.id').onDelete('cascade').onUpdate('cascade'))
254
+ .addColumn('userId', 'uuid', col => col.notNull().references('users.id').onDelete('cascade').notNull())
246
255
  .addColumn('publicKey', 'bytea', col => col.notNull())
247
256
  .addColumn('counter', 'integer', col => col.notNull())
248
257
  .addColumn('deviceType', 'text', col => col.notNull())
@@ -250,14 +259,15 @@ export async function init(opt) {
250
259
  .addColumn('transports', sql `text[]`)
251
260
  .execute()
252
261
  .then(io.done)
253
- .catch(warnExists);
254
- io.start('Creating index for passkeys.id');
255
- await db.schema.createIndex('passkeys_id_key').on('passkeys').column('id').execute().then(io.done).catch(warnExists);
262
+ .catch(maybeCheck('passkeys'));
263
+ await createIndex('passkeys', 'userId');
264
+ io.start('Creating schema acl');
265
+ await db.schema.createSchema('acl').execute().then(io.done).catch(warnExists);
256
266
  for (const plugin of plugins) {
257
267
  if (!plugin.hooks.db_init)
258
268
  continue;
259
269
  io.plugin(plugin.name);
260
- await plugin.hooks.db_init(opt, db);
270
+ await plugin.hooks.db_init(opt);
261
271
  }
262
272
  }
263
273
  catch (e_1) {
@@ -270,6 +280,90 @@ export async function init(opt) {
270
280
  await result_1;
271
281
  }
272
282
  }
283
+ export const expectedTypes = {
284
+ users: {
285
+ email: { type: 'text', required: true },
286
+ emailVerified: { type: 'timestamptz' },
287
+ id: { type: 'uuid', required: true, hasDefault: true },
288
+ image: { type: 'text' },
289
+ isAdmin: { type: 'bool', required: true, hasDefault: true },
290
+ name: { type: 'text' },
291
+ preferences: { type: 'jsonb', required: true, hasDefault: true },
292
+ registeredAt: { type: 'timestamptz', required: true, hasDefault: true },
293
+ roles: { type: '_text', required: true, hasDefault: true },
294
+ tags: { type: '_text', required: true, hasDefault: true },
295
+ },
296
+ verifications: {
297
+ userId: { type: 'uuid', required: true },
298
+ token: { type: 'text', required: true },
299
+ expires: { type: 'timestamptz', required: true },
300
+ role: { type: 'text', required: true },
301
+ },
302
+ passkeys: {
303
+ id: { type: 'text', required: true },
304
+ name: { type: 'text' },
305
+ createdAt: { type: 'timestamptz', required: true, hasDefault: true },
306
+ userId: { type: 'uuid', required: true },
307
+ publicKey: { type: 'bytea', required: true },
308
+ counter: { type: 'int4', required: true },
309
+ deviceType: { type: 'text', required: true },
310
+ backedUp: { type: 'bool', required: true },
311
+ transports: { type: '_text' },
312
+ },
313
+ sessions: {
314
+ id: { type: 'uuid', required: true, hasDefault: true },
315
+ userId: { type: 'uuid', required: true },
316
+ created: { type: 'timestamptz', required: true },
317
+ token: { type: 'text', required: true },
318
+ expires: { type: 'timestamptz', required: true },
319
+ elevated: { type: 'bool', required: true },
320
+ },
321
+ };
322
+ /**
323
+ * Checks that a table has the expected column types, nullability, and default values.
324
+ */
325
+ export async function checkTableTypes(tableName, types, opt) {
326
+ io.start(`Checking for table ${tableName}`);
327
+ const dbTables = opt._metadata || (await database.introspection.getTables());
328
+ const table = dbTables.find(t => t.name === tableName);
329
+ if (!table)
330
+ throw 'missing.';
331
+ io.done();
332
+ const columns = Object.fromEntries(table.columns.map(c => [c.name, c]));
333
+ for (const [key, { type, required = false, hasDefault = false }] of Object.entries(types)) {
334
+ io.start(`Checking column ${tableName}.${key}`);
335
+ const col = columns[key];
336
+ if (!col)
337
+ throw 'missing.';
338
+ try {
339
+ if (col.dataType != type)
340
+ throw `incorrect type "${col.dataType}", expected ${type}`;
341
+ if (col.isNullable != !required)
342
+ throw required ? 'nullable' : 'not nullable';
343
+ if (col.hasDefaultValue != hasDefault)
344
+ throw hasDefault ? 'missing default' : 'has default';
345
+ io.done();
346
+ }
347
+ catch (e) {
348
+ if (opt.strict)
349
+ throw e;
350
+ io.warn(e);
351
+ }
352
+ delete columns[key];
353
+ }
354
+ if (!opt.extra)
355
+ return;
356
+ io.start('Checking for extra columns in ' + tableName);
357
+ const unchecked = Object.keys(columns)
358
+ .map(c => `${tableName}.${c}`)
359
+ .join(', ');
360
+ if (!unchecked.length)
361
+ io.done();
362
+ else if (opt.strict)
363
+ throw unchecked;
364
+ else
365
+ io.warn(unchecked);
366
+ }
273
367
  export async function check(opt) {
274
368
  const env_2 = { stack: [], error: void 0, hasError: false };
275
369
  try {
@@ -280,18 +374,22 @@ export async function check(opt) {
280
374
  io.start('Connecting to database');
281
375
  const db = __addDisposableResource(env_2, connect(), true);
282
376
  io.done();
283
- io.start('Checking users table');
284
- await db.selectFrom('users').select(['id', 'email', 'emailVerified', 'image', 'name', 'preferences']).execute().then(io.done);
285
- io.start('Checking sessions table');
286
- await db.selectFrom('sessions').select(['id', 'userId', 'token', 'created', 'expires', 'elevated']).execute().then(io.done);
287
- io.start('Checking verifications table');
288
- await db.selectFrom('verifications').select(['userId', 'token', 'expires', 'role']).execute().then(io.done);
289
- io.start('Checking passkeys table');
290
- await db
291
- .selectFrom('passkeys')
292
- .select(['id', 'name', 'createdAt', 'userId', 'publicKey', 'counter', 'deviceType', 'backedUp', 'transports'])
293
- .execute()
294
- .then(io.done);
377
+ io.start('Getting table metadata');
378
+ opt._metadata = await db.introspection.getTables();
379
+ const tables = Object.fromEntries(opt._metadata.map(t => [t.name, t]));
380
+ io.done();
381
+ for (const table of Object.keys(expectedTypes)) {
382
+ await checkTableTypes(table, expectedTypes[table], opt);
383
+ delete tables[table];
384
+ }
385
+ io.start('Checking for extra tables');
386
+ const unchecked = Object.keys(tables).join(', ');
387
+ if (!unchecked.length)
388
+ io.done();
389
+ else if (opt.strict)
390
+ throw unchecked;
391
+ else
392
+ io.warn(unchecked);
295
393
  }
296
394
  catch (e_2) {
297
395
  env_2.error = e_2;
@@ -314,7 +412,7 @@ export async function clean(opt) {
314
412
  if (!plugin.hooks.clean)
315
413
  continue;
316
414
  io.plugin(plugin.name);
317
- await plugin.hooks.clean(opt, db);
415
+ await plugin.hooks.clean(opt);
318
416
  }
319
417
  }
320
418
  /**
@@ -323,12 +421,12 @@ export async function clean(opt) {
323
421
  export async function uninstall(opt) {
324
422
  const env_3 = { stack: [], error: void 0, hasError: false };
325
423
  try {
326
- const db = __addDisposableResource(env_3, connect(), true);
424
+ const _ = __addDisposableResource(env_3, connect(), true);
327
425
  for (const plugin of plugins) {
328
426
  if (!plugin.hooks.remove)
329
427
  continue;
330
428
  io.plugin(plugin.name);
331
- await plugin.hooks.remove(opt, db);
429
+ await plugin.hooks.remove(opt);
332
430
  }
333
431
  await _sql('DROP DATABASE axium', 'Dropping database');
334
432
  await _sql('REVOKE ALL PRIVILEGES ON SCHEMA public FROM axium', 'Revoking schema privileges');
@@ -365,13 +463,20 @@ export async function wipe(opt) {
365
463
  if (!plugin.hooks.db_wipe)
366
464
  continue;
367
465
  io.plugin(plugin.name);
368
- await plugin.hooks.db_wipe(opt, db);
466
+ await plugin.hooks.db_wipe(opt);
369
467
  }
370
468
  for (const table of ['users', 'passkeys', 'sessions', 'verifications']) {
371
- io.start(`Removing data from ${table}`);
469
+ io.start(`Wiping ${table}`);
372
470
  await db.deleteFrom(table).execute();
373
471
  io.done();
374
472
  }
473
+ for (const table of await database.introspection.getTables()) {
474
+ if (!table.name.startsWith('acl.'))
475
+ continue;
476
+ const name = table.name;
477
+ io.debug(`Wiping ${name}`);
478
+ await db.deleteFrom(name).execute();
479
+ }
375
480
  }
376
481
  export async function rotatePassword() {
377
482
  io.start('Generating new password');
package/dist/plugins.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as z from 'zod';
2
- import type { Database, InitOptions, OpOptions } from './database.js';
2
+ import type { InitOptions, OpOptions } from './database.js';
3
3
  export declare const PluginMetadata: z.ZodObject<{
4
4
  name: z.ZodString;
5
5
  version: z.ZodString;
@@ -21,12 +21,12 @@ interface PluginInternal extends Plugin {
21
21
  hooks: Hooks;
22
22
  }
23
23
  export interface Hooks {
24
- db_init?: (opt: InitOptions, db: Database) => void | Promise<void>;
24
+ db_init?: (opt: InitOptions) => void | Promise<void>;
25
25
  remove?: (opt: {
26
26
  force?: boolean;
27
- }, db: Database) => void | Promise<void>;
28
- db_wipe?: (opt: OpOptions, db: Database) => void | Promise<void>;
29
- clean?: (opt: Partial<OpOptions>, db: Database) => void | Promise<void>;
27
+ }) => void | Promise<void>;
28
+ db_wipe?: (opt: OpOptions) => void | Promise<void>;
29
+ clean?: (opt: Partial<OpOptions>) => void | Promise<void>;
30
30
  }
31
31
  export declare const plugins: Set<PluginInternal>;
32
32
  export declare function resolvePlugin(search: string): PluginInternal | undefined;
package/dist/routes.d.ts CHANGED
@@ -42,6 +42,9 @@ export type Route = ServerRoute | WebRoute;
42
42
  * @internal
43
43
  */
44
44
  export declare const routes: Map<string, Route>;
45
+ /**
46
+ * @category Plugin API
47
+ */
45
48
  export declare function addRoute(opt: RouteOptions): void;
46
49
  /**
47
50
  * Resolve a request URL into a route.
package/dist/routes.js CHANGED
@@ -6,6 +6,9 @@ import { _unique } from './state.js';
6
6
  * @internal
7
7
  */
8
8
  export const routes = _unique('routes', new Map());
9
+ /**
10
+ * @category Plugin API
11
+ */
9
12
  export function addRoute(opt) {
10
13
  const route = { ...opt, server: !('page' in opt) };
11
14
  if (!route.path.startsWith('/')) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axium/server",
3
- "version": "0.16.3",
3
+ "version": "0.17.1",
4
4
  "author": "James Prevett <axium@jamespre.dev> (https://jamespre.dev)",
5
5
  "funding": {
6
6
  "type": "individual",
@@ -0,0 +1,37 @@
1
+ <script lang="ts">
2
+ import UserCard from './UserCard.svelte';
3
+ import type { Permission, AccessControl } from '@axium/core/access';
4
+ import { permissionNames } from '@axium/core/access';
5
+ import type { Entries } from 'utilium';
6
+
7
+ const { control, editable }: { control: AccessControl; editable: boolean } = $props();
8
+
9
+ const perm = $derived(permissionNames[control.permission as Permission]);
10
+
11
+ const permEntries = Object.entries(permissionNames) as any as Entries<typeof permissionNames>;
12
+ </script>
13
+
14
+ <div class="AccessControl">
15
+ {#if !control.user}<i>Unknown</i>
16
+ {:else}
17
+ <UserCard user={control.user} />
18
+ {#if editable}
19
+ <input type="hidden" name="userId" value={control.user.id} />
20
+ <select name="permission">
21
+ {#each permEntries as [key, name]}
22
+ <option value={key} selected={key == control.permission}>{name}</option>
23
+ {/each}
24
+ </select>
25
+ {:else}
26
+ <span>{perm}</span>
27
+ {/if}
28
+ {/if}
29
+ </div>
30
+
31
+ <style>
32
+ .AccessControl {
33
+ display: flex;
34
+ gap: 1em;
35
+ padding: 1em 2em;
36
+ }
37
+ </style>
@@ -0,0 +1,12 @@
1
+ <script lang="ts">
2
+ import FormDialog from './FormDialog.svelte';
3
+ import AccessControl from './AccessControl.svelte';
4
+
5
+ let { item, editable } = $props();
6
+ </script>
7
+
8
+ <FormDialog submitText="Save">
9
+ {#each item.acl as control}
10
+ <AccessControl {control} {editable} />
11
+ {/each}
12
+ </FormDialog>