UNPKG

@actyx/sdk

Version:
218 lines 9.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.BlobSnapshotStore = void 0; const cross_fetch_1 = require("cross-fetch"); const t = require("io-ts"); const E = require("fp-ts/lib/Either"); const semver_1 = require("semver"); const __1 = require(".."); const wire_1 = require("../types/wire"); const utils_1 = require("./utils"); const log_1 = require("../internal_common/log"); const ENDPOINT = 'blob'; const entityFolder = (api, semantics, entity) => `${api}/${ENDPOINT}/-/@pond/snap/${semantics}/${entity}`; const versionFolder = (api, semantics, entity, version) => `${api}/${ENDPOINT}/-/@pond/snap/${semantics}/${entity}/${version}`; const snapFolder = (api, semantics, entity, version, tag) => `${api}/${ENDPOINT}/-/@pond/snap/${semantics}/${entity}/${version}/${tag}`; const Folder = t.record(t.string, t.union([ t.type({ type: t.literal('folder'), }), t.type({ type: t.literal('file'), originalSize: t.number, compressedSize: t.number, atimeMillis: t.number, ctimeMillis: t.number, }), ])); const Meta = t.type({ key: wire_1.EventKeyIO, offsets: wire_1.OffsetMapIO, horizon: t.union([wire_1.EventKeyIO, t.undefined]), cycle: t.number, }); const mkMeta = (key, offsets, horizon, cycle) => ({ key, offsets, horizon, cycle, }); const Persistent = t.type({ mode: t.literal('persistent'), hardQuota: t.number, }); const Elastic = t.type({ mode: t.literal('elastic'), fixedAllowance: t.number, ceiling: t.union([t.number, t.undefined]), cleaningOrder: t.keyof({ atimeAsc: 1, atimeDesc: 1, ctimeAsc: 1, ctimeDesc: 1, timeAsc: 1, timeDesc: 1, sizeAsc: 1, sizeDesc: 1, }), }); const Elasticity = t.union([Persistent, Elastic]); const SetElasticity = t.type({ type: t.literal('setElasticity'), definition: Elasticity, }); const fetch = async (input, init) => { const res = await (0, cross_fetch_1.default)(input, init); if (!res.ok) { const method = (init === null || init === void 0 ? void 0 : init.method) || 'GET'; throw new Error(`fetch ${method} ${input}: (${res.status}) ${await res.text()}`); } return res; }; class BlobSnapshotStore { constructor(api, currentToken, currentActyxVersion, reservedStorage) { this.api = api; this.currentToken = currentToken; this.currentActyxVersion = currentActyxVersion; this.storeSnapshot = async (semantics, entity, key, offsets, horizon, cycle, version, tag, blob) => { if ((0, semver_1.lt)(this.currentActyxVersion(), '2.12.0')) return false; log_1.default.http.debug('storeSnapshot start', semantics, entity, tag); try { const headers = (0, utils_1.mkHeaders)(this.currentToken()); const folder = snapFolder(this.api, semantics, entity, version, tag); await fetch(folder, { method: 'DELETE', headers }); await fetch(`${folder}/blob`, { method: 'PUT', body: blob, headers }); await fetch(`${folder}/meta`, { method: 'PUT', body: JSON.stringify(mkMeta(key, offsets, horizon, cycle)), headers, }); const entityF = entityFolder(this.api, semantics, entity); const ls = Folder.decode(await (await fetch(entityF, { headers })).json()); if (E.isRight(ls)) { for (const v of Object.keys(ls.right)) { if (Number(v) < version) { await fetch(`${entityF}/${v}`, { method: 'DELETE', headers }); } } } return true; } catch (e) { log_1.default.http.error('storeSnapshot', semantics, entity, tag, e); return false; } finally { log_1.default.http.debug('storeSnapshot done', semantics, entity, tag); } }; this.retrieveSnapshot = async (semantics, entity, version) => { if ((0, semver_1.lt)(this.currentActyxVersion(), '2.12.0')) return undefined; log_1.default.http.debug('retrieveSnapshot start', semantics, entity); try { const headers = (0, utils_1.mkHeaders)(this.currentToken()); const folder = versionFolder(this.api, semantics, entity, version); const ls = Folder.decode(await (await fetch(folder, { headers })).json()); if (E.isLeft(ls)) return; let meta = undefined; let blob = ''; for (const tag of Object.keys(ls.right)) { const metaRes = await (0, cross_fetch_1.default)(`${folder}/${tag}/meta`, { headers }); if (!metaRes.ok) continue; const m = Meta.decode(await metaRes.json()); if (E.isLeft(m)) continue; if (meta !== undefined && __1.EventKey.ord.compare(meta.key, m.right.key) > 0) continue; const blobRes = await (0, cross_fetch_1.default)(`${folder}/${tag}/blob`, { headers }); if (!blobRes.ok) continue; meta = m.right; blob = await blobRes.text(); } if (meta === undefined) return; return { state: blob, offsets: meta.offsets, eventKey: meta.key, horizon: meta.horizon, cycle: meta.cycle, }; } catch (e) { log_1.default.http.error('retrieveSnapshot', semantics, entity, e); return; } finally { log_1.default.http.debug('retrieveSnapshot done', semantics, entity); } }; this.invalidateSnapshots = async (semantics, entity, key) => { if ((0, semver_1.lt)(this.currentActyxVersion(), '2.12.0')) return; log_1.default.http.debug('invalidateSnapshots start', semantics, entity, key); try { const headers = (0, utils_1.mkHeaders)(this.currentToken()); const folder = entityFolder(this.api, semantics, entity); const ls = Folder.decode(await (await fetch(folder, { headers })).json()); if (E.isLeft(ls)) return; for (const version of Object.keys(ls)) { const vf = `${folder}/${version}`; const vfls = Folder.decode(await (await fetch(vf, { headers })).json()); if (E.isLeft(vfls)) continue; for (const tag of Object.keys(vfls)) { const meta = Meta.decode(await (await fetch(`${vf}/${tag}/meta`, { headers })).json()); if (E.isLeft(meta)) continue; if (__1.EventKey.ord.compare(key, meta.right.key) < 0) { await fetch(`${vf}/${tag}`, { method: 'DELETE', headers }); } } } } catch (e) { log_1.default.http.error('invalidateSnapshot', semantics, entity, key, e); } finally { log_1.default.http.debug('invalidateSnapshots', semantics, entity, key); } }; this.invalidateAllSnapshots = async () => { if ((0, semver_1.lt)(this.currentActyxVersion(), '2.12.0')) return; try { const headers = (0, utils_1.mkHeaders)(this.currentToken()); await fetch(`${this.api}/${ENDPOINT}/-/@pond/snap`); } catch (e) { log_1.default.http.error('invalidateAllSnapshots', e); } }; if ((0, semver_1.gte)(currentActyxVersion(), '2.12.0')) { const headers = (0, utils_1.mkHeaders)(currentToken()); const cmd = SetElasticity.encode({ type: 'setElasticity', definition: { mode: 'elastic', fixedAllowance: reservedStorage, ceiling: undefined, cleaningOrder: 'timeAsc', }, }); fetch(`${api}/${ENDPOINT}/-/@pond/snap`, { method: 'POST', headers, body: JSON.stringify(cmd), }).catch((e) => log_1.default.actyx.warn('cannot set Pond snapshot elasticity:', e)); } } } exports.BlobSnapshotStore = BlobSnapshotStore; //# sourceMappingURL=blobSnapshotStore.js.map