@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.
- package/README.md +29 -0
- package/dist/browser/{FlowCanvas-SqPUBqze.js → FlowCanvas-C4iuvVKB.js} +2 -2
- package/dist/browser/{exportPng-CLZKdNzR.js → exportPng-DMsB9ku_.js} +4 -4
- package/dist/browser/{flowChanges-CnOOPJJR.js → flowChanges-SKYJBr6X.js} +12 -11
- package/dist/browser/{flowWorkspace-d9Ef-aRK.js → flowWorkspace-4UHRaHcz.js} +10 -7
- package/dist/browser/{markdownContent-D0BBrFcF.js → markdownContent-B75ojl0E.js} +1 -1
- package/dist/browser/preview.css +14 -5
- package/dist/browser/preview.d.ts +4 -1
- package/dist/browser/preview.js +397 -95
- package/dist/browser/workbench-contract.d.ts +3 -0
- package/dist/browser/workbench.css +14 -5
- package/dist/browser/workbench.js +12 -9
- package/dist/browser/{wrapLayout-BB_qwy3k.js → wrapLayout-DU5HG1qH.js} +371 -71
- package/dist/command/index.js +13 -13
- package/dist/common/cache.d.ts +7 -0
- package/dist/common/catalogCache.d.ts +3 -7
- package/dist/common/control-api-conformance.js +10 -9
- package/dist/common/control-api.d.ts +3 -1
- package/dist/common/control-api.js +10 -9
- package/dist/common/mcp.js +10 -9
- package/dist/common/provider-icons.d.ts +35 -0
- package/dist/common/provider-icons.js +134 -0
- package/dist/render/browser/runtime.css +14 -5
- package/dist/render/browser/runtime.js +667 -365
- package/dist/render/common/png.d.ts +7 -0
- package/dist/render/node/index.d.ts +10 -2
- package/dist/render/node/index.js +1284 -37
- package/dist/render/node/licenses.md +24 -0
- package/package.json +5 -1
- package/src/base/common/cache.ts +8 -0
- package/src/connector/common/providerIconCache.ts +191 -0
- package/src/control/common/api.ts +10 -3
- 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
|
-
|
|
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
|
|
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 {
|
package/dist/common/mcp.js
CHANGED
|
@@ -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
|
-
.
|
|
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
|
-
.
|
|
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
|
-
.
|
|
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
|
-
.
|
|
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
|
-
.
|
|
8893
|
+
.ContentIcon_sprite_e2c99 > span {
|
|
8885
8894
|
position: absolute;
|
|
8886
8895
|
background-size: 100% 100%;
|
|
8887
8896
|
background-repeat: no-repeat;
|