feat(database): split stuff into multiple files
This commit is contained in:
parent
baf9dc54c6
commit
70d72f4419
9 changed files with 307 additions and 76 deletions
|
|
@ -9,6 +9,7 @@
|
|||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@fastify/jwt": "^9.1.0",
|
||||
"better-sqlite3": "^11.10.0",
|
||||
"fastify": "^5.0.0",
|
||||
"fastify-plugin": "^5.0.1",
|
||||
|
|
|
|||
11
src/@shared/src/auth/index.ts
Normal file
11
src/@shared/src/auth/index.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import fastifyJwt from "@fastify/jwt";
|
||||
import { FastifyPluginAsync } from "fastify";
|
||||
import fp from 'fastify-plugin'
|
||||
|
||||
export const jwtPlugin = fp<FastifyPluginAsync>(async (fastify, _opts) => {
|
||||
let env = process.env.JWT_SECRET;
|
||||
if (env === undefined || env === null)
|
||||
throw "JWT_SECRET is not defined"
|
||||
void fastify.register(fastifyJwt, { secret: env });
|
||||
});
|
||||
|
||||
|
|
@ -1,77 +1,14 @@
|
|||
import fp from 'fastify-plugin'
|
||||
import { FastifyInstance } from 'fastify'
|
||||
import sqlite from 'better-sqlite3'
|
||||
|
||||
// @ts-ignore: this file is included using vite, typescript doesn't know how to include this...
|
||||
import initSql from "./init.sql?raw"
|
||||
import { Base } from "./mixin/_base";
|
||||
import { UserDb } from "./mixin/user";
|
||||
import { SessionDb } from "./mixin/session";
|
||||
|
||||
/**
|
||||
* represent a unique user (by its ID.)
|
||||
* Having this means that the user does exist (aka it hasn't been deleted)
|
||||
*/
|
||||
export type UserID = number & { readonly __brand: unique symbol };
|
||||
/**
|
||||
* The full representation of an user
|
||||
*
|
||||
* @property id [UserID]: The id of the user (unique)
|
||||
* @property name [string]: The username of the user (unique)
|
||||
* @property password [?string]: The password hash of the user (if password is defined)
|
||||
*/
|
||||
export type DbUser = {
|
||||
readonly id: UserID,
|
||||
readonly name: string,
|
||||
readonly password: string | null,
|
||||
};
|
||||
|
||||
// Only way to use the database. Everything must be done through this.
|
||||
// Prefer to use prepared statement `using this.db.prepare`
|
||||
export class Database {
|
||||
private db: sqlite.Database;
|
||||
private st: Map<string, sqlite.Statement> = new Map();
|
||||
|
||||
|
||||
/**
|
||||
* Create a new instance of the database, and init it to a known state
|
||||
* the file ./init.sql will be ran onto the database, creating any table that might be missing
|
||||
*/
|
||||
constructor(db_path: string) {
|
||||
this.db = sqlite(db_path, {});
|
||||
this.db.pragma('journal_mode = WAL');
|
||||
this.db.transaction(() => this.db.exec(initSql))();
|
||||
class Database extends UserDb(SessionDb(Base as any)) {
|
||||
constructor(path: string) {
|
||||
super(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* close the database
|
||||
*/
|
||||
destroy(): void {
|
||||
// remove any statement from the cache
|
||||
this.st.clear();
|
||||
// close the database
|
||||
this.db?.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* use this to create queries. This will create statements (kinda expensive) and cache them
|
||||
* since they will be cached, this means that they are only created once,
|
||||
* otherwise they'll be just spat out from the cache
|
||||
* the statements are cached by the {query} argument,
|
||||
* meaning that if you try to make two identiqual statement, but with different {query} they won't be cached
|
||||
*
|
||||
* @example this.prepare('SELECT * FROM users WHERE id = ?')
|
||||
* @example this.prepare('SELECT * FROM users LIMIT 100 OFFSET ?')
|
||||
*/
|
||||
private prepare(query: string): sqlite.Statement {
|
||||
let st = this.st.get(query);
|
||||
if (st !== undefined) return st;
|
||||
|
||||
st = this.db.prepare(query);
|
||||
this.st.set(query, st);
|
||||
return st;
|
||||
}
|
||||
|
||||
public getUser(user: UserID): DbUser {
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
// When using .decorate you have to specify added properties for Typescript
|
||||
|
|
@ -90,6 +27,7 @@ export const useDatabase = fp<DatabaseOption>(async function(
|
|||
_options: DatabaseOption) {
|
||||
f.log.info("Database has been hooked up to fastify ?!");
|
||||
f.log.warn("TODO: actually hook up database to fastify...");
|
||||
f.decorate('db', new Database(_options.path));
|
||||
});
|
||||
|
||||
export default useDatabase;
|
||||
|
|
|
|||
64
src/@shared/src/database/mixin/_base.ts
Normal file
64
src/@shared/src/database/mixin/_base.ts
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
import sqlite from "better-sqlite3";
|
||||
|
||||
// @ts-ignore: this file is included using vite, typescript doesn't know how to include this...
|
||||
import initSql from "../init.sql?raw"
|
||||
|
||||
export type MixinBase<T = {}> = new (...args: any[]) => {
|
||||
constructor(db_path: string): void,
|
||||
destroy(): void,
|
||||
prepare(s: string): sqlite.Statement,
|
||||
db: sqlite.Database,
|
||||
} & T;
|
||||
|
||||
|
||||
// Only way to use the database. Everything must be done through this.
|
||||
// Prefer to use prepared statement `using this.db.prepare`
|
||||
//
|
||||
// this is the base, meaning that no actual query are made from this file.
|
||||
// To create a new query function, go open another file, create a class that inherit from this class
|
||||
// in the `index.ts` file, import the new class, and make the `Database` class inherit it
|
||||
export class Base {
|
||||
private db: sqlite.Database;
|
||||
private st: Map<string, sqlite.Statement> = new Map();
|
||||
|
||||
|
||||
/**
|
||||
* Create a new instance of the database, and init it to a known state
|
||||
* the file ./init.sql will be ran onto the database, creating any table that might be missing
|
||||
*/
|
||||
constructor(db_path: string) {
|
||||
console.log("NEW DB :)");
|
||||
this.db = sqlite(db_path, {});
|
||||
this.db.pragma('journal_mode = WAL');
|
||||
this.db.transaction(() => this.db.exec(initSql))();
|
||||
}
|
||||
|
||||
/**
|
||||
* close the database
|
||||
*/
|
||||
public destroy(): void {
|
||||
// remove any statement from the cache
|
||||
this.st.clear();
|
||||
// close the database
|
||||
this.db?.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* use this to create queries. This will create statements (kinda expensive) and cache them
|
||||
* since they will be cached, this means that they are only created once,
|
||||
* otherwise they'll be just spat out from the cache
|
||||
* the statements are cached by the {query} argument,
|
||||
* meaning that if you try to make two identiqual statement, but with different {query} they won't be cached
|
||||
*
|
||||
* @example this.prepare('SELECT * FROM users WHERE id = ?')
|
||||
* @example this.prepare('SELECT * FROM users LIMIT 100 OFFSET ?')
|
||||
*/
|
||||
protected prepare(query: string): sqlite.Statement {
|
||||
let st = this.st.get(query);
|
||||
if (st !== undefined) return st;
|
||||
|
||||
st = this.db.prepare(query);
|
||||
this.st.set(query, st);
|
||||
return st;
|
||||
}
|
||||
}
|
||||
21
src/@shared/src/database/mixin/_template.ts
Normal file
21
src/@shared/src/database/mixin/_template.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
//import sqlite from "better-sqlite3"
|
||||
import { MixinBase } from "./_base"
|
||||
|
||||
// never use this directly
|
||||
export const TemplateDb = function <TBase extends MixinBase>(Base: TBase) {
|
||||
return class extends Base {
|
||||
constructor(...args: any[]) {
|
||||
if (args.length != 1 && !(args[0] instanceof String))
|
||||
throw "Invalid arguments to mixing class"
|
||||
super(args[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type TemplateId = number & { readonly __brand: unique symbol };
|
||||
|
||||
export type TemplateType = {
|
||||
readonly id: TemplateId,
|
||||
readonly field: string,
|
||||
readonly field2: number,
|
||||
};
|
||||
27
src/@shared/src/database/mixin/session.ts
Normal file
27
src/@shared/src/database/mixin/session.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
//import sqlite from "better-sqlite3"
|
||||
import { MixinBase } from "./_base"
|
||||
|
||||
// never use this directly
|
||||
|
||||
export const SessionDb = function <TBase extends MixinBase>(Base: TBase) {
|
||||
return class extends Base {
|
||||
constructor(...args: any[]) {
|
||||
if (args.length != 1 && !(args[0] instanceof String))
|
||||
throw "Invalid arguments to mixing class"
|
||||
super(args[0]);
|
||||
}
|
||||
|
||||
public getSessionFromId(id: SessionId): Session | null {
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionId = number & { readonly __brand: unique symbol };
|
||||
|
||||
export type Session = {
|
||||
readonly id: SessionId,
|
||||
readonly name: string,
|
||||
readonly salt: string,
|
||||
readonly password: string,
|
||||
};
|
||||
35
src/@shared/src/database/mixin/user.ts
Normal file
35
src/@shared/src/database/mixin/user.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
//import sqlite from "better-sqlite3"
|
||||
import { MixinBase } from "./_base"
|
||||
|
||||
// never use this directly
|
||||
|
||||
|
||||
export const UserDb = function <TBase extends MixinBase>(Base: TBase) {
|
||||
return class extends Base {
|
||||
constructor(...args: any[]) {
|
||||
if (args.length != 1 && !(args[0] instanceof String))
|
||||
throw "Invalid arguments to mixing class"
|
||||
super(args[0]);
|
||||
}
|
||||
|
||||
private userFromRow(row: any): User {
|
||||
throw "TODO: User from Row"
|
||||
}
|
||||
|
||||
public getUser(id: UserId): User | null {
|
||||
return null
|
||||
}
|
||||
public setUser(id: UserId, partialUser: Partial<Omit<User, 'id'>>): User | null {
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type UserId = number & { readonly __brand: unique symbol };
|
||||
|
||||
export type User = {
|
||||
readonly id: UserId,
|
||||
readonly name: string,
|
||||
readonly salt: string,
|
||||
readonly password: string,
|
||||
};
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
import { Database } from "@shared/database";
|
||||
|
||||
export type UserID = Number & { readonly __brand: unique symbol };
|
||||
|
||||
export async function getUser(this: Database, id: UserID) {
|
||||
console.log(this);
|
||||
}
|
||||
141
src/package-lock.json
generated
141
src/package-lock.json
generated
|
|
@ -20,6 +20,7 @@
|
|||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@fastify/jwt": "^9.1.0",
|
||||
"better-sqlite3": "^11.10.0",
|
||||
"fastify": "^5.0.0",
|
||||
"fastify-plugin": "^5.0.1",
|
||||
|
|
@ -640,6 +641,29 @@
|
|||
"integrity": "sha512-kJExsp4JCms7ipzg7SJ3y8DwmePaELHxKYtg+tZow+k0znUTf3cb+npgyqm8+ATZOdmfgfydIebPDWM172wfyA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@fastify/jwt": {
|
||||
"version": "9.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@fastify/jwt/-/jwt-9.1.0.tgz",
|
||||
"integrity": "sha512-CiGHCnS5cPMdb004c70sUWhQTfzrJHAeTywt7nVw6dAiI0z1o4WRvU94xfijhkaId4bIxTCOjFgn4sU+Gvk43w==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/fastify"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/fastify"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@fastify/error": "^4.0.0",
|
||||
"@lukeed/ms": "^2.0.2",
|
||||
"fast-jwt": "^5.0.0",
|
||||
"fastify-plugin": "^5.0.0",
|
||||
"steed": "^1.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@fastify/merge-json-schemas": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@fastify/merge-json-schemas/-/merge-json-schemas-0.2.1.tgz",
|
||||
|
|
@ -1688,6 +1712,18 @@
|
|||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/asn1.js": {
|
||||
"version": "5.4.1",
|
||||
"resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz",
|
||||
"integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bn.js": "^4.0.0",
|
||||
"inherits": "^2.0.1",
|
||||
"minimalistic-assert": "^1.0.0",
|
||||
"safer-buffer": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/atomic-sleep": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz",
|
||||
|
|
@ -1765,6 +1801,12 @@
|
|||
"readable-stream": "^3.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bn.js": {
|
||||
"version": "4.12.2",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz",
|
||||
"integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
||||
|
|
@ -2050,6 +2092,15 @@
|
|||
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ecdsa-sig-formatter": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
|
||||
"integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/emoji-regex": {
|
||||
"version": "9.2.2",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
|
||||
|
|
@ -2164,6 +2215,21 @@
|
|||
"rfdc": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-jwt": {
|
||||
"version": "5.0.6",
|
||||
"resolved": "https://registry.npmjs.org/fast-jwt/-/fast-jwt-5.0.6.tgz",
|
||||
"integrity": "sha512-LPE7OCGUl11q3ZgW681cEU2d0d2JZ37hhJAmetCgNyW8waVaJVZXhyFF6U2so1Iim58Yc7pfxJe2P7MNetQH2g==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@lukeed/ms": "^2.0.2",
|
||||
"asn1.js": "^5.4.1",
|
||||
"ecdsa-sig-formatter": "^1.0.11",
|
||||
"mnemonist": "^0.40.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-querystring": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/fast-querystring/-/fast-querystring-1.1.2.tgz",
|
||||
|
|
@ -2204,6 +2270,18 @@
|
|||
],
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/fastfall": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/fastfall/-/fastfall-1.5.1.tgz",
|
||||
"integrity": "sha512-KH6p+Z8AKPXnmA7+Iz2Lh8ARCMr+8WNPVludm1LGkZoD2MjY6LVnRMtTKhkdzI+jr0RzQWXKzKyBJm1zoHEL4Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"reusify": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fastify": {
|
||||
"version": "5.4.0",
|
||||
"resolved": "https://registry.npmjs.org/fastify/-/fastify-5.4.0.tgz",
|
||||
|
|
@ -2286,6 +2364,16 @@
|
|||
],
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/fastparallel": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/fastparallel/-/fastparallel-2.4.1.tgz",
|
||||
"integrity": "sha512-qUmhxPgNHmvRjZKBFUNI0oZuuH9OlSIOXmJ98lhKPxMZZ7zS/Fi0wRHOihDSz0R1YiIOjxzOY4bq65YTcdBi2Q==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"reusify": "^1.0.4",
|
||||
"xtend": "^4.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/fastq": {
|
||||
"version": "1.19.1",
|
||||
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
|
||||
|
|
@ -2295,6 +2383,16 @@
|
|||
"reusify": "^1.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/fastseries": {
|
||||
"version": "1.7.2",
|
||||
"resolved": "https://registry.npmjs.org/fastseries/-/fastseries-1.7.2.tgz",
|
||||
"integrity": "sha512-dTPFrPGS8SNSzAt7u/CbMKCJ3s01N04s4JFbORHcmyvVfVKmbhMD1VtRbh5enGHxkaQDqWyLefiKOGGmohGDDQ==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"reusify": "^1.0.0",
|
||||
"xtend": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fdir": {
|
||||
"version": "6.4.6",
|
||||
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz",
|
||||
|
|
@ -2743,6 +2841,12 @@
|
|||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/minimalistic-assert": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
|
||||
"integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "9.0.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
|
||||
|
|
@ -2783,6 +2887,15 @@
|
|||
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/mnemonist": {
|
||||
"version": "0.40.3",
|
||||
"resolved": "https://registry.npmjs.org/mnemonist/-/mnemonist-0.40.3.tgz",
|
||||
"integrity": "sha512-Vjyr90sJ23CKKH/qPAgUKicw/v6pRoamxIEDFOF8uSgFME7DqPRpHgRTejWVjkdGg5dXj0/NyxZHZ9bcjH+2uQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"obliterator": "^2.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
|
|
@ -2827,6 +2940,12 @@
|
|||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/obliterator": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/obliterator/-/obliterator-2.0.5.tgz",
|
||||
"integrity": "sha512-42CPE9AhahZRsMNslczq0ctAEtqk8Eka26QofnqC346BZdHDySk3LWka23LI7ULIw11NmltpiLagIq8gBozxTw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/on-exit-leak-free": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz",
|
||||
|
|
@ -3588,6 +3707,19 @@
|
|||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/steed": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/steed/-/steed-1.1.3.tgz",
|
||||
"integrity": "sha512-EUkci0FAUiE4IvGTSKcDJIQ/eRUP2JJb56+fvZ4sdnguLTqIdKjSxUe138poW8mkvKWXW2sFPrgTsxqoISnmoA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fastfall": "^1.5.0",
|
||||
"fastparallel": "^2.2.0",
|
||||
"fastq": "^1.3.0",
|
||||
"fastseries": "^1.7.0",
|
||||
"reusify": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/string_decoder": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||
|
|
@ -4104,6 +4236,15 @@
|
|||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/xtend": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/yargs-parser": {
|
||||
"version": "21.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue