@objectstack/runtime 7.1.0 → 7.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -8475,6 +8475,36 @@ function resolveCloudUrl(explicit) {
8475
8475
  return picked.replace(/\/+$/, "");
8476
8476
  }
8477
8477
 
8478
+ // src/cloud/marketplace-public-url.ts
8479
+ function resolveMarketplacePublicBaseUrl(explicit) {
8480
+ const raw = (explicit ?? process.env.OS_MARKETPLACE_PUBLIC_BASE_URL ?? "").trim();
8481
+ const lower = raw.toLowerCase();
8482
+ if (!raw || lower === "off" || lower === "none" || lower === "disabled" || lower === "false") {
8483
+ return "";
8484
+ }
8485
+ return raw.replace(/\/+$/, "");
8486
+ }
8487
+ function publicMarketplaceKeyForApiPath(pathname) {
8488
+ const prefix = "/api/v1/marketplace/packages";
8489
+ if (pathname === prefix) return "packages.json";
8490
+ if (!pathname.startsWith(`${prefix}/`)) return null;
8491
+ const tail = pathname.slice(prefix.length + 1);
8492
+ if (!tail) return null;
8493
+ const parts = tail.split("/");
8494
+ if (parts.length === 1) {
8495
+ const id = decodeURIComponent(parts[0] ?? "");
8496
+ if (!id) return null;
8497
+ return `packages/${encodeURIComponent(id)}.json`;
8498
+ }
8499
+ if (parts.length === 4 && parts[1] === "versions" && parts[3] === "manifest") {
8500
+ const id = decodeURIComponent(parts[0] ?? "");
8501
+ const versionId = decodeURIComponent(parts[2] ?? "");
8502
+ if (!id || !versionId) return null;
8503
+ return `packages/${encodeURIComponent(id)}/versions/${encodeURIComponent(versionId)}/manifest.json`;
8504
+ }
8505
+ return null;
8506
+ }
8507
+
8478
8508
  // src/cloud/marketplace-proxy-plugin.ts
8479
8509
  var MARKETPLACE_PREFIX = "/api/v1/marketplace";
8480
8510
  var DEFAULT_LRU_MAX = 200;
