includio-cms 0.5.2 → 0.5.5

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 (132) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/ROADMAP.md +29 -0
  3. package/dist/admin/api/rest/handler.d.ts +7 -0
  4. package/dist/admin/api/rest/handler.js +116 -0
  5. package/dist/admin/api/rest/middleware/apiKey.d.ts +6 -0
  6. package/dist/admin/api/rest/middleware/apiKey.js +45 -0
  7. package/dist/admin/api/rest/routes/collections.d.ts +5 -0
  8. package/dist/admin/api/rest/routes/collections.js +104 -0
  9. package/dist/admin/api/rest/routes/entries.d.ts +2 -0
  10. package/dist/admin/api/rest/routes/entries.js +37 -0
  11. package/dist/admin/api/rest/routes/languages.d.ts +1 -0
  12. package/dist/admin/api/rest/routes/languages.js +5 -0
  13. package/dist/admin/api/rest/routes/schema.d.ts +2 -0
  14. package/dist/admin/api/rest/routes/schema.js +78 -0
  15. package/dist/admin/api/rest/routes/singletons.d.ts +3 -0
  16. package/dist/admin/api/rest/routes/singletons.js +60 -0
  17. package/dist/admin/auth-client.d.ts +7 -7
  18. package/dist/admin/client/collection/collection-entries.svelte +56 -5
  19. package/dist/admin/client/collection/data-table.svelte +127 -18
  20. package/dist/admin/client/collection/data-table.svelte.d.ts +2 -0
  21. package/dist/admin/client/entry/entry-form.svelte +1 -0
  22. package/dist/admin/client/entry/entry.svelte +130 -123
  23. package/dist/admin/client/entry/hybrid/hybrid-preview.svelte +92 -9
  24. package/dist/admin/components/fields/blocks-field.svelte +142 -112
  25. package/dist/admin/components/fields/blocks-field.svelte.d.ts +10 -30
  26. package/dist/admin/components/fields/boolean-field.svelte +28 -38
  27. package/dist/admin/components/fields/boolean-field.svelte.d.ts +5 -27
  28. package/dist/admin/components/fields/checkboxes-field.svelte +12 -24
  29. package/dist/admin/components/fields/checkboxes-field.svelte.d.ts +5 -27
  30. package/dist/admin/components/fields/content-field.svelte +4 -17
  31. package/dist/admin/components/fields/content-field.svelte.d.ts +5 -27
  32. package/dist/admin/components/fields/date-field.svelte +8 -21
  33. package/dist/admin/components/fields/date-field.svelte.d.ts +5 -27
  34. package/dist/admin/components/fields/datetime-field.svelte +8 -21
  35. package/dist/admin/components/fields/datetime-field.svelte.d.ts +5 -27
  36. package/dist/admin/components/fields/field-renderer.svelte +32 -19
  37. package/dist/admin/components/fields/field-renderer.svelte.d.ts +1 -1
  38. package/dist/admin/components/fields/field-value-bridge.svelte +21 -0
  39. package/dist/admin/components/fields/field-value-bridge.svelte.d.ts +31 -0
  40. package/dist/admin/components/fields/fields-form.svelte +13 -10
  41. package/dist/admin/components/fields/file-field.svelte +12 -27
  42. package/dist/admin/components/fields/file-field.svelte.d.ts +5 -27
  43. package/dist/admin/components/fields/image-field.svelte +13 -28
  44. package/dist/admin/components/fields/image-field.svelte.d.ts +5 -27
  45. package/dist/admin/components/fields/media-field.svelte +15 -30
  46. package/dist/admin/components/fields/media-field.svelte.d.ts +5 -27
  47. package/dist/admin/components/fields/number-field.svelte +6 -20
  48. package/dist/admin/components/fields/number-field.svelte.d.ts +5 -27
  49. package/dist/admin/components/fields/object-field.svelte +26 -29
  50. package/dist/admin/components/fields/object-field.svelte.d.ts +11 -31
  51. package/dist/admin/components/fields/radio-field.svelte +8 -20
  52. package/dist/admin/components/fields/radio-field.svelte.d.ts +5 -27
  53. package/dist/admin/components/fields/relation-field.svelte +28 -40
  54. package/dist/admin/components/fields/relation-field.svelte.d.ts +5 -27
  55. package/dist/admin/components/fields/richtext-field.svelte +4 -17
  56. package/dist/admin/components/fields/richtext-field.svelte.d.ts +5 -27
  57. package/dist/admin/components/fields/select-field.svelte +14 -28
  58. package/dist/admin/components/fields/select-field.svelte.d.ts +5 -27
  59. package/dist/admin/components/fields/seo-field.svelte +5 -12
  60. package/dist/admin/components/fields/seo-field.svelte.d.ts +8 -28
  61. package/dist/admin/components/fields/simple-array-field.svelte +29 -42
  62. package/dist/admin/components/fields/simple-array-field.svelte.d.ts +5 -27
  63. package/dist/admin/components/fields/slug-field.svelte +6 -11
  64. package/dist/admin/components/fields/slug-field.svelte.d.ts +6 -26
  65. package/dist/admin/components/fields/text-field-wrapper.svelte +22 -40
  66. package/dist/admin/components/fields/text-field.svelte +7 -19
  67. package/dist/admin/components/fields/text-field.svelte.d.ts +5 -27
  68. package/dist/admin/components/fields/url-field-wrapper.svelte +8 -3
  69. package/dist/admin/components/fields/url-field.svelte +294 -128
  70. package/dist/admin/components/fields/url-field.svelte.d.ts +5 -27
  71. package/dist/admin/components/layout/layout-renderer.svelte +8 -6
  72. package/dist/admin/components/layout/nav-collections.svelte +2 -1
  73. package/dist/admin/components/layout/nav-forms.svelte +2 -1
  74. package/dist/admin/components/layout/nav-singletons.svelte +2 -1
  75. package/dist/admin/components/tiptap/InlineBlockNodeView.svelte +221 -31
  76. package/dist/admin/components/tiptap/content-editor.svelte +13 -2
  77. package/dist/admin/components/tiptap/inline-block-node.d.ts +1 -0
  78. package/dist/admin/components/tiptap/inline-block-node.js +18 -1
  79. package/dist/admin/components/tiptap/slash-command.js +2 -3
  80. package/dist/admin/components/tiptap/standalone-form.d.ts +7 -0
  81. package/dist/admin/components/tiptap/standalone-form.js +31 -0
  82. package/dist/admin/components/tiptap/tiptap-editor.svelte +7 -0
  83. package/dist/admin/remote/entry.remote.d.ts +9 -1
  84. package/dist/admin/remote/entry.remote.js +30 -2
  85. package/dist/admin/styles/admin.css +10 -0
  86. package/dist/admin/utils/fieldCondition.d.ts +6 -0
  87. package/dist/admin/utils/fieldCondition.js +20 -0
  88. package/dist/cli/scaffold/admin.js +8 -0
  89. package/dist/components/ui/switch/index.d.ts +2 -0
  90. package/dist/components/ui/switch/index.js +4 -0
  91. package/dist/components/ui/switch/switch.svelte +26 -0
  92. package/dist/components/ui/switch/switch.svelte.d.ts +4 -0
  93. package/dist/core/cms.d.ts +2 -1
  94. package/dist/core/cms.js +2 -0
  95. package/dist/core/fields/fieldSchemaToTs.js +15 -3
  96. package/dist/core/fields/formFieldSchemaToTs.js +22 -6
  97. package/dist/core/fields/urlUtils.d.ts +14 -0
  98. package/dist/core/fields/urlUtils.js +21 -0
  99. package/dist/core/server/entries/operations/get.js +2 -1
  100. package/dist/core/server/entries/operations/update.d.ts +1 -0
  101. package/dist/core/server/entries/operations/update.js +5 -1
  102. package/dist/core/server/fields/populateEntry.js +43 -0
  103. package/dist/core/server/fields/resolveImageFields.js +33 -1
  104. package/dist/core/server/fields/resolveRelationFields.js +46 -0
  105. package/dist/core/server/fields/resolveRichtextLinks.js +15 -1
  106. package/dist/core/server/fields/resolveUrlFields.js +65 -0
  107. package/dist/core/server/generator/formFieldSchemaToString.js +40 -9
  108. package/dist/core/server/generator/formFields.js +2 -0
  109. package/dist/core/server/generator/generator.js +25 -1
  110. package/dist/db-postgres/schema/entry.d.ts +17 -0
  111. package/dist/db-postgres/schema/entry.js +4 -2
  112. package/dist/schemas/field/url.d.ts +2 -0
  113. package/dist/schemas/field/url.js +4 -2
  114. package/dist/server/auth.d.ts +6 -6
  115. package/dist/sveltekit/server/handle.js +1 -0
  116. package/dist/types/cms.d.ts +7 -0
  117. package/dist/types/collections.d.ts +2 -0
  118. package/dist/types/entries.d.ts +7 -1
  119. package/dist/types/fields.d.ts +9 -0
  120. package/dist/types/formFields.d.ts +15 -2
  121. package/dist/types/index.d.ts +2 -1
  122. package/dist/types/index.js +1 -0
  123. package/dist/updates/0.5.3/index.d.ts +2 -0
  124. package/dist/updates/0.5.3/index.js +19 -0
  125. package/dist/updates/0.5.4/index.d.ts +2 -0
  126. package/dist/updates/0.5.4/index.js +15 -0
  127. package/dist/updates/0.5.5/index.d.ts +2 -0
  128. package/dist/updates/0.5.5/index.js +20 -0
  129. package/dist/updates/index.js +4 -1
  130. package/package.json +7 -1
  131. package/dist/admin/components/fields/standalone-field-renderer.svelte +0 -148
  132. package/dist/admin/components/fields/standalone-field-renderer.svelte.d.ts +0 -9
