@actuate-media/cms-core 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/dist/__tests__/api/collections-ai-create.test.d.ts +2 -0
- package/dist/__tests__/api/collections-ai-create.test.d.ts.map +1 -0
- package/dist/__tests__/api/collections-ai-create.test.js +313 -0
- package/dist/__tests__/api/collections-ai-create.test.js.map +1 -0
- package/dist/__tests__/fields/relations.test.d.ts +2 -0
- package/dist/__tests__/fields/relations.test.d.ts.map +1 -0
- package/dist/__tests__/fields/relations.test.js +243 -0
- package/dist/__tests__/fields/relations.test.js.map +1 -0
- package/dist/actions.d.ts +10 -0
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +71 -0
- package/dist/actions.js.map +1 -1
- package/dist/api/handlers.d.ts.map +1 -1
- package/dist/api/handlers.js +204 -3
- package/dist/api/handlers.js.map +1 -1
- package/dist/collections/presets.d.ts.map +1 -1
- package/dist/collections/presets.js +17 -1
- package/dist/collections/presets.js.map +1 -1
- package/dist/fields/index.d.ts +3 -1
- package/dist/fields/index.d.ts.map +1 -1
- package/dist/fields/index.js +2 -1
- package/dist/fields/index.js.map +1 -1
- package/dist/fields/presets.d.ts +28 -0
- package/dist/fields/presets.d.ts.map +1 -1
- package/dist/fields/presets.js +45 -0
- package/dist/fields/presets.js.map +1 -1
- package/dist/fields/relations.d.ts +87 -0
- package/dist/fields/relations.d.ts.map +1 -0
- package/dist/fields/relations.js +199 -0
- package/dist/fields/relations.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"collections-ai-create.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/api/collections-ai-create.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
// IMPORTANT: vi.mock calls are hoisted. We mock `@actuate-media/plugin-ai`
|
|
3
|
+
// so the dynamic import inside handlers.ts resolves to a deterministic
|
|
4
|
+
// generator instead of trying to hit a real LLM provider.
|
|
5
|
+
vi.mock('@actuate-media/plugin-ai', () => ({
|
|
6
|
+
generateDocumentContent: vi.fn(async (input) => ({
|
|
7
|
+
data: {
|
|
8
|
+
title: 'Five Spring Cleaning Tips',
|
|
9
|
+
slug: 'five-spring-cleaning-tips',
|
|
10
|
+
excerpt: 'A practical guide.',
|
|
11
|
+
body: 'When spring arrives, tackle these five tasks first.',
|
|
12
|
+
},
|
|
13
|
+
title: 'Five Spring Cleaning Tips',
|
|
14
|
+
slug: 'five-spring-cleaning-tips',
|
|
15
|
+
seo: {
|
|
16
|
+
metaTitle: 'Spring Cleaning Tips',
|
|
17
|
+
metaDescription: 'Five practical spring cleaning tips for busy households.',
|
|
18
|
+
schemaType: input?.collection?.seo?.defaultSchemaType ?? 'Article',
|
|
19
|
+
},
|
|
20
|
+
usage: { promptTokens: 100, completionTokens: 250 },
|
|
21
|
+
durationMs: 42,
|
|
22
|
+
})),
|
|
23
|
+
}));
|
|
24
|
+
import { handleActuateAPI } from '../../api/index.js';
|
|
25
|
+
import { initDB } from '../../db.js';
|
|
26
|
+
import { generateApiKey } from '../../security/api-key-enhanced.js';
|
|
27
|
+
const VALID_SECRET = 'a'.repeat(48);
|
|
28
|
+
function createMockDB(opts) {
|
|
29
|
+
const apiKey = opts.apiKey;
|
|
30
|
+
let lastCreatedDoc = null;
|
|
31
|
+
const dbMock = {
|
|
32
|
+
apiKey: {
|
|
33
|
+
findMany: async () => (apiKey ? [apiKey] : []),
|
|
34
|
+
findUnique: async ({ where }) => {
|
|
35
|
+
if (!apiKey)
|
|
36
|
+
return null;
|
|
37
|
+
if (apiKey.keyHash !== where.keyHash)
|
|
38
|
+
return null;
|
|
39
|
+
return apiKey;
|
|
40
|
+
},
|
|
41
|
+
update: async () => apiKey,
|
|
42
|
+
},
|
|
43
|
+
document: {
|
|
44
|
+
findMany: async () => [],
|
|
45
|
+
findUnique: async () => lastCreatedDoc,
|
|
46
|
+
findFirst: async ({ where }) => {
|
|
47
|
+
// Return the most recently created doc if its id matches; otherwise
|
|
48
|
+
// null. updateDocument calls findFirst({ id, collection, deletedAt: null }).
|
|
49
|
+
if (!lastCreatedDoc)
|
|
50
|
+
return null;
|
|
51
|
+
if (where?.id && where.id !== lastCreatedDoc.id)
|
|
52
|
+
return null;
|
|
53
|
+
return lastCreatedDoc;
|
|
54
|
+
},
|
|
55
|
+
count: async () => 0,
|
|
56
|
+
create: async ({ data }) => {
|
|
57
|
+
lastCreatedDoc = {
|
|
58
|
+
...data,
|
|
59
|
+
id: 'doc-ai-1',
|
|
60
|
+
createdAt: new Date(),
|
|
61
|
+
updatedAt: new Date(),
|
|
62
|
+
};
|
|
63
|
+
return lastCreatedDoc;
|
|
64
|
+
},
|
|
65
|
+
update: async ({ data }) => {
|
|
66
|
+
lastCreatedDoc = { ...lastCreatedDoc, ...data, updatedAt: new Date() };
|
|
67
|
+
return lastCreatedDoc;
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
version: {
|
|
71
|
+
create: async ({ data }) => ({ ...data, id: 'ver-1' }),
|
|
72
|
+
findMany: async () => [],
|
|
73
|
+
},
|
|
74
|
+
documentRevision: {
|
|
75
|
+
create: async ({ data }) => ({ ...data, id: 'rev-1' }),
|
|
76
|
+
},
|
|
77
|
+
auditLog: { create: async () => ({}) },
|
|
78
|
+
webhook: { findMany: async () => [] },
|
|
79
|
+
webhookDelivery: { create: async () => ({}) },
|
|
80
|
+
user: {},
|
|
81
|
+
session: {},
|
|
82
|
+
media: {},
|
|
83
|
+
};
|
|
84
|
+
// createDocument may wrap the create in a transaction. Run the callback
|
|
85
|
+
// against the mock itself so the same model proxies fire.
|
|
86
|
+
dbMock.$transaction = async (arg) => {
|
|
87
|
+
if (typeof arg === 'function') {
|
|
88
|
+
return await arg(dbMock);
|
|
89
|
+
}
|
|
90
|
+
if (Array.isArray(arg)) {
|
|
91
|
+
return Promise.all(arg);
|
|
92
|
+
}
|
|
93
|
+
return arg;
|
|
94
|
+
};
|
|
95
|
+
return dbMock;
|
|
96
|
+
}
|
|
97
|
+
async function makeKey(scopes) {
|
|
98
|
+
const { key, keyHash, keyPrefix } = await generateApiKey({ prefix: 'act_sk', scopes });
|
|
99
|
+
return {
|
|
100
|
+
key,
|
|
101
|
+
record: {
|
|
102
|
+
id: 'apikey-ai',
|
|
103
|
+
keyHash,
|
|
104
|
+
keyPrefix,
|
|
105
|
+
userId: 'user-1',
|
|
106
|
+
scopes,
|
|
107
|
+
ipRestrictions: null,
|
|
108
|
+
expiresAt: null,
|
|
109
|
+
lastUsedAt: null,
|
|
110
|
+
revokedAt: null,
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
const postsCollection = {
|
|
115
|
+
slug: 'posts',
|
|
116
|
+
labels: { singular: 'Post', plural: 'Posts' },
|
|
117
|
+
type: 'post',
|
|
118
|
+
seo: { defaultSchemaType: 'BlogPosting' },
|
|
119
|
+
fields: {
|
|
120
|
+
title: { type: 'text', label: 'Title', required: true },
|
|
121
|
+
slug: { type: 'slug', label: 'Slug', required: true },
|
|
122
|
+
excerpt: { type: 'text', label: 'Excerpt' },
|
|
123
|
+
body: { type: 'richText', label: 'Body' },
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
const pagesCollection = {
|
|
127
|
+
slug: 'pages',
|
|
128
|
+
labels: { singular: 'Page', plural: 'Pages' },
|
|
129
|
+
type: 'page',
|
|
130
|
+
fields: {
|
|
131
|
+
title: { type: 'text', label: 'Title', required: true },
|
|
132
|
+
slug: { type: 'slug', label: 'Slug', required: true },
|
|
133
|
+
layout: { type: 'blocks', label: 'Layout' },
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
describe('POST /collections/:slug/ai-create', () => {
|
|
137
|
+
beforeEach(() => {
|
|
138
|
+
delete globalThis.__actuateConfig;
|
|
139
|
+
process.env.CMS_SECRET = VALID_SECRET;
|
|
140
|
+
});
|
|
141
|
+
it('creates a document via AI when caller has create scope on the collection', async () => {
|
|
142
|
+
const { key, record } = await makeKey({ collections: ['posts'], actions: ['create'] });
|
|
143
|
+
const db = createMockDB({ apiKey: record });
|
|
144
|
+
initDB(db);
|
|
145
|
+
const handler = handleActuateAPI({
|
|
146
|
+
prismaClient: db,
|
|
147
|
+
config: { collections: { posts: postsCollection } },
|
|
148
|
+
});
|
|
149
|
+
const response = await handler(new Request('https://example.com/api/cms/collections/posts/ai-create', {
|
|
150
|
+
method: 'POST',
|
|
151
|
+
headers: {
|
|
152
|
+
Authorization: `Bearer ${key}`,
|
|
153
|
+
'Content-Type': 'application/json',
|
|
154
|
+
},
|
|
155
|
+
body: JSON.stringify({ prompt: 'Write a post about spring cleaning' }),
|
|
156
|
+
}));
|
|
157
|
+
const body = await response.json();
|
|
158
|
+
expect({ status: response.status, body }).toMatchObject({ status: 201 });
|
|
159
|
+
expect(body.data.document.id).toBe('doc-ai-1');
|
|
160
|
+
expect(body.data.document.title).toBe('Five Spring Cleaning Tips');
|
|
161
|
+
expect(body.data.document.slug).toBe('five-spring-cleaning-tips');
|
|
162
|
+
expect(body.data.document.status).toBe('DRAFT');
|
|
163
|
+
// SEO metadata flows back through the generation block (not pageSettings,
|
|
164
|
+
// which is a page-builder-only wire shape).
|
|
165
|
+
expect(body.data.generation.seo.schemaType).toBe('BlogPosting');
|
|
166
|
+
expect(body.data.generation.usage.completionTokens).toBe(250);
|
|
167
|
+
});
|
|
168
|
+
it('returns 400 when prompt is missing', async () => {
|
|
169
|
+
const { key, record } = await makeKey({ collections: ['posts'], actions: ['create'] });
|
|
170
|
+
const db = createMockDB({ apiKey: record });
|
|
171
|
+
initDB(db);
|
|
172
|
+
const handler = handleActuateAPI({
|
|
173
|
+
prismaClient: db,
|
|
174
|
+
config: { collections: { posts: postsCollection } },
|
|
175
|
+
});
|
|
176
|
+
const response = await handler(new Request('https://example.com/api/cms/collections/posts/ai-create', {
|
|
177
|
+
method: 'POST',
|
|
178
|
+
headers: { Authorization: `Bearer ${key}`, 'Content-Type': 'application/json' },
|
|
179
|
+
body: JSON.stringify({}),
|
|
180
|
+
}));
|
|
181
|
+
expect(response.status).toBe(400);
|
|
182
|
+
const body = await response.json();
|
|
183
|
+
expect(body.error).toContain('prompt');
|
|
184
|
+
});
|
|
185
|
+
it('returns 404 when the collection is not configured', async () => {
|
|
186
|
+
const { key, record } = await makeKey({ admin: true });
|
|
187
|
+
const db = createMockDB({ apiKey: record });
|
|
188
|
+
initDB(db);
|
|
189
|
+
const handler = handleActuateAPI({
|
|
190
|
+
prismaClient: db,
|
|
191
|
+
config: { collections: { posts: postsCollection } },
|
|
192
|
+
});
|
|
193
|
+
const response = await handler(new Request('https://example.com/api/cms/collections/no-such-collection/ai-create', {
|
|
194
|
+
method: 'POST',
|
|
195
|
+
headers: { Authorization: `Bearer ${key}`, 'Content-Type': 'application/json' },
|
|
196
|
+
body: JSON.stringify({ prompt: 'test' }),
|
|
197
|
+
}));
|
|
198
|
+
expect(response.status).toBe(404);
|
|
199
|
+
});
|
|
200
|
+
it('refuses page-builder collections and points to /page-builder/create', async () => {
|
|
201
|
+
const { key, record } = await makeKey({ collections: ['pages'], actions: ['create'] });
|
|
202
|
+
const db = createMockDB({ apiKey: record });
|
|
203
|
+
initDB(db);
|
|
204
|
+
const handler = handleActuateAPI({
|
|
205
|
+
prismaClient: db,
|
|
206
|
+
config: { collections: { pages: pagesCollection } },
|
|
207
|
+
});
|
|
208
|
+
const response = await handler(new Request('https://example.com/api/cms/collections/pages/ai-create', {
|
|
209
|
+
method: 'POST',
|
|
210
|
+
headers: { Authorization: `Bearer ${key}`, 'Content-Type': 'application/json' },
|
|
211
|
+
body: JSON.stringify({ prompt: 'Create a homepage' }),
|
|
212
|
+
}));
|
|
213
|
+
expect(response.status).toBe(400);
|
|
214
|
+
const body = await response.json();
|
|
215
|
+
expect(body.error).toContain('page-builder/create');
|
|
216
|
+
});
|
|
217
|
+
it('rejects callers without create scope on the target collection', async () => {
|
|
218
|
+
// Key has read scope only.
|
|
219
|
+
const { key, record } = await makeKey({ collections: ['posts'], actions: ['read'] });
|
|
220
|
+
const db = createMockDB({ apiKey: record });
|
|
221
|
+
initDB(db);
|
|
222
|
+
const handler = handleActuateAPI({
|
|
223
|
+
prismaClient: db,
|
|
224
|
+
config: { collections: { posts: postsCollection } },
|
|
225
|
+
});
|
|
226
|
+
const response = await handler(new Request('https://example.com/api/cms/collections/posts/ai-create', {
|
|
227
|
+
method: 'POST',
|
|
228
|
+
headers: { Authorization: `Bearer ${key}`, 'Content-Type': 'application/json' },
|
|
229
|
+
body: JSON.stringify({ prompt: 'test' }),
|
|
230
|
+
}));
|
|
231
|
+
expect(response.status).toBe(403);
|
|
232
|
+
});
|
|
233
|
+
it('publishes immediately when publish: true is passed AND caller has admin scope', async () => {
|
|
234
|
+
// Publish requires admin scope on the API key — under the hood that
|
|
235
|
+
// maps to role=ADMIN, which clears the publish gate in updateDocument.
|
|
236
|
+
// A non-admin key that has only create+update will still create the
|
|
237
|
+
// draft but be denied the publish transition (see the next test).
|
|
238
|
+
const { key, record } = await makeKey({ admin: true });
|
|
239
|
+
const db = createMockDB({ apiKey: record });
|
|
240
|
+
initDB(db);
|
|
241
|
+
const handler = handleActuateAPI({
|
|
242
|
+
prismaClient: db,
|
|
243
|
+
config: { collections: { posts: postsCollection } },
|
|
244
|
+
});
|
|
245
|
+
const response = await handler(new Request('https://example.com/api/cms/collections/posts/ai-create', {
|
|
246
|
+
method: 'POST',
|
|
247
|
+
headers: { Authorization: `Bearer ${key}`, 'Content-Type': 'application/json' },
|
|
248
|
+
body: JSON.stringify({ prompt: 'test', publish: true }),
|
|
249
|
+
}));
|
|
250
|
+
expect(response.status).toBe(201);
|
|
251
|
+
const body = await response.json();
|
|
252
|
+
expect(body.data.document.status).toBe('PUBLISHED');
|
|
253
|
+
});
|
|
254
|
+
it('falls back to DRAFT + warning when publish requested but update scope is missing', async () => {
|
|
255
|
+
// create-only key — draft will be created, publish will fail.
|
|
256
|
+
const { key, record } = await makeKey({ collections: ['posts'], actions: ['create'] });
|
|
257
|
+
const db = createMockDB({ apiKey: record });
|
|
258
|
+
initDB(db);
|
|
259
|
+
const handler = handleActuateAPI({
|
|
260
|
+
prismaClient: db,
|
|
261
|
+
config: { collections: { posts: postsCollection } },
|
|
262
|
+
});
|
|
263
|
+
const response = await handler(new Request('https://example.com/api/cms/collections/posts/ai-create', {
|
|
264
|
+
method: 'POST',
|
|
265
|
+
headers: { Authorization: `Bearer ${key}`, 'Content-Type': 'application/json' },
|
|
266
|
+
body: JSON.stringify({ prompt: 'test', publish: true }),
|
|
267
|
+
}));
|
|
268
|
+
expect(response.status).toBe(201);
|
|
269
|
+
const body = await response.json();
|
|
270
|
+
expect(body.data.document.status).toBe('DRAFT');
|
|
271
|
+
expect(body.warning).toMatch(/publish failed/i);
|
|
272
|
+
});
|
|
273
|
+
it('honors caller-supplied title and slug overrides', async () => {
|
|
274
|
+
const { key, record } = await makeKey({ collections: ['posts'], actions: ['create'] });
|
|
275
|
+
const db = createMockDB({ apiKey: record });
|
|
276
|
+
initDB(db);
|
|
277
|
+
const handler = handleActuateAPI({
|
|
278
|
+
prismaClient: db,
|
|
279
|
+
config: { collections: { posts: postsCollection } },
|
|
280
|
+
});
|
|
281
|
+
const response = await handler(new Request('https://example.com/api/cms/collections/posts/ai-create', {
|
|
282
|
+
method: 'POST',
|
|
283
|
+
headers: { Authorization: `Bearer ${key}`, 'Content-Type': 'application/json' },
|
|
284
|
+
body: JSON.stringify({
|
|
285
|
+
prompt: 'test',
|
|
286
|
+
title: 'My Custom Title',
|
|
287
|
+
slug: 'my-custom-slug',
|
|
288
|
+
}),
|
|
289
|
+
}));
|
|
290
|
+
expect(response.status).toBe(201);
|
|
291
|
+
const body = await response.json();
|
|
292
|
+
expect(body.data.document.title).toBe('My Custom Title');
|
|
293
|
+
expect(body.data.document.slug).toBe('my-custom-slug');
|
|
294
|
+
});
|
|
295
|
+
it('returns 400 when prompt exceeds 4000 char limit', async () => {
|
|
296
|
+
const { key, record } = await makeKey({ collections: ['posts'], actions: ['create'] });
|
|
297
|
+
const db = createMockDB({ apiKey: record });
|
|
298
|
+
initDB(db);
|
|
299
|
+
const handler = handleActuateAPI({
|
|
300
|
+
prismaClient: db,
|
|
301
|
+
config: { collections: { posts: postsCollection } },
|
|
302
|
+
});
|
|
303
|
+
const response = await handler(new Request('https://example.com/api/cms/collections/posts/ai-create', {
|
|
304
|
+
method: 'POST',
|
|
305
|
+
headers: { Authorization: `Bearer ${key}`, 'Content-Type': 'application/json' },
|
|
306
|
+
body: JSON.stringify({ prompt: 'A'.repeat(5000) }),
|
|
307
|
+
}));
|
|
308
|
+
expect(response.status).toBe(400);
|
|
309
|
+
const body = await response.json();
|
|
310
|
+
expect(body.error).toContain('exceeds');
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
//# sourceMappingURL=collections-ai-create.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"collections-ai-create.test.js","sourceRoot":"","sources":["../../../src/__tests__/api/collections-ai-create.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relations.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/fields/relations.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { collectRelationRefs, parsePopulateParam, populateRelations, validateRelations, } from '../../fields/relations.js';
|
|
3
|
+
// ─── Test fixtures ────────────────────────────────────────────────────────
|
|
4
|
+
function buildLookup(docs) {
|
|
5
|
+
return {
|
|
6
|
+
async fetchByIds(collections, ids) {
|
|
7
|
+
const result = new Map();
|
|
8
|
+
for (const id of ids) {
|
|
9
|
+
const doc = docs[id];
|
|
10
|
+
if (doc && collections.includes(doc.collection)) {
|
|
11
|
+
result.set(id, doc);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return result;
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
const blogFields = {
|
|
19
|
+
title: { type: 'text', label: 'Title' },
|
|
20
|
+
author: { type: 'relationship', label: 'Author', relationTo: 'team' },
|
|
21
|
+
relatedPosts: {
|
|
22
|
+
type: 'relationship',
|
|
23
|
+
label: 'Related posts',
|
|
24
|
+
relationTo: 'posts',
|
|
25
|
+
hasMany: true,
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
const arrayFields = {
|
|
29
|
+
title: { type: 'text', label: 'Title' },
|
|
30
|
+
sections: {
|
|
31
|
+
type: 'array',
|
|
32
|
+
label: 'Sections',
|
|
33
|
+
fields: {
|
|
34
|
+
heading: { type: 'text', label: 'Heading' },
|
|
35
|
+
service: { type: 'relationship', label: 'Service', relationTo: 'services' },
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
// ─── collectRelationRefs ─────────────────────────────────────────────────
|
|
40
|
+
describe('collectRelationRefs', () => {
|
|
41
|
+
it('extracts top-level single + many relation ids', () => {
|
|
42
|
+
const { byCollection, refsByPath } = collectRelationRefs(blogFields, {
|
|
43
|
+
title: 'Hello',
|
|
44
|
+
author: 'team-1',
|
|
45
|
+
relatedPosts: ['post-1', 'post-2'],
|
|
46
|
+
});
|
|
47
|
+
expect(byCollection.get('team')).toEqual(new Set(['team-1']));
|
|
48
|
+
expect(byCollection.get('posts')).toEqual(new Set(['post-1', 'post-2']));
|
|
49
|
+
expect(refsByPath).toHaveLength(2);
|
|
50
|
+
expect(refsByPath[0]?.path).toBe('author');
|
|
51
|
+
expect(refsByPath[1]?.path).toBe('relatedPosts');
|
|
52
|
+
});
|
|
53
|
+
it('recurses into array fields', () => {
|
|
54
|
+
const { byCollection, refsByPath } = collectRelationRefs(arrayFields, {
|
|
55
|
+
title: 'Page',
|
|
56
|
+
sections: [
|
|
57
|
+
{ heading: 'A', service: 'svc-1' },
|
|
58
|
+
{ heading: 'B', service: 'svc-2' },
|
|
59
|
+
],
|
|
60
|
+
});
|
|
61
|
+
expect(byCollection.get('services')).toEqual(new Set(['svc-1', 'svc-2']));
|
|
62
|
+
expect(refsByPath.map((r) => r.path)).toEqual(['sections[0].service', 'sections[1].service']);
|
|
63
|
+
});
|
|
64
|
+
it('ignores empty / null / non-string values', () => {
|
|
65
|
+
const { byCollection } = collectRelationRefs(blogFields, {
|
|
66
|
+
title: 'X',
|
|
67
|
+
author: '',
|
|
68
|
+
relatedPosts: [null, 'post-1', '', undefined, 123],
|
|
69
|
+
});
|
|
70
|
+
expect(byCollection.get('team')).toBeUndefined();
|
|
71
|
+
expect(byCollection.get('posts')).toEqual(new Set(['post-1']));
|
|
72
|
+
});
|
|
73
|
+
it('handles multi-target relationTo arrays', () => {
|
|
74
|
+
const fields = {
|
|
75
|
+
featured: {
|
|
76
|
+
type: 'relationship',
|
|
77
|
+
label: 'Featured',
|
|
78
|
+
relationTo: ['posts', 'case-studies'],
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
const { byCollection } = collectRelationRefs(fields, { featured: 'doc-1' });
|
|
82
|
+
expect(byCollection.get('posts')).toEqual(new Set(['doc-1']));
|
|
83
|
+
expect(byCollection.get('case-studies')).toEqual(new Set(['doc-1']));
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
// ─── validateRelations ───────────────────────────────────────────────────
|
|
87
|
+
describe('validateRelations', () => {
|
|
88
|
+
it('passes when every id resolves to a live document', async () => {
|
|
89
|
+
const lookup = buildLookup({
|
|
90
|
+
'team-1': { id: 'team-1', collection: 'team', title: 'Jane' },
|
|
91
|
+
'post-1': { id: 'post-1', collection: 'posts', title: 'A' },
|
|
92
|
+
});
|
|
93
|
+
await expect(validateRelations(blogFields, { title: 'X', author: 'team-1', relatedPosts: ['post-1'] }, lookup)).resolves.toBeUndefined();
|
|
94
|
+
});
|
|
95
|
+
it('throws when a single-ref id is missing, naming the path + collection', async () => {
|
|
96
|
+
const lookup = buildLookup({});
|
|
97
|
+
await expect(validateRelations(blogFields, { title: 'X', author: 'ghost' }, lookup)).rejects.toThrow(/Relation field "author" references document "ghost" which does not exist in collection "team"/);
|
|
98
|
+
});
|
|
99
|
+
it('throws when ANY id in a hasMany array is missing', async () => {
|
|
100
|
+
const lookup = buildLookup({
|
|
101
|
+
'post-1': { id: 'post-1', collection: 'posts' },
|
|
102
|
+
// post-2 missing
|
|
103
|
+
});
|
|
104
|
+
await expect(validateRelations(blogFields, { relatedPosts: ['post-1', 'post-2'] }, lookup)).rejects.toThrow(/post-2.*posts/);
|
|
105
|
+
});
|
|
106
|
+
it('treats null / empty values as "not set" (no validation needed)', async () => {
|
|
107
|
+
const lookup = buildLookup({});
|
|
108
|
+
await expect(validateRelations(blogFields, { title: 'X', author: '', relatedPosts: [] }, lookup)).resolves.toBeUndefined();
|
|
109
|
+
});
|
|
110
|
+
it('accepts a doc when it lives in ANY of the multi-target collections', async () => {
|
|
111
|
+
const fields = {
|
|
112
|
+
featured: {
|
|
113
|
+
type: 'relationship',
|
|
114
|
+
label: 'Featured',
|
|
115
|
+
relationTo: ['posts', 'case-studies'],
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
const lookup = buildLookup({
|
|
119
|
+
'cs-1': { id: 'cs-1', collection: 'case-studies', title: 'Big Co' },
|
|
120
|
+
});
|
|
121
|
+
await expect(validateRelations(fields, { featured: 'cs-1' }, lookup)).resolves.toBeUndefined();
|
|
122
|
+
});
|
|
123
|
+
it('validates relations nested inside array fields', async () => {
|
|
124
|
+
const lookup = buildLookup({
|
|
125
|
+
'svc-1': { id: 'svc-1', collection: 'services' },
|
|
126
|
+
});
|
|
127
|
+
await expect(validateRelations(arrayFields, {
|
|
128
|
+
sections: [
|
|
129
|
+
{ heading: 'A', service: 'svc-1' },
|
|
130
|
+
{ heading: 'B', service: 'missing' },
|
|
131
|
+
],
|
|
132
|
+
}, lookup)).rejects.toThrow(/sections\[1\]\.service.*missing.*services/);
|
|
133
|
+
});
|
|
134
|
+
it('batches per collection — one fetchByIds call per target collection', async () => {
|
|
135
|
+
let callCount = 0;
|
|
136
|
+
let totalIds = 0;
|
|
137
|
+
const lookup = {
|
|
138
|
+
async fetchByIds(_cols, ids) {
|
|
139
|
+
callCount++;
|
|
140
|
+
totalIds += ids.length;
|
|
141
|
+
const m = new Map();
|
|
142
|
+
for (const id of ids)
|
|
143
|
+
m.set(id, { id, collection: _cols[0] });
|
|
144
|
+
return m;
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
await validateRelations(blogFields, { author: 'team-1', relatedPosts: ['post-1', 'post-2', 'post-3'] }, lookup);
|
|
148
|
+
expect(callCount).toBe(2); // team, posts
|
|
149
|
+
expect(totalIds).toBe(4); // 1 team + 3 posts
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
// ─── populateRelations ───────────────────────────────────────────────────
|
|
153
|
+
describe('populateRelations', () => {
|
|
154
|
+
const lookup = buildLookup({
|
|
155
|
+
'team-1': {
|
|
156
|
+
id: 'team-1',
|
|
157
|
+
collection: 'team',
|
|
158
|
+
title: 'Jane',
|
|
159
|
+
slug: 'jane',
|
|
160
|
+
status: 'PUBLISHED',
|
|
161
|
+
},
|
|
162
|
+
'post-1': {
|
|
163
|
+
id: 'post-1',
|
|
164
|
+
collection: 'posts',
|
|
165
|
+
title: 'Post A',
|
|
166
|
+
slug: 'a',
|
|
167
|
+
status: 'PUBLISHED',
|
|
168
|
+
},
|
|
169
|
+
'post-2': { id: 'post-2', collection: 'posts', title: 'Post B', slug: 'b', status: 'DRAFT' },
|
|
170
|
+
});
|
|
171
|
+
it('expands a single-ref id into a summary object', async () => {
|
|
172
|
+
const populated = await populateRelations(blogFields, { title: 'X', author: 'team-1' }, lookup, { paths: '*' });
|
|
173
|
+
expect(populated.title).toBe('X');
|
|
174
|
+
expect(populated.author.id).toBe('team-1');
|
|
175
|
+
expect(populated.author.title).toBe('Jane');
|
|
176
|
+
expect(populated.author.collection).toBe('team');
|
|
177
|
+
});
|
|
178
|
+
it('expands a hasMany id array into summary objects in order', async () => {
|
|
179
|
+
const populated = await populateRelations(blogFields, { title: 'X', relatedPosts: ['post-1', 'post-2'] }, lookup, { paths: '*' });
|
|
180
|
+
const list = populated.relatedPosts;
|
|
181
|
+
expect(list).toHaveLength(2);
|
|
182
|
+
expect(list[0].title).toBe('Post A');
|
|
183
|
+
expect(list[1].title).toBe('Post B');
|
|
184
|
+
});
|
|
185
|
+
it('leaves the original document untouched (returns a clone)', async () => {
|
|
186
|
+
const input = { title: 'X', author: 'team-1' };
|
|
187
|
+
const populated = await populateRelations(blogFields, input, lookup, { paths: '*' });
|
|
188
|
+
expect(input.author).toBe('team-1'); // original id preserved
|
|
189
|
+
expect(populated.author).not.toBe(input.author);
|
|
190
|
+
});
|
|
191
|
+
it('returns input verbatim when populate paths is empty / not provided', async () => {
|
|
192
|
+
const input = { title: 'X', author: 'team-1' };
|
|
193
|
+
const populated = await populateRelations(blogFields, input, lookup, {});
|
|
194
|
+
expect(populated).toEqual(input);
|
|
195
|
+
});
|
|
196
|
+
it('only populates the requested top-level paths', async () => {
|
|
197
|
+
const populated = await populateRelations(blogFields, { title: 'X', author: 'team-1', relatedPosts: ['post-1'] }, lookup, { paths: ['author'] });
|
|
198
|
+
expect(populated.author.title).toBe('Jane');
|
|
199
|
+
// relatedPosts not in whitelist → left as raw ids
|
|
200
|
+
expect(populated.relatedPosts).toEqual(['post-1']);
|
|
201
|
+
});
|
|
202
|
+
it('falls back to the raw id when a referenced doc cannot be found', async () => {
|
|
203
|
+
const populated = await populateRelations(blogFields, { title: 'X', author: 'ghost' }, lookup, {
|
|
204
|
+
paths: '*',
|
|
205
|
+
});
|
|
206
|
+
expect(populated.author).toBe('ghost');
|
|
207
|
+
});
|
|
208
|
+
it('populates relations nested in array fields', async () => {
|
|
209
|
+
const populated = await populateRelations(arrayFields, { title: 'P', sections: [{ heading: 'A', service: 'svc-1' }] }, buildLookup({
|
|
210
|
+
'svc-1': { id: 'svc-1', collection: 'services', title: 'Cleaning' },
|
|
211
|
+
}), { paths: '*' });
|
|
212
|
+
const sec = populated.sections[0];
|
|
213
|
+
expect(sec.service.title).toBe('Cleaning');
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
// ─── parsePopulateParam ──────────────────────────────────────────────────
|
|
217
|
+
describe('parsePopulateParam', () => {
|
|
218
|
+
it('returns null for missing / falsy values', () => {
|
|
219
|
+
expect(parsePopulateParam(null)).toBeNull();
|
|
220
|
+
expect(parsePopulateParam(undefined)).toBeNull();
|
|
221
|
+
expect(parsePopulateParam('')).toBeNull();
|
|
222
|
+
expect(parsePopulateParam('false')).toBeNull();
|
|
223
|
+
expect(parsePopulateParam('0')).toBeNull();
|
|
224
|
+
});
|
|
225
|
+
it('treats * and true as "expand all"', () => {
|
|
226
|
+
expect(parsePopulateParam('*')).toEqual({ paths: '*' });
|
|
227
|
+
expect(parsePopulateParam('true')).toEqual({ paths: '*' });
|
|
228
|
+
});
|
|
229
|
+
it('parses a comma-separated whitelist', () => {
|
|
230
|
+
expect(parsePopulateParam('author,relatedPosts')).toEqual({
|
|
231
|
+
paths: ['author', 'relatedPosts'],
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
it('promotes a list that contains * to expand-all', () => {
|
|
235
|
+
expect(parsePopulateParam('author,*,relatedPosts')).toEqual({ paths: '*' });
|
|
236
|
+
});
|
|
237
|
+
it('trims whitespace and drops empty entries', () => {
|
|
238
|
+
expect(parsePopulateParam(' author , , relatedPosts ')).toEqual({
|
|
239
|
+
paths: ['author', 'relatedPosts'],
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
//# sourceMappingURL=relations.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relations.test.js","sourceRoot":"","sources":["../../../src/__tests__/fields/relations.test.ts"],"names":[],"mappings":"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"}
|
package/dist/actions.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type RelationLookup } from './fields/relations.js';
|
|
1
2
|
export interface ActionContext {
|
|
2
3
|
userId: string;
|
|
3
4
|
role: string;
|
|
@@ -23,6 +24,15 @@ export interface ListResult {
|
|
|
23
24
|
pageSize: number;
|
|
24
25
|
totalPages: number;
|
|
25
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* Build a {@link RelationLookup} backed by the active Prisma client. The
|
|
29
|
+
* lookup batches by collection and filters out soft-deleted docs so a
|
|
30
|
+
* relation that points at a tombstoned record is treated as broken.
|
|
31
|
+
*
|
|
32
|
+
* Exported so the API layer (resolve, list, get) can reuse it without
|
|
33
|
+
* pulling in the entire actions module.
|
|
34
|
+
*/
|
|
35
|
+
export declare function buildRelationLookup(db: any): RelationLookup;
|
|
26
36
|
/** Create a new document in a collection. */
|
|
27
37
|
export declare function createDocument(collection: string, data: Record<string, unknown>, ctx: ActionContext): Promise<Record<string, unknown>>;
|
|
28
38
|
/** Update an existing document. Merges incoming data with existing to prevent field loss. */
|