40 lines
1.8 KiB
JavaScript
40 lines
1.8 KiB
JavaScript
const idPattern = /^[a-z][a-z0-9-]{0,63}$/u;
|
|
const fileNamePattern = /^[a-z0-9][a-z0-9-]*\.(avif|jpg|png|svg|webp|mp4|webm|opus|mp3)$/u;
|
|
const managedFileNamePattern = /^(?:project|social)-[a-z0-9][a-z0-9-]*\.(?:avif|jpg|png|svg|webp|mp4|webm)$/u;
|
|
const sha256Pattern = /^[a-f0-9]{64}$/u;
|
|
|
|
const assertSafeRecord = (asset) => {
|
|
if (!asset || typeof asset !== 'object' || Array.isArray(asset) ||
|
|
typeof asset.id !== 'string' || !idPattern.test(asset.id) ||
|
|
typeof asset.fileName !== 'string' || !fileNamePattern.test(asset.fileName) ||
|
|
typeof asset.sha256 !== 'string' || !sha256Pattern.test(asset.sha256)) {
|
|
throw new Error('ASSET-MERGE-RECORD-001');
|
|
}
|
|
};
|
|
|
|
export const mergeAssetRecords = (baseAssets, generatedAssets, managedIds) => {
|
|
if (!Array.isArray(baseAssets) || !Array.isArray(generatedAssets) || !(managedIds instanceof Set)) {
|
|
throw new Error('ASSET-MERGE-RECORD-001');
|
|
}
|
|
for (const asset of [...baseAssets, ...generatedAssets]) {
|
|
if (!asset || typeof asset !== 'object' || Array.isArray(asset) ||
|
|
typeof asset.id !== 'string' || typeof asset.fileName !== 'string') {
|
|
throw new Error('ASSET-MERGE-RECORD-001');
|
|
}
|
|
}
|
|
const retained = baseAssets.filter(({ id, fileName }) =>
|
|
!managedIds.has(id) || !managedFileNamePattern.test(fileName));
|
|
const combined = [...retained, ...generatedAssets];
|
|
const ids = new Set();
|
|
const names = new Set();
|
|
for (const asset of combined) {
|
|
const folded = asset.fileName.toLocaleLowerCase('en-US');
|
|
if (ids.has(asset.id)) throw new Error('ASSET-MERGE-ID-001');
|
|
if (names.has(folded)) throw new Error('ASSET-MERGE-NAME-001');
|
|
ids.add(asset.id);
|
|
names.add(folded);
|
|
}
|
|
for (const asset of [...baseAssets, ...generatedAssets]) assertSafeRecord(asset);
|
|
return combined.sort((left, right) => left.fileName.localeCompare(right.fileName, 'en-US'));
|
|
};
|