MCPcopy Create free account
hub / github.com/Forward-Future/loopy / normalizeLoopDocument

Function normalizeLoopDocument

loop-library/worker/src/loop-schema.js:39–169  ·  view source on GitHub ↗
(input)

Source from the content-addressed store, hash-verified

37}
38
39export function normalizeLoopDocument(input) {
40 if (!isPlainObject(input)) {
41 throw new LoopValidationError("Loop data must be a JSON object.");
42 }
43
44 const loop = {};
45
46 for (const [field, maxLength] of Object.entries(REQUIRED_STRINGS)) {
47 loop[field] = requiredString(input[field], field, maxLength);
48 }
49
50 if (!/^\d{3}$/.test(loop.number)) {
51 throw new LoopValidationError("number must contain exactly three digits.");
52 }
53
54 if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(loop.slug)) {
55 throw new LoopValidationError("slug must use lowercase words separated by hyphens.");
56 }
57
58 validateDate(loop.published, "published");
59 validateDate(loop.modified, "modified");
60
61 if (loop.modified < loop.published) {
62 throw new LoopValidationError("modified cannot be earlier than published.");
63 }
64
65 loop.category = requiredString(input.category, "category", 40);
66 loop.featured = input.featured === true;
67
68 if (!CATEGORY_LABELS[loop.category]) {
69 throw new LoopValidationError(
70 `category must be one of: ${Object.keys(CATEGORY_LABELS).join(", ")}.`,
71 );
72 }
73
74 loop.steps = stringArray(input.steps, "steps", { min: 3, max: 12, itemMax: 1200 });
75 loop.keywords = stringArray(input.keywords, "keywords", { min: 3, max: 20, itemMax: 100 });
76 loop.related = stringArray(input.related, "related", { min: 1, max: 8, itemMax: 80 });
77
78 if (
79 new Set(loop.keywords.map((keyword) => keyword.toLowerCase())).size !==
80 loop.keywords.length
81 ) {
82 throw new LoopValidationError("keywords must be unique.");
83 }
84
85 if (new Set(loop.related).size !== loop.related.length) {
86 throw new LoopValidationError("related must not contain duplicates.");
87 }
88
89 for (const slug of loop.related) {
90 if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(slug) || slug === loop.slug) {
91 throw new LoopValidationError("related must contain other valid loop slugs.");
92 }
93 }
94
95 const sourceUrl = optionalString(input.sourceUrl, "sourceUrl", 500);
96

Callers 4

handleAdminRequestFunction · 0.90
normalizeRestoreManifestFunction · 0.90
normalizeRestoreChunkFunction · 0.90
publish-loop.mjsFile · 0.90

Calls 5

requiredStringFunction · 0.85
validateDateFunction · 0.85
stringArrayFunction · 0.85
optionalStringFunction · 0.85
isPlainObjectFunction · 0.70

Tested by

no test coverage detected