@@ -8514,7 +8544,7 @@ var LruTtlCache = class {
8514
8544
  var MarketplaceProxyPlugin = class _MarketplaceProxyPlugin {
8515
8545
  constructor(config = {}) {
8516
8546
  this.name = "com.objectstack.runtime.marketplace-proxy";
8517
- this.version = "1.0.0";
8547
+ this.version = "1.1.0";
8518
8548
  this.init = async (_ctx) => {
8519
8549
  };
8520
8550
  this.start = async (ctx) => {
@@ -8532,7 +8562,11 @@ var MarketplaceProxyPlugin = class _MarketplaceProxyPlugin {
8532
8562
  }
8533
8563
  const rawApp = httpServer.getRawApp();
8534
8564
  const cloudUrl = this.cloudUrl;
8565
+ const publicBaseUrl = this.publicBaseUrl;
8535
8566
  const cache = this.cache;
8567
+ if (publicBaseUrl) {
8568
+ ctx.logger?.info?.(`[MarketplaceProxyPlugin] public R2 fast-path enabled \u2192 ${publicBaseUrl}`);
8569
+ }
8536
8570
  const handler = async (c, next) => {
8537
8571
  if (!cloudUrl) {
8538
8572
  return c.json({
@@ -8548,8 +8582,18 @@ var MarketplaceProxyPlugin = class _MarketplaceProxyPlugin {
8548
8582
  if (incomingUrl.pathname.startsWith(`${MARKETPLACE_PREFIX}/install-local`)) {
8549
8583
  return next();
8550
8584
  }
8551
- const target = `${cloudUrl}${incomingUrl.pathname}${incomingUrl.search}`;
8552
8585
  const method = String(c.req.method ?? "GET").toUpperCase();
8586
+ if (publicBaseUrl && (method === "GET" || method === "HEAD")) {
8587
+ const r2Resp = await tryPublicMarketplaceFetch(
8588
+ publicBaseUrl,
8589
+ incomingUrl,
8590
+ method,
8591
+ c.req.header("accept"),
8592
+ ctx.logger
8593
+ );
8594
+ if (r2Resp) return r2Resp;
8595
+ }
8596
+ const target = `${cloudUrl}${incomingUrl.pathname}${incomingUrl.search}`;
8553
8597
  if (method !== "GET" && method !== "HEAD") {
8554
8598
  return c.json({
8555
8599
  success: false,
@@ -8630,12 +8674,82 @@ var MarketplaceProxyPlugin = class _MarketplaceProxyPlugin {
8630
8674
  });
8631
8675
  };
8632
8676
  this.cloudUrl = resolveCloudUrl(config.controlPlaneUrl);
8677
+ this.publicBaseUrl = resolveMarketplacePublicBaseUrl(config.publicMarketplaceBaseUrl);
8633
8678
  const envFlag = (process.env.OS_MARKETPLACE_CACHE ?? "").trim().toLowerCase();
8634
8679
  const envDisabled = ["off", "false", "0", "no", "disable", "disabled"].includes(envFlag);
8635
8680
  const disabled = config.cacheDisabled ?? envDisabled;
8636
8681
  this.cache = disabled ? null : new LruTtlCache(Math.max(8, config.cacheMaxEntries ?? DEFAULT_LRU_MAX));
8637
8682
  }
8638
8683
  };
8684
+ async function tryPublicMarketplaceFetch(publicBaseUrl, incomingUrl, method, acceptHeader, logger) {
8685
+ const key = publicMarketplaceKeyForApiPath(incomingUrl.pathname);
8686
+ if (!key) return null;
8687
+ const target = `${publicBaseUrl}/${key}`;
8688
+ let resp;
8689
+ try {
8690
+ resp = await fetch(target, {
8691
+ method: "GET",
8692
+ headers: {
8693
+ "Accept": acceptHeader || "application/json",
8694
+ "User-Agent": `objectos-marketplace-proxy/public-r2`
8695
+ }
8696
+ });
8697
+ } catch (err) {
8698
+ logger?.warn?.(`[MarketplaceProxyPlugin] public R2 fetch failed (${target}): ${err?.message ?? err}`);
8699
+ return null;
8700
+ }
8701
+ if (resp.status === 404) return null;
8702
+ if (!resp.ok) {
8703
+ logger?.warn?.(`[MarketplaceProxyPlugin] public R2 ${target} returned ${resp.status} \u2014 falling back to cloud`);
8704
+ return null;
8705
+ }
8706
+ const isList = key === "packages.json";
8707
+ const hasFilters = isList && (incomingUrl.searchParams.has("q") || incomingUrl.searchParams.has("category") || incomingUrl.searchParams.has("limit") || incomingUrl.searchParams.has("offset"));
8708
+ if (!hasFilters) {
8709
+ const headers2 = new Headers();
8710
+ const ct = resp.headers.get("content-type") ?? "application/json; charset=utf-8";
8711
+ headers2.set("content-type", ct);
8712
+ const cc = resp.headers.get("cache-control");
8713
+ if (cc) headers2.set("cache-control", cc);
8714
+ const etag = resp.headers.get("etag");
8715
+ if (etag) headers2.set("etag", etag);
8716
+ headers2.set("x-cache", "PUBLIC-R2");
8717
+ const body2 = method === "HEAD" ? null : resp.body;
8718
+ return new Response(body2, { status: 200, headers: headers2 });
8719
+ }
8720
+ let snapshot;
8721
+ try {
8722
+ snapshot = await resp.json();
8723
+ } catch (err) {
8724
+ logger?.warn?.(`[MarketplaceProxyPlugin] public R2 list snapshot parse failed: ${err?.message ?? err}`);
8725
+ return null;
8726
+ }
8727
+ const items = Array.isArray(snapshot?.data?.items) ? snapshot.data.items : [];
8728
+ const q = (incomingUrl.searchParams.get("q") ?? "").trim().toLowerCase();
8729
+ const category = (incomingUrl.searchParams.get("category") ?? "").trim();
8730
+ const limit = Math.min(Math.max(Number(incomingUrl.searchParams.get("limit") ?? 50), 1), 100);
8731
+ const offset = Math.max(Number(incomingUrl.searchParams.get("offset") ?? 0), 0);
8732
+ let filtered = items;
8733
+ if (q) {
8734
+ filtered = filtered.filter((r) => {
8735
+ const dn = String(r?.display_name ?? "").toLowerCase();
8736
+ const mid = String(r?.manifest_id ?? "").toLowerCase();
8737
+ return dn.includes(q) || mid.includes(q);
8738
+ });
8739
+ }
8740
+ if (category) {
8741
+ filtered = filtered.filter((r) => String(r?.category ?? "") === category);
8742
+ }
8743
+ const total = filtered.length;
8744
+ const page = filtered.slice(offset, offset + limit);
8745
+ const body = JSON.stringify({ success: true, data: { items: page, total, limit, offset } });
8746
+ const headers = new Headers({
8747
+ "content-type": "application/json; charset=utf-8",
8748
+ "cache-control": "public, max-age=30",
8749
+ "x-cache": "PUBLIC-R2-FILTERED"
8750
+ });
8751
+ return new Response(method === "HEAD" ? null : body, { status: 200, headers });
8752
+ }
8639
8753
  var PASSTHROUGH_HEADERS = ["content-type", "cache-control", "etag", "last-modified", "vary"];
8640
8754
  function collectHeaders(src) {
8641
8755
  const out = {};
@@ -9119,22 +9233,55 @@ var MarketplaceInstallLocalPlugin = class {
9119
9233
  return c.json({ success: false, error: { code: "bad_request", message: "packageId is required." } }, 400);
9120
9234
  }
9121
9235
  let payload;
9122
- try {
9123
- const url = `${this.cloudUrl}/api/v1/marketplace/packages/${encodeURIComponent(packageId)}/versions/${encodeURIComponent(versionId)}/manifest`;
9124
- const resp = await fetch(url, { headers: { "Accept": "application/json" } });
9125
- if (!resp.ok) {
9126
- const text = await resp.text().catch(() => "");
9236
+ const publicBase = resolveMarketplacePublicBaseUrl();
9237
+ const fetchAttempts = [];
9238
+ if (publicBase) {
9239
+ fetchAttempts.push({
9240
+ label: "public-r2",
9241
+ url: `${publicBase}/packages/${encodeURIComponent(packageId)}/versions/${encodeURIComponent(versionId)}/manifest.json`
9242
+ });
9243
+ }
9244
+ fetchAttempts.push({
9245
+ label: "cloud",
9246
+ url: `${this.cloudUrl}/api/v1/marketplace/packages/${encodeURIComponent(packageId)}/versions/${encodeURIComponent(versionId)}/manifest`
9247
+ });
9248
+ let lastErrStatus = 0;
9249
+ let lastErrText = "";
9250
+ for (const attempt of fetchAttempts) {
9251
+ try {
9252
+ const resp = await fetch(attempt.url, { headers: { "Accept": "application/json" } });
9253
+ if (!resp.ok) {
9254
+ lastErrStatus = resp.status;
9255
+ lastErrText = (await resp.text().catch(() => "")).slice(0, 200);
9256
+ if (attempt.label === "public-r2" && resp.status === 404) {
9257
+ ctx.logger?.info?.(`[MarketplaceInstallLocal] public-r2 miss for ${packageId}@${versionId}, falling back to cloud`);
9258
+ continue;
9259
+ }
9260
+ if (attempt.label === "public-r2" && resp.status >= 500) {
9261
+ ctx.logger?.warn?.(`[MarketplaceInstallLocal] public-r2 ${resp.status}, falling back to cloud`);
9262
+ continue;
9263
+ }
9264
+ break;
9265
+ }
9266
+ payload = await resp.json();
9267
+ lastErrStatus = 0;
9268
+ break;
9269
+ } catch (err) {
9270
+ if (attempt.label === "public-r2") {
9271
+ ctx.logger?.warn?.(`[MarketplaceInstallLocal] public-r2 fetch error: ${err?.message ?? err}, falling back to cloud`);
9272
+ continue;
9273
+ }
9127
9274
  return c.json({
9128
9275
  success: false,
9129
- error: { code: "cloud_fetch_failed", message: `Cloud returned ${resp.status}: ${text.slice(0, 200)}` }
9130
- }, resp.status === 404 ? 404 : 502);
9276
+ error: { code: "cloud_fetch_failed", message: err?.message ?? String(err) }
9277
+ }, 502);
9131
9278
  }
9132
- payload = await resp.json();
9133
- } catch (err) {
9279
+ }
9280
+ if (!payload) {
9134
9281
  return c.json({
9135
9282
  success: false,
9136
- error: { code: "cloud_fetch_failed", message: err?.message ?? String(err) }
9137
- }, 502);
9283
+ error: { code: "cloud_fetch_failed", message: `Cloud returned ${lastErrStatus}: ${lastErrText}` }
9284
+ }, lastErrStatus === 404 ? 404 : 502);
9138
9285
  }
9139
9286
  const data = payload?.data ?? payload;
9140
9287
  const manifest = data?.manifest;