includio-cms 0.22.0 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/API.md +1 -1
- package/CHANGELOG.md +42 -0
- package/DOCS.md +1169 -146
- package/ROADMAP.md +5 -330
- package/dist/core/server/entries/operations/get.bench.d.ts +1 -0
- package/dist/core/server/entries/operations/get.bench.js +68 -0
- package/dist/core/server/entries/operations/get.js +17 -7
- package/dist/core/server/fields/utils/imageStyles.bench.d.ts +1 -0
- package/dist/core/server/fields/utils/imageStyles.bench.js +82 -0
- package/dist/core/server/fields/utils/imageStyles.js +49 -53
- package/dist/core/server/media/operations/backgroundMaintenance.d.ts +6 -0
- package/dist/core/server/media/operations/backgroundMaintenance.js +6 -1
- package/dist/core/server/media/styles/operations/getImageStyle.d.ts +7 -0
- package/dist/core/server/media/styles/operations/getImageStyle.js +24 -0
- package/dist/db-postgres/index.d.ts +1 -1
- package/dist/db-postgres/index.js +27 -0
- package/dist/paraglide/messages/_index.d.ts +36 -3
- package/dist/paraglide/messages/_index.js +71 -3
- package/dist/paraglide/messages/en.d.ts +5 -0
- package/dist/paraglide/messages/en.js +14 -0
- package/dist/paraglide/messages/pl.d.ts +5 -0
- package/dist/paraglide/messages/pl.js +14 -0
- package/dist/types/adapters/db.d.ts +16 -0
- package/dist/types/adapters/db.js +8 -1
- package/dist/updates/0.23.0/index.d.ts +2 -0
- package/dist/updates/0.23.0/index.js +21 -0
- package/dist/updates/0.24.0/index.d.ts +2 -0
- package/dist/updates/0.24.0/index.js +20 -0
- package/dist/updates/index.js +3 -1
- package/package.json +2 -1
- package/dist/paraglide/messages/hello_world.d.ts +0 -5
- package/dist/paraglide/messages/hello_world.js +0 -33
- package/dist/paraglide/messages/login_hello.d.ts +0 -16
- package/dist/paraglide/messages/login_hello.js +0 -34
- package/dist/paraglide/messages/login_please_login.d.ts +0 -16
- package/dist/paraglide/messages/login_please_login.js +0 -34
package/ROADMAP.md
CHANGED
|
@@ -5,344 +5,19 @@
|
|
|
5
5
|
> `[P0]` critical | `[P1]` important | `[P2]` nice-to-have
|
|
6
6
|
> `<!-- files: path/to/file.ts -->` optionally linked files
|
|
7
7
|
>
|
|
8
|
-
> **Versioning:** 0.0
|
|
9
|
-
|
|
10
|
-
## 0.0.69 _(last 0.0.x)_
|
|
11
|
-
|
|
12
|
-
- [x] `[feature]` `[P1]` DnD reordering in array fields + arrayMove utility <!-- files: src/cms/fields/array-field -->
|
|
13
|
-
- [x] `[fix]` `[P1]` Nav-search: batch-fetch data before rendering Command dialog <!-- files: src/cms/nav-search -->
|
|
14
|
-
|
|
15
|
-
## 0.1.0 — Stabilization
|
|
16
|
-
|
|
17
|
-
- [x] `[fix]` `[P0]` Collection table pagination — server-side, persistent page state, archived tab <!-- files: src/lib/admin/client/collection/collection-entries.svelte, src/lib/admin/client/collection/table-pagination.svelte -->
|
|
18
|
-
- [x] `[fix]` `[P0]` Language switcher — reactive globally without reload, remove hardcoded en/pl <!-- files: src/lib/admin/state/interface-language.svelte.ts, src/lib/admin/components/layout/header-actions.svelte -->
|
|
19
|
-
- [x] `[fix]` `[P0]` User account section — fix name input, aria-pressed on prefs, clean up 2FA stub (email change deferred to 0.1.2, avatar upload skipped) <!-- files: src/lib/admin/client/account/ -->
|
|
20
|
-
- [x] `[fix]` `[P0]` Media tag counts always 0 — pass actual files to TagSidebar <!-- files: src/lib/admin/components/media/media-library.svelte, src/lib/admin/components/media/tag-sidebar.svelte -->
|
|
21
|
-
- [x] `[fix]` `[P0]` Hybrid target — don't render `data-hybrid-path` for non-logged-in users <!-- files: src/lib/sveltekit/components/hybrid-target.svelte -->
|
|
22
|
-
|
|
23
|
-
## 0.1.1 — Input integrity
|
|
24
|
-
|
|
25
|
-
- [x] `[fix]` `[P1]` Input constraints UI — HTML maxlength, character counter, pattern feedback <!-- files: src/lib/admin/components/fields/text-field.svelte, src/lib/admin/components/fields/text-field-wrapper.svelte -->
|
|
26
|
-
- [x] `[fix]` `[P1]` Array field maxItems — disable Add button when max reached <!-- files: src/lib/admin/components/fields/array-field.svelte -->
|
|
27
|
-
- [x] `[feature]` `[P1]` Array field fixed length — fixed item count, no add/remove, reorder only
|
|
28
|
-
- [x] `[feature]` `[P1]` Field constraint info display — show constraints before validation error (WCAG/ATAG) <!-- files: src/lib/admin/components/fields/text-field-wrapper.svelte, src/lib/admin/components/fields/field-renderer.svelte -->
|
|
29
|
-
|
|
30
|
-
## 0.1.2 — User management & RBAC
|
|
31
|
-
|
|
32
|
-
### Phase 1 — Core
|
|
33
|
-
|
|
34
|
-
- [x] `[feature]` `[P0]` RBAC middleware — `requireRole()`, role check w `requireAuth()` <!-- files: src/lib/admin/remote/middleware/auth.ts -->
|
|
35
|
-
- [x] `[feature]` `[P0]` Admin users page — list, search, pagination via `authClient.admin.listUsers` <!-- files: src/lib/admin/client/users/ -->
|
|
36
|
-
- [x] `[feature]` `[P0]` Create user — dialog z email/password/name/role via `authClient.admin.createUser` <!-- files: src/lib/admin/client/users/create-user-dialog.svelte -->
|
|
37
|
-
- [x] `[feature]` `[P0]` Edit user — name, email, role via `adminUpdateUser` + `setRole` <!-- files: src/lib/admin/client/users/edit-user-dialog.svelte -->
|
|
38
|
-
- [x] `[feature]` `[P0]` Delete user — confirmation dialog via `removeUser` <!-- files: src/lib/admin/client/users/ -->
|
|
39
|
-
- [x] `[feature]` `[P0]` Route/sidebar gating — ukryj Users nav + chroń `/admin/users` dla non-admin <!-- files: src/lib/admin/components/layout/nav-main.svelte, src/lib/sveltekit/server/handle.ts -->
|
|
40
|
-
- [x] `[feature]` `[P0]` First user bootstrap — pierwszy utworzony user auto-gets role `admin` <!-- files: src/lib/server/auth.ts -->
|
|
41
|
-
|
|
42
|
-
### Phase 2 — Extended
|
|
43
|
-
|
|
44
|
-
- [x] `[feature]` `[P1]` Admin session mgmt — list/revoke sesji innych userów <!-- files: src/lib/admin/client/users/user-sessions-sheet.svelte -->
|
|
45
|
-
- [x] `[feature]` `[P2]` Email invitation system — invite link generation (custom, nie w better-auth) <!-- files: src/lib/admin/remote/invite.ts -->
|
|
46
|
-
|
|
47
|
-
## 0.1.3 — Admin DX
|
|
48
|
-
|
|
49
|
-
- [x] `[feature]` `[P1]` Admin route scaffolding — CLI `includio scaffold admin`, catch-all API handler, page.params fallbacks for CollectionPage/EntryPage <!-- files: src/lib/cli/scaffold/admin.ts, src/lib/admin/api/handler.ts, src/lib/admin/client/collection/collection-page.svelte, src/lib/admin/client/entry/entry-page.svelte -->
|
|
50
|
-
|
|
51
|
-
## 0.1.4 — Build & reactivity fixes
|
|
52
|
-
|
|
53
|
-
- [x] `[fix]` `[P0]` Prepack: remove dist/paraglide/.gitignore before publint (publish validation) <!-- files: package.json -->
|
|
54
|
-
- [x] `[fix]` `[P1]` Collection entries: {#key} wrapper on query blocks to force re-fetch on param change <!-- files: src/lib/admin/client/collection/collection-entries.svelte -->
|
|
55
|
-
|
|
56
|
-
## 0.2.1–0.5.0 — Hybrid Editor System
|
|
57
|
-
|
|
58
|
-
> Szczegóły w **[ROADMAP-EDITOR.md](./ROADMAP-EDITOR.md)**
|
|
59
|
-
|
|
60
|
-
- **0.2.1** — array→blocks rename + simple array `[breaking]`
|
|
61
|
-
- **0.2.2** — Structured Content field (TipTap JSON output)
|
|
62
|
-
- **0.3.0** — Bard-style inline blocks
|
|
63
|
-
- **0.4.0** — Zone system (layout orchestration)
|
|
64
|
-
- **0.4.1** — Accessibility layer (ATAG Part B)
|
|
65
|
-
- **0.5.0** — Frontend rendering
|
|
66
|
-
|
|
67
|
-
## 0.5.1 — Patches & dashboard
|
|
68
|
-
|
|
69
|
-
- [x] `[feature]` `[P1]` Restore archived entries — banner, read-only mode, list actions
|
|
70
|
-
- [x] `[feature]` `[P2]` Dashboard: changelog modal, orphaned entries redesign, grid layout
|
|
71
|
-
- [x] `[fix]` `[P1]` Translation flow — reactive status, switcher UX, dynamic ref, copyFrom crash, panel scroll
|
|
72
|
-
- [x] `[fix]` `[P1]` False "Niezapisane zmiany" on entry load
|
|
73
|
-
- [x] `[fix]` `[P2]` Hide translation dots for non-required empty fields
|
|
74
|
-
|
|
75
|
-
## 0.5.2 — Update system fix
|
|
76
|
-
|
|
77
|
-
- [x] `[fix]` `[P1]` Split `migration` field into `sql` + `notes` — fixes CLI crash on text descriptions
|
|
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
|
-
|
|
108
|
-
## 0.5.6 — Relation field & collection improvements
|
|
109
|
-
|
|
110
|
-
- [x] `[feature]` `[P1]` Relation field: dialog picker, reorder, search, multi-select UX <!-- files: src/lib/admin/components/fields/relation-field.svelte -->
|
|
111
|
-
- [x] `[feature]` `[P1]` getEntryLabels — lightweight labels endpoint for relation fields <!-- files: src/lib/admin/remote/entry.remote.ts -->
|
|
112
|
-
- [x] `[feature]` `[P2]` Collection list: relation labels in columns, delete only for archived <!-- files: src/lib/admin/client/collection/collection-entries.svelte -->
|
|
113
|
-
- [x] `[feature]` `[P2]` Icon map — string-based sidebar icons replace component refs <!-- files: src/lib/admin/components/layout/nav-main.svelte -->
|
|
114
|
-
- [x] `[fix]` `[P1]` TipTap: auto-expand new inline blocks, editor overflow, toolbar bg <!-- files: src/lib/admin/components/fields/structured-content/ -->
|
|
115
|
-
- [x] `[chore]` `[P2]` Runtime types: FormEntryMap rename, add remote.ts
|
|
116
|
-
- [x] `[chore]` `[P2]` Remove mode-watcher, hardcode light theme
|
|
117
|
-
|
|
118
|
-
## 0.5.7 — SEO field & content fixes
|
|
119
|
-
|
|
120
|
-
- [x] `[feature]` `[P1]` SEO field: auto/manual slug toggle with Switch <!-- files: src/lib/admin/components/fields/seo-field.svelte -->
|
|
121
|
-
- [x] `[fix]` `[P1]` Structured content: skip empty paragraphs, wrap inline blocks in div <!-- files: src/lib/core/server/entries/operations/ -->
|
|
122
|
-
- [x] `[fix]` `[P1]` resolveUrlFields: normalize flat string url/text, use raw DB calls <!-- files: src/lib/core/server/entries/ -->
|
|
123
|
-
- [x] `[chore]` `[P1]` Migrate #await to reactive query pattern across admin UI
|
|
124
|
-
|
|
125
|
-
## 0.5.8 — Media GC
|
|
126
|
-
|
|
127
|
-
- [x] `[feature]` `[P1]` Image style upsert: replace duplicates instead of creating new files
|
|
128
|
-
- [x] `[feature]` `[P1]` Auto-cleanup old style files from disk on regeneration
|
|
129
|
-
- [x] `[feature]` `[P1]` Admin maintenance page: purge styles, reconcile orphaned disk files
|
|
130
|
-
- [x] `[fix]` `[P1]` Deduplicate existing image style records (keep newest per unique key)
|
|
131
|
-
|
|
132
|
-
## 0.6.0 — Entity module & DX
|
|
133
|
-
|
|
134
|
-
- [x] `[feature]` `[P0]` Entity module — programmatic CRUD API for entries (`getEntity`) <!-- files: src/lib/entity/ -->
|
|
135
|
-
- [x] `[feature]` `[P1]` CLI `install-peers` command <!-- files: src/lib/cli/ -->
|
|
136
|
-
- [x] `[feature]` `[P1]` Collection data filters — filter by select/radio fields in toolbar <!-- files: src/lib/admin/client/collection/ -->
|
|
137
|
-
- [x] `[feature]` `[P1]` Layout dot-notation — distribute object fields across layout nodes <!-- files: src/lib/admin/components/fields/ -->
|
|
138
|
-
- [x] `[fix]` `[P1]` AI Claude: lazy client init, no crash without API key <!-- files: src/lib/ai-claude/ -->
|
|
139
|
-
- [x] `[fix]` `[P1]` Codegen: quote hyphenated slugs, PascalCase form schemas, improved query types <!-- files: src/lib/core/server/generator/ -->
|
|
140
|
-
- [x] `[fix]` `[P1]` TipTap: inline block content field lang fix + placeholder UX
|
|
141
|
-
- [x] `[fix]` `[P1]` Zod schema: skip lang wrapper for non-localized content field
|
|
142
|
-
- [x] `[breaking]` `[P0]` Runtime deps → peerDependencies (run `pnpm includio install-peers`)
|
|
143
|
-
|
|
144
|
-
## 0.6.1 — Admin experience
|
|
145
|
-
|
|
146
|
-
- [ ] `[feature]` `[P1]` Admin overlay — built-in admin bar on site (quick edit, preview, audit)
|
|
147
|
-
- [ ] `[feature]` `[P1]` Media gallery virtualization/pagination <!-- files: src/lib/admin/components/media/files-list.svelte -->
|
|
148
|
-
- [ ] `[feature]` `[P2]` Image styles UI — preview generated variants in editor
|
|
149
|
-
|
|
150
|
-
## 0.7.0 — Auth core refactor & design fixes
|
|
151
|
-
|
|
152
|
-
- [x] `[breaking]` `[P0]` Auth moved into CMS core — lazy betterAuth init from config, AuthAdapter → AuthConfig <!-- files: src/lib/core/cms.ts, src/lib/types/cms.ts, src/lib/sveltekit/server/handle.ts -->
|
|
153
|
-
- [x] `[fix]` `[P1]` Media checkered bg & overlay gradient aligned with design system palette <!-- files: src/lib/admin/styles/admin.css -->
|
|
154
|
-
- [x] `[fix]` `[P1]` Form inputs (input, textarea, select, input-group) use consistent bg-card background
|
|
155
|
-
- [x] `[fix]` `[P2]` Blocks field accordion: rounded borders, card background
|
|
156
|
-
- [x] `[fix]` `[P2]` Media file miniature: reset img/svg margins
|
|
157
|
-
|
|
158
|
-
## 0.7.1 — Custom fields & admin DX
|
|
159
|
-
|
|
160
|
-
- [x] `[feature]` `[P0]` Custom fields plugin system — type, zod/TS codegen, field renderer, populate resolver
|
|
161
|
-
- [x] `[feature]` `[P1]` pathTemplate — configurable URL paths for collections/singles
|
|
162
|
-
- [x] `[feature]` `[P1]` Admin public exports: admin/helpers & admin/ui for plugin authors
|
|
163
|
-
- [x] `[feature]` `[P2]` resolveMediaWithStyles utility & core svelte export
|
|
164
|
-
- [x] `[fix]` `[P1]` Schema serialization type cast (stricter TS compatibility)
|
|
165
|
-
|
|
166
|
-
## 0.7.2 — CLI create-user
|
|
167
|
-
|
|
168
|
-
- [x] `[feature]` `[P1]` CLI `create-user` command — interactive user creation with role assignment <!-- files: src/lib/cli/create-user.ts, src/lib/cli/index.ts -->
|
|
169
|
-
|
|
170
|
-
## 0.7.3 — Typography
|
|
171
|
-
|
|
172
|
-
- [x] `[feature]` `[P1]` Read-time orphan fix — replace space after single-letter Polish conjunctions with `\u00A0` <!-- files: src/lib/core/server/fields/resolveTypographyOrphans.ts, src/lib/core/server/fields/utils/fixOrphans.ts -->
|
|
173
|
-
|
|
174
|
-
## 0.8.0 — Flat entry format
|
|
175
|
-
|
|
176
|
-
- [x] `[breaking]` `[P0]` Flat entry format: objects use `_slug` + spread fields instead of `slug` + `data` wrapper
|
|
177
|
-
- [x] `[breaking]` `[P0]` Blocks use `_slug` instead of `slug` with flat fields
|
|
178
|
-
- [x] `[breaking]` `[P0]` `FlatEntry` type removed — use `Entry` instead
|
|
179
|
-
|
|
180
|
-
## 0.9.0 — Per-language entry versions
|
|
181
|
-
|
|
182
|
-
- [x] `[breaking]` `[P0]` Per-language entry versions: each language gets its own `entry_version` with flat data
|
|
183
|
-
- [x] `[feature]` `[P0]` Per-language publication: publish/unpublish each language independently
|
|
184
|
-
- [x] `[breaking]` `[P0]` Simplified data format: no more nested `{ pl: "X", en: "Y" }` — values are flat strings
|
|
185
|
-
|
|
186
|
-
## 0.10.0 — Field type consolidation
|
|
187
|
-
|
|
188
|
-
- [x] `[breaking]` `[P1]` Remove `image` field type — use `media` with `accept: "image/*"`
|
|
189
|
-
- [x] `[breaking]` `[P1]` Remove `richtext` field type — use `content` (structured JSON)
|
|
190
|
-
- [x] `[chore]` `[P1]` Remove `ImageField`, `RichtextField` interfaces + components
|
|
191
|
-
|
|
192
|
-
## 0.11.0 — Codegen & REST consolidation
|
|
193
|
-
|
|
194
|
-
- [x] `[feature]` `[P1]` Codegen: flat types, inline block types, `countEntries`, simplified API
|
|
195
|
-
- [x] `[feature]` `[P1]` REST API: catch-all route, upload endpoint, media endpoint, schema templates
|
|
196
|
-
- [x] `[chore]` `[P1]` Admin scaffold updated for new REST route structure
|
|
197
|
-
|
|
198
|
-
## 0.12.0 — File field, dataOrderBy, _url
|
|
199
|
-
|
|
200
|
-
- [x] `[feature]` `[P1]` File field type with multipart upload, validation, rate limiting
|
|
201
|
-
- [x] `[feature]` `[P1]` `dataOrderBy` — sort entries by JSON data fields
|
|
202
|
-
- [x] `[feature]` `[P1]` Auto-populate `_url` field on entries from slug resolver
|
|
203
|
-
|
|
204
|
-
## 0.13.0 — Private file uploads
|
|
205
|
-
|
|
206
|
-
- [x] `[feature]` `[P1]` Private file uploads — form submissions can upload files to non-public directory
|
|
207
|
-
|
|
208
|
-
## 0.13.1 — Admin i18n, codegen cleanup, docs
|
|
209
|
-
|
|
210
|
-
- [x] `[feature]` `[P1]` Admin UI i18n — TipTap, blocks, array, media components support pl/en <!-- files: src/lib/admin/components/tiptap/lang.ts -->
|
|
211
|
-
- [x] `[feature]` `[P2]` Inline block accordion labels — show field value in collapsed header <!-- files: src/lib/admin/components/tiptap/InlineBlockNodeView.svelte -->
|
|
212
|
-
- [x] `[feature]` `[P2]` Blocks field UrlFieldData label support <!-- files: src/lib/admin/components/fields/blocks-field.svelte -->
|
|
213
|
-
- [x] `[feature]` `[P2]` Email configuration remote endpoint <!-- files: src/lib/admin/remote/email.remote.ts -->
|
|
214
|
-
- [x] `[fix]` `[P1]` Codegen: remove unused Flat*FieldData types, use ImageFieldData/VideoFieldData <!-- files: src/lib/core/server/generator/fields.ts, src/lib/core/server/generator/generator.ts -->
|
|
215
|
-
- [x] `[fix]` `[P1]` Layout renderer: hide card header when label empty <!-- files: src/lib/admin/components/layout/layout-renderer.svelte -->
|
|
216
|
-
- [x] `[fix]` `[P1]` Collection entries: improved relation label fetching (UUID validation, multi-version) <!-- files: src/lib/admin/client/collection/collection-entries.svelte -->
|
|
217
|
-
- [x] `[fix]` `[P1]` Users page: client-side email config, remove server load <!-- files: src/lib/admin/client/users/users-page.svelte -->
|
|
218
|
-
- [x] `[fix]` `[P2]` Entry remote: stricter UUID validation for ids <!-- files: src/lib/admin/remote/entry.remote.ts -->
|
|
219
|
-
- [x] `[fix]` `[P2]` Layout type: remove unused label property <!-- files: src/lib/types/layout.ts -->
|
|
220
|
-
- [x] `[chore]` `[P1]` Documentation rewrite — new pages for blocks, content, media-field, layout; expanded API, auth, entries, forms, plugins docs
|
|
221
|
-
|
|
222
|
-
## 0.13.2 — Upload limits, stable reordering, image style optimization
|
|
223
|
-
|
|
224
|
-
- [x] `[feature]` `[P1]` Configurable upload size limit — `BODY_SIZE_LIMIT` env var, pre-validation in UI <!-- files: src/lib/core/server/media/uploadLimit.ts, src/lib/admin/api/upload-limit.ts, src/lib/admin/components/media/file-upload.svelte -->
|
|
225
|
-
- [x] `[feature]` `[P1]` Stable slot reordering — preserve filtered-out entry positions during DnD <!-- files: src/lib/admin/remote/reorder.ts, src/lib/admin/remote/entry.remote.ts -->
|
|
226
|
-
- [x] `[feature]` `[P1]` Collection relation field filtering — filter entries by relation values <!-- files: src/lib/admin/client/collection/collection-entries.svelte -->
|
|
227
|
-
- [x] `[feature]` `[P2]` Expose `_sortOrder` on entries + codegen for orderable collections <!-- files: src/lib/core/server/entries/operations/get.ts, src/lib/core/server/generator/generator.ts, src/lib/types/entries.ts -->
|
|
228
|
-
- [x] `[feature]` `[P2]` `getImageStyleIfExists` — non-blocking image style lookup <!-- files: src/lib/core/server/media/styles/operations/getImageStyle.ts -->
|
|
229
|
-
- [x] `[fix]` `[P1]` Concurrent image style generation with buffer reuse (download once, parallel limit 3) <!-- files: src/lib/core/server/media/styles/operations/generateDefaultStyles.ts, src/lib/core/server/media/styles/sharp/generateImageStyle.ts -->
|
|
230
|
-
- [x] `[fix]` `[P1]` Image style resolution gracefully skips missing styles <!-- files: src/lib/core/server/fields/utils/imageStyles.ts -->
|
|
231
|
-
|
|
232
|
-
## 0.13.3 — Security hardening
|
|
233
|
-
|
|
234
|
-
- [x] `[fix]` `[P0]` Timing-safe API key comparison (prevents timing attacks) <!-- files: src/lib/admin/api/rest/middleware/apiKey.ts -->
|
|
235
|
-
- [x] `[fix]` `[P0]` Remove demo reset endpoint with hardcoded fallback secret <!-- files: src/routes/api/demo/reset/ -->
|
|
236
|
-
- [x] `[fix]` `[P1]` Form rate limit: all requests + getClientAddress() instead of x-forwarded-for <!-- files: src/routes/api/forms/[slug]/submit/+server.ts -->
|
|
237
|
-
- [x] `[feature]` `[P1]` MIME blocklist on upload/replace endpoints <!-- files: src/lib/core/server/media/mimeBlocklist.ts -->
|
|
238
|
-
- [x] `[feature]` `[P1]` Security headers: X-Content-Type-Options, X-Frame-Options, Referrer-Policy <!-- files: src/lib/sveltekit/server/handle.ts -->
|
|
239
|
-
- [x] `[fix]` `[P1]` CMS constructor validates non-empty languages array <!-- files: src/lib/core/cms.ts -->
|
|
240
|
-
- [x] `[fix]` `[P2]` Unhandled promise rejection handlers in admin components
|
|
241
|
-
|
|
242
|
-
## 0.13.4 — Video poster regeneration
|
|
243
|
-
|
|
244
|
-
- [x] `[feature]` `[P1]` Batch video poster regeneration — admin maintenance page with SSE progress <!-- files: src/lib/admin/api/regenerate-posters.ts, src/lib/core/server/media/operations/batchRegenerateVideoPosters.ts, src/lib/admin/client/maintenance/maintenance-page.svelte -->
|
|
245
|
-
- [x] `[feature]` `[P1]` Video poster status reporting in media GC endpoint <!-- files: src/lib/admin/api/media-gc.ts -->
|
|
246
|
-
- [x] `[chore]` `[P1]` Extracted video processing to reusable module <!-- files: src/lib/files-local/video.ts, src/lib/files-local/index.ts -->
|
|
247
|
-
- [x] `[fix]` `[P1]` Filename sanitization normalizes Unicode to NFC <!-- files: src/lib/files-local/sanitizeFilename.ts -->
|
|
248
|
-
|
|
249
|
-
## 0.14.0 — Video transcoding
|
|
250
|
-
|
|
251
|
-
- [x] `[feature]` `[P1]` Video styles system — auto-transcode to mp4 h264 + webm vp9 <!-- files: src/lib/db-postgres/schema/videoStyle.ts, src/lib/files-local/transcode.ts -->
|
|
252
|
-
- [x] `[feature]` `[P1]` Background transcoding pipeline on upload with skip logic <!-- files: src/lib/core/server/media/styles/operations/generateDefaultVideoStyles.ts -->
|
|
253
|
-
- [x] `[feature]` `[P1]` Admin maintenance: video transcoding card (batch, purge, SSE progress) <!-- files: src/lib/admin/client/maintenance/maintenance-page.svelte -->
|
|
254
|
-
- [x] `[feature]` `[P2]` Frontend multi-source video delivery (webm, mp4, original) <!-- files: src/lib/sveltekit/components/video.svelte -->
|
|
255
|
-
- [x] `[feature]` `[P2]` Configurable video transcoding in MediaConfig <!-- files: src/lib/types/cms.ts -->
|
|
256
|
-
- [x] `[feature]` `[P2]` System info endpoint — CMS version, Node, PostgreSQL, ffmpeg, sharp, OS <!-- files: src/lib/admin/api/system-info.ts, src/lib/core/server/media/operations/getSystemInfo.ts -->
|
|
257
|
-
- [x] `[feature]` `[P2]` Disk usage endpoint — breakdown per category (originals, image styles, video styles, posters) <!-- files: src/lib/admin/api/system-info.ts, src/lib/core/server/media/operations/getDiskUsage.ts -->
|
|
258
|
-
|
|
259
|
-
## 0.14.1 — CMS context, Svelte 5 compat, docs
|
|
260
|
-
|
|
261
|
-
- [x] `[feature]` `[P1]` CMS context provider — Safari mp4 preference for video delivery <!-- files: src/lib/sveltekit/components/cms-provider.svelte, src/lib/sveltekit/components/video-context.ts, src/lib/types/cms-context.ts -->
|
|
262
|
-
- [x] `[fix]` `[P1]` Svelte 5 compatibility — a11y annotations, warning suppression across admin components
|
|
263
|
-
- [x] `[chore]` `[P1]` Documentation overhaul — new pages, README rewrite, DOCS.md compilation script <!-- files: scripts/compile-docs.ts, DOCS.md -->
|
|
264
|
-
- [x] `[chore]` `[P2]` Remove obsolete docs/ Obsidian config & stale docs
|
|
265
|
-
|
|
266
|
-
## 0.14.2 — Image styles: aspectRatio, skip defaults, lazy generation
|
|
267
|
-
|
|
268
|
-
- [x] `[feature]` `[P1]` aspectRatio option for image styles — crop to ratio without fixed dimensions <!-- files: src/lib/types/fields.ts, src/lib/core/server/media/styles/sharp/generateImageStyle.ts, src/lib/db-postgres/schema/imageStyle.ts -->
|
|
269
|
-
- [x] `[feature]` `[P1]` Skip default styles when field defines custom styles (prevents `<source>` conflicts in `<picture>`) <!-- files: src/lib/core/server/fields/utils/imageStyles.ts -->
|
|
270
|
-
- [x] `[feature]` `[P2]` Lazy generation of custom field styles on first read <!-- files: src/lib/core/server/fields/utils/imageStyles.ts -->
|
|
271
|
-
|
|
272
|
-
## 0.14.3 — Background maintenance
|
|
273
|
-
|
|
274
|
-
- [x] `[feature]` `[P1]` Background maintenance scheduler — auto styles, posters, transcodes, orphan cleanup on configurable interval <!-- files: src/lib/core/server/media/operations/backgroundMaintenance.ts, src/lib/core/cms.ts -->
|
|
275
|
-
- [x] `[feature]` `[P1]` Maintenance status API endpoint <!-- files: src/lib/admin/api/maintenance-status.ts, src/lib/admin/api/handler.ts -->
|
|
276
|
-
- [x] `[feature]` `[P2]` Configurable maintenance interval via `media.maintenance` config <!-- files: src/lib/types/cms.ts -->
|
|
277
|
-
- [x] `[feature]` `[P1]` Maintenance page UI redesign — status banner, sections, SSE handler dedup <!-- files: src/lib/admin/client/maintenance/maintenance-page.svelte -->
|
|
278
|
-
|
|
279
|
-
## 0.14.5 — Schema cleanup
|
|
280
|
-
|
|
281
|
-
- [x] `[chore]` `[P2]` Move image & video style unique indexes into Drizzle ORM schema (single source of truth) <!-- files: src/lib/db-postgres/schema/imageStyle.ts, src/lib/db-postgres/schema/videoStyle.ts -->
|
|
282
|
-
|
|
283
|
-
## 0.14.6 — Admin page titles
|
|
284
|
-
|
|
285
|
-
- [x] `[fix]` `[P2]` Admin `<title>` from breadcrumbs + pre-login titles + AriaCMS brand <!-- files: src/lib/admin/utils/pageTitle.ts, src/lib/admin/client/admin/admin-after-login-layout-content.svelte -->
|
|
286
|
-
- [x] `[fix]` `[P1]` Video poster always from first frame (no jump on load) <!-- files: src/lib/files-local/video.ts -->
|
|
287
|
-
- [x] `[feature]` `[P2]` Manual poster regeneration in media library file details <!-- files: src/lib/core/server/media/operations/regenerateVideoPoster.ts, src/lib/admin/components/media/file/file-details.svelte -->
|
|
288
|
-
- [x] `[feature]` `[P2]` Config flag `sidebarHelp` to hide admin sidebar Help link (default true) <!-- files: src/lib/types/cms.ts, src/lib/core/cms.ts, src/routes/admin/(afterLogin)/+layout.server.ts, src/lib/admin/components/layout/nav-footer.svelte -->
|
|
289
|
-
|
|
290
|
-
## 0.15.0 — Shop MVP (headless e-commerce)
|
|
291
|
-
|
|
292
|
-
- [x] `[feature]` `[P0]` Scaffold: `defineShop`, types, CMSConfig slot, package export `./shop` <!-- files: src/lib/shop/index.ts, src/lib/shop/types.ts -->
|
|
293
|
-
- [x] `[feature]` `[P0]` Drizzle tables: products (entry-backed), variants, orders, order items, status history, payments, shipping methods, stock reservations <!-- files: src/lib/db-postgres/schema/shop/ -->
|
|
294
|
-
- [x] `[feature]` `[P0]` Runtime `schema.ts` generator for drizzle-kit (core + auth + optional shop) <!-- files: src/lib/core/server/generator/generator.ts, src/lib/db-postgres/schema-core.ts, src/lib/db-postgres/schema-shop.ts -->
|
|
295
|
-
- [x] `[feature]` `[P0]` Rate-limit middleware (checkout/webhook) <!-- files: src/lib/shop/rate-limit.ts -->
|
|
296
|
-
- [x] `[feature]` `[P0]` Built-in `shop` field type — makes entries purchasable, auto-hydrated via populateEntryData <!-- files: src/lib/admin/components/fields/shop-field.svelte, src/lib/shop/server/populate.ts -->
|
|
297
|
-
- [x] `[feature]` `[P0]` Net/gross price toggle + live VAT preview in shop field and shipping form
|
|
298
|
-
- [x] `[feature]` `[P0]` Auto default variant on upsert — cart/order always reference variantId
|
|
299
|
-
- [x] `[feature]` `[P0]` Shop admin: products list (JOIN entries × shop_products), shipping methods CRUD with drag-n-drop reorder + free-above threshold
|
|
300
|
-
- [x] `[feature]` `[P0]` Cart (signed cookie) service + `/api/shop/cart` REST + headless SDK `createShopClient()` <!-- files: src/lib/shop/cart/, src/lib/shop/client/ -->
|
|
301
|
-
- [x] `[feature]` `[P0]` Orders + checkout + `manualAdapter` + status emails (inline HTML, PL/EN) <!-- files: src/lib/shop/server/orders.ts, src/lib/shop/http/checkout-handler.ts -->
|
|
302
|
-
- [x] `[feature]` `[P0]` Stock reservation with 30-min TTL — released on cancel/reject, consumed on paid
|
|
303
|
-
- [x] `[feature]` `[P0]` Admin orders view — list (status + email filter), detail (items, history, customer/address/consents, change status, resend email)
|
|
304
|
-
- [x] `[feature]` `[P0]` Random Crockford base32 order numbers (`XXXXX-XXXXX`), unguessable
|
|
305
|
-
- [x] `[feature]` `[P0]` CLI scaffold provisions all shop routes (admin + public API) in consumer app
|
|
306
|
-
- [x] `[feature]` `[P1]` PayU payment adapter + webhook — shipped in 0.15.1 (access-token-gated order view, idempotent webhooks, MD5 signature, shipping↔payment compat, poll fallback)
|
|
307
|
-
- [ ] `[feature]` `[P1]` Stripe payment adapter + webhook — deferred to 0.15.4
|
|
308
|
-
- [x] `[feature]` `[P2]` InPost carrier adapter — shipped in 0.15.3 (Geowidget v5 picker + ShipX shipments + auto-confirm + webhook status updates)
|
|
309
|
-
|
|
310
|
-
## 0.15.2 — Price precision fix
|
|
311
|
-
|
|
312
|
-
- [x] `[fix]` `[P1]` Price drift on reload — storage jako `numeric(20,6)` (PLN z 6dp), brutto round-tripuje bez utraty ±1gr. Snapshot zamówienia dalej w groszach (KSeF). <!-- files: src/lib/db-postgres/schema/shop/product.ts, productVariant.ts, shippingMethod.ts, src/lib/shop/pricing.ts, src/lib/shop/server/cart-hydrate.ts, shipping.ts, shop-data.ts, src/lib/admin/components/fields/shop-field.svelte, src/lib/admin/client/shop/shipping-method-form.svelte -->
|
|
313
|
-
- [x] `[feature]` `[P2]` Variant price toggle netto/brutto (spójne z toggle\'em ceny bazowej)
|
|
314
|
-
- [x] `[feature]` `[P2]` `nodemailerAdapter` — typowanie `transportOptions` jako `SMTPTransport.Options` (OAuth2, pool, itd.)
|
|
315
|
-
|
|
316
|
-
## 0.15.3 — InPost carrier
|
|
317
|
-
|
|
318
|
-
- [x] `[feature]` `[P2]` `inpostAdapter()` — Geowidget v5 (`<InpostPicker>` Svelte + raw config endpoint), ShipX shipment create + auto-buy + label PDF + cancel, webhook → status + tracking, per-shipping-method service config, customer tracking display in `<OrderStatus>` + email templates
|
|
319
|
-
- [x] `[chore]` `[P2]` Verbose logging on adapter auto-confirm flow (offer prep wait, buy POST, polling) for sandbox debugging
|
|
320
|
-
|
|
321
|
-
## 0.15.4 — Forms submission scaffold + best-effort notification emails
|
|
322
|
-
|
|
323
|
-
- [x] `[feature]` `[P1]` CLI scaffold emits `src/routes/api/forms/[slug]/submit/+server.ts` — public form submit endpoint auto-generated, no manual setup per project <!-- files: src/lib/cli/scaffold/admin.ts, src/lib/cli/scaffold/admin.spec.ts -->
|
|
324
|
-
- [x] `[fix]` `[P0]` `createFormSubmission` — split try/catch so SMTP failure no longer returns `false` (endpoint responded 500 even though submission was persisted); notification email is best-effort, logged via `console.error` <!-- files: src/lib/core/server/forms/submissions/operations/create.ts -->
|
|
325
|
-
- [ ] `[feature]` `[P1]` Built-in `/api/health` + `/api/health/ready` with per-adapter checks (db/files/email/ai) + ręczny SMTP diagnostics panel in maintenance page <!-- files: ideas/health-check-module.md -->
|
|
326
|
-
|
|
327
|
-
## 0.16.0 — Hard reset
|
|
328
|
-
|
|
329
|
-
> Start drogi do v1.0.0. Decyzje: **[V1-DECISIONS.md](./V1-DECISIONS.md)** | Workflow: **[V1-WORKFLOW.md](./V1-WORKFLOW.md)**
|
|
330
|
-
|
|
331
|
-
- [x] `[breaking]` `[P0]` Wycięty `src/lib/inline-edit-proto/` (kod na lokalnym branchu `archive/inline-edit-proto`) — inline edit od zera w v1.x
|
|
332
|
-
- [x] `[breaking]` `[P0]` Wycięty `src/lib/demo/seed.ts` + demo routes (`/demo/*`, `/admin/demo/*`) — demo content od zera w v1.x
|
|
333
|
-
- [x] `[breaking]` `[P0]` Skasowany `ROADMAP-EDITOR.md` (treść na archive branch)
|
|
334
|
-
- [x] `[chore]` `[P2]` `ideas/*.md` przeniesione do `ideas/post-v1/` (lokalnie, w `.gitignore`); `select-field-defaultvalue-bug` promowany do v1.0 fix
|
|
335
|
-
- [x] `[chore]` `[P2]` `V1-DECISIONS.md` jako referencja decyzji v1 dla każdej kolejnej sesji
|
|
8
|
+
> **Versioning:** since 0.16.0 droga do v1.0.0. Pre-v1.0 history → **[ROADMAP-ARCHIVE.md](./ROADMAP-ARCHIVE.md)**.
|
|
336
9
|
|
|
337
10
|
## v1.0.0 — Stabilizacja (in progress)
|
|
338
11
|
|
|
339
|
-
> 13 faz w ~16 sesjach, droga 0.16 → 1.0.0. Lean scope: stabilizacja, security, testy, docs, audit + polish shopa. Bez nowych feature'ów.
|
|
12
|
+
> 13 faz w ~16 sesjach, droga 0.16 → 1.0.0. Lean scope: stabilizacja, security, testy, docs, audit + polish shopa. Bez nowych feature'ów. Decyzje: **[V1-DECISIONS.md](./V1-DECISIONS.md)** | Workflow: **[V1-WORKFLOW.md](./V1-WORKFLOW.md)**.
|
|
340
13
|
|
|
341
|
-
- [x] `[breaking]` `[P0]` API surface lock — exports trim 26→
|
|
14
|
+
- [x] `[breaking]` `[P0]` API surface lock — exports trim 26→16, JSDoc tagi (@public/@internal/@experimental), `API.md` autogenerated (0.20.0) <!-- files: package.json, scripts/generate-api-md.ts -->
|
|
342
15
|
- [x] `[chore]` `[P1]` Faza 6 — Test foundation: docker postgres + integration project + 43 cases (30 REST + 6 shop checkout + 7 webhook signature/idempotency dla PayU/InPost), coverage report <!-- files: tests/integration/rest/, tests/helpers/{rest,shop}.ts, tests/integration/fixtures/shopConfig.ts, tests/setup.ts -->
|
|
343
16
|
- [x] `[chore]` `[P1]` Faza 7 — E2E foundation: Playwright config (storageState reuse, setup project), `db_test_e2e` na 5435, `e2e/setup/global-setup.ts` (drizzle push), `e2e/admin/auth.setup.ts` (sign-up + admin role + login), 4 specy zielone w ~23s: login, collection-crud (blog-post create→edit→archive→permanent-delete), media-upload, user-mgmt (create→delete); data-testid na critical buttons admin <!-- files: playwright.config.ts, e2e/admin/*, src/lib/admin/client/{collection,entry,users}/, src/lib/admin/components/media/file-upload.svelte -->
|
|
344
17
|
- [x] `[chore]` `[P1]` Faza 8 — CI na PR: `.github/workflows/ci.yml` z jobs lint/typecheck/unit/integration (postgres :5434)/e2e (postgres :5435 + playwright cache)/build, pnpm cache, trace artifacts on fail, README CI badge <!-- files: .github/workflows/ci.yml, README.md -->
|
|
345
18
|
- [x] `[breaking]` `[P0]` Faza 9 — DX & config validation (0.22.0): `defineConfig` Zod strict z field path + hint, `CmsError` + `ConfigValidationError` z code/context, resolver throw sites zmigrowane, CLI `--help` per subcommand + `--version`, `.env.example` rozszerzony o `INCLUDIO_*`, README rebuild (TOC + system req + 5-min quickstart), JSDoc body (opis + @param + @returns + @example) na każdym `@public` <!-- files: src/lib/core/errors.ts, src/lib/types/cms.schema.ts, src/lib/sveltekit/config.ts, src/lib/cli/index.ts, README.md, .env.example, src/lib/updates/0.22.0/ -->
|
|
19
|
+
- [x] `[chore]` `[P1]` Faza 10 — Documentation Pass (0.23.0): DOCS.md uzupełnione (number/boolean/date edge cases, REST cURL + error codes, Entries error handling + transactions, Admin UI a11y, Adapter Contracts), nowe sekcje (Stability Promise, Security Model), Migration Guide v0.x → v1.0 master cheatsheet (0.16-0.22), ROADMAP cleanup (pre-v1.0 → ROADMAP-ARCHIVE.md) <!-- files: src/routes/docs/{stability-promise,security}/+page.svx, src/routes/docs/migration/+page.svx, src/routes/docs/_config/nav.ts, scripts/compile-docs.ts, ROADMAP-ARCHIVE.md, src/lib/updates/0.23.0/ -->
|
|
20
|
+
- [x] `[chore]` `[P1]` Faza 11 — Performance Pass (0.24.0): N+1 elimination (`getImageStyle` batch IN query, `getRawEntries` batch entry versions), Sharp srcset `Promise.allSettled` (one timeout doesn't kill batch), background maintenance in-process lock verified + tested, bench infra (`pnpm bench` + representative benchmarks), lazy adapter imports verified <!-- files: src/lib/core/server/media/styles/operations/, src/lib/core/server/fields/resolveImageFields.ts, src/lib/core/server/entries/operations/get.ts, src/lib/db-postgres/index.ts, src/lib/core/server/media/operations/backgroundMaintenance.spec.ts, src/lib/updates/0.24.0/ -->
|
|
346
21
|
- [ ] `[fix]` `[P1]` Select field — `defaultValue` propagacja do zod schema (full repro: `ideas/post-v1/select-field-defaultvalue-bug.md`); fix planowany w Fazie 12 (RC)
|
|
347
22
|
|
|
348
23
|
## v1.x — Post-v1.0 deferred
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { bench, describe, vi, beforeAll } from 'vitest';
|
|
2
|
+
const QUERY_LATENCY_MS = 1;
|
|
3
|
+
const ENTRY_COUNT = 100;
|
|
4
|
+
function buildDbEntry(i) {
|
|
5
|
+
return {
|
|
6
|
+
id: `entry-${i}`,
|
|
7
|
+
slug: 'blog-post',
|
|
8
|
+
type: 'collection',
|
|
9
|
+
sortOrder: i,
|
|
10
|
+
archivedAt: null,
|
|
11
|
+
createdAt: new Date(),
|
|
12
|
+
updatedAt: new Date()
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
function buildVersion(entryId, n) {
|
|
16
|
+
return {
|
|
17
|
+
id: `${entryId}-v${n}`,
|
|
18
|
+
entryId,
|
|
19
|
+
versionNumber: n,
|
|
20
|
+
lang: 'en',
|
|
21
|
+
data: { title: `Entry ${entryId} v${n}` },
|
|
22
|
+
publishedAt: n === 2 ? new Date(Date.now() - 1000) : null,
|
|
23
|
+
createdAt: new Date(),
|
|
24
|
+
updatedAt: new Date()
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
const dbEntries = Array.from({ length: ENTRY_COUNT }, (_, i) => buildDbEntry(i));
|
|
28
|
+
const allVersions = dbEntries.flatMap((e) => [buildVersion(e.id, 1), buildVersion(e.id, 2)]);
|
|
29
|
+
const adapter = {
|
|
30
|
+
getEntries: vi.fn(async (_options) => {
|
|
31
|
+
await new Promise((r) => setTimeout(r, QUERY_LATENCY_MS));
|
|
32
|
+
return dbEntries;
|
|
33
|
+
}),
|
|
34
|
+
getEntryVersions: vi.fn(async (options) => {
|
|
35
|
+
await new Promise((r) => setTimeout(r, QUERY_LATENCY_MS));
|
|
36
|
+
const ids = new Set(options.entryIds ?? []);
|
|
37
|
+
return allVersions.filter((v) => ids.has(v.entryId));
|
|
38
|
+
})
|
|
39
|
+
};
|
|
40
|
+
vi.mock('$lib/core/cms.js', () => ({
|
|
41
|
+
getCMS: () => ({
|
|
42
|
+
databaseAdapter: adapter,
|
|
43
|
+
getBySlug: () => ({ slug: 'blog-post', type: 'collection', orderable: false })
|
|
44
|
+
})
|
|
45
|
+
}));
|
|
46
|
+
let _getRawEntries;
|
|
47
|
+
beforeAll(async () => {
|
|
48
|
+
const mod = await import('./get.js');
|
|
49
|
+
_getRawEntries = mod._getRawEntries;
|
|
50
|
+
});
|
|
51
|
+
describe('_getRawEntries — batch path (current implementation)', () => {
|
|
52
|
+
bench(`100 entries × 2 versions each — 1 batch getEntryVersions call`, async () => {
|
|
53
|
+
await _getRawEntries({ slug: 'blog-post' });
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
describe('synthetic N+1 vs batch comparison', () => {
|
|
57
|
+
bench(`baseline: ${ENTRY_COUNT} sequential per-entry getEntryVersions calls`, async () => {
|
|
58
|
+
// Simulates the pre-0.24.0 N+1 pattern: one query per entry.
|
|
59
|
+
await new Promise((r) => setTimeout(r, QUERY_LATENCY_MS)); // getEntries
|
|
60
|
+
for (let i = 0; i < ENTRY_COUNT; i++) {
|
|
61
|
+
await new Promise((r) => setTimeout(r, QUERY_LATENCY_MS));
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
bench('batched: 1 getEntries + 1 getEntryVersions', async () => {
|
|
65
|
+
await new Promise((r) => setTimeout(r, QUERY_LATENCY_MS));
|
|
66
|
+
await new Promise((r) => setTimeout(r, QUERY_LATENCY_MS));
|
|
67
|
+
});
|
|
68
|
+
});
|
|
@@ -55,16 +55,26 @@ function buildPerLangVersionMaps(versions) {
|
|
|
55
55
|
}
|
|
56
56
|
export const _getRawEntries = async (options) => {
|
|
57
57
|
const dbEntries = await _getDbEntries(options);
|
|
58
|
-
|
|
58
|
+
if (dbEntries.length === 0)
|
|
59
|
+
return [];
|
|
60
|
+
const cms = getCMS();
|
|
61
|
+
const allVersions = await cms.databaseAdapter.getEntryVersions({
|
|
62
|
+
entryIds: dbEntries.map((e) => e.id)
|
|
63
|
+
});
|
|
64
|
+
const versionsByEntry = new Map();
|
|
65
|
+
for (const v of allVersions) {
|
|
66
|
+
const arr = versionsByEntry.get(v.entryId) || [];
|
|
67
|
+
arr.push(v);
|
|
68
|
+
versionsByEntry.set(v.entryId, arr);
|
|
69
|
+
}
|
|
70
|
+
const entries = dbEntries.map((entry) => {
|
|
59
71
|
try {
|
|
60
|
-
const versions =
|
|
61
|
-
entryIds: [entry.id]
|
|
62
|
-
});
|
|
72
|
+
const versions = versionsByEntry.get(entry.id) ?? [];
|
|
63
73
|
const { publishedVersions, scheduledVersions, draftVersions } = buildPerLangVersionMaps(versions);
|
|
64
74
|
return {
|
|
65
75
|
...entry,
|
|
66
|
-
collection:
|
|
67
|
-
versions: versions.sort((a, b) => b.versionNumber - a.versionNumber),
|
|
76
|
+
collection: cms.getBySlug(entry.slug),
|
|
77
|
+
versions: [...versions].sort((a, b) => b.versionNumber - a.versionNumber),
|
|
68
78
|
publishedVersions,
|
|
69
79
|
scheduledVersions,
|
|
70
80
|
draftVersions
|
|
@@ -74,7 +84,7 @@ export const _getRawEntries = async (options) => {
|
|
|
74
84
|
// Orphaned entry - slug removed from config
|
|
75
85
|
return null;
|
|
76
86
|
}
|
|
77
|
-
})
|
|
87
|
+
});
|
|
78
88
|
return entries.filter((e) => e !== null);
|
|
79
89
|
};
|
|
80
90
|
export const _getRawEntry = async (options) => {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { bench, describe, vi, beforeAll } from 'vitest';
|
|
2
|
+
import { imageStyleKey } from '../../../../types/adapters/db.js';
|
|
3
|
+
vi.mock('../../media/utils/generateBlurDataUrl.js', () => ({
|
|
4
|
+
generateBlurDataUrl: vi.fn().mockResolvedValue('data:image/webp;base64,abc')
|
|
5
|
+
}));
|
|
6
|
+
const QUERY_LATENCY_MS = 1;
|
|
7
|
+
function fakeStyle(req) {
|
|
8
|
+
return {
|
|
9
|
+
url: `/uploads/${req.mediaFileId}-${req.style.name}.${req.style.format ?? 'webp'}`,
|
|
10
|
+
mimeType: `image/${req.style.format ?? 'webp'}`
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
const batchAdapter = {
|
|
14
|
+
getImageStyle: vi.fn(async (mediaFileId, style) => {
|
|
15
|
+
await new Promise((r) => setTimeout(r, QUERY_LATENCY_MS));
|
|
16
|
+
return fakeStyle({ mediaFileId, style });
|
|
17
|
+
}),
|
|
18
|
+
getImageStylesBatch: vi.fn(async (requests) => {
|
|
19
|
+
await new Promise((r) => setTimeout(r, QUERY_LATENCY_MS));
|
|
20
|
+
const map = new Map();
|
|
21
|
+
for (const r of requests)
|
|
22
|
+
map.set(imageStyleKey(r.mediaFileId, r.style), fakeStyle(r));
|
|
23
|
+
return map;
|
|
24
|
+
}),
|
|
25
|
+
updateMediaFile: vi.fn().mockResolvedValue(undefined)
|
|
26
|
+
};
|
|
27
|
+
vi.mock('$lib/core/cms.js', () => ({
|
|
28
|
+
getCMS: () => ({
|
|
29
|
+
databaseAdapter: batchAdapter,
|
|
30
|
+
filesAdapter: { downloadFile: vi.fn().mockResolvedValue(null) }
|
|
31
|
+
})
|
|
32
|
+
}));
|
|
33
|
+
let getImageStyles;
|
|
34
|
+
beforeAll(async () => {
|
|
35
|
+
const mod = await import('./imageStyles.js');
|
|
36
|
+
getImageStyles = mod.getImageStyles;
|
|
37
|
+
});
|
|
38
|
+
const mediaFile = {
|
|
39
|
+
id: 'media-1',
|
|
40
|
+
name: 'photo.jpg',
|
|
41
|
+
url: '/uploads/photo.jpg',
|
|
42
|
+
type: 'image',
|
|
43
|
+
size: 1024,
|
|
44
|
+
width: 3000,
|
|
45
|
+
height: 2000,
|
|
46
|
+
alt: null,
|
|
47
|
+
tags: [],
|
|
48
|
+
createdAt: new Date(),
|
|
49
|
+
thumbnailUrl: null,
|
|
50
|
+
duration: null,
|
|
51
|
+
posterUrl: null,
|
|
52
|
+
mimeType: 'image/jpeg',
|
|
53
|
+
blurDataUrl: 'data:image/webp;base64,existing',
|
|
54
|
+
focalX: null,
|
|
55
|
+
focalY: null,
|
|
56
|
+
transcriptFileId: null,
|
|
57
|
+
audioDescriptionFileId: null
|
|
58
|
+
};
|
|
59
|
+
const customStyles = [
|
|
60
|
+
{ name: 'card', width: 600, srcset: [320, 640, 1024] },
|
|
61
|
+
{ name: 'hero', width: 1920, srcset: [640, 1024, 1920, 2560] },
|
|
62
|
+
{ name: 'thumb', width: 200 }
|
|
63
|
+
];
|
|
64
|
+
const field = { styles: customStyles };
|
|
65
|
+
describe('getImageStyles — batch path (current implementation)', () => {
|
|
66
|
+
bench('1 media × 3 styles × 3 formats × srcset (1 batch query)', async () => {
|
|
67
|
+
await getImageStyles(field, mediaFile);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
describe('synthetic N+1 vs batch comparison', () => {
|
|
71
|
+
const styleCount = 3 * 3; // 3 styles × 3 format expansions
|
|
72
|
+
const srcsetCount = 7; // total srcset variants across the 3 styles
|
|
73
|
+
const totalLookups = styleCount + styleCount * srcsetCount; // ~72 per media
|
|
74
|
+
bench(`baseline: ${totalLookups} sequential per-style queries`, async () => {
|
|
75
|
+
for (let i = 0; i < totalLookups; i++) {
|
|
76
|
+
await new Promise((r) => setTimeout(r, QUERY_LATENCY_MS));
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
bench('batched: 1 query covers all lookups', async () => {
|
|
80
|
+
await new Promise((r) => setTimeout(r, QUERY_LATENCY_MS));
|
|
81
|
+
});
|
|
82
|
+
});
|