From 8e63a16fd4ab481a3fd11f197538b6ec761d6a8a Mon Sep 17 00:00:00 2001 From: Piero Maddaleni Date: Thu, 23 Sep 2021 02:36:27 +0000 Subject: [PATCH 1/2] lol --- .semaphore/semaphore.yml | 21 --- LICENSE | 25 +++ README.md | 37 +++-- index.js | 336 +++++++++++++++++++++++---------------- index.test.js | 81 ---------- package-lock.json | 18 ++- package.json | 15 +- tests/better.test.js | 88 ++++++++++ tests/regular.test.js | 88 ++++++++++ 9 files changed, 447 insertions(+), 262 deletions(-) delete mode 100644 .semaphore/semaphore.yml delete mode 100644 index.test.js create mode 100644 tests/better.test.js create mode 100644 tests/regular.test.js diff --git a/.semaphore/semaphore.yml b/.semaphore/semaphore.yml deleted file mode 100644 index 70b8507..0000000 --- a/.semaphore/semaphore.yml +++ /dev/null @@ -1,21 +0,0 @@ -version: v1.0 -name: database-node -agent: - machine: - type: e1-standard-2 - os_image: ubuntu1804 -blocks: - - name: Test - task: - jobs: - - name: Test - commands: - - checkout - - sem-version node 12 - - cache restore - - npm install - - cache store - - npm run build --if-present - - npm test - secrets: - - name: replit-database diff --git a/LICENSE b/LICENSE index 441d91d..c15a89b 100644 --- a/LICENSE +++ b/LICENSE @@ -1,5 +1,30 @@ MIT License +Copyright (c) 2021 Piero Maddaleni + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +--- +The following license is carried over from the original source code: + +MIT License + Copyright (c) 2020 Repl.it Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/README.md b/README.md index b0e80d6..4cf2530 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,35 @@ -[![npm version](https://badge.fury.io/js/%40replit%2Fdatabase.svg)](https://badge.fury.io/js/%40replit%2Fdatabase) +[![npm version](https://badge.fury.io/js/better-replit-db.svg)](https://badge.fury.io/js/better-replit-db) -[![Run on Repl.it](https://repl.it/badge/github/replit/database-node)](https://repl.it/github/replit/database-node) +[![Run on Repl.it](https://replit.com/badge/github/pieromqwerty/better-replit-db)](https://replit.com/github/pieromqwerty/better-replit-db) -# Repl.it Database client -Repl.it Database client is a simple way to use Repl.it Database in your Node.js repls. It uses `await/async`. +# Better Replit DB +Better Replit DB is a fork of the official Replit Database client, with the added benefit of the contents being cached in memory. This allows for much faster load read and write times than the regular client. It also uses `await/async`, just like the official client. + +*Better Replit DB is a drop-in replacement as well, simply change `require('@replit/database')` to `require('better-replit-db')` in your JS file.* + +***IT IS LITERALLY DOZENS OF TIMES FASTER THAN THE OFFICIAL LIB*** + +## Speed Comparison +### 20x Ops +|Test|better-replit-database|@replit/database|Times Faser +-|-|-|- +Creating a Client|**16 ms**|120 ms|**7.5x** +Setting a Value|**80 ms**|2858 ms|**35.7x** +Listing Keys|**107 ms**|1583 ms|**14.8x** +Getting a Value|**25 ms**|1349 ms|**54.0x** +Deleting a Value|**199 ms**|4046 ms|**20.3x** +Listing Keys|**36 ms**|1422 ms|**39.5x** +Ensuring Values are Escaped|**35 ms**|1350ms|**38.6x** + +### 100x Ops +> Coming Soon - Regular DB Host Gets Ratelimited ## Get started ```js -const Client = require("@replit/database"); -const client = new Client(); -await Client.set("key", "value"); -let key = await Client.get("key"); +const database = require("better-replit-db"); +const db = new database(); +await db.set("key", "value"); +let key = await db.get("key"); console.log(key); ``` @@ -43,7 +62,7 @@ Lists all of the keys, or all of the keys starting with `prefix` if specifed. **Dynamic Functions** -These functions have been added by me. +These functions have been added by the original author. > `empty()` diff --git a/index.js b/index.js index 81a2b69..b8fece8 100644 --- a/index.js +++ b/index.js @@ -1,147 +1,201 @@ const fetch = require("node-fetch"); class Client { - /** - * Initiates Class. - * @param {String} key Custom database URL - */ - constructor(key) { - if (key) this.key = key; - else this.key = process.env.REPLIT_DB_URL; - } - - // Native Functions - /** - * Gets a key - * @param {String} key Key - * @param {boolean} [options.raw=false] Makes it so that we return the raw string value. Default is false. - */ - async get(key, options) { - return await fetch(this.key + "/" + key) - .then((e) => e.text()) - .then((strValue) => { - if (options && options.raw) { - return strValue; - } - - if (!strValue) { - return null; - } - - let value = strValue; - try { - // Try to parse as JSON, if it fails, we throw - value = JSON.parse(strValue); - } catch (_err) { - throw new SyntaxError( - `Failed to parse value of ${key}, try passing a raw option to get the raw value` - ); - } - - if (value === null || value === undefined) { - return null; - } - - return value; - }); - } - - /** - * Sets a key - * @param {String} key Key - * @param {any} value Value - */ - async set(key, value) { - const strValue = JSON.stringify(value); - - await fetch(this.key, { - method: "POST", - headers: { "Content-Type": "application/x-www-form-urlencoded" }, - body: encodeURIComponent(key) + "=" + encodeURIComponent(strValue), - }); - return this; - } - - /** - * Deletes a key - * @param {String} key Key - */ - async delete(key) { - await fetch(this.key + "/" + key, { method: "DELETE" }); - return this; - } - - /** - * List key starting with a prefix or list all. - * @param {String} prefix Filter keys starting with prefix. - */ - async list(prefix = "") { - return await fetch( - this.key + `?encode=true&prefix=${encodeURIComponent(prefix)}` - ) - .then((r) => r.text()) - .then((t) => { - if (t.length === 0) { - return []; - } - return t.split("\n").map(decodeURIComponent); - }); - } - - // Dynamic Functions - /** - * Clears the database. - */ - async empty() { - const promises = []; - for (const key of await this.list()) { - promises.push(this.delete(key)); - } - - await Promise.all(promises); - - return this; - } - - /** - * Get all key/value pairs and return as an object - */ - async getAll() { - let output = {}; - for (const key of await this.list()) { - let value = await this.get(key); - output[key] = value; - } - return output; - } - - /** - * Sets the entire database through an object. - * @param {Object} obj The object. - */ - async setAll(obj) { - for (const key in obj) { - let val = obj[key]; - await this.set(key, val); - } - return this; - } - - /** - * Delete multiple entries by keys - * @param {Array} args Keys - */ - async deleteMultiple(...args) { - const promises = []; - - for (const arg of args) { - promises.push(this.delete(arg)); - } - - await Promise.all(promises); - - return this; - } + /** + * Initiates Class. + * @param {String} key Custom database URL + */ + constructor(key) { + if (key) this.key = key; + else this.key = process.env.REPLIT_DB_URL; + + this.dbCache = {}; + + this.getAllNoCache().then((all) => { + this.dbCache = all; + }); + } + + // Native Functions + /** + * Gets a key + * @param {String} key Key + * @param {boolean} [options.raw=false] Makes it so that we return the raw string value. Default is false. + */ + async get(key, options) { + let value = this.dbCache[key] + if (options && options.raw) { + return JSON.stringify(value); + } + + if (!value) { + return null; + } + + if (value === null || value === undefined) { + return null; + } + + return value; + } + + /** + * Gets a key without the cache + * @param {String} key Key + * @param {boolean} [options.raw=false] Makes it so that we return the raw string value. Default is false. + */ + async getNoCache(key, options) { + return await fetch(this.key + "/" + key) + .then((e) => e.text()) + .then((strValue) => { + if (options && options.raw) { + return strValue; + } + + if (!strValue) { + return null; + } + + let value = strValue; + try { + // Try to parse as JSON, if it fails, we throw + value = JSON.parse(strValue); + } catch (_err) { + throw new SyntaxError( + `Failed to parse value of ${key}, try passing a raw option to get the raw value` + ); + } + + if (value === null || value === undefined) { + return null; + } + + return value; + }); + } + + /** + * Sets a key + * @param {String} key Key + * @param {any} value Value + */ + async set(key, value) { + const strValue = JSON.stringify(value); + + this.dbCache[key] = value; + + fetch(this.key, { + method: "POST", + headers: { "Content-Type": "application/x-www-form-urlencoded" }, + body: encodeURIComponent(key) + "=" + encodeURIComponent(strValue), + }); + + return this; + } + + /** + * Deletes a key + * @param {String} key Key + */ + async delete(key) { + await delete this.dbCache[key]; + fetch(this.key + "/" + key, { method: "DELETE" }); + return this; + } + + /** + * List key starting with a prefix or list all. + * @param {String} prefix Filter keys starting with prefix. + */ + async list(prefix = "") { + let list = Object.keys(this.dbCache); + + return list.filter(key => key.startsWith(prefix)); + } + + /** + * List key starting with a prefix or list all without the cache. + * @param {String} prefix Filter keys starting with prefix. + */ + async listNoCache(prefix = "") { + return await fetch( + this.key + `?encode=true&prefix=${encodeURIComponent(prefix)}` + ) + .then((r) => r.text()) + .then((t) => { + if (t.length === 0) { + return []; + } + return t.split("\n").map(decodeURIComponent); + }); + } + + // Dynamic Functions + /** + * Clears the database. + */ + async empty() { + const promises = []; + for (const key of await this.list()) { + promises.push(this.delete(key)); + } + + await Promise.all(promises); + + return this; + } + + /** + * Get all key/value pairs and return as an object + */ + async getAll() { + let output = {}; + for (const key of await this.list()) { + let value = await this.get(key); + output[key] = value; + } + return output; + } + + /** + * Get all key/value pairs and return as an object without the cache + */ + async getAllNoCache() { + let output = {}; + for (const key of await this.listNoCache()) { + let value = await this.getNoCache(key); + output[key] = value; + } + return output; + } + + /** + * Sets the entire database through an object. + * @param {Object} obj The object. + */ + async setAll(obj) { + for (const key in obj) { + let val = obj[key]; + await this.set(key, val); + } + return this; + } + + /** + * Delete multiple entries by keys + * @param {Array} args Keys + */ + async deleteMultiple(...args) { + const promises = []; + + for (const arg of args) { + promises.push(this.delete(arg)); + } + + await Promise.all(promises); + + return this; + } } module.exports = Client; diff --git a/index.test.js b/index.test.js deleted file mode 100644 index e700ffe..0000000 --- a/index.test.js +++ /dev/null @@ -1,81 +0,0 @@ -const fetch = require("node-fetch"); -const Client = require("./index"); - -let client; - -beforeAll(async () => { - const pass = process.env.PASSWORD; - const resp = await fetch("https://database-test-jwt.kochman.repl.co", { - headers: { - Authorization: "Basic " + btoa("test:" + pass), - }, - }); - const url = await resp.text(); - client = new Client(url); - await client.empty(); -}); - -afterEach(async () => { - await client.empty(); -}); - -test("create a client with a key", async () => { - expect(client).toBeTruthy(); - expect(typeof client.key).toBe("string"); -}); - -test("sets a value", async () => { - expect(await client.set("key", "value")).toEqual(client); - expect(await client.setAll({ key: "value", second: "secondThing" })).toEqual( - client - ); -}); - -test("list keys", async () => { - await client.setAll({ - key: "value", - second: "secondThing", - }); - - expect(await client.list()).toEqual(["key", "second"]); -}); - -test("gets a value", async () => { - await client.setAll({ - key: "value", - }); - - expect(await client.getAll()).toEqual({ key: "value" }); -}); - -test("delete a value", async () => { - await client.setAll({ - key: "value", - deleteThis: "please", - somethingElse: "in delete multiple", - andAnother: "again same thing", - }); - - expect(await client.delete("deleteThis")).toEqual(client); - expect(await client.deleteMultiple("somethingElse", "andAnother")).toEqual( - client - ); - expect(await client.list()).toEqual(["key"]); - expect(await client.empty()).toEqual(client); - expect(await client.list()).toEqual([]); -}); - -test("list keys with newline", async () => { - await client.setAll({ - "key\nwit": "first", - keywidout: "second", - }); - - expect(await client.list()).toEqual(["keywidout", "key\nwit"]); -}); - -test("ensure that we escape values when setting", async () => { - expect(await client.set("a", "1;b=2")).toEqual(client); - expect(await client.list()).toEqual(["a"]) - expect(await client.get("a")).toEqual("1;b=2") -}); diff --git a/package-lock.json b/package-lock.json index 0e83ac7..0f0c352 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { - "name": "@replit/database", - "version": "2.0.1", + "name": "better-replit-db", + "version": "1.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -610,6 +610,14 @@ "chalk": "^4.0.0" } }, + "@replit/database": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@replit/database/-/database-2.0.1.tgz", + "integrity": "sha512-SeW/qkk4FEteZ75B2S/MIQJ/bYdOO0f2+tdsikwO5k9TyYI3q7YANTpUJ7W79xarSd5gJ9NFk5Y55Pov2z1dwA==", + "requires": { + "node-fetch": "^2.6.0" + } + }, "@sinonjs/commons": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.0.tgz", @@ -1795,9 +1803,9 @@ "dev": true }, "fsevents": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", - "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", "dev": true, "optional": true }, diff --git a/package.json b/package.json index dcda535..f60941b 100644 --- a/package.json +++ b/package.json @@ -1,23 +1,28 @@ { - "name": "@replit/database", - "version": "2.0.1", - "description": "Client for Repl.it Database", + "name": "better-replit-db", + "version": "1.0.0", + "description": "Client for Replit Database with built in memory caching.", "main": "index.js", "scripts": { - "test": "jest" + "test": "cd tests && jest --verbose --silent=false" }, "contributors": [ { "name": "Junhao Zhang", "url": "https://github.com/mrlapizgithub/repl.it-db" + }, + { + "name": "Replit", + "url": "https://github.com/replit/database-node" } ], "license": "MIT", "repository": { "type": "git", - "url": "https://github.com/replit/database-node.git" + "url": "https://github.com/pieromqwerty/better-replit-db.git" }, "dependencies": { + "@replit/database": "^2.0.1", "node-fetch": "^2.6.0" }, "devDependencies": { diff --git a/tests/better.test.js b/tests/better.test.js new file mode 100644 index 0000000..b3f16cb --- /dev/null +++ b/tests/better.test.js @@ -0,0 +1,88 @@ +const fetch = require("node-fetch"); +const Client = require("../index"); + +let client; + +beforeAll(async () => { + client = new Client(); + await client.empty(); +}); + +afterEach(async () => { + await client.empty(); +}); + +test("create a client with a key 20 times", async () => { + for (let i = 0; i < 20; i++) { + expect(client).toBeTruthy(); + expect(typeof client.key).toBe("string"); + } +}); + +test("sets a value 20 times", async () => { + for (let i = 0; i < 20; i++) { + expect(await client.set("key", "value")).toEqual(client); + expect(await client.setAll({ key: "value", second: "secondThing" })).toEqual( + client + ); + } +}); + +test("list keys 20 times", async () => { + for (let i = 0; i < 20; i++) { + await client.setAll({ + key: "value", + second: "secondThing", + }); + + expect(await client.list()).toEqual(["key", "second"]); + } +}); + +test("gets a value 20 times", async () => { + for (let i = 0; i < 20; i++) { + await client.setAll({ + key: "value", + }); + + expect(await client.getAll()).toEqual({ key: "value" }); + } +}); + +test("delete a value 20 times", async () => { + for (let i = 0; i < 20; i++) { + await client.setAll({ + key: "value", + deleteThis: "please", + somethingElse: "in delete multiple", + andAnother: "again same thing", + }); + + expect(await client.delete("deleteThis")).toEqual(client); + expect(await client.deleteMultiple("somethingElse", "andAnother")).toEqual( + client + ); + expect(await client.list()).toEqual(["key"]); + expect(await client.empty()).toEqual(client); + expect(await client.list()).toEqual([]); + } +}); + +test("list keys with newline", async () => { + for (let i = 0; i < 20; i++) { + await client.setAll({ + "key\nwit": "first", + keywidout: "second", + }); + + expect(await client.list()).toEqual(["key\nwit", "keywidout"]); + } +}); + +test("ensure that we escape values when setting", async () => { + for (let i = 0; i < 20; i++) { + expect(await client.set("a", "1;b=2")).toEqual(client); + expect(await client.list()).toEqual(["a"]) + expect(await client.get("a")).toEqual("1;b=2") + } +}); \ No newline at end of file diff --git a/tests/regular.test.js b/tests/regular.test.js new file mode 100644 index 0000000..10f3005 --- /dev/null +++ b/tests/regular.test.js @@ -0,0 +1,88 @@ +const fetch = require("node-fetch"); +const Client = require("@replit/database"); + +let client; + +beforeAll(async () => { + client = new Client(); + await client.empty(); +}); + +afterEach(async () => { + await client.empty(); +}); + +test("create a client with a key 20 times", async () => { + for (let i = 0; i < 20; i++) { + expect(client).toBeTruthy(); + expect(typeof client.key).toBe("string"); + } +}); + +test("sets a value 20 times", async () => { + for (let i = 0; i < 20; i++) { + expect(await client.set("key", "value")).toEqual(client); + expect(await client.setAll({ key: "value", second: "secondThing" })).toEqual( + client + ); + } +}); + +test("list keys 20 times", async () => { + for (let i = 0; i < 20; i++) { + await client.setAll({ + key: "value", + second: "secondThing", + }); + + expect(await client.list()).toEqual(["key", "second"]); + } +}); + +test("gets a value 20 times", async () => { + for (let i = 0; i < 20; i++) { + await client.setAll({ + key: "value", + }); + + expect(await client.getAll()).toEqual({ key: "value" }); + } +}); + +test("delete a value 20 times", async () => { + for (let i = 0; i < 20; i++) { + await client.setAll({ + key: "value", + deleteThis: "please", + somethingElse: "in delete multiple", + andAnother: "again same thing", + }); + + expect(await client.delete("deleteThis")).toEqual(client); + expect(await client.deleteMultiple("somethingElse", "andAnother")).toEqual( + client + ); + expect(await client.list()).toEqual(["key"]); + expect(await client.empty()).toEqual(client); + expect(await client.list()).toEqual([]); + } +}); + +test("list keys with newline 20 times", async () => { + for (let i = 0; i < 20; i++) { + await client.setAll({ + "key\nwit": "first", + keywidout: "second", + }); + + expect(await client.list()).toEqual(["keywidout", "key\nwit"]); + } +}); + +test("ensure that we escape values when setting 20 times", async () => { + for (let i = 0; i < 20; i++) { + expect(await client.set("a", "1;b=2")).toEqual(client); + expect(await client.list()).toEqual(["a"]) + expect(await client.get("a")).toEqual("1;b=2") + } +}); From a3e08d14dd37081e5ed391038a22b19c733ef004 Mon Sep 17 00:00:00 2001 From: Piero Maddaleni Date: Thu, 23 Sep 2021 02:48:45 +0000 Subject: [PATCH 2/2] lol pt 2 --- .gitignore | 3 ++- package-lock.json | 8 -------- package.json | 1 - tests/package-lock.json | 43 +++++++++++++++++++++++++++++++++++++++++ tests/package.json | 14 ++++++++++++++ 5 files changed, 59 insertions(+), 10 deletions(-) create mode 100644 tests/package-lock.json create mode 100644 tests/package.json diff --git a/.gitignore b/.gitignore index b512c09..1e4471c 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -node_modules \ No newline at end of file +node_modules +tests/node_modules \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 0f0c352..7dc70e9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -610,14 +610,6 @@ "chalk": "^4.0.0" } }, - "@replit/database": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@replit/database/-/database-2.0.1.tgz", - "integrity": "sha512-SeW/qkk4FEteZ75B2S/MIQJ/bYdOO0f2+tdsikwO5k9TyYI3q7YANTpUJ7W79xarSd5gJ9NFk5Y55Pov2z1dwA==", - "requires": { - "node-fetch": "^2.6.0" - } - }, "@sinonjs/commons": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.0.tgz", diff --git a/package.json b/package.json index f60941b..4d7d888 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,6 @@ "url": "https://github.com/pieromqwerty/better-replit-db.git" }, "dependencies": { - "@replit/database": "^2.0.1", "node-fetch": "^2.6.0" }, "devDependencies": { diff --git a/tests/package-lock.json b/tests/package-lock.json new file mode 100644 index 0000000..baa75c9 --- /dev/null +++ b/tests/package-lock.json @@ -0,0 +1,43 @@ +{ + "name": "tests", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@replit/database": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@replit/database/-/database-2.0.1.tgz", + "integrity": "sha512-SeW/qkk4FEteZ75B2S/MIQJ/bYdOO0f2+tdsikwO5k9TyYI3q7YANTpUJ7W79xarSd5gJ9NFk5Y55Pov2z1dwA==", + "requires": { + "node-fetch": "^2.6.0" + } + }, + "node-fetch": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.5.tgz", + "integrity": "sha512-mmlIVHJEu5rnIxgEgez6b9GgWXbkZj5YZ7fx+2r94a2E+Uirsp6HsPTPlomfdHtpt/B0cdKviwkoaM6pyvUOpQ==", + "requires": { + "whatwg-url": "^5.0.0" + } + }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + } + } +} diff --git a/tests/package.json b/tests/package.json new file mode 100644 index 0000000..f2d0235 --- /dev/null +++ b/tests/package.json @@ -0,0 +1,14 @@ +{ + "name": "tests", + "version": "1.0.0", + "description": "", + "main": "better.test.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "dependencies": { + "@replit/database": "^2.0.1" + } +}