UNPKG

zephyr-agent

Version:
486 lines (485 loc) • 22.6 kB
"use strict"; var __webpack_require__ = {}; (()=>{ __webpack_require__.d = (exports1, getters, values)=>{ var define = (defs, kind)=>{ for(var key in defs)if (__webpack_require__.o(defs, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, { enumerable: true, [kind]: defs[key] }); }; define(getters, "get"); define(values, "value"); }; })(); (()=>{ __webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop); })(); (()=>{ __webpack_require__.r = (exports1)=>{ if ("u" > typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, { value: 'Module' }); Object.defineProperty(exports1, '__esModule', { value: true }); }; })(); var __webpack_exports__ = {}; __webpack_require__.r(__webpack_exports__); __webpack_require__.d(__webpack_exports__, { claimPartialAssetMap: ()=>claimPartialAssetMap, claimPartialAssetMapBatch: ()=>claimPartialAssetMapBatch, commitPartialAssetMapClaim: ()=>commitPartialAssetMapClaim, commitPartialAssetMapClaimBatch: ()=>commitPartialAssetMapClaimBatch, deserializePartialAssetMaps: ()=>deserializePartialAssetMaps, getLegacyPartialAssetStorePath: ()=>getLegacyPartialAssetStorePath, getPartialAssetLockPath: ()=>getPartialAssetLockPath, getPartialAssetMap: ()=>getPartialAssetMap, getPartialAssetStorePath: ()=>getPartialAssetStorePath, removePartialAssetMap: ()=>removePartialAssetMap, rollbackPartialAssetMapClaim: ()=>rollbackPartialAssetMapClaim, rollbackPartialAssetMapClaimBatch: ()=>rollbackPartialAssetMapClaimBatch, savePartialAssetMap: ()=>savePartialAssetMap, serializePartialAssetMaps: ()=>serializePartialAssetMaps, takePartialAssetMap: ()=>takePartialAssetMap }); const external_node_crypto_namespaceObject = require("node:crypto"); const promises_namespaceObject = require("node:fs/promises"); const external_node_path_namespaceObject = require("node:path"); const ze_build_assets_js_namespaceObject = require("../transformers/ze-build-assets.js"); const external_atomic_file_js_namespaceObject = require("./atomic-file.js"); const external_storage_keys_js_namespaceObject = require("./storage-keys.js"); const external_storage_lock_js_namespaceObject = require("./storage-lock.js"); const BUFFER_ENCODING_KEY = '__zephyr_buffer_base64__'; const STORE_VERSION_KEY = '__zephyr_partial_store_version__'; const STORE_VERSION = 2; const CLAIM_STALE_MS = 1800000; const GROUP_STALE_MS = 86400000; const LEGACY_SCOPE = { invocationId: '__zephyr_legacy_unscoped__', generation: 0 }; function isRecord(value) { return 'object' == typeof value && null !== value && !Array.isArray(value); } function decodeBuffer(value) { if (Buffer.isBuffer(value) || 'string' == typeof value) return value; if (isRecord(value) && 'string' == typeof value[BUFFER_ENCODING_KEY]) return Buffer.from(value[BUFFER_ENCODING_KEY], 'base64'); if (isRecord(value) && 'Buffer' === value['type'] && Array.isArray(value['data']) && value['data'].every((byte)=>Number.isInteger(byte) && byte >= 0 && byte <= 255)) return Buffer.from(value['data']); throw new TypeError('Persisted partial asset contains an invalid buffer'); } function serializePartialAssetMaps(partialAssetMaps) { return Object.fromEntries(Object.entries(partialAssetMaps).map(([partialKey, assetsMap])=>[ partialKey, Object.fromEntries(Object.entries(assetsMap).map(([hash, asset])=>[ hash, { ...asset, buffer: Buffer.isBuffer(asset.buffer) ? { [BUFFER_ENCODING_KEY]: asset.buffer.toString('base64') } : asset.buffer } ])) ])); } function deserializePartialAssetMaps(value) { if (null == value) return; if (!isRecord(value)) throw new TypeError('Persisted partial asset maps must be an object'); return Object.fromEntries(Object.entries(value).map(([partialKey, rawAssetsMap])=>{ if (!isRecord(rawAssetsMap)) throw new TypeError(`Persisted partial "${partialKey}" must be an object`); return [ partialKey, Object.fromEntries(Object.entries(rawAssetsMap).map(([hash, rawAsset])=>{ if (!isRecord(rawAsset)) throw new TypeError(`Persisted partial asset "${partialKey}:${hash}" must be an object`); const assetPath = rawAsset['path']; if ('string' != typeof assetPath) throw new TypeError(`Persisted partial asset "${partialKey}:${hash}" has no valid path`); const slashPath = assetPath.replace(/\\/g, '/'); const pathSegments = slashPath.split('/').filter((segment)=>segment && '.' !== segment); if (!slashPath || slashPath.includes('\0') || slashPath.startsWith('/') || /^[A-Za-z]:/.test(slashPath) || 0 === pathSegments.length || pathSegments.includes('..')) throw new TypeError(`Persisted partial asset "${partialKey}:${hash}" escapes the snapshot root`); const buffer = decodeBuffer(rawAsset['buffer']); const rebuilt = (0, ze_build_assets_js_namespaceObject.zeBuildAssets)({ filepath: assetPath, content: buffer }); if (rawAsset['hash'] !== hash || rebuilt.hash !== hash || rawAsset['size'] !== rebuilt.size) throw new TypeError(`Persisted partial asset "${partialKey}:${hash}" failed its content integrity check`); const normalizedPath = pathSegments.join('/'); const normalizedAsset = normalizedPath === assetPath ? rebuilt : (0, ze_build_assets_js_namespaceObject.zeBuildAssets)({ filepath: normalizedPath, content: buffer }); return [ normalizedAsset.hash, { ...rawAsset, path: normalizedAsset.path, hash: normalizedAsset.hash, size: normalizedAsset.size, extname: normalizedAsset.extname, buffer } ]; })) ]; })); } function assertScope(scope) { if (!scope.invocationId.trim()) throw new TypeError('Partial asset invocationId must not be empty'); if (!Number.isSafeInteger(scope.generation) || scope.generation < 0) throw new TypeError('Partial asset generation must be a non-negative safe integer'); } function getScopeKey(scope) { assertScope(scope); return JSON.stringify([ scope.invocationId, scope.generation ]); } function emptyStore() { return { [STORE_VERSION_KEY]: STORE_VERSION, nextRevision: 1, groups: {}, claims: {} }; } function parseScope(value) { if (!isRecord(value) || 'string' != typeof value['invocationId'] || 'number' != typeof value['generation']) throw new TypeError('Persisted partial group has an invalid scope'); const scope = { invocationId: value['invocationId'], generation: value['generation'] }; assertScope(scope); return scope; } function readStore(value) { if (null == value) return emptyStore(); if (isRecord(value) && value[STORE_VERSION_KEY] === STORE_VERSION) { if (!Number.isSafeInteger(value['nextRevision']) || value['nextRevision'] < 1 || !isRecord(value['groups']) || !isRecord(value['claims'])) throw new TypeError('Persisted partial asset store is malformed'); const store = value; for (const [scopeKey, group] of Object.entries(store.groups)){ if (!isRecord(group) || !isRecord(group.entries) || 'number' != typeof group.updatedAt) throw new TypeError(`Persisted partial group "${scopeKey}" is malformed`); const scope = parseScope(group.scope); if (getScopeKey(scope) !== scopeKey) throw new TypeError(`Persisted partial group "${scopeKey}" has a mismatched scope`); for (const [partialKey, entry] of Object.entries(group.entries)){ if (!isRecord(entry) || !Number.isSafeInteger(entry.revision) || entry.revision < 1 || !isRecord(entry.assets)) throw new TypeError(`Persisted partial contribution "${scopeKey}:${partialKey}" is malformed`); deserializePartialAssetMaps({ [partialKey]: entry.assets }); } } for (const [claimId, claim] of Object.entries(store.claims))if (!isRecord(claim) || 'string' != typeof claim.scopeKey || !isRecord(claim.revisions) || 'number' != typeof claim.createdAt || !Object.values(claim.revisions).every((revision)=>Number.isSafeInteger(revision) && revision >= 1)) throw new TypeError(`Persisted partial claim "${claimId}" is malformed`); return store; } const legacyMaps = deserializePartialAssetMaps(value); const store = emptyStore(); if (legacyMaps && Object.keys(legacyMaps).length > 0) { const scopeKey = getScopeKey(LEGACY_SCOPE); store.groups[scopeKey] = { scope: { ...LEGACY_SCOPE }, entries: {}, updatedAt: Date.now() }; for (const [partialKey, assetsMap] of Object.entries(legacyMaps))store.groups[scopeKey].entries[partialKey] = { revision: store.nextRevision++, assets: serializePartialAssetMaps({ [partialKey]: assetsMap })[partialKey] }; } return store; } function readVersionedStore(value) { if (!isRecord(value) || value[STORE_VERSION_KEY] !== STORE_VERSION) throw new TypeError('Persisted partial asset store has an unsupported schema'); return readStore(value); } function pruneStaleStore(store, now = Date.now()) { for (const [claimId, claim] of Object.entries(store.claims))if (now - claim.createdAt >= CLAIM_STALE_MS) delete store.claims[claimId]; const claimedScopes = new Set(Object.values(store.claims).map((claim)=>claim.scopeKey)); for (const [scopeKey, group] of Object.entries(store.groups))if (!claimedScopes.has(scopeKey) && now - group.updatedAt >= GROUP_STALE_MS) delete store.groups[scopeKey]; } function deserializeGroup(group) { return Object.fromEntries(Object.entries(group.entries).map(([partialKey, entry])=>{ const assetsMap = deserializePartialAssetMaps({ [partialKey]: entry.assets }); return [ partialKey, assetsMap?.[partialKey] ?? {} ]; })); } function get_key(application_uid) { return [ external_storage_keys_js_namespaceObject.StorageKeys.ze_app_partial_asset_map, application_uid ].join('.'); } function get_application_digest(application_uid) { return (0, external_node_crypto_namespaceObject.createHash)('sha256').update(application_uid).digest('hex'); } function getPartialAssetStorePath(application_uid) { return external_node_path_namespaceObject.join(external_storage_keys_js_namespaceObject.ZE_PATH, 'partial-assets', `${get_application_digest(application_uid)}.json`); } function getLegacyPartialAssetStorePath(application_uid) { const legacyKeyDigest = (0, external_node_crypto_namespaceObject.createHash)('sha256').update(get_key(application_uid)).digest('hex'); return external_node_path_namespaceObject.join(external_storage_keys_js_namespaceObject.ZE_STORAGE_PATH, legacyKeyDigest); } function getPartialAssetLockName(application_uid) { return `partial-assets-${get_application_digest(application_uid)}`; } function getPartialAssetLockPath(application_uid) { return (0, external_storage_lock_js_namespaceObject.getStorageLockPath)(getPartialAssetLockName(application_uid)); } function withPartialAssetsLock(application_uid, action) { return (0, external_storage_lock_js_namespaceObject.withStorageLock)(getPartialAssetLockName(application_uid), action, { retries: { retries: 8, factor: 1.5, minTimeout: 25, maxTimeout: 500 }, staleMs: external_storage_keys_js_namespaceObject.PARTIAL_ASSET_LOCK_STALE_MS, whenUnavailable: 'throw' }); } async function readJsonFileStrict(filePath) { let content; try { content = await (0, promises_namespaceObject.readFile)(filePath, 'utf8'); } catch (error) { if (error?.code === 'ENOENT') return; throw error; } try { return JSON.parse(content); } catch (error) { throw Object.assign(new TypeError(`Partial asset store "${filePath}" contains invalid JSON`), { cause: error }); } } async function readLegacyStoreValue(application_uid) { const legacyPath = getLegacyPartialAssetStorePath(application_uid); const datum = await readJsonFileStrict(legacyPath); if (void 0 === datum) return { found: false }; if (!isRecord(datum) || datum['key'] !== get_key(application_uid)) throw new TypeError(`Legacy partial asset store "${legacyPath}" is malformed`); if ('number' == typeof datum['ttl'] && Number.isFinite(datum['ttl']) && datum['ttl'] < Date.now()) { await (0, promises_namespaceObject.rm)(legacyPath, { force: true }); return { found: false }; } if (!Object.prototype.hasOwnProperty.call(datum, 'value')) throw new TypeError(`Legacy partial asset store "${legacyPath}" has no value`); return { found: true, value: datum['value'] }; } async function writeStore(application_uid, store) { await (0, external_atomic_file_js_namespaceObject.writeFileAtomically)(getPartialAssetStorePath(application_uid), JSON.stringify(store)); } async function readApplicationStore(application_uid) { const storePath = getPartialAssetStorePath(application_uid); const persisted = await readJsonFileStrict(storePath); if (void 0 !== persisted) return readVersionedStore(persisted); const legacy = await readLegacyStoreValue(application_uid); if (!legacy.found) return emptyStore(); const migrated = readStore(legacy.value); await writeStore(application_uid, migrated); await (0, promises_namespaceObject.rm)(getLegacyPartialAssetStorePath(application_uid), { force: true }); return migrated; } async function savePartialAssetMap(application_uid, partial_key, assetMap, scope = LEGACY_SCOPE) { assertScope(scope); await withPartialAssetsLock(application_uid, async ()=>{ const store = await readApplicationStore(application_uid); pruneStaleStore(store); const scopeKey = getScopeKey(scope); const group = store.groups[scopeKey] ??= { scope: { ...scope }, entries: {}, updatedAt: Date.now() }; group.updatedAt = Date.now(); group.entries[partial_key] = { revision: store.nextRevision++, assets: serializePartialAssetMaps({ [partial_key]: assetMap })[partial_key] }; await writeStore(application_uid, store); }); } async function getPartialAssetMap(application_uid, scope = LEGACY_SCOPE) { assertScope(scope); return withPartialAssetsLock(application_uid, async ()=>{ const store = await readApplicationStore(application_uid); const group = store.groups[getScopeKey(scope)]; return group ? deserializeGroup(group) : void 0; }); } async function claimPartialAssetMapBatch(application_uid, scopes) { if (0 === scopes.length) throw new TypeError('At least one partial asset scope must be claimed'); const scopeKeys = scopes.map((scope)=>{ assertScope(scope); return getScopeKey(scope); }); if (new Set(scopeKeys).size !== scopeKeys.length) throw new TypeError('Partial asset scopes must be unique within a claim batch'); return withPartialAssetsLock(application_uid, async ()=>{ const store = await readApplicationStore(application_uid); pruneStaleStore(store); const candidates = scopes.map((scope, index)=>{ const scopeKey = scopeKeys[index]; const group = store.groups[scopeKey]; if (!group || 0 === Object.keys(group.entries).length) return; const revisions = Object.fromEntries(Object.entries(group.entries).map(([partialKey, entry])=>[ partialKey, entry.revision ])); const isAlreadyClaimed = Object.values(store.claims).some((claim)=>claim.scopeKey === scopeKey && Object.entries(revisions).some(([partialKey, revision])=>claim.revisions[partialKey] === revision)); return isAlreadyClaimed ? void 0 : { scope, scopeKey, group, revisions }; }); if (candidates.some((candidate)=>!candidate)) return; const now = Date.now(); const claims = candidates.map((candidate)=>{ if (!candidate) throw new TypeError('Partial claim candidate was unavailable'); const claimId = (0, external_node_crypto_namespaceObject.randomUUID)(); store.claims[claimId] = { scopeKey: candidate.scopeKey, revisions: candidate.revisions, createdAt: now }; return { claimId, scope: { ...candidate.scope }, partialAssetMaps: deserializeGroup(candidate.group) }; }); await writeStore(application_uid, store); return { claims: Object.freeze(claims) }; }); } async function claimPartialAssetMap(application_uid, scope) { const batch = await claimPartialAssetMapBatch(application_uid, [ scope ]); return batch?.claims[0]; } async function commitPartialAssetMapClaimBatch(application_uid, claimIds) { const uniqueClaimIds = [ ...new Set(claimIds) ]; if (0 === uniqueClaimIds.length) return; await withPartialAssetsLock(application_uid, async ()=>{ const store = await readApplicationStore(application_uid); let changed = false; for (const claimId of uniqueClaimIds){ const claim = store.claims[claimId]; if (!claim) continue; const group = store.groups[claim.scopeKey]; if (group) { for (const [partialKey, revision] of Object.entries(claim.revisions))if (group.entries[partialKey]?.revision === revision) delete group.entries[partialKey]; if (0 === Object.keys(group.entries).length) delete store.groups[claim.scopeKey]; } delete store.claims[claimId]; changed = true; } if (changed) await writeStore(application_uid, store); }); } async function commitPartialAssetMapClaim(application_uid, claimId) { await commitPartialAssetMapClaimBatch(application_uid, [ claimId ]); } async function rollbackPartialAssetMapClaimBatch(application_uid, claimIds) { const uniqueClaimIds = [ ...new Set(claimIds) ]; if (0 === uniqueClaimIds.length) return; await withPartialAssetsLock(application_uid, async ()=>{ const store = await readApplicationStore(application_uid); let changed = false; for (const claimId of uniqueClaimIds)if (store.claims[claimId]) { delete store.claims[claimId]; changed = true; } if (changed) await writeStore(application_uid, store); }); } async function rollbackPartialAssetMapClaim(application_uid, claimId) { await rollbackPartialAssetMapClaimBatch(application_uid, [ claimId ]); } async function takePartialAssetMap(application_uid) { const claim = await claimPartialAssetMap(application_uid, LEGACY_SCOPE); if (!claim) return; await commitPartialAssetMapClaim(application_uid, claim.claimId); return claim.partialAssetMaps; } async function removePartialAssetMap(application_uid) { await withPartialAssetsLock(application_uid, async ()=>{ await (0, promises_namespaceObject.rm)(getLegacyPartialAssetStorePath(application_uid), { force: true }); await (0, promises_namespaceObject.rm)(getPartialAssetStorePath(application_uid), { force: true }); }); } exports.claimPartialAssetMap = __webpack_exports__.claimPartialAssetMap; exports.claimPartialAssetMapBatch = __webpack_exports__.claimPartialAssetMapBatch; exports.commitPartialAssetMapClaim = __webpack_exports__.commitPartialAssetMapClaim; exports.commitPartialAssetMapClaimBatch = __webpack_exports__.commitPartialAssetMapClaimBatch; exports.deserializePartialAssetMaps = __webpack_exports__.deserializePartialAssetMaps; exports.getLegacyPartialAssetStorePath = __webpack_exports__.getLegacyPartialAssetStorePath; exports.getPartialAssetLockPath = __webpack_exports__.getPartialAssetLockPath; exports.getPartialAssetMap = __webpack_exports__.getPartialAssetMap; exports.getPartialAssetStorePath = __webpack_exports__.getPartialAssetStorePath; exports.removePartialAssetMap = __webpack_exports__.removePartialAssetMap; exports.rollbackPartialAssetMapClaim = __webpack_exports__.rollbackPartialAssetMapClaim; exports.rollbackPartialAssetMapClaimBatch = __webpack_exports__.rollbackPartialAssetMapClaimBatch; exports.savePartialAssetMap = __webpack_exports__.savePartialAssetMap; exports.serializePartialAssetMaps = __webpack_exports__.serializePartialAssetMaps; exports.takePartialAssetMap = __webpack_exports__.takePartialAssetMap; for(var __rspack_i in __webpack_exports__)if (-1 === [ "claimPartialAssetMap", "claimPartialAssetMapBatch", "commitPartialAssetMapClaim", "commitPartialAssetMapClaimBatch", "deserializePartialAssetMaps", "getLegacyPartialAssetStorePath", "getPartialAssetLockPath", "getPartialAssetMap", "getPartialAssetStorePath", "removePartialAssetMap", "rollbackPartialAssetMapClaim", "rollbackPartialAssetMapClaimBatch", "savePartialAssetMap", "serializePartialAssetMaps", "takePartialAssetMap" ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i]; Object.defineProperty(exports, '__esModule', { value: true }); //# sourceMappingURL=partial-assets-map.js.map