UNPKG

@jimpick/fireproof-netlify

Version:

PartyKit gateway for Netlify

307 lines (305 loc) 10.5 kB
// src/connection-from-store.ts import { BuildURI, runtimeFn, URI } from "@adviser/cement"; import { bs, ensureLogger } from "@jimpick/fireproof-core"; var ConnectionFromStore = class extends bs.ConnectionBase { constructor(sthis, url) { const logger = 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 = 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, URI.from(iurl)); } function makeKeyBagUrlExtractable(sthis) { let base = sthis.env.get("FP_KEYBAG_URL"); if (!base) { if (runtimeFn().isBrowser) { base = "indexdb://fp-keybag"; } else { base = "file:///tmp/dist/kb-dir-partykit"; } } const kbUrl = 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/netlify/gateway.ts import { KeyedResolvOnce, Result, URI as URI2, BuildURI as BuildURI2, exception2Result } from "@adviser/cement"; import { bs as bs2, getStore, NotFoundError, ensureSuperLog } from "@jimpick/fireproof-core"; var NetlifyGateway = class { constructor(sthis) { this.sthis = ensureSuperLog(sthis, "NetlifyGateway"); this.logger = this.sthis.logger; } async buildUrl(baseUrl, key) { return Result.Ok(baseUrl.build().setParam("key", key).URI()); } async destroy(url) { const { store } = getStore(url, this.sthis, (...args) => args.join("/")); if (store !== "meta") { return Result.Ok(void 0); } const rName = url.getParamResult("name"); if (rName.isErr()) { return Result.Err(rName.Err()); } let name = rName.Ok(); const index = url.getParam("index"); if (index) { name += `-${index}`; } name += ".fp"; const remoteBaseUrl = url.getParam("remoteBaseUrl"); if (!remoteBaseUrl) { return Result.Err(new Error("Remote base URL not found in the URI")); } const fetchUrl = BuildURI2.from(remoteBaseUrl).setParam("meta", name).URI(); const response = await fetch(fetchUrl.asURL(), { method: "DELETE" }); if (!response.ok) { return this.logger.Error().Str("status", response.statusText).Msg("Failed to destroy meta database").ResultError(); } return Result.Ok(void 0); } async start(uri) { const protocol = uri.host.startsWith("localhost") ? "http" : "https"; const host = uri.host; const path = "/fireproof"; const urlString = `${protocol}://${host}${path}`; const baseUrl = BuildURI2.from(urlString).URI(); const ret = uri.build().defParam("version", "v0.1-netlify").defParam("remoteBaseUrl", baseUrl.toString()).URI(); return Result.Ok(ret); } async close() { return Result.Ok(void 0); } async put(url, body) { const { store } = getStore(url, this.sthis, (...args) => args.join("/")); const rParams = url.getParamsResult("key", "name"); if (rParams.isErr()) { return this.logger.Error().Url(url).Err(rParams).Msg("Put Error").ResultError(); } const { key } = rParams.Ok(); let { name } = rParams.Ok(); const index = url.getParam("index"); if (index) { name += `-${index}`; } name += ".fp"; const remoteBaseUrl = url.getParam("remoteBaseUrl"); if (!remoteBaseUrl) { return Result.Err(new Error("Remote base URL not found in the URI")); } const fetchUrl = BuildURI2.from(remoteBaseUrl); switch (store) { case "meta": fetchUrl.setParam("meta", name); break; default: fetchUrl.setParam("car", key); break; } if (store === "meta") { const bodyRes = await bs2.addCryptoKeyToGatewayMetaPayload(url, this.sthis, body); if (bodyRes.isErr()) { return Result.Err(bodyRes.Err()); } body = bodyRes.Ok(); } const done = await fetch(fetchUrl.asURL(), { method: "PUT", body }); if (!done.ok) { return this.logger.Error().Url(fetchUrl.URI()).Int("status", done.status).Str("statusText", done.statusText).Msg(`failed to upload ${store}`).ResultError(); } return Result.Ok(void 0); } async get(url) { const { store } = getStore(url, this.sthis, (...args) => args.join("/")); const rParams = url.getParamsResult("key", "name", "remoteBaseUrl"); if (rParams.isErr()) { return Result.Err(rParams.Err()); } const { key, remoteBaseUrl } = rParams.Ok(); let { name } = rParams.Ok(); const index = url.getParam("index"); if (index) { name += `-${index}`; } name += ".fp"; const fetchUrl = BuildURI2.from(remoteBaseUrl); switch (store) { case "meta": fetchUrl.setParam("meta", name); break; default: fetchUrl.setParam("car", key); break; } const rresponse = await exception2Result(() => { return fetch(fetchUrl.URI().asURL()); }); if (rresponse.isErr()) { return this.logger.Error().Url(fetchUrl).Err(rresponse).Msg("Failed to fetch").ResultError(); } const response = rresponse.Ok(); if (!response.ok) { return Result.Err(new NotFoundError(`${store} not found: ${url}`)); } const data = new Uint8Array(await response.arrayBuffer()); if (store === "meta") { const res = await bs2.setCryptoKeyFromGatewayMetaPayload(url, this.sthis, data); if (res.isErr()) { return this.logger.Error().Url(url).Err(res).Msg("Failed to set crypto key").ResultError(); } } return Result.Ok(data); } async delete(url) { const { store } = getStore(url, this.sthis, (...args) => args.join("/")); const rParams = url.getParamsResult("key", "name", "remoteBaseUrl"); if (rParams.isErr()) { return Result.Err(rParams.Err()); } const { key, remoteBaseUrl } = rParams.Ok(); let { name } = rParams.Ok(); const index = url.getParam("index"); if (index) { name += `-${index}`; } name += ".fp"; const fetchUrl = BuildURI2.from(remoteBaseUrl); switch (store) { case "meta": fetchUrl.setParam("meta", name); break; default: if (!key) { return Result.Err(new Error("Key not found in the URI")); } fetchUrl.setParam("car", key); break; } const response = await fetch(fetchUrl.URI().asURL(), { method: "DELETE" }); if (!response.ok) { return Result.Err(new Error(`Failed to delete car: ${response.statusText}`)); } return Result.Ok(void 0); } async subscribe(url, callback) { url = url.build().setParam("key", "main").defParam("interval", "100").defParam("maxInterval", "3000").URI(); let lastData = void 0; const initInterval = parseInt(url.getParam("interval") || "100", 10); const maxInterval = parseInt(url.getParam("maxInterval") || "3000", 10); let interval = initInterval; 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 = initInterval; } else { interval = Math.min(interval * 2, maxInterval); } } timeoutId = setTimeout(fetchData, interval); }; let timeoutId = setTimeout(fetchData, interval); return Result.Ok(() => { clearTimeout(timeoutId); }); } }; var NetlifyTestStore = class { constructor(sthis, gw) { this.sthis = ensureSuperLog(sthis, "NetlifyTestStore"); 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 onceRegisterNetlifyStoreProtocol = new KeyedResolvOnce(); function registerNetlifyStoreProtocol(protocol = "netlify:", overrideBaseURL) { return onceRegisterNetlifyStoreProtocol.get(protocol).once(() => { URI2.protocolHasHostpart(protocol); return bs2.registerStoreProtocol({ protocol, overrideBaseURL, gateway: async (sthis) => { return new NetlifyGateway(sthis); }, test: async (sthis) => { const gateway = new NetlifyGateway(sthis); return new NetlifyTestStore(sthis, gateway); } }); }); } // src/netlify/index.ts import { BuildURI as BuildURI3, KeyedResolvOnce as KeyedResolvOnce2, runtimeFn as runtimeFn2 } from "@adviser/cement"; if (!runtimeFn2().isBrowser) { const url = BuildURI3.from(process.env.FP_KEYBAG_URL || "file://./dist/kb-dir-netlify"); url.setParam("extractKey", "_deprecated_internal_api"); process.env.FP_KEYBAG_URL = url.toString(); } registerNetlifyStoreProtocol(); var connectionCache = new KeyedResolvOnce2(); var connect = (db, remoteDbName = "", url = "netlify://localhost:8888?protocol=ws") => { const { sthis, blockstore, name: dbName } = db; if (!dbName) { throw new Error("dbName is required"); } const urlObj = BuildURI3.from(url); const existingName = urlObj.getParam("name"); urlObj.defParam("name", remoteDbName || existingName || dbName); urlObj.defParam("localName", dbName); urlObj.defParam("storekey", `@${dbName}:data@`); return connectionCache.get(urlObj.toString()).once(() => { makeKeyBagUrlExtractable(sthis); const connection = connectionFactory(sthis, urlObj); connection.connect_X(blockstore); return connection; }); }; export { connect }; //# sourceMappingURL=index.js.map