mirror of
https://github.com/xtr-dev/rondevu-client.git
synced 2025-12-08 00:33:24 +00:00
Initial commit: Rondevu TypeScript client
TypeScript client library for Rondevu peer signaling and discovery server. Features: - Fully typed API with TypeScript definitions - Support for all Rondevu server endpoints - Configurable base URL for any server instance - Browser and Node.js compatible - Comprehensive documentation and examples - Type-safe request/response handling 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
node_modules/
|
||||
dist/
|
||||
*.log
|
||||
.DS_Store
|
||||
*.tsbuildinfo
|
||||
6
.npmignore
Normal file
6
.npmignore
Normal file
@@ -0,0 +1,6 @@
|
||||
src/
|
||||
tsconfig.json
|
||||
*.tsbuildinfo
|
||||
node_modules/
|
||||
.DS_Store
|
||||
*.log
|
||||
234
README.md
Normal file
234
README.md
Normal file
@@ -0,0 +1,234 @@
|
||||
# @rondevu/client
|
||||
|
||||
TypeScript client for interacting with the Rondevu peer signaling and discovery server. Provides a simple, type-safe API for WebRTC peer discovery and connection establishment.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install @rondevu/client
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Setup
|
||||
|
||||
```typescript
|
||||
import { RondevuClient } from '@rondevu/client';
|
||||
|
||||
const client = new RondevuClient({
|
||||
baseUrl: 'https://rondevu.example.com',
|
||||
// Optional: custom origin for session isolation
|
||||
origin: 'https://myapp.com'
|
||||
});
|
||||
```
|
||||
|
||||
### Peer Discovery Flow
|
||||
|
||||
#### 1. List Available Topics
|
||||
|
||||
```typescript
|
||||
// Get all topics with peer counts
|
||||
const { topics, pagination } = await client.listTopics();
|
||||
|
||||
topics.forEach(topic => {
|
||||
console.log(`${topic.topic}: ${topic.count} peers available`);
|
||||
});
|
||||
```
|
||||
|
||||
#### 2. Create an Offer (Peer A)
|
||||
|
||||
```typescript
|
||||
// Announce availability in a topic
|
||||
const { code } = await client.createOffer('my-room', {
|
||||
info: 'peer-A-unique-id',
|
||||
offer: webrtcOfferData
|
||||
});
|
||||
|
||||
console.log('Session code:', code);
|
||||
```
|
||||
|
||||
#### 3. Discover Peers (Peer B)
|
||||
|
||||
```typescript
|
||||
// Find available peers in a topic
|
||||
const { sessions } = await client.listSessions('my-room');
|
||||
|
||||
// Filter out your own sessions
|
||||
const otherPeers = sessions.filter(s => s.info !== 'my-peer-id');
|
||||
|
||||
if (otherPeers.length > 0) {
|
||||
const peer = otherPeers[0];
|
||||
console.log('Found peer:', peer.info);
|
||||
}
|
||||
```
|
||||
|
||||
#### 4. Send Answer (Peer B)
|
||||
|
||||
```typescript
|
||||
// Connect to a peer by answering their offer
|
||||
await client.sendAnswer({
|
||||
code: peer.code,
|
||||
answer: webrtcAnswerData,
|
||||
side: 'answerer'
|
||||
});
|
||||
```
|
||||
|
||||
#### 5. Poll for Data (Both Peers)
|
||||
|
||||
```typescript
|
||||
// Offerer polls for answer
|
||||
const offererData = await client.poll(code, 'offerer');
|
||||
if (offererData.answer) {
|
||||
console.log('Received answer from peer');
|
||||
}
|
||||
|
||||
// Answerer polls for offer details
|
||||
const answererData = await client.poll(code, 'answerer');
|
||||
console.log('Offer candidates:', answererData.offerCandidates);
|
||||
```
|
||||
|
||||
#### 6. Exchange ICE Candidates
|
||||
|
||||
```typescript
|
||||
// Send additional signaling data
|
||||
await client.sendAnswer({
|
||||
code: sessionCode,
|
||||
candidate: iceCandidate,
|
||||
side: 'offerer' // or 'answerer'
|
||||
});
|
||||
```
|
||||
|
||||
### Health Check
|
||||
|
||||
```typescript
|
||||
const health = await client.health();
|
||||
console.log('Server status:', health.status);
|
||||
console.log('Timestamp:', health.timestamp);
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `RondevuClient`
|
||||
|
||||
#### Constructor
|
||||
|
||||
```typescript
|
||||
new RondevuClient(options: RondevuClientOptions)
|
||||
```
|
||||
|
||||
**Options:**
|
||||
- `baseUrl` (string, required): Base URL of the Rondevu server
|
||||
- `origin` (string, optional): Origin header for session isolation (defaults to baseUrl origin)
|
||||
- `fetch` (function, optional): Custom fetch implementation (for Node.js)
|
||||
|
||||
#### Methods
|
||||
|
||||
##### `listTopics(page?, limit?)`
|
||||
|
||||
Lists all topics with peer counts.
|
||||
|
||||
**Parameters:**
|
||||
- `page` (number, optional): Page number, default 1
|
||||
- `limit` (number, optional): Results per page, default 100, max 1000
|
||||
|
||||
**Returns:** `Promise<ListTopicsResponse>`
|
||||
|
||||
##### `listSessions(topic)`
|
||||
|
||||
Discovers available peers for a given topic.
|
||||
|
||||
**Parameters:**
|
||||
- `topic` (string): Topic identifier
|
||||
|
||||
**Returns:** `Promise<ListSessionsResponse>`
|
||||
|
||||
##### `createOffer(topic, request)`
|
||||
|
||||
Announces peer availability and creates a new session.
|
||||
|
||||
**Parameters:**
|
||||
- `topic` (string): Topic identifier (max 256 characters)
|
||||
- `request` (CreateOfferRequest):
|
||||
- `info` (string): Peer identifier/metadata (max 1024 characters)
|
||||
- `offer` (string): WebRTC signaling data
|
||||
|
||||
**Returns:** `Promise<CreateOfferResponse>`
|
||||
|
||||
##### `sendAnswer(request)`
|
||||
|
||||
Sends an answer or candidate to an existing session.
|
||||
|
||||
**Parameters:**
|
||||
- `request` (AnswerRequest):
|
||||
- `code` (string): Session UUID
|
||||
- `answer` (string, optional): Answer signaling data
|
||||
- `candidate` (string, optional): ICE candidate data
|
||||
- `side` ('offerer' | 'answerer'): Which peer is sending
|
||||
|
||||
**Returns:** `Promise<AnswerResponse>`
|
||||
|
||||
##### `poll(code, side)`
|
||||
|
||||
Polls for session data from the other peer.
|
||||
|
||||
**Parameters:**
|
||||
- `code` (string): Session UUID
|
||||
- `side` ('offerer' | 'answerer'): Which side is polling
|
||||
|
||||
**Returns:** `Promise<PollOffererResponse | PollAnswererResponse>`
|
||||
|
||||
##### `health()`
|
||||
|
||||
Checks server health.
|
||||
|
||||
**Returns:** `Promise<HealthResponse>`
|
||||
|
||||
## TypeScript Types
|
||||
|
||||
All types are exported from the main package:
|
||||
|
||||
```typescript
|
||||
import {
|
||||
RondevuClient,
|
||||
Session,
|
||||
TopicInfo,
|
||||
CreateOfferRequest,
|
||||
AnswerRequest,
|
||||
PollRequest,
|
||||
Side,
|
||||
// ... and more
|
||||
} from '@rondevu/client';
|
||||
```
|
||||
|
||||
## Node.js Usage
|
||||
|
||||
For Node.js environments (v18+), the built-in fetch is used automatically. For older Node.js versions, provide a fetch implementation:
|
||||
|
||||
```typescript
|
||||
import fetch from 'node-fetch';
|
||||
import { RondevuClient } from '@rondevu/client';
|
||||
|
||||
const client = new RondevuClient({
|
||||
baseUrl: 'https://rondevu.example.com',
|
||||
fetch: fetch as any
|
||||
});
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
All API methods throw errors with descriptive messages:
|
||||
|
||||
```typescript
|
||||
try {
|
||||
await client.createOffer('my-room', {
|
||||
info: 'peer-id',
|
||||
offer: data
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to create offer:', error.message);
|
||||
}
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
28
package.json
Normal file
28
package.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "@rondevu/client",
|
||||
"version": "1.0.0",
|
||||
"description": "TypeScript client for Rondevu peer signaling and discovery server",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"keywords": [
|
||||
"webrtc",
|
||||
"p2p",
|
||||
"signaling",
|
||||
"peer-discovery",
|
||||
"rondevu"
|
||||
],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"typescript": "^5.9.3"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"README.md"
|
||||
]
|
||||
}
|
||||
223
src/client.ts
Normal file
223
src/client.ts
Normal file
@@ -0,0 +1,223 @@
|
||||
import {
|
||||
RondevuClientOptions,
|
||||
ListTopicsResponse,
|
||||
ListSessionsResponse,
|
||||
CreateOfferRequest,
|
||||
CreateOfferResponse,
|
||||
AnswerRequest,
|
||||
AnswerResponse,
|
||||
PollRequest,
|
||||
PollOffererResponse,
|
||||
PollAnswererResponse,
|
||||
HealthResponse,
|
||||
ErrorResponse,
|
||||
Side,
|
||||
} from './types';
|
||||
|
||||
/**
|
||||
* HTTP client for Rondevu peer signaling and discovery server
|
||||
*/
|
||||
export class RondevuClient {
|
||||
private readonly baseUrl: string;
|
||||
private readonly origin: string;
|
||||
private readonly fetchImpl: typeof fetch;
|
||||
|
||||
/**
|
||||
* Creates a new Rondevu client instance
|
||||
* @param options - Client configuration options
|
||||
*/
|
||||
constructor(options: RondevuClientOptions) {
|
||||
this.baseUrl = options.baseUrl.replace(/\/$/, ''); // Remove trailing slash
|
||||
this.origin = options.origin || new URL(this.baseUrl).origin;
|
||||
this.fetchImpl = options.fetch || fetch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes an HTTP request to the Rondevu server
|
||||
*/
|
||||
private async request<T>(
|
||||
endpoint: string,
|
||||
options: RequestInit = {}
|
||||
): Promise<T> {
|
||||
const url = `${this.baseUrl}${endpoint}`;
|
||||
|
||||
const headers: Record<string, string> = {
|
||||
'Origin': this.origin,
|
||||
...(options.headers as Record<string, string>),
|
||||
};
|
||||
|
||||
if (options.body) {
|
||||
headers['Content-Type'] = 'application/json';
|
||||
}
|
||||
|
||||
const response = await this.fetchImpl(url, {
|
||||
...options,
|
||||
headers,
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
const error = data as ErrorResponse;
|
||||
throw new Error(error.error || `HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
|
||||
return data as T;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists all topics with peer counts
|
||||
*
|
||||
* @param page - Page number (starting from 1)
|
||||
* @param limit - Results per page (max 1000)
|
||||
* @returns List of topics with pagination info
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const client = new RondevuClient({ baseUrl: 'https://example.com' });
|
||||
* const { topics, pagination } = await client.listTopics();
|
||||
* console.log(`Found ${topics.length} topics`);
|
||||
* ```
|
||||
*/
|
||||
async listTopics(page = 1, limit = 100): Promise<ListTopicsResponse> {
|
||||
const params = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
limit: limit.toString(),
|
||||
});
|
||||
return this.request<ListTopicsResponse>(`/?${params}`, {
|
||||
method: 'GET',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Discovers available peers for a given topic
|
||||
*
|
||||
* @param topic - Topic identifier
|
||||
* @returns List of available sessions
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const client = new RondevuClient({ baseUrl: 'https://example.com' });
|
||||
* const { sessions } = await client.listSessions('my-room');
|
||||
* const otherPeers = sessions.filter(s => s.info !== myPeerId);
|
||||
* ```
|
||||
*/
|
||||
async listSessions(topic: string): Promise<ListSessionsResponse> {
|
||||
return this.request<ListSessionsResponse>(`/${encodeURIComponent(topic)}/sessions`, {
|
||||
method: 'GET',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Announces peer availability and creates a new session
|
||||
*
|
||||
* @param topic - Topic identifier for grouping peers (max 256 characters)
|
||||
* @param request - Offer details including peer info and signaling data
|
||||
* @returns Unique session code (UUID)
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const client = new RondevuClient({ baseUrl: 'https://example.com' });
|
||||
* const { code } = await client.createOffer('my-room', {
|
||||
* info: 'peer-123',
|
||||
* offer: signalingData
|
||||
* });
|
||||
* console.log('Session code:', code);
|
||||
* ```
|
||||
*/
|
||||
async createOffer(
|
||||
topic: string,
|
||||
request: CreateOfferRequest
|
||||
): Promise<CreateOfferResponse> {
|
||||
return this.request<CreateOfferResponse>(
|
||||
`/${encodeURIComponent(topic)}/offer`,
|
||||
{
|
||||
method: 'POST',
|
||||
body: JSON.stringify(request),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an answer or candidate to an existing session
|
||||
*
|
||||
* @param request - Answer details including session code and signaling data
|
||||
* @returns Success confirmation
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const client = new RondevuClient({ baseUrl: 'https://example.com' });
|
||||
*
|
||||
* // Send answer
|
||||
* await client.sendAnswer({
|
||||
* code: sessionCode,
|
||||
* answer: answerData,
|
||||
* side: 'answerer'
|
||||
* });
|
||||
*
|
||||
* // Send candidate
|
||||
* await client.sendAnswer({
|
||||
* code: sessionCode,
|
||||
* candidate: candidateData,
|
||||
* side: 'offerer'
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
async sendAnswer(request: AnswerRequest): Promise<AnswerResponse> {
|
||||
return this.request<AnswerResponse>('/answer', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(request),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Polls for session data from the other peer
|
||||
*
|
||||
* @param code - Session UUID
|
||||
* @param side - Which side is polling ('offerer' or 'answerer')
|
||||
* @returns Session data including offers, answers, and candidates
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const client = new RondevuClient({ baseUrl: 'https://example.com' });
|
||||
*
|
||||
* // Offerer polls for answer
|
||||
* const offererData = await client.poll(sessionCode, 'offerer');
|
||||
* if (offererData.answer) {
|
||||
* console.log('Received answer:', offererData.answer);
|
||||
* }
|
||||
*
|
||||
* // Answerer polls for offer
|
||||
* const answererData = await client.poll(sessionCode, 'answerer');
|
||||
* console.log('Received offer:', answererData.offer);
|
||||
* ```
|
||||
*/
|
||||
async poll(
|
||||
code: string,
|
||||
side: Side
|
||||
): Promise<PollOffererResponse | PollAnswererResponse> {
|
||||
const request: PollRequest = { code, side };
|
||||
return this.request<PollOffererResponse | PollAnswererResponse>('/poll', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(request),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks server health
|
||||
*
|
||||
* @returns Health status and timestamp
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const client = new RondevuClient({ baseUrl: 'https://example.com' });
|
||||
* const health = await client.health();
|
||||
* console.log('Server status:', health.status);
|
||||
* ```
|
||||
*/
|
||||
async health(): Promise<HealthResponse> {
|
||||
return this.request<HealthResponse>('/health', {
|
||||
method: 'GET',
|
||||
});
|
||||
}
|
||||
}
|
||||
31
src/index.ts
Normal file
31
src/index.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* @rondevu/client - TypeScript client for Rondevu peer signaling server
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { RondevuClient } from '@rondevu/client';
|
||||
*
|
||||
* const client = new RondevuClient({
|
||||
* baseUrl: 'https://rondevu.example.com'
|
||||
* });
|
||||
*
|
||||
* // Create an offer
|
||||
* const { code } = await client.createOffer('my-room', {
|
||||
* info: 'peer-123',
|
||||
* offer: signalingData
|
||||
* });
|
||||
*
|
||||
* // Discover peers
|
||||
* const { sessions } = await client.listSessions('my-room');
|
||||
*
|
||||
* // Send answer
|
||||
* await client.sendAnswer({
|
||||
* code: sessions[0].code,
|
||||
* answer: answerData,
|
||||
* side: 'answerer'
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
|
||||
export { RondevuClient } from './client';
|
||||
export * from './types';
|
||||
162
src/types.ts
Normal file
162
src/types.ts
Normal file
@@ -0,0 +1,162 @@
|
||||
/**
|
||||
* Session side - identifies which peer in a connection
|
||||
*/
|
||||
export type Side = 'offerer' | 'answerer';
|
||||
|
||||
/**
|
||||
* Session information returned from discovery endpoints
|
||||
*/
|
||||
export interface Session {
|
||||
/** Unique session identifier (UUID) */
|
||||
code: string;
|
||||
/** Peer identifier/metadata */
|
||||
info: string;
|
||||
/** Signaling data for peer connection */
|
||||
offer: string;
|
||||
/** Additional signaling data from offerer */
|
||||
offerCandidates: string[];
|
||||
/** Unix timestamp when session was created */
|
||||
createdAt: number;
|
||||
/** Unix timestamp when session expires */
|
||||
expiresAt: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Topic information with peer count
|
||||
*/
|
||||
export interface TopicInfo {
|
||||
/** Topic identifier */
|
||||
topic: string;
|
||||
/** Number of available peers in this topic */
|
||||
count: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pagination information
|
||||
*/
|
||||
export interface Pagination {
|
||||
/** Current page number */
|
||||
page: number;
|
||||
/** Results per page */
|
||||
limit: number;
|
||||
/** Total number of results */
|
||||
total: number;
|
||||
/** Whether there are more results available */
|
||||
hasMore: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Response from GET / - list all topics
|
||||
*/
|
||||
export interface ListTopicsResponse {
|
||||
topics: TopicInfo[];
|
||||
pagination: Pagination;
|
||||
}
|
||||
|
||||
/**
|
||||
* Response from GET /:topic/sessions - list sessions in a topic
|
||||
*/
|
||||
export interface ListSessionsResponse {
|
||||
sessions: Session[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Request body for POST /:topic/offer
|
||||
*/
|
||||
export interface CreateOfferRequest {
|
||||
/** Peer identifier/metadata (max 1024 characters) */
|
||||
info: string;
|
||||
/** Signaling data for peer connection */
|
||||
offer: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Response from POST /:topic/offer
|
||||
*/
|
||||
export interface CreateOfferResponse {
|
||||
/** Unique session identifier (UUID) */
|
||||
code: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request body for POST /answer
|
||||
*/
|
||||
export interface AnswerRequest {
|
||||
/** Session UUID from the offer */
|
||||
code: string;
|
||||
/** Response signaling data (required if candidate not provided) */
|
||||
answer?: string;
|
||||
/** Additional signaling data (required if answer not provided) */
|
||||
candidate?: string;
|
||||
/** Which peer is sending the data */
|
||||
side: Side;
|
||||
}
|
||||
|
||||
/**
|
||||
* Response from POST /answer
|
||||
*/
|
||||
export interface AnswerResponse {
|
||||
success: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request body for POST /poll
|
||||
*/
|
||||
export interface PollRequest {
|
||||
/** Session UUID */
|
||||
code: string;
|
||||
/** Which side is polling */
|
||||
side: Side;
|
||||
}
|
||||
|
||||
/**
|
||||
* Response from POST /poll when side=offerer
|
||||
*/
|
||||
export interface PollOffererResponse {
|
||||
/** Answer from answerer (null if not yet received) */
|
||||
answer: string | null;
|
||||
/** Additional signaling data from answerer */
|
||||
answerCandidates: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Response from POST /poll when side=answerer
|
||||
*/
|
||||
export interface PollAnswererResponse {
|
||||
/** Offer from offerer */
|
||||
offer: string;
|
||||
/** Additional signaling data from offerer */
|
||||
offerCandidates: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Response from POST /poll (union type)
|
||||
*/
|
||||
export type PollResponse = PollOffererResponse | PollAnswererResponse;
|
||||
|
||||
/**
|
||||
* Response from GET /health
|
||||
*/
|
||||
export interface HealthResponse {
|
||||
status: 'ok';
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Error response structure
|
||||
*/
|
||||
export interface ErrorResponse {
|
||||
error: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Client configuration options
|
||||
*/
|
||||
export interface RondevuClientOptions {
|
||||
/** Base URL of the Rondevu server (e.g., 'https://example.com') */
|
||||
baseUrl: string;
|
||||
/** Origin header value for session isolation (defaults to baseUrl origin) */
|
||||
origin?: string;
|
||||
/** Optional fetch implementation (for Node.js environments) */
|
||||
fetch?: typeof fetch;
|
||||
}
|
||||
18
tsconfig.json
Normal file
18
tsconfig.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "commonjs",
|
||||
"lib": ["ES2020", "DOM"],
|
||||
"declaration": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
Reference in New Issue
Block a user