Merge pull request 'Generate TypeScript API client from OpenAPI spec' (#443) from feature/426-generate-ts-api-client into main

Merge pull request #443: Generate TypeScript API client from OpenAPI spec

Closes leeworks-agents/SPARC#426
This commit was merged in pull request #443.
This commit is contained in:
2026-03-27 20:42:17 +00:00
4 changed files with 1019 additions and 42 deletions
+6
View File
@@ -39,6 +39,12 @@ jobs:
apk add --no-cache nodejs npm apk add --no-cache nodejs npm
cd frontend cd frontend
npm ci npm ci
npm run generate:local
if ! git diff --quiet src/api/schema.d.ts; then
echo "ERROR: src/api/schema.d.ts is out of date. Run 'npm run generate:local' and commit the result."
git diff src/api/schema.d.ts
exit 1
fi
npx tsc --noEmit npx tsc --noEmit
- name: Run pytest - name: Run pytest
+10
View File
@@ -40,6 +40,16 @@ jobs:
apk add --no-cache nodejs npm apk add --no-cache nodejs npm
cd frontend && npm ci cd frontend && npm ci
- name: Verify generated API types are up to date
shell: sh
run: |
cd frontend && npm run generate:local
if ! git diff --quiet src/api/schema.d.ts; then
echo "ERROR: src/api/schema.d.ts is out of date. Run 'npm run generate:local' and commit the result."
git diff src/api/schema.d.ts
exit 1
fi
- name: Run TypeScript type check - name: Run TypeScript type check
shell: sh shell: sh
run: | run: |
+975
View File
@@ -0,0 +1,975 @@
/**
* This file was auto-generated by openapi-typescript.
* Do not make direct changes to the file.
*/
export interface paths {
"/auth/register": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/**
* Register
* @description Register a new user.
*
* The first registered user automatically becomes an admin.
*/
post: operations["register_auth_register_post"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/auth/login": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/**
* Login
* @description Authenticate user and return JWT tokens.
*/
post: operations["login_auth_login_post"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/auth/refresh": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/**
* Refresh Token
* @description Refresh access token using refresh token.
*/
post: operations["refresh_token_auth_refresh_post"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/auth/me": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
/**
* Get Me
* @description Get current authenticated user.
*/
get: operations["get_me_auth_me_get"];
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/admin/users": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
/**
* List Users
* @description List all users (admin only).
*/
get: operations["list_users_admin_users_get"];
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/admin/users/{user_id}/role": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
/**
* Update User Role
* @description Update a user's role (admin only).
*/
patch: operations["update_user_role_admin_users__user_id__role_patch"];
trace?: never;
};
"/admin/users/{user_id}": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
post?: never;
/**
* Delete User
* @description Delete a user (admin only).
*/
delete: operations["delete_user_admin_users__user_id__delete"];
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/analytics": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
/**
* Get Analytics
* @description Get analytics data (authenticated users only).
*/
get: operations["get_analytics_analytics_get"];
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/health": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
/**
* Health Check
* @description Check API health status.
*/
get: operations["health_check_health_get"];
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/analyze/{company_name}": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
/**
* Analyze Company
* @description Analyze a single company's patent portfolio.
*
* This endpoint retrieves recent patents for the specified company,
* parses them, and uses AI to generate a comprehensive analysis.
*
* Args:
* company_name: Name of the company to analyze (e.g., "nvidia", "intel")
*
* Returns:
* Analysis results including patent count, AI insights, and success status
*/
get: operations["analyze_company_analyze__company_name__get"];
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/analyze/batch": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/**
* Analyze Companies Batch
* @description Analyze multiple companies' patent portfolios.
*
* Processes companies concurrently for improved performance.
* Limited to 20 companies per request.
*
* Args:
* request: List of company names and optional worker count
*
* Returns:
* Batch results with individual company analyses and summary statistics
*/
post: operations["analyze_companies_batch_analyze_batch_post"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/analyze/batch/async": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/**
* Analyze Companies Async
* @description Start an asynchronous batch analysis job.
*
* Returns immediately with a job ID that can be used to poll for status.
* Useful for large batch analyses that may take a long time.
*
* Args:
* request: List of company names and optional worker count
*
* Returns:
* Job status with job_id for polling
*/
post: operations["analyze_companies_async_analyze_batch_async_post"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/jobs/{job_id}": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
/**
* Get Job Status
* @description Get the status of a background analysis job.
*
* Args:
* job_id: The job ID returned from the async batch endpoint
*
* Returns:
* Current job status including progress and results when complete
*/
get: operations["get_job_status_jobs__job_id__get"];
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/jobs": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
/**
* List Jobs
* @description List all analysis jobs.
*
* Args:
* status: Optional filter by job status
* limit: Maximum number of jobs to return (default 10, max 100)
*
* Returns:
* List of job statuses
*/
get: operations["list_jobs_jobs_get"];
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
}
export type webhooks = Record<string, never>;
export interface components {
schemas: {
/**
* AnalyticsResponse
* @description Analytics response model.
*/
AnalyticsResponse: {
/** Total Messages */
total_messages: number;
/** By Company */
by_company: {
[key: string]: unknown;
}[];
/** By Type */
by_type: {
[key: string]: unknown;
}[];
/** Period Days */
period_days: number;
};
/**
* BatchAnalysisRequest
* @description Request model for batch company analysis.
*/
BatchAnalysisRequest: {
/**
* Companies
* @description List of company names to analyze
*/
companies: string[];
/**
* Max Workers
* @description Max concurrent analyses
* @default 3
*/
max_workers: number;
};
/**
* BatchAnalysisResponse
* @description Response model for batch company analysis.
*/
BatchAnalysisResponse: {
/** Results */
results: components["schemas"]["CompanyAnalysisResponse"][];
/** Total Companies */
total_companies: number;
/** Successful */
successful: number;
/** Failed */
failed: number;
/**
* Timestamp
* Format: date-time
*/
timestamp: string;
};
/**
* CompanyAnalysisResponse
* @description Response model for single company analysis.
*/
CompanyAnalysisResponse: {
/** Company Name */
company_name: string;
/** Analysis */
analysis: string;
/** Patent Count */
patent_count: number;
/** Success */
success: boolean;
/** Error */
error?: string | null;
/**
* Timestamp
* Format: date-time
*/
timestamp: string;
};
/** HTTPValidationError */
HTTPValidationError: {
/** Detail */
detail?: components["schemas"]["ValidationError"][];
};
/**
* HealthResponse
* @description Health check response.
*/
HealthResponse: {
/** Status */
status: string;
/** Version */
version: string;
/**
* Timestamp
* Format: date-time
*/
timestamp: string;
};
/**
* JobStatus
* @description Status of a background analysis job.
*/
JobStatus: {
/** Job Id */
job_id: string;
/** Status */
status: string;
/** Progress */
progress: number;
/** Total Companies */
total_companies: number;
/** Completed Companies */
completed_companies: number;
result?: components["schemas"]["BatchAnalysisResponse"] | null;
/** Error */
error?: string | null;
};
/**
* LoginRequest
* @description User login request.
*/
LoginRequest: {
/**
* Email
* Format: email
*/
email: string;
/** Password */
password: string;
};
/**
* RefreshRequest
* @description Token refresh request.
*/
RefreshRequest: {
/** Refresh Token */
refresh_token: string;
};
/**
* RegisterRequest
* @description User registration request.
*/
RegisterRequest: {
/**
* Email
* Format: email
*/
email: string;
/**
* Password
* @description Password (min 8 characters)
*/
password: string;
};
/**
* TokenResponse
* @description Token response model.
*/
TokenResponse: {
/** Access Token */
access_token: string;
/** Refresh Token */
refresh_token: string;
/**
* Token Type
* @default bearer
*/
token_type: string;
};
/**
* UpdateRoleRequest
* @description Update user role request.
*/
UpdateRoleRequest: {
/** Role */
role: string;
};
/**
* UserResponse
* @description User response model.
*/
UserResponse: {
/** Id */
id: number;
/** Email */
email: string;
/** Role */
role: string;
/**
* Created At
* Format: date-time
*/
created_at: string;
};
/** ValidationError */
ValidationError: {
/** Location */
loc: (string | number)[];
/** Message */
msg: string;
/** Error Type */
type: string;
/** Input */
input?: unknown;
/** Context */
ctx?: Record<string, never>;
};
};
responses: never;
parameters: never;
requestBodies: never;
headers: never;
pathItems: never;
}
export type $defs = Record<string, never>;
export interface operations {
register_auth_register_post: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody: {
content: {
"application/json": components["schemas"]["RegisterRequest"];
};
};
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["UserResponse"];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
login_auth_login_post: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody: {
content: {
"application/json": components["schemas"]["LoginRequest"];
};
};
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["TokenResponse"];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
refresh_token_auth_refresh_post: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody: {
content: {
"application/json": components["schemas"]["RefreshRequest"];
};
};
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["TokenResponse"];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
get_me_auth_me_get: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["UserResponse"];
};
};
};
};
list_users_admin_users_get: {
parameters: {
query?: {
limit?: number;
offset?: number;
};
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["UserResponse"][];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
update_user_role_admin_users__user_id__role_patch: {
parameters: {
query?: never;
header?: never;
path: {
user_id: number;
};
cookie?: never;
};
requestBody: {
content: {
"application/json": components["schemas"]["UpdateRoleRequest"];
};
};
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["UserResponse"];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
delete_user_admin_users__user_id__delete: {
parameters: {
query?: never;
header?: never;
path: {
user_id: number;
};
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": unknown;
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
get_analytics_analytics_get: {
parameters: {
query?: {
days?: number;
};
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["AnalyticsResponse"];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
health_check_health_get: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HealthResponse"];
};
};
};
};
analyze_company_analyze__company_name__get: {
parameters: {
query?: never;
header?: never;
path: {
company_name: string;
};
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["CompanyAnalysisResponse"];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
analyze_companies_batch_analyze_batch_post: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody: {
content: {
"application/json": components["schemas"]["BatchAnalysisRequest"];
};
};
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["BatchAnalysisResponse"];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
analyze_companies_async_analyze_batch_async_post: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody: {
content: {
"application/json": components["schemas"]["BatchAnalysisRequest"];
};
};
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["JobStatus"];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
get_job_status_jobs__job_id__get: {
parameters: {
query?: never;
header?: never;
path: {
job_id: string;
};
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["JobStatus"];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
list_jobs_jobs_get: {
parameters: {
query?: {
/** @description Filter by status: pending, running, completed, failed */
status?: string | null;
limit?: number;
};
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["JobStatus"][];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
}
+28 -42
View File
@@ -1,46 +1,32 @@
export interface User { /**
id: number; * Application types derived from the auto-generated OpenAPI schema.
email: string; *
role: 'admin' | 'user'; * Run `npm run generate:local` (or `npm run generate` with the API running)
created_at: string; * to regenerate `src/api/schema.d.ts` from the backend OpenAPI spec.
} *
* These aliases keep the rest of the codebase stable while the source of
* truth lives in the generated file.
*/
export interface TokenResponse { import type { components } from '../api/schema';
access_token: string;
refresh_token: string;
token_type: string;
}
export interface CompanyAnalysis { // Re-export schema types under the names the rest of the app expects.
company_name: string; export type User = components['schemas']['UserResponse'];
analysis: string; export type TokenResponse = components['schemas']['TokenResponse'];
patent_count: number; export type CompanyAnalysis = components['schemas']['CompanyAnalysisResponse'];
success: boolean; export type BatchAnalysisResult = components['schemas']['BatchAnalysisResponse'];
error: string | null; export type JobStatus = components['schemas']['JobStatus'];
timestamp: string; export type Analytics = Omit<components['schemas']['AnalyticsResponse'], 'by_company' | 'by_type'> & {
}
export interface BatchAnalysisResult {
results: CompanyAnalysis[];
total_companies: number;
successful: number;
failed: number;
timestamp: string;
}
export interface JobStatus {
job_id: string;
status: 'pending' | 'running' | 'completed' | 'failed';
progress: number;
total_companies: number;
completed_companies: number;
result: BatchAnalysisResult | null;
error: string | null;
}
export interface Analytics {
total_messages: number;
by_company: Array<{ company_name: string; count: number }>; by_company: Array<{ company_name: string; count: number }>;
by_type: Array<{ analysis_type: string; count: number }>; by_type: Array<{ analysis_type: string; count: number }>;
period_days: number; };
}
// Additional generated types that may be useful elsewhere.
export type RegisterRequest = components['schemas']['RegisterRequest'];
export type LoginRequest = components['schemas']['LoginRequest'];
export type RefreshRequest = components['schemas']['RefreshRequest'];
export type UpdateRoleRequest = components['schemas']['UpdateRoleRequest'];
export type HealthResponse = components['schemas']['HealthResponse'];
export type BatchAnalysisRequest = components['schemas']['BatchAnalysisRequest'];
export type ValidationError = components['schemas']['ValidationError'];
export type HTTPValidationError = components['schemas']['HTTPValidationError'];