Implement pages, tables & users; WIP

Co-authored-by: Timo <timo.lins@gmail.com>
This commit is contained in:
Tobias Lins
2020-04-21 13:54:53 +02:00
parent a379a9df5a
commit d6cd8e4403
16 changed files with 723 additions and 7 deletions

2
.gitattributes vendored
View File

@@ -1,2 +0,0 @@
# Auto detect text files and perform LF normalization
* text=auto

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
dist
node_modules
worker
wrangler.toml

View File

@@ -1,2 +1,36 @@
# notion-cloudflare-worker
A serverless layer on top of the private Notion API. It leverages [Cloudflare Workers](https://workers.cloudflare.com/), to provide fast and easy access to all your Notion content.
_This package might become obsolete, once the official Notion API arrives._
## Features
🍭 **Easy to use** Receive Notion data with a single GET request
**Fast CDN** Leverage the global Cloudflare CDN
🛫 **CORS Friendly** Access your data where you need it
🗄 **Table Access** Get structured data from tables & databases
## Use Cases
- Use a table to manage posts for your blog
## Endpoints
We provide a hosted version of this project on [https://notion.splitbee.io/](https://notion.splitbee.io/). You can also [host your own](https://workers.cloudflare.com/). Cloudflare offers a generous free plan with up to 100,000 request per day.
### Get data from a page - `/v1/page/<PAGE_ID>`
[Example](https://notion.splitbee.io/v1/page/2e22de6b770e4166be301490f6ffd420)
Returns all block data for a given page.
For example, you can render this data with [`react-notion`](https://github.com/splitbee/react-notion).
### Get parsed data from table `/v1/table/<PAGE_ID>`
[Example](https://notion.splitbee.io/v1/page/2e22de6b770e4166be301490f6ffd420)
## Credits
- [Timo Lins](https://timo.sh) Idea, Documentation
- [Tobias Lins](https://tobi.sh) Code

View File

@@ -1,7 +1,21 @@
{
"name": "notion-cloudflare-worker",
"version": "1.0.0",
"main": "index.js",
"author": "Tobias Lins",
"license": "MIT"
"version": "0.1.0",
"main": "dist/index.js",
"license": "MIT",
"scripts": {
"build": "webpack",
"dev": "wrangler preview --watch",
"deploy": "wrangler publish -e production"
},
"dependencies": {
"tiny-request-router": "^1.2.2"
},
"devDependencies": {
"@cloudflare/workers-types": "^1.0.9",
"@types/node": "^13.13.1",
"prettier": "^2.0.4",
"ts-loader": "^7.0.1",
"typescript": "^3.8.3"
}
}

94
src/api/notion.ts Normal file
View File

@@ -0,0 +1,94 @@
import { resolve } from "dns";
const NOTION_API = "https://www.notion.so/api/v3";
type JSONData =
| null
| boolean
| number
| string
| JSONData[]
| { [prop: string]: JSONData };
type INotionParams = {
resource: string;
body: JSONData;
};
const loadPageChunkBody = {
limit: 999,
cursor: { stack: [] },
chunkNumber: 0,
verticalColumns: false,
};
const fetchNotionData = async ({ resource, body }: INotionParams) => {
const res = await fetch(`${NOTION_API}/${resource}`, {
method: "POST",
headers: {
"content-type": "application/json",
},
body: JSON.stringify(body),
//cf: {}
});
return res.json();
};
export const fetchPageById = async (pageId: string) => {
const res = await fetchNotionData({
resource: "loadPageChunk",
body: {
pageId,
...loadPageChunkBody,
},
});
return res;
};
const queryCollectionBody = {
query: { aggregations: [{ property: "title", aggregator: "count" }] },
loader: {
type: "table",
limit: 999,
searchQuery: "",
userTimeZone: "Europe/Vienna",
userLocale: "en",
loadContentCover: true,
},
};
export const fetchTableData = async (
collectionId: string,
collectionViewId: string
) => {
const table = await fetchNotionData({
resource: "queryCollection",
body: {
collectionId,
collectionViewId,
...queryCollectionBody,
},
});
return table;
};
export const fetchNotionUser = async (
userIds: string[]
): Promise<{ id: string; full_name: string }[]> => {
const users = await fetchNotionData({
resource: "getRecordValues",
body: {
requests: userIds.map((id) => ({ id, table: "notion_user" })),
},
});
return users.results.map((u: any) => {
const user = {
id: u.value.id,
full_name: u.value.given_name + " " + u.value.family_name,
};
return user;
});
};

86
src/api/types.ts Normal file
View File

@@ -0,0 +1,86 @@
type BoldFormatType = ["b"];
type ItalicFormatType = ["i"];
type StrikeFormatType = ["s"];
type CodeFormatType = ["c"];
type LinkFormatType = ["a", string];
type DateFormatType = [
"d",
{
type: "date";
start_date: string;
date_format: string;
}
];
type UserFormatType = ["u", string];
type PageFormatType = ["p", string];
type SubDecorationType =
| BoldFormatType
| ItalicFormatType
| StrikeFormatType
| CodeFormatType
| LinkFormatType
| DateFormatType
| UserFormatType
| PageFormatType;
type BaseDecorationType = [string];
type AdditionalDecorationType = [string, SubDecorationType[]];
export type DecorationType = BaseDecorationType | AdditionalDecorationType;
export type ColumnType =
| "select"
| "text"
| "date"
| "person"
| "checkbox"
| "title"
| "multi_select"
| "number";
export type ColumnSchemaType = {
name: string;
type: ColumnType;
};
export type RowContentType =
| string
| boolean
| number
| string[]
| { title: string; id: string };
export interface BaseValueType {
id: string;
version: number;
created_time: number;
last_edited_time: number;
parent_id: string;
parent_table: string;
alive: boolean;
created_by_table: string;
created_by_id: string;
last_edited_by_table: string;
last_edited_by_id: string;
content?: string[];
}
export interface CollectionType {
value: {
id: string;
version: number;
name: string[][];
schema: { [key: string]: ColumnSchemaType };
icon: string;
parent_id: string;
parent_table: string;
alive: boolean;
copied_from: string;
};
}
export interface RowType {
value: {
id: string;
parent_id: string;
properties: { [key: string]: DecorationType[] };
};
}

45
src/api/utils.ts Normal file
View File

@@ -0,0 +1,45 @@
import { DecorationType, ColumnType, RowContentType } from "./types";
export const pathToId = (path: string) =>
`${path.substr(0, 8)}-${path.substr(8, 4)}-${path.substr(
12,
4
)}-${path.substr(16, 4)}-${path.substr(20)}`;
export const parsePageId = (id: string) => {
return id.includes("-") ? id : pathToId(id);
};
export const getNotionValue = (
val: DecorationType[],
type: ColumnType
): RowContentType => {
switch (type) {
case "text":
return getTextContent(val);
case "person":
return (
val.filter((v) => v.length > 1).map((v) => v[1]![0][1] as string) || []
);
case "checkbox":
return val[0][0] === "Yes";
case "date":
if (val[0][1]![0][0] === "d") return val[0]![1]![0]![1]!.start_date;
else return "";
case "title":
return getTextContent(val);
case "select":
return val[0][0];
case "multi_select":
return val[0] as string[];
case "number":
return Number(val[0][0]);
default:
console.log({ val, type });
return "Not supported";
}
};
const getTextContent = (text: DecorationType[]) => {
return text.reduce((prev, current) => prev + current[0], "");
};

43
src/index.ts Normal file
View File

@@ -0,0 +1,43 @@
import {} from "@cloudflare/workers-types";
import { Router, Method } from "tiny-request-router";
import { pageRoute } from "./routes/page";
import { tableRoute } from "./routes/table";
import { userRoute } from "./routes/user";
const router = new Router();
router.options("*", () => new Response("", { headers: {} }));
router.get("/v1/page/:pageId", pageRoute);
router.get("/v1/table/:pageId", tableRoute);
router.get("/v1/user/:userId", userRoute);
router.get(
"*",
async (event: FetchEvent) =>
new Response(
`Route not found!
Available routes:
- /v1/page/:pageId
- /v1/table/:pageId`,
{ status: 404 }
)
);
const handleRequest = async (fetchEvent: FetchEvent): Promise<Response> => {
const request = fetchEvent.request;
const { pathname } = new URL(request.url);
const match = router.match(request.method as Method, pathname);
if (!match) {
return new Response("Endpoint not found.", { status: 404 });
}
return match.handler(match.params);
};
self.addEventListener("fetch", async (event: Event) => {
const fetchEvent = event as FetchEvent;
fetchEvent.respondWith(handleRequest(fetchEvent));
});

12
src/response.ts Normal file
View File

@@ -0,0 +1,12 @@
export const createResponse = (
body: string,
headers?: { [key: string]: string }
) => {
return new Response(body, {
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, OPTIONS",
...headers,
},
});
};

10
src/routes/page.ts Normal file
View File

@@ -0,0 +1,10 @@
import { fetchPageById } from "../api/notion";
import { parsePageId } from "../api/utils";
import { createResponse } from "../response";
export async function pageRoute(params: { pageId: string }) {
const pageId = parsePageId(params.pageId);
const res = await fetchPageById(pageId);
return createResponse(JSON.stringify(res.recordMap.block));
}

53
src/routes/table.ts Normal file
View File

@@ -0,0 +1,53 @@
import { fetchPageById, fetchTableData } from "../api/notion";
import { parsePageId, getNotionValue } from "../api/utils";
import { RowContentType, CollectionType, RowType } from "../api/types";
import { createResponse } from "../response";
export async function tableRoute(params: { pageId: string }) {
const pageId = parsePageId(params.pageId);
const page = await fetchPageById(pageId);
console.log({ page });
const collection: CollectionType = Object.keys(page.recordMap.collection).map(
(k) => page.recordMap.collection[k]
)[0];
const collectionView: {
value: { id: CollectionType["value"]["id"] };
} = Object.keys(page.recordMap.collection_view).map(
(k) => page.recordMap.collection_view[k]
)[0];
const table = await fetchTableData(
collection.value.id,
collectionView.value.id
);
console.log({ table });
const collectionRows = collection.value.schema;
const collectionColKeys = Object.keys(collectionRows);
const tableArr: RowType[] = table.result.blockIds.map(
(id: string) => table.recordMap.block[id]
);
const tableData = tableArr.filter(
(b) =>
b.value && b.value.properties && b.value.parent_id === collection.value.id
);
const rows = tableData.map((td) => {
let row: { [key: string]: RowContentType } = { id: td.value.id };
collectionColKeys.forEach((key) => {
const val = td.value.properties[key];
if (val) {
const schema = collectionRows[key];
row[schema.name] = getNotionValue(val, schema.type);
}
});
return row;
});
return createResponse(JSON.stringify(rows));
}

8
src/routes/user.ts Normal file
View File

@@ -0,0 +1,8 @@
import { fetchNotionUser } from "../api/notion";
import { createResponse } from "../response";
export async function userRoute(params: { userId: string }) {
const users = await fetchNotionUser([params.userId]);
return createResponse(JSON.stringify(users[0]));
}

20
tsconfig.json Normal file
View File

@@ -0,0 +1,20 @@
{
"compilerOptions": {
"outDir": "./dist",
"module": "commonjs",
"target": "esnext",
"lib": ["esnext", "DOM", "DOM.Iterable", "WebWorker"],
"alwaysStrict": true,
"strict": true,
"preserveConstEnums": true,
"moduleResolution": "node",
"sourceMap": true,
"esModuleInterop": true
},
"include": [
"./src/*.ts",
"./src/**/*.ts",
"./node_modules/@cloudflare/workers-types/index.d.ts"
],
"exclude": ["node_modules/", "dist/"]
}

32
webpack.config.js Normal file
View File

@@ -0,0 +1,32 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const path = require("path");
const mode = process.env.NODE_ENV || "production";
module.exports = {
output: {
filename: `worker.${mode}.js`,
path: path.join(__dirname, "dist"),
},
target: "webworker",
devtool: "source-map",
mode,
resolve: {
extensions: [".ts", ".js"],
},
module: {
rules: [
{
test: /\.tsx?$/,
loader: "ts-loader",
options: {
transpileOnly: true,
},
},
{ enforce: "pre", test: /\.js$/, loader: "source-map-loader" },
],
},
optimization: {
usedExports: true,
},
};

8
wrangler.example.toml Normal file
View File

@@ -0,0 +1,8 @@
name = "notion-cloudflare-worker"
webpack_config = "webpack.config.js"
type = "webpack"
account_id = ""
zone_id = ""
route = "notion-api.splitbee.io/*"

255
yarn.lock Normal file
View File

@@ -0,0 +1,255 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@cloudflare/workers-types@^1.0.9":
version "1.0.9"
resolved "https://registry.yarnpkg.com/@cloudflare/workers-types/-/workers-types-1.0.9.tgz#00cfb188e93125f95ccc27d7a4c6aabd6b62e699"
integrity sha512-x6aA5FRK0fR0pC/mkDq0nG6WNkf3aVu6vLRfA7FoMtajbvWKN9JBveI/HhbjpMOIRWfNnGd2rw6WH7NAv4XesA==
"@types/node@^13.13.1":
version "13.13.1"
resolved "https://registry.yarnpkg.com/@types/node/-/node-13.13.1.tgz#1ba94c5a177a1692518bfc7b41aec0aa1a14354e"
integrity sha512-uysqysLJ+As9jqI5yqjwP3QJrhOcUwBjHUlUxPxjbplwKoILvXVsmYWEhfmAQlrPfbRZmhJB007o4L9sKqtHqQ==
ansi-styles@^3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==
dependencies:
color-convert "^1.9.0"
big.js@^5.2.2:
version "5.2.2"
resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328"
integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==
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==
dependencies:
fill-range "^7.0.1"
chalk@^2.3.0:
version "2.4.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
dependencies:
ansi-styles "^3.2.1"
escape-string-regexp "^1.0.5"
supports-color "^5.3.0"
color-convert@^1.9.0:
version "1.9.3"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==
dependencies:
color-name "1.1.3"
color-name@1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
core-util-is@~1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
emojis-list@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78"
integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==
enhanced-resolve@^4.0.0:
version "4.1.1"
resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.1.1.tgz#2937e2b8066cd0fe7ce0990a98f0d71a35189f66"
integrity sha512-98p2zE+rL7/g/DzMHMTF4zZlCgeVdJ7yr6xzEpJRYwFYrGi9ANdn5DnJURg6RpBkyk60XYDnWIv51VfIhfNGuA==
dependencies:
graceful-fs "^4.1.2"
memory-fs "^0.5.0"
tapable "^1.0.0"
errno@^0.1.3:
version "0.1.7"
resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618"
integrity sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==
dependencies:
prr "~1.0.1"
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"
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
fill-range@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==
dependencies:
to-regex-range "^5.0.1"
graceful-fs@^4.1.2:
version "4.2.3"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423"
integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==
has-flag@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0=
inherits@~2.0.3:
version "2.0.4"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
is-number@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
isarray@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
json5@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe"
integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==
dependencies:
minimist "^1.2.0"
loader-utils@^1.0.2:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613"
integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==
dependencies:
big.js "^5.2.2"
emojis-list "^3.0.0"
json5 "^1.0.1"
memory-fs@^0.5.0:
version "0.5.0"
resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.5.0.tgz#324c01288b88652966d161db77838720845a8e3c"
integrity sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==
dependencies:
errno "^0.1.3"
readable-stream "^2.0.1"
micromatch@^4.0.0:
version "4.0.2"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259"
integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==
dependencies:
braces "^3.0.1"
picomatch "^2.0.5"
minimist@^1.2.0:
version "1.2.5"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
path-to-regexp@^6.1.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-6.1.0.tgz#0b18f88b7a0ce0bfae6a25990c909ab86f512427"
integrity sha512-h9DqehX3zZZDCEm+xbfU0ZmwCGFCAAraPJWMXJ4+v32NjZJilVg3k1TcKsRgIb8IQ/izZSaydDc1OhJCZvs2Dw==
picomatch@^2.0.5:
version "2.2.2"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad"
integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==
prettier@^2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.0.4.tgz#2d1bae173e355996ee355ec9830a7a1ee05457ef"
integrity sha512-SVJIQ51spzFDvh4fIbCLvciiDMCrRhlN3mbZvv/+ycjvmF5E73bKdGfU8QDLNmjYJf+lsGnDBC4UUnvTe5OO0w==
process-nextick-args@~2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
prr@~1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476"
integrity sha1-0/wRS6BplaRexok/SEzrHXj19HY=
readable-stream@^2.0.1:
version "2.3.7"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57"
integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==
dependencies:
core-util-is "~1.0.0"
inherits "~2.0.3"
isarray "~1.0.0"
process-nextick-args "~2.0.0"
safe-buffer "~5.1.1"
string_decoder "~1.1.1"
util-deprecate "~1.0.1"
safe-buffer@~5.1.0, safe-buffer@~5.1.1:
version "5.1.2"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
semver@^6.0.0:
version "6.3.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
string_decoder@~1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
dependencies:
safe-buffer "~5.1.0"
supports-color@^5.3.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
dependencies:
has-flag "^3.0.0"
tapable@^1.0.0:
version "1.1.3"
resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2"
integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==
tiny-request-router@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/tiny-request-router/-/tiny-request-router-1.2.2.tgz#1b80694497e4e8dcbb8e93851ec7f03c6ca13e75"
integrity sha512-6ZMFU7AP9so+hkqmMM9fJ11V44EAcYuHCmNdsyM8k94oVnNDPQwUAAPoBHqchHSpKG6yZbCasgVeRxaY5v2BCg==
dependencies:
path-to-regexp "^6.1.0"
to-regex-range@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
dependencies:
is-number "^7.0.0"
ts-loader@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-7.0.1.tgz#ac9ae9eb8f5ebd0aa7b78b44db20691b6e31251b"
integrity sha512-wdGs9xO8UnwASwbluehzXciBtc9HfGlYA8Aiv856etLmdv8mJfAxCkt3YpS4g7G1IsGxaCVKQ102Qh0zycpeZQ==
dependencies:
chalk "^2.3.0"
enhanced-resolve "^4.0.0"
loader-utils "^1.0.2"
micromatch "^4.0.0"
semver "^6.0.0"
typescript@^3.8.3:
version "3.8.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.3.tgz#409eb8544ea0335711205869ec458ab109ee1061"
integrity sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==
util-deprecate@~1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=