includio-cms 0.23.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.
Files changed (34) hide show
  1. package/API.md +1 -1
  2. package/CHANGELOG.md +21 -0
  3. package/DOCS.md +1 -1
  4. package/ROADMAP.md +1 -0
  5. package/dist/core/server/entries/operations/get.bench.d.ts +1 -0
  6. package/dist/core/server/entries/operations/get.bench.js +68 -0
  7. package/dist/core/server/entries/operations/get.js +17 -7
  8. package/dist/core/server/fields/utils/imageStyles.bench.d.ts +1 -0
  9. package/dist/core/server/fields/utils/imageStyles.bench.js +82 -0
  10. package/dist/core/server/fields/utils/imageStyles.js +49 -53
  11. package/dist/core/server/media/operations/backgroundMaintenance.d.ts +6 -0
  12. package/dist/core/server/media/operations/backgroundMaintenance.js +6 -1
  13. package/dist/core/server/media/styles/operations/getImageStyle.d.ts +7 -0
  14. package/dist/core/server/media/styles/operations/getImageStyle.js +24 -0
  15. package/dist/db-postgres/index.d.ts +1 -1
  16. package/dist/db-postgres/index.js +27 -0
  17. package/dist/paraglide/messages/_index.d.ts +36 -3
  18. package/dist/paraglide/messages/_index.js +71 -3
  19. package/dist/paraglide/messages/en.d.ts +5 -0
  20. package/dist/paraglide/messages/en.js +14 -0
  21. package/dist/paraglide/messages/pl.d.ts +5 -0
  22. package/dist/paraglide/messages/pl.js +14 -0
  23. package/dist/types/adapters/db.d.ts +16 -0
  24. package/dist/types/adapters/db.js +8 -1
  25. package/dist/updates/0.24.0/index.d.ts +2 -0
  26. package/dist/updates/0.24.0/index.js +20 -0
  27. package/dist/updates/index.js +2 -1
  28. package/package.json +2 -1
  29. package/dist/paraglide/messages/hello_world.d.ts +0 -5
  30. package/dist/paraglide/messages/hello_world.js +0 -33
  31. package/dist/paraglide/messages/login_hello.d.ts +0 -16
  32. package/dist/paraglide/messages/login_hello.js +0 -34
  33. package/dist/paraglide/messages/login_please_login.d.ts +0 -16
  34. package/dist/paraglide/messages/login_please_login.js +0 -34
package/API.md CHANGED
@@ -1,4 +1,4 @@
1
- # includio-cms — Public API v0.23.0
1
+ # includio-cms — Public API v0.24.0
2
2
 
3
3
  > Auto-generated by `scripts/generate-api-md.ts`. Do not edit by hand.
4
4
 
package/CHANGELOG.md CHANGED
@@ -3,6 +3,27 @@
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.24.0 — 2026-04-30
7
+
8
+ Faza 11 — Performance Pass. Eliminacja N+1 w render path: `getImageStylesBatch` (jeden SELECT zamiast N×getImageStyle dla styles + srcset wariantów) + `_getRawEntries` z batchowanym `getEntryVersions` (jeden call zamiast per-entry). Sharp srcset pipeline odrefactorowany — Promise.all wokół per-style queries usunięty (jeden batch + sequential Sharp generation z try/catch dla missing entries). Background maintenance lock potwierdzony testami (concurrent invocation skipped). Bench infra: nowy projekt vitest `bench` + skrypt `pnpm bench` + 2 representative benchmarks (image styles + raw entries).
9
+
10
+ ### Added
11
+ - `databaseAdapter.getImageStylesBatch(requests)` — nowa metoda adaptera. Zwraca `Map<key, ImageStyle>` keyed przez `imageStyleKey(mediaFileId, style)`. Implementacja `db-postgres`: jeden SELECT `WHERE mediaFileId IN (...)` + JS-side filter po (name, width, height, quality, aspectRatio) matching unique index. Skala impactu: 100 entries × 3 styles × 4 srcset = ~1200 queries → 1 query per media file (~30 queries dla 30 unique files). Bench `getImageStyles`: ~750 ops/s (1.3ms p75) dla 1 media × 3 styles × srcset; baseline N+1 (72 sequential): ~11 ops/s — **71× speedup**.
12
+ - `_getRawEntries()` (`src/lib/core/server/entries/operations/get.ts`) zrefactorowany — zamiast per-entry `getEntryVersions({ entryIds: [entry.id] })` w `Promise.all(dbEntries.map(...))` (N+1) teraz jeden `getEntryVersions({ entryIds: dbEntries.map(e => e.id) })` + group by `entryId` w Map. Adapter API bez zmian (już obsługiwał array `entryIds`). Skala: 100 entries → 2 queries zamiast 101. Bench: 387 ops/s (~2.6ms) dla 100 entries; baseline N+1 (101 sequential): ~7.9 ops/s — **50× speedup**.
13
+ - Sharp srcset variant generation: `Promise.all(widths.map(getImageStyle))` całkowicie wyeliminowany w `imageStyles.ts`. Nowy flow: zebrać wszystkie style requesty (base + srcset variants), jeden `getImageStylesBatch()` call, fallback do `createImageStyle()` per missing z try/catch. Konsekwencje: jeden Sharp timeout (30s, env `INCLUDIO_SHARP_TIMEOUT_MS`) nie zabija batch innych wariantów — zachowanie identyczne jak `Promise.allSettled`, ale bez Promise.all w pipeline.
14
+ - `getImageStylesBatch(requests)` operations wrapper (`src/lib/core/server/media/styles/operations/getImageStyle.ts`): koordynuje DB lookup + Sharp generation. Krótkie ścieżki: empty requests → empty Map bez DB call; wszystkie hity → cache return bez `createImageStyle`. Failed Sharp → `console.warn` + omission, nie throw.
15
+ - `imageStyleKey(mediaFileId, style)` — stabilna helper key z `$lib/types/adapters/db.js` (eksport public). Format: `${mediaFileId}|${name}|${width||0}|${height||0}|${quality||0}|${aspectRatio||0}` — matchuje unique index na `image_styles`.
16
+ - Background maintenance — in-process lock (`state.running` na `globalThis`) potwierdzony test suitem: `runMaintenance` exported (`@internal`), test concurrent dwóch wywołań → drugie loguje `Skipping — previous run still active`, batch operations wywołane dokładnie 1×. Komment w pliku: dla cluster mode wrap w `pg_advisory_lock`.
17
+ - `pnpm bench` — nowy skrypt + dedykowany vitest project `bench` (vite.config.ts). Include `src/**/*.bench.ts`. 2 nowe benchmarki: `imageStyles.bench.ts` (real `getImageStyles` z mocked adapter latency 1ms + synthetic N+1 vs batch comparison), `get.bench.ts` (real `_getRawEntries` 100 entries + comparison). Istniejący `resolveTypographyOrphans.bench.ts` teraz uruchamiany w tej samej komendzie.
18
+ - Lazy adapter imports (OpenAI / Anthropic / Nodemailer) — verified no-op. Wszystkie 3 adaptery (`src/lib/{ai-claude,ai-openai,email-nodemailer}/index.ts`) już używają `await import()` + cached singleton w closure. Type imports (`import type`) nie ładują runtime code. Trigger: pierwszy call metody adaptera (np. `generateAltText`, `sendMail`).
19
+
20
+ ### Breaking
21
+ - `DatabaseAdapter.getImageStylesBatch` — nowa **wymagana** metoda interfejsu (`src/lib/types/adapters/db.ts`). Custom adapters muszą zaimplementować. Sygnatura: `(requests: ImageStyleRequest[]) => Promise<Map<string, ImageStyle>>`. Reference impl: `src/lib/db-postgres/index.ts` (jeden SELECT z `inArray` po `mediaFileId` + JS filter). Helper key: `imageStyleKey(mediaFileId, style)` z `$lib/types/adapters/db.js`.
22
+
23
+ ### Notes
24
+
25
+ Zero schema migration — używa istniejącej tabeli `image_styles` i unique indeksu. Hot path render: collection list z 100 entries × image fields powinien zobaczyć ~50-70× redukcję wall-time queries (mierzone via bench na mocked latency 1ms; real PG latency wyższa, więc bezwzględne win większe). Sharp generation pozostaje sekwencyjne dla missing styles per request — celowo, żeby nie obciążać CPU; background maintenance batch (`batchGenerateAllStyles`) prewarmuje DB. `runMaintenance` eksportowany jako `@internal` dla testów — nie zaliczany do public API surface.
26
+
6
27
  ## 0.23.0 — 2026-04-30
