feat(gpt-runner-web): move server state to storage api
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -4,6 +4,9 @@
|
||||
.nuxt
|
||||
.output
|
||||
.temp
|
||||
.gradle
|
||||
.qodana
|
||||
build
|
||||
*.local
|
||||
*.log
|
||||
*.vsix
|
||||
|
||||
@@ -21,6 +21,7 @@ export default defineBuildConfig({
|
||||
clean: true,
|
||||
declaration: true,
|
||||
externals: [
|
||||
'@kvs/node-localstorage',
|
||||
'unconfig',
|
||||
'express',
|
||||
'debug',
|
||||
@@ -29,5 +30,14 @@ export default defineBuildConfig({
|
||||
rollup: {
|
||||
emitCJS: true,
|
||||
inlineDependencies: true,
|
||||
dts: {
|
||||
compilerOptions: {
|
||||
baseUrl: '.',
|
||||
paths: {
|
||||
// fix types error
|
||||
'@kvs/storage': ['./node_modules/@kvs/storage/'],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -56,6 +56,8 @@
|
||||
"stub": "unbuild --stub"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@kvs/node-localstorage": "*",
|
||||
"cachedir": "*",
|
||||
"debug": "*",
|
||||
"find-free-ports": "*",
|
||||
"ip": "*",
|
||||
@@ -63,6 +65,9 @@
|
||||
"zod": "*"
|
||||
},
|
||||
"dependencies": {
|
||||
"@kvs/storage": "^2.1.3",
|
||||
"@kvs/node-localstorage": "^2.1.3",
|
||||
"cachedir": "^2.3.0",
|
||||
"debug": "^4.3.4",
|
||||
"find-free-ports": "^3.1.1",
|
||||
"ip": "^1.1.8",
|
||||
|
||||
@@ -21,3 +21,8 @@ export enum GptFileTreeItemType {
|
||||
File = 'file',
|
||||
Chat = 'chat',
|
||||
}
|
||||
|
||||
export enum ServerStorageName {
|
||||
FrontendState = 'frontend-state',
|
||||
WebPreset = 'web-preset',
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { GptFileInfo, GptFileInfoTree, SingleChatMessage, SingleFileConfig, UserConfig } from './config'
|
||||
import type { ServerStorageName } from './enum'
|
||||
|
||||
export interface BaseResponse<T = any> {
|
||||
type: 'Success' | 'Fail'
|
||||
@@ -35,20 +36,22 @@ export interface GetUserConfigResData {
|
||||
userConfig: UserConfig
|
||||
}
|
||||
|
||||
export interface GetStateReqParams {
|
||||
export interface GetStorageReqParams {
|
||||
storageName: ServerStorageName
|
||||
key: string
|
||||
}
|
||||
|
||||
export type FrontendState = Record<string, any> | null | undefined
|
||||
export type ServerStorageValue = Record<string, any> | null | undefined
|
||||
|
||||
export interface GetStateResData {
|
||||
state: FrontendState
|
||||
export interface GetStorageResData {
|
||||
value: ServerStorageValue
|
||||
cacheDir: string
|
||||
}
|
||||
|
||||
export interface SaveStateReqParams {
|
||||
export interface SaveStorageReqParams {
|
||||
storageName: ServerStorageName
|
||||
key: string
|
||||
state: FrontendState
|
||||
value?: ServerStorageValue
|
||||
}
|
||||
|
||||
export type SaveStateResData = null
|
||||
export type SaveStorageResData = null
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { z } from 'zod'
|
||||
import { ChatMessageStatus, ChatRole, ClientEventName, GptFileTreeItemType } from '../types'
|
||||
import { ChatMessageStatus, ChatRole, ClientEventName, GptFileTreeItemType, ServerStorageName } from '../types'
|
||||
|
||||
export const ChatRoleSchema = z.nativeEnum(ChatRole)
|
||||
|
||||
@@ -8,3 +8,5 @@ export const ChatMessageStatusSchema = z.nativeEnum(ChatMessageStatus)
|
||||
export const ClientEventNameSchema = z.nativeEnum(ClientEventName)
|
||||
|
||||
export const GptFileTreeItemTypeSchema = z.nativeEnum(GptFileTreeItemType)
|
||||
|
||||
export const ServerStorageNameSchema = z.nativeEnum(ServerStorageName)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { z } from 'zod'
|
||||
import type { ChatStreamReqParams, GetGptFilesReqParams, GetUserConfigReqParams } from '../types'
|
||||
import type { ChatStreamReqParams, GetGptFilesReqParams, GetStorageReqParams, GetUserConfigReqParams, SaveStorageReqParams } from '../types'
|
||||
import { SingleChatMessageSchema, SingleFileConfigSchema } from './config.zod'
|
||||
import { ServerStorageNameSchema } from './enum.zod'
|
||||
|
||||
export const ChatStreamReqParamsSchema = z.object({
|
||||
messages: z.array(SingleChatMessageSchema),
|
||||
@@ -18,11 +19,13 @@ export const GetUserConfigReqParamsSchema = z.object({
|
||||
rootPath: z.string(),
|
||||
}) satisfies z.ZodType<GetUserConfigReqParams>
|
||||
|
||||
export const GetStateReqParamsSchema = z.object({
|
||||
export const GetStorageReqParamsSchema = z.object({
|
||||
storageName: ServerStorageNameSchema,
|
||||
key: z.string(),
|
||||
})
|
||||
}) satisfies z.ZodType<GetStorageReqParams>
|
||||
|
||||
export const SaveStateReqParamsSchema = z.object({
|
||||
export const SaveStorageReqParamsSchema = z.object({
|
||||
storageName: ServerStorageNameSchema,
|
||||
key: z.string(),
|
||||
state: z.record(z.any()).nullable().optional(),
|
||||
})
|
||||
value: z.record(z.any()).nullable().optional(),
|
||||
}) satisfies z.ZodType<SaveStorageReqParams>
|
||||
|
||||
19
packages/gpt-runner-shared/src/node/helpers/get-cache-dir.ts
Normal file
19
packages/gpt-runner-shared/src/node/helpers/get-cache-dir.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import fs from 'node:fs'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment, @typescript-eslint/prefer-ts-expect-error
|
||||
// @ts-ignore
|
||||
import getCacheDir from 'cachedir'
|
||||
import { PathUtils } from './path-utils'
|
||||
|
||||
export async function getGlobalCacheDir(name: string) {
|
||||
const cacheDir = getCacheDir(name)
|
||||
await createCacheDir(cacheDir)
|
||||
return cacheDir
|
||||
}
|
||||
|
||||
async function createCacheDir(cacheDir: string) {
|
||||
if (await PathUtils.isAccessible(cacheDir, 'W'))
|
||||
return
|
||||
|
||||
await fs.promises.mkdir(cacheDir, { recursive: true })
|
||||
}
|
||||
18
packages/gpt-runner-shared/src/node/helpers/get-storage.ts
Normal file
18
packages/gpt-runner-shared/src/node/helpers/get-storage.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { kvsLocalStorage } from '@kvs/node-localstorage'
|
||||
import type { ServerStorageName } from '../../common/types'
|
||||
import { getGlobalCacheDir } from './get-cache-dir'
|
||||
|
||||
export async function getStorage(storageName: ServerStorageName) {
|
||||
const cacheFolder = await getGlobalCacheDir('gpt-runner-server')
|
||||
|
||||
const storage = await kvsLocalStorage<Record<string, Record<string, any> | null>>({
|
||||
name: storageName,
|
||||
storeFilePath: cacheFolder,
|
||||
version: 1,
|
||||
})
|
||||
|
||||
return {
|
||||
cacheDir: cacheFolder,
|
||||
storage,
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
export * from './file-utils'
|
||||
export * from './get-cache-dir'
|
||||
export * from './get-storage'
|
||||
export * from './network'
|
||||
export * from './path-utils'
|
||||
export * from './request'
|
||||
export * from './server'
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import type { BaseResponse, GetStorageReqParams, GetStorageResData, SaveStorageReqParams, SaveStorageResData } from '@nicepkg/gpt-runner-shared/common'
|
||||
import { getGlobalConfig } from '../helpers/global-config'
|
||||
|
||||
export interface GetServerStorageParams extends GetStorageReqParams {
|
||||
}
|
||||
|
||||
export type GetServerStorageRes = BaseResponse<GetStorageResData>
|
||||
|
||||
export async function getServerStorage(params: GetServerStorageParams): Promise<GetServerStorageRes> {
|
||||
const { storageName, key } = params
|
||||
|
||||
const res = await fetch(`${getGlobalConfig().serverBaseUrl}/api/storage?storageName=${storageName}&key=${key}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
const data = await res.json()
|
||||
return data
|
||||
}
|
||||
|
||||
export interface SaveServerStorageParams extends SaveStorageReqParams {
|
||||
}
|
||||
|
||||
export type SaveServerStorageRes = BaseResponse<SaveStorageResData>
|
||||
|
||||
export async function saveServerStorage(params: SaveServerStorageParams): Promise<SaveServerStorageRes> {
|
||||
const { storageName, key, value } = params
|
||||
|
||||
const res = await fetch(`${getGlobalConfig().serverBaseUrl}/api/storage`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ storageName, key, value }),
|
||||
})
|
||||
const data = await res.json()
|
||||
return data
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
import type { BaseResponse, GetStateReqParams, GetStateResData, SaveStateReqParams, SaveStateResData } from '@nicepkg/gpt-runner-shared/common'
|
||||
import { getGlobalConfig } from '../helpers/global-config'
|
||||
|
||||
export interface FetchStateParams extends GetStateReqParams {
|
||||
}
|
||||
|
||||
export type FetchStateRes = BaseResponse<GetStateResData>
|
||||
|
||||
export async function fetchState(params: FetchStateParams): Promise<FetchStateRes> {
|
||||
const { key } = params
|
||||
|
||||
const res = await fetch(`${getGlobalConfig().serverBaseUrl}/api/state?key=${key}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
const data = await res.json()
|
||||
return data
|
||||
}
|
||||
|
||||
export interface SaveStateParams extends SaveStateReqParams {
|
||||
}
|
||||
|
||||
export type SaveStateRes = BaseResponse<SaveStateResData>
|
||||
|
||||
export async function saveState(params: SaveStateParams): Promise<SaveStateRes> {
|
||||
const { key, state } = params
|
||||
|
||||
const res = await fetch(`${getGlobalConfig().serverBaseUrl}/api/state`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ key, state }),
|
||||
})
|
||||
const data = await res.json()
|
||||
return data
|
||||
}
|
||||
@@ -1,27 +1,36 @@
|
||||
import type { StateStorage } from 'zustand/middleware'
|
||||
import type { FrontendState } from '@nicepkg/gpt-runner-shared/common'
|
||||
import { debounce, runOnceByKey, tryParseJson } from '@nicepkg/gpt-runner-shared/common'
|
||||
import type { ServerStorageValue } from '@nicepkg/gpt-runner-shared/common'
|
||||
import { ServerStorageName, debounce, tryParseJson } from '@nicepkg/gpt-runner-shared/common'
|
||||
import { getGlobalConfig } from '../../helpers/global-config'
|
||||
import { fetchState, saveState } from '../../networks/state'
|
||||
import { getServerStorage, saveServerStorage } from '../../networks/server-storage'
|
||||
|
||||
let hasUpdateStateFromRemote = false
|
||||
|
||||
const debounceSaveState = debounce(async (key: string, state: FrontendState) => {
|
||||
if (!hasUpdateStateFromRemote)
|
||||
// just only request once onload
|
||||
let hasUpdateStateFromRemote: 'pending' | 'finish' | false = false
|
||||
async function getStateFromServerOnce(key: string) {
|
||||
if (hasUpdateStateFromRemote !== false)
|
||||
return
|
||||
|
||||
return await saveState({ key, state })
|
||||
}, 1000)
|
||||
|
||||
function updateStateFromRemoteOnce(key: string) {
|
||||
return runOnceByKey(async (key: string) => {
|
||||
const res = await fetchState({ key })
|
||||
hasUpdateStateFromRemote = true
|
||||
|
||||
return res
|
||||
}, key)(key)
|
||||
hasUpdateStateFromRemote = 'pending'
|
||||
const res = await getServerStorage({
|
||||
storageName: ServerStorageName.FrontendState,
|
||||
key,
|
||||
})
|
||||
hasUpdateStateFromRemote = 'finish'
|
||||
return res?.data?.value
|
||||
}
|
||||
|
||||
// will save each action state to server
|
||||
const debounceSaveStateToServer = debounce(async (key: string, value: ServerStorageValue) => {
|
||||
if (hasUpdateStateFromRemote !== 'finish')
|
||||
return
|
||||
|
||||
return await saveServerStorage({
|
||||
storageName: ServerStorageName.FrontendState,
|
||||
key,
|
||||
value,
|
||||
})
|
||||
}, 1000)
|
||||
|
||||
export class CustomStorage implements StateStorage {
|
||||
#storage: Storage
|
||||
|
||||
@@ -36,7 +45,7 @@ export class CustomStorage implements StateStorage {
|
||||
|
||||
getItem = async (key: string) => {
|
||||
const finalKey = CustomStorage.prefixKey + key
|
||||
const remoteSourceValue = (await updateStateFromRemoteOnce(finalKey))?.data?.state
|
||||
const remoteSourceValue = await getStateFromServerOnce(finalKey)
|
||||
|
||||
if (remoteSourceValue !== undefined) {
|
||||
const remoteString = JSON.stringify(remoteSourceValue)
|
||||
@@ -52,7 +61,7 @@ export class CustomStorage implements StateStorage {
|
||||
const finalKey = CustomStorage.prefixKey + key
|
||||
|
||||
// save to server
|
||||
debounceSaveState(finalKey, tryParseJson(value))
|
||||
debounceSaveStateToServer(finalKey, tryParseJson(value))
|
||||
|
||||
return this.#storage.setItem(finalKey, value)
|
||||
}
|
||||
@@ -61,7 +70,7 @@ export class CustomStorage implements StateStorage {
|
||||
const finalKey = CustomStorage.prefixKey + key
|
||||
|
||||
// save to server
|
||||
debounceSaveState(finalKey, null)
|
||||
debounceSaveStateToServer(finalKey, null)
|
||||
|
||||
return this.#storage.removeItem(finalKey)
|
||||
}
|
||||
|
||||
@@ -62,13 +62,11 @@
|
||||
"stub": "unbuild --stub"
|
||||
},
|
||||
"dependencies": {
|
||||
"@kvs/node-localstorage": "^2.1.3",
|
||||
"@microsoft/fetch-event-source": "^2.0.1",
|
||||
"@nicepkg/gpt-runner-core": "workspace:*",
|
||||
"@nicepkg/gpt-runner-shared": "workspace:*",
|
||||
"@tanstack/react-query": "^4.29.11",
|
||||
"@vscode/webview-ui-toolkit": "^1.2.2",
|
||||
"cachedir": "^2.3.0",
|
||||
"clsx": "^1.2.1",
|
||||
"commander": "^10.0.1",
|
||||
"connect-history-api-fallback": "^2.0.0",
|
||||
|
||||
@@ -3,14 +3,14 @@ import type { Controller, ControllerConfig } from '../types'
|
||||
import { chatgptControllers } from './chatgpt.controller'
|
||||
import { configControllers } from './config.controller'
|
||||
import { gptFilesControllers } from './gpt-files.controller'
|
||||
import { stateControllers } from './state.controller'
|
||||
import { storageControllers } from './storage.controller'
|
||||
|
||||
export function processControllers(router: Router) {
|
||||
const allControllersConfig: ControllerConfig[] = [
|
||||
chatgptControllers,
|
||||
configControllers,
|
||||
gptFilesControllers,
|
||||
stateControllers,
|
||||
storageControllers,
|
||||
]
|
||||
|
||||
allControllersConfig.forEach((controllerConfig) => {
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
import { sendSuccessResponse, verifyParamsByZod } from '@nicepkg/gpt-runner-shared/node'
|
||||
import type { GetStateReqParams, GetStateResData, SaveStateReqParams, SaveStateResData } from '@nicepkg/gpt-runner-shared/common'
|
||||
import { Debug, GetStateReqParamsSchema, SaveStateReqParamsSchema } from '@nicepkg/gpt-runner-shared/common'
|
||||
import { kvsLocalStorage } from '@kvs/node-localstorage'
|
||||
import { getGlobalCacheDir } from '../helpers/get-cache-dir'
|
||||
import type { ControllerConfig } from '../types'
|
||||
|
||||
const debug = new Debug('state.controller')
|
||||
|
||||
async function getStorage() {
|
||||
const cacheFolder = await getGlobalCacheDir('gpt-runner-server')
|
||||
debug.log('cacheFolder', cacheFolder)
|
||||
|
||||
const storage = await kvsLocalStorage<Record<string, Record<string, any> | null>>({
|
||||
name: 'frontend-state',
|
||||
storeFilePath: cacheFolder,
|
||||
version: 1,
|
||||
})
|
||||
|
||||
return {
|
||||
cacheDir: cacheFolder,
|
||||
storage,
|
||||
}
|
||||
}
|
||||
|
||||
export const stateControllers: ControllerConfig = {
|
||||
namespacePath: '/state',
|
||||
controllers: [
|
||||
{
|
||||
url: '/',
|
||||
method: 'get',
|
||||
handler: async (req, res) => {
|
||||
const query = req.query as GetStateReqParams
|
||||
|
||||
verifyParamsByZod(query, GetStateReqParamsSchema)
|
||||
|
||||
const { key } = query
|
||||
|
||||
const { storage, cacheDir } = await getStorage()
|
||||
const state = await storage.get(key)
|
||||
|
||||
sendSuccessResponse(res, {
|
||||
data: {
|
||||
state,
|
||||
cacheDir,
|
||||
} satisfies GetStateResData,
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
url: '/',
|
||||
method: 'post',
|
||||
handler: async (req, res) => {
|
||||
const body = req.body as SaveStateReqParams
|
||||
|
||||
verifyParamsByZod(body, SaveStateReqParamsSchema)
|
||||
|
||||
const { key, state } = body
|
||||
|
||||
const { storage } = await getStorage()
|
||||
|
||||
switch (state) {
|
||||
case undefined:
|
||||
// remove
|
||||
await storage.delete(key)
|
||||
break
|
||||
default:
|
||||
// set
|
||||
await storage.set(key, state)
|
||||
break
|
||||
}
|
||||
|
||||
sendSuccessResponse(res, {
|
||||
data: null satisfies SaveStateResData,
|
||||
})
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import { getStorage, sendSuccessResponse, verifyParamsByZod } from '@nicepkg/gpt-runner-shared/node'
|
||||
import type { GetStorageReqParams, GetStorageResData, SaveStorageReqParams, SaveStorageResData } from '@nicepkg/gpt-runner-shared/common'
|
||||
import { GetStorageReqParamsSchema, SaveStorageReqParamsSchema } from '@nicepkg/gpt-runner-shared/common'
|
||||
import type { ControllerConfig } from '../types'
|
||||
|
||||
export const storageControllers: ControllerConfig = {
|
||||
namespacePath: '/storage',
|
||||
controllers: [
|
||||
{
|
||||
url: '/',
|
||||
method: 'get',
|
||||
handler: async (req, res) => {
|
||||
const query = req.query as GetStorageReqParams
|
||||
|
||||
verifyParamsByZod(query, GetStorageReqParamsSchema)
|
||||
|
||||
const { key, storageName } = query
|
||||
|
||||
const { storage, cacheDir } = await getStorage(storageName)
|
||||
const value = await storage.get(key)
|
||||
|
||||
sendSuccessResponse(res, {
|
||||
data: {
|
||||
value,
|
||||
cacheDir,
|
||||
} satisfies GetStorageResData,
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
url: '/',
|
||||
method: 'post',
|
||||
handler: async (req, res) => {
|
||||
const body = req.body as SaveStorageReqParams
|
||||
|
||||
verifyParamsByZod(body, SaveStorageReqParamsSchema)
|
||||
|
||||
const { storageName, key, value } = body
|
||||
|
||||
const { storage } = await getStorage(storageName)
|
||||
|
||||
switch (value) {
|
||||
case undefined:
|
||||
// remove
|
||||
await storage.delete(key)
|
||||
break
|
||||
default:
|
||||
// set
|
||||
await storage.set(key, value)
|
||||
break
|
||||
}
|
||||
|
||||
sendSuccessResponse(res, {
|
||||
data: null satisfies SaveStorageResData,
|
||||
})
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
22
packages/gpt-runner-web/server/src/helpers/get-storage.ts
Normal file
22
packages/gpt-runner-web/server/src/helpers/get-storage.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { kvsLocalStorage } from '@kvs/node-localstorage'
|
||||
import { getGlobalCacheDir } from './get-cache-dir'
|
||||
|
||||
export enum StorageName {
|
||||
FrontendState = 'frontend-state',
|
||||
WebPreset = 'web-preset',
|
||||
}
|
||||
|
||||
export async function getStorage(storageName: StorageName) {
|
||||
const cacheFolder = await getGlobalCacheDir('gpt-runner-server')
|
||||
|
||||
const storage = await kvsLocalStorage<Record<string, Record<string, any> | null>>({
|
||||
name: storageName,
|
||||
storeFilePath: cacheFolder,
|
||||
version: 1,
|
||||
})
|
||||
|
||||
return {
|
||||
cacheDir: cacheFolder,
|
||||
storage,
|
||||
}
|
||||
}
|
||||
15
pnpm-lock.yaml
generated
15
pnpm-lock.yaml
generated
@@ -199,6 +199,15 @@ importers:
|
||||
|
||||
packages/gpt-runner-shared:
|
||||
dependencies:
|
||||
'@kvs/node-localstorage':
|
||||
specifier: ^2.1.3
|
||||
version: 2.1.3
|
||||
'@kvs/storage':
|
||||
specifier: ^2.1.3
|
||||
version: 2.1.3
|
||||
cachedir:
|
||||
specifier: ^2.3.0
|
||||
version: 2.3.0
|
||||
debug:
|
||||
specifier: ^4.3.4
|
||||
version: 4.3.4
|
||||
@@ -252,9 +261,6 @@ importers:
|
||||
|
||||
packages/gpt-runner-web:
|
||||
dependencies:
|
||||
'@kvs/node-localstorage':
|
||||
specifier: ^2.1.3
|
||||
version: 2.1.3
|
||||
'@microsoft/fetch-event-source':
|
||||
specifier: ^2.0.1
|
||||
version: 2.0.1
|
||||
@@ -270,9 +276,6 @@ importers:
|
||||
'@vscode/webview-ui-toolkit':
|
||||
specifier: ^1.2.2
|
||||
version: 1.2.2(react@18.2.0)
|
||||
cachedir:
|
||||
specifier: ^2.3.0
|
||||
version: 2.3.0
|
||||
clsx:
|
||||
specifier: ^1.2.1
|
||||
version: 1.2.1
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"moduleResolution": "node",
|
||||
"useDefineForClassFields": true,
|
||||
"esModuleInterop": true,
|
||||
"noImplicitAny": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
|
||||
Reference in New Issue
Block a user