插件与扩展
扩展 API 遵循当前 LogicForm V2 契约。
LogicForm V2 的扩展对象都由调用方创建、注册并传给执行入口。它们不是全局单例,不会自动影响其他请求、租户或进程。
扩展边界怎么选
自定义 Semantic Type
定义契约
interface SemanticTypeDefinition {
name: string;
primalType: 'string' | 'number' | 'boolean' | 'date' | 'object';
validateProperty?: (property: ResolvedSemanticProperty) => void;
normalizeQuery?: (
value: unknown,
context: SemanticTypeNormalizeContext,
) => unknown;
requiresPerPeriodEvaluation?: (
context: SemanticPeriodEvaluationContext,
) => boolean;
hydrateResult?: (
value: unknown,
context: SemanticTypeResultContext,
) => unknown;
}
primalType 描述数组元素,数组容器仍由 property.isArray 表示。所有 primalType: 'date' 的类型自动获得统一日期 Query 能力,不需要在自定义类型中重复实现。
注册示例
import {
SemanticDBError,
createSemanticTypeRegistry,
type SemanticTypeDefinition,
} from 'semanticdb-v2';
const ratingType: SemanticTypeDefinition = {
name: 'rating',
primalType: 'number',
validateProperty(property) {
const values = property.constraints?.enum;
if (!Array.isArray(values) || values.some((value) =>
typeof value !== 'number' || value < 1 || value > 5)) {
throw new SemanticDBError(
'INVALID_RATING_CONFIG',
`Property ${property.name} must declare ratings from 1 to 5`,
);
}
},
};
const semanticTypes = createSemanticTypeRegistry()
.register(ratingType);
await Logicform.execute(logicform, {
schemas,
schemaMappings,
connections,
semanticTypes,
});
createSemanticTypeRegistry() 已包含所有内置类型。注册名必须非空且唯一,不能覆盖内置定义。Registry 应在应用启动时创建并跨执行复用。
自定义 Operator
Operator 类别
type OperatorKind = 'scalar' | 'aggregate' | 'window' | 'logical' | 'component';
核心定义
interface OperatorDefinitionBase {
name: string;
kind: OperatorKind;
defaultName(context: OperatorDefaultNameContext): string;
requiresProperty?: boolean;
dependencies?: string[];
getOutputProperty?(context: OperatorOutputPropertyContext): ResultProperty;
normalizePred?(context: OperatorNormalizePredContext): PredItemType;
planInput?(context: OperatorPlanInputContext): OperatorPlannedInput;
planExpression?(context: OperatorPlanExpressionContext): Expression;
validatePlan?(context: OperatorValidatePlanContext): void;
rewriteLogicform?(context: OperatorRewriteLogicformContext): LogicformType;
requiresPerPeriodEvaluation?(context: OperatorPeriodEvaluationContext): boolean;
}
根据 kind 还必须提供对应执行能力:
- SQL scalar/aggregate:
toSQL(context);
- JavaScript scalar:
evaluate(context);
- window:
windowFunction;
- logical:
planExpression(context);
- component:
createComponent(context)。
每个 Operator 必须只产生一个结果列,并通过 getOutputProperty 准确描述 name、Semantic Type、Primal Type、isArray、ref、日期粒度等结果元数据。
简单示例
import { createOperatorRegistry } from 'semanticdb-v2';
const operators = createOperatorRegistry().register({
name: '$double',
kind: 'scalar',
defaultName: ({ operandName, locale }) =>
locale.startsWith('en') ? `Double ${operandName}` : `${operandName}的两倍`,
toSQL({ expression }) {
if (!expression) throw new Error('$double requires pred');
return `(${expression} * 2)`;
},
getOutputProperty({ name, inputProperty }) {
if (!inputProperty) throw new Error('$double requires input');
return { ...inputProperty, name, isArray: false };
},
});
传入 ExecuteOptions.operators:
await Logicform.execute(logicform, {
schemas,
schemaMappings,
connections,
operators,
});
createOperatorRegistry() 已包含核心 Operator 和内置插件 Operator。重复名称会失败,不会静默覆盖。
SQL 能力
自定义 SQL Operator 应通过 context 能力生成方言无关表达式:
interface OperatorSqlCapabilities {
aggregateFunction(name: string, expression?: string): string;
quoteIdentifier(identifier: string): string;
concat(expressions: string[]): string;
substring(expression: string, start: number, length: number): string;
dateBucket(expression: string, granularity: TimeGranularity): string;
value(value: unknown): string;
condition(expression: string, condition: unknown): string;
}
value() 返回绑定参数占位符;不要自行把用户值拼进 SQL。
SemanticDB Plugin
当一个能力横跨多种扩展边界时,使用 Plugin:
interface SemanticDBPlugin {
name: string;
schemaMetadata?: {
keys: string[];
resolve(
schema: SemanticSchemaDraft,
properties: ResolvedSemanticProperty[],
): Record<string, unknown>;
};
legacySchemaKeys?: string[];
normalizeLegacySchema?(context: {
source: Record<string, unknown>;
properties: SemanticPropertyDraft[];
}): Record<string, unknown>;
resolveEntityFilterProperty?(
schema: ResolvedSemanticSchema,
): ResolvedSemanticProperty | undefined;
operators?: OperatorDefinition[];
}
字段含义
Plugin 应是冻结、无状态的定义。请求状态放在调用方或执行 context 中。
使用
await Logicform.execute(logicform, {
schemas,
schemaMappings,
connections,
plugins: [organizationPlugin],
});
插件仅作用于传入它的执行。碰撞检查包括:
- 重复插件名称;
- 多个插件声明相同 Schema 元数据 key;
- 重复 Operator 名;
- 插件 Operator 与核心 Operator 冲突。
所有冲突都会明确失败。
内置 hierarchy Plugin
系统默认启用 hierarchy 插件,它拥有:
- Schema 的
hierarchy 元数据;
- 旧
hierarchy_property 的兼容转换;
- 层级实体的
entity_id 字段选择;
$hierarchyLevel Operator。
调用方不需要在 plugins 中重复传入它。
Database Provider
当前内置 MySQL、Doris、StarRocks、PostgreSQL、ClickHouse、Snowflake 和 Oracle Provider。调用方通常只需提供 DatabaseConnection。
新的 Provider 需要实现数据库连接生命周期、参数绑定、标识符引用、Query 条件、数组关系、递归查询以及所有已启用 Operator 所需的 SQL 能力。Provider 只负责物理数据库协议和方言,不应自行改变 LogicForm 语义。具体 Provider 包的发布与注册入口将在 V2 正式发布前确定。
Query Cache
缓存通过 ExecuteOptions.cache 注入:
cache: {
store: cache,
namespace: 'tenant-a:analytics',
}
Cache 身份至少包含 namespace、Provider、参数化 SQL 和参数。Mapping 的 freshness 决定是否缓存及何时失效:
ttl:固定秒数;
daily:每天指定时区和时间过期;
manual:不过期,必须按物理 source 主动失效;
- 未配置:该 source 参与的查询不跨执行缓存。
缓存由调用方持有并关闭。缓存读写失败时执行降级到数据库,数据库错误仍正常返回。