7
28
 
8
29
  Faza 10 — Documentation Pass. DOCS.md uzupełniony (REST cURL + error codes, Entries error handling + transaction patterns, Admin UI a11y guide, Adapter Contracts z interfaces + peer deps + lazy import, Shop retry-payment lifecycle, edge cases dla number/boolean/date/datetime). Nowe sekcje: Stability Promise (semver, @public/@experimental/@internal, deprecation timeline) i Security Model (CSRF, CSP, rate-limit, API keys, link do KNOWN-RISKS.md). Migration Guide rozszerzony o master cheatsheet 0.x → 1.0 (agregat 0.16-0.22). ROADMAP cleanup: pre-v1.0 historia → ROADMAP-ARCHIVE.md.
package/DOCS.md CHANGED
@@ -1,4 +1,4 @@
1
- # Includio CMS Documentation (v0.23.0)
1
+ # Includio CMS Documentation (v0.24.0)
2
2
 
3
3
  > This file is auto-generated from the docs site. For the latest version, update the package.
4
4
 
package/ROADMAP.md CHANGED
@@ -17,6 +17,7 @@
17
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 -->
18
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
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/ -->
20
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)
21
22
 
22
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
- const entries = await Promise.all(dbEntries.map(async (entry) => {
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 = await getCMS().databaseAdapter.getEntryVersions({
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: getCMS().getBySlug(entry.slug),
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,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
+ });
@@ -1,4 +1,5 @@
1
- import { getImageStyle } from '../../media/styles/operations/getImageStyle.js';
1
+ import { getImageStylesBatch } from '../../media/styles/operations/getImageStyle.js';
2
+ import { imageStyleKey } from '../../../../types/adapters/db.js';
2
3
  import { getCMS } from '../../../cms.js';
3
4
  import { generateBlurDataUrl } from '../../media/utils/generateBlurDataUrl.js';
4
5
  export const defaultStyles = [
@@ -60,64 +61,59 @@ async function ensureBlurDataUrl(val) {
60
61
  return null;
61
62
  }
62
63
  }
64
+ function variantStyleFor(style, w) {
65
+ return {
66
+ ...style,
67
+ name: `${style.name}_${w}w`,
68
+ width: w,
69
+ srcset: undefined,
70
+ sizes: undefined
71
+ };
72
+ }
63
73
  export async function getImageStyles(field, val) {
64
74
  const hasCustomStyles = field.styles && field.styles.length > 0;
65
75
  const rawStyles = [...(isProcessableImage(val) && !hasCustomStyles ? defaultStyles : []), ...(field.styles || [])];
66
76
  const originalFormat = getOriginalFormat(val);
67
77
  const stylesArr = expandStyleFormats(rawStyles, originalFormat);
68
- const [styles, blurDataUrl] = await Promise.all([
69
- Promise.all(stylesArr.map(async (style) => {
70
- try {
71
- const styleDbData = await getImageStyle(val.id, style);
72
- if (!styleDbData)
73
- return null;
74
- const result = {
75
- url: styleDbData.url,
76
- media: styleDbData.media,
77
- mimeType: styleDbData.mimeType
78
- };
79
- // srcset expansion: generate width variants
80
- if (style.srcset && style.srcset.length > 0 && val.width) {
81
- const widths = style.srcset.filter((w) => w <= (val.width ?? 0));
82
- if (widths.length > 0) {
83
- const variants = await Promise.all(widths.map(async (w) => {
84
- try {
85
- const variantStyle = {
86
- ...style,
87
- name: `${style.name}_${w}w`,
88
- width: w,
89
- srcset: undefined,
90
- sizes: undefined
91
- };
92
- const variantData = await getImageStyle(val.id, variantStyle);
93
- if (!variantData)
94
- return null;
95
- return `${variantData.url} ${w}w`;
96
- }
97
- catch (e) {
98
- console.warn(`[CMS] Failed to generate srcset variant ${w}w for style "${style.name}" of media ${val.id}:`, e);
99
- return null;
100
- }
101
- }));
102
- const validVariants = variants.filter((v) => v !== null);
103
- if (validVariants.length > 0) {
104
- result.srcset = validVariants.join(', ');
105
- result.sizes = style.sizes;
106
- }
107
- }
108
- }
109
- return [style.name, result];
78
+ // Collect every (mediaFileId, style) request base styles + srcset variants — into one batch.
79
+ const requests = [];
80
+ for (const style of stylesArr) {
81
+ requests.push({ mediaFileId: val.id, style });
82
+ if (style.srcset && style.srcset.length > 0 && val.width) {
83
+ const widths = style.srcset.filter((w) => w <= (val.width ?? 0));
84
+ for (const w of widths) {
85
+ requests.push({ mediaFileId: val.id, style: variantStyleFor(style, w) });
110
86
  }
111
- catch (e) {
112
- console.warn(`[CMS] Failed to generate image style "${style.name}" for media ${val.id}:`, e);
113
- return null;
114
- }
115
- })),
87
+ }
88
+ }
89
+ const [styleMap, blurDataUrl] = await Promise.all([
90
+ getImageStylesBatch(requests),
116
91
  ensureBlurDataUrl(val)
117
92
  ]);
118
- const validStyles = styles.filter((s) => s !== null);
119
- return {
120
- styles: Object.fromEntries(validStyles),
121
- blurDataUrl
122
- };
93
+ const styles = {};
94
+ for (const style of stylesArr) {
95
+ const baseData = styleMap.get(imageStyleKey(val.id, style));
96
+ if (!baseData)
97
+ continue;
98
+ const result = {
99
+ url: baseData.url,
100
+ media: baseData.media,
101
+ mimeType: baseData.mimeType
102
+ };
103
+ if (style.srcset && style.srcset.length > 0 && val.width) {
104
+ const widths = style.srcset.filter((w) => w <= (val.width ?? 0));
105
+ const validVariants = [];
106
+ for (const w of widths) {
107
+ const variantData = styleMap.get(imageStyleKey(val.id, variantStyleFor(style, w)));
108
+ if (variantData)
109
+ validVariants.push(`${variantData.url} ${w}w`);
110
+ }
111
+ if (validVariants.length > 0) {
112
+ result.srcset = validVariants.join(', ');
113
+ result.sizes = style.sizes;
114
+ }
115
+ }
116
+ styles[style.name] = result;
117
+ }
118
+ return { styles, blurDataUrl };
123
119
  }
@@ -11,5 +11,11 @@ export declare function getMaintenanceStatus(): {
11
11
  nextRun: Date | null;
12
12
  lastResult: MaintenanceResult | null;
13
13
  };
14
+ /**
15
+ * @internal — exported for tests. The `state.running` guard is in-process only;
16
+ * for cluster-mode deployments wrap this in a `pg_advisory_lock` (or equivalent)
17
+ * to prevent multiple workers from running maintenance concurrently.
18
+ */
19
+ export declare function runMaintenance(): Promise<void>;
14
20
  export declare function startBackgroundMaintenance(): void;
15
21
  export declare function stopBackgroundMaintenance(): void;
@@ -24,7 +24,12 @@ export function getMaintenanceStatus() {
24
24
  lastResult: state.lastResult
25
25
  };
26
26
  }
27
- async function runMaintenance() {
27
+ /**
28
+ * @internal — exported for tests. The `state.running` guard is in-process only;
29
+ * for cluster-mode deployments wrap this in a `pg_advisory_lock` (or equivalent)
30
+ * to prevent multiple workers from running maintenance concurrently.
31
+ */
32
+ export async function runMaintenance() {
28
33
  const state = getState();
29
34
  if (state.running) {
30
35
  console.info('[maintenance] Skipping — previous run still active');
@@ -1,4 +1,11 @@
1
+ import { type ImageStyleRequest } from '../../../../../types/adapters/db.js';
1
2
  import type { ImageFieldStyle } from '../../../../../types/fields.js';
2
3
  import type { ImageStyle } from '../../../../../types/media.js';
3
4
  export declare function getImageStyle(mediaFileId: string, style: ImageFieldStyle): Promise<ImageStyle>;
4
5
  export declare function getImageStyleIfExists(mediaFileId: string, style: ImageFieldStyle): Promise<ImageStyle | null>;
6
+ /**
7
+ * Batch resolver: looks up all requested image styles in one DB query, then sequentially
8
+ * generates any missing ones via Sharp (createImageStyle). Returns a Map keyed by
9
+ * `imageStyleKey(mediaFileId, style)`. Failed Sharp generations are logged and omitted.
10
+ */
11
+ export declare function getImageStylesBatch(requests: ImageStyleRequest[]): Promise<Map<string, ImageStyle>>;
@@ -1,4 +1,5 @@
1
1
  import { getCMS } from '../../../../cms.js';
2
+ import { imageStyleKey } from '../../../../../types/adapters/db.js';
2
3
  import { createImageStyle } from './createMediaStyle.js';
3
4
  export async function getImageStyle(mediaFileId, style) {
4
5
  const imageStyle = await getCMS().databaseAdapter.getImageStyle(mediaFileId, style);
@@ -9,3 +10,26 @@ export async function getImageStyle(mediaFileId, style) {
9
10
  export async function getImageStyleIfExists(mediaFileId, style) {
10
11
  return getCMS().databaseAdapter.getImageStyle(mediaFileId, style);
11
12
  }
13
+ /**
14
+ * Batch resolver: looks up all requested image styles in one DB query, then sequentially
15
+ * generates any missing ones via Sharp (createImageStyle). Returns a Map keyed by
16
+ * `imageStyleKey(mediaFileId, style)`. Failed Sharp generations are logged and omitted.
17
+ */
18
+ export async function getImageStylesBatch(requests) {
19
+ if (requests.length === 0)
20
+ return new Map();
21
+ const cms = getCMS();
22
+ const cache = await cms.databaseAdapter.getImageStylesBatch(requests);
23
+ const missing = requests.filter((r) => !cache.has(imageStyleKey(r.mediaFileId, r.style)));
24
+ for (const { mediaFileId, style } of missing) {
25
+ const key = imageStyleKey(mediaFileId, style);
26
+ try {
27
+ const created = await createImageStyle(mediaFileId, style);
28
+ cache.set(key, created);
29
+ }
30
+ catch (e) {
31
+ console.warn(`[CMS] Failed to generate missing image style "${style.name}" for media ${mediaFileId}:`, e);
32
+ }
33
+ }
34
+ return cache;
35
+ }
@@ -1,7 +1,7 @@
1
1
  import { drizzle } from 'drizzle-orm/postgres-js';
2
2
  import type { Config } from './types.js';
3
3
  import * as schema from './schema/index.js';
4
- import type { DatabaseAdapter } from '../types/adapters/db.js';
4
+ import { type DatabaseAdapter } from '../types/adapters/db.js';
5
5
  export * from './schema/index.js';
6
6
  export * from '../server/db/schema/auth-schema.js';
7
7
  export type DatabaseAdapterWithDrizzle = DatabaseAdapter & {
@@ -1,6 +1,7 @@
1
1
  import { drizzle } from 'drizzle-orm/postgres-js';
2
2
  import postgres from 'postgres';
3
3
  import * as schema from './schema/index.js';
4
+ import { imageStyleKey } from '../types/adapters/db.js';
4
5
  import { eq, and, inArray, sql, isNull, desc, asc, SQL, ilike, or, count, gte, lte } from 'drizzle-orm';
5
6
  const SAFE_JSON_KEY = /^[a-zA-Z0-9_]+$/;
6
7
  function validateJsonPathKeys(path) {
@@ -656,6 +657,32 @@ export function pg(config) {
656
657
  media: imageStyle.media ? imageStyle.media : undefined
657
658
  };
658
659
  },
660
+ getImageStylesBatch: async (requests) => {
661
+ const result = new Map();
662
+ if (requests.length === 0)
663
+ return result;
664
+ const wantedKeys = new Set();
665
+ const mediaFileIds = new Set();
666
+ for (const r of requests) {
667
+ wantedKeys.add(imageStyleKey(r.mediaFileId, r.style));
668
+ mediaFileIds.add(r.mediaFileId);
669
+ }
670
+ const rows = await db
671
+ .select()
672
+ .from(schema.imageStylesTable)
673
+ .where(inArray(schema.imageStylesTable.mediaFileId, [...mediaFileIds]));
674
+ for (const row of rows) {
675
+ const key = `${row.mediaFileId}|${row.name}|${row.width ?? 0}|${row.height ?? 0}|${row.quality ?? 0}|${row.aspectRatio ?? 0}`;
676
+ if (!wantedKeys.has(key))
677
+ continue;
678
+ result.set(key, {
679
+ url: row.url,
680
+ mimeType: row.mimeType,
681
+ media: row.media ? row.media : undefined
682
+ });
683
+ }
684
+ return result;
685
+ },
659
686
  createImageStyle: async (mediaFileId, file, style) => {
660
687
  const mimeType = file.mimeType ?? `image/${style.format}`;
661
688
  const rows = await db.execute(sql `
@@ -1,3 +1,36 @@
1
- export * from "./hello_world.js";
2
- export * from "./login_hello.js";
3
- export * from "./login_please_login.js";
1
+ export function hello_world(inputs: {
2
+ name: NonNullable<unknown>;
3
+ }, options?: {
4
+ locale?: "en" | "pl";
5
+ }): string;
6
+ /**
7
+ * This function has been compiled by [Paraglide JS](https://inlang.com/m/gerre34r).
8
+ *
9
+ * - Changing this function will be over-written by the next build.
10
+ *
11
+ * - If you want to change the translations, you can either edit the source files e.g. `en.json`, or
12
+ * use another inlang app like [Fink](https://inlang.com/m/tdozzpar) or the [VSCode extension Sherlock](https://inlang.com/m/r7kp499g).
13
+ *
14
+ * @param {{}} inputs
15
+ * @param {{ locale?: "en" | "pl" }} options
16
+ * @returns {string}
17
+ */
18
+ declare function login_hello(inputs?: {}, options?: {
19
+ locale?: "en" | "pl";
20
+ }): string;
21
+ /**
22
+ * This function has been compiled by [Paraglide JS](https://inlang.com/m/gerre34r).
23
+ *
24
+ * - Changing this function will be over-written by the next build.
25
+ *
26
+ * - If you want to change the translations, you can either edit the source files e.g. `en.json`, or
27
+ * use another inlang app like [Fink](https://inlang.com/m/tdozzpar) or the [VSCode extension Sherlock](https://inlang.com/m/r7kp499g).
28
+ *
29
+ * @param {{}} inputs
30
+ * @param {{ locale?: "en" | "pl" }} options
31
+ * @returns {string}
32
+ */
33
+ declare function login_please_login(inputs?: {}, options?: {
34
+ locale?: "en" | "pl";
35
+ }): string;
36
+ export { login_hello as login.hello, login_please_login as login.please_login };
@@ -1,4 +1,72 @@
1
1
  /* eslint-disable */
2
- export * from './hello_world.js'
3
- export * from './login_hello.js'
4
- export * from './login_please_login.js'
2
+ import { getLocale, trackMessageCall, experimentalMiddlewareLocaleSplitting, isServer } from "../runtime.js"
3
+ import * as en from "./en.js"
4
+ import * as pl from "./pl.js"
5
+ /**
6
+ * This function has been compiled by [Paraglide JS](https://inlang.com/m/gerre34r).
7
+ *
8
+ * - Changing this function will be over-written by the next build.
9
+ *
10
+ * - If you want to change the translations, you can either edit the source files e.g. `en.json`, or
11
+ * use another inlang app like [Fink](https://inlang.com/m/tdozzpar) or the [VSCode extension Sherlock](https://inlang.com/m/r7kp499g).
12
+ *
13
+ * @param {{ name: NonNullable<unknown> }} inputs
14
+ * @param {{ locale?: "en" | "pl" }} options
15
+ * @returns {string}
16
+ */
17
+ /* @__NO_SIDE_EFFECTS__ */
18
+ export const hello_world = (inputs, options = {}) => {
19
+ if (experimentalMiddlewareLocaleSplitting && isServer === false) {
20
+ return /** @type {any} */ (globalThis).__paraglide_ssr.hello_world(inputs)
21
+ }
22
+ const locale = options.locale ?? getLocale()
23
+ trackMessageCall("hello_world", locale)
24
+ if (locale === "en") return en.hello_world(inputs)
25
+ return pl.hello_world(inputs)
26
+ };
27
+ /**
28
+ * This function has been compiled by [Paraglide JS](https://inlang.com/m/gerre34r).
29
+ *
30
+ * - Changing this function will be over-written by the next build.
31
+ *
32
+ * - If you want to change the translations, you can either edit the source files e.g. `en.json`, or
33
+ * use another inlang app like [Fink](https://inlang.com/m/tdozzpar) or the [VSCode extension Sherlock](https://inlang.com/m/r7kp499g).
34
+ *
35
+ * @param {{}} inputs
36
+ * @param {{ locale?: "en" | "pl" }} options
37
+ * @returns {string}
38
+ */
39
+ /* @__NO_SIDE_EFFECTS__ */
40
+ const login_hello = (inputs = {}, options = {}) => {
41
+ if (experimentalMiddlewareLocaleSplitting && isServer === false) {
42
+ return /** @type {any} */ (globalThis).__paraglide_ssr.login_hello(inputs)
43
+ }
44
+ const locale = options.locale ?? getLocale()
45
+ trackMessageCall("login_hello", locale)
46
+ if (locale === "en") return en.login_hello(inputs)
47
+ return pl.login_hello(inputs)
48
+ };
49
+ export { login_hello as "login.hello" }
50
+ /**
51
+ * This function has been compiled by [Paraglide JS](https://inlang.com/m/gerre34r).
52
+ *
53
+ * - Changing this function will be over-written by the next build.
54
+ *
55
+ * - If you want to change the translations, you can either edit the source files e.g. `en.json`, or
56
+ * use another inlang app like [Fink](https://inlang.com/m/tdozzpar) or the [VSCode extension Sherlock](https://inlang.com/m/r7kp499g).
57
+ *
58
+ * @param {{}} inputs
59
+ * @param {{ locale?: "en" | "pl" }} options
60
+ * @returns {string}
61
+ */
62
+ /* @__NO_SIDE_EFFECTS__ */
63
+ const login_please_login = (inputs = {}, options = {}) => {
64
+ if (experimentalMiddlewareLocaleSplitting && isServer === false) {
65
+ return /** @type {any} */ (globalThis).__paraglide_ssr.login_please_login(inputs)
66
+ }
67
+ const locale = options.locale ?? getLocale()
68
+ trackMessageCall("login_please_login", locale)
69
+ if (locale === "en") return en.login_please_login(inputs)
70
+ return pl.login_please_login(inputs)
71
+ };
72
+ export { login_please_login as "login.please_login" }
@@ -0,0 +1,5 @@
1
+ export const hello_world: (inputs: {
2
+ name: NonNullable<unknown>;
3
+ }) => string;
4
+ export const login_hello: (inputs: {}) => string;
5
+ export const login_please_login: (inputs: {}) => string;
@@ -0,0 +1,14 @@
1
+ /* eslint-disable */
2
+
3
+
4
+ export const hello_world = /** @type {(inputs: { name: NonNullable<unknown> }) => string} */ (i) => {
5
+ return `Hello, ${i.name} from en!`
6
+ };
7
+
8
+ export const login_hello = /** @type {(inputs: {}) => string} */ () => {
9
+ return `Welcome back`
10
+ };
11
+
12
+ export const login_please_login = /** @type {(inputs: {}) => string} */ () => {
13
+ return `Login to your account`
14
+ };
@@ -0,0 +1,5 @@
1
+ export const hello_world: (inputs: {
2
+ name: NonNullable<unknown>;
3
+ }) => string;
4
+ export const login_hello: (inputs: {}) => string;
5
+ export const login_please_login: (inputs: {}) => string;
@@ -0,0 +1,14 @@
1
+ /* eslint-disable */
2
+
3
+
4
+ export const hello_world = /** @type {(inputs: { name: NonNullable<unknown> }) => string} */ (i) => {
5
+ return `Hello, ${i.name} from pl!`
6
+ };
7
+
8
+ export const login_hello = /** @type {(inputs: {}) => string} */ () => {
9
+ return `Witaj ponownie`
10
+ };
11
+
12
+ export const login_please_login = /** @type {(inputs: {}) => string} */ () => {
13
+ return `Zaloguj się na swoje konto`
14
+ };
@@ -44,6 +44,7 @@ export interface DatabaseAdapter {
44
44
  bulkDeleteMediaFiles: BulkDeleteMediaFiles;
45
45
  createMediaFile: CreateMediaFile;
46
46
  getImageStyle: GetImageStyle;
47
+ getImageStylesBatch: GetImageStylesBatch;
47
48
  createImageStyle: CreateImageStyle;
48
49
  deleteImageStylesByMediaFileId: DeleteImageStylesByMediaFileId;
49
50
  getAllImageStyles: GetAllImageStyles;
@@ -192,3 +193,18 @@ export type GetAllVideoStyles = () => Promise<{
192
193
  }[]>;
193
194
  export type DeleteAllVideoStyles = () => Promise<number>;
194
195
  export type GetImageStyle = (mediaFileId: string, style: ImageFieldStyle) => Promise<ImageStyle | null>;
196
+ export interface ImageStyleRequest {
197
+ mediaFileId: string;
198
+ style: ImageFieldStyle;
199
+ }
200
+ /**
201
+ * Batch lookup for image styles. Resolves multiple (mediaFileId, style) pairs in a single query
202
+ * to avoid N+1 in render paths that materialize image fields. Result is keyed by `imageStyleKey`.
203
+ */
204
+ export type GetImageStylesBatch = (requests: ImageStyleRequest[]) => Promise<Map<string, ImageStyle>>;
205
+ /**
206
+ * Stable cache key for an (mediaFileId, ImageFieldStyle) pair. Matches the unique index on
207
+ * `image_styles` (mediaFileId, name, COALESCE(width,0), COALESCE(height,0), COALESCE(quality,0),
208
+ * COALESCE(aspectRatio,0)).
209
+ */
210
+ export declare function imageStyleKey(mediaFileId: string, style: ImageFieldStyle): string;
@@ -1 +1,8 @@
1
- export {};
1
+ /**
2
+ * Stable cache key for an (mediaFileId, ImageFieldStyle) pair. Matches the unique index on
3
+ * `image_styles` (mediaFileId, name, COALESCE(width,0), COALESCE(height,0), COALESCE(quality,0),
4
+ * COALESCE(aspectRatio,0)).
5
+ */
6
+ export function imageStyleKey(mediaFileId, style) {
7
+ return `${mediaFileId}|${style.name}|${style.width ?? 0}|${style.height ?? 0}|${style.quality ?? 0}|${style.aspectRatio ?? 0}`;
8
+ }
@@ -0,0 +1,2 @@
1
+ import type { CmsUpdate } from '../index.js';
2
+ export declare const update: CmsUpdate;
@@ -0,0 +1,20 @@
1
+ export const update = {
2
+ version: '0.24.0',
3
+ date: '2026-04-30',
4
+ description: 'Faza 11 — Performance Pass. Eliminacja N+1 w render path: `getImageStylesBatch` (jeden SELECT zamiast N×getImageStyle dla styles + srcset wariantów) + `_getRawEntries` z batchowanym `getEntryVersions` (jeden call zamiast per-entry). Sharp srcset pipeline odrefactorowany — Promise.all wokół per-style queries usunięty (jeden batch + sequential Sharp generation z try/catch dla missing entries). Background maintenance lock potwierdzony testami (concurrent invocation skipped). Bench infra: nowy projekt vitest `bench` + skrypt `pnpm bench` + 2 representative benchmarks (image styles + raw entries).',
5
+ features: [
6
+ '`databaseAdapter.getImageStylesBatch(requests)` — nowa metoda adaptera. Zwraca `Map<key, ImageStyle>` keyed przez `imageStyleKey(mediaFileId, style)`. Implementacja `db-postgres`: jeden SELECT `WHERE mediaFileId IN (...)` + JS-side filter po (name, width, height, quality, aspectRatio) matching unique index. Skala impactu: 100 entries × 3 styles × 4 srcset = ~1200 queries → 1 query per media file (~30 queries dla 30 unique files). Bench `getImageStyles`: ~750 ops/s (1.3ms p75) dla 1 media × 3 styles × srcset; baseline N+1 (72 sequential): ~11 ops/s — **71× speedup**.',
7
+ '`_getRawEntries()` (`src/lib/core/server/entries/operations/get.ts`) zrefactorowany — zamiast per-entry `getEntryVersions({ entryIds: [entry.id] })` w `Promise.all(dbEntries.map(...))` (N+1) teraz jeden `getEntryVersions({ entryIds: dbEntries.map(e => e.id) })` + group by `entryId` w Map. Adapter API bez zmian (już obsługiwał array `entryIds`). Skala: 100 entries → 2 queries zamiast 101. Bench: 387 ops/s (~2.6ms) dla 100 entries; baseline N+1 (101 sequential): ~7.9 ops/s — **50× speedup**.',
8
+ 'Sharp srcset variant generation: `Promise.all(widths.map(getImageStyle))` całkowicie wyeliminowany w `imageStyles.ts`. Nowy flow: zebrać wszystkie style requesty (base + srcset variants), jeden `getImageStylesBatch()` call, fallback do `createImageStyle()` per missing z try/catch. Konsekwencje: jeden Sharp timeout (30s, env `INCLUDIO_SHARP_TIMEOUT_MS`) nie zabija batch innych wariantów — zachowanie identyczne jak `Promise.allSettled`, ale bez Promise.all w pipeline.',
9
+ '`getImageStylesBatch(requests)` operations wrapper (`src/lib/core/server/media/styles/operations/getImageStyle.ts`): koordynuje DB lookup + Sharp generation. Krótkie ścieżki: empty requests → empty Map bez DB call; wszystkie hity → cache return bez `createImageStyle`. Failed Sharp → `console.warn` + omission, nie throw.',
10
+ '`imageStyleKey(mediaFileId, style)` — stabilna helper key z `$lib/types/adapters/db.js` (eksport public). Format: `${mediaFileId}|${name}|${width||0}|${height||0}|${quality||0}|${aspectRatio||0}` — matchuje unique index na `image_styles`.',
11
+ 'Background maintenance — in-process lock (`state.running` na `globalThis`) potwierdzony test suitem: `runMaintenance` exported (`@internal`), test concurrent dwóch wywołań → drugie loguje `Skipping — previous run still active`, batch operations wywołane dokładnie 1×. Komment w pliku: dla cluster mode wrap w `pg_advisory_lock`.',
12
+ '`pnpm bench` — nowy skrypt + dedykowany vitest project `bench` (vite.config.ts). Include `src/**/*.bench.ts`. 2 nowe benchmarki: `imageStyles.bench.ts` (real `getImageStyles` z mocked adapter latency 1ms + synthetic N+1 vs batch comparison), `get.bench.ts` (real `_getRawEntries` 100 entries + comparison). Istniejący `resolveTypographyOrphans.bench.ts` teraz uruchamiany w tej samej komendzie.',
13
+ 'Lazy adapter imports (OpenAI / Anthropic / Nodemailer) — verified no-op. Wszystkie 3 adaptery (`src/lib/{ai-claude,ai-openai,email-nodemailer}/index.ts`) już używają `await import()` + cached singleton w closure. Type imports (`import type`) nie ładują runtime code. Trigger: pierwszy call metody adaptera (np. `generateAltText`, `sendMail`).'
14
+ ],
15
+ fixes: [],
16
+ breakingChanges: [
17
+ '`DatabaseAdapter.getImageStylesBatch` — nowa **wymagana** metoda interfejsu (`src/lib/types/adapters/db.ts`). Custom adapters muszą zaimplementować. Sygnatura: `(requests: ImageStyleRequest[]) => Promise<Map<string, ImageStyle>>`. Reference impl: `src/lib/db-postgres/index.ts` (jeden SELECT z `inArray` po `mediaFileId` + JS filter). Helper key: `imageStyleKey(mediaFileId, style)` z `$lib/types/adapters/db.js`.'
18
+ ],
19
+ notes: 'Zero schema migration — używa istniejącej tabeli `image_styles` i unique indeksu. Hot path render: collection list z 100 entries × image fields powinien zobaczyć ~50-70× redukcję wall-time queries (mierzone via bench na mocked latency 1ms; real PG latency wyższa, więc bezwzględne win większe). Sharp generation pozostaje sekwencyjne dla missing styles per request — celowo, żeby nie obciążać CPU; background maintenance batch (`batchGenerateAllStyles`) prewarmuje DB. `runMaintenance` eksportowany jako `@internal` dla testów — nie zaliczany do public API surface.'
20
+ };
@@ -57,7 +57,8 @@ import { update as update0200 } from './0.20.0/index.js';
57
57
  import { update as update0210 } from './0.21.0/index.js';
58
58
  import { update as update0220 } from './0.22.0/index.js';
59
59
  import { update as update0230 } from './0.23.0/index.js';
60
- export const updates = [update0065, update0066, update0067, update0068, update0069, update010, update011, update012, update013, update014, update015, update020, update022, update050, update051, update052, update053, update054, update055, update056, update057, update058, update060, update061, update062, update070, update071, update072, update073, update080, update090, update0100, update0110, update0120, update0130, update0131, update0132, update0133, update0134, update0140, update0141, update0142, update0143, update0144, update0145, update0146, update0150, update0151, update0152, update0153, update0154, update0155, update0160, update0180, update0190, update0200, update0210, update0220, update0230];
60
+ import { update as update0240 } from './0.24.0/index.js';
61
+ export const updates = [update0065, update0066, update0067, update0068, update0069, update010, update011, update012, update013, update014, update015, update020, update022, update050, update051, update052, update053, update054, update055, update056, update057, update058, update060, update061, update062, update070, update071, update072, update073, update080, update090, update0100, update0110, update0120, update0130, update0131, update0132, update0133, update0134, update0140, update0141, update0142, update0143, update0144, update0145, update0146, update0150, update0151, update0152, update0153, update0154, update0155, update0160, update0180, update0190, update0200, update0210, update0220, update0230, update0240];
61
62
  export const getUpdatesFrom = (fromVersion) => {
62
63
  const fromParts = fromVersion.split('.').map(Number);
63
64
  return updates.filter((update) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "includio-cms",
3
- "version": "0.23.0",
3
+ "version": "0.24.0",
4
4
  "scripts": {
5
5
  "dev": "vite dev",
6
6
  "build": "vite build && npm run prepack",
@@ -12,6 +12,7 @@
12
12
  "format": "prettier --write .",
13
13
  "lint": "prettier --check . && eslint .",
14
14
  "test:unit": "vitest",
15
+ "bench": "vitest bench --run --project=bench",
15
16
  "test": "npm run test:unit -- --run && npm run test:e2e",
16
17
  "test:e2e": "playwright test",
17
18
  "test:coverage": "vitest run --coverage",
@@ -1,5 +0,0 @@
1
- export function hello_world(inputs: {
2
- name: NonNullable<unknown>;
3
- }, options?: {
4
- locale?: "en" | "pl";
5
- }): string;
@@ -1,33 +0,0 @@
1
- /* eslint-disable */
2
- import { getLocale, trackMessageCall, experimentalMiddlewareLocaleSplitting, isServer } from '../runtime.js';
3
-
4
- const en_hello_world = /** @type {(inputs: { name: NonNullable<unknown> }) => string} */ (i) => {
5
- return `Hello, ${i.name} from en!`
6
- };
7
-
8
- const pl_hello_world = /** @type {(inputs: { name: NonNullable<unknown> }) => string} */ (i) => {
9
- return `Hello, ${i.name} from pl!`
10
- };
11
-
12
- /**
13
- * This function has been compiled by [Paraglide JS](https://inlang.com/m/gerre34r).
14
- *
15
- * - Changing this function will be over-written by the next build.
16
- *
17
- * - If you want to change the translations, you can either edit the source files e.g. `en.json`, or
18
- * use another inlang app like [Fink](https://inlang.com/m/tdozzpar) or the [VSCode extension Sherlock](https://inlang.com/m/r7kp499g).
19
- *
20
- * @param {{ name: NonNullable<unknown> }} inputs
21
- * @param {{ locale?: "en" | "pl" }} options
22
- * @returns {string}
23
- */
24
- /* @__NO_SIDE_EFFECTS__ */
25
- export const hello_world = (inputs, options = {}) => {
26
- if (experimentalMiddlewareLocaleSplitting && isServer === false) {
27
- return /** @type {any} */ (globalThis).__paraglide_ssr.hello_world(inputs)
28
- }
29
- const locale = options.locale ?? getLocale()
30
- trackMessageCall("hello_world", locale)
31
- if (locale === "en") return en_hello_world(inputs)
32
- return pl_hello_world(inputs)
33
- };
@@ -1,16 +0,0 @@
1
- export { login_hello as login.hello };
2
- /**
3
- * This function has been compiled by [Paraglide JS](https://inlang.com/m/gerre34r).
4
- *
5
- * - Changing this function will be over-written by the next build.
6
- *
7
- * - If you want to change the translations, you can either edit the source files e.g. `en.json`, or
8
- * use another inlang app like [Fink](https://inlang.com/m/tdozzpar) or the [VSCode extension Sherlock](https://inlang.com/m/r7kp499g).
9
- *
10
- * @param {{}} inputs
11
- * @param {{ locale?: "en" | "pl" }} options
12
- * @returns {string}
13
- */
14
- declare function login_hello(inputs?: {}, options?: {
15
- locale?: "en" | "pl";
16
- }): string;
@@ -1,34 +0,0 @@
1
- /* eslint-disable */
2
- import { getLocale, trackMessageCall, experimentalMiddlewareLocaleSplitting, isServer } from '../runtime.js';
3
-
4
- const en_login_hello = /** @type {(inputs: {}) => string} */ () => {
5
- return `Welcome back`
6
- };
7
-
8
- const pl_login_hello = /** @type {(inputs: {}) => string} */ () => {
9
- return `Witaj ponownie`
10
- };
11
-
12
- /**
13
- * This function has been compiled by [Paraglide JS](https://inlang.com/m/gerre34r).
14
- *
15
- * - Changing this function will be over-written by the next build.
16
- *
17
- * - If you want to change the translations, you can either edit the source files e.g. `en.json`, or
18
- * use another inlang app like [Fink](https://inlang.com/m/tdozzpar) or the [VSCode extension Sherlock](https://inlang.com/m/r7kp499g).
19
- *
20
- * @param {{}} inputs
21
- * @param {{ locale?: "en" | "pl" }} options
22
- * @returns {string}
23
- */
24
- /* @__NO_SIDE_EFFECTS__ */
25
- const login_hello = (inputs = {}, options = {}) => {
26
- if (experimentalMiddlewareLocaleSplitting && isServer === false) {
27
- return /** @type {any} */ (globalThis).__paraglide_ssr.login_hello(inputs)
28
- }
29
- const locale = options.locale ?? getLocale()
30
- trackMessageCall("login_hello", locale)
31
- if (locale === "en") return en_login_hello(inputs)
32
- return pl_login_hello(inputs)
33
- };
34
- export { login_hello as "login.hello" }
@@ -1,16 +0,0 @@
1
- export { login_please_login as login.please_login };
2
- /**
3
- * This function has been compiled by [Paraglide JS](https://inlang.com/m/gerre34r).
4
- *
5
- * - Changing this function will be over-written by the next build.
6
- *
7
- * - If you want to change the translations, you can either edit the source files e.g. `en.json`, or
8
- * use another inlang app like [Fink](https://inlang.com/m/tdozzpar) or the [VSCode extension Sherlock](https://inlang.com/m/r7kp499g).
9
- *
10
- * @param {{}} inputs
11
- * @param {{ locale?: "en" | "pl" }} options
12
- * @returns {string}
13
- */
14
- declare function login_please_login(inputs?: {}, options?: {
15
- locale?: "en" | "pl";
16
- }): string;
@@ -1,34 +0,0 @@
1
- /* eslint-disable */
2
- import { getLocale, trackMessageCall, experimentalMiddlewareLocaleSplitting, isServer } from '../runtime.js';
3
-
4
- const en_login_please_login = /** @type {(inputs: {}) => string} */ () => {
5
- return `Login to your account`
6
- };
7
-
8
- const pl_login_please_login = /** @type {(inputs: {}) => string} */ () => {
9
- return `Zaloguj się na swoje konto`
10
- };
11
-
12
- /**
13
- * This function has been compiled by [Paraglide JS](https://inlang.com/m/gerre34r).
14
- *
15
- * - Changing this function will be over-written by the next build.
16
- *
17
- * - If you want to change the translations, you can either edit the source files e.g. `en.json`, or
18
- * use another inlang app like [Fink](https://inlang.com/m/tdozzpar) or the [VSCode extension Sherlock](https://inlang.com/m/r7kp499g).
19
- *
20
- * @param {{}} inputs
21
- * @param {{ locale?: "en" | "pl" }} options
22
- * @returns {string}
23
- */
24
- /* @__NO_SIDE_EFFECTS__ */
25
- const login_please_login = (inputs = {}, options = {}) => {
26
- if (experimentalMiddlewareLocaleSplitting && isServer === false) {
27
- return /** @type {any} */ (globalThis).__paraglide_ssr.login_please_login(inputs)
28
- }
29
- const locale = options.locale ?? getLocale()
30
- trackMessageCall("login_please_login", locale)
31
- if (locale === "en") return en_login_please_login(inputs)
32
- return pl_login_please_login(inputs)
33
- };
34
- export { login_please_login as "login.please_login" }