@axium/server 0.16.2 → 0.17.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 (115) hide show
  1. package/build/client/_app/immutable/chunks/{Dm7VIzXy.js → BIqJFsZ3.js} +1 -1
  2. package/build/client/_app/immutable/chunks/BIqJFsZ3.js.br +0 -0
  3. package/build/client/_app/immutable/chunks/BIqJFsZ3.js.gz +0 -0
  4. package/build/client/_app/immutable/chunks/{DIqfHWch.js → BQEW-soH.js} +1 -1
  5. package/build/client/_app/immutable/chunks/BQEW-soH.js.br +0 -0
  6. package/build/client/_app/immutable/chunks/BQEW-soH.js.gz +0 -0
  7. package/build/client/_app/immutable/chunks/ClV-nJVy.js +31 -0
  8. package/build/client/_app/immutable/chunks/ClV-nJVy.js.br +0 -0
  9. package/build/client/_app/immutable/chunks/ClV-nJVy.js.gz +0 -0
  10. package/build/client/_app/immutable/chunks/{Ccv5p-Lr.js → DQ-d8a5w.js} +2 -2
  11. package/build/client/_app/immutable/chunks/DQ-d8a5w.js.br +0 -0
  12. package/build/client/_app/immutable/chunks/DQ-d8a5w.js.gz +0 -0
  13. package/build/client/_app/immutable/entry/{app.CYeEqzuG.js → app.Bexu3pho.js} +2 -2
  14. package/build/client/_app/immutable/entry/app.Bexu3pho.js.br +0 -0
  15. package/build/client/_app/immutable/entry/app.Bexu3pho.js.gz +0 -0
  16. package/build/client/_app/immutable/entry/start.CkXOtoPI.js +1 -0
  17. package/build/client/_app/immutable/entry/start.CkXOtoPI.js.br +2 -0
  18. package/build/client/_app/immutable/entry/start.CkXOtoPI.js.gz +0 -0
  19. package/build/client/_app/immutable/nodes/{1.BHYgH-pu.js → 1.Di0t29Xx.js} +1 -1
  20. package/build/client/_app/immutable/nodes/1.Di0t29Xx.js.br +0 -0
  21. package/build/client/_app/immutable/nodes/1.Di0t29Xx.js.gz +0 -0
  22. package/build/client/_app/immutable/nodes/{3.C6woCAgZ.js → 3.dB5xKZIR.js} +1 -1
  23. package/build/client/_app/immutable/nodes/3.dB5xKZIR.js.br +0 -0
  24. package/build/client/_app/immutable/nodes/3.dB5xKZIR.js.gz +0 -0
  25. package/build/client/_app/immutable/nodes/{4.CMRdSHhP.js → 4.DC1TCp7U.js} +1 -1
  26. package/build/client/_app/immutable/nodes/4.DC1TCp7U.js.br +0 -0
  27. package/build/client/_app/immutable/nodes/4.DC1TCp7U.js.gz +0 -0
  28. package/build/client/_app/immutable/nodes/5.DGB0QJIF.js +1 -0
  29. package/build/client/_app/immutable/nodes/5.DGB0QJIF.js.br +0 -0
  30. package/build/client/_app/immutable/nodes/5.DGB0QJIF.js.gz +0 -0
  31. package/build/client/_app/immutable/nodes/{6.asNffl6m.js → 6.WTfzqOhT.js} +1 -1
  32. package/build/client/_app/immutable/nodes/6.WTfzqOhT.js.br +0 -0
  33. package/build/client/_app/immutable/nodes/6.WTfzqOhT.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-B46rUG8d.js → 1-Db3AncWW.js} +2 -2
  38. package/build/server/chunks/{1-B46rUG8d.js.map → 1-Db3AncWW.js.map} +1 -1
  39. package/build/server/chunks/{3-CeRz1jLd.js → 3-BzEpIldk.js} +3 -3
  40. package/build/server/chunks/{3-CeRz1jLd.js.map → 3-BzEpIldk.js.map} +1 -1
  41. package/build/server/chunks/{4-CPwP8Ikn.js → 4-J7bxt5HT.js} +3 -3
  42. package/build/server/chunks/{4-CPwP8Ikn.js.map → 4-J7bxt5HT.js.map} +1 -1
  43. package/build/server/chunks/{5-xNibYAp4.js → 5-uwjgYAk8.js} +3 -3
  44. package/build/server/chunks/{5-xNibYAp4.js.map → 5-uwjgYAk8.js.map} +1 -1
  45. package/build/server/chunks/{6-CivW166X.js → 6-Cj4cg3MT.js} +3 -3
  46. package/build/server/chunks/{6-CivW166X.js.map → 6-Cj4cg3MT.js.map} +1 -1
  47. package/build/server/chunks/{Logout-jfPnYBz7.js → Logout-CkwmxNTt.js} +2 -2
  48. package/build/server/chunks/{Logout-jfPnYBz7.js.map → Logout-CkwmxNTt.js.map} +1 -1
  49. package/build/server/chunks/{_page.svelte-D1Tk1-7i.js → _page.svelte-CVJoQAAB.js} +2 -2
  50. package/build/server/chunks/{_page.svelte-D1Tk1-7i.js.map → _page.svelte-CVJoQAAB.js.map} +1 -1
  51. package/build/server/chunks/{_page.svelte-CPmgKX2f.js → _page.svelte-D3kXcWlK.js} +2 -2
  52. package/build/server/chunks/{_page.svelte-CPmgKX2f.js.map → _page.svelte-D3kXcWlK.js.map} +1 -1
  53. package/build/server/chunks/{_page.svelte-C5KWK5D-.js → _page.svelte-DPdvFuWg.js} +3 -3
  54. package/build/server/chunks/{_page.svelte-C5KWK5D-.js.map → _page.svelte-DPdvFuWg.js.map} +1 -1
  55. package/build/server/chunks/{_page.svelte-BUYPMfbS.js → _page.svelte-whmO3TtB.js} +3 -3
  56. package/build/server/chunks/{_page.svelte-BUYPMfbS.js.map → _page.svelte-whmO3TtB.js.map} +1 -1
  57. package/build/server/chunks/user-DW-tjDe0.js +5656 -0
  58. package/build/server/chunks/user-DW-tjDe0.js.map +1 -0
  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/passkeys.js +1 -1
  68. package/dist/api/register.js +1 -1
  69. package/dist/api/users.js +17 -13
  70. package/dist/auth.js +1 -0
  71. package/dist/cli.js +15 -9
  72. package/dist/database.d.ts +52 -7
  73. package/dist/database.js +147 -41
  74. package/dist/plugins.d.ts +10 -10
  75. package/dist/plugins.js +1 -1
  76. package/dist/requests.d.ts +1 -1
  77. package/dist/requests.js +1 -1
  78. package/dist/routes.d.ts +3 -0
  79. package/dist/routes.js +3 -0
  80. package/dist/sveltekit.js +1 -1
  81. package/package.json +2 -2
  82. package/svelte.config.js +3 -0
  83. package/web/lib/AccessControl.svelte +37 -0
  84. package/web/lib/AccessControlDialog.svelte +12 -0
  85. package/web/lib/Popover.svelte +0 -2
  86. package/web/lib/Upload.svelte +19 -21
  87. package/web/lib/WithContextMenu.svelte +0 -1
  88. package/build/client/_app/immutable/chunks/Ccv5p-Lr.js.br +0 -0
  89. package/build/client/_app/immutable/chunks/Ccv5p-Lr.js.gz +0 -0
  90. package/build/client/_app/immutable/chunks/DIqfHWch.js.br +0 -0
  91. package/build/client/_app/immutable/chunks/DIqfHWch.js.gz +0 -0
  92. package/build/client/_app/immutable/chunks/Dm7VIzXy.js.br +0 -0
  93. package/build/client/_app/immutable/chunks/Dm7VIzXy.js.gz +0 -0
  94. package/build/client/_app/immutable/chunks/iuOP1-8s.js +0 -33
  95. package/build/client/_app/immutable/chunks/iuOP1-8s.js.br +0 -0
  96. package/build/client/_app/immutable/chunks/iuOP1-8s.js.gz +0 -0
  97. package/build/client/_app/immutable/entry/app.CYeEqzuG.js.br +0 -0
  98. package/build/client/_app/immutable/entry/app.CYeEqzuG.js.gz +0 -0
  99. package/build/client/_app/immutable/entry/start.B8r-M4qY.js +0 -1
  100. package/build/client/_app/immutable/entry/start.B8r-M4qY.js.br +0 -2
  101. package/build/client/_app/immutable/entry/start.B8r-M4qY.js.gz +0 -0
  102. package/build/client/_app/immutable/nodes/1.BHYgH-pu.js.br +0 -0
  103. package/build/client/_app/immutable/nodes/1.BHYgH-pu.js.gz +0 -0
  104. package/build/client/_app/immutable/nodes/3.C6woCAgZ.js.br +0 -0
  105. package/build/client/_app/immutable/nodes/3.C6woCAgZ.js.gz +0 -0
  106. package/build/client/_app/immutable/nodes/4.CMRdSHhP.js.br +0 -0
  107. package/build/client/_app/immutable/nodes/4.CMRdSHhP.js.gz +0 -0
  108. package/build/client/_app/immutable/nodes/5.DQgmXUpj.js +0 -1
  109. package/build/client/_app/immutable/nodes/5.DQgmXUpj.js.br +0 -0
  110. package/build/client/_app/immutable/nodes/5.DQgmXUpj.js.gz +0 -0
  111. package/build/client/_app/immutable/nodes/6.asNffl6m.js.br +0 -0
  112. package/build/client/_app/immutable/nodes/6.asNffl6m.js.gz +0 -0
  113. package/build/server/chunks/user-C2RJxOZ2.js +0 -13552
  114. package/build/server/chunks/user-C2RJxOZ2.js.map +0 -1
  115. package/schema.json +0 -154
