includio-cms 0.16.0 → 0.18.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/CHANGELOG.md +80 -0
- package/DOCS.md +1 -1
- package/dist/admin/api/rest/routes/collections.js +1 -1
- package/dist/admin/api/rest/routes/entries.js +1 -1
- package/dist/admin/api/rest/routes/singletons.js +1 -1
- package/dist/admin/remote/entry.remote.js +20 -3
- package/dist/admin/remote/invite.d.ts +1 -1
- package/dist/admin/remote/preview.remote.js +10 -2
- package/dist/admin/remote/reorder.js +1 -1
- package/dist/admin/remote/shop.remote.d.ts +18 -18
- package/dist/cms/runtime/api.d.ts +10 -6
- package/dist/cms/runtime/api.js +7 -7
- package/dist/components/ui/accordion/accordion.svelte.d.ts +1 -1
- package/dist/components/ui/calendar/calendar.svelte.d.ts +1 -1
- package/dist/components/ui/command/command-dialog.svelte.d.ts +1 -1
- package/dist/components/ui/command/command-input.svelte.d.ts +1 -1
- package/dist/components/ui/command/command.svelte.d.ts +1 -1
- package/dist/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte.d.ts +1 -1
- package/dist/components/ui/input/input.svelte.d.ts +1 -1
- package/dist/components/ui/input-group/input-group-input.svelte.d.ts +1 -1
- package/dist/components/ui/input-group/input-group-textarea.svelte.d.ts +1 -1
- package/dist/components/ui/radio-group/radio-group.svelte.d.ts +1 -1
- package/dist/components/ui/sidebar/sidebar-input.svelte.d.ts +1 -1
- package/dist/components/ui/tabs/tabs.svelte.d.ts +1 -1
- package/dist/components/ui/textarea/textarea.svelte.d.ts +1 -1
- package/dist/components/ui/toggle-group/toggle-group-item.svelte.d.ts +1 -1
- package/dist/components/ui/toggle-group/toggle-group.svelte.d.ts +1 -1
- package/dist/core/server/entries/operations/create.js +1 -1
- package/dist/core/server/entries/operations/get.d.ts +20 -16
- package/dist/core/server/entries/operations/get.js +45 -214
- package/dist/core/server/entries/operations/resolveEntry.d.ts +94 -0
- package/dist/core/server/entries/operations/resolveEntry.js +210 -0
- package/dist/core/server/entries/operations/update.js +1 -1
- package/dist/core/server/fields/populateEntry.d.ts +9 -1
- package/dist/core/server/fields/populateEntry.js +22 -18
- package/dist/core/server/fields/resolveRelationFields.d.ts +2 -1
- package/dist/core/server/fields/resolveRelationFields.js +140 -34
- package/dist/core/server/fields/resolveRichtextLinks.d.ts +2 -1
- package/dist/core/server/fields/resolveRichtextLinks.js +2 -1
- package/dist/core/server/fields/resolveUrlFields.d.ts +2 -1
- package/dist/core/server/fields/resolveUrlFields.js +6 -5
- package/dist/core/server/generator/generator.js +17 -14
- package/dist/entity/index.js +1 -1
- package/dist/shop/server/populate.d.ts +2 -1
- package/dist/shop/server/populate.js +2 -1
- package/dist/sveltekit/server/index.d.ts +1 -1
- package/dist/sveltekit/server/index.js +1 -1
- package/dist/types/plugins.d.ts +6 -2
- package/dist/updates/0.17.0/index.d.ts +2 -0
- package/dist/updates/0.17.0/index.js +78 -0
- package/dist/updates/index.js +2 -1
- package/package.json +1 -1
|
@@ -1,3 +1,11 @@
|
|
|
1
1
|
import type { EntryData, PopulatedEntryData } from '../../../types/entries.js';
|
|
2
2
|
import type { Field } from '../../../types/fields.js';
|
|
3
|
-
|
|
3
|
+
import type { PopulateCtx } from '../entries/operations/resolveEntry.js';
|
|
4
|
+
/**
|
|
5
|
+
* Internal populate chain: relations → urls → media → richtext → custom → shop → typography orphans.
|
|
6
|
+
* Carries `ctx` (locale, status, depth, visited, populate config, current entryId) so every nested
|
|
7
|
+
* field resolver can cascade locale + recursion guards consistently.
|
|
8
|
+
*
|
|
9
|
+
* Not part of the public API. Userland uses `resolveEntry`/`resolveEntries`.
|
|
10
|
+
*/
|
|
11
|
+
export declare function _populate(data: EntryData, fields: Field[], ctx: PopulateCtx): Promise<PopulatedEntryData>;
|
|
@@ -5,11 +5,8 @@ import { resolveUrlFields } from './resolveUrlFields.js';
|
|
|
5
5
|
import { resolveTypographyOrphans } from './resolveTypographyOrphans.js';
|
|
6
6
|
import { getCMS } from '../../cms.js';
|
|
7
7
|
import { resolveShopFields } from '../../../shop/server/populate.js';
|
|
8
|
-
async function resolveCustomFields(data, fields) {
|
|
9
|
-
|
|
10
|
-
const hasCustom = fields.some((f) => f.type === 'custom' ||
|
|
11
|
-
f.type === 'object' ||
|
|
12
|
-
f.type === 'blocks');
|
|
8
|
+
async function resolveCustomFields(data, fields, ctx) {
|
|
9
|
+
const hasCustom = fields.some((f) => f.type === 'custom' || f.type === 'object' || f.type === 'blocks');
|
|
13
10
|
if (!hasCustom)
|
|
14
11
|
return data;
|
|
15
12
|
let cms;
|
|
@@ -26,13 +23,14 @@ async function resolveCustomFields(data, fields) {
|
|
|
26
23
|
case 'custom': {
|
|
27
24
|
const def = cms.customFields.get(field.fieldType);
|
|
28
25
|
if (def?.populateResolver && val != null) {
|
|
29
|
-
|
|
26
|
+
// Plugin populateResolver receives ctx as 3rd arg (breaking in 0.17.0; plugin API @experimental)
|
|
27
|
+
result[field.slug] = await def.populateResolver(val, field, ctx);
|
|
30
28
|
}
|
|
31
29
|
break;
|
|
32
30
|
}
|
|
33
31
|
case 'object':
|
|
34
32
|
if (val && typeof val === 'object') {
|
|
35
|
-
result[field.slug] = await resolveCustomFields(val, field.fields);
|
|
33
|
+
result[field.slug] = await resolveCustomFields(val, field.fields, ctx);
|
|
36
34
|
}
|
|
37
35
|
break;
|
|
38
36
|
case 'blocks':
|
|
@@ -40,7 +38,7 @@ async function resolveCustomFields(data, fields) {
|
|
|
40
38
|
result[field.slug] = await Promise.all(val.map(async (item) => {
|
|
41
39
|
const blockDef = field.of.find((d) => d.slug === item._slug);
|
|
42
40
|
if (blockDef) {
|
|
43
|
-
return await resolveCustomFields(item, blockDef.fields);
|
|
41
|
+
return await resolveCustomFields(item, blockDef.fields, ctx);
|
|
44
42
|
}
|
|
45
43
|
return item;
|
|
46
44
|
}));
|
|
@@ -50,14 +48,20 @@ async function resolveCustomFields(data, fields) {
|
|
|
50
48
|
}
|
|
51
49
|
return result;
|
|
52
50
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
51
|
+
/**
|
|
52
|
+
* Internal populate chain: relations → urls → media → richtext → custom → shop → typography orphans.
|
|
53
|
+
* Carries `ctx` (locale, status, depth, visited, populate config, current entryId) so every nested
|
|
54
|
+
* field resolver can cascade locale + recursion guards consistently.
|
|
55
|
+
*
|
|
56
|
+
* Not part of the public API. Userland uses `resolveEntry`/`resolveEntries`.
|
|
57
|
+
*/
|
|
58
|
+
export async function _populate(data, fields, ctx) {
|
|
59
|
+
let populated = await resolveRelationFields(data, fields, ctx);
|
|
60
|
+
populated = await resolveUrlFields(populated, fields, ctx);
|
|
61
|
+
populated = await resolveMediaFields(populated, fields);
|
|
62
|
+
populated = await resolveRichtextLinks(populated, fields, ctx);
|
|
63
|
+
populated = (await resolveCustomFields(populated, fields, ctx));
|
|
64
|
+
populated = (await resolveShopFields(populated, fields, ctx));
|
|
61
65
|
let fixOrphans = true;
|
|
62
66
|
try {
|
|
63
67
|
fixOrphans = getCMS().typographyConfig.fixOrphans !== false;
|
|
@@ -66,7 +70,7 @@ export async function populateEntryData(data, fields, language, entryId) {
|
|
|
66
70
|
// CMS not initialized — keep default
|
|
67
71
|
}
|
|
68
72
|
if (fixOrphans) {
|
|
69
|
-
|
|
73
|
+
populated = resolveTypographyOrphans(populated, fields);
|
|
70
74
|
}
|
|
71
|
-
return
|
|
75
|
+
return populated;
|
|
72
76
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import type { EntryData, PopulatedEntryData } from '../../../types/entries.js';
|
|
2
2
|
import type { Field } from '../../../types/fields.js';
|
|
3
|
-
|
|
3
|
+
import type { PopulateCtx } from '../entries/operations/resolveEntry.js';
|
|
4
|
+
export declare function resolveRelationFields(data: EntryData, fields: Field[], ctx: PopulateCtx): Promise<PopulatedEntryData>;
|
|
@@ -1,53 +1,72 @@
|
|
|
1
1
|
import { walkInlineBlockNodes, cloneDoc } from '../../../admin/components/tiptap/structured-content-utils.js';
|
|
2
|
-
import
|
|
3
|
-
|
|
2
|
+
import { getCMS } from '../../cms.js';
|
|
3
|
+
import { getFieldsFromConfig } from '../../fields/layoutUtils.js';
|
|
4
|
+
import { getEntrySlugPath, getSlugFromEntryData, getEntryPath } from './slugResolver.js';
|
|
5
|
+
function pickVersion(versions, status) {
|
|
6
|
+
const sorted = versions.slice().sort((a, b) => b.versionNumber - a.versionNumber);
|
|
7
|
+
const now = new Date();
|
|
8
|
+
switch (status) {
|
|
9
|
+
case 'published':
|
|
10
|
+
return sorted.find((v) => v.publishedAt != null && v.publishedAt <= now) ?? null;
|
|
11
|
+
case 'draft':
|
|
12
|
+
return sorted.find((v) => v.publishedAt == null) ?? null;
|
|
13
|
+
case 'scheduled':
|
|
14
|
+
return sorted.find((v) => v.publishedAt != null && v.publishedAt > now) ?? null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export async function resolveRelationFields(data, fields, ctx) {
|
|
4
18
|
const entriesIds = [];
|
|
19
|
+
const optedOutFields = new Set();
|
|
20
|
+
// Collect ids skipping fields opted out at top level (raw passthrough).
|
|
5
21
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
6
|
-
const collectIds = (value, fields) => {
|
|
22
|
+
const collectIds = (value, fields, topLevel) => {
|
|
7
23
|
for (const field of fields) {
|
|
8
24
|
const val = value?.[field.slug];
|
|
9
25
|
if (val == null)
|
|
10
26
|
continue;
|
|
27
|
+
if (topLevel && ctx.populate.fields?.[field.slug] === false) {
|
|
28
|
+
optedOutFields.add(field.slug);
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
11
31
|
switch (field.type) {
|
|
12
32
|
case 'relation': {
|
|
13
33
|
if (field.multiple && Array.isArray(val)) {
|
|
14
|
-
|
|
15
|
-
if (
|
|
34
|
+
for (const id of val) {
|
|
35
|
+
if (typeof id === 'string')
|
|
16
36
|
entriesIds.push(id);
|
|
17
|
-
|
|
18
|
-
});
|
|
37
|
+
}
|
|
19
38
|
}
|
|
20
39
|
else if (typeof val === 'string') {
|
|
21
|
-
|
|
22
|
-
entriesIds.push(val);
|
|
23
|
-
}
|
|
40
|
+
entriesIds.push(val);
|
|
24
41
|
}
|
|
25
42
|
break;
|
|
26
43
|
}
|
|
27
44
|
case 'object':
|
|
28
|
-
collectIds(val, field.fields);
|
|
45
|
+
collectIds(val, field.fields, false);
|
|
29
46
|
break;
|
|
30
47
|
case 'blocks':
|
|
31
48
|
if (Array.isArray(val)) {
|
|
32
49
|
val.forEach((item) => {
|
|
33
50
|
const objectDef = field.of.find((objDef) => objDef.slug === item._slug);
|
|
34
51
|
if (objectDef) {
|
|
35
|
-
collectIds(item, objectDef.fields);
|
|
52
|
+
collectIds(item, objectDef.fields, false);
|
|
36
53
|
}
|
|
37
54
|
});
|
|
38
55
|
}
|
|
39
56
|
break;
|
|
40
57
|
case 'content': {
|
|
41
58
|
const cf = field;
|
|
42
|
-
|
|
43
|
-
|
|
59
|
+
if (val &&
|
|
60
|
+
typeof val === 'object' &&
|
|
61
|
+
val.type === 'doc' &&
|
|
62
|
+
cf.inlineBlocks?.length) {
|
|
44
63
|
walkInlineBlockNodes(val, (node) => {
|
|
45
64
|
const bd = node.attrs?.blockData;
|
|
46
65
|
if (!bd || typeof bd !== 'object')
|
|
47
66
|
return;
|
|
48
67
|
const def = cf.inlineBlocks.find((b) => b.slug === node.attrs?.blockType);
|
|
49
68
|
if (def)
|
|
50
|
-
collectIds(bd, def.fields);
|
|
69
|
+
collectIds(bd, def.fields, false);
|
|
51
70
|
});
|
|
52
71
|
}
|
|
53
72
|
break;
|
|
@@ -55,20 +74,97 @@ export async function resolveRelationFields(data, fields, language) {
|
|
|
55
74
|
}
|
|
56
75
|
}
|
|
57
76
|
};
|
|
58
|
-
collectIds(data, fields);
|
|
77
|
+
collectIds(data, fields, true);
|
|
59
78
|
if (entriesIds.length === 0)
|
|
60
79
|
return data;
|
|
61
|
-
//
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
80
|
+
// Filter: don't fetch already-visited (cycle) or beyond maxDepth — those stay raw IDs
|
|
81
|
+
const overDepth = ctx.depth >= ctx.maxDepth;
|
|
82
|
+
const fetchableIds = overDepth
|
|
83
|
+
? []
|
|
84
|
+
: [...new Set(entriesIds)].filter((id) => !ctx.visited.has(id));
|
|
85
|
+
const cms = getCMS();
|
|
86
|
+
// entriesMap: id → Entry (populated) | null (fetched but missing version/missing entry).
|
|
87
|
+
// Absent key = id was NOT fetched (cycle, max depth, top-level opt-out) → falls back to raw id.
|
|
88
|
+
const entriesMap = {};
|
|
89
|
+
if (fetchableIds.length > 0) {
|
|
90
|
+
const dbEntries = await cms.databaseAdapter.getEntries({ ids: fetchableIds });
|
|
91
|
+
const aliveDbEntries = dbEntries.filter((e) => e.archivedAt == null);
|
|
92
|
+
const aliveIds = new Set(aliveDbEntries.map((e) => e.id));
|
|
93
|
+
// Strict: missing entry / archived → null in nested
|
|
94
|
+
for (const id of fetchableIds) {
|
|
95
|
+
if (!aliveIds.has(id))
|
|
96
|
+
entriesMap[id] = null;
|
|
97
|
+
}
|
|
98
|
+
if (aliveDbEntries.length > 0) {
|
|
99
|
+
const versions = await cms.databaseAdapter.getEntryVersions({
|
|
100
|
+
entryIds: aliveDbEntries.map((e) => e.id),
|
|
101
|
+
lang: ctx.locale
|
|
102
|
+
});
|
|
103
|
+
const versionsByEntry = new Map();
|
|
104
|
+
for (const v of versions) {
|
|
105
|
+
const arr = versionsByEntry.get(v.entryId) ?? [];
|
|
106
|
+
arr.push(v);
|
|
107
|
+
versionsByEntry.set(v.entryId, arr);
|
|
108
|
+
}
|
|
109
|
+
// Lazy import to avoid static circular dep with populateEntry → resolveRelationFields
|
|
110
|
+
const { _populate } = await import('./populateEntry.js');
|
|
111
|
+
await Promise.all(aliveDbEntries.map(async (dbEntry) => {
|
|
112
|
+
const vs = versionsByEntry.get(dbEntry.id) ?? [];
|
|
113
|
+
const picked = pickVersion(vs, ctx.status);
|
|
114
|
+
if (!picked) {
|
|
115
|
+
entriesMap[dbEntry.id] = null; // strict: missing version in locale → null
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
let config;
|
|
119
|
+
try {
|
|
120
|
+
config = cms.getBySlug(dbEntry.slug);
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
entriesMap[dbEntry.id] = null;
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const nestedFields = getFieldsFromConfig(config);
|
|
127
|
+
const nestedCtx = {
|
|
128
|
+
locale: ctx.locale,
|
|
129
|
+
status: ctx.status,
|
|
130
|
+
depth: ctx.depth + 1,
|
|
131
|
+
maxDepth: ctx.maxDepth,
|
|
132
|
+
visited: new Set([...ctx.visited, dbEntry.id]),
|
|
133
|
+
populate: ctx.populate,
|
|
134
|
+
entryId: dbEntry.id
|
|
135
|
+
};
|
|
136
|
+
try {
|
|
137
|
+
const populated = await _populate(picked.data, nestedFields, nestedCtx);
|
|
138
|
+
const slugPath = getEntrySlugPath(dbEntry.slug);
|
|
139
|
+
const slugValue = getSlugFromEntryData(picked.data, slugPath, ctx.locale);
|
|
140
|
+
const _url = slugValue ? getEntryPath(dbEntry.slug, slugValue) : undefined;
|
|
141
|
+
entriesMap[dbEntry.id] = {
|
|
142
|
+
_id: dbEntry.id,
|
|
143
|
+
_slug: dbEntry.slug,
|
|
144
|
+
_type: dbEntry.type,
|
|
145
|
+
_publishedAt: picked.publishedAt,
|
|
146
|
+
_url,
|
|
147
|
+
...(config.type === 'collection' && config.orderable
|
|
148
|
+
? { _sortOrder: dbEntry.sortOrder }
|
|
149
|
+
: {}),
|
|
150
|
+
...populated
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
console.error(`[CMS] Failed to populate nested entry ${dbEntry.id} (${dbEntry.slug}):`, error);
|
|
155
|
+
}
|
|
156
|
+
}));
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
const resolveRefId = (id) => {
|
|
160
|
+
// Has key (even if value is null) → use mapped value (Entry | null)
|
|
161
|
+
if (Object.prototype.hasOwnProperty.call(entriesMap, id))
|
|
162
|
+
return entriesMap[id];
|
|
163
|
+
// Not fetched (cycle, max depth, top-level opt-out) → raw ID
|
|
164
|
+
return id;
|
|
165
|
+
};
|
|
69
166
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
70
|
-
const resolveValues = (value, fields) => {
|
|
71
|
-
// Start with a copy of all original data to preserve non-field properties
|
|
167
|
+
const resolveValues = (value, fields, topLevel) => {
|
|
72
168
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
73
169
|
const result = { ...value };
|
|
74
170
|
for (const field of fields) {
|
|
@@ -77,33 +173,43 @@ export async function resolveRelationFields(data, fields, language) {
|
|
|
77
173
|
result[field.slug] = val;
|
|
78
174
|
continue;
|
|
79
175
|
}
|
|
176
|
+
// Per-field opt-out at top level → raw passthrough
|
|
177
|
+
if (topLevel && optedOutFields.has(field.slug)) {
|
|
178
|
+
result[field.slug] = val;
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
80
181
|
switch (field.type) {
|
|
81
182
|
case 'relation': {
|
|
82
183
|
if (field.multiple && Array.isArray(val)) {
|
|
83
|
-
result[field.slug] = val.map((id) =>
|
|
184
|
+
result[field.slug] = val.map((id) => typeof id === 'string' ? resolveRefId(id) : id);
|
|
185
|
+
}
|
|
186
|
+
else if (typeof val === 'string') {
|
|
187
|
+
result[field.slug] = resolveRefId(val);
|
|
84
188
|
}
|
|
85
189
|
else {
|
|
86
|
-
result[field.slug] =
|
|
190
|
+
result[field.slug] = val;
|
|
87
191
|
}
|
|
88
192
|
break;
|
|
89
193
|
}
|
|
90
194
|
case 'object':
|
|
91
|
-
result[field.slug] = resolveValues(val, field.fields);
|
|
195
|
+
result[field.slug] = resolveValues(val, field.fields, false);
|
|
92
196
|
break;
|
|
93
197
|
case 'blocks':
|
|
94
198
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
95
199
|
result[field.slug] = val.map((item) => {
|
|
96
200
|
const objectDef = field.of.find((objDef) => objDef.slug === item._slug);
|
|
97
201
|
if (objectDef) {
|
|
98
|
-
return resolveValues(item, objectDef.fields);
|
|
202
|
+
return resolveValues(item, objectDef.fields, false);
|
|
99
203
|
}
|
|
100
204
|
return item;
|
|
101
205
|
});
|
|
102
206
|
break;
|
|
103
207
|
case 'content': {
|
|
104
208
|
const cf = field;
|
|
105
|
-
|
|
106
|
-
|
|
209
|
+
if (val &&
|
|
210
|
+
typeof val === 'object' &&
|
|
211
|
+
val.type === 'doc' &&
|
|
212
|
+
cf.inlineBlocks?.length) {
|
|
107
213
|
const cloned = cloneDoc(val);
|
|
108
214
|
walkInlineBlockNodes(cloned, (node) => {
|
|
109
215
|
const bd = node.attrs?.blockData;
|
|
@@ -112,7 +218,7 @@ export async function resolveRelationFields(data, fields, language) {
|
|
|
112
218
|
const def = cf.inlineBlocks.find((b) => b.slug === node.attrs?.blockType);
|
|
113
219
|
if (!def)
|
|
114
220
|
return;
|
|
115
|
-
node.attrs.blockData = resolveValues(bd, def.fields);
|
|
221
|
+
node.attrs.blockData = resolveValues(bd, def.fields, false);
|
|
116
222
|
});
|
|
117
223
|
result[field.slug] = cloned;
|
|
118
224
|
}
|
|
@@ -124,5 +230,5 @@ export async function resolveRelationFields(data, fields, language) {
|
|
|
124
230
|
}
|
|
125
231
|
return result;
|
|
126
232
|
};
|
|
127
|
-
return resolveValues(data, fields);
|
|
233
|
+
return resolveValues(data, fields, true);
|
|
128
234
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import type { EntryData, PopulatedEntryData } from '../../../types/entries.js';
|
|
2
2
|
import type { Field } from '../../../types/fields.js';
|
|
3
|
-
|
|
3
|
+
import type { PopulateCtx } from '../entries/operations/resolveEntry.js';
|
|
4
|
+
export declare function resolveRichtextLinks(data: EntryData, fields: Field[], ctx: PopulateCtx): Promise<PopulatedEntryData>;
|
|
@@ -34,7 +34,8 @@ function resolveContentDoc(doc, slugMap) {
|
|
|
34
34
|
});
|
|
35
35
|
return cloned;
|
|
36
36
|
}
|
|
37
|
-
export async function resolveRichtextLinks(data, fields,
|
|
37
|
+
export async function resolveRichtextLinks(data, fields, ctx) {
|
|
38
|
+
const language = ctx.locale;
|
|
38
39
|
const entriesIds = [];
|
|
39
40
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
40
41
|
const collectIds = (value, fields) => {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import type { EntryData, PopulatedEntryData } from '../../../types/entries.js';
|
|
2
2
|
import type { Field } from '../../../types/fields.js';
|
|
3
|
-
|
|
3
|
+
import type { PopulateCtx } from '../entries/operations/resolveEntry.js';
|
|
4
|
+
export declare function resolveUrlFields(data: EntryData, fields: Field[], ctx: PopulateCtx): Promise<PopulatedEntryData>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { urlFieldDataSchema, urlFieldDataWithRelationSchema } from '../../../schemas/field/url.js';
|
|
2
2
|
import { walkInlineBlockNodes, cloneDoc } from '../../../admin/components/tiptap/structured-content-utils.js';
|
|
3
|
-
import {
|
|
3
|
+
import { _getDbEntries, _getDbEntryVersions } from '../entries/operations/get.js';
|
|
4
4
|
import { getEntrySlugPath, getSlugFromEntryData, getEntryPath } from './slugResolver.js';
|
|
5
5
|
import { isExternalUrl, mergeRel } from '../../fields/urlUtils.js';
|
|
6
6
|
const FLAT_KEY = '_flat';
|
|
@@ -34,7 +34,8 @@ function applyExternalAutoDetect(resolvedUrl, extras) {
|
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
|
-
export async function resolveUrlFields(data, fields,
|
|
37
|
+
export async function resolveUrlFields(data, fields, ctx) {
|
|
38
|
+
const language = ctx.locale;
|
|
38
39
|
const entriesIds = [];
|
|
39
40
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
40
41
|
const collectIds = (value, fields) => {
|
|
@@ -102,12 +103,12 @@ export async function resolveUrlFields(data, fields, language) {
|
|
|
102
103
|
collectIds(data, fields);
|
|
103
104
|
const slugMap = {};
|
|
104
105
|
if (entriesIds.length > 0) {
|
|
105
|
-
// Use raw DB calls to avoid recursive
|
|
106
|
-
const dbEntries = await
|
|
106
|
+
// Use raw DB calls to avoid recursive _populate → resolveUrlFields loop
|
|
107
|
+
const dbEntries = await _getDbEntries({ ids: entriesIds });
|
|
107
108
|
const entryIds = dbEntries.map((e) => e.id);
|
|
108
109
|
if (entryIds.length > 0) {
|
|
109
110
|
// Get published versions for the target language
|
|
110
|
-
const versions = await
|
|
111
|
+
const versions = await _getDbEntryVersions({
|
|
111
112
|
entryIds,
|
|
112
113
|
lang: language
|
|
113
114
|
});
|
|
@@ -179,21 +179,21 @@ function generateAPI(config) {
|
|
|
179
179
|
const filePath = join(cmsDir, 'api.ts');
|
|
180
180
|
let code = `// This file is auto-generated. Do not edit directly.\n\n`;
|
|
181
181
|
code += `
|
|
182
|
-
|
|
182
|
+
|
|
183
183
|
import type { SingleEntryMap, SingleSlug, CollectionEntryMap, CollectionSlug, FormEntryMap, SiteLanguage } from './types';
|
|
184
|
-
import {
|
|
184
|
+
import { resolveEntry, resolveEntries, countEntries, createFormSubmission, type PopulateConfig } from 'includio-cms/sveltekit/server';
|
|
185
185
|
|
|
186
186
|
`;
|
|
187
187
|
code += `
|
|
188
188
|
|
|
189
189
|
interface GetEntryOptions {
|
|
190
190
|
id?: string;
|
|
191
|
-
status?: '
|
|
192
|
-
|
|
191
|
+
status?: 'published' | 'draft' | 'scheduled';
|
|
192
|
+
populate?: PopulateConfig;
|
|
193
193
|
`;
|
|
194
194
|
if (config.languages && config.languages.length > 0) {
|
|
195
195
|
code += `
|
|
196
|
-
|
|
196
|
+
locale?: SiteLanguage;
|
|
197
197
|
`;
|
|
198
198
|
}
|
|
199
199
|
code += `
|
|
@@ -201,8 +201,11 @@ function generateAPI(config) {
|
|
|
201
201
|
|
|
202
202
|
interface GetEntriesOptions extends GetEntryOptions {
|
|
203
203
|
ids?: string[];
|
|
204
|
-
|
|
205
|
-
|
|
204
|
+
filter?: {
|
|
205
|
+
dataValues?: Record<string, unknown>;
|
|
206
|
+
dataLike?: Record<string, unknown>;
|
|
207
|
+
dataILikeOr?: Record<string, unknown>;
|
|
208
|
+
};
|
|
206
209
|
orderBy?: { column: 'createdAt' | 'updatedAt' | 'sortOrder'; direction: 'asc' | 'desc' };
|
|
207
210
|
dataOrderBy?: { field: string; direction: 'asc' | 'desc' };
|
|
208
211
|
limit?: number;
|
|
@@ -213,25 +216,25 @@ function generateAPI(config) {
|
|
|
213
216
|
code += `
|
|
214
217
|
|
|
215
218
|
export async function getSingleEntry<K extends SingleSlug>(slug: K, options: GetEntryOptions = {}): Promise<SingleEntryMap[K] | null> {
|
|
216
|
-
return (await
|
|
217
|
-
slug,
|
|
219
|
+
return (await resolveEntry({
|
|
220
|
+
collection: slug,
|
|
218
221
|
...options
|
|
219
222
|
})) as unknown as SingleEntryMap[K] | null;
|
|
220
223
|
}
|
|
221
224
|
|
|
222
225
|
export async function getCollectionEntry<K extends CollectionSlug>(slug: K, options: GetEntryOptions = {}): Promise<CollectionEntryMap[K] | null> {
|
|
223
|
-
return (await
|
|
224
|
-
slug,
|
|
226
|
+
return (await resolveEntry({
|
|
227
|
+
collection: slug,
|
|
225
228
|
...options
|
|
226
229
|
})) as unknown as CollectionEntryMap[K] | null;
|
|
227
230
|
}
|
|
228
231
|
|
|
229
232
|
export async function getCollectionEntries<K extends CollectionSlug>(slug: K, options: GetEntriesOptions = {}): Promise<CollectionEntryMap[K][]> {
|
|
230
|
-
return (await
|
|
233
|
+
return (await resolveEntries({ collection: slug, ...options })) as unknown as CollectionEntryMap[K][];
|
|
231
234
|
}
|
|
232
235
|
|
|
233
|
-
export async function countCollectionEntries<K extends CollectionSlug>(slug: K, options: Omit<GetEntriesOptions, 'limit' | 'offset' | 'orderBy'> = {}): Promise<number> {
|
|
234
|
-
return countEntries({ slug, ...options });
|
|
236
|
+
export async function countCollectionEntries<K extends CollectionSlug>(slug: K, options: Omit<GetEntriesOptions, 'limit' | 'offset' | 'orderBy' | 'populate'> = {}): Promise<number> {
|
|
237
|
+
return countEntries({ collection: slug, ...options });
|
|
235
238
|
}
|
|
236
239
|
|
|
237
240
|
${config.forms && config.forms.length > 0
|
package/dist/entity/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { generateZodSchemaFromFields } from '../core/fields/fieldSchemaToTs.js';
|
|
2
2
|
import { getFieldsFromConfig } from '../core/fields/layoutUtils.js';
|
|
3
|
-
import { getRawEntries } from '../core/server/entries/operations/get.js';
|
|
3
|
+
import { _getRawEntries as getRawEntries } from '../core/server/entries/operations/get.js';
|
|
4
4
|
export function createEntityAPI(cms, opts) {
|
|
5
5
|
const db = cms.databaseAdapter;
|
|
6
6
|
const userId = opts?.userId ?? 'system';
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Field } from '../../types/fields.js';
|
|
2
|
+
import type { PopulateCtx } from '../../core/server/entries/operations/resolveEntry.js';
|
|
2
3
|
export interface PopulatedShopField {
|
|
3
4
|
/** Netto w PLN (number, ≤6dp). Od 0.15.2. */
|
|
4
5
|
basePrice: number;
|
|
@@ -14,4 +15,4 @@ export interface PopulatedShopField {
|
|
|
14
15
|
attributes: Record<string, string> | null;
|
|
15
16
|
}>;
|
|
16
17
|
}
|
|
17
|
-
export declare function resolveShopFields(data: Record<string, unknown>, fields: Field[],
|
|
18
|
+
export declare function resolveShopFields(data: Record<string, unknown>, fields: Field[], ctx: PopulateCtx): Promise<Record<string, unknown>>;
|
|
@@ -15,7 +15,8 @@ function toPopulated(row) {
|
|
|
15
15
|
}))
|
|
16
16
|
};
|
|
17
17
|
}
|
|
18
|
-
export async function resolveShopFields(data, fields,
|
|
18
|
+
export async function resolveShopFields(data, fields, ctx) {
|
|
19
|
+
const entryId = ctx.entryId;
|
|
19
20
|
if (!entryId)
|
|
20
21
|
return data;
|
|
21
22
|
// Skip if shop is not configured
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { includioCMS } from './handle.js';
|
|
2
2
|
export { cmsLayoutLoad } from './layout.js';
|
|
3
|
-
export {
|
|
3
|
+
export { resolveEntry, resolveEntries, countEntries, type ResolveEntryOptions, type ResolveEntriesOptions, type CountEntriesOptions, type PopulateConfig, type ResolveStatus } from '../../core/server/entries/operations/resolveEntry.js';
|
|
4
4
|
export { createFormSubmission } from '../../core/server/forms/submissions/operations/create.js';
|
|
5
5
|
export { parseFormDataForSubmission } from '../../core/server/forms/submissions/utils/parseMultipart.js';
|
|
6
6
|
export { createConsentLog } from '../../core/server/consentLogs/operations/create.js';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { includioCMS } from './handle.js';
|
|
2
2
|
export { cmsLayoutLoad } from './layout.js';
|
|
3
|
-
export {
|
|
3
|
+
export { resolveEntry, resolveEntries, countEntries } from '../../core/server/entries/operations/resolveEntry.js';
|
|
4
4
|
export { createFormSubmission } from '../../core/server/forms/submissions/operations/create.js';
|
|
5
5
|
export { parseFormDataForSubmission } from '../../core/server/forms/submissions/utils/parseMultipart.js';
|
|
6
6
|
export { createConsentLog } from '../../core/server/consentLogs/operations/create.js';
|
package/dist/types/plugins.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { Component } from 'svelte';
|
|
|
2
2
|
import type { z } from 'zod';
|
|
3
3
|
import type { RawEntry } from './entries.js';
|
|
4
4
|
import type { CustomField } from './fields.js';
|
|
5
|
+
import type { PopulateCtx } from '../core/server/entries/operations/resolveEntry.js';
|
|
5
6
|
export interface CustomFieldDefinition {
|
|
6
7
|
/** Unique field type slug, e.g. 'photo-grid' */
|
|
7
8
|
fieldType: string;
|
|
@@ -13,8 +14,11 @@ export interface CustomFieldDefinition {
|
|
|
13
14
|
zodSchema: (field: CustomField, languages: string[]) => z.ZodType;
|
|
14
15
|
/** TypeScript type string for codegen */
|
|
15
16
|
tsType?: string;
|
|
16
|
-
/**
|
|
17
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Populate/resolve step for API output.
|
|
19
|
+
* @experimental ctx (3rd arg) added in 0.17.0 — carries locale/status/depth/visited cascade.
|
|
20
|
+
*/
|
|
21
|
+
populateResolver?: (value: unknown, field: CustomField, ctx: PopulateCtx) => Promise<unknown>;
|
|
18
22
|
}
|
|
19
23
|
export interface PluginConfig {
|
|
20
24
|
slug: string;
|