runeforge 0.0.7 → 0.0.8
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 +8 -0
- package/dist/components/crud/GenericCRUD.svelte +17 -10
- package/dist/components/crud/GenericCRUD.svelte.d.ts +1 -0
- package/dist/components/crud/utils/formatters.d.ts +1 -3
- package/dist/components/crud/utils/formatters.js +2 -2
- package/dist/components/crud/views/List.svelte +3 -1
- package/dist/components/crud/views/List.svelte.d.ts +1 -0
- package/dist/components/crud/views/Read.svelte +4 -2
- package/dist/components/crud/views/Read.svelte.d.ts +1 -0
- package/dist/components/crud/views/Update.svelte +4 -2
- package/dist/components/crud/views/Update.svelte.d.ts +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -284,6 +284,14 @@ The `load` function returns a single record when `?id=` is present (used by the
|
|
|
284
284
|
|
|
285
285
|
`dataKey` must match the key returned by the load function for the list. Each `endpoint` maps to a SvelteKit form action on the same page.
|
|
286
286
|
|
|
287
|
+
If your records use a different identifier field than `_id` (e.g. a plain `id`), pass the `idKey` prop:
|
|
288
|
+
|
|
289
|
+
```svelte
|
|
290
|
+
<GenericCRUD idKey="id" ... />
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
This propagates to navigation URLs, form submissions, deletion calls, and the auto-excluded column list, so no other changes are needed on your end.
|
|
294
|
+
|
|
287
295
|
---
|
|
288
296
|
|
|
289
297
|
## Components
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
let {
|
|
19
19
|
data = undefined as Record<string, unknown> | undefined,
|
|
20
20
|
dataKey = undefined as string | undefined,
|
|
21
|
+
idKey = '_id',
|
|
21
22
|
labelOne = '',
|
|
22
23
|
labelMany = '',
|
|
23
24
|
icon,
|
|
@@ -34,6 +35,7 @@
|
|
|
34
35
|
}: {
|
|
35
36
|
data?: Record<string, unknown>;
|
|
36
37
|
dataKey?: string;
|
|
38
|
+
idKey?: string;
|
|
37
39
|
labelOne?: string;
|
|
38
40
|
labelMany?: string;
|
|
39
41
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -61,9 +63,11 @@
|
|
|
61
63
|
const reading = $derived(idParam !== null && viewParam === null);
|
|
62
64
|
const editing = $derived(idParam !== null && viewParam === 'edit');
|
|
63
65
|
|
|
66
|
+
const excluded = $derived(new Set([...AUTO_EXCLUDED, idKey]));
|
|
67
|
+
|
|
64
68
|
const singleInstance = $derived<T | undefined>(
|
|
65
69
|
(Object.values(page.data as Record<string, unknown>).find(
|
|
66
|
-
(v) => v !== null && typeof v === 'object' && !Array.isArray(v) &&
|
|
70
|
+
(v) => v !== null && typeof v === 'object' && !Array.isArray(v) && idKey in (v as object)
|
|
67
71
|
) as T | undefined)
|
|
68
72
|
);
|
|
69
73
|
|
|
@@ -72,10 +76,10 @@
|
|
|
72
76
|
async function navList() { await goto('?'); }
|
|
73
77
|
async function navCreate() { await goto('?view=create'); }
|
|
74
78
|
async function navRead(item: T) {
|
|
75
|
-
await goto(`?id=${encodeURIComponent(String((item as Record<string, unknown>)
|
|
79
|
+
await goto(`?id=${encodeURIComponent(String((item as Record<string, unknown>)[idKey] ?? ''))}`);
|
|
76
80
|
}
|
|
77
81
|
async function navEdit(item: T) {
|
|
78
|
-
await goto(`?id=${encodeURIComponent(String((item as Record<string, unknown>)
|
|
82
|
+
await goto(`?id=${encodeURIComponent(String((item as Record<string, unknown>)[idKey] ?? ''))}&view=edit`);
|
|
79
83
|
}
|
|
80
84
|
|
|
81
85
|
const resolvedColumns: ColumnDefinition<T>[] = $derived(
|
|
@@ -93,7 +97,7 @@
|
|
|
93
97
|
}))
|
|
94
98
|
: entityData.length > 0
|
|
95
99
|
? (Object.keys(entityData[0]) as (keyof T & string)[])
|
|
96
|
-
.filter((k) => k
|
|
100
|
+
.filter((k) => !excluded.has(k))
|
|
97
101
|
.map((k) => ({ attribute: k, title: k }))
|
|
98
102
|
: [])
|
|
99
103
|
);
|
|
@@ -101,7 +105,7 @@
|
|
|
101
105
|
const resolvedFields: FieldDefinition<T>[] = $derived(
|
|
102
106
|
fields ?? (meta
|
|
103
107
|
? (Object.entries(meta) as [string, AttributeMetadata][])
|
|
104
|
-
.filter(([k, m]) => !
|
|
108
|
+
.filter(([k, m]) => !excluded.has(k) && !m.excludedFromCreate)
|
|
105
109
|
.map(([k, m]) => ({
|
|
106
110
|
attribute: k as keyof T & string,
|
|
107
111
|
title: m.label,
|
|
@@ -114,7 +118,7 @@
|
|
|
114
118
|
}))
|
|
115
119
|
: entityData.length > 0
|
|
116
120
|
? (Object.entries(entityData[0]) as [string, unknown][])
|
|
117
|
-
.filter(([k]) => !
|
|
121
|
+
.filter(([k]) => !excluded.has(k))
|
|
118
122
|
.map(([k, v]) => ({ attribute: k as keyof T & string, type: inferType(k, v) }))
|
|
119
123
|
: [])
|
|
120
124
|
);
|
|
@@ -122,7 +126,7 @@
|
|
|
122
126
|
const resolvedReadFields: FieldDefinition<T>[] = $derived(
|
|
123
127
|
fields ?? (meta
|
|
124
128
|
? (Object.entries(meta) as [string, AttributeMetadata][])
|
|
125
|
-
.filter(([k, m]) => !
|
|
129
|
+
.filter(([k, m]) => !excluded.has(k) && !m.excludedFromRead)
|
|
126
130
|
.map(([k, m]) => ({
|
|
127
131
|
attribute: k as keyof T & string,
|
|
128
132
|
title: m.label,
|
|
@@ -135,7 +139,7 @@
|
|
|
135
139
|
}))
|
|
136
140
|
: entityData.length > 0
|
|
137
141
|
? (Object.entries(entityData[0]) as [string, unknown][])
|
|
138
|
-
.filter(([k]) => !
|
|
142
|
+
.filter(([k]) => !excluded.has(k))
|
|
139
143
|
.map(([k, v]) => ({ attribute: k as keyof T & string, type: inferType(k, v) }))
|
|
140
144
|
: [])
|
|
141
145
|
);
|
|
@@ -143,7 +147,7 @@
|
|
|
143
147
|
const resolvedUpdateFields: FieldDefinition<T>[] = $derived(
|
|
144
148
|
fields ?? (meta
|
|
145
149
|
? (Object.entries(meta) as [string, AttributeMetadata][])
|
|
146
|
-
.filter(([k, m]) => !
|
|
150
|
+
.filter(([k, m]) => !excluded.has(k) && !m.excludedFromUpdate)
|
|
147
151
|
.map(([k, m]) => ({
|
|
148
152
|
attribute: k as keyof T & string,
|
|
149
153
|
title: m.label,
|
|
@@ -156,7 +160,7 @@
|
|
|
156
160
|
}))
|
|
157
161
|
: entityData.length > 0
|
|
158
162
|
? (Object.entries(entityData[0]) as [string, unknown][])
|
|
159
|
-
.filter(([k]) => !
|
|
163
|
+
.filter(([k]) => !excluded.has(k))
|
|
160
164
|
.map(([k, v]) => ({ attribute: k as keyof T & string, type: inferType(k, v) }))
|
|
161
165
|
: [])
|
|
162
166
|
);
|
|
@@ -184,6 +188,7 @@
|
|
|
184
188
|
{labelOne}
|
|
185
189
|
{labelMany}
|
|
186
190
|
{icon}
|
|
191
|
+
{idKey}
|
|
187
192
|
fields={resolvedReadFields}
|
|
188
193
|
instance={singleInstance ?? {} as T}
|
|
189
194
|
{read}
|
|
@@ -194,6 +199,7 @@
|
|
|
194
199
|
{labelOne}
|
|
195
200
|
{labelMany}
|
|
196
201
|
{icon}
|
|
202
|
+
{idKey}
|
|
197
203
|
fields={resolvedUpdateFields}
|
|
198
204
|
instance={singleInstance ?? {} as T}
|
|
199
205
|
{update}
|
|
@@ -208,6 +214,7 @@
|
|
|
208
214
|
{labelMany}
|
|
209
215
|
{icon}
|
|
210
216
|
{pageSize}
|
|
217
|
+
{idKey}
|
|
211
218
|
{creation}
|
|
212
219
|
{update}
|
|
213
220
|
{read}
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
export declare const formatBoolean: (trueLabel?: string, falseLabel?: string) => () => (value: boolean) => string;
|
|
2
2
|
export declare const formatDatetime: (format?: string) => () => (value: Date) => string;
|
|
3
3
|
export declare function formatTruncateTextUpTo(maxLength: number): () => (value: string) => string;
|
|
4
|
-
export declare function formatInstance<T extends
|
|
5
|
-
_id: string;
|
|
6
|
-
}>(attribute: keyof T & string, instances: T[], urlPath: string): (value: unknown) => string;
|
|
4
|
+
export declare function formatInstance<T extends Record<string, unknown>>(attribute: keyof T & string, instances: T[], urlPath: string, idKey?: keyof T & string): (value: unknown) => string;
|
|
@@ -30,8 +30,8 @@ function escapeHtml(str) {
|
|
|
30
30
|
.replace(/>/g, '>')
|
|
31
31
|
.replace(/"/g, '"');
|
|
32
32
|
}
|
|
33
|
-
export function formatInstance(attribute, instances, urlPath) {
|
|
34
|
-
const map = new Map(instances.map((i) => [i
|
|
33
|
+
export function formatInstance(attribute, instances, urlPath, idKey = '_id') {
|
|
34
|
+
const map = new Map(instances.map((i) => [String(i[idKey] ?? ''), i]));
|
|
35
35
|
return (value) => {
|
|
36
36
|
const id = String(value ?? '');
|
|
37
37
|
const instance = map.get(id);
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
labelMany = '',
|
|
23
23
|
icon,
|
|
24
24
|
pageSize = 10,
|
|
25
|
+
idKey = '_id',
|
|
25
26
|
creation = {} as ActionConfiguration<T>,
|
|
26
27
|
update = {} as ActionConfiguration<T>,
|
|
27
28
|
read = {} as ActionConfiguration<T>,
|
|
@@ -39,6 +40,7 @@
|
|
|
39
40
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
40
41
|
icon?: any;
|
|
41
42
|
pageSize?: number;
|
|
43
|
+
idKey?: string;
|
|
42
44
|
creation?: ActionConfiguration<T>;
|
|
43
45
|
update?: ActionConfiguration<T>;
|
|
44
46
|
read?: ActionConfiguration<T>;
|
|
@@ -67,7 +69,7 @@
|
|
|
67
69
|
async function runEndpointAction(endpoint: string, items: T[]) {
|
|
68
70
|
await Promise.all(items.map((item) => {
|
|
69
71
|
const fd = new FormData();
|
|
70
|
-
fd.set('id', String((item as Record<string, unknown>)
|
|
72
|
+
fd.set('id', String((item as Record<string, unknown>)[idKey] ?? ''));
|
|
71
73
|
return fetch(endpoint, { method: 'POST', body: fd });
|
|
72
74
|
}));
|
|
73
75
|
await invalidateAll();
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
labelOne = '',
|
|
16
16
|
labelMany = '',
|
|
17
17
|
icon,
|
|
18
|
+
idKey = '_id',
|
|
18
19
|
fields = [] as FieldDefinition<T>[],
|
|
19
20
|
instance = {} as T,
|
|
20
21
|
read = {} as ActionConfiguration<T>,
|
|
@@ -24,6 +25,7 @@
|
|
|
24
25
|
labelMany?: string;
|
|
25
26
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
26
27
|
icon?: any;
|
|
28
|
+
idKey?: string;
|
|
27
29
|
fields?: FieldDefinition<T>[];
|
|
28
30
|
instance?: T;
|
|
29
31
|
read?: ActionConfiguration<T>;
|
|
@@ -37,7 +39,7 @@
|
|
|
37
39
|
|
|
38
40
|
$effect(() => {
|
|
39
41
|
const endpoint = read.endpoint;
|
|
40
|
-
const id = (instance as Record<string, unknown>)
|
|
42
|
+
const id = (instance as Record<string, unknown>)[idKey];
|
|
41
43
|
if (!endpoint || id == null) return;
|
|
42
44
|
|
|
43
45
|
let cancelled = false;
|
|
@@ -50,7 +52,7 @@
|
|
|
50
52
|
const data = result.data as Record<string, unknown> | undefined;
|
|
51
53
|
const fetched = data
|
|
52
54
|
? Object.values(data).find(
|
|
53
|
-
(v) => v !== null && typeof v === 'object' && !Array.isArray(v) &&
|
|
55
|
+
(v) => v !== null && typeof v === 'object' && !Array.isArray(v) && idKey in (v as object)
|
|
54
56
|
)
|
|
55
57
|
: undefined;
|
|
56
58
|
if (fetched && typeof fetched === 'object') record = fetched as Record<string, unknown>;
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
labelOne = '',
|
|
17
17
|
labelMany = '',
|
|
18
18
|
icon,
|
|
19
|
+
idKey = '_id',
|
|
19
20
|
fields = [] as FieldDefinition<T>[],
|
|
20
21
|
instance = {} as T,
|
|
21
22
|
update = {} as ActionConfiguration<T>,
|
|
@@ -27,6 +28,7 @@
|
|
|
27
28
|
labelMany?: string;
|
|
28
29
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
29
30
|
icon?: any;
|
|
31
|
+
idKey?: string;
|
|
30
32
|
fields?: FieldDefinition<T>[];
|
|
31
33
|
instance?: T;
|
|
32
34
|
update?: ActionConfiguration<T>;
|
|
@@ -56,7 +58,7 @@
|
|
|
56
58
|
);
|
|
57
59
|
|
|
58
60
|
$effect(() => {
|
|
59
|
-
const id = (instance as Record<string, unknown>)
|
|
61
|
+
const id = (instance as Record<string, unknown>)[idKey];
|
|
60
62
|
if (!id) return;
|
|
61
63
|
untrack(() => { record = seedFromInstance(instance as Record<string, unknown>); });
|
|
62
64
|
});
|
|
@@ -130,7 +132,7 @@
|
|
|
130
132
|
};
|
|
131
133
|
}}
|
|
132
134
|
>
|
|
133
|
-
<input type="hidden" name="id" value={String(record
|
|
135
|
+
<input type="hidden" name="id" value={String(record[idKey] ?? '')} />
|
|
134
136
|
|
|
135
137
|
{#each fields as field (field.attribute)}
|
|
136
138
|
<Field {field} bind:record error={fieldErrors[field.attribute] ?? ''} />
|