package/dist/database.js CHANGED
@@ -58,6 +58,7 @@ import pg from 'pg';
58
58
  import config from './config.js';
59
59
  import * as io from './io.js';
60
60
  import { plugins } from './plugins.js';
61
+ import * as acl from './acl.js';
61
62
  const sym = Symbol.for('Axium:database');
62
63
  export let database;
63
64
  export function connect() {
@@ -77,11 +78,12 @@ export function connect() {
77
78
  return database;
78
79
  }
79
80
  // 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;
81
+ export async function count(...tables) {
82
+ return await database
83
+ .selectFrom(tables)
84
+ .select(() => tables.map(t => database.fn.countAll(t).as(t)))
85
+ .$castTo()
86
+ .executeTakeFirstOrThrow();
85
87
  }
86
88
  /**
87
89
  * Select the user with the id from the userId column of a table, placing it in the `user` property.
@@ -92,16 +94,9 @@ export function userFromId(eb) {
92
94
  .$castTo()
93
95
  .as('user');
94
96
  }
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() {
97
+ export async function statText() {
103
98
  try {
104
- const stats = await status();
99
+ const stats = await count('users', 'passkeys', 'sessions');
105
100
  return `${stats.users} users, ${stats.passkeys} passkeys, ${stats.sessions} sessions`;
106
101
  }
107
102
  catch (error) {
@@ -152,6 +147,13 @@ const throwUnlessRows = (text) => {
152
147
  throw 'missing.';
153
148
  return text;
154
149
  };
150
+ export async function createIndex(table, column, mod) {
151
+ io.start(`Creating index for ${table}.${column}`);
152
+ let query = database.schema.createIndex(`${table}_${column}_index`).on(table).column(column);
153
+ if (mod)
154
+ query = mod(query);
155
+ await query.execute().then(io.done).catch(warnExists);
156
+ }
155
157
  export async function init(opt) {
156
158
  const env_1 = { stack: [], error: void 0, hasError: false };
157
159
  try {
@@ -195,7 +197,16 @@ export async function init(opt) {
195
197
  })
196
198
  .catch(io.warn);
197
199
  await _sql('SELECT pg_reload_conf()', 'Reloading configuration');
200
+ io.start('Connecting to database');
198
201
  const db = __addDisposableResource(env_1, connect(), true);
202
+ io.done();
203
+ function maybeCheck(table) {
204
+ return (e) => {
205
+ warnExists(e);
206
+ if (opt.check)
207
+ return checkTableTypes(table, expectedTypes[table], opt);
208
+ };
209
+ }
199
210
  io.start('Creating table users');
200
211
  await db.schema
201
212
  .createTable('users')
@@ -211,7 +222,7 @@ export async function init(opt) {
211
222
  .addColumn('registeredAt', 'timestamptz', col => col.notNull().defaultTo(sql `now()`))
212
223
  .execute()
213
224
  .then(io.done)
214
- .catch(warnExists);
225
+ .catch(maybeCheck('users'));
215
226
  io.start('Creating table sessions');
216
227
  await db.schema
217
228
  .createTable('sessions')
@@ -223,9 +234,8 @@ export async function init(opt) {
223
234
  .addColumn('elevated', 'boolean', col => col.notNull())
224
235
  .execute()
225
236
  .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);
237
+ .catch(maybeCheck('sessions'));
238
+ await createIndex('sessions', 'id');
229
239
  io.start('Creating table verifications');
230
240
  await db.schema
231
241
  .createTable('verifications')
@@ -235,14 +245,14 @@ export async function init(opt) {
235
245
  .addColumn('role', 'text', col => col.notNull())
236
246
  .execute()
237
247
  .then(io.done)
238
- .catch(warnExists);
248
+ .catch(maybeCheck('verifications'));
239
249
  io.start('Creating table passkeys');
240
250
  await db.schema
241
251
  .createTable('passkeys')
242
252
  .addColumn('id', 'text', col => col.primaryKey().notNull())
243
253
  .addColumn('name', 'text')
244
254
  .addColumn('createdAt', 'timestamptz', col => col.notNull().defaultTo(sql `now()`))
245
- .addColumn('userId', 'uuid', col => col.notNull().references('users.id').onDelete('cascade').onUpdate('cascade'))
255
+ .addColumn('userId', 'uuid', col => col.notNull().references('users.id').onDelete('cascade').notNull())
246
256
  .addColumn('publicKey', 'bytea', col => col.notNull())
247
257
  .addColumn('counter', 'integer', col => col.notNull())
248
258
  .addColumn('deviceType', 'text', col => col.notNull())
@@ -250,14 +260,15 @@ export async function init(opt) {
250
260
  .addColumn('transports', sql `text[]`)
251
261
  .execute()
252
262
  .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);
263
+ .catch(maybeCheck('passkeys'));
264
+ await createIndex('passkeys', 'userId');
265
+ io.start('Creating schema acl');
266
+ await db.schema.createSchema('acl').execute().then(io.done).catch(warnExists);
256
267
  for (const plugin of plugins) {
257
268
  if (!plugin.hooks.db_init)
258
269
  continue;
259
270
  io.plugin(plugin.name);
260
- await plugin.hooks.db_init(opt, db);
271
+ await plugin.hooks.db_init(opt);
261
272
  }
262
273
  }
263
274
  catch (e_1) {
@@ -270,6 +281,90 @@ export async function init(opt) {
270
281
  await result_1;
271
282
  }
272
283
  }
284
+ export const expectedTypes = {
285
+ users: {
286
+ email: { type: 'text', required: true },
287
+ emailVerified: { type: 'timestamptz' },
288
+ id: { type: 'uuid', required: true, hasDefault: true },
289
+ image: { type: 'text' },
290
+ isAdmin: { type: 'bool', required: true, hasDefault: true },
291
+ name: { type: 'text' },
292
+ preferences: { type: 'jsonb', required: true, hasDefault: true },
293
+ registeredAt: { type: 'timestamptz', required: true, hasDefault: true },
294
+ roles: { type: '_text', required: true, hasDefault: true },
295
+ tags: { type: '_text', required: true, hasDefault: true },
296
+ },
297
+ verifications: {
298
+ userId: { type: 'uuid', required: true },
299
+ token: { type: 'text', required: true },
300
+ expires: { type: 'timestamptz', required: true },
301
+ role: { type: 'text', required: true },
302
+ },
303
+ passkeys: {
304
+ id: { type: 'text', required: true },
305
+ name: { type: 'text' },
306
+ createdAt: { type: 'timestamptz', required: true, hasDefault: true },
307
+ userId: { type: 'uuid', required: true },
308
+ publicKey: { type: 'bytea', required: true },
309
+ counter: { type: 'int4', required: true },
310
+ deviceType: { type: 'text', required: true },
311
+ backedUp: { type: 'bool', required: true },
312
+ transports: { type: '_text' },
313
+ },
314
+ sessions: {
315
+ id: { type: 'uuid', required: true, hasDefault: true },
316
+ userId: { type: 'uuid', required: true },
317
+ created: { type: 'timestamptz', required: true },
318
+ token: { type: 'text', required: true },
319
+ expires: { type: 'timestamptz', required: true },
320
+ elevated: { type: 'bool', required: true },
321
+ },
322
+ };
323
+ /**
324
+ * Checks that a table has the expected column types, nullability, and default values.
325
+ */
326
+ export async function checkTableTypes(tableName, types, opt) {
327
+ io.start(`Checking for table ${tableName}`);
328
+ const dbTables = opt._metadata || (await database.introspection.getTables());
329
+ const table = dbTables.find(t => t.name === tableName);
330
+ if (!table)
331
+ throw 'missing.';
332
+ io.done();
333
+ const columns = Object.fromEntries(table.columns.map(c => [c.name, c]));
334
+ for (const [key, { type, required = false, hasDefault = false }] of Object.entries(types)) {
335
+ io.start(`Checking column ${tableName}.${key}`);
336
+ const col = columns[key];
337
+ if (!col)
338
+ throw 'missing.';
339
+ try {
340
+ if (col.dataType != type)
341
+ throw `incorrect type "${col.dataType}", expected ${type}`;
342
+ if (col.isNullable != !required)
343
+ throw required ? 'nullable' : 'not nullable';
344
+ if (col.hasDefaultValue != hasDefault)
345
+ throw hasDefault ? 'missing default' : 'has default';
346
+ io.done();
347
+ }
348
+ catch (e) {
349
+ if (opt.strict)
350
+ throw e;
351
+ io.warn(e);
352
+ }
353
+ delete columns[key];
354
+ }
355
+ if (!opt.extra)
356
+ return;
357
+ io.start('Checking for extra columns in ' + tableName);
358
+ const unchecked = Object.keys(columns)
359
+ .map(c => `${tableName}.${c}`)
360
+ .join(', ');
361
+ if (!unchecked.length)
362
+ io.done();
363
+ else if (opt.strict)
364
+ throw unchecked;
365
+ else
366
+ io.warn(unchecked);
367
+ }
273
368
  export async function check(opt) {
274
369
  const env_2 = { stack: [], error: void 0, hasError: false };
275
370
  try {
@@ -280,18 +375,22 @@ export async function check(opt) {
280
375
  io.start('Connecting to database');
281
376
  const db = __addDisposableResource(env_2, connect(), true);
282
377
  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);
378
+ io.start('Getting table metadata');
379
+ opt._metadata = await db.introspection.getTables();
380
+ const tables = Object.fromEntries(opt._metadata.map(t => [t.name, t]));
381
+ io.done();
382
+ for (const table of Object.keys(expectedTypes)) {
383
+ await checkTableTypes(table, expectedTypes[table], opt);
384
+ delete tables[table];
385
+ }
386
+ io.start('Checking for extra tables');
387
+ const unchecked = Object.keys(tables).join(', ');
388
+ if (!unchecked.length)
389
+ io.done();
390
+ else if (opt.strict)
391
+ throw unchecked;
392
+ else
393
+ io.warn(unchecked);
295
394
  }
296
395
  catch (e_2) {
297
396
  env_2.error = e_2;
@@ -314,7 +413,7 @@ export async function clean(opt) {
314
413
  if (!plugin.hooks.clean)
315
414
  continue;
316
415
  io.plugin(plugin.name);
317
- await plugin.hooks.clean(opt, db);
416
+ await plugin.hooks.clean(opt);
318
417
  }
319
418
  }
320
419
  /**
@@ -323,12 +422,12 @@ export async function clean(opt) {
323
422
  export async function uninstall(opt) {
324
423
  const env_3 = { stack: [], error: void 0, hasError: false };
325
424
  try {
326
- const db = __addDisposableResource(env_3, connect(), true);
425
+ const _ = __addDisposableResource(env_3, connect(), true);
327
426
  for (const plugin of plugins) {
328
427
  if (!plugin.hooks.remove)
329
428
  continue;
330
429
  io.plugin(plugin.name);
331
- await plugin.hooks.remove(opt, db);
430
+ await plugin.hooks.remove(opt);
332
431
  }
333
432
  await _sql('DROP DATABASE axium', 'Dropping database');
334
433
  await _sql('REVOKE ALL PRIVILEGES ON SCHEMA public FROM axium', 'Revoking schema privileges');
@@ -365,13 +464,20 @@ export async function wipe(opt) {
365
464
  if (!plugin.hooks.db_wipe)
366
465
  continue;
367
466
  io.plugin(plugin.name);
368
- await plugin.hooks.db_wipe(opt, db);
467
+ await plugin.hooks.db_wipe(opt);
369
468
  }
370
469
  for (const table of ['users', 'passkeys', 'sessions', 'verifications']) {
371
- io.start(`Removing data from ${table}`);
470
+ io.start(`Wiping ${table}`);
372
471
  await db.deleteFrom(table).execute();
373
472
  io.done();
374
473
  }
474
+ for (const table of await database.introspection.getTables()) {
475
+ if (!table.name.startsWith('acl.'))
476
+ continue;
477
+ const name = table.name;
478
+ io.debug(`Wiping ${name}`);
479
+ await db.deleteFrom(name).execute();
480
+ }
375
481
  }
376
482
  export async function rotatePassword() {
377
483
  io.start('Generating new password');
package/dist/plugins.d.ts CHANGED
@@ -1,19 +1,19 @@
1
- import z from 'zod';
2
- import type { Database, InitOptions, OpOptions } from './database.js';
1
+ import * as z from 'zod';
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;
6
6
  description: z.ZodOptional<z.ZodString>;
7
7
  routes: z.ZodOptional<z.ZodString>;
8
- }, z.z.core.$loose>;
8
+ }, z.core.$loose>;
9
9
  export declare const Plugin: z.ZodObject<{
10
10
  name: z.ZodString;
11
11
  version: z.ZodString;
12
12
  description: z.ZodOptional<z.ZodString>;
13
13
  routes: z.ZodOptional<z.ZodString>;
14
- statusText: z.ZodCustom<z.z.core.$InferInnerFunctionTypeAsync<z.z.core.$ZodTuple<[], null>, z.ZodString>, z.z.core.$InferInnerFunctionTypeAsync<z.z.core.$ZodTuple<[], null>, z.ZodString>>;
15
- hooks: z.ZodOptional<z.ZodRecord<z.ZodLiteral<"remove" | "db_init" | "db_wipe" | "clean"> & z.z.core.$partial, z.ZodCustom<(...args: any[]) => any, (...args: any[]) => any>>>;
16
- }, z.z.core.$loose>;
14
+ statusText: z.ZodCustom<z.core.$InferInnerFunctionTypeAsync<z.core.$ZodTuple<[], null>, z.ZodString>, z.core.$InferInnerFunctionTypeAsync<z.core.$ZodTuple<[], null>, z.ZodString>>;
15
+ hooks: z.ZodOptional<z.ZodRecord<z.ZodLiteral<"remove" | "db_init" | "db_wipe" | "clean"> & z.core.$partial, z.ZodCustom<(...args: any[]) => any, (...args: any[]) => any>>>;
16
+ }, z.core.$loose>;
17
17
  declare const kSpecifier: unique symbol;
18
18
  export type Plugin = z.infer<typeof Plugin>;
19
19
  interface PluginInternal extends Plugin {
@@ -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/plugins.js CHANGED
@@ -2,7 +2,7 @@ import { zAsyncFunction } from '@axium/core/schemas';
2
2
  import * as fs from 'node:fs';
3
3
  import { resolve } from 'node:path/posix';
4
4
  import { styleText } from 'node:util';
5
- import z from 'zod';
5
+ import * as z from 'zod';
6
6
  import { output } from './io.js';
7
7
  import { _unique } from './state.js';
8
8
  export const PluginMetadata = z.looseObject({
@@ -1,6 +1,6 @@
1
1
  import { type User } from '@axium/core/user';
2
2
  import { type HttpError, type RequestEvent } from '@sveltejs/kit';
3
- import z from 'zod';
3
+ import * as z from 'zod';
4
4
  import { type SessionAndUser, type UserInternal } from './auth.js';
5
5
  export declare function parseBody<const Schema extends z.ZodType, const Result extends z.infer<Schema> = z.infer<Schema>>(event: RequestEvent, schema: Schema): Promise<Result>;
6
6
  export declare function getToken(event: RequestEvent, sensitive?: boolean): string | undefined;
package/dist/requests.js CHANGED
@@ -2,7 +2,7 @@ import { userProtectedFields, userPublicFields } from '@axium/core/user';
2
2
  import { error, json } from '@sveltejs/kit';
3
3
  import { serialize as serializeCookie } from 'cookie';
4
4
  import { pick } from 'utilium';
5
- import z from 'zod';
5
+ import * as z from 'zod';
6
6
  import { createSession, getSessionAndUser, getUser } from './auth.js';
7
7
  import { config } from './config.js';
8
8
  export async function parseBody(event, schema) {
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/dist/sveltekit.js CHANGED
@@ -2,7 +2,7 @@ import { error, json } from '@sveltejs/kit';
2
2
  import { readFileSync } from 'node:fs';
3
3
  import { styleText } from 'node:util';
4
4
  import { render } from 'svelte/server';
5
- import z from 'zod';
5
+ import * as z from 'zod';
6
6
  import { config } from './config.js';
7
7
  import { resolveRoute } from './routes.js';
8
8
  async function handleAPIRequest(event, route) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axium/server",
3
- "version": "0.16.2",
3
+ "version": "0.17.0",
4
4
  "author": "James Prevett <axium@jamespre.dev> (https://jamespre.dev)",
5
5
  "funding": {
6
6
  "type": "individual",
@@ -21,6 +21,7 @@
21
21
  "exports": {
22
22
  ".": "./dist/index.js",
23
23
  "./*": "./dist/*.js",
24
+ "./lib": "./web/lib/index.js",
24
25
  "./lib/*": "./web/lib/*",
25
26
  "./$hooks": "./web/hooks.server.ts",
26
27
  "./$routes": "./routes",
@@ -32,7 +33,6 @@
32
33
  "build",
33
34
  "dist",
34
35
  "routes",
35
- "schema.json",
36
36
  "svelte.config.js",
37
37
  "web"
38
38
  ],
package/svelte.config.js CHANGED
@@ -13,6 +13,9 @@ const fixed = p => join(import.meta.dirname, p);
13
13
  export default {
14
14
  compilerOptions: {
15
15
  runes: true,
16
+ warningFilter(w) {
17
+ if (w.code.startsWith('a11y')) return false;
18
+ },
16
19
  },
17
20
  preprocess: vitePreprocess({ script: true }),
18
21
  vitePlugin: {
@@ -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>
@@ -7,8 +7,6 @@
7
7
  }: { children(): any; toggle?(): any; id?: string } = $props();
8
8
  </script>
9
9
 
10
- <!-- svelte-ignore a11y_click_events_have_key_events -->
11
- <!-- svelte-ignore a11y_no_static_element_interactions -->
12
10
  <div onclick={e => e.stopPropagation()}>
13
11
  <button style:display="contents" popovertarget={id}>
14
12
  {#if toggle}
@@ -10,27 +10,25 @@
10
10
  </script>
11
11
 
12
12
  <div>
13
- {#if files?.length}
14
- <label for={id} class="file">
15
- {#each files as file}
16
- <Icon i={forMime(file.type)} />
17
- <span>{file.name}</span>
18
- <button
19
- onclick={e => {
20
- e.preventDefault();
21
- const dt = new DataTransfer();
22
- for (let f of files) if (file !== f) dt.items.add(f);
23
- input.files = files = dt.files;
24
- }}
25
- style:display="contents"
26
- >
27
- <Icon i="trash" />
28
- </button>
29
- {/each}
30
- </label>
31
- {:else}
32
- <label for={id}><Icon i="upload" />Upload</label>
33
- {/if}
13
+ <label for={id} class={[files?.length && 'file']}>
14
+ {#each files as file}
15
+ <Icon i={forMime(file.type)} />
16
+ <span>{file.name}</span>
17
+ <button
18
+ onclick={e => {
19
+ e.preventDefault();
20
+ const dt = new DataTransfer();
21
+ for (let f of files) if (file !== f) dt.items.add(f);
22
+ input.files = files = dt.files;
23
+ }}
24
+ style:display="contents"
25
+ >
26
+ <Icon i="trash" />
27
+ </button>
28
+ {:else}
29
+ <Icon i="upload" /> Upload
30
+ {/each}
31
+ </label>
34
32
 
35
33
  <input bind:this={input} {name} {id} type="file" bind:files {...rest} />
36
34
  </div>
@@ -13,7 +13,6 @@
13
13
  }
14
14
  </script>
15
15
 
16
- <!-- svelte-ignore a11y_no_static_element_interactions -->
17
16
  <div class="WithContextMenu" {oncontextmenu} tabindex="-1">
18
17
  {@render children()}
19
18