Skip to content

Commit

Permalink
✨ feat: support node runtime
Browse files Browse the repository at this point in the history
  • Loading branch information
arvinxx committed Dec 13, 2023
1 parent e8c8cb5 commit a8e2933
Show file tree
Hide file tree
Showing 8 changed files with 120 additions and 18 deletions.
7 changes: 7 additions & 0 deletions api/v1/edge.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { createGatewayOnEdgeRuntime } from '../../src';

export const config = {
runtime: 'edge',
};

export default createGatewayOnEdgeRuntime();
3 changes: 3 additions & 0 deletions api/v1/node.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { createGatewayOnNodeRuntime } from '../../src';

export default createGatewayOnNodeRuntime();
4 changes: 2 additions & 2 deletions api/v1/runner.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createLobeChatPluginGateway } from '../../src';
import { createGatewayOnEdgeRuntime } from '../../src';

export const config = {
runtime: 'edge',
};

export default createLobeChatPluginGateway();
export default createGatewayOnEdgeRuntime();
8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,11 @@
"not ie <= 10"
],
"dependencies": {
"@babel/runtime": "^7",
"@cfworker/json-schema": "^1",
"@lobehub/chat-plugin-sdk": "latest",
"swagger-client": "^3.24.5",
"@lobehub/chat-plugin-sdk": "^1",
"ajv": "^8",
"swagger-client": "^3",
"zod": "^3"
},
"devDependencies": {
Expand All @@ -71,7 +73,7 @@
"remark-cli": "^11",
"semantic-release": "^21",
"typescript": "^5",
"vercel": "^28.20.0",
"vercel": "^32.7.1",
"vitest": "0.34.6"
},
"publishConfig": {
Expand Down
47 changes: 34 additions & 13 deletions src/gateway.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
// reason to use cfworker json schema:
// https://github.com/vercel/next.js/discussions/47063#discussioncomment-5303951
import { Validator } from '@cfworker/json-schema';
import { Schema } from '@cfworker/json-schema';
import {
IPluginErrorType,
LobeChatPluginApi,
LobeChatPluginManifest,
LobeChatPluginsMarketIndex,
Expand All @@ -13,11 +14,15 @@ import {
pluginMetaSchema,
pluginRequestPayloadSchema,
} from '@lobehub/chat-plugin-sdk';
import { IPluginErrorType } from '@lobehub/chat-plugin-sdk/lib/error';
// @ts-ignore
import SwaggerClient from 'swagger-client';

export const DEFAULT_PLUGINS_INDEX_URL = 'https://chat-plugins.lobehub.com';

type IValidator = (schema: Schema, value: any) => { errors?: any; valid: boolean };

export interface GatewayOptions {
Validator?: IValidator;
/**
* @default https://chat-plugins.lobehub.com
*/
Expand All @@ -33,23 +38,40 @@ export interface GatewayErrorResponse {
errorType: IPluginErrorType;
success: false;
}

export class Gateway {
private pluginIndexUrl = DEFAULT_PLUGINS_INDEX_URL;
private _validator: IValidator | undefined;

constructor(options?: GatewayOptions) {
if (options?.pluginsIndexUrl) {
this.pluginIndexUrl = options.pluginsIndexUrl;
}
if (options?.Validator) {
this._validator = options.Validator;
}
}

createSuccessResponse = (data: string) => {
private createSuccessResponse = (data: string) => {
return { data, success: true } as const;
};

createErrorResponse = (errorType: IPluginErrorType | string, body?: string | object) => {
private createErrorResponse = (errorType: IPluginErrorType | string, body?: string | object) => {
throw { body, errorType, success: false };
};

private async validate(schema: Schema, value: any) {
if (this._validator) return this._validator(schema, value);

// reason to use cfworker json schema:
// https://github.com/vercel/next.js/discussions/47063#discussioncomment-5303951
const { Validator } = await import('@cfworker/json-schema');
const v = new Validator(schema);
const validator = v.validate(value);
if (!validator.valid) return { errors: validator.errors, valid: false };
return { valid: true };
}

execute = async (
payload: PluginRequestPayload,
settings?: any,
Expand Down Expand Up @@ -164,11 +186,11 @@ export class Gateway {
// ========== 6. 校验是否按照 manifest 包含了 settings 配置 ========== //

if (manifest.settings) {
const v = new Validator(manifest.settings as any);
const validator = v.validate(settings || {});
if (!validator.valid)
const { valid, errors } = await this.validate(manifest.settings as any, settings || {});

if (!valid)
return this.createErrorResponse(PluginErrorType.PluginSettingsInvalid, {
error: validator.errors,
error: errors,
message: '[plugin] your settings is invalid with plugin manifest setting schema',
settings,
});
Expand All @@ -188,14 +210,13 @@ export class Gateway {
});

if (args) {
const v = new Validator(api.parameters as any);
const params = JSON.parse(args!);
const validator = v.validate(params);
const { valid, errors } = await this.validate(api.parameters as any, params);

if (!validator.valid)
if (!valid)
return this.createErrorResponse(PluginErrorType.PluginApiParamsError, {
api,
error: validator.errors,
error: errors,
message: '[plugin] args is invalid with plugin manifest api schema',
request: params,
});
Expand Down Expand Up @@ -247,7 +268,7 @@ export class Gateway {
const { arguments: args, apiName } = payload;

// @ts-ignore
const { default: SwaggerClient } = await import('swagger-client');
// const { default: SwaggerClient } = await import('swagger-client');

const authorizations = {} as {
[key: string]: any;
Expand Down
5 changes: 5 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
export {
createGatewayOnEdgeRuntime,
/**
* please use `createGatewayOnEdgeRuntime` instead
* @deprecated
*/
createGatewayOnEdgeRuntime as createLobeChatPluginGateway,
} from './edge';
export * from './gateway';
export * from './node';
54 changes: 54 additions & 0 deletions src/node.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import {
PluginErrorType,
PluginRequestPayload,
getPluginErrorStatus,
getPluginSettingsFromHeaders,
} from '@lobehub/chat-plugin-sdk';
import { VercelRequest, VercelResponse } from '@vercel/node';
import Ajv from 'ajv';

import { Gateway, GatewayErrorResponse, GatewayOptions } from './gateway';

export type NodeRuntimeGatewayOptions = Pick<GatewayOptions, 'pluginsIndexUrl'>;

export const createGatewayOnNodeRuntime = (options: NodeRuntimeGatewayOptions = {}) => {
const gateway = new Gateway({
...options,
Validator: (schema, value) => {
const ajv = new Ajv();
const validate = ajv.compile(schema);

const valid = validate(value);
return {
errors: validate.errors,
valid,
};
},
});

return async (req: VercelRequest, res: VercelResponse) => {
// ========== 1. 校验请求方法 ========== //
if (req.method !== 'POST') {
res.status(PluginErrorType.MethodNotAllowed).send({
message: '[gateway] only allow POST method',
});

return;
}

const requestPayload = req.body as PluginRequestPayload;

const settings = getPluginSettingsFromHeaders(req.headers as any);

try {
const { data } = await gateway.execute(requestPayload, settings);

res.send(data);
} catch (error) {
console.log(error);
const { errorType, body } = error as GatewayErrorResponse;

res.status(getPluginErrorStatus(errorType)).send({ body, errorType });
}
};
};
10 changes: 10 additions & 0 deletions tests/gateway.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Gateway } from '@lobehub/chat-plugins-gateway';
import { describe, expect, it } from 'vitest';

describe('Gateway', () => {
it('should init with pluginIndexUrl', () => {
const gateway = new Gateway({ pluginsIndexUrl: 'https://test-market-index-url.com' });

expect(gateway['pluginIndexUrl']).toBe('https://test-market-index-url.com');
});
});

0 comments on commit a8e2933

Please sign in to comment.