├── tsconfig.json ├── store ├── Recipe.ts └── recipes.ts ├── README.md ├── .gitignore ├── package.json ├── api ├── auth.ts └── sheets.ts ├── index.ts └── yarn.lock /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2016", 4 | "module": "commonjs", 5 | "outDir": "lib" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /store/Recipe.ts: -------------------------------------------------------------------------------- 1 | export type Recipe = { 2 | id: string; 3 | name: string; 4 | desc: string; 5 | createdAt: string; 6 | updatedAt: string; 7 | }; 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Google sheet as database 2 | 3 | This repository contains all the code used / referenced in the blog article ["Google sheet as database"](https://blog.codecentric.de/en/2019/03/google-sheets-as-database/). 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # build 7 | /lib 8 | 9 | # misc 10 | .DS_Store 11 | 12 | yarn-debug.log* 13 | yarn-error.log* 14 | 15 | credentials.json -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "google-sheets-as-database", 3 | "version": "1.0.0", 4 | "license": "MIT", 5 | "scripts": { 6 | "build": "tsc -p .", 7 | "start": "node lib/index" 8 | }, 9 | "dependencies": { 10 | "@types/lokijs": "^1.5.2", 11 | "@types/node": "^10.12.21", 12 | "google-auth-library": "^3.0.1", 13 | "googleapis": "^37.1.0", 14 | "lokijs": "^1.5.6", 15 | "typescript": "^3.3.1" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /api/auth.ts: -------------------------------------------------------------------------------- 1 | import { readFile } from 'fs'; 2 | import { resolve } from 'path'; 3 | import { promisify } from 'util'; 4 | import { JWT } from 'google-auth-library'; 5 | 6 | const promisedFile = promisify(readFile); 7 | 8 | export async function auth() { 9 | const credentials = JSON.parse( 10 | await promisedFile('./credentials.json', 'utf-8'), 11 | ); 12 | const client = new JWT({ 13 | email: credentials.client_email, 14 | key: credentials.private_key, 15 | scopes: ['https://www.googleapis.com/auth/spreadsheets'], 16 | }); 17 | await client.authorize(); 18 | return client; 19 | } 20 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | import { createServer } from 'http'; 2 | import { collection, setup, refresh } from './store/recipes'; 3 | 4 | const port = +process.env.PORT || 8000; 5 | 6 | setup().then(() => { 7 | createServer(async (req, res) => { 8 | try { 9 | const data = collection.find(); 10 | res.statusCode = 200; 11 | res.end(JSON.stringify(data), 'utf8'); 12 | } catch (err) { 13 | res.statusCode = 500; 14 | res.end(JSON.stringify(err)); 15 | } 16 | }).listen(port, () => { 17 | console.log(`🚀 Server listening on port ${port}!`); 18 | 19 | setInterval(refresh, 30000); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /store/recipes.ts: -------------------------------------------------------------------------------- 1 | import * as Loki from 'lokijs'; 2 | import { readSheet } from '../api/sheets'; 3 | import { Recipe } from './Recipe'; 4 | 5 | const sheetId = '1o0VAQ4f2QafBjLUd53yCWEtdwKonu5wPM33CttxBTXI'; 6 | const sheetRange = 'Recipes!A:E'; 7 | 8 | const db = new Loki('recipes.json'); 9 | const collection = db.addCollection('recipes', { indices: ['id'] }); 10 | 11 | export async function setup() { 12 | const data = await readSheet(sheetId, sheetRange); 13 | collection.insert(data); 14 | } 15 | 16 | export async function refresh() { 17 | const data = await readSheet(sheetId, sheetRange); 18 | const ids = data.map(d => d.id); 19 | collection.findAndUpdate( 20 | obj => 21 | ids.includes(obj.id) && 22 | new Date(data.find(d => d.id === obj.id).updatedAt).getTime() > 23 | new Date(obj.updatedAt).getTime(), 24 | obj => Object.assign(obj, data.find(d => d.id === obj.id)), 25 | ); 26 | collection.findAndRemove({ id: { $not: { $in: ids } } }); 27 | collection.insert(data.filter(d => !collection.findOne({ id: d.id }))); 28 | } 29 | 30 | export { collection }; 31 | -------------------------------------------------------------------------------- /api/sheets.ts: -------------------------------------------------------------------------------- 1 | import { google } from 'googleapis'; 2 | import { auth } from './auth'; 3 | 4 | const sheetsApi = google.sheets({ version: 'v4' }); 5 | 6 | export async function readSheet( 7 | spreadsheetId: string, 8 | range: string, 9 | firstRowAsKeys?: true, 10 | ): Promise; 11 | export async function readSheet( 12 | spreadsheetId: string, 13 | range: string, 14 | firstRowAsKeys: boolean = true, 15 | ): Promise { 16 | const { 17 | data: { 18 | values: [keys, ...values], 19 | }, 20 | } = await sheetsApi.spreadsheets.values.get({ 21 | auth: await auth(), 22 | spreadsheetId, 23 | range, 24 | valueRenderOption: 'UNFORMATTED_VALUE', 25 | }); 26 | return firstRowAsKeys 27 | ? values.map(columns => 28 | keys.reduce( 29 | (acc, key, idx) => ({ 30 | ...acc, 31 | [key]: columns[idx], 32 | }), 33 | {} as T, 34 | ), 35 | ) 36 | : [keys, ...values]; 37 | } 38 | 39 | export async function writeSheet( 40 | spreadsheetId: string, 41 | range: string, 42 | ...columns: string[][] 43 | ) { 44 | await sheetsApi.spreadsheets.values.update({ 45 | auth: await auth(), 46 | spreadsheetId, 47 | range, 48 | valueInputOption: 'USER_ENTERED', 49 | requestBody: { values: [...columns] }, 50 | }); 51 | } 52 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@types/lokijs@^1.5.2": 6 | version "1.5.2" 7 | resolved "https://registry.yarnpkg.com/@types/lokijs/-/lokijs-1.5.2.tgz#ed228f080033ce1fb16eff4acde65cb9ae0f1bf2" 8 | integrity sha512-ZF14v1P1Bjbw8VJRu+p4WS9V926CAOjWF4yq23QmSBWRPe0/GXlUKzSxjP1fi/xi8nrq6zr9ECo8Z/8KsRqroQ== 9 | 10 | "@types/node@^10.12.21": 11 | version "10.12.21" 12 | resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.21.tgz#7e8a0c34cf29f4e17a36e9bd0ea72d45ba03908e" 13 | integrity sha512-CBgLNk4o3XMnqMc0rhb6lc77IwShMEglz05deDcn2lQxyXEZivfwgYJu7SMha9V5XcrP6qZuevTHV/QrN2vjKQ== 14 | 15 | agent-base@^4.1.0: 16 | version "4.2.1" 17 | resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9" 18 | integrity sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg== 19 | dependencies: 20 | es6-promisify "^5.0.0" 21 | 22 | base64-js@^1.3.0: 23 | version "1.3.0" 24 | resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.0.tgz#cab1e6118f051095e58b5281aea8c1cd22bfc0e3" 25 | integrity sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw== 26 | 27 | bignumber.js@^7.0.0: 28 | version "7.2.1" 29 | resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-7.2.1.tgz#80c048759d826800807c4bfd521e50edbba57a5f" 30 | integrity sha512-S4XzBk5sMB+Rcb/LNcpzXr57VRTxgAvaAEDAl1AwRx27j00hT84O6OkteE7u8UB3NuaaygCRrEpqox4uDOrbdQ== 31 | 32 | buffer-equal-constant-time@1.0.1: 33 | version "1.0.1" 34 | resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" 35 | integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk= 36 | 37 | debug@^3.1.0: 38 | version "3.2.6" 39 | resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" 40 | integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== 41 | dependencies: 42 | ms "^2.1.1" 43 | 44 | ecdsa-sig-formatter@1.0.10: 45 | version "1.0.10" 46 | resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.10.tgz#1c595000f04a8897dfb85000892a0f4c33af86c3" 47 | integrity sha1-HFlQAPBKiJffuFAAiSoPTDOvhsM= 48 | dependencies: 49 | safe-buffer "^5.0.1" 50 | 51 | es6-promise@^4.0.3: 52 | version "4.2.5" 53 | resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.5.tgz#da6d0d5692efb461e082c14817fe2427d8f5d054" 54 | integrity sha512-n6wvpdE43VFtJq+lUDYDBFUwV8TZbuGXLV4D6wKafg13ldznKsyEvatubnmUe31zcvelSzOHF+XbaT+Bl9ObDg== 55 | 56 | es6-promisify@^5.0.0: 57 | version "5.0.0" 58 | resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203" 59 | integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM= 60 | dependencies: 61 | es6-promise "^4.0.3" 62 | 63 | extend@^3.0.2: 64 | version "3.0.2" 65 | resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" 66 | integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== 67 | 68 | fast-text-encoding@^1.0.0: 69 | version "1.0.0" 70 | resolved "https://registry.yarnpkg.com/fast-text-encoding/-/fast-text-encoding-1.0.0.tgz#3e5ce8293409cfaa7177a71b9ca84e1b1e6f25ef" 71 | integrity sha512-R9bHCvweUxxwkDwhjav5vxpFvdPGlVngtqmx4pIZfSUhM/Q4NiIUHB456BAf+Q1Nwu3HEZYONtu+Rya+af4jiQ== 72 | 73 | gaxios@^1.0.2, gaxios@^1.0.4, gaxios@^1.2.1, gaxios@^1.2.2: 74 | version "1.4.0" 75 | resolved "https://registry.yarnpkg.com/gaxios/-/gaxios-1.4.0.tgz#4f5c2def1abcaa64b5fe45dbf188fb72b246bc7d" 76 | integrity sha512-qW0q08OcvFwaSmwUiELnif+q5NvAAoQfUN6iq8lx/HnmgMcJ9U+jiB+c+5C1muSBGsQ3D3PiLFpJ9jjO8BRCDg== 77 | dependencies: 78 | extend "^3.0.2" 79 | https-proxy-agent "^2.2.1" 80 | node-fetch "^2.2.0" 81 | 82 | gcp-metadata@^0.9.3: 83 | version "0.9.3" 84 | resolved "https://registry.yarnpkg.com/gcp-metadata/-/gcp-metadata-0.9.3.tgz#1f9d7495f7460a14526481f29e11596dd563dd26" 85 | integrity sha512-caV4S84xAjENtpezLCT/GILEAF5h/bC4cNqZFmt/tjTn8t+JBtTkQrgBrJu3857YdsnlM8rxX/PMcKGtE8hUlw== 86 | dependencies: 87 | gaxios "^1.0.2" 88 | json-bigint "^0.3.0" 89 | 90 | google-auth-library@^3.0.0, google-auth-library@^3.0.1: 91 | version "3.0.1" 92 | resolved "https://registry.yarnpkg.com/google-auth-library/-/google-auth-library-3.0.1.tgz#9eb5dc36978fe216a9af9354b8b8f91a5438007c" 93 | integrity sha512-ZGTBMiQga/pwEw26ZKCn+q9PTPXvE4v5sL2V9HV3f2Gt0lrS+2H7XgbVCx850jrvlEL59JIheFiDqEn9CIa0nA== 94 | dependencies: 95 | base64-js "^1.3.0" 96 | fast-text-encoding "^1.0.0" 97 | gaxios "^1.2.1" 98 | gcp-metadata "^0.9.3" 99 | gtoken "^2.3.2" 100 | https-proxy-agent "^2.2.1" 101 | jws "^3.1.5" 102 | lru-cache "^5.0.0" 103 | semver "^5.5.0" 104 | 105 | google-p12-pem@^1.0.0: 106 | version "1.0.3" 107 | resolved "https://registry.yarnpkg.com/google-p12-pem/-/google-p12-pem-1.0.3.tgz#3d8acc140573339a5bca7b2f6a4b206bbea6d8d7" 108 | integrity sha512-KGnAiMMWaJp4j4tYVvAjfP3wCKZRLv9M1Nir2wRRNWUYO7j1aX8O9Qgz+a8/EQ5rAvuo4SIu79n6SIdkNl7Msg== 109 | dependencies: 110 | node-forge "^0.7.5" 111 | pify "^4.0.0" 112 | 113 | googleapis-common@^0.7.0: 114 | version "0.7.2" 115 | resolved "https://registry.yarnpkg.com/googleapis-common/-/googleapis-common-0.7.2.tgz#a694f55d979cb7c2eac21a0e0439af12f9b418ba" 116 | integrity sha512-9DEJIiO4nS7nw0VE1YVkEfXEj8x8MxsuB+yZIpOBULFSN9OIKcUU8UuKgSZFU4lJmRioMfngktrbkMwWJcUhQg== 117 | dependencies: 118 | gaxios "^1.2.2" 119 | google-auth-library "^3.0.0" 120 | pify "^4.0.0" 121 | qs "^6.5.2" 122 | url-template "^2.0.8" 123 | uuid "^3.2.1" 124 | 125 | googleapis@^37.1.0: 126 | version "37.1.0" 127 | resolved "https://registry.yarnpkg.com/googleapis/-/googleapis-37.1.0.tgz#34a663179ea5a1b290b7af1ea1a1ae9a9eabfcb0" 128 | integrity sha512-ajQF/BA95pCknQWtNFelcSuwZpiUKyKlfbEFd/q3gWMWJWlkMd4Bb+oI6+A0lQ0spqQF/zzMyviTiLCaws5hkw== 129 | dependencies: 130 | google-auth-library "^3.0.0" 131 | googleapis-common "^0.7.0" 132 | 133 | gtoken@^2.3.2: 134 | version "2.3.2" 135 | resolved "https://registry.yarnpkg.com/gtoken/-/gtoken-2.3.2.tgz#49890a866c1f44e173099be95515db5872a92151" 136 | integrity sha512-F8EObUGyC8Qd3WXTloNULZBwfUsOABoHElihB1F6zGhT/cy38iPL09wGLRY712I+hQnOyA+sYlgPFX2cOKz0qg== 137 | dependencies: 138 | gaxios "^1.0.4" 139 | google-p12-pem "^1.0.0" 140 | jws "^3.1.5" 141 | mime "^2.2.0" 142 | pify "^4.0.0" 143 | 144 | https-proxy-agent@^2.2.1: 145 | version "2.2.1" 146 | resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz#51552970fa04d723e04c56d04178c3f92592bbc0" 147 | integrity sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ== 148 | dependencies: 149 | agent-base "^4.1.0" 150 | debug "^3.1.0" 151 | 152 | json-bigint@^0.3.0: 153 | version "0.3.0" 154 | resolved "https://registry.yarnpkg.com/json-bigint/-/json-bigint-0.3.0.tgz#0ccd912c4b8270d05f056fbd13814b53d3825b1e" 155 | integrity sha1-DM2RLEuCcNBfBW+9E4FLU9OCWx4= 156 | dependencies: 157 | bignumber.js "^7.0.0" 158 | 159 | jwa@^1.2.0: 160 | version "1.2.0" 161 | resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.2.0.tgz#606da70c1c6d425cad329c77c99f2df2a981489a" 162 | integrity sha512-Grku9ZST5NNQ3hqNUodSkDfEBqAmGA1R8yiyPHOnLzEKI0GaCQC/XhFmsheXYuXzFQJdILbh+lYBiliqG5R/Vg== 163 | dependencies: 164 | buffer-equal-constant-time "1.0.1" 165 | ecdsa-sig-formatter "1.0.10" 166 | safe-buffer "^5.0.1" 167 | 168 | jws@^3.1.5: 169 | version "3.2.1" 170 | resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.1.tgz#d79d4216a62c9afa0a3d5e8b5356d75abdeb2be5" 171 | integrity sha512-bGA2omSrFUkd72dhh05bIAN832znP4wOU3lfuXtRBuGTbsmNmDXMQg28f0Vsxaxgk4myF5YkKQpz6qeRpMgX9g== 172 | dependencies: 173 | jwa "^1.2.0" 174 | safe-buffer "^5.0.1" 175 | 176 | lokijs@^1.5.6: 177 | version "1.5.6" 178 | resolved "https://registry.yarnpkg.com/lokijs/-/lokijs-1.5.6.tgz#6de6b8c3ff7a972fd0104169f81e7ddc244c029f" 179 | integrity sha512-xJoDXy8TASTjmXMKr4F8vvNUCu4dqlwY5gmn0g5BajGt1GM3goDCafNiGAh/sfrWgkfWu1J4OfsxWm8yrWweJA== 180 | 181 | lru-cache@^5.0.0: 182 | version "5.1.1" 183 | resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" 184 | integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== 185 | dependencies: 186 | yallist "^3.0.2" 187 | 188 | mime@^2.2.0: 189 | version "2.4.0" 190 | resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.0.tgz#e051fd881358585f3279df333fe694da0bcffdd6" 191 | integrity sha512-ikBcWwyqXQSHKtciCcctu9YfPbFYZ4+gbHEmE0Q8jzcTYQg5dHCr3g2wwAZjPoJfQVXZq6KXAjpXOTf5/cjT7w== 192 | 193 | ms@^2.1.1: 194 | version "2.1.1" 195 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" 196 | integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== 197 | 198 | node-fetch@^2.2.0: 199 | version "2.3.0" 200 | resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.3.0.tgz#1a1d940bbfb916a1d3e0219f037e89e71f8c5fa5" 201 | integrity sha512-MOd8pV3fxENbryESLgVIeaGKrdl+uaYhCSSVkjeOb/31/njTpcis5aWfdqgNlHIrKOLRbMnfPINPOML2CIFeXA== 202 | 203 | node-forge@^0.7.5: 204 | version "0.7.6" 205 | resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.6.tgz#fdf3b418aee1f94f0ef642cd63486c77ca9724ac" 206 | integrity sha512-sol30LUpz1jQFBjOKwbjxijiE3b6pjd74YwfD0fJOKPjF+fONKb2Yg8rYgS6+bK6VDl+/wfr4IYpC7jDzLUIfw== 207 | 208 | pify@^4.0.0: 209 | version "4.0.1" 210 | resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" 211 | integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== 212 | 213 | qs@^6.5.2: 214 | version "6.6.0" 215 | resolved "https://registry.yarnpkg.com/qs/-/qs-6.6.0.tgz#a99c0f69a8d26bf7ef012f871cdabb0aee4424c2" 216 | integrity sha512-KIJqT9jQJDQx5h5uAVPimw6yVg2SekOKu959OCtktD3FjzbpvaPr8i4zzg07DOMz+igA4W/aNM7OV8H37pFYfA== 217 | 218 | safe-buffer@^5.0.1: 219 | version "5.1.2" 220 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" 221 | integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== 222 | 223 | semver@^5.5.0: 224 | version "5.6.0" 225 | resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" 226 | integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg== 227 | 228 | typescript@^3.3.1: 229 | version "3.3.1" 230 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.3.1.tgz#6de14e1db4b8a006ac535e482c8ba018c55f750b" 231 | integrity sha512-cTmIDFW7O0IHbn1DPYjkiebHxwtCMU+eTy30ZtJNBPF9j2O1ITu5XH2YnBeVRKWHqF+3JQwWJv0Q0aUgX8W7IA== 232 | 233 | url-template@^2.0.8: 234 | version "2.0.8" 235 | resolved "https://registry.yarnpkg.com/url-template/-/url-template-2.0.8.tgz#fc565a3cccbff7730c775f5641f9555791439f21" 236 | integrity sha1-/FZaPMy/93MMd19WQflVV5FDnyE= 237 | 238 | uuid@^3.2.1: 239 | version "3.3.2" 240 | resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" 241 | integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== 242 | 243 | yallist@^3.0.2: 244 | version "3.0.3" 245 | resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.3.tgz#b4b049e314be545e3ce802236d6cd22cd91c3de9" 246 | integrity sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A== 247 | --------------------------------------------------------------------------------