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.
- package/README.md +61 -4
- package/dist/index.d.ts +3 -1
- package/dist/index.js +1 -1
- package/dist/server/adapters/drizzle/dataAdapter.js +196 -42
- package/dist/server/adapters/drizzle/index.d.ts +10 -1
- package/dist/server/adapters/drizzle/index.js +4 -0
- package/dist/server/adapters/prisma/dataAdapter.js +71 -11
- package/dist/server/adapters/prisma/handler.d.ts +14 -0
- package/dist/server/adapters/prisma/handler.js +37 -0
- package/dist/server/adapters/retry.d.ts +27 -0
- package/dist/server/adapters/retry.js +53 -0
- package/dist/server/adapters/types.d.ts +42 -3
- package/dist/server/audit.d.ts +65 -0
- package/dist/server/audit.js +106 -0
- package/dist/server/csrf.d.ts +33 -0
- package/dist/server/csrf.js +55 -0
- package/dist/server/data.d.ts +18 -2
- package/dist/server/data.js +39 -8
- package/dist/server/errors.d.ts +47 -0
- package/dist/server/errors.js +90 -0
- package/dist/server/handler.d.ts +92 -26
- package/dist/server/handler.js +263 -560
- package/dist/server/introspection/parser.d.ts +15 -0
- package/dist/server/introspection/parser.js +17 -0
- package/dist/server/mutations.d.ts +13 -0
- package/dist/server/mutations.js +476 -0
- package/dist/server/plugin.d.ts +47 -0
- package/dist/server/plugin.js +1 -0
- package/dist/server/pluginAccess.d.ts +7 -0
- package/dist/server/pluginAccess.js +79 -0
- package/dist/server/pluginRegistry.d.ts +12 -0
- package/dist/server/pluginRegistry.js +72 -0
- package/dist/server/query/listColumns.d.ts +19 -0
- package/dist/server/query/listColumns.js +43 -0
- package/dist/server/query/listQuery.d.ts +1 -1
- package/dist/server/query/pageSize.d.ts +19 -0
- package/dist/server/query/pageSize.js +26 -0
- package/dist/server/query/sortQuery.d.ts +31 -0
- package/dist/server/query/sortQuery.js +33 -0
- package/dist/server/query/urls.js +9 -1
- package/dist/server/relationLoaders.d.ts +42 -0
- package/dist/server/relationLoaders.js +188 -0
- package/dist/server/router.d.ts +10 -0
- package/dist/server/router.js +42 -19
- package/dist/server/runtime.d.ts +51 -0
- package/dist/server/runtime.js +263 -0
- package/dist/server/search.d.ts +14 -0
- package/dist/server/search.js +78 -0
- package/dist/server/submitted.d.ts +20 -0
- package/dist/server/submitted.js +54 -0
- package/dist/server/views/FieldInput.svelte +114 -8
- package/dist/server/views/FieldInput.svelte.d.ts +7 -0
- package/dist/server/views/Form.svelte +96 -5
- package/dist/server/views/Form.svelte.d.ts +19 -1
- package/dist/server/views/Layout.svelte +32 -4
- package/dist/server/views/Layout.svelte.d.ts +2 -0
- package/dist/server/views/List.svelte +156 -26
- package/dist/server/views/List.svelte.d.ts +7 -2
- package/dist/server/views/RelationCheckboxes.svelte +26 -5
- package/dist/server/views/RelationCheckboxes.svelte.d.ts +9 -0
- package/dist/server/views/RelationSelect.svelte +14 -3
- package/dist/server/views/RelationSelect.svelte.d.ts +2 -0
- package/dist/server/views/html.d.ts +19 -0
- package/dist/server/views/html.js +25 -0
- package/dist/server/views/pagination.d.ts +9 -0
- package/dist/server/views/pagination.js +31 -0
- package/dist/server/views/theme.js +147 -3
- package/dist/server/views/types.d.ts +15 -0
- 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
|
+
}
|
package/dist/server/handler.d.ts
CHANGED
|
@@ -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
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
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
|
-
|
|
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;
|