@fireproof/aws
Version:
PartyKit gateway for AWS
345 lines (341 loc) • 13.2 kB
JavaScript
"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