@actyx/sdk
Version:
Actyx SDK
218 lines • 9.1 kB
JavaScript
;
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