package/CHANGELOG.md CHANGED
@@ -3,6 +3,66 @@
3
3
  All notable changes to includio-cms are documented here.
4
4
  Generated from `src/lib/updates/` — do not edit manually.
5
5
 
6
+ ## 0.5.5 — 2026-03-04
7
+
8
+ REST API with API key auth, orderable collection fixes
9
+
10
+ ### Added
11
+ - REST API with API key authentication — CRUD for collections, singletons, entries, schema introspection, language listing
12
+ - API key configuration in CMS config (`apiKeys` array)
13
+ - getEntries supports `orderBy` parameter (column + direction)
14
+ - Admin scaffold generates REST API catch-all route
15
+
16
+ ### Fixed
17
+ - Orderable collections: DnD rows use proper `<tr>` element instead of `<div>` wrapper
18
+ - Reorder buttons always visible (not sr-only) for better discoverability
19
+ - Selection enabled for orderable collections
20
+ - Relation field respects orderable collection sort order
21
+ - Nav active state: exact match prevents false highlights on similarly-named routes
22
+
23
+ ### Notes
24
+
25
+ Add `apiKeys: [{ key: "your-secret", name: "My App" }]` to CMS config. REST API available at `/admin/api/rest/`.
26
+
27
+ ## 0.5.4 — 2026-03-04
28
+
29
+ Collection ordering (DnD), custom list columns
30
+
31
+ ### Added
32
+ - Orderable collections: DnD reordering of entries in admin list with keyboard alternative (WCAG 2.1)
33
+ - Custom list columns: display arbitrary entry fields as columns in collection list
34
+ - sortOrder column on entries table for persistent manual ordering
35
+ - reorderEntriesCommand remote for bulk sort order updates
36
+
37
+ ### Migration
38
+
39
+ ```sql
40
+ ALTER TABLE entry ADD COLUMN sort_order INTEGER;
41
+ ```
42
+
43
+ ### Notes
44
+
45
+ Add `orderable: true` to collections that need manual ordering. Add `listColumns: ["fieldSlug"]` to show fields in the entry list.
46
+
47
+ ## 0.5.3 — 2026-03-04
48
+
49
+ Form fields, inline blocks, hybrid preview, field components refactor
50
+
51
+ ### Added
52
+ - Form fields: select type, minLength/maxLength, errorMessage, remote commands
53
+ - Conditional field visibility (showWhen)
54
+ - Hybrid preview: device frames, container queries
55
+ - TipTap placeholder extension + Notion-style styles
56
+ - Server-side field resolution for inline blocks
57
+ - Inline blocks: standalone form, full field rendering, collapse UI
58
+ - URL field: rel attribute, external auto-detect, UI redesign
59
+ - Switch UI component (bits-ui)
60
+ - Boolean field: Switch toggle zamiast checkbox
61
+ - Storybook stories for all field types
62
+
63
+ ### Fixed
64
+ - Hybrid preview layout fix
65
+
6
66
  ## 0.5.2 — 2026-02-25
