@momentumcms/core 0.1.0 → 0.1.3
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 +20 -0
- package/index.js +653 -0
- package/package.json +31 -31
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,23 @@
|
|
|
1
|
+
## 0.1.3 (2026-02-16)
|
|
2
|
+
|
|
3
|
+
This was a version bump only for core to align it with other projects, there were no code changes.
|
|
4
|
+
|
|
5
|
+
## 0.1.2 (2026-02-16)
|
|
6
|
+
|
|
7
|
+
### 🩹 Fixes
|
|
8
|
+
|
|
9
|
+
- **release:** centralize manifestRootsToUpdate to update both source and dist ([2b8f832](https://github.com/DonaldMurillo/momentum-cms/commit/2b8f832))
|
|
10
|
+
- **create-app:** fix Angular SSR, Analog builds, and CJS/ESM compatibility ([28d4d0a](https://github.com/DonaldMurillo/momentum-cms/commit/28d4d0a))
|
|
11
|
+
|
|
12
|
+
### ❤️ Thank You
|
|
13
|
+
|
|
14
|
+
- Claude Opus 4.6
|
|
15
|
+
- Donald Murillo @DonaldMurillo
|
|
16
|
+
|
|
17
|
+
## 0.1.1 (2026-02-16)
|
|
18
|
+
|
|
19
|
+
This was a version bump only for core to align it with other projects, there were no code changes.
|
|
20
|
+
|
|
1
21
|
## 0.1.0 (2026-02-16)
|
|
2
22
|
|
|
3
23
|
### 🚀 Features
|
package/index.js
ADDED
|
@@ -0,0 +1,653 @@
|
|
|
1
|
+
// libs/core/src/lib/collections/define-collection.ts
|
|
2
|
+
function defineCollection(config) {
|
|
3
|
+
const collection = {
|
|
4
|
+
timestamps: true,
|
|
5
|
+
// Enable timestamps by default
|
|
6
|
+
...config
|
|
7
|
+
};
|
|
8
|
+
if (!collection.slug) {
|
|
9
|
+
throw new Error("Collection must have a slug");
|
|
10
|
+
}
|
|
11
|
+
if (!collection.fields || collection.fields.length === 0) {
|
|
12
|
+
throw new Error(`Collection "${collection.slug}" must have at least one field`);
|
|
13
|
+
}
|
|
14
|
+
if (!/^[a-z][a-z0-9-]*$/.test(collection.slug)) {
|
|
15
|
+
throw new Error(
|
|
16
|
+
`Collection slug "${collection.slug}" must be kebab-case (lowercase letters, numbers, and hyphens, starting with a letter)`
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
return collection;
|
|
20
|
+
}
|
|
21
|
+
function defineGlobal(config) {
|
|
22
|
+
if (!config.slug) {
|
|
23
|
+
throw new Error("Global must have a slug");
|
|
24
|
+
}
|
|
25
|
+
if (!config.fields || config.fields.length === 0) {
|
|
26
|
+
throw new Error(`Global "${config.slug}" must have at least one field`);
|
|
27
|
+
}
|
|
28
|
+
if (!/^[a-z][a-z0-9-]*$/.test(config.slug)) {
|
|
29
|
+
throw new Error(
|
|
30
|
+
`Global slug "${config.slug}" must be kebab-case (lowercase letters, numbers, and hyphens, starting with a letter)`
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
return config;
|
|
34
|
+
}
|
|
35
|
+
function getSoftDeleteField(config) {
|
|
36
|
+
if (!config.softDelete)
|
|
37
|
+
return null;
|
|
38
|
+
if (config.softDelete === true)
|
|
39
|
+
return "deletedAt";
|
|
40
|
+
const sdConfig = config.softDelete;
|
|
41
|
+
return sdConfig.field ?? "deletedAt";
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// libs/core/src/lib/fields/field.types.ts
|
|
45
|
+
var LAYOUT_FIELD_TYPES = /* @__PURE__ */ new Set(["tabs", "collapsible", "row"]);
|
|
46
|
+
var ReferentialIntegrityError = class extends Error {
|
|
47
|
+
constructor(table, constraint) {
|
|
48
|
+
super(`Cannot delete from "${table}": referenced by foreign key constraint "${constraint}"`);
|
|
49
|
+
this.name = "ReferentialIntegrityError";
|
|
50
|
+
this.table = table;
|
|
51
|
+
this.constraint = constraint;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
function isLayoutField(field) {
|
|
55
|
+
return LAYOUT_FIELD_TYPES.has(field.type);
|
|
56
|
+
}
|
|
57
|
+
function flattenDataFields(fields) {
|
|
58
|
+
const result = [];
|
|
59
|
+
for (const field of fields) {
|
|
60
|
+
if (field.type === "tabs") {
|
|
61
|
+
for (const tab of field.tabs) {
|
|
62
|
+
result.push(...flattenDataFields(tab.fields));
|
|
63
|
+
}
|
|
64
|
+
} else if (field.type === "collapsible" || field.type === "row") {
|
|
65
|
+
result.push(...flattenDataFields(field.fields));
|
|
66
|
+
} else {
|
|
67
|
+
result.push(field);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return result;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// libs/core/src/lib/fields/field-builders.ts
|
|
74
|
+
function text(name, options = {}) {
|
|
75
|
+
return {
|
|
76
|
+
name,
|
|
77
|
+
type: "text",
|
|
78
|
+
...options
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function textarea(name, options = {}) {
|
|
82
|
+
return {
|
|
83
|
+
name,
|
|
84
|
+
type: "textarea",
|
|
85
|
+
...options
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function richText(name, options = {}) {
|
|
89
|
+
return {
|
|
90
|
+
name,
|
|
91
|
+
type: "richText",
|
|
92
|
+
...options
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
function number(name, options = {}) {
|
|
96
|
+
return {
|
|
97
|
+
name,
|
|
98
|
+
type: "number",
|
|
99
|
+
...options
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function date(name, options = {}) {
|
|
103
|
+
return {
|
|
104
|
+
name,
|
|
105
|
+
type: "date",
|
|
106
|
+
...options
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
function checkbox(name, options = {}) {
|
|
110
|
+
return {
|
|
111
|
+
name,
|
|
112
|
+
type: "checkbox",
|
|
113
|
+
...options,
|
|
114
|
+
defaultValue: options.defaultValue ?? false
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
function select(name, options) {
|
|
118
|
+
return {
|
|
119
|
+
name,
|
|
120
|
+
type: "select",
|
|
121
|
+
...options
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
function radio(name, options) {
|
|
125
|
+
return {
|
|
126
|
+
name,
|
|
127
|
+
type: "radio",
|
|
128
|
+
...options
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
function email(name, options = {}) {
|
|
132
|
+
return {
|
|
133
|
+
name,
|
|
134
|
+
type: "email",
|
|
135
|
+
...options
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
function password(name, options = {}) {
|
|
139
|
+
return {
|
|
140
|
+
name,
|
|
141
|
+
type: "password",
|
|
142
|
+
...options
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function upload(name, options = {}) {
|
|
146
|
+
return {
|
|
147
|
+
name,
|
|
148
|
+
type: "upload",
|
|
149
|
+
relationTo: options.relationTo ?? "media",
|
|
150
|
+
...options
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
function relationship(name, options) {
|
|
154
|
+
return {
|
|
155
|
+
name,
|
|
156
|
+
type: "relationship",
|
|
157
|
+
...options
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
function array(name, options) {
|
|
161
|
+
return {
|
|
162
|
+
name,
|
|
163
|
+
type: "array",
|
|
164
|
+
...options
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
function group(name, options) {
|
|
168
|
+
return {
|
|
169
|
+
name,
|
|
170
|
+
type: "group",
|
|
171
|
+
...options
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
function blocks(name, options) {
|
|
175
|
+
return {
|
|
176
|
+
name,
|
|
177
|
+
type: "blocks",
|
|
178
|
+
...options
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
function json(name, options = {}) {
|
|
182
|
+
return {
|
|
183
|
+
name,
|
|
184
|
+
type: "json",
|
|
185
|
+
...options
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
function point(name, options = {}) {
|
|
189
|
+
return {
|
|
190
|
+
name,
|
|
191
|
+
type: "point",
|
|
192
|
+
...options
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
function slug(name, options) {
|
|
196
|
+
return {
|
|
197
|
+
name,
|
|
198
|
+
type: "slug",
|
|
199
|
+
...options
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
function tabs(name, options) {
|
|
203
|
+
return {
|
|
204
|
+
name,
|
|
205
|
+
type: "tabs",
|
|
206
|
+
...options
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
function collapsible(name, options) {
|
|
210
|
+
return {
|
|
211
|
+
name,
|
|
212
|
+
type: "collapsible",
|
|
213
|
+
...options
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
function row(name, options) {
|
|
217
|
+
return {
|
|
218
|
+
name,
|
|
219
|
+
type: "row",
|
|
220
|
+
...options
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// libs/core/src/lib/fields/humanize-field-name.ts
|
|
225
|
+
function humanizeFieldName(name) {
|
|
226
|
+
if (!name)
|
|
227
|
+
return "";
|
|
228
|
+
return name.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/[_-]+/g, " ").replace(/\b\w/g, (char) => char.toUpperCase()).trim();
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// libs/core/src/lib/fields/field-validators.ts
|
|
232
|
+
var EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
233
|
+
function validateFieldConstraints(field, value) {
|
|
234
|
+
if (value === null || value === void 0) {
|
|
235
|
+
return [];
|
|
236
|
+
}
|
|
237
|
+
const label = field.label ?? humanizeFieldName(field.name);
|
|
238
|
+
const errors = [];
|
|
239
|
+
switch (field.type) {
|
|
240
|
+
case "text":
|
|
241
|
+
case "textarea":
|
|
242
|
+
if (typeof value === "string") {
|
|
243
|
+
validateStringLength(field.name, label, value, field.minLength, field.maxLength, errors);
|
|
244
|
+
}
|
|
245
|
+
break;
|
|
246
|
+
case "password":
|
|
247
|
+
if (typeof value === "string" && field.minLength !== void 0) {
|
|
248
|
+
validateStringLength(field.name, label, value, field.minLength, void 0, errors);
|
|
249
|
+
}
|
|
250
|
+
break;
|
|
251
|
+
case "number":
|
|
252
|
+
if (typeof value === "number") {
|
|
253
|
+
if (field.min !== void 0 && value < field.min) {
|
|
254
|
+
errors.push({ field: field.name, message: `${label} must be at least ${field.min}` });
|
|
255
|
+
}
|
|
256
|
+
if (field.max !== void 0 && value > field.max) {
|
|
257
|
+
errors.push({
|
|
258
|
+
field: field.name,
|
|
259
|
+
message: `${label} must be no more than ${field.max}`
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
if (field.step !== void 0 && field.step > 0) {
|
|
263
|
+
const remainder = Math.abs(Math.round(value / field.step * 1e10) % Math.round(1e10));
|
|
264
|
+
if (remainder > 1) {
|
|
265
|
+
errors.push({
|
|
266
|
+
field: field.name,
|
|
267
|
+
message: `${label} must be a multiple of ${field.step}`
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
break;
|
|
273
|
+
case "email":
|
|
274
|
+
if (typeof value === "string" && value !== "" && !EMAIL_REGEX.test(value)) {
|
|
275
|
+
errors.push({
|
|
276
|
+
field: field.name,
|
|
277
|
+
message: `${label} must be a valid email address`
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
break;
|
|
281
|
+
case "select":
|
|
282
|
+
validateSelectOptions(field.name, label, value, field.options, field.hasMany, errors);
|
|
283
|
+
break;
|
|
284
|
+
case "radio":
|
|
285
|
+
validateSelectOptions(field.name, label, value, field.options, false, errors);
|
|
286
|
+
break;
|
|
287
|
+
case "array":
|
|
288
|
+
if (Array.isArray(value)) {
|
|
289
|
+
validateRowCount(field.name, label, value.length, field.minRows, field.maxRows, errors);
|
|
290
|
+
}
|
|
291
|
+
break;
|
|
292
|
+
case "blocks":
|
|
293
|
+
if (Array.isArray(value)) {
|
|
294
|
+
validateRowCount(field.name, label, value.length, field.minRows, field.maxRows, errors);
|
|
295
|
+
}
|
|
296
|
+
break;
|
|
297
|
+
}
|
|
298
|
+
return errors;
|
|
299
|
+
}
|
|
300
|
+
function validateStringLength(name, label, value, minLength, maxLength, errors) {
|
|
301
|
+
if (minLength !== void 0 && value.length < minLength) {
|
|
302
|
+
errors.push({ field: name, message: `${label} must be at least ${minLength} characters` });
|
|
303
|
+
}
|
|
304
|
+
if (maxLength !== void 0 && value.length > maxLength) {
|
|
305
|
+
errors.push({
|
|
306
|
+
field: name,
|
|
307
|
+
message: `${label} must be no more than ${maxLength} characters`
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
function validateSelectOptions(name, label, value, options, hasMany, errors) {
|
|
312
|
+
if (value === "")
|
|
313
|
+
return;
|
|
314
|
+
const validValues = new Set(options.map((o) => o.value));
|
|
315
|
+
if (hasMany && Array.isArray(value)) {
|
|
316
|
+
const allValid = value.every((v) => validValues.has(v));
|
|
317
|
+
if (!allValid) {
|
|
318
|
+
errors.push({ field: name, message: `${label} has an invalid selection` });
|
|
319
|
+
}
|
|
320
|
+
} else if (!Array.isArray(value)) {
|
|
321
|
+
if (!validValues.has(value)) {
|
|
322
|
+
errors.push({ field: name, message: `${label} has an invalid selection` });
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
function validateRowCount(name, label, count, minRows, maxRows, errors) {
|
|
327
|
+
if (minRows !== void 0 && count < minRows) {
|
|
328
|
+
errors.push({ field: name, message: `${label} requires at least ${minRows} rows` });
|
|
329
|
+
}
|
|
330
|
+
if (maxRows !== void 0 && count > maxRows) {
|
|
331
|
+
errors.push({ field: name, message: `${label} allows at most ${maxRows} rows` });
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// libs/core/src/lib/collections/media.collection.ts
|
|
336
|
+
var MediaCollection = defineCollection({
|
|
337
|
+
slug: "media",
|
|
338
|
+
labels: {
|
|
339
|
+
singular: "Media",
|
|
340
|
+
plural: "Media"
|
|
341
|
+
},
|
|
342
|
+
admin: {
|
|
343
|
+
useAsTitle: "filename",
|
|
344
|
+
defaultColumns: ["filename", "mimeType", "filesize", "createdAt"]
|
|
345
|
+
},
|
|
346
|
+
fields: [
|
|
347
|
+
text("filename", {
|
|
348
|
+
required: true,
|
|
349
|
+
label: "Filename",
|
|
350
|
+
description: "Original filename of the uploaded file"
|
|
351
|
+
}),
|
|
352
|
+
text("mimeType", {
|
|
353
|
+
required: true,
|
|
354
|
+
label: "MIME Type",
|
|
355
|
+
description: "File MIME type (e.g., image/jpeg, application/pdf)"
|
|
356
|
+
}),
|
|
357
|
+
number("filesize", {
|
|
358
|
+
label: "File Size",
|
|
359
|
+
description: "File size in bytes"
|
|
360
|
+
}),
|
|
361
|
+
text("path", {
|
|
362
|
+
required: true,
|
|
363
|
+
label: "Storage Path",
|
|
364
|
+
description: "Path/key where the file is stored",
|
|
365
|
+
admin: {
|
|
366
|
+
hidden: true
|
|
367
|
+
}
|
|
368
|
+
}),
|
|
369
|
+
text("url", {
|
|
370
|
+
label: "URL",
|
|
371
|
+
description: "Public URL to access the file"
|
|
372
|
+
}),
|
|
373
|
+
text("alt", {
|
|
374
|
+
label: "Alt Text",
|
|
375
|
+
description: "Alternative text for accessibility"
|
|
376
|
+
}),
|
|
377
|
+
number("width", {
|
|
378
|
+
label: "Width",
|
|
379
|
+
description: "Image width in pixels (for images only)"
|
|
380
|
+
}),
|
|
381
|
+
number("height", {
|
|
382
|
+
label: "Height",
|
|
383
|
+
description: "Image height in pixels (for images only)"
|
|
384
|
+
}),
|
|
385
|
+
json("focalPoint", {
|
|
386
|
+
label: "Focal Point",
|
|
387
|
+
description: "Focal point coordinates for image cropping",
|
|
388
|
+
admin: {
|
|
389
|
+
hidden: true
|
|
390
|
+
}
|
|
391
|
+
})
|
|
392
|
+
],
|
|
393
|
+
access: {
|
|
394
|
+
// Media is readable by anyone by default
|
|
395
|
+
read: () => true,
|
|
396
|
+
// Only authenticated users can create/update/delete
|
|
397
|
+
create: ({ req }) => !!req?.user,
|
|
398
|
+
update: ({ req }) => !!req?.user,
|
|
399
|
+
delete: ({ req }) => !!req?.user
|
|
400
|
+
}
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
// libs/core/src/lib/access/access-helpers.ts
|
|
404
|
+
function access(callback) {
|
|
405
|
+
return ({ req, id, data }) => {
|
|
406
|
+
const user = req.user;
|
|
407
|
+
return callback({ user, id, data });
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
function allowAll() {
|
|
411
|
+
return () => true;
|
|
412
|
+
}
|
|
413
|
+
function denyAll() {
|
|
414
|
+
return () => false;
|
|
415
|
+
}
|
|
416
|
+
function isAuthenticated() {
|
|
417
|
+
return ({ req }) => !!req.user;
|
|
418
|
+
}
|
|
419
|
+
function hasRole(role) {
|
|
420
|
+
return ({ req }) => req.user?.role === role;
|
|
421
|
+
}
|
|
422
|
+
function hasAnyRole(roles) {
|
|
423
|
+
return ({ req }) => {
|
|
424
|
+
const userRole = req.user?.role;
|
|
425
|
+
return userRole !== void 0 && roles.includes(userRole);
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
function hasAllRoles(roles) {
|
|
429
|
+
return ({ req }) => {
|
|
430
|
+
const userRoles = req.user?.["roles"];
|
|
431
|
+
if (!Array.isArray(userRoles))
|
|
432
|
+
return false;
|
|
433
|
+
return roles.every((role) => userRoles.includes(role));
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
function and(...fns) {
|
|
437
|
+
return async (args) => {
|
|
438
|
+
for (const fn of fns) {
|
|
439
|
+
const result = await fn(args);
|
|
440
|
+
if (!result)
|
|
441
|
+
return false;
|
|
442
|
+
}
|
|
443
|
+
return true;
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
function or(...fns) {
|
|
447
|
+
return async (args) => {
|
|
448
|
+
for (const fn of fns) {
|
|
449
|
+
const result = await fn(args);
|
|
450
|
+
if (result)
|
|
451
|
+
return true;
|
|
452
|
+
}
|
|
453
|
+
return false;
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
function not(fn) {
|
|
457
|
+
return async (args) => {
|
|
458
|
+
const result = await fn(args);
|
|
459
|
+
return !result;
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
function isOwner(ownerField = "createdBy") {
|
|
463
|
+
return ({ req, data }) => {
|
|
464
|
+
if (!req.user?.id)
|
|
465
|
+
return false;
|
|
466
|
+
const ownerId = data?.[ownerField];
|
|
467
|
+
if (ownerId === void 0 || ownerId === null)
|
|
468
|
+
return false;
|
|
469
|
+
return ownerId === req.user.id || String(ownerId) === String(req.user.id);
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// libs/core/src/lib/config.ts
|
|
474
|
+
var MIN_PASSWORD_LENGTH = 8;
|
|
475
|
+
function defineMomentumConfig(config) {
|
|
476
|
+
return {
|
|
477
|
+
...config,
|
|
478
|
+
admin: {
|
|
479
|
+
basePath: config.admin?.basePath ?? "/admin",
|
|
480
|
+
branding: config.admin?.branding ?? {},
|
|
481
|
+
toasts: config.admin?.toasts ?? true
|
|
482
|
+
},
|
|
483
|
+
server: {
|
|
484
|
+
port: config.server?.port ?? 3e3,
|
|
485
|
+
cors: config.server?.cors ?? {
|
|
486
|
+
origin: "*",
|
|
487
|
+
methods: ["GET", "POST", "PATCH", "PUT", "DELETE", "OPTIONS"],
|
|
488
|
+
headers: ["Content-Type", "Authorization", "X-API-Key"]
|
|
489
|
+
}
|
|
490
|
+
},
|
|
491
|
+
seeding: config.seeding ? {
|
|
492
|
+
...config.seeding,
|
|
493
|
+
options: {
|
|
494
|
+
onConflict: config.seeding.options?.onConflict ?? "skip",
|
|
495
|
+
runOnStart: config.seeding.options?.runOnStart ?? "development",
|
|
496
|
+
quiet: config.seeding.options?.quiet ?? false
|
|
497
|
+
}
|
|
498
|
+
} : void 0,
|
|
499
|
+
logging: {
|
|
500
|
+
level: config.logging?.level ?? "info",
|
|
501
|
+
format: config.logging?.format ?? "pretty",
|
|
502
|
+
timestamps: config.logging?.timestamps ?? true
|
|
503
|
+
}
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
function getDbAdapter(config) {
|
|
507
|
+
return config.db.adapter;
|
|
508
|
+
}
|
|
509
|
+
function getCollections(config) {
|
|
510
|
+
return config.collections;
|
|
511
|
+
}
|
|
512
|
+
function getGlobals(config) {
|
|
513
|
+
return config.globals ?? [];
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// libs/core/src/lib/seeding/seeding.types.ts
|
|
517
|
+
var SEED_TRACKING_COLLECTION_SLUG = "_momentum_seeds";
|
|
518
|
+
var SeedConflictError = class extends Error {
|
|
519
|
+
constructor(seedId, collection) {
|
|
520
|
+
super(`Seed conflict: seedId "${seedId}" already exists in collection "${collection}"`);
|
|
521
|
+
this.name = "SeedConflictError";
|
|
522
|
+
this.seedId = seedId;
|
|
523
|
+
this.collection = collection;
|
|
524
|
+
}
|
|
525
|
+
};
|
|
526
|
+
var SeedRollbackError = class extends Error {
|
|
527
|
+
constructor(originalError, rolledBackSeeds, rollbackFailures) {
|
|
528
|
+
const rollbackStatus = rollbackFailures.length > 0 ? `Rollback partially failed: ${rolledBackSeeds.length} rolled back, ${rollbackFailures.length} failed` : `Rollback successful: ${rolledBackSeeds.length} seeds removed`;
|
|
529
|
+
super(`Seeding failed: ${originalError.message}. ${rollbackStatus}`);
|
|
530
|
+
this.name = "SeedRollbackError";
|
|
531
|
+
this.originalError = originalError;
|
|
532
|
+
this.rolledBackSeeds = rolledBackSeeds;
|
|
533
|
+
this.rollbackFailures = rollbackFailures;
|
|
534
|
+
}
|
|
535
|
+
};
|
|
536
|
+
|
|
537
|
+
// libs/core/src/lib/seeding/seed-helpers.ts
|
|
538
|
+
function createSeedHelpers() {
|
|
539
|
+
return {
|
|
540
|
+
admin(seedId, data, options) {
|
|
541
|
+
return {
|
|
542
|
+
seedId,
|
|
543
|
+
collection: "user",
|
|
544
|
+
// Better Auth user table
|
|
545
|
+
data: {
|
|
546
|
+
role: "admin",
|
|
547
|
+
// Admin role by default
|
|
548
|
+
emailVerified: true,
|
|
549
|
+
// Admins are pre-verified
|
|
550
|
+
...data
|
|
551
|
+
},
|
|
552
|
+
options
|
|
553
|
+
};
|
|
554
|
+
},
|
|
555
|
+
user(seedId, data, options) {
|
|
556
|
+
return {
|
|
557
|
+
seedId,
|
|
558
|
+
collection: "user",
|
|
559
|
+
// Better Auth user table
|
|
560
|
+
data: {
|
|
561
|
+
role: "user",
|
|
562
|
+
// Default role
|
|
563
|
+
emailVerified: false,
|
|
564
|
+
// Default not verified
|
|
565
|
+
...data
|
|
566
|
+
},
|
|
567
|
+
options
|
|
568
|
+
};
|
|
569
|
+
},
|
|
570
|
+
authUser(seedId, data, options) {
|
|
571
|
+
return {
|
|
572
|
+
seedId,
|
|
573
|
+
collection: "user",
|
|
574
|
+
// Better Auth user table (auth-user collection with dbName: 'user')
|
|
575
|
+
data: {
|
|
576
|
+
role: "user",
|
|
577
|
+
emailVerified: true,
|
|
578
|
+
...data
|
|
579
|
+
},
|
|
580
|
+
options: {
|
|
581
|
+
...options,
|
|
582
|
+
useAuthSignup: true
|
|
583
|
+
}
|
|
584
|
+
};
|
|
585
|
+
},
|
|
586
|
+
collection(slug2) {
|
|
587
|
+
return {
|
|
588
|
+
create(seedId, data, options) {
|
|
589
|
+
return {
|
|
590
|
+
seedId,
|
|
591
|
+
collection: slug2,
|
|
592
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- User provides partial data that will be merged with defaults during seeding
|
|
593
|
+
data,
|
|
594
|
+
options
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
export {
|
|
602
|
+
LAYOUT_FIELD_TYPES,
|
|
603
|
+
MIN_PASSWORD_LENGTH,
|
|
604
|
+
MediaCollection,
|
|
605
|
+
ReferentialIntegrityError,
|
|
606
|
+
SEED_TRACKING_COLLECTION_SLUG,
|
|
607
|
+
SeedConflictError,
|
|
608
|
+
SeedRollbackError,
|
|
609
|
+
access,
|
|
610
|
+
allowAll,
|
|
611
|
+
and,
|
|
612
|
+
array,
|
|
613
|
+
blocks,
|
|
614
|
+
checkbox,
|
|
615
|
+
collapsible,
|
|
616
|
+
createSeedHelpers,
|
|
617
|
+
date,
|
|
618
|
+
defineCollection,
|
|
619
|
+
defineGlobal,
|
|
620
|
+
defineMomentumConfig,
|
|
621
|
+
denyAll,
|
|
622
|
+
email,
|
|
623
|
+
flattenDataFields,
|
|
624
|
+
getCollections,
|
|
625
|
+
getDbAdapter,
|
|
626
|
+
getGlobals,
|
|
627
|
+
getSoftDeleteField,
|
|
628
|
+
group,
|
|
629
|
+
hasAllRoles,
|
|
630
|
+
hasAnyRole,
|
|
631
|
+
hasRole,
|
|
632
|
+
humanizeFieldName,
|
|
633
|
+
isAuthenticated,
|
|
634
|
+
isLayoutField,
|
|
635
|
+
isOwner,
|
|
636
|
+
json,
|
|
637
|
+
not,
|
|
638
|
+
number,
|
|
639
|
+
or,
|
|
640
|
+
password,
|
|
641
|
+
point,
|
|
642
|
+
radio,
|
|
643
|
+
relationship,
|
|
644
|
+
richText,
|
|
645
|
+
row,
|
|
646
|
+
select,
|
|
647
|
+
slug,
|
|
648
|
+
tabs,
|
|
649
|
+
text,
|
|
650
|
+
textarea,
|
|
651
|
+
upload,
|
|
652
|
+
validateFieldConstraints
|
|
653
|
+
};
|
package/package.json
CHANGED
|
@@ -1,32 +1,32 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
2
|
+
"name": "@momentumcms/core",
|
|
3
|
+
"version": "0.1.3",
|
|
4
|
+
"description": "Core collection config, fields, hooks, and access control for Momentum CMS",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Momentum CMS Contributors",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/momentum-cms/momentum-cms.git",
|
|
10
|
+
"directory": "libs/core"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/momentum-cms/momentum-cms#readme",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/momentum-cms/momentum-cms/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"cms",
|
|
18
|
+
"headless-cms",
|
|
19
|
+
"angular",
|
|
20
|
+
"momentum-cms",
|
|
21
|
+
"collections",
|
|
22
|
+
"fields",
|
|
23
|
+
"content-management"
|
|
24
|
+
],
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=18"
|
|
27
|
+
},
|
|
28
|
+
"main": "./index.cjs",
|
|
29
|
+
"types": "./src/index.d.ts",
|
|
30
|
+
"dependencies": {},
|
|
31
|
+
"module": "./index.js"
|
|
32
|
+
}
|