@svadmin/lite 0.3.12 → 0.3.13

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 (55) hide show
  1. package/README.md +30 -5
  2. package/dist/components/LiteArrayItem.svelte +31 -10
  3. package/dist/components/LiteAuditLog.svelte +8 -17
  4. package/dist/components/LiteAuditLog.svelte.d.ts +5 -13
  5. package/dist/components/LiteForm.svelte +37 -7
  6. package/dist/components/LiteForm.svelte.d.ts +1 -1
  7. package/dist/components/LiteLayout.svelte +12 -7
  8. package/dist/components/LiteLayout.svelte.d.ts +2 -0
  9. package/dist/components/LitePermissionMatrix.svelte +48 -15
  10. package/dist/components/LitePermissionMatrix.svelte.d.ts +10 -9
  11. package/dist/components/LiteShow.svelte +3 -2
  12. package/dist/components/LiteShowField.svelte +5 -3
  13. package/dist/components/LiteTable.svelte +19 -9
  14. package/dist/components/LiteTable.svelte.d.ts +1 -0
  15. package/dist/components/advanced/LiteVirtualTable.svelte +5 -2
  16. package/dist/components/advanced/LiteVirtualTable.svelte.d.ts +1 -0
  17. package/dist/components/buttons/LiteCloneButton.svelte +2 -2
  18. package/dist/components/buttons/LiteCreateButton.svelte +2 -2
  19. package/dist/components/buttons/LiteDeleteButton.svelte +2 -2
  20. package/dist/components/buttons/LiteEditButton.svelte +2 -2
  21. package/dist/components/buttons/LiteExportButton.svelte +2 -2
  22. package/dist/components/buttons/LiteImportButton.svelte +2 -2
  23. package/dist/components/buttons/LiteListButton.svelte +2 -2
  24. package/dist/components/buttons/LiteRefreshButton.svelte +7 -11
  25. package/dist/components/buttons/LiteSaveButton.svelte +2 -2
  26. package/dist/components/buttons/LiteShowButton.svelte +2 -2
  27. package/dist/components/fields/LiteBooleanField.svelte +9 -8
  28. package/dist/components/fields/LiteDateField.svelte +2 -2
  29. package/dist/components/fields/LiteEmailField.svelte +1 -2
  30. package/dist/components/fields/LiteImageField.svelte +20 -11
  31. package/dist/components/fields/LiteNumberField.svelte +1 -2
  32. package/dist/components/fields/LiteRelationField.svelte +15 -7
  33. package/dist/components/fields/LiteSelectField.svelte +2 -1
  34. package/dist/components/fields/LiteTextField.svelte +1 -2
  35. package/dist/components/fields/LiteUrlField.svelte +1 -5
  36. package/dist/components/layout/LiteCatchAllNavigate.svelte +3 -9
  37. package/dist/components/layout/LiteNavigateToResource.svelte +5 -16
  38. package/dist/components/layout/LiteSidebar.svelte +9 -5
  39. package/dist/components/layout/LiteSidebar.svelte.d.ts +1 -0
  40. package/dist/components/pages/LiteEditPage.svelte +6 -4
  41. package/dist/components/pages/LiteListPage.svelte +14 -6
  42. package/dist/components/pages/LiteListPage.svelte.d.ts +1 -0
  43. package/dist/components/pages/LiteShowPage.svelte +6 -4
  44. package/dist/components/widgets/LiteAnomalyBadge.svelte +5 -1
  45. package/dist/index.js +1 -1
  46. package/dist/lite.css +2 -1
  47. package/dist/menu-visibility.d.ts +4 -0
  48. package/dist/menu-visibility.js +34 -0
  49. package/dist/schema-generator.d.ts +1 -1
  50. package/dist/schema-generator.js +112 -51
  51. package/dist/server-adapter.d.ts +53 -4
  52. package/dist/server-adapter.js +235 -67
  53. package/dist/value-normalization.d.ts +2 -0
  54. package/dist/value-normalization.js +21 -0
  55. package/package.json +6 -14
@@ -20,23 +20,25 @@
20
20
  record,
21
21
  errors = {},
22
22
  basePath = '/lite',
23
- canDelete = true,
24
- canShow = true,
23
+ canDelete,
24
+ canShow,
25
25
  }: Props = $props();
26
26
 
27
27
  let pk = $derived(resource.primaryKey ?? 'id');
