sveltekit-admin 0.6.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/README.md +61 -4
  2. package/dist/index.d.ts +3 -1
  3. package/dist/index.js +1 -1
  4. package/dist/server/adapters/drizzle/dataAdapter.js +196 -42
  5. package/dist/server/adapters/drizzle/index.d.ts +10 -1
  6. package/dist/server/adapters/drizzle/index.js +4 -0
  7. package/dist/server/adapters/prisma/dataAdapter.js +71 -11
  8. package/dist/server/adapters/prisma/handler.d.ts +14 -0
  9. package/dist/server/adapters/prisma/handler.js +37 -0
  10. package/dist/server/adapters/retry.d.ts +27 -0
  11. package/dist/server/adapters/retry.js +53 -0
  12. package/dist/server/adapters/types.d.ts +42 -3
  13. package/dist/server/audit.d.ts +65 -0
  14. package/dist/server/audit.js +106 -0
  15. package/dist/server/csrf.d.ts +33 -0
  16. package/dist/server/csrf.js +55 -0
  17. package/dist/server/data.d.ts +18 -2
  18. package/dist/server/data.js +39 -8
  19. package/dist/server/errors.d.ts +47 -0
  20. package/dist/server/errors.js +90 -0
  21. package/dist/server/handler.d.ts +92 -26
  22. package/dist/server/handler.js +263 -560
  23. package/dist/server/introspection/parser.d.ts +15 -0
  24. package/dist/server/introspection/parser.js +17 -0
  25. package/dist/server/mutations.d.ts +13 -0
  26. package/dist/server/mutations.js +476 -0
  27. package/dist/server/plugin.d.ts +47 -0
  28. package/dist/server/plugin.js +1 -0
  29. package/dist/server/pluginAccess.d.ts +7 -0
  30. package/dist/server/pluginAccess.js +79 -0
  31. package/dist/server/pluginRegistry.d.ts +12 -0
  32. package/dist/server/pluginRegistry.js +72 -0
  33. package/dist/server/query/listColumns.d.ts +19 -0
  34. package/dist/server/query/listColumns.js +43 -0
  35. package/dist/server/query/listQuery.d.ts +1 -1
  36. package/dist/server/query/pageSize.d.ts +19 -0
  37. package/dist/server/query/pageSize.js +26 -0
  38. package/dist/server/query/sortQuery.d.ts +31 -0
  39. package/dist/server/query/sortQuery.js +33 -0
  40. package/dist/server/query/urls.js +9 -1
  41. package/dist/server/relationLoaders.d.ts +42 -0
  42. package/dist/server/relationLoaders.js +188 -0
  43. package/dist/server/router.d.ts +10 -0
  44. package/dist/server/router.js +42 -19
  45. package/dist/server/runtime.d.ts +51 -0
  46. package/dist/server/runtime.js +263 -0
  47. package/dist/server/search.d.ts +14 -0
  48. package/dist/server/search.js +78 -0
  49. package/dist/server/submitted.d.ts +20 -0
  50. package/dist/server/submitted.js +54 -0
  51. package/dist/server/views/FieldInput.svelte +114 -8
  52. package/dist/server/views/FieldInput.svelte.d.ts +7 -0
  53. package/dist/server/views/Form.svelte +96 -5
  54. package/dist/server/views/Form.svelte.d.ts +19 -1
  55. package/dist/server/views/Layout.svelte +32 -4
  56. package/dist/server/views/Layout.svelte.d.ts +2 -0
  57. package/dist/server/views/List.svelte +156 -26
  58. package/dist/server/views/List.svelte.d.ts +7 -2
  59. package/dist/server/views/RelationCheckboxes.svelte +26 -5
  60. package/dist/server/views/RelationCheckboxes.svelte.d.ts +9 -0
  61. package/dist/server/views/RelationSelect.svelte +14 -3
  62. package/dist/server/views/RelationSelect.svelte.d.ts +2 -0
  63. package/dist/server/views/html.d.ts +19 -0
  64. package/dist/server/views/html.js +25 -0
  65. package/dist/server/views/pagination.d.ts +9 -0
  66. package/dist/server/views/pagination.js +31 -0
  67. package/dist/server/views/theme.js +147 -3
  68. package/dist/server/views/types.d.ts +15 -0
  69. package/package.json +24 -21
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Forme unique des échecs de mutation admin.
3
+ *
4
+ * Deux producteurs, un seul type : `mutations.ts` pour les refus que la
5
+ * bibliothèque décide elle-même (validation, scope), `classifyWriteError`
6
+ * pour ceux que le moteur signale. Un seul consommateur : le site d'appel
7
+ * de `handleMutation` dans `handler.ts`, qui ne rend QUE le message d'une
8
+ * `AdminMutationError` — jamais celui d'une erreur pilote brute.
9
+ *
10
+ * La classification se fait par code, jamais par texte : les messages des
11
+ * pilotes changent entre versions, les codes non.
12
+ */
13
+ export class AdminMutationError extends Error {
14
+ kind;
15
+ field;
16
+ constructor(kind, message, field) {
17
+ super(message);
18
+ this.name = 'AdminMutationError';
19
+ this.kind = kind;
20
+ this.field = field;
21
+ }
22
+ }
23
+ /**
24
+ * Erreur de configuration de la bibliothèque elle-même (scope non injectable,
25
+ * tenant absent…), destinée au développeur intégrateur et non à l'utilisateur
26
+ * de l'admin. Distincte d'`AdminMutationError` : elle ne décrit pas un refus
27
+ * de la donnée soumise, mais un montage incorrect côté consommateur, et c'est
28
+ * la SEULE erreur non typée que le chemin de mutation relaie telle quelle.
29
+ */
30
+ export class AdminConfigError extends Error {
31
+ constructor(message) {
32
+ super(message);
33
+ this.name = 'AdminConfigError';
34
+ }
35
+ }
36
+ /**
37
+ * Les pilotes exposent le code SQLSTATE à des endroits différents : `code` sur
38
+ * `pg`, `mysql2` et `better-sqlite3`, `meta.code` sur une
39
+ * `PrismaClientKnownRequestError` issue d'une transaction interactive.
40
+ *
41
+ * Vit ici plutôt que dans `retry.ts` : deux modules classent désormais les
42
+ * erreurs pilote, et un second exemplaire de ce helper dériverait du premier.
43
+ */
44
+ export function codeOf(error) {
45
+ const candidate = error;
46
+ const raw = candidate?.code ?? candidate?.meta?.code;
47
+ return typeof raw === 'string' ? raw : undefined;
48
+ }
49
+ const UNIQUE_CODES = new Set([
50
+ 'P2002', // Prisma
51
+ '23505', // PostgreSQL — unique_violation
52
+ 'ER_DUP_ENTRY', // MySQL 1062
53
+ 'SQLITE_CONSTRAINT_UNIQUE'
54
+ ]);
55
+ const FOREIGN_KEY_CODES = new Set([
56
+ 'P2003', // Prisma
57
+ '23503', // PostgreSQL — foreign_key_violation
58
+ 'ER_NO_REFERENCED_ROW_2', // MySQL 1452 — la cible soumise n'existe pas
59
+ 'ER_ROW_IS_REFERENCED_2', // MySQL 1451 — la ligne est référencée ailleurs
60
+ 'SQLITE_CONSTRAINT_FOREIGNKEY'
61
+ ]);
62
+ const NOT_FOUND_CODES = new Set(['P2025']);
63
+ /**
64
+ * Traduit un échec d'écriture en `AdminMutationError`, ou `null` si le code
65
+ * n'est pas reconnu — l'appelant rend alors un message générique.
66
+ *
67
+ * `reference` et `restrict` partagent le même code SQLSTATE (PostgreSQL 23503,
68
+ * SQLite SQLITE_CONSTRAINT_FOREIGNKEY) : c'est l'action en cours qui les
69
+ * sépare, pas le message. Sur create/update une cible soumise est invalide ;
70
+ * sur delete la ligne est référencée ailleurs.
71
+ */
72
+ export function classifyWriteError(error, action) {
73
+ if (error instanceof AdminMutationError)
74
+ return error;
75
+ const code = codeOf(error);
76
+ if (code === undefined)
77
+ return null;
78
+ if (UNIQUE_CODES.has(code)) {
79
+ return new AdminMutationError('conflict', 'A record with these values already exists.');
80
+ }
81
+ if (FOREIGN_KEY_CODES.has(code)) {
82
+ return action === 'delete'
83
+ ? new AdminMutationError('restrict', 'This record is referenced by other records.')
84
+ : new AdminMutationError('reference', 'A referenced record no longer exists.');
85
+ }
86
+ if (NOT_FOUND_CODES.has(code)) {
87
+ return new AdminMutationError('notFound', 'This record no longer exists.');
88
+ }
89
+ return null;
90
+ }
@@ -3,23 +3,17 @@
3
3
  * Zero files needed in routes - everything handled via hook
