Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 26 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Adapter for web frameworks (Express, Koa) to run on serverless platforms across
| ---------------------- | ------------------------------- | ------------ | ------------ |
| Alibaba Cloud (Aliyun) | Function Compute | ✅ Supported | API Gateway |
| Tencent Cloud | Serverless Cloud Function (SCF) | ✅ Supported | API Gateway |
| Volcengine | veFaaS (函数服务) | ✅ Supported | API Gateway |

## Supported Frameworks

Expand Down Expand Up @@ -107,6 +108,22 @@ app.get('/api/users', (req, res) => {
export const main_handler = serverlessAdapter(app, { provider: 'tencent' });
```

#### Volcengine veFaaS Example

```typescript
import express from 'express';
import serverlessAdapter from '@geek-fun/serverless-adapter';

const app = express();

app.get('/api/users', (req, res) => {
res.json({ users: [] });
});

// Handler for Volcengine veFaaS
export const handler = serverlessAdapter(app, { provider: 'volcengine' });
```

## API Reference

### `serverlessAdapter(app, options?)`
Expand All @@ -115,10 +132,10 @@ Creates a serverless handler for your Express or Koa application.

#### Parameters

| Parameter | Type | Required | Description |
| ------------------ | ----------------------- | -------- | ------------------------------------------------------------ |
| `app` | `Express \| Koa` | Yes | Express or Koa application instance |
| `options.provider` | `'aliyun' \| 'tencent'` | No | Explicitly specify cloud provider (auto-detected if omitted) |
| Parameter | Type | Required | Description |
| ------------------ | --------------------------------------- | -------- | ------------------------------------------------------------ |
| `app` | `Express \| Koa` | Yes | Express or Koa application instance |
| `options.provider` | `'aliyun' \| 'tencent' \| 'volcengine'` | No | Explicitly specify cloud provider (auto-detected if omitted) |

#### Returns

Expand All @@ -138,10 +155,11 @@ A function that handles serverless events:

The adapter automatically detects the cloud provider by examining the `context` object:

| Provider | Detection Fields |
| -------- | -------------------------------------------------------- |
| Aliyun | `accountId`, `credentials`, `service`, `tracing` |
| Tencent | `tencentcloud_region`, `tencentcloud_appid`, `namespace` |
| Provider | Detection Fields |
| ---------- | -------------------------------------------------------- |
| Aliyun | `service.name`, `tracing`, `logger`, `function.memory` |
| Tencent | `tencentcloud_region`, `tencentcloud_appid`, `namespace` |
| Volcengine | `requestId`, `region`, `function.memoryMb` |

## License

Expand Down
11 changes: 10 additions & 1 deletion src/providers/aliyun.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,15 @@ export class AliyunProvider extends BaseProvider {

detect(_rawEvent: ProviderEvent, rawContext: ProviderContext): boolean {
const context = rawContext as AliyunApiGatewayContext;
return !!(context.accountId || context.credentials || context.service || context.tracing);

if (context.service?.name || context.tracing || context.logger) {
return true;
}

if (context.credentials && context.function?.memory !== undefined) {
return true;
}

return false;
}
}
3 changes: 3 additions & 0 deletions src/providers/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ServerlessProvider } from './base';
import { AliyunProvider } from './aliyun';
import { TencentProvider } from './tencent';
import { VolcengineProvider } from './volcengine';
import { CloudProvider, ProviderContext, ProviderEvent } from '../types';

const providers: Map<CloudProvider, ServerlessProvider> = new Map();
Expand Down Expand Up @@ -31,7 +32,9 @@ export function getAllProviders(): Map<CloudProvider, ServerlessProvider> {

registerProvider(new AliyunProvider());
registerProvider(new TencentProvider());
registerProvider(new VolcengineProvider());

export { ServerlessProvider } from './base';
export { AliyunProvider } from './aliyun';
export { TencentProvider } from './tencent';
export { VolcengineProvider } from './volcengine';
62 changes: 62 additions & 0 deletions src/providers/volcengine.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { BaseProvider } from './base';
import {
VolcengineApiGatewayEvent,
VolcengineVefaasContext,
VolcengineVefaasResponse,
ServerlessEvent,
ServerlessResponse,
ProviderContext,
ProviderEvent,
} from '../types';

export class VolcengineProvider extends BaseProvider {
readonly name = 'volcengine' as const;

normalizeEvent(rawEvent: ProviderEvent): ServerlessEvent {
const volcengineEvent = JSON.parse(
Buffer.from(rawEvent as Buffer).toString(),
) as VolcengineApiGatewayEvent;

return {
path: volcengineEvent.path,
httpMethod: volcengineEvent.method,
headers: volcengineEvent.headers || {},
queryParameters: volcengineEvent.query || {},
pathParameters: {},
body: volcengineEvent.body,
isBase64Encoded: false,
};
}

formatResponse(response: ServerlessResponse): VolcengineVefaasResponse {
return {
statusCode: response.statusCode,
headers: response.headers,
body: response.body,
};
}

detect(_rawEvent: ProviderEvent, rawContext: ProviderContext): boolean {
const context = rawContext as VolcengineVefaasContext;
const aliyunContext = rawContext as {
service?: { name?: string };
tracing?: unknown;
logger?: unknown;
};
const hasAliyunSpecificFields = !!(
aliyunContext.service?.name ||
aliyunContext.tracing ||
aliyunContext.logger
);

if (hasAliyunSpecificFields) {
return false;
}

return !!(
context.requestId &&
context.region &&
(context.accountId || context.credentials || context.function?.memoryMb)
);
}
}
20 changes: 17 additions & 3 deletions src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ import {
TencentEvent,
TencentHandler,
} from './tencent';
import {
VolcengineApiGatewayEvent,
VolcengineVefaasContext,
VolcengineVefaasResponse,
VolcengineEvent,
VolcengineHandler,
} from './volcengine';

export { AliyunApiGatewayContext, AliyunEvent, AliyunResponse };
export {
Expand All @@ -18,6 +25,13 @@ export {
TencentEvent,
TencentHandler,
};
export {
VolcengineApiGatewayEvent,
VolcengineVefaasContext,
VolcengineVefaasResponse,
VolcengineEvent,
VolcengineHandler,
};

export type Context = AliyunApiGatewayContext;
export type Event = Buffer;
Expand Down Expand Up @@ -48,17 +62,17 @@ export type ServerlessResponse = {
/**
* Supported cloud providers
*/
export type CloudProvider = 'aliyun' | 'tencent';
export type CloudProvider = 'aliyun' | 'tencent' | 'volcengine';

/**
* Provider-specific context types
*/
export type ProviderContext = AliyunApiGatewayContext | TencentScfContext;
export type ProviderContext = AliyunApiGatewayContext | TencentScfContext | VolcengineVefaasContext;

/**
* Provider-specific event types
*/
export type ProviderEvent = AliyunEvent | TencentEvent;
export type ProviderEvent = AliyunEvent | TencentEvent | VolcengineEvent;

export type ServerlessAdapter = (app: Express | Application) => (
event: Event,
Expand Down
68 changes: 68 additions & 0 deletions src/types/volcengine.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { IncomingHttpHeaders } from 'http';

/**
* Volcengine veFaaS API Gateway Trigger Event
* @see https://www.volcengine.com/docs/6662/116904
*/
export interface VolcengineApiGatewayEvent {
path: string;
method: string;
headers: Record<string, string>;
query: Record<string, string>;
body: string;
requestContext: {
requestId: string;
stage: string;
serviceId: string;
sourceIp?: string;
};
}

/**
* Volcengine veFaaS Context Object
* @see https://www.volcengine.com/docs/6662/116908
*/
export interface VolcengineVefaasContext {
requestId: string;
credentials?: {
accessKeyId: string;
accessKeySecret: string;
securityToken: string;
};
function?: {
name: string;
handler: string;
memoryMb: number;
timeout: number;
};
service?: {
logProject: string;
logStore: string;
qualifier: string;
versionId: string;
};
region: string;
accountId: string;
}

/**
* Volcengine veFaaS Response for API Gateway Integration
*/
export interface VolcengineVefaasResponse {
statusCode: number;
headers: IncomingHttpHeaders;
body: string;
}

/**
* Volcengine veFaaS Event (Buffer containing JSON)
*/
export type VolcengineEvent = Buffer;

/**
* Volcengine veFaaS Handler Type
*/
export type VolcengineHandler = (
event: VolcengineEvent,
context: VolcengineVefaasContext,
) => Promise<VolcengineVefaasResponse>;
4 changes: 2 additions & 2 deletions tests/fixtures/tencentContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export const defaultTencentContext: TencentScfContext = {
export const defaultTencentApiGatewayEvent: TencentApiGatewayEvent = {
requestContext: {
serviceId: 'service-test-id',
path: '/test',
path: '/api/test',
httpMethod: 'GET',
requestId: 'req-12345',
identity: {
Expand All @@ -28,7 +28,7 @@ export const defaultTencentApiGatewayEvent: TencentApiGatewayEvent = {
sourceIp: '192.168.1.1',
stage: 'release',
},
path: '/test',
path: '/api/test',
httpMethod: 'GET',
queryString: {},
body: '',
Expand Down
53 changes: 53 additions & 0 deletions tests/fixtures/volcengineContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { VolcengineVefaasContext, VolcengineApiGatewayEvent } from '../../src/types/volcengine';

export const defaultVolcengineContext: VolcengineVefaasContext = {
requestId: 'volcengine-request-id-12345',
credentials: {
accessKeyId: 'test-access-key-id',
accessKeySecret: 'test-access-key-secret',
securityToken: 'test-security-token',
},
function: {
name: 'test-function',
handler: 'index.handler',
memoryMb: 128,
timeout: 30,
},
service: {
logProject: 'test-log-project',
logStore: 'test-log-store',
qualifier: '$LATEST',
versionId: 'v1',
},
region: 'cn-beijing',
accountId: '1234567890',
};

export const defaultVolcengineApiGatewayEvent: VolcengineApiGatewayEvent = {
path: '/api/test',
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
query: {},
body: '',
requestContext: {
requestId: 'req-12345',
stage: 'release',
serviceId: 'service-test-id',
},
};

export const createVolcengineEvent = (
overrides: Partial<VolcengineApiGatewayEvent> = {},
): VolcengineApiGatewayEvent => ({
...defaultVolcengineApiGatewayEvent,
...overrides,
});

export const createVolcengineContext = (
overrides: Partial<VolcengineVefaasContext> = {},
): VolcengineVefaasContext => ({
...defaultVolcengineContext,
...overrides,
});
Loading
Loading