UNPKG

@fireproof/aws

Version:

PartyKit gateway for AWS

345 lines (341 loc) 13.2 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/aws/index.ts var aws_exports = {}; __export(aws_exports, { connect: () => connect }); module.exports = __toCommonJS(aws_exports); // src/connection-from-store.ts var import_cement = require("@adviser/cement"); var import_core = require("@fireproof/core"); var ConnectionFromStore = class extends import_core.bs.ConnectionBase { constructor(sthis, url) { const logger = (0, import_core.ensureLogger)(sthis, "ConnectionFromStore", { url: () => url.toString(), this: 1, log: 1 }); super(url, logger); this.stores = void 0; this.sthis = sthis; } async onConnect() { this.logger.Debug().Msg("onConnect-start"); const stores = { base: this.url // data: this.urlData, // meta: this.urlMeta, }; const rName = this.url.getParamResult("name"); if (rName.isErr()) { throw this.logger.Error().Err(rName).Msg("missing Parameter").AsError(); } const storeRuntime = import_core.bs.toStoreRuntime({ stores }, this.sthis); const loader = { name: rName.Ok(), ebOpts: { logger: this.logger, store: { stores }, storeRuntime }, sthis: this.sthis }; this.stores = { data: await storeRuntime.makeDataStore(loader), meta: await storeRuntime.makeMetaStore(loader) }; this.logger.Debug().Msg("onConnect-done"); return; } }; function connectionFactory(sthis, iurl) { return new ConnectionFromStore(sthis, import_cement.URI.from(iurl)); } function makeKeyBagUrlExtractable(sthis) { let base = sthis.env.get("FP_KEYBAG_URL"); if (!base) { if ((0, import_cement.runtimeFn)().isBrowser) { base = "indexdb://fp-keybag"; } else { base = "file://./dist/kb-dir-partykit"; } } const kbUrl = import_cement.BuildURI.from(base); kbUrl.defParam("extractKey", "_deprecated_internal_api"); sthis.env.set("FP_KEYBAG_URL", kbUrl.toString()); sthis.logger.Debug().Url(kbUrl, "keyBagUrl").Msg("Make keybag url extractable"); } // src/aws/gateway.ts var import_cement2 = require("@adviser/cement"); var import_core2 = require("@fireproof/core"); async function resultFetch(logger, curl, init) { const url = import_cement2.URI.from(curl); try { const ret = await fetch(url.asURL(), init); return import_cement2.Result.Ok(ret); } catch (err) { return logger.Error().Url(url).Any("init", init).Err(err).Msg("Fetch Error").ResultError(); } } var AWSGateway = class { constructor(sthis) { this.sthis = (0, import_core2.ensureSuperLog)(sthis, "AWSGateway"); this.logger = this.sthis.logger; } async buildUrl(baseUrl, key) { return import_cement2.Result.Ok(baseUrl.build().setParam("key", key).URI()); } async destroy() { return import_cement2.Result.Ok(void 0); } async start(baseUrl) { await this.sthis.start(); this.logger.Debug().Str("url", baseUrl.toString()).Msg("start"); const rParams = baseUrl.getParamsResult("uploadUrl", "webSocketUrl", "dataUrl"); if (rParams.isErr()) { return import_cement2.Result.Err(rParams.Err()); } const ret = baseUrl.build().defParam("version", "v0.1-aws").defParam("region", baseUrl.getParam("region") || "us-east-2").URI(); return import_cement2.Result.Ok(ret); } async close() { return import_cement2.Result.Ok(void 0); } async put(url, body) { const { store } = (0, import_core2.getStore)(url, this.sthis, (...args) => args.join("/")); const rParams = url.getParamsResult("uploadUrl", "key", "name"); if (rParams.isErr()) { return this.logger.Error().Url(url).Err(rParams).Msg("Put Error").ResultError(); } const { uploadUrl, key, name } = rParams.Ok(); return store === "meta" ? this.putMeta(url, uploadUrl, key, name, body) : this.putData(uploadUrl, store, key, name, body); } async putMeta(url, uploadUrl, key, name, body) { const index = url.getParam("index"); if (index) { name += `-${index}`; } name += ".fp"; const fetchUrl = import_cement2.BuildURI.from(uploadUrl).setParam("type", "meta").setParam("key", key).setParam("name", name).URI(); const meta = await import_core2.bs.addCryptoKeyToGatewayMetaPayload(url, this.sthis, body); if (meta.isErr()) { return import_cement2.Result.Err(meta.Err()); } this.logger.Debug().Url(fetchUrl).Any({ meta }).Msg("putMeta"); const rDone = await resultFetch(this.logger, fetchUrl, { method: "PUT", body: this.sthis.txt.decode(meta.Ok()) }); if (rDone.isErr()) { return import_cement2.Result.Err(rDone.Err()); } const done = rDone.Ok(); if (!done.ok) { return this.logger.Error().Url(fetchUrl).Any({ done, x: await done.text() }).Msg("failed to upload meta").ResultError(); } return import_cement2.Result.Ok(void 0); } async putData(uploadUrl, store, key, name, body) { const fetchUrl = import_cement2.BuildURI.from(uploadUrl).setParam("type", store).setParam("car", key).setParam("name", name).URI(); const rDone = await resultFetch(this.logger, fetchUrl, { method: "GET" }); if (rDone.isErr()) { return import_cement2.Result.Err(rDone.Err()); } const done = rDone.Ok(); if (!done.ok) { return this.logger.Error().Any({ resp: done }).Msg("failed to upload meta").ResultError(); } const doneJson = await done.json(); if (!doneJson.uploadURL) { return this.logger.Error().Url(fetchUrl).Msg("Upload URL not found in the response").ResultError(); } const ruploadDone = await resultFetch(this.logger, doneJson.uploadURL, { method: "PUT", body }); if (ruploadDone.isErr()) { return import_cement2.Result.Err(ruploadDone.Err()); } const uploadDone = ruploadDone.Ok(); if (!uploadDone.ok) { return this.logger.Error().Any({ resp: uploadDone }).Msg("Upload Data response error").ResultError(); } return import_cement2.Result.Ok(void 0); } async get(url) { const { store } = (0, import_core2.getStore)(url, this.sthis, (...args) => args.join("/")); switch (store) { case "meta": return this.getMeta(url); case "data": return this.getData(url); case "wal": return this.getWal(url); default: throw new Error(`Unknown store type: ${store}`); } } async getData(url) { const rParams = url.getParamsResult("dataUrl", "key", "name"); if (rParams.isErr()) { return import_cement2.Result.Err(rParams.Err()); } const { dataUrl, name, key } = rParams.Ok(); const fetchUrl = import_cement2.BuildURI.from(dataUrl).appendRelative(`/data/${name}/${key}.car`).URI(); const rresponse = await resultFetch(this.logger, fetchUrl); if (rresponse.isErr()) { return import_cement2.Result.Err(rresponse.Err()); } const response = rresponse.Ok(); if (!response.ok) { this.logger.Error().Url(fetchUrl, "fetchUrl").Url(dataUrl, "dataUrl").Int("status", response.status).Msg("Download Data response error"); return import_cement2.Result.Err(new import_core2.NotFoundError(`data not found: ${url}`)); } const data = new Uint8Array(await response.arrayBuffer()); return import_cement2.Result.Ok(data); } async getMeta(url) { const rParams = url.getParamsResult("uploadUrl", "name", "key"); if (rParams.isErr()) { return import_cement2.Result.Err(rParams.Err()); } const { uploadUrl, key } = rParams.Ok(); let name = rParams.Ok().name; const index = url.getParam("index"); if (index) { name += `-${index}`; } name += ".fp"; const fetchUrl = import_cement2.BuildURI.from(uploadUrl).setParam("type", "meta").setParam("key", key).setParam("name", name).URI(); const rresponse = await resultFetch(this.logger, fetchUrl); if (rresponse.isErr()) { return import_cement2.Result.Err(rresponse.Err()); } const response = rresponse.Ok(); if (!response.ok) { if (response.status === 403) { return import_cement2.Result.Err(new import_core2.NotFoundError(`meta not found: ${url}->${fetchUrl}`)); } return this.logger.Error().Url(fetchUrl).Any({ response }).Msg("Download Meta response error").ResultError(); } const data = new Uint8Array(await response.arrayBuffer()); const res = await import_core2.bs.setCryptoKeyFromGatewayMetaPayload(url, this.sthis, data); if (res.isErr()) { return import_cement2.Result.Err(res.Err()); } return import_cement2.Result.Ok(data); } async getWal(url) { const rParams = url.getParamsResult("dataUrl", "key", "name"); if (rParams.isErr()) { return import_cement2.Result.Err(rParams.Err()); } const { dataUrl, name } = rParams.Ok(); const fetchUrl = import_cement2.BuildURI.from(dataUrl).appendRelative(`/wal/${name}.wal`).URI(); const rresponse = await (0, import_cement2.exception2Result)(() => fetch(fetchUrl.asURL())); if (rresponse.isErr()) { return import_cement2.Result.Err(rresponse.Err()); } const response = rresponse.Ok(); if (!response.ok) { return import_cement2.Result.Err(new import_core2.NotFoundError(`wal not found: ${url}`)); } const data = new Uint8Array(await response.arrayBuffer()); return import_cement2.Result.Ok(data); } async delete(_url) { return import_cement2.Result.Ok(void 0); } async subscribe(url, callback) { url = url.build().setParam("key", "main").defParam("interval", "100").URI(); let lastData = void 0; let interval = parseInt(url.getParam("interval") || "100", 10); const fetchData = async () => { const result = await this.get(url); if (result.isOk()) { const data = result.Ok(); if (!lastData || !data.every((value, index) => lastData && value === lastData[index])) { lastData = data; callback(data); interval = 100; } else { interval = Math.min(interval * 2, 3e3); } } timeoutId = setTimeout(fetchData, interval); }; let timeoutId = setTimeout(fetchData, interval); return import_cement2.Result.Ok(() => { clearTimeout(timeoutId); }); } }; var AWSTestStore = class { constructor(sthis, gw) { this.sthis = (0, import_core2.ensureSuperLog)(sthis, "AWSTestStore"); this.logger = this.sthis.logger; this.gateway = gw; } async get(iurl, key) { const url = iurl.build().setParam("key", key).URI(); const buffer = await this.gateway.get(url); return buffer.Ok(); } }; var onceRegisterAWSStoreProtocol = new import_cement2.KeyedResolvOnce(); function registerAWSStoreProtocol(protocol = "aws:", overrideBaseURL) { return onceRegisterAWSStoreProtocol.get(protocol).once(() => { import_cement2.URI.protocolHasHostpart(protocol); return import_core2.bs.registerStoreProtocol({ protocol, overrideBaseURL, gateway: async (sthis) => { return new AWSGateway(sthis); }, test: async (sthis) => { const gateway = new AWSGateway(sthis); return new AWSTestStore(sthis, gateway); } }); }); } // src/aws/index.ts var import_cement3 = require("@adviser/cement"); registerAWSStoreProtocol(); var connectionCache = new import_cement3.KeyedResolvOnce(); var connect = (db, remoteDbName = "", url = "aws://aws.amazon.com", region = "us-east-2", uploadUrl = "https://7leodn3dj2.execute-api.us-east-2.amazonaws.com/uploads", webSocketUrl = "wss://fufauby0ii.execute-api.us-east-2.amazonaws.com/Prod", dataUrl = "https://fp1-uploads-201698179963.s3.us-east-2.amazonaws.com") => { const { sthis, blockstore, name: dbName } = db; if (!dbName) { throw new Error("dbName is required"); } const urlObj = import_cement3.BuildURI.from(url); const existingName = urlObj.getParam("name"); urlObj.setParam("name", remoteDbName || existingName || dbName); urlObj.defParam("localName", dbName); urlObj.defParam("storekey", `@${dbName}:data@`); urlObj.defParam("region", region); urlObj.defParam("uploadUrl", uploadUrl); urlObj.defParam("webSocketUrl", webSocketUrl); urlObj.defParam("dataUrl", dataUrl); return connectionCache.get(urlObj.toString()).once(() => { makeKeyBagUrlExtractable(sthis); const connection = connectionFactory(sthis, urlObj); connection.connect_X(blockstore); return connection; }); }; //# sourceMappingURL=index.cjs.map