├── LICENSE.md
├── README.md
├── disco-db-demo
├── .eslintrc.json
├── .gitignore
├── README.md
├── __mocks__
│ ├── fileMock.js
│ └── styleMock.js
├── __tests__
│ ├── __snapshots__
│ │ └── snapshot.js.snap
│ ├── e2e
│ │ └── example.spec.ts
│ ├── index.test.jsx
│ ├── server
│ │ └── authController.js
│ ├── snapshot.js
│ ├── supertest.js
│ └── sw-test.js
├── babel.config.js
├── components
│ ├── Navbar.js
│ ├── Notes.js
│ ├── Sidebar.js
│ ├── Weather.js
│ ├── WeatherDisplay.js
│ ├── layout.js
│ └── weatherAPI.js
├── jest-setup.js
├── jest-teardown.js
├── jest.config.js
├── next.config.js
├── package-lock.json
├── package.json
├── pages
│ ├── _app.js
│ ├── about.js
│ ├── api
│ │ └── hello.js
│ ├── auth
│ │ ├── login.js
│ │ └── signup.js
│ ├── index.js
│ └── user
│ │ ├── index.js
│ │ └── notes.js
├── public
│ ├── backgroundSync.js
│ ├── configMap.js
│ ├── discoGlobals.js
│ ├── discoSync.js
│ ├── discodb.config.js
│ ├── favicon.ico
│ ├── idbOperations.js
│ ├── swCacheSite-indexedDB.js
│ └── vercel.svg
├── server
│ ├── controllers
│ │ ├── authController.js
│ │ ├── userController.js
│ │ └── weatherController.js
│ ├── models
│ │ ├── dbConnection.js
│ │ ├── model.js
│ │ └── responseModel.js
│ ├── routers
│ │ ├── apiRouter.js
│ │ ├── authRouter.js
│ │ └── userRouter.js
│ └── server.js
└── styles
│ ├── Home.module.css
│ └── globals.css
├── discodb
├── LICENSE.md
├── README.md
├── discoFunctions
│ ├── backgroundSync.js
│ ├── discoGlobals.js
│ ├── discoSync.js
│ └── idbOperations.js
├── index.js
├── package-lock.json
└── package.json
├── package-lock.json
└── package.json
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 OSLabs Beta
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | # DiscoDB:
6 |
7 | ## Table of Contents
8 |
9 | - [Features](#features)
10 |
11 | - [Installation](#installation)
12 |
13 | - [How It works](#how-it-works)
14 |
15 | - [Demo Apps](#demo-app)
16 |
17 | - [Contributing](#contributing)
18 |
19 | - [Authors](#authors)
20 |
21 | - [License](#license)
22 |
23 | ## Features
24 |
25 | - A minimalist IndexedDB (IDB) wrapper and syncing solution for when your application disco(nnects) from the network.
26 | - Lightweight with zero dependencies.
27 | - Functionalities can be implemented via Service Workers with minimal modification to client side code
28 | - Supports syncing IDB with NoSQL databases (MongoDB) with a unique keypath
29 | - Promise based wrapper for IDB to perform local CRUD operations while offline to provide seamless UX
30 | - Sync to main database via action queue with automatic network availability detection.
31 |
32 |
33 |
34 | Offline Capabilities
35 |
36 | 
37 |
38 |
39 |
40 | Dynamic Offline Data
41 |
42 | 
43 |
44 |
45 |
46 | Custom Action Queue
47 |
48 | 
49 |
50 |
51 |
52 | ## Installation
53 |
54 | ```bash
55 | npm install disco-db
56 | ```
57 | ### Import the Library into the Service Worker
58 | Assuming a bundler such as Webpack, Rollup, etc is used:
59 | ```js
60 | import { discoConnect, discoSyncToServer, discoSyncOffline, discoSyncOnline, onlineUrlArr, offlineUrlArr, dbGlobals, idbPromise } from 'discodb';
61 | ```
62 | When registering the service worker, pass in an option object with property ``type:'module'``
63 | ```js
64 | if("serviceWorker" in navigator) {
65 | window.addEventListener("load", function () {
66 | navigator.serviceWorker.register("sw.js", {type: 'module'})
67 | ```
68 | ## How It works
69 |
70 | ### Setting up the Config File
71 | Our library requires some minimal configuration. In the root directory of the project, create a file labeled ``discodb.config.js`` and update the values of the corresponding key property.
72 | ```js
73 | // discodb.config.js
74 |
75 | const dbGlobals =
76 | {
77 | version: "IDB version",
78 | databaseName: "IDB database name",
79 | storeName: "IDB Object Store name",
80 | syncQueue: "IDB Object Store Queue name",
81 | keypath: "Primary key of main database table",
82 | // Add all routes to be intercepted
83 | onlineRoutes: [
84 | {
85 | url: "",
86 | }
87 | ],
88 | offlineRoutes: [
89 | {
90 | url: " ",
91 | },
92 | {
93 | url: " ",
94 | }
95 | ]
96 | }
97 |
98 | export { dbGlobals };
99 |
100 | ```
101 |
102 | ### DiscoSyncOffline & DiscoSyncOnline
103 |
104 | ``discoSyncOffline(method, url, clonedRequest)``
105 |
106 | 1. ``discoSyncOffline`` is a request reducer that intercepts fetch requests and implements CRUD operations on the passed in endpoints.
107 |
108 | 2. ``discoSyncOffline`` takes in three parameters, the method and url of the ``event.request`` as well as a clone of the ``event.request``, utilizing the ``.clone()`` method.
109 |
110 | 3. Under the hood, ``discoSyncOffline`` will check the url, and perform a **GET**, **DELETE**, or **PATCH** operation with indexedDB and return a new **Response** object back to the client.
111 |
112 |
113 | ``discoSyncOnline(method, url, clonedResponse)``
114 |
115 | 1. ``discoSyncOnline`` establishes a connection to indexedDB and populates the object store with your noSQL data.
116 |
117 | 2. ``discoSyncOnline`` takes in three paramaters, the method and url of the ``event.request`` as well as a clone of the ``response`` that was sent back from the server.
118 |
119 | 3. Under the hood, ``discoSyncOnline`` will check the url passed in and first clear indexedDB of any stale data, and repopulate with the response body that it received back from the server.
120 |
121 |
122 | ### Initializing IndexedDB Database
123 | To initialize an idb with attributes passed into ```discodb.config.js```, invoke ``` discoConnect()``` when installing the service worker
124 | ```js
125 | self.addEventListener('install', event => {
126 | discoConnect();
127 | });
128 | ```
129 |
130 | ### Intercepting Event Requests
131 | ```discoSyncOffline()``` and ```discoSyncOnline()``` require the following to be true:
132 | 1. Network falling back to cache Service Worker strategy: [Documentation](https://developers.google.com/web/ilt/pwa/caching-files-with-service-worker)
133 | 2. Response and Request objects passed into ```discoSyncOffline()``` and ```discoSyncOnline()``` respectively will need to be a cloned version.
134 |
135 | ```onlineUrlArr``` and ```offlineUrlArr``` will include the ```onlineRoutes``` and ```offlineRoutes``` from ```discodb.config.js ``` respectively in an Array.
136 | ```js
137 | self.addEventListener('fetch', event => {
138 | // clone the request
139 | const reqClone = event.request.clone();
140 | event.respondWith(
141 | // network first approach
142 | fetch(event.request)
143 | .then((response) => {
144 | // clone the response
145 | const resCloneCache = response.clone();
146 | const resCloneDB = response.clone()
147 | // open caches and store resCloneCache
148 | // ...
149 | // intercept routes included in onlineUrlArr
150 | if (onlineUrlArr.includes(url)){
151 | discoSyncOnline(method, url, resCloneDB);
152 | }
153 | return response;
154 | })
155 | // Fallback to Cache
156 | .catch((err) => {
157 | //invoke offline reducer to perform RUD functions to indexedDB
158 | if (offlineUrlArr.includes(url)){
159 | return discoSyncOffline(method, url, reqClone);
160 | }
161 | // return cache
162 | // ...
163 | })
164 | )
165 | });
166 | ```
167 |
168 | ### Implementing the Action Queue Synchonization
169 | You can also use the synchronization queue separately from our reducers! Make sure to ``discoConnect()`` to the IDB database and have your configuration file ready.
170 | 1. Set up an event handler to listen for "***sync***" and a conditional to catch our tag, "***discoSync***".
171 | 1. Request for a synchronization event by registering through the service worker through ``discoRegisterSync()``. By default, the sync tag assigned to the request is '***discoSync***'.
172 | 1. Once a sync request has been registered, we now can add an object containing the HTTP request to the Object Store Queue by invoking ``discoAddToQueue(object)``.
173 | * Object format must contain these properties
174 | ```js
175 | {
176 | url: "Route URL",
177 | method: "HTTP Method",
178 | body: "data object from HTTP request"
179 | }
180 | ```
181 | 1. Now that the Object Store Queue has been populated with HTTP requests, we can send them to the main server! Within the event handler for sync, invoke ``discoSyncToServer()``.
182 | ```js
183 | self.addEventListener('sync', (event) => {
184 | if(event.tag === 'discoSync'){
185 | discoSyncToServer();
186 | };
187 | };
188 | ```
189 |
190 | ## Demo App
191 | Our demostration application utilizing this library is a simple note taking app. The demo app relies on service workers intercepting all HTTP requests for both online and offline requests.
192 |
193 | Fork and clone our repository onto your local repository and follow the README in the app.
194 |
195 | ## Contributing
196 |
197 | We'd love for you to test this library out and submit any issues you encounter. Also feel free to fork to your own repo and submit pull requests!
198 |
199 | ## Authors
200 |
201 |
202 | [Eric Gomez](https://github.com/ergomez0201) | [LinkedIn](https://www.linkedin.com/in/eric-gomez/)
203 |
204 |
205 | [Eric McCorkle](https://github.com/ericmccorkle) | [LinkedIn](https://www.linkedin.com/in/eric-mccorkle/)
206 |
207 |
208 | [Jackson Tong](https://github.com/jacksonktong) | [LinkedIn](www.linkedin.com/in/jacksonktong)
209 |
210 |
211 | [Young Min Lee](https://github.com/youngmineeh) | [LinkedIn](www.linkedin.com/in/youngminlee-)
212 |
213 |
214 |
215 | ## License
216 |
217 | DiscoDB is [MIT licensed](https://github.com/oslabs-beta/discodb/blob/main/LICENSE.md).
218 |
--------------------------------------------------------------------------------
/disco-db-demo/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | // "extends": "next/core-web-vitals"
3 | "extends": ["next/babel","next/core-web-vitals"]
4 | }
5 |
--------------------------------------------------------------------------------
/disco-db-demo/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # local env files
28 | .env.local
29 | .env.development.local
30 | .env.test.local
31 | .env.production.local
32 | .env
33 |
34 | # vercel
35 | .vercel
36 |
37 | ./DiscoDB/disco-db-demo/server/models/testQuery.js
--------------------------------------------------------------------------------
/disco-db-demo/README.md:
--------------------------------------------------------------------------------
1 | Things to Note in our Readme
2 | - Offline sync queue currently maxes out at 50 requests (validate this)
3 |
4 |
5 | //Config file//
6 | version: config required
7 | databaseName: default to 'database', but user should specify. Default values may be more difficult to update functions with. Should require it
8 | storeName: default to 'objectStore', but user should specify. Default values may be more difficult to update functions with. Should require it
9 | syncQueue: default to 'Queue', but user should specify. Default values may be more difficult to update functions with. Should require it
10 | keyPath: required, user needs to specify
11 |
12 |
13 |
14 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
15 |
16 | ## Getting Started
17 |
18 | First, run the development server:
19 |
20 | ```bash
21 | npm run dev
22 | # or
23 | yarn dev
24 | ```
25 |
26 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
27 |
28 | You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file.
29 |
30 | [API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.js`.
31 |
32 | The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
33 |
34 | ## Learn More
35 |
36 | To learn more about Next.js, take a look at the following resources:
37 |
38 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
39 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
40 |
41 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
42 |
43 | ## Deploy on Vercel
44 |
45 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
46 |
47 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
48 |
--------------------------------------------------------------------------------
/disco-db-demo/__mocks__/fileMock.js:
--------------------------------------------------------------------------------
1 | module.exports = 'test-file-stub'
2 | // If you're running into the issue "Failed to parse src "test-file-stub" on 'next/image'", add a '/' to your fileMock.
--------------------------------------------------------------------------------
/disco-db-demo/__mocks__/styleMock.js:
--------------------------------------------------------------------------------
1 | module.exports = {}
--------------------------------------------------------------------------------
/disco-db-demo/__tests__/__snapshots__/snapshot.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`renders homepage unchanged 1`] = `
4 |
5 |
8 |
11 |
21 |
24 | Get started by editing
25 |
26 |
29 | pages/index.js
30 |
31 |
32 |
80 |
81 |
119 |
120 |
121 | `;
122 |
--------------------------------------------------------------------------------
/disco-db-demo/__tests__/e2e/example.spec.ts:
--------------------------------------------------------------------------------
1 | import { test, expect } from '@playwright/test'
2 |
3 | test('should navigate to the about page', async ({ page }) => {
4 | // Start from the index page (the baseURL is set via the webServer in the playwright.config.ts)
5 | await page.goto('http://localhost:3000/')
6 | // Find an element with the text 'About Page' and click on it
7 | await page.click('text=About Page')
8 | // The new url should be "/about" (baseURL is used there)
9 | await expect(page).toHaveURL('http://localhost:3000/about')
10 | // The new page should contain an h1 with "About Page"
11 | await expect(page.locator('h1')).toContainText('About Page')
12 | })
--------------------------------------------------------------------------------
/disco-db-demo/__tests__/index.test.jsx:
--------------------------------------------------------------------------------
1 | // sample test from Next docs
2 |
3 | import { render, screen } from '@testing-library/react'
4 | import '@testing-library/jest-dom'
5 | import Home from '../pages/index'
6 |
7 | describe('Home', () => {
8 | it('renders a heading', () => {
9 | render( )
10 |
11 | const heading = screen.getByRole('heading', {
12 | name: /welcome to next\.js!/i,
13 | })
14 |
15 | expect(heading).toBeInTheDocument()
16 | })
17 | })
--------------------------------------------------------------------------------
/disco-db-demo/__tests__/server/authController.js:
--------------------------------------------------------------------------------
1 | // import the exported module from ./server/controllers/authController.js
2 | import authController from '../../server/controllers/authController';
3 | const db = require('../../server/models/dbConnection')
4 | // const mongoose = require('mongoose');
5 | const bcrypt = require('bcrypt');
6 | const { Users, Notes } = require('../../server/models/model');
7 |
8 |
9 | describe('Auth Controller', () => {
10 | const deleteTestUsers = () => {
11 | Users.deleteOne({username: 'testUser1'});
12 | Users.deleteOne({username: 'testUser2'});
13 | }
14 | const setExistingUser = username => {
15 | return user1 = {username: username}
16 | }
17 |
18 | const mockRequest = (username, password) => {
19 | return {body: {username: username, password: password}}
20 | }
21 |
22 | const mockResponse = () => {
23 | const res = {};
24 | res.status = jest.fn().mockReturnValue(res);
25 | res.json = jest.fn().mockReturnValue(res);
26 | return res;
27 | };
28 |
29 | const encrypt = (password, workFactor) => {
30 | return bcrypt.hash(password, workFactor);
31 | }
32 |
33 | berforeAll(() => {
34 | db.connection();
35 | deleteTestUsers();
36 | const workFactor = 10;
37 | const username = 'testUser1';
38 | const password = 'test123';
39 | const encryptPW = encrypt(password, workFactor);
40 | })
41 |
42 | // afterEach? afterAll?
43 | // drop the record(s) that were just created
44 |
45 | describe('Sign up ', async () => {
46 | // declare a test req.body object with a test username and password
47 | const req = mockRequest(username, password);
48 | const res = mockResponse();
49 | await authController.signup(req, res)
50 | // it should hash the password
51 | it('Should hash the password', () => {
52 | expect(res.locals.password).toBe(encryptPW)
53 | })
54 | // it should create the new user with password in the db
55 | // it should reject a username if it is not unique
56 |
57 | // after each
58 | // remove the user just created via Delete operation
59 | });
60 |
61 |
62 | // before
63 | // create a test user
64 |
65 | // describe('Log in', )
66 | // it should log in the test user
67 | // it should return the appropriate error message if password incorrect
68 | // it should return next() if password compare successful
69 | // it should return error if no matching username found
70 |
71 | // after
72 | // delete the test user
73 | });
--------------------------------------------------------------------------------
/disco-db-demo/__tests__/snapshot.js:
--------------------------------------------------------------------------------
1 | // sample test from Next docs
2 |
3 | import { render } from '@testing-library/react'
4 | import Home from '../pages/index'
5 |
6 | it('renders homepage unchanged', () => {
7 | const { container } = render( )
8 | expect(container).toMatchSnapshot()
9 | })
--------------------------------------------------------------------------------
/disco-db-demo/__tests__/supertest.js:
--------------------------------------------------------------------------------
1 | const { resolveHref } = require('next/dist/shared/lib/router/router');
2 | const request = require('supertest');
3 | // const app = require('../server/server.js');
4 |
5 | const app = 'http://localhost:3000';
6 |
7 | // authRouter testing
8 |
9 | // /auth/login
10 | describe('userRouter /login testing', ()=>{
11 | it('Loads the login page', async () => {
12 | await request(app)
13 | .get('/auth/login')
14 | .expect(200)
15 | })
16 |
17 | // //
18 | // it('Successfully logs in a user', () => {
19 | // return request(app)
20 | // .post('/auth/login')
21 | // .send({username: 'test123', password: 'test123'})
22 | // .expect(200)
23 | // .end((err, res) => {
24 | // if (err) {
25 | // reject(new Error ('An error occurred:', err))
26 | // }
27 | // resolve(res.body)
28 | // })
29 | // })
30 | })
--------------------------------------------------------------------------------
/disco-db-demo/__tests__/sw-test.js:
--------------------------------------------------------------------------------
1 | const makeServiceWorkerEnv = require('service-worker-mock');
2 | // const makeFetchMock = require('service-worker-mock/fetch');
3 | require('cross-fetch/polyfill');
4 |
5 | describe('Service worker', () => {
6 | beforeEach(() => {
7 | const serviceWorkerEnv = makeServiceWorkerEnv();
8 | Object.defineProperty(serviceWorkerEnv, 'addEventListener', {
9 | value: serviceWorkerEnv.addEventListener,
10 | enumerable: true
11 | });
12 | Object.assign(global, serviceWorkerEnv)
13 | // const fetchMock = makeFetchMock();
14 | // Object.assign(global, fetchMock);
15 | jest.resetModules();
16 | });
17 |
18 | it('should add listeners', async () => {
19 | require('../public/swCacheSite-indexedDB.js');
20 | await self.trigger('install');
21 | expect(self.listeners.get('install')).toBeDefined();
22 | expect(self.listeners.get('activate')).toBeDefined();
23 | expect(self.listeners.get('fetch')).toBeDefined();
24 | });
25 |
26 | it('should delete old caches on activate', async () => {
27 | require('../public/swCacheSite-indexedDB.js');
28 |
29 | // Create old cache
30 | await self.caches.open('cacheName');
31 | expect(self.snapshot().caches.cacheName).toBeDefined();
32 |
33 | // Activate and verify old cache is removed
34 | await self.trigger('activate');
35 | expect(self.snapshot().caches.cacheName).toStrictEqual(undefined);
36 | });
37 |
38 | it('should return a cached response', async () => {
39 | require('../public/swCacheSite-indexedDB.js');
40 |
41 | const cachedResponse = { clone: () => { }, data: { key: 'value' } };
42 | const cachedRequest = new Request('/test');
43 | const cache = await self.caches.open('TEST');
44 | cache.put(cachedRequest, cachedResponse);
45 |
46 | const response = await self.trigger('fetch', cachedRequest);
47 | expect(response.data.key).toEqual('value');
48 | });
49 |
50 | it('should fetch and cache an uncached request', async () => {
51 | const mockResponse = { clone: () => { return { data: { key: 'value' } } } };
52 | global.fetch = (response) => Promise.resolve({ ...mockResponse, headers: response.headers });
53 |
54 | require('../public/swCacheSite-indexedDB.js');
55 |
56 | const request = new Request('/test');
57 | const response = await self.trigger('fetch', request);
58 | expect(response.clone()).toEqual(mockResponse.clone());
59 |
60 | const runtimeCache = self.snapshot().caches['my-site-cache-v3'];
61 | expect(runtimeCache[request.url]).toEqual(mockResponse.clone());
62 | });
63 |
64 |
65 | });
66 |
--------------------------------------------------------------------------------
/disco-db-demo/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | require.resolve('next/babel')
4 | ]
5 | };
6 |
--------------------------------------------------------------------------------
/disco-db-demo/components/Navbar.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import AppBar from '@mui/material/AppBar';
3 | import Box from '@mui/material/Box';
4 | import Toolbar from '@mui/material/Toolbar';
5 | import Typography from '@mui/material/Typography';
6 | import Button from '@mui/material/Button';
7 | import IconButton from '@mui/material/IconButton';
8 | import MenuIcon from '@mui/icons-material/Menu';
9 | import Switch from '@mui/material/Switch';
10 | import FormControlLabel from '@mui/material/FormControlLabel';
11 | import FormGroup from '@mui/material/FormGroup';
12 | import { useRouter } from 'next/router';
13 |
14 | export default function Navbar(props) {
15 | const router = useRouter();
16 |
17 | const handleChange = (event) => {
18 | //will need to add logic for offline mode
19 | props.setOnline(event.target.checked);
20 | }
21 |
22 | //check for network connectivity
23 | window.addEventListener('offline', (event) => {
24 | props.setOnline(false);
25 | })
26 | window.addEventListener('online', (event) => {
27 | props.setOnline(true);
28 | })
29 |
30 | const handleLogout = (event) => {
31 | event.preventDefault();
32 | //sends GET request to auth/logout
33 |
34 | const testURL = '/api/hello';
35 | const devURL = '/auth/logout';
36 |
37 | fetch(testURL)
38 | .then(response => response.json())
39 | .then(data => {
40 | //clear out username in local storage when user logs off
41 | localStorage.clear();
42 | router.push('/auth/login');
43 | })
44 | .catch(error => console.log('Error', error));
45 |
46 | }
47 | return (
48 |
49 | theme.zIndex.drawer + 1}}>
50 |
51 |
58 |
59 |
60 |
61 | Notes
62 |
63 |
64 |
72 | }
73 | label={props.online ? 'Online' : 'Offline'}
74 | />
75 |
76 | Logout
77 |
78 |
79 |
80 | );
81 | }
--------------------------------------------------------------------------------
/disco-db-demo/components/Notes.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import Button from '@mui/material/Button';
3 | import TextField from '@mui/material/TextField';
4 | import Box from '@mui/material/Box';
5 | import Container from '@mui/material/Container';
6 | import CssBaseline from '@mui/material/CssBaseline';
7 |
8 | export default function NotesContainer(props) {
9 |
10 | const handleTitle = (event) => {
11 | props.setNewTitle(event.target.value);
12 | };
13 |
14 | const handleContent = (event) => {
15 | props.setNewContent(event.target.value);
16 | }
17 |
18 | const formField = [
19 |
20 |
28 |
36 |
37 | ]
38 |
39 | const handleSave = (event) => {
40 | event.preventDefault();
41 |
42 | const noteTitle = document.querySelector('[name="noteTitle"]');
43 | const noteContent = document.querySelector('[name="noteContent"]');
44 | const saveBody = {
45 | //grab id from query params
46 | _id: props.noteID,
47 | title: noteTitle.value,
48 | content: noteContent.value,
49 | updatedAt: Date.now(),
50 | }
51 |
52 | const testURL = '/api/hello';
53 | const devURL = '/user/notes';
54 | fetch(devURL, {
55 | method: 'PATCH',
56 | headers: {
57 | 'Content-Type': 'application/json',
58 | },
59 | body: JSON.stringify(saveBody),
60 | })
61 | .then(res => res.json())
62 | .then(data => {
63 | props.setRefresh(true);
64 | })
65 | .catch((err) => {
66 | console.log('Error in patching notes:', err)
67 | }
68 | )};
69 |
70 | const handleDelete = (event) => {
71 | event.preventDefault();
72 |
73 | const deleteBody = {
74 | //grab id from query params
75 | _id: props.noteID,
76 | username: localStorage.user,
77 | }
78 |
79 | const testURL = '/api/hello';
80 | const devURL = '/user/notes';
81 | fetch(devURL, {
82 | method: 'DELETE',
83 | headers: {
84 | 'Content-Type': 'application/json',
85 | },
86 | body: JSON.stringify(deleteBody),
87 | })
88 | .then(res => res.json())
89 | .then(data => {
90 | console.log('Success', data);
91 | //should remove entry from array
92 |
93 | props.setRefresh(true);
94 | })
95 | .catch((err) => {
96 | console.log('Error in deleting notes:', err)
97 | props.setRefresh(true);
98 | })
99 | };
100 |
101 | return (
102 |
103 |
104 |
105 |
106 | {formField}
107 |
108 |
109 | {/* Delete click should open modal for verification - implement after MVP */}
110 | Delete
111 |
112 |
113 | Save
114 |
115 |
116 |
117 |
118 |
119 | )
120 | }
121 |
--------------------------------------------------------------------------------
/disco-db-demo/components/Sidebar.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import { useRouter } from 'next/router';
3 | import Box from '@mui/material/Box';
4 | import Drawer from '@mui/material/Drawer';
5 | import Toolbar from '@mui/material/Toolbar';
6 | import List from '@mui/material/List';
7 | import Divider from '@mui/material/Divider';
8 | import ListItem from '@mui/material/ListItem';
9 | import ListItemText from '@mui/material/ListItemText';
10 | import NotesIcon from '@mui/icons-material/Notes';
11 | import { Button } from '@mui/material';
12 |
13 | const drawerWidth = 240;
14 |
15 | export default function SideBar(props) {
16 | const router = useRouter();
17 | const [setSidebar, setNewSidebar] = useState([]);
18 | //Saves all notes as buttons for user on front-end for later access.
19 | const sidebarArray = [];
20 | //On initial render, invoke useEffect to grab all notes on props pertaining to user.
21 | //Populate the notes in an array and update state to reflect.
22 |
23 | useEffect(() => {
24 | props.noteArray.forEach((ele) => {
25 | //usernote has entire object per note for user
26 | const userNoteButton =
27 |
28 |
29 |
30 | //Convert each usernote into a button and push in array for useState.
31 | sidebarArray.push(userNoteButton);
32 | })
33 | setNewSidebar([sidebarArray])
34 | }, [props.noteArray])
35 |
36 | function newNoteHandler(){
37 | //POST request to user/notes with object of {username: username, createdAt: unix time}
38 | //Expect response of res.locals.data = {_id:id}
39 | //Poplate note array with a new icon with unique ID
40 | const newNoteInfo = {
41 | //Placeholder username, need to replace
42 | username: localStorage.getItem('user'),
43 | createdAt: Date.now()
44 | }
45 | const testURL = '/api/hello';
46 | const devURL = '/user/notes';
47 | fetch(devURL, {
48 | method: 'POST',
49 | headers: {
50 | 'Content-Type': 'application/json',
51 | },
52 | body: JSON.stringify(newNoteInfo),
53 | })
54 | .then(res => res.json())
55 | .then((data) => {
56 | const uniqId = data.data._id;
57 | const newNote =
58 |
59 |
60 |
61 |
62 | setNewSidebar([...setSidebar, newNote])
63 | props.setRefresh(true);
64 | })
65 | .catch((err) => {
66 | console.log('Error in creating new note:', err)
67 | });
68 | };
69 | //Click handler to obtain ID attribute and shallow route to the note.
70 | function currNoteHandler (e){
71 | const targetId = e.currentTarget.id
72 | router.push(`/user/notes?${targetId}`, undefined, {shallow: true});
73 | }
74 |
75 | return (
76 |
77 |
89 |
90 |
91 | New Note
92 |
93 |
94 | {setSidebar}
95 |
96 |
97 |
98 | );
99 | }
100 |
--------------------------------------------------------------------------------
/disco-db-demo/components/Weather.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import Grid from '@mui/material/Grid';
3 | import Card from '@mui/material/Card';
4 | import CardContent from '@mui/material/CardContent';
5 | import TextField from '@mui/material/TextField';
6 | import WeatherAPI from './weatherAPI';
7 | import Typography from '@mui/material/Typography';
8 |
9 |
10 | export default function Weather() {
11 | const [city, setCity] = React.useState(null);
12 |
13 | return (
14 |
15 |
16 |
17 |
18 | Weather Report
19 |
20 | setCity(event.target.value)}
24 | />
25 |
26 |
27 |
28 |
29 | )
30 | }
31 |
--------------------------------------------------------------------------------
/disco-db-demo/components/WeatherDisplay.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import Typography from '@mui/material/Typography';
3 | import CardContent from '@mui/material/CardContent';
4 | import Box from '@mui/material/Box';
5 |
6 | export default function WeatherDisplay(props) {
7 | const weatherReport = props.weatherReport;
8 | const weatherMain = weatherReport.weather[0].main;
9 | const weatherDescription = weatherReport.weather[0].description;
10 | const temp = weatherReport.main.temp;
11 | const humidity = weatherReport.main.humidity;
12 | const wind = weatherReport.wind.speed;
13 | const country = weatherReport.sys.country;
14 | const city = weatherReport.name;
15 |
16 | return (
17 |
18 |
19 |
20 |
21 |
22 | {city}, {country}
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | Temp: {temp}
32 | °
33 | {"F"}
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | {weatherDescription}
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | Humidity: {humidity} %
52 |
53 |
54 |
55 |
56 | Wind: {wind} mph
57 |
58 |
59 |
60 |
61 |
62 | )
63 | }
--------------------------------------------------------------------------------
/disco-db-demo/components/layout.js:
--------------------------------------------------------------------------------
1 | import Navbar from './Navbar';
2 | import Sidebar from './Sidebar';
3 | import Box from '@mui/material/Box';
4 | import CssBaseline from '@mui/material/CssBaseline';
5 | import Toolbar from '@mui/material/Toolbar';
6 | import React, { useEffect, useState } from 'react';
7 |
8 | export default function Layout({ children }) {
9 | const [isLoading, setLoading] = useState(true);
10 | const userNoteArr = [];
11 | const [online, setOnline] = React.useState(true);
12 | const [noteArray, setNewNote] = useState([]);
13 | const [refresh, setRefresh] = useState(false);
14 | //passing online prop to all children
15 | const childrenWithProps = React.Children.map(children, child => {
16 | // Checking isValidElement is the safe way and avoids a typescript error too.
17 | if (React.isValidElement(child)) {
18 | return React.cloneElement(child, { online: online, data: noteArray, setNewNote: setNewNote, setRefresh: setRefresh });
19 | }
20 | return child;
21 | });
22 |
23 |
24 |
25 | useEffect(() => {
26 |
27 | const testURL = '/api/hello';
28 | const devURL = '/user/load';
29 | fetch(devURL)
30 | .then((res) => res.json())
31 | .then( (data) => {
32 | //Iterate thru retrived data and create a copy of each object into state array.
33 | data.data.forEach((ele) => {
34 | userNoteArr.push(ele);
35 | });
36 | setNewNote(userNoteArr);
37 |
38 | setLoading(false);
39 | setRefresh(false);
40 |
41 | })
42 | .catch((err) => console.log('Error in fetching data', err))
43 | }, [refresh, online]);
44 |
45 | if (isLoading) return null;
46 | else {
47 | return (
48 | <>
49 |
50 |
51 |
52 |
53 |
54 |
55 | {childrenWithProps}
56 |
57 |
58 | >
59 | )
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/disco-db-demo/components/weatherAPI.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { useState, useEffect } from 'react';
3 | import LinearProgress from '@mui/material/LinearProgress';
4 | import WeatherDisplay from './WeatherDisplay';
5 |
6 | export default function WeatherAPI(props) {
7 | const [weatherReport, setWeatherReport] = useState(null);
8 | const [isLoading, setLoading] = useState(true);
9 | const [isError, setError] = useState(null);
10 |
11 | useEffect(() => {
12 | const devURL = '/api/weather';
13 |
14 | //sends POST request to api/weather in backend with body as city:
15 | fetch(devURL, {
16 | method: 'POST',
17 | headers: {
18 | 'Content-Type': 'application/json',
19 | },
20 | body: JSON.stringify( {city: props.city}),
21 | })
22 | .then( response => response.json())
23 | .then( data => {
24 | //check for statuscode - this is the object that we will get back from the backend
25 | if (data.statusCode !== 200) {
26 | setLoading(true);
27 | } else {
28 | setWeatherReport(data.data);
29 | setLoading(false);
30 | }
31 | })
32 | .catch( error => {
33 | console.log('Error: ', error);
34 | setError(error);
35 | setLoading(true);
36 | })
37 |
38 | }, [props.city])
39 |
40 | if(isLoading) {
41 | if(props.city) {
42 | return (
43 |
44 |
45 |
46 | )
47 | }
48 | else return null;
49 | } else {
50 | return(
51 |
52 | )
53 | }
54 | }
--------------------------------------------------------------------------------
/disco-db-demo/jest-setup.js:
--------------------------------------------------------------------------------
1 | import regeneratorRuntime from 'regenerator-runtime';
2 |
3 | module.exports = () => {
4 | global.testServer = require('./server/server');
5 | };
6 |
--------------------------------------------------------------------------------
/disco-db-demo/jest-teardown.js:
--------------------------------------------------------------------------------
1 | module.exports = async (globalConfig) => {
2 | testServer.close();
3 | };
4 |
--------------------------------------------------------------------------------
/disco-db-demo/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | collectCoverageFrom: [
3 | '**/*.{js,jsx,ts,tsx}',
4 | '!**/*.d.ts',
5 | '!**/node_modules/**',
6 | ],
7 | moduleNameMapper: {
8 | // Handle CSS imports (with CSS modules)
9 | // https://jestjs.io/docs/webpack#mocking-css-modules
10 | '^.+\\.module\\.(css|sass|scss)$': 'identity-obj-proxy',
11 |
12 | // Handle CSS imports (without CSS modules)
13 | '^.+\\.(css|sass|scss)$': '/__mocks__/styleMock.js',
14 |
15 | // Handle image imports
16 | // https://jestjs.io/docs/webpack#handling-static-assets
17 | '^.+\\.(jpg|jpeg|png|gif|webp|avif|svg)$': `/__mocks__/fileMock.js`,
18 |
19 | // Handle module aliases
20 | '^@/components/(.*)$': '/components/$1',
21 | },
22 | // Add more setup options before each test is run
23 | // setupFilesAfterEnv: ['/jest.setup.js'],
24 | testPathIgnorePatterns: ['/node_modules/', '/.next/'],
25 | testEnvironment: 'jsdom',
26 | transform: {
27 | // Use babel-jest to transpile tests with the next/babel preset
28 | // https://jestjs.io/docs/configuration#transform-objectstring-pathtotransformer--pathtotransformer-object
29 | '^.+\\.(js|jsx|ts|tsx)$': ['babel-jest', { presets: ['next/babel'] }],
30 | },
31 | transformIgnorePatterns: [
32 | '/node_modules/',
33 | '^.+\\.module\\.(css|sass|scss)$',
34 | ],
35 | }
--------------------------------------------------------------------------------
/disco-db-demo/next.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | reactStrictMode: true,
3 | }
4 |
--------------------------------------------------------------------------------
/disco-db-demo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "disco-db-demo",
3 | "private": true,
4 | "scripts": {
5 | "dev": "nodemon server/server.js",
6 | "next-dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint",
10 | "test": "jest --watch",
11 | "test:e2e": "playwright test"
12 | },
13 | "dependencies": {
14 | "@emotion/react": "^11.8.1",
15 | "@emotion/styled": "^11.8.1",
16 | "@fontsource/roboto": "^4.5.3",
17 | "@mui/icons-material": "^5.4.2",
18 | "@mui/material": "^5.4.2",
19 | "axios": "^0.26.0",
20 | "bcrypt": "^5.0.1",
21 | "cookie-parser": "^1.4.6",
22 | "dotenv": "^16.0.0",
23 | "express": "^4.17.2",
24 | "mongoose": "^6.2.2",
25 | "next": "12.0.10",
26 | "react": "17.0.2",
27 | "react-dom": "17.0.2",
28 | "regenerator-runtime": "^0.13.9"
29 | },
30 | "devDependencies": {
31 | "@babel/core": "^7.17.4",
32 | "@babel/preset-env": "^7.16.11",
33 | "@playwright/test": "^1.19.1",
34 | "@testing-library/jest-dom": "^5.16.2",
35 | "@testing-library/react": "^12.1.3",
36 | "babel-jest": "^27.5.1",
37 | "cross-fetch": "^3.1.5",
38 | "eslint": "8.9.0",
39 | "eslint-config-next": "12.0.10",
40 | "identity-obj-proxy": "^3.0.0",
41 | "jest": "^27.5.1",
42 | "nodemon": "^2.0.15",
43 | "service-worker-mock": "^2.0.5",
44 | "supertest": "^6.2.2"
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/disco-db-demo/pages/_app.js:
--------------------------------------------------------------------------------
1 | import '../styles/globals.css'
2 | import '@fontsource/roboto/300.css';
3 | import '@fontsource/roboto/400.css';
4 | import '@fontsource/roboto/500.css';
5 | import '@fontsource/roboto/700.css';
6 | import { useEffect } from 'react';
7 |
8 | function MyApp({ Component, pageProps }) {
9 | // // // service worker
10 | useEffect(() => {
11 | if("serviceWorker" in navigator && 'SyncManager' in window) {
12 | window.addEventListener("load", function () {
13 | navigator.serviceWorker.register("../swCacheSite-indexedDB.js", {type: 'module', scope: '/'}).then(
14 | function (registration) {
15 | console.log("Service Worker registration successful with scope: ", registration.scope);
16 | },
17 | function (err) {
18 | console.log("Service Worker registration failed: ", err);
19 | }
20 | );
21 | // navigator.serviceWorker.ready.then(function(swRegistration) {
22 | // console.log('successfully requested a one time sync')
23 | // return swRegistration.sync.register('myFirstSync');
24 | // });
25 | });
26 | }
27 | }, [])
28 |
29 |
30 |
31 | const getLayout = Component.getLayout || ((page) => page)
32 | return getLayout( )
33 | }
34 |
35 | export default MyApp
36 |
--------------------------------------------------------------------------------
/disco-db-demo/pages/about.js:
--------------------------------------------------------------------------------
1 | export default function About() {
2 | return (
3 |
4 |
About Page
5 | Hello
6 |
7 | )
8 | }
--------------------------------------------------------------------------------
/disco-db-demo/pages/api/hello.js:
--------------------------------------------------------------------------------
1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction
2 |
3 | export default function handler(req, res) {
4 | //const body = req.body;
5 | //const body = {actionSuccess: true};
6 | const body = [{_id: 'foobar', title: 'foo', content: 'bar'}, {_id: '123', title: 'foobar', content: 'I love pizza'}];
7 | res.status(200).json({ body })
8 | }
9 |
--------------------------------------------------------------------------------
/disco-db-demo/pages/auth/login.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import Avatar from '@mui/material/Avatar';
3 | import Button from '@mui/material/Button';
4 | import CssBaseline from '@mui/material/CssBaseline';
5 | import TextField from '@mui/material/TextField';
6 | import FormControlLabel from '@mui/material/FormControlLabel';
7 | import Checkbox from '@mui/material/Checkbox';
8 | import { default as MUILink } from '@mui/material/Link';
9 | import Link from 'next/link';
10 | import Grid from '@mui/material/Grid';
11 | import Box from '@mui/material/Box';
12 | import LockOutlinedIcon from '@mui/icons-material/LockOutlined';
13 | import Typography from '@mui/material/Typography';
14 | import Container from '@mui/material/Container';
15 | import { createTheme, ThemeProvider } from '@mui/material/styles';
16 | import { useRouter } from 'next/router';
17 | import Alert from '@mui/material/Alert';
18 |
19 | function Copyright(props) {
20 | return (
21 |
22 | {'Copyright © '}
23 |
24 | DiscoDB
25 | {' '}
26 | {new Date().getFullYear()}
27 | {'.'}
28 |
29 | );
30 | }
31 |
32 | const theme = createTheme();
33 |
34 | export default function SignIn() {
35 | const router = useRouter();
36 | const [auth, setAuth] = React.useState(null);
37 |
38 | const handleSubmit = (event) => {
39 | event.preventDefault();
40 | const data = new FormData(event.currentTarget);
41 | // eslint-disable-next-line no-console
42 |
43 | const loginBody = {
44 | username: data.get('username'),
45 | password: data.get('password'),
46 | }
47 |
48 | const testURL = '/api/hello';
49 | const devURL = '/auth/login';
50 | fetch(devURL, {
51 | method: 'POST',
52 | headers: {
53 | 'Content-Type': 'application/json',
54 | },
55 | body: JSON.stringify(loginBody),
56 | })
57 | .then(res => res.json())
58 | .then(data => {
59 | console.log('Success', data);
60 | //get back response from backend to verify user authentication
61 | //if success, redirect user to user endpoint
62 | if (!data.actionSuccess) {
63 | setAuth(false);
64 | } else {
65 | setAuth(true);
66 | //temp username to add to localStorage
67 | localStorage.setItem('user', loginBody.username);
68 | router.push('/user');
69 | }
70 |
71 | })
72 | .catch(err => console.log('Error', err))
73 | };
74 |
75 | return (
76 |
77 |
78 |
79 |
87 |
88 |
89 |
90 |
91 | Sign in
92 |
93 |
94 |
104 |
114 | {auth === false &&
115 |
116 | Please enter a valid username and password
117 |
118 | }
119 | }
121 | label="Remember me"
122 | />
123 |
129 | Sign In
130 |
131 |
132 |
133 |
134 |
135 | Forgot password?
136 |
137 |
138 |
139 |
140 |
141 |
142 | {"Don't have an account? Sign Up"}
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 | );
153 | }
154 |
--------------------------------------------------------------------------------
/disco-db-demo/pages/auth/signup.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import Avatar from '@mui/material/Avatar';
3 | import Button from '@mui/material/Button';
4 | import CssBaseline from '@mui/material/CssBaseline';
5 | import TextField from '@mui/material/TextField';
6 | import FormControlLabel from '@mui/material/FormControlLabel';
7 | import Checkbox from '@mui/material/Checkbox';
8 | import {default as MUILink} from '@mui/material/Link';
9 | import Link from 'next/link';
10 | import Grid from '@mui/material/Grid';
11 | import Box from '@mui/material/Box';
12 | import LockOutlinedIcon from '@mui/icons-material/LockOutlined';
13 | import Typography from '@mui/material/Typography';
14 | import Container from '@mui/material/Container';
15 | import { createTheme, ThemeProvider } from '@mui/material/styles';
16 | import { useRouter } from 'next/router';
17 | import Alert from '@mui/material/Alert';
18 |
19 | function Copyright(props) {
20 | return (
21 |
22 | {'Copyright © '}
23 |
24 | DiscoDB
25 | {' '}
26 | {new Date().getFullYear()}
27 | {'.'}
28 |
29 | );
30 | }
31 |
32 | const theme = createTheme();
33 |
34 | export default function SignUp() {
35 | const [auth, setAuth] = React.useState(null);
36 | const router = useRouter();
37 |
38 | const handleSubmit = (event) => {
39 | event.preventDefault();
40 | const data = new FormData(event.currentTarget);
41 |
42 | const signupBody = {
43 | username: data.get('username'),
44 | password: data.get('password'),
45 | firstName: data.get('firstName'),
46 | email: data.get('email')
47 | }
48 |
49 | const testURL = '/api/hello';
50 | const devURL = '/auth/signup';
51 | fetch(devURL, {
52 | method: 'POST',
53 | headers: {
54 | 'Content-Type': 'application/json',
55 | },
56 | body: JSON.stringify(signupBody),
57 | })
58 | .then(res => res.json())
59 | .then(data => {
60 | console.log('Success', data);
61 | //have backend send back response that user successfully created account
62 | //if success, redirect user to login page, else have them try again
63 | if (!data.actionSuccess) {
64 | setAuth(false);
65 | } else {
66 | setAuth(true);
67 | //temp username to add to localStorage
68 | localStorage.setItem('user', signupBody.username);
69 | router.push('/user');
70 | }
71 | })
72 | .catch(err => console.log('Error', err))
73 | };
74 |
75 | return (
76 |
77 |
78 |
79 |
87 |
88 |
89 |
90 |
91 | Sign up
92 |
93 |
94 |
95 |
96 |
104 |
105 |
106 |
113 |
114 |
115 |
123 |
124 |
125 |
134 |
135 |
136 | {auth === false &&
137 |
138 | Username already exists. Please try again
139 |
140 | }
141 |
147 | Sign Up
148 |
149 |
150 |
151 |
152 |
153 | Already have an account? Sign in
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 | );
164 | }
165 |
--------------------------------------------------------------------------------
/disco-db-demo/pages/index.js:
--------------------------------------------------------------------------------
1 | import Head from 'next/head'
2 | import Image from 'next/image'
3 | import styles from '../styles/Home.module.css'
4 | import Link from 'next/link'
5 |
6 | export default function Home() {
7 | return (
8 |
9 |
10 |
Create Next App
11 |
12 |
13 |
14 |
15 |
16 | About Page
17 |
18 |
19 |
20 |
21 | Welcome to Next.js!
22 |
23 |
24 |
25 | Get started by editing{' '}
26 | pages/index.js
27 |
28 |
29 |
58 |
59 |
60 |
72 |
73 | )
74 | }
75 |
--------------------------------------------------------------------------------
/disco-db-demo/pages/user/index.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react';
2 | import Box from '@mui/material/Box';
3 | import Typography from '@mui/material/Typography';
4 | import Weather from '../../components/Weather';
5 | import Layout from '../../components/layout';
6 |
7 | export default function User(props) {
8 | console.log(props);
9 |
10 | return (
11 |
12 |
13 | Hello User
14 |
15 |
16 |
17 | )
18 | }
19 |
20 | User.getLayout = function getLayout(user) {
21 | return (
22 |
23 | {user}
24 |
25 | )
26 | }
27 |
--------------------------------------------------------------------------------
/disco-db-demo/pages/user/notes.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import Box from '@mui/material/Box';
3 | import Typography from '@mui/material/Typography';
4 | import Layout from '../../components/layout';
5 | import NotesContainer from '../../components/Notes';
6 | import { useRouter } from 'next/router';
7 |
8 | export default function Notes(props) {
9 | //grab query paramaters from note
10 | const [setContent, setNewContent] = React.useState('');
11 | const [setTitle, setNewTitle] = React.useState('');
12 |
13 | const router = useRouter();
14 | let noteID = Object.keys(router.query)[0];
15 |
16 | React.useEffect(() => {
17 | for (let i = 0; i < props.data.length; i++) {
18 | if (props.data[i]._id === noteID) {
19 | setNewTitle(props.data[i].title);
20 | setNewContent(props.data[i].content);
21 | }
22 | }
23 | // setNewContent(noteContent)
24 | // setNewTitle(noteTitle)
25 | }, [props.data, noteID]);
26 |
27 | return (
28 |
29 |
30 | User Notes
31 |
32 |
33 |
34 | )
35 | }
36 |
37 |
38 | Notes.getLayout = function getLayout(notes) {
39 | return (
40 |
41 | {notes}
42 |
43 | )
44 | }
--------------------------------------------------------------------------------
/disco-db-demo/public/backgroundSync.js:
--------------------------------------------------------------------------------
1 | import { idbPromise, dbGlobals } from './discoGlobals.js';
2 |
3 | /**
4 | * @property {Function} accessObjectStore Access object store in IDB database and start a transaction
5 | * @param {String} storeName Object store to be accessed for transaction
6 | * @param {String} method Method for transaction, "readwrite, readonly"
7 | * @return {Object} Accessed Object store Object
8 | */
9 | function accessObjectStore (storeName, method) {
10 | return idbPromise.DB.transaction([storeName], method).objectStore(storeName)
11 | };
12 | /**
13 | * @property {Function} discoAddToQueue Adds Object into Object store
14 | * @param {Object} dataObject Objected to be added to Object store
15 | *
16 | */
17 | function discoAddToQueue (dataObject) {
18 | //Open a transaction to object store 'Queue'
19 | const store = accessObjectStore(dbGlobals.syncQueue, 'readwrite')
20 | //Add data to object store
21 | store.add(dataObject)
22 | };
23 | /**
24 | * @property {Function} discoRegisterSync Request a "Sync" event to reattempt request when network is online.
25 | *
26 | */
27 | async function discoRegisterSync() {
28 | try {
29 | const register = await registration.sync.register('failed_requests');
30 | return register;
31 | } catch(error) {
32 | console.log('Error:' , error);
33 | return error;
34 | }
35 | };
36 |
37 | /**
38 | * @property {Function} discoSyncToServer Accesses the Queue Object Store and re-sends all requests saved in application/json.
39 | *
40 | */
41 | function discoSyncToServer() {
42 | const store = accessObjectStore(dbGlobals.syncQueue, 'readwrite');
43 | const request = store.getAll();
44 |
45 | request.onsuccess = function (event) {
46 | const httpQueue = event.target.result;
47 | //Comes back as an array of objects
48 | //Iterate Queue store and initialize Fetch request
49 | httpQueue.forEach((data) => {
50 | const { url, method, body } = data
51 | const headers = {'Content-Type': 'application/json'};
52 | fetch(url, {
53 | method: method,
54 | headers: headers,
55 | body: JSON.stringify(body)
56 | })
57 | .then((res) => res.json())
58 | .then((res) => {
59 | //Previous transaction was closed due to getAll()
60 | //Reopen object store and delete the corresponding object on successful HTTP request
61 | const newStore = accessObjectStore(dbGlobals.syncQueue, 'readwrite');
62 | newStore.delete(data.id);
63 | })
64 | .catch((error) => {
65 | console.error('Failed to sync data to server:', error);
66 | throw error
67 | })
68 | });
69 | }
70 | request.onerror = (err) => {
71 | console.log('Attempt to sync queue failed:', err);
72 | }
73 | };
74 |
75 | export { discoAddToQueue, discoRegisterSync, discoSyncToServer };
--------------------------------------------------------------------------------
/disco-db-demo/public/configMap.js:
--------------------------------------------------------------------------------
1 | // const fs = require('fs');
2 | // const path = require('path');
3 | // import * as fs from 'fs';
4 | // import path from 'path';
5 |
6 | import { dbGlobals } from './discodb.config.js'
7 |
8 | // function find(targetPath) {
9 | // return findStartingWith(path.dirname(require.main.filename), targetPath);
10 | // }
11 |
12 | // function findStartingWith(start, target) {
13 | // const file = path.join(start, target);
14 | // try {
15 | // data = fs.readFileSync(file, 'utf-8');
16 | // return JSON.parse(data);
17 | // } catch (err) {
18 |
19 | // if (path.dirname(start) !== start) {
20 | // return findStartingWith(path.dirname(start), target);
21 | // }
22 | // }
23 | // }
24 |
25 | // const dbGlobals = JSON.parse(userConfig);
26 | console.log(dbGlobals)
27 |
28 | // const dbGlobals = find('discodb.config.json');
29 | // module.exports = dbGlobals;
30 | export default dbGlobals;
--------------------------------------------------------------------------------
/disco-db-demo/public/discoGlobals.js:
--------------------------------------------------------------------------------
1 | // const configVariables = require('./configMap.js');
2 | // import dbGlobals from './configMap.js';
3 | import { dbGlobals } from './discodb.config.js'
4 | const idbPromise = {
5 | DB: null
6 | }
7 |
8 | const onlineUrlArr = [];
9 | dbGlobals.onlineRoutes.forEach(el => {
10 | onlineUrlArr.push(el.url)
11 | });
12 |
13 | const offlineUrlArr = [];
14 | dbGlobals.offlineRoutes.forEach(el => {
15 | offlineUrlArr.push(el.url);
16 | });
17 |
18 | // module.exports = { idbPromise, onlineUrlStoreMap, offlineUrlStoreMap };
19 |
20 | // module.exports = {
21 | // DB: null,
22 |
23 | // }
24 |
25 | export { dbGlobals, idbPromise, onlineUrlArr, offlineUrlArr }
--------------------------------------------------------------------------------
/disco-db-demo/public/discoSync.js:
--------------------------------------------------------------------------------
1 | import { discoConnect, discoGetAll, discoDeleteOne, discoUpdateOne, discoAdd, discoDeleteAll } from './idbOperations.js';
2 | import { discoAddToQueue, discoRegisterSync } from './backgroundSync.js';
3 | import { idbPromise , dbGlobals } from './discoGlobals.js';
4 |
5 | /**
6 | * @property {Function} discoSyncOffline Executes different IndexedDB logic based on the value of passed in method
7 | * @param {String} method This is the method property of the intercepted fetch request
8 | * @param {String} url This is the url property of the intercepted fetch request
9 | * @param {String} store This is the store property associated with the url provided in the config file
10 | * @param {Request} eventRequest This is the cloned version of the intercepted fetch request
11 | *
12 | */
13 | function discoSyncOffline(method, url, clonedRequest) {
14 | // assuming store can be managed by config file and imported into indexedDB.js
15 | // config.store = store
16 | switch(method) {
17 | case 'GET':
18 | if (idbPromise.DB) {
19 | // if store
20 | return discoGetAll().then((data) => {
21 | //REVISIT THIS, make sure to change data back to data!!
22 | const responseBody = { data };
23 | const IDBData = new Response(JSON.stringify(responseBody));
24 | return IDBData;
25 | })
26 | } else {
27 | return discoConnect( () => {
28 | discoGetAll().then((data) => {
29 | const responseBody = {data: data};
30 | const IDBData = new Response(JSON.stringify(responseBody));
31 | return IDBData;
32 | });
33 | })
34 | }
35 | case 'DELETE':
36 | return clonedRequest.json()
37 | .then((data) => {
38 | const reqBody = {
39 | url: url,
40 | method: method,
41 | body: data
42 | };
43 | discoRegisterSync();
44 | discoAddToQueue(reqBody);
45 | const keypath = dbGlobals.keypath;
46 | const id = data[keypath];
47 | //call function to DELETE note
48 | discoDeleteOne(id);
49 | const deleteResponse = new Response(JSON.stringify({}));
50 | return deleteResponse;
51 | })
52 | .catch( err => {
53 | console.log('this is in the dbDeleteOne catch block: ', err);
54 | })
55 | case 'PATCH':
56 | return clonedRequest.json()
57 | .then((data) => {
58 | const reqBody = {
59 | url: url,
60 | method: method,
61 | body: data
62 | };
63 | discoRegisterSync();
64 | discoAddToQueue(reqBody);
65 | //call function to UPDATE note
66 | const keypath = dbGlobals.keypath;
67 | const id = data[keypath];
68 | discoUpdateOne(data);
69 |
70 | // returns empty object to trigger rerender in our app
71 | // assumes developer does not want to do anything with the response
72 | const patchResponse = new Response(JSON.stringify({}));
73 | return patchResponse;
74 | })
75 | default:
76 | console.log('this url is not configured');
77 | return caches.match(clonedRequest)
78 | .then(response => {
79 | return response
80 | })
81 | }
82 | }
83 |
84 | /**
85 | * @property {Function} discoSyncOnline Establishes connection to indexedDB & create Object Stores as specified in Configuration.
86 | * @param {String} method This is the method property of the intercepted fetch request
87 | * @param {String} url This is the url property of the intercepted fetch request
88 | * @param {String} store This is the store property associated with the url provided in the config file
89 | * @param {Request} clonedResponse This is the cloned version of the intercepted fetch response
90 | *
91 | */
92 | function discoSyncOnline(method, url, clonedResponse) {
93 | switch(method) {
94 | case 'GET':
95 | const resCloneDB = clonedResponse;
96 | resCloneDB.json().then(data => {
97 | //delete existing indexedDB data
98 | if (idbPromise.DB) {
99 | discoDeleteAll();
100 | } else {
101 | discoConnect( () => {
102 | discoDeleteAll();
103 | })
104 | }
105 | //populate indexedDB here
106 | data.data.forEach( note => {
107 | if (idbPromise.DB) {
108 | discoAdd(note);
109 | } else {
110 | discoConnect( () => {
111 | discoAdd(note);
112 | })
113 | }
114 | })
115 | });
116 | break;
117 | default:
118 | console.log('this method is not configured');
119 | break;
120 | }
121 | }
122 |
123 |
124 | export { discoSyncOffline, discoSyncOnline };
--------------------------------------------------------------------------------
/disco-db-demo/public/discodb.config.js:
--------------------------------------------------------------------------------
1 | const dbGlobals =
2 | {
3 | "version": 9,
4 | "databaseName": "notesDB",
5 | "storeName": "notesStore",
6 | "syncQueue": "Queue",
7 | "keypath": "_id",
8 | "onlineRoutes": [
9 | {
10 | "url": "http://localhost:3000/user/load",
11 | }
12 | ],
13 | "offlineRoutes": [
14 | {
15 | "url": "http://localhost:3000/user/load",
16 | },
17 | {
18 | "url": "http://localhost:3000/user/notes",
19 | }
20 | ]
21 | }
22 |
23 |
24 | export { dbGlobals };
--------------------------------------------------------------------------------
/disco-db-demo/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/DiscoDB/b8553c013c84aaf13f2aa7226446770921386d3c/disco-db-demo/public/favicon.ico
--------------------------------------------------------------------------------
/disco-db-demo/public/idbOperations.js:
--------------------------------------------------------------------------------
1 | import { idbPromise, dbGlobals } from './discoGlobals.js';
2 |
3 | /**
4 | * @property {Function} discoConnect Establishes connection to indexedDB & create Object Stores as specified in Configuration.
5 | * @param {Function} callback
6 | *
7 | */
8 | function discoConnect(callback) {
9 | return new Promise((resolve, reject) => {
10 | let req = indexedDB.open(dbGlobals.databaseName, dbGlobals.version)
11 | req.onerror = (err) => {
12 | //could not open db
13 | console.log('Error: ', err);
14 | idbPromise.DB = null;
15 | reject(err);
16 | };
17 | req.onupgradeneeded = (event) => {
18 | let db = event.target.result;
19 | // Database Version Upgraded
20 | if (!db.objectStoreNames.contains(dbGlobals.storeName)) {
21 | db.createObjectStore(dbGlobals.storeName, {
22 | keyPath: dbGlobals.keypath,
23 | });
24 | }
25 | if (!db.objectStoreNames.contains(dbGlobals.syncQueue)) {
26 | //Creating Object Store for Sync Queue
27 | db.createObjectStore(dbGlobals.syncQueue, {
28 | keyPath: 'id', autoIncrement: true,
29 | })
30 | }
31 | };
32 | req.onsuccess = (event) => {
33 | idbPromise.DB = event.target.result;
34 | //Database connected
35 | if (callback) {
36 | callback();
37 | }
38 | resolve(idbPromise.DB);
39 | };
40 | })
41 | };
42 |
43 | /**
44 | * @property {Function} discoAdd Adds an object in to the configured Object Store.
45 | * @param {Object} dataObject Object data to be stored.
46 | *
47 | */
48 | function discoAdd(dataObject) {
49 | // return new Promise ( (resolve, reject) => {
50 | if (dataObject && idbPromise.DB) {
51 | let tx = idbPromise.DB.transaction(dbGlobals.storeName, 'readwrite');
52 |
53 | tx.onerror = (err) => {
54 | console.log('Error:', err);
55 | // reject(err);
56 | };
57 | tx.oncomplete = (event) => {
58 | //Data added successfully
59 | };
60 |
61 | let store = tx.objectStore(dbGlobals.storeName);
62 | let req = store.put(dataObject);
63 |
64 | req.onsuccess = (event) => {
65 | // const result = event.target.result;
66 | // resolve(result);
67 | };
68 | } else {
69 | console.log('No data provided.');
70 | }
71 | // })
72 | };
73 |
74 | /**
75 | * @property {Function} discoDeleteAll Deletes all properties of the configured Object Store.
76 | *
77 | */
78 | function discoDeleteAll() {
79 | return new Promise( (resolve, reject) => {
80 | if (idbPromise.DB) {
81 | let tx = idbPromise.DB.transaction(dbGlobals.storeName, 'readwrite');
82 | tx.onerror = (err) => {
83 | console.log('Error:', err);
84 | reject(err);
85 | };
86 | tx.oncomplete = (event) => {
87 | // data deleted successfully
88 | };
89 | let store = tx.objectStore(dbGlobals.storeName);
90 | const req = store.clear();
91 | req.onsuccess = (event) => {
92 | const result = event.target.result;
93 | resolve(result);
94 | };
95 | } else {
96 | console.log('DB is not connected');
97 | }
98 | })
99 | };
100 |
101 | /**
102 | * @property {Function} discoGetAll Gets all properties saved on the configured Object Store.
103 | * @return {Array} array of objects containing all properties in the Object Store.
104 | */
105 | function discoGetAll() {
106 | return new Promise((resolve, reject) => {
107 | if (idbPromise.DB) {
108 | let tx = idbPromise.DB.transaction(dbGlobals.storeName, 'readonly');
109 | tx.onerror = (err) => {
110 | console.log('Error: ', err);
111 | reject(err);
112 | };
113 | tx.oncomplete = (event) => {
114 | //Transaction successful, all objects retrieved.
115 | };
116 | let store = tx.objectStore(dbGlobals.storeName);
117 | const req = store.getAll();
118 | req.onsuccess = (event) => {
119 | const result = event.target.result;
120 | resolve(result);
121 | };
122 | } else {
123 | console.log('DB is not connected');
124 | }
125 | })
126 | }
127 |
128 | /**
129 | * @property {Function} discoDeleteOne Deletes one property on the configured Object Store.
130 | * @param {String} id The id of the object on the configured Object Store *
131 | */
132 | function discoDeleteOne(id) {
133 | return new Promise( (resolve, reject) => {
134 | if (idbPromise.DB) {
135 | let tx = idbPromise.DB.transaction(dbGlobals.storeName, 'readwrite');
136 | tx.onerror = (err) => {
137 | console.log('Error: ', err);
138 | reject(err);
139 | };
140 | tx.oncomplete = (event) => {
141 | //Transaction successful
142 | };
143 | let store = tx.objectStore(dbGlobals.storeName);
144 | const req = store.delete(id);
145 | req.onsuccess = (event) => {
146 | const result = event.target.result;
147 | resolve(result);
148 | };
149 | } else {
150 | console.log('DB is not connected');
151 | }
152 | })
153 | }
154 |
155 | /**
156 | * @property {Function} discoUpdateOne Updates one property on the configured Object Store.
157 | * @param {Object} dataObject The req body object on the configured Object Store
158 | *
159 | */
160 | function discoUpdateOne(dataObject) {
161 | return new Promise ( (resolve, reject) => {
162 | if (idbPromise.DB) {
163 | let tx = idbPromise.DB.transaction(dbGlobals.storeName, 'readwrite');
164 | tx.onerror = (err) => {
165 | console.log('Error: ', err);
166 | reject(err);
167 | };
168 | tx.oncomplete = (event) => {
169 | //Transaction successful
170 | };
171 | let store = tx.objectStore(dbGlobals.storeName);
172 | const req = store.put(dataObject);
173 | req.onsuccess = (event) => {
174 | const result = event.target.result;
175 | resolve(result);
176 | };
177 | } else {
178 | console.log('DB is not connected');
179 | }
180 | })
181 | };
182 |
183 | export { discoConnect, discoAdd, discoDeleteAll, discoGetAll, discoDeleteOne, discoUpdateOne };
--------------------------------------------------------------------------------
/disco-db-demo/public/swCacheSite-indexedDB.js:
--------------------------------------------------------------------------------
1 | import { discoConnect } from './idbOperations.js';
2 | import { discoSyncToServer } from './backgroundSync.js';
3 | import { discoSyncOffline, discoSyncOnline } from './discoSync.js';
4 | import { onlineUrlArr, offlineUrlArr, dbGlobals, idbPromise } from './discoGlobals.js';
5 |
6 |
7 | const cacheName = 'my-site-cache-v3';
8 |
9 | self.addEventListener('install', event => {
10 | console.log('Attempting to install service worker and cache static assets');
11 | self.skipWaiting();
12 | console.log('opening DB since sw is activated')
13 | discoConnect();
14 | });
15 |
16 | self.addEventListener('activate', event => {
17 | console.log('Activating new service worker...');
18 | const cacheAllowlist = [cacheName];
19 | //Remove unwanted caches
20 | event.waitUntil(
21 | caches.keys().then(cacheNames => {
22 | return Promise.all(
23 | cacheNames.map(cacheName => {
24 | if (cacheAllowlist.indexOf(cacheName) === -1) {
25 | return caches.delete(cacheName);
26 | }
27 | })
28 | )
29 | })
30 | );
31 | //Force SW to become available to all pages
32 | event.waitUntil(self.clients.claim());
33 | });
34 |
35 | self.addEventListener('fetch', event => {
36 | //clone the request so that the body of the request will still be available
37 | const reqClone = event.request.clone();
38 | const { url, method } = event.request;
39 | event.respondWith(
40 | fetch(event.request)
41 | .then( (response) => {
42 | // Make clone of response
43 | const resCloneCache = response.clone();
44 | const resCloneDB = response.clone()
45 | //Open cache
46 | caches
47 | .open(cacheName)
48 | .then(cache => {
49 | //Add response to cache
50 | cache.put(event.request, resCloneCache);
51 | })
52 | //invoke online reducer to populate indexedDB
53 | if (onlineUrlArr.includes(url)){
54 | discoSyncOnline(method, url, resCloneDB);
55 | }
56 | return response;
57 | })
58 | // if network is unavailable
59 | .catch((err) => {
60 | //invoke offline reducer to perform RUD functions to indexedDB
61 | if (offlineUrlArr.includes(url)){
62 | return discoSyncOffline(method, url, reqClone);
63 | }
64 | return caches.match(reqClone)
65 | .then(response => {
66 | return response
67 | })
68 | })
69 | )
70 | });
71 |
72 | //When back online, listener will be invoked.
73 | //WaitUntil: waits for service workers until promise resolves
74 | //Then invoke syncData
75 | self.addEventListener('sync', (event) => {
76 | if(event.tag === 'failed_requests'){
77 | event.waitUntil(discoSyncToServer())
78 | };
79 | });
80 |
--------------------------------------------------------------------------------
/disco-db-demo/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
3 |
4 |
--------------------------------------------------------------------------------
/disco-db-demo/server/controllers/authController.js:
--------------------------------------------------------------------------------
1 | // const mongoose = require('mongoose');
2 | const bcrypt = require('bcrypt');
3 | //const responseModel = require('../models/responseModel');
4 | const { createResponse } = require('../models/responseModel');
5 | // require User object from db schema
6 | const { Users } = require('../models/model');
7 |
8 | // function to encrypt account password
9 | function encrypt(password) {
10 | const workFactor = 10;
11 | return bcrypt.hash(password, workFactor);
12 | }
13 |
14 | // auth middleware functions
15 | const authController = {
16 |
17 | signup(req, res, next) {
18 | console.log('this is in the authcontroller/signup: ', req.body)
19 | // encrypt password
20 | encrypt(req.body.password)
21 | .then(hash => {
22 | Users.create({
23 | username: req.body.username,
24 | password: hash,
25 | first_name: req.body.firstName, // double check firstName on req.body
26 | email: req.body.email
27 | },
28 | (err, result) => {
29 | if (err) {
30 | return next(err);
31 | }
32 | return next();
33 | })
34 | })
35 | },
36 |
37 |
38 | login(req, res, next) {
39 | console.log('this is in the authcontroller/login', req.body);
40 | const { username, password } = req.body;
41 |
42 | Users.findOne({ username: username },
43 | (err, result) => {
44 |
45 | //if user not found, there is no error but result is set to null
46 | //causing error when you try to destructure password from result
47 | if (result === null) {
48 | return res.status(406).json(createResponse(false, 406, 'Wrong username and/or password'));
49 | }
50 | if(err) {
51 | return next(err);
52 | }
53 | // destructure hashedPassword and compare to user's inputed password
54 | const { password: hashedPassword } = result;
55 | bcrypt.compare(password, hashedPassword, (err, bcryptRes) => {
56 | if (bcryptRes) {
57 | console.log('passwords match!');
58 | return next();
59 | } else {
60 | console.log('passwords do not match');
61 | return res.status(406).json(createResponse(false, 406, 'Wrong username and/or password'));
62 | }
63 | })
64 | });
65 | },
66 |
67 | generateCookie(req, res, next) {
68 |
69 | const { username } = req.body;
70 | res.cookie('username', username, { httpOnly: true });
71 | return next();
72 | },
73 |
74 | logout(req, res, next) {
75 | res.clearCookie('username');
76 | return next();
77 | }
78 | };
79 |
80 |
81 |
82 |
83 | // logout
84 |
85 | module.exports = authController;
--------------------------------------------------------------------------------
/disco-db-demo/server/controllers/userController.js:
--------------------------------------------------------------------------------
1 | const { Notes } = require('../models/model');
2 | const userController = {};
3 |
4 | // get all user notes from Notes collection
5 | userController.getUserNotes = (req, res, next) => {
6 | const {username} = req.cookies;
7 | // const {username} = req.body;
8 | console.log('Retrieving all notes from username: ', username)
9 | Notes.find({username: username})
10 | .then(data => {
11 | console.log(data);
12 | res.locals.data = data;
13 | return next();
14 | })
15 | .catch(err => {
16 | return next(err);
17 | })
18 | }
19 |
20 | // create a new notes entry in Notes collection
21 | userController.createAndGetNewNote = (req, res, next) => {
22 | const {username, createdAt} = req.body;
23 | console.log('Creating a new note for username: ', username)
24 | Notes.create({username: [username], title: '', content: '', updatedAt: createdAt, createdAt: createdAt})
25 | .then(data => {
26 | console.log(data);
27 | res.locals.data = data;
28 | return next();
29 | })
30 | .catch(err => {
31 | return next(err);
32 | })
33 | }
34 |
35 | // modifies an entry in Notes collection
36 | userController.modifyAndGetNote = (req, res, next) => {
37 | const {_id, title, content, updatedAt} = req.body;
38 | console.log('Modifying note _id: ', _id)
39 | Notes.findByIdAndUpdate(_id, {title: title, content: content, updatedAt: updatedAt}, {new: true})
40 | .then(data => {
41 | console.log(data);
42 | res.locals.data = data;
43 | return next();
44 | })
45 | .catch(err => {
46 | return next(err);
47 | })
48 | }
49 |
50 | // deletes an entry in Notes collection
51 | userController.deleteNote = (req, res, next) => {
52 | const {_id, username} = req.body;
53 | console.log('Deleting note _id: ', _id)
54 | Notes.deleteOne({_id: _id})
55 | .then(data => {
56 | console.log('Note successfully deleted')
57 | return next();
58 | })
59 | .catch(err => {
60 | return next(err);
61 | })
62 | }
63 |
64 | module.exports = userController;
--------------------------------------------------------------------------------
/disco-db-demo/server/controllers/weatherController.js:
--------------------------------------------------------------------------------
1 | const weatherController = {};
2 | const axios = require('axios');
3 | const { createResponse } = require('../models/responseModel');
4 |
5 | const API_KEY = '90c77ec3042549f5cc0b7aa750034457'
6 | const UNITS = 'imperial';
7 | const LANG = 'en';
8 |
9 | // make request to openweathermap API
10 | weatherController.getWeather = (req, res, next) => {
11 | let CITY = req.body.city;
12 | const weatherURL = `http://api.openweathermap.org/data/2.5/weather?q=${CITY}&lang=${LANG}&appid=${API_KEY}&units=${UNITS}`;
13 | axios({
14 | method: 'get',
15 | url: weatherURL,
16 | }).then(data => {
17 | console.log(data.data);
18 | res.locals = data.data;
19 | return next();
20 | })
21 | .catch(err => {
22 | return res.status(404).json(createResponse(false, 404, 'city not found'));
23 | // return next(err);
24 | })
25 | }
26 |
27 | module.exports = weatherController;
28 |
--------------------------------------------------------------------------------
/disco-db-demo/server/models/dbConnection.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | require('dotenv').config({path: path.resolve(__dirname, '../../.env')});
3 | const mongoose = require('mongoose');
4 | const URI = process.env.DB_CONNECTION_STRING;
5 |
6 |
7 | // unsure if the query method is what we call to query the DB
8 | // eric - changed to connection
9 | // connection method invoked in server.js to establish connection, possibly not needed
10 | module.exports = {
11 | connection: function (){
12 | mongoose.connect(URI, { useNewUrlParser: true, useUnifiedTopology: true, dbName: 'demo' });
13 | mongoose.connection.once('open', ()=>{
14 | console.log('Connected to MongoDB');
15 | });
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/disco-db-demo/server/models/model.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 |
3 | const Schema = mongoose.Schema;
4 |
5 | const userSchema = new Schema({
6 | username: {type: String, required: true, unique: true},
7 | password: {type: String, required: true},
8 | first_name: {type: String},
9 | email: {type: String}
10 | });
11 |
12 | const noteSchema = new Schema({
13 | username: {type: [String], required: true},
14 | title: {type: String},
15 | content: {type: String},
16 |
17 | // unsure if this date field is correctly written
18 | createdAt: {type: Date},
19 | updatedAt: {type: Date}
20 | })
21 |
22 | // creating model objects
23 | const Users = mongoose.model('user', userSchema);
24 | const Notes = mongoose.model('note', noteSchema);
25 |
26 | // exporting model object
27 | module.exports = { Users, Notes }
--------------------------------------------------------------------------------
/disco-db-demo/server/models/responseModel.js:
--------------------------------------------------------------------------------
1 | function createResponse(actionSuccess, statusCode = 200, message = '', data = {}) {
2 | return {
3 | actionSuccess, // boolean
4 | statusCode, // HTTP code
5 | message, // string
6 | data // object
7 | };
8 | }
9 |
10 |
11 | module.exports = { createResponse };
--------------------------------------------------------------------------------
/disco-db-demo/server/routers/apiRouter.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const router = express.Router();
3 | const weatherController = require('../controllers/weatherController')
4 | const { createResponse } = require('../models/responseModel');
5 |
6 | router.post('/weather',
7 | weatherController.getWeather,
8 | (req, res) => {
9 | return res.status(200).json(createResponse(true, 200, 'API call successful', res.locals));
10 | });
11 |
12 | module.exports = router;
13 |
--------------------------------------------------------------------------------
/disco-db-demo/server/routers/authRouter.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const router = express.Router();
3 | const authController = require('../controllers/authController')
4 | const { createResponse } = require('../models/responseModel');
5 |
6 | // Do we need this? Or will it be handled by nextJS?
7 | // router.get('/login',
8 | // (req, res) => {
9 | // res.sendStatus(200);
10 | // });
11 |
12 | // Authenticate user using bcrypt. Send error if not valid. Send session cookie once validated.
13 | router.post('/login',
14 | authController.login,
15 | authController.generateCookie,
16 | (req, res) => {
17 | return res.status(200).json(createResponse(true, 200, 'Login successful'));
18 | });
19 |
20 | // Do we need this? Or will it be handled by nextJS?
21 | // router.get('/signup',
22 | // (req, res) => {
23 | // return res.sendStatus(200);
24 | // });
25 |
26 | // Checks if username is valid. Send error if not valid. Once successfully signed up, automatically logs in user and sends session cookie
27 | router.post('/signup',
28 | authController.signup,
29 | authController.generateCookie,
30 | (req, res) => {
31 | return res.status(200).json(createResponse(true, 200, 'Signup successful'));
32 | });
33 |
34 | // end session and clear cookies middleware
35 | router.get('/logout', authController.logout,
36 | (req, res) => {
37 | return res.status(200).json(createResponse(true, 200, 'Logout successful'));
38 | });
39 |
40 | module.exports = router;
--------------------------------------------------------------------------------
/disco-db-demo/server/routers/userRouter.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const router = express.Router();
3 | const userController = require('../controllers/userController')
4 |
5 | //commenting this out so nextjs routes to page without having to login/signup each time
6 | // router.get('/',
7 | // userController.getUserNotes,
8 | // (req, res) => {
9 | // return res.status(200).json(res.locals);
10 | // });
11 |
12 | router.get('/load',
13 | userController.getUserNotes,
14 | (req, res) => {
15 | return res.status(200).json(res.locals);
16 | });
17 |
18 | // Create a new entry in notes database. Send back unique id for the new entry
19 | router.post('/notes',
20 | userController.createAndGetNewNote,
21 | (req, res) => {
22 | return res.status(200).json(res.locals);
23 | });
24 |
25 | // Modify an entry in notes database. Send back all data related to the updated entry
26 | router.patch('/notes',
27 | userController.modifyAndGetNote,
28 | (req, res) => {
29 | res.status(200).json(res.locals);
30 | });
31 |
32 | // Delete an entry in notes database.
33 | router.delete('/notes',
34 | userController.deleteNote,
35 | (req, res) => {
36 | res.status(200).json({});
37 | });
38 |
39 | module.exports = router
--------------------------------------------------------------------------------
/disco-db-demo/server/server.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const cookieParser = require('cookie-parser');
3 | const next = require('next');
4 |
5 | const port = parseInt(process.env.PORT, 10) || 3000;
6 | const dev = process.env.NODE_ENV !== 'production';
7 | const app = next({ dev });
8 | const handle = app.getRequestHandler();
9 | const db = require('./models/dbConnection.js');
10 |
11 | const authRouter = require('./routers/authRouter');
12 | const userRouter = require('./routers/userRouter');
13 | const apiRouter = require('./routers/apiRouter');
14 | const { sendData } = require('next/dist/server/api-utils');
15 |
16 |
17 | app.prepare().then(() => {
18 | const server = express();
19 |
20 | // Allows server to process incoming JSON, form data into the req.body, cookies
21 | server.use(express.json());
22 | server.use(express.urlencoded({extended: true}));
23 | server.use(cookieParser()); // add cookieParser in when cookies implemented
24 |
25 | // establish connection to our MongoDB cluster
26 | // validate this is necessary while testing authRouter
27 | db.connection();
28 |
29 | server.use(cookieParser());
30 | server.use(express.json());
31 | server.use('/auth', authRouter);
32 | server.use('/user', userRouter);
33 | server.use('/api', apiRouter);
34 |
35 | server.get('/', (req, res) => {
36 | // console.log('this is to the root endpoint');
37 | return handle(req, res);
38 | });
39 |
40 | server.all('*', (req, res) => {
41 | // console.log('this is a request to the server');
42 | return handle(req, res);
43 | });
44 |
45 | // local error handler
46 | server.use((req, res) => {
47 | res.status(404).send('Not Found')
48 | });
49 |
50 | // global error handler
51 | server.use((err, req, res, next) => {
52 | const defaultErr = {
53 | log: 'Express error handler caught unknown middleware error',
54 | status: 500,
55 | message: { err: 'An error occurred' },
56 | };
57 | const errorObj = Object.assign({}, defaultErr, err);
58 | console.log(errorObj.log);
59 | return res.status(errorObj.status).json(errorObj.message);
60 | });
61 |
62 | server.listen(port, (err) => {
63 | if (err) throw err;
64 | console.log(`> Ready on http://localhost:${port}`);
65 | });
66 | });
67 |
68 | module.exports = app;
69 |
--------------------------------------------------------------------------------
/disco-db-demo/styles/Home.module.css:
--------------------------------------------------------------------------------
1 | .container {
2 | padding: 0 2rem;
3 | }
4 |
5 | .main {
6 | min-height: 100vh;
7 | padding: 4rem 0;
8 | flex: 1;
9 | display: flex;
10 | flex-direction: column;
11 | justify-content: center;
12 | align-items: center;
13 | }
14 |
15 | .footer {
16 | display: flex;
17 | flex: 1;
18 | padding: 2rem 0;
19 | border-top: 1px solid #eaeaea;
20 | justify-content: center;
21 | align-items: center;
22 | }
23 |
24 | .footer a {
25 | display: flex;
26 | justify-content: center;
27 | align-items: center;
28 | flex-grow: 1;
29 | }
30 |
31 | .title a {
32 | color: #0070f3;
33 | text-decoration: none;
34 | }
35 |
36 | .title a:hover,
37 | .title a:focus,
38 | .title a:active {
39 | text-decoration: underline;
40 | }
41 |
42 | .title {
43 | margin: 0;
44 | line-height: 1.15;
45 | font-size: 4rem;
46 | }
47 |
48 | .title,
49 | .description {
50 | text-align: center;
51 | }
52 |
53 | .description {
54 | margin: 4rem 0;
55 | line-height: 1.5;
56 | font-size: 1.5rem;
57 | }
58 |
59 | .code {
60 | background: #fafafa;
61 | border-radius: 5px;
62 | padding: 0.75rem;
63 | font-size: 1.1rem;
64 | font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
65 | Bitstream Vera Sans Mono, Courier New, monospace;
66 | }
67 |
68 | .grid {
69 | display: flex;
70 | align-items: center;
71 | justify-content: center;
72 | flex-wrap: wrap;
73 | max-width: 800px;
74 | }
75 |
76 | .card {
77 | margin: 1rem;
78 | padding: 1.5rem;
79 | text-align: left;
80 | color: inherit;
81 | text-decoration: none;
82 | border: 1px solid #eaeaea;
83 | border-radius: 10px;
84 | transition: color 0.15s ease, border-color 0.15s ease;
85 | max-width: 300px;
86 | }
87 |
88 | .card:hover,
89 | .card:focus,
90 | .card:active {
91 | color: #0070f3;
92 | border-color: #0070f3;
93 | }
94 |
95 | .card h2 {
96 | margin: 0 0 1rem 0;
97 | font-size: 1.5rem;
98 | }
99 |
100 | .card p {
101 | margin: 0;
102 | font-size: 1.25rem;
103 | line-height: 1.5;
104 | }
105 |
106 | .logo {
107 | height: 1em;
108 | margin-left: 0.5rem;
109 | }
110 |
111 | @media (max-width: 600px) {
112 | .grid {
113 | width: 100%;
114 | flex-direction: column;
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/disco-db-demo/styles/globals.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | padding: 0;
4 | margin: 0;
5 | font-family: Roboto, -apple-system, BlinkMacSystemFont, Segoe UI, Oxygen,
6 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
7 | }
8 |
9 | a {
10 | color: inherit;
11 | text-decoration: none;
12 | }
13 |
14 | * {
15 | box-sizing: border-box;
16 | }
17 |
--------------------------------------------------------------------------------
/discodb/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 OSLabs Beta
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/discodb/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | # DiscoDB:
6 |
7 | ## Table of Contents
8 |
9 | - [Features](#features)
10 |
11 | - [Installation](#installation)
12 |
13 | - [How It works](#how-it-works)
14 |
15 | - [Demo Apps](#demo-app)
16 |
17 | - [Contributing](#contributing)
18 |
19 | - [Authors](#authors)
20 |
21 | - [License](#license)
22 |
23 | ## Features
24 |
25 | - A minimalist IndexedDB (IDB) wrapper and syncing solution for when your application disco(nnects) from the network.
26 | - Lightweight with zero dependencies.
27 | - Functionalities can be implemented via Service Workers with minimal modification to client side code
28 | - Supports syncing IDB with NoSQL databases (MongoDB) with a unique keypath
29 | - Promise based wrapper for IDB to perform local CRUD operations while offline to provide seamless UX
30 | - Sync to main database via action queue with automatic network availability detection.
31 |
32 |
33 |
34 | Offline Capabilities
35 |
36 | 
37 |
38 |
39 |
40 | Dynamic Offline Data
41 |
42 | 
43 |
44 |
45 |
46 | Custom Action Queue
47 |
48 | 
49 |
50 |
51 |
52 | ## Installation
53 |
54 | ```bash
55 | npm install disco-db
56 | ```
57 | ### Import the Library into the Service Worker
58 | Assuming a bundler such as Webpack, Rollup, etc is used:
59 | ```js
60 | import { discoConnect, discoSyncToServer, discoSyncOffline, discoSyncOnline, onlineUrlArr, offlineUrlArr, dbGlobals, idbPromise } from 'discodb';
61 | ```
62 | When registering the service worker, pass in an option object with property ``type:'module'``
63 | ```js
64 | if("serviceWorker" in navigator) {
65 | window.addEventListener("load", function () {
66 | navigator.serviceWorker.register("sw.js", {type: 'module'})
67 | ```
68 | ## How It works
69 |
70 | ### Setting up the Config File
71 | Our library requires some minimal configuration. In the root directory of the project, create a file labeled ``discodb.config.js`` and update the values of the corresponding key property.
72 | ```js
73 | // discodb.config.js
74 |
75 | const dbGlobals =
76 | {
77 | version: "IDB version",
78 | databaseName: "IDB database name",
79 | storeName: "IDB Object Store name",
80 | syncQueue: "IDB Object Store Queue name",
81 | keypath: "Primary key of main database table",
82 | // Add all routes to be intercepted
83 | onlineRoutes: [
84 | {
85 | url: "",
86 | }
87 | ],
88 | offlineRoutes: [
89 | {
90 | url: " ",
91 | },
92 | {
93 | url: " ",
94 | }
95 | ]
96 | }
97 |
98 | export { dbGlobals };
99 |
100 | ```
101 |
102 | ### DiscoSyncOffline & DiscoSyncOnline
103 |
104 | ``discoSyncOffline(method, url, clonedRequest)``
105 |
106 | 1. ``discoSyncOffline`` is a request reducer that intercepts fetch requests and implements CRUD operations on the passed in endpoints.
107 |
108 | 2. ``discoSyncOffline`` takes in three parameters, the method and url of the ``event.request`` as well as a clone of the ``event.request``, utilizing the ``.clone()`` method.
109 |
110 | 3. Under the hood, ``discoSyncOffline`` will check the url, and perform a **GET**, **DELETE**, or **PATCH** operation with indexedDB and return a new **Response** object back to the client.
111 |
112 |
113 | ``discoSyncOnline(method, url, clonedResponse)``
114 |
115 | 1. ``discoSyncOnline`` establishes a connection to indexedDB and populates the object store with your noSQL data.
116 |
117 | 2. ``discoSyncOnline`` takes in three paramaters, the method and url of the ``event.request`` as well as a clone of the ``response`` that was sent back from the server.
118 |
119 | 3. Under the hood, ``discoSyncOnline`` will check the url passed in and first clear indexedDB of any stale data, and repopulate with the response body that it received back from the server.
120 |
121 |
122 | ### Initializing IndexedDB Database
123 | To initialize an idb with attributes passed into ```discodb.config.js```, invoke ``` discoConnect()``` when installing the service worker
124 | ```js
125 | self.addEventListener('install', event => {
126 | discoConnect();
127 | });
128 | ```
129 |
130 | ### Intercepting Event Requests
131 | ```discoSyncOffline()``` and ```discoSyncOnline()``` require the following to be true:
132 | 1. Network falling back to cache Service Worker strategy: [Documentation](https://developers.google.com/web/ilt/pwa/caching-files-with-service-worker)
133 | 2. Response and Request objects passed into ```discoSyncOffline()``` and ```discoSyncOnline()``` respectively will need to be a cloned version.
134 |
135 | ```onlineUrlArr``` and ```offlineUrlArr``` will include the ```onlineRoutes``` and ```offlineRoutes``` from ```discodb.config.js ``` respectively in an Array.
136 | ```js
137 | self.addEventListener('fetch', event => {
138 | // clone the request
139 | const reqClone = event.request.clone();
140 | event.respondWith(
141 | // network first approach
142 | fetch(event.request)
143 | .then((response) => {
144 | // clone the response
145 | const resCloneCache = response.clone();
146 | const resCloneDB = response.clone()
147 | // open caches and store resCloneCache
148 | // ...
149 | // intercept routes included in onlineUrlArr
150 | if (onlineUrlArr.includes(url)){
151 | discoSyncOnline(method, url, resCloneDB);
152 | }
153 | return response;
154 | })
155 | // Fallback to Cache
156 | .catch((err) => {
157 | //invoke offline reducer to perform RUD functions to indexedDB
158 | if (offlineUrlArr.includes(url)){
159 | return discoSyncOffline(method, url, reqClone);
160 | }
161 | // return cache
162 | // ...
163 | })
164 | )
165 | });
166 | ```
167 |
168 | ### Implementing the Action Queue Synchonization
169 | You can also use the synchronization queue separately from our reducers! Make sure to ``discoConnect()`` to the IDB database and have your configuration file ready.
170 | 1. Set up an event handler to listen for "***sync***" and a conditional to catch our tag, "***discoSync***".
171 | 1. Request for a synchronization event by registering through the service worker through ``discoRegisterSync()``. By default, the sync tag assigned to the request is '***discoSync***'.
172 | 1. Once a sync request has been registered, we now can add an object containing the HTTP request to the Object Store Queue by invoking ``discoAddToQueue(object)``.
173 | * Object format must contain these properties
174 | ```js
175 | {
176 | url: "Route URL",
177 | method: "HTTP Method",
178 | body: "data object from HTTP request"
179 | }
180 | ```
181 | 1. Now that the Object Store Queue has been populated with HTTP requests, we can send them to the main server! Within the event handler for sync, invoke ``discoSyncToServer()``.
182 | ```js
183 | self.addEventListener('sync', (event) => {
184 | if(event.tag === 'discoSync'){
185 | discoSyncToServer();
186 | };
187 | };
188 | ```
189 |
190 | ## Demo App
191 | Our demostration application utilizing this library is a simple note taking app. The demo app relies on service workers intercepting all HTTP requests for both online and offline requests.
192 |
193 | Fork and clone our repository onto your local repository and follow the README in the app.
194 |
195 | ## Contributing
196 |
197 | We'd love for you to test this library out and submit any issues you encounter. Also feel free to fork to your own repo and submit pull requests!
198 |
199 | ## Authors
200 |
201 |
202 | [Eric Gomez](https://github.com/ergomez0201) | [LinkedIn](https://www.linkedin.com/in/eric-gomez/)
203 |
204 |
205 | [Eric McCorkle](https://github.com/ericmccorkle) | [LinkedIn](https://www.linkedin.com/in/eric-mccorkle/)
206 |
207 |
208 | [Jackson Tong](https://github.com/jacksonktong) | [LinkedIn](www.linkedin.com/in/jacksonktong)
209 |
210 |
211 | [Young Min Lee](https://github.com/youngmineeh) | [LinkedIn](www.linkedin.com/in/youngminlee-)
212 |
213 |
214 |
215 | ## License
216 |
217 | DiscoDB is [MIT licensed](https://github.com/oslabs-beta/discodb/blob/main/LICENSE.md).
218 |
219 |
220 |
221 |
--------------------------------------------------------------------------------
/discodb/discoFunctions/backgroundSync.js:
--------------------------------------------------------------------------------
1 | import { idbPromise, dbGlobals } from '../../../discodb.config.js';
2 |
3 | /**
4 | * @property {Function} accessObjectStore Access object store in IDB database and start a transaction
5 | * @param {String} storeName Object store to be accessed for transaction
6 | * @param {String} method Method for transaction, "readwrite, readonly"
7 | * @return {Object} Accessed Object store Object
8 | */
9 | function accessObjectStore (storeName, method) {
10 | return idbPromise.DB.transaction([storeName], method).objectStore(storeName)
11 | };
12 | /**
13 | * @property {Function} discoAddToQueue Adds Object into Object store
14 | * @param {Object} dataObject Objected to be added to Object store
15 | *
16 | */
17 | function discoAddToQueue (dataObject) {
18 | //Open a transaction to object store 'Queue'
19 | const store = accessObjectStore(dbGlobals.syncQueue, 'readwrite')
20 | //Add data to object store
21 | store.add(dataObject)
22 | };
23 | /**
24 | * @property {Function} discoRegisterSync Request a "Sync" event to reattempt request when network is online.
25 | *
26 | */
27 | async function discoRegisterSync() {
28 | try {
29 | const register = await registration.sync.register('discoSync');
30 | return register;
31 | } catch(error) {
32 | console.log('Error:' , error);
33 | return error;
34 | }
35 | };
36 |
37 | /**
38 | * @property {Function} discoSyncToServer Accesses the Queue Object Store and re-sends all requests saved in application/json.
39 | *
40 | */
41 | function discoSyncToServer() {
42 | const store = accessObjectStore(dbGlobals.syncQueue, 'readwrite');
43 | const request = store.getAll();
44 |
45 | request.onsuccess = function (event) {
46 | const httpQueue = event.target.result;
47 | //Comes back as an array of objects
48 | //Iterate Queue store and initialize Fetch request
49 | httpQueue.forEach((data) => {
50 | const { url, method, body } = data
51 | const headers = {'Content-Type': 'application/json'};
52 | fetch(url, {
53 | method: method,
54 | headers: headers,
55 | body: JSON.stringify(body)
56 | })
57 | .then((res) => res.json())
58 | .then((res) => {
59 | //Previous transaction was closed due to getAll()
60 | //Reopen object store and delete the corresponding object on successful HTTP request
61 | const newStore = accessObjectStore(dbGlobals.syncQueue, 'readwrite');
62 | newStore.delete(data.id);
63 | })
64 | .catch((error) => {
65 | console.error('Failed to sync data to server:', error);
66 | throw error
67 | })
68 | });
69 | }
70 | request.onerror = (err) => {
71 | console.log('Attempt to sync queue failed:', err);
72 | }
73 | };
74 |
75 | export { discoAddToQueue, discoRegisterSync, discoSyncToServer };
76 |
--------------------------------------------------------------------------------
/discodb/discoFunctions/discoGlobals.js:
--------------------------------------------------------------------------------
1 | import { dbGlobals } from '../../../discodb.config.js'
2 |
3 | const idbPromise = {
4 | DB: null
5 | }
6 |
7 | const onlineUrlArr = [];
8 | dbGlobals.onlineRoutes.forEach(el => {
9 | onlineUrlArr.push(el.url)
10 | });
11 |
12 | const offlineUrlArr = [];
13 | dbGlobals.offlineRoutes.forEach(el => {
14 | offlineUrlArr.push(el.url);
15 | });
16 |
17 | export { dbGlobals, idbPromise, onlineUrlArr, offlineUrlArr }
--------------------------------------------------------------------------------
/discodb/discoFunctions/discoSync.js:
--------------------------------------------------------------------------------
1 | import { discoConnect, discoGetAll, discoDeleteOne, discoUpdateOne, discoAdd, discoDeleteAll } from './idbOperations.js';
2 | import { discoAddToQueue, discoRegisterSync } from './backgroundSync.js';
3 | import { idbPromise , dbGlobals } from './discoGlobals.js';
4 |
5 | /**
6 | * @property {Function} discoSyncOffline Executes different IndexedDB logic based on the value of passed in method
7 | * @param {String} method This is the method property of the intercepted fetch request
8 | * @param {String} url This is the url property of the intercepted fetch request
9 | * @param {String} store This is the store property associated with the url provided in the config file
10 | * @param {Request} eventRequest This is the cloned version of the intercepted fetch request
11 | *
12 | */
13 | function discoSyncOffline(method, url, clonedRequest) {
14 | switch(method) {
15 | case 'GET':
16 | if (idbPromise.DB) {
17 | return discoGetAll().then((data) => {
18 | const responseBody = { data };
19 | const IDBData = new Response(JSON.stringify(responseBody));
20 | return IDBData;
21 | })
22 | } else {
23 | return discoConnect(() => {
24 | discoGetAll().then((data) => {
25 | const responseBody = {data: data};
26 | const IDBData = new Response(JSON.stringify(responseBody));
27 | return IDBData;
28 | });
29 | })
30 | }
31 | case 'DELETE':
32 | return clonedRequest.json()
33 | .then((data) => {
34 | const reqBody = {
35 | url: url,
36 | method: method,
37 | body: data
38 | };
39 | discoRegisterSync();
40 | discoAddToQueue(reqBody);
41 | const keypath = dbGlobals.keypath;
42 | const id = data[keypath];
43 | discoDeleteOne(id);
44 | const deleteResponse = new Response(JSON.stringify({}));
45 | return deleteResponse;
46 | })
47 | .catch( err => {
48 | console.log('Error in DELETE block: ', err);
49 | })
50 | case 'PATCH':
51 | return clonedRequest.json()
52 | .then((data) => {
53 | const reqBody = {
54 | url: url,
55 | method: method,
56 | body: data
57 | };
58 | discoRegisterSync();
59 | discoAddToQueue(reqBody);
60 | const keypath = dbGlobals.keypath;
61 | const id = data[keypath];
62 | discoUpdateOne(data);
63 | // returns empty object to trigger rerender in our app
64 | const patchResponse = new Response(JSON.stringify({}));
65 | return patchResponse;
66 | })
67 | default:
68 | return caches.match(clonedRequest)
69 | .then(response => {
70 | return response
71 | })
72 | }
73 | }
74 |
75 | /**
76 | * @property {Function} discoSyncOnline Establishes connection to indexedDB & create Object Stores as specified in Configuration.
77 | * @param {String} method This is the method property of the intercepted fetch request
78 | * @param {String} url This is the url property of the intercepted fetch request
79 | * @param {String} store This is the store property associated with the url provided in the config file
80 | * @param {Request} clonedResponse This is the cloned version of the intercepted fetch response
81 | *
82 | */
83 | function discoSyncOnline(method, url, clonedResponse) {
84 | switch(method) {
85 | case 'GET':
86 | const resCloneDB = clonedResponse;
87 | resCloneDB.json().then(data => {
88 | if (idbPromise.DB) {
89 | discoDeleteAll();
90 | } else {
91 | discoConnect( () => {
92 | discoDeleteAll();
93 | })
94 | }
95 | //populate indexedDB here
96 | data.data.forEach( note => {
97 | if (idbPromise.DB) {
98 | discoAdd(note);
99 | } else {
100 | discoConnect(() => {
101 | discoAdd(note);
102 | })
103 | }
104 | })
105 | });
106 | break;
107 | default:
108 | break;
109 | }
110 | }
111 |
112 | export { discoSyncOffline, discoSyncOnline };
--------------------------------------------------------------------------------
/discodb/discoFunctions/idbOperations.js:
--------------------------------------------------------------------------------
1 | import { idbPromise, dbGlobals } from './discoGlobals.js';
2 |
3 | /**
4 | * @property {Function} discoConnect Establishes connection to indexedDB & create Object Stores as specified in Configuration.
5 | * @param {Function} callback
6 | *
7 | */
8 | function discoConnect(callback) {
9 | return new Promise((resolve, reject) => {
10 | let req = indexedDB.open(dbGlobals.databaseName, dbGlobals.version)
11 | req.onerror = (err) => {
12 | //could not open db
13 | console.log('Error: ', err);
14 | idbPromise.DB = null;
15 | reject(err);
16 | };
17 | req.onupgradeneeded = (event) => {
18 | let db = event.target.result;
19 | // Database Version Upgraded
20 | if (!db.objectStoreNames.contains(dbGlobals.storeName)) {
21 | db.createObjectStore(dbGlobals.storeName, {
22 | keyPath: dbGlobals.keypath,
23 | });
24 | }
25 | if (!db.objectStoreNames.contains(dbGlobals.syncQueue)) {
26 | //Creating Object Store for Sync Queue
27 | db.createObjectStore(dbGlobals.syncQueue, {
28 | keyPath: 'id', autoIncrement: true,
29 | })
30 | }
31 | };
32 | req.onsuccess = (event) => {
33 | idbPromise.DB = event.target.result;
34 | //Database connected
35 | if (callback) {
36 | callback();
37 | }
38 | resolve(idbPromise.DB);
39 | };
40 | })
41 | };
42 |
43 | /**
44 | * @property {Function} discoAdd Adds an object in to the configured Object Store.
45 | * @param {Object} dataObject Object data to be stored.
46 | *
47 | */
48 | function discoAdd(dataObject) {
49 | return new Promise ( (resolve, reject) => {
50 | if (dataObject && idbPromise.DB) {
51 | let tx = idbPromise.DB.transaction(dbGlobals.storeName, 'readwrite');
52 |
53 | tx.onerror = (err) => {
54 | console.log('Error:', err);
55 | reject(err);
56 | };
57 | tx.oncomplete = (event) => {
58 | //Data added successfully
59 | };
60 |
61 | let store = tx.objectStore(dbGlobals.storeName);
62 | let req = store.put(dataObject);
63 |
64 | req.onsuccess = (event) => {
65 | const result = event.target.result;
66 | resolve(result);
67 | };
68 | } else {
69 | console.log('DB is not connected');
70 | }
71 | })
72 | };
73 |
74 | /**
75 | * @property {Function} discoDeleteAll Deletes all properties of the configured Object Store.
76 | *
77 | */
78 | function discoDeleteAll() {
79 | return new Promise( (resolve, reject) => {
80 | if (idbPromise.DB) {
81 | let tx = idbPromise.DB.transaction(dbGlobals.storeName, 'readwrite');
82 | tx.onerror = (err) => {
83 | console.log('Error:', err);
84 | reject(err);
85 | };
86 | tx.oncomplete = (event) => {
87 | // data deleted successfully
88 | };
89 | let store = tx.objectStore(dbGlobals.storeName);
90 | const req = store.clear();
91 | req.onsuccess = (event) => {
92 | const result = event.target.result;
93 | resolve(result);
94 | };
95 | } else {
96 | console.log('DB is not connected');
97 | }
98 | })
99 | };
100 |
101 | /**
102 | * @property {Function} discoGetAll Gets all properties saved on the configured Object Store.
103 | * @return {Array} array of objects containing all properties in the Object Store.
104 | */
105 | function discoGetAll() {
106 | return new Promise((resolve, reject) => {
107 | if (idbPromise.DB) {
108 | let tx = idbPromise.DB.transaction(dbGlobals.storeName, 'readonly');
109 | tx.onerror = (err) => {
110 | console.log('Error: ', err);
111 | reject(err);
112 | };
113 | tx.oncomplete = (event) => {
114 | //Transaction successful, all objects retrieved.
115 | };
116 | let store = tx.objectStore(dbGlobals.storeName);
117 | const req = store.getAll();
118 | req.onsuccess = (event) => {
119 | const result = event.target.result;
120 | resolve(result);
121 | };
122 | } else {
123 | console.log('DB is not connected');
124 | }
125 | })
126 | }
127 |
128 | /**
129 | * @property {Function} discoDeleteOne Deletes one property on the configured Object Store.
130 | * @param {String} id The id of the object on the configured Object Store *
131 | */
132 | function discoDeleteOne(id) {
133 | return new Promise( (resolve, reject) => {
134 | if (idbPromise.DB) {
135 | let tx = idbPromise.DB.transaction(dbGlobals.storeName, 'readwrite');
136 | tx.onerror = (err) => {
137 | console.log('Error: ', err);
138 | reject(err);
139 | };
140 | tx.oncomplete = (event) => {
141 | //Transaction successful
142 | };
143 | let store = tx.objectStore(dbGlobals.storeName);
144 | const req = store.delete(id);
145 | req.onsuccess = (event) => {
146 | const result = event.target.result;
147 | resolve(result);
148 | };
149 | } else {
150 | console.log('DB is not connected');
151 | }
152 | })
153 | }
154 |
155 | /**
156 | * @property {Function} discoUpdateOne Updates one property on the configured Object Store.
157 | * @param {Object} dataObject The req body object on the configured Object Store
158 | *
159 | */
160 | function discoUpdateOne(dataObject) {
161 | return new Promise ( (resolve, reject) => {
162 | if (idbPromise.DB) {
163 | let tx = idbPromise.DB.transaction(dbGlobals.storeName, 'readwrite');
164 | tx.onerror = (err) => {
165 | console.log('Error: ', err);
166 | reject(err);
167 | };
168 | tx.oncomplete = (event) => {
169 | //Transaction successful
170 | };
171 | let store = tx.objectStore(dbGlobals.storeName);
172 | const req = store.put(dataObject);
173 | req.onsuccess = (event) => {
174 | const result = event.target.result;
175 | resolve(result);
176 | };
177 | } else {
178 | console.log('DB is not connected');
179 | }
180 | })
181 | };
182 |
183 | export { discoConnect, discoAdd, discoDeleteAll, discoGetAll, discoDeleteOne, discoUpdateOne };
184 |
--------------------------------------------------------------------------------
/discodb/index.js:
--------------------------------------------------------------------------------
1 | import {
2 | discoSyncOffline,
3 | discoSyncOnline
4 | } from './discoFunctions/discoSync';
5 |
6 | import {
7 | discoConnect,
8 | discoAdd,
9 | discoDeleteAll,
10 | discoGetAll,
11 | discoDeleteOne,
12 | discoUpdateOne
13 | } from './discoFunctions/idbOperations';
14 |
15 | import {
16 | discoAddToQueue,
17 | discoRegisterSync,
18 | discoSyncToServer
19 | } from './discoFunctions/backgroundSync';
20 |
21 | export {
22 | discoSyncOffline,
23 | discoSyncOnline,
24 | discoConnect,
25 | discoAdd,
26 | discoDeleteAll,
27 | discoGetAll,
28 | discoDeleteOne,
29 | discoUpdateOne,
30 | discoAddToQueue,
31 | discoRegisterSync,
32 | discoSyncToServer }
--------------------------------------------------------------------------------
/discodb/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "disco-db",
3 | "version": "1.0.3",
4 | "lockfileVersion": 2,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "disco-db",
9 | "version": "1.0.3",
10 | "license": "MIT",
11 | "dependencies": {
12 | "@rollup/plugin-commonjs": "^21.0.2"
13 | },
14 | "devDependencies": {
15 | "@rollup/plugin-babel": "^5.3.1",
16 | "@rollup/plugin-node-resolve": "^13.1.3",
17 | "rollup": "^2.70.1",
18 | "rollup-plugin-node-polyfills": "^0.2.1",
19 | "rollup-plugin-terser": "^7.0.2"
20 | }
21 | },
22 | "node_modules/@ampproject/remapping": {
23 | "version": "2.1.2",
24 | "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.1.2.tgz",
25 | "integrity": "sha512-hoyByceqwKirw7w3Z7gnIIZC3Wx3J484Y3L/cMpXFbr7d9ZQj2mODrirNzcJa+SM3UlpWXYvKV4RlRpFXlWgXg==",
26 | "dev": true,
27 | "peer": true,
28 | "dependencies": {
29 | "@jridgewell/trace-mapping": "^0.3.0"
30 | },
31 | "engines": {
32 | "node": ">=6.0.0"
33 | }
34 | },
35 | "node_modules/@babel/code-frame": {
36 | "version": "7.16.7",
37 | "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz",
38 | "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==",
39 | "dev": true,
40 | "dependencies": {
41 | "@babel/highlight": "^7.16.7"
42 | },
43 | "engines": {
44 | "node": ">=6.9.0"
45 | }
46 | },
47 | "node_modules/@babel/compat-data": {
48 | "version": "7.17.7",
49 | "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.17.7.tgz",
50 | "integrity": "sha512-p8pdE6j0a29TNGebNm7NzYZWB3xVZJBZ7XGs42uAKzQo8VQ3F0By/cQCtUEABwIqw5zo6WA4NbmxsfzADzMKnQ==",
51 | "dev": true,
52 | "peer": true,
53 | "engines": {
54 | "node": ">=6.9.0"
55 | }
56 | },
57 | "node_modules/@babel/core": {
58 | "version": "7.17.7",
59 | "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.17.7.tgz",
60 | "integrity": "sha512-djHlEfFHnSnTAcPb7dATbiM5HxGOP98+3JLBZtjRb5I7RXrw7kFRoG2dXM8cm3H+o11A8IFH/uprmJpwFynRNQ==",
61 | "dev": true,
62 | "peer": true,
63 | "dependencies": {
64 | "@ampproject/remapping": "^2.1.0",
65 | "@babel/code-frame": "^7.16.7",
66 | "@babel/generator": "^7.17.7",
67 | "@babel/helper-compilation-targets": "^7.17.7",
68 | "@babel/helper-module-transforms": "^7.17.7",
69 | "@babel/helpers": "^7.17.7",
70 | "@babel/parser": "^7.17.7",
71 | "@babel/template": "^7.16.7",
72 | "@babel/traverse": "^7.17.3",
73 | "@babel/types": "^7.17.0",
74 | "convert-source-map": "^1.7.0",
75 | "debug": "^4.1.0",
76 | "gensync": "^1.0.0-beta.2",
77 | "json5": "^2.1.2",
78 | "semver": "^6.3.0"
79 | },
80 | "engines": {
81 | "node": ">=6.9.0"
82 | },
83 | "funding": {
84 | "type": "opencollective",
85 | "url": "https://opencollective.com/babel"
86 | }
87 | },
88 | "node_modules/@babel/generator": {
89 | "version": "7.17.7",
90 | "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.7.tgz",
91 | "integrity": "sha512-oLcVCTeIFadUoArDTwpluncplrYBmTCCZZgXCbgNGvOBBiSDDK3eWO4b/+eOTli5tKv1lg+a5/NAXg+nTcei1w==",
92 | "dev": true,
93 | "peer": true,
94 | "dependencies": {
95 | "@babel/types": "^7.17.0",
96 | "jsesc": "^2.5.1",
97 | "source-map": "^0.5.0"
98 | },
99 | "engines": {
100 | "node": ">=6.9.0"
101 | }
102 | },
103 | "node_modules/@babel/generator/node_modules/source-map": {
104 | "version": "0.5.7",
105 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
106 | "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
107 | "dev": true,
108 | "peer": true,
109 | "engines": {
110 | "node": ">=0.10.0"
111 | }
112 | },
113 | "node_modules/@babel/helper-compilation-targets": {
114 | "version": "7.17.7",
115 | "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.17.7.tgz",
116 | "integrity": "sha512-UFzlz2jjd8kroj0hmCFV5zr+tQPi1dpC2cRsDV/3IEW8bJfCPrPpmcSN6ZS8RqIq4LXcmpipCQFPddyFA5Yc7w==",
117 | "dev": true,
118 | "peer": true,
119 | "dependencies": {
120 | "@babel/compat-data": "^7.17.7",
121 | "@babel/helper-validator-option": "^7.16.7",
122 | "browserslist": "^4.17.5",
123 | "semver": "^6.3.0"
124 | },
125 | "engines": {
126 | "node": ">=6.9.0"
127 | },
128 | "peerDependencies": {
129 | "@babel/core": "^7.0.0"
130 | }
131 | },
132 | "node_modules/@babel/helper-environment-visitor": {
133 | "version": "7.16.7",
134 | "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz",
135 | "integrity": "sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==",
136 | "dev": true,
137 | "peer": true,
138 | "dependencies": {
139 | "@babel/types": "^7.16.7"
140 | },
141 | "engines": {
142 | "node": ">=6.9.0"
143 | }
144 | },
145 | "node_modules/@babel/helper-function-name": {
146 | "version": "7.16.7",
147 | "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz",
148 | "integrity": "sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==",
149 | "dev": true,
150 | "peer": true,
151 | "dependencies": {
152 | "@babel/helper-get-function-arity": "^7.16.7",
153 | "@babel/template": "^7.16.7",
154 | "@babel/types": "^7.16.7"
155 | },
156 | "engines": {
157 | "node": ">=6.9.0"
158 | }
159 | },
160 | "node_modules/@babel/helper-get-function-arity": {
161 | "version": "7.16.7",
162 | "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz",
163 | "integrity": "sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==",
164 | "dev": true,
165 | "peer": true,
166 | "dependencies": {
167 | "@babel/types": "^7.16.7"
168 | },
169 | "engines": {
170 | "node": ">=6.9.0"
171 | }
172 | },
173 | "node_modules/@babel/helper-hoist-variables": {
174 | "version": "7.16.7",
175 | "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz",
176 | "integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==",
177 | "dev": true,
178 | "peer": true,
179 | "dependencies": {
180 | "@babel/types": "^7.16.7"
181 | },
182 | "engines": {
183 | "node": ">=6.9.0"
184 | }
185 | },
186 | "node_modules/@babel/helper-module-imports": {
187 | "version": "7.16.7",
188 | "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz",
189 | "integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==",
190 | "dev": true,
191 | "dependencies": {
192 | "@babel/types": "^7.16.7"
193 | },
194 | "engines": {
195 | "node": ">=6.9.0"
196 | }
197 | },
198 | "node_modules/@babel/helper-module-transforms": {
199 | "version": "7.17.7",
200 | "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.17.7.tgz",
201 | "integrity": "sha512-VmZD99F3gNTYB7fJRDTi+u6l/zxY0BE6OIxPSU7a50s6ZUQkHwSDmV92FfM+oCG0pZRVojGYhkR8I0OGeCVREw==",
202 | "dev": true,
203 | "peer": true,
204 | "dependencies": {
205 | "@babel/helper-environment-visitor": "^7.16.7",
206 | "@babel/helper-module-imports": "^7.16.7",
207 | "@babel/helper-simple-access": "^7.17.7",
208 | "@babel/helper-split-export-declaration": "^7.16.7",
209 | "@babel/helper-validator-identifier": "^7.16.7",
210 | "@babel/template": "^7.16.7",
211 | "@babel/traverse": "^7.17.3",
212 | "@babel/types": "^7.17.0"
213 | },
214 | "engines": {
215 | "node": ">=6.9.0"
216 | }
217 | },
218 | "node_modules/@babel/helper-simple-access": {
219 | "version": "7.17.7",
220 | "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.17.7.tgz",
221 | "integrity": "sha512-txyMCGroZ96i+Pxr3Je3lzEJjqwaRC9buMUgtomcrLe5Nd0+fk1h0LLA+ixUF5OW7AhHuQ7Es1WcQJZmZsz2XA==",
222 | "dev": true,
223 | "peer": true,
224 | "dependencies": {
225 | "@babel/types": "^7.17.0"
226 | },
227 | "engines": {
228 | "node": ">=6.9.0"
229 | }
230 | },
231 | "node_modules/@babel/helper-split-export-declaration": {
232 | "version": "7.16.7",
233 | "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz",
234 | "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==",
235 | "dev": true,
236 | "peer": true,
237 | "dependencies": {
238 | "@babel/types": "^7.16.7"
239 | },
240 | "engines": {
241 | "node": ">=6.9.0"
242 | }
243 | },
244 | "node_modules/@babel/helper-validator-identifier": {
245 | "version": "7.16.7",
246 | "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz",
247 | "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==",
248 | "dev": true,
249 | "engines": {
250 | "node": ">=6.9.0"
251 | }
252 | },
253 | "node_modules/@babel/helper-validator-option": {
254 | "version": "7.16.7",
255 | "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz",
256 | "integrity": "sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==",
257 | "dev": true,
258 | "peer": true,
259 | "engines": {
260 | "node": ">=6.9.0"
261 | }
262 | },
263 | "node_modules/@babel/helpers": {
264 | "version": "7.17.7",
265 | "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.17.7.tgz",
266 | "integrity": "sha512-TKsj9NkjJfTBxM7Phfy7kv6yYc4ZcOo+AaWGqQOKTPDOmcGkIFb5xNA746eKisQkm4yavUYh4InYM9S+VnO01w==",
267 | "dev": true,
268 | "peer": true,
269 | "dependencies": {
270 | "@babel/template": "^7.16.7",
271 | "@babel/traverse": "^7.17.3",
272 | "@babel/types": "^7.17.0"
273 | },
274 | "engines": {
275 | "node": ">=6.9.0"
276 | }
277 | },
278 | "node_modules/@babel/highlight": {
279 | "version": "7.16.10",
280 | "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.10.tgz",
281 | "integrity": "sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==",
282 | "dev": true,
283 | "dependencies": {
284 | "@babel/helper-validator-identifier": "^7.16.7",
285 | "chalk": "^2.0.0",
286 | "js-tokens": "^4.0.0"
287 | },
288 | "engines": {
289 | "node": ">=6.9.0"
290 | }
291 | },
292 | "node_modules/@babel/parser": {
293 | "version": "7.17.7",
294 | "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.7.tgz",
295 | "integrity": "sha512-bm3AQf45vR4gKggRfvJdYJ0gFLoCbsPxiFLSH6hTVYABptNHY6l9NrhnucVjQ/X+SPtLANT9lc0fFhikj+VBRA==",
296 | "dev": true,
297 | "peer": true,
298 | "bin": {
299 | "parser": "bin/babel-parser.js"
300 | },
301 | "engines": {
302 | "node": ">=6.0.0"
303 | }
304 | },
305 | "node_modules/@babel/template": {
306 | "version": "7.16.7",
307 | "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz",
308 | "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==",
309 | "dev": true,
310 | "peer": true,
311 | "dependencies": {
312 | "@babel/code-frame": "^7.16.7",
313 | "@babel/parser": "^7.16.7",
314 | "@babel/types": "^7.16.7"
315 | },
316 | "engines": {
317 | "node": ">=6.9.0"
318 | }
319 | },
320 | "node_modules/@babel/traverse": {
321 | "version": "7.17.3",
322 | "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.3.tgz",
323 | "integrity": "sha512-5irClVky7TxRWIRtxlh2WPUUOLhcPN06AGgaQSB8AEwuyEBgJVuJ5imdHm5zxk8w0QS5T+tDfnDxAlhWjpb7cw==",
324 | "dev": true,
325 | "peer": true,
326 | "dependencies": {
327 | "@babel/code-frame": "^7.16.7",
328 | "@babel/generator": "^7.17.3",
329 | "@babel/helper-environment-visitor": "^7.16.7",
330 | "@babel/helper-function-name": "^7.16.7",
331 | "@babel/helper-hoist-variables": "^7.16.7",
332 | "@babel/helper-split-export-declaration": "^7.16.7",
333 | "@babel/parser": "^7.17.3",
334 | "@babel/types": "^7.17.0",
335 | "debug": "^4.1.0",
336 | "globals": "^11.1.0"
337 | },
338 | "engines": {
339 | "node": ">=6.9.0"
340 | }
341 | },
342 | "node_modules/@babel/types": {
343 | "version": "7.17.0",
344 | "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.0.tgz",
345 | "integrity": "sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==",
346 | "dev": true,
347 | "dependencies": {
348 | "@babel/helper-validator-identifier": "^7.16.7",
349 | "to-fast-properties": "^2.0.0"
350 | },
351 | "engines": {
352 | "node": ">=6.9.0"
353 | }
354 | },
355 | "node_modules/@jridgewell/resolve-uri": {
356 | "version": "3.0.5",
357 | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.5.tgz",
358 | "integrity": "sha512-VPeQ7+wH0itvQxnG+lIzWgkysKIr3L9sslimFW55rHMdGu/qCQ5z5h9zq4gI8uBtqkpHhsF4Z/OwExufUCThew==",
359 | "dev": true,
360 | "peer": true,
361 | "engines": {
362 | "node": ">=6.0.0"
363 | }
364 | },
365 | "node_modules/@jridgewell/sourcemap-codec": {
366 | "version": "1.4.11",
367 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.11.tgz",
368 | "integrity": "sha512-Fg32GrJo61m+VqYSdRSjRXMjQ06j8YIYfcTqndLYVAaHmroZHLJZCydsWBOTDqXS2v+mjxohBWEMfg97GXmYQg==",
369 | "dev": true,
370 | "peer": true
371 | },
372 | "node_modules/@jridgewell/trace-mapping": {
373 | "version": "0.3.4",
374 | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.4.tgz",
375 | "integrity": "sha512-vFv9ttIedivx0ux3QSjhgtCVjPZd5l46ZOMDSCwnH1yUO2e964gO8LZGyv2QkqcgR6TnBU1v+1IFqmeoG+0UJQ==",
376 | "dev": true,
377 | "peer": true,
378 | "dependencies": {
379 | "@jridgewell/resolve-uri": "^3.0.3",
380 | "@jridgewell/sourcemap-codec": "^1.4.10"
381 | }
382 | },
383 | "node_modules/@rollup/plugin-babel": {
384 | "version": "5.3.1",
385 | "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz",
386 | "integrity": "sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==",
387 | "dev": true,
388 | "dependencies": {
389 | "@babel/helper-module-imports": "^7.10.4",
390 | "@rollup/pluginutils": "^3.1.0"
391 | },
392 | "engines": {
393 | "node": ">= 10.0.0"
394 | },
395 | "peerDependencies": {
396 | "@babel/core": "^7.0.0",
397 | "@types/babel__core": "^7.1.9",
398 | "rollup": "^1.20.0||^2.0.0"
399 | },
400 | "peerDependenciesMeta": {
401 | "@types/babel__core": {
402 | "optional": true
403 | }
404 | }
405 | },
406 | "node_modules/@rollup/plugin-commonjs": {
407 | "version": "21.0.2",
408 | "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-21.0.2.tgz",
409 | "integrity": "sha512-d/OmjaLVO4j/aQX69bwpWPpbvI3TJkQuxoAk7BH8ew1PyoMBLTOuvJTjzG8oEoW7drIIqB0KCJtfFLu/2GClWg==",
410 | "dependencies": {
411 | "@rollup/pluginutils": "^3.1.0",
412 | "commondir": "^1.0.1",
413 | "estree-walker": "^2.0.1",
414 | "glob": "^7.1.6",
415 | "is-reference": "^1.2.1",
416 | "magic-string": "^0.25.7",
417 | "resolve": "^1.17.0"
418 | },
419 | "engines": {
420 | "node": ">= 8.0.0"
421 | },
422 | "peerDependencies": {
423 | "rollup": "^2.38.3"
424 | }
425 | },
426 | "node_modules/@rollup/plugin-commonjs/node_modules/estree-walker": {
427 | "version": "2.0.2",
428 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
429 | "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
430 | },
431 | "node_modules/@rollup/plugin-node-resolve": {
432 | "version": "13.1.3",
433 | "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.1.3.tgz",
434 | "integrity": "sha512-BdxNk+LtmElRo5d06MGY4zoepyrXX1tkzX2hrnPEZ53k78GuOMWLqmJDGIIOPwVRIFZrLQOo+Yr6KtCuLIA0AQ==",
435 | "dev": true,
436 | "dependencies": {
437 | "@rollup/pluginutils": "^3.1.0",
438 | "@types/resolve": "1.17.1",
439 | "builtin-modules": "^3.1.0",
440 | "deepmerge": "^4.2.2",
441 | "is-module": "^1.0.0",
442 | "resolve": "^1.19.0"
443 | },
444 | "engines": {
445 | "node": ">= 10.0.0"
446 | },
447 | "peerDependencies": {
448 | "rollup": "^2.42.0"
449 | }
450 | },
451 | "node_modules/@rollup/pluginutils": {
452 | "version": "3.1.0",
453 | "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz",
454 | "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==",
455 | "dependencies": {
456 | "@types/estree": "0.0.39",
457 | "estree-walker": "^1.0.1",
458 | "picomatch": "^2.2.2"
459 | },
460 | "engines": {
461 | "node": ">= 8.0.0"
462 | },
463 | "peerDependencies": {
464 | "rollup": "^1.20.0||^2.0.0"
465 | }
466 | },
467 | "node_modules/@types/estree": {
468 | "version": "0.0.39",
469 | "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz",
470 | "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw=="
471 | },
472 | "node_modules/@types/node": {
473 | "version": "17.0.21",
474 | "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.21.tgz",
475 | "integrity": "sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ==",
476 | "dev": true
477 | },
478 | "node_modules/@types/resolve": {
479 | "version": "1.17.1",
480 | "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz",
481 | "integrity": "sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==",
482 | "dev": true,
483 | "dependencies": {
484 | "@types/node": "*"
485 | }
486 | },
487 | "node_modules/acorn": {
488 | "version": "8.7.0",
489 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz",
490 | "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==",
491 | "dev": true,
492 | "bin": {
493 | "acorn": "bin/acorn"
494 | },
495 | "engines": {
496 | "node": ">=0.4.0"
497 | }
498 | },
499 | "node_modules/ansi-styles": {
500 | "version": "3.2.1",
501 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
502 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
503 | "dev": true,
504 | "dependencies": {
505 | "color-convert": "^1.9.0"
506 | },
507 | "engines": {
508 | "node": ">=4"
509 | }
510 | },
511 | "node_modules/balanced-match": {
512 | "version": "1.0.2",
513 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
514 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
515 | },
516 | "node_modules/brace-expansion": {
517 | "version": "1.1.11",
518 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
519 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
520 | "dependencies": {
521 | "balanced-match": "^1.0.0",
522 | "concat-map": "0.0.1"
523 | }
524 | },
525 | "node_modules/browserslist": {
526 | "version": "4.20.2",
527 | "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.20.2.tgz",
528 | "integrity": "sha512-CQOBCqp/9pDvDbx3xfMi+86pr4KXIf2FDkTTdeuYw8OxS9t898LA1Khq57gtufFILXpfgsSx5woNgsBgvGjpsA==",
529 | "dev": true,
530 | "funding": [
531 | {
532 | "type": "opencollective",
533 | "url": "https://opencollective.com/browserslist"
534 | },
535 | {
536 | "type": "tidelift",
537 | "url": "https://tidelift.com/funding/github/npm/browserslist"
538 | }
539 | ],
540 | "peer": true,
541 | "dependencies": {
542 | "caniuse-lite": "^1.0.30001317",
543 | "electron-to-chromium": "^1.4.84",
544 | "escalade": "^3.1.1",
545 | "node-releases": "^2.0.2",
546 | "picocolors": "^1.0.0"
547 | },
548 | "bin": {
549 | "browserslist": "cli.js"
550 | },
551 | "engines": {
552 | "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
553 | }
554 | },
555 | "node_modules/buffer-from": {
556 | "version": "1.1.2",
557 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
558 | "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
559 | "dev": true
560 | },
561 | "node_modules/builtin-modules": {
562 | "version": "3.2.0",
563 | "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.2.0.tgz",
564 | "integrity": "sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA==",
565 | "dev": true,
566 | "engines": {
567 | "node": ">=6"
568 | },
569 | "funding": {
570 | "url": "https://github.com/sponsors/sindresorhus"
571 | }
572 | },
573 | "node_modules/caniuse-lite": {
574 | "version": "1.0.30001317",
575 | "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001317.tgz",
576 | "integrity": "sha512-xIZLh8gBm4dqNX0gkzrBeyI86J2eCjWzYAs40q88smG844YIrN4tVQl/RhquHvKEKImWWFIVh1Lxe5n1G/N+GQ==",
577 | "dev": true,
578 | "peer": true,
579 | "funding": {
580 | "type": "opencollective",
581 | "url": "https://opencollective.com/browserslist"
582 | }
583 | },
584 | "node_modules/chalk": {
585 | "version": "2.4.2",
586 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
587 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
588 | "dev": true,
589 | "dependencies": {
590 | "ansi-styles": "^3.2.1",
591 | "escape-string-regexp": "^1.0.5",
592 | "supports-color": "^5.3.0"
593 | },
594 | "engines": {
595 | "node": ">=4"
596 | }
597 | },
598 | "node_modules/color-convert": {
599 | "version": "1.9.3",
600 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
601 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
602 | "dev": true,
603 | "dependencies": {
604 | "color-name": "1.1.3"
605 | }
606 | },
607 | "node_modules/color-name": {
608 | "version": "1.1.3",
609 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
610 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
611 | "dev": true
612 | },
613 | "node_modules/commander": {
614 | "version": "2.20.3",
615 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
616 | "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
617 | "dev": true
618 | },
619 | "node_modules/commondir": {
620 | "version": "1.0.1",
621 | "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
622 | "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs="
623 | },
624 | "node_modules/concat-map": {
625 | "version": "0.0.1",
626 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
627 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
628 | },
629 | "node_modules/convert-source-map": {
630 | "version": "1.8.0",
631 | "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz",
632 | "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==",
633 | "dev": true,
634 | "peer": true,
635 | "dependencies": {
636 | "safe-buffer": "~5.1.1"
637 | }
638 | },
639 | "node_modules/convert-source-map/node_modules/safe-buffer": {
640 | "version": "5.1.2",
641 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
642 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
643 | "dev": true,
644 | "peer": true
645 | },
646 | "node_modules/debug": {
647 | "version": "4.3.3",
648 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz",
649 | "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==",
650 | "dev": true,
651 | "peer": true,
652 | "dependencies": {
653 | "ms": "2.1.2"
654 | },
655 | "engines": {
656 | "node": ">=6.0"
657 | },
658 | "peerDependenciesMeta": {
659 | "supports-color": {
660 | "optional": true
661 | }
662 | }
663 | },
664 | "node_modules/deepmerge": {
665 | "version": "4.2.2",
666 | "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
667 | "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==",
668 | "dev": true,
669 | "engines": {
670 | "node": ">=0.10.0"
671 | }
672 | },
673 | "node_modules/electron-to-chromium": {
674 | "version": "1.4.86",
675 | "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.86.tgz",
676 | "integrity": "sha512-EVTZ+igi8x63pK4bPuA95PXIs2b2Cowi3WQwI9f9qManLiZJOD1Lash1J3W4TvvcUCcIR4o/rgi9o8UicXSO+w==",
677 | "dev": true,
678 | "peer": true
679 | },
680 | "node_modules/escalade": {
681 | "version": "3.1.1",
682 | "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
683 | "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
684 | "dev": true,
685 | "peer": true,
686 | "engines": {
687 | "node": ">=6"
688 | }
689 | },
690 | "node_modules/escape-string-regexp": {
691 | "version": "1.0.5",
692 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
693 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
694 | "dev": true,
695 | "engines": {
696 | "node": ">=0.8.0"
697 | }
698 | },
699 | "node_modules/estree-walker": {
700 | "version": "1.0.1",
701 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz",
702 | "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg=="
703 | },
704 | "node_modules/fs.realpath": {
705 | "version": "1.0.0",
706 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
707 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
708 | },
709 | "node_modules/fsevents": {
710 | "version": "2.3.2",
711 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
712 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
713 | "hasInstallScript": true,
714 | "optional": true,
715 | "os": [
716 | "darwin"
717 | ],
718 | "engines": {
719 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
720 | }
721 | },
722 | "node_modules/function-bind": {
723 | "version": "1.1.1",
724 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
725 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
726 | },
727 | "node_modules/gensync": {
728 | "version": "1.0.0-beta.2",
729 | "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
730 | "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
731 | "dev": true,
732 | "peer": true,
733 | "engines": {
734 | "node": ">=6.9.0"
735 | }
736 | },
737 | "node_modules/glob": {
738 | "version": "7.2.0",
739 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
740 | "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
741 | "dependencies": {
742 | "fs.realpath": "^1.0.0",
743 | "inflight": "^1.0.4",
744 | "inherits": "2",
745 | "minimatch": "^3.0.4",
746 | "once": "^1.3.0",
747 | "path-is-absolute": "^1.0.0"
748 | },
749 | "engines": {
750 | "node": "*"
751 | },
752 | "funding": {
753 | "url": "https://github.com/sponsors/isaacs"
754 | }
755 | },
756 | "node_modules/globals": {
757 | "version": "11.12.0",
758 | "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
759 | "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
760 | "dev": true,
761 | "peer": true,
762 | "engines": {
763 | "node": ">=4"
764 | }
765 | },
766 | "node_modules/has": {
767 | "version": "1.0.3",
768 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
769 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
770 | "dependencies": {
771 | "function-bind": "^1.1.1"
772 | },
773 | "engines": {
774 | "node": ">= 0.4.0"
775 | }
776 | },
777 | "node_modules/has-flag": {
778 | "version": "3.0.0",
779 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
780 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
781 | "dev": true,
782 | "engines": {
783 | "node": ">=4"
784 | }
785 | },
786 | "node_modules/inflight": {
787 | "version": "1.0.6",
788 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
789 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
790 | "dependencies": {
791 | "once": "^1.3.0",
792 | "wrappy": "1"
793 | }
794 | },
795 | "node_modules/inherits": {
796 | "version": "2.0.4",
797 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
798 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
799 | },
800 | "node_modules/is-core-module": {
801 | "version": "2.8.1",
802 | "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz",
803 | "integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==",
804 | "dependencies": {
805 | "has": "^1.0.3"
806 | },
807 | "funding": {
808 | "url": "https://github.com/sponsors/ljharb"
809 | }
810 | },
811 | "node_modules/is-module": {
812 | "version": "1.0.0",
813 | "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
814 | "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=",
815 | "dev": true
816 | },
817 | "node_modules/is-reference": {
818 | "version": "1.2.1",
819 | "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz",
820 | "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==",
821 | "dependencies": {
822 | "@types/estree": "*"
823 | }
824 | },
825 | "node_modules/jest-worker": {
826 | "version": "26.6.2",
827 | "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz",
828 | "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==",
829 | "dev": true,
830 | "dependencies": {
831 | "@types/node": "*",
832 | "merge-stream": "^2.0.0",
833 | "supports-color": "^7.0.0"
834 | },
835 | "engines": {
836 | "node": ">= 10.13.0"
837 | }
838 | },
839 | "node_modules/jest-worker/node_modules/has-flag": {
840 | "version": "4.0.0",
841 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
842 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
843 | "dev": true,
844 | "engines": {
845 | "node": ">=8"
846 | }
847 | },
848 | "node_modules/jest-worker/node_modules/supports-color": {
849 | "version": "7.2.0",
850 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
851 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
852 | "dev": true,
853 | "dependencies": {
854 | "has-flag": "^4.0.0"
855 | },
856 | "engines": {
857 | "node": ">=8"
858 | }
859 | },
860 | "node_modules/js-tokens": {
861 | "version": "4.0.0",
862 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
863 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
864 | "dev": true
865 | },
866 | "node_modules/jsesc": {
867 | "version": "2.5.2",
868 | "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
869 | "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
870 | "dev": true,
871 | "peer": true,
872 | "bin": {
873 | "jsesc": "bin/jsesc"
874 | },
875 | "engines": {
876 | "node": ">=4"
877 | }
878 | },
879 | "node_modules/json5": {
880 | "version": "2.2.0",
881 | "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz",
882 | "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==",
883 | "dev": true,
884 | "peer": true,
885 | "dependencies": {
886 | "minimist": "^1.2.5"
887 | },
888 | "bin": {
889 | "json5": "lib/cli.js"
890 | },
891 | "engines": {
892 | "node": ">=6"
893 | }
894 | },
895 | "node_modules/magic-string": {
896 | "version": "0.25.9",
897 | "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz",
898 | "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==",
899 | "dependencies": {
900 | "sourcemap-codec": "^1.4.8"
901 | }
902 | },
903 | "node_modules/merge-stream": {
904 | "version": "2.0.0",
905 | "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
906 | "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
907 | "dev": true
908 | },
909 | "node_modules/minimatch": {
910 | "version": "3.1.2",
911 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
912 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
913 | "dependencies": {
914 | "brace-expansion": "^1.1.7"
915 | },
916 | "engines": {
917 | "node": "*"
918 | }
919 | },
920 | "node_modules/minimist": {
921 | "version": "1.2.5",
922 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
923 | "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
924 | "dev": true,
925 | "peer": true
926 | },
927 | "node_modules/ms": {
928 | "version": "2.1.2",
929 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
930 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
931 | "dev": true,
932 | "peer": true
933 | },
934 | "node_modules/node-releases": {
935 | "version": "2.0.2",
936 | "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.2.tgz",
937 | "integrity": "sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg==",
938 | "dev": true,
939 | "peer": true
940 | },
941 | "node_modules/once": {
942 | "version": "1.4.0",
943 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
944 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
945 | "dependencies": {
946 | "wrappy": "1"
947 | }
948 | },
949 | "node_modules/path-is-absolute": {
950 | "version": "1.0.1",
951 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
952 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
953 | "engines": {
954 | "node": ">=0.10.0"
955 | }
956 | },
957 | "node_modules/path-parse": {
958 | "version": "1.0.7",
959 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
960 | "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
961 | },
962 | "node_modules/picocolors": {
963 | "version": "1.0.0",
964 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
965 | "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
966 | "dev": true,
967 | "peer": true
968 | },
969 | "node_modules/picomatch": {
970 | "version": "2.3.1",
971 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
972 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
973 | "engines": {
974 | "node": ">=8.6"
975 | },
976 | "funding": {
977 | "url": "https://github.com/sponsors/jonschlinkert"
978 | }
979 | },
980 | "node_modules/randombytes": {
981 | "version": "2.1.0",
982 | "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
983 | "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
984 | "dev": true,
985 | "dependencies": {
986 | "safe-buffer": "^5.1.0"
987 | }
988 | },
989 | "node_modules/resolve": {
990 | "version": "1.22.0",
991 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz",
992 | "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==",
993 | "dependencies": {
994 | "is-core-module": "^2.8.1",
995 | "path-parse": "^1.0.7",
996 | "supports-preserve-symlinks-flag": "^1.0.0"
997 | },
998 | "bin": {
999 | "resolve": "bin/resolve"
1000 | },
1001 | "funding": {
1002 | "url": "https://github.com/sponsors/ljharb"
1003 | }
1004 | },
1005 | "node_modules/rollup": {
1006 | "version": "2.70.1",
1007 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.70.1.tgz",
1008 | "integrity": "sha512-CRYsI5EuzLbXdxC6RnYhOuRdtz4bhejPMSWjsFLfVM/7w/85n2szZv6yExqUXsBdz5KT8eoubeyDUDjhLHEslA==",
1009 | "bin": {
1010 | "rollup": "dist/bin/rollup"
1011 | },
1012 | "engines": {
1013 | "node": ">=10.0.0"
1014 | },
1015 | "optionalDependencies": {
1016 | "fsevents": "~2.3.2"
1017 | }
1018 | },
1019 | "node_modules/rollup-plugin-inject": {
1020 | "version": "3.0.2",
1021 | "resolved": "https://registry.npmjs.org/rollup-plugin-inject/-/rollup-plugin-inject-3.0.2.tgz",
1022 | "integrity": "sha512-ptg9PQwzs3orn4jkgXJ74bfs5vYz1NCZlSQMBUA0wKcGp5i5pA1AO3fOUEte8enhGUC+iapTCzEWw2jEFFUO/w==",
1023 | "deprecated": "This package has been deprecated and is no longer maintained. Please use @rollup/plugin-inject.",
1024 | "dev": true,
1025 | "dependencies": {
1026 | "estree-walker": "^0.6.1",
1027 | "magic-string": "^0.25.3",
1028 | "rollup-pluginutils": "^2.8.1"
1029 | }
1030 | },
1031 | "node_modules/rollup-plugin-inject/node_modules/estree-walker": {
1032 | "version": "0.6.1",
1033 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz",
1034 | "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==",
1035 | "dev": true
1036 | },
1037 | "node_modules/rollup-plugin-node-polyfills": {
1038 | "version": "0.2.1",
1039 | "resolved": "https://registry.npmjs.org/rollup-plugin-node-polyfills/-/rollup-plugin-node-polyfills-0.2.1.tgz",
1040 | "integrity": "sha512-4kCrKPTJ6sK4/gLL/U5QzVT8cxJcofO0OU74tnB19F40cmuAKSzH5/siithxlofFEjwvw1YAhPmbvGNA6jEroA==",
1041 | "dev": true,
1042 | "dependencies": {
1043 | "rollup-plugin-inject": "^3.0.0"
1044 | }
1045 | },
1046 | "node_modules/rollup-plugin-terser": {
1047 | "version": "7.0.2",
1048 | "resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz",
1049 | "integrity": "sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==",
1050 | "dev": true,
1051 | "dependencies": {
1052 | "@babel/code-frame": "^7.10.4",
1053 | "jest-worker": "^26.2.1",
1054 | "serialize-javascript": "^4.0.0",
1055 | "terser": "^5.0.0"
1056 | },
1057 | "peerDependencies": {
1058 | "rollup": "^2.0.0"
1059 | }
1060 | },
1061 | "node_modules/rollup-pluginutils": {
1062 | "version": "2.8.2",
1063 | "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz",
1064 | "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==",
1065 | "dev": true,
1066 | "dependencies": {
1067 | "estree-walker": "^0.6.1"
1068 | }
1069 | },
1070 | "node_modules/rollup-pluginutils/node_modules/estree-walker": {
1071 | "version": "0.6.1",
1072 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz",
1073 | "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==",
1074 | "dev": true
1075 | },
1076 | "node_modules/safe-buffer": {
1077 | "version": "5.2.1",
1078 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
1079 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
1080 | "dev": true,
1081 | "funding": [
1082 | {
1083 | "type": "github",
1084 | "url": "https://github.com/sponsors/feross"
1085 | },
1086 | {
1087 | "type": "patreon",
1088 | "url": "https://www.patreon.com/feross"
1089 | },
1090 | {
1091 | "type": "consulting",
1092 | "url": "https://feross.org/support"
1093 | }
1094 | ]
1095 | },
1096 | "node_modules/semver": {
1097 | "version": "6.3.0",
1098 | "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
1099 | "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
1100 | "dev": true,
1101 | "peer": true,
1102 | "bin": {
1103 | "semver": "bin/semver.js"
1104 | }
1105 | },
1106 | "node_modules/serialize-javascript": {
1107 | "version": "4.0.0",
1108 | "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz",
1109 | "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==",
1110 | "dev": true,
1111 | "dependencies": {
1112 | "randombytes": "^2.1.0"
1113 | }
1114 | },
1115 | "node_modules/source-map": {
1116 | "version": "0.7.3",
1117 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
1118 | "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
1119 | "dev": true,
1120 | "engines": {
1121 | "node": ">= 8"
1122 | }
1123 | },
1124 | "node_modules/source-map-support": {
1125 | "version": "0.5.21",
1126 | "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
1127 | "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
1128 | "dev": true,
1129 | "dependencies": {
1130 | "buffer-from": "^1.0.0",
1131 | "source-map": "^0.6.0"
1132 | }
1133 | },
1134 | "node_modules/source-map-support/node_modules/source-map": {
1135 | "version": "0.6.1",
1136 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
1137 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
1138 | "dev": true,
1139 | "engines": {
1140 | "node": ">=0.10.0"
1141 | }
1142 | },
1143 | "node_modules/sourcemap-codec": {
1144 | "version": "1.4.8",
1145 | "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
1146 | "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA=="
1147 | },
1148 | "node_modules/supports-color": {
1149 | "version": "5.5.0",
1150 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
1151 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
1152 | "dev": true,
1153 | "dependencies": {
1154 | "has-flag": "^3.0.0"
1155 | },
1156 | "engines": {
1157 | "node": ">=4"
1158 | }
1159 | },
1160 | "node_modules/supports-preserve-symlinks-flag": {
1161 | "version": "1.0.0",
1162 | "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
1163 | "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
1164 | "engines": {
1165 | "node": ">= 0.4"
1166 | },
1167 | "funding": {
1168 | "url": "https://github.com/sponsors/ljharb"
1169 | }
1170 | },
1171 | "node_modules/terser": {
1172 | "version": "5.12.1",
1173 | "resolved": "https://registry.npmjs.org/terser/-/terser-5.12.1.tgz",
1174 | "integrity": "sha512-NXbs+7nisos5E+yXwAD+y7zrcTkMqb0dEJxIGtSKPdCBzopf7ni4odPul2aechpV7EXNvOudYOX2bb5tln1jbQ==",
1175 | "dev": true,
1176 | "dependencies": {
1177 | "acorn": "^8.5.0",
1178 | "commander": "^2.20.0",
1179 | "source-map": "~0.7.2",
1180 | "source-map-support": "~0.5.20"
1181 | },
1182 | "bin": {
1183 | "terser": "bin/terser"
1184 | },
1185 | "engines": {
1186 | "node": ">=10"
1187 | }
1188 | },
1189 | "node_modules/to-fast-properties": {
1190 | "version": "2.0.0",
1191 | "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
1192 | "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=",
1193 | "dev": true,
1194 | "engines": {
1195 | "node": ">=4"
1196 | }
1197 | },
1198 | "node_modules/wrappy": {
1199 | "version": "1.0.2",
1200 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
1201 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
1202 | }
1203 | },
1204 | "dependencies": {
1205 | "@ampproject/remapping": {
1206 | "version": "2.1.2",
1207 | "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.1.2.tgz",
1208 | "integrity": "sha512-hoyByceqwKirw7w3Z7gnIIZC3Wx3J484Y3L/cMpXFbr7d9ZQj2mODrirNzcJa+SM3UlpWXYvKV4RlRpFXlWgXg==",
1209 | "dev": true,
1210 | "peer": true,
1211 | "requires": {
1212 | "@jridgewell/trace-mapping": "^0.3.0"
1213 | }
1214 | },
1215 | "@babel/code-frame": {
1216 | "version": "7.16.7",
1217 | "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz",
1218 | "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==",
1219 | "dev": true,
1220 | "requires": {
1221 | "@babel/highlight": "^7.16.7"
1222 | }
1223 | },
1224 | "@babel/compat-data": {
1225 | "version": "7.17.7",
1226 | "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.17.7.tgz",
1227 | "integrity": "sha512-p8pdE6j0a29TNGebNm7NzYZWB3xVZJBZ7XGs42uAKzQo8VQ3F0By/cQCtUEABwIqw5zo6WA4NbmxsfzADzMKnQ==",
1228 | "dev": true,
1229 | "peer": true
1230 | },
1231 | "@babel/core": {
1232 | "version": "7.17.7",
1233 | "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.17.7.tgz",
1234 | "integrity": "sha512-djHlEfFHnSnTAcPb7dATbiM5HxGOP98+3JLBZtjRb5I7RXrw7kFRoG2dXM8cm3H+o11A8IFH/uprmJpwFynRNQ==",
1235 | "dev": true,
1236 | "peer": true,
1237 | "requires": {
1238 | "@ampproject/remapping": "^2.1.0",
1239 | "@babel/code-frame": "^7.16.7",
1240 | "@babel/generator": "^7.17.7",
1241 | "@babel/helper-compilation-targets": "^7.17.7",
1242 | "@babel/helper-module-transforms": "^7.17.7",
1243 | "@babel/helpers": "^7.17.7",
1244 | "@babel/parser": "^7.17.7",
1245 | "@babel/template": "^7.16.7",
1246 | "@babel/traverse": "^7.17.3",
1247 | "@babel/types": "^7.17.0",
1248 | "convert-source-map": "^1.7.0",
1249 | "debug": "^4.1.0",
1250 | "gensync": "^1.0.0-beta.2",
1251 | "json5": "^2.1.2",
1252 | "semver": "^6.3.0"
1253 | }
1254 | },
1255 | "@babel/generator": {
1256 | "version": "7.17.7",
1257 | "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.7.tgz",
1258 | "integrity": "sha512-oLcVCTeIFadUoArDTwpluncplrYBmTCCZZgXCbgNGvOBBiSDDK3eWO4b/+eOTli5tKv1lg+a5/NAXg+nTcei1w==",
1259 | "dev": true,
1260 | "peer": true,
1261 | "requires": {
1262 | "@babel/types": "^7.17.0",
1263 | "jsesc": "^2.5.1",
1264 | "source-map": "^0.5.0"
1265 | },
1266 | "dependencies": {
1267 | "source-map": {
1268 | "version": "0.5.7",
1269 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
1270 | "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
1271 | "dev": true,
1272 | "peer": true
1273 | }
1274 | }
1275 | },
1276 | "@babel/helper-compilation-targets": {
1277 | "version": "7.17.7",
1278 | "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.17.7.tgz",
1279 | "integrity": "sha512-UFzlz2jjd8kroj0hmCFV5zr+tQPi1dpC2cRsDV/3IEW8bJfCPrPpmcSN6ZS8RqIq4LXcmpipCQFPddyFA5Yc7w==",
1280 | "dev": true,
1281 | "peer": true,
1282 | "requires": {
1283 | "@babel/compat-data": "^7.17.7",
1284 | "@babel/helper-validator-option": "^7.16.7",
1285 | "browserslist": "^4.17.5",
1286 | "semver": "^6.3.0"
1287 | }
1288 | },
1289 | "@babel/helper-environment-visitor": {
1290 | "version": "7.16.7",
1291 | "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz",
1292 | "integrity": "sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==",
1293 | "dev": true,
1294 | "peer": true,
1295 | "requires": {
1296 | "@babel/types": "^7.16.7"
1297 | }
1298 | },
1299 | "@babel/helper-function-name": {
1300 | "version": "7.16.7",
1301 | "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz",
1302 | "integrity": "sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==",
1303 | "dev": true,
1304 | "peer": true,
1305 | "requires": {
1306 | "@babel/helper-get-function-arity": "^7.16.7",
1307 | "@babel/template": "^7.16.7",
1308 | "@babel/types": "^7.16.7"
1309 | }
1310 | },
1311 | "@babel/helper-get-function-arity": {
1312 | "version": "7.16.7",
1313 | "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz",
1314 | "integrity": "sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==",
1315 | "dev": true,
1316 | "peer": true,
1317 | "requires": {
1318 | "@babel/types": "^7.16.7"
1319 | }
1320 | },
1321 | "@babel/helper-hoist-variables": {
1322 | "version": "7.16.7",
1323 | "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz",
1324 | "integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==",
1325 | "dev": true,
1326 | "peer": true,
1327 | "requires": {
1328 | "@babel/types": "^7.16.7"
1329 | }
1330 | },
1331 | "@babel/helper-module-imports": {
1332 | "version": "7.16.7",
1333 | "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz",
1334 | "integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==",
1335 | "dev": true,
1336 | "requires": {
1337 | "@babel/types": "^7.16.7"
1338 | }
1339 | },
1340 | "@babel/helper-module-transforms": {
1341 | "version": "7.17.7",
1342 | "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.17.7.tgz",
1343 | "integrity": "sha512-VmZD99F3gNTYB7fJRDTi+u6l/zxY0BE6OIxPSU7a50s6ZUQkHwSDmV92FfM+oCG0pZRVojGYhkR8I0OGeCVREw==",
1344 | "dev": true,
1345 | "peer": true,
1346 | "requires": {
1347 | "@babel/helper-environment-visitor": "^7.16.7",
1348 | "@babel/helper-module-imports": "^7.16.7",
1349 | "@babel/helper-simple-access": "^7.17.7",
1350 | "@babel/helper-split-export-declaration": "^7.16.7",
1351 | "@babel/helper-validator-identifier": "^7.16.7",
1352 | "@babel/template": "^7.16.7",
1353 | "@babel/traverse": "^7.17.3",
1354 | "@babel/types": "^7.17.0"
1355 | }
1356 | },
1357 | "@babel/helper-simple-access": {
1358 | "version": "7.17.7",
1359 | "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.17.7.tgz",
1360 | "integrity": "sha512-txyMCGroZ96i+Pxr3Je3lzEJjqwaRC9buMUgtomcrLe5Nd0+fk1h0LLA+ixUF5OW7AhHuQ7Es1WcQJZmZsz2XA==",
1361 | "dev": true,
1362 | "peer": true,
1363 | "requires": {
1364 | "@babel/types": "^7.17.0"
1365 | }
1366 | },
1367 | "@babel/helper-split-export-declaration": {
1368 | "version": "7.16.7",
1369 | "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz",
1370 | "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==",
1371 | "dev": true,
1372 | "peer": true,
1373 | "requires": {
1374 | "@babel/types": "^7.16.7"
1375 | }
1376 | },
1377 | "@babel/helper-validator-identifier": {
1378 | "version": "7.16.7",
1379 | "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz",
1380 | "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==",
1381 | "dev": true
1382 | },
1383 | "@babel/helper-validator-option": {
1384 | "version": "7.16.7",
1385 | "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz",
1386 | "integrity": "sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==",
1387 | "dev": true,
1388 | "peer": true
1389 | },
1390 | "@babel/helpers": {
1391 | "version": "7.17.7",
1392 | "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.17.7.tgz",
1393 | "integrity": "sha512-TKsj9NkjJfTBxM7Phfy7kv6yYc4ZcOo+AaWGqQOKTPDOmcGkIFb5xNA746eKisQkm4yavUYh4InYM9S+VnO01w==",
1394 | "dev": true,
1395 | "peer": true,
1396 | "requires": {
1397 | "@babel/template": "^7.16.7",
1398 | "@babel/traverse": "^7.17.3",
1399 | "@babel/types": "^7.17.0"
1400 | }
1401 | },
1402 | "@babel/highlight": {
1403 | "version": "7.16.10",
1404 | "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.10.tgz",
1405 | "integrity": "sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==",
1406 | "dev": true,
1407 | "requires": {
1408 | "@babel/helper-validator-identifier": "^7.16.7",
1409 | "chalk": "^2.0.0",
1410 | "js-tokens": "^4.0.0"
1411 | }
1412 | },
1413 | "@babel/parser": {
1414 | "version": "7.17.7",
1415 | "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.7.tgz",
1416 | "integrity": "sha512-bm3AQf45vR4gKggRfvJdYJ0gFLoCbsPxiFLSH6hTVYABptNHY6l9NrhnucVjQ/X+SPtLANT9lc0fFhikj+VBRA==",
1417 | "dev": true,
1418 | "peer": true
1419 | },
1420 | "@babel/template": {
1421 | "version": "7.16.7",
1422 | "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz",
1423 | "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==",
1424 | "dev": true,
1425 | "peer": true,
1426 | "requires": {
1427 | "@babel/code-frame": "^7.16.7",
1428 | "@babel/parser": "^7.16.7",
1429 | "@babel/types": "^7.16.7"
1430 | }
1431 | },
1432 | "@babel/traverse": {
1433 | "version": "7.17.3",
1434 | "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.3.tgz",
1435 | "integrity": "sha512-5irClVky7TxRWIRtxlh2WPUUOLhcPN06AGgaQSB8AEwuyEBgJVuJ5imdHm5zxk8w0QS5T+tDfnDxAlhWjpb7cw==",
1436 | "dev": true,
1437 | "peer": true,
1438 | "requires": {
1439 | "@babel/code-frame": "^7.16.7",
1440 | "@babel/generator": "^7.17.3",
1441 | "@babel/helper-environment-visitor": "^7.16.7",
1442 | "@babel/helper-function-name": "^7.16.7",
1443 | "@babel/helper-hoist-variables": "^7.16.7",
1444 | "@babel/helper-split-export-declaration": "^7.16.7",
1445 | "@babel/parser": "^7.17.3",
1446 | "@babel/types": "^7.17.0",
1447 | "debug": "^4.1.0",
1448 | "globals": "^11.1.0"
1449 | }
1450 | },
1451 | "@babel/types": {
1452 | "version": "7.17.0",
1453 | "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.0.tgz",
1454 | "integrity": "sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==",
1455 | "dev": true,
1456 | "requires": {
1457 | "@babel/helper-validator-identifier": "^7.16.7",
1458 | "to-fast-properties": "^2.0.0"
1459 | }
1460 | },
1461 | "@jridgewell/resolve-uri": {
1462 | "version": "3.0.5",
1463 | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.5.tgz",
1464 | "integrity": "sha512-VPeQ7+wH0itvQxnG+lIzWgkysKIr3L9sslimFW55rHMdGu/qCQ5z5h9zq4gI8uBtqkpHhsF4Z/OwExufUCThew==",
1465 | "dev": true,
1466 | "peer": true
1467 | },
1468 | "@jridgewell/sourcemap-codec": {
1469 | "version": "1.4.11",
1470 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.11.tgz",
1471 | "integrity": "sha512-Fg32GrJo61m+VqYSdRSjRXMjQ06j8YIYfcTqndLYVAaHmroZHLJZCydsWBOTDqXS2v+mjxohBWEMfg97GXmYQg==",
1472 | "dev": true,
1473 | "peer": true
1474 | },
1475 | "@jridgewell/trace-mapping": {
1476 | "version": "0.3.4",
1477 | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.4.tgz",
1478 | "integrity": "sha512-vFv9ttIedivx0ux3QSjhgtCVjPZd5l46ZOMDSCwnH1yUO2e964gO8LZGyv2QkqcgR6TnBU1v+1IFqmeoG+0UJQ==",
1479 | "dev": true,
1480 | "peer": true,
1481 | "requires": {
1482 | "@jridgewell/resolve-uri": "^3.0.3",
1483 | "@jridgewell/sourcemap-codec": "^1.4.10"
1484 | }
1485 | },
1486 | "@rollup/plugin-babel": {
1487 | "version": "5.3.1",
1488 | "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz",
1489 | "integrity": "sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==",
1490 | "dev": true,
1491 | "requires": {
1492 | "@babel/helper-module-imports": "^7.10.4",
1493 | "@rollup/pluginutils": "^3.1.0"
1494 | }
1495 | },
1496 | "@rollup/plugin-commonjs": {
1497 | "version": "21.0.2",
1498 | "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-21.0.2.tgz",
1499 | "integrity": "sha512-d/OmjaLVO4j/aQX69bwpWPpbvI3TJkQuxoAk7BH8ew1PyoMBLTOuvJTjzG8oEoW7drIIqB0KCJtfFLu/2GClWg==",
1500 | "requires": {
1501 | "@rollup/pluginutils": "^3.1.0",
1502 | "commondir": "^1.0.1",
1503 | "estree-walker": "^2.0.1",
1504 | "glob": "^7.1.6",
1505 | "is-reference": "^1.2.1",
1506 | "magic-string": "^0.25.7",
1507 | "resolve": "^1.17.0"
1508 | },
1509 | "dependencies": {
1510 | "estree-walker": {
1511 | "version": "2.0.2",
1512 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
1513 | "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
1514 | }
1515 | }
1516 | },
1517 | "@rollup/plugin-node-resolve": {
1518 | "version": "13.1.3",
1519 | "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.1.3.tgz",
1520 | "integrity": "sha512-BdxNk+LtmElRo5d06MGY4zoepyrXX1tkzX2hrnPEZ53k78GuOMWLqmJDGIIOPwVRIFZrLQOo+Yr6KtCuLIA0AQ==",
1521 | "dev": true,
1522 | "requires": {
1523 | "@rollup/pluginutils": "^3.1.0",
1524 | "@types/resolve": "1.17.1",
1525 | "builtin-modules": "^3.1.0",
1526 | "deepmerge": "^4.2.2",
1527 | "is-module": "^1.0.0",
1528 | "resolve": "^1.19.0"
1529 | }
1530 | },
1531 | "@rollup/pluginutils": {
1532 | "version": "3.1.0",
1533 | "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz",
1534 | "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==",
1535 | "requires": {
1536 | "@types/estree": "0.0.39",
1537 | "estree-walker": "^1.0.1",
1538 | "picomatch": "^2.2.2"
1539 | }
1540 | },
1541 | "@types/estree": {
1542 | "version": "0.0.39",
1543 | "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz",
1544 | "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw=="
1545 | },
1546 | "@types/node": {
1547 | "version": "17.0.21",
1548 | "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.21.tgz",
1549 | "integrity": "sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ==",
1550 | "dev": true
1551 | },
1552 | "@types/resolve": {
1553 | "version": "1.17.1",
1554 | "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz",
1555 | "integrity": "sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==",
1556 | "dev": true,
1557 | "requires": {
1558 | "@types/node": "*"
1559 | }
1560 | },
1561 | "acorn": {
1562 | "version": "8.7.0",
1563 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz",
1564 | "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==",
1565 | "dev": true
1566 | },
1567 | "ansi-styles": {
1568 | "version": "3.2.1",
1569 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
1570 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
1571 | "dev": true,
1572 | "requires": {
1573 | "color-convert": "^1.9.0"
1574 | }
1575 | },
1576 | "balanced-match": {
1577 | "version": "1.0.2",
1578 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
1579 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
1580 | },
1581 | "brace-expansion": {
1582 | "version": "1.1.11",
1583 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
1584 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
1585 | "requires": {
1586 | "balanced-match": "^1.0.0",
1587 | "concat-map": "0.0.1"
1588 | }
1589 | },
1590 | "browserslist": {
1591 | "version": "4.20.2",
1592 | "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.20.2.tgz",
1593 | "integrity": "sha512-CQOBCqp/9pDvDbx3xfMi+86pr4KXIf2FDkTTdeuYw8OxS9t898LA1Khq57gtufFILXpfgsSx5woNgsBgvGjpsA==",
1594 | "dev": true,
1595 | "peer": true,
1596 | "requires": {
1597 | "caniuse-lite": "^1.0.30001317",
1598 | "electron-to-chromium": "^1.4.84",
1599 | "escalade": "^3.1.1",
1600 | "node-releases": "^2.0.2",
1601 | "picocolors": "^1.0.0"
1602 | }
1603 | },
1604 | "buffer-from": {
1605 | "version": "1.1.2",
1606 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
1607 | "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
1608 | "dev": true
1609 | },
1610 | "builtin-modules": {
1611 | "version": "3.2.0",
1612 | "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.2.0.tgz",
1613 | "integrity": "sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA==",
1614 | "dev": true
1615 | },
1616 | "caniuse-lite": {
1617 | "version": "1.0.30001317",
1618 | "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001317.tgz",
1619 | "integrity": "sha512-xIZLh8gBm4dqNX0gkzrBeyI86J2eCjWzYAs40q88smG844YIrN4tVQl/RhquHvKEKImWWFIVh1Lxe5n1G/N+GQ==",
1620 | "dev": true,
1621 | "peer": true
1622 | },
1623 | "chalk": {
1624 | "version": "2.4.2",
1625 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
1626 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
1627 | "dev": true,
1628 | "requires": {
1629 | "ansi-styles": "^3.2.1",
1630 | "escape-string-regexp": "^1.0.5",
1631 | "supports-color": "^5.3.0"
1632 | }
1633 | },
1634 | "color-convert": {
1635 | "version": "1.9.3",
1636 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
1637 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
1638 | "dev": true,
1639 | "requires": {
1640 | "color-name": "1.1.3"
1641 | }
1642 | },
1643 | "color-name": {
1644 | "version": "1.1.3",
1645 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
1646 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
1647 | "dev": true
1648 | },
1649 | "commander": {
1650 | "version": "2.20.3",
1651 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
1652 | "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
1653 | "dev": true
1654 | },
1655 | "commondir": {
1656 | "version": "1.0.1",
1657 | "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
1658 | "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs="
1659 | },
1660 | "concat-map": {
1661 | "version": "0.0.1",
1662 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
1663 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
1664 | },
1665 | "convert-source-map": {
1666 | "version": "1.8.0",
1667 | "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz",
1668 | "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==",
1669 | "dev": true,
1670 | "peer": true,
1671 | "requires": {
1672 | "safe-buffer": "~5.1.1"
1673 | },
1674 | "dependencies": {
1675 | "safe-buffer": {
1676 | "version": "5.1.2",
1677 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
1678 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
1679 | "dev": true,
1680 | "peer": true
1681 | }
1682 | }
1683 | },
1684 | "debug": {
1685 | "version": "4.3.3",
1686 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz",
1687 | "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==",
1688 | "dev": true,
1689 | "peer": true,
1690 | "requires": {
1691 | "ms": "2.1.2"
1692 | }
1693 | },
1694 | "deepmerge": {
1695 | "version": "4.2.2",
1696 | "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
1697 | "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==",
1698 | "dev": true
1699 | },
1700 | "electron-to-chromium": {
1701 | "version": "1.4.86",
1702 | "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.86.tgz",
1703 | "integrity": "sha512-EVTZ+igi8x63pK4bPuA95PXIs2b2Cowi3WQwI9f9qManLiZJOD1Lash1J3W4TvvcUCcIR4o/rgi9o8UicXSO+w==",
1704 | "dev": true,
1705 | "peer": true
1706 | },
1707 | "escalade": {
1708 | "version": "3.1.1",
1709 | "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
1710 | "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
1711 | "dev": true,
1712 | "peer": true
1713 | },
1714 | "escape-string-regexp": {
1715 | "version": "1.0.5",
1716 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
1717 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
1718 | "dev": true
1719 | },
1720 | "estree-walker": {
1721 | "version": "1.0.1",
1722 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz",
1723 | "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg=="
1724 | },
1725 | "fs.realpath": {
1726 | "version": "1.0.0",
1727 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
1728 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
1729 | },
1730 | "fsevents": {
1731 | "version": "2.3.2",
1732 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
1733 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
1734 | "optional": true
1735 | },
1736 | "function-bind": {
1737 | "version": "1.1.1",
1738 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
1739 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
1740 | },
1741 | "gensync": {
1742 | "version": "1.0.0-beta.2",
1743 | "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
1744 | "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
1745 | "dev": true,
1746 | "peer": true
1747 | },
1748 | "glob": {
1749 | "version": "7.2.0",
1750 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
1751 | "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
1752 | "requires": {
1753 | "fs.realpath": "^1.0.0",
1754 | "inflight": "^1.0.4",
1755 | "inherits": "2",
1756 | "minimatch": "^3.0.4",
1757 | "once": "^1.3.0",
1758 | "path-is-absolute": "^1.0.0"
1759 | }
1760 | },
1761 | "globals": {
1762 | "version": "11.12.0",
1763 | "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
1764 | "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
1765 | "dev": true,
1766 | "peer": true
1767 | },
1768 | "has": {
1769 | "version": "1.0.3",
1770 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
1771 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
1772 | "requires": {
1773 | "function-bind": "^1.1.1"
1774 | }
1775 | },
1776 | "has-flag": {
1777 | "version": "3.0.0",
1778 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
1779 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
1780 | "dev": true
1781 | },
1782 | "inflight": {
1783 | "version": "1.0.6",
1784 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
1785 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
1786 | "requires": {
1787 | "once": "^1.3.0",
1788 | "wrappy": "1"
1789 | }
1790 | },
1791 | "inherits": {
1792 | "version": "2.0.4",
1793 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
1794 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
1795 | },
1796 | "is-core-module": {
1797 | "version": "2.8.1",
1798 | "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz",
1799 | "integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==",
1800 | "requires": {
1801 | "has": "^1.0.3"
1802 | }
1803 | },
1804 | "is-module": {
1805 | "version": "1.0.0",
1806 | "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
1807 | "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=",
1808 | "dev": true
1809 | },
1810 | "is-reference": {
1811 | "version": "1.2.1",
1812 | "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz",
1813 | "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==",
1814 | "requires": {
1815 | "@types/estree": "*"
1816 | }
1817 | },
1818 | "jest-worker": {
1819 | "version": "26.6.2",
1820 | "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz",
1821 | "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==",
1822 | "dev": true,
1823 | "requires": {
1824 | "@types/node": "*",
1825 | "merge-stream": "^2.0.0",
1826 | "supports-color": "^7.0.0"
1827 | },
1828 | "dependencies": {
1829 | "has-flag": {
1830 | "version": "4.0.0",
1831 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
1832 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
1833 | "dev": true
1834 | },
1835 | "supports-color": {
1836 | "version": "7.2.0",
1837 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
1838 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
1839 | "dev": true,
1840 | "requires": {
1841 | "has-flag": "^4.0.0"
1842 | }
1843 | }
1844 | }
1845 | },
1846 | "js-tokens": {
1847 | "version": "4.0.0",
1848 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
1849 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
1850 | "dev": true
1851 | },
1852 | "jsesc": {
1853 | "version": "2.5.2",
1854 | "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
1855 | "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
1856 | "dev": true,
1857 | "peer": true
1858 | },
1859 | "json5": {
1860 | "version": "2.2.0",
1861 | "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz",
1862 | "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==",
1863 | "dev": true,
1864 | "peer": true,
1865 | "requires": {
1866 | "minimist": "^1.2.5"
1867 | }
1868 | },
1869 | "magic-string": {
1870 | "version": "0.25.9",
1871 | "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz",
1872 | "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==",
1873 | "requires": {
1874 | "sourcemap-codec": "^1.4.8"
1875 | }
1876 | },
1877 | "merge-stream": {
1878 | "version": "2.0.0",
1879 | "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
1880 | "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
1881 | "dev": true
1882 | },
1883 | "minimatch": {
1884 | "version": "3.1.2",
1885 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
1886 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
1887 | "requires": {
1888 | "brace-expansion": "^1.1.7"
1889 | }
1890 | },
1891 | "minimist": {
1892 | "version": "1.2.5",
1893 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
1894 | "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
1895 | "dev": true,
1896 | "peer": true
1897 | },
1898 | "ms": {
1899 | "version": "2.1.2",
1900 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
1901 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
1902 | "dev": true,
1903 | "peer": true
1904 | },
1905 | "node-releases": {
1906 | "version": "2.0.2",
1907 | "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.2.tgz",
1908 | "integrity": "sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg==",
1909 | "dev": true,
1910 | "peer": true
1911 | },
1912 | "once": {
1913 | "version": "1.4.0",
1914 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
1915 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
1916 | "requires": {
1917 | "wrappy": "1"
1918 | }
1919 | },
1920 | "path-is-absolute": {
1921 | "version": "1.0.1",
1922 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
1923 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
1924 | },
1925 | "path-parse": {
1926 | "version": "1.0.7",
1927 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
1928 | "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
1929 | },
1930 | "picocolors": {
1931 | "version": "1.0.0",
1932 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
1933 | "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
1934 | "dev": true,
1935 | "peer": true
1936 | },
1937 | "picomatch": {
1938 | "version": "2.3.1",
1939 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
1940 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="
1941 | },
1942 | "randombytes": {
1943 | "version": "2.1.0",
1944 | "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
1945 | "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
1946 | "dev": true,
1947 | "requires": {
1948 | "safe-buffer": "^5.1.0"
1949 | }
1950 | },
1951 | "resolve": {
1952 | "version": "1.22.0",
1953 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz",
1954 | "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==",
1955 | "requires": {
1956 | "is-core-module": "^2.8.1",
1957 | "path-parse": "^1.0.7",
1958 | "supports-preserve-symlinks-flag": "^1.0.0"
1959 | }
1960 | },
1961 | "rollup": {
1962 | "version": "2.70.1",
1963 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.70.1.tgz",
1964 | "integrity": "sha512-CRYsI5EuzLbXdxC6RnYhOuRdtz4bhejPMSWjsFLfVM/7w/85n2szZv6yExqUXsBdz5KT8eoubeyDUDjhLHEslA==",
1965 | "requires": {
1966 | "fsevents": "~2.3.2"
1967 | }
1968 | },
1969 | "rollup-plugin-inject": {
1970 | "version": "3.0.2",
1971 | "resolved": "https://registry.npmjs.org/rollup-plugin-inject/-/rollup-plugin-inject-3.0.2.tgz",
1972 | "integrity": "sha512-ptg9PQwzs3orn4jkgXJ74bfs5vYz1NCZlSQMBUA0wKcGp5i5pA1AO3fOUEte8enhGUC+iapTCzEWw2jEFFUO/w==",
1973 | "dev": true,
1974 | "requires": {
1975 | "estree-walker": "^0.6.1",
1976 | "magic-string": "^0.25.3",
1977 | "rollup-pluginutils": "^2.8.1"
1978 | },
1979 | "dependencies": {
1980 | "estree-walker": {
1981 | "version": "0.6.1",
1982 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz",
1983 | "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==",
1984 | "dev": true
1985 | }
1986 | }
1987 | },
1988 | "rollup-plugin-node-polyfills": {
1989 | "version": "0.2.1",
1990 | "resolved": "https://registry.npmjs.org/rollup-plugin-node-polyfills/-/rollup-plugin-node-polyfills-0.2.1.tgz",
1991 | "integrity": "sha512-4kCrKPTJ6sK4/gLL/U5QzVT8cxJcofO0OU74tnB19F40cmuAKSzH5/siithxlofFEjwvw1YAhPmbvGNA6jEroA==",
1992 | "dev": true,
1993 | "requires": {
1994 | "rollup-plugin-inject": "^3.0.0"
1995 | }
1996 | },
1997 | "rollup-plugin-terser": {
1998 | "version": "7.0.2",
1999 | "resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz",
2000 | "integrity": "sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==",
2001 | "dev": true,
2002 | "requires": {
2003 | "@babel/code-frame": "^7.10.4",
2004 | "jest-worker": "^26.2.1",
2005 | "serialize-javascript": "^4.0.0",
2006 | "terser": "^5.0.0"
2007 | }
2008 | },
2009 | "rollup-pluginutils": {
2010 | "version": "2.8.2",
2011 | "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz",
2012 | "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==",
2013 | "dev": true,
2014 | "requires": {
2015 | "estree-walker": "^0.6.1"
2016 | },
2017 | "dependencies": {
2018 | "estree-walker": {
2019 | "version": "0.6.1",
2020 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz",
2021 | "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==",
2022 | "dev": true
2023 | }
2024 | }
2025 | },
2026 | "safe-buffer": {
2027 | "version": "5.2.1",
2028 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
2029 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
2030 | "dev": true
2031 | },
2032 | "semver": {
2033 | "version": "6.3.0",
2034 | "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
2035 | "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
2036 | "dev": true,
2037 | "peer": true
2038 | },
2039 | "serialize-javascript": {
2040 | "version": "4.0.0",
2041 | "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz",
2042 | "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==",
2043 | "dev": true,
2044 | "requires": {
2045 | "randombytes": "^2.1.0"
2046 | }
2047 | },
2048 | "source-map": {
2049 | "version": "0.7.3",
2050 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
2051 | "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
2052 | "dev": true
2053 | },
2054 | "source-map-support": {
2055 | "version": "0.5.21",
2056 | "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
2057 | "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
2058 | "dev": true,
2059 | "requires": {
2060 | "buffer-from": "^1.0.0",
2061 | "source-map": "^0.6.0"
2062 | },
2063 | "dependencies": {
2064 | "source-map": {
2065 | "version": "0.6.1",
2066 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
2067 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
2068 | "dev": true
2069 | }
2070 | }
2071 | },
2072 | "sourcemap-codec": {
2073 | "version": "1.4.8",
2074 | "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
2075 | "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA=="
2076 | },
2077 | "supports-color": {
2078 | "version": "5.5.0",
2079 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
2080 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
2081 | "dev": true,
2082 | "requires": {
2083 | "has-flag": "^3.0.0"
2084 | }
2085 | },
2086 | "supports-preserve-symlinks-flag": {
2087 | "version": "1.0.0",
2088 | "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
2089 | "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="
2090 | },
2091 | "terser": {
2092 | "version": "5.12.1",
2093 | "resolved": "https://registry.npmjs.org/terser/-/terser-5.12.1.tgz",
2094 | "integrity": "sha512-NXbs+7nisos5E+yXwAD+y7zrcTkMqb0dEJxIGtSKPdCBzopf7ni4odPul2aechpV7EXNvOudYOX2bb5tln1jbQ==",
2095 | "dev": true,
2096 | "requires": {
2097 | "acorn": "^8.5.0",
2098 | "commander": "^2.20.0",
2099 | "source-map": "~0.7.2",
2100 | "source-map-support": "~0.5.20"
2101 | }
2102 | },
2103 | "to-fast-properties": {
2104 | "version": "2.0.0",
2105 | "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
2106 | "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=",
2107 | "dev": true
2108 | },
2109 | "wrappy": {
2110 | "version": "1.0.2",
2111 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
2112 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
2113 | }
2114 | }
2115 | }
2116 |
--------------------------------------------------------------------------------
/discodb/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "disco-db",
3 | "version": "1.0.3",
4 | "description": "A minimalist IndexedDB wrapper and syncing solution for when your application disco(nnects) from the network",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "https://github.com/oslabs-beta/DiscoDB.git"
12 | },
13 | "keywords": [
14 | "indexeddb",
15 | "service",
16 | "worker",
17 | "offline",
18 | "synchronization"
19 | ],
20 | "author": "",
21 | "license": "MIT",
22 | "contributors": [
23 | {
24 | "name": "Young Min Lee",
25 | "url": "https://github.com/youngmineeh"
26 | },
27 | {
28 | "name": "Jackson Tong",
29 | "url": "https://github.com/jacksonktong"
30 | },
31 | {
32 | "name": "Eric Gomez",
33 | "url": "https://github.com/ergomez0201"
34 | },
35 | {
36 | "name": "Eric McCorkle",
37 | "url": "https://github.com/ericmccorkle"
38 | }
39 | ],
40 | "bugs": {
41 | "url": "https://github.com/oslabs-beta/DiscoDB/issues"
42 | },
43 | "homepage": "https://discodb.dev/"
44 | }
45 |
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "DiscoDB",
3 | "lockfileVersion": 2,
4 | "requires": true,
5 | "packages": {}
6 | }
7 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------