7
67
 
8
68
  Update system: split migration into sql + notes
package/ROADMAP.md CHANGED
@@ -76,6 +76,35 @@
76
76
 
77
77
  - [x] `[fix]` `[P1]` Split `migration` field into `sql` + `notes` — fixes CLI crash on text descriptions
78
78
 
79
+ ## 0.5.3 — Form fields, inline blocks, hybrid preview
80
+
81
+ - [x] `[feature]` `[P1]` Form fields: select type, minLength/maxLength, errorMessage, remote commands
82
+ - [x] `[feature]` `[P1]` Conditional field visibility (showWhen)
83
+ - [x] `[feature]` `[P1]` Hybrid preview: device frames, container queries
84
+ - [x] `[feature]` `[P2]` TipTap placeholder extension + Notion-style styles
85
+ - [x] `[feature]` `[P1]` Server-side field resolution for inline blocks
86
+ - [x] `[feature]` `[P1]` Inline blocks: standalone form, field rendering, collapse UI
87
+ - [x] `[feature]` `[P2]` URL field: rel attribute, external auto-detect, UI redesign
88
+ - [x] `[feature]` `[P2]` Switch UI component + boolean field toggle
89
+ - [x] `[chore]` `[P2]` Storybook stories for all field types
90
+ - [x] `[chore]` `[P1]` Field components refactor: bindable value props
91
+
92
+ ## 0.5.4 — Collection ordering & list columns
93
+
94
+ - [x] `[feature]` `[P1]` Orderable collections: DnD reordering with keyboard alternative (WCAG 2.1) <!-- files: src/lib/admin/client/collection/data-table.svelte, src/lib/admin/client/collection/collection-entries.svelte -->
95
+ - [x] `[feature]` `[P1]` Custom list columns: display entry fields as columns in collection list <!-- files: src/lib/admin/client/collection/collection-entries.svelte -->
96
+ - [x] `[feature]` `[P1]` `sortOrder` column + `reorderEntriesCommand` remote <!-- files: src/lib/db-postgres/schema/entry.ts, src/lib/admin/remote/entry.remote.ts -->
97
+
98
+ ## 0.5.5 — REST API & orderable fixes
99
+
100
+ - [x] `[feature]` `[P1]` REST API with API key authentication — CRUD for collections, singletons, entries, schema, languages <!-- files: src/lib/admin/api/rest/ -->
101
+ - [x] `[feature]` `[P1]` API key configuration in CMS config (`apiKeys`) <!-- files: src/lib/types/cms.ts, src/lib/core/cms.ts -->
102
+ - [x] `[feature]` `[P2]` getEntries `orderBy` parameter support <!-- files: src/lib/types/entries.ts, src/lib/core/server/entries/operations/get.ts -->
103
+ - [x] `[fix]` `[P1]` Orderable collections: proper `<tr>` markup, visible reorder buttons, selection enabled <!-- files: src/lib/admin/client/collection/data-table.svelte -->
104
+ - [x] `[fix]` `[P1]` Relation field respects orderable collection sort order <!-- files: src/lib/admin/components/fields/relation-field.svelte -->
105
+ - [x] `[fix]` `[P1]` Nav active state exact match — prevents false highlights <!-- files: src/lib/admin/components/layout/nav-*.svelte -->
106
+ - [x] `[chore]` `[P2]` Admin scaffold generates REST API route <!-- files: src/lib/cli/scaffold/admin.ts -->
107
+
79
108
  ## 0.6.0 — Plugin system _(deferred from 0.2.0)_
