@oomol-lab/open-flow 0.1.0-beta.89 → 0.1.0-beta.90

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.
Files changed (33) hide show
  1. package/README.md +29 -0
  2. package/dist/browser/{FlowCanvas-SqPUBqze.js → FlowCanvas-C4iuvVKB.js} +2 -2
  3. package/dist/browser/{exportPng-CLZKdNzR.js → exportPng-DMsB9ku_.js} +4 -4
  4. package/dist/browser/{flowChanges-CnOOPJJR.js → flowChanges-SKYJBr6X.js} +12 -11
  5. package/dist/browser/{flowWorkspace-d9Ef-aRK.js → flowWorkspace-4UHRaHcz.js} +10 -7
  6. package/dist/browser/{markdownContent-D0BBrFcF.js → markdownContent-B75ojl0E.js} +1 -1
  7. package/dist/browser/preview.css +14 -5
  8. package/dist/browser/preview.d.ts +4 -1
  9. package/dist/browser/preview.js +397 -95
  10. package/dist/browser/workbench-contract.d.ts +3 -0
  11. package/dist/browser/workbench.css +14 -5
  12. package/dist/browser/workbench.js +12 -9
  13. package/dist/browser/{wrapLayout-BB_qwy3k.js → wrapLayout-DU5HG1qH.js} +371 -71
  14. package/dist/command/index.js +13 -13
  15. package/dist/common/cache.d.ts +7 -0
  16. package/dist/common/catalogCache.d.ts +3 -7
  17. package/dist/common/control-api-conformance.js +10 -9
  18. package/dist/common/control-api.d.ts +3 -1
  19. package/dist/common/control-api.js +10 -9
  20. package/dist/common/mcp.js +10 -9
  21. package/dist/common/provider-icons.d.ts +35 -0
  22. package/dist/common/provider-icons.js +134 -0
  23. package/dist/render/browser/runtime.css +14 -5
  24. package/dist/render/browser/runtime.js +667 -365
  25. package/dist/render/common/png.d.ts +7 -0
  26. package/dist/render/node/index.d.ts +10 -2
  27. package/dist/render/node/index.js +1284 -37
  28. package/dist/render/node/licenses.md +24 -0
  29. package/package.json +5 -1
  30. package/src/base/common/cache.ts +8 -0
  31. package/src/connector/common/providerIconCache.ts +191 -0
  32. package/src/control/common/api.ts +10 -3
  33. package/src/control/common/catalogCache.ts +19 -11
@@ -0,0 +1,7 @@
1
+ import type { JsonValue } from '../index.js';
2
+ /** Storage is host-owned. Keys and JSON records are opaque to the delegate. */
3
+ export interface CacheDelegate {
4
+ get(key: string): Promise<unknown>;
5
+ set(key: string, value: JsonValue): Promise<void>;
6
+ delete(key: string): Promise<void>;
7
+ }
@@ -1,10 +1,6 @@
1
+ import type { CacheDelegate } from './cache.js';
1
2
  import type { JsonValue } from './flow-change.js';
