MMap native library
This commit is contained in:
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
node_modules/
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
*.log
|
||||||
|
*.node
|
||||||
|
.DS_Store
|
||||||
6
.npmignore
Normal file
6
.npmignore
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
node_modules/
|
||||||
|
build/
|
||||||
|
*.log
|
||||||
|
.DS_Store
|
||||||
|
test.ts
|
||||||
|
tsconfig.json
|
||||||
25
binding.gyp
Normal file
25
binding.gyp
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# binding.gyp
|
||||||
|
{
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"target_name": "mmap_binding",
|
||||||
|
"sources": ["src/mmap_binding.cc"],
|
||||||
|
"include_dirs": [],
|
||||||
|
"cflags!": ["-fno-exceptions"],
|
||||||
|
"cflags_cc!": ["-fno-exceptions"],
|
||||||
|
"cflags_cc": ["-std=c++20", "-fexceptions"],
|
||||||
|
"conditions": [
|
||||||
|
["OS=='mac'", {
|
||||||
|
"xcode_settings": {
|
||||||
|
"CLANG_CXX_LANGUAGE_STANDARD": "c++20",
|
||||||
|
"GCC_ENABLE_CPP_EXCEPTIONS": "YES",
|
||||||
|
"MACOSX_DEPLOYMENT_TARGET": "10.15"
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
["OS=='linux'", {
|
||||||
|
"cflags_cc": ["-std=c++20"]
|
||||||
|
}]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
89
lib/index.ts
Normal file
89
lib/index.ts
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
// lib/index.ts
|
||||||
|
import { createRequire } from 'node:module';
|
||||||
|
import * as path from 'node:path';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
const require = createRequire(import.meta.url);
|
||||||
|
|
||||||
|
// dist/lib/index.js 에서 실행될 때 -> ../../build/Release/
|
||||||
|
// lib/index.ts 에서 실행될 때 -> ../build/Release/
|
||||||
|
// 둘 다 지원하도록 동적으로 찾기
|
||||||
|
|
||||||
|
function findBinding(): string {
|
||||||
|
const candidates = [
|
||||||
|
path.join(__dirname, '..', '..', 'build', 'Release', 'mmap_binding.node'), // dist/lib/ 기준
|
||||||
|
path.join(__dirname, '..', 'build', 'Release', 'mmap_binding.node'), // lib/ 기준
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const candidate of candidates) {
|
||||||
|
try {
|
||||||
|
require.resolve(candidate);
|
||||||
|
return candidate;
|
||||||
|
} catch {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error('Cannot find mmap_binding.node. Run `npm run build:native` first.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const binding = require(findBinding());
|
||||||
|
|
||||||
|
// 타입 정의
|
||||||
|
export interface MmapBinding {
|
||||||
|
map(size: number, prot: number, flags: number, fd: number, offset: number): Buffer;
|
||||||
|
sync(buffer: Buffer, offset: number, length: number, flags: number): void;
|
||||||
|
unmap(buffer: Buffer): void;
|
||||||
|
advise(buffer: Buffer, offset: number, length: number, advice: number): void;
|
||||||
|
pageSize(): number;
|
||||||
|
|
||||||
|
PROT_NONE: number;
|
||||||
|
PROT_READ: number;
|
||||||
|
PROT_WRITE: number;
|
||||||
|
PROT_EXEC: number;
|
||||||
|
|
||||||
|
MAP_SHARED: number;
|
||||||
|
MAP_PRIVATE: number;
|
||||||
|
MAP_ANONYMOUS?: number;
|
||||||
|
MAP_ANON?: number;
|
||||||
|
|
||||||
|
MS_ASYNC: number;
|
||||||
|
MS_SYNC: number;
|
||||||
|
MS_INVALIDATE: number;
|
||||||
|
|
||||||
|
MADV_NORMAL: number;
|
||||||
|
MADV_RANDOM: number;
|
||||||
|
MADV_SEQUENTIAL: number;
|
||||||
|
MADV_WILLNEED: number;
|
||||||
|
MADV_DONTNEED: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mmap: MmapBinding = binding;
|
||||||
|
|
||||||
|
export default mmap;
|
||||||
|
|
||||||
|
export const {
|
||||||
|
map,
|
||||||
|
sync,
|
||||||
|
unmap,
|
||||||
|
advise,
|
||||||
|
pageSize,
|
||||||
|
PROT_NONE,
|
||||||
|
PROT_READ,
|
||||||
|
PROT_WRITE,
|
||||||
|
PROT_EXEC,
|
||||||
|
MAP_SHARED,
|
||||||
|
MAP_PRIVATE,
|
||||||
|
MAP_ANONYMOUS,
|
||||||
|
MAP_ANON,
|
||||||
|
MS_ASYNC,
|
||||||
|
MS_SYNC,
|
||||||
|
MS_INVALIDATE,
|
||||||
|
MADV_NORMAL,
|
||||||
|
MADV_RANDOM,
|
||||||
|
MADV_SEQUENTIAL,
|
||||||
|
MADV_WILLNEED,
|
||||||
|
MADV_DONTNEED,
|
||||||
|
} = mmap;
|
||||||
1282
package-lock.json
generated
Normal file
1282
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
32
package.json
Normal file
32
package.json
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"name": "@elilee/mmap-native",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"author": "",
|
||||||
|
"type": "module",
|
||||||
|
"main": "./dist/lib/index.js",
|
||||||
|
"types": "./dist/lib/index.d.ts",
|
||||||
|
"scripts": {
|
||||||
|
"install": "node-gyp rebuild",
|
||||||
|
"build:native": "node-gyp rebuild",
|
||||||
|
"build:ts": "tsc",
|
||||||
|
"build": "npm run build:native && npm run build:ts",
|
||||||
|
"clean": "node-gyp clean && rm -rf dist"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^22.0.0",
|
||||||
|
"node-gyp": "^11.0.0",
|
||||||
|
"typescript": "^5.7.0"
|
||||||
|
},
|
||||||
|
"overrides": {
|
||||||
|
"tar": "^7.0.0",
|
||||||
|
"glob": "^10.0.0",
|
||||||
|
"rimraf": "^5.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
},
|
||||||
|
"os": ["linux", "darwin"],
|
||||||
|
"cpu": ["x64", "arm64"]
|
||||||
|
}
|
||||||
253
src/mmap_binding.cc
Normal file
253
src/mmap_binding.cc
Normal file
@@ -0,0 +1,253 @@
|
|||||||
|
// src/mmap_binding.cc
|
||||||
|
#define NAPI_VERSION 8
|
||||||
|
#include <node_api.h>
|
||||||
|
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <cerrno>
|
||||||
|
#include <cstring>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
// ============== 에러 처리 매크로 ==============
|
||||||
|
#define NAPI_CALL(env, call) \
|
||||||
|
do { \
|
||||||
|
napi_status status = (call); \
|
||||||
|
if (status != napi_ok) { \
|
||||||
|
const napi_extended_error_info* error_info = nullptr; \
|
||||||
|
napi_get_last_error_info((env), &error_info); \
|
||||||
|
const char* msg = (error_info && error_info->error_message) \
|
||||||
|
? error_info->error_message \
|
||||||
|
: "Unknown N-API error"; \
|
||||||
|
napi_throw_error((env), nullptr, msg); \
|
||||||
|
return nullptr; \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#define THROW_ERRNO(env, prefix) \
|
||||||
|
do { \
|
||||||
|
char buf[256]; \
|
||||||
|
std::snprintf(buf, sizeof(buf), "%s: %s", prefix, std::strerror(errno)); \
|
||||||
|
napi_throw_error(env, nullptr, buf); \
|
||||||
|
return nullptr; \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
// ============== 매핑 정보 저장 ==============
|
||||||
|
struct MmapInfo {
|
||||||
|
void* addr;
|
||||||
|
size_t length;
|
||||||
|
int fd;
|
||||||
|
bool owns_fd;
|
||||||
|
};
|
||||||
|
|
||||||
|
static std::unordered_map<void*, MmapInfo> g_mappings;
|
||||||
|
static std::mutex g_mutex;
|
||||||
|
|
||||||
|
// ============== Buffer Destructor (GC 시 자동 unmap) ==============
|
||||||
|
static void buffer_release_callback(napi_env env, void* data, void* hint) {
|
||||||
|
std::lock_guard<std::mutex> lock(g_mutex);
|
||||||
|
|
||||||
|
auto it = g_mappings.find(data);
|
||||||
|
if (it != g_mappings.end()) {
|
||||||
|
munmap(it->second.addr, it->second.length);
|
||||||
|
if (it->second.owns_fd && it->second.fd >= 0) {
|
||||||
|
close(it->second.fd);
|
||||||
|
}
|
||||||
|
g_mappings.erase(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============== mmap(size, prot, flags, fd, offset) -> Buffer ==============
|
||||||
|
static napi_value MmapMap(napi_env env, napi_callback_info info) {
|
||||||
|
size_t argc = 5;
|
||||||
|
napi_value args[5];
|
||||||
|
NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, nullptr, nullptr));
|
||||||
|
|
||||||
|
if (argc < 5) {
|
||||||
|
napi_throw_error(env, nullptr, "mmap.map requires 5 arguments: size, prot, flags, fd, offset");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t size, prot, flags, fd, offset;
|
||||||
|
NAPI_CALL(env, napi_get_value_int64(env, args[0], &size));
|
||||||
|
NAPI_CALL(env, napi_get_value_int64(env, args[1], &prot));
|
||||||
|
NAPI_CALL(env, napi_get_value_int64(env, args[2], &flags));
|
||||||
|
NAPI_CALL(env, napi_get_value_int64(env, args[3], &fd));
|
||||||
|
NAPI_CALL(env, napi_get_value_int64(env, args[4], &offset));
|
||||||
|
|
||||||
|
void* addr = mmap(nullptr, static_cast<size_t>(size),
|
||||||
|
static_cast<int>(prot), static_cast<int>(flags),
|
||||||
|
static_cast<int>(fd), static_cast<off_t>(offset));
|
||||||
|
|
||||||
|
if (addr == MAP_FAILED) {
|
||||||
|
THROW_ERRNO(env, "mmap failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(g_mutex);
|
||||||
|
g_mappings[addr] = {addr, static_cast<size_t>(size), static_cast<int>(fd), false};
|
||||||
|
}
|
||||||
|
|
||||||
|
napi_value buffer;
|
||||||
|
NAPI_CALL(env, napi_create_external_buffer(env, static_cast<size_t>(size),
|
||||||
|
addr, buffer_release_callback,
|
||||||
|
nullptr, &buffer));
|
||||||
|
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============== msync(buffer, offset, length, flags) ==============
|
||||||
|
static napi_value MmapSync(napi_env env, napi_callback_info info) {
|
||||||
|
size_t argc = 4;
|
||||||
|
napi_value args[4];
|
||||||
|
NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, nullptr, nullptr));
|
||||||
|
|
||||||
|
if (argc < 4) {
|
||||||
|
napi_throw_error(env, nullptr, "mmap.sync requires 4 arguments: buffer, offset, length, flags");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* data;
|
||||||
|
size_t buffer_length;
|
||||||
|
NAPI_CALL(env, napi_get_buffer_info(env, args[0], &data, &buffer_length));
|
||||||
|
|
||||||
|
int64_t offset, length, flags;
|
||||||
|
NAPI_CALL(env, napi_get_value_int64(env, args[1], &offset));
|
||||||
|
NAPI_CALL(env, napi_get_value_int64(env, args[2], &length));
|
||||||
|
NAPI_CALL(env, napi_get_value_int64(env, args[3], &flags));
|
||||||
|
|
||||||
|
if (offset < 0 || length < 0 || static_cast<size_t>(offset + length) > buffer_length) {
|
||||||
|
napi_throw_range_error(env, nullptr, "Invalid offset/length for msync");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
char* sync_addr = static_cast<char*>(data) + offset;
|
||||||
|
if (msync(sync_addr, static_cast<size_t>(length), static_cast<int>(flags)) != 0) {
|
||||||
|
THROW_ERRNO(env, "msync failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
napi_value undefined;
|
||||||
|
NAPI_CALL(env, napi_get_undefined(env, &undefined));
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============== munmap(buffer) ==============
|
||||||
|
static napi_value MmapUnmap(napi_env env, napi_callback_info info) {
|
||||||
|
size_t argc = 1;
|
||||||
|
napi_value args[1];
|
||||||
|
NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, nullptr, nullptr));
|
||||||
|
|
||||||
|
if (argc < 1) {
|
||||||
|
napi_throw_error(env, nullptr, "mmap.unmap requires 1 argument: buffer");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* data;
|
||||||
|
size_t length;
|
||||||
|
NAPI_CALL(env, napi_get_buffer_info(env, args[0], &data, &length));
|
||||||
|
|
||||||
|
std::lock_guard<std::mutex> lock(g_mutex);
|
||||||
|
auto it = g_mappings.find(data);
|
||||||
|
if (it != g_mappings.end()) {
|
||||||
|
if (munmap(it->second.addr, it->second.length) != 0) {
|
||||||
|
THROW_ERRNO(env, "munmap failed");
|
||||||
|
}
|
||||||
|
g_mappings.erase(it);
|
||||||
|
}
|
||||||
|
|
||||||
|
napi_value undefined;
|
||||||
|
NAPI_CALL(env, napi_get_undefined(env, &undefined));
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============== madvise(buffer, offset, length, advice) ==============
|
||||||
|
static napi_value MmapAdvise(napi_env env, napi_callback_info info) {
|
||||||
|
size_t argc = 4;
|
||||||
|
napi_value args[4];
|
||||||
|
NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, nullptr, nullptr));
|
||||||
|
|
||||||
|
if (argc < 4) {
|
||||||
|
napi_throw_error(env, nullptr, "mmap.advise requires 4 arguments: buffer, offset, length, advice");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* data;
|
||||||
|
size_t buffer_length;
|
||||||
|
NAPI_CALL(env, napi_get_buffer_info(env, args[0], &data, &buffer_length));
|
||||||
|
|
||||||
|
int64_t offset, length, advice;
|
||||||
|
NAPI_CALL(env, napi_get_value_int64(env, args[1], &offset));
|
||||||
|
NAPI_CALL(env, napi_get_value_int64(env, args[2], &length));
|
||||||
|
NAPI_CALL(env, napi_get_value_int64(env, args[3], &advice));
|
||||||
|
|
||||||
|
char* advise_addr = static_cast<char*>(data) + offset;
|
||||||
|
if (madvise(advise_addr, static_cast<size_t>(length), static_cast<int>(advice)) != 0) {
|
||||||
|
THROW_ERRNO(env, "madvise failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
napi_value undefined;
|
||||||
|
NAPI_CALL(env, napi_get_undefined(env, &undefined));
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============== pageSize() -> number ==============
|
||||||
|
static napi_value GetPageSize(napi_env env, napi_callback_info info) {
|
||||||
|
napi_value result;
|
||||||
|
NAPI_CALL(env, napi_create_int64(env, sysconf(_SC_PAGESIZE), &result));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============== 상수 정의 헬퍼 ==============
|
||||||
|
static void DefineConstant(napi_env env, napi_value exports, const char* name, int64_t value) {
|
||||||
|
napi_value val;
|
||||||
|
napi_create_int64(env, value, &val);
|
||||||
|
napi_set_named_property(env, exports, name, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============== 모듈 초기화 ==============
|
||||||
|
static napi_value Init(napi_env env, napi_value exports) {
|
||||||
|
napi_property_descriptor props[] = {
|
||||||
|
{"map", nullptr, MmapMap, nullptr, nullptr, nullptr, napi_default, nullptr},
|
||||||
|
{"sync", nullptr, MmapSync, nullptr, nullptr, nullptr, napi_default, nullptr},
|
||||||
|
{"unmap", nullptr, MmapUnmap, nullptr, nullptr, nullptr, napi_default, nullptr},
|
||||||
|
{"advise", nullptr, MmapAdvise, nullptr, nullptr, nullptr, napi_default, nullptr},
|
||||||
|
{"pageSize", nullptr, GetPageSize, nullptr, nullptr, nullptr, napi_default, nullptr},
|
||||||
|
};
|
||||||
|
|
||||||
|
NAPI_CALL(env, napi_define_properties(env, exports, sizeof(props) / sizeof(props[0]), props));
|
||||||
|
|
||||||
|
// Protection flags
|
||||||
|
DefineConstant(env, exports, "PROT_NONE", PROT_NONE);
|
||||||
|
DefineConstant(env, exports, "PROT_READ", PROT_READ);
|
||||||
|
DefineConstant(env, exports, "PROT_WRITE", PROT_WRITE);
|
||||||
|
DefineConstant(env, exports, "PROT_EXEC", PROT_EXEC);
|
||||||
|
|
||||||
|
// Map flags
|
||||||
|
DefineConstant(env, exports, "MAP_SHARED", MAP_SHARED);
|
||||||
|
DefineConstant(env, exports, "MAP_PRIVATE", MAP_PRIVATE);
|
||||||
|
#ifdef MAP_ANONYMOUS
|
||||||
|
DefineConstant(env, exports, "MAP_ANONYMOUS", MAP_ANONYMOUS);
|
||||||
|
#endif
|
||||||
|
#ifdef MAP_ANON
|
||||||
|
DefineConstant(env, exports, "MAP_ANON", MAP_ANON);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Sync flags
|
||||||
|
DefineConstant(env, exports, "MS_ASYNC", MS_ASYNC);
|
||||||
|
DefineConstant(env, exports, "MS_SYNC", MS_SYNC);
|
||||||
|
DefineConstant(env, exports, "MS_INVALIDATE", MS_INVALIDATE);
|
||||||
|
|
||||||
|
// Advise flags
|
||||||
|
DefineConstant(env, exports, "MADV_NORMAL", MADV_NORMAL);
|
||||||
|
DefineConstant(env, exports, "MADV_RANDOM", MADV_RANDOM);
|
||||||
|
DefineConstant(env, exports, "MADV_SEQUENTIAL", MADV_SEQUENTIAL);
|
||||||
|
DefineConstant(env, exports, "MADV_WILLNEED", MADV_WILLNEED);
|
||||||
|
DefineConstant(env, exports, "MADV_DONTNEED", MADV_DONTNEED);
|
||||||
|
|
||||||
|
return exports;
|
||||||
|
}
|
||||||
|
|
||||||
|
NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)
|
||||||
40
test.ts
Normal file
40
test.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
// test.ts
|
||||||
|
import * as fs from 'node:fs';
|
||||||
|
import mmap from './lib/index.js';
|
||||||
|
|
||||||
|
const FILE_PATH = './test_mmap.bin';
|
||||||
|
const FILE_SIZE = 4096;
|
||||||
|
|
||||||
|
// 파일 생성
|
||||||
|
const fd = fs.openSync(FILE_PATH, 'w+');
|
||||||
|
fs.ftruncateSync(fd, FILE_SIZE);
|
||||||
|
|
||||||
|
// mmap 매핑
|
||||||
|
const buffer = mmap.map(
|
||||||
|
FILE_SIZE,
|
||||||
|
mmap.PROT_READ | mmap.PROT_WRITE,
|
||||||
|
mmap.MAP_SHARED,
|
||||||
|
fd,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log('Mapped buffer length:', buffer.length);
|
||||||
|
console.log('Page size:', mmap.pageSize());
|
||||||
|
|
||||||
|
// 데이터 쓰기
|
||||||
|
buffer.write('Hello, mmap!', 0, 'utf8');
|
||||||
|
buffer.writeUInt32LE(12345, 100);
|
||||||
|
|
||||||
|
// msync
|
||||||
|
mmap.sync(buffer, 0, FILE_SIZE, mmap.MS_SYNC);
|
||||||
|
console.log('Data synced to disk');
|
||||||
|
|
||||||
|
// 데이터 읽기
|
||||||
|
console.log('String:', buffer.toString('utf8', 0, 12));
|
||||||
|
console.log('Number:', buffer.readUInt32LE(100));
|
||||||
|
|
||||||
|
// 정리
|
||||||
|
fs.closeSync(fd);
|
||||||
|
fs.unlinkSync(FILE_PATH);
|
||||||
|
|
||||||
|
console.log('Test passed!');
|
||||||
16
tsconfig.json
Normal file
16
tsconfig.json
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
// tsconfig.json
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"module": "NodeNext",
|
||||||
|
"moduleResolution": "NodeNext",
|
||||||
|
"outDir": "./dist",
|
||||||
|
"rootDir": ".",
|
||||||
|
"strict": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"declaration": true
|
||||||
|
},
|
||||||
|
"include": ["lib/**/*", "test.ts"],
|
||||||
|
"exclude": ["node_modules", "dist", "build"]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user