Initial transfer API rail: client, router, docs fetcher, tests
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
14
.env.example
Normal file
14
.env.example
Normal file
@@ -0,0 +1,14 @@
|
||||
# Transfer API Rail — external ISO 20022 API
|
||||
# Copy to .env and set values. Do not commit .env.
|
||||
|
||||
# Base URL of the external API (e.g. IPv4 address)
|
||||
TRANSFER_RAIL_BASE_URL=http://187.43.157.150
|
||||
|
||||
# API key (e.g. IPv6 address or token provided by the provider)
|
||||
TRANSFER_RAIL_API_KEY=
|
||||
|
||||
# Optional: path to API docs on the external host (default: /openapi.json)
|
||||
TRANSFER_RAIL_DOCS_PATH=/openapi.json
|
||||
|
||||
# Optional: header name for API key (default: X-API-Key)
|
||||
TRANSFER_RAIL_API_KEY_HEADER=X-API-Key
|
||||
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
node_modules/
|
||||
dist/
|
||||
.env
|
||||
*.log
|
||||
.DS_Store
|
||||
coverage/
|
||||
*.tsbuildinfo
|
||||
115
README.md
Normal file
115
README.md
Normal file
@@ -0,0 +1,115 @@
|
||||
# api-transfer-joesph
|
||||
|
||||
ISO 20022 transfer API rail for single-point cash transfer credit messages. Connects to an external API (base URL + API key) and exposes a client and optional HTTP endpoint for core banking applications.
|
||||
|
||||
## Configuration
|
||||
|
||||
Environment variables (see [.env.example](.env.example)):
|
||||
|
||||
| Variable | Description |
|
||||
|----------|-------------|
|
||||
| `TRANSFER_RAIL_BASE_URL` | External API base URL (e.g. `http://187.43.157.150`) |
|
||||
| `TRANSFER_RAIL_API_KEY` | API key (e.g. IPv6 or token from provider) |
|
||||
| `TRANSFER_RAIL_DOCS_PATH` | Optional; path to API docs (default: `/openapi.json`) |
|
||||
| `TRANSFER_RAIL_API_KEY_HEADER` | Optional; header name for API key (default: `X-API-Key`) |
|
||||
| `TRANSFER_RAIL_PORT` | Optional; port for standalone server (default: `4001`) |
|
||||
|
||||
Do not commit `.env`. Use a secret manager in production.
|
||||
|
||||
## Usage
|
||||
|
||||
### Option A — As a library (in-process)
|
||||
|
||||
In the core banking app, add this repo as a submodule (e.g. `transfer-rail/`) and depend on it:
|
||||
|
||||
```json
|
||||
"dependencies": {
|
||||
"api-transfer-joesph": "file:./transfer-rail"
|
||||
}
|
||||
```
|
||||
|
||||
Then:
|
||||
|
||||
```ts
|
||||
import { createTransferRailClient, getTransferRailConfig } from 'api-transfer-joesph';
|
||||
|
||||
const config = getTransferRailConfig();
|
||||
const client = createTransferRailClient(config);
|
||||
|
||||
const result = await client.sendCreditTransfer({
|
||||
messageType: 'pacs.008',
|
||||
sender: 'SENDERBIC',
|
||||
receiver: 'RECEIVERBIC',
|
||||
document: { amount: '100', currency: 'USD' },
|
||||
});
|
||||
```
|
||||
|
||||
### Option B — As an HTTP endpoint (sidecar)
|
||||
|
||||
Mount the router in your Express app:
|
||||
|
||||
```ts
|
||||
import { createTransferRailRouter } from 'api-transfer-joesph/router';
|
||||
|
||||
app.use('/api/transfer-rail', createTransferRailRouter());
|
||||
```
|
||||
|
||||
Or run the standalone server:
|
||||
|
||||
```bash
|
||||
npm run start
|
||||
# Listens on TRANSFER_RAIL_PORT or 4001
|
||||
```
|
||||
|
||||
Endpoints:
|
||||
|
||||
- `GET /api/transfer-rail/health` — health check (and optional external API reachability).
|
||||
- `POST /api/transfer-rail/iso20022/send` — body: `{ messageType, sender, receiver, document }` (aligned with asle bank API). Returns `{ success, messageId }`.
|
||||
|
||||
### Fetching API docs from the external host
|
||||
|
||||
```ts
|
||||
import { fetchApiDocs, getTransferRailConfig } from 'api-transfer-joesph';
|
||||
|
||||
const config = getTransferRailConfig();
|
||||
const docs = await fetchApiDocs(config);
|
||||
if (docs.ok && typeof docs.body === 'object') {
|
||||
console.log('OpenAPI spec:', docs.body);
|
||||
}
|
||||
```
|
||||
|
||||
## Adding this repo as a submodule (core banking app)
|
||||
|
||||
From the root of the core banking application repo:
|
||||
|
||||
```bash
|
||||
git submodule add <repo-url> transfer-rail
|
||||
git add .gitmodules transfer-rail
|
||||
git commit -m "Add api-transfer-joesph as transfer-rail submodule"
|
||||
```
|
||||
|
||||
Clone the parent repo with submodules:
|
||||
|
||||
```bash
|
||||
git clone --recurse-submodules <parent-repo-url>
|
||||
# or after clone:
|
||||
git submodule update --init --recursive
|
||||
```
|
||||
|
||||
Update the submodule to latest:
|
||||
|
||||
```bash
|
||||
git submodule update --remote transfer-rail
|
||||
```
|
||||
|
||||
## Build and test
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run build
|
||||
npm test
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
ISC
|
||||
10
jest.config.js
Normal file
10
jest.config.js
Normal file
@@ -0,0 +1,10 @@
|
||||
/** @type {import('ts-jest').JestConfigWithTsJest} */
|
||||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
roots: ['<rootDir>/src'],
|
||||
testMatch: ['**/*.test.ts'],
|
||||
collectCoverageFrom: ['src/**/*.ts', '!src/**/*.test.ts'],
|
||||
coverageDirectory: 'coverage',
|
||||
moduleFileExtensions: ['ts', 'js', 'json'],
|
||||
};
|
||||
4852
package-lock.json
generated
Normal file
4852
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
49
package.json
Normal file
49
package.json
Normal file
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"name": "api-transfer-joesph",
|
||||
"version": "1.0.0",
|
||||
"description": "ISO 20022 transfer API rail — single-point cash transfer credit messages (external API client + optional HTTP endpoint)",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"prepare": "npm run build",
|
||||
"test": "jest",
|
||||
"test:watch": "jest --watch",
|
||||
"start": "node dist/server.js"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/index.js",
|
||||
"require": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts"
|
||||
},
|
||||
"./client": {
|
||||
"import": "./dist/client.js",
|
||||
"require": "./dist/client.js",
|
||||
"types": "./dist/client.d.ts"
|
||||
},
|
||||
"./router": {
|
||||
"import": "./dist/router.js",
|
||||
"require": "./dist/router.js",
|
||||
"types": "./dist/router.d.ts"
|
||||
}
|
||||
},
|
||||
"keywords": ["iso20022", "transfer", "rail", "pacs.008", "banking"],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"type": "commonjs",
|
||||
"dependencies": {
|
||||
"express": "^4.22.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/node": "^22.10.1",
|
||||
"jest": "^29.7.0",
|
||||
"ts-jest": "^29.2.5",
|
||||
"typescript": "^5.6.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
}
|
||||
116
src/client.test.ts
Normal file
116
src/client.test.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
import { createTransferRailClient } from './client';
|
||||
|
||||
const baseUrl = 'http://187.43.157.150';
|
||||
const apiKey = '2804:388:c339:5954:48da:4664:dca1:d00c';
|
||||
const config = { baseUrl, apiKey, apiKeyHeader: 'X-API-Key' as const };
|
||||
|
||||
describe('createTransferRailClient', () => {
|
||||
const originalFetch = globalThis.fetch;
|
||||
|
||||
afterEach(() => {
|
||||
globalThis.fetch = originalFetch;
|
||||
});
|
||||
|
||||
describe('sendCreditTransfer', () => {
|
||||
it('sends ISO 20022 message and returns messageId on 200', async () => {
|
||||
globalThis.fetch = jest.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
status: 200,
|
||||
headers: new Headers({ 'content-type': 'application/json' }),
|
||||
json: () => Promise.resolve({ messageId: 'msg-123' }),
|
||||
text: () => Promise.resolve(''),
|
||||
});
|
||||
|
||||
const client = createTransferRailClient(config);
|
||||
const result = await client.sendCreditTransfer({
|
||||
messageType: 'pacs.008',
|
||||
sender: 'SENDERBIC',
|
||||
receiver: 'RECEIVERBIC',
|
||||
document: { amount: '100', currency: 'USD' },
|
||||
});
|
||||
|
||||
expect(result.ok).toBe(true);
|
||||
expect(result.messageId).toBe('msg-123');
|
||||
expect(result.statusCode).toBe(200);
|
||||
expect(fetch).toHaveBeenCalledWith(
|
||||
`${baseUrl}/api/iso20022/send`,
|
||||
expect.objectContaining({
|
||||
method: 'POST',
|
||||
headers: expect.objectContaining({ 'X-API-Key': apiKey }),
|
||||
body: expect.any(String),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('returns ok: false and error on non-2xx', async () => {
|
||||
globalThis.fetch = jest.fn().mockResolvedValue({
|
||||
ok: false,
|
||||
status: 502,
|
||||
headers: new Headers({ 'content-type': 'application/json' }),
|
||||
json: () => Promise.resolve({ error: 'Bad gateway' }),
|
||||
text: () => Promise.resolve(''),
|
||||
});
|
||||
|
||||
const client = createTransferRailClient(config);
|
||||
const result = await client.sendCreditTransfer({
|
||||
messageType: 'pacs.008',
|
||||
sender: 'A',
|
||||
receiver: 'B',
|
||||
document: {},
|
||||
});
|
||||
|
||||
expect(result.ok).toBe(false);
|
||||
expect(result.error).toBeDefined();
|
||||
expect(result.statusCode).toBe(502);
|
||||
});
|
||||
|
||||
it('returns ok: false on network error', async () => {
|
||||
globalThis.fetch = jest.fn().mockRejectedValue(new Error('ECONNREFUSED'));
|
||||
|
||||
const client = createTransferRailClient(config);
|
||||
const result = await client.sendCreditTransfer({
|
||||
messageType: 'pacs.008',
|
||||
sender: 'A',
|
||||
receiver: 'B',
|
||||
document: {},
|
||||
});
|
||||
|
||||
expect(result.ok).toBe(false);
|
||||
expect(result.error).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('health', () => {
|
||||
it('returns ok and externalReachable when /health returns 200', async () => {
|
||||
globalThis.fetch = jest.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
status: 200,
|
||||
headers: new Headers(),
|
||||
json: () => Promise.resolve({}),
|
||||
text: () => Promise.resolve(''),
|
||||
});
|
||||
|
||||
const client = createTransferRailClient(config);
|
||||
const result = await client.health();
|
||||
|
||||
expect(result.ok).toBe(true);
|
||||
expect(result.externalReachable).toBe(true);
|
||||
});
|
||||
|
||||
it('returns ok: false when /health fails', async () => {
|
||||
globalThis.fetch = jest.fn().mockResolvedValue({
|
||||
ok: false,
|
||||
status: 503,
|
||||
headers: new Headers(),
|
||||
json: () => Promise.resolve({}),
|
||||
text: () => Promise.resolve(''),
|
||||
});
|
||||
|
||||
const client = createTransferRailClient(config);
|
||||
const result = await client.health();
|
||||
|
||||
expect(result.ok).toBe(false);
|
||||
expect(result.externalReachable).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
88
src/client.ts
Normal file
88
src/client.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import type { TransferRailConfig } from './config';
|
||||
|
||||
export interface ISO20022Message {
|
||||
messageType: string;
|
||||
sender: string;
|
||||
receiver: string;
|
||||
document: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface SendCreditTransferResult {
|
||||
messageId: string;
|
||||
ok: boolean;
|
||||
statusCode?: number;
|
||||
raw?: unknown;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export interface TransferRailClient {
|
||||
sendCreditTransfer(message: ISO20022Message): Promise<SendCreditTransferResult>;
|
||||
health(): Promise<{ ok: boolean; externalReachable?: boolean; error?: string }>;
|
||||
}
|
||||
|
||||
function defaultPostPath(baseUrl: string): string {
|
||||
return `${baseUrl.replace(/\/$/, '')}/api/iso20022/send`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a transfer rail client that uses base URL + API key for all requests.
|
||||
* Sends ISO 20022 single-point cash transfer credit messages (e.g. pacs.008).
|
||||
*/
|
||||
export function createTransferRailClient(config: TransferRailConfig): TransferRailClient {
|
||||
const baseUrl = config.baseUrl.replace(/\/$/, '');
|
||||
const apiKeyHeader = config.apiKeyHeader || 'X-API-Key';
|
||||
|
||||
async function sendCreditTransfer(message: ISO20022Message): Promise<SendCreditTransferResult> {
|
||||
const url = defaultPostPath(baseUrl);
|
||||
const headers: Record<string, string> = {
|
||||
[apiKeyHeader]: config.apiKey,
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
|
||||
try {
|
||||
const res = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify(message),
|
||||
});
|
||||
|
||||
const contentType = res.headers.get('content-type') || '';
|
||||
let raw: unknown;
|
||||
if (contentType.includes('application/json')) {
|
||||
raw = await res.json();
|
||||
} else {
|
||||
raw = await res.text();
|
||||
}
|
||||
|
||||
if (res.ok) {
|
||||
const data = raw as { messageId?: string; id?: string };
|
||||
const messageId = data.messageId ?? data.id ?? `ISO20022-${Date.now()}`;
|
||||
return { messageId, ok: true, statusCode: res.status, raw };
|
||||
}
|
||||
|
||||
const error = typeof raw === 'object' && raw !== null && 'error' in raw
|
||||
? String((raw as { error: unknown }).error)
|
||||
: `HTTP ${res.status}`;
|
||||
return { messageId: '', ok: false, statusCode: res.status, raw, error };
|
||||
} catch (err) {
|
||||
const error = err instanceof Error ? err.message : String(err);
|
||||
return { messageId: '', ok: false, error };
|
||||
}
|
||||
}
|
||||
|
||||
async function health(): Promise<{ ok: boolean; externalReachable?: boolean; error?: string }> {
|
||||
const url = `${baseUrl}/health`;
|
||||
try {
|
||||
const res = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: { [apiKeyHeader]: config.apiKey },
|
||||
});
|
||||
return { ok: res.ok, externalReachable: true };
|
||||
} catch (err) {
|
||||
const error = err instanceof Error ? err.message : String(err);
|
||||
return { ok: false, externalReachable: false, error };
|
||||
}
|
||||
}
|
||||
|
||||
return { sendCreditTransfer, health };
|
||||
}
|
||||
25
src/config.ts
Normal file
25
src/config.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
export interface TransferRailConfig {
|
||||
baseUrl: string;
|
||||
apiKey: string;
|
||||
docsPath?: string;
|
||||
apiKeyHeader?: string;
|
||||
}
|
||||
|
||||
const DEFAULT_DOCS_PATH = '/openapi.json';
|
||||
const DEFAULT_API_KEY_HEADER = 'X-API-Key';
|
||||
|
||||
export function getTransferRailConfig(): TransferRailConfig {
|
||||
const baseUrl = process.env.TRANSFER_RAIL_BASE_URL || '';
|
||||
const apiKey = process.env.TRANSFER_RAIL_API_KEY || '';
|
||||
return {
|
||||
baseUrl: baseUrl.replace(/\/$/, ''),
|
||||
apiKey,
|
||||
docsPath: process.env.TRANSFER_RAIL_DOCS_PATH || DEFAULT_DOCS_PATH,
|
||||
apiKeyHeader: process.env.TRANSFER_RAIL_API_KEY_HEADER || DEFAULT_API_KEY_HEADER,
|
||||
};
|
||||
}
|
||||
|
||||
export function isTransferRailConfigured(config?: TransferRailConfig): boolean {
|
||||
const c = config ?? getTransferRailConfig();
|
||||
return Boolean(c.baseUrl && c.apiKey);
|
||||
}
|
||||
60
src/docs.ts
Normal file
60
src/docs.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import type { TransferRailConfig } from './config';
|
||||
|
||||
export interface DocsFetcherResult {
|
||||
ok: boolean;
|
||||
contentType?: string;
|
||||
body?: string | object;
|
||||
statusCode?: number;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches API documentation from the external host using the same API key.
|
||||
* Tries common paths (openapi.json, swagger.json, /docs) if docsPath fails.
|
||||
*/
|
||||
export async function fetchApiDocs(config: TransferRailConfig): Promise<DocsFetcherResult> {
|
||||
const headers: Record<string, string> = {
|
||||
[config.apiKeyHeader || 'X-API-Key']: config.apiKey,
|
||||
Accept: 'application/json, text/plain, */*',
|
||||
};
|
||||
|
||||
const pathsToTry = [
|
||||
config.docsPath || '/openapi.json',
|
||||
'/openapi.json',
|
||||
'/swagger.json',
|
||||
'/api-docs',
|
||||
'/docs',
|
||||
].filter((p, i, a) => a.indexOf(p) === i);
|
||||
|
||||
for (const path of pathsToTry) {
|
||||
const url = `${config.baseUrl.replace(/\/$/, '')}${path.startsWith('/') ? path : '/' + path}`;
|
||||
try {
|
||||
const res = await fetch(url, { headers, method: 'GET' });
|
||||
const contentType = res.headers.get('content-type') || '';
|
||||
let body: string | object;
|
||||
if (contentType.includes('application/json')) {
|
||||
body = (await res.json()) as object;
|
||||
} else {
|
||||
body = await res.text();
|
||||
}
|
||||
if (res.ok) {
|
||||
return { ok: true, contentType, body, statusCode: res.status };
|
||||
}
|
||||
return {
|
||||
ok: false,
|
||||
statusCode: res.status,
|
||||
contentType,
|
||||
body,
|
||||
error: `HTTP ${res.status}`,
|
||||
};
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
if (path === pathsToTry[pathsToTry.length - 1]) {
|
||||
return { ok: false, error: message };
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return { ok: false, error: 'Could not fetch docs from any path' };
|
||||
}
|
||||
8
src/index.ts
Normal file
8
src/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export { getTransferRailConfig, isTransferRailConfigured } from './config';
|
||||
export type { TransferRailConfig } from './config';
|
||||
export { fetchApiDocs } from './docs';
|
||||
export type { DocsFetcherResult } from './docs';
|
||||
export { createTransferRailClient } from './client';
|
||||
export type { TransferRailClient, ISO20022Message, SendCreditTransferResult } from './client';
|
||||
export { createTransferRailRouter } from './router';
|
||||
export { startTransferRailServer } from './server';
|
||||
74
src/router.ts
Normal file
74
src/router.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import { Router, Request, Response } from 'express';
|
||||
import { getTransferRailConfig, isTransferRailConfigured } from './config';
|
||||
import { createTransferRailClient } from './client';
|
||||
|
||||
/**
|
||||
* Express router for the transfer rail HTTP API (Option B).
|
||||
* Mount at e.g. app.use('/api/transfer-rail', railRouter).
|
||||
* GET /health, POST /iso20022/send — body aligned with asle bank API.
|
||||
*/
|
||||
export function createTransferRailRouter(): Router {
|
||||
const router = Router();
|
||||
const config = getTransferRailConfig();
|
||||
|
||||
if (!isTransferRailConfigured(config)) {
|
||||
router.use((_req: Request, res: Response) => {
|
||||
res.status(503).json({
|
||||
error: 'Transfer rail not configured',
|
||||
hint: 'Set TRANSFER_RAIL_BASE_URL and TRANSFER_RAIL_API_KEY',
|
||||
});
|
||||
});
|
||||
return router;
|
||||
}
|
||||
|
||||
const client = createTransferRailClient(config);
|
||||
|
||||
router.get('/health', async (_req: Request, res: Response) => {
|
||||
try {
|
||||
const health = await client.health();
|
||||
res.status(health.ok ? 200 : 503).json({
|
||||
status: health.ok ? 'ok' : 'degraded',
|
||||
externalReachable: health.externalReachable,
|
||||
error: health.error,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
} catch (err) {
|
||||
res.status(503).json({
|
||||
status: 'error',
|
||||
error: err instanceof Error ? err.message : String(err),
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/iso20022/send', async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { messageType, sender, receiver, document } = req.body;
|
||||
if (!messageType || !sender || !receiver || document === undefined) {
|
||||
return res.status(400).json({
|
||||
error: 'messageType, sender, receiver, and document are required',
|
||||
});
|
||||
}
|
||||
const result = await client.sendCreditTransfer({
|
||||
messageType,
|
||||
sender,
|
||||
receiver,
|
||||
document: typeof document === 'object' ? document : {},
|
||||
});
|
||||
if (!result.ok) {
|
||||
return res.status(result.statusCode && result.statusCode >= 400 ? result.statusCode : 502).json({
|
||||
error: result.error || 'Failed to send ISO 20022 message',
|
||||
success: false,
|
||||
});
|
||||
}
|
||||
res.json({ success: true, messageId: result.messageId });
|
||||
} catch (err) {
|
||||
res.status(500).json({
|
||||
error: err instanceof Error ? err.message : String(err),
|
||||
success: false,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return router;
|
||||
}
|
||||
20
src/server.ts
Normal file
20
src/server.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import express from 'express';
|
||||
import { createTransferRailRouter } from './router';
|
||||
|
||||
/**
|
||||
* Standalone server for the transfer rail (Option B — run as separate process).
|
||||
* Use startTransferRailServer() from the core app or run: node dist/server.js
|
||||
*/
|
||||
export function startTransferRailServer(port?: number): ReturnType<express.Application['listen']> {
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
app.use('/', createTransferRailRouter());
|
||||
const p = port ?? (Number(process.env.TRANSFER_RAIL_PORT) || 4001);
|
||||
return app.listen(p, () => {
|
||||
console.log(`Transfer rail server listening on port ${p}`);
|
||||
});
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
startTransferRailServer();
|
||||
}
|
||||
18
tsconfig.json
Normal file
18
tsconfig.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "commonjs",
|
||||
"lib": ["ES2020"],
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist", "**/*.test.ts"]
|
||||
}
|
||||
Reference in New Issue
Block a user