28
28
  let idStr = $derived(String(record[pk]));
29
+ const showDelete = $derived(canDelete ?? resource.canDelete !== false);
30
+ const showView = $derived(canShow ?? resource.canShow !== false);
29
31
  </script>
30
32
 
31
33
  <div class="lite-page">
32
34
  <div class="lite-page-header">
33
35
  <h1 class="lite-page-title">{t('common.edit') || 'Edit'} {resource.label || resource.name} #{idStr}</h1>
34
36
  <div class="lite-page-actions">
35
- {#if canShow}
37
+ {#if showView}
36
38
  <LiteShowButton resource={resource.name} recordItemId={idStr} {basePath} />
37
39
  {/if}
38
40
  <LiteListButton resource={resource.name} {basePath} />
39
- {#if canDelete}
41
+ {#if showDelete}
40
42
  <LiteDeleteButton resource={resource.name} recordItemId={idStr} redirectUrl={`${basePath}/${resource.name}`} {basePath} />
41
43
  {/if}
42
44
  </div>
@@ -17,6 +17,7 @@
17
17
  currentSearch?: string;
18
18
  basePath?: string;
19
19
  canCreate?: boolean;
20
+ canShow?: boolean;
20
21
  canEdit?: boolean;
21
22
  canDelete?: boolean;
22
23
  }
@@ -30,17 +31,23 @@
30
31
  currentOrder = 'asc',
31
32
  currentSearch,
32
33
  basePath = '/lite',
33
- canCreate = true,
34
- canEdit = true,
35
- canDelete = true,
34
+ canCreate,
35
+ canShow,
36
+ canEdit,
37
+ canDelete,
36
38
  }: Props = $props();
39
+
40
+ const showCreate = $derived(canCreate ?? resource.canCreate !== false);
41
+ const showView = $derived(canShow ?? resource.canShow !== false);
42
+ const showEdit = $derived(canEdit ?? resource.canEdit !== false);
43
+ const showDelete = $derived(canDelete ?? resource.canDelete !== false);
37
44
  </script>
38
45
 
39
46
  <div class="lite-page">
40
47
  <div class="lite-page-header">
41
48
  <h1 class="lite-page-title">{resource.label || resource.name} {t('common.list') || 'List'}</h1>
42
49
  <div class="lite-page-actions">
43
- {#if canCreate}
50
+ {#if showCreate}
44
51
  <LiteCreateButton resource={resource.name} {basePath} />
45
52
  {/if}
46
53
  <LiteRefreshButton hideText />
@@ -62,8 +69,9 @@
62
69
  {currentOrder}
63
70
  {currentSearch}
64
71
  {basePath}
65
- {canEdit}
66
- {canDelete}
72
+ canShow={showView}
73
+ canEdit={showEdit}
74
+ canDelete={showDelete}
67
75
  />
68
76
 
69
77
  {#if total > pagination.perPage}
@@ -12,6 +12,7 @@ interface Props {
12
12
  currentSearch?: string;
13
13
  basePath?: string;
14
14
  canCreate?: boolean;
15
+ canShow?: boolean;
15
16
  canEdit?: boolean;
16
17
  canDelete?: boolean;
17
18
  }
@@ -18,12 +18,14 @@
18
18
  resource,
19
19
  record,
20
20
  basePath = '/lite',
21
- canEdit = true,
22
- canDelete = true,
21
+ canEdit,
22
+ canDelete,
23
23
  }: Props = $props();
24
24
 
25
25
  let pk = $derived(resource.primaryKey ?? 'id');
26
26
  let idStr = $derived(String(record[pk]));
27
+ const showEdit = $derived(canEdit ?? resource.canEdit !== false);
28
+ const showDelete = $derived(canDelete ?? resource.canDelete !== false);
27
29
 
28
30
  const showFields = $derived(
29
31
  resource.fields.filter(f => f.showInShow !== false)
@@ -34,11 +36,11 @@
34
36
  <div class="lite-page-header">
35
37
  <h1 class="lite-page-title">{t('common.show') || 'Show'} {resource.label || resource.name} #{idStr}</h1>
36
38
  <div class="lite-page-actions">
37
- {#if canEdit}
39
+ {#if showEdit}
38
40
  <LiteEditButton resource={resource.name} recordItemId={idStr} {basePath} />
39
41
  {/if}
40
42
  <LiteListButton resource={resource.name} {basePath} />
41
- {#if canDelete}
43
+ {#if showDelete}
42
44
  <LiteDeleteButton resource={resource.name} recordItemId={idStr} redirectUrl={`${basePath}/${resource.name}`} {basePath} />
43
45
  {/if}
44
46
  </div>
@@ -20,7 +20,11 @@
20
20
  lowerIsBetter = false,
21
21
  }: Props = $props();
22
22
 
23
- const diff = $derived(baseline === 0 ? 0 : (value - baseline) / Math.abs(baseline));
23
+ const diff = $derived(
24
+ baseline === 0
25
+ ? value === 0 ? 0 : value > 0 ? Infinity : -Infinity
26
+ : (value - baseline) / Math.abs(baseline),
27
+ );
24
28
  const isAnomaly = $derived(Math.abs(diff) >= threshold);
25
29
  const isGood = $derived(!isAnomaly ? null : (lowerIsBetter ? diff < 0 : diff > 0));
26
30
  const percentLabel = $derived(`${Math.abs(diff * 100).toFixed(1)}%`);
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  // Server-rendered by default, with optional progressive enhancement.
3
3
  // Server utilities (use in +page.server.ts / hooks.server.ts)
4
4
  export { createListLoader, createDetailLoader, createCrudActions, createAuthGuard, createAuthActions, createLegacyRedirectHook, isLegacyBrowser, } from './server-adapter';
5
- // Schema generator (use with sveltekit-superforms)
5
+ // Schema generator (used by Lite actions; also reusable by other Zod consumers)
6
6
  export { fieldsToZodSchema, resourceToZodSchema, fieldToInputType, fieldToPlaceholder, } from './schema-generator';
7
7
  // UI Components (use in +page.svelte with csr = false)
8
8
  export { default as LiteLayout } from './components/LiteLayout.svelte';
package/dist/lite.css CHANGED
@@ -802,7 +802,8 @@ textarea.lite-input {
802
802
  .lite-search,
803
803
  .lite-pagination,
804
804
  .lite-btn,
805
- .lite-confirm {
805
+ .lite-confirm,
806
+ .lite-confirm-details {
806
807
  display: none !important;
807
808
  }
808
809
  .lite-main {
@@ -0,0 +1,4 @@
1
+ import type { MenuItem, ResourceDefinition } from '@svadmin/core';
2
+ export type MenuAccessCheck = (resource: string, action: string) => boolean;
3
+ export declare function filterVisibleResources(resources: ResourceDefinition[], canAccess?: MenuAccessCheck): ResourceDefinition[];
4
+ export declare function filterVisibleMenu(menuEntries: MenuItem[], canAccess?: MenuAccessCheck): MenuItem[];
@@ -0,0 +1,34 @@
1
+ function passesMenuAccess(resource, action, canAccess) {
2
+ if (!canAccess)
3
+ return true;
4
+ try {
5
+ return canAccess(resource, action);
6
+ }
7
+ catch {
8
+ // Permission callbacks fail closed so an evaluator error cannot expose navigation.
9
+ return false;
10
+ }
11
+ }
12
+ export function filterVisibleResources(resources, canAccess) {
13
+ return resources.filter((resource) => resource.showInMenu !== false
14
+ && passesMenuAccess(resource.name, 'list', canAccess));
15
+ }
16
+ export function filterVisibleMenu(menuEntries, canAccess) {
17
+ const visibleEntries = [];
18
+ for (const menuEntry of menuEntries) {
19
+ if (menuEntry.meta?.hidden)
20
+ continue;
21
+ if (menuEntry.meta?.resource
22
+ && !passesMenuAccess(menuEntry.meta.resource, menuEntry.meta.action ?? 'list', canAccess)) {
23
+ continue;
24
+ }
25
+ const visibleChildren = menuEntry.children
26
+ ? filterVisibleMenu(menuEntry.children, canAccess)
27
+ : undefined;
28
+ if (!menuEntry.href && menuEntry.children && menuEntry.children.length > 0
29
+ && visibleChildren?.length === 0)
30
+ continue;
31
+ visibleEntries.push({ ...menuEntry, ...(visibleChildren ? { children: visibleChildren } : {}) });
32
+ }
33
+ return visibleEntries;
34
+ }
@@ -2,7 +2,7 @@
2
2
  * @svadmin/lite — Schema Generator
3
3
  *
4
4
  * Auto-generates Zod schemas from @svadmin/core FieldDefinitions.
5
- * Used with sveltekit-superforms for server-side form validation.
5
+ * Used by the Lite server actions and compatible with other Zod consumers.
6
6
  */
7
7
  import { z } from 'zod';
8
8
  import type { FieldDefinition, ResourceDefinition } from '@svadmin/core';
@@ -2,15 +2,23 @@
2
2
  * @svadmin/lite — Schema Generator
3
3
  *
4
4
  * Auto-generates Zod schemas from @svadmin/core FieldDefinitions.
5
- * Used with sveltekit-superforms for server-side form validation.
5
+ * Used by the Lite server actions and compatible with other Zod consumers.
6
6
  */
7
7
  import { z } from 'zod';
8
+ import { parseExplicitBoolean } from './value-normalization';
8
9
  function isNativeFile(value) {
9
10
  return typeof File !== 'undefined' && value instanceof File;
10
11
  }
11
12
  function isNonEmptyNativeFile(value) {
12
13
  return isNativeFile(value) && value.size > 0 && value.name !== '';
13
14
  }
15
+ function normalizeSingleUpload(submittedUpload) {
16
+ if (submittedUpload === undefined || submittedUpload === null || submittedUpload === '')
17
+ return undefined;
18
+ if (isNativeFile(submittedUpload) && !isNonEmptyNativeFile(submittedUpload))
19
+ return undefined;
20
+ return submittedUpload;
21
+ }
14
22
  function hasRequiredValue(value) {
15
23
  if (value === undefined || value === null)
16
24
  return false;
@@ -22,6 +30,17 @@ function hasRequiredValue(value) {
22
30
  return isNonEmptyNativeFile(value);
23
31
  return true;
24
32
  }
33
+ function applyCustomValidation(schema, field) {
34
+ if (!field.validate)
35
+ return schema;
36
+ return schema.superRefine((parsedFieldValue, context) => {
37
+ if (parsedFieldValue === undefined || parsedFieldValue === null || parsedFieldValue === '')
38
+ return;
39
+ const message = field.validate?.(parsedFieldValue);
40
+ if (message)
41
+ context.addIssue({ code: 'custom', message });
42
+ });
43
+ }
25
44
  function numberFieldToZod(field) {
26
45
  const numberSchema = z.coerce.number({ message: `${field.label} must be a number` });
27
46
  const targetSchema = field.required ? numberSchema : numberSchema.optional();
@@ -33,6 +52,32 @@ function numberFieldToZod(field) {
33
52
  return value;
34
53
  }, targetSchema);
35
54
  }
55
+ function booleanFieldToZod(field) {
56
+ const booleanSchema = z.boolean();
57
+ const targetSchema = field.required ? booleanSchema : booleanSchema.optional();
58
+ return z.preprocess((rawBoolean) => {
59
+ if (rawBoolean === undefined || rawBoolean === null || rawBoolean === '')
60
+ return undefined;
61
+ return parseExplicitBoolean(rawBoolean) ?? rawBoolean;
62
+ }, targetSchema);
63
+ }
64
+ function restoreOptionValue(field, submittedOption) {
65
+ const option = field.options?.find((candidate) => String(candidate.value) === String(submittedOption));
66
+ return option?.value ?? submittedOption;
67
+ }
68
+ function optionValueSchema(field) {
69
+ const options = field.options ?? [];
70
+ return z.union([z.string(), z.number()]).refine((parsedOption) => options.length === 0
71
+ || options.some((option) => Object.is(option.value, parsedOption)), { message: `${field.label} must be one of the options` });
72
+ }
73
+ function singleOptionFieldToZod(field) {
74
+ return z.preprocess((submittedOption) => restoreOptionValue(field, submittedOption), optionValueSchema(field));
75
+ }
76
+ function multipleOptionFieldToZod(field) {
77
+ return z.preprocess((submittedOptions) => Array.isArray(submittedOptions)
78
+ ? submittedOptions.map((submittedOption) => restoreOptionValue(field, submittedOption))
79
+ : submittedOptions, z.array(optionValueSchema(field)).default([]));
80
+ }
36
81
  function singleFileFieldToZod(field, options) {
37
82
  const fileSchema = z.custom(isNonEmptyNativeFile, {
38
83
  message: `${field.label} must be a non-empty file`,
@@ -40,36 +85,44 @@ function singleFileFieldToZod(field, options) {
40
85
  const referenceSchema = z.string().trim().min(1, `${field.label} must reference an existing file`);
41
86
  const uploadSchema = options.allowReference ? z.union([fileSchema, referenceSchema]) : fileSchema;
42
87
  const targetSchema = options.required ? uploadSchema : uploadSchema.optional();
43
- return z.preprocess((value) => {
44
- if (value === undefined || value === null || value === '')
45
- return undefined;
46
- if (isNativeFile(value) && !isNonEmptyNativeFile(value))
47
- return undefined;
48
- return value;
49
- }, targetSchema);
88
+ return z.preprocess(normalizeSingleUpload, targetSchema);
50
89
  }
51
- function multipleFilesFieldToZod(field, options) {
52
- const fileSchema = z.custom(isNonEmptyNativeFile, {
53
- message: `${field.label} contains an empty or invalid file`,
54
- });
55
- const filesSchema = z.array(fileSchema).min(1, `${field.label} must contain at least one file`);
56
- const referencesSchema = z.array(z.string().trim().min(1, `${field.label} contains an empty file reference`)).min(1, `${field.label} must contain at least one file`);
57
- const uploadSchema = options.allowReference ? z.union([filesSchema, referencesSchema]) : filesSchema;
58
- const targetSchema = options.required ? uploadSchema : uploadSchema.optional();
59
- return z.preprocess((value) => {
60
- if (value === undefined || value === null || value === '')
61
- return undefined;
62
- if (!Array.isArray(value))
63
- return value;
64
- const invalidEntries = value.filter((entry) => !isNativeFile(entry) && typeof entry !== 'string');
65
- if (invalidEntries.length > 0)
66
- return value;
67
- const uploadedFiles = value.filter(isNonEmptyNativeFile);
68
- if (uploadedFiles.length > 0)
69
- return uploadedFiles;
70
- const references = value.filter((entry) => typeof entry === 'string' && entry.trim().length > 0);
71
- return references.length > 0 ? references : undefined;
72
- }, targetSchema);
90
+ function imageFieldSchemas(field) {
91
+ const imageSchema = z.union([
92
+ z.string().trim().min(1, `${field.label} must reference an image`),
93
+ z.custom(isNonEmptyNativeFile, {
94
+ message: `${field.label} must reference an image or contain a non-empty file`,
95
+ }),
96
+ ]);
97
+ return {
98
+ required: z.preprocess(normalizeSingleUpload, imageSchema),
99
+ optional: z.preprocess(normalizeSingleUpload, imageSchema.optional()),
100
+ };
101
+ }
102
+ function normalizeImageEntries(submittedImages) {
103
+ if (submittedImages === undefined || submittedImages === null || submittedImages === '')
104
+ return undefined;
105
+ const entries = Array.isArray(submittedImages) ? submittedImages : [submittedImages];
106
+ const normalized = entries.flatMap((entry) => {
107
+ if (typeof entry !== 'string')
108
+ return [entry];
109
+ return entry.split(/[\r\n]+/u).map((reference) => reference.trim()).filter(Boolean);
110
+ }).filter((entry) => !isNativeFile(entry) || isNonEmptyNativeFile(entry));
111
+ return normalized.length > 0 ? normalized : undefined;
112
+ }
113
+ function imagesFieldSchemas(field) {
114
+ const imageEntrySchema = z.union([
115
+ z.string().trim().min(1, `${field.label} contains an empty image reference`),
116
+ z.custom(isNonEmptyNativeFile, {
117
+ message: `${field.label} contains an empty or invalid file`,
118
+ }),
119
+ ]);
120
+ const requiredImages = z.array(imageEntrySchema)
121
+ .min(1, `${field.label} must contain at least one image`);
122
+ return {
123
+ required: z.preprocess(normalizeImageEntries, requiredImages),
124
+ optional: z.preprocess(normalizeImageEntries, z.array(imageEntrySchema).optional()),
125
+ };
73
126
  }
74
127
  /**
75
128
  * Convert a single FieldDefinition to its corresponding Zod type.
@@ -80,8 +133,7 @@ function fieldToZod(field, mode, withinArray = false) {
80
133
  case 'number':
81
134
  return numberFieldToZod(field);
82
135
  case 'boolean':
83
- schema = z.coerce.boolean();
84
- break;
136
+ return booleanFieldToZod(field);
85
137
  case 'email':
86
138
  schema = z.string().email(`${field.label} must be a valid email`);
87
139
  break;
@@ -92,20 +144,18 @@ function fieldToZod(field, mode, withinArray = false) {
92
144
  schema = z.string().refine((v) => !v || !isNaN(Date.parse(v)), { message: `${field.label} must be a valid date` });
93
145
  break;
94
146
  case 'select':
95
- if (field.options?.length) {
96
- schema = z.enum(field.options.map((o) => String(o.value)), { message: `${field.label} must be one of the options` });
97
- }
98
- else {
99
- schema = z.string();
100
- }
147
+ schema = singleOptionFieldToZod(field);
101
148
  break;
102
149
  case 'multiselect':
103
- schema = z.array(z.string()).default([]);
150
+ schema = multipleOptionFieldToZod(field);
151
+ break;
152
+ case 'relation':
153
+ schema = field.options?.length ? singleOptionFieldToZod(field) : z.string();
104
154
  break;
105
155
  case 'array': {
106
156
  const shape = {};
107
157
  for (const subField of field.subFields ?? []) {
108
- shape[subField.key] = fieldToZod(subField, mode, true);
158
+ shape[subField.key] = applyCustomValidation(fieldToZod(subField, mode, true), subField);
109
159
  }
110
160
  const arraySchema = z.array(z.object(shape));
111
161
  return field.required
@@ -120,6 +170,7 @@ function fieldToZod(field, mode, withinArray = false) {
120
170
  break;
121
171
  case 'textarea':
122
172
  case 'richtext':
173
+ case 'markdown':
123
174
  schema = z.string().max(50000, `${field.label} is too long`);
124
175
  break;
125
176
  case 'json':
@@ -139,16 +190,22 @@ function fieldToZod(field, mode, withinArray = false) {
139
190
  schema = z.string().regex(/^[+\d\s()-]*$/, `${field.label} must be a valid phone number`);
140
191
  break;
141
192
  case 'file':
142
- case 'image':
143
193
  return singleFileFieldToZod(field, {
144
194
  required: field.required === true && (mode === 'create' || withinArray),
145
195
  allowReference: mode === 'edit' && withinArray,
146
196
  });
147
- case 'images':
148
- return multipleFilesFieldToZod(field, {
149
- required: field.required === true && (mode === 'create' || withinArray),
150
- allowReference: mode === 'edit' && withinArray,
151
- });
197
+ case 'image': {
198
+ const imageSchemas = imageFieldSchemas(field);
199
+ return field.required === true && (mode === 'create' || withinArray)
200
+ ? imageSchemas.required
201
+ : imageSchemas.optional;
202
+ }
203
+ case 'images': {
204
+ const imagesSchemas = imagesFieldSchemas(field);
205
+ return field.required === true && (mode === 'create' || withinArray)
206
+ ? imagesSchemas.required
207
+ : imagesSchemas.optional;
208
+ }
152
209
  default:
153
210
  schema = z.string();
154
211
  }
@@ -177,7 +234,7 @@ export function fieldsToZodSchema(fields, mode = 'create') {
177
234
  continue;
178
235
  if (mode === 'edit' && field.showInEdit === false)
179
236
  continue;
180
- shape[field.key] = fieldToZod(field, mode);
237
+ shape[field.key] = applyCustomValidation(fieldToZod(field, mode), field);
181
238
  }
182
239
  return z.object(shape);
183
240
  }
@@ -186,7 +243,8 @@ export function fieldsToZodSchema(fields, mode = 'create') {
186
243
  * Convenience wrapper around fieldsToZodSchema.
187
244
  */
188
245
  export function resourceToZodSchema(resource, mode = 'create') {
189
- return fieldsToZodSchema(resource.fields, mode);
246
+ const primaryKey = resource.primaryKey ?? 'id';
247
+ return fieldsToZodSchema(resource.fields.filter((field) => field.key !== primaryKey), mode);
190
248
  }
191
249
  /**
192
250
  * Determine a conservative HTML input type for server-rendered forms.
@@ -201,13 +259,16 @@ export function fieldToInputType(field) {
201
259
  case 'boolean': return 'checkbox';
202
260
  case 'date': return 'text';
203
261
  case 'textarea':
204
- case 'richtext': return 'textarea';
262
+ case 'richtext':
263
+ case 'markdown':
264
+ case 'images': return 'textarea';
265
+ case 'json': return 'textarea';
205
266
  case 'select':
206
267
  case 'multiselect': return 'select';
268
+ case 'relation': return field.options?.length ? 'select' : 'text';
207
269
  case 'array': return 'text';
208
- case 'image':
209
- case 'images':
210
270
  case 'file': return 'file';
271
+ case 'password': return 'password';
211
272
  default: return 'text';
212
273
  }
213
274
  }
@@ -16,6 +16,14 @@ export interface ListLoaderResult {
16
16
  sort?: string;
17
17
  order?: 'asc' | 'desc';
18
18
  search?: string;
19
+ /** Aliases consumed directly by LiteListPage. */
20
+ pagination: {
21
+ page: number;
22
+ perPage: number;
23
+ };
24
+ currentSort?: string;
25
+ currentOrder?: 'asc' | 'desc';
26
+ currentSearch?: string;
19
27
  resource: ResourceDefinition;
20
28
  }
21
29
  /**
@@ -43,6 +51,11 @@ export declare function createCrudActions(dp: DataProvider, resource: ResourceDe
43
51
  error: string;
44
52
  values: Record<string, unknown>;
45
53
  errors: Record<string, string[]>;
54
+ } | {
55
+ success: boolean;
56
+ error: string;
57
+ id?: undefined;
58
+ values?: undefined;
46
59
  } | {
47
60
  success: boolean;
48
61
  id: unknown;
@@ -59,6 +72,10 @@ export declare function createCrudActions(dp: DataProvider, resource: ResourceDe
59
72
  error: string;
60
73
  values: Record<string, unknown>;
61
74
  errors: Record<string, string[]>;
75
+ } | {
76
+ success: boolean;
77
+ error: string;
78
+ values?: undefined;
62
79
  } | {
63
80
  success: boolean;
64
81
  error?: undefined;
@@ -70,10 +87,10 @@ export declare function createCrudActions(dp: DataProvider, resource: ResourceDe
70
87
  }>;
71
88
  delete: ({ request }: RequestEvent) => Promise<{
72
89
  success: boolean;
73
- error?: undefined;
90
+ error: string;
74
91
  } | {
75
92
  success: boolean;
76
- error: string;
93
+ error?: undefined;
77
94
  }>;
78
95
  };
79
96
  /**
@@ -85,15 +102,47 @@ export declare function createAuthGuard(authProvider: AuthProvider, loginPath?:
85
102
  resolve: (event: RequestEvent) => Promise<Response>;
86
103
  }) => Promise<Response>;
87
104
  /**
88
- * Creates login/logout form actions using AuthProvider.
105
+ * Creates the form actions used by all exported Lite authentication pages.
89
106
  */
90
107
  export declare function createAuthActions(authProvider: AuthProvider): {
91
- login: ({ request, cookies }: RequestEvent) => Promise<{
108
+ login: ({ request, cookies, url }: RequestEvent) => Promise<{
92
109
  success: boolean;
93
110
  error: string;
94
111
  }>;
95
112
  logout: ({ cookies }: RequestEvent) => Promise<{
96
113
  success: boolean;
114
+ error: string;
115
+ } | {
116
+ success: boolean;
117
+ error?: undefined;
118
+ }>;
119
+ register: ({ request }: RequestEvent) => Promise<{
120
+ success: boolean;
121
+ error: string;
122
+ } | {
123
+ success: boolean;
124
+ error?: undefined;
125
+ }>;
126
+ forgot_password: ({ request }: RequestEvent) => Promise<{
127
+ success: boolean;
128
+ error: string;
129
+ } | {
130
+ success: boolean;
131
+ error?: undefined;
132
+ }>;
133
+ update_password: ({ request }: RequestEvent) => Promise<{
134
+ success: boolean;
135
+ error: string;
136
+ } | {
137
+ success: boolean;
138
+ error?: undefined;
139
+ }>;
140
+ update_profile: ({ request }: RequestEvent) => Promise<{
141
+ success: boolean;
142
+ error: string;
143
+ } | {
144
+ success: boolean;
145
+ error?: undefined;
97
146
  }>;
98
147
  };
99
148
  /**