'use strict'; const crypto = require('crypto'); const clientS3 = require('@aws-sdk/client-s3'); const cacheHandler = require('@neshca/cache-handler'); cacheHandler.CacheHandler.onCreation(async ({ buildId }) => { const client = new clientS3.S3({ region: process.env.AWS_REGION }); const bucketName = process.env.CACHE_BUCKET_NAME; const prefix = process.env.CACHE_PREFIX + buildId; const tagPrefix = `TAG${prefix}`; const cacheExtension = "json"; const s3NotFoundErrors = ["NotFound", "NoSuchKey"]; function md5(data) { return crypto.createHash("md5").update(data).digest("hex"); } function buildTagKeys(tags) { if (!tags?.length) return ""; return tags.map((tag, index) => `${tagPrefix}${index}=${md5(tag)}`).join("&"); } return { handlers: [ { name: "aws-s3-handler", async get(key) { if (!client) { return null; } const data = await client.getObject({ Bucket: bucketName, Key: `${prefix}-${key}.${cacheExtension}` }).catch((error) => { if (s3NotFoundErrors.includes(error.name)) return null; throw error; }); if (!data?.Body) return null; return JSON.parse( await data.Body.transformToString("utf-8") ); }, async set(key, value) { const expireAt = value.lifespan.expireAt; await client.putObject({ Bucket: bucketName, Key: `${prefix}-${key}.${cacheExtension}`, Body: JSON.stringify(value), CacheControl: `max-age=${Math.floor( expireAt - Date.now() / 1e3 )}`, Tagging: buildTagKeys(value.tags) }); }, async revalidateTag(tag) { const keysToDelete = []; let nextContinuationToken = void 0; do { const { Contents: contents = [], NextContinuationToken: token } = await client.listObjectsV2({ Bucket: bucketName, ContinuationToken: nextContinuationToken }); nextContinuationToken = token; keysToDelete.push( ...await contents.reduce( async (acc, { Key: key }) => { if (!key || !key.startsWith(prefix)) return acc; const { TagSet = [] } = await client.getObjectTagging({ Bucket: bucketName, Key: key }); const tags = TagSet.filter( ({ Key: key2 }) => key2?.startsWith(tagPrefix) ).map(({ Value: tags2 }) => tags2 || ""); if (tags.includes(md5(tag))) { return [...await acc, key]; } return acc; }, Promise.resolve([]) ) ); } while (nextContinuationToken); await client.deleteObjects({ Bucket: bucketName, Delete: { Objects: keysToDelete.map((Key) => ({ Key })) } }); }, async delete(key) { await client.deleteObjects({ Bucket: bucketName, Delete: { Objects: [ { Key: `${prefix}-${key}.${cacheExtension}` } ] } }); } } ] }; }); module.exports = cacheHandler.CacheHandler;