2
- /** Storage is host-owned. Keys and records are opaque to the delegate. */
3
- export interface ControlCacheDelegate {
4
- get(key: string): Promise<unknown>;
5
- set(key: string, value: JsonValue): Promise<void>;
6
- delete(key: string): Promise<void>;
7
- }
3
+ export type ControlCacheDelegate = CacheDelegate;
8
4
  export interface ControlCacheRead {
9
5
  readonly path: string;
10
6
  readonly source: 'cache' | 'network' | 'revalidated';
@@ -38,7 +34,7 @@ export declare class ControlCatalogCache {
38
34
  #private;
39
35
  private readonly options;
40
36
  constructor(options: ControlCacheOptions);
41
- read<Value>(query: CatalogQuery<Value>, load: CatalogLoad, maxAgeMs: number, signal?: AbortSignal, fresh?: boolean): Promise<{
37
+ read<Value>(query: CatalogQuery<Value>, load: CatalogLoad, maxAgeMs: number, signal?: AbortSignal, fresh?: boolean, cacheOnly?: boolean): Promise<{
42
38
  data: Value;
43
39
  etag: string | null;
44
40
  }>;
@@ -11502,7 +11502,7 @@ var ControlCatalogCache = class {
11502
11502
  if (!options.scope.trim()) throw new TypeError("Control cache scope must be nonempty.");
11503
11503
  if (options.maxAgeMs != null && (!Number.isFinite(options.maxAgeMs) || options.maxAgeMs < 0)) throw new TypeError("Control cache maxAgeMs must be finite and nonnegative.");
11504
11504
  }
11505
- async read(query, load, maxAgeMs, signal, fresh = false) {
11505
+ async read(query, load, maxAgeMs, signal, fresh = false, cacheOnly = false) {
11506
11506
  signal?.throwIfAborted();
11507
11507
  const key = JSON.stringify([
11508
11508
  "open-flow:catalog",
@@ -11510,7 +11510,7 @@ var ControlCatalogCache = class {
11510
11510
  this.options.scope,
11511
11511
  query.path
11512
11512
  ]);
11513
- const result = await wait(this.#request(key, query, load, this.options.maxAgeMs ?? maxAgeMs, fresh || this.options.mode == "refresh"), signal);
11513
+ const result = await wait(cacheOnly ? this.#load(key, query, load, this.options.maxAgeMs ?? maxAgeMs, false, true) : this.#request(key, query, load, this.options.maxAgeMs ?? maxAgeMs, fresh || this.options.mode == "refresh"), signal);
11514
11514
  const data = query.decode(structuredClone(result.body));
11515
11515
  try {
11516
11516
  this.options.onRead?.({
@@ -11538,7 +11538,7 @@ var ControlCatalogCache = class {
11538
11538
  });
11539
11539
  return promise;
11540
11540
  }
11541
- async #load(key, query, load, maxAgeMs, fresh) {
11541
+ async #load(key, query, load, maxAgeMs, fresh, cacheOnly = false) {
11542
11542
  let cached;
11543
11543
  try {
11544
11544
  const stored = await this.options.delegate.get(key);
@@ -11553,13 +11553,13 @@ var ControlCatalogCache = class {
11553
11553
  }
11554
11554
  const now = Date.now();
11555
11555
  const stale = cached == null || cached.validatedAt > now || now - cached.validatedAt >= maxAgeMs;
11556
- if (cached != null && (this.options.mode == "offline" || !fresh && !stale)) return {
11556
+ if (cached != null && (cacheOnly || this.options.mode == "offline" || !fresh && !stale)) return {
11557
11557
  ...cached,
11558
11558
  path: query.path,
11559
11559
  source: "cache",
11560
11560
  stale
11561
11561
  };
11562
- if (this.options.mode == "offline") throw new ApiError(503, "catalog.cache-miss", `No compatible cached response for ${query.path}.`);
11562
+ if (cacheOnly || this.options.mode == "offline") throw new ApiError(503, "catalog.cache-miss", `No compatible cached response for ${query.path}.`);
11563
11563
  const result = await load(cached?.etag ?? null);
11564
11564
  if (!result.modified && cached == null) throw new ApiError(502, "response.invalid", "A catalog returned 304 without a cached response.");
11565
11565
  const entry = {
@@ -12939,9 +12939,10 @@ var ControlClient = class {
12939
12939
  version: 1
12940
12940
  };
12941
12941
  }
12942
- async listConnectorProviders(signal, flowId, locale) {
12942
+ async listConnectorProviders(signal, flowId, locale, options = {}) {
12943
12943
  const query = connectorProvidersQuery(flowId, locale);
12944
- if (this.catalogCache != null) return (await this.catalogResponse(query, 3e5, signal)).data;
12944
+ if (this.catalogCache != null) return (await this.catalogResponse(query, 3e5, signal, false, options.cacheOnly)).data;
12945
+ if (options.cacheOnly) throw new ApiError(503, "catalog.cache-miss", "No Provider catalog cache is configured.");
12945
12946
  return this.connectorRequest(query.path, "providers", signal, query.decode);
12946
12947
  }
12947
12948
  async listLeinaBots(flowId, signal) {
@@ -13270,11 +13271,11 @@ var ControlClient = class {
13270
13271
  method: "POST"
13271
13272
  }));
13272
13273
  }
13273
- async catalogResponse(query, maxAgeMs, signal, fresh = false) {
13274
+ async catalogResponse(query, maxAgeMs, signal, fresh = false, cacheOnly = false) {
13274
13275
  if (this.catalogCache != null) return this.catalogCache.read(query, (etag) => this.readCatalog({
13275
13276
  path: query.path,
13276
13277
  decode: jsonValue
13277
- }, etag), maxAgeMs, signal, fresh);
13278
+ }, etag), maxAgeMs, signal, fresh, cacheOnly);
13278
13279
  const response = await this.readCatalog(query, null, signal);
13279
13280
  if (!response.modified) return invalidResponse();
13280
13281
  return {
@@ -933,7 +933,9 @@ export declare class ControlClient {
933
933
  }[];
934
934
  version: 1;
935
935
  }>;
936
- listConnectorProviders(signal?: AbortSignal, flowId?: string, locale?: string): Promise<readonly ConnectorProvider[]>;
936
+ listConnectorProviders(signal?: AbortSignal, flowId?: string, locale?: string, options?: {
937
+ readonly cacheOnly?: boolean;
938
+ }): Promise<readonly ConnectorProvider[]>;
937
939
  listLeinaBots(flowId: string, signal?: AbortSignal): Promise<readonly LeinaBot[]>;
938
940
  removeConnectionUsage(flowId: string, connectionId: string, expectedRevisionId: string, expectedAccessRevision: number, idempotencyKey: string): Promise<DraftChange>;
939
941
  getConnectorAccess(flowId: string, signal?: AbortSignal): Promise<ConnectorAccess>;
@@ -12188,7 +12188,7 @@ var ControlCatalogCache = class {
12188
12188
  if (!options.scope.trim()) throw new TypeError("Control cache scope must be nonempty.");
12189
12189
  if (options.maxAgeMs != null && (!Number.isFinite(options.maxAgeMs) || options.maxAgeMs < 0)) throw new TypeError("Control cache maxAgeMs must be finite and nonnegative.");
12190
12190
  }
12191
- async read(query, load, maxAgeMs, signal, fresh = false) {
12191
+ async read(query, load, maxAgeMs, signal, fresh = false, cacheOnly = false) {
12192
12192
  signal?.throwIfAborted();
12193
12193
  const key = JSON.stringify([
12194
12194
  "open-flow:catalog",
@@ -12196,7 +12196,7 @@ var ControlCatalogCache = class {
12196
12196
  this.options.scope,
12197
12197
  query.path
12198
12198
  ]);
12199
- const result = await wait(this.#request(key, query, load, this.options.maxAgeMs ?? maxAgeMs, fresh || this.options.mode == "refresh"), signal);
12199
+ const result = await wait(cacheOnly ? this.#load(key, query, load, this.options.maxAgeMs ?? maxAgeMs, false, true) : this.#request(key, query, load, this.options.maxAgeMs ?? maxAgeMs, fresh || this.options.mode == "refresh"), signal);
12200
12200
  const data = query.decode(structuredClone(result.body));
12201
12201
  try {
12202
12202
  this.options.onRead?.({
@@ -12224,7 +12224,7 @@ var ControlCatalogCache = class {
12224
12224
  });
12225
12225
  return promise;
12226
12226
  }
12227
- async #load(key, query, load, maxAgeMs, fresh) {
12227
+ async #load(key, query, load, maxAgeMs, fresh, cacheOnly = false) {
12228
12228
  let cached;
12229
12229
  try {
12230
12230
  const stored = await this.options.delegate.get(key);
@@ -12239,13 +12239,13 @@ var ControlCatalogCache = class {
12239
12239
  }
12240
12240
  const now = Date.now();
12241
12241
  const stale = cached == null || cached.validatedAt > now || now - cached.validatedAt >= maxAgeMs;
12242
- if (cached != null && (this.options.mode == "offline" || !fresh && !stale)) return {
12242
+ if (cached != null && (cacheOnly || this.options.mode == "offline" || !fresh && !stale)) return {
12243
12243
  ...cached,
12244
12244
  path: query.path,
12245
12245
  source: "cache",
12246
12246
  stale
12247
12247
  };
12248
- if (this.options.mode == "offline") throw new ApiError(503, "catalog.cache-miss", `No compatible cached response for ${query.path}.`);
12248
+ if (cacheOnly || this.options.mode == "offline") throw new ApiError(503, "catalog.cache-miss", `No compatible cached response for ${query.path}.`);
12249
12249
  const result = await load(cached?.etag ?? null);
12250
12250
  if (!result.modified && cached == null) throw new ApiError(502, "response.invalid", "A catalog returned 304 without a cached response.");
12251
12251
  const entry = {
@@ -13628,9 +13628,10 @@ var ControlClient = class {
13628
13628
  version: 1
13629
13629
  };
13630
13630
  }
13631
- async listConnectorProviders(signal, flowId, locale) {
13631
+ async listConnectorProviders(signal, flowId, locale, options = {}) {
13632
13632
  const query = connectorProvidersQuery(flowId, locale);
13633
- if (this.catalogCache != null) return (await this.catalogResponse(query, 3e5, signal)).data;
13633
+ if (this.catalogCache != null) return (await this.catalogResponse(query, 3e5, signal, false, options.cacheOnly)).data;
13634
+ if (options.cacheOnly) throw new ApiError(503, "catalog.cache-miss", "No Provider catalog cache is configured.");
13634
13635
  return this.connectorRequest(query.path, "providers", signal, query.decode);
13635
13636
  }
13636
13637
  async listLeinaBots(flowId, signal) {
@@ -13959,11 +13960,11 @@ var ControlClient = class {
13959
13960
  method: "POST"
13960
13961
  }));
13961
13962
  }
13962
- async catalogResponse(query, maxAgeMs, signal, fresh = false) {
13963
+ async catalogResponse(query, maxAgeMs, signal, fresh = false, cacheOnly = false) {
13963
13964
  if (this.catalogCache != null) return this.catalogCache.read(query, (etag) => this.readCatalog({
13964
13965
  path: query.path,
13965
13966
  decode: jsonValue
13966
- }, etag), maxAgeMs, signal, fresh);
13967
+ }, etag), maxAgeMs, signal, fresh, cacheOnly);
13967
13968
  const response = await this.readCatalog(query, null, signal);
13968
13969
  if (!response.modified) return invalidResponse();
13969
13970
  return {
@@ -11703,7 +11703,7 @@ var ControlCatalogCache = class {
11703
11703
  if (!options.scope.trim()) throw new TypeError("Control cache scope must be nonempty.");
11704
11704
  if (options.maxAgeMs != null && (!Number.isFinite(options.maxAgeMs) || options.maxAgeMs < 0)) throw new TypeError("Control cache maxAgeMs must be finite and nonnegative.");
11705
11705
  }
11706
- async read(query, load, maxAgeMs, signal, fresh = false) {
11706
+ async read(query, load, maxAgeMs, signal, fresh = false, cacheOnly = false) {
11707
11707
  signal?.throwIfAborted();
11708
11708
  const key = JSON.stringify([
11709
11709
  "open-flow:catalog",
@@ -11711,7 +11711,7 @@ var ControlCatalogCache = class {
11711
11711
  this.options.scope,
11712
11712
  query.path
11713
11713
  ]);
11714
- const result = await wait(this.#request(key, query, load, this.options.maxAgeMs ?? maxAgeMs, fresh || this.options.mode == "refresh"), signal);
11714
+ const result = await wait(cacheOnly ? this.#load(key, query, load, this.options.maxAgeMs ?? maxAgeMs, false, true) : this.#request(key, query, load, this.options.maxAgeMs ?? maxAgeMs, fresh || this.options.mode == "refresh"), signal);
11715
11715
  const data = query.decode(structuredClone(result.body));
11716
11716
  try {
11717
11717
  this.options.onRead?.({
@@ -11739,7 +11739,7 @@ var ControlCatalogCache = class {
11739
11739
  });
11740
11740
  return promise;
11741
11741
  }
11742
- async #load(key, query, load, maxAgeMs, fresh) {
11742
+ async #load(key, query, load, maxAgeMs, fresh, cacheOnly = false) {
11743
11743
  let cached;
11744
11744
  try {
11745
11745
  const stored = await this.options.delegate.get(key);
@@ -11754,13 +11754,13 @@ var ControlCatalogCache = class {
11754
11754
  }
11755
11755
  const now = Date.now();
11756
11756
  const stale = cached == null || cached.validatedAt > now || now - cached.validatedAt >= maxAgeMs;
11757
- if (cached != null && (this.options.mode == "offline" || !fresh && !stale)) return {
11757
+ if (cached != null && (cacheOnly || this.options.mode == "offline" || !fresh && !stale)) return {
11758
11758
  ...cached,
11759
11759
  path: query.path,
11760
11760
  source: "cache",
11761
11761
  stale
11762
11762
  };
11763
- if (this.options.mode == "offline") throw new ApiError(503, "catalog.cache-miss", `No compatible cached response for ${query.path}.`);
11763
+ if (cacheOnly || this.options.mode == "offline") throw new ApiError(503, "catalog.cache-miss", `No compatible cached response for ${query.path}.`);
11764
11764
  const result = await load(cached?.etag ?? null);
11765
11765
  if (!result.modified && cached == null) throw new ApiError(502, "response.invalid", "A catalog returned 304 without a cached response.");
11766
11766
  const entry = {
@@ -13129,9 +13129,10 @@ var ControlClient = class {
13129
13129
  version: 1
13130
13130
  };
13131
13131
  }
13132
- async listConnectorProviders(signal, flowId, locale) {
13132
+ async listConnectorProviders(signal, flowId, locale, options = {}) {
13133
13133
  const query = connectorProvidersQuery(flowId, locale);
13134
- if (this.catalogCache != null) return (await this.catalogResponse(query, 3e5, signal)).data;
13134
+ if (this.catalogCache != null) return (await this.catalogResponse(query, 3e5, signal, false, options.cacheOnly)).data;
13135
+ if (options.cacheOnly) throw new ApiError(503, "catalog.cache-miss", "No Provider catalog cache is configured.");
13135
13136
  return this.connectorRequest(query.path, "providers", signal, query.decode);
13136
13137
  }
13137
13138
  async listLeinaBots(flowId, signal) {
@@ -13460,11 +13461,11 @@ var ControlClient = class {
13460
13461
  method: "POST"
13461
13462
  }));
13462
13463
  }
13463
- async catalogResponse(query, maxAgeMs, signal, fresh = false) {
13464
+ async catalogResponse(query, maxAgeMs, signal, fresh = false, cacheOnly = false) {
13464
13465
  if (this.catalogCache != null) return this.catalogCache.read(query, (etag) => this.readCatalog({
13465
13466
  path: query.path,
13466
13467
  decode: jsonValue
13467
- }, etag), maxAgeMs, signal, fresh);
13468
+ }, etag), maxAgeMs, signal, fresh, cacheOnly);
13468
13469
  const response = await this.readCatalog(query, null, signal);
13469
13470
  if (!response.modified) return invalidResponse();
13470
13471
  return {
@@ -0,0 +1,35 @@
1
+ import type { CacheDelegate } from './cache.js';
2
+ export type { CacheDelegate } from './cache.js';
3
+ export type ProviderIconRequest = {
4
+ readonly kind: 'sprite';
5
+ readonly theme: 'light' | 'dark';
6
+ readonly url: string;
7
+ readonly version: string;
8
+ } | {
9
+ readonly kind: 'image';
10
+ readonly providerId: string;
11
+ readonly url: string;
12
+ };
13
+ export interface ProviderIconCacheOptions {
14
+ /** Non-secret identity of the artwork source or deployment. */
15
+ readonly scope: string;
16
+ /** Defaults to instance-local memory. Records contain base64 image bytes. */
17
+ readonly delegate?: CacheDelegate;
18
+ readonly mode?: 'default' | 'refresh' | 'offline';
19
+ readonly fetch?: typeof fetch;
20
+ }
21
+ export interface ProviderIconReadOptions {
22
+ /** Restricts this read even if another caller is loading the same resource online. */
23
+ readonly allowNetwork?: boolean;
24
+ }
25
+ /** The browser renderer can consume the same interface over a Node bridge. */
26
+ export interface ProviderIconLoader {
27
+ get(request: ProviderIconRequest, options?: ProviderIconReadOptions): Promise<string>;
28
+ }
29
+ /** Complete image responses in bounded, identity-checked slots; no catalog entities live here. */
30
+ export declare class ProviderIconCache implements ProviderIconLoader {
31
+ #private;
32
+ private readonly options;
33
+ constructor(options: ProviderIconCacheOptions);
34
+ get(request: ProviderIconRequest, options?: ProviderIconReadOptions): Promise<string>;
35
+ }
@@ -0,0 +1,134 @@
1
+ //#region src/connector/common/providerIconCache.ts
2
+ var defaultMaxAgeMs = 864e5;
3
+ var dataUrl = (entry) => `data:${entry.contentType};base64,${entry.body}`;
4
+ function decodeRecord(value) {
5
+ if (value == null || typeof value !== "object") return;
6
+ const v = value;
7
+ if (v.formatVersion !== 1 || typeof v.sourceUrl !== "string" || !(v.spriteVersion === null || typeof v.spriteVersion === "string") || typeof v.contentType !== "string" || !/^image\/[\w.+-]+$/i.test(v.contentType) || typeof v.body !== "string" || !v.body || v.body.length % 4 !== 0 || !/^[A-Za-z0-9+/]*={0,2}$/.test(v.body) || !(v.etag === null || typeof v.etag === "string") || !Number.isFinite(v.validatedAt) || v.validatedAt < 0 || !(v.maxAgeMs === null || Number.isFinite(v.maxAgeMs) && v.maxAgeMs >= 0)) return;
8
+ return v;
9
+ }
10
+ function encodeBytes(bytes) {
11
+ let text = "";
12
+ for (let index = 0; index < bytes.length; index += 8192) text += String.fromCharCode(...bytes.subarray(index, index + 8192));
13
+ return btoa(text);
14
+ }
15
+ function freshness(response, immutable, previous) {
16
+ const control = response.headers.get("cache-control") ?? "";
17
+ if (/\bno-cache\b/i.test(control)) return 0;
18
+ if (immutable) return null;
19
+ const maxAge = /(?:^|,)\s*max-age\s*=\s*"?(\d+)/i.exec(control);
20
+ if (maxAge) return Math.max(0, Number(maxAge[1]) * 1e3 - (Number(response.headers.get("age")) || 0) * 1e3);
21
+ const expires = Date.parse(response.headers.get("expires") ?? "");
22
+ if (Number.isFinite(expires)) return Math.max(0, expires - Date.parse(response.headers.get("date") ?? (/* @__PURE__ */ new Date()).toUTCString()));
23
+ return previous?.maxAgeMs ?? defaultMaxAgeMs;
24
+ }
25
+ /** Complete image responses in bounded, identity-checked slots; no catalog entities live here. */
26
+ var ProviderIconCache = class {
27
+ #memory = /* @__PURE__ */ new Map();
28
+ #pending = /* @__PURE__ */ new Map();
29
+ #targets = /* @__PURE__ */ new Map();
30
+ #writes = /* @__PURE__ */ new Map();
31
+ constructor(options) {
32
+ this.options = options;
33
+ if (!options.scope.trim()) throw new TypeError("Provider icon cache scope must be nonempty.");
34
+ }
35
+ get(request, options = {}) {
36
+ if (request.url.startsWith("data:image/")) return Promise.resolve(request.url);
37
+ let protocol;
38
+ try {
39
+ protocol = new URL(request.url).protocol;
40
+ } catch (error) {
41
+ return Promise.reject(error);
42
+ }
43
+ if (!["http:", "https:"].includes(protocol)) return Promise.reject(/* @__PURE__ */ new TypeError("Unsupported Provider icon URL."));
44
+ const slot = JSON.stringify([
45
+ "open-flow:provider-icons",
46
+ 1,
47
+ this.options.scope,
48
+ request.kind,
49
+ request.kind === "sprite" ? request.theme : request.providerId
50
+ ]);
51
+ const version = request.kind === "sprite" ? request.version : null;
52
+ const identity = JSON.stringify([request.url, version]);
53
+ const offline = options.allowNetwork === false || this.options.mode === "offline";
54
+ const key = JSON.stringify([
55
+ slot,
56
+ identity,
57
+ offline
58
+ ]);
59
+ const pending = this.#pending.get(key);
60
+ if (pending) return pending;
61
+ if (!offline) this.#targets.set(slot, identity);
62
+ const promise = this.#load(slot, identity, request.url, version, offline).finally(() => this.#pending.delete(key));
63
+ this.#pending.set(key, promise);
64
+ return promise;
65
+ }
66
+ async #read(slot, url, version) {
67
+ const memory = this.#memory.get(slot);
68
+ if (this.#memory.has(slot) && (!memory || memory.sourceUrl === url && memory.spriteVersion === version)) return memory;
69
+ if (!this.options.delegate) return this.#memory.get(slot);
70
+ try {
71
+ const stored = await this.options.delegate.get(slot);
72
+ if (stored === void 0) return this.#memory.get(slot);
73
+ const entry = decodeRecord(stored);
74
+ if (entry) {
75
+ this.#memory.set(slot, entry);
76
+ return entry;
77
+ }
78
+ this.#memory.delete(slot);
79
+ await this.options.delegate.delete(slot);
80
+ } catch {
81
+ return this.#memory.get(slot);
82
+ }
83
+ }
84
+ async #store(slot, identity, entry) {
85
+ const operation = (this.#writes.get(slot) ?? Promise.resolve()).then(async () => {
86
+ if (this.#targets.get(slot) !== identity) return;
87
+ this.#memory.set(slot, entry);
88
+ if (!this.options.delegate) return;
89
+ try {
90
+ if (entry) await this.options.delegate.set(slot, { ...entry });
91
+ else await this.options.delegate.delete(slot);
92
+ } catch {}
93
+ });
94
+ this.#writes.set(slot, operation);
95
+ try {
96
+ await operation;
97
+ } finally {
98
+ if (this.#writes.get(slot) === operation) this.#writes.delete(slot);
99
+ }
100
+ }
101
+ async #load(slot, identity, url, version, offline) {
102
+ const stored = await this.#read(slot, url, version);
103
+ const cached = stored?.sourceUrl === url && stored.spriteVersion === version ? stored : void 0;
104
+ const age = cached == null ? Infinity : Date.now() - cached.validatedAt;
105
+ if (cached && (offline || this.options.mode !== "refresh" && age >= 0 && (cached.maxAgeMs === null || age < cached.maxAgeMs))) return dataUrl(cached);
106
+ if (offline) throw new Error(`Provider icon is not cached: ${url}`);
107
+ const response = await (this.options.fetch ?? globalThis.fetch)(url, {
108
+ credentials: "omit",
109
+ referrerPolicy: "no-referrer",
110
+ signal: AbortSignal.timeout(15e3),
111
+ headers: cached?.etag ? { "if-none-match": cached.etag } : {}
112
+ });
113
+ if (response.status !== 304 && !response.ok) throw new Error(`Could not load Provider icon: ${response.status}`);
114
+ if (response.status === 304 && !cached) throw new Error("Provider icon returned 304 without a cached image.");
115
+ const contentType = response.status === 304 ? cached.contentType : response.headers.get("content-type")?.split(";")[0]?.trim().toLowerCase();
116
+ if (!contentType || !/^image\/[\w.+-]+$/i.test(contentType)) throw new Error("Provider icon response is not an image.");
117
+ const body = response.status === 304 ? cached.body : encodeBytes(new Uint8Array(await response.arrayBuffer()));
118
+ if (!body) throw new Error("Provider icon response is empty.");
119
+ const entry = {
120
+ formatVersion: 1,
121
+ sourceUrl: url,
122
+ spriteVersion: version,
123
+ contentType,
124
+ body,
125
+ etag: response.headers.get("etag") ?? (response.status === 304 ? cached.etag : null),
126
+ validatedAt: Date.now(),
127
+ maxAgeMs: freshness(response, version !== null, response.status === 304 ? cached : void 0)
128
+ };
129
+ await this.#store(slot, identity, /\bno-store\b/i.test(response.headers.get("cache-control") ?? "") ? void 0 : entry);
130
+ return dataUrl(entry);
131
+ }
132
+ };
133
+ //#endregion
134
+ export { ProviderIconCache };
@@ -8844,7 +8844,7 @@ svg.react-flow__connectionline {
8844
8844
  filter: blur(var(--tw-exit-blur, 0));
8845
8845
  }
8846
8846
  }
8847
- .ContentIcon_img_4e5d2 {
8847
+ .ContentIcon_img_e2c99 {
8848
8848
  width: 1em;
8849
8849
  height: 1em;
8850
8850
  vertical-align: middle;
@@ -8852,7 +8852,16 @@ svg.react-flow__connectionline {
8852
8852
  object-fit: contain;
8853
8853
  }
8854
8854
 
8855
- .ContentIcon_initials_4e5d2 {
8855
+ .ContentIcon_pending_e2c99 {
8856
+ display: inline-flex;
8857
+ width: 1em;
8858
+ height: 1em;
8859
+ align-items: center;
8860
+ justify-content: center;
8861
+ vertical-align: middle;
8862
+ }
8863
+
8864
+ .ContentIcon_initials_e2c99 {
8856
8865
  display: inline-flex;
8857
8866
  width: 1em;
8858
8867
  height: 1em;
@@ -8865,13 +8874,13 @@ svg.react-flow__connectionline {
8865
8874
  vertical-align: middle;
8866
8875
  pointer-events: none;
8867
8876
  }
8868
- .ContentIcon_initials_4e5d2 > span {
8877
+ .ContentIcon_initials_e2c99 > span {
8869
8878
  font-size: 0.42em;
8870
8879
  font-weight: 400;
8871
8880
  line-height: 1;
8872
8881
  }
8873
8882
 
8874
- .ContentIcon_sprite_4e5d2 {
8883
+ .ContentIcon_sprite_e2c99 {
8875
8884
  display: inline-block;
8876
8885
  position: relative;
8877
8886
  overflow: hidden;
@@ -8881,7 +8890,7 @@ svg.react-flow__connectionline {
8881
8890
  vertical-align: middle;
8882
8891
  pointer-events: none;
8883
8892
  }
8884
- .ContentIcon_sprite_4e5d2 > span {
8893
+ .ContentIcon_sprite_e2c99 > span {
8885
8894
  position: absolute;
8886
8895
  background-size: 100% 100%;
8887
8896
  background-repeat: no-repeat;