@ranwhenparked/trustap-sdk 0.1.0
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/README.md +251 -0
- package/deno.json +9 -0
- package/eslint.config.js +21 -0
- package/package.json +47 -0
- package/scripts/build-node.mjs +75 -0
- package/scripts/generate-operations-map.mjs +57 -0
- package/scripts/generate-security-map.mjs +92 -0
- package/src/__tests__/auth-middleware.test.ts +171 -0
- package/src/__tests__/client-factory.test.ts +105 -0
- package/src/__tests__/error-handling.test.ts +302 -0
- package/src/__tests__/helpers/mock-http-client.ts +193 -0
- package/src/__tests__/helpers/run-guard.ts +24 -0
- package/src/__tests__/helpers/test-fixtures.ts +82 -0
- package/src/__tests__/node-client.test.ts +244 -0
- package/src/__tests__/operation-proxy.test.ts +268 -0
- package/src/__tests__/query-params.test.ts +232 -0
- package/src/__tests__/setup.ts +44 -0
- package/src/__tests__/types.test.ts +169 -0
- package/src/client-deno.ts +219 -0
- package/src/client-factory.ts +45 -0
- package/src/core.ts +619 -0
- package/src/index.deno.ts +28 -0
- package/src/index.ts +36 -0
- package/src/operations-map.ts +667 -0
- package/src/schema.d.ts +12046 -0
- package/src/security-map.ts +770 -0
- package/src/state-machine.ts +79 -0
- package/src/webhook-schemas.ts +400 -0
- package/tsconfig.build.json +27 -0
- package/tsconfig.json +22 -0
- package/vitest.config.ts +31 -0
package/README.md
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
# @ranwhenparked/trustap-sdk
|
|
2
|
+
|
|
3
|
+
A lightweight typed [Trustap API](https://docs.trustap.com/apis/openapi) wrapper built with openapi-typescript + openapi-fetch.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @ranwhenparked/trustap-sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
### Path-based client
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { createTrustapClient } from "@ranwhenparked/trustap-sdk";
|
|
17
|
+
|
|
18
|
+
const trustap = createTrustapClient({
|
|
19
|
+
apiUrl: process.env.TRUSTAP_API_URL!,
|
|
20
|
+
basicAuth: {
|
|
21
|
+
username: process.env.TRUSTAP_API_KEY!,
|
|
22
|
+
password: "", // Trustap basic auth uses API key + empty password
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const { data, error } = await trustap["/users/me/balances"].GET({
|
|
27
|
+
headers: { Authorization: `Bearer ${accessToken}` },
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// Or verb-based via raw
|
|
31
|
+
const { data: d2 } = await trustap.raw.GET("/users/me/balances", {
|
|
32
|
+
headers: { Authorization: `Bearer ${accessToken}` },
|
|
33
|
+
});
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Operation ID-based client
|
|
37
|
+
|
|
38
|
+
Call API methods directly by their operation ID with full type safety:
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
const { data, error } = await trustap["users.getBalances"]();
|
|
42
|
+
|
|
43
|
+
// With path parameters
|
|
44
|
+
const { data: tx } = await trustap["basic_client.getTransaction"]({
|
|
45
|
+
params: {
|
|
46
|
+
path: { client_id: "my-client", transaction_id: "123" },
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// With query parameters
|
|
51
|
+
const { data: transactions } = await trustap["basic_client.getTransactions"]({
|
|
52
|
+
params: {
|
|
53
|
+
path: { client_id: "my-client" },
|
|
54
|
+
query: { status: "paid" },
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Authentication
|
|
60
|
+
|
|
61
|
+
The SDK supports multiple authentication strategies:
|
|
62
|
+
|
|
63
|
+
### Basic Auth (Server-to-server)
|
|
64
|
+
|
|
65
|
+
Configure HTTP Basic auth for server-to-server endpoints:
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
const trustap = createTrustapClient({
|
|
69
|
+
apiUrl: "https://api.trustap.com",
|
|
70
|
+
basicAuth: {
|
|
71
|
+
username: process.env.TRUSTAP_API_KEY!,
|
|
72
|
+
password: "", // Trustap uses API key + empty password
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### OAuth2 Access Token
|
|
78
|
+
|
|
79
|
+
For user-context endpoints, provide a `getAccessToken` callback:
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
const trustap = createTrustapClient({
|
|
83
|
+
apiUrl: "https://api.trustap.com",
|
|
84
|
+
basicAuth: {
|
|
85
|
+
username: process.env.TRUSTAP_API_KEY!,
|
|
86
|
+
password: "",
|
|
87
|
+
},
|
|
88
|
+
getAccessToken: async () => {
|
|
89
|
+
// Return the current user's OAuth2 access token
|
|
90
|
+
return session.accessToken;
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
The SDK automatically selects the correct auth strategy per endpoint based on the OpenAPI spec. For endpoints that support both, it prefers Basic auth for server-to-server calls.
|
|
96
|
+
|
|
97
|
+
### Auth Overrides
|
|
98
|
+
|
|
99
|
+
Override the automatic auth selection for specific endpoints:
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
const trustap = createTrustapClient({
|
|
103
|
+
apiUrl: "https://api.trustap.com",
|
|
104
|
+
basicAuth: { username: apiKey, password: "" },
|
|
105
|
+
getAccessToken: async () => userAccessToken,
|
|
106
|
+
authOverrides: {
|
|
107
|
+
"/charge": "basic", // Always use Basic auth
|
|
108
|
+
"/users/me/balances": "oauth2", // Always use OAuth2
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Webhook Schemas
|
|
114
|
+
|
|
115
|
+
Validate incoming Trustap webhooks with Zod schemas:
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
import {
|
|
119
|
+
trustapWebhookEventSchema,
|
|
120
|
+
type TrustapWebhookEvent,
|
|
121
|
+
} from "@ranwhenparked/trustap-sdk";
|
|
122
|
+
|
|
123
|
+
// Parse and validate webhook payload
|
|
124
|
+
const result = trustapWebhookEventSchema.safeParse(req.body);
|
|
125
|
+
if (!result.success) {
|
|
126
|
+
console.error("Invalid webhook:", result.error);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const event: TrustapWebhookEvent = result.data;
|
|
131
|
+
console.log(event.code); // e.g., "basic_tx.paid"
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Exhaustive Handler Pattern
|
|
135
|
+
|
|
136
|
+
Use `createWebhookHandlers` for compile-time exhaustiveness checking:
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
import {
|
|
140
|
+
createWebhookHandlers,
|
|
141
|
+
type TrustapWebhookEvent,
|
|
142
|
+
} from "@ranwhenparked/trustap-sdk";
|
|
143
|
+
|
|
144
|
+
const handlers = createWebhookHandlers({
|
|
145
|
+
"basic_tx.joined": (event) => {
|
|
146
|
+
console.log("Transaction joined at:", event.target_preview.joined);
|
|
147
|
+
},
|
|
148
|
+
"basic_tx.paid": (event) => {
|
|
149
|
+
console.log("Transaction paid at:", event.target_preview.paid);
|
|
150
|
+
},
|
|
151
|
+
"basic_tx.rejected": (event) => { /* ... */ },
|
|
152
|
+
"basic_tx.cancelled": (event) => { /* ... */ },
|
|
153
|
+
"basic_tx.claimed": (event) => { /* ... */ },
|
|
154
|
+
"basic_tx.listing_transaction_accepted": (event) => { /* ... */ },
|
|
155
|
+
"basic_tx.listing_transaction_rejected": (event) => { /* ... */ },
|
|
156
|
+
"basic_tx.payment_failed": (event) => { /* ... */ },
|
|
157
|
+
"basic_tx.payment_refunded": (event) => { /* ... */ },
|
|
158
|
+
"basic_tx.payment_review_flagged": (event) => { /* ... */ },
|
|
159
|
+
"basic_tx.payment_review_finished": (event) => { /* ... */ },
|
|
160
|
+
"basic_tx.tracking_details_submission_deadline_extended": (event) => { /* ... */ },
|
|
161
|
+
"basic_tx.tracked": (event) => { /* ... */ },
|
|
162
|
+
"basic_tx.delivered": (event) => { /* ... */ },
|
|
163
|
+
"basic_tx.complained": (event) => { /* ... */ },
|
|
164
|
+
"basic_tx.complaint_period_ended": (event) => { /* ... */ },
|
|
165
|
+
"basic_tx.funds_released": (event) => { /* ... */ },
|
|
166
|
+
"basic_tx.funds_refunded": (event) => { /* ... */ },
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// TypeScript will error if any event code is missing from handlers
|
|
170
|
+
|
|
171
|
+
function handleWebhook(event: TrustapWebhookEvent) {
|
|
172
|
+
const handler = handlers[event.code];
|
|
173
|
+
handler(event as never);
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Individual Event Types
|
|
178
|
+
|
|
179
|
+
Import specific event types for targeted handling:
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
import type {
|
|
183
|
+
BasicTxPaidEvent,
|
|
184
|
+
BasicTxFundsReleasedEvent,
|
|
185
|
+
} from "@ranwhenparked/trustap-sdk";
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## State Machine
|
|
189
|
+
|
|
190
|
+
Map webhook events to transaction states:
|
|
191
|
+
|
|
192
|
+
```ts
|
|
193
|
+
import {
|
|
194
|
+
mapWebhookToTrustapState,
|
|
195
|
+
type TrustapTransactionState,
|
|
196
|
+
} from "@ranwhenparked/trustap-sdk";
|
|
197
|
+
|
|
198
|
+
const state = mapWebhookToTrustapState("basic_tx.paid");
|
|
199
|
+
// state: "paid"
|
|
200
|
+
|
|
201
|
+
const unknown = mapWebhookToTrustapState("unknown.event");
|
|
202
|
+
// unknown: null
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### Available States
|
|
206
|
+
|
|
207
|
+
```ts
|
|
208
|
+
type TrustapTransactionState =
|
|
209
|
+
| "created"
|
|
210
|
+
| "joined"
|
|
211
|
+
| "rejected"
|
|
212
|
+
| "paid"
|
|
213
|
+
| "cancelled"
|
|
214
|
+
| "cancelled_with_payment"
|
|
215
|
+
| "payment_refunded"
|
|
216
|
+
| "tracked"
|
|
217
|
+
| "delivered"
|
|
218
|
+
| "complained"
|
|
219
|
+
| "complaint_period_ended"
|
|
220
|
+
| "funds_released";
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
## Deno
|
|
224
|
+
|
|
225
|
+
For Deno projects, import from the Deno-specific entry point:
|
|
226
|
+
|
|
227
|
+
```ts
|
|
228
|
+
import { createTrustapClient } from "@ranwhenparked/trustap-sdk/deno";
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## Development
|
|
232
|
+
|
|
233
|
+
### Generate types from OpenAPI spec
|
|
234
|
+
|
|
235
|
+
```bash
|
|
236
|
+
npm run generate
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
This pulls the latest OpenAPI schema from `https://docs.trustap.com/apis/trustap-openapi.yaml` and generates:
|
|
240
|
+
|
|
241
|
+
- `src/schema.d.ts` - TypeScript types for all API paths and operations
|
|
242
|
+
- `src/operations-map.ts` - Mapping of operation IDs to paths/methods
|
|
243
|
+
- `src/security-map.ts` - Security requirements per endpoint
|
|
244
|
+
|
|
245
|
+
Individual generation scripts:
|
|
246
|
+
|
|
247
|
+
```bash
|
|
248
|
+
npm run generate:types # Generate schema.d.ts
|
|
249
|
+
npm run generate:ops # Generate operations-map.ts
|
|
250
|
+
npm run generate:security # Generate security-map.ts
|
|
251
|
+
```
|
package/deno.json
ADDED
package/eslint.config.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import baseConfig from "@rwp/eslint-config/base";
|
|
2
|
+
|
|
3
|
+
/** @type {import('typescript-eslint').Config} */
|
|
4
|
+
export default [
|
|
5
|
+
{
|
|
6
|
+
ignores: [
|
|
7
|
+
"dist/**",
|
|
8
|
+
"scripts/**",
|
|
9
|
+
"src/schema.d.ts",
|
|
10
|
+
"src/__tests__/deno/**",
|
|
11
|
+
],
|
|
12
|
+
},
|
|
13
|
+
...baseConfig,
|
|
14
|
+
{
|
|
15
|
+
files: ["src/**/*.{ts,tsx}"],
|
|
16
|
+
rules: {
|
|
17
|
+
"sonarjs/function-return-type": "error",
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
];
|
|
21
|
+
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ranwhenparked/trustap-sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"deno": "./src/index.deno.ts",
|
|
10
|
+
"node": "./dist/index.js",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./deno": "./src/index.deno.ts",
|
|
14
|
+
"./node": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"default": "./dist/index.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"main": "./dist/index.js",
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "node scripts/build-node.mjs",
|
|
23
|
+
"clean": "git clean -xdf .cache .turbo dist node_modules",
|
|
24
|
+
"format": "prettier --check . --ignore-path ../../.gitignore",
|
|
25
|
+
"lint": "eslint --fix --cache --cache-location .cache/.eslintcache",
|
|
26
|
+
"test": "vitest --run",
|
|
27
|
+
"test:watch": "vitest",
|
|
28
|
+
"typecheck": "tsc --noEmit --emitDeclarationOnly false",
|
|
29
|
+
"generate:types": "npx openapi-typescript https://docs.trustap.com/apis/trustap-openapi.yaml -o src/schema.d.ts",
|
|
30
|
+
"generate:ops": "node ./scripts/generate-operations-map.mjs",
|
|
31
|
+
"generate:security": "node ./scripts/generate-security-map.mjs https://docs.trustap.com/apis/trustap-openapi.yaml",
|
|
32
|
+
"generate": "npm run generate:types && npm run generate:ops && npm run generate:security"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"openapi-fetch": "^0.15.0"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
39
|
+
"eslint": "^9.39.2",
|
|
40
|
+
"openapi-typescript": "^7.10.1",
|
|
41
|
+
"prettier": "^3.8.0",
|
|
42
|
+
"typescript": "^5.9.3",
|
|
43
|
+
"typescript-eslint": "^8.53.1",
|
|
44
|
+
"vitest": "^3.2.4",
|
|
45
|
+
"yaml": "^2.8.2"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build script for Node.js output.
|
|
3
|
+
*
|
|
4
|
+
* This script handles the dual-runtime extension problem:
|
|
5
|
+
* - Source files use .ts extensions (for Deno compatibility)
|
|
6
|
+
* - Node.js ESM needs .js extensions
|
|
7
|
+
* - TypeScript's tsc can't emit when sources have .ts extensions
|
|
8
|
+
*
|
|
9
|
+
* Solution:
|
|
10
|
+
* 1. Temporarily rewrite .ts → .js in source files
|
|
11
|
+
* 2. Run tsc to compile
|
|
12
|
+
* 3. Restore original .ts extensions in source files
|
|
13
|
+
* 4. Copy schema.d.ts to dist
|
|
14
|
+
*/
|
|
15
|
+
import { execFileSync } from "node:child_process";
|
|
16
|
+
import { readFile, writeFile, copyFile } from "node:fs/promises";
|
|
17
|
+
import { join } from "node:path";
|
|
18
|
+
import { fileURLToPath } from "node:url";
|
|
19
|
+
|
|
20
|
+
const __dirname = fileURLToPath(new URL(".", import.meta.url));
|
|
21
|
+
const srcDir = join(__dirname, "..", "src");
|
|
22
|
+
const distDir = join(__dirname, "..", "dist");
|
|
23
|
+
|
|
24
|
+
const filesToProcess = ["index.ts", "client-factory.ts"];
|
|
25
|
+
|
|
26
|
+
async function rewriteExtensions(files, from, to) {
|
|
27
|
+
for (const file of files) {
|
|
28
|
+
const filePath = join(srcDir, file);
|
|
29
|
+
let content = await readFile(filePath, "utf-8");
|
|
30
|
+
|
|
31
|
+
// Rewrite import/export extensions
|
|
32
|
+
const fromPattern = new RegExp(`from\\s+["'](\\.[^"']+)\\${from}["']`, "g");
|
|
33
|
+
content = content.replace(fromPattern, `from "$1${to}"`);
|
|
34
|
+
|
|
35
|
+
await writeFile(filePath, content);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function build() {
|
|
40
|
+
console.log("1. Rewriting .ts → .js extensions in source files...");
|
|
41
|
+
await rewriteExtensions(filesToProcess, ".ts", ".js");
|
|
42
|
+
await rewriteExtensions(filesToProcess, ".d.ts", ".js");
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
console.log("2. Compiling with tsc...");
|
|
46
|
+
execFileSync("npm", ["exec", "tsc", "-p", "tsconfig.build.json"], {
|
|
47
|
+
cwd: join(__dirname, ".."),
|
|
48
|
+
stdio: "inherit",
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
console.log("3. Copying schema.d.ts to dist...");
|
|
52
|
+
await copyFile(join(srcDir, "schema.d.ts"), join(distDir, "schema.d.ts"));
|
|
53
|
+
|
|
54
|
+
console.log("Build complete!");
|
|
55
|
+
} finally {
|
|
56
|
+
console.log("4. Restoring .ts extensions in source files...");
|
|
57
|
+
await rewriteExtensions(filesToProcess, ".js", ".ts");
|
|
58
|
+
|
|
59
|
+
// Also restore .d.ts for schema imports
|
|
60
|
+
for (const file of filesToProcess) {
|
|
61
|
+
const filePath = join(srcDir, file);
|
|
62
|
+
let content = await readFile(filePath, "utf-8");
|
|
63
|
+
content = content.replace(
|
|
64
|
+
/from\s+["']\.\/schema\.ts["']/g,
|
|
65
|
+
'from "./schema.d.ts"',
|
|
66
|
+
);
|
|
67
|
+
await writeFile(filePath, content);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
build().catch((err) => {
|
|
73
|
+
console.error("Build failed:", err);
|
|
74
|
+
process.exit(1);
|
|
75
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import yaml from "yaml";
|
|
5
|
+
|
|
6
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const ROOT = path.resolve(__dirname, "..");
|
|
8
|
+
const OUTPUT = path.join(ROOT, "src", "operations-map.ts");
|
|
9
|
+
|
|
10
|
+
async function main() {
|
|
11
|
+
const res = await fetch("https://docs.trustap.com/_spec/apis/openapi.yaml");
|
|
12
|
+
if (!res.ok) throw new Error(`Failed to fetch OpenAPI: ${res.status}`);
|
|
13
|
+
const text = await res.text();
|
|
14
|
+
const doc = yaml.parse(text);
|
|
15
|
+
|
|
16
|
+
const opToPath = {};
|
|
17
|
+
for (const [p, methods] of Object.entries(doc.paths ?? {})) {
|
|
18
|
+
for (const [method, op] of Object.entries(methods)) {
|
|
19
|
+
if (!op || typeof op !== "object") continue;
|
|
20
|
+
const id = op.operationId;
|
|
21
|
+
if (!id) continue;
|
|
22
|
+
opToPath[id] = { path: p, method: method.toUpperCase() };
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const overrideOperations = {
|
|
27
|
+
"basic.createTransaction": { path: "/transactions", method: "POST" },
|
|
28
|
+
"basic.getTransactions": { path: "/transactions", method: "GET" },
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
for (const [key, value] of Object.entries(overrideOperations)) {
|
|
32
|
+
opToPath[key] = value;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const additionalOperations = {
|
|
36
|
+
"oauth.getUser": { path: "/users/{userId}", method: "GET" },
|
|
37
|
+
"oauth.updateUser": { path: "/users/{userId}", method: "PUT" },
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
for (const [key, value] of Object.entries(additionalOperations)) {
|
|
41
|
+
if (!opToPath[key]) {
|
|
42
|
+
opToPath[key] = value;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const file = `// generated by scripts/generate-operations-map.mjs\n` +
|
|
47
|
+
`export const operationIdToPath = ${JSON.stringify(opToPath, null, 2)} as const;\n`;
|
|
48
|
+
|
|
49
|
+
fs.mkdirSync(path.dirname(OUTPUT), { recursive: true });
|
|
50
|
+
fs.writeFileSync(OUTPUT, file);
|
|
51
|
+
console.log(`Wrote ${OUTPUT}`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
main().catch((err) => {
|
|
55
|
+
console.error(err);
|
|
56
|
+
process.exit(1);
|
|
57
|
+
});
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Generate a minimal security map from an OpenAPI document (JSON or YAML)
|
|
3
|
+
// Usage: node scripts/generate-security-map.mjs [/absolute/or/relative/path/or/url]
|
|
4
|
+
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import process from 'node:process';
|
|
8
|
+
import { fileURLToPath } from 'node:url';
|
|
9
|
+
import { createRequire } from 'node:module';
|
|
10
|
+
|
|
11
|
+
const require = createRequire(import.meta.url);
|
|
12
|
+
let YAML;
|
|
13
|
+
try {
|
|
14
|
+
YAML = require('yaml');
|
|
15
|
+
} catch {}
|
|
16
|
+
|
|
17
|
+
const inputArg = process.argv[2] || 'https://docs.trustap.com/_spec/apis/openapi.yaml';
|
|
18
|
+
|
|
19
|
+
async function readOpenApi(source) {
|
|
20
|
+
if (/^https?:\/\//.test(source)) {
|
|
21
|
+
const res = await fetch(source);
|
|
22
|
+
if (!res.ok) throw new Error(`Failed to fetch ${source}: ${res.status}`);
|
|
23
|
+
const text = await res.text();
|
|
24
|
+
if (source.endsWith('.yaml') || source.endsWith('.yml')) {
|
|
25
|
+
if (!YAML) throw new Error('yaml package not available');
|
|
26
|
+
return YAML.parse(text);
|
|
27
|
+
}
|
|
28
|
+
return JSON.parse(text);
|
|
29
|
+
}
|
|
30
|
+
const content = fs.readFileSync(source, 'utf8');
|
|
31
|
+
if (source.endsWith('.yaml') || source.endsWith('.yml')) {
|
|
32
|
+
if (!YAML) throw new Error('yaml package not available');
|
|
33
|
+
return YAML.parse(content);
|
|
34
|
+
}
|
|
35
|
+
return JSON.parse(content);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function normalizeSecurityArray(secArr) {
|
|
39
|
+
// Convert OpenAPI security array to a set of scheme names
|
|
40
|
+
// e.g., [{ OAuth2: [] }, { APIKey: [] }] -> Set(['OAuth2','APIKey'])
|
|
41
|
+
const set = new Set();
|
|
42
|
+
if (Array.isArray(secArr)) {
|
|
43
|
+
for (const entry of secArr) {
|
|
44
|
+
if (entry && typeof entry === 'object') {
|
|
45
|
+
for (const key of Object.keys(entry)) set.add(key);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return set;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function buildSecurityMap(doc) {
|
|
53
|
+
const out = {}; // { path: { METHOD: ['APIKey','OAuth2'] } }
|
|
54
|
+
const globalSec = normalizeSecurityArray(doc.security);
|
|
55
|
+
const paths = doc.paths || {};
|
|
56
|
+
for (const p of Object.keys(paths)) {
|
|
57
|
+
const item = paths[p] || {};
|
|
58
|
+
for (const method of Object.keys(item)) {
|
|
59
|
+
const op = item[method];
|
|
60
|
+
if (!op || typeof op !== 'object') continue;
|
|
61
|
+
if (!['get','post','put','patch','delete','head','options','trace'].includes(method)) continue;
|
|
62
|
+
const opSec = normalizeSecurityArray(op.security);
|
|
63
|
+
const effective = opSec.size > 0 ? opSec : globalSec;
|
|
64
|
+
if (!out[p]) out[p] = {};
|
|
65
|
+
out[p][method.toUpperCase()] = Array.from(effective);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return out;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function emitTs(securityMap) {
|
|
72
|
+
const header = `// generated by scripts/generate-security-map.mjs\n`;
|
|
73
|
+
const body = `export const securityMap = ${JSON.stringify(securityMap, null, 2)} as const;\n`;
|
|
74
|
+
return header + body;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function main() {
|
|
78
|
+
const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
79
|
+
const outFile = path.join(rootDir, 'src', 'security-map.ts');
|
|
80
|
+
const api = await readOpenApi(inputArg);
|
|
81
|
+
const map = buildSecurityMap(api);
|
|
82
|
+
const ts = emitTs(map);
|
|
83
|
+
fs.writeFileSync(outFile, ts, 'utf8');
|
|
84
|
+
console.log(`Wrote ${outFile}`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
main().catch((err) => {
|
|
88
|
+
console.error(err);
|
|
89
|
+
process.exit(1);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
|