初始化项目

This commit is contained in:
2026-10-19 00:11:38 +08:00
commit 9545e48e4d
150 changed files with 9559 additions and 0 deletions
+17
View File
@@ -0,0 +1,17 @@
/**
* Use these variables when you tailor your ArkTS code. They must be of the const type.
*/
export const HAR_VERSION = '1.0.0';
export const BUILD_MODE_NAME = 'debug';
export const DEBUG = true;
export const TARGET_NAME = 'default';
/**
* BuildProfile Class is used only for compatibility purposes.
*/
export default class BuildProfile {
static readonly HAR_VERSION = HAR_VERSION;
static readonly BUILD_MODE_NAME = BUILD_MODE_NAME;
static readonly DEBUG = DEBUG;
static readonly TARGET_NAME = TARGET_NAME;
}
+26
View File
@@ -0,0 +1,26 @@
/**
* @kuge/engine 的唯一公开入口。
*
* 约定(见 docs/adr/ADR-001):HAR 模块只通过本文件暴露能力,
* 外部模块禁止直接 import src/main/ets 下的实现文件。
*/
export {
ConnectionConfig,
ConnectionError,
ConnectionState,
Driver,
DriverFactory,
EngineKind,
engineLabel,
stateLabel
} from './src/main/ets/connection/types';
export { Connection, ConnectionManager } from './src/main/ets/connection/ConnectionManager';
export { Frame, FrameCodec } from './src/main/ets/protocol/FrameCodec';
export { MockDriver, MockDriverFactory } from './src/main/ets/driver/MockDriver';
export { EnginePlugin, PluginMeta } from './src/main/ets/plugin/EnginePlugin';
export { EngineRegistry, ENGINE_API_VERSION } from './src/main/ets/plugin/EngineRegistry';
export { SemVerUtil } from './src/main/ets/plugin/SemVerUtil';
export { ConnectionTester, TestResult, TestStage, TestErrorCode } from './src/main/ets/connection/ConnectionTester';
export { AppError, ErrorCategory, ErrorSeverity } from './src/main/ets/error/AppError';
export { ErrorMapper } from './src/main/ets/error/ErrorMapper';
export { ReconnectPolicy, ReconnectOptions } from './src/main/ets/connection/ReconnectPolicy';
+9
View File
@@ -0,0 +1,9 @@
{
"apiType": "stageMode",
"buildOption": {},
"targets": [
{
"name": "default"
}
]
}
+6
View File
@@ -0,0 +1,6 @@
import { harTasks } from '@ohos/hvigor-ohos-plugin';
export default {
system: harTasks, /* Built-in plugin of Hvigor. It cannot be modified. */
plugins: [] /* Custom plugin to extend the functionality of Hvigor. */
}
+9
View File
@@ -0,0 +1,9 @@
{
"name": "@kuge/engine",
"version": "1.0.0",
"description": "Kuge engine layer: connection abstraction, protocol framing, drivers",
"main": "Index.ets",
"author": "Kuge Team",
"license": "Apache-2.0",
"dependencies": {}
}
@@ -0,0 +1,122 @@
/**
* 连接管理内核。
*
* 职责:按引擎选择驱动、维护连接状态机、提供超时与关闭语义。
* 连接池策略(按引擎配额、复用、空闲回收)在 1.4.19 会话恢复与 1.4.1 后续迭代中补,
* 这里只保留"可注册、可创建、可关闭"的最小闭环。
*/
import { ConnectionConfig, ConnectionError, ConnectionState, Driver, DriverFactory, EngineKind } from './types';
/** 单条连接句柄。 */
export class Connection {
readonly config: ConnectionConfig;
private readonly driver: Driver;
private state: ConnectionState = ConnectionState.IDLE;
constructor(config: ConnectionConfig, driver: Driver) {
this.config = config;
this.driver = driver;
}
getState(): ConnectionState {
return this.state;
}
async connect(): Promise<void> {
if (this.state === ConnectionState.CONNECTED) {
return;
}
this.state = ConnectionState.CONNECTING;
try {
await this.withTimeout(this.driver.connect(this.config));
this.state = ConnectionState.CONNECTED;
} catch (err) {
this.state = ConnectionState.FAILED;
const message: string = err instanceof Error ? err.message : String(err);
throw new ConnectionError(message);
}
}
async ping(): Promise<void> {
if (this.state !== ConnectionState.CONNECTED) {
throw new ConnectionError('connection is not ready, state=' + this.state);
}
await this.driver.ping();
}
async close(): Promise<void> {
if (this.state === ConnectionState.CLOSED || this.state === ConnectionState.IDLE) {
this.state = ConnectionState.CLOSED;
return;
}
this.state = ConnectionState.DISCONNECTING;
try {
await this.driver.disconnect();
} catch (err) {
// 关闭失败不阻断清理,状态仍置为 CLOSED。
}
this.state = ConnectionState.CLOSED;
}
private withTimeout(task: Promise<void>): Promise<void> {
const ms: number = this.config.connectTimeoutMs;
const timeout = new Promise<void>((_resolve, reject) => {
setTimeout(() => {
reject(new ConnectionError('connect timeout after ' + ms + 'ms'));
}, ms);
});
return Promise.race([task, timeout]);
}
}
/** 连接管理器。 */
export class ConnectionManager {
private factories: Map<EngineKind, DriverFactory> = new Map<EngineKind, DriverFactory>();
private connections: Map<string, Connection> = new Map<string, Connection>();
private order: string[] = [];
/** 注册某引擎的驱动工厂,后注册覆盖先注册。 */
register(kind: EngineKind, factory: DriverFactory): void {
this.factories.set(kind, factory);
}
isSupported(kind: EngineKind): boolean {
return this.factories.has(kind);
}
/** 创建连接;id 重复或引擎未注册时抛出 ConnectionError。 */
create(config: ConnectionConfig): Connection {
if (this.connections.has(config.id)) {
throw new ConnectionError('duplicate connection id: ' + config.id);
}
const factory = this.factories.get(config.engine);
if (factory === undefined) {
throw new ConnectionError('no driver registered for engine: ' + config.engine);
}
const conn = new Connection(config, factory.create());
this.connections.set(config.id, conn);
this.order.push(config.id);
return conn;
}
get(id: string): Connection | undefined {
return this.connections.get(id);
}
size(): number {
return this.order.length;
}
/** 关闭全部连接并清空注册表。 */
async closeAll(): Promise<void> {
for (let i = 0; i < this.order.length; i++) {
const conn = this.connections.get(this.order[i]);
if (conn !== undefined) {
await conn.close();
}
}
this.connections.clear();
this.order = [];
}
}
@@ -0,0 +1,89 @@
/**
* 连接测试服务(1.4.2)。
*
* 目标:把「解析凭据 → 建连 → ping」拆成可诊断的阶段,返回稳定的 TestResult,
* 供连接对话框与诊断面板展示(原因 + 建议)。
*
* 设计取舍:凭据由调用方解析后传入(secret),引擎层不直接依赖 core 的凭据实现,
* 避免 ArkTS 名义类型带来的跨模块适配样板。
*/
import { ConnectionConfig, ConnectionError, Driver, DriverFactory, EngineKind } from './types';
import { Connection, ConnectionManager } from './ConnectionManager';
/** 失败阶段。 */
export enum TestStage {
RESOLVE = 0,
CONNECT = 1,
PING = 2,
DONE = 3
}
/** 测试结果。 */
export interface TestResult {
ok: boolean;
stage: TestStage;
errorCode: string;
message: string;
elapsedMs: number;
}
/** 分阶段诊断的 code 约定(对上层稳定,可映射多语言)。 */
export class TestErrorCode {
static readonly OK: string = 'KUGE-CONN-0000';
static readonly CREDENTIAL_MISSING: string = 'KUGE-CONN-0001';
static readonly CONNECT_FAILED: string = 'KUGE-CONN-0002';
static readonly PING_FAILED: string = 'KUGE-CONN-0003';
}
export class ConnectionTester {
private readonly manager: ConnectionManager;
constructor(manager: ConnectionManager) {
this.manager = manager;
}
/**
* 执行连接测试。
* @param config 连接配置
* @param secret 已解析的凭据;config.credentialRef 非空时必须提供
*/
async test(config: ConnectionConfig, secret: string | undefined): Promise<TestResult> {
const started: number = Date.now();
if (config.credentialRef.length > 0 && (secret === undefined || secret.length === 0)) {
return this.fail(TestStage.RESOLVE, TestErrorCode.CREDENTIAL_MISSING, '凭据未找到,请重新输入密码', started);
}
let conn: Connection;
try {
conn = this.manager.create(config);
await conn.connect();
} catch (err) {
const msg: string = err instanceof Error ? err.message : String(err);
return this.fail(TestStage.CONNECT, TestErrorCode.CONNECT_FAILED, msg, started);
}
try {
await conn.ping();
} catch (err) {
const msg: string = err instanceof Error ? err.message : String(err);
await conn.close();
return this.fail(TestStage.PING, TestErrorCode.PING_FAILED, msg, started);
}
await conn.close();
return {
ok: true,
stage: TestStage.DONE,
errorCode: TestErrorCode.OK,
message: '连接成功',
elapsedMs: Date.now() - started
};
}
private fail(stage: TestStage, code: string, message: string, started: number): TestResult {
return {
ok: false,
stage: stage,
errorCode: code,
message: message,
elapsedMs: Date.now() - started
};
}
}
@@ -0,0 +1,55 @@
/**
* 断线重连策略(1.4.19)。
*
* 纯策略逻辑:决定「是否重试」与「等多久」。真实重连动作由 Connection 的调用方执行,
* 定时器的接入在平台层完成,便于单元测试与后续加入抖动(jitter)。
*
* 规则:
* - 指数退避:delay = min(base * multiplier^attempt, maxDelay);
* - 认证/协议/资源限制类错误不重试(重试也不会成功);
* - 达到最大次数后不再重试。
*/
import { ErrorCategory } from '../error/AppError';
export interface ReconnectOptions {
/** 最大尝试次数(不含首次连接)。 */
maxAttempts: number;
/** 首次退避,毫秒。 */
baseDelayMs: number;
/** 退避倍数。 */
multiplier: number;
/** 退避上限,毫秒。 */
maxDelayMs: number;
}
export class ReconnectPolicy {
static defaults(): ReconnectOptions {
return { maxAttempts: 3, baseDelayMs: 500, multiplier: 2, maxDelayMs: 5000 };
}
/** 第 attempt 次重试(0 起)的等待时间。 */
static delayFor(attempt: number, options: ReconnectOptions): number {
if (attempt < 0) {
return 0;
}
let delay: number = options.baseDelayMs;
for (let i = 0; i < attempt; i++) {
delay = delay * options.multiplier;
if (delay >= options.maxDelayMs) {
return options.maxDelayMs;
}
}
return delay > options.maxDelayMs ? options.maxDelayMs : delay;
}
/** 是否应继续重试。 */
static shouldRetry(attempt: number, options: ReconnectOptions, category: ErrorCategory): boolean {
if (attempt >= options.maxAttempts) {
return false;
}
if (category === ErrorCategory.AUTH || category === ErrorCategory.PROTOCOL || category === ErrorCategory.LIMIT) {
return false;
}
return true;
}
}
+119
View File
@@ -0,0 +1,119 @@
/**
* 连接层公共类型。
*
* 设计约束(对应 WBS 1.4.1 / ADR-001):
* 1. 凭据不进入配置结构,只保存 credentialRef,实际解密由 1.3.8 保险箱负责;
* 2. 引擎以枚举收敛,新增引擎只在末尾追加,保持数值稳定;
* 3. 驱动只暴露 connect / disconnect / ping 三个生命周期动作,查询与元数据
* 能力在 1.4.x 后续工作包中按接口扩展,避免一开始就把契约定死。
*/
/** 支持的数据库引擎(10 种,不含 ODBC / Access / DBase)。 */
export enum EngineKind {
SQLITE = 0,
MYSQL = 1,
MARIADB = 2,
POSTGRESQL = 3,
SQLSERVER = 4,
ORACLE = 5,
MONGODB = 6,
REDIS = 7,
OCEANBASE = 8,
TIDB = 9
}
/** 连接生命周期状态。 */
export enum ConnectionState {
IDLE = 0,
CONNECTING = 1,
CONNECTED = 2,
DISCONNECTING = 3,
CLOSED = 4,
FAILED = 5
}
/** 连接配置。 */
export interface ConnectionConfig {
/** 连接唯一标识,由连接管理器保证不重复。 */
id: string;
/** 用户可见名称。 */
name: string;
engine: EngineKind;
host: string;
port: number;
database: string;
user: string;
/** 凭据引用,不存明文。 */
credentialRef: string;
/** 建连超时,毫秒。 */
connectTimeoutMs: number;
}
/** 引擎驱动契约。 */
export interface Driver {
readonly kind: EngineKind;
connect(config: ConnectionConfig): Promise<void>;
disconnect(): Promise<void>;
ping(): Promise<void>;
}
/** 驱动工厂。 */
export interface DriverFactory {
create(): Driver;
}
/** 连接层统一错误。 */
export class ConnectionError extends Error {
constructor(message: string) {
super(message);
this.name = 'ConnectionError';
}
}
/** 引擎显示名。 */
export function engineLabel(kind: EngineKind): string {
switch (kind) {
case EngineKind.SQLITE:
return 'SQLite';
case EngineKind.MYSQL:
return 'MySQL';
case EngineKind.MARIADB:
return 'MariaDB';
case EngineKind.POSTGRESQL:
return 'PostgreSQL';
case EngineKind.SQLSERVER:
return 'SQL Server';
case EngineKind.ORACLE:
return 'Oracle';
case EngineKind.MONGODB:
return 'MongoDB';
case EngineKind.REDIS:
return 'Redis';
case EngineKind.OCEANBASE:
return 'OceanBase';
case EngineKind.TIDB:
return 'TiDB';
default:
return 'Unknown';
}
}
/** 状态显示名。 */
export function stateLabel(state: ConnectionState): string {
switch (state) {
case ConnectionState.IDLE:
return '空闲';
case ConnectionState.CONNECTING:
return '连接中';
case ConnectionState.CONNECTED:
return '已连接';
case ConnectionState.DISCONNECTING:
return '断开中';
case ConnectionState.CLOSED:
return '已关闭';
case ConnectionState.FAILED:
return '连接失败';
default:
return '未知';
}
}
+48
View File
@@ -0,0 +1,48 @@
/**
* Mock 驱动(仅供骨架自检与后续单元测试使用)。
*
* 行为:所有动作带极小延迟后成功返回,用于验证连接状态机、超时与注册表,
* 不产生任何网络 IO。真实驱动见 1.4.3(MySQL/MariaDB)、1.4.7(SQLite)等。
*/
import { ConnectionConfig, Driver, DriverFactory, EngineKind } from '../connection/types';
export class MockDriver implements Driver {
readonly kind: EngineKind;
constructor(kind: EngineKind) {
this.kind = kind;
}
async connect(config: ConnectionConfig): Promise<void> {
await MockDriver.delay(30);
}
async disconnect(): Promise<void> {
await MockDriver.delay(10);
}
async ping(): Promise<void> {
await MockDriver.delay(5);
}
private static delay(ms: number): Promise<void> {
return new Promise<void>((resolve) => {
setTimeout(() => {
resolve();
}, ms);
});
}
}
export class MockDriverFactory implements DriverFactory {
private readonly kind: EngineKind;
constructor(kind: EngineKind) {
this.kind = kind;
}
create(): Driver {
return new MockDriver(this.kind);
}
}
+39
View File
@@ -0,0 +1,39 @@
/**
* 统一错误模型(1.4.18)。
*
* 上层 UI 与日志只认 AppError:稳定错误码、类别、严重度、可翻译 key 与建议。
* 各引擎原始错误先经 ErrorMapper 归类,再上抛,避免把厂商错误原文泄露到界面。
*/
export enum ErrorCategory {
CONNECT = 'connect',
AUTH = 'auth',
PROTOCOL = 'protocol',
IO = 'io',
LIMIT = 'limit',
UNKNOWN = 'unknown'
}
export enum ErrorSeverity {
WARN = 'warn',
ERROR = 'error',
FATAL = 'fatal'
}
export class AppError extends Error {
readonly code: string;
readonly category: ErrorCategory;
readonly severity: ErrorSeverity;
readonly messageKey: string;
readonly suggestion: string;
constructor(code: string, category: ErrorCategory, severity: ErrorSeverity,
messageKey: string, suggestion: string, cause?: string) {
super(cause === undefined ? messageKey : cause);
this.name = 'AppError';
this.code = code;
this.category = category;
this.severity = severity;
this.messageKey = messageKey;
this.suggestion = suggestion;
}
}
+94
View File
@@ -0,0 +1,94 @@
/**
* 错误映射表(1.4.18)。
*
* 把各引擎的原始错误码/文案归类为 AppError。规则按「先精确码、后关键字」排列,
* 命中即返回;未命中归为 UNKNOWN 并保留原始文案供诊断日志使用。
*
* 覆盖范围:网络层(ECONNREFUSED/ETIMEDOUT/ENOTFOUND)、
* MySQL(1045/2003/1213)、PostgreSQL(28P01/42501)、Oracle(ORA-01017/ORA-12170)、
* 以及通用关键字(access denied / timeout / deadlock)。
*/
import { AppError, ErrorCategory, ErrorSeverity } from './AppError';
interface ErrorRule {
pattern: string;
code: string;
category: ErrorCategory;
severity: ErrorSeverity;
messageKey: string;
suggestion: string;
}
export class ErrorMapper {
private static readonly RULES: ErrorRule[] = [
{
pattern: '1045', code: 'KUGE-DB-1045', category: ErrorCategory.AUTH, severity: ErrorSeverity.ERROR,
messageKey: 'error.auth.denied', suggestion: '请检查用户名、密码与主机白名单'
},
{
pattern: '28P01', code: 'KUGE-DB-28P01', category: ErrorCategory.AUTH, severity: ErrorSeverity.ERROR,
messageKey: 'error.auth.denied', suggestion: '请检查用户名与密码'
},
{
pattern: 'ORA-01017', code: 'KUGE-DB-ORA01017', category: ErrorCategory.AUTH, severity: ErrorSeverity.ERROR,
messageKey: 'error.auth.denied', suggestion: '请检查用户名、密码与服务名'
},
{
pattern: '42501', code: 'KUGE-DB-42501', category: ErrorCategory.AUTH, severity: ErrorSeverity.ERROR,
messageKey: 'error.auth.insufficient', suggestion: '当前账号权限不足,请联系 DBA 授权'
},
{
pattern: 'ECONNREFUSED', code: 'KUGE-NET-0001', category: ErrorCategory.CONNECT, severity: ErrorSeverity.ERROR,
messageKey: 'error.connect.refused', suggestion: '目标端口未监听,请检查主机与端口'
},
{
pattern: 'ENOTFOUND', code: 'KUGE-NET-0002', category: ErrorCategory.CONNECT, severity: ErrorSeverity.ERROR,
messageKey: 'error.connect.dns', suggestion: '主机名无法解析,请检查地址或 DNS'
},
{
pattern: 'ORA-12170', code: 'KUGE-DB-ORA12170', category: ErrorCategory.CONNECT, severity: ErrorSeverity.ERROR,
messageKey: 'error.connect.timeout', suggestion: '连接超时,请检查网络与防火墙'
},
{
pattern: 'ETIMEDOUT', code: 'KUGE-NET-0003', category: ErrorCategory.IO, severity: ErrorSeverity.WARN,
messageKey: 'error.io.timeout', suggestion: '网络较慢或服务繁忙,可稍后重试'
},
{
pattern: '2003', code: 'KUGE-DB-2003', category: ErrorCategory.CONNECT, severity: ErrorSeverity.ERROR,
messageKey: 'error.connect.unreachable', suggestion: '无法连接服务器,请检查主机、端口与网络'
},
{
pattern: '1213', code: 'KUGE-DB-1213', category: ErrorCategory.IO, severity: ErrorSeverity.WARN,
messageKey: 'error.io.deadlock', suggestion: '发生死锁,事务已回滚,可重试'
},
{
pattern: 'ACCESS DENIED', code: 'KUGE-DB-0001', category: ErrorCategory.AUTH, severity: ErrorSeverity.ERROR,
messageKey: 'error.auth.denied', suggestion: '请检查凭据与账号状态'
},
{
pattern: 'TIMEOUT', code: 'KUGE-NET-0004', category: ErrorCategory.IO, severity: ErrorSeverity.WARN,
messageKey: 'error.io.timeout', suggestion: '操作超时,请检查网络后重试'
},
{
pattern: 'DEADLOCK', code: 'KUGE-DB-0002', category: ErrorCategory.IO, severity: ErrorSeverity.WARN,
messageKey: 'error.io.deadlock', suggestion: '发生死锁,可重试'
}
];
/**
* 映射原始错误。
* @param rawCode 引擎错误码或网络错误码(可为空)
* @param rawMessage 原始错误文案
*/
static map(rawCode: string, rawMessage: string): AppError {
const haystack: string = (rawCode + ' ' + rawMessage).toUpperCase();
for (let i = 0; i < ErrorMapper.RULES.length; i++) {
const rule: ErrorRule = ErrorMapper.RULES[i];
if (haystack.indexOf(rule.pattern) >= 0) {
return new AppError(rule.code, rule.category, rule.severity, rule.messageKey, rule.suggestion, rawMessage);
}
}
return new AppError('KUGE-UNKNOWN-0000', ErrorCategory.UNKNOWN, ErrorSeverity.ERROR,
'error.unknown', '请查看诊断日志或联系支持', rawMessage);
}
}
@@ -0,0 +1,24 @@
/**
* 引擎插件契约(1.2.9 / 1.3.4)。
*
* 每个 engine-<kind> HAR 导出一个 EnginePlugin;装配层只认这个契约。
* meta.apiVersion 表示插件依赖的 engine-api 版本,由注册表校验。
*/
import { Driver, EngineKind } from '../connection/types';
/** 插件元信息。 */
export interface PluginMeta {
/** 模块名,如 engine-sqlite。 */
readonly name: string;
/** 插件自身版本(SemVer)。 */
readonly version: string;
/** 依赖的 engine-api 版本(SemVer)。 */
readonly apiVersion: string;
}
/** 引擎插件。 */
export interface EnginePlugin {
readonly kind: EngineKind;
readonly meta: PluginMeta;
createDriver(): Driver;
}
@@ -0,0 +1,66 @@
/**
* 引擎插件注册表(1.3.4)。
*
* 职责:
* 1. 注册引擎插件,拒绝重复引擎与不兼容的 engine-api 版本;
* 2. 提供查询与列举,供 ConnectionManager / 装配层使用;
* 3. 记录注册顺序,便于诊断输出。
*
* 加载方式:装配层(entry/app)显式 import 各引擎插件并调用 register;
* 按需分发的引擎在模块安装完成后注册(ADR-004,拟议)。
*/
import { EngineKind } from '../connection/types';
import { EnginePlugin } from './EnginePlugin';
import { SemVerUtil } from './SemVerUtil';
/** 当前 engine-api 版本;契约变化时同步升级(见 ADR-003)。 */
export const ENGINE_API_VERSION: string = '1.0.0';
export class EngineRegistry {
private plugins: Map<EngineKind, EnginePlugin> = new Map<EngineKind, EnginePlugin>();
private order: EngineKind[] = [];
/**
* 注册插件。重复引擎或版本不兼容时抛错,避免装配期静默覆盖。
*/
register(plugin: EnginePlugin): void {
if (this.plugins.has(plugin.kind)) {
throw new Error('duplicate engine plugin: ' + plugin.kind + ' (' + plugin.meta.name + ')');
}
if (!SemVerUtil.isCompatible(plugin.meta.apiVersion, ENGINE_API_VERSION)) {
throw new Error('incompatible engine-api version: ' + plugin.meta.name + ' requires ' +
plugin.meta.apiVersion + ', current ' + ENGINE_API_VERSION);
}
this.plugins.set(plugin.kind, plugin);
this.order.push(plugin.kind);
}
get(kind: EngineKind): EnginePlugin | undefined {
return this.plugins.get(kind);
}
has(kind: EngineKind): boolean {
return this.plugins.has(kind);
}
/** 按注册顺序返回插件名,用于诊断日志。 */
names(): string[] {
const out: string[] = [];
for (let i = 0; i < this.order.length; i++) {
const plugin = this.plugins.get(this.order[i]);
if (plugin !== undefined) {
out.push(plugin.meta.name + '@' + plugin.meta.version);
}
}
return out;
}
size(): number {
return this.order.length;
}
clear(): void {
this.plugins.clear();
this.order = [];
}
}
+55
View File
@@ -0,0 +1,55 @@
/**
* 语义化版本工具(1.3.4)。
*
* 只实现插件兼容性判断所需的最小子集:解析 major.minor.patch 与兼容性比较。
* 纯函数,不依赖平台 API,可独立测试。
*/
export class SemVerUtil {
/** 解析版本号;非法返回 undefined。 */
static parse(version: string): number[] | undefined {
if (version.length === 0) {
return undefined;
}
const parts: string[] = version.split('.');
if (parts.length !== 3) {
return undefined;
}
const out: number[] = [];
for (let i = 0; i < parts.length; i++) {
const text: string = parts[i];
if (text.length === 0) {
return undefined;
}
for (let j = 0; j < text.length; j++) {
const code: number = text.charCodeAt(j);
if (code < 48 || code > 57) {
return undefined;
}
}
out.push(Number(text));
}
return out;
}
/**
* 插件要求的 API 版本是否可被当前版本满足。
* 规则:主版本必须相同(不兼容变更需升主版本);要求的 minor/patch 不得高于当前。
*/
static isCompatible(required: string, current: string): boolean {
const req: number[] | undefined = SemVerUtil.parse(required);
const cur: number[] | undefined = SemVerUtil.parse(current);
if (req === undefined || cur === undefined) {
return false;
}
if (req[0] !== cur[0]) {
return false;
}
if (req[1] > cur[1]) {
return false;
}
if (req[1] === cur[1] && req[2] > cur[2]) {
return false;
}
return true;
}
}
@@ -0,0 +1,73 @@
/**
* 协议帧编解码基座(1.4.11)。
*
* 帧格式(小端):
* [0..3] uint32 整帧长度(含头,6 + payload)
* [4..5] uint16 序号
* [6..] bytes 载荷
*
* 该格式与 MySQL / MariaDB 的 3 字节长度头不同,是引擎无关的内部封装层:
* 各引擎协议实现(1.4.3–1.4.10)在载荷里携带自己的报文,本层只负责
* 传输分帧、粘包拆分与错误长度防御。
*/
import { ConnectionError } from '../connection/types';
/** 一帧数据。 */
export class Frame {
readonly seq: number;
readonly payload: Uint8Array;
constructor(seq: number, payload: Uint8Array) {
this.seq = seq;
this.payload = payload;
}
}
export class FrameCodec {
/** 头长度:4 字节长度 + 2 字节序号。 */
static readonly HEADER_BYTES: number = 6;
/** 单帧载荷上限:16 MB。 */
static readonly MAX_PAYLOAD_BYTES: number = 16 * 1024 * 1024;
/** 组帧。载荷超限时抛 ConnectionError。 */
static encode(seq: number, payload: Uint8Array): Uint8Array {
if (payload.length > FrameCodec.MAX_PAYLOAD_BYTES) {
throw new ConnectionError('payload too large: ' + payload.length);
}
const total: number = FrameCodec.HEADER_BYTES + payload.length;
const out: Uint8Array = new Uint8Array(total);
out[0] = total & 0xFF;
out[1] = (total >>> 8) & 0xFF;
out[2] = (total >>> 16) & 0xFF;
out[3] = (total >>> 24) & 0xFF;
out[4] = seq & 0xFF;
out[5] = (seq >>> 8) & 0xFF;
out.set(payload, FrameCodec.HEADER_BYTES);
return out;
}
/** 读取头部声明的整帧长度;不足 4 字节返回 -1。 */
static frameLength(header: Uint8Array): number {
if (header.length < 4) {
return -1;
}
return header[0] | (header[1] << 8) | (header[2] << 16) | (header[3] << 24);
}
/** 解出第一帧;数据不足返回 undefined(由调用方继续收)。 */
static decode(buffer: Uint8Array): Frame | undefined {
const total: number = FrameCodec.frameLength(buffer);
if (total < 0) {
return undefined;
}
if (total < FrameCodec.HEADER_BYTES || total > FrameCodec.HEADER_BYTES + FrameCodec.MAX_PAYLOAD_BYTES) {
throw new ConnectionError('invalid frame length: ' + total);
}
if (buffer.length < total) {
return undefined;
}
const seq: number = buffer[4] | (buffer[5] << 8);
return new Frame(seq, buffer.slice(FrameCodec.HEADER_BYTES, total));
}
}
+10
View File
@@ -0,0 +1,10 @@
{
"module": {
"name": "engine",
"type": "har",
"deviceTypes": [
"tablet",
"2in1"
]
}
}
@@ -0,0 +1,8 @@
{
"string": [
{
"name": "engine_module_desc",
"value": "库阁连接层:连接抽象、协议帧编解码与引擎驱动"
}
]
}