80
109
 
81
110
  - [ ] `[feature]` `[P0]` Wire plugin hooks into CRUD operations (before/afterCreate, Update, Delete) <!-- files: src/lib/types/plugins.ts, src/lib/core/server/entries/operations/ -->
@@ -0,0 +1,7 @@
1
+ import { type RequestHandler } from '@sveltejs/kit';
2
+ export declare function createRestApiHandler(): {
3
+ GET: RequestHandler;
4
+ POST: RequestHandler;
5
+ PUT: RequestHandler;
6
+ DELETE: RequestHandler;
7
+ };
@@ -0,0 +1,116 @@
1
+ import { json } from '@sveltejs/kit';
2
+ import { authenticateApiKey } from './middleware/apiKey.js';
3
+ import * as schemaRoutes from './routes/schema.js';
4
+ import * as languagesRoutes from './routes/languages.js';
5
+ import * as collectionsRoutes from './routes/collections.js';
6
+ import * as singletonsRoutes from './routes/singletons.js';
7
+ import * as entriesRoutes from './routes/entries.js';
8
+ function matchRoute(method, path) {
9
+ // Schema routes
10
+ if (method === 'GET' && path === 'schema') {
11
+ return { handler: 'schema', params: [] };
12
+ }
13
+ if (method === 'GET' && path.startsWith('schema/')) {
14
+ return { handler: 'schema', params: [] };
15
+ }
16
+ // Languages
17
+ if (method === 'GET' && path === 'languages') {
18
+ return { handler: 'languages', params: [] };
19
+ }
20
+ // Entries lifecycle: POST /entries/:id/:action
21
+ const entriesMatch = path.match(/^entries\/([^/]+)\/(publish|unpublish|archive|unarchive)$/);
22
+ if (method === 'POST' && entriesMatch) {
23
+ return { handler: 'entries', params: [entriesMatch[1], entriesMatch[2]] };
24
+ }
25
+ // Collections: /collections/:slug/:id?
26
+ const collectionWithId = path.match(/^collections\/([^/]+)\/([^/]+)$/);
27
+ if (collectionWithId) {
28
+ return { handler: 'collections', params: [collectionWithId[1], collectionWithId[2]] };
29
+ }
30
+ const collectionOnly = path.match(/^collections\/([^/]+)$/);
31
+ if (collectionOnly) {
32
+ return { handler: 'collections', params: [collectionOnly[1]] };
33
+ }
34
+ // Singletons: /singletons/:slug
35
+ const singletonMatch = path.match(/^singletons\/([^/]+)$/);
36
+ if (singletonMatch) {
37
+ return { handler: 'singletons', params: [singletonMatch[1]] };
38
+ }
39
+ return null;
40
+ }
41
+ export function createRestApiHandler() {
42
+ function handle(method) {
43
+ return async (event) => {
44
+ // Authenticate
45
+ const apiKey = authenticateApiKey(event);
46
+ if (!apiKey) {
47
+ return json({ error: 'Unauthorized — invalid or missing API key' }, { status: 401 });
48
+ }
49
+ const restPath = event.params.restPath || '';
50
+ const route = matchRoute(method, restPath);
51
+ if (!route) {
52
+ return json({ error: 'Not found' }, { status: 404 });
53
+ }
54
+ try {
55
+ switch (route.handler) {
56
+ case 'schema': {
57
+ // Pass restPath minus 'schema/' prefix
58
+ const schemaPath = restPath === 'schema' ? '' : restPath.slice(7);
59
+ event.params.restPath = schemaPath;
60
+ return await schemaRoutes.GET(event);
61
+ }
62
+ case 'languages':
63
+ return await languagesRoutes.GET();
64
+ case 'collections': {
65
+ const [slug, id] = route.params;
66
+ switch (method) {
67
+ case 'GET':
68
+ return await collectionsRoutes.GET(event, slug, id);
69
+ case 'POST':
70
+ return await collectionsRoutes.POST(event, slug);
71
+ case 'PUT':
72
+ if (!id)
73
+ return json({ error: 'Entry ID required' }, { status: 400 });
74
+ return await collectionsRoutes.PUT(event, slug, id);
75
+ case 'DELETE':
76
+ if (!id)
77
+ return json({ error: 'Entry ID required' }, { status: 400 });
78
+ return await collectionsRoutes.DELETE(event, slug, id);
79
+ }
80
+ break;
81
+ }
82
+ case 'singletons': {
83
+ const [slug] = route.params;
84
+ switch (method) {
85
+ case 'GET':
86
+ return await singletonsRoutes.GET(event, slug);
87
+ case 'PUT':
88
+ return await singletonsRoutes.PUT(event, slug);
89
+ default:
90
+ return json({ error: 'Method not allowed' }, { status: 405 });
91
+ }
92
+ }
93
+ case 'entries': {
94
+ const [id, action] = route.params;
95
+ return await entriesRoutes.POST(event, id, action);
96
+ }
97
+ }
98
+ }
99
+ catch (err) {
100
+ const message = err instanceof Error ? err.message : 'Internal server error';
101
+ const status = message === 'Unauthorized' ? 401
102
+ : message === 'Forbidden' ? 403
103
+ : message.includes('not found') || message.includes('Not found') ? 404
104
+ : 500;
105
+ return json({ error: message }, { status });
106
+ }
107
+ return json({ error: 'Not found' }, { status: 404 });
108
+ };
109
+ }
110
+ return {
111
+ GET: handle('GET'),
112
+ POST: handle('POST'),
113
+ PUT: handle('PUT'),
114
+ DELETE: handle('DELETE')
115
+ };
116
+ }
@@ -0,0 +1,6 @@
1
+ import type { RequestEvent } from '@sveltejs/kit';
2
+ import type { ApiKeyConfig } from '../../../../types/cms.js';
3
+ export declare function extractApiKey(event: RequestEvent): string | null;
4
+ export declare function validateApiKey(key: string): ApiKeyConfig | null;
5
+ export declare function setSyntheticUser(event: RequestEvent, apiKey: ApiKeyConfig): void;
6
+ export declare function authenticateApiKey(event: RequestEvent): ApiKeyConfig | null;
@@ -0,0 +1,45 @@
1
+ import { getCMS } from '../../../../core/cms.js';
2
+ export function extractApiKey(event) {
3
+ const authHeader = event.request.headers.get('authorization');
4
+ if (authHeader?.startsWith('Bearer ')) {
5
+ return authHeader.slice(7);
6
+ }
7
+ return event.request.headers.get('x-api-key');
8
+ }
9
+ export function validateApiKey(key) {
10
+ const cms = getCMS();
11
+ return cms.apiKeys.find((k) => k.key === key) ?? null;
12
+ }
13
+ export function setSyntheticUser(event, apiKey) {
14
+ const name = apiKey.name || 'api';
15
+ event.locals.user = {
16
+ id: `api:${name}`,
17
+ name,
18
+ role: apiKey.role || 'admin',
19
+ email: `${name}@api.local`,
20
+ emailVerified: true,
21
+ createdAt: new Date(),
22
+ updatedAt: new Date(),
23
+ image: null
24
+ };
25
+ event.locals.session = {
26
+ id: 'api-session',
27
+ userId: `api:${name}`,
28
+ expiresAt: new Date(Date.now() + 86400000),
29
+ token: '',
30
+ createdAt: new Date(),
31
+ updatedAt: new Date(),
32
+ ipAddress: null,
33
+ userAgent: null
34
+ };
35
+ }
36
+ export function authenticateApiKey(event) {
37
+ const key = extractApiKey(event);
38
+ if (!key)
39
+ return null;
40
+ const apiKey = validateApiKey(key);
41
+ if (!apiKey)
42
+ return null;
43
+ setSyntheticUser(event, apiKey);
44
+ return apiKey;
45
+ }
@@ -0,0 +1,5 @@
1
+ import { type RequestEvent } from '@sveltejs/kit';
2
+ export declare function GET(event: RequestEvent, slug: string, id?: string): Promise<Response>;
3
+ export declare function POST(event: RequestEvent, slug: string): Promise<Response>;
4
+ export declare function PUT(event: RequestEvent, slug: string, id: string): Promise<Response>;
5
+ export declare function DELETE(_event: RequestEvent, slug: string, id: string): Promise<Response>;
@@ -0,0 +1,104 @@
1
+ import { json } from '@sveltejs/kit';
2
+ import { getCMS } from '../../../../core/cms.js';
3
+ import { getRawEntries, getRawEntry, countRawEntries } from '../../../../core/server/entries/operations/get.js';
4
+ import { createEntry } from '../../../../core/server/entries/operations/create.js';
5
+ import { upsertDraftVersion } from '../../../../core/server/entries/operations/update.js';
6
+ import { deleteEntry } from '../../../../core/server/entries/operations/delete.js';
7
+ export async function GET(event, slug, id) {
8
+ const cms = getCMS();
9
+ if (!cms.collections[slug]) {
10
+ return json({ error: `Collection "${slug}" not found` }, { status: 404 });
11
+ }
12
+ // GET /collections/:slug/:id — single entry
13
+ if (id) {
14
+ const entry = await getRawEntry({ id, slug, includeArchived: true });
15
+ if (!entry) {
16
+ return json({ error: 'Entry not found' }, { status: 404 });
17
+ }
18
+ return json({
19
+ id: entry.id,
20
+ slug: entry.slug,
21
+ type: entry.type,
22
+ createdAt: entry.createdAt,
23
+ updatedAt: entry.updatedAt,
24
+ publishedAt: entry.publishedAt,
25
+ archivedAt: entry.archivedAt,
26
+ availableLocales: entry.availableLocales,
27
+ sortOrder: entry.sortOrder,
28
+ draftData: entry.draftVersion?.data ?? null,
29
+ publishedData: entry.publishedVersion?.data ?? null,
30
+ draftVersionId: entry.draftVersion?.id ?? null,
31
+ publishedVersionId: entry.publishedVersion?.id ?? null
32
+ });
33
+ }
34
+ // GET /collections/:slug — list
35
+ const url = event.url;
36
+ const status = url.searchParams.get('status') || undefined;
37
+ const limit = parseInt(url.searchParams.get('limit') || '50', 10);
38
+ const offset = parseInt(url.searchParams.get('offset') || '0', 10);
39
+ const includeArchived = status === 'archived';
40
+ const onlyArchived = status === 'archived';
41
+ const [entries, total] = await Promise.all([
42
+ getRawEntries({ slug, limit, offset, includeArchived, onlyArchived }),
43
+ countRawEntries({ slug, includeArchived, onlyArchived })
44
+ ]);
45
+ const items = entries.map((entry) => ({
46
+ id: entry.id,
47
+ slug: entry.slug,
48
+ type: entry.type,
49
+ createdAt: entry.createdAt,
50
+ updatedAt: entry.updatedAt,
51
+ publishedAt: entry.publishedAt,
52
+ archivedAt: entry.archivedAt,
53
+ availableLocales: entry.availableLocales,
54
+ sortOrder: entry.sortOrder,
55
+ draftData: entry.draftVersion?.data ?? null,
56
+ publishedData: entry.publishedVersion?.data ?? null
57
+ }));
58
+ return json({ items, total, limit, offset });
59
+ }
60
+ export async function POST(event, slug) {
61
+ const cms = getCMS();
62
+ if (!cms.collections[slug]) {
63
+ return json({ error: `Collection "${slug}" not found` }, { status: 404 });
64
+ }
65
+ const body = await event.request.json().catch(() => null);
66
+ const entry = await createEntry({ slug, type: 'collection' });
67
+ // If data provided, save as draft
68
+ if (body?.data && typeof body.data === 'object') {
69
+ await upsertDraftVersion(entry.id, body.data, { skipValidation: true });
70
+ }
71
+ return json({ id: entry.id, slug: entry.slug }, { status: 201 });
72
+ }
73
+ export async function PUT(event, slug, id) {
74
+ const cms = getCMS();
75
+ if (!cms.collections[slug]) {
76
+ return json({ error: `Collection "${slug}" not found` }, { status: 404 });
77
+ }
78
+ const entry = await getRawEntry({ id, slug });
79
+ if (!entry) {
80
+ return json({ error: 'Entry not found' }, { status: 404 });
81
+ }
82
+ const body = await event.request.json().catch(() => null);
83
+ if (!body?.data || typeof body.data !== 'object') {
84
+ return json({ error: 'Request body must contain "data" object' }, { status: 400 });
85
+ }
86
+ const version = await upsertDraftVersion(entry.id, body.data, { skipValidation: true });
87
+ return json({
88
+ id: entry.id,
89
+ versionId: version.id,
90
+ versionNumber: version.versionNumber
91
+ });
92
+ }
93
+ export async function DELETE(_event, slug, id) {
94
+ const cms = getCMS();
95
+ if (!cms.collections[slug]) {
96
+ return json({ error: `Collection "${slug}" not found` }, { status: 404 });
97
+ }
98
+ const entry = await getRawEntry({ id, slug, includeArchived: true });
99
+ if (!entry) {
100
+ return json({ error: 'Entry not found' }, { status: 404 });
101
+ }
102
+ await deleteEntry(entry.id);
103
+ return json({ success: true });
104
+ }
@@ -0,0 +1,2 @@
1
+ import { type RequestEvent } from '@sveltejs/kit';
2
+ export declare function POST(_event: RequestEvent, id: string, action: string): Promise<Response>;
@@ -0,0 +1,37 @@
1
+ import { json } from '@sveltejs/kit';
2
+ import { requireAuth } from '../../../remote/middleware/auth.js';
3
+ import { getRawEntryOrThrow } from '../../../../core/server/entries/operations/get.js';
4
+ import { updateEntry, unpublishEntry } from '../../../../core/server/entries/operations/update.js';
5
+ export async function POST(_event, id, action) {
6
+ const { user } = requireAuth();
7
+ switch (action) {
8
+ case 'publish': {
9
+ const entry = await getRawEntryOrThrow({ id, includeArchived: false });
10
+ const draftVersion = entry.draftVersion;
11
+ if (!draftVersion) {
12
+ return json({ error: 'No draft version to publish' }, { status: 400 });
13
+ }
14
+ await updateEntry(id, {
15
+ publishedVersionId: draftVersion.id,
16
+ publishedAt: new Date(),
17
+ publishedBy: user.id,
18
+ archivedAt: null
19
+ });
20
+ return json({ success: true, publishedVersionId: draftVersion.id });
21
+ }
22
+ case 'unpublish': {
23
+ await unpublishEntry(id);
24
+ return json({ success: true });
25
+ }
26
+ case 'archive': {
27
+ await updateEntry(id, { archivedAt: new Date() });
28
+ return json({ success: true });
29
+ }
30
+ case 'unarchive': {
31
+ await updateEntry(id, { archivedAt: null });
32
+ return json({ success: true });
33
+ }
34
+ default:
35
+ return json({ error: `Unknown action "${action}"` }, { status: 400 });
36
+ }
37
+ }
@@ -0,0 +1 @@
1
+ export declare function GET(): Promise<Response>;
@@ -0,0 +1,5 @@
1
+ import { json } from '@sveltejs/kit';
2
+ import { getCMS } from '../../../../core/cms.js';
3
+ export async function GET() {
4
+ return json({ languages: getCMS().languages });
5
+ }
@@ -0,0 +1,2 @@
1
+ import { type RequestEvent } from '@sveltejs/kit';
2
+ export declare function GET(event: RequestEvent): Promise<Response>;
@@ -0,0 +1,78 @@
1
+ import { json } from '@sveltejs/kit';
2
+ import { getCMS } from '../../../../core/cms.js';
3
+ import { getFieldsFromConfig } from '../../../../core/fields/layoutUtils.js';
4
+ function serializeField(field) {
5
+ const { ...rest } = field;
6
+ // Remove non-serializable properties
7
+ delete rest.icon;
8
+ return rest;
9
+ }
10
+ function serializeFields(fields) {
11
+ return fields.map(serializeField);
12
+ }
13
+ export async function GET(event) {
14
+ const cms = getCMS();
15
+ const path = event.params.restPath || '';
16
+ // GET /schema/collections/:slug
17
+ const collectionMatch = path.match(/^collections\/([^/]+)$/);
18
+ if (collectionMatch) {
19
+ const slug = collectionMatch[1];
20
+ const collection = cms.collections[slug];
21
+ if (!collection) {
22
+ return json({ error: `Collection "${slug}" not found` }, { status: 404 });
23
+ }
24
+ return json({
25
+ slug: collection.slug,
26
+ type: collection.type,
27
+ labels: collection.labels,
28
+ fields: serializeFields(getFieldsFromConfig(collection)),
29
+ orderable: collection.orderable,
30
+ listColumns: collection.listColumns,
31
+ slugField: collection.slugField
32
+ });
33
+ }
34
+ // GET /schema/singletons/:slug
35
+ const singletonMatch = path.match(/^singletons\/([^/]+)$/);
36
+ if (singletonMatch) {
37
+ const slug = singletonMatch[1];
38
+ const single = cms.singles[slug];
39
+ if (!single) {
40
+ return json({ error: `Singleton "${slug}" not found` }, { status: 404 });
41
+ }
42
+ return json({
43
+ slug: single.slug,
44
+ type: single.type,
45
+ label: single.label,
46
+ fields: serializeFields(getFieldsFromConfig(single))
47
+ });
48
+ }
49
+ // GET /schema — full schema overview
50
+ if (path === '') {
51
+ const collections = Object.values(cms.collections).map((c) => ({
52
+ slug: c.slug,
53
+ type: c.type,
54
+ labels: c.labels,
55
+ fields: serializeFields(getFieldsFromConfig(c)),
56
+ orderable: c.orderable,
57
+ listColumns: c.listColumns,
58
+ slugField: c.slugField
59
+ }));
60
+ const singletons = Object.values(cms.singles).map((s) => ({
61
+ slug: s.slug,
62
+ type: s.type,
63
+ label: s.label,
64
+ fields: serializeFields(getFieldsFromConfig(s))
65
+ }));
66
+ const forms = Object.values(cms.forms).map((f) => ({
67
+ slug: f.slug,
68
+ fields: f.fields
69
+ }));
70
+ return json({
71
+ collections,
72
+ singletons,
73
+ forms,
74
+ languages: cms.languages
75
+ });
76
+ }
77
+ return json({ error: 'Not found' }, { status: 404 });
78
+ }
@@ -0,0 +1,3 @@
1
+ import { type RequestEvent } from '@sveltejs/kit';
2
+ export declare function GET(_event: RequestEvent, slug: string): Promise<Response>;
3
+ export declare function PUT(event: RequestEvent, slug: string): Promise<Response>;
@@ -0,0 +1,60 @@
1
+ import { json } from '@sveltejs/kit';
2
+ import { getCMS } from '../../../../core/cms.js';
3
+ import { getRawEntries } from '../../../../core/server/entries/operations/get.js';
4
+ import { createEntry } from '../../../../core/server/entries/operations/create.js';
5
+ import { upsertDraftVersion } from '../../../../core/server/entries/operations/update.js';
6
+ export async function GET(_event, slug) {
7
+ const cms = getCMS();
8
+ if (!cms.singles[slug]) {
9
+ return json({ error: `Singleton "${slug}" not found` }, { status: 404 });
10
+ }
11
+ const entries = await getRawEntries({ slug });
12
+ const entry = entries[0];
13
+ if (!entry) {
14
+ return json({
15
+ slug,
16
+ type: 'single',
17
+ draftData: null,
18
+ publishedData: null
19
+ });
20
+ }
21
+ return json({
22
+ id: entry.id,
23
+ slug: entry.slug,
24
+ type: entry.type,
25
+ createdAt: entry.createdAt,
26
+ updatedAt: entry.updatedAt,
27
+ publishedAt: entry.publishedAt,
28
+ draftData: entry.draftVersion?.data ?? null,
29
+ publishedData: entry.publishedVersion?.data ?? null,
30
+ draftVersionId: entry.draftVersion?.id ?? null,
31
+ publishedVersionId: entry.publishedVersion?.id ?? null
32
+ });
33
+ }
34
+ export async function PUT(event, slug) {
35
+ const cms = getCMS();
36
+ if (!cms.singles[slug]) {
37
+ return json({ error: `Singleton "${slug}" not found` }, { status: 404 });
38
+ }
39
+ const body = await event.request.json().catch(() => null);
40
+ if (!body?.data || typeof body.data !== 'object') {
41
+ return json({ error: 'Request body must contain "data" object' }, { status: 400 });
42
+ }
43
+ // Find or create singleton entry
44
+ const entries = await getRawEntries({ slug });
45
+ let entry = entries[0];
46
+ if (!entry) {
47
+ const dbEntry = await createEntry({ slug, type: 'single' });
48
+ const created = await getRawEntries({ slug });
49
+ entry = created[0];
50
+ if (!entry) {
51
+ return json({ error: 'Failed to create singleton entry' }, { status: 500 });
52
+ }
53
+ }
54
+ const version = await upsertDraftVersion(entry.id, body.data, { skipValidation: true });
55
+ return json({
56
+ id: entry.id,
57
+ versionId: version.id,
58
+ versionNumber: version.versionNumber
59
+ });
60
+ }
@@ -45,7 +45,7 @@ export declare const authClient: {
45
45
  }) | undefined;
