@objectstack/runtime 7.0.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
@@ -1686,7 +1686,8 @@ var init_app_plugin = __esm({
1686
1686
  "translations",
1687
1687
  "sharingRules",
1688
1688
  "ragPipelines",
1689
- "data"
1689
+ "data",
1690
+ "emailTemplates"
1690
1691
  ];
1691
1692
  const hasAppPayload = APP_CATEGORY_KEYS.some((k) => {
1692
1693
  const v = (bundle && bundle[k]) ?? (sys && sys[k]);
@@ -8393,6 +8394,36 @@ function resolveCloudUrl(explicit) {
8393
8394
  return picked.replace(/\/+$/, "");
8394
8395
  }
8395
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
+
8396
8427
  // src/cloud/marketplace-proxy-plugin.ts
8397
8428
  var MARKETPLACE_PREFIX = "/api/v1/marketplace";
8398
8429
  var DEFAULT_LRU_MAX = 200;
@@ -8432,7 +8463,7 @@ var LruTtlCache = class {
8432
8463
  var MarketplaceProxyPlugin = class _MarketplaceProxyPlugin {
8433
8464
  constructor(config = {}) {
8434
8465
  this.name = "com.objectstack.runtime.marketplace-proxy";
8435
- this.version = "1.0.0";
8466
+ this.version = "1.1.0";
8436
8467
  this.init = async (_ctx) => {
8437
8468
  };
8438
8469
  this.start = async (ctx) => {
@@ -8450,7 +8481,11 @@ var MarketplaceProxyPlugin = class _MarketplaceProxyPlugin {
8450
8481
  }
8451
8482
  const rawApp = httpServer.getRawApp();
8452
8483
  const cloudUrl = this.cloudUrl;
8484
+ const publicBaseUrl = this.publicBaseUrl;
8453
8485
  const cache = this.cache;
8486
+ if (publicBaseUrl) {
8487
+ ctx.logger?.info?.(`[MarketplaceProxyPlugin] public R2 fast-path enabled \u2192 ${publicBaseUrl}`);
8488
+ }
8454
8489
  const handler = async (c, next) => {
8455
8490
  if (!cloudUrl) {
8456
8491
  return c.json({
@@ -8466,8 +8501,18 @@ var MarketplaceProxyPlugin = class _MarketplaceProxyPlugin {
8466
8501
  if (incomingUrl.pathname.startsWith(`${MARKETPLACE_PREFIX}/install-local`)) {
8467
8502
  return next();
8468
8503
  }
8469
- const target = `${cloudUrl}${incomingUrl.pathname}${incomingUrl.search}`;
8470
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}`;
8471
8516
  if (method !== "GET" && method !== "HEAD") {
8472
8517
  return c.json({
8473
8518
  success: false,
@@ -8548,12 +8593,82 @@ var MarketplaceProxyPlugin = class _MarketplaceProxyPlugin {
8548
8593
  });
8549
8594
  };
8550
8595
  this.cloudUrl = resolveCloudUrl(config.controlPlaneUrl);
8596
+ this.publicBaseUrl = resolveMarketplacePublicBaseUrl(config.publicMarketplaceBaseUrl);
8551
8597
  const envFlag = (process.env.OS_MARKETPLACE_CACHE ?? "").trim().toLowerCase();
8552
8598
  const envDisabled = ["off", "false", "0", "no", "disable", "disabled"].includes(envFlag);
8553
8599
  const disabled = config.cacheDisabled ?? envDisabled;
8554
8600
  this.cache = disabled ? null : new LruTtlCache(Math.max(8, config.cacheMaxEntries ?? DEFAULT_LRU_MAX));
8555
8601
  }
8556
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
+ }
8557
8672
  var PASSTHROUGH_HEADERS = ["content-type", "cache-control", "etag", "last-modified", "vary"];
8558
8673
  function collectHeaders(src) {
8559
8674
  const out = {};
@@ -9037,22 +9152,55 @@ var MarketplaceInstallLocalPlugin = class {
9037
9152
  return c.json({ success: false, error: { code: "bad_request", message: "packageId is required." } }, 400);
9038
9153
  }
9039
9154
  let payload;
9040
- try {
9041
- const url = `${this.cloudUrl}/api/v1/marketplace/packages/${encodeURIComponent(packageId)}/versions/${encodeURIComponent(versionId)}/manifest`;
9042
- const resp = await fetch(url, { headers: { "Accept": "application/json" } });
9043
- if (!resp.ok) {
9044
- 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
+ }
9045
9193
  return c.json({
9046
9194
  success: false,
9047
- error: { code: "cloud_fetch_failed", message: `Cloud returned ${resp.status}: ${text.slice(0, 200)}` }
9048
- }, resp.status === 404 ? 404 : 502);
9195
+ error: { code: "cloud_fetch_failed", message: err?.message ?? String(err) }
9196
+ }, 502);
9049
9197
  }
9050
- payload = await resp.json();
9051
- } catch (err) {
9198
+ }
9199
+ if (!payload) {
9052
9200
  return c.json({
9053
9201
  success: false,
9054
- error: { code: "cloud_fetch_failed", message: err?.message ?? String(err) }
9055
- }, 502);
9202
+ error: { code: "cloud_fetch_failed", message: `Cloud returned ${lastErrStatus}: ${lastErrText}` }
9203
+ }, lastErrStatus === 404 ? 404 : 502);
9056
9204
  }
9057
9205
  const data = payload?.data ?? payload;
9058
9206
  const manifest = data?.manifest;