mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2026-01-13 17:48:13 +00:00
feat: add datasource engine & handlers
This commit is contained in:
parent
d5a98c090d
commit
d115ce0bdc
1
packages/datasource-engine/.eslintignore
Normal file
1
packages/datasource-engine/.eslintignore
Normal file
@ -0,0 +1 @@
|
||||
/node_modules
|
||||
7
packages/datasource-engine/.eslintrc.js
Normal file
7
packages/datasource-engine/.eslintrc.js
Normal file
@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
extends: '../../.eslintrc.js',
|
||||
rules: {
|
||||
'@typescript-eslint/no-parameter-properties': 1,
|
||||
'no-param-reassign': 0
|
||||
},
|
||||
};
|
||||
6
packages/datasource-engine/.gitignore
vendored
Normal file
6
packages/datasource-engine/.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
/node_modules/
|
||||
*.log
|
||||
.DS_Store
|
||||
/es/
|
||||
/lib/
|
||||
/dist
|
||||
4
packages/datasource-engine/.prettierrc.js
Normal file
4
packages/datasource-engine/.prettierrc.js
Normal file
@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
singleQuote: true,
|
||||
trailingComma: 'always',
|
||||
};
|
||||
30
packages/datasource-engine/CHANGELOG.md
Normal file
30
packages/datasource-engine/CHANGELOG.md
Normal file
@ -0,0 +1,30 @@
|
||||
# Change Log
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
<a name="0.1.14"></a>
|
||||
## [0.1.14](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-datasource-engine@0.1.13...@ali/lowcode-datasource-engine@0.1.14) (2020-09-29)
|
||||
|
||||
|
||||
|
||||
|
||||
**Note:** Version bump only for package @ali/lowcode-datasource-engine
|
||||
|
||||
<a name="0.1.13"></a>
|
||||
## [0.1.13](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-datasource-engine@0.1.12...@ali/lowcode-datasource-engine@0.1.13) (2020-09-28)
|
||||
|
||||
|
||||
|
||||
|
||||
**Note:** Version bump only for package @ali/lowcode-datasource-engine
|
||||
|
||||
<a name="0.1.12"></a>
|
||||
## 0.1.12 (2020-09-28)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* 🎸 按 826 对齐结论调整出码和数据源引擎 ([b9a562e](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/b9a562e))
|
||||
* 🎸 添加数据源引擎 ([624e2f8](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/624e2f8))
|
||||
* 🎸 与国凯的数据源保持一致,将 urlParams 所需的 search 参数直接传入 ([19fabc1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/19fabc1))
|
||||
8
packages/datasource-engine/ava.config.js
Normal file
8
packages/datasource-engine/ava.config.js
Normal file
@ -0,0 +1,8 @@
|
||||
export default {
|
||||
babel: {
|
||||
compileEnhancements: false,
|
||||
},
|
||||
files: ['./test/core/*.ts', './test/scenes/**/*.test.ts'],
|
||||
require: ['ts-node/register/transpile-only'],
|
||||
extensions: ['ts'],
|
||||
};
|
||||
1
packages/datasource-engine/interpret.d.ts
vendored
Normal file
1
packages/datasource-engine/interpret.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
export { create } from './dist/interpret';
|
||||
1
packages/datasource-engine/interpret.js
Normal file
1
packages/datasource-engine/interpret.js
Normal file
@ -0,0 +1 @@
|
||||
module.exports = require('./dist/interpret');
|
||||
39
packages/datasource-engine/package.json
Normal file
39
packages/datasource-engine/package.json
Normal file
@ -0,0 +1,39 @@
|
||||
{
|
||||
"name": "@ali/lowcode-datasource-engine",
|
||||
"version": "1.0.1",
|
||||
"main": "dist/index.js",
|
||||
"files": [
|
||||
"dist",
|
||||
"src",
|
||||
"interpret*",
|
||||
"runtime*"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "rm -rf dist && tsc --outDir ./dist --module esnext",
|
||||
"test": "ava",
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"typings": "dist/index.d.ts",
|
||||
"dependencies": {
|
||||
"@ali/build-success-types": "^0.1.2-alpha.35",
|
||||
"typescript": "^3.9.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ali/datasource-engine-universal-mtop-handler": "1.0.0-alpha.2",
|
||||
"@ali/datasource-engine-url-params-handler": "^1.0.0-alpha.3",
|
||||
"@ali/lowcode-datasource-http-handler": "^1.0.0-alpha.1",
|
||||
"@ali/lowcode-datasource-url-params-handler": "^1.0.0-alpha.3",
|
||||
"@ali/ng-lc-types": "^1.0.0-r2009222d5o6k8r8",
|
||||
"@ava/babel": "^1.0.1",
|
||||
"@types/sinon": "^9.0.5",
|
||||
"ava": "3.11.1",
|
||||
"get-port": "^5.1.1",
|
||||
"json5": "^2.1.3",
|
||||
"sinon": "^9.0.3",
|
||||
"ts-node": "^8.10.2",
|
||||
"tslib": "^2.0.1"
|
||||
},
|
||||
"publishConfig": {
|
||||
"registry": "https://registry.npm.alibaba-inc.com"
|
||||
}
|
||||
}
|
||||
45
packages/datasource-engine/readme.md
Normal file
45
packages/datasource-engine/readme.md
Normal file
@ -0,0 +1,45 @@
|
||||
## 关于 @ali/lc-datasource-engine
|
||||
|
||||
低代码引擎数据源核心代码
|
||||
|
||||
## doc
|
||||
|
||||
[原理介绍](https://yuque.antfin-inc.com/docs/share/6ba9dab7-0712-4302-a5bb-b17d4a5f8505?# 《DataSource Engine》)
|
||||
|
||||
|
||||
[fetch流程图](https://yuque.antfin-inc.com/docs/share/e9baef9a-3586-40fc-8708-eaeee0d7937e?# 《fetch 流程》)
|
||||
|
||||
|
||||
## 使用
|
||||
|
||||
```ts
|
||||
// 面向运行时渲染,直接给 schema
|
||||
import { create } from '@ali/lowcode-datasource-engine/interpret';
|
||||
|
||||
// 面向出码,需要给处理过后的内容
|
||||
import { create } from '@ali/lowcode-datasource-engine/runtime';
|
||||
|
||||
// dataSource 可以是 schema 协议内容 或者是运行时的转化后的配置内容 (出码专用)
|
||||
|
||||
// context 上下文(setState 为必选)
|
||||
const dsf = create(dataSource, context, {
|
||||
requestHandlersMap: { // 可选参数,以下内容为当前默认的内容
|
||||
urlParams: handlersMap.urlParams('?bar=1&test=2'),
|
||||
mtop: mtophandlers,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
console.log(dsf.dataSourceMap) // 符合集团协议的 datasourceMap https://yuque.antfin-inc.com/mo/spec/spec-low-code-building-schema#QUSn5
|
||||
|
||||
dsf.dataSourceMap['id'].load() // 加载
|
||||
|
||||
dsf.dataSourceMap['id'].status // 获取状态
|
||||
|
||||
dsf.dataSourceMap['id'].data // 获取数据
|
||||
|
||||
dsf.dataSourceMap['id'].error // 获取错误信息
|
||||
|
||||
dsf.reloadDataSource(); // 刷新所有数据源
|
||||
|
||||
```
|
||||
1
packages/datasource-engine/runtime.d.ts
vendored
Normal file
1
packages/datasource-engine/runtime.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
export { create } from './dist/runtime';
|
||||
1
packages/datasource-engine/runtime.js
Normal file
1
packages/datasource-engine/runtime.js
Normal file
@ -0,0 +1 @@
|
||||
module.exports = require('./dist/runtime/index');
|
||||
144
packages/datasource-engine/src/core/RuntimeDataSourceItem.ts
Normal file
144
packages/datasource-engine/src/core/RuntimeDataSourceItem.ts
Normal file
@ -0,0 +1,144 @@
|
||||
import {
|
||||
IRuntimeDataSource,
|
||||
RuntimeDataSourceConfig,
|
||||
RuntimeDataSourceStatus,
|
||||
IRuntimeContext,
|
||||
RequestHandler,
|
||||
RuntimeOptionsConfig,
|
||||
UrlParamsHandler,
|
||||
} from '@ali/build-success-types';
|
||||
|
||||
class RuntimeDataSourceItem<
|
||||
TParams extends Record<string, unknown> = Record<string, unknown>,
|
||||
TResultData = unknown
|
||||
> implements IRuntimeDataSource<TParams, TResultData> {
|
||||
private _data?: TResultData;
|
||||
|
||||
private _error?: Error;
|
||||
|
||||
private _status = RuntimeDataSourceStatus.Initial;
|
||||
|
||||
private _dataSourceConfig: RuntimeDataSourceConfig;
|
||||
|
||||
private _request:
|
||||
| RequestHandler<{ data: TResultData }>
|
||||
| UrlParamsHandler<TResultData>;
|
||||
|
||||
private _context: IRuntimeContext;
|
||||
|
||||
private _options?: RuntimeOptionsConfig;
|
||||
|
||||
constructor(
|
||||
dataSourceConfig: RuntimeDataSourceConfig,
|
||||
request:
|
||||
| RequestHandler<{ data: TResultData }>
|
||||
| UrlParamsHandler<TResultData>,
|
||||
context: IRuntimeContext,
|
||||
) {
|
||||
this._dataSourceConfig = dataSourceConfig;
|
||||
this._request = request;
|
||||
this._context = context;
|
||||
}
|
||||
|
||||
get data() {
|
||||
return this._data;
|
||||
}
|
||||
|
||||
get error() {
|
||||
return this._error;
|
||||
}
|
||||
|
||||
get status() {
|
||||
return this._status;
|
||||
}
|
||||
|
||||
public async load(params?: TParams) {
|
||||
if (!this._dataSourceConfig) return;
|
||||
// 考虑没有绑定对应的 handler 的情况
|
||||
if (!this._request) {
|
||||
throw new Error(`no ${this._dataSourceConfig.type} handler provide`);
|
||||
}
|
||||
|
||||
// TODO: urlParams 有没有更好的处理方式
|
||||
if (this._dataSourceConfig.type === 'urlParams') {
|
||||
const response = await (this._request as UrlParamsHandler<TResultData>)(
|
||||
this._context,
|
||||
);
|
||||
this._context.setState({
|
||||
[this._dataSourceConfig.id]: response,
|
||||
});
|
||||
|
||||
this._data = response;
|
||||
this._status = RuntimeDataSourceStatus.Loaded;
|
||||
return response;
|
||||
}
|
||||
|
||||
if (!this._dataSourceConfig.options) {
|
||||
throw new Error(`${this._dataSourceConfig.id} has no options`);
|
||||
}
|
||||
|
||||
if (typeof this._dataSourceConfig.options === 'function') {
|
||||
this._options = this._dataSourceConfig.options();
|
||||
}
|
||||
|
||||
// 考虑转换之后是 null 的场景
|
||||
if (!this._options) {
|
||||
throw new Error(`${this._dataSourceConfig.id} options transform error`);
|
||||
}
|
||||
|
||||
// 临时变量存,每次可能结果不一致,不做缓存
|
||||
let shouldFetch = true;
|
||||
|
||||
if (this._dataSourceConfig.shouldFetch) {
|
||||
if (typeof this._dataSourceConfig.shouldFetch === 'function') {
|
||||
shouldFetch = this._dataSourceConfig.shouldFetch();
|
||||
} else if (typeof this._dataSourceConfig.shouldFetch === 'boolean') {
|
||||
shouldFetch = this._dataSourceConfig.shouldFetch;
|
||||
}
|
||||
}
|
||||
|
||||
if (!shouldFetch) {
|
||||
this._status = RuntimeDataSourceStatus.Error;
|
||||
this._error = new Error(
|
||||
`the ${this._dataSourceConfig.id} request should not fetch, please check the condition`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// willFetch
|
||||
this._dataSourceConfig.willFetch!();
|
||||
|
||||
// 约定如果 params 有内容,直接做替换,如果没有就用默认的 options 的
|
||||
if (params && this._options) {
|
||||
this._options.params = params;
|
||||
}
|
||||
|
||||
const dataHandler = this._dataSourceConfig.dataHandler!;
|
||||
const { errorHandler } = this._dataSourceConfig;
|
||||
|
||||
// 调用实际的请求,获取到对应的数据和状态后赋值给当前的 dataSource
|
||||
try {
|
||||
this._status = RuntimeDataSourceStatus.Loading;
|
||||
|
||||
// _context 会给传,但是用不用由 handler 说了算
|
||||
const result = await (this._request as RequestHandler<{
|
||||
data: TResultData;
|
||||
}>)(this._options, this._context).then(dataHandler, errorHandler);
|
||||
|
||||
// setState
|
||||
this._context.setState({
|
||||
[this._dataSourceConfig.id]: result,
|
||||
});
|
||||
// 结果赋值
|
||||
this._data = result;
|
||||
this._status = RuntimeDataSourceStatus.Loaded;
|
||||
return this._data;
|
||||
} catch (error) {
|
||||
this._error = error;
|
||||
this._status = RuntimeDataSourceStatus.Error;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { RuntimeDataSourceItem };
|
||||
66
packages/datasource-engine/src/core/adapter.ts
Normal file
66
packages/datasource-engine/src/core/adapter.ts
Normal file
@ -0,0 +1,66 @@
|
||||
import {
|
||||
transformFunction,
|
||||
getRuntimeValueFromConfig,
|
||||
getRuntimeJsValue,
|
||||
buildOptions,
|
||||
buildShouldFetch,
|
||||
} from './../utils';
|
||||
// 将不同渠道给的 schema 转为 runtime 需要的类型
|
||||
|
||||
import {
|
||||
DataSource,
|
||||
IPageContext,
|
||||
DataSourceConfig,
|
||||
RuntimeDataSourceConfig,
|
||||
DataSourceMap,
|
||||
} from '@ali/build-success-types';
|
||||
import { defaultDataHandler, defaultWillFetch } from '../helpers';
|
||||
|
||||
const adapt2Runtime = (dataSource: DataSource, context: IPageContext) => {
|
||||
const {
|
||||
list: interpretConfigList,
|
||||
dataHandler: interpretDataHandler,
|
||||
} = dataSource;
|
||||
const dataHandler: (dataMap?: DataSourceMap) => void =
|
||||
interpretDataHandler &&
|
||||
interpretDataHandler.compiled &&
|
||||
transformFunction(interpretDataHandler.compiled, context);
|
||||
|
||||
// 为空判断
|
||||
if (!interpretConfigList || !interpretConfigList.length) {
|
||||
return {
|
||||
list: [],
|
||||
dataHandler,
|
||||
};
|
||||
}
|
||||
const list: RuntimeDataSourceConfig[] = interpretConfigList.map(
|
||||
(el: DataSourceConfig) => {
|
||||
return {
|
||||
id: el.id,
|
||||
isInit:
|
||||
getRuntimeValueFromConfig('boolean', el.isInit, context) || true, // 默认 true
|
||||
isSync:
|
||||
getRuntimeValueFromConfig('boolean', el.isSync, context) || false, // 默认 false
|
||||
type: el.type || 'fetch',
|
||||
willFetch: el.willFetch
|
||||
? getRuntimeJsValue(el.willFetch, context)
|
||||
: defaultWillFetch,
|
||||
shouldFetch: buildShouldFetch(el, context),
|
||||
dataHandler: el.dataHandler
|
||||
? getRuntimeJsValue(el.dataHandler, context)
|
||||
: defaultDataHandler,
|
||||
errorHandler: el.errorHandler
|
||||
? getRuntimeJsValue(el.errorHandler, context)
|
||||
: undefined,
|
||||
options: buildOptions(el, context),
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
list,
|
||||
dataHandler,
|
||||
};
|
||||
};
|
||||
|
||||
export { adapt2Runtime };
|
||||
2
packages/datasource-engine/src/core/index.ts
Normal file
2
packages/datasource-engine/src/core/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export { RuntimeDataSourceItem } from './RuntimeDataSourceItem';
|
||||
export { adapt2Runtime } from './adapter';
|
||||
@ -0,0 +1,64 @@
|
||||
import {
|
||||
RuntimeDataSource,
|
||||
DataSourceMap,
|
||||
RuntimeDataSourceConfig,
|
||||
} from '@ali/build-success-types';
|
||||
|
||||
export const reloadDataSourceFactory = (
|
||||
dataSource: RuntimeDataSource,
|
||||
dataSourceMap: DataSourceMap,
|
||||
) => async () => {
|
||||
const allAsyncLoadings: Array<Promise<any>> = [];
|
||||
|
||||
// TODO: 那么,如果有新的类型过来,这个地方怎么处理???
|
||||
// 单独处理 urlParams 类型的
|
||||
dataSource.list
|
||||
.filter(
|
||||
(el: RuntimeDataSourceConfig) => el.type === 'urlParams' &&
|
||||
(typeof el.isInit === 'boolean' ? el.isInit : true),
|
||||
)
|
||||
.forEach((el: RuntimeDataSourceConfig) => {
|
||||
dataSourceMap[el.id].load();
|
||||
});
|
||||
|
||||
const remainRuntimeDataSourceList = dataSource.list.filter(
|
||||
(el: RuntimeDataSourceConfig) => el.type !== 'urlParams',
|
||||
);
|
||||
|
||||
// 处理并行
|
||||
for (const ds of remainRuntimeDataSourceList) {
|
||||
if (!ds.options) {
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
// 需要考虑出码直接不传值的情况
|
||||
ds.isInit &&
|
||||
!ds.isSync
|
||||
) {
|
||||
allAsyncLoadings.push(dataSourceMap[ds.id].load());
|
||||
}
|
||||
}
|
||||
|
||||
// 处理串行
|
||||
for (const ds of remainRuntimeDataSourceList) {
|
||||
if (!ds.options) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
// 需要考虑出码直接不传值的情况
|
||||
ds.isInit &&
|
||||
ds.isSync
|
||||
) {
|
||||
// TODO: 我理解这个异常也应该吃掉的,待确认
|
||||
try {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await dataSourceMap[ds.id].load();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.allSettled(allAsyncLoadings);
|
||||
};
|
||||
14
packages/datasource-engine/src/helpers/index.ts
Normal file
14
packages/datasource-engine/src/helpers/index.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { DataHandler } from '@ali/build-success-types';
|
||||
|
||||
function noop() {}
|
||||
|
||||
// 默认的 dataSourceItem 的 dataHandler
|
||||
export const defaultDataHandler: DataHandler = async <T = unknown>(response: {
|
||||
data: T;
|
||||
}) => response.data;
|
||||
|
||||
// 默认的 dataSourceItem 的 willFetch
|
||||
export const defaultWillFetch = noop;
|
||||
|
||||
// 默认的 dataSourceItem 的 shouldFetch
|
||||
export const defaultShouldFetch = () => true;
|
||||
4
packages/datasource-engine/src/index.ts
Normal file
4
packages/datasource-engine/src/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import createInterpret from './interpret/DataSourceEngineFactory';
|
||||
import createRuntime from './runtime/RuntimeDataSourceEngineFactory';
|
||||
|
||||
export { createInterpret, createRuntime };
|
||||
@ -0,0 +1,56 @@
|
||||
import {
|
||||
IRuntimeContext,
|
||||
IRuntimeDataSource,
|
||||
DataSource,
|
||||
RuntimeDataSourceConfig,
|
||||
RuntimeDataSource,
|
||||
RequestHandlersMap,
|
||||
} from '@ali/build-success-types';
|
||||
|
||||
import { adapt2Runtime } from '../core/adapter';
|
||||
import { RuntimeDataSourceItem } from '../core/RuntimeDataSourceItem';
|
||||
import { reloadDataSourceFactory } from '../core/reloadDataSourceFactory';
|
||||
|
||||
// TODO: requestConfig mtop 默认的请求 config 怎么处理?
|
||||
/**
|
||||
* @param dataSource
|
||||
* @param context
|
||||
*/
|
||||
|
||||
export default (
|
||||
dataSource: DataSource,
|
||||
context: IRuntimeContext,
|
||||
extraConfig: {
|
||||
requestHandlersMap: RequestHandlersMap<{ data: unknown }>;
|
||||
} = {
|
||||
requestHandlersMap: {},
|
||||
},
|
||||
) => {
|
||||
const { requestHandlersMap } = extraConfig;
|
||||
|
||||
const runtimeDataSource: RuntimeDataSource = adapt2Runtime(
|
||||
dataSource,
|
||||
context,
|
||||
);
|
||||
|
||||
const dataSourceMap = runtimeDataSource.list.reduce(
|
||||
(
|
||||
prev: Record<string, IRuntimeDataSource>,
|
||||
current: RuntimeDataSourceConfig,
|
||||
) => {
|
||||
prev[current.id] = new RuntimeDataSourceItem(
|
||||
current,
|
||||
// type 协议默认值 fetch
|
||||
requestHandlersMap[current.type || 'fetch'],
|
||||
context,
|
||||
);
|
||||
return prev;
|
||||
},
|
||||
{},
|
||||
);
|
||||
|
||||
return {
|
||||
dataSourceMap,
|
||||
reloadDataSource: reloadDataSourceFactory(runtimeDataSource, dataSourceMap),
|
||||
};
|
||||
};
|
||||
3
packages/datasource-engine/src/interpret/index.ts
Normal file
3
packages/datasource-engine/src/interpret/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import createInterpret from './DataSourceEngineFactory';
|
||||
|
||||
export const create = createInterpret;
|
||||
@ -0,0 +1,69 @@
|
||||
/* eslint-disable no-nested-ternary */
|
||||
import {
|
||||
IRuntimeContext,
|
||||
IRuntimeDataSource,
|
||||
RuntimeDataSourceConfig,
|
||||
RuntimeDataSource,
|
||||
RequestHandlersMap,
|
||||
} from '@ali/build-success-types';
|
||||
|
||||
import { RuntimeDataSourceItem } from '../core';
|
||||
import { reloadDataSourceFactory } from '../core/reloadDataSourceFactory';
|
||||
import {
|
||||
defaultDataHandler,
|
||||
defaultShouldFetch,
|
||||
defaultWillFetch,
|
||||
} from '../helpers';
|
||||
|
||||
// TODO: requestConfig mtop 默认的请求 config 怎么处理?
|
||||
/**
|
||||
* @param dataSource
|
||||
* @param context
|
||||
*/
|
||||
export default (
|
||||
dataSource: RuntimeDataSource,
|
||||
context: IRuntimeContext,
|
||||
extraConfig: {
|
||||
requestHandlersMap: RequestHandlersMap<{ data: unknown }>;
|
||||
} = {
|
||||
requestHandlersMap: {},
|
||||
},
|
||||
) => {
|
||||
const { requestHandlersMap } = extraConfig;
|
||||
|
||||
// TODO: 对于出码类型,需要做一层数据兼容,给一些必要的值设置默认值,先兜底几个必要的
|
||||
dataSource.list.forEach((ds) => {
|
||||
ds.isInit = ds.isInit || true;
|
||||
ds.isSync = ds.isSync || false;
|
||||
ds.shouldFetch = !ds.shouldFetch
|
||||
? defaultShouldFetch
|
||||
: typeof ds.shouldFetch === 'function'
|
||||
? ds.shouldFetch.bind(context)
|
||||
: ds.shouldFetch;
|
||||
ds.willFetch = ds.willFetch ? ds.willFetch.bind(context) : defaultWillFetch;
|
||||
ds.dataHandler = ds.dataHandler
|
||||
? ds.dataHandler.bind(context)
|
||||
: defaultDataHandler;
|
||||
});
|
||||
|
||||
const dataSourceMap = dataSource.list.reduce(
|
||||
(
|
||||
prev: Record<string, IRuntimeDataSource>,
|
||||
current: RuntimeDataSourceConfig,
|
||||
) => {
|
||||
prev[current.id] = new RuntimeDataSourceItem(
|
||||
current,
|
||||
// type 协议默认值 fetch
|
||||
requestHandlersMap[current.type || 'fetch'],
|
||||
context,
|
||||
);
|
||||
return prev;
|
||||
},
|
||||
{},
|
||||
);
|
||||
|
||||
return {
|
||||
dataSourceMap,
|
||||
reloadDataSource: reloadDataSourceFactory(dataSource, dataSourceMap),
|
||||
};
|
||||
};
|
||||
3
packages/datasource-engine/src/runtime/index.ts
Normal file
3
packages/datasource-engine/src/runtime/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import createRuntime from './RuntimeDataSourceEngineFactory';
|
||||
|
||||
export const create = createRuntime;
|
||||
3
packages/datasource-engine/src/typings.d.ts
vendored
Normal file
3
packages/datasource-engine/src/typings.d.ts
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
declare module '@ali/mirror-io-client-mopen';
|
||||
declare module '@ali/mirror-io-client-mtop';
|
||||
declare module '@ali/mirror-io-client-universal-mtop';
|
||||
139
packages/datasource-engine/src/utils.ts
Normal file
139
packages/datasource-engine/src/utils.ts
Normal file
@ -0,0 +1,139 @@
|
||||
/* eslint-disable no-new-func */
|
||||
import {
|
||||
JSExpression,
|
||||
IRuntimeContext,
|
||||
CompositeValue,
|
||||
JSFunction,
|
||||
JSONObject,
|
||||
isJSExpression,
|
||||
DataSourceConfig,
|
||||
isJSFunction,
|
||||
} from '@ali/build-success-types';
|
||||
|
||||
export const transformExpression = (code: string, context: IRuntimeContext) => {
|
||||
try {
|
||||
return new Function(`return (${code})`).call(context);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`transformExpression error, code is ${code}, context is ${context}, error is ${error}`,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const transformFunction = (code: string, context: IRuntimeContext) => {
|
||||
try {
|
||||
return new Function(`return (${code})`).call(context).bind(context);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`transformFunction error, code is ${code}, context is ${context}, error is ${error}`,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const transformBoolStr = (str: string) => {
|
||||
return str !== 'false';
|
||||
};
|
||||
|
||||
export const getRuntimeJsValue = (
|
||||
value: JSExpression | JSFunction,
|
||||
context: IRuntimeContext,
|
||||
) => {
|
||||
if (!['JSExpression', 'JSFunction'].includes(value.type)) {
|
||||
console.error(`translate error, value is ${JSON.stringify(value)}`);
|
||||
return '';
|
||||
}
|
||||
const code = value.compiled || value.value;
|
||||
return value.type === 'JSFunction'
|
||||
? transformFunction(code, context)
|
||||
: transformExpression(code, context);
|
||||
};
|
||||
|
||||
export const getRuntimeBaseValue = (type: string, value: any) => {
|
||||
switch (type) {
|
||||
case 'string':
|
||||
return `${value}`;
|
||||
case 'boolean':
|
||||
return typeof value === 'string'
|
||||
? transformBoolStr(value as string)
|
||||
: value;
|
||||
case 'number':
|
||||
return Number(value);
|
||||
default:
|
||||
return value;
|
||||
}
|
||||
};
|
||||
|
||||
export const getRuntimeValueFromConfig = (
|
||||
type: string,
|
||||
value: CompositeValue,
|
||||
context: IRuntimeContext,
|
||||
) => {
|
||||
if (!value) return undefined;
|
||||
if (isJSExpression(value) || isJSFunction(value)) {
|
||||
return getRuntimeBaseValue(type, getRuntimeJsValue(value, context));
|
||||
}
|
||||
return getRuntimeBaseValue(type, value);
|
||||
};
|
||||
|
||||
export const buildJsonObj = (
|
||||
params: JSONObject | JSExpression,
|
||||
context: IRuntimeContext,
|
||||
) => {
|
||||
const result: Record<string, any> = {};
|
||||
if (isJSExpression(params)) {
|
||||
return transformExpression(params.value, context);
|
||||
}
|
||||
Object.keys(params).forEach((key: string) => {
|
||||
const currentParam: any = params[key];
|
||||
if (isJSExpression(currentParam)) {
|
||||
result[key] = transformExpression(currentParam.value, context);
|
||||
} else {
|
||||
result[key] = getRuntimeBaseValue(currentParam.type, currentParam.value);
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
export const buildShouldFetch = (
|
||||
ds: DataSourceConfig,
|
||||
context: IRuntimeContext,
|
||||
) => {
|
||||
if (!ds.options || !ds.shouldFetch) {
|
||||
return true; // 默认为 true
|
||||
}
|
||||
if (isJSExpression(ds.shouldFetch) || isJSFunction(ds.shouldFetch)) {
|
||||
return getRuntimeJsValue(ds.shouldFetch, context);
|
||||
}
|
||||
|
||||
return getRuntimeBaseValue('boolean', ds.shouldFetch);
|
||||
};
|
||||
|
||||
export const buildOptions = (
|
||||
ds: DataSourceConfig,
|
||||
context: IRuntimeContext,
|
||||
) => {
|
||||
const { options } = ds;
|
||||
if (!options) return undefined;
|
||||
return function () {
|
||||
return {
|
||||
uri: getRuntimeValueFromConfig('string', options.uri, context),
|
||||
params: options.params ? buildJsonObj(options.params, context) : {},
|
||||
method: options.method
|
||||
? getRuntimeValueFromConfig('string', options.method, context)
|
||||
: 'GET',
|
||||
isCors: options.isCors
|
||||
? getRuntimeValueFromConfig('boolean', options.isCors, context)
|
||||
: true,
|
||||
timeout: options.timeout
|
||||
? getRuntimeValueFromConfig('number', options.timeout, context)
|
||||
: 5000,
|
||||
headers: options.headers
|
||||
? buildJsonObj(options.headers, context)
|
||||
: undefined,
|
||||
v: options.v
|
||||
? getRuntimeValueFromConfig('string', options.v, context)
|
||||
: '1.0',
|
||||
};
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,25 @@
|
||||
export function bindRuntimeContext<T, U>(x: T, ctx: U): T {
|
||||
if (typeof x === 'function') {
|
||||
return x.bind(ctx);
|
||||
}
|
||||
|
||||
if (typeof x !== 'object') {
|
||||
return x;
|
||||
}
|
||||
|
||||
if (x === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Array.isArray(x)) {
|
||||
return (x.map((item) => bindRuntimeContext(item, ctx)) as unknown) as T;
|
||||
}
|
||||
|
||||
const res = {} as Record<string, unknown>;
|
||||
|
||||
Object.entries(x).forEach(([k, v]) => {
|
||||
res[k] = bindRuntimeContext(v, ctx);
|
||||
});
|
||||
|
||||
return (res as unknown) as T;
|
||||
}
|
||||
3
packages/datasource-engine/test/_helpers/delay.ts
Normal file
3
packages/datasource-engine/test/_helpers/delay.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export async function delay(ms: number = 0) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
3
packages/datasource-engine/test/_helpers/index.ts
Normal file
3
packages/datasource-engine/test/_helpers/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from './mock-context';
|
||||
export * from './delay';
|
||||
export * from './bind-runtime-context';
|
||||
50
packages/datasource-engine/test/_helpers/mock-context.ts
Normal file
50
packages/datasource-engine/test/_helpers/mock-context.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import { IDataSourceEngine, IRuntimeContext } from '@ali/build-success-types';
|
||||
|
||||
export class MockContext<TState extends object = Record<string, unknown>>
|
||||
implements IRuntimeContext<TState> {
|
||||
private _dataSourceEngine: IDataSourceEngine;
|
||||
|
||||
public constructor(
|
||||
private _state: TState,
|
||||
private _createDataSourceEngine: (
|
||||
context: IRuntimeContext<TState>
|
||||
) => IDataSourceEngine,
|
||||
private _customMethods: Record<string, () => any> = {}
|
||||
) {
|
||||
this._dataSourceEngine = _createDataSourceEngine(this);
|
||||
|
||||
// 自定义方法
|
||||
Object.assign(this, _customMethods);
|
||||
}
|
||||
|
||||
public get state() {
|
||||
return this._state;
|
||||
}
|
||||
|
||||
public setState(state: Partial<TState>) {
|
||||
this._state = {
|
||||
...this._state,
|
||||
...state,
|
||||
};
|
||||
}
|
||||
|
||||
public get dataSourceMap() {
|
||||
return this._dataSourceEngine.dataSourceMap;
|
||||
}
|
||||
|
||||
public async reloadDataSource(): Promise<void> {
|
||||
this._dataSourceEngine.reloadDataSource();
|
||||
}
|
||||
|
||||
public get page(): any {
|
||||
throw new Error('this.page should not be accessed by datasource-engine');
|
||||
}
|
||||
|
||||
public get component(): any {
|
||||
throw new Error(
|
||||
'this.component should not be accessed by datasource-engine'
|
||||
);
|
||||
}
|
||||
|
||||
[customMethod: string]: any;
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
# 关于此场景
|
||||
|
||||
这个是一个及其简单的场景 -- 就是直接调用 fetch,没有啥 dataHandler 之类的
|
||||
@ -0,0 +1,15 @@
|
||||
import { RuntimeDataSource } from '@ali/build-success-types';
|
||||
|
||||
// 这里仅仅是数据源部分的:
|
||||
// @see: https://yuque.antfin-inc.com/mo/spec/spec-low-code-building-schema#XMeF5
|
||||
export const dataSource: RuntimeDataSource = {
|
||||
list: [
|
||||
{
|
||||
id: 'user',
|
||||
type: 'fetch',
|
||||
options: () => ({
|
||||
uri: 'https://mocks.alibaba-inc.com/user.json',
|
||||
}),
|
||||
},
|
||||
],
|
||||
};
|
||||
@ -0,0 +1,15 @@
|
||||
import { DataSource } from '@ali/build-success-types';
|
||||
|
||||
// 这里仅仅是数据源部分的 schema:
|
||||
// @see: https://yuque.antfin-inc.com/mo/spec/spec-low-code-building-schema#XMeF5
|
||||
export const DATA_SOURCE_SCHEMA: DataSource = {
|
||||
list: [
|
||||
{
|
||||
id: 'user',
|
||||
type: 'fetch',
|
||||
options: {
|
||||
uri: 'https://mocks.alibaba-inc.com/user.json',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
@ -0,0 +1,72 @@
|
||||
import {
|
||||
DataSource,
|
||||
IDataSourceEngine,
|
||||
IRuntimeContext,
|
||||
RuntimeDataSource,
|
||||
RuntimeDataSourceStatus,
|
||||
} from '@ali/build-success-types';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import { delay, MockContext } from '../../_helpers';
|
||||
// import { DATA_SOURCE_SCHEMA } from './_datasource-schema';
|
||||
|
||||
import type { ExecutionContext, Macro } from 'ava';
|
||||
import type { SinonFakeTimers } from 'sinon';
|
||||
|
||||
export const abnormalScene: Macro<[
|
||||
{
|
||||
create: (
|
||||
dataSource: any,
|
||||
ctx: IRuntimeContext,
|
||||
options: any
|
||||
) => IDataSourceEngine;
|
||||
dataSource: RuntimeDataSource | DataSource;
|
||||
}
|
||||
]> = async (
|
||||
t: ExecutionContext<{ clock: SinonFakeTimers }>,
|
||||
{ create, dataSource },
|
||||
) => {
|
||||
const { clock } = t.context;
|
||||
const ERROR_MSG = 'test error';
|
||||
const fetchHandler = sinon.fake(async () => {
|
||||
await delay(100);
|
||||
throw new Error(ERROR_MSG);
|
||||
});
|
||||
|
||||
const context = new MockContext<Record<string, unknown>>({}, (ctx) => create(dataSource, ctx, {
|
||||
requestHandlersMap: {
|
||||
fetch: fetchHandler,
|
||||
},
|
||||
}));
|
||||
|
||||
const setState = sinon.spy(context, 'setState');
|
||||
|
||||
// 一开始应该是初始状态
|
||||
t.is(context.dataSourceMap.user.status, RuntimeDataSourceStatus.Initial);
|
||||
|
||||
const loading = context.reloadDataSource();
|
||||
|
||||
await clock.tickAsync(50);
|
||||
|
||||
// 中间应该有 loading 态
|
||||
t.is(context.dataSourceMap.user.status, RuntimeDataSourceStatus.Loading);
|
||||
|
||||
await Promise.all([clock.runAllAsync(), loading]);
|
||||
|
||||
// 最后应该失败了,error 状态
|
||||
t.is(context.dataSourceMap.user.status, RuntimeDataSourceStatus.Error);
|
||||
|
||||
// 检查数据源的数据
|
||||
t.deepEqual(context.dataSourceMap.user.data, undefined);
|
||||
t.not(context.dataSourceMap.user.error, undefined);
|
||||
t.regex(context.dataSourceMap.user.error!.message, new RegExp(ERROR_MSG));
|
||||
|
||||
// 检查状态数据
|
||||
t.assert(setState.notCalled);
|
||||
t.deepEqual(context.state.user, undefined);
|
||||
|
||||
// fetchHandler 不应该被调
|
||||
t.assert(fetchHandler.calledOnce);
|
||||
};
|
||||
|
||||
abnormalScene.title = (providedTitle) => providedTitle || 'abnormal scene';
|
||||
@ -0,0 +1,81 @@
|
||||
import {
|
||||
DataSource,
|
||||
IDataSourceEngine,
|
||||
IRuntimeContext,
|
||||
RuntimeDataSource,
|
||||
RuntimeDataSourceStatus,
|
||||
} from '@ali/build-success-types';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import { delay, MockContext } from '../../_helpers';
|
||||
import { DATA_SOURCE_SCHEMA } from './_datasource-schema';
|
||||
|
||||
import type { ExecutionContext, Macro } from 'ava';
|
||||
import type { SinonFakeTimers } from 'sinon';
|
||||
|
||||
export const normalScene: Macro<[
|
||||
{
|
||||
create: (
|
||||
dataSource: any,
|
||||
ctx: IRuntimeContext,
|
||||
options: any
|
||||
) => IDataSourceEngine;
|
||||
dataSource: RuntimeDataSource | DataSource;
|
||||
}
|
||||
]> = async (
|
||||
t: ExecutionContext<{ clock: SinonFakeTimers }>,
|
||||
{ create, dataSource },
|
||||
) => {
|
||||
const { clock } = t.context;
|
||||
|
||||
const USER_DATA = {
|
||||
name: 'Alice',
|
||||
age: 18,
|
||||
};
|
||||
|
||||
const fetchHandler = sinon.fake(async () => {
|
||||
await delay(100);
|
||||
return { data: USER_DATA };
|
||||
});
|
||||
|
||||
const context = new MockContext<Record<string, unknown>>({}, (ctx) => create(dataSource, ctx, {
|
||||
requestHandlersMap: {
|
||||
fetch: fetchHandler,
|
||||
},
|
||||
}));
|
||||
|
||||
const setState = sinon.spy(context, 'setState');
|
||||
|
||||
// 一开始应该是初始状态
|
||||
t.is(context.dataSourceMap.user.status, RuntimeDataSourceStatus.Initial);
|
||||
|
||||
const loading = context.reloadDataSource();
|
||||
|
||||
await clock.tickAsync(50);
|
||||
|
||||
// 中间应该有 loading 态
|
||||
t.is(context.dataSourceMap.user.status, RuntimeDataSourceStatus.Loading);
|
||||
|
||||
await Promise.all([clock.runAllAsync(), loading]);
|
||||
|
||||
// 最后应该成功了,loaded 状态
|
||||
t.is(context.dataSourceMap.user.status, RuntimeDataSourceStatus.Loaded);
|
||||
|
||||
// 检查数据源的数据
|
||||
t.deepEqual(context.dataSourceMap.user.data, USER_DATA);
|
||||
t.deepEqual(context.dataSourceMap.user.error, undefined);
|
||||
|
||||
// 检查状态数据
|
||||
t.assert(setState.calledOnce);
|
||||
t.deepEqual(context.state.user, USER_DATA);
|
||||
|
||||
// fetchHandler 应该被调用了一次
|
||||
t.assert(fetchHandler.calledOnce);
|
||||
|
||||
const firstListItemOptions = DATA_SOURCE_SCHEMA.list[0].options;
|
||||
const fetchHandlerCallArgs = fetchHandler.firstCall.args[0];
|
||||
// 检查调用参数
|
||||
t.is(firstListItemOptions.uri, fetchHandlerCallArgs.uri);
|
||||
};
|
||||
|
||||
normalScene.title = (providedTitle) => providedTitle || 'normal scene';
|
||||
@ -0,0 +1,22 @@
|
||||
import test, { ExecutionContext } from 'ava';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import { create } from '../../../src/interpret';
|
||||
|
||||
import { DATA_SOURCE_SCHEMA } from './_datasource-schema';
|
||||
import { abnormalScene } from './_macro-abnormal';
|
||||
|
||||
import type { SinonFakeTimers } from 'sinon';
|
||||
|
||||
test.before((t: ExecutionContext<{ clock: SinonFakeTimers }>) => {
|
||||
t.context.clock = sinon.useFakeTimers();
|
||||
});
|
||||
|
||||
test.after((t: ExecutionContext<{ clock: SinonFakeTimers }>) => {
|
||||
t.context.clock.restore();
|
||||
});
|
||||
|
||||
test(abnormalScene, {
|
||||
create,
|
||||
dataSource: DATA_SOURCE_SCHEMA,
|
||||
});
|
||||
@ -0,0 +1,20 @@
|
||||
import test, { ExecutionContext } from 'ava';
|
||||
import sinon, { SinonFakeTimers } from 'sinon';
|
||||
|
||||
import { create } from '../../../src/runtime';
|
||||
|
||||
import { dataSource } from './_datasource-runtime';
|
||||
import { abnormalScene } from './_macro-abnormal';
|
||||
|
||||
test.before((t: ExecutionContext<{ clock: SinonFakeTimers }>) => {
|
||||
t.context.clock = sinon.useFakeTimers();
|
||||
});
|
||||
|
||||
test.after((t: ExecutionContext<{ clock: SinonFakeTimers }>) => {
|
||||
t.context.clock.restore();
|
||||
});
|
||||
|
||||
test(abnormalScene, {
|
||||
create,
|
||||
dataSource,
|
||||
});
|
||||
@ -0,0 +1,20 @@
|
||||
import test, { ExecutionContext } from 'ava';
|
||||
import sinon, { SinonFakeTimers } from 'sinon';
|
||||
|
||||
import { create } from '../../../src/interpret';
|
||||
|
||||
import { DATA_SOURCE_SCHEMA } from './_datasource-schema';
|
||||
import { normalScene } from './_macro-normal';
|
||||
|
||||
test.before((t: ExecutionContext<{ clock: SinonFakeTimers }>) => {
|
||||
t.context.clock = sinon.useFakeTimers();
|
||||
});
|
||||
|
||||
test.after((t: ExecutionContext<{ clock: SinonFakeTimers }>) => {
|
||||
t.context.clock.restore();
|
||||
});
|
||||
|
||||
test(normalScene, {
|
||||
create,
|
||||
dataSource: DATA_SOURCE_SCHEMA,
|
||||
});
|
||||
@ -0,0 +1,20 @@
|
||||
import test, { ExecutionContext } from 'ava';
|
||||
import sinon, { SinonFakeTimers } from 'sinon';
|
||||
|
||||
import { create } from '../../../src/runtime';
|
||||
|
||||
import { dataSource } from './_datasource-runtime';
|
||||
import { normalScene } from './_macro-normal';
|
||||
|
||||
test.before((t: ExecutionContext<{ clock: SinonFakeTimers }>) => {
|
||||
t.context.clock = sinon.useFakeTimers();
|
||||
});
|
||||
|
||||
test.after((t: ExecutionContext<{ clock: SinonFakeTimers }>) => {
|
||||
t.context.clock.restore();
|
||||
});
|
||||
|
||||
test(normalScene, {
|
||||
create,
|
||||
dataSource,
|
||||
});
|
||||
@ -0,0 +1,3 @@
|
||||
# 关于此场景
|
||||
|
||||
有些场景下,多个数据源之间有依赖关系,这时候可以将其都设置为 `isSync: true`, 这样这些数据源就会按配置面板的顺序进行串行调用。
|
||||
@ -0,0 +1,33 @@
|
||||
import { RuntimeDataSource } from '@ali/build-success-types';
|
||||
|
||||
// 这里仅仅是数据源部分的:
|
||||
// @see: https://yuque.antfin-inc.com/mo/spec/spec-low-code-building-schema#XMeF5
|
||||
export const dataSource: RuntimeDataSource = {
|
||||
list: [
|
||||
{
|
||||
id: 'user',
|
||||
isInit: true,
|
||||
type: 'fetch',
|
||||
isSync: true,
|
||||
options() {
|
||||
return {
|
||||
uri: 'https://mocks.alibaba-inc.com/user.json',
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'orders',
|
||||
isInit: true,
|
||||
type: 'fetch',
|
||||
isSync: true,
|
||||
options() {
|
||||
return {
|
||||
uri: 'https://mocks.alibaba-inc.com/orders.json',
|
||||
params: {
|
||||
userId: this.state.user.id,
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
@ -0,0 +1,30 @@
|
||||
import { DataSource } from '@ali/build-success-types';
|
||||
|
||||
// 这里仅仅是数据源部分的 schema:
|
||||
// @see: https://yuque.antfin-inc.com/mo/spec/spec-low-code-building-schema#XMeF5
|
||||
export const DATA_SOURCE_SCHEMA: DataSource = {
|
||||
list: [
|
||||
{
|
||||
id: 'user',
|
||||
isInit: true,
|
||||
type: 'fetch',
|
||||
isSync: true,
|
||||
options: {
|
||||
uri: 'https://mocks.alibaba-inc.com/user.json',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'orders',
|
||||
isInit: true,
|
||||
type: 'fetch',
|
||||
isSync: true,
|
||||
options: {
|
||||
uri: 'https://mocks.alibaba-inc.com/orders.json',
|
||||
params: {
|
||||
type: 'JSExpression',
|
||||
value: '{ userId: this.state.user.id }',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
@ -0,0 +1,96 @@
|
||||
import {
|
||||
DataSource,
|
||||
IDataSourceEngine,
|
||||
IRuntimeContext,
|
||||
RuntimeDataSource,
|
||||
RuntimeDataSourceStatus,
|
||||
} from '@ali/build-success-types';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import { bindRuntimeContext, delay, MockContext } from '../../_helpers';
|
||||
import { DATA_SOURCE_SCHEMA } from './_datasource-schema';
|
||||
|
||||
import type { ExecutionContext, Macro } from 'ava';
|
||||
import type { SinonFakeTimers } from 'sinon';
|
||||
|
||||
export const abnormalScene: Macro<[
|
||||
{
|
||||
create: (
|
||||
dataSource: any,
|
||||
ctx: IRuntimeContext,
|
||||
options: any
|
||||
) => IDataSourceEngine;
|
||||
dataSource: RuntimeDataSource | DataSource;
|
||||
}
|
||||
]> = async (
|
||||
t: ExecutionContext<{ clock: SinonFakeTimers }>,
|
||||
{ create, dataSource },
|
||||
) => {
|
||||
const { clock } = t.context;
|
||||
|
||||
const USER_DATA = {
|
||||
id: 9527,
|
||||
name: 'Alice',
|
||||
};
|
||||
const ERROR_MSG = 'test error';
|
||||
const fetchHandler = sinon.fake(async ({ uri }) => {
|
||||
await delay(100);
|
||||
if (/user/.test(uri)) {
|
||||
return { data: USER_DATA };
|
||||
} else {
|
||||
throw new Error(ERROR_MSG);
|
||||
}
|
||||
});
|
||||
|
||||
const context = new MockContext<Record<string, unknown>>({}, (ctx) => create(bindRuntimeContext(dataSource, ctx), ctx, {
|
||||
requestHandlersMap: {
|
||||
fetch: fetchHandler,
|
||||
},
|
||||
}));
|
||||
|
||||
const setState = sinon.spy(context, 'setState');
|
||||
|
||||
// 一开始应该是初始状态
|
||||
t.is(context.dataSourceMap.user.status, RuntimeDataSourceStatus.Initial);
|
||||
t.is(context.dataSourceMap.orders.status, RuntimeDataSourceStatus.Initial);
|
||||
|
||||
const loading = context.reloadDataSource();
|
||||
|
||||
await clock.tickAsync(50);
|
||||
|
||||
// 中间应该有 loading 态
|
||||
t.is(context.dataSourceMap.user.status, RuntimeDataSourceStatus.Loading);
|
||||
|
||||
await clock.tickAsync(50);
|
||||
|
||||
t.is(context.dataSourceMap.orders.status, RuntimeDataSourceStatus.Loading);
|
||||
|
||||
await Promise.all([clock.runAllAsync(), loading]);
|
||||
|
||||
// 最后 user 应该成功了,loaded
|
||||
t.is(context.dataSourceMap.user.status, RuntimeDataSourceStatus.Loaded);
|
||||
// 最后 orders 应该失败了,error 状态
|
||||
t.is(context.dataSourceMap.orders.status, RuntimeDataSourceStatus.Error);
|
||||
|
||||
// 检查数据源的数据
|
||||
t.deepEqual(context.dataSourceMap.user.data, USER_DATA);
|
||||
t.is(context.dataSourceMap.user.error, undefined);
|
||||
t.deepEqual(context.dataSourceMap.orders.data, undefined);
|
||||
t.not(context.dataSourceMap.orders.error, undefined);
|
||||
t.regex(context.dataSourceMap.orders.error!.message, new RegExp(ERROR_MSG));
|
||||
|
||||
// 检查状态数据
|
||||
t.assert(setState.calledOnce);
|
||||
t.deepEqual(context.state.user, USER_DATA);
|
||||
t.is(context.state.orders, undefined);
|
||||
|
||||
// fetchHandler 应该被调用了2次
|
||||
t.assert(fetchHandler.calledTwice);
|
||||
|
||||
const firstListItemOptions = DATA_SOURCE_SCHEMA.list[0].options;
|
||||
const fetchHandlerCallArgs = fetchHandler.firstCall.args[0];
|
||||
// 检查调用参数
|
||||
t.is(firstListItemOptions.uri, fetchHandlerCallArgs.uri);
|
||||
};
|
||||
|
||||
abnormalScene.title = (providedTitle) => providedTitle || 'abnormal scene';
|
||||
@ -0,0 +1,92 @@
|
||||
import {
|
||||
DataSource,
|
||||
IDataSourceEngine,
|
||||
IRuntimeContext,
|
||||
RuntimeDataSource,
|
||||
RuntimeDataSourceStatus,
|
||||
} from '@ali/build-success-types';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import { bindRuntimeContext, delay, MockContext } from '../../_helpers';
|
||||
import { DATA_SOURCE_SCHEMA } from './_datasource-schema';
|
||||
|
||||
import type { ExecutionContext, Macro } from 'ava';
|
||||
import type { SinonFakeTimers } from 'sinon';
|
||||
|
||||
export const normalScene: Macro<[
|
||||
{
|
||||
create: (
|
||||
dataSource: any,
|
||||
ctx: IRuntimeContext,
|
||||
options: any
|
||||
) => IDataSourceEngine;
|
||||
dataSource: RuntimeDataSource | DataSource;
|
||||
}
|
||||
]> = async (
|
||||
t: ExecutionContext<{ clock: SinonFakeTimers }>,
|
||||
{ create, dataSource },
|
||||
) => {
|
||||
const { clock } = t.context;
|
||||
|
||||
const USER_DATA = {
|
||||
id: 9527,
|
||||
name: 'Alice',
|
||||
};
|
||||
|
||||
const ORDERS_DATA = [{ id: 123 }, { id: 456 }];
|
||||
|
||||
const fetchHandler = sinon.fake(async ({ uri }) => {
|
||||
await delay(100);
|
||||
return { data: /user/.test(uri) ? USER_DATA : ORDERS_DATA };
|
||||
});
|
||||
|
||||
const context = new MockContext<Record<string, unknown>>({}, (ctx) => create(bindRuntimeContext(dataSource, ctx), ctx, {
|
||||
requestHandlersMap: {
|
||||
fetch: fetchHandler,
|
||||
},
|
||||
}));
|
||||
|
||||
const setState = sinon.spy(context, 'setState');
|
||||
|
||||
// 一开始应该是初始状态
|
||||
t.is(context.dataSourceMap.user.status, RuntimeDataSourceStatus.Initial);
|
||||
t.is(context.dataSourceMap.orders.status, RuntimeDataSourceStatus.Initial);
|
||||
|
||||
const loading = context.reloadDataSource();
|
||||
|
||||
await clock.tickAsync(50);
|
||||
|
||||
// 中间应该有 loading 态
|
||||
t.is(context.dataSourceMap.user.status, RuntimeDataSourceStatus.Loading);
|
||||
|
||||
await clock.tickAsync(50);
|
||||
|
||||
t.is(context.dataSourceMap.orders.status, RuntimeDataSourceStatus.Loading);
|
||||
|
||||
await Promise.all([clock.runAllAsync(), loading]);
|
||||
|
||||
// 最后应该成功了,loaded 状态
|
||||
t.is(context.dataSourceMap.user.status, RuntimeDataSourceStatus.Loaded);
|
||||
t.is(context.dataSourceMap.orders.status, RuntimeDataSourceStatus.Loaded);
|
||||
|
||||
// 检查数据源的数据
|
||||
t.deepEqual(context.dataSourceMap.user.data, USER_DATA);
|
||||
t.deepEqual(context.dataSourceMap.user.error, undefined);
|
||||
t.deepEqual(context.dataSourceMap.orders.data, ORDERS_DATA);
|
||||
t.deepEqual(context.dataSourceMap.orders.error, undefined);
|
||||
|
||||
// 检查状态数据
|
||||
t.assert(setState.calledTwice);
|
||||
t.deepEqual(context.state.user, USER_DATA);
|
||||
t.deepEqual(context.state.orders, ORDERS_DATA);
|
||||
|
||||
// fetchHandler 应该被调用了2次
|
||||
t.assert(fetchHandler.calledTwice);
|
||||
|
||||
// 检查调用参数
|
||||
const firstListItemOptions = DATA_SOURCE_SCHEMA.list[0].options;
|
||||
const fetchHandlerCallArgs = fetchHandler.firstCall.args[0];
|
||||
t.is(firstListItemOptions.uri, fetchHandlerCallArgs.uri);
|
||||
};
|
||||
|
||||
normalScene.title = (providedTitle) => providedTitle || 'normal scene';
|
||||
@ -0,0 +1,22 @@
|
||||
import test, { ExecutionContext } from 'ava';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import { create } from '../../../src/interpret';
|
||||
|
||||
import { DATA_SOURCE_SCHEMA } from './_datasource-schema';
|
||||
import { abnormalScene } from './_macro-abnormal';
|
||||
|
||||
import type { SinonFakeTimers } from 'sinon';
|
||||
|
||||
test.before((t: ExecutionContext<{ clock: SinonFakeTimers }>) => {
|
||||
t.context.clock = sinon.useFakeTimers();
|
||||
});
|
||||
|
||||
test.after((t: ExecutionContext<{ clock: SinonFakeTimers }>) => {
|
||||
t.context.clock.restore();
|
||||
});
|
||||
|
||||
test(abnormalScene, {
|
||||
create,
|
||||
dataSource: DATA_SOURCE_SCHEMA,
|
||||
});
|
||||
@ -0,0 +1,20 @@
|
||||
import test, { ExecutionContext } from 'ava';
|
||||
import sinon, { SinonFakeTimers } from 'sinon';
|
||||
|
||||
import { create } from '../../../src/runtime';
|
||||
|
||||
import { dataSource } from './_datasource-runtime';
|
||||
import { abnormalScene } from './_macro-abnormal';
|
||||
|
||||
test.before((t: ExecutionContext<{ clock: SinonFakeTimers }>) => {
|
||||
t.context.clock = sinon.useFakeTimers();
|
||||
});
|
||||
|
||||
test.after((t: ExecutionContext<{ clock: SinonFakeTimers }>) => {
|
||||
t.context.clock.restore();
|
||||
});
|
||||
|
||||
test(abnormalScene, {
|
||||
create,
|
||||
dataSource,
|
||||
});
|
||||
@ -0,0 +1,20 @@
|
||||
import test, { ExecutionContext } from 'ava';
|
||||
import sinon, { SinonFakeTimers } from 'sinon';
|
||||
|
||||
import { create } from '../../../src/interpret';
|
||||
|
||||
import { DATA_SOURCE_SCHEMA } from './_datasource-schema';
|
||||
import { normalScene } from './_macro-normal';
|
||||
|
||||
test.before((t: ExecutionContext<{ clock: SinonFakeTimers }>) => {
|
||||
t.context.clock = sinon.useFakeTimers();
|
||||
});
|
||||
|
||||
test.after((t: ExecutionContext<{ clock: SinonFakeTimers }>) => {
|
||||
t.context.clock.restore();
|
||||
});
|
||||
|
||||
test(normalScene, {
|
||||
create,
|
||||
dataSource: DATA_SOURCE_SCHEMA,
|
||||
});
|
||||
@ -0,0 +1,20 @@
|
||||
import test, { ExecutionContext } from 'ava';
|
||||
import sinon, { SinonFakeTimers } from 'sinon';
|
||||
|
||||
import { create } from '../../../src/runtime';
|
||||
|
||||
import { dataSource } from './_datasource-runtime';
|
||||
import { normalScene } from './_macro-normal';
|
||||
|
||||
test.before((t: ExecutionContext<{ clock: SinonFakeTimers }>) => {
|
||||
t.context.clock = sinon.useFakeTimers();
|
||||
});
|
||||
|
||||
test.after((t: ExecutionContext<{ clock: SinonFakeTimers }>) => {
|
||||
t.context.clock.restore();
|
||||
});
|
||||
|
||||
test(normalScene, {
|
||||
create,
|
||||
dataSource,
|
||||
});
|
||||
@ -0,0 +1,3 @@
|
||||
# 关于此场景
|
||||
|
||||
这个是一个及其简单的场景 -- 就是直接调用 fetch,没有啥 dataHandler 之类的
|
||||
@ -0,0 +1,13 @@
|
||||
import { RuntimeDataSource } from '@ali/build-success-types';
|
||||
|
||||
// 这里仅仅是数据源部分的:
|
||||
// @see: https://yuque.antfin-inc.com/mo/spec/spec-low-code-building-schema#XMeF5
|
||||
export const dataSource: RuntimeDataSource = {
|
||||
list: [
|
||||
{
|
||||
id: 'urlParams',
|
||||
isInit: true,
|
||||
type: 'urlParams',
|
||||
},
|
||||
],
|
||||
};
|
||||
@ -0,0 +1,13 @@
|
||||
import { DataSource } from '@ali/build-success-types';
|
||||
|
||||
// 这里仅仅是数据源部分的 schema:
|
||||
// @see: https://yuque.antfin-inc.com/mo/spec/spec-low-code-building-schema#XMeF5
|
||||
export const DATA_SOURCE_SCHEMA: DataSource = {
|
||||
list: [
|
||||
{
|
||||
id: 'urlParams',
|
||||
isInit: true,
|
||||
type: 'urlParams',
|
||||
},
|
||||
],
|
||||
};
|
||||
@ -0,0 +1,72 @@
|
||||
import {
|
||||
DataSource,
|
||||
IDataSourceEngine,
|
||||
IRuntimeContext,
|
||||
RuntimeDataSource,
|
||||
RuntimeDataSourceStatus,
|
||||
} from '@ali/build-success-types';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import { MockContext } from '../../_helpers';
|
||||
|
||||
import type { ExecutionContext, Macro } from 'ava';
|
||||
import type { SinonFakeTimers } from 'sinon';
|
||||
|
||||
export const normalScene: Macro<[
|
||||
{
|
||||
create: (
|
||||
dataSource: any,
|
||||
ctx: IRuntimeContext,
|
||||
options: any
|
||||
) => IDataSourceEngine;
|
||||
dataSource: RuntimeDataSource | DataSource;
|
||||
}
|
||||
]> = async (
|
||||
t: ExecutionContext<{ clock: SinonFakeTimers }>,
|
||||
{ create, dataSource },
|
||||
) => {
|
||||
const { clock } = t.context;
|
||||
|
||||
const URL_PARAMS = {
|
||||
name: 'Alice',
|
||||
age: '18',
|
||||
};
|
||||
|
||||
const urlParamsHandler = sinon.fake(async () => {
|
||||
return URL_PARAMS; // TODO: 别的都是返回的套了一层 data 的,但是 urlParams 的为啥不一样?
|
||||
});
|
||||
|
||||
const context = new MockContext<Record<string, unknown>>({}, (ctx) => create(dataSource, ctx, {
|
||||
requestHandlersMap: {
|
||||
urlParams: urlParamsHandler,
|
||||
},
|
||||
}));
|
||||
|
||||
const setState = sinon.spy(context, 'setState');
|
||||
|
||||
// 一开始应该是初始状态
|
||||
t.is(context.dataSourceMap.urlParams.status, RuntimeDataSourceStatus.Initial);
|
||||
|
||||
const loading = context.reloadDataSource();
|
||||
|
||||
await Promise.all([clock.runAllAsync(), loading]);
|
||||
|
||||
// 最后应该成功了,loaded 状态
|
||||
t.is(context.dataSourceMap.urlParams.status, RuntimeDataSourceStatus.Loaded);
|
||||
|
||||
// 检查数据源的数据
|
||||
t.deepEqual(context.dataSourceMap.urlParams.data, URL_PARAMS);
|
||||
t.deepEqual(context.dataSourceMap.urlParams.error, undefined);
|
||||
|
||||
// 检查状态数据
|
||||
t.assert(setState.calledOnce);
|
||||
t.deepEqual(context.state.urlParams, URL_PARAMS);
|
||||
|
||||
// fetchHandler 应该被调用了一次
|
||||
t.assert(urlParamsHandler.calledOnce);
|
||||
|
||||
// 检查调用参数 url 没有 options
|
||||
t.deepEqual(urlParamsHandler.firstCall.args, [context]);
|
||||
};
|
||||
|
||||
normalScene.title = (providedTitle) => providedTitle || 'normal scene';
|
||||
@ -0,0 +1,20 @@
|
||||
import test, { ExecutionContext } from 'ava';
|
||||
import sinon, { SinonFakeTimers } from 'sinon';
|
||||
|
||||
import { create } from '../../../src/interpret';
|
||||
|
||||
import { DATA_SOURCE_SCHEMA } from './_datasource-schema';
|
||||
import { normalScene } from './_macro-normal';
|
||||
|
||||
test.before((t: ExecutionContext<{ clock: SinonFakeTimers }>) => {
|
||||
t.context.clock = sinon.useFakeTimers();
|
||||
});
|
||||
|
||||
test.after((t: ExecutionContext<{ clock: SinonFakeTimers }>) => {
|
||||
t.context.clock.restore();
|
||||
});
|
||||
|
||||
test(normalScene, {
|
||||
create,
|
||||
dataSource: DATA_SOURCE_SCHEMA,
|
||||
});
|
||||
@ -0,0 +1,20 @@
|
||||
import test, { ExecutionContext } from 'ava';
|
||||
import sinon, { SinonFakeTimers } from 'sinon';
|
||||
|
||||
import { create } from '../../../src/runtime';
|
||||
|
||||
import { dataSource } from './_datasource-runtime';
|
||||
import { normalScene } from './_macro-normal';
|
||||
|
||||
test.before((t: ExecutionContext<{ clock: SinonFakeTimers }>) => {
|
||||
t.context.clock = sinon.useFakeTimers();
|
||||
});
|
||||
|
||||
test.after((t: ExecutionContext<{ clock: SinonFakeTimers }>) => {
|
||||
t.context.clock.restore();
|
||||
});
|
||||
|
||||
test(normalScene, {
|
||||
create,
|
||||
dataSource,
|
||||
});
|
||||
@ -0,0 +1,26 @@
|
||||
# 关于此场景
|
||||
|
||||
这个是一个很常见的场景 -- 查出来的数据里面套的还有一层数据,可能有异常状态得需要处理下。
|
||||
|
||||
比如,期望的正常的数据应该是:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
而异常场景下,服务端会返回:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"message": "这是错误原因",
|
||||
"code": "错误码"
|
||||
}
|
||||
```
|
||||
|
||||
-- 这时候期望有异常监控埋点。
|
||||
@ -0,0 +1,30 @@
|
||||
import { RuntimeDataSource } from '@ali/build-success-types';
|
||||
|
||||
// 这里仅仅是数据源部分的:
|
||||
// @see: https://yuque.antfin-inc.com/mo/spec/spec-low-code-building-schema#XMeF5
|
||||
export const dataSource: RuntimeDataSource = {
|
||||
list: [
|
||||
{
|
||||
id: 'user',
|
||||
isInit: true,
|
||||
type: 'fetch',
|
||||
options: () => ({
|
||||
uri: 'https://mocks.alibaba-inc.com/user.json',
|
||||
}),
|
||||
dataHandler: function dataHandler(response: any) {
|
||||
const { data } = response;
|
||||
if (!data) {
|
||||
throw new Error('empty data!');
|
||||
}
|
||||
|
||||
if (data.success) {
|
||||
return data.data;
|
||||
}
|
||||
|
||||
this.recordError({ type: 'FETCH_ERROR', detail: data });
|
||||
|
||||
throw new Error(data.message);
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
@ -0,0 +1,35 @@
|
||||
import { DataSource } from '@ali/build-success-types';
|
||||
|
||||
// 这里仅仅是数据源部分的 schema:
|
||||
// @see: https://yuque.antfin-inc.com/mo/spec/spec-low-code-building-schema#XMeF5
|
||||
export const DATA_SOURCE_SCHEMA: DataSource = {
|
||||
list: [
|
||||
{
|
||||
id: 'user',
|
||||
isInit: true,
|
||||
type: 'fetch',
|
||||
options: {
|
||||
uri: 'https://mocks.alibaba-inc.com/user.json',
|
||||
},
|
||||
dataHandler: {
|
||||
type: 'JSFunction',
|
||||
value: `
|
||||
function dataHandler(response){
|
||||
const { data } = response;
|
||||
if (!data) {
|
||||
throw new Error('empty data!');
|
||||
}
|
||||
|
||||
if (data.success){
|
||||
return data.data;
|
||||
}
|
||||
|
||||
this.recordError({ type: 'FETCH_ERROR', detail: data });
|
||||
|
||||
throw new Error(data.message);
|
||||
}
|
||||
`,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
@ -0,0 +1,95 @@
|
||||
import {
|
||||
DataSource,
|
||||
IDataSourceEngine,
|
||||
IRuntimeContext,
|
||||
RuntimeDataSource,
|
||||
RuntimeDataSourceStatus,
|
||||
} from '@ali/build-success-types';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import { bindRuntimeContext, delay, MockContext } from '../../_helpers';
|
||||
import { DATA_SOURCE_SCHEMA } from './_datasource-schema';
|
||||
|
||||
import type { ExecutionContext, Macro } from 'ava';
|
||||
import type { SinonFakeTimers } from 'sinon';
|
||||
|
||||
export const abnormalScene: Macro<[
|
||||
{
|
||||
create: (
|
||||
dataSource: any,
|
||||
ctx: IRuntimeContext,
|
||||
options: any
|
||||
) => IDataSourceEngine;
|
||||
dataSource: RuntimeDataSource | DataSource;
|
||||
}
|
||||
]> = async (
|
||||
t: ExecutionContext<{ clock: SinonFakeTimers }>,
|
||||
{ create, dataSource },
|
||||
) => {
|
||||
const { clock } = t.context;
|
||||
const ERROR_MSG = 'test error';
|
||||
const fetchHandler = sinon.fake(async () => {
|
||||
await delay(100);
|
||||
return {
|
||||
data: {
|
||||
success: false,
|
||||
message: ERROR_MSG,
|
||||
code: 'E_FOO',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const context = new MockContext<Record<string, unknown>>(
|
||||
{},
|
||||
(ctx) => create(bindRuntimeContext(dataSource, ctx), ctx, {
|
||||
requestHandlersMap: {
|
||||
fetch: fetchHandler,
|
||||
},
|
||||
}),
|
||||
{
|
||||
recordError() {},
|
||||
},
|
||||
);
|
||||
|
||||
const setState = sinon.spy(context, 'setState');
|
||||
const recordError = sinon.spy(context, 'recordError');
|
||||
|
||||
// 一开始应该是初始状态
|
||||
t.is(context.dataSourceMap.user.status, RuntimeDataSourceStatus.Initial);
|
||||
|
||||
const loading = context.reloadDataSource();
|
||||
|
||||
await clock.tickAsync(50);
|
||||
|
||||
// 中间应该有 loading 态
|
||||
t.is(context.dataSourceMap.user.status, RuntimeDataSourceStatus.Loading);
|
||||
|
||||
await Promise.all([clock.runAllAsync(), loading]);
|
||||
|
||||
// 最后应该失败了,error 状态
|
||||
t.is(context.dataSourceMap.user.status, RuntimeDataSourceStatus.Error);
|
||||
|
||||
// 检查数据源的数据
|
||||
t.deepEqual(context.dataSourceMap.user.data, undefined);
|
||||
t.not(context.dataSourceMap.user.error, undefined);
|
||||
|
||||
t.regex(context.dataSourceMap.user.error!.message, new RegExp(ERROR_MSG));
|
||||
|
||||
// 检查状态数据
|
||||
t.assert(setState.notCalled);
|
||||
t.deepEqual(context.state.user, undefined);
|
||||
|
||||
// fetchHandler 应该被调用了一次
|
||||
t.assert(fetchHandler.calledOnce);
|
||||
|
||||
// 检查调用参数
|
||||
const firstListItemOptions = DATA_SOURCE_SCHEMA.list[0].options;
|
||||
const fetchHandlerCallArgs = fetchHandler.firstCall.args[0];
|
||||
t.is(firstListItemOptions.uri, fetchHandlerCallArgs.uri);
|
||||
|
||||
// 埋点应该也会被调用
|
||||
t.assert(recordError.calledOnce);
|
||||
t.snapshot(recordError.firstCall.args);
|
||||
};
|
||||
|
||||
abnormalScene.title = (providedTitle) => providedTitle || 'abnormal scene';
|
||||
@ -0,0 +1,96 @@
|
||||
import {
|
||||
DataSource,
|
||||
IDataSourceEngine,
|
||||
IRuntimeContext,
|
||||
RuntimeDataSource,
|
||||
RuntimeDataSourceStatus,
|
||||
} from '@ali/build-success-types';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import { bindRuntimeContext, delay, MockContext } from '../../_helpers';
|
||||
import { DATA_SOURCE_SCHEMA } from './_datasource-schema';
|
||||
|
||||
import type { ExecutionContext, Macro } from 'ava';
|
||||
import type { SinonFakeTimers } from 'sinon';
|
||||
|
||||
export const normalScene: Macro<[
|
||||
{
|
||||
create: (
|
||||
dataSource: any,
|
||||
ctx: IRuntimeContext,
|
||||
options: any
|
||||
) => IDataSourceEngine;
|
||||
dataSource: RuntimeDataSource | DataSource;
|
||||
}
|
||||
]> = async (
|
||||
t: ExecutionContext<{ clock: SinonFakeTimers }>,
|
||||
{ create, dataSource },
|
||||
) => {
|
||||
const { clock } = t.context;
|
||||
|
||||
const USER_DATA = {
|
||||
name: 'Alice',
|
||||
age: 18,
|
||||
};
|
||||
|
||||
const fetchHandler = sinon.fake(async () => {
|
||||
await delay(100);
|
||||
return {
|
||||
data: {
|
||||
success: true,
|
||||
data: USER_DATA,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const context = new MockContext<Record<string, unknown>>(
|
||||
{},
|
||||
(ctx) => create(bindRuntimeContext(dataSource, ctx), ctx, {
|
||||
requestHandlersMap: {
|
||||
fetch: fetchHandler,
|
||||
},
|
||||
}),
|
||||
{
|
||||
recordError() {},
|
||||
},
|
||||
);
|
||||
|
||||
const setState = sinon.spy(context, 'setState');
|
||||
const recordError = sinon.spy(context, 'recordError');
|
||||
|
||||
// 一开始应该是初始状态
|
||||
t.is(context.dataSourceMap.user.status, RuntimeDataSourceStatus.Initial);
|
||||
|
||||
const loading = context.reloadDataSource();
|
||||
|
||||
await clock.tickAsync(50);
|
||||
|
||||
// 中间应该有 loading 态
|
||||
t.is(context.dataSourceMap.user.status, RuntimeDataSourceStatus.Loading);
|
||||
|
||||
await Promise.all([clock.runAllAsync(), loading]);
|
||||
|
||||
// 最后应该成功了,loaded 状态
|
||||
t.is(context.dataSourceMap.user.status, RuntimeDataSourceStatus.Loaded);
|
||||
|
||||
// 检查数据源的数据
|
||||
t.deepEqual(context.dataSourceMap.user.data, USER_DATA);
|
||||
t.deepEqual(context.dataSourceMap.user.error, undefined);
|
||||
|
||||
// 检查状态数据
|
||||
t.assert(setState.calledOnce);
|
||||
t.deepEqual(context.state.user, USER_DATA);
|
||||
|
||||
// fetchHandler 应该被调用了一次
|
||||
t.assert(fetchHandler.calledOnce);
|
||||
|
||||
// 检查调用参数
|
||||
const firstListItemOptions = DATA_SOURCE_SCHEMA.list[0].options;
|
||||
const fetchHandlerCallArgs = fetchHandler.firstCall.args[0];
|
||||
t.is(firstListItemOptions.uri, fetchHandlerCallArgs.uri);
|
||||
|
||||
// 埋点不应该被调用
|
||||
t.assert(recordError.notCalled);
|
||||
};
|
||||
|
||||
normalScene.title = (providedTitle) => providedTitle || 'normal scene';
|
||||
@ -0,0 +1,21 @@
|
||||
import test, { ExecutionContext } from 'ava';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import { create } from '../../../src/interpret';
|
||||
import { DATA_SOURCE_SCHEMA } from './_datasource-schema';
|
||||
import { abnormalScene } from './_macro-abnormal';
|
||||
|
||||
import type { SinonFakeTimers } from 'sinon';
|
||||
|
||||
test.before((t: ExecutionContext<{ clock: SinonFakeTimers }>) => {
|
||||
t.context.clock = sinon.useFakeTimers();
|
||||
});
|
||||
|
||||
test.after((t: ExecutionContext<{ clock: SinonFakeTimers }>) => {
|
||||
t.context.clock.restore();
|
||||
});
|
||||
|
||||
test(abnormalScene, {
|
||||
create,
|
||||
dataSource: DATA_SOURCE_SCHEMA,
|
||||
});
|
||||
@ -0,0 +1,20 @@
|
||||
import test, { ExecutionContext } from 'ava';
|
||||
import sinon, { SinonFakeTimers } from 'sinon';
|
||||
|
||||
import { create } from '../../../src/runtime';
|
||||
|
||||
import { dataSource } from './_datasource-runtime';
|
||||
import { abnormalScene } from './_macro-abnormal';
|
||||
|
||||
test.before((t: ExecutionContext<{ clock: SinonFakeTimers }>) => {
|
||||
t.context.clock = sinon.useFakeTimers();
|
||||
});
|
||||
|
||||
test.after((t: ExecutionContext<{ clock: SinonFakeTimers }>) => {
|
||||
t.context.clock.restore();
|
||||
});
|
||||
|
||||
test(abnormalScene, {
|
||||
create,
|
||||
dataSource,
|
||||
});
|
||||
@ -0,0 +1,20 @@
|
||||
import test, { ExecutionContext } from 'ava';
|
||||
import sinon, { SinonFakeTimers } from 'sinon';
|
||||
|
||||
import { create } from '../../../src/interpret';
|
||||
|
||||
import { DATA_SOURCE_SCHEMA } from './_datasource-schema';
|
||||
import { normalScene } from './_macro-normal';
|
||||
|
||||
test.before((t: ExecutionContext<{ clock: SinonFakeTimers }>) => {
|
||||
t.context.clock = sinon.useFakeTimers();
|
||||
});
|
||||
|
||||
test.after((t: ExecutionContext<{ clock: SinonFakeTimers }>) => {
|
||||
t.context.clock.restore();
|
||||
});
|
||||
|
||||
test(normalScene, {
|
||||
create,
|
||||
dataSource: DATA_SOURCE_SCHEMA,
|
||||
});
|
||||
@ -0,0 +1,20 @@
|
||||
import test, { ExecutionContext } from 'ava';
|
||||
import sinon, { SinonFakeTimers } from 'sinon';
|
||||
|
||||
import { create } from '../../../src/runtime';
|
||||
|
||||
import { dataSource } from './_datasource-runtime';
|
||||
import { normalScene } from './_macro-normal';
|
||||
|
||||
test.before((t: ExecutionContext<{ clock: SinonFakeTimers }>) => {
|
||||
t.context.clock = sinon.useFakeTimers();
|
||||
});
|
||||
|
||||
test.after((t: ExecutionContext<{ clock: SinonFakeTimers }>) => {
|
||||
t.context.clock.restore();
|
||||
});
|
||||
|
||||
test(normalScene, {
|
||||
create,
|
||||
dataSource,
|
||||
});
|
||||
@ -0,0 +1,20 @@
|
||||
# Snapshot report for `test/scenes/custom-response-status/abnormal-interpret.test.ts`
|
||||
|
||||
The actual snapshot is saved in `abnormal-interpret.test.ts.snap`.
|
||||
|
||||
Generated by [AVA](https://avajs.dev).
|
||||
|
||||
## abnormal scene
|
||||
|
||||
> Snapshot 1
|
||||
|
||||
[
|
||||
{
|
||||
detail: {
|
||||
code: 'E_FOO',
|
||||
message: 'test error',
|
||||
success: false,
|
||||
},
|
||||
type: 'FETCH_ERROR',
|
||||
},
|
||||
]
|
||||
Binary file not shown.
@ -0,0 +1,20 @@
|
||||
# Snapshot report for `test/scenes/p0-1-custom-response-status/abnormal-runtime.test.ts`
|
||||
|
||||
The actual snapshot is saved in `abnormal-runtime.test.ts.snap`.
|
||||
|
||||
Generated by [AVA](https://avajs.dev).
|
||||
|
||||
## abnormal scene
|
||||
|
||||
> Snapshot 1
|
||||
|
||||
[
|
||||
{
|
||||
detail: {
|
||||
code: 'E_FOO',
|
||||
message: 'test error',
|
||||
success: false,
|
||||
},
|
||||
type: 'FETCH_ERROR',
|
||||
},
|
||||
]
|
||||
Binary file not shown.
@ -0,0 +1,3 @@
|
||||
# 关于此场景
|
||||
|
||||
有些数据源的错误可以忽略(吃掉)-- 通过 dataHandler 捕获 error,只要其不重新抛出 error 而且不返回 rejected 状态的 Promise.
|
||||
@ -0,0 +1,21 @@
|
||||
import { RuntimeDataSource } from '@ali/build-success-types';
|
||||
|
||||
export const DEFAULT_USER_DATA = { id: 0, name: 'guest' }; // 返回一个兜底的数据
|
||||
|
||||
// 这里仅仅是数据源部分的:
|
||||
// @see: https://yuque.antfin-inc.com/mo/spec/spec-low-code-building-schema#XMeF5
|
||||
export const dataSource: RuntimeDataSource = {
|
||||
list: [
|
||||
{
|
||||
id: 'user',
|
||||
isInit: true,
|
||||
type: 'fetch',
|
||||
options: () => ({
|
||||
uri: 'https://mocks.alibaba-inc.com/user.json',
|
||||
}),
|
||||
dataHandler: function dataHandler(response: any) {
|
||||
return response.data;
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
@ -0,0 +1,24 @@
|
||||
import { DataSource } from '@ali/build-success-types';
|
||||
|
||||
// 这里仅仅是数据源部分的 schema:
|
||||
// @see: https://yuque.antfin-inc.com/mo/spec/spec-low-code-building-schema#XMeF5
|
||||
export const DATA_SOURCE_SCHEMA: DataSource = {
|
||||
list: [
|
||||
{
|
||||
id: 'user',
|
||||
isInit: true,
|
||||
type: 'fetch',
|
||||
options: {
|
||||
uri: 'https://mocks.alibaba-inc.com/user.json',
|
||||
},
|
||||
dataHandler: {
|
||||
type: 'JSFunction',
|
||||
value: `
|
||||
function dataHandler(response) {
|
||||
return response.data;
|
||||
}
|
||||
`,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
@ -0,0 +1,78 @@
|
||||
import {
|
||||
DataSource,
|
||||
IDataSourceEngine,
|
||||
IRuntimeContext,
|
||||
RuntimeDataSource,
|
||||
RuntimeDataSourceStatus,
|
||||
} from '@ali/build-success-types';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import { delay, MockContext } from '../../_helpers';
|
||||
import { DATA_SOURCE_SCHEMA } from './_datasource-schema';
|
||||
|
||||
import type { ExecutionContext, Macro } from 'ava';
|
||||
import type { SinonFakeTimers } from 'sinon';
|
||||
|
||||
export const abnormalScene: Macro<[
|
||||
{
|
||||
create: (
|
||||
dataSource: any,
|
||||
ctx: IRuntimeContext,
|
||||
options: any
|
||||
) => IDataSourceEngine;
|
||||
dataSource: RuntimeDataSource | DataSource;
|
||||
}
|
||||
]> = async (
|
||||
t: ExecutionContext<{ clock: SinonFakeTimers }>,
|
||||
{ create, dataSource },
|
||||
) => {
|
||||
const { clock } = t.context;
|
||||
const ERROR_MSG = 'test error';
|
||||
const fetchHandler = sinon.fake(async () => {
|
||||
await delay(100);
|
||||
throw new Error(ERROR_MSG);
|
||||
});
|
||||
|
||||
const context = new MockContext<Record<string, unknown>>({}, (ctx) => create(dataSource, ctx, {
|
||||
requestHandlersMap: {
|
||||
fetch: fetchHandler,
|
||||
},
|
||||
}));
|
||||
|
||||
const setState = sinon.spy(context, 'setState');
|
||||
|
||||
// 一开始应该是初始状态
|
||||
t.is(context.dataSourceMap.user.status, RuntimeDataSourceStatus.Initial);
|
||||
|
||||
const loading = context.reloadDataSource();
|
||||
|
||||
await clock.tickAsync(50);
|
||||
|
||||
// 中间应该有 loading 态
|
||||
t.is(context.dataSourceMap.user.status, RuntimeDataSourceStatus.Loading);
|
||||
|
||||
await Promise.all([clock.runAllAsync(), loading]);
|
||||
|
||||
// 注意 error 是会被吃掉了,还是 loaded 状态
|
||||
// FIXME: 根据协议内容,dataHandler 返回的结果是需要抛出错误的,那么 fetchHandler 的错误难道不需要处理?
|
||||
// TODO: 提案:request 如果挂了,不应该需要走 dataHandler 了,没有意义
|
||||
t.is(context.dataSourceMap.user.status, RuntimeDataSourceStatus.Error);
|
||||
|
||||
// 检查数据源的数据
|
||||
t.deepEqual(context.dataSourceMap.user.data, undefined);
|
||||
t.not(context.dataSourceMap.user.error, undefined);
|
||||
t.regex(context.dataSourceMap.user.error!.message, new RegExp(ERROR_MSG));
|
||||
|
||||
// 检查状态数据
|
||||
t.assert(setState.notCalled);
|
||||
|
||||
// fetchHandler 应该没调
|
||||
t.assert.skip(fetchHandler.notCalled);
|
||||
|
||||
// 检查调用参数
|
||||
const firstListItemOptions = DATA_SOURCE_SCHEMA.list[0].options;
|
||||
const fetchHandlerCallArgs = fetchHandler.firstCall.args[0];
|
||||
t.is(firstListItemOptions.uri, fetchHandlerCallArgs.uri);
|
||||
};
|
||||
|
||||
abnormalScene.title = (providedTitle) => providedTitle || 'abnormal scene';
|
||||
@ -0,0 +1,81 @@
|
||||
import {
|
||||
DataSource,
|
||||
IDataSourceEngine,
|
||||
IRuntimeContext,
|
||||
RuntimeDataSource,
|
||||
RuntimeDataSourceStatus,
|
||||
} from '@ali/build-success-types';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import { delay, MockContext } from '../../_helpers';
|
||||
import { DATA_SOURCE_SCHEMA } from './_datasource-schema';
|
||||
|
||||
import type { ExecutionContext, Macro } from 'ava';
|
||||
import type { SinonFakeTimers } from 'sinon';
|
||||
|
||||
export const normalScene: Macro<[
|
||||
{
|
||||
create: (
|
||||
dataSource: any,
|
||||
ctx: IRuntimeContext,
|
||||
options: any
|
||||
) => IDataSourceEngine;
|
||||
dataSource: RuntimeDataSource | DataSource;
|
||||
}
|
||||
]> = async (
|
||||
t: ExecutionContext<{ clock: SinonFakeTimers }>,
|
||||
{ create, dataSource },
|
||||
) => {
|
||||
const { clock } = t.context;
|
||||
|
||||
const USER_DATA = {
|
||||
name: 'Alice',
|
||||
age: 18,
|
||||
};
|
||||
|
||||
const fetchHandler = sinon.fake(async () => {
|
||||
await delay(100);
|
||||
return { data: USER_DATA };
|
||||
});
|
||||
|
||||
const context = new MockContext<Record<string, unknown>>({}, (ctx) => create(dataSource, ctx, {
|
||||
requestHandlersMap: {
|
||||
fetch: fetchHandler,
|
||||
},
|
||||
}));
|
||||
|
||||
const setState = sinon.spy(context, 'setState');
|
||||
|
||||
// 一开始应该是初始状态
|
||||
t.is(context.dataSourceMap.user.status, RuntimeDataSourceStatus.Initial);
|
||||
|
||||
const loading = context.reloadDataSource();
|
||||
|
||||
await clock.tickAsync(50);
|
||||
|
||||
// 中间应该有 loading 态
|
||||
t.is(context.dataSourceMap.user.status, RuntimeDataSourceStatus.Loading);
|
||||
|
||||
await Promise.all([clock.runAllAsync(), loading]);
|
||||
|
||||
// 最后应该成功了,loaded 状态
|
||||
t.is(context.dataSourceMap.user.status, RuntimeDataSourceStatus.Loaded);
|
||||
|
||||
// 检查数据源的数据
|
||||
t.deepEqual(context.dataSourceMap.user.data, USER_DATA);
|
||||
t.deepEqual(context.dataSourceMap.user.error, undefined);
|
||||
|
||||
// 检查状态数据
|
||||
t.assert(setState.calledOnce);
|
||||
t.deepEqual(context.state.user, USER_DATA);
|
||||
|
||||
// fetchHandler 应该被调用了一次
|
||||
t.assert(fetchHandler.calledOnce);
|
||||
|
||||
// 检查调用参数
|
||||
const firstListItemOptions = DATA_SOURCE_SCHEMA.list[0].options;
|
||||
const fetchHandlerCallArgs = fetchHandler.firstCall.args[0];
|
||||
t.is(firstListItemOptions.uri, fetchHandlerCallArgs.uri);
|
||||
};
|
||||
|
||||
normalScene.title = (providedTitle) => providedTitle || 'normal scene';
|
||||
@ -0,0 +1,22 @@
|
||||
import test, { ExecutionContext } from 'ava';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import { create } from '../../../src/interpret';
|
||||
|
||||
import { DATA_SOURCE_SCHEMA } from './_datasource-schema';
|
||||
import { abnormalScene } from './_macro-abnormal';
|
||||
|
||||
import type { SinonFakeTimers } from 'sinon';
|
||||
|
||||
test.before((t: ExecutionContext<{ clock: SinonFakeTimers }>) => {
|
||||
t.context.clock = sinon.useFakeTimers();
|
||||
});
|
||||
|
||||
test.after((t: ExecutionContext<{ clock: SinonFakeTimers }>) => {
|
||||
t.context.clock.restore();
|
||||
});
|
||||
|
||||
test(abnormalScene, {
|
||||
create,
|
||||
dataSource: DATA_SOURCE_SCHEMA,
|
||||
});
|
||||
@ -0,0 +1,20 @@
|
||||
import test, { ExecutionContext } from 'ava';
|
||||
import sinon, { SinonFakeTimers } from 'sinon';
|
||||
|
||||
import { create } from '../../../src/runtime';
|
||||
|
||||
import { dataSource } from './_datasource-runtime';
|
||||
import { abnormalScene } from './_macro-abnormal';
|
||||
|
||||
test.before((t: ExecutionContext<{ clock: SinonFakeTimers }>) => {
|
||||
t.context.clock = sinon.useFakeTimers();
|
||||
});
|
||||
|
||||
test.after((t: ExecutionContext<{ clock: SinonFakeTimers }>) => {
|
||||
t.context.clock.restore();
|
||||
});
|
||||
|
||||
test(abnormalScene, {
|
||||
create,
|
||||
dataSource,
|
||||
});
|
||||
@ -0,0 +1,20 @@
|
||||
import test, { ExecutionContext } from 'ava';
|
||||
import sinon, { SinonFakeTimers } from 'sinon';
|
||||
|
||||
import { create } from '../../../src/interpret';
|
||||
|
||||
import { DATA_SOURCE_SCHEMA } from './_datasource-schema';
|
||||
import { normalScene } from './_macro-normal';
|
||||
|
||||
test.before((t: ExecutionContext<{ clock: SinonFakeTimers }>) => {
|
||||
t.context.clock = sinon.useFakeTimers();
|
||||
});
|
||||
|
||||
test.after((t: ExecutionContext<{ clock: SinonFakeTimers }>) => {
|
||||
t.context.clock.restore();
|
||||
});
|
||||
|
||||
test(normalScene, {
|
||||
create,
|
||||
dataSource: DATA_SOURCE_SCHEMA,
|
||||
});
|
||||
@ -0,0 +1,20 @@
|
||||
import test, { ExecutionContext } from 'ava';
|
||||
import sinon, { SinonFakeTimers } from 'sinon';
|
||||
|
||||
import { create } from '../../../src/runtime';
|
||||
|
||||
import { dataSource } from './_datasource-runtime';
|
||||
import { normalScene } from './_macro-normal';
|
||||
|
||||
test.before((t: ExecutionContext<{ clock: SinonFakeTimers }>) => {
|
||||
t.context.clock = sinon.useFakeTimers();
|
||||
});
|
||||
|
||||
test.after((t: ExecutionContext<{ clock: SinonFakeTimers }>) => {
|
||||
t.context.clock.restore();
|
||||
});
|
||||
|
||||
test(normalScene, {
|
||||
create,
|
||||
dataSource,
|
||||
});
|
||||
@ -0,0 +1,3 @@
|
||||
# 关于此场景
|
||||
|
||||
某些场景下 dataHandler 不能同步返回,比如可能需要读取某个特殊的异步的数据源并合并响应数据。
|
||||
@ -0,0 +1,30 @@
|
||||
import { RuntimeDataSource } from '@ali/build-success-types';
|
||||
|
||||
// 这里仅仅是数据源部分的:
|
||||
// @see: https://yuque.antfin-inc.com/mo/spec/spec-low-code-building-schema#XMeF5
|
||||
export const dataSource: RuntimeDataSource = {
|
||||
list: [
|
||||
{
|
||||
id: 'user',
|
||||
isInit: true,
|
||||
type: 'fetch',
|
||||
options: () => ({
|
||||
uri: 'https://mocks.alibaba-inc.com/user.json',
|
||||
}),
|
||||
dataHandler: async function dataHandler(response: any) {
|
||||
const { data } = response;
|
||||
if (!data) {
|
||||
throw new Error('empty data!');
|
||||
}
|
||||
|
||||
if (data.success) {
|
||||
return data.data;
|
||||
}
|
||||
|
||||
this.recordError({ type: 'FETCH_ERROR', detail: data });
|
||||
|
||||
throw new Error(data.message);
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
@ -0,0 +1,35 @@
|
||||
import { DataSource } from '@ali/build-success-types';
|
||||
|
||||
// 这里仅仅是数据源部分的 schema:
|
||||
// @see: https://yuque.antfin-inc.com/mo/spec/spec-low-code-building-schema#XMeF5
|
||||
export const DATA_SOURCE_SCHEMA: DataSource = {
|
||||
list: [
|
||||
{
|
||||
id: 'user',
|
||||
isInit: true,
|
||||
type: 'fetch',
|
||||
options: {
|
||||
uri: 'https://mocks.alibaba-inc.com/user.json',
|
||||
},
|
||||
dataHandler: {
|
||||
type: 'JSFunction',
|
||||
value: `
|
||||
async function dataHandler(response){
|
||||
const { data } = response;
|
||||
if (!data) {
|
||||
throw new Error('empty data!');
|
||||
}
|
||||
|
||||
if (data.success){
|
||||
return data.data;
|
||||
}
|
||||
|
||||
this.recordError({ type: 'FETCH_ERROR', detail: data });
|
||||
|
||||
throw new Error(data.message);
|
||||
}
|
||||
`,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
@ -0,0 +1,95 @@
|
||||
import {
|
||||
DataSource,
|
||||
IDataSourceEngine,
|
||||
IRuntimeContext,
|
||||
RuntimeDataSource,
|
||||
RuntimeDataSourceStatus,
|
||||
} from '@ali/build-success-types';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import { bindRuntimeContext, delay, MockContext } from '../../_helpers';
|
||||
import { DATA_SOURCE_SCHEMA } from './_datasource-schema';
|
||||
|
||||
import type { ExecutionContext, Macro } from 'ava';
|
||||
import type { SinonFakeTimers } from 'sinon';
|
||||
|
||||
export const abnormalScene: Macro<[
|
||||
{
|
||||
create: (
|
||||
dataSource: any,
|
||||
ctx: IRuntimeContext,
|
||||
options: any
|
||||
) => IDataSourceEngine;
|
||||
dataSource: RuntimeDataSource | DataSource;
|
||||
}
|
||||
]> = async (
|
||||
t: ExecutionContext<{ clock: SinonFakeTimers }>,
|
||||
{ create, dataSource },
|
||||
) => {
|
||||
const { clock } = t.context;
|
||||
const ERROR_MSG = 'test error';
|
||||
const fetchHandler = sinon.fake(async () => {
|
||||
await delay(100);
|
||||
return {
|
||||
data: {
|
||||
success: false,
|
||||
message: ERROR_MSG,
|
||||
code: 'E_FOO',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const context = new MockContext<Record<string, unknown>>(
|
||||
{},
|
||||
(ctx) => create(bindRuntimeContext(dataSource, ctx), ctx, {
|
||||
requestHandlersMap: {
|
||||
fetch: fetchHandler,
|
||||
},
|
||||
}),
|
||||
{
|
||||
recordError() {},
|
||||
},
|
||||
);
|
||||
|
||||
const setState = sinon.spy(context, 'setState');
|
||||
const recordError = sinon.spy(context, 'recordError');
|
||||
|
||||
// 一开始应该是初始状态
|
||||
t.is(context.dataSourceMap.user.status, RuntimeDataSourceStatus.Initial);
|
||||
|
||||
const loading = context.reloadDataSource();
|
||||
|
||||
await clock.tickAsync(50);
|
||||
|
||||
// 中间应该有 loading 态
|
||||
t.is(context.dataSourceMap.user.status, RuntimeDataSourceStatus.Loading);
|
||||
|
||||
await Promise.all([clock.runAllAsync(), loading]);
|
||||
|
||||
// 最后应该失败了,error 状态
|
||||
t.is(context.dataSourceMap.user.status, RuntimeDataSourceStatus.Error);
|
||||
|
||||
// 检查数据源的数据
|
||||
t.deepEqual(context.dataSourceMap.user.data, undefined);
|
||||
t.not(context.dataSourceMap.user.error, undefined);
|
||||
|
||||
t.regex(context.dataSourceMap.user.error!.message, new RegExp(ERROR_MSG));
|
||||
|
||||
// 检查状态数据
|
||||
t.assert(setState.notCalled);
|
||||
t.deepEqual(context.state.user, undefined);
|
||||
|
||||
// fetchHandler 应该被调用了一次
|
||||
t.assert(fetchHandler.calledOnce);
|
||||
|
||||
// 检查调用参数
|
||||
const firstListItemOptions = DATA_SOURCE_SCHEMA.list[0].options;
|
||||
const fetchHandlerCallArgs = fetchHandler.firstCall.args[0];
|
||||
t.is(firstListItemOptions.uri, fetchHandlerCallArgs.uri);
|
||||
|
||||
// 埋点应该也会被调用
|
||||
t.assert(recordError.calledOnce);
|
||||
t.snapshot(recordError.firstCall.args);
|
||||
};
|
||||
|
||||
abnormalScene.title = (providedTitle) => providedTitle || 'abnormal scene';
|
||||
@ -0,0 +1,96 @@
|
||||
import {
|
||||
DataSource,
|
||||
IDataSourceEngine,
|
||||
IRuntimeContext,
|
||||
RuntimeDataSource,
|
||||
RuntimeDataSourceStatus,
|
||||
} from '@ali/build-success-types';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import { bindRuntimeContext, delay, MockContext } from '../../_helpers';
|
||||
import { DATA_SOURCE_SCHEMA } from './_datasource-schema';
|
||||
|
||||
import type { ExecutionContext, Macro } from 'ava';
|
||||
import type { SinonFakeTimers } from 'sinon';
|
||||
|
||||
export const normalScene: Macro<[
|
||||
{
|
||||
create: (
|
||||
dataSource: any,
|
||||
ctx: IRuntimeContext,
|
||||
options: any
|
||||
) => IDataSourceEngine;
|
||||
dataSource: RuntimeDataSource | DataSource;
|
||||
}
|
||||
]> = async (
|
||||
t: ExecutionContext<{ clock: SinonFakeTimers }>,
|
||||
{ create, dataSource },
|
||||
) => {
|
||||
const { clock } = t.context;
|
||||
|
||||
const USER_DATA = {
|
||||
name: 'Alice',
|
||||
age: 18,
|
||||
};
|
||||
|
||||
const fetchHandler = sinon.fake(async () => {
|
||||
await delay(100);
|
||||
return {
|
||||
data: {
|
||||
success: true,
|
||||
data: USER_DATA,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const context = new MockContext<Record<string, unknown>>(
|
||||
{},
|
||||
(ctx) => create(bindRuntimeContext(dataSource, ctx), ctx, {
|
||||
requestHandlersMap: {
|
||||
fetch: fetchHandler,
|
||||
},
|
||||
}),
|
||||
{
|
||||
recordError() {},
|
||||
},
|
||||
);
|
||||
|
||||
const setState = sinon.spy(context, 'setState');
|
||||
const recordError = sinon.spy(context, 'recordError');
|
||||
|
||||
// 一开始应该是初始状态
|
||||
t.is(context.dataSourceMap.user.status, RuntimeDataSourceStatus.Initial);
|
||||
|
||||
const loading = context.reloadDataSource();
|
||||
|
||||
await clock.tickAsync(50);
|
||||
|
||||
// 中间应该有 loading 态
|
||||
t.is(context.dataSourceMap.user.status, RuntimeDataSourceStatus.Loading);
|
||||
|
||||
await Promise.all([clock.runAllAsync(), loading]);
|
||||
|
||||
// 最后应该成功了,loaded 状态
|
||||
t.is(context.dataSourceMap.user.status, RuntimeDataSourceStatus.Loaded);
|
||||
|
||||
// 检查数据源的数据
|
||||
t.deepEqual(context.dataSourceMap.user.data, USER_DATA);
|
||||
t.deepEqual(context.dataSourceMap.user.error, undefined);
|
||||
|
||||
// 检查状态数据
|
||||
t.assert(setState.calledOnce);
|
||||
t.deepEqual(context.state.user, USER_DATA);
|
||||
|
||||
// fetchHandler 应该被调用了一次
|
||||
t.assert(fetchHandler.calledOnce);
|
||||
|
||||
// 检查调用参数
|
||||
const firstListItemOptions = DATA_SOURCE_SCHEMA.list[0].options;
|
||||
const fetchHandlerCallArgs = fetchHandler.firstCall.args[0];
|
||||
t.is(firstListItemOptions.uri, fetchHandlerCallArgs.uri);
|
||||
|
||||
// 埋点不应该被调用
|
||||
t.assert(recordError.notCalled);
|
||||
};
|
||||
|
||||
normalScene.title = (providedTitle) => providedTitle || 'normal scene';
|
||||
@ -0,0 +1,21 @@
|
||||
import test, { ExecutionContext } from 'ava';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import { create } from '../../../src/interpret';
|
||||
import { DATA_SOURCE_SCHEMA } from './_datasource-schema';
|
||||
import { abnormalScene } from './_macro-abnormal';
|
||||
|
||||
import type { SinonFakeTimers } from 'sinon';
|
||||
|
||||
test.before((t: ExecutionContext<{ clock: SinonFakeTimers }>) => {
|
||||
t.context.clock = sinon.useFakeTimers();
|
||||
});
|
||||
|
||||
test.after((t: ExecutionContext<{ clock: SinonFakeTimers }>) => {
|
||||
t.context.clock.restore();
|
||||
});
|
||||
|
||||
test(abnormalScene, {
|
||||
create,
|
||||
dataSource: DATA_SOURCE_SCHEMA,
|
||||
});
|
||||
@ -0,0 +1,20 @@
|
||||
import test, { ExecutionContext } from 'ava';
|
||||
import sinon, { SinonFakeTimers } from 'sinon';
|
||||
|
||||
import { create } from '../../../src/runtime';
|
||||
|
||||
import { dataSource } from './_datasource-runtime';
|
||||
import { abnormalScene } from './_macro-abnormal';
|
||||
|
||||
test.before((t: ExecutionContext<{ clock: SinonFakeTimers }>) => {
|
||||
t.context.clock = sinon.useFakeTimers();
|
||||
});
|
||||
|
||||
test.after((t: ExecutionContext<{ clock: SinonFakeTimers }>) => {
|
||||
t.context.clock.restore();
|
||||
});
|
||||
|
||||
test(abnormalScene, {
|
||||
create,
|
||||
dataSource,
|
||||
});
|
||||
@ -0,0 +1,20 @@
|
||||
import test, { ExecutionContext } from 'ava';
|
||||
import sinon, { SinonFakeTimers } from 'sinon';
|
||||
|
||||
import { create } from '../../../src/interpret';
|
||||
|
||||
import { DATA_SOURCE_SCHEMA } from './_datasource-schema';
|
||||
import { normalScene } from './_macro-normal';
|
||||
|
||||
test.before((t: ExecutionContext<{ clock: SinonFakeTimers }>) => {
|
||||
t.context.clock = sinon.useFakeTimers();
|
||||
});
|
||||
|
||||
test.after((t: ExecutionContext<{ clock: SinonFakeTimers }>) => {
|
||||
t.context.clock.restore();
|
||||
});
|
||||
|
||||
test(normalScene, {
|
||||
create,
|
||||
dataSource: DATA_SOURCE_SCHEMA,
|
||||
});
|
||||
@ -0,0 +1,20 @@
|
||||
import test, { ExecutionContext } from 'ava';
|
||||
import sinon, { SinonFakeTimers } from 'sinon';
|
||||
|
||||
import { create } from '../../../src/runtime';
|
||||
|
||||
import { dataSource } from './_datasource-runtime';
|
||||
import { normalScene } from './_macro-normal';
|
||||
|
||||
test.before((t: ExecutionContext<{ clock: SinonFakeTimers }>) => {
|
||||
t.context.clock = sinon.useFakeTimers();
|
||||
});
|
||||
|
||||
test.after((t: ExecutionContext<{ clock: SinonFakeTimers }>) => {
|
||||
t.context.clock.restore();
|
||||
});
|
||||
|
||||
test(normalScene, {
|
||||
create,
|
||||
dataSource,
|
||||
});
|
||||
@ -0,0 +1,20 @@
|
||||
# Snapshot report for `test/scenes/data-handler-returns-promise/abnormal-interpret.test.ts`
|
||||
|
||||
The actual snapshot is saved in `abnormal-interpret.test.ts.snap`.
|
||||
|
||||
Generated by [AVA](https://avajs.dev).
|
||||
|
||||
## abnormal scene
|
||||
|
||||
> Snapshot 1
|
||||
|
||||
[
|
||||
{
|
||||
detail: {
|
||||
code: 'E_FOO',
|
||||
message: 'test error',
|
||||
success: false,
|
||||
},
|
||||
type: 'FETCH_ERROR',
|
||||
},
|
||||
]
|
||||
Binary file not shown.
@ -0,0 +1,20 @@
|
||||
# Snapshot report for `test/scenes/p1-0-data-handler-returns-promise/abnormal-runtime.test.ts`
|
||||
|
||||
The actual snapshot is saved in `abnormal-runtime.test.ts.snap`.
|
||||
|
||||
Generated by [AVA](https://avajs.dev).
|
||||
|
||||
## abnormal scene
|
||||
|
||||
> Snapshot 1
|
||||
|
||||
[
|
||||
{
|
||||
detail: {
|
||||
code: 'E_FOO',
|
||||
message: 'test error',
|
||||
success: false,
|
||||
},
|
||||
type: 'FETCH_ERROR',
|
||||
},
|
||||
]
|
||||
Binary file not shown.
@ -0,0 +1,3 @@
|
||||
# 关于此场景
|
||||
|
||||
某些场景下 shouldFetch 的结果可以影响请求是否被发出去。
|
||||
@ -0,0 +1,32 @@
|
||||
import { RuntimeDataSource } from '@ali/build-success-types';
|
||||
|
||||
// 这里仅仅是数据源部分的:
|
||||
// @see: https://yuque.antfin-inc.com/mo/spec/spec-low-code-building-schema#XMeF5
|
||||
export const dataSource: RuntimeDataSource = {
|
||||
list: [
|
||||
{
|
||||
id: 'user',
|
||||
isInit: true,
|
||||
type: 'fetch',
|
||||
isSync: true,
|
||||
options: () => ({
|
||||
uri: 'https://mocks.alibaba-inc.com/user.json',
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'orders',
|
||||
isInit: true,
|
||||
type: 'fetch',
|
||||
isSync: true,
|
||||
shouldFetch: () => false,
|
||||
options() {
|
||||
return {
|
||||
uri: 'https://mocks.alibaba-inc.com/orders.json',
|
||||
params: {
|
||||
userId: this.state.user.id,
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
@ -0,0 +1,38 @@
|
||||
import { DataSource } from '@ali/build-success-types';
|
||||
|
||||
// 这里仅仅是数据源部分的 schema:
|
||||
// @see: https://yuque.antfin-inc.com/mo/spec/spec-low-code-building-schema#XMeF5
|
||||
export const DATA_SOURCE_SCHEMA: DataSource = {
|
||||
list: [
|
||||
{
|
||||
id: 'user',
|
||||
isInit: true,
|
||||
type: 'fetch',
|
||||
isSync: true,
|
||||
options: {
|
||||
uri: 'https://mocks.alibaba-inc.com/user.json',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'orders',
|
||||
isInit: true,
|
||||
type: 'fetch',
|
||||
isSync: true,
|
||||
shouldFetch: {
|
||||
type: 'JSFunction',
|
||||
value: `
|
||||
function (){
|
||||
return false;
|
||||
}
|
||||
`,
|
||||
},
|
||||
options: {
|
||||
uri: 'https://mocks.alibaba-inc.com/orders.json',
|
||||
params: {
|
||||
type: 'JSExpression',
|
||||
value: '{ userId: this.state.user.id }',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
@ -0,0 +1,108 @@
|
||||
import {
|
||||
DataSource,
|
||||
IDataSourceEngine,
|
||||
IRuntimeContext,
|
||||
RuntimeDataSource,
|
||||
RuntimeDataSourceStatus,
|
||||
} from '@ali/build-success-types';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import { bindRuntimeContext, delay, MockContext } from '../../_helpers';
|
||||
|
||||
import type { ExecutionContext, Macro } from 'ava';
|
||||
import type { SinonFakeTimers } from 'sinon';
|
||||
import { DATA_SOURCE_SCHEMA } from './_datasource-schema';
|
||||
|
||||
export const normalScene: Macro<[
|
||||
{
|
||||
create: (
|
||||
dataSource: any,
|
||||
ctx: IRuntimeContext,
|
||||
options: any
|
||||
) => IDataSourceEngine;
|
||||
dataSource: RuntimeDataSource | DataSource;
|
||||
}
|
||||
]> = async (
|
||||
t: ExecutionContext<{ clock: SinonFakeTimers }>,
|
||||
{ create, dataSource },
|
||||
) => {
|
||||
const { clock } = t.context;
|
||||
const ORDERS_ERROR_MSG =
|
||||
'the orders request should not fetch, please check the condition';
|
||||
|
||||
const USER_DATA = {
|
||||
name: 'Alice',
|
||||
age: 18,
|
||||
};
|
||||
|
||||
const fetchHandler = sinon.fake(async () => {
|
||||
await delay(100);
|
||||
return {
|
||||
data: USER_DATA,
|
||||
};
|
||||
});
|
||||
|
||||
const context = new MockContext<Record<string, unknown>>(
|
||||
{},
|
||||
(ctx) => create(bindRuntimeContext(dataSource, ctx), ctx, {
|
||||
requestHandlersMap: {
|
||||
fetch: fetchHandler,
|
||||
},
|
||||
}),
|
||||
{
|
||||
recordError() {},
|
||||
},
|
||||
);
|
||||
|
||||
const setState = sinon.spy(context, 'setState');
|
||||
// const recordError = sinon.spy(context, 'recordError');
|
||||
|
||||
// 一开始应该是初始状态
|
||||
t.is(context.dataSourceMap.user.status, RuntimeDataSourceStatus.Initial);
|
||||
t.is(context.dataSourceMap.orders.status, RuntimeDataSourceStatus.Initial);
|
||||
|
||||
const loading = context.reloadDataSource();
|
||||
|
||||
await clock.tickAsync(50);
|
||||
|
||||
// 中间应该有 loading 态
|
||||
t.is(context.dataSourceMap.user.status, RuntimeDataSourceStatus.Loading);
|
||||
|
||||
await clock.tickAsync(50);
|
||||
|
||||
// 中间应该有 loading 态
|
||||
t.is(context.dataSourceMap.orders.status, RuntimeDataSourceStatus.Error);
|
||||
|
||||
await Promise.all([clock.runAllAsync(), loading]);
|
||||
|
||||
// 最后 user 成功, orders 失败
|
||||
t.is(context.dataSourceMap.user.status, RuntimeDataSourceStatus.Loaded);
|
||||
t.is(context.dataSourceMap.orders.status, RuntimeDataSourceStatus.Error);
|
||||
|
||||
// 检查数据源的数据
|
||||
t.deepEqual(context.dataSourceMap.user.data, USER_DATA);
|
||||
t.is(context.dataSourceMap.user.error, undefined);
|
||||
t.regex(
|
||||
context.dataSourceMap.orders.error!.message,
|
||||
new RegExp(ORDERS_ERROR_MSG),
|
||||
);
|
||||
|
||||
// 检查状态数据
|
||||
t.assert(setState.calledOnce);
|
||||
t.deepEqual(context.state.user, USER_DATA);
|
||||
|
||||
// fetchHandler 应该被调用了 1 次
|
||||
t.assert(fetchHandler.calledOnce);
|
||||
|
||||
// 检查调用参数
|
||||
|
||||
const firstListItemOptions = DATA_SOURCE_SCHEMA.list[0].options;
|
||||
const fetchHandlerCallArgs = fetchHandler.firstCall.args[0];
|
||||
t.is(firstListItemOptions.uri, fetchHandlerCallArgs.uri);
|
||||
|
||||
// // 埋点应该也会被调用
|
||||
// t.assert(recordError.calledOnce);
|
||||
// t.snapshot(recordError.firstCall.args);
|
||||
};
|
||||
|
||||
normalScene.title = (providedTitle) => providedTitle || 'normal scene';
|
||||
@ -0,0 +1,20 @@
|
||||
import test, { ExecutionContext } from 'ava';
|
||||
import sinon, { SinonFakeTimers } from 'sinon';
|
||||
|
||||
import { create } from '../../../src/interpret';
|
||||
|
||||
import { DATA_SOURCE_SCHEMA } from './_datasource-schema';
|
||||
import { normalScene } from './_macro-normal';
|
||||
|
||||
test.before((t: ExecutionContext<{ clock: SinonFakeTimers }>) => {
|
||||
t.context.clock = sinon.useFakeTimers();
|
||||
});
|
||||
|
||||
test.after((t: ExecutionContext<{ clock: SinonFakeTimers }>) => {
|
||||
t.context.clock.restore();
|
||||
});
|
||||
|
||||
test(normalScene, {
|
||||
create,
|
||||
dataSource: DATA_SOURCE_SCHEMA,
|
||||
});
|
||||
@ -0,0 +1,20 @@
|
||||
import test, { ExecutionContext } from 'ava';
|
||||
import sinon, { SinonFakeTimers } from 'sinon';
|
||||
|
||||
import { create } from '../../../src/runtime';
|
||||
|
||||
import { dataSource } from './_datasource-runtime';
|
||||
import { normalScene } from './_macro-normal';
|
||||
|
||||
test.before((t: ExecutionContext<{ clock: SinonFakeTimers }>) => {
|
||||
t.context.clock = sinon.useFakeTimers();
|
||||
});
|
||||
|
||||
test.after((t: ExecutionContext<{ clock: SinonFakeTimers }>) => {
|
||||
t.context.clock.restore();
|
||||
});
|
||||
|
||||
test(normalScene, {
|
||||
create,
|
||||
dataSource,
|
||||
});
|
||||
68
packages/datasource-engine/tsconfig.json
Normal file
68
packages/datasource-engine/tsconfig.json
Normal file
@ -0,0 +1,68 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
/* Basic Options */
|
||||
// "incremental": true, /* Enable incremental compilation */
|
||||
"target": "esnext" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */,
|
||||
"module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */,
|
||||
// "lib": [], /* Specify library files to be included in the compilation. */
|
||||
// "allowJs": true, /* Allow javascript files to be compiled. */
|
||||
// "checkJs": true, /* Report errors in .js files. */
|
||||
"jsx": "preserve" /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */,
|
||||
"declaration": true /* Generates corresponding '.d.ts' file. */,
|
||||
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
|
||||
// "sourceMap": true, /* Generates corresponding '.map' file. */
|
||||
// "outFile": "./", /* Concatenate and emit output to single file. */
|
||||
"outDir": "dist" /* Redirect output structure to the directory. */,
|
||||
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
|
||||
// "composite": true, /* Enable project compilation */
|
||||
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
|
||||
// "removeComments": true, /* Do not emit comments to output. */
|
||||
// "noEmit": true, /* Do not emit outputs. */
|
||||
"importHelpers": true /* Import emit helpers from 'tslib'. */,
|
||||
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
|
||||
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
|
||||
|
||||
/* Strict Type-Checking Options */
|
||||
"strict": true /* Enable all strict type-checking options. */,
|
||||
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
|
||||
// "strictNullChecks": true, /* Enable strict null checks. */
|
||||
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
|
||||
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
|
||||
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
|
||||
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
|
||||
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
|
||||
|
||||
/* Additional Checks */
|
||||
// "noUnusedLocals": true, /* Report errors on unused locals. */
|
||||
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
||||
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||
|
||||
/* Module Resolution Options */
|
||||
"moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */,
|
||||
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
|
||||
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
||||
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
||||
// "typeRoots": [], /* List of folders to include type definitions from. */
|
||||
// "types": [], /* Type declaration files to be included in compilation. */
|
||||
"allowSyntheticDefaultImports": true /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */,
|
||||
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
|
||||
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
|
||||
/* Source Map Options */
|
||||
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
|
||||
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
|
||||
|
||||
/* Experimental Options */
|
||||
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
|
||||
|
||||
/* Advanced Options */
|
||||
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */,
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
1
packages/datasource-handlers/.eslintignore
Normal file
1
packages/datasource-handlers/.eslintignore
Normal file
@ -0,0 +1 @@
|
||||
/node_modules/
|
||||
7
packages/datasource-handlers/.eslintrc.js
Normal file
7
packages/datasource-handlers/.eslintrc.js
Normal file
@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
extends: '../../.eslintrc.js',
|
||||
rules: {
|
||||
'@typescript-eslint/no-parameter-properties': 1,
|
||||
'no-param-reassign': 0,
|
||||
},
|
||||
};
|
||||
4
packages/datasource-handlers/.prettierrc.js
Normal file
4
packages/datasource-handlers/.prettierrc.js
Normal file
@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
singleQuote: true,
|
||||
trailingComma: 'always',
|
||||
};
|
||||
5
packages/datasource-handlers/lerna.json
Normal file
5
packages/datasource-handlers/lerna.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"packages": ["packages/*"],
|
||||
"version": "independent",
|
||||
"npmClient": "yarn"
|
||||
}
|
||||
9
packages/datasource-handlers/package.json
Normal file
9
packages/datasource-handlers/package.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "@ali/lowcode-datasource-handlers",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"lerna": "^3.22.0"
|
||||
}
|
||||
}
|
||||
2
packages/datasource-handlers/packages/fetch/es/index.d.ts
vendored
Normal file
2
packages/datasource-handlers/packages/fetch/es/index.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
import { RuntimeOptionsConfig } from '@ali/build-success-types';
|
||||
export declare function createFetchHandler(config?: unknown): (options: RuntimeOptionsConfig) => Promise<any>;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user