4
4
  */
5
5
  import type { DataAdapter, SchemaIntrospector } from './adapters/types.js';
6
+ import type { AuditEvent } from './audit.js';
7
+ import { type CsrfConfig } from './csrf.js';
8
+ import type { AdminPlugin } from './plugin.js';
6
9
  export interface AdminHandlerConfig {
7
10
  /**
8
- * Prisma client instance. Required unless `adapter` is provided directly —
9
- * exactly one of the two must be set. Kept required-looking here (not `?`)
10
- * for source compatibility with every existing call site; passing neither
11
- * throws at handler-creation time (see the boot block).
11
+ * Explicit `{ introspector, data }` pair, from `createPrismaAdapter`,
12
+ * `createDrizzleAdapter`, or a custom implementation. The `{ prisma,
13
+ * prismaSchemaPath }` shortcut lives on the Prisma wrapper exported by
14
+ * the package root, not here.
12
15
  */
13
- prisma?: any;
14
- /** Path to Prisma schema file */
15
- prismaSchemaPath?: string;
16
- /**
17
- * Explicit adapter, built via `createPrismaAdapter(...)` (or, in a future
18
- * release, a Drizzle/other adapter). Takes priority over `prisma`/
19
- * `prismaSchemaPath` when both are somehow set. Most consumers never touch
20
- * this — passing `prisma`/`prismaSchemaPath` builds one internally.
21
- */
22
- adapter?: {
16
+ adapter: {
23
17
  introspector: SchemaIntrospector;
24
18
  data: DataAdapter;
25
19
  };
@@ -48,12 +42,50 @@ export interface AdminHandlerConfig {
48
42
  logout?: (event: any) => void | Promise<void>;
49
43
  /** Where to redirect after logout (default: '/') */
50
44
  logoutRedirectTo?: string;
45
+ /**
46
+ * Cross-site protection for every state-changing admin request (create /
47
+ * update / delete, `_logout`, `_search`). On by default; a missing `Origin`
48
+ * is rejected, as SvelteKit does. `trustedOrigins` allows a second
49
+ * legitimate origin, `csrf: false` opts out entirely.
50
+ *
51
+ * Why this isn't left to `kit.csrf.checkOrigin`, and the same-origin threat
52
+ * it does not cover: see `csrf.ts` and /docs/csrf.
53
+ */
54
+ csrf?: CsrfConfig;
55
+ /**
56
+ * Audit sink — same "bring your own" philosophy as `authCheck` / `logout`.
57
+ * The library has no log table and no session of its own, so it cannot
58
+ * know where to persist "admin X changed row Y" (your `AuditLog` model,
59
+ * a logger, an HTTP sink…). You provide the side effect; the handler
60
+ * calls it **after a successful create / update / delete** with a
61
+ * redacted `AuditEvent`. No callback means no behaviour change: no
62
+ * extra reads, no calls.
63
+ *
64
+ * The actor is whatever you already put on `event.locals` (the same
65
+ * object `authCheck` sees). Sensitive field names (`password` / `hash` /
66
+ * `secret` / `token`) and per-model `hidden` fields are stripped from
67
+ * `values` / `before` / `after` / `changes` so the sink cannot become a
68
+ * second oracle for secrets. Reads (GET), logout, and `_search` are
69
+ * not audited.
70
+ *
71
+ * Awaited before the 303 so a `prisma.auditLog.create(...)` inside the
72
+ * callback commits before the redirect. If the callback throws, the
73
+ * mutation still redirects — the write is the source of truth, the log
74
+ * is a sidecar (`console.error` with prefix
75
+ * `[sveltekit-admin] audit callback failed:`). There is no way to wrap
76
+ * the adapter write and your sink in one transaction without owning
77
+ * both stores.
78
+ */
79
+ audit?: (entry: AuditEvent) => void | Promise<void>;
51
80
  /** Per-model configuration */
52
81
  models?: Record<string, {
53
82
  hidden?: string[];
54
83
  readonly?: string[];
55
84
  listFields?: string[];
56
85
  label?: string;
86
+ scope?: (ctx: {
87
+ locals?: any;
88
+ }) => Record<string, unknown> | import('./adapters/types.js').Filter;
57
89
  /**
58
90
  * Scoping `where` applied to the LIST VIEW ONLY of this model
59
91
  * (search, sidebar filters, FK filter, pagination count) — composed
@@ -121,7 +153,42 @@ export interface AdminHandlerConfig {
121
153
  * doit échouer fort plutôt que produire un filtre silencieusement mort.
122
154
  */
123
155
  listFilter?: import('./query/filterDetection.js').ListFilterConfigEntry[];
156
+ /**
157
+ * Ordre d'arrivée sur la liste, avant tout `?sort=` dans l'URL. `dir` vaut
158
+ * `'asc'` par défaut.
159
+ *
160
+ * Volontairement explicite plutôt qu'auto-détecté : deviner « trie par
161
+ * `name` s'il y en a un » changerait l'ordre de toutes les listes
162
+ * existantes sans que personne l'ait demandé, et l'heuristique dériverait
163
+ * de ce que la vue affiche réellement.
164
+ *
165
+ * `field` doit être une colonne que la liste AFFICHE — sinon aucun en-tête
166
+ * ne peut annoncer le tri ni permettre d'en sortir. Validé au démarrage :
167
+ * une colonne inexistante ou non affichée lève, plutôt que de produire un
168
+ * tri mort à chaque rendu.
169
+ */
170
+ defaultSort?: {
171
+ field: string;
172
+ dir?: 'asc' | 'desc';
173
+ };
124
174
  }>;
175
+ /**
176
+ * Lignes par page de la vue liste (défaut : 20). Entier de 1 à 200 : au-delà
177
+ * ce n'est plus une page, c'est un export — et une requête qui tient la
178
+ * connexion sur une table volumineuse. Validé au démarrage.
179
+ */
180
+ perPage?: number;
181
+ /**
182
+ * Tailles de page qu'un visiteur peut choisir (défaut : `[10, 20, 50, 100]`).
183
+ * `perPage` y est ajouté d'office s'il n'y figure pas, sinon la taille active
184
+ * n'apparaîtrait pas dans le sélecteur.
185
+ *
186
+ * Un `?perPage=` n'est honoré que s'il appartient à cette liste : sans cette
187
+ * règle, `?perPage=100000` est un `take` non borné, donc un déni de service à
188
+ * un paramètre près. `[]` désactive entièrement le mécanisme — aucun
189
+ * sélecteur rendu, `?perPage=` sans effet.
190
+ */
191
+ pageSizeOptions?: number[];
125
192
  /** Models to exclude from admin */
126
193
  exclude?: string[];
127
194
  /** Hide pivot/junction tables automatically (default: true) */
@@ -145,23 +212,22 @@ export interface AdminHandlerConfig {
145
212
  linkThreshold?: number;
146
213
  autoDetect?: boolean;
147
214
  };
148
- /**
149
- * Recherche texte libre : configuration globale.
150
- * `mode`: 'auto' détecte le provider du schéma et n'émet `mode: 'insensitive'`
151
- * que sur postgresql/cockroachdb/mongodb (les seuls où Prisma le supporte —
152
- * l'émettre sur sqlite/mysql/sqlserver lève une erreur Prisma). 'insensitive'
153
- * et 'default' forcent le comportement, pour un provider non détectable
154
- * (`provider = env(...)`) ou un besoin spécifique (index `citext`, etc.).
155
- * Voir docs/design/list-search-filters.md §2.5.
156
- */
157
- search?: {
158
- mode?: 'auto' | 'insensitive' | 'default';
159
- };
160
215
  /** Custom branding */
161
216
  branding?: {
162
217
  title?: string;
163
218
  primaryColor?: string;
164
219
  };
220
+ /**
221
+ * Optional admin plugins (new pages + record actions). Omitted or `[]`
222
+ * keeps every builtin view byte-identical to a build without plugins.
223
+ * Plugin routes are matched before builtins, so a registered pattern
224
+ * with a literal token in a `:model`/`:id` position can take over a
225
+ * builtin path when it matches first (e.g. `['user']` shadows the User
226
+ * list); only an identical, token-for-token overlay throws at boot.
227
+ * See `AdminPlugin`. Options like graph `depth` belong on the author's
228
+ * factory, not here.
229
+ */
230
+ plugins?: AdminPlugin[];
165
231
  }
166
232
  export declare function createAdminHandler(config: AdminHandlerConfig): ({ event, resolve }: {
167
233
  event: any;