feat: sql resource

This commit is contained in:
chenos
2025-07-31 16:55:27 +08:00
parent b214a75465
commit e22547f46d
4 changed files with 108 additions and 15 deletions
+17 -14
View File
@@ -22,7 +22,7 @@ import { FlowEngine } from './flowEngine';
import { FlowI18n } from './flowI18n';
import { JSRunner, JSRunnerOptions } from './JSRunner';
import { FlowModel, ForkFlowModel } from './models';
import { APIResource, BaseRecordResource, MultiRecordResource, SingleRecordResource } from './resources';
import { APIResource, BaseRecordResource, MultiRecordResource, SingleRecordResource, SQLResource } from './resources';
import { FlowExitException, resolveDefaultParams, resolveExpressions } from './utils';
type Getter<T = any> = (ctx: FlowContext) => T | Promise<T>;
@@ -618,19 +618,22 @@ export class FlowRuntimeContext<
) {
super();
this.addDelegate(this.model.context);
const ResourceMap = { APIResource, BaseRecordResource, SingleRecordResource, MultiRecordResource };
this.defineMethod('useResource', (className: 'APIResource' | 'SingleRecordResource' | 'MultiRecordResource') => {
if (model['resource']) {
return;
}
const R = ResourceMap[className];
if (!R) {
throw new Error(`Resource class ${className} not found in ResourceMap`);
}
const resource = new R() as APIResource;
resource.setAPIClient(this.api);
model['resource'] = resource;
});
const ResourceMap = { APIResource, BaseRecordResource, SingleRecordResource, MultiRecordResource, SQLResource };
this.defineMethod(
'useResource',
(className: 'APIResource' | 'SingleRecordResource' | 'MultiRecordResource' | 'SQLResource') => {
if (model['resource']) {
return;
}
const R = ResourceMap[className];
if (!R) {
throw new Error(`Resource class ${className} not found in ResourceMap`);
}
const resource = new R() as APIResource;
resource.setAPIClient(this.api);
model['resource'] = resource;
},
);
this.defineProperty('resource', {
get: () => model['resource'] || model.context['resource'],
cache: false,
@@ -12,4 +12,5 @@ export * from './baseRecordResource';
export * from './flowResource';
export * from './multiRecordResource';
export * from './singleRecordResource';
export * from './sqlResource';
//
@@ -0,0 +1,89 @@
/**
* This file is part of the NocoBase (R) project.
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
* Authors: NocoBase Team.
*
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { observable } from '@formily/reactive';
import _ from 'lodash';
import { BaseRecordResource } from './baseRecordResource';
export class SQLResource<TData = any> extends BaseRecordResource<TData> {
protected _data = observable.ref<TData>(null);
protected _meta = observable.ref<Record<string, any>>({});
private refreshTimer: NodeJS.Timeout | null = null;
// 请求配置 - 与 APIClient 接口保持一致
protected request = {
// url: null as string | null,
method: 'post' as string,
params: {} as Record<string, any>,
data: {} as Record<string, any>,
headers: {} as Record<string, any>,
};
protected buildURL(action?: string): string {
return `flowSql:${action || 'run'}`;
}
setType(type: 'selectRows' | 'selectRow' | 'selectVar') {
this.request.data.type = type;
return this;
}
setFilterByTk(filterByTk: any) {
this.request.data.uid = filterByTk;
return this;
}
setFilter(filter: Record<string, any>) {
this.request.data.filter = filter;
return this;
}
setSQL(sql: string) {
this.request.data.sql = sql;
return this;
}
setBind(bind: Record<string, any> | Array<any>) {
this.request.data.bind = bind;
return this;
}
/**
* 在同一个事件循环内多次调用 refresh 方法时,只有最后一次调用会生效。避免触发多次相同的接口请求。
* @returns
*/
async refresh(): Promise<void> {
// 清除之前的定时器,确保只有最后一次调用生效
if (this.refreshTimer) {
clearTimeout(this.refreshTimer);
}
// 设置新的定时器,在下一个事件循环执行
return new Promise<void>((resolve, reject) => {
this.refreshTimer = setTimeout(async () => {
try {
this.loading = true;
const { data, meta } = await this.runAction<TData, any>('run', {
method: 'post',
...this.getRefreshRequestOptions(),
});
this.setData(data).setMeta(meta);
this.emit('refresh');
this.loading = false;
resolve();
} catch (error) {
this.loading = false;
reject(error instanceof Error ? error : new Error(String(error)));
} finally {
this.refreshTimer = null;
}
});
});
}
}
@@ -33,7 +33,7 @@ export class PluginCollectionSQLServer extends Plugin {
this.app.acl.allow('flowSql', 'run', 'loggedIn');
this.app.resourceManager.registerActionHandlers({
'flowSql:run': async (ctx, next) => {
const { uid, sql, type, filter, bind } = ctx.action.params.values || {};
const { uid, sql, type = 'selectRows', filter, bind } = ctx.action.params.values || {};
if (!uid) {
ctx.throw(400, 'UID is required');
}