46
46
  body?: (Partial<{
47
47
  userId: string;
48
- role: "user" | "admin" | ("user" | "admin")[];
48
+ role: "admin" | "user" | ("admin" | "user")[];
49
49
  }> & Record<string, any>) | undefined;
50
50
  query?: (Partial<Record<string, any>> & Record<string, any>) | undefined;
51
51
  params?: Record<string, any> | undefined;
@@ -58,7 +58,7 @@ export declare const authClient: {
58
58
  disableValidation?: boolean | undefined;
59
59
  }>(data_0: import("better-auth", { with: { "resolution-mode": "require" } }).Prettify<{
60
60
  userId: string;
61
- role: "user" | "admin" | ("user" | "admin")[];
61
+ role: "admin" | "user" | ("admin" | "user")[];
62
62
  } & {
63
63
  fetchOptions?: FetchOptions | undefined;
64
64
  }>, data_1?: FetchOptions | undefined) => Promise<import("better-auth/svelte", { with: { "resolution-mode": "require" } }).BetterFetchResponse<{
@@ -192,7 +192,7 @@ export declare const authClient: {
192
192
  email: string;
193
193
  password: string;
194
194
  name: string;
195
- role?: "user" | "admin" | ("user" | "admin")[] | undefined;
195
+ role?: "admin" | "user" | ("admin" | "user")[] | undefined;
196
196
  data?: Record<string, any>;
197
197
  }> & Record<string, any>) | undefined;
