This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.*
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/versions
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# env files (can opt-in for committing if needed)
|
||||
.env*
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
@@ -0,0 +1,16 @@
|
||||
# FlowGram Runtime NodeJS
|
||||
|
||||
## Starting the server
|
||||
```bash
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
## Building the server
|
||||
```bash
|
||||
pnpm build
|
||||
```
|
||||
|
||||
## Running the server
|
||||
```bash
|
||||
node dist/index.js
|
||||
```
|
||||
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
import { defineFlatConfig } from '@flowgram.ai/eslint-config';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
export default defineFlatConfig({
|
||||
parser: '@typescript-eslint/parser',
|
||||
preset: 'node',
|
||||
packageRoot: __dirname,
|
||||
ignore: ['.eslintrc.cjs'],
|
||||
parserOptions: {
|
||||
requireConfigFile: false,
|
||||
ecmaVersion: 2017,
|
||||
sourceType: 'module',
|
||||
ecmaFeatures: {
|
||||
modules: true,
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
'no-console': 'off',
|
||||
},
|
||||
plugins: ['json', '@typescript-eslint'],
|
||||
settings: {
|
||||
react: {
|
||||
version: '18',
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"name": "@flowgram.ai/runtime-nodejs",
|
||||
"version": "0.1.8",
|
||||
"homepage": "https://flowgram.ai/",
|
||||
"repository": "https://github.com/bytedance/flowgram.ai",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js"
|
||||
},
|
||||
"module": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"dev": "tsx watch src",
|
||||
"build": "npm run build:fast -- --dts-resolve",
|
||||
"build:fast": "tsup src/index.ts --format esm --sourcemap --out-dir dist",
|
||||
"build:watch": "npm run build:fast -- --dts-resolve",
|
||||
"lint": "eslint --cache src",
|
||||
"lint-fix": "eslint --fix src",
|
||||
"type-check": "tsc",
|
||||
"start": "node dist/index.js",
|
||||
"test": "exit 0",
|
||||
"test:cov": "exit 0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@flowgram.ai/runtime-js": "workspace:*",
|
||||
"@flowgram.ai/runtime-interface": "workspace:*",
|
||||
"@fastify/cors": "^8.2.1",
|
||||
"@fastify/swagger": "^8.5.1",
|
||||
"@fastify/swagger-ui": "4.1.0",
|
||||
"@fastify/websocket": "^10.0.1",
|
||||
"@trpc/server": "^10.27.1",
|
||||
"trpc-openapi": "^1.2.0",
|
||||
"fastify": "^4.17.0",
|
||||
"zod": "^3.24.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@flowgram.ai/ts-config": "workspace:*",
|
||||
"@flowgram.ai/eslint-config": "workspace:*",
|
||||
"@types/cors": "^2.8.13",
|
||||
"dotenv": "~16.5.0",
|
||||
"@types/node": "^18",
|
||||
"eslint": "^9.0.0",
|
||||
"typescript": "^5.8.3",
|
||||
"tsup": "^8.0.1",
|
||||
"tsx": "~4.19.4",
|
||||
"eslint-plugin-json": "^4.0.1",
|
||||
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
||||
"@typescript-eslint/parser": "^8.0.0",
|
||||
"@vitest/coverage-v8": "^3.2.4",
|
||||
"vitest": "^3.2.4"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"registry": "https://registry.npmjs.org/"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
import z from 'zod';
|
||||
import { WorkflowRuntimeAPIs } from '@flowgram.ai/runtime-js';
|
||||
import { FlowGramAPIMethod, FlowGramAPIName, FlowGramAPIs } from '@flowgram.ai/runtime-interface';
|
||||
|
||||
import { APIHandler } from './type';
|
||||
import { publicProcedure } from './trpc';
|
||||
|
||||
export const createAPI = (apiName: FlowGramAPIName): APIHandler => {
|
||||
const define = FlowGramAPIs[apiName];
|
||||
const caller = WorkflowRuntimeAPIs[apiName];
|
||||
if (define.method === FlowGramAPIMethod.GET) {
|
||||
const procedure = publicProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
method: define.method,
|
||||
path: define.path,
|
||||
summary: define.name,
|
||||
tags: [define.module],
|
||||
},
|
||||
})
|
||||
.input(define.schema.input)
|
||||
.output(z.union([define.schema.output, z.undefined()]))
|
||||
.query(async (opts) => {
|
||||
const input = opts.input;
|
||||
try {
|
||||
const output = await caller(input);
|
||||
return output;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
define,
|
||||
procedure: procedure as any,
|
||||
};
|
||||
}
|
||||
|
||||
const procedure = publicProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
method: define.method,
|
||||
path: define.path,
|
||||
summary: define.name,
|
||||
tags: [define.module],
|
||||
},
|
||||
})
|
||||
.input(define.schema.input)
|
||||
.output(z.union([define.schema.output, z.undefined()]))
|
||||
.mutation(async (opts) => {
|
||||
const input = opts.input;
|
||||
try {
|
||||
const output = await caller(input);
|
||||
return output;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
return {
|
||||
define,
|
||||
procedure: procedure as any,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
import { FlowGramAPINames } from '@flowgram.ai/runtime-interface';
|
||||
|
||||
import { APIRouter } from './type';
|
||||
import { router } from './trpc';
|
||||
import { createAPI } from './create-api';
|
||||
|
||||
const APIS = FlowGramAPINames.map((apiName) => createAPI(apiName));
|
||||
|
||||
export const routers = APIS.reduce((acc, api) => {
|
||||
acc[api.define.path] = api.procedure;
|
||||
return acc;
|
||||
}, {} as APIRouter);
|
||||
|
||||
export const appRouter = router(routers);
|
||||
|
||||
export type AppRouter = typeof appRouter;
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
import { OpenApiMeta } from 'trpc-openapi';
|
||||
import { initTRPC } from '@trpc/server';
|
||||
|
||||
import type { Context } from '../server/context';
|
||||
|
||||
const t = initTRPC
|
||||
.context<Context>()
|
||||
.meta<OpenApiMeta>()
|
||||
.create({
|
||||
errorFormatter({ shape }) {
|
||||
return shape;
|
||||
},
|
||||
});
|
||||
|
||||
export const router = t.router;
|
||||
export const publicProcedure = t.procedure;
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
import { BuildProcedure } from '@trpc/server';
|
||||
import { FlowGramAPIDefine } from '@flowgram.ai/runtime-interface';
|
||||
|
||||
export interface APIHandler {
|
||||
define: FlowGramAPIDefine;
|
||||
procedure: BuildProcedure<any, any, any>;
|
||||
}
|
||||
|
||||
export type APIRouter = Record<FlowGramAPIDefine['path'], APIHandler['procedure']>;
|
||||
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
import type { ServerParams } from '@server/type';
|
||||
|
||||
export const ServerConfig: ServerParams = {
|
||||
name: 'flowgram-runtime',
|
||||
title: 'FlowGram Runtime',
|
||||
description: 'FlowGram Runtime Demo',
|
||||
runtime: 'nodejs',
|
||||
version: '0.0.1',
|
||||
dev: false,
|
||||
port: 4000,
|
||||
basePath: '/api',
|
||||
docsPath: '/docs',
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
import { createServer } from '@server/index';
|
||||
|
||||
async function main() {
|
||||
const server = await createServer();
|
||||
server.start();
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
import type { CreateFastifyContextOptions } from '@trpc/server/adapters/fastify';
|
||||
|
||||
export function createContext(ctx: CreateFastifyContextOptions) {
|
||||
const { req, res } = ctx;
|
||||
return { req, res };
|
||||
}
|
||||
|
||||
export type Context = Awaited<ReturnType<typeof createContext>>;
|
||||
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
import { generateOpenApiDocument } from 'trpc-openapi';
|
||||
|
||||
import { ServerConfig } from '@config/index';
|
||||
import { appRouter } from '@api/index';
|
||||
|
||||
// Generate OpenAPI schema document
|
||||
export const serverDocument = generateOpenApiDocument(appRouter, {
|
||||
title: ServerConfig.title,
|
||||
description: ServerConfig.description,
|
||||
version: ServerConfig.version,
|
||||
baseUrl: `http://localhost:${ServerConfig.port}${ServerConfig.basePath}`,
|
||||
docsUrl: 'https://flowgram.ai',
|
||||
tags: ['Task'],
|
||||
});
|
||||
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
import { fastifyTRPCOpenApiPlugin } from 'trpc-openapi';
|
||||
import fastify from 'fastify';
|
||||
import { fastifyTRPCPlugin } from '@trpc/server/adapters/fastify';
|
||||
import { ServerInfoDefine, type ServerInfoOutput } from '@flowgram.ai/runtime-interface';
|
||||
import ws from '@fastify/websocket';
|
||||
import fastifySwaggerUI from '@fastify/swagger-ui';
|
||||
import fastifySwagger from '@fastify/swagger';
|
||||
import cors from '@fastify/cors';
|
||||
|
||||
import { ServerConfig } from '@config/index';
|
||||
import { appRouter } from '@api/index';
|
||||
import { serverDocument } from './docs';
|
||||
import { createContext } from './context';
|
||||
|
||||
export async function createServer() {
|
||||
const server = fastify({ logger: ServerConfig.dev });
|
||||
|
||||
await server.register(cors);
|
||||
await server.register(ws);
|
||||
await server.register(fastifyTRPCPlugin, {
|
||||
prefix: '/trpc',
|
||||
useWss: false,
|
||||
trpcOptions: { router: appRouter, createContext },
|
||||
});
|
||||
await server.register(fastifyTRPCOpenApiPlugin, {
|
||||
basePath: ServerConfig.basePath,
|
||||
router: appRouter,
|
||||
createContext,
|
||||
} as any);
|
||||
|
||||
await server.register(fastifySwagger, {
|
||||
mode: 'static',
|
||||
specification: { document: serverDocument },
|
||||
uiConfig: { displayOperationId: true },
|
||||
exposeRoute: true,
|
||||
} as any);
|
||||
|
||||
await server.register(fastifySwaggerUI, {
|
||||
routePrefix: ServerConfig.docsPath,
|
||||
uiConfig: {
|
||||
docExpansion: 'full',
|
||||
deepLinking: false,
|
||||
},
|
||||
uiHooks: {
|
||||
onRequest: function (request, reply, next) {
|
||||
next();
|
||||
},
|
||||
preHandler: function (request, reply, next) {
|
||||
next();
|
||||
},
|
||||
},
|
||||
staticCSP: true,
|
||||
transformStaticCSP: (header) => header,
|
||||
transformSpecification: (swaggerObject, request, reply) => swaggerObject,
|
||||
transformSpecificationClone: true,
|
||||
});
|
||||
|
||||
server.get(ServerInfoDefine.path, async (): Promise<ServerInfoOutput> => {
|
||||
const serverTime = new Date();
|
||||
const output: ServerInfoOutput = {
|
||||
name: ServerConfig.name,
|
||||
title: ServerConfig.title,
|
||||
description: ServerConfig.description,
|
||||
runtime: ServerConfig.runtime,
|
||||
version: ServerConfig.version,
|
||||
time: serverTime.toISOString(),
|
||||
};
|
||||
return output;
|
||||
});
|
||||
|
||||
const stop = async () => {
|
||||
await server.close();
|
||||
};
|
||||
const start = async () => {
|
||||
try {
|
||||
const address = await server.listen({ port: ServerConfig.port });
|
||||
await server.ready();
|
||||
server.swagger();
|
||||
console.log(
|
||||
`> Listen Port: ${ServerConfig.port}\n> Server Address: ${address}\n> API Docs: http://localhost:4000/docs`
|
||||
);
|
||||
} catch (err) {
|
||||
server.log.error(err);
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
return { server, start, stop };
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
import { ServerInfoOutput } from '@flowgram.ai/runtime-interface';
|
||||
|
||||
export interface ServerParams extends Omit<ServerInfoOutput, 'time'> {
|
||||
dev: boolean;
|
||||
port: number;
|
||||
basePath: string;
|
||||
docsPath: string;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"extends": "@flowgram.ai/ts-config/tsconfig.flow.path.json",
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"plugins": [],
|
||||
"baseUrl": "src",
|
||||
"paths": {
|
||||
"@api/*": [
|
||||
"api/*"
|
||||
],
|
||||
"@application/*": [
|
||||
"application/*"
|
||||
],
|
||||
"@server/*": [
|
||||
"server/*"
|
||||
],
|
||||
"@config/*": [
|
||||
"config/*"
|
||||
],
|
||||
"@workflow/*": [
|
||||
"workflow/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
"src/workflow/executor/condition/constant.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
import { config } from "dotenv";
|
||||
import path from 'path';
|
||||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
export default defineConfig({
|
||||
build: {
|
||||
commonjsOptions: {
|
||||
transformMixedEsModules: true,
|
||||
},
|
||||
},
|
||||
resolve: {
|
||||
alias: [
|
||||
{find: "@api", replacement: path.resolve(__dirname, './src/api') },
|
||||
{find: "@application", replacement: path.resolve(__dirname, './src/application') },
|
||||
{find: "@server", replacement: path.resolve(__dirname, './src/server') },
|
||||
{find: "@config", replacement: path.resolve(__dirname, './src/config') },
|
||||
{find: "@workflow", replacement: path.resolve(__dirname, './src/workflow') },
|
||||
],
|
||||
},
|
||||
test: {
|
||||
globals: true,
|
||||
mockReset: false,
|
||||
environment: 'jsdom',
|
||||
testTimeout: 15000,
|
||||
setupFiles: [path.resolve(__dirname, './src/workflow/__tests__/setup.ts')],
|
||||
include: ['**/?(*.){test,spec}.?(c|m)[jt]s?(x)'],
|
||||
exclude: [
|
||||
'**/__mocks__**',
|
||||
'**/node_modules/**',
|
||||
'**/dist/**',
|
||||
'**/lib/**', // lib 编译结果忽略掉
|
||||
'**/cypress/**',
|
||||
'**/.{idea,git,cache,output,temp}/**',
|
||||
'**/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress,tsup,build}.config.*',
|
||||
],
|
||||
env: {
|
||||
...config({ path: path.resolve(__dirname, './.env/.env.test') }).parsed
|
||||
}
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user