commit
2e1e99b117
|
@ -1 +1,3 @@
|
|||
/target
|
||||
/node_modules
|
||||
/test-ledger
|
|
@ -23,5 +23,10 @@ This project is currently based on an unstable feature of block subscription of
|
|||
|
||||
|
||||
```
|
||||
cargo run --bin lite-rpc -- --port 9000 --subscription-port 9001 --url http://localhost:8899
|
||||
cargo run --bin lite-rpc -- run --port 9000 --subscription-port 9001 --rpc-url http://localhost:8899
|
||||
```
|
||||
|
||||
## Tests
|
||||
```
|
||||
cargo run --bin lite-rpc -- test
|
||||
```
|
|
@ -0,0 +1,8 @@
|
|||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
transform: {
|
||||
'^.+\\.ts?$': 'ts-jest',
|
||||
},
|
||||
transformIgnorePatterns: ['<rootDir>/node_modules/'],
|
||||
};
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"name": "lightrpc-test",
|
||||
"version": "1.0.0",
|
||||
"repository": "",
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@solana/web3.js": "^1.66.2",
|
||||
"@types/jest": "^29.2.3",
|
||||
"jest": "^29.3.1",
|
||||
"start-server-and-test": "^1.14.0",
|
||||
"ts-jest": "^29.0.3"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "jest --detectOpenHandles",
|
||||
"start:lite-rpc": "lite-rpc",
|
||||
"test:test-validator": "start-server-and-test 'solana-test-validator --reset --quiet & target/debug/lite-rpc run --port 9000 --rpc-url http://localhost:8899 --subscription-port 9001 --websocket-url ws://localhost:8900/' http://localhost:8899/health test"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^4.8.4"
|
||||
}
|
||||
}
|
38
src/cli.rs
38
src/cli.rs
|
@ -1,4 +1,6 @@
|
|||
use {clap::Parser, solana_cli_config::ConfigInput, std::net::SocketAddr};
|
||||
use clap::Subcommand;
|
||||
|
||||
use clap::Parser;
|
||||
|
||||
/// Holds the configuration for a single run of the benchmark
|
||||
#[derive(Parser, Debug)]
|
||||
|
@ -11,18 +13,24 @@ use {clap::Parser, solana_cli_config::ConfigInput, std::net::SocketAddr};
|
|||
"
|
||||
)]
|
||||
pub struct Args {
|
||||
#[arg(short, long, default_value_t = SocketAddr::from(([127, 0, 0, 1], 8899)))]
|
||||
pub port: SocketAddr,
|
||||
#[arg(short, long, default_value_t = SocketAddr::from(([127, 0, 0, 1], 8900)))]
|
||||
pub subscription_port: SocketAddr,
|
||||
#[arg(short, long, default_value_t = String::new())]
|
||||
#[clap(subcommand)]
|
||||
pub command: Command,
|
||||
/*
|
||||
#[arg(short, long, default_value_t = String::from("8899"))]
|
||||
pub port: String,
|
||||
#[arg(short, long, default_value_t = String::from("8900"))]
|
||||
pub subscription_port: String,
|
||||
#[arg(short, long, default_value_t = String::from("http://localhost:8899"))]
|
||||
pub rpc_url: String,
|
||||
#[arg(short, long, default_value_t = String::new())]
|
||||
pub websocket_url: String,
|
||||
*/
|
||||
}
|
||||
|
||||
/*
|
||||
impl Args {
|
||||
|
||||
pub fn resolve_address(&mut self) {
|
||||
|
||||
if self.rpc_url.is_empty() {
|
||||
let (_, rpc_url) = ConfigInput::compute_json_rpc_url_setting(
|
||||
self.rpc_url.as_str(),
|
||||
|
@ -40,4 +48,20 @@ impl Args {
|
|||
self.websocket_url = ws_url;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
*/
|
||||
#[derive(Subcommand, Debug)]
|
||||
pub enum Command {
|
||||
Run {
|
||||
#[arg(short, long, default_value_t = String::from("8899"))]
|
||||
port: String,
|
||||
#[arg(short, long, default_value_t = String::from("8900"))]
|
||||
subscription_port: String,
|
||||
#[arg(short, long, default_value_t = String::from("http://localhost:8899"))]
|
||||
rpc_url: String,
|
||||
#[arg(short, long, default_value_t = String::new())]
|
||||
websocket_url: String,
|
||||
},
|
||||
Test,
|
||||
}
|
||||
|
|
89
src/main.rs
89
src/main.rs
|
@ -1,10 +1,11 @@
|
|||
use std::sync::Arc;
|
||||
use std::{net::SocketAddr, sync::Arc};
|
||||
|
||||
use clap::Parser;
|
||||
use context::LiteRpcSubsrciptionControl;
|
||||
use jsonrpc_core::MetaIoHandler;
|
||||
use jsonrpc_http_server::{hyper, AccessControlAllowOrigin, DomainsValidation, ServerBuilder};
|
||||
use pubsub::LitePubSubService;
|
||||
use solana_cli_config::ConfigInput;
|
||||
use solana_perf::thread::renice_this_thread;
|
||||
use tokio::sync::broadcast;
|
||||
|
||||
|
@ -23,21 +24,31 @@ mod rpc;
|
|||
|
||||
use cli::Args;
|
||||
|
||||
pub fn main() {
|
||||
let mut cli_config = Args::parse();
|
||||
cli_config.resolve_address();
|
||||
fn run(port: String, subscription_port: String, rpc_url: String, websocket_url: String) {
|
||||
let rpc_url = if rpc_url.is_empty() {
|
||||
let (_, rpc_url) = ConfigInput::compute_json_rpc_url_setting(
|
||||
rpc_url.as_str(),
|
||||
&ConfigInput::default().json_rpc_url,
|
||||
);
|
||||
rpc_url
|
||||
} else {
|
||||
rpc_url
|
||||
};
|
||||
let websocket_url = if websocket_url.is_empty() {
|
||||
let (_, ws_url) = ConfigInput::compute_websocket_url_setting(
|
||||
&websocket_url.as_str(),
|
||||
"",
|
||||
rpc_url.as_str(),
|
||||
"",
|
||||
);
|
||||
ws_url
|
||||
} else {
|
||||
websocket_url
|
||||
};
|
||||
println!(
|
||||
"Using rpc server {} and ws server {}",
|
||||
cli_config.rpc_url, cli_config.websocket_url
|
||||
rpc_url, websocket_url
|
||||
);
|
||||
let Args {
|
||||
rpc_url: json_rpc_url,
|
||||
websocket_url,
|
||||
port: rpc_addr,
|
||||
subscription_port,
|
||||
..
|
||||
} = &cli_config;
|
||||
|
||||
let performance_counter = PerformanceCounter::new();
|
||||
launch_performance_updating_thread(performance_counter.clone());
|
||||
|
||||
|
@ -49,14 +60,16 @@ pub fn main() {
|
|||
notification_reciever,
|
||||
));
|
||||
|
||||
let subscription_port = format!("127.0.0.1:{subscription_port}")
|
||||
.parse::<SocketAddr>()
|
||||
.expect("Invalid subscription port");
|
||||
|
||||
// start websocket server
|
||||
let (_trigger, websocket_service) = LitePubSubService::new(
|
||||
pubsub_control.clone(),
|
||||
*subscription_port,
|
||||
subscription_port,
|
||||
performance_counter.clone(),
|
||||
);
|
||||
|
||||
// start recieving notifications and broadcast them
|
||||
{
|
||||
let pubsub_control = pubsub_control.clone();
|
||||
std::thread::Builder::new()
|
||||
|
@ -71,12 +84,11 @@ pub fn main() {
|
|||
io.extend_with(lite_rpc.to_delegate());
|
||||
|
||||
let mut request_processor = LightRpcRequestProcessor::new(
|
||||
json_rpc_url,
|
||||
websocket_url,
|
||||
rpc_url.as_str(),
|
||||
&websocket_url,
|
||||
notification_sender,
|
||||
performance_counter.clone(),
|
||||
);
|
||||
|
||||
let runtime = Arc::new(
|
||||
tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(1)
|
||||
|
@ -87,7 +99,7 @@ pub fn main() {
|
|||
.expect("Runtime"),
|
||||
);
|
||||
let max_request_body_size: usize = 50 * (1 << 10);
|
||||
let socket_addr = *rpc_addr;
|
||||
let socket_addr = port.parse::<SocketAddr>().unwrap();
|
||||
|
||||
{
|
||||
let request_processor = request_processor.clone();
|
||||
|
@ -109,3 +121,40 @@ pub fn main() {
|
|||
request_processor.free();
|
||||
websocket_service.close().unwrap();
|
||||
}
|
||||
|
||||
fn ts_test() {
|
||||
let res = std::process::Command::new("yarn")
|
||||
.args(["run", "test:test-validator"])
|
||||
.output()
|
||||
.unwrap();
|
||||
println!("{}", String::from_utf8_lossy(&res.stdout));
|
||||
println!("{}", String::from_utf8_lossy(&res.stderr));
|
||||
}
|
||||
|
||||
pub fn main() {
|
||||
let cli_command = Args::parse();
|
||||
|
||||
match cli_command.command {
|
||||
cli::Command::Run {
|
||||
port,
|
||||
subscription_port,
|
||||
rpc_url,
|
||||
websocket_url,
|
||||
} => run(port, subscription_port, rpc_url, websocket_url),
|
||||
cli::Command::Test => ts_test(),
|
||||
}
|
||||
//cli_config.resolve_address();
|
||||
//println!(
|
||||
// "Using rpc server {} and ws server {}",
|
||||
// cli_config.rpc_url, cli_config.websocket_url
|
||||
//);
|
||||
//let Args {
|
||||
// rpc_url: json_rpc_url,
|
||||
// websocket_url,
|
||||
// port: rpc_addr,
|
||||
// subscription_port,
|
||||
// ..
|
||||
//} = &cli_config;
|
||||
|
||||
// start recieving notifications and broadcast them
|
||||
}
|
||||
|
|
96
src/rpc.rs
96
src/rpc.rs
|
@ -195,7 +195,6 @@ impl LightRpcRequestProcessor {
|
|||
for signature in signatures {
|
||||
match signature_status.entry(signature.clone()) {
|
||||
dashmap::mapref::entry::Entry::Occupied(mut x) => {
|
||||
|
||||
let signature_notification = SignatureNotification {
|
||||
signature: Signature::from_str(signature.as_str())
|
||||
.unwrap(),
|
||||
|
@ -270,7 +269,7 @@ pub mod lite_rpc {
|
|||
|
||||
use itertools::Itertools;
|
||||
use solana_sdk::{fee_calculator::FeeCalculator, pubkey::Pubkey};
|
||||
use solana_transaction_status::{TransactionStatus, TransactionConfirmationStatus};
|
||||
use solana_transaction_status::{TransactionConfirmationStatus, TransactionStatus};
|
||||
|
||||
use super::*;
|
||||
#[rpc]
|
||||
|
@ -329,7 +328,6 @@ pub mod lite_rpc {
|
|||
signature_strs: Vec<String>,
|
||||
config: Option<RpcSignatureStatusConfig>,
|
||||
) -> Result<RpcResponse<Vec<Option<TransactionStatus>>>>;
|
||||
|
||||
}
|
||||
pub struct LightRpc;
|
||||
impl Lite for LightRpc {
|
||||
|
@ -461,17 +459,15 @@ pub mod lite_rpc {
|
|||
|
||||
match k_value {
|
||||
Some(value) => match *value {
|
||||
Some(commitment_for_signature) => {
|
||||
Ok(RpcResponse {
|
||||
context: RpcResponseContext::new(slot),
|
||||
value: if commitment.eq(&CommitmentLevel::Finalized) {
|
||||
commitment_for_signature.eq(&CommitmentLevel::Finalized)
|
||||
} else {
|
||||
commitment_for_signature.eq(&CommitmentLevel::Finalized)
|
||||
|| commitment_for_signature.eq(&CommitmentLevel::Confirmed)
|
||||
},
|
||||
})
|
||||
}
|
||||
Some(commitment_for_signature) => Ok(RpcResponse {
|
||||
context: RpcResponseContext::new(slot),
|
||||
value: if commitment.eq(&CommitmentLevel::Finalized) {
|
||||
commitment_for_signature.eq(&CommitmentLevel::Finalized)
|
||||
} else {
|
||||
commitment_for_signature.eq(&CommitmentLevel::Finalized)
|
||||
|| commitment_for_signature.eq(&CommitmentLevel::Confirmed)
|
||||
},
|
||||
}),
|
||||
None => Ok(RpcResponse {
|
||||
context: RpcResponseContext::new(slot),
|
||||
value: false,
|
||||
|
@ -502,43 +498,51 @@ pub mod lite_rpc {
|
|||
signature_strs: Vec<String>,
|
||||
_config: Option<RpcSignatureStatusConfig>,
|
||||
) -> Result<RpcResponse<Vec<Option<TransactionStatus>>>> {
|
||||
let confirmed_slot = meta.context.confirmed_block_info.slot.load(Ordering::Relaxed);
|
||||
let status = signature_strs.iter().map(|x| {
|
||||
let singature_status = meta.context.signature_status.get(x);
|
||||
let k_value = singature_status;
|
||||
match k_value {
|
||||
Some(value) => match *value {
|
||||
Some(commitment_for_signature) => {
|
||||
let slot = meta.context
|
||||
let confirmed_slot = meta
|
||||
.context
|
||||
.confirmed_block_info
|
||||
.slot
|
||||
.load(Ordering::Relaxed);
|
||||
let status = signature_strs
|
||||
.iter()
|
||||
.map(|x| {
|
||||
let singature_status = meta.context.signature_status.get(x);
|
||||
let k_value = singature_status;
|
||||
match k_value {
|
||||
Some(value) => match *value {
|
||||
Some(commitment_for_signature) => {
|
||||
let slot = meta
|
||||
.context
|
||||
.confirmed_block_info
|
||||
.slot
|
||||
.load(Ordering::Relaxed);
|
||||
meta.performance_counter
|
||||
.update_confirm_transaction_counter();
|
||||
meta.performance_counter
|
||||
.update_confirm_transaction_counter();
|
||||
|
||||
let status = match commitment_for_signature {
|
||||
CommitmentLevel::Finalized => TransactionConfirmationStatus::Finalized,
|
||||
_ => TransactionConfirmationStatus::Confirmed,
|
||||
};
|
||||
Some(TransactionStatus {
|
||||
slot,
|
||||
confirmations: Some(1),
|
||||
status: Ok(()),
|
||||
err: None,
|
||||
confirmation_status : Some(status)
|
||||
})
|
||||
}
|
||||
let status = match commitment_for_signature {
|
||||
CommitmentLevel::Finalized => {
|
||||
TransactionConfirmationStatus::Finalized
|
||||
}
|
||||
_ => TransactionConfirmationStatus::Confirmed,
|
||||
};
|
||||
Some(TransactionStatus {
|
||||
slot,
|
||||
confirmations: Some(1),
|
||||
status: Ok(()),
|
||||
err: None,
|
||||
confirmation_status: Some(status),
|
||||
})
|
||||
}
|
||||
None => None,
|
||||
},
|
||||
None => None,
|
||||
},
|
||||
None => None
|
||||
}
|
||||
}).collect_vec();
|
||||
Ok(
|
||||
RpcResponse {
|
||||
context : RpcResponseContext::new(confirmed_slot),
|
||||
value: status,
|
||||
}
|
||||
)
|
||||
}
|
||||
})
|
||||
.collect_vec();
|
||||
Ok(RpcResponse {
|
||||
context: RpcResponseContext::new(confirmed_slot),
|
||||
value: status,
|
||||
})
|
||||
}
|
||||
|
||||
fn request_airdrop(
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
import { Connection, Keypair, LAMPORTS_PER_SOL, Message, VersionedTransaction } from "@solana/web3.js";
|
||||
import { url } from "./urls";
|
||||
|
||||
test('send and confirm transaction', async () => {
|
||||
const connection = new Connection(url, 'confirmed');
|
||||
const payer = Keypair.generate();
|
||||
|
||||
await connection.requestAirdrop(payer.publicKey, LAMPORTS_PER_SOL);
|
||||
const recentBlockhash = (await connection.getLatestBlockhash('confirmed')).blockhash;
|
||||
const versionedTx = new VersionedTransaction(
|
||||
new Message({
|
||||
header: {
|
||||
numRequiredSignatures: 1,
|
||||
numReadonlySignedAccounts: 0,
|
||||
numReadonlyUnsignedAccounts: 0,
|
||||
},
|
||||
recentBlockhash,
|
||||
instructions: [],
|
||||
accountKeys: [payer.publicKey.toBase58()],
|
||||
}),
|
||||
);
|
||||
|
||||
versionedTx.sign([payer]);
|
||||
const signature = await connection.sendTransaction(versionedTx);
|
||||
const latestBlockHash = await connection.getLatestBlockhash();
|
||||
await connection.confirmTransaction({
|
||||
blockhash: latestBlockHash.blockhash,
|
||||
lastValidBlockHeight: latestBlockHash.lastValidBlockHeight,
|
||||
signature: signature,
|
||||
});
|
||||
});
|
|
@ -0,0 +1 @@
|
|||
export const url = "http://127.0.0.1:9000";
|
|
@ -0,0 +1,82 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
/* Visit https://aka.ms/tsconfig to read more about this file */
|
||||
|
||||
/* Projects */
|
||||
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
|
||||
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
||||
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
|
||||
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
|
||||
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
||||
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||
|
||||
/* Language and Environment */
|
||||
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
||||
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
||||
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
||||
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
|
||||
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
||||
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
|
||||
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
|
||||
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
||||
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
||||
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
||||
|
||||
/* Modules */
|
||||
"module": "commonjs", /* Specify what module code is generated. */
|
||||
// "rootDir": "./", /* Specify the root folder within your source files. */
|
||||
// "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
|
||||
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
||||
// "resolveJsonModule": true, /* Enable importing .json files. */
|
||||
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
||||
|
||||
/* JavaScript Support */
|
||||
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
|
||||
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
||||
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
|
||||
|
||||
/* Emit */
|
||||
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
||||
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
|
||||
// "outDir": "./", /* Specify an output folder for all emitted files. */
|
||||
// "removeComments": true, /* Disable emitting comments. */
|
||||
// "noEmit": true, /* Disable emitting files from a compilation. */
|
||||
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
|
||||
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
||||
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
||||
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
||||
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
||||
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
||||
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
|
||||
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
|
||||
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
||||
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
|
||||
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
|
||||
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
|
||||
|
||||
/* Interop Constraints */
|
||||
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
||||
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
|
||||
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
||||
|
||||
/* Type Checking */
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue