sveltekit-admin 0.6.0 → 0.8.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/README.md +61 -4
- package/dist/index.d.ts +3 -1
- package/dist/index.js +1 -1
- package/dist/server/adapters/drizzle/dataAdapter.js +121 -36
- 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 +52 -10
- 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 +9 -2
- 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/errors.d.ts +47 -0
- package/dist/server/errors.js +90 -0
- package/dist/server/handler.d.ts +57 -26
- package/dist/server/handler.js +212 -561
- package/dist/server/mutations.d.ts +9 -0
- package/dist/server/mutations.js +295 -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/listQuery.d.ts +1 -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 +46 -0
- package/dist/server/runtime.js +210 -0
- package/dist/server/search.d.ts +14 -0
- package/dist/server/search.js +78 -0
- package/dist/server/views/Form.svelte +26 -2
- package/dist/server/views/Form.svelte.d.ts +2 -1
- package/dist/server/views/Layout.svelte +27 -2
- package/dist/server/views/Layout.svelte.d.ts +2 -0
- package/dist/server/views/List.svelte +28 -3
- package/dist/server/views/List.svelte.d.ts +2 -2
- package/dist/server/views/types.d.ts +8 -0
- package/package.json +23 -21
|
@@ -1,23 +1,26 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { AdminHandlerConfig } from '../handler.js';
|
|
3
|
-
import type { ViewModel } from './types.js';
|
|
3
|
+
import type { RecordAction, ViewModel } from './types.js';
|
|
4
4
|
import FieldInput from './FieldInput.svelte';
|
|
5
5
|
import RelationSelect from './RelationSelect.svelte';
|
|
6
6
|
import RelationCheckboxes from './RelationCheckboxes.svelte';
|
|
7
7
|
import RelatedBlock from './RelatedBlock.svelte';
|
|
8
|
+
import { escapeHtml } from './html.js';
|
|
8
9
|
|
|
9
10
|
let {
|
|
10
11
|
mode,
|
|
11
12
|
model,
|
|
12
13
|
basePath,
|
|
13
14
|
config,
|
|
14
|
-
item
|
|
15
|
+
item,
|
|
16
|
+
recordActions = []
|
|
15
17
|
}: {
|
|
16
18
|
mode: 'create' | 'edit';
|
|
17
19
|
model: ViewModel;
|
|
18
20
|
basePath: string;
|
|
19
21
|
config: AdminHandlerConfig;
|
|
20
22
|
item?: any;
|
|
23
|
+
recordActions?: RecordAction[];
|
|
21
24
|
} = $props();
|
|
22
25
|
|
|
23
26
|
const modelConfig = $derived(config.models?.[model.name] || {});
|
|
@@ -77,6 +80,24 @@
|
|
|
77
80
|
: []
|
|
78
81
|
);
|
|
79
82
|
|
|
83
|
+
// Built as a single string (rather than {#if}/{#each}) so an empty/create-mode
|
|
84
|
+
// render stays a single @html call: Svelte 5's SSR wraps every {#if}/{#each} node
|
|
85
|
+
// in its own hydration-boundary comment regardless of the branch/array taken, so
|
|
86
|
+
// nesting recordActions in its own control-flow blocks would add bytes to every
|
|
87
|
+
// edit-form render even when recordActions is []. `label` and `href` are both
|
|
88
|
+
// escaped manually since this goes through @html instead of Svelte's
|
|
89
|
+
// auto-escaped text/attributes.
|
|
90
|
+
const recordActionsHtml = $derived(
|
|
91
|
+
mode === 'edit' && recordActions.length > 0
|
|
92
|
+
? `<div class="ska-record-actions">${recordActions
|
|
93
|
+
.map(
|
|
94
|
+
(action) =>
|
|
95
|
+
`<a href="${escapeHtml(action.href)}" class="ska-btn ska-btn--secondary ska-btn--sm">${escapeHtml(action.label)}</a>`
|
|
96
|
+
)
|
|
97
|
+
.join('')}</div>`
|
|
98
|
+
: ''
|
|
99
|
+
);
|
|
100
|
+
|
|
80
101
|
const inverseEdges = $derived(
|
|
81
102
|
model.relationGraph
|
|
82
103
|
? [...model.relationGraph.edges.values()].filter(
|
|
@@ -92,6 +113,9 @@
|
|
|
92
113
|
<p class="ska-subtitle">ID: {item[model.primaryKey]}</p>
|
|
93
114
|
{/if}
|
|
94
115
|
|
|
116
|
+
<!-- eslint-disable-next-line svelte/no-at-html-tags -- recordActionsHtml escapes both action.label and action.href via escapeHtml -->
|
|
117
|
+
{@html recordActionsHtml}
|
|
118
|
+
|
|
95
119
|
<div class="ska-card">
|
|
96
120
|
<form method="POST" class="ska-form">
|
|
97
121
|
<input type="hidden" name="_action" value={mode === 'create' ? 'create' : 'update'} />
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import type { AdminHandlerConfig } from '../handler.js';
|
|
2
|
-
import type { ViewModel } from './types.js';
|
|
2
|
+
import type { RecordAction, ViewModel } from './types.js';
|
|
3
3
|
type $$ComponentProps = {
|
|
4
4
|
mode: 'create' | 'edit';
|
|
5
5
|
model: ViewModel;
|
|
6
6
|
basePath: string;
|
|
7
7
|
config: AdminHandlerConfig;
|
|
8
8
|
item?: any;
|
|
9
|
+
recordActions?: RecordAction[];
|
|
9
10
|
};
|
|
10
11
|
declare const Form: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
11
12
|
type Form = ReturnType<typeof Form>;
|
|
@@ -6,12 +6,16 @@
|
|
|
6
6
|
content,
|
|
7
7
|
config,
|
|
8
8
|
modelList,
|
|
9
|
-
currentModel
|
|
9
|
+
currentModel,
|
|
10
|
+
extraStyles = '',
|
|
11
|
+
extraScripts = ''
|
|
10
12
|
}: {
|
|
11
13
|
content: string;
|
|
12
14
|
config: AdminHandlerConfig;
|
|
13
15
|
modelList: Array<{ name: string; label: string }>;
|
|
14
16
|
currentModel?: string;
|
|
17
|
+
extraStyles?: string;
|
|
18
|
+
extraScripts?: string;
|
|
15
19
|
} = $props();
|
|
16
20
|
|
|
17
21
|
const branding = $derived(config.branding ?? {});
|
|
@@ -21,6 +25,25 @@
|
|
|
21
25
|
// No button at all if `logout` isn't configured — an admin that never
|
|
22
26
|
// opted into this option looks exactly as it did before it existed.
|
|
23
27
|
const showLogout = $derived(Boolean(config.logout));
|
|
28
|
+
|
|
29
|
+
// extraStyles is concatenated into the SAME @html expression as the theme <style>
|
|
30
|
+
// below, rather than a sibling {#if}/{@html} block: Svelte 5's SSR unconditionally
|
|
31
|
+
// wraps every {#if}/{#each}/{@html} node in its own hydration-boundary comment, even
|
|
32
|
+
// for a false/empty branch (see svelte/internal/server's `html()` helper) — a sibling
|
|
33
|
+
// block would add bytes to every render regardless of extraStyles being set.
|
|
34
|
+
// Concatenating keeps this ONE @html call, byte-identical to the pre-plugin-slots
|
|
35
|
+
// template when extraStyles is ''. extraScripts (bottom of <body>) has no such
|
|
36
|
+
// pre-existing @html call to fold into, so it stays its own @html — the smallest
|
|
37
|
+
// achievable footprint, though it still adds a fixed hydration-boundary comment
|
|
38
|
+
// pair even when empty (see task-6-report.md fix-round-1 notes).
|
|
39
|
+
//
|
|
40
|
+
// Built as a $derived here (rather than a nested template literal inline in the
|
|
41
|
+
// markup below) so tooling that tag-sniffs {@html} expressions for literal
|
|
42
|
+
// <style>/<script> text doesn't misparse the nested backticks.
|
|
43
|
+
const headStyleHtml = $derived(
|
|
44
|
+
`<style>${styles(primaryColor)}</style>${extraStyles ? `<style>${extraStyles}</style>` : ''}`
|
|
45
|
+
);
|
|
46
|
+
const bodyScriptHtml = $derived(extraScripts ? '<script>' + extraScripts + '</scr' + 'ipt>' : '');
|
|
24
47
|
</script>
|
|
25
48
|
|
|
26
49
|
<!doctype html>
|
|
@@ -31,7 +54,7 @@
|
|
|
31
54
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
32
55
|
<title>{title}</title>
|
|
33
56
|
<!-- eslint-disable-next-line svelte/no-at-html-tags -- CSS injected as raw text; a literal <style> block can't take a dynamic value; primaryColor is developer-supplied config, not request/database data, and this raw interpolation is unchanged from the original layout.ts implementation, not a new injection point introduced by this migration -->
|
|
34
|
-
{@html
|
|
57
|
+
{@html headStyleHtml}
|
|
35
58
|
</head>
|
|
36
59
|
<!-- eslint-disable-next-line svelte/no-raw-special-elements -- server-only full-document template, never mounted client-side -->
|
|
37
60
|
<body>
|
|
@@ -74,5 +97,7 @@
|
|
|
74
97
|
{@html content}
|
|
75
98
|
</main>
|
|
76
99
|
</div>
|
|
100
|
+
<!-- eslint-disable-next-line svelte/no-at-html-tags -- plugin JS is developer-supplied, same trust as branding.primaryColor -->
|
|
101
|
+
{@html bodyScriptHtml}
|
|
77
102
|
</body>
|
|
78
103
|
</html>
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { AdminHandlerConfig } from '../handler.js';
|
|
3
|
-
import type { ViewModel } from './types.js';
|
|
3
|
+
import type { ViewModel, ListRecordAction, FkFilterMeta } from './types.js';
|
|
4
4
|
import type { ListQuery } from '../query/listQuery.js';
|
|
5
5
|
import type { ResolvedFilterField } from '../query/filterDetection.js';
|
|
6
6
|
import { DATETIME_PRESETS } from '../query/filterDetection.js';
|
|
7
|
-
import type { FkFilterMeta } from './types.js';
|
|
8
7
|
import { getDisplayFields } from '../introspection/parser.js';
|
|
9
8
|
import { buildListUrl, hiddenParams } from '../query/urls.js';
|
|
10
9
|
import { escapeHtml, toLabel, formatValue } from './html.js';
|
|
@@ -19,7 +18,8 @@
|
|
|
19
18
|
query,
|
|
20
19
|
currentUrl,
|
|
21
20
|
listFilters,
|
|
22
|
-
fkFilterMeta
|
|
21
|
+
fkFilterMeta,
|
|
22
|
+
recordActions = []
|
|
23
23
|
}: {
|
|
24
24
|
model: ViewModel;
|
|
25
25
|
items: any[];
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
listFilters?: ResolvedFilterField[];
|
|
35
35
|
/** Métadonnées async (options scopées + label actif) pour les filtres FK configurés. */
|
|
36
36
|
fkFilterMeta?: Map<string, FkFilterMeta>;
|
|
37
|
+
recordActions?: ListRecordAction[];
|
|
37
38
|
} = $props();
|
|
38
39
|
|
|
39
40
|
const modelConfig = $derived(config.models?.[model.name] || {});
|
|
@@ -126,6 +127,28 @@
|
|
|
126
127
|
* pour un champ sensible que pour un champ inconnu (§0.a, §5.4) — ne
|
|
127
128
|
* jamais dire "champ interdit", ça confirmerait son existence.
|
|
128
129
|
*/
|
|
130
|
+
// A per-row function (rather than {#each} in the markup) so an empty recordActions
|
|
131
|
+
// keeps this to the smallest possible footprint: Svelte 5's SSR wraps every
|
|
132
|
+
// {#each}/{@html} node in its own hydration-boundary comment regardless of the
|
|
133
|
+
// array's length/content (verified empirically — even {@html ''} still emits
|
|
134
|
+
// `<!--hash--><!---->`), so there is no template-level construct that renders zero
|
|
135
|
+
// bytes for an empty array here. Folding this into the pre-existing delete-form
|
|
136
|
+
// {@html} call (right below) was considered and rejected: recordActions must render
|
|
137
|
+
// *before* Edit (see list.test.ts "rend le lien avant Edit"), but the delete form's
|
|
138
|
+
// pre-existing {@html} — and thus its hydration marker — sits *after* Edit, so
|
|
139
|
+
// reusing it would either reorder Edit/recordActions or move the marker in front of
|
|
140
|
+
// Edit for every row, not just when recordActions is non-empty. Neither is
|
|
141
|
+
// byte-identical to the pre-recordActions baseline (see task-6-report.md fix-round-1
|
|
142
|
+
// notes). `action.label` and `hrefFor`'s return value are both escaped manually
|
|
143
|
+
// since this goes through @html instead of Svelte's auto-escaped text/attributes.
|
|
144
|
+
const recordActionsHtml = (id: string | number) =>
|
|
145
|
+
recordActions
|
|
146
|
+
.map(
|
|
147
|
+
(action) =>
|
|
148
|
+
`<a href="${escapeHtml(action.hrefFor(id))}" class="ska-btn ska-btn--secondary ska-btn--sm">${escapeHtml(action.label)}</a>`
|
|
149
|
+
)
|
|
150
|
+
.join('');
|
|
151
|
+
|
|
129
152
|
const ignoredMessages = $derived.by(() => {
|
|
130
153
|
return (query?.ignored ?? []).map((entry) => {
|
|
131
154
|
// `param` est soit `f.<field>` / `f.<field>__<op>` (nouveau format),
|
|
@@ -217,6 +240,8 @@
|
|
|
217
240
|
<!-- eslint-disable-next-line svelte/no-at-html-tags -- formatValue already escapes string values itself and returns a literal <span> only for null/undefined -->
|
|
218
241
|
{#each displayFields as f (f.name)}<td>{@html formatValue(item[f.name], f.type)}</td>{/each}
|
|
219
242
|
<td class="ska-table__actions">
|
|
243
|
+
<!-- eslint-disable-next-line svelte/no-at-html-tags -- recordActionsHtml escapes both action.label and hrefFor's return value via escapeHtml -->
|
|
244
|
+
{@html recordActionsHtml(item[model.primaryKey])}
|
|
220
245
|
<a href="{listPath}/{item[model.primaryKey]}" class="ska-btn ska-btn--secondary ska-btn--sm">Edit</a>
|
|
221
246
|
<!-- eslint-disable-next-line svelte/no-at-html-tags -- Svelte 5 rejects a literal onsubmit string as an event attribute; the PK is escaped manually here since it can't go through Svelte's native attribute escaping; the whole form (not just onsubmit) is rendered as raw HTML because there's no native-Svelte way to attach a plain inline onsubmit="..." string attribute at all in Svelte 5 templates, so the whole element had to be raw text to preserve the exact prior confirm-dialog behavior in a page that's never hydrated by a Svelte runtime -->
|
|
222
247
|
{@html `<form method="POST" action="${listPath}/${escapeHtml(String(item[model.primaryKey]))}" style="display:inline" onsubmit="return confirm('Delete this item?')"><input type="hidden" name="_action" value="delete"><button type="submit" class="ska-btn ska-btn--danger ska-btn--sm">Delete</button></form>`}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import type { AdminHandlerConfig } from '../handler.js';
|
|
2
|
-
import type { ViewModel } from './types.js';
|
|
2
|
+
import type { ViewModel, ListRecordAction, FkFilterMeta } from './types.js';
|
|
3
3
|
import type { ListQuery } from '../query/listQuery.js';
|
|
4
4
|
import type { ResolvedFilterField } from '../query/filterDetection.js';
|
|
5
|
-
import type { FkFilterMeta } from './types.js';
|
|
6
5
|
type $$ComponentProps = {
|
|
7
6
|
model: ViewModel;
|
|
8
7
|
items: any[];
|
|
@@ -21,6 +20,7 @@ type $$ComponentProps = {
|
|
|
21
20
|
listFilters?: ResolvedFilterField[];
|
|
22
21
|
/** Métadonnées async (options scopées + label actif) pour les filtres FK configurés. */
|
|
23
22
|
fkFilterMeta?: Map<string, FkFilterMeta>;
|
|
23
|
+
recordActions?: ListRecordAction[];
|
|
24
24
|
};
|
|
25
25
|
declare const List: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
26
26
|
type List = ReturnType<typeof List>;
|
|
@@ -22,6 +22,14 @@ export interface ViewModel {
|
|
|
22
22
|
/** Compteurs des relations inverses (1-N, 1-1), indexés par "Model.field" */
|
|
23
23
|
relatedCounts?: Map<string, number>;
|
|
24
24
|
}
|
|
25
|
+
export interface RecordAction {
|
|
26
|
+
label: string;
|
|
27
|
+
href: string;
|
|
28
|
+
}
|
|
29
|
+
export interface ListRecordAction {
|
|
30
|
+
label: string;
|
|
31
|
+
hrefFor: (id: string | number) => string;
|
|
32
|
+
}
|
|
25
33
|
/**
|
|
26
34
|
* Résolution async d'un filtre FK (kind 'fk' dans ResolvedFilterField) :
|
|
27
35
|
* options scopées pour la sidebar + label du chip actif scopé lui aussi
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sveltekit-admin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"description": "Django-like admin panel for SvelteKit + Prisma",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"svelte": "./dist/index.js",
|
|
@@ -22,23 +22,8 @@
|
|
|
22
22
|
"!dist/**/*.test.*",
|
|
23
23
|
"!dist/**/*.spec.*"
|
|
24
24
|
],
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"build": "npm run package",
|
|
28
|
-
"package": "svelte-kit sync && svelte-package -o dist && echo 'Package size:' && bun pm pack --dry-run 2>&1 | grep -E 'Total files|Unpacked size'",
|
|
29
|
-
"prepublishOnly": "npm run package",
|
|
30
|
-
"size": "bun run package && node scripts/package-size.mjs",
|
|
31
|
-
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
|
32
|
-
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
|
33
|
-
"test:gen": "prisma generate --schema tests/fixtures/prisma/schema.prisma",
|
|
34
|
-
"test": "svelte-kit sync && npm run test:gen && vitest run",
|
|
35
|
-
"test:watch": "svelte-kit sync && npm run test:gen && vitest",
|
|
36
|
-
"test:coverage": "svelte-kit sync && npm run test:gen && vitest run --coverage",
|
|
37
|
-
"lint": "svelte-kit sync && eslint .",
|
|
38
|
-
"format": "prettier --write .",
|
|
39
|
-
"changeset": "changeset",
|
|
40
|
-
"version-packages": "changeset version",
|
|
41
|
-
"release": "npm run package && changeset publish"
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"provenance": true
|
|
42
27
|
},
|
|
43
28
|
"peerDependencies": {
|
|
44
29
|
"@prisma/client": ">=5.0.0",
|
|
@@ -55,7 +40,7 @@
|
|
|
55
40
|
}
|
|
56
41
|
},
|
|
57
42
|
"devDependencies": {
|
|
58
|
-
"@changesets/cli": "^3.0.
|
|
43
|
+
"@changesets/cli": "^3.0.1",
|
|
59
44
|
"@eslint/js": "9",
|
|
60
45
|
"@prisma/client": "^6.19.3",
|
|
61
46
|
"@sveltejs/adapter-auto": "^7.0.1",
|
|
@@ -98,5 +83,22 @@
|
|
|
98
83
|
"bugs": {
|
|
99
84
|
"url": "https://github.com/dotNacer/sveltekit-admin/issues"
|
|
100
85
|
},
|
|
101
|
-
"author": "dotNacer"
|
|
102
|
-
|
|
86
|
+
"author": "dotNacer",
|
|
87
|
+
"scripts": {
|
|
88
|
+
"dev": "vite dev",
|
|
89
|
+
"build": "pnpm run package",
|
|
90
|
+
"package": "svelte-kit sync && svelte-package -o dist",
|
|
91
|
+
"size": "pnpm run package && node scripts/package-size.mjs",
|
|
92
|
+
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
|
93
|
+
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
|
94
|
+
"test:gen": "prisma generate --schema tests/fixtures/prisma/schema.prisma",
|
|
95
|
+
"test": "svelte-kit sync && pnpm run test:gen && vitest run",
|
|
96
|
+
"test:watch": "svelte-kit sync && pnpm run test:gen && vitest",
|
|
97
|
+
"test:coverage": "svelte-kit sync && pnpm run test:gen && vitest run --coverage",
|
|
98
|
+
"lint": "svelte-kit sync && eslint .",
|
|
99
|
+
"format": "prettier --write .",
|
|
100
|
+
"changeset": "changeset",
|
|
101
|
+
"version-packages": "changeset version",
|
|
102
|
+
"release": "pnpm run package && changeset publish"
|
|
103
|
+
}
|
|
104
|
+
}
|