@ruiapp/rapid-core 0.3.2 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +3 -0
- package/dist/index.js +48 -24
- package/dist/plugins/cronJob/CronJobPluginTypes.d.ts +14 -0
- package/dist/utilities/entityUtility.d.ts +1 -0
- package/package.json +1 -1
- package/rollup.config.js +16 -16
- package/src/core/actionHandler.ts +22 -22
- package/src/core/eventManager.ts +20 -20
- package/src/core/facility.ts +7 -7
- package/src/core/http/formDataParser.ts +89 -89
- package/src/core/http-types.ts +4 -4
- package/src/core/pluginManager.ts +175 -175
- package/src/core/providers/runtimeProvider.ts +5 -5
- package/src/core/request.ts +86 -86
- package/src/core/routesBuilder.ts +88 -88
- package/src/deno-std/assert/assert.ts +9 -9
- package/src/deno-std/assert/assertion_error.ts +7 -7
- package/src/deno-std/datetime/to_imf.ts +32 -32
- package/src/deno-std/encoding/base64.ts +141 -141
- package/src/facilities/log/LogFacility.ts +35 -35
- package/src/helpers/inputHelper.ts +11 -11
- package/src/index.ts +3 -0
- package/src/plugins/auth/actionHandlers/deleteSession.ts +18 -18
- package/src/plugins/auth/actionHandlers/getMyProfile.ts +35 -35
- package/src/plugins/auth/actionHandlers/index.ts +8 -8
- package/src/plugins/auth/models/AccessToken.ts +56 -56
- package/src/plugins/auth/models/index.ts +3 -3
- package/src/plugins/auth/routes/changePassword.ts +15 -15
- package/src/plugins/auth/routes/getMyProfile.ts +15 -15
- package/src/plugins/auth/routes/index.ts +7 -7
- package/src/plugins/auth/routes/resetPassword.ts +15 -15
- package/src/plugins/auth/routes/signin.ts +15 -15
- package/src/plugins/auth/routes/signout.ts +15 -15
- package/src/plugins/cronJob/CronJobPlugin.ts +1 -0
- package/src/plugins/cronJob/CronJobPluginTypes.ts +65 -49
- package/src/plugins/cronJob/actionHandlers/index.ts +4 -4
- package/src/plugins/cronJob/actionHandlers/runCronJob.ts +29 -29
- package/src/plugins/cronJob/routes/index.ts +3 -3
- package/src/plugins/cronJob/routes/runCronJob.ts +15 -15
- package/src/plugins/dataManage/actionHandlers/queryDatabase.ts +22 -22
- package/src/plugins/fileManage/actionHandlers/uploadFile.ts +33 -33
- package/src/plugins/fileManage/routes/downloadDocument.ts +15 -15
- package/src/plugins/fileManage/routes/downloadFile.ts +15 -15
- package/src/plugins/fileManage/routes/index.ts +5 -5
- package/src/plugins/fileManage/routes/uploadFile.ts +15 -15
- package/src/plugins/metaManage/actionHandlers/getMetaModelDetail.ts +10 -10
- package/src/plugins/metaManage/actionHandlers/listMetaModels.ts +9 -9
- package/src/plugins/metaManage/actionHandlers/listMetaRoutes.ts +9 -9
- package/src/plugins/routeManage/actionHandlers/httpProxy.ts +13 -13
- package/src/plugins/sequence/actionHandlers/index.ts +4 -4
- package/src/plugins/sequence/models/SequenceAutoIncrementRecord.ts +49 -49
- package/src/plugins/sequence/models/SequenceRule.ts +42 -42
- package/src/plugins/sequence/models/index.ts +4 -4
- package/src/plugins/sequence/routes/generateSn.ts +15 -15
- package/src/plugins/sequence/routes/index.ts +3 -3
- package/src/plugins/sequence/segment-utility.ts +11 -11
- package/src/plugins/sequence/segments/index.ts +9 -9
- package/src/plugins/serverOperation/ServerOperationPlugin.ts +91 -91
- package/src/plugins/serverOperation/ServerOperationPluginTypes.ts +15 -15
- package/src/plugins/serverOperation/actionHandlers/index.ts +4 -4
- package/src/plugins/serverOperation/actionHandlers/runServerOperation.ts +15 -15
- package/src/plugins/setting/SettingService.ts +213 -213
- package/src/plugins/setting/actionHandlers/getSystemSettingValues.ts +30 -30
- package/src/plugins/setting/actionHandlers/getUserSettingValues.ts +38 -38
- package/src/plugins/setting/actionHandlers/index.ts +6 -6
- package/src/plugins/setting/actionHandlers/setSystemSettingValues.ts +30 -30
- package/src/plugins/setting/models/SystemSettingGroupSetting.ts +57 -57
- package/src/plugins/setting/models/SystemSettingItemSetting.ts +73 -73
- package/src/plugins/setting/models/UserSettingGroupSetting.ts +57 -57
- package/src/plugins/setting/models/UserSettingItemSetting.ts +73 -73
- package/src/plugins/setting/models/index.ts +8 -8
- package/src/plugins/setting/routes/getSystemSettingValues.ts +15 -15
- package/src/plugins/setting/routes/getUserSettingValues.ts +15 -15
- package/src/plugins/setting/routes/index.ts +5 -5
- package/src/plugins/setting/routes/setSystemSettingValues.ts +15 -15
- package/src/plugins/stateMachine/actionHandlers/index.ts +4 -4
- package/src/plugins/stateMachine/models/StateMachine.ts +42 -42
- package/src/plugins/stateMachine/models/index.ts +3 -3
- package/src/plugins/stateMachine/routes/index.ts +3 -3
- package/src/plugins/stateMachine/routes/sendStateMachineEvent.ts +15 -15
- package/src/plugins/stateMachine/stateMachineHelper.ts +36 -36
- package/src/polyfill.ts +5 -5
- package/src/proxy/mod.ts +38 -38
- package/src/proxy/types.ts +21 -21
- package/src/queryBuilder/index.ts +1 -1
- package/src/utilities/accessControlUtility.ts +33 -33
- package/src/utilities/entityUtility.ts +18 -0
- package/src/utilities/fsUtility.ts +61 -61
- package/src/utilities/httpUtility.ts +19 -19
- package/src/utilities/jwtUtility.ts +26 -26
- package/src/utilities/timeUtility.ts +9 -9
- package/tsconfig.json +19 -19
package/dist/index.d.ts
CHANGED
|
@@ -5,7 +5,10 @@ export * from "./core/routeContext";
|
|
|
5
5
|
export * from "./core/server";
|
|
6
6
|
export * from "./core/http-types";
|
|
7
7
|
export * from "./core/actionHandler";
|
|
8
|
+
export * from "./utilities/accessControlUtility";
|
|
9
|
+
export * from "./utilities/entityUtility";
|
|
8
10
|
export * from "./utilities/jwtUtility";
|
|
11
|
+
export * from "./utilities/timeUtility";
|
|
9
12
|
export { mapDbRowToEntity } from "./dataAccess/entityMapper";
|
|
10
13
|
export * as bootstrapApplicationConfig from "./bootstrapApplicationConfig";
|
|
11
14
|
export { default as MetaManagePlugin } from "./plugins/metaManage/MetaManagePlugin";
|
package/dist/index.js
CHANGED
|
@@ -2304,6 +2304,9 @@ function getEntityPartChanges(server, model, before, after) {
|
|
|
2304
2304
|
return null;
|
|
2305
2305
|
}
|
|
2306
2306
|
|
|
2307
|
+
function getNowString() {
|
|
2308
|
+
return dayjs__default["default"]().format("YYYY-MM-DD HH:mm:ss.SSS");
|
|
2309
|
+
}
|
|
2307
2310
|
function getNowStringWithTimezone() {
|
|
2308
2311
|
return dayjs__default["default"]().format("YYYY-MM-DD HH:mm:ss.SSSZ");
|
|
2309
2312
|
}
|
|
@@ -4175,6 +4178,46 @@ class RapidServer {
|
|
|
4175
4178
|
}
|
|
4176
4179
|
}
|
|
4177
4180
|
|
|
4181
|
+
function isAccessAllowed(policy, allowedActions) {
|
|
4182
|
+
let isAnyCheckPassed = true;
|
|
4183
|
+
let isAllCheckPassed = true;
|
|
4184
|
+
if (policy.any) {
|
|
4185
|
+
isAnyCheckPassed = false;
|
|
4186
|
+
for (const action of policy.any) {
|
|
4187
|
+
if (lodash.find(allowedActions, (item) => item === action) != null) {
|
|
4188
|
+
isAnyCheckPassed = true;
|
|
4189
|
+
break;
|
|
4190
|
+
}
|
|
4191
|
+
}
|
|
4192
|
+
}
|
|
4193
|
+
if (policy.all) {
|
|
4194
|
+
isAllCheckPassed = true;
|
|
4195
|
+
for (const action of policy.all) {
|
|
4196
|
+
if (lodash.find(allowedActions, (item) => item === action) == null) {
|
|
4197
|
+
isAnyCheckPassed = false;
|
|
4198
|
+
break;
|
|
4199
|
+
}
|
|
4200
|
+
}
|
|
4201
|
+
}
|
|
4202
|
+
return isAnyCheckPassed && isAllCheckPassed;
|
|
4203
|
+
}
|
|
4204
|
+
|
|
4205
|
+
function getEntityRelationTargetId(entity, propName, targetColumnName) {
|
|
4206
|
+
if (!entity) {
|
|
4207
|
+
throw new Error(`"entity" parameter is required.`);
|
|
4208
|
+
}
|
|
4209
|
+
let targetId = entity[propName];
|
|
4210
|
+
if (!targetId && targetColumnName) {
|
|
4211
|
+
targetId = entity[targetColumnName];
|
|
4212
|
+
}
|
|
4213
|
+
if (lodash.isObject(targetId)) {
|
|
4214
|
+
return targetId.id;
|
|
4215
|
+
}
|
|
4216
|
+
else {
|
|
4217
|
+
return targetId;
|
|
4218
|
+
}
|
|
4219
|
+
}
|
|
4220
|
+
|
|
4178
4221
|
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
|
4179
4222
|
// This module is browser compatible.
|
|
4180
4223
|
/**
|
|
@@ -7810,6 +7853,7 @@ class CronJobPlugin {
|
|
|
7810
7853
|
async onApplicationReady(server, applicationConfig) {
|
|
7811
7854
|
for (const job of this.#jobs) {
|
|
7812
7855
|
const jobInstance = cron__namespace.CronJob.from({
|
|
7856
|
+
...(job.jobOptions || {}),
|
|
7813
7857
|
cronTime: job.cronTime,
|
|
7814
7858
|
onTick: async () => {
|
|
7815
7859
|
server.getLogger().info(`Executing cron job '${job.code}'...`);
|
|
@@ -8111,30 +8155,6 @@ function getStateMachineCode(model, property) {
|
|
|
8111
8155
|
}
|
|
8112
8156
|
}
|
|
8113
8157
|
|
|
8114
|
-
function isAccessAllowed(policy, allowedActions) {
|
|
8115
|
-
let isAnyCheckPassed = true;
|
|
8116
|
-
let isAllCheckPassed = true;
|
|
8117
|
-
if (policy.any) {
|
|
8118
|
-
isAnyCheckPassed = false;
|
|
8119
|
-
for (const action of policy.any) {
|
|
8120
|
-
if (lodash.find(allowedActions, (item) => item === action) != null) {
|
|
8121
|
-
isAnyCheckPassed = true;
|
|
8122
|
-
break;
|
|
8123
|
-
}
|
|
8124
|
-
}
|
|
8125
|
-
}
|
|
8126
|
-
if (policy.all) {
|
|
8127
|
-
isAllCheckPassed = true;
|
|
8128
|
-
for (const action of policy.all) {
|
|
8129
|
-
if (lodash.find(allowedActions, (item) => item === action) == null) {
|
|
8130
|
-
isAnyCheckPassed = false;
|
|
8131
|
-
break;
|
|
8132
|
-
}
|
|
8133
|
-
}
|
|
8134
|
-
}
|
|
8135
|
-
return isAnyCheckPassed && isAllCheckPassed;
|
|
8136
|
-
}
|
|
8137
|
-
|
|
8138
8158
|
class EntityAccessControlPlugin {
|
|
8139
8159
|
constructor() { }
|
|
8140
8160
|
get code() {
|
|
@@ -8279,5 +8299,9 @@ exports.bootstrapApplicationConfig = bootstrapApplicationConfig$1;
|
|
|
8279
8299
|
exports.createJwt = createJwt;
|
|
8280
8300
|
exports.decodeJwt = decodeJwt;
|
|
8281
8301
|
exports.generateJwtSecretKey = generateJwtSecretKey;
|
|
8302
|
+
exports.getEntityRelationTargetId = getEntityRelationTargetId;
|
|
8303
|
+
exports.getNowString = getNowString;
|
|
8304
|
+
exports.getNowStringWithTimezone = getNowStringWithTimezone;
|
|
8305
|
+
exports.isAccessAllowed = isAccessAllowed;
|
|
8282
8306
|
exports.mapDbRowToEntity = mapDbRowToEntity;
|
|
8283
8307
|
exports.verifyJwt = verifyJwt;
|
|
@@ -12,6 +12,10 @@ export interface CronJobConfiguration {
|
|
|
12
12
|
* crontab 表达式
|
|
13
13
|
*/
|
|
14
14
|
cronTime: string;
|
|
15
|
+
/**
|
|
16
|
+
* 任务设置
|
|
17
|
+
*/
|
|
18
|
+
jobOptions?: CronJobOptions;
|
|
15
19
|
/**
|
|
16
20
|
* 任务处理程序编号。当指定处理程序编号时,忽略 handler 配置。
|
|
17
21
|
*/
|
|
@@ -28,6 +32,16 @@ export interface CronJobConfiguration {
|
|
|
28
32
|
*/
|
|
29
33
|
handleOptions?: any;
|
|
30
34
|
}
|
|
35
|
+
export interface CronJobOptions {
|
|
36
|
+
/**
|
|
37
|
+
* Instantly triggers the onTick function post initialization. Default is false.
|
|
38
|
+
*/
|
|
39
|
+
runOnInit?: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* If true, no additional instances of the onTick callback function will run until the current onTick callback has completed. Any new scheduled executions that occur while the current callback is running will be skipped entirely. Default is false.
|
|
42
|
+
*/
|
|
43
|
+
waitForCompletion?: boolean;
|
|
44
|
+
}
|
|
31
45
|
export interface CronJobPluginInitOptions {
|
|
32
46
|
jobs: CronJobConfiguration[];
|
|
33
47
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function getEntityRelationTargetId(entity: Record<string, any>, propName: string, targetColumnName?: string): number;
|
package/package.json
CHANGED
package/rollup.config.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import typescript from "rollup-plugin-typescript2";
|
|
2
|
-
import tscAlias from "rollup-plugin-tsc-alias";
|
|
3
|
-
|
|
4
|
-
export default {
|
|
5
|
-
input: ["src/index.ts"],
|
|
6
|
-
output: [
|
|
7
|
-
{
|
|
8
|
-
dir: "dist",
|
|
9
|
-
entryFileNames: "[name].js",
|
|
10
|
-
format: "cjs",
|
|
11
|
-
exports: "named",
|
|
12
|
-
},
|
|
13
|
-
],
|
|
14
|
-
plugins: [typescript(), tscAlias()],
|
|
15
|
-
external: [],
|
|
16
|
-
};
|
|
1
|
+
import typescript from "rollup-plugin-typescript2";
|
|
2
|
+
import tscAlias from "rollup-plugin-tsc-alias";
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
input: ["src/index.ts"],
|
|
6
|
+
output: [
|
|
7
|
+
{
|
|
8
|
+
dir: "dist",
|
|
9
|
+
entryFileNames: "[name].js",
|
|
10
|
+
format: "cjs",
|
|
11
|
+
exports: "named",
|
|
12
|
+
},
|
|
13
|
+
],
|
|
14
|
+
plugins: [typescript(), tscAlias()],
|
|
15
|
+
external: [],
|
|
16
|
+
};
|
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
import { RpdApplicationConfig } from "~/types";
|
|
2
|
-
import { IRpdServer, RapidPlugin } from "./server";
|
|
3
|
-
import { Next, RouteContext } from "./routeContext";
|
|
4
|
-
import { Logger } from "~/facilities/log/LogFacility";
|
|
5
|
-
|
|
6
|
-
export interface ActionHandlerContext {
|
|
7
|
-
logger: Logger;
|
|
8
|
-
routerContext: RouteContext;
|
|
9
|
-
next: Next;
|
|
10
|
-
server: IRpdServer;
|
|
11
|
-
applicationConfig: RpdApplicationConfig;
|
|
12
|
-
input?: any;
|
|
13
|
-
output?: any;
|
|
14
|
-
status?: Response["status"];
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export type ActionHandler = (ctx: ActionHandlerContext, options: any) => void | Promise<void>;
|
|
18
|
-
|
|
19
|
-
export interface IPluginActionHandler {
|
|
20
|
-
code: string;
|
|
21
|
-
handler: (plugin: RapidPlugin, ctx: ActionHandlerContext, options: any) => void | Promise<void>;
|
|
22
|
-
}
|
|
1
|
+
import { RpdApplicationConfig } from "~/types";
|
|
2
|
+
import { IRpdServer, RapidPlugin } from "./server";
|
|
3
|
+
import { Next, RouteContext } from "./routeContext";
|
|
4
|
+
import { Logger } from "~/facilities/log/LogFacility";
|
|
5
|
+
|
|
6
|
+
export interface ActionHandlerContext {
|
|
7
|
+
logger: Logger;
|
|
8
|
+
routerContext: RouteContext;
|
|
9
|
+
next: Next;
|
|
10
|
+
server: IRpdServer;
|
|
11
|
+
applicationConfig: RpdApplicationConfig;
|
|
12
|
+
input?: any;
|
|
13
|
+
output?: any;
|
|
14
|
+
status?: Response["status"];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type ActionHandler = (ctx: ActionHandlerContext, options: any) => void | Promise<void>;
|
|
18
|
+
|
|
19
|
+
export interface IPluginActionHandler {
|
|
20
|
+
code: string;
|
|
21
|
+
handler: (plugin: RapidPlugin, ctx: ActionHandlerContext, options: any) => void | Promise<void>;
|
|
22
|
+
}
|
package/src/core/eventManager.ts
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
import { EventEmitter } from "events";
|
|
2
|
-
|
|
3
|
-
export default class EventManager<EventTypes extends Record<string, any[]>> {
|
|
4
|
-
#eventEmitter: EventEmitter;
|
|
5
|
-
|
|
6
|
-
constructor() {
|
|
7
|
-
this.#eventEmitter = new EventEmitter();
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
on<K extends keyof EventTypes>(eventName: K, listener: (...args: EventTypes[K]) => void) {
|
|
11
|
-
this.#eventEmitter.on(eventName as string, listener);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
async emit<K extends keyof EventTypes>(eventName: K, ...args: EventTypes[K]) {
|
|
15
|
-
const listeners = this.#eventEmitter.listeners(eventName as string);
|
|
16
|
-
for (const listener of listeners) {
|
|
17
|
-
await listener(...args);
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
}
|
|
1
|
+
import { EventEmitter } from "events";
|
|
2
|
+
|
|
3
|
+
export default class EventManager<EventTypes extends Record<string, any[]>> {
|
|
4
|
+
#eventEmitter: EventEmitter;
|
|
5
|
+
|
|
6
|
+
constructor() {
|
|
7
|
+
this.#eventEmitter = new EventEmitter();
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
on<K extends keyof EventTypes>(eventName: K, listener: (...args: EventTypes[K]) => void) {
|
|
11
|
+
this.#eventEmitter.on(eventName as string, listener);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async emit<K extends keyof EventTypes>(eventName: K, ...args: EventTypes[K]) {
|
|
15
|
+
const listeners = this.#eventEmitter.listeners(eventName as string);
|
|
16
|
+
for (const listener of listeners) {
|
|
17
|
+
await listener(...args);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
package/src/core/facility.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { IRpdServer } from "./server";
|
|
2
|
-
|
|
3
|
-
export interface FacilityFactory {
|
|
4
|
-
name: string;
|
|
5
|
-
|
|
6
|
-
createFacility: (server: IRpdServer, options?: any) => Promise<any>;
|
|
7
|
-
}
|
|
1
|
+
import { IRpdServer } from "./server";
|
|
2
|
+
|
|
3
|
+
export interface FacilityFactory {
|
|
4
|
+
name: string;
|
|
5
|
+
|
|
6
|
+
createFacility: (server: IRpdServer, options?: any) => Promise<any>;
|
|
7
|
+
}
|
|
@@ -1,89 +1,89 @@
|
|
|
1
|
-
import type { RapidRequest } from "../request";
|
|
2
|
-
|
|
3
|
-
export type BodyData = Record<string, string | File | (string | File)[]>;
|
|
4
|
-
export type ParseBodyOptions = {
|
|
5
|
-
/**
|
|
6
|
-
* Parse all fields with multiple values should be parsed as an array.
|
|
7
|
-
* @default false
|
|
8
|
-
* @example
|
|
9
|
-
* ```ts
|
|
10
|
-
* const data = new FormData()
|
|
11
|
-
* data.append('file', 'aaa')
|
|
12
|
-
* data.append('file', 'bbb')
|
|
13
|
-
* data.append('message', 'hello')
|
|
14
|
-
* ```
|
|
15
|
-
*
|
|
16
|
-
* If `all` is `false`:
|
|
17
|
-
* parseBody should return `{ file: 'bbb', message: 'hello' }`
|
|
18
|
-
*
|
|
19
|
-
* If `all` is `true`:
|
|
20
|
-
* parseBody should return `{ file: ['aaa', 'bbb'], message: 'hello' }`
|
|
21
|
-
*/
|
|
22
|
-
all?: boolean;
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
export const parseFormDataBody = async <T extends BodyData = BodyData>(request: Request, options: ParseBodyOptions = { all: false }): Promise<T> => {
|
|
26
|
-
const contentType = request.headers.get("Content-Type");
|
|
27
|
-
|
|
28
|
-
if (isFormDataContent(contentType)) {
|
|
29
|
-
return parseFormData<T>(request, options);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
return {} as T;
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
function isFormDataContent(contentType: string | null): boolean {
|
|
36
|
-
if (contentType === null) {
|
|
37
|
-
return false;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return contentType.startsWith("multipart/form-data") || contentType.startsWith("application/x-www-form-urlencoded");
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
async function parseFormData<T extends BodyData = BodyData>(request: Request, options: ParseBodyOptions): Promise<T> {
|
|
44
|
-
const formData = await (request as Request).formData();
|
|
45
|
-
|
|
46
|
-
if (formData) {
|
|
47
|
-
return convertFormDataToBodyData<T>(formData, options);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
return {} as T;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function convertFormDataToBodyData<T extends BodyData = BodyData>(formData: FormData, options: ParseBodyOptions): T {
|
|
54
|
-
const form: BodyData = {};
|
|
55
|
-
|
|
56
|
-
formData.forEach((value, key) => {
|
|
57
|
-
const shouldParseAllValues = options.all || key.endsWith("[]");
|
|
58
|
-
|
|
59
|
-
if (!shouldParseAllValues) {
|
|
60
|
-
form[key] = value;
|
|
61
|
-
} else {
|
|
62
|
-
handleParsingAllValues(form, key, value);
|
|
63
|
-
}
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
return form as T;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const handleParsingAllValues = (form: BodyData, key: string, value: FormDataEntryValue): void => {
|
|
70
|
-
if (form[key] && isArrayField(form[key])) {
|
|
71
|
-
appendToExistingArray(form[key] as (string | File)[], value);
|
|
72
|
-
} else if (form[key]) {
|
|
73
|
-
convertToNewArray(form, key, value);
|
|
74
|
-
} else {
|
|
75
|
-
form[key] = value;
|
|
76
|
-
}
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
function isArrayField(field: unknown): field is (string | File)[] {
|
|
80
|
-
return Array.isArray(field);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const appendToExistingArray = (arr: (string | File)[], value: FormDataEntryValue): void => {
|
|
84
|
-
arr.push(value);
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
const convertToNewArray = (form: BodyData, key: string, value: FormDataEntryValue): void => {
|
|
88
|
-
form[key] = [form[key] as string | File, value];
|
|
89
|
-
};
|
|
1
|
+
import type { RapidRequest } from "../request";
|
|
2
|
+
|
|
3
|
+
export type BodyData = Record<string, string | File | (string | File)[]>;
|
|
4
|
+
export type ParseBodyOptions = {
|
|
5
|
+
/**
|
|
6
|
+
* Parse all fields with multiple values should be parsed as an array.
|
|
7
|
+
* @default false
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* const data = new FormData()
|
|
11
|
+
* data.append('file', 'aaa')
|
|
12
|
+
* data.append('file', 'bbb')
|
|
13
|
+
* data.append('message', 'hello')
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* If `all` is `false`:
|
|
17
|
+
* parseBody should return `{ file: 'bbb', message: 'hello' }`
|
|
18
|
+
*
|
|
19
|
+
* If `all` is `true`:
|
|
20
|
+
* parseBody should return `{ file: ['aaa', 'bbb'], message: 'hello' }`
|
|
21
|
+
*/
|
|
22
|
+
all?: boolean;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const parseFormDataBody = async <T extends BodyData = BodyData>(request: Request, options: ParseBodyOptions = { all: false }): Promise<T> => {
|
|
26
|
+
const contentType = request.headers.get("Content-Type");
|
|
27
|
+
|
|
28
|
+
if (isFormDataContent(contentType)) {
|
|
29
|
+
return parseFormData<T>(request, options);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return {} as T;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
function isFormDataContent(contentType: string | null): boolean {
|
|
36
|
+
if (contentType === null) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return contentType.startsWith("multipart/form-data") || contentType.startsWith("application/x-www-form-urlencoded");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function parseFormData<T extends BodyData = BodyData>(request: Request, options: ParseBodyOptions): Promise<T> {
|
|
44
|
+
const formData = await (request as Request).formData();
|
|
45
|
+
|
|
46
|
+
if (formData) {
|
|
47
|
+
return convertFormDataToBodyData<T>(formData, options);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return {} as T;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function convertFormDataToBodyData<T extends BodyData = BodyData>(formData: FormData, options: ParseBodyOptions): T {
|
|
54
|
+
const form: BodyData = {};
|
|
55
|
+
|
|
56
|
+
formData.forEach((value, key) => {
|
|
57
|
+
const shouldParseAllValues = options.all || key.endsWith("[]");
|
|
58
|
+
|
|
59
|
+
if (!shouldParseAllValues) {
|
|
60
|
+
form[key] = value;
|
|
61
|
+
} else {
|
|
62
|
+
handleParsingAllValues(form, key, value);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
return form as T;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const handleParsingAllValues = (form: BodyData, key: string, value: FormDataEntryValue): void => {
|
|
70
|
+
if (form[key] && isArrayField(form[key])) {
|
|
71
|
+
appendToExistingArray(form[key] as (string | File)[], value);
|
|
72
|
+
} else if (form[key]) {
|
|
73
|
+
convertToNewArray(form, key, value);
|
|
74
|
+
} else {
|
|
75
|
+
form[key] = value;
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
function isArrayField(field: unknown): field is (string | File)[] {
|
|
80
|
+
return Array.isArray(field);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const appendToExistingArray = (arr: (string | File)[], value: FormDataEntryValue): void => {
|
|
84
|
+
arr.push(value);
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const convertToNewArray = (form: BodyData, key: string, value: FormDataEntryValue): void => {
|
|
88
|
+
form[key] = [form[key] as string | File, value];
|
|
89
|
+
};
|
package/src/core/http-types.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type HttpStatus = number;
|
|
2
|
-
export type ResponseData = string | ArrayBuffer | ReadableStream;
|
|
3
|
-
|
|
4
|
-
export type HeadersDefinition = [string, string][] | Record<string, string>;
|
|
1
|
+
export type HttpStatus = number;
|
|
2
|
+
export type ResponseData = string | ArrayBuffer | ReadableStream;
|
|
3
|
+
|
|
4
|
+
export type HeadersDefinition = [string, string][] | Record<string, string>;
|