@deepexi/datasense-connector 3.0.1 → 3.1.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/README.md +158 -139
- package/package.json +19 -28
- package/scripts/build.ts +188 -0
- package/src/define.ts +267 -0
- package/src/descriptor.ts +116 -0
- package/src/errors.ts +105 -0
- package/src/index.ts +108 -0
- package/src/internal/location.ts +23 -0
- package/src/runtime/guest/bridge.ts +238 -0
- package/src/runtime/guest/cache.ts +43 -0
- package/src/runtime/guest/check.ts +17 -0
- package/src/runtime/guest/crypto.ts +166 -0
- package/src/runtime/guest/ctx.ts +157 -0
- package/src/runtime/guest/db.ts +59 -0
- package/src/runtime/guest/failure.ts +69 -0
- package/src/runtime/guest/http.ts +194 -0
- package/src/runtime/guest/index.ts +102 -0
- package/{runtime/guest/limits.d.ts → src/runtime/guest/limits.ts} +12 -12
- package/src/runtime/guest/loader.ts +210 -0
- package/src/runtime/guest/log.ts +47 -0
- package/src/runtime/guest/map.ts +45 -0
- package/src/runtime/guest/operation.ts +193 -0
- package/src/runtime/guest/paginate.ts +169 -0
- package/src/runtime/guest/pull.ts +177 -0
- package/src/runtime/guest/push.ts +92 -0
- package/src/runtime/guest/signal.ts +14 -0
- package/src/runtime/guest/suggest.ts +51 -0
- package/src/runtime/guest/time.ts +137 -0
- package/src/runtime/guest-runtime.ts +75 -0
- package/src/runtime/index.ts +53 -0
- package/src/runtime/protocol.ts +557 -0
- package/src/runtime/session.ts +216 -0
- package/src/testing/child.ts +21 -0
- package/src/testing/compliance.ts +343 -0
- package/src/testing/engine.ts +445 -0
- package/src/testing/fixtures.ts +591 -0
- package/src/testing/gateway.ts +482 -0
- package/src/testing/import-guard.ts +221 -0
- package/src/testing/index.ts +43 -0
- package/src/testing/local-db.ts +226 -0
- package/src/testing/mock-external-system.ts +249 -0
- package/src/testing/pinned-http.ts +144 -0
- package/src/testing/recorder.ts +140 -0
- package/src/testing/redaction.ts +94 -0
- package/src/testing/replay.ts +437 -0
- package/src/testing/run-tests.ts +115 -0
- package/src/testing/schema.ts +70 -0
- package/src/testing/source-loader.ts +374 -0
- package/src/testing/types.ts +78 -0
- package/src/types.ts +329 -0
- package/src/validate.ts +267 -0
- package/test/build.test.ts +159 -0
- package/test/define.test-d.ts +182 -0
- package/test/fixtures/acceptance.ts +156 -0
- package/test/fixtures/connectors/http-pull/.ontos/descriptor.json +72 -0
- package/test/fixtures/connectors/http-pull/.ontos/types.ts +61 -0
- package/test/fixtures/connectors/http-pull/package.json +5 -0
- package/test/fixtures/connectors/http-pull/streams/items/index.ts +37 -0
- package/test/fixtures/connectors/http-pull/streams/items/map.ts +12 -0
- package/test/fixtures/connectors/http-pull/streams/items/tests/basic/case.json +15 -0
- package/test/fixtures/connectors/http-pull/streams/items/tests/basic/exchanges.ndjson +2 -0
- package/test/fixtures/connectors/pg-pull/.ontos/descriptor.json +151 -0
- package/test/fixtures/connectors/pg-pull/.ontos/types.ts +62 -0
- package/test/fixtures/connectors/pg-pull/lib/sql.ts +14 -0
- package/test/fixtures/connectors/pg-pull/package.json +5 -0
- package/test/fixtures/connectors/pg-pull/streams/customers/index.ts +30 -0
- package/test/fixtures/connectors/pg-pull/streams/customers/tests/customers-full/case.json +15 -0
- package/test/fixtures/connectors/pg-pull/streams/customers/tests/customers-full/exchanges.ndjson +3 -0
- package/test/fixtures/connectors/pg-pull/streams/orders/index.ts +32 -0
- package/test/fixtures/descriptor.ts +54 -0
- package/test/fixtures/kingdee-example.ts +82 -0
- package/test/fixtures/record-acceptance.ts +32 -0
- package/test/runtime/ctx.test.ts +434 -0
- package/test/runtime/helpers.ts +157 -0
- package/test/runtime/jobs.test.ts +444 -0
- package/test/runtime/operation.test.ts +175 -0
- package/test/runtime/session.test.ts +229 -0
- package/test/testing/acceptance-fixtures.test.ts +69 -0
- package/test/testing/compliance.test.ts +247 -0
- package/test/testing/engine.test.ts +283 -0
- package/test/testing/fixtures.test.ts +221 -0
- package/test/testing/gateway.test.ts +337 -0
- package/test/testing/import-guard.test.ts +45 -0
- package/test/testing/mock-external-system.test.ts +144 -0
- package/test/testing/operation-fixtures.test.ts +160 -0
- package/test/testing/recorder.test.ts +163 -0
- package/test/testing/redaction.test.ts +54 -0
- package/test/testing/replay.test.ts +207 -0
- package/test/testing/run-tests.test.ts +137 -0
- package/test/testing/source-loader.test.ts +220 -0
- package/test/validate-config.test.ts +104 -0
- package/tsconfig.build.json +23 -0
- package/tsconfig.json +19 -0
- package/cjs/index.cjs +0 -1015
- package/cjs/runtime.cjs +0 -752
- package/cjs/testing.cjs +0 -3591
- package/errors.d.ts +0 -17
- package/errors.js +0 -53
- package/guest.cjs +0 -2659
- package/index.d.ts +0 -13
- package/index.js +0 -7
- package/internal/location.d.ts +0 -6
- package/internal/location.js +0 -8
- package/presentation.d.ts +0 -38
- package/presentation.js +0 -268
- package/runtime/guest/bridge.d.ts +0 -53
- package/runtime/guest/bridge.js +0 -187
- package/runtime/guest/cache.d.ts +0 -3
- package/runtime/guest/cache.js +0 -42
- package/runtime/guest/crypto.d.ts +0 -13
- package/runtime/guest/crypto.js +0 -177
- package/runtime/guest/ctx.d.ts +0 -30
- package/runtime/guest/ctx.js +0 -124
- package/runtime/guest/db.d.ts +0 -6
- package/runtime/guest/db.js +0 -59
- package/runtime/guest/discover.d.ts +0 -29
- package/runtime/guest/discover.js +0 -51
- package/runtime/guest/failure.d.ts +0 -24
- package/runtime/guest/failure.js +0 -63
- package/runtime/guest/http.d.ts +0 -4
- package/runtime/guest/http.js +0 -204
- package/runtime/guest/index.d.ts +0 -12
- package/runtime/guest/index.js +0 -89
- package/runtime/guest/limits.js +0 -12
- package/runtime/guest/loader.d.ts +0 -15
- package/runtime/guest/loader.js +0 -42
- package/runtime/guest/log.d.ts +0 -10
- package/runtime/guest/log.js +0 -36
- package/runtime/guest/map.d.ts +0 -4
- package/runtime/guest/map.js +0 -45
- package/runtime/guest/paginate.d.ts +0 -6
- package/runtime/guest/paginate.js +0 -180
- package/runtime/guest/pull.d.ts +0 -12
- package/runtime/guest/pull.js +0 -150
- package/runtime/guest/push.d.ts +0 -18
- package/runtime/guest/push.js +0 -80
- package/runtime/guest/signal.d.ts +0 -3
- package/runtime/guest/signal.js +0 -12
- package/runtime/guest/time.d.ts +0 -5
- package/runtime/guest/time.js +0 -127
- package/runtime/guest-runtime.d.ts +0 -7
- package/runtime/guest-runtime.js +0 -51
- package/runtime/index.d.ts +0 -18
- package/runtime/index.js +0 -3
- package/runtime/protocol.d.ts +0 -276
- package/runtime/protocol.js +0 -409
- package/runtime/session.d.ts +0 -12
- package/runtime/session.js +0 -191
- package/t.d.ts +0 -18
- package/t.js +0 -26
- package/testing/child.cjs +0 -350
- package/testing/child.d.ts +0 -1
- package/testing/child.js +0 -12
- package/testing/compliance.d.ts +0 -41
- package/testing/compliance.js +0 -291
- package/testing/engine.d.ts +0 -66
- package/testing/engine.js +0 -339
- package/testing/fixtures.d.ts +0 -145
- package/testing/fixtures.js +0 -407
- package/testing/gateway.d.ts +0 -65
- package/testing/gateway.js +0 -402
- package/testing/import-guard.d.ts +0 -57
- package/testing/import-guard.js +0 -187
- package/testing/index.d.ts +0 -33
- package/testing/index.js +0 -10
- package/testing/local-db.d.ts +0 -27
- package/testing/local-db.js +0 -181
- package/testing/pinned-http.d.ts +0 -14
- package/testing/pinned-http.js +0 -125
- package/testing/recorder.d.ts +0 -15
- package/testing/recorder.js +0 -116
- package/testing/redaction.d.ts +0 -28
- package/testing/redaction.js +0 -67
- package/testing/replay.d.ts +0 -50
- package/testing/replay.js +0 -313
- package/testing/schema.d.ts +0 -13
- package/testing/schema.js +0 -65
- package/testing/source-loader.d.ts +0 -19
- package/testing/source-loader.js +0 -238
- package/testing/types.d.ts +0 -79
- package/testing/types.js +0 -18
- package/types.d.ts +0 -302
- package/types.js +0 -13
- package/validate.d.ts +0 -78
- package/validate.js +0 -597
package/index.d.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
export type { ArrayType, CheckContext, CheckpointItem, ConnectorCache, ConnectorCrypto, ConnectorDb, ConnectorDefinition, ConnectorLog, ConnectorRateLimit, ConnectorSignal, ConnectorTime, CursorPaginateOptions, DigestEncoding, DiscoverContext, DiscoverResult, FieldMeta, HashAlgorithm, HmacAlgorithm, HttpAuth, HttpClient, HttpMethod, HttpOptions, HttpResponse, KeysetPaginateOptions, LinkPaginateOptions, ObjectType, OffsetPaginateOptions, Page, PagePaginateOptions, Paginate, PullContext, PushContext, PushDiscovery, PushHandler, PushRequest, RecordItem, RemoveItem, ScalarKind, ScalarType, SdkType, SecretType, SignAlgorithm, StreamDiscovery, StreamFunction, StreamInput, StreamItem, SyncMode, TableDeclaration, } from './types.js';
|
|
2
|
-
export { defineConnector } from './types.js';
|
|
3
|
-
export { t } from './t.js';
|
|
4
|
-
export type { RateLimitedError } from './errors.js';
|
|
5
|
-
export { ConnectorError, errors } from './errors.js';
|
|
6
|
-
export type { DeclarationIssue, DefinitionSummary, NormalizedDiscoverResult } from './validate.js';
|
|
7
|
-
export { DEFAULT_PUSH_NAME, normalizeDiscoverResult, PRESENTATION_LIMITS, RESERVED_PUSH_NAMES, SDK_NAME_PATTERN, summarizeDefinition, validateConfig, validateConnectorDefinition, validateDiscoverResult, } from './validate.js';
|
|
8
|
-
export type { ConnectorIconPath, ConnectorPresentation, Sha256Hex } from './presentation.js';
|
|
9
|
-
export { decodeConnectorIcon, PNG_DATA_URI_PREFIX, readConnectorPresentation } from './presentation.js';
|
|
10
|
-
/** SDK 版本,等于本包 `package.json` 的 `version`;meta 打包校验 `dependencies` 范围与 runner 都用它。 */
|
|
11
|
-
export declare const SDK_VERSION: string;
|
|
12
|
-
/** runner 支持的 SDK 主版本集合;连接器 `dependencies` 里 SDK 的下限主版本必须在其中。 */
|
|
13
|
-
export declare const SUPPORTED_SDK_MAJORS: readonly number[];
|
package/index.js
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
export { defineConnector } from './types.js';
|
|
2
|
-
export { t } from './t.js';
|
|
3
|
-
export { ConnectorError, errors } from './errors.js';
|
|
4
|
-
export { DEFAULT_PUSH_NAME, normalizeDiscoverResult, PRESENTATION_LIMITS, RESERVED_PUSH_NAMES, SDK_NAME_PATTERN, summarizeDefinition, validateConfig, validateConnectorDefinition, validateDiscoverResult, } from './validate.js';
|
|
5
|
-
export { decodeConnectorIcon, PNG_DATA_URI_PREFIX, readConnectorPresentation } from './presentation.js';
|
|
6
|
-
export const SDK_VERSION = '3.0.1';
|
|
7
|
-
export const SUPPORTED_SDK_MAJORS = [3];
|
package/internal/location.d.ts
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
/** 当前是否直接从源码(`.ts`)执行。 */
|
|
2
|
-
export declare const IS_SOURCE: boolean;
|
|
3
|
-
/** `src/`(源码)或 `dist/`(构建产物)。 */
|
|
4
|
-
export declare const PACKAGE_ROOT: string;
|
|
5
|
-
/** 源码与 dist 下同一模块的路径:`relative` 不带扩展名,源码取 `.ts`,dist 取 `.js`。 */
|
|
6
|
-
export declare function moduleFile(relative: string): string;
|
package/internal/location.js
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
|
-
import { fileURLToPath } from 'node:url';
|
|
3
|
-
const self = fileURLToPath(import.meta.url);
|
|
4
|
-
export const IS_SOURCE = self.endsWith('.ts');
|
|
5
|
-
export const PACKAGE_ROOT = path.resolve(path.dirname(self), '..');
|
|
6
|
-
export function moduleFile(relative) {
|
|
7
|
-
return path.join(PACKAGE_ROOT, `${relative}${IS_SOURCE ? '.ts' : '.js'}`);
|
|
8
|
-
}
|
package/presentation.d.ts
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 连接器清单的展示元数据:`package.json` 的 `description`、`ontos.title`、`ontos.icon` 与根目录 `README.md`。
|
|
3
|
-
* CLI(`connector validate` / `publish`)与 meta(写草稿、在线编辑器保存)共用这一个校验函数,结果确定、不抛异常。
|
|
4
|
-
*
|
|
5
|
-
* 源码 `files` 是 UTF-8 文本映射:`icon.svg` 存原文,`icon.png` 存 `data:image/png;base64,<…>`。
|
|
6
|
-
* 图标是租户上传的不可信内容,不合规的 SVG 直接拒绝、不做清洗(界面只用 `<img>` 渲染)。
|
|
7
|
-
* 本文件只用 `TextEncoder` 与 `atob`,不引用 Node / Bun 内置模块;sha256 由调用方传入。
|
|
8
|
-
*/
|
|
9
|
-
import { type DeclarationIssue } from './validate.js';
|
|
10
|
-
export type ConnectorIconPath = 'icon.svg' | 'icon.png';
|
|
11
|
-
export interface ConnectorPresentation {
|
|
12
|
-
title: string | null;
|
|
13
|
-
description: string | null;
|
|
14
|
-
icon: {
|
|
15
|
-
path: ConnectorIconPath;
|
|
16
|
-
mime: 'image/svg+xml' | 'image/png';
|
|
17
|
-
sha256: string;
|
|
18
|
-
bytes: number;
|
|
19
|
-
} | null;
|
|
20
|
-
readme: boolean;
|
|
21
|
-
}
|
|
22
|
-
/** 字节的 sha256,小写十六进制。CLI 与 meta 用 `node:crypto` 实现。 */
|
|
23
|
-
export type Sha256Hex = (bytes: Uint8Array) => string;
|
|
24
|
-
/** `icon.png` 在源码映射里的前缀。 */
|
|
25
|
-
export declare const PNG_DATA_URI_PREFIX = "data:image/png;base64,";
|
|
26
|
-
/** SVG 图标的问题(文字不带路径);合法时 null。按小写文本扫描,不做 DOM 解析。 */
|
|
27
|
-
export declare function svgIconProblem(text: string): string | null;
|
|
28
|
-
/**
|
|
29
|
-
* 读 `package.json` 与 `files` 里的展示元数据。展示元数据都可选;`package.json` 缺失或不是 JSON 对象时报 `package.json` 问题。
|
|
30
|
-
* 问题路径:`package.json:description`、`package.json:ontos`、`package.json:ontos.title`、`package.json:ontos.icon`、
|
|
31
|
-
* 图标文件路径(`icon.svg` / `icon.png`)与 `README.md`。有问题的项在 presentation 里为 null / false。
|
|
32
|
-
*/
|
|
33
|
-
export declare function readConnectorPresentation(files: Record<string, string>, sha256: Sha256Hex): {
|
|
34
|
-
presentation: ConnectorPresentation;
|
|
35
|
-
issues: DeclarationIssue[];
|
|
36
|
-
};
|
|
37
|
-
/** 解码图标为字节;文件不存在或不合规返回 null。 */
|
|
38
|
-
export declare function decodeConnectorIcon(files: Record<string, string>, path: ConnectorIconPath): Uint8Array | null;
|
package/presentation.js
DELETED
|
@@ -1,268 +0,0 @@
|
|
|
1
|
-
import { PRESENTATION_LIMITS } from './validate.js';
|
|
2
|
-
const PACKAGE_JSON = 'package.json';
|
|
3
|
-
const README = 'README.md';
|
|
4
|
-
const ICON_PATHS = ['icon.svg', 'icon.png'];
|
|
5
|
-
const ICON_MIME = { 'icon.svg': 'image/svg+xml', 'icon.png': 'image/png' };
|
|
6
|
-
export const PNG_DATA_URI_PREFIX = 'data:image/png;base64,';
|
|
7
|
-
const PNG_SIGNATURE = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a];
|
|
8
|
-
const BASE64 = /^[A-Za-z0-9+/]*={0,2}$/;
|
|
9
|
-
const FORBIDDEN_SVG_ELEMENTS = new Set(['script', 'foreignobject', 'iframe', 'object', 'embed']);
|
|
10
|
-
const ALLOWED_ENTITIES = new Set(['amp', 'lt', 'gt', 'quot', 'apos']);
|
|
11
|
-
const EMPTY_PRESENTATION = { title: null, description: null, icon: null, readme: false };
|
|
12
|
-
function isRecord(value) {
|
|
13
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
14
|
-
}
|
|
15
|
-
function charCount(text) {
|
|
16
|
-
return [...text].length;
|
|
17
|
-
}
|
|
18
|
-
function utf8Length(text) {
|
|
19
|
-
return new TextEncoder().encode(text).length;
|
|
20
|
-
}
|
|
21
|
-
function readText(owner, key, path, max, issues) {
|
|
22
|
-
const value = owner[key];
|
|
23
|
-
if (value === undefined)
|
|
24
|
-
return null;
|
|
25
|
-
if (typeof value !== 'string' || value.trim().length === 0) {
|
|
26
|
-
issues.push({ path, message: `${path.slice(PACKAGE_JSON.length + 1)} must be a non-empty string` });
|
|
27
|
-
return null;
|
|
28
|
-
}
|
|
29
|
-
if (charCount(value) > max) {
|
|
30
|
-
issues.push({ path, message: `${path.slice(PACKAGE_JSON.length + 1)} must be at most ${max} characters` });
|
|
31
|
-
return null;
|
|
32
|
-
}
|
|
33
|
-
return value;
|
|
34
|
-
}
|
|
35
|
-
function scanSvgTags(text) {
|
|
36
|
-
const tags = [];
|
|
37
|
-
let i = 0;
|
|
38
|
-
const n = text.length;
|
|
39
|
-
const isSpace = (ch) => ch === ' ' || ch === '\t' || ch === '\n' || ch === '\r';
|
|
40
|
-
while (i < n) {
|
|
41
|
-
const lt = text.indexOf('<', i);
|
|
42
|
-
if (lt === -1)
|
|
43
|
-
break;
|
|
44
|
-
if (text.startsWith('<!--', lt)) {
|
|
45
|
-
const end = text.indexOf('-->', lt + 4);
|
|
46
|
-
if (end === -1)
|
|
47
|
-
return null;
|
|
48
|
-
i = end + 3;
|
|
49
|
-
continue;
|
|
50
|
-
}
|
|
51
|
-
if (text.startsWith('<?', lt)) {
|
|
52
|
-
const end = text.indexOf('?>', lt + 2);
|
|
53
|
-
if (end === -1)
|
|
54
|
-
return null;
|
|
55
|
-
i = end + 2;
|
|
56
|
-
continue;
|
|
57
|
-
}
|
|
58
|
-
i = lt + 1;
|
|
59
|
-
if (text[i] === '/') {
|
|
60
|
-
const end = text.indexOf('>', i);
|
|
61
|
-
if (end === -1)
|
|
62
|
-
return null;
|
|
63
|
-
i = end + 1;
|
|
64
|
-
continue;
|
|
65
|
-
}
|
|
66
|
-
while (isSpace(text[i]))
|
|
67
|
-
i += 1;
|
|
68
|
-
let name = '';
|
|
69
|
-
while (i < n && !isSpace(text[i]) && text[i] !== '>' && text[i] !== '/')
|
|
70
|
-
name += text[i++];
|
|
71
|
-
const tag = { name: name.slice(name.lastIndexOf(':') + 1), attributes: [] };
|
|
72
|
-
for (;;) {
|
|
73
|
-
while (isSpace(text[i]))
|
|
74
|
-
i += 1;
|
|
75
|
-
if (i >= n)
|
|
76
|
-
return null;
|
|
77
|
-
if (text[i] === '>') {
|
|
78
|
-
i += 1;
|
|
79
|
-
break;
|
|
80
|
-
}
|
|
81
|
-
if (text[i] === '/') {
|
|
82
|
-
i += 1;
|
|
83
|
-
continue;
|
|
84
|
-
}
|
|
85
|
-
let attr = '';
|
|
86
|
-
while (i < n && !isSpace(text[i]) && text[i] !== '=' && text[i] !== '>' && text[i] !== '/')
|
|
87
|
-
attr += text[i++];
|
|
88
|
-
while (isSpace(text[i]))
|
|
89
|
-
i += 1;
|
|
90
|
-
let value = '';
|
|
91
|
-
if (text[i] === '=') {
|
|
92
|
-
i += 1;
|
|
93
|
-
while (isSpace(text[i]))
|
|
94
|
-
i += 1;
|
|
95
|
-
const quote = text[i];
|
|
96
|
-
if (quote === '"' || quote === "'") {
|
|
97
|
-
const end = text.indexOf(quote, i + 1);
|
|
98
|
-
if (end === -1)
|
|
99
|
-
return null;
|
|
100
|
-
value = text.slice(i + 1, end);
|
|
101
|
-
i = end + 1;
|
|
102
|
-
}
|
|
103
|
-
else {
|
|
104
|
-
while (i < n && !isSpace(text[i]) && text[i] !== '>')
|
|
105
|
-
value += text[i++];
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
if (attr)
|
|
109
|
-
tag.attributes.push({ name: attr, value });
|
|
110
|
-
}
|
|
111
|
-
tags.push(tag);
|
|
112
|
-
}
|
|
113
|
-
return tags;
|
|
114
|
-
}
|
|
115
|
-
function cssProblem(css) {
|
|
116
|
-
const lower = css.toLowerCase();
|
|
117
|
-
if (lower.includes('url(') || lower.includes('@import'))
|
|
118
|
-
return 'must not reference url() or @import in styles';
|
|
119
|
-
if (css.includes('\\'))
|
|
120
|
-
return 'must not use escapes in styles';
|
|
121
|
-
return null;
|
|
122
|
-
}
|
|
123
|
-
export function svgIconProblem(text) {
|
|
124
|
-
const lower = text.toLowerCase();
|
|
125
|
-
if (lower.includes('<!doctype') || lower.includes('<!entity'))
|
|
126
|
-
return 'icon.svg must not contain <!DOCTYPE> or <!ENTITY>';
|
|
127
|
-
if (lower.includes('<![cdata['))
|
|
128
|
-
return 'icon.svg must not contain CDATA sections';
|
|
129
|
-
for (const match of lower.matchAll(/&(#?[a-z0-9]*);?/g)) {
|
|
130
|
-
if (!ALLOWED_ENTITIES.has(match[1] ?? '') || !match[0].endsWith(';'))
|
|
131
|
-
return 'icon.svg must not contain character or entity references other than & < > " '';
|
|
132
|
-
}
|
|
133
|
-
const tags = scanSvgTags(lower);
|
|
134
|
-
if (tags === null)
|
|
135
|
-
return 'icon.svg is not well-formed XML';
|
|
136
|
-
const root = tags[0];
|
|
137
|
-
if (!root || root.name !== 'svg')
|
|
138
|
-
return 'icon.svg must have an <svg> root element';
|
|
139
|
-
if (!root.attributes.some((attribute) => attribute.name === 'viewbox'))
|
|
140
|
-
return 'icon.svg root element must have a viewBox';
|
|
141
|
-
for (const tag of tags) {
|
|
142
|
-
if (FORBIDDEN_SVG_ELEMENTS.has(tag.name))
|
|
143
|
-
return `icon.svg must not contain <${tag.name}>`;
|
|
144
|
-
for (const { name, value } of tag.attributes) {
|
|
145
|
-
const local = name.slice(name.lastIndexOf(':') + 1);
|
|
146
|
-
if (local.startsWith('on'))
|
|
147
|
-
return `icon.svg must not contain event handler attributes (${name})`;
|
|
148
|
-
if (local === 'href' && !value.trim().startsWith('#'))
|
|
149
|
-
return `icon.svg ${name} must be a local reference starting with #`;
|
|
150
|
-
if (local === 'style') {
|
|
151
|
-
const problem = cssProblem(value);
|
|
152
|
-
if (problem)
|
|
153
|
-
return `icon.svg style attribute ${problem}`;
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
for (const match of lower.matchAll(/<(?:[a-z0-9_.-]+:)?style\b[^>]*>([\s\S]*?)(?:<\/|$)/g)) {
|
|
158
|
-
const problem = cssProblem(match[1] ?? '');
|
|
159
|
-
if (problem)
|
|
160
|
-
return `icon.svg <style> ${problem}`;
|
|
161
|
-
}
|
|
162
|
-
return null;
|
|
163
|
-
}
|
|
164
|
-
function decodeBase64(text) {
|
|
165
|
-
if (text.length % 4 !== 0 || !BASE64.test(text))
|
|
166
|
-
return null;
|
|
167
|
-
try {
|
|
168
|
-
const binary = atob(text);
|
|
169
|
-
const bytes = new Uint8Array(binary.length);
|
|
170
|
-
for (let i = 0; i < binary.length; i += 1)
|
|
171
|
-
bytes[i] = binary.charCodeAt(i);
|
|
172
|
-
return bytes;
|
|
173
|
-
}
|
|
174
|
-
catch {
|
|
175
|
-
return null;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
function readUint32(bytes, offset) {
|
|
179
|
-
return ((bytes[offset] ?? 0) * 0x1000000) + (((bytes[offset + 1] ?? 0) << 16) | ((bytes[offset + 2] ?? 0) << 8) | (bytes[offset + 3] ?? 0));
|
|
180
|
-
}
|
|
181
|
-
function checkIcon(path, content) {
|
|
182
|
-
const limit = PRESENTATION_LIMITS.iconBytes;
|
|
183
|
-
if (path === 'icon.svg') {
|
|
184
|
-
const bytes = new TextEncoder().encode(content);
|
|
185
|
-
if (bytes.length > limit)
|
|
186
|
-
return { problem: `icon.svg must be at most ${limit} bytes` };
|
|
187
|
-
const problem = svgIconProblem(content);
|
|
188
|
-
return problem ? { problem } : { bytes };
|
|
189
|
-
}
|
|
190
|
-
if (!content.startsWith(PNG_DATA_URI_PREFIX))
|
|
191
|
-
return { problem: `icon.png must be stored as ${PNG_DATA_URI_PREFIX}<data>` };
|
|
192
|
-
const encoded = content.slice(PNG_DATA_URI_PREFIX.length);
|
|
193
|
-
if (Math.floor(encoded.length / 4) * 3 - 2 > limit)
|
|
194
|
-
return { problem: `icon.png must be at most ${limit} bytes` };
|
|
195
|
-
const bytes = decodeBase64(encoded);
|
|
196
|
-
if (bytes === null)
|
|
197
|
-
return { problem: 'icon.png is not valid base64' };
|
|
198
|
-
if (bytes.length > limit)
|
|
199
|
-
return { problem: `icon.png must be at most ${limit} bytes` };
|
|
200
|
-
if (bytes.length < 24 || PNG_SIGNATURE.some((byte, i) => bytes[i] !== byte) || String.fromCharCode(...bytes.subarray(12, 16)) !== 'IHDR') {
|
|
201
|
-
return { problem: 'icon.png is not a PNG image' };
|
|
202
|
-
}
|
|
203
|
-
const width = readUint32(bytes, 16);
|
|
204
|
-
const height = readUint32(bytes, 20);
|
|
205
|
-
const min = PRESENTATION_LIMITS.iconMinPx;
|
|
206
|
-
if (width !== height || width < min)
|
|
207
|
-
return { problem: `icon.png must be square and at least ${min}×${min} pixels, got ${width}×${height}` };
|
|
208
|
-
return { bytes };
|
|
209
|
-
}
|
|
210
|
-
export function readConnectorPresentation(files, sha256) {
|
|
211
|
-
const issues = [];
|
|
212
|
-
let pkg;
|
|
213
|
-
try {
|
|
214
|
-
pkg = JSON.parse(files[PACKAGE_JSON] ?? '');
|
|
215
|
-
}
|
|
216
|
-
catch {
|
|
217
|
-
pkg = undefined;
|
|
218
|
-
}
|
|
219
|
-
if (!isRecord(pkg)) {
|
|
220
|
-
return { presentation: { ...EMPTY_PRESENTATION }, issues: [{ path: PACKAGE_JSON, message: 'package.json must be a JSON object' }] };
|
|
221
|
-
}
|
|
222
|
-
const presentation = { ...EMPTY_PRESENTATION };
|
|
223
|
-
presentation.description = readText(pkg, 'description', `${PACKAGE_JSON}:description`, PRESENTATION_LIMITS.description, issues);
|
|
224
|
-
const ontos = pkg.ontos;
|
|
225
|
-
if (ontos !== undefined && !isRecord(ontos)) {
|
|
226
|
-
issues.push({ path: `${PACKAGE_JSON}:ontos`, message: 'ontos must be an object' });
|
|
227
|
-
}
|
|
228
|
-
else if (ontos) {
|
|
229
|
-
presentation.title = readText(ontos, 'title', `${PACKAGE_JSON}:ontos.title`, PRESENTATION_LIMITS.title, issues);
|
|
230
|
-
const icon = ontos.icon;
|
|
231
|
-
if (icon !== undefined) {
|
|
232
|
-
if (typeof icon !== 'string' || !ICON_PATHS.includes(icon)) {
|
|
233
|
-
issues.push({ path: `${PACKAGE_JSON}:ontos.icon`, message: 'ontos.icon must be icon.svg or icon.png (a file in the source root)' });
|
|
234
|
-
}
|
|
235
|
-
else {
|
|
236
|
-
const path = icon;
|
|
237
|
-
const content = files[path];
|
|
238
|
-
if (content === undefined) {
|
|
239
|
-
issues.push({ path, message: `${path} is referenced by ontos.icon but missing` });
|
|
240
|
-
}
|
|
241
|
-
else {
|
|
242
|
-
const checked = checkIcon(path, content);
|
|
243
|
-
if ('problem' in checked)
|
|
244
|
-
issues.push({ path, message: checked.problem });
|
|
245
|
-
else
|
|
246
|
-
presentation.icon = { path, mime: ICON_MIME[path], sha256: sha256(checked.bytes), bytes: checked.bytes.length };
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
const readme = files[README];
|
|
252
|
-
if (readme !== undefined) {
|
|
253
|
-
if (utf8Length(readme) > PRESENTATION_LIMITS.readmeBytes) {
|
|
254
|
-
issues.push({ path: README, message: `README.md must be at most ${PRESENTATION_LIMITS.readmeBytes} bytes` });
|
|
255
|
-
}
|
|
256
|
-
else {
|
|
257
|
-
presentation.readme = true;
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
return { presentation, issues };
|
|
261
|
-
}
|
|
262
|
-
export function decodeConnectorIcon(files, path) {
|
|
263
|
-
const content = files[path];
|
|
264
|
-
if (content === undefined || !ICON_PATHS.includes(path))
|
|
265
|
-
return null;
|
|
266
|
-
const checked = checkIcon(path, content);
|
|
267
|
-
return 'bytes' in checked ? checked.bytes : null;
|
|
268
|
-
}
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* guest 与宿主之间的唯一通道(在 vm 上下文**里**执行)。
|
|
3
|
-
*
|
|
4
|
-
* 宿主只调用 guest 交出的三个函数,参数与返回值只有字符串和上下文自己的值:
|
|
5
|
-
* - `handle(json)`:父进程的 `reply` / `cancel`;
|
|
6
|
-
* - `poll()`:取走待发的消息(`call` / `result` / `failure`),同时触发到期的定时器,返回 JSON 字符串;
|
|
7
|
-
* - `start(json, factory)`:见 `index.ts`。
|
|
8
|
-
* 宿主不往上下文里放任何函数或对象(宿主值的 `constructor` 链能拿到宿主的 `Function`),也不 `await`
|
|
9
|
-
* 上下文里的 Promise。上下文里没有 `setTimeout`,`sleep` 由宿主按 `poll()` 回报的 `wake` 定时再调 `poll()` 实现。
|
|
10
|
-
*
|
|
11
|
-
* 用到的内置函数在本模块求值时取引用:guest 打包产物先于连接器代码执行,连接器之后改写全局不影响这里。
|
|
12
|
-
*/
|
|
13
|
-
import { ConnectorError } from '../../errors.js';
|
|
14
|
-
import type { HostMethod } from '../protocol.js';
|
|
15
|
-
declare const jsonParse: (text: string, reviver?: (this: any, key: string, value: any) => any) => any;
|
|
16
|
-
declare const jsonStringify: {
|
|
17
|
-
(value: any, replacer?: (this: any, key: string, value: any) => any, space?: string | number): string;
|
|
18
|
-
(value: any, replacer?: (number | string)[] | null, space?: string | number): string;
|
|
19
|
-
};
|
|
20
|
-
export { jsonParse, jsonStringify };
|
|
21
|
-
/** 宿主调用失败:`code` 来自宿主(如 `EGRESS_RATE_LIMITED`),连接器按 `code` 判断。 */
|
|
22
|
-
export declare class HostCallFailure extends ConnectorError {
|
|
23
|
-
readonly status: number | undefined;
|
|
24
|
-
readonly retryAfterMs: number | undefined;
|
|
25
|
-
constructor(code: string, message: string, status?: number, retryAfterMs?: number);
|
|
26
|
-
}
|
|
27
|
-
/** 运行被取消。 */
|
|
28
|
-
export declare class CanceledError extends ConnectorError {
|
|
29
|
-
constructor();
|
|
30
|
-
}
|
|
31
|
-
/** 当前时间(毫秒)。 */
|
|
32
|
-
export declare function now(): number;
|
|
33
|
-
/** 发一次宿主调用,等父进程的 `reply`。 */
|
|
34
|
-
export declare function call(method: HostMethod, payload: unknown): Promise<unknown>;
|
|
35
|
-
/** 等待 `ms` 毫秒;运行被取消时提前结束(调用方随后检查 `isAborted()`)。 */
|
|
36
|
-
export declare function sleep(ms: number): Promise<void>;
|
|
37
|
-
export declare function isAborted(): boolean;
|
|
38
|
-
/** 是否因宿主要求提前结束(而不是取消)而中止。 */
|
|
39
|
-
export declare function isStopped(): boolean;
|
|
40
|
-
/** 宿主要求提前结束:触发 `ctx.signal`(`aborted` 置位、回调执行),拉取随后以 `stopped: true` 正常结束。 */
|
|
41
|
-
export declare function requestStop(): void;
|
|
42
|
-
export declare function throwIfAborted(): void;
|
|
43
|
-
export declare function onAbort(listener: () => void): void;
|
|
44
|
-
/** 发出最终结果(`result` / `failure`);只生效一次。 */
|
|
45
|
-
export declare function finish(message: Record<string, unknown>): void;
|
|
46
|
-
export declare function isFinished(): boolean;
|
|
47
|
-
/** 父进程消息(已由宿主侧 `parseParentMessage` 校验并重新序列化)。 */
|
|
48
|
-
export declare function handle(json: string): void;
|
|
49
|
-
/**
|
|
50
|
-
* 触发到期的定时器并取走待发消息。返回 `{ out, wake, fired }`:`out` 是待发消息的 JSON 字符串,
|
|
51
|
-
* `wake` 是距下一个定时器的毫秒数(没有则为 -1),`fired` 表示本次触发了定时器(宿主应在微任务跑完后再 poll 一次)。
|
|
52
|
-
*/
|
|
53
|
-
export declare function poll(): string;
|
package/runtime/guest/bridge.js
DELETED
|
@@ -1,187 +0,0 @@
|
|
|
1
|
-
import { ConnectorError } from '../../errors.js';
|
|
2
|
-
const jsonParse = JSON.parse;
|
|
3
|
-
const jsonStringify = JSON.stringify;
|
|
4
|
-
const dateNow = Date.now;
|
|
5
|
-
const reflectApply = Reflect.apply;
|
|
6
|
-
const mathMin = Math.min;
|
|
7
|
-
const MapCtor = Map;
|
|
8
|
-
export { jsonParse, jsonStringify };
|
|
9
|
-
export class HostCallFailure extends ConnectorError {
|
|
10
|
-
status;
|
|
11
|
-
retryAfterMs;
|
|
12
|
-
constructor(code, message, status, retryAfterMs) {
|
|
13
|
-
super(code, message);
|
|
14
|
-
this.name = 'HostCallFailure';
|
|
15
|
-
this.status = status;
|
|
16
|
-
this.retryAfterMs = retryAfterMs;
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
export class CanceledError extends ConnectorError {
|
|
20
|
-
constructor() {
|
|
21
|
-
super('CANCELED', 'run was canceled');
|
|
22
|
-
this.name = 'CanceledError';
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
let nextCallId = 1;
|
|
26
|
-
const pending = new MapCtor();
|
|
27
|
-
let timers = [];
|
|
28
|
-
let outbox = [];
|
|
29
|
-
let finished = false;
|
|
30
|
-
let aborted = false;
|
|
31
|
-
let declared = false;
|
|
32
|
-
let stopped = false;
|
|
33
|
-
const abortListeners = [];
|
|
34
|
-
function isRecord(value) {
|
|
35
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
36
|
-
}
|
|
37
|
-
export function now() {
|
|
38
|
-
return reflectApply(dateNow, Date, []);
|
|
39
|
-
}
|
|
40
|
-
function enqueue(message) {
|
|
41
|
-
const text = jsonStringify(message);
|
|
42
|
-
if (typeof text !== 'string')
|
|
43
|
-
throw new TypeError('message is not serializable');
|
|
44
|
-
outbox[outbox.length] = text;
|
|
45
|
-
}
|
|
46
|
-
export function call(method, payload) {
|
|
47
|
-
if (method === 'declaration') {
|
|
48
|
-
if (declared)
|
|
49
|
-
return Promise.reject(new ConnectorError('FATAL_ERROR', 'declaration was already reported in this run'));
|
|
50
|
-
declared = true;
|
|
51
|
-
}
|
|
52
|
-
const id = nextCallId;
|
|
53
|
-
nextCallId += 1;
|
|
54
|
-
return new Promise((resolve, reject) => {
|
|
55
|
-
try {
|
|
56
|
-
enqueue({ type: 'call', id, method, payload });
|
|
57
|
-
}
|
|
58
|
-
catch (error) {
|
|
59
|
-
reject(new TypeError(`${method} payload is not serializable: ${error instanceof Error ? error.message : String(error)}`));
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
pending.set(id, { resolve, reject });
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
export function sleep(ms) {
|
|
66
|
-
if (aborted || !(ms > 0))
|
|
67
|
-
return Promise.resolve();
|
|
68
|
-
return new Promise((resolve) => {
|
|
69
|
-
timers[timers.length] = { at: now() + ms, resolve };
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
export function isAborted() {
|
|
73
|
-
return aborted;
|
|
74
|
-
}
|
|
75
|
-
export function isStopped() {
|
|
76
|
-
return stopped;
|
|
77
|
-
}
|
|
78
|
-
export function requestStop() {
|
|
79
|
-
if (aborted)
|
|
80
|
-
return;
|
|
81
|
-
stopped = true;
|
|
82
|
-
abort();
|
|
83
|
-
}
|
|
84
|
-
export function throwIfAborted() {
|
|
85
|
-
if (aborted)
|
|
86
|
-
throw new CanceledError();
|
|
87
|
-
}
|
|
88
|
-
export function onAbort(listener) {
|
|
89
|
-
if (aborted) {
|
|
90
|
-
void Promise.resolve().then(() => runListener(listener));
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
|
-
abortListeners[abortListeners.length] = listener;
|
|
94
|
-
}
|
|
95
|
-
function runListener(listener) {
|
|
96
|
-
try {
|
|
97
|
-
listener();
|
|
98
|
-
}
|
|
99
|
-
catch {
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
function abort() {
|
|
103
|
-
if (aborted)
|
|
104
|
-
return;
|
|
105
|
-
aborted = true;
|
|
106
|
-
const due = timers;
|
|
107
|
-
timers = [];
|
|
108
|
-
for (const timer of due)
|
|
109
|
-
timer.resolve();
|
|
110
|
-
for (const [id, entry] of pending) {
|
|
111
|
-
pending.delete(id);
|
|
112
|
-
entry.reject(new CanceledError());
|
|
113
|
-
}
|
|
114
|
-
const listeners = abortListeners.splice(0, abortListeners.length);
|
|
115
|
-
for (const listener of listeners)
|
|
116
|
-
runListener(listener);
|
|
117
|
-
}
|
|
118
|
-
export function finish(message) {
|
|
119
|
-
if (finished)
|
|
120
|
-
return;
|
|
121
|
-
finished = true;
|
|
122
|
-
try {
|
|
123
|
-
enqueue(message);
|
|
124
|
-
}
|
|
125
|
-
catch (error) {
|
|
126
|
-
outbox[outbox.length] = jsonStringify({
|
|
127
|
-
type: 'failure', code: 'FATAL_ERROR', logs: [],
|
|
128
|
-
message: `connector result is not serializable: ${error instanceof Error ? error.message : String(error)}`,
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
export function isFinished() {
|
|
133
|
-
return finished;
|
|
134
|
-
}
|
|
135
|
-
export function handle(json) {
|
|
136
|
-
if (typeof json !== 'string')
|
|
137
|
-
return;
|
|
138
|
-
let message;
|
|
139
|
-
try {
|
|
140
|
-
message = jsonParse(json);
|
|
141
|
-
}
|
|
142
|
-
catch {
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
if (!isRecord(message))
|
|
146
|
-
return;
|
|
147
|
-
if (message.type === 'cancel') {
|
|
148
|
-
abort();
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
if (message.type !== 'reply' || typeof message.id !== 'number')
|
|
152
|
-
return;
|
|
153
|
-
const entry = pending.get(message.id);
|
|
154
|
-
if (!entry)
|
|
155
|
-
return;
|
|
156
|
-
pending.delete(message.id);
|
|
157
|
-
if (message.ok === true) {
|
|
158
|
-
entry.resolve(message.value);
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
const error = isRecord(message.error) ? message.error : {};
|
|
162
|
-
entry.reject(new HostCallFailure(typeof error.code === 'string' ? error.code : 'HOST_ERROR', typeof error.message === 'string' ? error.message : 'host call failed', typeof error.status === 'number' ? error.status : undefined, typeof error.retryAfterMs === 'number' ? error.retryAfterMs : undefined));
|
|
163
|
-
}
|
|
164
|
-
export function poll() {
|
|
165
|
-
const current = now();
|
|
166
|
-
let fired = false;
|
|
167
|
-
if (timers.length > 0) {
|
|
168
|
-
const due = [];
|
|
169
|
-
const rest = [];
|
|
170
|
-
for (const timer of timers) {
|
|
171
|
-
if (timer.at <= current)
|
|
172
|
-
due[due.length] = timer;
|
|
173
|
-
else
|
|
174
|
-
rest[rest.length] = timer;
|
|
175
|
-
}
|
|
176
|
-
timers = rest;
|
|
177
|
-
for (const timer of due)
|
|
178
|
-
timer.resolve();
|
|
179
|
-
fired = due.length > 0;
|
|
180
|
-
}
|
|
181
|
-
let wake = -1;
|
|
182
|
-
for (const timer of timers)
|
|
183
|
-
wake = wake < 0 ? timer.at - current : mathMin(wake, timer.at - current);
|
|
184
|
-
const out = outbox;
|
|
185
|
-
outbox = [];
|
|
186
|
-
return `{"out":[${out.join(',')}],"wake":${wake < 0 ? -1 : wake},"fired":${fired ? 'true' : 'false'}}`;
|
|
187
|
-
}
|
package/runtime/guest/cache.d.ts
DELETED
package/runtime/guest/cache.js
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { call, now } from './bridge.js';
|
|
2
|
-
function key(value) {
|
|
3
|
-
if (typeof value !== 'string' || value.length === 0)
|
|
4
|
-
throw new TypeError('cache key must be a non-empty string');
|
|
5
|
-
return value;
|
|
6
|
-
}
|
|
7
|
-
function ttl(value) {
|
|
8
|
-
if (typeof value !== 'number' || !Number.isInteger(value) || value < 1)
|
|
9
|
-
throw new TypeError('cache ttlSeconds must be a positive integer');
|
|
10
|
-
return value;
|
|
11
|
-
}
|
|
12
|
-
async function get(name) {
|
|
13
|
-
const entry = await call('cache.get', { key: key(name) });
|
|
14
|
-
if (entry === null || typeof entry !== 'object' || typeof entry.value !== 'string')
|
|
15
|
-
return undefined;
|
|
16
|
-
const expiresAt = typeof entry.expiresAt === 'string' ? Date.parse(entry.expiresAt) : Number.NaN;
|
|
17
|
-
if (!Number.isNaN(expiresAt) && expiresAt <= now())
|
|
18
|
-
return undefined;
|
|
19
|
-
return entry.value;
|
|
20
|
-
}
|
|
21
|
-
async function set(name, value, ttlSeconds) {
|
|
22
|
-
if (typeof value !== 'string')
|
|
23
|
-
throw new TypeError('cache value must be a string');
|
|
24
|
-
await call('cache.set', { key: key(name), value, ttlSeconds: ttl(ttlSeconds) });
|
|
25
|
-
}
|
|
26
|
-
export function createCache() {
|
|
27
|
-
return Object.freeze({
|
|
28
|
-
get: (name) => Promise.resolve().then(() => get(name)),
|
|
29
|
-
set: (name, value, ttlSeconds) => Promise.resolve().then(() => set(name, value, ttlSeconds)),
|
|
30
|
-
async getOrSet(name, ttlSeconds, fn) {
|
|
31
|
-
ttl(ttlSeconds);
|
|
32
|
-
if (typeof fn !== 'function')
|
|
33
|
-
throw new TypeError('cache.getOrSet expects a function');
|
|
34
|
-
const cached = await get(name);
|
|
35
|
-
if (cached !== undefined)
|
|
36
|
-
return cached;
|
|
37
|
-
const value = await fn();
|
|
38
|
-
await set(name, value, ttlSeconds);
|
|
39
|
-
return value;
|
|
40
|
-
},
|
|
41
|
-
});
|
|
42
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `ctx.crypto`:`hmac` / `sign` / `hash` 走宿主(vm 上下文里没有 WebCrypto;`key` 可以是凭证句柄,由网关代算),
|
|
3
|
-
* `base64Encode` / `base64Decode` 在 guest 里纯 JS 实现(按 UTF-8)。
|
|
4
|
-
*/
|
|
5
|
-
import type { ConnectorCrypto } from '../../types.js';
|
|
6
|
-
export declare function utf8Encode(input: string): number[];
|
|
7
|
-
/** UTF-8 字节数,用于批次大小统计。 */
|
|
8
|
-
export declare function utf8Length(input: string): number;
|
|
9
|
-
export declare function bytesToBase64(bytes: readonly number[]): string;
|
|
10
|
-
export declare function base64ToBytes(input: string): number[];
|
|
11
|
-
export declare function base64Encode(input: string): string;
|
|
12
|
-
export declare function base64Decode(input: string): string;
|
|
13
|
-
export declare function createCrypto(): ConnectorCrypto;
|