198
198
  query?: (Partial<Record<string, any>> & Record<string, any>) | undefined;
@@ -208,7 +208,7 @@ export declare const authClient: {
208
208
  email: string;
209
209
  password: string;
210
210
  name: string;
211
- role?: "user" | "admin" | ("user" | "admin")[] | undefined;
211
+ role?: "admin" | "user" | ("admin" | "user")[] | undefined;
212
212
  data?: Record<string, any>;
213
213
  } & {
214
214
  fetchOptions?: FetchOptions | undefined;
@@ -1073,7 +1073,7 @@ export declare const authClient: {
1073
1073
  permission?: never;
1074
1074
  }) & {
1075
1075
  userId?: string;
1076
- role?: "user" | "admin" | undefined;
1076
+ role?: "admin" | "user" | undefined;
1077
1077
  }> & Record<string, any>) | undefined;
1078
1078
  query?: (Partial<Record<string, any>> & Record<string, any>) | undefined;
1079
1079
  params?: Record<string, any> | undefined;
@@ -1098,7 +1098,7 @@ export declare const authClient: {
1098
1098
  permission?: never;
1099
1099
  }) & {
1100
1100
  userId?: string;
1101
- role?: "user" | "admin" | undefined;
1101
+ role?: "admin" | "user" | undefined;
1102
1102
  }) & {
1103
1103
  fetchOptions?: FetchOptions | undefined;
1104
1104
  }>, data_1?: FetchOptions | undefined) => Promise<import("better-auth/svelte", { with: { "resolution-mode": "require" } }).BetterFetchResponse<{
@@ -3105,7 +3105,7 @@ export declare const authClient: {
3105
3105
  }, FetchOptions["throw"] extends true ? true : false>>;
3106
3106
  } & {
3107
3107
  admin: {
3108
- checkRolePermission: <R extends "user" | "admin">(data: ({
3108
+ checkRolePermission: <R extends "admin" | "user">(data: ({
3109
3109
  permission: {
3110
3110
  readonly user?: ("create" | "list" | "set-role" | "ban" | "impersonate" | "delete" | "set-password" | "get" | "update")[] | undefined;
3111
3111
  readonly session?: ("list" | "delete" | "revoke")[] | undefined;