diff --git a/.gitignore b/.gitignore index 21f36683..bdb16b16 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +dist + # Cloudflare Workers worker @@ -92,7 +94,6 @@ out # Nuxt.js build / generate output .nuxt -dist # Gatsby files .cache/ diff --git a/.npmignore b/.npmignore index 061a7967..d6833efa 100644 --- a/.npmignore +++ b/.npmignore @@ -1,3 +1,5 @@ example benchmark -.github \ No newline at end of file +.github +tsconfig.json +src diff --git a/README.md b/README.md index 779f6bc0..91cbad48 100644 --- a/README.md +++ b/README.md @@ -16,19 +16,21 @@ app.fire() ## Feature - Fast - the router is implemented with Trie-Tree structure. -- Tiny - use only standard API. - Portable - zero dependencies. - Flexible - you can make your own middlewares. -- Optimized - for Cloudflare Workers and Fastly Compute@Edge. +- Easy - simple API, builtin middleware, and TypeScript support. +- Optimized - for Cloudflare Workers or Fastly Compute@Edge. ## Benchmark +Hono is fastest!! + ``` -hono x 813,001 ops/sec ±2.96% (75 runs sampled) -itty-router x 160,415 ops/sec ±3.31% (85 runs sampled) -sunder x 307,438 ops/sec ±4.77% (73 runs sampled) +hono x 758,264 ops/sec ±5.41% (75 runs sampled) +itty-router x 158,359 ops/sec ±3.21% (89 runs sampled) +sunder x 297,581 ops/sec ±4.74% (83 runs sampled) Fastest is hono -✨ Done in 37.03s. +✨ Done in 42.84s. ``` ## Install @@ -45,8 +47,8 @@ $ npm install hono ## Methods -- app.**HTTP_METHOD**(path, callback) -- app.**all**(path, callback) +- app.**HTTP_METHOD**(path, handler) +- app.**all**(path, handler) - app.**route**(path) - app.**use**(path, middleware) @@ -120,25 +122,25 @@ const { Hono, Middleware } = require('hono') ... -app.use('*', Middleware.poweredBy) +app.use('*', Middleware.poweredBy()) +app.use('*', Middleware.logger()) ``` ### Custom Middleware ```js -const logger = async (c, next) => { +// Custom logger +app.use('*', async (c, next) => { console.log(`[${c.req.method}] ${c.req.url}`) await next() -} +}) -const addHeader = async (c, next) => { +// Add custom header +app.use('/message/*', async (c, next) => { await next() await c.res.headers.add('x-message', 'This is middleware!') -} - -app.use('*', logger) -app.use('/message/*', addHeader) +}) app.get('/message/hello', () => 'Hello Middleware!') ``` @@ -146,20 +148,18 @@ app.get('/message/hello', () => 'Hello Middleware!') ### Custom 404 Response ```js -const customNotFound = async (c, next) => { +app.use('*', async (c, next) => { await next() if (c.res.status === 404) { c.res = new Response('Custom 404 Not Found', { status: 404 }) } -} - -app.use('*', customNotFound) +}) ``` ### Complex Pattern ```js -// Log response time +// Output response time app.use('*', async (c, next) => { await next() const responseTime = await c.res.headers.get('X-Response-Time') @@ -180,38 +180,41 @@ app.use('*', async (c, next) => { ### req ```js + +// Get Request object app.get('/hello', (c) => { const userAgent = c.req.headers.get('User-Agent') ... }) + +// Query params +app.get('/search', (c) => { + const query = c.req.query('q') + ... +}) + +// Captured params +app.get('/entry/:id', (c) => { + const id = c.req.params('id') + ... +}) ``` ### res ```js +// Response object app.use('/', (c, next) => { next() c.res.headers.append('X-Debug', 'Debug message') }) ``` -## Request - -### query +### text ```js -app.get('/search', (c) => { - const query = c.req.query('q') - ... -}) -``` - -### params - -```js -app.get('/entry/:id', (c) => { - const id = c.req.params('id') - ... +app.get('/say', (c) => { + return c.text('Hello!') }) ``` @@ -219,7 +222,7 @@ app.get('/entry/:id', (c) => { Create your first Cloudflare Workers with Hono from scratch. -### Demo +### How to setup ![Demo](https://user-images.githubusercontent.com/10682/147877447-ff5907cd-49be-4976-b3b4-5df2ac6dfda4.gif) diff --git a/benchmark/handleEvent/index.js b/benchmark/handleEvent/index.js index 194dc0f0..0e9029f6 100644 --- a/benchmark/handleEvent/index.js +++ b/benchmark/handleEvent/index.js @@ -1,6 +1,6 @@ import Benchmark from 'benchmark' import { makeEdgeEnv } from 'edge-mock' -import { Hono } from '../../src/hono.js' +import { Hono } from '../../dist/index' import itty from 'itty-router' const { Router: IttyRouter } = itty import { Router as SunderRouter, Sunder } from 'sunder' diff --git a/benchmark/webapp/hono/index.js b/benchmark/webapp/hono/index.js index 5f370ce5..d668de46 100644 --- a/benchmark/webapp/hono/index.js +++ b/benchmark/webapp/hono/index.js @@ -1,4 +1,4 @@ -const { Hono } = require('../../../src/hono') +const { Hono } = require('../../../dist/index') const hono = new Hono() hono.get('/user', () => new Response('User')) diff --git a/example/basic/index.js b/example/basic/index.js index 057bee6a..4a1e0207 100644 --- a/example/basic/index.js +++ b/example/basic/index.js @@ -1,17 +1,18 @@ -const { Hono, Middleware } = require('../../src/hono') +const { Hono, Middleware } = require('../../dist/index') +// or install from npm: +// const { Hono, Middleware } = require('hono') const app = new Hono() // Mount Builtin Middleware -app.use('*', Middleware.poweredBy) +app.use('*', Middleware.poweredBy()) app.use('*', Middleware.logger()) // Custom Middleware // Add Custom Header -const addHeader = async (c, next) => { +app.use('/hello/*', async (c, next) => { await next() await c.res.headers.append('X-message', 'This is addHeader middleware!') -} -app.use('/hello/*', addHeader) +}) // Log response time app.use('*', async (c, next) => { @@ -29,7 +30,7 @@ app.use('*', async (c, next) => { }) // Routing -app.get('/', () => 'Hono!!') +app.get('/', () => new Response('Hono!!')) app.get('/hello', () => new Response('This is /hello')) app.get('/entry/:id', (c) => { const id = c.req.params('id') @@ -42,6 +43,7 @@ app.get('/fetch-url', async () => { return new Response(`https://example.com/ is ${response.status}`) }) +// Request headers app.get('/user-agent', (c) => { const userAgent = c.req.headers.get('User-Agent') return new Response(`Your UserAgent is ${userAgent}`) diff --git a/example/compute-at-edge/index.js b/example/compute-at-edge/index.js index 71e95deb..5e013764 100644 --- a/example/compute-at-edge/index.js +++ b/example/compute-at-edge/index.js @@ -1,7 +1,7 @@ const { Hono } = require('hono') const app = new Hono() -app.use('*', (c, next) => { +app.use('*', async (c, next) => { console.log(`[${c.req.method}] ${c.req.url}`) next() }) diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 00000000..2c1fc104 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,6 @@ +module.exports = { + testMatch: ['**/test/**/*.+(ts|tsx|js)', '**/src/**/(*.)+(spec|test).+(ts|tsx|js)'], + transform: { + '^.+\\.(ts|tsx)$': 'ts-jest', + }, +} diff --git a/package.json b/package.json index 7320c0e9..51c845d1 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,17 @@ { "name": "hono", - "version": "0.0.10", - "description": "Minimal web framework for Cloudflare Workers and Fastly Compute@Edge", - "main": "src/hono.js", + "version": "0.0.11", + "description": "Tiny web framework for Cloudflare Workers and others.", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "files": [ + "dist" + ], "scripts": { - "test": "jest" + "test": "jest", + "build": "rimraf dist && tsc", + "watch": "tsc -w", + "prepublishOnly": "yarn build" }, "author": "Yusuke Wada (https://github.com/yusukebe)", "license": "MIT", @@ -26,7 +33,13 @@ "compute@edge" ], "devDependencies": { - "edge-mock": "^0.0.15", - "jest": "^27.4.5" + "@cloudflare/workers-types": "^3.3.0", + "@types/jest": "^27.4.0", + "@types/service-worker-mock": "^2.0.1", + "jest": "^27.4.5", + "rimraf": "^3.0.2", + "service-worker-mock": "^2.0.5", + "ts-jest": "^27.1.2", + "typescript": "^4.5.4" } -} +} \ No newline at end of file diff --git a/src/compose.js b/src/compose.ts similarity index 77% rename from src/compose.js rename to src/compose.ts index 665ec952..4d92f651 100644 --- a/src/compose.js +++ b/src/compose.ts @@ -1,9 +1,9 @@ // Based on the code in the MIT licensed `koa-compose` package. -const compose = (middleware) => { - return function (context, next) { +export const compose = (middleware: any) => { + return function (context: any, next?: Function) { let index = -1 return dispatch(0) - function dispatch(i) { + function dispatch(i: number): any { if (i <= index) return Promise.reject(new Error('next() called multiple times')) index = i let fn = middleware[i] @@ -17,5 +17,3 @@ const compose = (middleware) => { } } } - -module.exports = compose diff --git a/src/hono.d.ts b/src/hono.d.ts deleted file mode 100644 index 58bb46a9..00000000 --- a/src/hono.d.ts +++ /dev/null @@ -1,67 +0,0 @@ -type Result = { - handler: any - params: {} -} - -declare class Node { - method: string - handler: any - children: Node[] - middlewares: any[] - - insert(method: string, path: string, handler: any): Node - search(method: string, path: string): Result -} - -declare class Context { - req: Request - res: Response - newResponse(params: {}): Response -} - -type Handler = (c: Context, next: () => void) => Response | void - -declare class Router { - tempPath: string - node: Node - - add(method: string, path: string, handler: Handler): Node - match(method: string, path: string): Node -} - -export class Hono { - router: Router - middlewareRouters: Router[] - - use(path: string, middleware: Handler): void - - route(path: string): Hono - fire(): void - - all(path: string, handler: Handler): Hono - get(path: string, handler: Handler): Hono - post(path: string, handler: Handler): Hono - put(path: string, handler: Handler): Hono - head(path: string, handler: Handler): Hono - delete(path: string, handler: Handler): Hono - - notFound(): Response - - getRouter(): Router - addRoute(method: string, args: any[]): Hono - matchRoute(method: string, path: string): Promise - createContext(req: Request, res: Response): Promise - dispatch(req: Request, res: Response): Promise - handleEvent(event: FetchEvent): Promise -} - -export class Middleware { - static defaultFilter: Handler - // Add builtin middlewares - static poweredBy: Handler -} - -interface FetchEvent extends Event { - request: Request - respondWith(response: Promise | Response): Promise -} diff --git a/src/hono.js b/src/hono.js deleted file mode 100644 index d084fa0f..00000000 --- a/src/hono.js +++ /dev/null @@ -1,141 +0,0 @@ -'use strict' - -const Node = require('./node') -const Middleware = require('./middleware') -const compose = require('./compose') -const methods = require('./methods') -const { getPathFromURL } = require('./util') - -const METHOD_NAME_OF_ALL = 'ALL' - -class Router { - constructor() { - this.node = new Node() - this.tempPath = '/' - } - - add(method, path, ...handlers) { - this.node.insert(method, path, handlers) - } - - match(method, path) { - return this.node.search(method, path) - } -} - -class Hono { - constructor() { - this.router = new Router() - this.middlewareRouters = [] - - for (const method of methods) { - this[method] = (...args) => { - return this.addRoute(method, ...args) - } - } - } - - all(...args) { - this.addRoute('ALL', ...args) - } - - getRouter() { - return this.router - } - - addRoute(method, ...args) { - method = method.toUpperCase() - if (args.length === 1) { - this.router.add(method, this.router.tempPath, ...args) - } else { - this.router.add(method, ...args) - } - return this - } - - route(path) { - this.router.tempPath = path - return this - } - - use(path, middleware) { - if (middleware.constructor.name !== 'AsyncFunction') { - throw new TypeError('middleware must be a async function!') - } - - const router = new Router() - router.add(METHOD_NAME_OF_ALL, path, middleware) - this.middlewareRouters.push(router) - } - - async matchRoute(method, path) { - return this.router.match(method, path) - } - - // XXX - async createContext(req, res) { - return { - req: req, - res: res, - newResponse: (params) => { - return new Response(params) - }, - } - } - - async dispatch(request, response) { - const [method, path] = [request.method, getPathFromURL(request.url)] - - const result = await this.matchRoute(method, path) - - request.params = (key) => result.params[key] - - let handler = result ? result.handler[0] : this.notFound // XXX - - const middleware = [] - - for (const mr of this.middlewareRouters) { - const mwResult = mr.match(METHOD_NAME_OF_ALL, path) - if (mwResult) { - middleware.push(...mwResult.handler) - } - } - - let wrappedHandler = async (context, next) => { - context.res = await handler(context) - await next() - } - - middleware.push(Middleware.defaultFilter) - middleware.push(wrappedHandler) - - const composed = compose(middleware) - const c = await this.createContext(request, response) - - await composed(c) - - return c.res - } - - async handleEvent(event) { - return this.dispatch(event.request, {}) // XXX - } - - fire() { - addEventListener('fetch', (event) => { - event.respondWith(this.handleEvent(event)) - }) - } - - notFound() { - return new Response('Not Found', { status: 404 }) - } -} - -// Default Export -module.exports = Hono -exports = module.exports - -// Named Export -exports.Hono = Hono -exports.Middleware = Middleware diff --git a/src/hono.ts b/src/hono.ts new file mode 100644 index 00000000..79720fae --- /dev/null +++ b/src/hono.ts @@ -0,0 +1,196 @@ +import { Node, Result } from './node' +import { compose } from './compose' +import { getPathFromURL } from './util' +import { Middleware } from './middleware' + +export { Middleware } + +const METHOD_NAME_OF_ALL = 'ALL' + +declare global { + interface Request { + params: (key: string) => any + query: (key: string) => string | null + } +} + +export class Context { + req: Request + res: Response + + constructor(req: Request, res: Response) { + this.req = req + this.res = res + } + + newResponse(body?: BodyInit | null | undefined, init?: ResponseInit | undefined): Response { + return new Response(body, init) + } + + text(body: string) { + return this.newResponse(body, { + status: 200, + headers: { + 'Content-Type': 'text/plain', + }, + }) + } +} + +type Handler = (c: Context, next?: Function) => Response | Promise +type MiddlwareHandler = (c: Context, next: Function) => Promise + +export class Router { + node: Node + + constructor() { + this.node = new Node() + } + + add(method: string, path: string, handler: T) { + this.node.insert(method, path, handler) + } + + match(method: string, path: string): Result | null { + return this.node.search(method, path) + } +} + +export class Hono { + router: Router + middlewareRouters: Router[] + tempPath: string + + constructor() { + this.router = new Router() + this.middlewareRouters = [] + this.tempPath = '/' + } + + /* HTTP METHODS */ + get(arg: string | Handler, ...args: Handler[]): Hono { + return this.addRoute('get', arg, ...args) + } + post(arg: string | Handler, ...args: Handler[]): Hono { + return this.addRoute('post', arg, ...args) + } + put(arg: string | Handler, ...args: Handler[]): Hono { + return this.addRoute('put', arg, ...args) + } + head(arg: string | Handler, ...args: Handler[]): Hono { + return this.addRoute('head', arg, ...args) + } + delete(arg: string | Handler, ...args: Handler[]): Hono { + return this.addRoute('delete', arg, ...args) + } + options(arg: string | Handler, ...args: Handler[]): Hono { + return this.addRoute('options', arg, ...args) + } + patch(arg: string | Handler, ...args: Handler[]): Hono { + return this.addRoute('patch', arg, ...args) + } + + /* + trace + copy + lock + purge + unlock + report + checkout + merge + notify + subscribe + unsubscribe + search + connect + */ + + all(arg: string | Handler, ...args: Handler[]): Hono { + return this.addRoute('all', arg, ...args) + } + + route(path: string): Hono { + this.tempPath = path + return this + } + + use(path: string, middleware: MiddlwareHandler): void { + if (middleware.constructor.name !== 'AsyncFunction') { + throw new TypeError('middleware must be a async function!') + } + + const router = new Router() + router.add(METHOD_NAME_OF_ALL, path, middleware) + this.middlewareRouters.push(router) + } + + // addRoute('get', '/', handler) + addRoute(method: string, arg: string | Handler, ...args: Handler[]): Hono { + method = method.toUpperCase() + if (typeof arg === 'string') { + this.router.add(method, arg, args) + } else { + args.unshift(arg) + this.router.add(method, this.tempPath, args) + } + return this + } + + async matchRoute(method: string, path: string): Promise> { + return this.router.match(method, path) + } + + async dispatch(request: Request, response?: Response) { + const [method, path] = [request.method, getPathFromURL(request.url)] + + const result = await this.matchRoute(method, path) + + request.params = (key: string): string => { + if (result) { + return result.params[key] + } + return '' + } + + let handler = result ? result.handler[0] : this.notFound // XXX + + const middleware = [] + + for (const mr of this.middlewareRouters) { + const mwResult = mr.match(METHOD_NAME_OF_ALL, path) + if (mwResult) { + middleware.push(mwResult.handler) + } + } + + let wrappedHandler = async (context: Context, next: Function) => { + context.res = await handler(context) + await next() + } + + middleware.push(Middleware.defaultFilter) + middleware.push(wrappedHandler) + + const composed = compose(middleware) + const c = new Context(request, response) + + await composed(c) + + return c.res + } + + async handleEvent(event: FetchEvent): Promise { + return this.dispatch(event.request) + } + + fire() { + addEventListener('fetch', (event: FetchEvent): void => { + event.respondWith(this.handleEvent(event)) + }) + } + + notFound() { + return new Response('Not Found', { status: 404 }) + } +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 00000000..0ec1fd81 --- /dev/null +++ b/src/index.ts @@ -0,0 +1 @@ +export { Hono, Middleware, Context } from './hono' diff --git a/src/methods.js b/src/methods.ts similarity index 87% rename from src/methods.js rename to src/methods.ts index 5ab1fa8d..2d9f97d9 100644 --- a/src/methods.js +++ b/src/methods.ts @@ -1,4 +1,4 @@ -const methods = [ +export const methods = [ 'get', 'post', 'put', @@ -26,5 +26,3 @@ const methods = [ 'search', 'connect', ] - -module.exports = methods diff --git a/src/middleware.js b/src/middleware.js deleted file mode 100644 index 02be2aa4..00000000 --- a/src/middleware.js +++ /dev/null @@ -1,11 +0,0 @@ -const defaultFilter = require('./middleware/defaultFilter') -const poweredBy = require('./middleware/poweredBy/poweredBy') -const logger = require('./middleware/logger/logger') - -function Middleware() {} - -Middleware.defaultFilter = defaultFilter -Middleware.poweredBy = poweredBy -Middleware.logger = logger - -module.exports = Middleware diff --git a/src/middleware.ts b/src/middleware.ts new file mode 100644 index 00000000..9ed0700b --- /dev/null +++ b/src/middleware.ts @@ -0,0 +1,9 @@ +import { defaultFilter } from './middleware/defaultFilter' +import { poweredBy } from './middleware/poweredBy/poweredBy' +import { logger } from './middleware/logger/logger' + +export class Middleware { + static defaultFilter = defaultFilter + static poweredBy = poweredBy + static logger = logger +} diff --git a/src/middleware/defaultFilter.js b/src/middleware/defaultFilter.js deleted file mode 100644 index 13132f95..00000000 --- a/src/middleware/defaultFilter.js +++ /dev/null @@ -1,19 +0,0 @@ -const defaultFilter = async (c, next) => { - c.req.query = (key) => { - const url = new URL(c.req.url) - return url.searchParams.get(key) - } - - await next() - - if (typeof c.res === 'string') { - c.res = new Response(c.res, { - status: 200, - headers: { - 'Conten-Type': 'text/plain', - }, - }) - } -} - -module.exports = defaultFilter diff --git a/src/middleware/defaultFilter.ts b/src/middleware/defaultFilter.ts new file mode 100644 index 00000000..26361c24 --- /dev/null +++ b/src/middleware/defaultFilter.ts @@ -0,0 +1,10 @@ +import { Context } from '../hono' + +export const defaultFilter = async (c: Context, next: Function) => { + c.req.query = (key: string) => { + const url = new URL(c.req.url) + return url.searchParams.get(key) + } + + await next() +} diff --git a/src/middleware/logger/logger.test.js b/src/middleware/logger/logger.test.ts similarity index 79% rename from src/middleware/logger/logger.test.js rename to src/middleware/logger/logger.test.ts index 6ac42045..29c15884 100644 --- a/src/middleware/logger/logger.test.js +++ b/src/middleware/logger/logger.test.ts @@ -1,13 +1,14 @@ -const { makeEdgeEnv } = require('edge-mock') -const { Hono, Middleware } = require('../../hono') +import makeServiceWorkerEnv from 'service-worker-mock' +import { Hono, Middleware } from '../../hono' -makeEdgeEnv() +declare var global: any +Object.assign(global, makeServiceWorkerEnv()) describe('Logger by Middleware', () => { const app = new Hono() let log = '' - const logFn = (str) => { + const logFn = (str: string) => { log = str } diff --git a/src/middleware/logger/logger.js b/src/middleware/logger/logger.ts similarity index 68% rename from src/middleware/logger/logger.js rename to src/middleware/logger/logger.ts index 4a90d3f7..aee009c6 100644 --- a/src/middleware/logger/logger.js +++ b/src/middleware/logger/logger.ts @@ -1,6 +1,7 @@ -const { getPathFromURL } = require('../../util') +import { getPathFromURL } from '../../util' +import { Context } from '../../hono' -const humanize = (n, opts) => { +const humanize = (n: string[], opts?: any) => { const options = opts || {} const d = options.delimiter || ',' const s = options.separator || '.' @@ -9,9 +10,9 @@ const humanize = (n, opts) => { return n.join(s) } -const time = (start) => { +const time = (start: number) => { const delta = Date.now() - start - return humanize(delta < 10000 ? delta + 'ms' : Math.round(delta / 1000) + 's') + return humanize([delta < 10000 ? delta + 'ms' : Math.round(delta / 1000) + 's']) } const LogPrefix = { @@ -20,8 +21,8 @@ const LogPrefix = { Error: 'xxx', } -const colorStatus = (status) => { - const out = { +const colorStatus = (status: number = 0) => { + const out: { [key: number]: string } = { 7: `\x1b[35m${status}\x1b[0m`, 5: `\x1b[31m${status}\x1b[0m`, 4: `\x1b[33m${status}\x1b[0m`, @@ -32,7 +33,7 @@ const colorStatus = (status) => { } return out[(status / 100) | 0] } -function log(fn, prefix, method, path, status, elasped) { +function log(fn: Function, prefix: string, method: string, path: string, status?: number, elasped?: string) { const out = prefix === LogPrefix.Incoming ? ` ${prefix} ${method} ${path}` @@ -40,8 +41,8 @@ function log(fn, prefix, method, path, status, elasped) { fn(out) } -const logger = (fn = console.log) => { - return async (c, next) => { +export const logger = (fn = console.log) => { + return async (c: Context, next: Function) => { const { method } = c.req const path = getPathFromURL(c.req.url) @@ -59,5 +60,3 @@ const logger = (fn = console.log) => { log(fn, LogPrefix.Outgoing, method, path, c.res.status, time(start)) } } - -module.exports = logger diff --git a/src/middleware/poweredBy/poweredBy.js b/src/middleware/poweredBy/poweredBy.js deleted file mode 100644 index c1ad1ab5..00000000 --- a/src/middleware/poweredBy/poweredBy.js +++ /dev/null @@ -1,6 +0,0 @@ -const poweredBy = async (c, next) => { - await next() - await c.res.headers.append('X-Powered-By', 'Hono') -} - -module.exports = poweredBy diff --git a/src/middleware/poweredBy/poweredBy.test.js b/src/middleware/poweredBy/poweredBy.test.ts similarity index 63% rename from src/middleware/poweredBy/poweredBy.test.js rename to src/middleware/poweredBy/poweredBy.test.ts index deaaf18e..f551dc5d 100644 --- a/src/middleware/poweredBy/poweredBy.test.js +++ b/src/middleware/poweredBy/poweredBy.test.ts @@ -1,12 +1,13 @@ -const { makeEdgeEnv } = require('edge-mock') -const { Hono, Middleware } = require('../../hono') +import makeServiceWorkerEnv from 'service-worker-mock' +import { Hono, Middleware } from '../../hono' -makeEdgeEnv() +declare var global: any +Object.assign(global, makeServiceWorkerEnv()) describe('Powered by Middleware', () => { const app = new Hono() - app.use('*', Middleware.poweredBy) + app.use('*', Middleware.poweredBy()) app.get('/', () => new Response('root')) it('Response headers include X-Powered-By', async () => { diff --git a/src/middleware/poweredBy/poweredBy.ts b/src/middleware/poweredBy/poweredBy.ts new file mode 100644 index 00000000..26bf0612 --- /dev/null +++ b/src/middleware/poweredBy/poweredBy.ts @@ -0,0 +1,9 @@ +import { Context } from '../../hono' + +export const poweredBy = () => { + return async (c: Context, next: Function) => { + await next() + // await c.res.headers.append('X-Powered-By', 'Hono') + c.res.headers.append('X-Powered-By', 'Hono') + } +} diff --git a/src/node.js b/src/node.js deleted file mode 100644 index fc7f9f2c..00000000 --- a/src/node.js +++ /dev/null @@ -1,97 +0,0 @@ -'use strict' - -const { splitPath, getPattern } = require('./util') - -const METHOD_NAME_OF_ALL = 'ALL' - -const createResult = (handler, params) => { - return { handler: handler, params: params } -} - -const noRoute = () => { - return null -} - -function Node(method, handler, children) { - this.children = children || {} - this.method = {} - if (method && handler) { - this.method[method] = handler - } - this.middlewares = [] -} - -Node.prototype.insert = function (method, path, handler) { - let curNode = this - const parts = splitPath(path) - for (let i = 0; i < parts.length; i++) { - const p = parts[i] - if (Object.keys(curNode.children).includes(p)) { - curNode = curNode.children[p] - continue - } - curNode.children[p] = new Node(method, handler) - curNode = curNode.children[p] - } - curNode.method[method] = handler - return curNode -} - -Node.prototype.search = function (method, path) { - let curNode = this - - const params = {} - const parts = splitPath(path) - - for (let i = 0; i < parts.length; i++) { - const p = parts[i] - const nextNode = curNode.children[p] - if (nextNode) { - curNode = nextNode - continue - } - - let isWildcard = false - let isParamMatch = false - const keys = Object.keys(curNode.children) - for (let j = 0; j < keys.length; j++) { - const key = keys[j] - // Wildcard - if (key === '*') { - curNode = curNode.children['*'] - isWildcard = true - break - } - const pattern = getPattern(key) - if (pattern) { - const match = p.match(new RegExp(pattern[1])) - if (match) { - const k = pattern[0] - params[k] = match[1] - curNode = curNode.children[key] - isParamMatch = true - break - } - return noRoute() - } - } - - if (isWildcard) { - break - } - - if (isParamMatch === false) { - return noRoute() - } - } - - const handler = curNode.method[METHOD_NAME_OF_ALL] || curNode.method[method] - - if (!handler) { - return noRoute() - } - - return createResult(handler, params) -} - -module.exports = Node diff --git a/src/node.ts b/src/node.ts new file mode 100644 index 00000000..f592bc29 --- /dev/null +++ b/src/node.ts @@ -0,0 +1,123 @@ +import { splitPath, getPattern } from './util' + +const METHOD_NAME_OF_ALL = 'ALL' + +export class Result { + handler: T + params: { [key: string]: string } + constructor(handler: T, params: { [key: string]: string }) { + this.handler = handler + this.params = params + } +} + +const noRoute = (): null => { + return null +} + +export class Node { + method: { [key: string]: T } + handler: T + children: { [key: string]: Node } + middlewares: [] + + constructor(method?: string, handler?: any, children?: { [key: string]: Node }) { + this.children = children || {} + this.method = {} + if (method && handler) { + this.method[method] = handler + } + this.middlewares = [] + } + + insert(method: string, path: string, handler: T): Node { + let curNode: Node = this + const parts = splitPath(path) + for (let i = 0, len = parts.length; i < len; i++) { + const p: string = parts[i] + if (Object.keys(curNode.children).includes(p)) { + curNode = curNode.children[p] + continue + } + curNode.children[p] = new Node() + curNode = curNode.children[p] + } + curNode.method[method] = handler + return curNode + } + + search(method: string, path: string): Result { + let curNode: Node = this + + const params: { [key: string]: string } = {} + let parts = splitPath(path) + + for (let i = 0, len = parts.length; i < len; i++) { + const p: string = parts[i] + + // '*' => match any path + if (curNode.children['*']) { + const astNode = curNode.children['*'] + if (Object.keys(astNode.children).length === 0) { + curNode = astNode + break + } + } + + const nextNode = curNode.children[p] + + if (nextNode) { + curNode = nextNode + // '/hello/*' => match '/hello' + if (!(i == len - 1 && nextNode.children['*'])) { + continue + } + } + + let isWildcard = false + let isParamMatch = false + const keys = Object.keys(curNode.children) + + for (let j = 0, len = keys.length; j < len; j++) { + const key: string = keys[j] + + // Wildcard + // '/hello/*/foo' => match /hello/bar/foo + if (key === '*') { + curNode = curNode.children['*'] + isWildcard = true + break + } + const pattern = getPattern(key) + // Named match + if (pattern) { + const match = p.match(new RegExp(pattern[1])) + if (match) { + const k: string = pattern[0] + params[k] = match[1] + curNode = curNode.children[key] + isParamMatch = true + break + } + return noRoute() + } + } + + if (isWildcard && i === len - 1) { + break + } + + if (isWildcard === false && isParamMatch === false) { + return noRoute() + } + } + + const handler = curNode.method[METHOD_NAME_OF_ALL] || curNode.method[method] + + if (!handler) { + return noRoute() + } + + return new Result(handler, params) + } +} diff --git a/src/util.js b/src/util.ts similarity index 51% rename from src/util.js rename to src/util.ts index 91346439..fcd98c84 100644 --- a/src/util.js +++ b/src/util.ts @@ -1,12 +1,12 @@ -const splitPath = (path) => { - path = path.split(/\//) // faster than path.split('/') - if (path[0] === '') { - path.shift() +export const splitPath = (path: string): string[] => { + const paths = path.split(/\//) // faster than path.split('/') + if (paths[0] === '') { + paths.shift() } - return path + return paths } -const getPattern = (label) => { +export const getPattern = (label: string): string[] | null => { // :id{[0-9]+} => ([0-9]+) // :id => (.+) //const name = '' @@ -18,16 +18,14 @@ const getPattern = (label) => { return [match[1], '(.+)'] } } + return null } -const getPathFromURL = (url) => { +export const getPathFromURL = (url: string) => { // XXX const match = url.match(/^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/) - return match[5] -} - -module.exports = { - splitPath, - getPattern, - getPathFromURL, + if (match) { + return match[5] + } + return '' } diff --git a/test/compose.test.js b/test/compose.test.ts similarity index 61% rename from test/compose.test.js rename to test/compose.test.ts index 7be6189e..2fa1c9e7 100644 --- a/test/compose.test.js +++ b/test/compose.test.ts @@ -1,21 +1,26 @@ -const compose = require('../src/compose') +import { compose } from '../src/compose' + +class C { + req: { [key: string]: any } + res: { [key: string]: any } +} describe('compose middleware', () => { - const middleware = [] + const middleware: Function[] = [] - const a = async (c, next) => { + const a = async (c: C, next: Function) => { c.req['log'] = 'log' await next() } middleware.push(a) - const b = async (c, next) => { + const b = async (c: C, next: Function) => { await next() - c.res['header'] = `${c.res.header}-custom-header` + c.res['headers'] = `${c.res.headers}-custom-header` } middleware.push(b) - const handler = async (c, next) => { + const handler = async (c: C, next: Function) => { c.req['log'] = `${c.req.log} message` await next() c.res = { message: 'new response' } @@ -26,14 +31,14 @@ describe('compose middleware', () => { const response = {} it('Request', async () => { - const c = { req: request, res: response } + const c: C = { req: request, res: response } const composed = compose(middleware) await composed(c) expect(c.req['log']).not.toBeNull() expect(c.req['log']).toBe('log message') }) it('Response', async () => { - const c = { req: request, res: response } + const c: C = { req: request, res: response } const composed = compose(middleware) await composed(c) expect(c.res['header']).not.toBeNull() diff --git a/test/hono.test.js b/test/hono.test.ts similarity index 64% rename from test/hono.test.js rename to test/hono.test.ts index 701795ff..7b77a152 100644 --- a/test/hono.test.js +++ b/test/hono.test.ts @@ -1,11 +1,11 @@ -const { makeEdgeEnv } = require('edge-mock') -const { Hono } = require('../src/hono') +import makeServiceWorkerEnv from 'service-worker-mock' +import { Hono, Context } from '../src/hono' -makeEdgeEnv() +declare var global: any +Object.assign(global, makeServiceWorkerEnv()) describe('GET Request', () => { const app = new Hono() - app.get('/hello', () => { return new Response('hello', { status: 200, @@ -31,6 +31,42 @@ describe('GET Request', () => { }) }) +describe('Routing', () => { + const app = new Hono() + + it('Return it self', async () => { + const appRes = app.get('/', () => new Response('get /')) + expect(appRes).not.toBeUndefined() + appRes.delete('/', () => new Response('delete /')) + let req = new Request('/', { method: 'DELETE' }) + const res = await appRes.dispatch(req) + expect(res).not.toBeNull() + expect(res.status).toBe(200) + expect(await res.text()).toBe('delete /') + }) + + it('Chained route', async () => { + app + .route('/route') + .get(() => new Response('get /route')) + .post(() => new Response('post /route')) + .put(() => new Response('put /route')) + let req = new Request('/route', { method: 'GET' }) + let res = await app.dispatch(req) + expect(res.status).toBe(200) + expect(await res.text()).toBe('get /route') + + req = new Request('/route', { method: 'POST' }) + res = await app.dispatch(req) + expect(res.status).toBe(200) + expect(await res.text()).toBe('post /route') + + req = new Request('/route', { method: 'DELETE' }) + res = await app.dispatch(req) + expect(res.status).toBe(404) + }) +}) + describe('params and query', () => { const app = new Hono() @@ -65,29 +101,27 @@ describe('params and query', () => { describe('Middleware', () => { const app = new Hono() - const logger = async (c, next) => { + // Custom Logger + app.use('*', async (c, next) => { console.log(`${c.req.method} : ${c.req.url}`) await next() - } + }) - const rootHeader = async (c, next) => { + // Apeend Custom Header + app.use('*', async (c, next) => { await next() await c.res.headers.append('x-custom', 'root') - } + }) - const customHeader = async (c, next) => { + app.use('/hello', async (c, next) => { await next() await c.res.headers.append('x-message', 'custom-header') - } - const customHeader2 = async (c, next) => { - await next() - c.res.headers.append('x-message-2', 'custom-header-2') - } + }) - app.use('*', logger) - app.use('*', rootHeader) - app.use('/hello', customHeader) - app.use('/hello/*', customHeader2) + app.use('/hello/*', async (c, next) => { + await next() + await c.res.headers.append('x-message-2', 'custom-header-2') + }) app.get('/hello', () => { return new Response('hello') @@ -120,21 +154,21 @@ describe('Middleware', () => { describe('Custom 404', () => { const app = new Hono() - const customNotFound = async (c, next) => { - await next() - if (c.res.status === 404) { - c.res = new Response('Custom 404 Not Found', { status: 404 }) - } - } - app.notFound = () => { return new Response('Default 404 Nout Found', { status: 404 }) } - app.use('*', customNotFound) + app.use('*', async (c, next) => { + await next() + if (c.res.status === 404) { + c.res = new Response('Custom 404 Not Found', { status: 404 }) + } + }) + app.get('/hello', () => { return new Response('hello') }) + it('Custom 404 Not Found', async () => { let req = new Request('/hello') let res = await app.dispatch(req) @@ -146,15 +180,16 @@ describe('Custom 404', () => { }) }) -describe('Error Handling', () => { +describe('Context', () => { const app = new Hono() + app.get('/', (c) => { + return c.text('get /') + }) - it('Middleware must be async function', () => { - expect(() => { - app.use('*', {}) - }).toThrow(TypeError) - expect(() => { - app.use('*', () => '') - }).toThrow(TypeError) + it('c.text', async () => { + let req = new Request('/') + let res = await app.dispatch(req) + expect(res.status).toBe(200) + expect(await res.text()).toBe('get /') }) }) diff --git a/test/middleware.test.js b/test/middleware.test.ts similarity index 63% rename from test/middleware.test.js rename to test/middleware.test.ts index 69db84e2..980e2c35 100644 --- a/test/middleware.test.js +++ b/test/middleware.test.ts @@ -1,12 +1,13 @@ -const { makeEdgeEnv } = require('edge-mock') -const { Hono, Middleware } = require('../src/hono') +import makeServiceWorkerEnv from 'service-worker-mock' +import { Hono, Middleware } from '../src/hono' -makeEdgeEnv() +declare var global: any +Object.assign(global, makeServiceWorkerEnv()) describe('Builtin Middleware', () => { const app = new Hono() - app.use('*', Middleware.poweredBy) + app.use('*', Middleware.poweredBy()) app.get('/', () => new Response('root')) it('Builtin Powered By Middleware', async () => { diff --git a/test/node.test.js b/test/node.test.ts similarity index 67% rename from test/node.test.js rename to test/node.test.ts index 462bd79a..28cfec2a 100644 --- a/test/node.test.js +++ b/test/node.test.ts @@ -1,4 +1,4 @@ -const Node = require('../src/node') +import { Node } from '../src/node' describe('Root Node', () => { const node = new Node() @@ -11,7 +11,7 @@ describe('Root Node', () => { }) }) -describe('Root Node id not defined', () => { +describe('Root Node is not defined', () => { const node = new Node() node.insert('get', '/hello', 'get hello') it('get /', () => { @@ -57,8 +57,11 @@ describe('Basic Usage', () => { describe('Name path', () => { const node = new Node() + node.insert('get', '/entry/:id', 'get entry') + node.insert('get', '/entry/:id/comment/:comment_id', 'get comment') + node.insert('get', '/map/:location/events', 'get events') + it('get /entry/123', () => { - node.insert('get', '/entry/:id', 'get entry') let res = node.search('get', '/entry/123') expect(res).not.toBeNull() expect(res.handler).toBe('get entry') @@ -68,13 +71,11 @@ describe('Name path', () => { }) it('get /entry/456/comment', () => { - node.insert('get', '/entry/:id', 'get entry') let res = node.search('get', '/entry/456/comment') expect(res).toBeNull() }) it('get /entry/789/comment/123', () => { - node.insert('get', '/entry/:id/comment/:comment_id', 'get comment') let res = node.search('get', '/entry/789/comment/123') expect(res).not.toBeNull() expect(res.handler).toBe('get comment') @@ -83,7 +84,6 @@ describe('Name path', () => { }) it('get /map/:location/events', () => { - node.insert('get', '/map/:location/events', 'get events') let res = node.search('get', '/map/yokohama/events') expect(res).not.toBeNull() expect(res.handler).toBe('get events') @@ -93,30 +93,36 @@ describe('Name path', () => { describe('Wildcard', () => { const node = new Node() + node.insert('get', '/wildcard-abc/*/wildcard-efg', 'wildcard') it('/wildcard-abc/xxxxxx/wildcard-efg', () => { - node.insert('get', '/wildcard-abc/*/wildcard-efg', 'wildcard') let res = node.search('get', '/wildcard-abc/xxxxxx/wildcard-efg') expect(res).not.toBeNull() expect(res.handler).toBe('wildcard') }) + node.insert('get', '/wildcard-abc/*/wildcard-efg/hijk', 'wildcard') + it('/wildcard-abc/xxxxxx/wildcard-efg/hijk', () => { + let res = node.search('get', '/wildcard-abc/xxxxxx/wildcard-efg/hijk') + expect(res).not.toBeNull() + expect(res.handler).toBe('wildcard') + }) }) describe('Regexp', () => { const node = new Node() node.insert('get', '/regex-abc/:id{[0-9]+}/comment/:comment_id{[a-z]+}', 'regexp') it('/regexp-abc/123/comment/abc', () => { - res = node.search('get', '/regex-abc/123/comment/abc') + const res = node.search('get', '/regex-abc/123/comment/abc') expect(res).not.toBeNull() expect(res.handler).toBe('regexp') expect(res.params['id']).toBe('123') expect(res.params['comment_id']).toBe('abc') }) it('/regexp-abc/abc', () => { - res = node.search('get', '/regex-abc/abc') + const res = node.search('get', '/regex-abc/abc') expect(res).toBeNull() }) it('/regexp-abc/123/comment/123', () => { - res = node.search('get', '/regex-abc/123/comment/123') + const res = node.search('get', '/regex-abc/123/comment/123') expect(res).toBeNull() }) }) @@ -125,7 +131,7 @@ describe('All', () => { const node = new Node() node.insert('ALL', '/all-methods', 'all methods') // ALL it('/all-methods', () => { - res = node.search('get', '/all-methods') + let res = node.search('get', '/all-methods') expect(res).not.toBeNull() expect(res.handler).toBe('all methods') res = node.search('put', '/all-methods') @@ -133,3 +139,39 @@ describe('All', () => { expect(res.handler).toBe('all methods') }) }) + +describe('Special Wildcard', () => { + const node = new Node() + node.insert('ALL', '*', 'match all') + + it('/foo', () => { + let res = node.search('get', '/foo') + expect(res).not.toBeNull() + expect(res.handler).toBe('match all') + }) + it('/hello', () => { + let res = node.search('get', '/hello') + expect(res).not.toBeNull() + expect(res.handler).toBe('match all') + }) + it('/hello/foo', () => { + let res = node.search('get', '/hello/foo') + expect(res).not.toBeNull() + expect(res.handler).toBe('match all') + }) +}) + +describe('Special Wildcard deeply', () => { + const node = new Node() + node.insert('ALL', '/hello/*', 'match hello') + it('/hello', () => { + let res = node.search('get', '/hello') + expect(res).not.toBeNull() + expect(res.handler).toBe('match hello') + }) + it('/hello/foo', () => { + let res = node.search('get', '/hello/foo') + expect(res).not.toBeNull() + expect(res.handler).toBe('match hello') + }) +}) diff --git a/test/router.test.js b/test/router.test.js deleted file mode 100644 index c307b573..00000000 --- a/test/router.test.js +++ /dev/null @@ -1,88 +0,0 @@ -const { Hono } = require('../src/hono') - -describe('Basic Usage', () => { - const router = new Hono() - - it('get, post hello', async () => { - router.get('/hello', 'get hello') - router.post('/hello', 'post hello') - - let res = await router.matchRoute('GET', '/hello') - expect(res).not.toBeNull() - expect(res.handler[0]).toBe('get hello') - - res = await router.matchRoute('POST', '/hello') - expect(res).not.toBeNull() - expect(res.handler[0]).toBe('post hello') - - res = await router.matchRoute('PUT', '/hello') - expect(res).toBeNull() - - res = await router.matchRoute('GET', '/') - expect(res).toBeNull() - }) -}) - -describe('Complex', () => { - let router = new Hono() - - it('Named Param', async () => { - router.get('/entry/:id', 'get entry') - res = await router.matchRoute('GET', '/entry/123') - expect(res).not.toBeNull() - expect(res.handler[0]).toBe('get entry') - expect(res.params['id']).toBe('123') - }) - - it('Wildcard', async () => { - router.get('/wild/*/card', 'get wildcard') - res = await router.matchRoute('GET', '/wild/xxx/card') - expect(res).not.toBeNull() - expect(res.handler[0]).toBe('get wildcard') - }) - - it('Regexp', async () => { - router.get('/post/:date{[0-9]+}/:title{[a-z]+}', 'get post') - res = await router.matchRoute('GET', '/post/20210101/hello') - expect(res).not.toBeNull() - expect(res.handler[0]).toBe('get post') - expect(res.params['date']).toBe('20210101') - expect(res.params['title']).toBe('hello') - res = await router.matchRoute('GET', '/post/onetwothree') - expect(res).toBeNull() - res = await router.matchRoute('GET', '/post/123/123') - expect(res).toBeNull() - }) -}) - -describe('Chained Route', () => { - let router = new Hono() - - it('Return rooter object', async () => { - router = router.patch('/hello', 'patch hello') - expect(router).not.toBeNull() - router = router.delete('/hello', 'delete hello') - res = await router.matchRoute('DELETE', '/hello') - expect(res).not.toBeNull() - expect(res.handler[0]).toBe('delete hello') - }) - - it('Chain with route method', async () => { - router.route('/api/book').get('get book').post('post book').put('put book') - - res = await router.matchRoute('GET', '/api/book') - expect(res).not.toBeNull() - expect(res.handler[0]).toBe('get book') - - res = await router.matchRoute('POST', '/api/book') - expect(res).not.toBeNull() - expect(res.handler[0]).toBe('post book') - - res = await router.matchRoute('PUT', '/api/book') - expect(res).not.toBeNull() - expect(res.handler[0]).toBe('put book') - - res = await router.matchRoute('DELETE', '/api/book') - expect(res).toBeNull() - }) -}) diff --git a/test/router.test.ts b/test/router.test.ts new file mode 100644 index 00000000..05915f56 --- /dev/null +++ b/test/router.test.ts @@ -0,0 +1,56 @@ +import { Router } from '../src/hono' + +describe('Basic Usage', () => { + const router = new Router() + + router.add('GET', '/hello', 'get hello') + router.add('POST', '/hello', 'post hello') + + it('get, post hello', async () => { + let res = router.match('GET', '/hello') + expect(res).not.toBeNull() + expect(res.handler).toBe('get hello') + + res = router.match('POST', '/hello') + expect(res).not.toBeNull() + expect(res.handler).toBe('post hello') + + res = router.match('PUT', '/hello') + expect(res).toBeNull() + + res = router.match('GET', '/') + expect(res).toBeNull() + }) +}) + +describe('Complex', () => { + const router = new Router() + + it('Named Param', async () => { + router.add('GET', '/entry/:id', 'get entry') + let res = router.match('GET', '/entry/123') + expect(res).not.toBeNull() + expect(res.handler).toBe('get entry') + expect(res.params['id']).toBe('123') + }) + + it('Wildcard', async () => { + router.add('GET', '/wild/*/card', 'get wildcard') + let res = router.match('GET', '/wild/xxx/card') + expect(res).not.toBeNull() + expect(res.handler).toBe('get wildcard') + }) + + it('Regexp', async () => { + router.add('GET', '/post/:date{[0-9]+}/:title{[a-z]+}', 'get post') + let res = router.match('GET', '/post/20210101/hello') + expect(res).not.toBeNull() + expect(res.handler).toBe('get post') + expect(res.params['date']).toBe('20210101') + expect(res.params['title']).toBe('hello') + res = router.match('GET', '/post/onetwothree') + expect(res).toBeNull() + res = router.match('GET', '/post/123/123') + expect(res).toBeNull() + }) +}) diff --git a/test/util.test.js b/test/util.test.ts similarity index 93% rename from test/util.test.js rename to test/util.test.ts index 7485f0fd..ebadd3ce 100644 --- a/test/util.test.js +++ b/test/util.test.ts @@ -1,4 +1,4 @@ -const { splitPath, getPattern, getPathFromURL } = require('../src/util') +import { splitPath, getPattern, getPathFromURL } from '../src/util' describe('Utility methods', () => { it('splitPath', () => { @@ -20,6 +20,7 @@ describe('Utility methods', () => { it('getPattern', () => { let res = getPattern(':id') + expect(res).not.toBeNull() expect(res[0]).toBe('id') expect(res[1]).toBe('(.+)') res = getPattern(':id{[0-9]+}') diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..317124f6 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "es2017", + "module": "commonjs", + "declaration": true, + "outDir": "./dist", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true, + "strictPropertyInitialization": false, + "strictNullChecks": false, + "types": [ + "jest", + "@cloudflare/workers-types" + ] + }, + "include": [ + "src/**/*" + ], + "exclude": [ + "src/**/*.test.ts" + ] +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 1f925cc0..1be1ce2a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -286,10 +286,10 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== -"@cloudflare/workers-types@^2.2.2": - version "2.2.2" - resolved "https://registry.yarnpkg.com/@cloudflare/workers-types/-/workers-types-2.2.2.tgz#1bced16bba801d7af987da835467545bb5cc7ac6" - integrity sha512-kaMn2rueJ0PL1TYVGknTCh0X0x0d9G+FNXAFep7/4uqecEZoQb/63o6rOmMuiqI09zLuHV6xhKRXinokV/MY9A== +"@cloudflare/workers-types@^3.3.0": + version "3.3.0" + resolved "https://registry.yarnpkg.com/@cloudflare/workers-types/-/workers-types-3.3.0.tgz#a05cf8e27bf007a93c6437bf22daa616b1b5fc42" + integrity sha512-3v3bm/hOuzNtHgDqPowrRE63H0GEn40LfhLMVpzS5yeg5tlE5nEQ0qobmGOJBCvJJ1LhgRRHZTJszXHs1DXQWg== "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" @@ -554,6 +554,14 @@ dependencies: "@types/istanbul-lib-report" "*" +"@types/jest@^27.4.0": + version "27.4.0" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-27.4.0.tgz#037ab8b872067cae842a320841693080f9cb84ed" + integrity sha512-gHl8XuC1RZ8H2j5sHv/JqsaxXkDDM9iDOgu0Wp8sjs4u/snb2PVehyWXJPr+ORA0RPpgw231mnutWI1+0hgjIQ== + dependencies: + jest-diff "^27.0.0" + pretty-format "^27.0.0" + "@types/node@*": version "17.0.6" resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.6.tgz#cc1589c9ee853b389e67e8fb4384e0f250a139b9" @@ -564,6 +572,11 @@ resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.4.2.tgz#4c62fae93eb479660c3bd93f9d24d561597a8281" integrity sha512-ekoj4qOQYp7CvjX8ZDBgN86w3MqQhLE1hczEJbEIjgFEumDy+na/4AJAbLXfgEWFNB2pKadM5rPFtuSGMWK7xA== +"@types/service-worker-mock@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@types/service-worker-mock/-/service-worker-mock-2.0.1.tgz#4857b2024318c395294a02eda5050ac083f41e56" + integrity sha512-LqaP0QmgppRF7YEaqx4amoazHNXaX5bIFDAu62LnWIc5ku0HbgqlPKroQstAu8WsdmWIqEfI9VGlP8Skkq+m5A== + "@types/stack-utils@^2.0.0": version "2.0.1" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" @@ -586,14 +599,6 @@ abab@^2.0.3, abab@^2.0.5: resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a" integrity sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q== -accepts@~1.3.7: - version "1.3.7" - resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" - integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA== - dependencies: - mime-types "~2.1.24" - negotiator "0.6.2" - acorn-globals@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-6.0.0.tgz#46cdd39f0f8ff08a876619b55f5ac8a6dc770b45" @@ -655,7 +660,7 @@ ansi-styles@^5.0.0: resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== -anymatch@^3.0.3, anymatch@~3.1.2: +anymatch@^3.0.3: version "3.1.2" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== @@ -670,11 +675,6 @@ argparse@^1.0.7: dependencies: sprintf-js "~1.0.2" -array-flatten@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" - integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= - asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -746,27 +746,6 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== -binary-extensions@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" - integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== - -body-parser@1.19.1: - version "1.19.1" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.1.tgz#1499abbaa9274af3ecc9f6f10396c995943e31d4" - integrity sha512-8ljfQi5eBk8EJfECMrgqNGWPEY5jWP+1IzkzkGdFFEwFQZZyaZ21UqdaHktgiMlH0xLHqIFtE/u2OYE5dOtViA== - dependencies: - bytes "3.1.1" - content-type "~1.0.4" - debug "2.6.9" - depd "~1.1.2" - http-errors "1.8.1" - iconv-lite "0.4.24" - on-finished "~2.3.0" - qs "6.9.6" - raw-body "2.4.2" - type-is "~1.6.18" - brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -775,7 +754,7 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" -braces@^3.0.1, braces@~3.0.2: +braces@^3.0.1: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== @@ -798,6 +777,13 @@ browserslist@^4.17.5: node-releases "^2.0.1" picocolors "^1.0.0" +bs-logger@0.x: + version "0.2.6" + resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" + integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== + dependencies: + fast-json-stable-stringify "2.x" + bser@2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" @@ -810,11 +796,6 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== -bytes@3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.1.tgz#3f018291cb4cbad9accb6e6970bca9c8889e879a" - integrity sha512-dWe4nWO/ruEOY7HkUJ5gFt1DCFV9zPRoJr8pV0/ASQermOZjtq8jMjOprC0Kd10GLN+l7xaUPvxzJFWtxGu8Fg== - callsites@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" @@ -857,21 +838,6 @@ char-regex@^1.0.2: resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== -chokidar@^3.5.0: - version "3.5.2" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.2.tgz#dba3976fcadb016f66fd365021d91600d01c1e75" - integrity sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ== - dependencies: - anymatch "~3.1.2" - braces "~3.0.2" - glob-parent "~5.1.2" - is-binary-path "~2.1.0" - is-glob "~4.0.1" - normalize-path "~3.0.0" - readdirp "~3.6.0" - optionalDependencies: - fsevents "~2.3.2" - ci-info@^3.2.0: version "3.3.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.3.0.tgz#b4ed1fb6818dea4803a55c623041f9165d2066b2" @@ -937,18 +903,6 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= -content-disposition@0.5.4: - version "0.5.4" - resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" - integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== - dependencies: - safe-buffer "5.2.1" - -content-type@~1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" - integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== - convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: version "1.8.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369" @@ -956,16 +910,6 @@ convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: dependencies: safe-buffer "~5.1.1" -cookie-signature@1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" - integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= - -cookie@0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1" - integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA== - cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" @@ -1001,13 +945,6 @@ data-urls@^2.0.0: whatwg-mimetype "^2.3.0" whatwg-url "^8.0.0" -debug@2.6.9: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== - dependencies: - ms "2.0.0" - debug@4, debug@^4.1.0, debug@^4.1.1: version "4.3.3" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664" @@ -1040,16 +977,6 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= -depd@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" - integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= - -destroy@~1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" - integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= - detect-newline@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" @@ -1060,6 +987,13 @@ diff-sequences@^27.4.0: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.4.0.tgz#d783920ad8d06ec718a060d00196dfef25b132a5" integrity sha512-YqiQzkrsmHMH5uuh8OdQFU9/ZpADnwzml8z0O5HvRNda+5UZsaX/xN+AAxfR2hWq1Y7HZnAzO9J5lJXOuDz2Ww== +dom-urls@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/dom-urls/-/dom-urls-1.1.0.tgz#001ddf81628cd1e706125c7176f53ccec55d918e" + integrity sha1-AB3fgWKM0ecGElxxdvU8zsVdkY4= + dependencies: + urijs "^1.16.1" + domexception@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/domexception/-/domexception-2.0.1.tgz#fb44aefba793e1574b0af6aed2801d057529f304" @@ -1067,21 +1001,6 @@ domexception@^2.0.1: dependencies: webidl-conversions "^5.0.0" -edge-mock@^0.0.15: - version "0.0.15" - resolved "https://registry.yarnpkg.com/edge-mock/-/edge-mock-0.0.15.tgz#f584b052c8023e2c1a1af454c18b3b91f01cbb2b" - integrity sha512-Nv0MI7TmLz1rum4vCxAamabMMbRn54oOWHQ2qyymvsr3+zWG4hciFOy+fBj8/E92XGXP5a57lUeXj1V6oclSNA== - dependencies: - "@cloudflare/workers-types" "^2.2.2" - express "^4.17.1" - livereload "^0.9.3" - node-fetch "^2.6.1" - -ee-first@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" - integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= - electron-to-chromium@^1.4.17: version "1.4.31" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.31.tgz#8d5ccc3f8253cd142b07afaa84f200fd33a7f2a6" @@ -1097,21 +1016,11 @@ emoji-regex@^8.0.0: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== -encodeurl@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" - integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= - escalade@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== -escape-html@~1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" - integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= - escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" @@ -1149,11 +1058,6 @@ esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== -etag@~1.8.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" - integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= - execa@^5.0.0: version "5.1.1" resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" @@ -1186,43 +1090,7 @@ expect@^27.4.2: jest-message-util "^27.4.2" jest-regex-util "^27.4.0" -express@^4.17.1: - version "4.17.2" - resolved "https://registry.yarnpkg.com/express/-/express-4.17.2.tgz#c18369f265297319beed4e5558753cc8c1364cb3" - integrity sha512-oxlxJxcQlYwqPWKVJJtvQiwHgosH/LrLSPA+H4UxpyvSS6jC5aH+5MoHFM+KABgTOt0APue4w66Ha8jCUo9QGg== - dependencies: - accepts "~1.3.7" - array-flatten "1.1.1" - body-parser "1.19.1" - content-disposition "0.5.4" - content-type "~1.0.4" - cookie "0.4.1" - cookie-signature "1.0.6" - debug "2.6.9" - depd "~1.1.2" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - finalhandler "~1.1.2" - fresh "0.5.2" - merge-descriptors "1.0.1" - methods "~1.1.2" - on-finished "~2.3.0" - parseurl "~1.3.3" - path-to-regexp "0.1.7" - proxy-addr "~2.0.7" - qs "6.9.6" - range-parser "~1.2.1" - safe-buffer "5.2.1" - send "0.17.2" - serve-static "1.14.2" - setprototypeof "1.2.0" - statuses "~1.5.0" - type-is "~1.6.18" - utils-merge "1.0.1" - vary "~1.1.2" - -fast-json-stable-stringify@^2.0.0: +fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== @@ -1246,19 +1114,6 @@ fill-range@^7.0.1: dependencies: to-regex-range "^5.0.1" -finalhandler@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" - integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA== - dependencies: - debug "2.6.9" - encodeurl "~1.0.2" - escape-html "~1.0.3" - on-finished "~2.3.0" - parseurl "~1.3.3" - statuses "~1.5.0" - unpipe "~1.0.0" - find-up@^4.0.0, find-up@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" @@ -1276,22 +1131,12 @@ form-data@^3.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" -forwarded@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" - integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== - -fresh@0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" - integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= - fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= -fsevents@^2.3.2, fsevents@~2.3.2: +fsevents@^2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== @@ -1321,13 +1166,6 @@ get-stream@^6.0.0: resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== -glob-parent@~5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" - integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== - dependencies: - is-glob "^4.0.1" - glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4: version "7.2.0" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" @@ -1379,17 +1217,6 @@ html-escaper@^2.0.0: resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== -http-errors@1.8.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.8.1.tgz#7c3f28577cbc8a207388455dbd62295ed07bd68c" - integrity sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g== - dependencies: - depd "~1.1.2" - inherits "2.0.4" - setprototypeof "1.2.0" - statuses ">= 1.5.0 < 2" - toidentifier "1.0.1" - http-proxy-agent@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a" @@ -1440,23 +1267,11 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4: +inherits@2: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== -ipaddr.js@1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" - integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== - -is-binary-path@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" - integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== - dependencies: - binary-extensions "^2.0.0" - is-core-module@^2.2.0: version "2.8.0" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.0.tgz#0321336c3d0925e497fd97f5d95cb114a5ccd548" @@ -1464,11 +1279,6 @@ is-core-module@^2.2.0: dependencies: has "^1.0.3" -is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= - is-fullwidth-code-point@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" @@ -1479,13 +1289,6 @@ is-generator-fn@^2.0.0: resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== -is-glob@^4.0.1, is-glob@~4.0.1: - version "4.0.3" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" - integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== - dependencies: - is-extglob "^2.1.1" - is-number@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" @@ -1643,7 +1446,7 @@ jest-config@^27.4.5: pretty-format "^27.4.2" slash "^3.0.0" -jest-diff@^27.4.2: +jest-diff@^27.0.0, jest-diff@^27.4.2: version "27.4.2" resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-27.4.2.tgz#786b2a5211d854f848e2dcc1e324448e9481f36f" integrity sha512-ujc9ToyUZDh9KcqvQDkk/gkbf6zSaeEg9AiBxtttXW59H/AcqEYp1ciXAtJp+jXWva5nAf/ePtSsgWwE5mqp4Q== @@ -1919,7 +1722,7 @@ jest-snapshot@^27.4.5: pretty-format "^27.4.2" semver "^7.3.2" -jest-util@^27.4.2: +jest-util@^27.0.0, jest-util@^27.4.2: version "27.4.2" resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-27.4.2.tgz#ed95b05b1adfd761e2cda47e0144c6a58e05a621" integrity sha512-YuxxpXU6nlMan9qyLuxHaMMOzXAl5aGZWCSzben5DhLHemYQxCc4YK+4L3ZrCutT8GPQ+ui9k5D8rUJoDioMnA== @@ -2025,7 +1828,7 @@ jsesc@^2.5.1: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== -json5@^2.1.2: +json5@2.x, json5@^2.1.2: version "2.2.0" resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3" integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA== @@ -2050,21 +1853,6 @@ levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" -livereload-js@^3.3.1: - version "3.3.2" - resolved "https://registry.yarnpkg.com/livereload-js/-/livereload-js-3.3.2.tgz#c88b009c6e466b15b91faa26fd7c99d620e12651" - integrity sha512-w677WnINxFkuixAoUEXOStewzLYGI76XVag+0JWMMEyjJQKs0ibWZMxkTlB96Lm3EjZ7IeOxVziBEbtxVQqQZA== - -livereload@^0.9.3: - version "0.9.3" - resolved "https://registry.yarnpkg.com/livereload/-/livereload-0.9.3.tgz#a714816375ed52471408bede8b49b2ee6a0c55b1" - integrity sha512-q7Z71n3i4X0R9xthAryBdNGVGAO2R5X+/xXpmKeuPMrteg+W2U8VusTKV3YiJbXZwKsOlFlHe+go6uSNjfxrZw== - dependencies: - chokidar "^3.5.0" - livereload-js "^3.3.1" - opts ">= 1.2.0" - ws "^7.4.3" - locate-path@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" @@ -2072,6 +1860,43 @@ locate-path@^5.0.0: dependencies: p-locate "^4.1.0" +lodash._basefor@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash._basefor/-/lodash._basefor-3.0.3.tgz#7550b4e9218ef09fad24343b612021c79b4c20c2" + integrity sha1-dVC06SGO8J+tJDQ7YSAhx5tMIMI= + +lodash.isarguments@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" + integrity sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo= + +lodash.isarray@^3.0.0: + version "3.0.4" + resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55" + integrity sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U= + +lodash.isplainobject@^3.0.2: + version "3.2.0" + resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-3.2.0.tgz#9a8238ae16b200432960cd7346512d0123fbf4c5" + integrity sha1-moI4rhayAEMpYM1zRlEtASP79MU= + dependencies: + lodash._basefor "^3.0.0" + lodash.isarguments "^3.0.0" + lodash.keysin "^3.0.0" + +lodash.keysin@^3.0.0: + version "3.0.8" + resolved "https://registry.yarnpkg.com/lodash.keysin/-/lodash.keysin-3.0.8.tgz#22c4493ebbedb1427962a54b445b2c8a767fb47f" + integrity sha1-IsRJPrvtsUJ5YqVLRFssinZ/tH8= + dependencies: + lodash.isarguments "^3.0.0" + lodash.isarray "^3.0.0" + +lodash.memoize@4.x: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= + lodash@^4.7.0: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" @@ -2091,6 +1916,11 @@ make-dir@^3.0.0: dependencies: semver "^6.0.0" +make-error@1.x: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + makeerror@1.0.12: version "1.0.12" resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" @@ -2098,26 +1928,11 @@ makeerror@1.0.12: dependencies: tmpl "1.0.5" -media-typer@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" - integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= - -merge-descriptors@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" - integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= - merge-stream@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== -methods@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" - integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= - micromatch@^4.0.4: version "4.0.4" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" @@ -2131,18 +1946,13 @@ mime-db@1.51.0: resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.51.0.tgz#d9ff62451859b18342d960850dc3cfb77e63fb0c" integrity sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g== -mime-types@^2.1.12, mime-types@~2.1.24: +mime-types@^2.1.12: version "2.1.34" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.34.tgz#5a712f9ec1503511a945803640fafe09d3793c24" integrity sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A== dependencies: mime-db "1.51.0" -mime@1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" - integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== - mimic-fn@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" @@ -2160,38 +1970,16 @@ minimist@^1.2.5: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= - ms@2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= -negotiator@0.6.2: - version "0.6.2" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" - integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== - -node-fetch@^2.6.1: - version "2.6.6" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.6.tgz#1751a7c01834e8e1697758732e9efb6eeadfaf89" - integrity sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA== - dependencies: - whatwg-url "^5.0.0" - node-int64@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" @@ -2202,7 +1990,7 @@ node-releases@^2.0.1: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.1.tgz#3d1d395f204f1f2f29a54358b9fb678765ad2fc5" integrity sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA== -normalize-path@^3.0.0, normalize-path@~3.0.0: +normalize-path@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== @@ -2219,13 +2007,6 @@ nwsapi@^2.2.0: resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7" integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ== -on-finished@~2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" - integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= - dependencies: - ee-first "1.1.1" - once@^1.3.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" @@ -2252,11 +2033,6 @@ optionator@^0.8.1: type-check "~0.3.2" word-wrap "~1.2.3" -"opts@>= 1.2.0": - version "2.0.2" - resolved "https://registry.yarnpkg.com/opts/-/opts-2.0.2.tgz#a17e189fbbfee171da559edd8a42423bc5993ce1" - integrity sha512-k41FwbcLnlgnFh69f4qdUfvDQ+5vaSDnVPFI/y5XuhKRq97EnVVneO9F1ESVCdiVu4fCS2L8usX3mU331hB7pg== - p-limit@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" @@ -2281,11 +2057,6 @@ parse5@6.0.1: resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== -parseurl@~1.3.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" - integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== - path-exists@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" @@ -2306,11 +2077,6 @@ path-parse@^1.0.6: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== -path-to-regexp@0.1.7: - version "0.1.7" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" - integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= - picocolors@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" @@ -2321,11 +2087,6 @@ picomatch@^2.0.4, picomatch@^2.2.3: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw== -picomatch@^2.2.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" - integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== - pirates@^4.0.1: version "4.0.4" resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.4.tgz#07df81e61028e402735cdd49db701e4885b4e6e6" @@ -2343,7 +2104,7 @@ prelude-ls@~1.1.2: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= -pretty-format@^27.4.2: +pretty-format@^27.0.0, pretty-format@^27.4.2: version "27.4.2" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.4.2.tgz#e4ce92ad66c3888423d332b40477c87d1dac1fb8" integrity sha512-p0wNtJ9oLuvgOQDEIZ9zQjZffK7KtyR6Si0jnXULIDwrlNF8Cuir3AZP0hHv0jmKuNN/edOnbMjnzd4uTcmWiw== @@ -2361,14 +2122,6 @@ prompts@^2.0.1: kleur "^3.0.3" sisteransi "^1.0.5" -proxy-addr@~2.0.7: - version "2.0.7" - resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" - integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== - dependencies: - forwarded "0.2.0" - ipaddr.js "1.9.1" - psl@^1.1.33: version "1.8.0" resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" @@ -2379,37 +2132,17 @@ punycode@^2.1.1: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== -qs@6.9.6: - version "6.9.6" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.6.tgz#26ed3c8243a431b2924aca84cc90471f35d5a0ee" - integrity sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ== - -range-parser@~1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" - integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== - -raw-body@2.4.2: - version "2.4.2" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.2.tgz#baf3e9c21eebced59dd6533ac872b71f7b61cb32" - integrity sha512-RPMAFUJP19WIet/99ngh6Iv8fzAbqum4Li7AD6DtGaW2RpMB/11xDoalPiJMTbu6I3hkbMVkATvZrqb9EEqeeQ== - dependencies: - bytes "3.1.1" - http-errors "1.8.1" - iconv-lite "0.4.24" - unpipe "1.0.0" - react-is@^17.0.1: version "17.0.2" resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== -readdirp@~3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" - integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== +realistic-structured-clone@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/realistic-structured-clone/-/realistic-structured-clone-1.0.1.tgz#1abe82af0b80cd7b109fdaf5d29308032852d45d" + integrity sha1-Gr6CrwuAzXsQn9r10pMIAyhS1F0= dependencies: - picomatch "^2.2.1" + lodash.isplainobject "^3.0.2" require-directory@^2.1.1: version "2.1.1" @@ -2441,18 +2174,13 @@ resolve@^1.20.0: is-core-module "^2.2.0" path-parse "^1.0.6" -rimraf@^3.0.0: +rimraf@^3.0.0, rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== dependencies: glob "^7.1.3" -safe-buffer@5.2.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" @@ -2470,51 +2198,27 @@ saxes@^5.0.1: dependencies: xmlchars "^2.2.0" -semver@^6.0.0, semver@^6.3.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" - integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== - -semver@^7.3.2: +semver@7.x, semver@^7.3.2: version "7.3.5" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== dependencies: lru-cache "^6.0.0" -send@0.17.2: - version "0.17.2" - resolved "https://registry.yarnpkg.com/send/-/send-0.17.2.tgz#926622f76601c41808012c8bf1688fe3906f7820" - integrity sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww== - dependencies: - debug "2.6.9" - depd "~1.1.2" - destroy "~1.0.4" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - fresh "0.5.2" - http-errors "1.8.1" - mime "1.6.0" - ms "2.1.3" - on-finished "~2.3.0" - range-parser "~1.2.1" - statuses "~1.5.0" +semver@^6.0.0, semver@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== -serve-static@1.14.2: - version "1.14.2" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.2.tgz#722d6294b1d62626d41b43a013ece4598d292bfa" - integrity sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ== +service-worker-mock@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/service-worker-mock/-/service-worker-mock-2.0.5.tgz#89d47ec1571130114d4deed66d69bdcfee4a4545" + integrity sha512-yk6NCFnRWGfbOlP+IS4hEbJnGU8dVgtodAAKLxhkTPsOmaES44XVSWTNozK6KwI+p/0PDRrFsb2RjTMhvXiNkA== dependencies: - encodeurl "~1.0.2" - escape-html "~1.0.3" - parseurl "~1.3.3" - send "0.17.2" - -setprototypeof@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" - integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== + dom-urls "^1.1.0" + shelving-mock-indexeddb "^1.1.0" + url-search-params "^0.10.0" + w3c-hr-time "^1.0.1" shebang-command@^2.0.0: version "2.0.0" @@ -2528,6 +2232,19 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== +shelving-mock-event@^1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/shelving-mock-event/-/shelving-mock-event-1.0.12.tgz#401dc90b3b49cbf2a817ecf2dd5a83eff4de2e14" + integrity sha512-2F+IZ010rwV3sA/Kd2hnC1vGNycsxeBJmjkXR8+4IOlv5e+Wvj+xH+A8Cv8/Z0lUyCut/HcxSpeDccYTVtnuaQ== + +shelving-mock-indexeddb@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/shelving-mock-indexeddb/-/shelving-mock-indexeddb-1.1.0.tgz#e065a8d7987d182d058e2b55f0f79a52d48a38f1" + integrity sha512-akHJAmGL/dplJ4FZNxPxVbOxMw8Ey6wAnB9+3+GCUNqPUcJaskS55GijxZtarTfAYB4XQyu+FLtjcq2Oa3e2Lg== + dependencies: + realistic-structured-clone "^1.0.1" + shelving-mock-event "^1.0.12" + signal-exit@^3.0.2, signal-exit@^3.0.3: version "3.0.6" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.6.tgz#24e630c4b0f03fea446a2bd299e62b4a6ca8d0af" @@ -2578,11 +2295,6 @@ stack-utils@^2.0.3: dependencies: escape-string-regexp "^2.0.0" -"statuses@>= 1.5.0 < 2", statuses@~1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" - integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= - string-length@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" @@ -2690,11 +2402,6 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" -toidentifier@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" - integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== - tough-cookie@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.0.0.tgz#d822234eeca882f991f0f908824ad2622ddbece4" @@ -2711,10 +2418,19 @@ tr46@^2.1.0: dependencies: punycode "^2.1.1" -tr46@~0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" - integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= +ts-jest@^27.1.2: + version "27.1.2" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-27.1.2.tgz#5991d6eb3fd8e1a8d4b8f6de3ec0a3cc567f3151" + integrity sha512-eSOiJOWq6Hhs6Khzk5wKC5sgWIXgXqOCiIl1+3lfnearu58Hj4QpE5tUhQcA3xtZrELbcvAGCsd6HB8OsaVaTA== + dependencies: + bs-logger "0.x" + fast-json-stable-stringify "2.x" + jest-util "^27.0.0" + json5 "2.x" + lodash.memoize "4.x" + make-error "1.x" + semver "7.x" + yargs-parser "20.x" type-check@~0.3.2: version "0.3.2" @@ -2733,14 +2449,6 @@ type-fest@^0.21.3: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== -type-is@~1.6.18: - version "1.6.18" - resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" - integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== - dependencies: - media-typer "0.3.0" - mime-types "~2.1.24" - typedarray-to-buffer@^3.1.5: version "3.1.5" resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" @@ -2748,20 +2456,25 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" +typescript@^4.5.4: + version "4.5.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.4.tgz#a17d3a0263bf5c8723b9c52f43c5084edf13c2e8" + integrity sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg== + universalify@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== -unpipe@1.0.0, unpipe@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" - integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= +urijs@^1.16.1: + version "1.19.7" + resolved "https://registry.yarnpkg.com/urijs/-/urijs-1.19.7.tgz#4f594e59113928fea63c00ce688fb395b1168ab9" + integrity sha512-Id+IKjdU0Hx+7Zx717jwLPsPeUqz7rAtuVBRLLs+qn+J2nf9NGITWVCxcijgYxBqe83C7sqsQPs6H1pyz3x9gA== -utils-merge@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" - integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= +url-search-params@^0.10.0: + version "0.10.2" + resolved "https://registry.yarnpkg.com/url-search-params/-/url-search-params-0.10.2.tgz#e9da69646e48c6140c6732e1f07fb669525f5a4e" + integrity sha512-d6GYsr992Bo9rzTZFc9BUw3UFAAg3prE9JGVBgW2TLTbI3rSvg4VDa0BFXHMzKkWbAuhrmaFWpucpRJl+3W7Jg== v8-to-istanbul@^8.1.0: version "8.1.0" @@ -2772,12 +2485,7 @@ v8-to-istanbul@^8.1.0: convert-source-map "^1.6.0" source-map "^0.7.3" -vary@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" - integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= - -w3c-hr-time@^1.0.2: +w3c-hr-time@^1.0.1, w3c-hr-time@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd" integrity sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ== @@ -2798,11 +2506,6 @@ walker@^1.0.7: dependencies: makeerror "1.0.12" -webidl-conversions@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" - integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE= - webidl-conversions@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff" @@ -2825,14 +2528,6 @@ whatwg-mimetype@^2.3.0: resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== -whatwg-url@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" - integrity sha1-lmRU6HZUYuN2RNNib2dCzotwll0= - dependencies: - tr46 "~0.0.3" - webidl-conversions "^3.0.0" - whatwg-url@^8.0.0, whatwg-url@^8.5.0: version "8.7.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-8.7.0.tgz#656a78e510ff8f3937bc0bcbe9f5c0ac35941b77" @@ -2878,7 +2573,7 @@ write-file-atomic@^3.0.0: signal-exit "^3.0.2" typedarray-to-buffer "^3.1.5" -ws@^7.4.3, ws@^7.4.6: +ws@^7.4.6: version "7.5.6" resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.6.tgz#e59fc509fb15ddfb65487ee9765c5a51dec5fe7b" integrity sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA== @@ -2903,7 +2598,7 @@ yallist@^4.0.0: resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== -yargs-parser@^20.2.2: +yargs-parser@20.x, yargs-parser@^20.2.2: version "20.2.9" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==