UNPKG

everything-dev

Version:

A consolidated product package for building Module Federation apps with oRPC APIs.

189 lines (187 loc) • 9.81 kB
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); const require_http_client = require('../../utils/http-client.cjs'); let effect = require("effect"); //#region src/services/registry/fastkv.ts function getNetworkIdForAccount(accountId) { return accountId.endsWith(".testnet") ? "testnet" : "mainnet"; } const FASTKV_TESTNET_URL = "https://kv.test.fastnear.com"; const FASTKV_MAINNET_URL = "https://kv.main.fastnear.com"; const REGISTRY_NAMESPACE_TESTNET = "dev.allthethings.testnet"; const REGISTRY_NAMESPACE_MAINNET = "dev.everything.near"; function getFastKvBaseUrlForNetwork(network) { return network === "testnet" ? FASTKV_TESTNET_URL : FASTKV_MAINNET_URL; } function getFastKvBaseUrlForAccount(accountId) { return getNetworkIdForAccount(accountId) === "testnet" ? getFastKvBaseUrlForNetwork("testnet") : getFastKvBaseUrlForNetwork("mainnet"); } function buildRegistryConfigUrl(accountId, gatewayId, registry) { const baseUrl = getFastKvBaseUrlForAccount(accountId); const namespace = getRegistryNamespaceForAccount(accountId, registry); const key = encodeURIComponent(getRegistryConfigKey(accountId, gatewayId)); return `${baseUrl}/v0/latest/${encodeURIComponent(namespace)}/${encodeURIComponent(accountId)}/${key}`; } function buildRegistryConfigUrlForNetwork(network, accountId, gatewayId, registry) { const baseUrl = getFastKvBaseUrlForNetwork(network); const namespace = getRegistryNamespaceForNetwork(network, registry); const key = encodeURIComponent(getRegistryConfigKey(accountId, gatewayId)); return `${baseUrl}/v0/latest/${encodeURIComponent(namespace)}/${encodeURIComponent(accountId)}/${key}`; } function getRegistryNamespaceForAccount(accountId, registry) { return registry ?? getRegistryNamespaceForNetwork(getNetworkIdForAccount(accountId)); } function getRegistryNamespaceForNetwork(network, registry) { if (registry) return registry; return network === "testnet" ? REGISTRY_NAMESPACE_TESTNET : REGISTRY_NAMESPACE_MAINNET; } function getRegistryConfigKey(accountId, gatewayId, pathSegments = []) { return `apps/${accountId}/${gatewayId}${pathSegments.length > 0 ? `/${pathSegments.map((segment) => encodeURIComponent(segment)).join("/")}` : ""}/bos.config.json`; } function parseBosUrl(bosUrl) { const match = bosUrl.split("#")[0].match(/^bos:\/\/([^/]+)\/(.+)$/); if (!match?.[1] || !match[2]) throw new Error(`Invalid BOS URL: ${bosUrl}`); const pathSegments = match[2].split("/").filter(Boolean).map((segment) => decodeURIComponent(segment)); if (pathSegments.length === 0) throw new Error(`Invalid BOS URL: ${bosUrl}`); const [gatewayId, ...pathSegmentsTail] = pathSegments; if (!gatewayId) throw new Error(`Invalid BOS URL: ${bosUrl}`); return { accountId: match[1], gatewayId, pathSegments: pathSegmentsTail }; } async function fetchBosConfigFromFastKv(bosUrl, registry) { const { accountId, gatewayId, pathSegments } = parseBosUrl(bosUrl); const key = encodeURIComponent(getRegistryConfigKey(accountId, gatewayId, pathSegments)); return parseFastKvConfigValue(await fetchJson(`${getFastKvBaseUrlForAccount(accountId)}/v0/latest/${encodeURIComponent(getRegistryNamespaceForAccount(accountId, registry))}/${encodeURIComponent(accountId)}/${key}`), bosUrl); } /** * Read an arbitrary registry key under `apps/<account>/<gateway>/` — the * well-known-slot seam (ADR 0034's `platform.json` lives beside the config). * Absent key (404) → null; a network failure throws so callers can distinguish * "the slot is empty" from "the slot is unreachable" (a swallowed error here * would silently tighten a compat floor). */ async function fetchRegistryKey(accountId, gatewayId, keyPath, registry) { const key = encodeURIComponent(`apps/${accountId}/${gatewayId}/${keyPath}`); const value = (await fetchJson(`${getFastKvBaseUrlForAccount(accountId)}/v0/latest/${encodeURIComponent(getRegistryNamespaceForAccount(accountId, registry))}/${encodeURIComponent(accountId)}/${key}`))?.entries?.find(Boolean)?.value; if (!value) return null; if (typeof value === "string") return JSON.parse(value); return value; } /** Tolerant variant of {@link fetchRegistryKey} — any failure collapses to null. */ async function fetchRegistryKeyOrNull(accountId, gatewayId, keyPath, registry) { try { return await fetchRegistryKey(accountId, gatewayId, keyPath, registry); } catch { return null; } } /** Read a published config from an already-built registry URL * (`buildRegistryConfigUrlForNetwork`). Unlike {@link fetchBosConfigFromFastKv} * this takes the URL directly, so the read key is exactly the one the publish * writes, and a network failure is reported as such instead of collapsing * into "No config found" (fetchJsonOrNull swallows errors into null). */ async function fetchPublishedConfigFromFastKv(url) { const response = await require_http_client.fetchRaw(url, { headers: { accept: "application/json" } }); if (!response.ok) { if (response.status === 404) throw new Error(`No config found for ${url}`); throw new Error(`HTTP ${response.status} fetching ${url}`); } return parseFastKvConfigValue(await response.json(), url); } function parseFastKvConfigValue(payload, label) { const value = payload?.entries?.find(Boolean)?.value; if (!value) throw new Error(`No config found for ${label}`); if (typeof value === "string") return JSON.parse(value); if (typeof value !== "object") throw new Error(`Invalid config value for ${label}`); return value; } const FASTKV_HISTORY_MAX_LIMIT = 200; /** * Publish history for the registry config key (atomic-deploys 11): the * exact-key variant of FastData's history endpoint, newest-first. The API * caps a page at 200 rows; a config key's write history fits well inside * that, so a single request is the whole story (the limit param is clamped). */ async function fetchConfigHistory(opts) { const key = encodeURIComponent(getRegistryConfigKey(opts.accountId, opts.gatewayId)); const limit = Math.max(1, Math.min(opts.limit ?? 20, FASTKV_HISTORY_MAX_LIMIT)); return ((await fetchJson(`${getFastKvBaseUrlForAccount(opts.accountId)}/v0/history/${encodeURIComponent(getRegistryNamespaceForAccount(opts.accountId, opts.registry))}/${encodeURIComponent(opts.accountId)}/${key}?limit=${limit}`))?.entries ?? []).filter(Boolean).map((entry) => ({ blockHeight: entry.block_height ?? 0, blockTimestampNs: String(entry.block_timestamp ?? ""), ...entry.tx_hash ? { txHash: entry.tx_hash } : {}, value: typeof entry.value === "string" && entry.value.length > 0 ? JSON.parse(entry.value) : entry.value })).sort((a, b) => b.blockHeight - a.blockHeight); } /** * The per-deploy audit trail (atomic-deploys 03/12): every publish writes * `apps/<account>/<gateway>/manifests/<ts>.json` atomically with the pointer * swap — this lists that key family newest-first via the history-by-prefix * endpoint. */ async function fetchDeployManifests(opts) { const keyPrefix = encodeURIComponent(`${getRegistryConfigKey(opts.accountId, opts.gatewayId).replace(/bos\.config\.json$/, "")}manifests/`); return ((await fetchJson(`${getFastKvBaseUrlForAccount(opts.accountId)}/v0/history/${encodeURIComponent(getRegistryNamespaceForAccount(opts.accountId, opts.registry))}/${encodeURIComponent(opts.accountId)}`, { method: "POST", body: JSON.stringify({ key_prefix: decodeURIComponent(keyPrefix), asc: false, limit: Math.max(1, Math.min(opts.limit ?? 20, FASTKV_HISTORY_MAX_LIMIT)) }) }))?.entries ?? []).filter(Boolean).map((entry) => ({ key: entry.key ?? "", blockHeight: entry.block_height ?? 0, blockTimestampNs: String(entry.block_timestamp ?? ""), value: typeof entry.value === "string" && entry.value.length > 0 ? JSON.parse(entry.value) : entry.value })).sort((a, b) => b.blockHeight - a.blockHeight); } async function fetchRemotePluginManifest(cdnUrl) { const baseUrl = cdnUrl.replace(/\/$/, ""); return require_http_client.fetchJsonOrNull(`${baseUrl}/plugin.manifest.json`, { retries: 2 }); } async function fetchJson(url, init) { const headers = { accept: "application/json", "content-type": "application/json", ...init?.headers }; return require_http_client.fetchJsonOrNull(url, { method: init?.method, headers, body: init?.body ?? void 0, retries: 3 }); } /** * The FastKV registry as a Context.Service (#425): reads (config, published * config, history, deploy manifests) and the bos:// URL algebra behind one * tag. The registry WRITE is a signing concern — it rides the signer * services (#423), not this tag. Callers may keep using the plain * functions — the module is the implementation; the tag is the seam. */ var FastKVRegistryService = class extends effect.Context.Service()("everything-dev/registry/fastkv") { static Live = effect.Layer.succeed(this, this.of({ fetchBosConfigFromFastKv, fetchPublishedConfigFromFastKv, fetchConfigHistory, fetchDeployManifests })); }; //#endregion exports.FastKVRegistryService = FastKVRegistryService; exports.buildRegistryConfigUrl = buildRegistryConfigUrl; exports.buildRegistryConfigUrlForNetwork = buildRegistryConfigUrlForNetwork; exports.fetchBosConfigFromFastKv = fetchBosConfigFromFastKv; exports.fetchConfigHistory = fetchConfigHistory; exports.fetchDeployManifests = fetchDeployManifests; exports.fetchPublishedConfigFromFastKv = fetchPublishedConfigFromFastKv; exports.fetchRegistryKey = fetchRegistryKey; exports.fetchRegistryKeyOrNull = fetchRegistryKeyOrNull; exports.fetchRemotePluginManifest = fetchRemotePluginManifest; exports.getFastKvBaseUrlForNetwork = getFastKvBaseUrlForNetwork; exports.getRegistryNamespaceForAccount = getRegistryNamespaceForAccount; exports.getRegistryNamespaceForNetwork = getRegistryNamespaceForNetwork; exports.parseBosUrl = parseBosUrl; exports.parseFastKvConfigValue = parseFastKvConfigValue; //# sourceMappingURL=fastkv.cjs.map