@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.d.cts CHANGED
@@ -1940,11 +1940,21 @@ interface MarketplaceProxyPluginConfig {
1940
1940
  * Override the LRU upper bound. Defaults to 200 entries.
1941
1941
  */
1942
1942
  cacheMaxEntries?: number;
1943
+ /**
1944
+ * Public R2 base URL for marketplace snapshots. When set, GETs for
1945
+ * snapshot-backed paths (`/packages`, `/packages/:id`,
1946
+ * `/packages/:id/versions/:vid/manifest`) are fetched directly from
1947
+ * R2 (CF edge) — bypassing the cloud control plane entirely.
1948
+ * Defaults to the value of OS_MARKETPLACE_PUBLIC_BASE_URL. Empty
1949
+ * string disables the public fast-path (legacy cloud-proxy only).
1950
+ */
1951
+ publicMarketplaceBaseUrl?: string;
1943
1952
  }
1944
1953
  declare class MarketplaceProxyPlugin implements Plugin {
1945
1954
  readonly name = "com.objectstack.runtime.marketplace-proxy";
1946
- readonly version = "1.0.0";
1955
+ readonly version = "1.1.0";
1947
1956
  private readonly cloudUrl;
1957
+ private readonly publicBaseUrl;
1948
1958
  private readonly cache;
1949
1959
  constructor(config?: MarketplaceProxyPluginConfig);
1950
1960
  init: (_ctx: PluginContext) => Promise<void>;
package/dist/index.d.ts CHANGED
@@ -1940,11 +1940,21 @@ interface MarketplaceProxyPluginConfig {
1940
1940
  * Override the LRU upper bound. Defaults to 200 entries.
1941
1941
  */
1942
1942
  cacheMaxEntries?: number;
1943
+ /**
1944
+ * Public R2 base URL for marketplace snapshots. When set, GETs for
1945
+ * snapshot-backed paths (`/packages`, `/packages/:id`,
1946
+ * `/packages/:id/versions/:vid/manifest`) are fetched directly from
1947
+ * R2 (CF edge) — bypassing the cloud control plane entirely.
1948
+ * Defaults to the value of OS_MARKETPLACE_PUBLIC_BASE_URL. Empty
1949
+ * string disables the public fast-path (legacy cloud-proxy only).
1950
+ */
1951
+ publicMarketplaceBaseUrl?: string;
1943
1952
  }
1944
1953
  declare class MarketplaceProxyPlugin implements Plugin {
1945
1954
  readonly name = "com.objectstack.runtime.marketplace-proxy";
1946
- readonly version = "1.0.0";
1955
+ readonly version = "1.1.0";
1947
1956
  private readonly cloudUrl;
1957
+ private readonly publicBaseUrl;
1948
1958
  private readonly cache;
1949
1959
  constructor(config?: MarketplaceProxyPluginConfig);
1950
1960
  init: (_ctx: PluginContext) => Promise<void>;
package/dist/index.js CHANGED
@@ -8394,6 +8394,36 @@ function resolveCloudUrl(explicit) {
8394
8394
  return picked.replace(/\/+$/, "");
8395
8395
  }
8396
8396
 
8397
+ // src/cloud/marketplace-public-url.ts
8398
+ function resolveMarketplacePublicBaseUrl(explicit) {
8399
+ const raw = (explicit ?? process.env.OS_MARKETPLACE_PUBLIC_BASE_URL ?? "").trim();
8400
+ const lower = raw.toLowerCase();
8401
+ if (!raw || lower === "off" || lower === "none" || lower === "disabled" || lower === "false") {
8402
+ return "";
8403
+ }
8404
+ return raw.replace(/\/+$/, "");
8405
+ }
8406
+ function publicMarketplaceKeyForApiPath(pathname) {
8407
+ const prefix = "/api/v1/marketplace/packages";
8408
+ if (pathname === prefix) return "packages.json";
8409
+ if (!pathname.startsWith(`${prefix}/`)) return null;
8410
+ const tail = pathname.slice(prefix.length + 1);
8411
+ if (!tail) return null;
8412
+ const parts = tail.split("/");
8413
+ if (parts.length === 1) {
8414
+ const id = decodeURIComponent(parts[0] ?? "");
8415
+ if (!id) return null;
8416
+ return `packages/${encodeURIComponent(id)}.json`;
8417
+ }
8418
+ if (parts.length === 4 && parts[1] === "versions" && parts[3] === "manifest") {
8419
+ const id = decodeURIComponent(parts[0] ?? "");
8420
+ const versionId = decodeURIComponent(parts[2] ?? "");
8421
+ if (!id || !versionId) return null;
8422
+ return `packages/${encodeURIComponent(id)}/versions/${encodeURIComponent(versionId)}/manifest.json`;
8423
+ }
8424
+ return null;
8425
+ }
8426
+
8397
8427
  // src/cloud/marketplace-proxy-plugin.ts
8398
8428
  var MARKETPLACE_PREFIX = "/api/v1/marketplace";
8399
8429
  var DEFAULT_LRU_MAX = 200;
@@ -8433,7 +8463,7 @@ var LruTtlCache = class {
8433
8463
  var MarketplaceProxyPlugin = class _MarketplaceProxyPlugin {
8434
8464
  constructor(config = {}) {
8435
8465
  this.name = "com.objectstack.runtime.marketplace-proxy";
8436
- this.version = "1.0.0";
8466
+ this.version = "1.1.0";
8437
8467
  this.init = async (_ctx) => {
8438
8468
  };
8439
8469
  this.start = async (ctx) => {
@@ -8451,7 +8481,11 @@ var MarketplaceProxyPlugin = class _MarketplaceProxyPlugin {
8451
8481
  }
8452
8482
  const rawApp = httpServer.getRawApp();
8453
8483
  const cloudUrl = this.cloudUrl;
8484
+ const publicBaseUrl = this.publicBaseUrl;
8454
8485
  const cache = this.cache;
8486
+ if (publicBaseUrl) {
8487
+ ctx.logger?.info?.(`[MarketplaceProxyPlugin] public R2 fast-path enabled \u2192 ${publicBaseUrl}`);
8488
+ }
8455
8489
  const handler = async (c, next) => {
8456
8490
  if (!cloudUrl) {
8457
8491
  return c.json({
@@ -8467,8 +8501,18 @@ var MarketplaceProxyPlugin = class _MarketplaceProxyPlugin {
8467
8501
  if (incomingUrl.pathname.startsWith(`${MARKETPLACE_PREFIX}/install-local`)) {
8468
8502
  return next();
8469
8503
  }
8470
- const target = `${cloudUrl}${incomingUrl.pathname}${incomingUrl.search}`;
8471
8504
  const method = String(c.req.method ?? "GET").toUpperCase();
8505
+ if (publicBaseUrl && (method === "GET" || method === "HEAD")) {
8506
+ const r2Resp = await tryPublicMarketplaceFetch(
8507
+ publicBaseUrl,
8508
+ incomingUrl,
8509
+ method,
8510
+ c.req.header("accept"),
8511
+ ctx.logger
8512
+ );
8513
+ if (r2Resp) return r2Resp;
8514
+ }
8515
+ const target = `${cloudUrl}${incomingUrl.pathname}${incomingUrl.search}`;
8472
8516
  if (method !== "GET" && method !== "HEAD") {
8473
8517
  return c.json({
8474
8518
  success: false,
@@ -8549,12 +8593,82 @@ var MarketplaceProxyPlugin = class _MarketplaceProxyPlugin {
8549
8593
  });
8550
8594
  };
8551
8595
  this.cloudUrl = resolveCloudUrl(config.controlPlaneUrl);
8596
+ this.publicBaseUrl = resolveMarketplacePublicBaseUrl(config.publicMarketplaceBaseUrl);
8552
8597
  const envFlag = (process.env.OS_MARKETPLACE_CACHE ?? "").trim().toLowerCase();
8553
8598
  const envDisabled = ["off", "false", "0", "no", "disable", "disabled"].includes(envFlag);
8554
8599
  const disabled = config.cacheDisabled ?? envDisabled;
8555
8600
  this.cache = disabled ? null : new LruTtlCache(Math.max(8, config.cacheMaxEntries ?? DEFAULT_LRU_MAX));
8556
8601
  }
8557
8602
  };
8603
+ async function tryPublicMarketplaceFetch(publicBaseUrl, incomingUrl, method, acceptHeader, logger) {
8604
+ const key = publicMarketplaceKeyForApiPath(incomingUrl.pathname);
8605
+ if (!key) return null;
8606
+ const target = `${publicBaseUrl}/${key}`;
8607
+ let resp;
8608
+ try {
8609
+ resp = await fetch(target, {
8610
+ method: "GET",
8611
+ headers: {
8612
+ "Accept": acceptHeader || "application/json",
8613
+ "User-Agent": `objectos-marketplace-proxy/public-r2`
8614
+ }
8615
+ });
8616
+ } catch (err) {
8617
+ logger?.warn?.(`[MarketplaceProxyPlugin] public R2 fetch failed (${target}): ${err?.message ?? err}`);
8618
+ return null;
8619
+ }
8620
+ if (resp.status === 404) return null;
8621
+ if (!resp.ok) {
8622
+ logger?.warn?.(`[MarketplaceProxyPlugin] public R2 ${target} returned ${resp.status} \u2014 falling back to cloud`);
8623
+ return null;
8624
+ }
8625
+ const isList = key === "packages.json";
8626
+ const hasFilters = isList && (incomingUrl.searchParams.has("q") || incomingUrl.searchParams.has("category") || incomingUrl.searchParams.has("limit") || incomingUrl.searchParams.has("offset"));
8627
+ if (!hasFilters) {
8628
+ const headers2 = new Headers();
8629
+ const ct = resp.headers.get("content-type") ?? "application/json; charset=utf-8";
8630
+ headers2.set("content-type", ct);
8631
+ const cc = resp.headers.get("cache-control");
8632
+ if (cc) headers2.set("cache-control", cc);
8633
+ const etag = resp.headers.get("etag");
8634
+ if (etag) headers2.set("etag", etag);
8635
+ headers2.set("x-cache", "PUBLIC-R2");
8636
+ const body2 = method === "HEAD" ? null : resp.body;
8637
+ return new Response(body2, { status: 200, headers: headers2 });
8638
+ }
8639
+ let snapshot;
8640
+ try {
8641
+ snapshot = await resp.json();
8642
+ } catch (err) {
8643
+ logger?.warn?.(`[MarketplaceProxyPlugin] public R2 list snapshot parse failed: ${err?.message ?? err}`);
8644
+ return null;
8645
+ }
8646
+ const items = Array.isArray(snapshot?.data?.items) ? snapshot.data.items : [];
8647
+ const q = (incomingUrl.searchParams.get("q") ?? "").trim().toLowerCase();
8648
+ const category = (incomingUrl.searchParams.get("category") ?? "").trim();
8649
+ const limit = Math.min(Math.max(Number(incomingUrl.searchParams.get("limit") ?? 50), 1), 100);
8650
+ const offset = Math.max(Number(incomingUrl.searchParams.get("offset") ?? 0), 0);
8651
+ let filtered = items;
8652
+ if (q) {
8653
+ filtered = filtered.filter((r) => {
8654
+ const dn = String(r?.display_name ?? "").toLowerCase();
8655
+ const mid = String(r?.manifest_id ?? "").toLowerCase();
8656
+ return dn.includes(q) || mid.includes(q);
8657
+ });
8658
+ }
8659
+ if (category) {
8660
+ filtered = filtered.filter((r) => String(r?.category ?? "") === category);
8661
+ }
8662
+ const total = filtered.length;
8663
+ const page = filtered.slice(offset, offset + limit);
8664
+ const body = JSON.stringify({ success: true, data: { items: page, total, limit, offset } });
8665
+ const headers = new Headers({
8666
+ "content-type": "application/json; charset=utf-8",
8667
+ "cache-control": "public, max-age=30",
8668
+ "x-cache": "PUBLIC-R2-FILTERED"
8669
+ });
8670
+ return new Response(method === "HEAD" ? null : body, { status: 200, headers });
8671
+ }
8558
8672
  var PASSTHROUGH_HEADERS = ["content-type", "cache-control", "etag", "last-modified", "vary"];
8559
8673
  function collectHeaders(src) {
8560
8674
  const out = {};
@@ -9038,22 +9152,55 @@ var MarketplaceInstallLocalPlugin = class {
9038
9152
  return c.json({ success: false, error: { code: "bad_request", message: "packageId is required." } }, 400);
9039
9153
  }
9040
9154
  let payload;
9041
- try {
9042
- const url = `${this.cloudUrl}/api/v1/marketplace/packages/${encodeURIComponent(packageId)}/versions/${encodeURIComponent(versionId)}/manifest`;
9043
- const resp = await fetch(url, { headers: { "Accept": "application/json" } });
9044
- if (!resp.ok) {
9045
- const text = await resp.text().catch(() => "");
9155
+ const publicBase = resolveMarketplacePublicBaseUrl();
9156
+ const fetchAttempts = [];
9157
+ if (publicBase) {
9158
+ fetchAttempts.push({
9159
+ label: "public-r2",
9160
+ url: `${publicBase}/packages/${encodeURIComponent(packageId)}/versions/${encodeURIComponent(versionId)}/manifest.json`
9161
+ });
9162
+ }
9163
+ fetchAttempts.push({
9164
+ label: "cloud",
9165
+ url: `${this.cloudUrl}/api/v1/marketplace/packages/${encodeURIComponent(packageId)}/versions/${encodeURIComponent(versionId)}/manifest`
9166
+ });
9167
+ let lastErrStatus = 0;
9168
+ let lastErrText = "";
9169
+ for (const attempt of fetchAttempts) {
9170
+ try {
9171
+ const resp = await fetch(attempt.url, { headers: { "Accept": "application/json" } });
9172
+ if (!resp.ok) {
9173
+ lastErrStatus = resp.status;
9174
+ lastErrText = (await resp.text().catch(() => "")).slice(0, 200);
9175
+ if (attempt.label === "public-r2" && resp.status === 404) {
9176
+ ctx.logger?.info?.(`[MarketplaceInstallLocal] public-r2 miss for ${packageId}@${versionId}, falling back to cloud`);
9177
+ continue;
9178
+ }
9179
+ if (attempt.label === "public-r2" && resp.status >= 500) {
9180
+ ctx.logger?.warn?.(`[MarketplaceInstallLocal] public-r2 ${resp.status}, falling back to cloud`);
9181
+ continue;
9182
+ }
9183
+ break;
9184
+ }
9185
+ payload = await resp.json();
9186
+ lastErrStatus = 0;
9187
+ break;
9188
+ } catch (err) {
9189
+ if (attempt.label === "public-r2") {
9190
+ ctx.logger?.warn?.(`[MarketplaceInstallLocal] public-r2 fetch error: ${err?.message ?? err}, falling back to cloud`);
9191
+ continue;
9192
+ }
9046
9193
  return c.json({
9047
9194
  success: false,
9048
- error: { code: "cloud_fetch_failed", message: `Cloud returned ${resp.status}: ${text.slice(0, 200)}` }
9049
- }, resp.status === 404 ? 404 : 502);
9195
+ error: { code: "cloud_fetch_failed", message: err?.message ?? String(err) }
9196
+ }, 502);
9050
9197
  }
9051
- payload = await resp.json();
9052
- } catch (err) {
9198
+ }
9199
+ if (!payload) {
9053
9200
  return c.json({
9054
9201
  success: false,
9055
- error: { code: "cloud_fetch_failed", message: err?.message ?? String(err) }
9056
- }, 502);
9202
+ error: { code: "cloud_fetch_failed", message: `Cloud returned ${lastErrStatus}: ${lastErrText}` }
9203
+ }, lastErrStatus === 404 ? 404 : 502);
9057
9204
  }
9058
9205
  const data = payload?.data ?? payload;
9059
9206
  const manifest = data?.manifest;